fuel_core_interfaces/model/
block.rs

1pub use super::BlockHeight;
2use crate::{
3    common::{
4        fuel_crypto::Hasher,
5        fuel_tx::{
6            Bytes32,
7            Input,
8            Transaction,
9            UniqueIdentifier,
10        },
11        fuel_types::{
12            bytes::SerializableVec,
13            Address,
14        },
15    },
16    model::DaBlockHeight,
17};
18use derive_more::{
19    AsRef,
20    Display,
21    From,
22    FromStr,
23    Into,
24    LowerHex,
25    UpperHex,
26};
27use fuel_vm::{
28    fuel_crypto,
29    fuel_merkle,
30    fuel_types::MessageId,
31    prelude::Signature,
32};
33use tai64::Tai64;
34
35/// A cryptographically secure hash, identifying a block.
36#[derive(
37    Clone,
38    Copy,
39    Debug,
40    PartialEq,
41    Eq,
42    PartialOrd,
43    Ord,
44    Hash,
45    Default,
46    FromStr,
47    From,
48    Into,
49    LowerHex,
50    UpperHex,
51    Display,
52    AsRef,
53)]
54#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
55#[cfg_attr(feature = "serde", serde(transparent))]
56#[repr(transparent)]
57pub struct BlockId(Bytes32);
58
59impl BlockId {
60    /// Converts the hash into a message having the same bytes.
61    pub fn into_message(self) -> fuel_crypto::Message {
62        // This is safe because BlockId is a cryptographically secure hash.
63        unsafe { fuel_crypto::Message::from_bytes_unchecked(*self.0) }
64        // Without this, the signature would be using a hash of the id making it more
65        // difficult to verify.
66    }
67
68    pub fn as_message(&self) -> &fuel_crypto::Message {
69        // This is safe because BlockId is a cryptographically secure hash.
70        unsafe { fuel_crypto::Message::as_ref_unchecked(self.0.as_slice()) }
71        // Without this, the signature would be using a hash of the id making it more
72        // difficult to verify.
73    }
74}
75
76#[derive(Clone, Debug)]
77#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
78#[cfg_attr(any(test, feature = "test-helpers"), derive(Default))]
79/// A fuel block header that has all the fields generated because it
80/// has been executed.
81pub struct FuelBlockHeader {
82    /// The application header.
83    pub application: FuelApplicationHeader<GeneratedApplicationFields>,
84    /// The consensus header.
85    pub consensus: FuelConsensusHeader<GeneratedConsensusFields>,
86    /// Header Metadata
87    #[cfg_attr(feature = "serde", serde(skip))]
88    pub metadata: Option<HeaderMetadata>,
89}
90
91#[derive(Clone, Debug)]
92#[cfg_attr(any(test, feature = "test-helpers"), derive(Default))]
93/// A partially complete fuel block header that doesn't not
94/// have any generated fields because it has not been executed yet.
95pub struct PartialFuelBlockHeader {
96    /// The application header.
97    pub application: FuelApplicationHeader<Empty>,
98    /// The consensus header.
99    pub consensus: FuelConsensusHeader<Empty>,
100    /// Header Metadata
101    pub metadata: Option<HeaderMetadata>,
102}
103
104#[derive(Clone, Copy, Debug, Default)]
105/// Empty generated fields.
106pub struct Empty;
107
108#[derive(Clone, Debug)]
109#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
110#[cfg_attr(any(test, feature = "test-helpers"), derive(Default))]
111/// The fuel block application header.
112/// Contains everything except consensus related data.
113pub struct FuelApplicationHeader<Generated> {
114    /// The layer 1 height of messages and events to include since the last layer 1 block number.
115    /// This is not meant to represent the layer 1 block this was committed to. Validators will need
116    /// to have some rules in place to ensure the block number was chosen in a reasonable way. For
117    /// example, they should verify that the block number satisfies the finality requirements of the
118    /// layer 1 chain. They should also verify that the block number isn't too stale and is increasing.
119    /// Some similar concerns are noted in this issue: https://github.com/FuelLabs/fuel-specs/issues/220
120    pub da_height: DaBlockHeight,
121    /// Generated application fields.
122    pub generated: Generated,
123}
124
125#[derive(Clone, Debug)]
126#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
127#[cfg_attr(any(test, feature = "test-helpers"), derive(Default))]
128/// Concrete generated application header fields.
129/// These are generated once the full block has been run.
130pub struct GeneratedApplicationFields {
131    /// Number of transactions in this block.
132    pub transactions_count: u64,
133    /// Number of output messages in this block.
134    pub output_messages_count: u64,
135    /// Merkle root of transactions.
136    pub transactions_root: Bytes32,
137    /// Merkle root of messages in this block.
138    pub output_messages_root: Bytes32,
139}
140
141#[derive(Clone, Debug)]
142#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
143/// The fuel block consensus header.
144/// This contains fields related to consensus plus
145/// the hash of the [`FuelApplicationHeader`].
146pub struct FuelConsensusHeader<Generated> {
147    /// Merkle root of all previous block header hashes.
148    pub prev_root: Bytes32,
149    /// Fuel block height.
150    pub height: BlockHeight,
151    /// The block producer time.
152    pub time: Tai64,
153    /// generated consensus fields.
154    pub generated: Generated,
155}
156
157#[derive(Clone, Debug)]
158#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
159#[cfg_attr(any(test, feature = "test-helpers"), derive(Default))]
160/// Concrete generated consensus header fields.
161/// These are generated once the full block has been run.
162pub struct GeneratedConsensusFields {
163    /// Hash of the application header.
164    pub application_hash: Bytes32,
165}
166
167#[derive(Clone, Debug)]
168#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
169/// Extra data that is not actually part of the header.
170pub struct HeaderMetadata {
171    /// Hash of the header.
172    id: BlockId,
173}
174
175#[derive(Debug, Clone, Copy, PartialEq, Eq)]
176pub enum ConsensusType {
177    PoA,
178}
179
180// Accessors for the consensus header.
181impl FuelBlockHeader {
182    /// Merkle root of all previous block header hashes.
183    pub fn prev_root(&self) -> &Bytes32 {
184        &self.as_ref().prev_root
185    }
186    /// Fuel block height.
187    pub fn height(&self) -> &BlockHeight {
188        &self.as_ref().height
189    }
190    /// The block producer time.
191    pub fn time(&self) -> Tai64 {
192        self.as_ref().time
193    }
194    /// The hash of the application header.
195    pub fn application_hash(&self) -> &Bytes32 {
196        &self.as_ref().application_hash
197    }
198
199    /// The type of consensus this header is using.
200    pub fn consensus_type(&self) -> ConsensusType {
201        ConsensusType::PoA
202    }
203}
204
205// Accessors for the consensus header.
206impl PartialFuelBlockHeader {
207    /// Merkle root of all previous block header hashes.
208    pub fn prev_root(&self) -> &Bytes32 {
209        &self.as_ref().prev_root
210    }
211    /// Fuel block height.
212    pub fn height(&self) -> &BlockHeight {
213        &self.as_ref().height
214    }
215    /// The block producer time.
216    pub fn time(&self) -> &Tai64 {
217        &self.as_ref().time
218    }
219    /// The type of consensus this header is using.
220    pub fn consensus_type(&self) -> ConsensusType {
221        ConsensusType::PoA
222    }
223}
224
225impl FuelBlockHeader {
226    /// Re-generate the header metadata.
227    pub fn recalculate_metadata(&mut self) {
228        self.metadata = Some(HeaderMetadata { id: self.hash() });
229    }
230
231    /// Get the hash of the fuel header.
232    pub fn hash(&self) -> BlockId {
233        // This internally hashes the hash of the application header.
234        self.consensus.hash()
235    }
236
237    /// Get the cached fuel header hash.
238    pub fn id(&self) -> BlockId {
239        if let Some(ref metadata) = self.metadata {
240            metadata.id
241        } else {
242            self.hash()
243        }
244    }
245}
246
247impl PartialFuelBlockHeader {
248    /// Generate all fields to create a full [`FuelBlockHeader`]
249    /// after running the transactions.
250    ///
251    /// The order of the transactions must be the same order they were
252    /// executed in.
253    /// The order of the messages must be the same as they were
254    /// produced in.
255    ///
256    /// Message ids are produced by executed the transactions and collecting
257    /// the ids from the receipts of messages outputs.
258    ///
259    /// The transactions are the bytes of the executed [`Transaction`]s.
260    pub fn generate(
261        self,
262        transactions: &[Vec<u8>],
263        message_ids: &[MessageId],
264    ) -> FuelBlockHeader {
265        // Generate the transaction merkle root.
266        let mut transaction_tree = fuel_merkle::binary::in_memory::MerkleTree::new();
267        for id in transactions {
268            transaction_tree.push(id.as_ref());
269        }
270        let transactions_root = transaction_tree.root().into();
271
272        // Generate the message merkle root.
273        let mut message_tree = fuel_merkle::binary::in_memory::MerkleTree::new();
274        for id in message_ids {
275            message_tree.push(id.as_ref());
276        }
277        let output_messages_root = message_tree.root().into();
278
279        let application = FuelApplicationHeader {
280            da_height: self.application.da_height,
281            generated: GeneratedApplicationFields {
282                transactions_count: transactions.len() as u64,
283                output_messages_count: message_ids.len() as u64,
284                transactions_root,
285                output_messages_root,
286            },
287        };
288
289        // Generate the hash of the complete application header.
290        let application_hash = application.hash();
291        let mut header = FuelBlockHeader {
292            application,
293            consensus: FuelConsensusHeader {
294                prev_root: self.consensus.prev_root,
295                height: self.consensus.height,
296                time: self.consensus.time,
297                generated: GeneratedConsensusFields { application_hash },
298            },
299            metadata: None,
300        };
301
302        // cache the hash.
303        header.recalculate_metadata();
304        header
305    }
306
307    /// Creates a [`FuelBlockHeader`] that has the
308    /// generated fields set to meaningless values.
309    fn without_generated(self) -> FuelBlockHeader {
310        FuelBlockHeader {
311            application: FuelApplicationHeader {
312                da_height: self.application.da_height,
313                generated: GeneratedApplicationFields {
314                    transactions_count: Default::default(),
315                    output_messages_count: Default::default(),
316                    transactions_root: Default::default(),
317                    output_messages_root: Default::default(),
318                },
319            },
320            consensus: FuelConsensusHeader {
321                prev_root: self.consensus.prev_root,
322                height: self.consensus.height,
323                time: self.consensus.time,
324                generated: GeneratedConsensusFields {
325                    application_hash: Default::default(),
326                },
327            },
328            metadata: None,
329        }
330    }
331}
332
333impl FuelApplicationHeader<GeneratedApplicationFields> {
334    /// Hash the application header.
335    fn hash(&self) -> Bytes32 {
336        // Order matters and is the same as the spec.
337        let mut hasher = Hasher::default();
338        hasher.input(&self.da_height.to_bytes()[..]);
339        hasher.input(self.transactions_count.to_be_bytes());
340        hasher.input(self.output_messages_count.to_be_bytes());
341        hasher.input(self.transactions_root.as_ref());
342        hasher.input(self.output_messages_root.as_ref());
343        hasher.digest()
344    }
345}
346
347impl FuelConsensusHeader<GeneratedConsensusFields> {
348    /// Hash the consensus header.
349    fn hash(&self) -> BlockId {
350        // Order matters and is the same as the spec.
351        let mut hasher = Hasher::default();
352        hasher.input(self.prev_root.as_ref());
353        hasher.input(&self.height.to_bytes()[..]);
354        hasher.input(self.time.0.to_be_bytes());
355        hasher.input(self.application_hash.as_ref());
356        BlockId(hasher.digest())
357    }
358}
359
360impl core::ops::Deref for FuelBlockHeader {
361    type Target = FuelApplicationHeader<GeneratedApplicationFields>;
362
363    fn deref(&self) -> &Self::Target {
364        &self.application
365    }
366}
367
368impl core::ops::Deref for PartialFuelBlockHeader {
369    type Target = FuelApplicationHeader<Empty>;
370
371    fn deref(&self) -> &Self::Target {
372        &self.application
373    }
374}
375
376impl core::ops::Deref for FuelApplicationHeader<GeneratedApplicationFields> {
377    type Target = GeneratedApplicationFields;
378
379    fn deref(&self) -> &Self::Target {
380        &self.generated
381    }
382}
383
384impl core::ops::Deref for FuelConsensusHeader<GeneratedConsensusFields> {
385    type Target = GeneratedConsensusFields;
386
387    fn deref(&self) -> &Self::Target {
388        &self.generated
389    }
390}
391
392impl core::convert::AsRef<FuelConsensusHeader<GeneratedConsensusFields>>
393    for FuelBlockHeader
394{
395    fn as_ref(&self) -> &FuelConsensusHeader<GeneratedConsensusFields> {
396        &self.consensus
397    }
398}
399
400impl core::convert::AsRef<FuelConsensusHeader<Empty>> for PartialFuelBlockHeader {
401    fn as_ref(&self) -> &FuelConsensusHeader<Empty> {
402        &self.consensus
403    }
404}
405
406/// The compact representation of a block used in the database
407#[derive(Clone, Debug)]
408#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
409#[cfg_attr(any(test, feature = "test-helpers"), derive(Default))]
410pub struct FuelBlockDb {
411    pub header: FuelBlockHeader,
412    pub transactions: Vec<Bytes32>,
413}
414
415impl FuelBlockDb {
416    /// Hash of the header.
417    pub fn id(&self) -> BlockId {
418        self.header.id()
419    }
420
421    /// The type of consensus this header is using.
422    pub fn consensus_type(&self) -> ConsensusType {
423        self.header.consensus_type()
424    }
425}
426
427/// Fuel block with all transaction data included
428#[derive(Clone, Debug)]
429#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
430#[cfg_attr(any(test, feature = "test-helpers"), derive(Default))]
431pub struct FuelBlock {
432    /// Generated complete header.
433    header: FuelBlockHeader,
434    /// Executed transactions.
435    transactions: Vec<Transaction>,
436}
437
438/// Fuel block with all transaction data included
439/// but without any data generated.
440/// This type can be created with unexecuted
441/// transactions to produce a [`FuelBlock`] or
442/// it can be created with pre-executed transactions in
443/// order to validate they were constructed correctly.
444#[derive(Clone, Debug)]
445pub struct PartialFuelBlock {
446    /// The partial header.
447    pub header: PartialFuelBlockHeader,
448    /// Transactions that can either be pre-executed
449    /// or not.
450    pub transactions: Vec<Transaction>,
451}
452
453impl FuelBlock {
454    /// Create a new full fuel block from a [`PartialFuelBlockHeader`],
455    /// executed transactions and the [`MessageId`]s.
456    ///
457    /// The order of the transactions must be the same order they were
458    /// executed in.
459    /// The order of the messages must be the same as they were
460    /// produced in.
461    ///
462    /// Message ids are produced by executed the transactions and collecting
463    /// the ids from the receipts of messages outputs.
464    pub fn new(
465        header: PartialFuelBlockHeader,
466        mut transactions: Vec<Transaction>,
467        message_ids: &[MessageId],
468    ) -> Self {
469        // I think this is safe as it doesn't appear that any of the reads actually mutate the data.
470        // Alternatively we can clone to be safe.
471        let transaction_ids: Vec<_> =
472            transactions.iter_mut().map(|tx| tx.to_bytes()).collect();
473        Self {
474            header: header.generate(&transaction_ids[..], message_ids),
475            transactions,
476        }
477    }
478
479    /// Get the hash of the header.
480    pub fn id(&self) -> BlockId {
481        self.header.id()
482    }
483
484    /// Create a database friendly fuel block.
485    pub fn to_db_block(&self) -> FuelBlockDb {
486        FuelBlockDb {
487            header: self.header.clone(),
488            transactions: self.transactions.iter().map(|tx| tx.id()).collect(),
489        }
490    }
491
492    /// Convert from a previously stored block back to a full block
493    pub fn from_db_block(db_block: FuelBlockDb, transactions: Vec<Transaction>) -> Self {
494        // TODO: should we perform an extra validation step to ensure the provided
495        //  txs match the expected ones in the block?
496        Self {
497            header: db_block.header,
498            transactions,
499        }
500    }
501
502    /// Get the executed transactions.
503    pub fn transactions(&self) -> &[Transaction] {
504        &self.transactions[..]
505    }
506
507    /// Get the complete header.
508    pub fn header(&self) -> &FuelBlockHeader {
509        &self.header
510    }
511
512    #[cfg(any(test, feature = "test-helpers"))]
513    pub fn transactions_mut(&mut self) -> &mut Vec<Transaction> {
514        &mut self.transactions
515    }
516
517    #[cfg(any(test, feature = "test-helpers"))]
518    pub fn header_mut(&mut self) -> &mut FuelBlockHeader {
519        &mut self.header
520    }
521}
522
523impl PartialFuelBlock {
524    pub fn new(header: PartialFuelBlockHeader, transactions: Vec<Transaction>) -> Self {
525        Self {
526            header,
527            transactions,
528        }
529    }
530
531    /// Creates a [`FuelBlockDb`] that has the
532    /// generated fields set to meaningless values.
533    ///
534    /// Hack until we figure out a better way to represent
535    /// a block in the database that hasn't been run.
536    pub fn to_partial_db_block(&self) -> FuelBlockDb {
537        FuelBlockDb {
538            header: self.header.clone().without_generated(),
539            transactions: self.transactions.iter().map(|tx| tx.id()).collect(),
540        }
541    }
542
543    /// Generate a [`FuelBlock`] after running this partial block.
544    ///
545    /// The order of the messages must be the same as they were
546    /// produced in.
547    ///
548    /// Message ids are produced by executed the transactions and collecting
549    /// the ids from the receipts of messages outputs.
550    pub fn generate(self, message_ids: &[MessageId]) -> FuelBlock {
551        FuelBlock::new(self.header, self.transactions, message_ids)
552    }
553}
554
555impl From<FuelBlock> for PartialFuelBlock {
556    fn from(block: FuelBlock) -> Self {
557        let FuelBlock {
558            header:
559                FuelBlockHeader {
560                    application: FuelApplicationHeader { da_height, .. },
561                    consensus:
562                        FuelConsensusHeader {
563                            prev_root,
564                            height,
565                            time,
566                            ..
567                        },
568                    ..
569                },
570            transactions,
571        } = block;
572        Self {
573            header: PartialFuelBlockHeader {
574                application: FuelApplicationHeader {
575                    da_height,
576                    generated: Empty {},
577                },
578                consensus: FuelConsensusHeader {
579                    prev_root,
580                    height,
581                    time,
582                    generated: Empty {},
583                },
584                metadata: None,
585            },
586            transactions,
587        }
588    }
589}
590
591/// The first block of the blockchain is a genesis block. It determines the initial state of the
592/// network - contracts states, contracts balances, unspent coins, and messages. It also contains
593/// the hash on the initial config of the network that defines the consensus rules for following
594/// blocks.
595#[derive(Clone, Debug, Default)]
596#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
597pub struct Genesis {
598    /// The chain config define what consensus type to use, what settlement layer to use,
599    /// rules of block validity, etc.
600    pub chain_config_hash: Bytes32,
601    /// The Binary Merkle Tree root of all genesis coins.
602    pub coins_root: Bytes32,
603    /// The Binary Merkle Tree root of state, balances, contracts code hash of each contract.
604    pub contracts_root: Bytes32,
605    /// The Binary Merkle Tree root of all genesis messages.
606    pub messages_root: Bytes32,
607}
608
609#[derive(Clone, Debug)]
610#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
611/// The consensus related data that doesn't live on the
612/// header.
613pub enum FuelBlockConsensus {
614    PoA(FuelBlockPoAConsensus),
615    /// The genesis block defines the consensus rules for future blocks.
616    Genesis(Genesis),
617}
618
619impl FuelBlockConsensus {
620    /// Retrieve the block producer address from the consensus data
621    pub fn block_producer(&self, block_id: &BlockId) -> anyhow::Result<Address> {
622        match &self {
623            FuelBlockConsensus::Genesis(_) => Ok(Address::zeroed()),
624            FuelBlockConsensus::PoA(poa_data) => {
625                let public_key = poa_data.signature.recover(block_id.as_message())?;
626                let address = Input::owner(&public_key);
627                Ok(address)
628            }
629        }
630    }
631}
632
633#[derive(Clone, Debug, Default)]
634#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
635/// The consensus related data that doesn't live on the
636/// header.
637pub struct FuelBlockPoAConsensus {
638    /// The signature of the [`FuelBlockHeader`].
639    pub signature: Signature,
640}
641
642#[cfg(any(test, feature = "test-helpers"))]
643impl<T> Default for FuelConsensusHeader<T>
644where
645    T: Default,
646{
647    fn default() -> Self {
648        Self {
649            time: Tai64::UNIX_EPOCH,
650            height: BlockHeight::default(),
651            prev_root: Bytes32::default(),
652            generated: Default::default(),
653        }
654    }
655}
656
657#[cfg(any(test, feature = "test-helpers"))]
658impl Default for FuelBlockConsensus {
659    fn default() -> Self {
660        FuelBlockConsensus::PoA(Default::default())
661    }
662}
663
664#[derive(Clone, Debug)]
665#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
666#[cfg_attr(any(test, feature = "test-helpers"), derive(Default))]
667/// A fuel block with the related consensus data.
668pub struct SealedFuelBlock {
669    pub block: FuelBlock,
670    pub consensus: FuelBlockConsensus,
671}
672
673impl FuelBlockPoAConsensus {
674    /// Create a new block consensus.
675    pub fn new(signature: Signature) -> Self {
676        Self { signature }
677    }
678}
679
680#[derive(Clone, Debug)]
681#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
682#[cfg_attr(any(test, feature = "test-helpers"), derive(Default))]
683/// A fuel block with the related consensus data.
684pub struct SealedFuelBlockHeader {
685    pub header: FuelBlockHeader,
686    pub consensus: FuelBlockConsensus,
687}