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 = "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
98pub 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 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 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
307impl<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 ¤t_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 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 let mut changes = changes.into();
524 let iterator = ChangesIterator::<Description::Column>::new(&changes);
525 let new_heights = heights_lookup(&iterator)?;
526
527 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 }
546 (Some(prev_height), Some(new_height)) => {
547 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 }
565 (Some(prev_height), None) => {
566 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 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 let mut guard = database.stage.height.lock();
605 database.data.commit_changes(new_height, changes)?;
606
607 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 let mut database = Database::<OnChain>::default();
700 assert_eq!(database.latest_height(), None);
701
702 let advanced_height = 1.into();
704 database
705 .storage_as_mut::<FuelBlocks>()
706 .insert(&advanced_height, &CompressedBlock::default())
707 .unwrap();
708
709 assert_eq!(database.latest_height(), Some(advanced_height));
711 }
712
713 #[test]
714 fn database_not_advances_without_block() {
715 let mut database = Database::<OnChain>::default();
717 assert_eq!(database.latest_height(), None);
718
719 database
721 .storage_as_mut::<Coins>()
722 .insert(&UtxoId::default(), &CompressedCoin::default())
723 .unwrap();
724
725 assert_eq!(HistoricalView::latest_height(&database), None);
727 }
728
729 #[test]
730 fn database_advances_with_linked_blocks() {
731 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 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 assert_eq!(database.latest_height(), Some(next_height));
749 }
750
751 #[test]
752 fn database_fails_with_unlinked_blocks() {
753 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 let prev_height = 0.into();
763 let result = database
764 .storage_as_mut::<FuelBlocks>()
765 .insert(&prev_height, &CompressedBlock::default());
766
767 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 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 let result = database
790 .storage_as_mut::<Coins>()
791 .insert(&UtxoId::default(), &CompressedCoin::default());
792
793 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 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 let result = transaction.commit();
826
827 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 let mut database = Database::<OffChain>::default();
855 assert_eq!(database.latest_height(), None);
856
857 let advanced_height = 1.into();
859 database
860 .storage_as_mut::<FuelBlockIdsToHeights>()
861 .insert(&Default::default(), &advanced_height)
862 .unwrap();
863
864 assert_eq!(database.latest_height(), Some(advanced_height));
866 }
867
868 #[test]
869 fn database_not_advances_without_block() {
870 let mut database = Database::<OffChain>::default();
872 assert_eq!(database.latest_height(), None);
873
874 database
876 .storage_as_mut::<OwnedMessageIds>()
877 .insert(&OwnedMessageKey::default(), &())
878 .unwrap();
879
880 assert_eq!(HistoricalView::latest_height(&database), None);
882 }
883
884 #[test]
885 fn database_advances_with_linked_blocks() {
886 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 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 assert_eq!(database.latest_height(), Some(next_height));
904 }
905
906 #[test]
907 fn database_fails_with_unlinked_blocks() {
908 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 let prev_height = 0.into();
918 let result = database
919 .storage_as_mut::<FuelBlockIdsToHeights>()
920 .insert(&Default::default(), &prev_height);
921
922 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 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 let result = database
946 .storage_as_mut::<OwnedMessageIds>()
947 .insert(&OwnedMessageKey::default(), &());
948
949 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 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 let result = transaction.commit();
982
983 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 let mut database = Database::<Relayer>::default();
1015 assert_eq!(database.latest_height(), None);
1016
1017 let advanced_height = 1u64.into();
1019 database
1020 .storage_as_mut::<EventsHistory>()
1021 .insert(&advanced_height, &[])
1022 .unwrap();
1023
1024 assert_eq!(database.latest_height(), Some(advanced_height));
1026 }
1027
1028 #[test]
1029 fn database_not_advances_without_block() {
1030 let mut database = Database::<Relayer>::default();
1032 assert_eq!(database.latest_height(), None);
1033
1034 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 assert_eq!(HistoricalView::latest_height(&database), None);
1048 }
1049
1050 #[test]
1051 fn database_advances_with_linked_blocks() {
1052 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 let next_height = starting_height.advance_height().unwrap();
1063 database
1064 .storage_as_mut::<EventsHistory>()
1065 .insert(&next_height, &[])
1066 .unwrap();
1067
1068 assert_eq!(database.latest_height(), Some(next_height));
1070 }
1071
1072 #[test]
1073 fn database_fails_with_unlinked_blocks() {
1074 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 let prev_height = 0u64.into();
1084 let result = database
1085 .storage_as_mut::<EventsHistory>()
1086 .insert(&prev_height, &[]);
1087
1088 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 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 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 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 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 let result = transaction.commit();
1152
1153 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 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 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(¤t_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(¤t_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}