Skip to main content

jam_primitives/
block.rs

1//! # JAM Protocol Block Structures
2//!
3//! Block and block body definitions for the JAM protocol.
4//! Based on the Gray Paper specification for JAM block structure.
5
6use crate::utils::codec::{Decode, Encode};
7use crate::{
8    Hashable, PrimitiveError, PrimitiveResult, Validate,
9    crypto::hashing::{Blake3Hasher, Hasher},
10    header::Header,
11    transaction::Transaction,
12    types::{AccountId, BlockNumber, Gas, Hash, ServiceId, Signature, TimeSlot, Timestamp, Weight},
13};
14use serde::{Deserialize, Serialize};
15
16/// A complete JAM block containing header and body
17#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode, Serialize, Deserialize)]
18pub struct Block {
19    /// Block header containing metadata
20    pub header: Header,
21    /// Block body containing transactions and other data
22    pub body: BlockBody,
23}
24
25impl Block {
26    /// Create a new block
27    pub fn new(header: Header, body: BlockBody) -> Self {
28        Self { header, body }
29    }
30
31    /// Get block number
32    pub fn number(&self) -> BlockNumber {
33        self.header.number
34    }
35
36    /// Get block hash (header hash)
37    pub fn hash(&self) -> Hash {
38        self.header.hash()
39    }
40
41    /// Get parent hash
42    pub fn parent_hash(&self) -> Hash {
43        self.header.parent_hash
44    }
45
46    /// Get block timestamp
47    pub fn timestamp(&self) -> Timestamp {
48        self.header.timestamp
49    }
50
51    /// Get block weight
52    pub fn weight(&self) -> Weight {
53        self.body.weight()
54    }
55
56    /// Check if this is a genesis block
57    pub fn is_genesis(&self) -> bool {
58        self.header.number == BlockNumber(0)
59    }
60
61    /// Get all transactions in the block
62    pub fn transactions(&self) -> &[Transaction] {
63        &self.body.transactions
64    }
65
66    /// Get block size in bytes
67    pub fn size(&self) -> usize {
68        self.header.encoded_size() + self.body.encoded_size()
69    }
70}
71
72impl Validate for Block {
73    fn validate(&self) -> PrimitiveResult<()> {
74        // Validate header
75        self.header.validate()?;
76
77        // Validate body
78        self.body.validate()?;
79
80        // Validate header-body consistency
81        if self.header.extrinsics_root != self.body.hash() {
82            return Err(PrimitiveError::InvalidBlock(
83                "Header extrinsics root doesn't match computed body hash".to_string(),
84            ));
85        }
86
87        // Validate block size
88        if self.size() > crate::constants::MAX_BLOCK_SIZE as usize {
89            return Err(PrimitiveError::SizeLimit(
90                "Block exceeds maximum size limit".to_string(),
91            ));
92        }
93
94        Ok(())
95    }
96}
97
98impl Hashable for Block {
99    fn hash(&self) -> Hash {
100        self.header.hash()
101    }
102}
103
104/// Block body containing the actual block data
105#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode, Serialize, Deserialize)]
106pub struct BlockBody {
107    /// List of transactions in this block
108    pub transactions: Vec<Transaction>,
109    /// Availability data for erasure coding
110    pub availability: AvailabilityData,
111    /// Work reports from cores
112    pub work_reports: Vec<WorkReport>,
113    /// Guarantees for parachain blocks
114    pub guarantees: Vec<Guarantee>,
115    /// Preimages for services
116    pub preimages: Vec<Preimage>,
117}
118
119impl BlockBody {
120    /// Create a new empty block body
121    pub fn new() -> Self {
122        Self {
123            transactions: Vec::new(),
124            availability: AvailabilityData::new(),
125            work_reports: Vec::new(),
126            guarantees: Vec::new(),
127            preimages: Vec::new(),
128        }
129    }
130
131    /// Create a block body with transactions
132    pub fn with_transactions(transactions: Vec<Transaction>) -> Self {
133        Self {
134            transactions,
135            availability: AvailabilityData::new(),
136            work_reports: Vec::new(),
137            guarantees: Vec::new(),
138            preimages: Vec::new(),
139        }
140    }
141
142    /// Add a transaction to the block body
143    pub fn add_transaction(&mut self, transaction: Transaction) {
144        self.transactions.push(transaction);
145    }
146
147    /// Get total weight of all items in the body
148    pub fn weight(&self) -> Weight {
149        self.transactions
150            .iter()
151            .map(|tx| tx.weight())
152            .sum::<Weight>()
153            + Weight(self.work_reports.len() as u64) * 1000
154            + Weight(self.guarantees.len() as u64) * 500
155            + self
156                .preimages
157                .iter()
158                .map(|p| Weight(p.data.len() as u64))
159                .sum::<Weight>()
160    }
161
162    /// Get number of transactions
163    pub fn transaction_count(&self) -> usize {
164        self.transactions.len()
165    }
166
167    /// Check if body is empty
168    pub fn is_empty(&self) -> bool {
169        self.transactions.is_empty()
170            && self.work_reports.is_empty()
171            && self.guarantees.is_empty()
172            && self.preimages.is_empty()
173    }
174
175    /// Get encoded size
176    pub fn encoded_size(&self) -> usize {
177        self.encode().len()
178    }
179}
180
181impl Default for BlockBody {
182    fn default() -> Self {
183        Self::new()
184    }
185}
186
187impl Validate for BlockBody {
188    fn validate(&self) -> PrimitiveResult<()> {
189        // Validate all transactions
190        for (i, tx) in self.transactions.iter().enumerate() {
191            tx.validate().map_err(|e| {
192                PrimitiveError::InvalidBlock(format!("Invalid transaction at index {}: {}", i, e))
193            })?;
194        }
195
196        // Validate work reports
197        for (i, report) in self.work_reports.iter().enumerate() {
198            report.validate().map_err(|e| {
199                PrimitiveError::InvalidBlock(format!("Invalid work report at index {}: {}", i, e))
200            })?;
201        }
202
203        // Validate guarantees
204        for (i, guarantee) in self.guarantees.iter().enumerate() {
205            guarantee.validate().map_err(|e| {
206                PrimitiveError::InvalidBlock(format!("Invalid guarantee at index {}: {}", i, e))
207            })?;
208        }
209
210        Ok(())
211    }
212}
213
214impl Hashable for BlockBody {
215    fn hash(&self) -> Hash {
216        let hasher = Blake3Hasher::new();
217        hasher.hash(&self.encode())
218    }
219}
220
221/// Availability data for erasure coding
222#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode, Serialize, Deserialize)]
223pub struct AvailabilityData {
224    /// Erasure coded chunks
225    pub chunks: Vec<AvailabilityChunk>,
226}
227
228impl AvailabilityData {
229    /// Create new empty availability data
230    pub fn new() -> Self {
231        Self { chunks: Vec::new() }
232    }
233}
234
235impl Default for AvailabilityData {
236    fn default() -> Self {
237        Self::new()
238    }
239}
240
241/// A single chunk of availability data
242#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode, Serialize, Deserialize)]
243pub struct AvailabilityChunk {
244    /// Chunk index
245    pub index: u32,
246    /// Chunk data
247    pub data: Vec<u8>,
248    /// Proof for this chunk
249    pub proof: Vec<u8>,
250}
251
252/// Work report from a core
253#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode, Serialize, Deserialize)]
254pub struct WorkReport {
255    /// Core index that produced this report
256    pub core_index: u16,
257    /// Work package hash
258    pub package_hash: Hash,
259    /// Result of work execution
260    pub result: WorkResult,
261    /// Context for work execution
262    pub context: WorkContext,
263    /// Authorizer signature
264    pub authorizer_signature: Signature,
265}
266
267impl WorkReport {
268    /// Create a new work report
269    pub fn new(
270        core_index: u16,
271        package_hash: Hash,
272        result: WorkResult,
273        context: WorkContext,
274        authorizer_signature: Signature,
275    ) -> Self {
276        Self {
277            core_index,
278            package_hash,
279            result,
280            context,
281            authorizer_signature,
282        }
283    }
284}
285
286impl Validate for WorkReport {
287    fn validate(&self) -> PrimitiveResult<()> {
288        // Validate core index is within bounds
289        if self.core_index >= crate::constants::MAX_CORES as u16 {
290            return Err(PrimitiveError::InvalidBlock(
291                "Core index exceeds maximum".to_string(),
292            ));
293        }
294
295        // Validate result
296        self.result.validate()?;
297
298        Ok(())
299    }
300}
301
302/// Result of work execution
303#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode, Serialize, Deserialize)]
304pub struct WorkResult {
305    /// Service that executed the work
306    pub service_id: ServiceId,
307    /// Payload hash
308    pub payload_hash: Hash,
309    /// Gas used
310    pub gas_used: Gas,
311    /// Result data
312    pub result_data: Vec<u8>,
313}
314
315impl Validate for WorkResult {
316    fn validate(&self) -> PrimitiveResult<()> {
317        // Check result data size
318        if self.result_data.len() > crate::constants::MAX_ACCUMULATION_SIZE as usize {
319            return Err(PrimitiveError::SizeLimit(
320                "Work result data exceeds maximum size".to_string(),
321            ));
322        }
323
324        Ok(())
325    }
326}
327
328/// Context for work execution
329#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode, Serialize, Deserialize)]
330pub struct WorkContext {
331    /// Anchor block hash
332    pub anchor: Hash,
333    /// State root at execution
334    pub state_root: Hash,
335    /// Lookup anchor
336    pub lookup_anchor: Hash,
337    /// Prerequisite work package
338    pub prerequisite: Option<Hash>,
339}
340
341/// Guarantee for parachain block
342#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode, Serialize, Deserialize)]
343pub struct Guarantee {
344    /// Work report being guaranteed
345    pub work_report: Hash,
346    /// Timeslot
347    pub timeslot: TimeSlot,
348    /// Guarantor signatures
349    pub signatures: Vec<(AccountId, Signature)>,
350}
351
352impl Validate for Guarantee {
353    fn validate(&self) -> PrimitiveResult<()> {
354        // Check minimum number of signatures
355        if self.signatures.is_empty() {
356            return Err(PrimitiveError::InvalidBlock(
357                "Guarantee must have at least one signature".to_string(),
358            ));
359        }
360
361        // Check for duplicate signers
362        let mut signers = std::collections::HashSet::new();
363        for (signer, _) in &self.signatures {
364            if !signers.insert(signer) {
365                return Err(PrimitiveError::InvalidBlock(
366                    "Duplicate signer in guarantee".to_string(),
367                ));
368            }
369        }
370
371        Ok(())
372    }
373}
374
375/// Preimage data for services
376#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode, Serialize, Deserialize)]
377pub struct Preimage {
378    /// Hash of the preimage
379    pub hash: Hash,
380    /// Preimage data
381    pub data: Vec<u8>,
382}
383
384impl Preimage {
385    /// Create a new preimage
386    pub fn new(data: Vec<u8>) -> Self {
387        let hasher = Blake3Hasher::new();
388        let hash = hasher.hash(&data);
389
390        Self { hash, data }
391    }
392
393    /// Verify that the hash matches the data
394    pub fn verify(&self) -> bool {
395        let hasher = Blake3Hasher::new();
396        hasher.hash(&self.data) == self.hash
397    }
398}