Skip to main content

fuel_core/
database.rs

1use crate::{
2    database::{
3        Error as DatabaseError,
4        database_description::{
5            DatabaseDescription,
6            DatabaseHeight,
7            DatabaseMetadata,
8            off_chain::OffChain,
9            on_chain::OnChain,
10            relayer::Relayer,
11        },
12        metadata::MetadataTable,
13    },
14    graphql_api::storage::blocks::FuelBlockIdsToHeights,
15    state::{
16        ColumnType,
17        IterableKeyValueView,
18        KeyValueView,
19        data_source::{
20            DataSource,
21            DataSourceType,
22        },
23        generic_database::GenericDatabase,
24        in_memory::memory_store::MemoryStore,
25    },
26};
27use database_description::compression::CompressionDatabase;
28use fuel_core_chain_config::TableEntry;
29pub use fuel_core_database::Error;
30use fuel_core_gas_price_service::common::fuel_core_storage_adapter::storage::GasPriceMetadata;
31use fuel_core_services::SharedMutex;
32use fuel_core_storage::{
33    self,
34    Error as StorageError,
35    Mappable,
36    Result as StorageResult,
37    StorageAsMut,
38    StorageInspect,
39    StorageMutate,
40    iter::{
41        IterDirection,
42        IterableTable,
43        IteratorOverTable,
44        changes_iterator::ChangesIterator,
45    },
46    not_found,
47    tables::FuelBlocks,
48    transactional::{
49        AtomicView,
50        Changes,
51        ConflictPolicy,
52        HistoricalView,
53        Modifiable,
54        StorageChanges,
55        StorageTransaction,
56    },
57};
58use fuel_core_types::{
59    blockchain::block::CompressedBlock,
60    fuel_types::BlockHeight,
61};
62use itertools::Itertools;
63use std::{
64    borrow::Cow,
65    fmt::Debug,
66    io::Empty,
67    sync::Arc,
68};
69pub type Result<T> = core::result::Result<T, Error>;
70
71// TODO: Extract `Database` and all belongs into `fuel-core-database`.
72#[cfg(feature = "rpc")]
73use crate::database::database_description::block_aggregator::BlockAggregatorDatabase;
74#[cfg(feature = "rocksdb")]
75use crate::state::{
76    historical_rocksdb::{
77        HistoricalRocksDB,
78        StateRewindPolicy,
79        description::Historical,
80    },
81    rocks_db::{
82        ColumnsPolicy,
83        DatabaseConfig,
84        RocksDb,
85    },
86};
87use crate::{
88    database::database_description::{
89        gas_price::GasPriceDatabase,
90        indexation_availability,
91    },
92    state::HeightType,
93};
94
95#[cfg(feature = "rocksdb")]
96use std::path::Path;
97
98// Storages implementation
99pub mod balances;
100pub mod block;
101pub mod coin;
102pub mod contracts;
103pub mod database_description;
104pub mod genesis_progress;
105pub mod message;
106pub mod metadata;
107pub mod sealed_block;
108pub mod state;
109#[cfg(feature = "test-helpers")]
110pub mod storage;
111pub mod transactions;
112
113#[derive(Default, Debug, Copy, Clone)]
114pub struct GenesisStage;
115
116#[derive(Debug, Clone)]
117pub struct RegularStage<Description>
118where
119    Description: DatabaseDescription,
120{
121    /// Cached value from Metadata table, used to speed up lookups.
122    height: SharedMutex<Option<Description::Height>>,
123}
124
125impl<Description> Default for RegularStage<Description>
126where
127    Description: DatabaseDescription,
128{
129    fn default() -> Self {
130        Self {
131            height: SharedMutex::new(None),
132        }
133    }
134}
135
136pub type Database<Description = OnChain, Stage = RegularStage<Description>> =
137    GenericDatabase<DataSource<Description, Stage>, Empty>;
138pub type OnChainKeyValueView = KeyValueView<ColumnType<OnChain>, HeightType<OnChain>>;
139pub type OnChainIterableKeyValueView =
140    IterableKeyValueView<ColumnType<OnChain>, HeightType<OnChain>>;
141pub type OffChainKeyValueView = KeyValueView<ColumnType<OffChain>, HeightType<OffChain>>;
142pub type OffChainIterableKeyValueView =
143    IterableKeyValueView<ColumnType<OffChain>, HeightType<OffChain>>;
144pub type RelayerIterableKeyValueView =
145    IterableKeyValueView<ColumnType<Relayer>, HeightType<Relayer>>;
146
147pub type GenesisDatabase<Description = OnChain> = Database<Description, GenesisStage>;
148
149impl OnChainIterableKeyValueView {
150    pub fn maybe_latest_height(&self) -> StorageResult<Option<BlockHeight>> {
151        self.iter_all_keys::<FuelBlocks>(Some(IterDirection::Reverse))
152            .next()
153            .transpose()
154    }
155
156    pub fn latest_height(&self) -> StorageResult<BlockHeight> {
157        self.metadata()
158            .cloned()
159            .ok_or_else(|| not_found!("Metadata"))
160    }
161
162    pub fn latest_block(&self) -> StorageResult<CompressedBlock> {
163        self.iter_all::<FuelBlocks>(Some(IterDirection::Reverse))
164            .next()
165            .transpose()?
166            .map(|(_, block)| block)
167            .ok_or_else(|| not_found!("FuelBlocks"))
168    }
169}
170
171impl<DbDesc> Database<DbDesc>
172where
173    DbDesc: DatabaseDescription,
174{
175    pub fn entries<'a, T>(
176        &'a self,
177        prefix: Option<Vec<u8>>,
178        direction: IterDirection,
179    ) -> impl Iterator<Item = StorageResult<TableEntry<T>>> + 'a
180    where
181        T: Mappable + 'a,
182        Self: IterableTable<T>,
183    {
184        self.iter_all_filtered::<T, _>(prefix, None, Some(direction))
185            .map_ok(|(key, value)| TableEntry { key, value })
186    }
187
188    pub fn shutdown(self) {
189        let (storage, _) = self.into_inner();
190
191        storage.data.shutdown()
192    }
193}
194
195impl<Description> GenesisDatabase<Description>
196where
197    Description: DatabaseDescription,
198{
199    pub fn new(data_source: DataSourceType<Description>) -> Self {
200        GenesisDatabase::from_storage_and_metadata(
201            DataSource::new(data_source, GenesisStage),
202            None,
203        )
204    }
205}
206
207impl<Description> Database<Description>
208where
209    Description: DatabaseDescription,
210    Database<Description>:
211        StorageInspect<MetadataTable<Description>, Error = StorageError>,
212{
213    pub fn new(data_source: DataSourceType<Description>) -> Self {
214        let mut database = Self::from_storage_and_metadata(
215            DataSource::new(
216                data_source,
217                RegularStage {
218                    height: SharedMutex::new(None),
219                },
220            ),
221            Some(Empty::default()),
222        );
223        let height = database
224            .latest_height_from_metadata()
225            .expect("Failed to get latest height during creation of the database");
226
227        database.stage.height = SharedMutex::new(height);
228
229        database
230    }
231
232    #[cfg(feature = "rocksdb")]
233    pub fn open_rocksdb(
234        path: &Path,
235        state_rewind_policy: StateRewindPolicy,
236        database_config: DatabaseConfig,
237    ) -> Result<Self> {
238        use anyhow::Context;
239
240        let db = HistoricalRocksDB::<Description>::default_open(
241            path,
242            state_rewind_policy,
243            database_config,
244        )
245        .map_err(Into::<anyhow::Error>::into)
246        .with_context(|| {
247            format!(
248                "Failed to open rocksdb, you may need to wipe a \
249                pre-existing incompatible db e.g. `rm -rf {path:?}`"
250            )
251        })?;
252
253        Ok(Self::new(Arc::new(db)))
254    }
255
256    /// Converts the regular database to an unchecked database.
257    ///
258    /// Returns an error in the case regular database is initialized with the `GenesisDatabase`,
259    /// to highlight that it is a bad idea and it is unsafe.
260    pub fn into_genesis(
261        self,
262    ) -> core::result::Result<GenesisDatabase<Description>, GenesisDatabase<Description>>
263    {
264        if !self.stage.height.lock().is_some() {
265            Ok(GenesisDatabase::new(self.into_inner().0.data))
266        } else {
267            tracing::warn!(
268                "Converting regular database into genesis, \
269                while height is already set for `{}`",
270                Description::name()
271            );
272            Err(GenesisDatabase::new(self.into_inner().0.data))
273        }
274    }
275}
276
277impl<Description, Stage> Database<Description, Stage>
278where
279    Description: DatabaseDescription,
280    Stage: Default,
281{
282    pub fn in_memory() -> Self {
283        let data = Arc::<MemoryStore<Description>>::new(MemoryStore::default());
284        Self::from_storage_and_metadata(
285            DataSource::new(data, Stage::default()),
286            Some(Empty::default()),
287        )
288    }
289
290    #[cfg(feature = "rocksdb")]
291    pub fn rocksdb_temp(
292        state_rewind_policy: StateRewindPolicy,
293        database_config: DatabaseConfig,
294    ) -> Result<Self> {
295        let db = RocksDb::<Historical<Description>>::default_open_temp_with_params(
296            database_config,
297        )?;
298        let historical_db = HistoricalRocksDB::new(db, state_rewind_policy)?;
299        let data = Arc::new(historical_db);
300        Ok(Self::from_storage_and_metadata(
301            DataSource::new(data, Stage::default()),
302            None,
303        ))
304    }
305}
306
307/// Construct an ephemeral database
308/// uses rocksdb when rocksdb features are enabled
309/// uses in-memory when rocksdb features are disabled
310impl<Description, Stage> Default for Database<Description, Stage>
311where
312    Description: DatabaseDescription,
313    Stage: Default,
314{
315    fn default() -> Self {
316        #[cfg(not(feature = "rocksdb"))]
317        {
318            Self::in_memory()
319        }
320        #[cfg(feature = "rocksdb")]
321        {
322            Self::rocksdb_temp(
323                StateRewindPolicy::NoRewind,
324                DatabaseConfig {
325                    cache_capacity: None,
326                    max_fds: 512,
327                    columns_policy: ColumnsPolicy::Lazy,
328                },
329            )
330            .expect("Failed to create a temporary database")
331        }
332    }
333}
334
335impl<Description> Database<Description>
336where
337    Description: DatabaseDescription,
338{
339    pub fn rollback_last_block(&self) -> StorageResult<()> {
340        let mut lock = self.inner_storage().stage.height.lock();
341        let height = *lock;
342
343        let Some(height) = height else {
344            return Err(
345                anyhow::anyhow!("Database doesn't have a height to rollback").into(),
346            );
347        };
348        self.inner_storage().data.rollback_block_to(&height)?;
349        let new_height = height.rollback_height();
350        *lock = new_height;
351        tracing::info!(
352            "Rollback of the {} to the height {:?} was successful",
353            Description::name(),
354            new_height
355        );
356
357        Ok(())
358    }
359
360    fn latest_view_with_height(
361        &self,
362        height: Option<Description::Height>,
363    ) -> StorageResult<IterableKeyValueView<ColumnType<Description>, Description::Height>>
364    {
365        let view = self.inner_storage().data.latest_view()?;
366
367        let (view, _) = view.into_inner();
368        Ok(IterableKeyValueView::from_storage_and_metadata(
369            view, height,
370        ))
371    }
372}
373
374impl<Description> AtomicView for Database<Description>
375where
376    Description: DatabaseDescription,
377{
378    type LatestView = IterableKeyValueView<ColumnType<Description>, Description::Height>;
379
380    fn latest_view(&self) -> StorageResult<Self::LatestView> {
381        let lock = self.inner_storage().stage.height.lock();
382        let view = self.latest_view_with_height(*lock)?;
383        Ok(view)
384    }
385}
386
387impl<Description> HistoricalView for Database<Description>
388where
389    Description: DatabaseDescription,
390{
391    type Height = Description::Height;
392    type ViewAtHeight = KeyValueView<ColumnType<Description>, Description::Height>;
393
394    fn latest_height(&self) -> Option<Self::Height> {
395        *self.inner_storage().stage.height.lock()
396    }
397
398    fn view_at(&self, height: &Self::Height) -> StorageResult<Self::ViewAtHeight> {
399        let lock = self.inner_storage().stage.height.lock();
400
401        match *lock {
402            None => {
403                return self
404                    .latest_view_with_height(None)
405                    .map(|view| view.into_key_value_view())
406            }
407            Some(current_height) if &current_height == height => {
408                return self
409                    .latest_view_with_height(Some(current_height))
410                    .map(|view| view.into_key_value_view())
411            }
412            _ => {}
413        };
414
415        self.inner_storage().data.view_at_height(height)
416    }
417}
418
419impl Modifiable for Database<OnChain> {
420    fn commit_changes(&mut self, changes: Changes) -> StorageResult<()> {
421        commit_changes_with_height_update(self, changes, |iter| {
422            iter.iter_all_keys::<FuelBlocks>(Some(IterDirection::Reverse))
423                .try_collect()
424        })
425    }
426}
427
428impl Modifiable for Database<OffChain> {
429    fn commit_changes(&mut self, changes: Changes) -> StorageResult<()> {
430        commit_changes_with_height_update(self, changes, |iter| {
431            iter.iter_all::<FuelBlockIdsToHeights>(Some(IterDirection::Reverse))
432                .map(|result| result.map(|(_, height)| height))
433                .try_collect()
434        })
435    }
436}
437
438impl Modifiable for Database<GasPriceDatabase> {
439    fn commit_changes(&mut self, changes: Changes) -> StorageResult<()> {
440        commit_changes_with_height_update(self, changes, |iter| {
441            iter.iter_all_keys::<GasPriceMetadata>(Some(IterDirection::Reverse))
442                .try_collect()
443        })
444    }
445}
446
447#[cfg(feature = "rpc")]
448impl Modifiable for Database<BlockAggregatorDatabase> {
449    fn commit_changes(&mut self, changes: Changes) -> StorageResult<()> {
450        // Does not need to be monotonically increasing because
451        // storage values are modified in parallel from different heights
452        commit_changes_with_height_update(self, changes, |iter| {
453            iter.iter_all::<fuel_core_block_aggregator_api::db::table::LatestBlock>(Some(
454                IterDirection::Reverse,
455            ))
456            .map(|result| result.map(|(_, mode)| mode.height()))
457            .try_collect()
458        })
459    }
460}
461
462#[cfg(feature = "relayer")]
463impl Modifiable for Database<Relayer> {
464    fn commit_changes(&mut self, changes: Changes) -> StorageResult<()> {
465        commit_changes_with_height_update(self, changes, |iter| {
466            iter.iter_all_keys::<fuel_core_relayer::storage::EventsHistory>(Some(
467                IterDirection::Reverse,
468            ))
469            .try_collect()
470        })
471    }
472}
473
474impl Modifiable for Database<CompressionDatabase> {
475    fn commit_changes(&mut self, changes: Changes) -> StorageResult<()> {
476        commit_changes_with_height_update(self, changes, |iter| {
477            iter.iter_all_keys::<fuel_core_compression_service::storage::CompressedBlocks>(Some(IterDirection::Reverse))
478                .try_collect()
479        })
480    }
481}
482
483#[cfg(not(feature = "relayer"))]
484impl Modifiable for Database<Relayer> {
485    fn commit_changes(&mut self, changes: Changes) -> StorageResult<()> {
486        commit_changes_with_height_update(self, changes, |_| Ok(vec![]))
487    }
488}
489
490impl Modifiable for GenesisDatabase<OnChain> {
491    fn commit_changes(&mut self, changes: Changes) -> StorageResult<()> {
492        self.data.as_ref().commit_changes(None, changes.into())
493    }
494}
495
496impl Modifiable for GenesisDatabase<OffChain> {
497    fn commit_changes(&mut self, changes: Changes) -> StorageResult<()> {
498        self.data.as_ref().commit_changes(None, changes.into())
499    }
500}
501
502impl Modifiable for GenesisDatabase<Relayer> {
503    fn commit_changes(&mut self, changes: Changes) -> StorageResult<()> {
504        self.data.as_ref().commit_changes(None, changes.into())
505    }
506}
507
508pub fn commit_changes_with_height_update<Description, Changes>(
509    database: &mut Database<Description>,
510    changes: Changes,
511    heights_lookup: impl Fn(
512        &ChangesIterator<Description::Column>,
513    ) -> StorageResult<Vec<Description::Height>>,
514) -> StorageResult<()>
515where
516    Description: DatabaseDescription,
517    Description::Height: Debug + PartialOrd + DatabaseHeight,
518    for<'a> StorageTransaction<&'a &'a mut Database<Description>>:
519        StorageMutate<MetadataTable<Description>, Error = StorageError>,
520    Changes: Into<StorageChanges>,
521{
522    // Gets the all new heights from the `changes`
523    let mut changes = changes.into();
524    let iterator = ChangesIterator::<Description::Column>::new(&changes);
525    let new_heights = heights_lookup(&iterator)?;
526
527    // Changes for each block should be committed separately.
528    // If we have more than one height, it means we are mixing commits
529    // for several heights in one batch - return error in this case.
530    if new_heights.len() > 1 {
531        return Err(DatabaseError::MultipleHeightsInCommit {
532            heights: new_heights.iter().map(DatabaseHeight::as_u64).collect(),
533        }
534        .into());
535    }
536
537    let new_height = new_heights.into_iter().next_back();
538    let prev_height = *database.stage.height.lock();
539
540    match (prev_height, new_height) {
541        (None, None) => {
542            // We are inside the regenesis process if the old and new heights are not set.
543            // In this case, we continue to commit until we discover a new height.
544            // This height will be the start of the database.
545        }
546        (Some(prev_height), Some(new_height)) => {
547            // Each new commit should be linked to the previous commit to create a monotonically growing database.
548
549            let next_expected_height = prev_height
550                .advance_height()
551                .ok_or(DatabaseError::FailedToAdvanceHeight)?;
552
553            if next_expected_height != new_height {
554                return Err(DatabaseError::HeightsAreNotLinked {
555                    prev_height: prev_height.as_u64(),
556                    new_height: new_height.as_u64(),
557                }
558                .into());
559            }
560        }
561        (None, Some(_)) => {
562            // The new height is finally found; starting at this point,
563            // all next commits should be linked(the height should increase each time by one).
564        }
565        (Some(prev_height), None) => {
566            // In production, we shouldn't have cases where we call `commit_changes` with intermediate changes.
567            // The commit always should contain all data for the corresponding height.
568            return Err(DatabaseError::NewHeightIsNotSet {
569                prev_height: prev_height.as_u64(),
570            }
571            .into());
572        }
573    };
574
575    if let Some(new_height) = new_height {
576        // We want to update the metadata table to include a new height.
577        // For that, we are building a new storage transaction.
578        // We get the changes from the database and add to our list of changes.
579        let mut transaction = StorageTransaction::transaction(
580            &database,
581            ConflictPolicy::Overwrite,
582            Default::default(),
583        );
584        let maybe_current_metadata = transaction
585            .storage_as_mut::<MetadataTable<Description>>()
586            .get(&())?;
587        let metadata = update_metadata::<Description>(maybe_current_metadata, new_height);
588        transaction
589            .storage_as_mut::<MetadataTable<Description>>()
590            .insert(&(), &metadata)?;
591
592        changes = match changes {
593            StorageChanges::Changes(c) => {
594                StorageChanges::ChangesList(vec![c, transaction.into_changes()])
595            }
596            StorageChanges::ChangesList(mut list) => {
597                list.push(transaction.into_changes());
598                StorageChanges::ChangesList(list)
599            }
600        }
601    };
602
603    // Atomically commit the changes to the database, and to the mutex-protected field.
604    let mut guard = database.stage.height.lock();
605    database.data.commit_changes(new_height, changes)?;
606
607    // Update the block height
608    if let Some(new_height) = new_height {
609        *guard = Some(new_height);
610    }
611
612    Ok(())
613}
614
615fn update_metadata<Description>(
616    maybe_current_metadata: Option<
617        Cow<DatabaseMetadata<<Description as DatabaseDescription>::Height>>,
618    >,
619    new_height: <Description as DatabaseDescription>::Height,
620) -> DatabaseMetadata<<Description as DatabaseDescription>::Height>
621where
622    Description: DatabaseDescription,
623{
624    match maybe_current_metadata.as_ref() {
625        Some(metadata) => match metadata.as_ref() {
626            DatabaseMetadata::V1 { .. } => DatabaseMetadata::V1 {
627                version: Description::version(),
628                height: new_height,
629            },
630            DatabaseMetadata::V2 {
631                indexation_availability,
632                ..
633            } => DatabaseMetadata::V2 {
634                version: Description::version(),
635                height: new_height,
636                indexation_availability: indexation_availability.clone(),
637            },
638        },
639        None => DatabaseMetadata::V2 {
640            version: Description::version(),
641            height: new_height,
642            indexation_availability: indexation_availability::<Description>(None),
643        },
644    }
645}
646
647#[cfg(feature = "rocksdb")]
648pub fn convert_to_rocksdb_direction(direction: IterDirection) -> rocksdb::Direction {
649    match direction {
650        IterDirection::Forward => rocksdb::Direction::Forward,
651        IterDirection::Reverse => rocksdb::Direction::Reverse,
652    }
653}
654
655#[cfg(test)]
656mod tests {
657    use super::*;
658    use crate::database::{
659        Database,
660        database_description::DatabaseDescription,
661    };
662
663    fn column_keys_not_exceed_count<Description>()
664    where
665        Description: DatabaseDescription,
666    {
667        use enum_iterator::all;
668        use fuel_core_storage::kv_store::StorageColumn;
669        use strum::EnumCount;
670        for column in all::<Description::Column>() {
671            assert!(column.as_usize() < Description::Column::COUNT);
672        }
673    }
674
675    mod on_chain {
676        use super::*;
677        use crate::database::{
678            DatabaseHeight,
679            database_description::on_chain::OnChain,
680        };
681        use fuel_core_storage::{
682            tables::Coins,
683            transactional::WriteTransaction,
684        };
685        use fuel_core_types::{
686            blockchain::block::CompressedBlock,
687            entities::coins::coin::CompressedCoin,
688            fuel_tx::UtxoId,
689        };
690
691        #[test]
692        fn column_keys_not_exceed_count_test() {
693            column_keys_not_exceed_count::<OnChain>();
694        }
695
696        #[test]
697        fn database_advances_with_a_new_block() {
698            // Given
699            let mut database = Database::<OnChain>::default();
700            assert_eq!(database.latest_height(), None);
701
702            // When
703            let advanced_height = 1.into();
704            database
705                .storage_as_mut::<FuelBlocks>()
706                .insert(&advanced_height, &CompressedBlock::default())
707                .unwrap();
708
709            // Then
710            assert_eq!(database.latest_height(), Some(advanced_height));
711        }
712
713        #[test]
714        fn database_not_advances_without_block() {
715            // Given
716            let mut database = Database::<OnChain>::default();
717            assert_eq!(database.latest_height(), None);
718
719            // When
720            database
721                .storage_as_mut::<Coins>()
722                .insert(&UtxoId::default(), &CompressedCoin::default())
723                .unwrap();
724
725            // Then
726            assert_eq!(HistoricalView::latest_height(&database), None);
727        }
728
729        #[test]
730        fn database_advances_with_linked_blocks() {
731            // Given
732            let mut database = Database::<OnChain>::default();
733            let starting_height = 1.into();
734            database
735                .storage_as_mut::<FuelBlocks>()
736                .insert(&starting_height, &CompressedBlock::default())
737                .unwrap();
738            assert_eq!(database.latest_height(), Some(starting_height));
739
740            // When
741            let next_height = starting_height.advance_height().unwrap();
742            database
743                .storage_as_mut::<FuelBlocks>()
744                .insert(&next_height, &CompressedBlock::default())
745                .unwrap();
746
747            // Then
748            assert_eq!(database.latest_height(), Some(next_height));
749        }
750
751        #[test]
752        fn database_fails_with_unlinked_blocks() {
753            // Given
754            let mut database = Database::<OnChain>::default();
755            let starting_height = 1.into();
756            database
757                .storage_as_mut::<FuelBlocks>()
758                .insert(&starting_height, &CompressedBlock::default())
759                .unwrap();
760
761            // When
762            let prev_height = 0.into();
763            let result = database
764                .storage_as_mut::<FuelBlocks>()
765                .insert(&prev_height, &CompressedBlock::default());
766
767            // Then
768            assert_eq!(
769                result.unwrap_err().to_string(),
770                StorageError::from(DatabaseError::HeightsAreNotLinked {
771                    prev_height: 1,
772                    new_height: 0
773                })
774                .to_string()
775            );
776        }
777
778        #[test]
779        fn database_fails_with_non_advancing_commit() {
780            // Given
781            let mut database = Database::<OnChain>::default();
782            let starting_height = 1.into();
783            database
784                .storage_as_mut::<FuelBlocks>()
785                .insert(&starting_height, &CompressedBlock::default())
786                .unwrap();
787
788            // When
789            let result = database
790                .storage_as_mut::<Coins>()
791                .insert(&UtxoId::default(), &CompressedCoin::default());
792
793            // Then
794            assert!(result.is_err());
795            assert_eq!(
796                result.unwrap_err().to_string(),
797                StorageError::from(DatabaseError::NewHeightIsNotSet { prev_height: 1 })
798                    .to_string()
799            );
800        }
801
802        #[test]
803        fn database_fails_when_commit_with_several_blocks() {
804            let mut database = Database::<OnChain>::default();
805            let starting_height = 1.into();
806            database
807                .storage_as_mut::<FuelBlocks>()
808                .insert(&starting_height, &CompressedBlock::default())
809                .unwrap();
810
811            // Given
812            let mut transaction = database.write_transaction();
813            let next_height = starting_height.advance_height().unwrap();
814            let next_next_height = next_height.advance_height().unwrap();
815            transaction
816                .storage_as_mut::<FuelBlocks>()
817                .insert(&next_height, &CompressedBlock::default())
818                .unwrap();
819            transaction
820                .storage_as_mut::<FuelBlocks>()
821                .insert(&next_next_height, &CompressedBlock::default())
822                .unwrap();
823
824            // When
825            let result = transaction.commit();
826
827            // Then
828            assert!(result.is_err());
829            assert_eq!(
830                result.unwrap_err().to_string(),
831                StorageError::from(DatabaseError::MultipleHeightsInCommit {
832                    heights: vec![3, 2]
833                })
834                .to_string()
835            );
836        }
837    }
838
839    mod off_chain {
840        use super::*;
841        use crate::{
842            database::{
843                DatabaseHeight,
844                database_description::off_chain::OffChain,
845            },
846            fuel_core_graphql_api::storage::messages::OwnedMessageKey,
847            graphql_api::storage::messages::OwnedMessageIds,
848        };
849        use fuel_core_storage::transactional::WriteTransaction;
850
851        #[test]
852        fn database_advances_with_a_new_block() {
853            // Given
854            let mut database = Database::<OffChain>::default();
855            assert_eq!(database.latest_height(), None);
856
857            // When
858            let advanced_height = 1.into();
859            database
860                .storage_as_mut::<FuelBlockIdsToHeights>()
861                .insert(&Default::default(), &advanced_height)
862                .unwrap();
863
864            // Then
865            assert_eq!(database.latest_height(), Some(advanced_height));
866        }
867
868        #[test]
869        fn database_not_advances_without_block() {
870            // Given
871            let mut database = Database::<OffChain>::default();
872            assert_eq!(database.latest_height(), None);
873
874            // When
875            database
876                .storage_as_mut::<OwnedMessageIds>()
877                .insert(&OwnedMessageKey::default(), &())
878                .unwrap();
879
880            // Then
881            assert_eq!(HistoricalView::latest_height(&database), None);
882        }
883
884        #[test]
885        fn database_advances_with_linked_blocks() {
886            // Given
887            let mut database = Database::<OffChain>::default();
888            let starting_height = 1.into();
889            database
890                .storage_as_mut::<FuelBlockIdsToHeights>()
891                .insert(&Default::default(), &starting_height)
892                .unwrap();
893            assert_eq!(database.latest_height(), Some(starting_height));
894
895            // When
896            let next_height = starting_height.advance_height().unwrap();
897            database
898                .storage_as_mut::<FuelBlockIdsToHeights>()
899                .insert(&Default::default(), &next_height)
900                .unwrap();
901
902            // Then
903            assert_eq!(database.latest_height(), Some(next_height));
904        }
905
906        #[test]
907        fn database_fails_with_unlinked_blocks() {
908            // Given
909            let mut database = Database::<OffChain>::default();
910            let starting_height = 1.into();
911            database
912                .storage_as_mut::<FuelBlockIdsToHeights>()
913                .insert(&Default::default(), &starting_height)
914                .unwrap();
915
916            // When
917            let prev_height = 0.into();
918            let result = database
919                .storage_as_mut::<FuelBlockIdsToHeights>()
920                .insert(&Default::default(), &prev_height);
921
922            // Then
923            assert!(result.is_err());
924            assert_eq!(
925                result.unwrap_err().to_string(),
926                StorageError::from(DatabaseError::HeightsAreNotLinked {
927                    prev_height: 1,
928                    new_height: 0
929                })
930                .to_string()
931            );
932        }
933
934        #[test]
935        fn database_fails_with_non_advancing_commit() {
936            // Given
937            let mut database = Database::<OffChain>::default();
938            let starting_height = 1.into();
939            database
940                .storage_as_mut::<FuelBlockIdsToHeights>()
941                .insert(&Default::default(), &starting_height)
942                .unwrap();
943
944            // When
945            let result = database
946                .storage_as_mut::<OwnedMessageIds>()
947                .insert(&OwnedMessageKey::default(), &());
948
949            // Then
950            assert!(result.is_err());
951            assert_eq!(
952                result.unwrap_err().to_string(),
953                StorageError::from(DatabaseError::NewHeightIsNotSet { prev_height: 1 })
954                    .to_string()
955            );
956        }
957
958        #[test]
959        fn database_fails_when_commit_with_several_blocks() {
960            let mut database = Database::<OffChain>::default();
961            let starting_height = 1.into();
962            database
963                .storage_as_mut::<FuelBlockIdsToHeights>()
964                .insert(&Default::default(), &starting_height)
965                .unwrap();
966
967            // Given
968            let mut transaction = database.write_transaction();
969            let next_height = starting_height.advance_height().unwrap();
970            let next_next_height = next_height.advance_height().unwrap();
971            transaction
972                .storage_as_mut::<FuelBlockIdsToHeights>()
973                .insert(&[1; 32].into(), &next_height)
974                .unwrap();
975            transaction
976                .storage_as_mut::<FuelBlockIdsToHeights>()
977                .insert(&[2; 32].into(), &next_next_height)
978                .unwrap();
979
980            // When
981            let result = transaction.commit();
982
983            // Then
984            assert!(result.is_err());
985            assert_eq!(
986                result.unwrap_err().to_string(),
987                StorageError::from(DatabaseError::MultipleHeightsInCommit {
988                    heights: vec![3, 2]
989                })
990                .to_string()
991            );
992        }
993    }
994
995    #[cfg(feature = "relayer")]
996    mod relayer {
997        use super::*;
998        use crate::database::{
999            DatabaseHeight,
1000            database_description::relayer::Relayer,
1001        };
1002        use fuel_core_relayer::storage::EventsHistory;
1003        use fuel_core_storage::transactional::WriteTransaction;
1004        use fuel_core_types::blockchain::primitives::DaBlockHeight;
1005
1006        #[test]
1007        fn column_keys_not_exceed_count_test() {
1008            column_keys_not_exceed_count::<Relayer>();
1009        }
1010
1011        #[test]
1012        fn database_advances_with_a_new_block() {
1013            // Given
1014            let mut database = Database::<Relayer>::default();
1015            assert_eq!(database.latest_height(), None);
1016
1017            // When
1018            let advanced_height = 1u64.into();
1019            database
1020                .storage_as_mut::<EventsHistory>()
1021                .insert(&advanced_height, &[])
1022                .unwrap();
1023
1024            // Then
1025            assert_eq!(database.latest_height(), Some(advanced_height));
1026        }
1027
1028        #[test]
1029        fn database_not_advances_without_block() {
1030            // Given
1031            let mut database = Database::<Relayer>::default();
1032            assert_eq!(database.latest_height(), None);
1033
1034            // When
1035            database
1036                .storage_as_mut::<MetadataTable<Relayer>>()
1037                .insert(
1038                    &(),
1039                    &DatabaseMetadata::<DaBlockHeight>::V1 {
1040                        version: Default::default(),
1041                        height: Default::default(),
1042                    },
1043                )
1044                .unwrap();
1045
1046            // Then
1047            assert_eq!(HistoricalView::latest_height(&database), None);
1048        }
1049
1050        #[test]
1051        fn database_advances_with_linked_blocks() {
1052            // Given
1053            let mut database = Database::<Relayer>::default();
1054            let starting_height = 1u64.into();
1055            database
1056                .storage_as_mut::<EventsHistory>()
1057                .insert(&starting_height, &[])
1058                .unwrap();
1059            assert_eq!(database.latest_height(), Some(starting_height));
1060
1061            // When
1062            let next_height = starting_height.advance_height().unwrap();
1063            database
1064                .storage_as_mut::<EventsHistory>()
1065                .insert(&next_height, &[])
1066                .unwrap();
1067
1068            // Then
1069            assert_eq!(database.latest_height(), Some(next_height));
1070        }
1071
1072        #[test]
1073        fn database_fails_with_unlinked_blocks() {
1074            // Given
1075            let mut database = Database::<Relayer>::default();
1076            let starting_height = 1u64.into();
1077            database
1078                .storage_as_mut::<EventsHistory>()
1079                .insert(&starting_height, &[])
1080                .unwrap();
1081
1082            // When
1083            let prev_height = 0u64.into();
1084            let result = database
1085                .storage_as_mut::<EventsHistory>()
1086                .insert(&prev_height, &[]);
1087
1088            // Then
1089            assert!(result.is_err());
1090            assert_eq!(
1091                result.unwrap_err().to_string(),
1092                StorageError::from(DatabaseError::HeightsAreNotLinked {
1093                    prev_height: 1,
1094                    new_height: 0
1095                })
1096                .to_string()
1097            );
1098        }
1099
1100        #[test]
1101        fn database_fails_with_non_advancing_commit() {
1102            // Given
1103            let mut database = Database::<Relayer>::default();
1104            let starting_height = 1u64.into();
1105            database
1106                .storage_as_mut::<EventsHistory>()
1107                .insert(&starting_height, &[])
1108                .unwrap();
1109
1110            // When
1111            let result = database.storage_as_mut::<MetadataTable<Relayer>>().insert(
1112                &(),
1113                &DatabaseMetadata::<DaBlockHeight>::V1 {
1114                    version: Default::default(),
1115                    height: Default::default(),
1116                },
1117            );
1118
1119            // Then
1120            assert!(result.is_err());
1121            assert_eq!(
1122                result.unwrap_err().to_string(),
1123                StorageError::from(DatabaseError::NewHeightIsNotSet { prev_height: 1 })
1124                    .to_string()
1125            );
1126        }
1127
1128        #[test]
1129        fn database_fails_when_commit_with_several_blocks() {
1130            let mut database = Database::<Relayer>::default();
1131            let starting_height = 1u64.into();
1132            database
1133                .storage_as_mut::<EventsHistory>()
1134                .insert(&starting_height, &[])
1135                .unwrap();
1136
1137            // Given
1138            let mut transaction = database.write_transaction();
1139            let next_height = starting_height.advance_height().unwrap();
1140            let next_next_height = next_height.advance_height().unwrap();
1141            transaction
1142                .storage_as_mut::<EventsHistory>()
1143                .insert(&next_height, &[])
1144                .unwrap();
1145            transaction
1146                .storage_as_mut::<EventsHistory>()
1147                .insert(&next_next_height, &[])
1148                .unwrap();
1149
1150            // When
1151            let result = transaction.commit();
1152
1153            // Then
1154            assert!(result.is_err());
1155            assert_eq!(
1156                result.unwrap_err().to_string(),
1157                StorageError::from(DatabaseError::MultipleHeightsInCommit {
1158                    heights: vec![3, 2]
1159                })
1160                .to_string()
1161            );
1162        }
1163    }
1164
1165    #[cfg(feature = "rocksdb")]
1166    #[test]
1167    fn database_iter_all_by_prefix_works() {
1168        use fuel_core_storage::tables::ContractsRawCode;
1169        use fuel_core_types::fuel_types::ContractId;
1170        use std::str::FromStr;
1171
1172        let test = |mut db: Database<OnChain>| {
1173            let contract_id_1 = ContractId::from_str(
1174                "5962be5ebddc516cb4ed7d7e76365f59e0d231ac25b53f262119edf76564aab4",
1175            )
1176            .unwrap();
1177
1178            let mut insert_empty_code = |id| {
1179                StorageMutate::<ContractsRawCode>::insert(&mut db, &id, &[]).unwrap()
1180            };
1181            insert_empty_code(contract_id_1);
1182
1183            let contract_id_2 = ContractId::from_str(
1184                "5baf0dcae7c114f647f6e71f1723f59bcfc14ecb28071e74895d97b14873c5dc",
1185            )
1186            .unwrap();
1187            insert_empty_code(contract_id_2);
1188
1189            let matched_keys: Vec<_> = db
1190                .iter_all_by_prefix::<ContractsRawCode, _>(Some(contract_id_1))
1191                .map_ok(|(k, _)| k)
1192                .try_collect()
1193                .unwrap();
1194
1195            assert_eq!(matched_keys, vec![contract_id_1]);
1196        };
1197
1198        let temp_dir = tempfile::tempdir().unwrap();
1199        let db = Database::<OnChain>::in_memory();
1200        // in memory passes
1201        test(db);
1202
1203        let db = Database::<OnChain>::open_rocksdb(
1204            temp_dir.path(),
1205            Default::default(),
1206            DatabaseConfig::config_for_tests(),
1207        )
1208        .unwrap();
1209        // rocks db fails
1210        test(db);
1211    }
1212
1213    mod metadata {
1214        use crate::database::database_description::IndexationKind;
1215        use fuel_core_storage::kv_store::StorageColumn;
1216        use std::{
1217            borrow::Cow,
1218            collections::HashSet,
1219        };
1220        use strum::EnumCount;
1221
1222        use super::{
1223            DatabaseHeight,
1224            DatabaseMetadata,
1225            database_description::DatabaseDescription,
1226            update_metadata,
1227        };
1228
1229        #[derive(Debug, Copy, Clone, Default, PartialEq, Eq)]
1230        struct HeightMock(u64);
1231        impl DatabaseHeight for HeightMock {
1232            fn as_u64(&self) -> u64 {
1233                1
1234            }
1235
1236            fn advance_height(&self) -> Option<Self> {
1237                None
1238            }
1239
1240            fn rollback_height(&self) -> Option<Self> {
1241                None
1242            }
1243        }
1244
1245        const MOCK_VERSION: u32 = 0;
1246
1247        #[derive(EnumCount, enum_iterator::Sequence, Debug, Clone, Copy)]
1248        enum ColumnMock {
1249            Column1,
1250        }
1251
1252        impl StorageColumn for ColumnMock {
1253            fn name(&self) -> String {
1254                "column".to_string()
1255            }
1256
1257            fn id(&self) -> u32 {
1258                42
1259            }
1260        }
1261
1262        #[derive(Debug, Clone, Copy)]
1263        struct DatabaseDescriptionMock;
1264        impl DatabaseDescription for DatabaseDescriptionMock {
1265            type Column = ColumnMock;
1266
1267            type Height = HeightMock;
1268
1269            fn version() -> u32 {
1270                MOCK_VERSION
1271            }
1272
1273            fn name() -> String {
1274                "mock".to_string()
1275            }
1276
1277            fn metadata_column() -> Self::Column {
1278                Self::Column::Column1
1279            }
1280
1281            fn prefix(_: &Self::Column) -> Option<usize> {
1282                None
1283            }
1284        }
1285
1286        #[test]
1287        fn update_metadata_preserves_v1() {
1288            let current_metadata: DatabaseMetadata<HeightMock> = DatabaseMetadata::V1 {
1289                version: MOCK_VERSION,
1290                height: HeightMock(1),
1291            };
1292            let new_metadata = update_metadata::<DatabaseDescriptionMock>(
1293                Some(Cow::Borrowed(&current_metadata)),
1294                HeightMock(2),
1295            );
1296
1297            match new_metadata {
1298                DatabaseMetadata::V1 { version, height } => {
1299                    assert_eq!(version, current_metadata.version());
1300                    assert_eq!(height, HeightMock(2));
1301                }
1302                DatabaseMetadata::V2 { .. } => panic!("should be V1"),
1303            }
1304        }
1305
1306        #[test]
1307        fn update_metadata_preserves_v2() {
1308            let available_indexation = HashSet::new();
1309
1310            let current_metadata: DatabaseMetadata<HeightMock> = DatabaseMetadata::V2 {
1311                version: MOCK_VERSION,
1312                height: HeightMock(1),
1313                indexation_availability: available_indexation.clone(),
1314            };
1315            let new_metadata = update_metadata::<DatabaseDescriptionMock>(
1316                Some(Cow::Borrowed(&current_metadata)),
1317                HeightMock(2),
1318            );
1319
1320            match new_metadata {
1321                DatabaseMetadata::V1 { .. } => panic!("should be V2"),
1322                DatabaseMetadata::V2 {
1323                    version,
1324                    height,
1325                    indexation_availability,
1326                } => {
1327                    assert_eq!(version, current_metadata.version());
1328                    assert_eq!(height, HeightMock(2));
1329                    assert_eq!(indexation_availability, available_indexation);
1330                }
1331            }
1332        }
1333
1334        #[test]
1335        fn update_metadata_none_becomes_v2() {
1336            let new_metadata =
1337                update_metadata::<DatabaseDescriptionMock>(None, HeightMock(2));
1338
1339            match new_metadata {
1340                DatabaseMetadata::V1 { .. } => panic!("should be V2"),
1341                DatabaseMetadata::V2 {
1342                    version,
1343                    height,
1344                    indexation_availability,
1345                } => {
1346                    assert_eq!(version, MOCK_VERSION);
1347                    assert_eq!(height, HeightMock(2));
1348                    assert_eq!(indexation_availability, IndexationKind::all().collect());
1349                }
1350            }
1351        }
1352    }
1353}