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