fuel_core/database/
block.rs

1use crate::{
2    database::{
3        OffChainIterableKeyValueView,
4        OnChainIterableKeyValueView,
5    },
6    fuel_core_graphql_api::storage::blocks::FuelBlockIdsToHeights,
7};
8use fuel_core_storage::{
9    Error as StorageError,
10    Result as StorageResult,
11    StorageAsRef,
12    iter::{
13        IterDirection,
14        IteratorOverTable,
15    },
16    not_found,
17    tables::{
18        FuelBlocks,
19        Transactions,
20        merkle::{
21            DenseMetadataKey,
22            FuelBlockMerkleData,
23            FuelBlockMerkleMetadata,
24        },
25    },
26};
27use fuel_core_types::{
28    blockchain::{
29        block::{
30            Block,
31            CompressedBlock,
32        },
33        primitives::BlockId,
34    },
35    entities::relayer::message::MerkleProof,
36    fuel_merkle::binary::MerkleTree,
37    fuel_types::BlockHeight,
38};
39use itertools::Itertools;
40use std::borrow::Cow;
41
42impl OffChainIterableKeyValueView {
43    pub fn get_block_height(&self, id: &BlockId) -> StorageResult<Option<BlockHeight>> {
44        self.storage::<FuelBlockIdsToHeights>()
45            .get(id)
46            .map(|v| v.map(|v| v.into_owned()))
47    }
48}
49
50impl OnChainIterableKeyValueView {
51    pub fn latest_compressed_block(&self) -> StorageResult<Option<CompressedBlock>> {
52        let pair = self
53            .iter_all::<FuelBlocks>(Some(IterDirection::Reverse))
54            .next()
55            .transpose()?;
56
57        Ok(pair.map(|(_, compressed_block)| compressed_block))
58    }
59
60    /// Get the current block at the head of the chain.
61    pub fn get_current_block(&self) -> StorageResult<Option<CompressedBlock>> {
62        self.latest_compressed_block()
63    }
64
65    /// Retrieve the full block and all associated transactions
66    pub fn get_full_block(&self, height: &BlockHeight) -> StorageResult<Option<Block>> {
67        let db_block = self.storage::<FuelBlocks>().get(height)?;
68        if let Some(block) = db_block {
69            // fetch all the transactions
70            // TODO: Use multiget when it's implemented.
71            //  https://github.com/FuelLabs/fuel-core/issues/2344
72            let txs = block
73                .transactions()
74                .iter()
75                .map(|tx_id| {
76                    self.storage::<Transactions>()
77                        .get(tx_id)
78                        .and_then(|tx| tx.ok_or(not_found!(Transactions)))
79                        .map(Cow::into_owned)
80                })
81                .try_collect()?;
82            Ok(Some(block.into_owned().uncompress(txs)))
83        } else {
84            Ok(None)
85        }
86    }
87}
88
89impl OnChainIterableKeyValueView {
90    pub fn block_history_proof(
91        &self,
92        message_block_height: &BlockHeight,
93        commit_block_height: &BlockHeight,
94    ) -> StorageResult<MerkleProof> {
95        if message_block_height > commit_block_height {
96            Err(anyhow::anyhow!(
97                "The `message_block_height` is higher than `commit_block_height`"
98            ))?;
99        }
100
101        let message_merkle_metadata = self
102            .storage::<FuelBlockMerkleMetadata>()
103            .get(&DenseMetadataKey::Primary(*message_block_height))?
104            .ok_or(not_found!(FuelBlockMerkleMetadata))?;
105
106        let commit_merkle_metadata = self
107            .storage::<FuelBlockMerkleMetadata>()
108            .get(&DenseMetadataKey::Primary(*commit_block_height))?
109            .ok_or(not_found!(FuelBlockMerkleMetadata))?;
110
111        let storage = self;
112        let tree: MerkleTree<FuelBlockMerkleData, _> =
113            MerkleTree::load(storage, commit_merkle_metadata.version())
114                .map_err(|err| StorageError::Other(anyhow::anyhow!(err)))?;
115
116        let proof_index = message_merkle_metadata
117            .version()
118            .checked_sub(1)
119            .ok_or(anyhow::anyhow!("The count of leaves - messages is zero"))?;
120        let (_, proof_set) = tree
121            .prove(proof_index)
122            .map_err(|err| StorageError::Other(anyhow::anyhow!(err)))?;
123
124        Ok(MerkleProof {
125            proof_set,
126            proof_index,
127        })
128    }
129}
130
131#[allow(clippy::arithmetic_side_effects)]
132#[cfg(test)]
133mod tests {
134    use super::*;
135    use crate::database::Database;
136    use fuel_core_storage::{
137        StorageMutate,
138        transactional::AtomicView,
139    };
140    use fuel_core_types::{
141        blockchain::{
142            block::PartialFuelBlock,
143            header::{
144                ConsensusHeader,
145                PartialBlockHeader,
146            },
147            primitives::Empty,
148        },
149        fuel_types::ChainId,
150    };
151    use test_case::test_case;
152
153    const TEST_BLOCKS_COUNT: u32 = 10;
154
155    fn insert_test_ascending_blocks(
156        database: &mut Database,
157        genesis_height: BlockHeight,
158    ) {
159        let start = *genesis_height;
160        // Generate 10 blocks with ascending heights
161        let blocks = (start..start + TEST_BLOCKS_COUNT)
162            .map(|height| {
163                let header = PartialBlockHeader {
164                    application: Default::default(),
165                    consensus: ConsensusHeader::<Empty> {
166                        height: BlockHeight::from(height),
167                        ..Default::default()
168                    },
169                };
170                let block = PartialFuelBlock::new(header, vec![]);
171                block
172                    .generate(
173                        &[],
174                        Default::default(),
175                        #[cfg(feature = "fault-proving")]
176                        &Default::default(),
177                    )
178                    .unwrap()
179            })
180            .collect::<Vec<_>>();
181
182        // Insert the blocks
183        for block in &blocks {
184            StorageMutate::<FuelBlocks>::insert(
185                database,
186                block.header().height(),
187                &block.compress(&ChainId::default()),
188            )
189            .unwrap();
190        }
191    }
192
193    #[test]
194    fn get_merkle_root_for_invalid_block_height_returns_not_found_error() {
195        let mut database = Database::default();
196
197        insert_test_ascending_blocks(&mut database, BlockHeight::from(0));
198
199        // check that root is not present
200        let err = database
201            .storage::<FuelBlocks>()
202            .root(&100u32.into())
203            .expect_err("expected error getting invalid Block Merkle root");
204
205        assert!(matches!(err, fuel_core_storage::Error::NotFound(_, _)));
206    }
207
208    #[test_case(0; "genesis block at height 0")]
209    #[test_case(1; "genesis block at height 1")]
210    #[test_case(100; "genesis block at height 100")]
211    fn block_history_proof_works(genesis_height: u32) {
212        let mut database = Database::default();
213
214        insert_test_ascending_blocks(&mut database, BlockHeight::from(genesis_height));
215        let view = database.latest_view().unwrap();
216
217        for l in 0..TEST_BLOCKS_COUNT {
218            for r in l..TEST_BLOCKS_COUNT {
219                let proof = view
220                    .block_history_proof(
221                        &BlockHeight::from(genesis_height + l),
222                        &BlockHeight::from(genesis_height + r),
223                    )
224                    .expect("Should return the merkle proof");
225                assert_eq!(proof.proof_index, l as u64);
226            }
227        }
228    }
229
230    #[test]
231    fn block_history_proof_error_if_message_higher_than_commit() {
232        let mut database = Database::default();
233
234        insert_test_ascending_blocks(&mut database, BlockHeight::from(0));
235        let view = database.latest_view().unwrap();
236
237        let result = view.block_history_proof(
238            &BlockHeight::from(TEST_BLOCKS_COUNT),
239            &BlockHeight::from(TEST_BLOCKS_COUNT - 1),
240        );
241        assert!(result.is_err());
242    }
243}