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#[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
95pub 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 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 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
304impl<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 ¤t_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 let mut changes = changes.into();
506 let iterator = ChangesIterator::<Description::Column>::new(&changes);
507 let new_heights = heights_lookup(&iterator)?;
508
509 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 }
528 (Some(prev_height), Some(new_height)) => {
529 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 }
547 (Some(prev_height), None) => {
548 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 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 let mut guard = database.stage.height.lock();
587 database.data.commit_changes(new_height, changes)?;
588
589 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 let mut database = Database::<OnChain>::default();
682 assert_eq!(database.latest_height(), None);
683
684 let advanced_height = 1.into();
686 database
687 .storage_as_mut::<FuelBlocks>()
688 .insert(&advanced_height, &CompressedBlock::default())
689 .unwrap();
690
691 assert_eq!(database.latest_height(), Some(advanced_height));
693 }
694
695 #[test]
696 fn database_not_advances_without_block() {
697 let mut database = Database::<OnChain>::default();
699 assert_eq!(database.latest_height(), None);
700
701 database
703 .storage_as_mut::<Coins>()
704 .insert(&UtxoId::default(), &CompressedCoin::default())
705 .unwrap();
706
707 assert_eq!(HistoricalView::latest_height(&database), None);
709 }
710
711 #[test]
712 fn database_advances_with_linked_blocks() {
713 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 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 assert_eq!(database.latest_height(), Some(next_height));
731 }
732
733 #[test]
734 fn database_fails_with_unlinked_blocks() {
735 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 let prev_height = 0.into();
745 let result = database
746 .storage_as_mut::<FuelBlocks>()
747 .insert(&prev_height, &CompressedBlock::default());
748
749 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 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 let result = database
772 .storage_as_mut::<Coins>()
773 .insert(&UtxoId::default(), &CompressedCoin::default());
774
775 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 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 let result = transaction.commit();
808
809 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 let mut database = Database::<OffChain>::default();
837 assert_eq!(database.latest_height(), None);
838
839 let advanced_height = 1.into();
841 database
842 .storage_as_mut::<FuelBlockIdsToHeights>()
843 .insert(&Default::default(), &advanced_height)
844 .unwrap();
845
846 assert_eq!(database.latest_height(), Some(advanced_height));
848 }
849
850 #[test]
851 fn database_not_advances_without_block() {
852 let mut database = Database::<OffChain>::default();
854 assert_eq!(database.latest_height(), None);
855
856 database
858 .storage_as_mut::<OwnedMessageIds>()
859 .insert(&OwnedMessageKey::default(), &())
860 .unwrap();
861
862 assert_eq!(HistoricalView::latest_height(&database), None);
864 }
865
866 #[test]
867 fn database_advances_with_linked_blocks() {
868 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 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 assert_eq!(database.latest_height(), Some(next_height));
886 }
887
888 #[test]
889 fn database_fails_with_unlinked_blocks() {
890 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 let prev_height = 0.into();
900 let result = database
901 .storage_as_mut::<FuelBlockIdsToHeights>()
902 .insert(&Default::default(), &prev_height);
903
904 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 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 let result = database
928 .storage_as_mut::<OwnedMessageIds>()
929 .insert(&OwnedMessageKey::default(), &());
930
931 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 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 let result = transaction.commit();
964
965 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 let mut database = Database::<Relayer>::default();
997 assert_eq!(database.latest_height(), None);
998
999 let advanced_height = 1u64.into();
1001 database
1002 .storage_as_mut::<EventsHistory>()
1003 .insert(&advanced_height, &[])
1004 .unwrap();
1005
1006 assert_eq!(database.latest_height(), Some(advanced_height));
1008 }
1009
1010 #[test]
1011 fn database_not_advances_without_block() {
1012 let mut database = Database::<Relayer>::default();
1014 assert_eq!(database.latest_height(), None);
1015
1016 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 assert_eq!(HistoricalView::latest_height(&database), None);
1030 }
1031
1032 #[test]
1033 fn database_advances_with_linked_blocks() {
1034 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 let next_height = starting_height.advance_height().unwrap();
1045 database
1046 .storage_as_mut::<EventsHistory>()
1047 .insert(&next_height, &[])
1048 .unwrap();
1049
1050 assert_eq!(database.latest_height(), Some(next_height));
1052 }
1053
1054 #[test]
1055 fn database_fails_with_unlinked_blocks() {
1056 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 let prev_height = 0u64.into();
1066 let result = database
1067 .storage_as_mut::<EventsHistory>()
1068 .insert(&prev_height, &[]);
1069
1070 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 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 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 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 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 let result = transaction.commit();
1134
1135 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 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 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(¤t_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(¤t_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}