diff --git a/crates/chain-config/src/config/coin.rs b/crates/chain-config/src/config/coin.rs index 452cf8af163..a8c4cac32d5 100644 --- a/crates/chain-config/src/config/coin.rs +++ b/crates/chain-config/src/config/coin.rs @@ -8,7 +8,7 @@ use crate::{ use fuel_core_storage::MerkleRoot; use fuel_core_types::{ blockchain::primitives::BlockHeight, - entities::coin::Coin, + entities::coin::CompressedCoin, fuel_crypto::Hasher, fuel_types::{ Address, @@ -52,7 +52,7 @@ pub struct CoinConfig { pub asset_id: AssetId, } -impl GenesisCommitment for Coin { +impl GenesisCommitment for CompressedCoin { fn root(&mut self) -> anyhow::Result { let coin_hash = *Hasher::default() .chain(self.owner) diff --git a/crates/client/assets/schema.sdl b/crates/client/assets/schema.sdl index a9ad01c27fe..3e5fa5f7082 100644 --- a/crates/client/assets/schema.sdl +++ b/crates/client/assets/schema.sdl @@ -588,7 +588,7 @@ enum ReceiptType { } """ -The schema analog of the [`crate::database::utils::Resource`]. +The schema analog of the [`resource::Resource`]. """ union Resource = Coin | Message diff --git a/crates/fuel-core/src/database.rs b/crates/fuel-core/src/database.rs index 2d5cc2cecf5..bb7caf2b299 100644 --- a/crates/fuel-core/src/database.rs +++ b/crates/fuel-core/src/database.rs @@ -67,7 +67,7 @@ use fuel_core_storage::tables::{ use fuel_core_txpool::ports::TxPoolDb; use fuel_core_types::{ entities::{ - coin::Coin, + coin::CompressedCoin, message::Message, }, fuel_tx::UtxoId, @@ -98,9 +98,9 @@ mod state; pub mod balances; pub mod metadata; -pub mod resource; -pub mod transaction; +// TODO: Rename in a separate PR into `transaction` pub mod transactional; +pub mod transactions; pub mod vm_database; /// Database tables column ids. @@ -135,7 +135,7 @@ pub enum Column { Receipts = 11, /// See [`FuelBlocks`](fuel_core_storage::tables::FuelBlocks) FuelBlocks = 12, - /// Maps fuel block id to fuel block hash + /// Maps fuel block height to fuel block id FuelBlockIds = 13, /// See [`Messages`](fuel_core_storage::tables::Messages) Messages = 14, @@ -330,7 +330,7 @@ impl Default for Database { impl BlockDb for Database { fn block_height(&self) -> anyhow::Result { - Ok(self.get_block_height()?.unwrap_or_default()) + Ok(self.latest_height()?.unwrap_or_default()) } fn seal_block( @@ -339,14 +339,14 @@ impl BlockDb for Database { consensus: Consensus, ) -> anyhow::Result<()> { self.storage::() - .insert(&block_id.into(), &consensus) + .insert(&block_id, &consensus) .map(|_| ()) .map_err(Into::into) } } impl TxPoolDb for Database { - fn utxo(&self, utxo_id: &UtxoId) -> StorageResult> { + fn utxo(&self, utxo_id: &UtxoId) -> StorageResult> { self.storage::() .get(utxo_id) .map(|t| t.map(|t| t.as_ref().clone())) @@ -363,7 +363,7 @@ impl TxPoolDb for Database { } fn current_block_height(&self) -> StorageResult { - self.get_block_height() + self.latest_height() .map(|h| h.unwrap_or_default()) .map_err(Into::into) } @@ -381,7 +381,7 @@ impl BlockProducerDatabase for Database { } fn current_block_height(&self) -> StorageResult { - self.get_block_height() + self.latest_height() .map(|h| h.unwrap_or_default()) .map_err(Into::into) } @@ -403,6 +403,6 @@ impl ChainConfigDb for Database { } fn get_block_height(&self) -> StorageResult> { - Self::get_block_height(self).map_err(Into::into) + Self::latest_height(self).map_err(Into::into) } } diff --git a/crates/fuel-core/src/database/block.rs b/crates/fuel-core/src/database/block.rs index 90d649eb5f0..271f67d13b3 100644 --- a/crates/fuel-core/src/database/block.rs +++ b/crates/fuel-core/src/database/block.rs @@ -25,7 +25,10 @@ use fuel_core_types::{ Block, CompressedBlock, }, - primitives::BlockHeight, + primitives::{ + BlockHeight, + BlockId, + }, }, fuel_tx::Bytes32, tai64::Tai64, @@ -42,19 +45,19 @@ use std::{ impl StorageInspect for Database { type Error = StorageError; - fn get(&self, key: &Bytes32) -> Result>, Self::Error> { - Database::get(self, key.as_ref(), Column::FuelBlocks).map_err(Into::into) + fn get(&self, key: &BlockId) -> Result>, Self::Error> { + Database::get(self, key.as_slice(), Column::FuelBlocks).map_err(Into::into) } - fn contains_key(&self, key: &Bytes32) -> Result { - Database::exists(self, key.as_ref(), Column::FuelBlocks).map_err(Into::into) + fn contains_key(&self, key: &BlockId) -> Result { + Database::exists(self, key.as_slice(), Column::FuelBlocks).map_err(Into::into) } } impl StorageMutate for Database { fn insert( &mut self, - key: &Bytes32, + key: &BlockId, value: &CompressedBlock, ) -> Result, Self::Error> { let _: Option = Database::insert( @@ -63,13 +66,13 @@ impl StorageMutate for Database { Column::FuelBlockIds, *key, )?; - Database::insert(self, key.as_ref(), Column::FuelBlocks, value) + Database::insert(self, key.as_slice(), Column::FuelBlocks, value) .map_err(Into::into) } - fn remove(&mut self, key: &Bytes32) -> Result, Self::Error> { + fn remove(&mut self, key: &BlockId) -> Result, Self::Error> { let block: Option = - Database::remove(self, key.as_ref(), Column::FuelBlocks)?; + Database::remove(self, key.as_slice(), Column::FuelBlocks)?; if let Some(block) = &block { let _: Option = Database::remove( self, @@ -82,32 +85,28 @@ impl StorageMutate for Database { } impl Database { - pub fn get_block_height(&self) -> DatabaseResult> { - let block_entry = self.latest_block()?; - // get block height from most recently indexed block - let mut id = block_entry.map(|(height, _)| { - // safety: we know that all block heights are stored with the correct amount of bytes - let bytes = <[u8; 4]>::try_from(height.as_slice()).unwrap(); - u32::from_be_bytes(bytes).into() - }); + pub fn latest_height(&self) -> DatabaseResult> { + let id = self.ids_of_latest_block()?; // if no blocks, check if chain was configured with a base height - if id.is_none() { - id = self.get_starting_chain_height()?; - } + let id = match id { + Some((id, _)) => Some(id), + None => self.get_starting_chain_height()?, + }; + Ok(id) } /// Get the current block at the head of the chain. pub fn get_current_block(&self) -> StorageResult>> { - let block_entry = self.latest_block()?; - match block_entry { + let block_ids = self.ids_of_latest_block()?; + match block_ids { Some((_, id)) => Ok(StorageAsRef::storage::(self).get(&id)?), None => Ok(None), } } - pub fn block_time(&self, height: u32) -> StorageResult { - let id = self.get_block_id(height.into())?.unwrap_or_default(); + pub fn block_time(&self, height: BlockHeight) -> StorageResult { + let id = self.get_block_id(height)?.unwrap_or_default(); let block = self .storage::() .get(&id)? @@ -115,7 +114,7 @@ impl Database { Ok(block.header().time().to_owned()) } - pub fn get_block_id(&self, height: BlockHeight) -> StorageResult> { + pub fn get_block_id(&self, height: BlockHeight) -> StorageResult> { Database::get(self, &height.to_bytes()[..], Column::FuelBlockIds) .map_err(Into::into) } @@ -123,22 +122,27 @@ impl Database { pub fn all_block_ids( &self, start: Option, - direction: Option, - ) -> impl Iterator> + '_ { + direction: IterDirection, + ) -> impl Iterator> + '_ { let start = start.map(|b| b.to_bytes().to_vec()); - self.iter_all::, Bytes32>(Column::FuelBlockIds, None, start, direction) - .map(|res| { - let (height, id) = res?; - Ok(( - height - .try_into() - .expect("block height always has correct number of bytes"), - id, - )) - }) + self.iter_all::, BlockId>( + Column::FuelBlockIds, + None, + start, + Some(direction), + ) + .map(|res| { + let (height, id) = res?; + Ok(( + height + .try_into() + .expect("block height always has correct number of bytes"), + id, + )) + }) } - pub fn genesis_block_ids(&self) -> DatabaseResult<(BlockHeight, Bytes32)> { + pub fn ids_of_genesis_block(&self) -> DatabaseResult<(BlockHeight, BlockId)> { self.iter_all( Column::FuelBlockIds, None, @@ -147,28 +151,36 @@ impl Database { ) .next() .ok_or(DatabaseError::ChainUninitialized)? - .map(|(height, id): (Vec, Bytes32)| { + .map(|(height, id): (Vec, BlockId)| { let bytes = <[u8; 4]>::try_from(height.as_slice()) .expect("all block heights are stored with the correct amount of bytes"); (u32::from_be_bytes(bytes).into(), id) }) } - fn latest_block(&self) -> DatabaseResult, Bytes32)>> { - self.iter_all( - Column::FuelBlockIds, - None, - None, - Some(IterDirection::Reverse), - ) - .next() - .transpose() + pub fn ids_of_latest_block(&self) -> DatabaseResult> { + let ids = self + .iter_all::, BlockId>( + Column::FuelBlockIds, + None, + None, + Some(IterDirection::Reverse), + ) + .next() + .transpose()? + .map(|(height, block)| { + // safety: we know that all block heights are stored with the correct amount of bytes + let bytes = <[u8; 4]>::try_from(height.as_slice()).unwrap(); + (u32::from_be_bytes(bytes).into(), block) + }); + + Ok(ids) } /// Retrieve the full block and all associated transactions pub(crate) fn get_full_block( &self, - block_id: &Bytes32, + block_id: &BlockId, ) -> StorageResult> { let db_block = self.storage::().get(block_id)?; if let Some(block) = db_block { diff --git a/crates/fuel-core/src/database/coin.rs b/crates/fuel-core/src/database/coin.rs index 5949b54aef9..8c75f4a7bfc 100644 --- a/crates/fuel-core/src/database/coin.rs +++ b/crates/fuel-core/src/database/coin.rs @@ -16,8 +16,8 @@ use fuel_core_storage::{ }; use fuel_core_types::{ entities::coin::{ - Coin, CoinStatus, + CompressedCoin, }, fuel_tx::{ Address, @@ -49,7 +49,7 @@ fn utxo_id_to_bytes(utxo_id: &UtxoId) -> Vec { impl StorageInspect for Database { type Error = StorageError; - fn get(&self, key: &UtxoId) -> Result>, Self::Error> { + fn get(&self, key: &UtxoId) -> Result>, Self::Error> { Database::get(self, &utxo_id_to_bytes(key), Column::Coins).map_err(Into::into) } @@ -62,8 +62,8 @@ impl StorageMutate for Database { fn insert( &mut self, key: &UtxoId, - value: &Coin, - ) -> Result, Self::Error> { + value: &CompressedCoin, + ) -> Result, Self::Error> { let coin_by_owner: Vec = owner_coin_id_key(&value.owner, key); // insert primary record let insert = Database::insert(self, utxo_id_to_bytes(key), Column::Coins, value)?; @@ -73,8 +73,8 @@ impl StorageMutate for Database { Ok(insert) } - fn remove(&mut self, key: &UtxoId) -> Result, Self::Error> { - let coin: Option = + fn remove(&mut self, key: &UtxoId) -> Result, Self::Error> { + let coin: Option = Database::remove(self, &utxo_id_to_bytes(key), Column::Coins)?; // cleanup secondary index @@ -114,7 +114,7 @@ impl Database { pub fn get_coin_config(&self) -> DatabaseResult>> { let configs = self - .iter_all::, Coin>(Column::Coins, None, None, None) + .iter_all::, CompressedCoin>(Column::Coins, None, None, None) .filter_map(|coin| { // Return only unspent coins if let Ok(coin) = coin { diff --git a/crates/fuel-core/src/database/relayer.rs b/crates/fuel-core/src/database/relayer.rs index b54c38f9cc7..20daf924024 100644 --- a/crates/fuel-core/src/database/relayer.rs +++ b/crates/fuel-core/src/database/relayer.rs @@ -19,9 +19,8 @@ use fuel_core_types::{ }, }; -#[async_trait::async_trait] impl RelayerDb for Database { - async fn insert_message( + fn insert_message( &mut self, message: &CheckedMessage, ) -> StorageResult> { @@ -34,16 +33,13 @@ impl RelayerDb for Database { .insert(message.id(), message.message()) } - async fn set_finalized_da_height( - &mut self, - block: DaBlockHeight, - ) -> StorageResult<()> { + fn set_finalized_da_height(&mut self, block: DaBlockHeight) -> StorageResult<()> { let _: Option = self.insert(metadata::FINALIZED_DA_HEIGHT_KEY, Column::Metadata, block)?; Ok(()) } - async fn get_finalized_da_height(&self) -> StorageResult { + fn get_finalized_da_height(&self) -> StorageResult { self.get(metadata::FINALIZED_DA_HEIGHT_KEY, Column::Metadata)? .ok_or(not_found!("FinalizedDaHeight")) } diff --git a/crates/fuel-core/src/database/sealed_block.rs b/crates/fuel-core/src/database/sealed_block.rs index 47efb55155e..5839adcc747 100644 --- a/crates/fuel-core/src/database/sealed_block.rs +++ b/crates/fuel-core/src/database/sealed_block.rs @@ -14,29 +14,30 @@ use fuel_core_storage::{ StorageInspect, StorageMutate, }; -use fuel_core_types::{ - blockchain::{ - consensus::{ - Consensus, - Genesis, - }, - primitives::BlockHeight, - SealedBlock, - SealedBlockHeader, +use fuel_core_types::blockchain::{ + consensus::{ + Consensus, + Genesis, }, - fuel_tx::Bytes32, + primitives::{ + BlockHeight, + BlockId, + }, + SealedBlock, + SealedBlockHeader, }; use std::borrow::Cow; impl StorageInspect for Database { type Error = StorageError; - fn get(&self, key: &Bytes32) -> Result>, Self::Error> { - Database::get(self, key.as_ref(), Column::FuelBlockConsensus).map_err(Into::into) + fn get(&self, key: &BlockId) -> Result>, Self::Error> { + Database::get(self, key.as_slice(), Column::FuelBlockConsensus) + .map_err(Into::into) } - fn contains_key(&self, key: &Bytes32) -> Result { - Database::exists(self, key.as_ref(), Column::FuelBlockConsensus) + fn contains_key(&self, key: &BlockId) -> Result { + Database::exists(self, key.as_slice(), Column::FuelBlockConsensus) .map_err(Into::into) } } @@ -44,15 +45,15 @@ impl StorageInspect for Database { impl StorageMutate for Database { fn insert( &mut self, - key: &Bytes32, + key: &BlockId, value: &Consensus, ) -> Result, Self::Error> { - Database::insert(self, key.as_ref(), Column::FuelBlockConsensus, value) + Database::insert(self, key.as_slice(), Column::FuelBlockConsensus, value) .map_err(Into::into) } - fn remove(&mut self, key: &Bytes32) -> Result, Self::Error> { - Database::remove(self, key.as_ref(), Column::FuelBlockConsensus) + fn remove(&mut self, key: &BlockId) -> Result, Self::Error> { + Database::remove(self, key.as_slice(), Column::FuelBlockConsensus) .map_err(Into::into) } } @@ -60,7 +61,7 @@ impl StorageMutate for Database { impl Database { pub fn get_sealed_block_by_id( &self, - block_id: &Bytes32, + block_id: &BlockId, ) -> StorageResult> { // combine the block and consensus metadata into a sealed fuel block type @@ -90,7 +91,7 @@ impl Database { } pub fn get_genesis(&self) -> StorageResult { - let (_, genesis_block_id) = self.genesis_block_ids()?; + let (_, genesis_block_id) = self.ids_of_genesis_block()?; let consensus = self .storage::() .get(&genesis_block_id)? @@ -105,7 +106,7 @@ impl Database { pub fn get_sealed_block_header( &self, - block_id: &Bytes32, + block_id: &BlockId, ) -> StorageResult> { let header = self.storage::().get(block_id)?; let consensus = self.storage::().get(block_id)?; diff --git a/crates/fuel-core/src/database/transactional.rs b/crates/fuel-core/src/database/transactional.rs index ff3059d1aed..33a13fe42ad 100644 --- a/crates/fuel-core/src/database/transactional.rs +++ b/crates/fuel-core/src/database/transactional.rs @@ -3,7 +3,7 @@ use crate::{ state::in_memory::transaction::MemoryTransactionView, }; use fuel_core_storage::{ - transactional::Transactional, + transactional::Transaction, Result as StorageResult, }; use std::{ @@ -55,7 +55,7 @@ impl Default for DatabaseTransaction { } } -impl Transactional for DatabaseTransaction { +impl Transaction for DatabaseTransaction { fn commit(&mut self) -> StorageResult<()> { // TODO: should commit be fallible if this api is meant to be atomic? Ok(self.changes.commit()?) diff --git a/crates/fuel-core/src/database/transaction.rs b/crates/fuel-core/src/database/transactions.rs similarity index 88% rename from crates/fuel-core/src/database/transaction.rs rename to crates/fuel-core/src/database/transactions.rs index c940e8cb8d1..7aaa6931662 100644 --- a/crates/fuel-core/src/database/transaction.rs +++ b/crates/fuel-core/src/database/transactions.rs @@ -18,6 +18,7 @@ use fuel_core_types::{ fuel_tx::{ Bytes32, Transaction, + TxPointer, }, fuel_types::Address, services::txpool::TransactionStatus, @@ -77,10 +78,9 @@ impl Database { pub fn owned_transactions( &self, owner: &Address, - start: Option<&OwnedTransactionIndexCursor>, + start: Option, direction: Option, - ) -> impl Iterator> + '_ - { + ) -> impl Iterator> + '_ { let start = start .map(|cursor| owned_tx_index_key(owner, cursor.block_height, cursor.tx_idx)); self.iter_all::( @@ -89,7 +89,11 @@ impl Database { start, direction, ) - .map(|res| res.map(|(key, tx_id)| (key.into(), tx_id))) + .map(|res| { + res.map(|(key, tx_id)| { + (TxPointer::new(key.block_height.into(), key.tx_idx), tx_id) + }) + }) } pub fn record_tx_id_owner( @@ -129,7 +133,7 @@ fn owned_tx_index_key( ) -> Vec { // generate prefix to enable sorted indexing of transactions by owner // owner + block_height + tx_idx - let mut key = Vec::with_capacity(40); + let mut key = Vec::with_capacity(38); key.extend(owner.as_ref()); key.extend(height.to_bytes()); key.extend(tx_idx.to_be_bytes()); @@ -138,7 +142,7 @@ fn owned_tx_index_key( ////////////////////////////////////// Not storage part ////////////////////////////////////// -pub type TransactionIndex = u32; +pub type TransactionIndex = u16; pub struct OwnedTransactionIndexKey { block_height: BlockHeight, @@ -150,13 +154,13 @@ impl From> for OwnedTransactionIndexKey { // the first 32 bytes are the owner, which is already known when querying let mut block_height_bytes: [u8; 4] = Default::default(); block_height_bytes.copy_from_slice(&bytes[32..36]); - let mut tx_idx_bytes: [u8; 4] = Default::default(); - tx_idx_bytes.copy_from_slice(&bytes[36..40]); + let mut tx_idx_bytes: [u8; 2] = Default::default(); + tx_idx_bytes.copy_from_slice(&bytes[36..38]); Self { // owner: Address::from(owner_bytes), block_height: u32::from_be_bytes(block_height_bytes).into(), - tx_idx: u32::from_be_bytes(tx_idx_bytes), + tx_idx: u16::from_be_bytes(tx_idx_bytes), } } } @@ -180,12 +184,12 @@ impl From> for OwnedTransactionIndexCursor { fn from(bytes: Vec) -> Self { let mut block_height_bytes: [u8; 4] = Default::default(); block_height_bytes.copy_from_slice(&bytes[..4]); - let mut tx_idx_bytes: [u8; 4] = Default::default(); - tx_idx_bytes.copy_from_slice(&bytes[4..8]); + let mut tx_idx_bytes: [u8; 2] = Default::default(); + tx_idx_bytes.copy_from_slice(&bytes[4..6]); Self { block_height: u32::from_be_bytes(block_height_bytes).into(), - tx_idx: u32::from_be_bytes(tx_idx_bytes), + tx_idx: u16::from_be_bytes(tx_idx_bytes), } } } diff --git a/crates/fuel-core/src/database/vm_database.rs b/crates/fuel-core/src/database/vm_database.rs index 43e74cc63a5..56339a78390 100644 --- a/crates/fuel-core/src/database/vm_database.rs +++ b/crates/fuel-core/src/database/vm_database.rs @@ -79,10 +79,6 @@ impl VmDatabase { database, } } - - pub fn block_height(&self) -> u32 { - self.current_block_height - } } impl StorageInspect for VmDatabase @@ -140,7 +136,7 @@ impl InterpreterStorage for VmDatabase { return Err(anyhow!("block height too high for timestamp").into()) } height if height == self.current_block_height => self.current_timestamp, - height => self.database.block_time(height)?, + height => self.database.block_time(height.into())?, }; Ok(timestamp.0) } @@ -155,6 +151,7 @@ impl InterpreterStorage for VmDatabase { self.database .get_block_id(block_height.into())? .ok_or(not_found!("BlockId")) + .map(Into::into) } } diff --git a/crates/fuel-core/src/executor.rs b/crates/fuel-core/src/executor.rs index 7a9bbad7cc3..4465260704c 100644 --- a/crates/fuel-core/src/executor.rs +++ b/crates/fuel-core/src/executor.rs @@ -1,7 +1,7 @@ use crate::{ database::{ - transaction::TransactionIndex, transactional::DatabaseTransaction, + transactions::TransactionIndex, vm_database::VmDatabase, Database, }, @@ -20,7 +20,7 @@ use fuel_core_storage::{ }, transactional::{ StorageTransaction, - Transactional, + Transaction as StorageTransactionTrait, }, StorageAsMut, StorageAsRef, @@ -40,8 +40,8 @@ use fuel_core_types::{ }, entities::{ coin::{ - Coin, CoinStatus, + CompressedCoin, }, message::Message, }, @@ -274,7 +274,7 @@ impl Executor { block_db_transaction .deref_mut() .storage::() - .insert(&finalized_block_id.into(), &result.block.compress())?; + .insert(&finalized_block_id, &result.block.compress())?; // Get the complete fuel block. Ok(UncommittedResult::new( @@ -847,7 +847,7 @@ impl Executor { db.storage::().insert( utxo_id, - &Coin { + &CompressedCoin { owner: *owner, amount: *amount, asset_id: *asset_id, @@ -1205,7 +1205,7 @@ impl Executor { // This is because variable or transfer outputs won't have any value // if there's a revert or panic and shouldn't be added to the utxo set. if *amount > Word::MIN { - let coin = Coin { + let coin = CompressedCoin { owner: *to, amount: *amount, asset_id: *asset_id, @@ -2151,7 +2151,7 @@ mod tests { let asset_id = Default::default(); let maturity = Default::default(); let block_created = Default::default(); - let coin = Coin { + let coin = CompressedCoin { owner, amount, asset_id, @@ -2499,7 +2499,7 @@ mod tests { db.storage::() .insert( &first_input.utxo_id().unwrap().clone(), - &Coin { + &CompressedCoin { owner: *first_input.input_owner().unwrap(), amount: 100, asset_id: AssetId::default(), @@ -2512,7 +2512,7 @@ mod tests { db.storage::() .insert( &second_input.utxo_id().unwrap().clone(), - &Coin { + &CompressedCoin { owner: *second_input.input_owner().unwrap(), amount: 100, asset_id: AssetId::default(), @@ -3028,7 +3028,7 @@ mod tests { db.storage::() .insert( &utxo_id, - &Coin { + &CompressedCoin { owner, amount, asset_id, @@ -3574,7 +3574,7 @@ mod tests { .storage::() .insert( coin_input.utxo_id().unwrap(), - &Coin { + &CompressedCoin { owner: *coin_input.input_owner().unwrap(), amount: coin_input.amount().unwrap(), asset_id: *coin_input.asset_id().unwrap(), @@ -3651,7 +3651,7 @@ mod tests { .storage::() .insert( coin_input.utxo_id().unwrap(), - &Coin { + &CompressedCoin { owner: *coin_input.input_owner().unwrap(), amount: coin_input.amount().unwrap(), asset_id: *coin_input.asset_id().unwrap(), diff --git a/crates/fuel-core/src/graphql_api.rs b/crates/fuel-core/src/graphql_api.rs index 8342ce71105..7614a0989a3 100644 --- a/crates/fuel-core/src/graphql_api.rs +++ b/crates/fuel-core/src/graphql_api.rs @@ -1,3 +1,4 @@ +use fuel_core_storage::Error as StorageError; use fuel_core_types::{ blockchain::primitives::SecretKeyWrapper, fuel_tx::ConsensusParameters, @@ -5,6 +6,7 @@ use fuel_core_types::{ }; use std::net::SocketAddr; +pub mod ports; pub mod service; #[derive(Clone, Debug)] @@ -19,3 +21,24 @@ pub struct Config { pub transaction_parameters: ConsensusParameters, pub consensus_key: Option>, } + +pub trait IntoApiResult { + fn into_api_result(self) -> Result, E> + where + NewT: From, + E: From; +} + +impl IntoApiResult for Result { + fn into_api_result(self) -> Result, E> + where + NewT: From, + E: From, + { + match self { + Ok(t) => Ok(Some(t.into())), + Err(StorageError::NotFound(_, _)) => Ok(None), + Err(err) => Err(err.into()), + } + } +} diff --git a/crates/fuel-core/src/graphql_api/ports.rs b/crates/fuel-core/src/graphql_api/ports.rs new file mode 100644 index 00000000000..2e7958ad9c3 --- /dev/null +++ b/crates/fuel-core/src/graphql_api/ports.rs @@ -0,0 +1,132 @@ +use crate::state::IterDirection; +use fuel_core_storage::{ + iter::BoxedIter, + tables::{ + Coins, + ContractsAssets, + ContractsInfo, + ContractsRawCode, + FuelBlocks, + Messages, + Receipts, + SealedBlockConsensus, + Transactions, + }, + Error as StorageError, + Result as StorageResult, + StorageInspect, +}; +use fuel_core_types::{ + blockchain::primitives::{ + BlockHeight, + BlockId, + DaBlockHeight, + }, + entities::message::Message, + fuel_tx::{ + TxId, + TxPointer, + UtxoId, + }, + fuel_types::{ + Address, + AssetId, + ContractId, + MessageId, + }, + services::{ + graphql_api::ContractBalance, + txpool::TransactionStatus, + }, +}; + +/// The database port expected by GraphQL API service. +pub trait DatabasePort: + Send + + Sync + + DatabaseBlocks + + DatabaseTransactions + + DatabaseMessages + + DatabaseCoins + + DatabaseContracts + + DatabaseChain +{ +} + +/// Trait that specifies all the getters required for blocks. +pub trait DatabaseBlocks: + StorageInspect + + StorageInspect +{ + fn block_id(&self, height: BlockHeight) -> StorageResult; + + fn blocks_ids( + &self, + start: Option, + direction: IterDirection, + ) -> BoxedIter<'_, StorageResult<(BlockHeight, BlockId)>>; + + fn ids_of_latest_block(&self) -> StorageResult<(BlockHeight, BlockId)>; +} + +/// Trait that specifies all the getters required for transactions. +pub trait DatabaseTransactions: + StorageInspect + + StorageInspect +{ + fn tx_status(&self, tx_id: &TxId) -> StorageResult; + + fn owned_transactions_ids( + &self, + owner: &Address, + start: Option, + direction: IterDirection, + ) -> BoxedIter>; +} + +/// Trait that specifies all the getters required for messages. +pub trait DatabaseMessages: StorageInspect { + fn owned_message_ids( + &self, + owner: &Address, + start_message_id: Option, + direction: IterDirection, + ) -> BoxedIter<'_, StorageResult>; + + fn all_messages( + &self, + start_message_id: Option, + direction: IterDirection, + ) -> BoxedIter<'_, StorageResult>; +} + +/// Trait that specifies all the getters required for coins. +pub trait DatabaseCoins: StorageInspect { + fn owned_coins_ids( + &self, + owner: &Address, + start_coin: Option, + direction: IterDirection, + ) -> BoxedIter<'_, StorageResult>; +} + +/// Trait that specifies all the getters required for contract. +pub trait DatabaseContracts: + StorageInspect + + StorageInspect + + for<'a> StorageInspect, Error = StorageError> +{ + fn contract_balances( + &self, + contract: ContractId, + start_asset: Option, + direction: IterDirection, + ) -> BoxedIter>; +} + +/// Trait that specifies all the getters required for chain metadata. +pub trait DatabaseChain { + fn chain_name(&self) -> StorageResult; + + fn base_chain_height(&self) -> StorageResult; +} diff --git a/crates/fuel-core/src/graphql_api/service.rs b/crates/fuel-core/src/graphql_api/service.rs index dc957a10607..2ad61df8686 100644 --- a/crates/fuel-core/src/graphql_api/service.rs +++ b/crates/fuel-core/src/graphql_api/service.rs @@ -1,9 +1,9 @@ use crate::{ + fuel_core_graphql_api::ports::DatabasePort, graphql_api::Config, schema::{ - build_schema, - dap, CoreSchema, + CoreSchemaBuilder, }, service::metrics::metrics, }; @@ -66,8 +66,7 @@ use tower_http::{ pub type Service = fuel_core_services::ServiceRunner; -// TODO: When the port of DB will exist we need to replace it with `Box -pub type Database = crate::database::Database; +pub type Database = Box; // TODO: When the port for `Executor` will exist we need to replace it with `Box pub type Executor = crate::service::adapters::ExecutorAdapter; // TODO: When the port of BlockProducer will exist we need to replace it with @@ -138,19 +137,20 @@ impl RunnableTask for Task { pub fn new_service( config: Config, database: Database, + schema: CoreSchemaBuilder, producer: BlockProducer, txpool: TxPool, executor: Executor, ) -> anyhow::Result { let network_addr = config.addr; - let params = config.transaction_parameters; - let schema = build_schema() + let schema = schema .data(config) .data(database) .data(producer) .data(txpool) - .data(executor); - let schema = dap::init(schema, params).extension(Tracing).finish(); + .data(executor) + .extension(Tracing) + .finish(); let router = Router::new() .route("/playground", get(graphql_playground)) diff --git a/crates/fuel-core/src/query.rs b/crates/fuel-core/src/query.rs index ce0bc9ace12..07edd9bbd7d 100644 --- a/crates/fuel-core/src/query.rs +++ b/crates/fuel-core/src/query.rs @@ -1,5 +1,19 @@ +mod balance; +mod block; +mod chain; +mod coin; +mod contract; +// TODO: Rename into `message` in a separate PR later. mod message_proof; mod subscriptions; +mod tx; +// TODO: Remove reexporting of everything +pub use balance::*; +pub use block::*; +pub use chain::*; +pub use coin::*; +pub use contract::*; pub use message_proof::*; pub(crate) use subscriptions::*; +pub use tx::*; diff --git a/crates/fuel-core/src/query/balance.rs b/crates/fuel-core/src/query/balance.rs new file mode 100644 index 00000000000..4c1c84deb50 --- /dev/null +++ b/crates/fuel-core/src/query/balance.rs @@ -0,0 +1,107 @@ +use crate::{ + fuel_core_graphql_api::service::Database, + state::IterDirection, +}; +use asset_query::{ + AssetQuery, + AssetSpendTarget, + AssetsQuery, +}; +use fuel_core_storage::Result as StorageResult; +use fuel_core_types::{ + fuel_tx::{ + Address, + AssetId, + }, + services::graphql_api::AddressBalance, +}; +use itertools::Itertools; +use std::{ + cmp::Ordering, + collections::HashMap, +}; + +pub mod asset_query; + +pub struct BalanceQueryContext<'a>(pub &'a Database); + +impl BalanceQueryContext<'_> { + pub fn balance( + &self, + owner: Address, + asset_id: AssetId, + ) -> StorageResult { + let db = self.0; + let amount = AssetQuery::new( + &owner, + &AssetSpendTarget::new(asset_id, u64::MAX, u64::MAX), + None, + db, + ) + .unspent_resources() + .map(|res| res.map(|resource| *resource.amount())) + .try_fold(0u64, |mut balance, res| -> StorageResult<_> { + let amount = res?; + + // Increase the balance + balance += amount; + + Ok(balance) + })?; + + Ok(AddressBalance { + owner, + amount, + asset_id, + }) + } + + pub fn balances( + &self, + owner: Address, + direction: IterDirection, + ) -> impl Iterator> + '_ { + let db = self.0; + + let mut amounts_per_asset = HashMap::new(); + let mut errors = vec![]; + + for resource in AssetsQuery::new(&owner, None, None, db).unspent_resources() { + match resource { + Ok(resource) => { + *amounts_per_asset.entry(*resource.asset_id()).or_default() += + resource.amount(); + } + Err(err) => { + errors.push(err); + } + } + } + + let mut balances = amounts_per_asset + .into_iter() + .map(|(asset_id, amount)| AddressBalance { + owner, + amount, + asset_id, + }) + .collect_vec(); + + balances.sort_by(|l, r| { + if l.asset_id < r.asset_id { + Ordering::Less + } else { + Ordering::Greater + } + }); + + if direction == IterDirection::Reverse { + balances.reverse(); + } + + balances + .into_iter() + .map(Ok) + .chain(errors.into_iter().map(Err)) + } +} diff --git a/crates/fuel-core/src/database/resource.rs b/crates/fuel-core/src/query/balance/asset_query.rs similarity index 55% rename from crates/fuel-core/src/database/resource.rs rename to crates/fuel-core/src/query/balance/asset_query.rs index 3170b2522e9..33d7a0db107 100644 --- a/crates/fuel-core/src/database/resource.rs +++ b/crates/fuel-core/src/query/balance/asset_query.rs @@ -1,38 +1,32 @@ -use crate::database::Database; -use fuel_core_storage::{ - not_found, - tables::{ - Coins, - Messages, +use crate::{ + fuel_core_graphql_api::service::Database, + query::{ + CoinQueryContext, + MessageQueryContext, }, + state::IterDirection, +}; +use fuel_core_storage::{ Error as StorageError, Result as StorageResult, - StorageAsRef, }; use fuel_core_types::{ entities::{ - coin::{ - Coin, - CoinStatus, + coin::CoinStatus, + resource::{ + Resource, + ResourceId, }, - message::Message, }, fuel_tx::UtxoId, fuel_types::{ Address, AssetId, MessageId, - Word, }, }; use itertools::Itertools; -use std::{ - borrow::{ - Borrow, - Cow, - }, - collections::HashSet, -}; +use std::collections::HashSet; /// At least required `target` of the query per asset's `id` with `max` resources. #[derive(Clone)] @@ -102,10 +96,9 @@ impl<'a> AssetsQuery<'a> { // https://github.com/FuelLabs/fuel-core/issues/588 pub fn unspent_resources( &self, - ) -> impl Iterator, Cow>>> + '_ { - let coins_iter = self - .database - .owned_coins_ids(self.owner, None, None) + ) -> impl Iterator> + '_ { + let coins_iter = CoinQueryContext(self.database) + .owned_coins_ids(self.owner, None, IterDirection::Forward) .filter_ok(|id| { if let Some(exclude) = self.exclude { !exclude.utxos.contains(id) @@ -113,32 +106,27 @@ impl<'a> AssetsQuery<'a> { true } }) - .map(|res| { + .map(move |res| { res.map_err(StorageError::from).and_then(|id| { - let coin = self - .database - .storage::() - .get(&id)? - .ok_or(not_found!(Coins))?; + let coin = CoinQueryContext(self.database).coin(id)?; - Ok(Resource::Coin { id, fields: coin }) + Ok(Resource::Coin(coin)) }) }) .filter_ok(|coin| { - if let Resource::Coin { fields, .. } = coin { - let is_unspent = fields.status == CoinStatus::Unspent; + if let Resource::Coin(coin) = coin { + let is_unspent = coin.status == CoinStatus::Unspent; self.assets .as_ref() - .map(|assets| assets.contains(&fields.asset_id) && is_unspent) + .map(|assets| assets.contains(&coin.asset_id) && is_unspent) .unwrap_or(is_unspent) } else { true } }); - let messages_iter = self - .database - .owned_message_ids(self.owner, None, None) + let messages_iter = MessageQueryContext(self.database) + .owned_message_ids(self.owner, None, IterDirection::Forward) .filter_ok(|id| { if let Some(exclude) = self.exclude { !exclude.messages.contains(id) @@ -146,23 +134,15 @@ impl<'a> AssetsQuery<'a> { true } }) - .map(|res| { - res.map_err(StorageError::from).and_then(|id| { - let message = self - .database - .storage::() - .get(&id)? - .ok_or(not_found!(Messages))?; - - Ok(Resource::Message { - id, - fields: message, - }) + .map(move |res| { + res.and_then(|id| { + let message = MessageQueryContext(self.database).message(&id)?; + Ok(Resource::Message(message)) }) }) .filter_ok(|message| { - if let Resource::Message { fields, .. } = message { - fields.fuel_block_spend.is_none() + if let Resource::Message(message) = message { + message.fuel_block_spend.is_none() } else { true } @@ -207,62 +187,7 @@ impl<'a> AssetQuery<'a> { /// for the `asset_id`. pub fn unspent_resources( &self, - ) -> impl Iterator, Cow>>> + '_ { + ) -> impl Iterator> + '_ { self.query.unspent_resources() } } - -/// The id of the resource. -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum ResourceId { - Utxo(UtxoId), - Message(MessageId), -} - -/// The primary type of spent or not spent resources(coins, messages, etc). The not spent resource -/// can be used as a source of information for the creation of the transaction's input. -#[derive(Debug)] -pub enum Resource { - Coin { id: UtxoId, fields: C }, - Message { id: MessageId, fields: M }, -} - -impl Resource -where - C: Borrow, - M: Borrow, -{ - pub fn amount(&self) -> &Word { - match self { - Resource::Coin { fields, .. } => &fields.borrow().amount, - Resource::Message { fields, .. } => &fields.borrow().amount, - } - } - - pub fn asset_id(&self) -> &AssetId { - match self { - Resource::Coin { fields, .. } => &fields.borrow().asset_id, - Resource::Message { .. } => &AssetId::BASE, - } - } -} - -impl<'c, 'm, C, M> Resource, Cow<'m, M>> -where - C: Clone, - M: Clone, -{ - /// Return owned `C` or `M`. Will clone if is borrowed. - pub fn into_owned(self) -> Resource { - match self { - Resource::Coin { id, fields } => Resource::Coin { - id, - fields: fields.into_owned(), - }, - Resource::Message { id, fields } => Resource::Message { - id, - fields: fields.into_owned(), - }, - } - } -} diff --git a/crates/fuel-core/src/query/block.rs b/crates/fuel-core/src/query/block.rs new file mode 100644 index 00000000000..0f1642831d5 --- /dev/null +++ b/crates/fuel-core/src/query/block.rs @@ -0,0 +1,80 @@ +use crate::{ + fuel_core_graphql_api::service::Database, + state::IterDirection, +}; +use fuel_core_storage::{ + not_found, + tables::{ + FuelBlocks, + SealedBlockConsensus, + }, + Result as StorageResult, + StorageAsRef, +}; +use fuel_core_types::blockchain::{ + block::CompressedBlock, + consensus::Consensus, + primitives::{ + BlockHeight, + BlockId, + }, +}; + +pub struct BlockQueryContext<'a>(pub &'a Database); + +impl BlockQueryContext<'_> { + pub fn block(&self, id: &BlockId) -> StorageResult { + let db = self.0; + + let block = db + .as_ref() + .storage::() + .get(id)? + .ok_or_else(|| not_found!(FuelBlocks))? + .into_owned(); + + Ok(block) + } + + pub fn block_id(&self, height: BlockHeight) -> StorageResult { + self.0.block_id(height) + } + + pub fn latest_block_id(&self) -> StorageResult { + self.0.ids_of_latest_block().map(|(_, id)| id) + } + + pub fn latest_block_height(&self) -> StorageResult { + self.0.ids_of_latest_block().map(|(height, _)| height) + } + + pub fn latest_block(&self) -> StorageResult { + self.latest_block_id().and_then(|id| self.block(&id)) + } + + pub fn compressed_blocks( + &self, + start: Option, + direction: IterDirection, + ) -> impl Iterator> + '_ { + let db = self.0; + db.blocks_ids(start.map(Into::into), direction) + .into_iter() + .map(|result| { + result.and_then(|(_, id)| { + let block = self.block(&id)?; + + Ok(block) + }) + }) + } + + pub fn consensus(&self, id: &BlockId) -> StorageResult { + self.0 + .as_ref() + .storage::() + .get(id) + .map(|c| c.map(|c| c.into_owned()))? + .ok_or(not_found!(SealedBlockConsensus)) + } +} diff --git a/crates/fuel-core/src/query/chain.rs b/crates/fuel-core/src/query/chain.rs new file mode 100644 index 00000000000..f839ad9e6a2 --- /dev/null +++ b/crates/fuel-core/src/query/chain.rs @@ -0,0 +1,10 @@ +use crate::fuel_core_graphql_api::service::Database; +use fuel_core_storage::Result as StorageResult; + +pub struct ChainQueryContext<'a>(pub &'a Database); + +impl ChainQueryContext<'_> { + pub fn name(&self) -> StorageResult { + self.0.chain_name() + } +} diff --git a/crates/fuel-core/src/query/coin.rs b/crates/fuel-core/src/query/coin.rs new file mode 100644 index 00000000000..59492a405b0 --- /dev/null +++ b/crates/fuel-core/src/query/coin.rs @@ -0,0 +1,50 @@ +use crate::{ + graphql_api::service::Database, + state::IterDirection, +}; +use fuel_core_storage::{ + not_found, + tables::Coins, + Result as StorageResult, + StorageAsRef, +}; +use fuel_core_types::{ + entities::coin::Coin, + fuel_tx::UtxoId, + fuel_types::Address, +}; + +pub struct CoinQueryContext<'a>(pub &'a Database); + +impl<'a> CoinQueryContext<'a> { + pub fn coin(&self, utxo_id: UtxoId) -> StorageResult { + let coin = self + .0 + .as_ref() + .storage::() + .get(&utxo_id)? + .ok_or(not_found!(Coins))? + .into_owned(); + + Ok(coin.uncompress(utxo_id)) + } + + pub fn owned_coins_ids( + &self, + owner: &Address, + start_coin: Option, + direction: IterDirection, + ) -> impl Iterator> + 'a { + self.0.owned_coins_ids(owner, start_coin, direction) + } + + pub fn owned_coins( + &self, + owner: &Address, + start_coin: Option, + direction: IterDirection, + ) -> impl Iterator> + '_ { + self.owned_coins_ids(owner, start_coin, direction) + .map(|res| res.and_then(|id| self.coin(id))) + } +} diff --git a/crates/fuel-core/src/query/contract.rs b/crates/fuel-core/src/query/contract.rs new file mode 100644 index 00000000000..17ea7bd4179 --- /dev/null +++ b/crates/fuel-core/src/query/contract.rs @@ -0,0 +1,93 @@ +use crate::{ + graphql_api::service::Database, + state::IterDirection, +}; +use fuel_core_storage::{ + not_found, + tables::{ + ContractsAssets, + ContractsInfo, + ContractsRawCode, + }, + Result as StorageResult, + StorageAsRef, +}; +use fuel_core_types::{ + fuel_types::{ + AssetId, + ContractId, + }, + fuel_vm::Salt, + services::graphql_api::ContractBalance, +}; + +pub struct ContractQueryContext<'a>(pub &'a Database); + +impl ContractQueryContext<'_> { + pub fn contract_id(&self, id: ContractId) -> StorageResult { + let contract_exists = self + .0 + .as_ref() + .storage::() + .contains_key(&id)?; + if contract_exists { + Ok(id) + } else { + Err(not_found!(ContractsRawCode)) + } + } + + pub fn contract_bytecode(&self, id: ContractId) -> StorageResult> { + let contract = self + .0 + .as_ref() + .storage::() + .get(&id)? + .ok_or(not_found!(ContractsRawCode))? + .into_owned(); + + Ok(contract.into()) + } + + pub fn contract_salt(&self, id: ContractId) -> StorageResult { + let (salt, _) = self + .0 + .as_ref() + .storage::() + .get(&id)? + .ok_or(not_found!(ContractsInfo))? + .into_owned(); + + Ok(salt) + } + + pub fn contract_balance( + &self, + contract_id: ContractId, + asset_id: AssetId, + ) -> StorageResult { + let amount = self + .0 + .as_ref() + .storage::() + .get(&(&contract_id, &asset_id))? + .ok_or(not_found!(ContractsAssets))? + .into_owned(); + + Ok(ContractBalance { + owner: contract_id, + amount, + asset_id, + }) + } + + pub fn contract_balances( + &self, + contract_id: ContractId, + start_asset: Option, + direction: IterDirection, + ) -> impl Iterator> + '_ { + self.0 + .contract_balances(contract_id, start_asset, direction) + } +} diff --git a/crates/fuel-core/src/query/message_proof.rs b/crates/fuel-core/src/query/message_proof.rs index fee634a097e..dd208fecb0e 100644 --- a/crates/fuel-core/src/query/message_proof.rs +++ b/crates/fuel-core/src/query/message_proof.rs @@ -1,7 +1,31 @@ -use fuel_core_storage::Result as StorageResult; +use crate::{ + fuel_core_graphql_api::{ + service::Database, + IntoApiResult, + }, + query::{ + BlockQueryContext, + TransactionQueryContext, + }, + state::IterDirection, +}; +use fuel_core_storage::{ + not_found, + tables::Messages, + Error as StorageError, + Result as StorageResult, + StorageAsRef, +}; use fuel_core_types::{ - blockchain::block::CompressedBlock, - entities::message::MessageProof, + blockchain::{ + block::CompressedBlock, + consensus::Consensus, + primitives::BlockId, + }, + entities::message::{ + Message, + MessageProof, + }, fuel_crypto::Signature, fuel_merkle, fuel_tx::{ @@ -9,41 +33,119 @@ use fuel_core_types::{ Output, Receipt, Transaction, + TxId, }, fuel_types::{ + Address, Bytes32, MessageId, }, services::txpool::TransactionStatus, }; +use itertools::Itertools; +use std::borrow::Cow; #[cfg(test)] mod test; +pub struct MessageQueryContext<'a>(pub &'a Database); + +impl<'a> MessageQueryContext<'a> { + pub fn message(&self, message_id: &MessageId) -> StorageResult { + self.0 + .as_ref() + .storage::() + .get(message_id)? + .ok_or(not_found!(Messages)) + .map(Cow::into_owned) + } + + pub fn owned_message_ids( + &self, + owner: &Address, + start_message_id: Option, + direction: IterDirection, + ) -> impl Iterator> + 'a { + self.0.owned_message_ids(owner, start_message_id, direction) + } + + pub fn owned_messages( + &self, + owner: &Address, + start_message_id: Option, + direction: IterDirection, + ) -> impl Iterator> + '_ { + self.owned_message_ids(owner, start_message_id, direction) + .map(|result| result.and_then(|id| self.message(&id))) + } + + pub fn all_messages( + &self, + start_message_id: Option, + direction: IterDirection, + ) -> impl Iterator> + 'a { + self.0.all_messages(start_message_id, direction) + } +} + #[cfg_attr(test, mockall::automock)] /// Trait that specifies all the data required by the output message query. pub trait MessageProofData { /// Return all receipts in the given transaction. - fn receipts(&self, transaction_id: &Bytes32) -> StorageResult>; + fn receipts(&self, transaction_id: &TxId) -> StorageResult>; /// Get the transaction. - fn transaction(&self, transaction_id: &Bytes32) - -> StorageResult>; + fn transaction(&self, transaction_id: &TxId) -> StorageResult; /// Get the status of a transaction. fn transaction_status( &self, - transaction_id: &Bytes32, - ) -> StorageResult>; + transaction_id: &TxId, + ) -> StorageResult; /// Get all transactions on a block. - fn transactions_on_block(&self, block_id: &Bytes32) -> StorageResult>; + fn transactions_on_block(&self, block_id: &BlockId) -> StorageResult>; /// Get the signature of a fuel block. - fn signature(&self, block_id: &Bytes32) -> StorageResult>; + fn signature(&self, block_id: &BlockId) -> StorageResult; /// Get the fuel block. - fn block(&self, block_id: &Bytes32) -> StorageResult>; + fn block(&self, block_id: &BlockId) -> StorageResult; +} + +impl MessageProofData for MessageQueryContext<'_> { + fn receipts(&self, transaction_id: &TxId) -> StorageResult> { + TransactionQueryContext(self.0).receipts(transaction_id) + } + + fn transaction(&self, transaction_id: &TxId) -> StorageResult { + TransactionQueryContext(self.0).transaction(transaction_id) + } + + fn transaction_status( + &self, + transaction_id: &TxId, + ) -> StorageResult { + TransactionQueryContext(self.0).status(transaction_id) + } + + fn transactions_on_block(&self, block_id: &BlockId) -> StorageResult> { + self.block(block_id).map(|block| block.into_inner().1) + } + + fn signature(&self, block_id: &BlockId) -> StorageResult { + let consensus = BlockQueryContext(self.0).consensus(block_id)?; + match consensus { + // TODO: https://github.com/FuelLabs/fuel-core/issues/816 + Consensus::Genesis(_) => Ok(Default::default()), + Consensus::PoA(c) => Ok(c.signature), + } + } + + fn block(&self, block_id: &BlockId) -> StorageResult { + BlockQueryContext(self.0).block(block_id) + } } /// Generate an output proof. -pub async fn message_proof( - data: &(dyn MessageProofData + Send + Sync), +// TODO: Do we want to return `Option` here? +pub fn message_proof( + data: &Data, transaction_id: Bytes32, message_id: MessageId, ) -> StorageResult> { @@ -72,56 +174,46 @@ pub async fn message_proof( }; // Get the block id from the transaction status if it's ready. - let block_id = data - .transaction_status(&transaction_id)? - .and_then(|status| match status { - TransactionStatus::Failed { block_id, .. } - | TransactionStatus::Success { block_id, .. } => Some(block_id), - TransactionStatus::Submitted { .. } - | TransactionStatus::SqueezedOut { .. } => None, - }); - - // Exit if the status doesn't exist or is not ready. - let block_id = match block_id { - Some(b) => b, - None => return Ok(None), + let block_id = match data + .transaction_status(&transaction_id) + .into_api_result::()? + { + Some(TransactionStatus::Success { block_id, .. }) => block_id, + _ => return Ok(None), }; // Get the message ids in the same order as the transactions. - let leaves = data - .transactions_on_block(&block_id.into())? + let leaves: Vec> = data + .transactions_on_block(&block_id)? .into_iter() - // Filter out transactions that contain no messages - // and get the receipts for the rest. - .filter_map(|transaction_id| match data.transaction(&transaction_id) { - Ok(transaction) => transaction.as_ref().map(|tx| match tx { - Transaction::Script(script) => script.outputs(), - Transaction::Create(create) => create.outputs(), - Transaction::Mint(mint) => mint.outputs(), - })? - .iter() - .any(Output::is_message) - .then(|| data.receipts(&transaction_id)), - Err(e) => Some(Err(e)), + .filter_map(|id| { + // Filter out transactions that contain no messages + // and get the receipts for the rest. + let result = data.transaction(&id).and_then(|tx| { + let outputs = match &tx { + Transaction::Script(script) => script.outputs(), + Transaction::Create(create) => create.outputs(), + Transaction::Mint(mint) => mint.outputs(), + }; + outputs + .iter() + .any(Output::is_message) + .then(|| data.receipts(&id)) + .transpose() + }); + result.transpose() }) + .filter_map(|result| result.into_api_result::<_, StorageError>().transpose()) + .try_collect()?; + + let leaves = leaves.into_iter() // Flatten the receipts after filtering on output messages // and mapping to message ids. - .flat_map(|receipts| match receipts { - Ok(receipts) => { - let iter: Box> = - Box::new(receipts.into_iter().filter_map(|r| match r { - Receipt::MessageOut { message_id, .. } => Some(Ok(message_id)), - _ => None, - })); - iter - } - Err(e) => { - // Boxing is required because of the different iterator types - // returned depending on the error case. - let iter: Box> = Box::new(std::iter::once(Err(e))); - iter - } - }).enumerate(); + .flat_map(|receipts| + receipts.into_iter().filter_map(|r| match r { + Receipt::MessageOut { message_id, .. } => Some(message_id), + _ => None, + })).enumerate(); // Build the merkle proof from the above iterator. let mut tree = fuel_merkle::binary::in_memory::MerkleTree::new(); @@ -129,8 +221,6 @@ pub async fn message_proof( let mut proof_index = None; for (index, id) in leaves { - let id = id?; - // Check id this is the message. if message_id == id { // Save the index of this message to use as the proof index. @@ -151,13 +241,19 @@ pub async fn message_proof( }; // Get the signature. - let signature = match data.signature(&block_id.into())? { + let signature = match data + .signature(&block_id) + .into_api_result::<_, StorageError>()? + { Some(t) => t, None => return Ok(None), }; // Get the fuel block. - let header = match data.block(&block_id.into())? { + let header = match data + .block(&block_id) + .into_api_result::()? + { Some(t) => t.into_inner().0, None => return Ok(None), }; diff --git a/crates/fuel-core/src/query/message_proof/test.rs b/crates/fuel-core/src/query/message_proof/test.rs index eea4441edce..a0283a2701b 100644 --- a/crates/fuel-core/src/query/message_proof/test.rs +++ b/crates/fuel-core/src/query/message_proof/test.rs @@ -113,29 +113,35 @@ async fn can_build_message_proof() { data.expect_transaction_status() .with(eq(transaction_id)) .returning(|_| { - Ok(Some(TransactionStatus::Success { + Ok(TransactionStatus::Success { block_id: Default::default(), time: Tai64::UNIX_EPOCH, result: None, - })) + }) }); data.expect_transactions_on_block() .once() - .with(eq(Bytes32::default())) + .with(eq(BlockId::default())) .returning(|_| Ok(TXNS.to_vec())); data.expect_transaction().returning(move |txn_id| { - Ok(TXNS.iter().find(|t| *t == txn_id).map(|_| { - let mut txn = Script::default(); - txn.outputs_mut().extend(out.get(txn_id).unwrap()); - txn.into() - })) + let tx = TXNS + .iter() + .find(|t| *t == txn_id) + .map(|_| { + let mut txn = Script::default(); + txn.outputs_mut().extend(out.get(txn_id).unwrap()); + txn.into() + }) + .ok_or(not_found!("Transaction in `TXNS`"))?; + + Ok(tx) }); data.expect_signature() .once() - .with(eq(Bytes32::default())) - .returning(|_| Ok(Some(Signature::default()))); + .with(eq(BlockId::default())) + .returning(|_| Ok(Signature::default())); let header = PartialBlockHeader { application: ApplicationHeader { @@ -152,19 +158,18 @@ async fn can_build_message_proof() { }; data.expect_block() .once() - .with(eq(Bytes32::default())) + .with(eq(BlockId::default())) .returning({ let header = header.clone(); let message_ids = message_ids.clone(); move |_| { let header = header.clone().generate(&[vec![]], &message_ids); let transactions = TXNS.to_vec(); - Ok(Some(CompressedBlock::test(header, transactions))) + Ok(CompressedBlock::test(header, transactions)) } }); let p = message_proof(&data, transaction_id, message_id) - .await .unwrap() .unwrap(); assert_eq!(p.message_id(), message_id); diff --git a/crates/fuel-core/src/query/subscriptions.rs b/crates/fuel-core/src/query/subscriptions.rs index 950b78ed12b..79eec553808 100644 --- a/crates/fuel-core/src/query/subscriptions.rs +++ b/crates/fuel-core/src/query/subscriptions.rs @@ -2,6 +2,7 @@ use crate::schema::tx::types::{ SqueezedOutStatus, TransactionStatus, }; +use fuel_core_storage::Result as StorageResult; use fuel_core_txpool::service::TxUpdate; use fuel_core_types::fuel_types::Bytes32; use futures::{ @@ -21,16 +22,23 @@ pub(crate) trait TxnStatusChangeState { /// Return the transaction status from the tx pool and database. async fn get_tx_status( &self, - id: fuel_core_types::fuel_types::Bytes32, - ) -> anyhow::Result>; + id: Bytes32, + ) -> StorageResult>; } -pub(crate) async fn transaction_status_change( - state: Box, - stream: BoxStream<'static, Result>, +pub(crate) async fn transaction_status_change<'a, State>( + state: State, + stream: BoxStream<'a, Result>, transaction_id: Bytes32, -) -> impl Stream> { - let check_db_first = state.get_tx_status(transaction_id).await.transpose(); +) -> impl Stream> + 'a +where + State: TxnStatusChangeState + Send + Sync + 'a, +{ + let check_db_first = state + .get_tx_status(transaction_id) + .await + .map_err(Into::into) + .transpose(); let (close, mut closed) = tokio::sync::oneshot::channel(); let mut close = Some(close); @@ -71,7 +79,7 @@ pub(crate) async fn transaction_status_change( // is to restart the stream. Ok(None) => None, // Got an error so return it. - Err(e) => Some((Some(Err(e)), (state, stream))), + Err(e) => Some((Some(Err(e.into())), (state, stream))), } } } @@ -80,7 +88,11 @@ pub(crate) async fn transaction_status_change( // Buffer filled up before this stream was polled. Some(Err(BroadcastStreamRecvError::Lagged(_))) => { // Check the db incase a missed status was our transaction. - let status = state.get_tx_status(transaction_id).await.transpose(); + let status = state + .get_tx_status(transaction_id) + .await + .map_err(Into::into) + .transpose(); Some((status, (state, stream))) } // Channel is closed. diff --git a/crates/fuel-core/src/query/subscriptions/test.rs b/crates/fuel-core/src/query/subscriptions/test.rs index a262e64772a..c63c33b7307 100644 --- a/crates/fuel-core/src/query/subscriptions/test.rs +++ b/crates/fuel-core/src/query/subscriptions/test.rs @@ -13,7 +13,7 @@ use test_case::test_case; struct Input where - I: Iterator>> + Send + 'static, + I: Iterator>> + Send + 'static, { requested_id: Bytes32, status_updates: Vec>, @@ -41,34 +41,34 @@ fn txn_squeezed(tx_id: Bytes32) -> TxUpdate { fn db_always_some( mut f: impl FnMut() -> TransactionStatus, -) -> impl Iterator>> { +) -> impl Iterator>> { std::iter::repeat_with(move || Ok(Some(f()))) } -fn db_always_none() -> impl Iterator>> { +fn db_always_none() -> impl Iterator>> { std::iter::repeat_with(|| Ok(None)) } fn db_always_error( mut f: impl FnMut() -> anyhow::Error, -) -> impl Iterator>> { - std::iter::repeat_with(move || Err(f())) +) -> impl Iterator>> { + std::iter::repeat_with(move || Err(f().into())) } fn db_some( f: Vec, -) -> impl Iterator>> { +) -> impl Iterator>> { f.into_iter().map(|t| Ok(Some(t))) } -fn db_none(n: usize) -> impl Iterator>> { +fn db_none(n: usize) -> impl Iterator>> { (0..n).map(|_| Ok(None)) } fn db_error( f: Vec, -) -> impl Iterator>> { - f.into_iter().map(Err) +) -> impl Iterator>> { + f.into_iter().map(|e| Err(e.into())) } fn submitted() -> TransactionStatus { @@ -227,7 +227,7 @@ fn squeezed() -> TransactionStatus { #[tokio::test] async fn create_tx_status_change_stream(input: Input) -> Expected where - I: Iterator>> + Send + 'static, + I: Iterator>> + Send + 'static, { let Input { requested_id: transaction_id, @@ -238,7 +238,6 @@ where state .expect_get_tx_status() .returning(move |_| db_statuses.next().unwrap().map(|t| t.map(|t| t.into()))); - let state = Box::new(state); let ids = status_updates.to_vec(); let reached_end = Arc::new(AtomicBool::new(false)); let re = reached_end.clone(); diff --git a/crates/fuel-core/src/query/tx.rs b/crates/fuel-core/src/query/tx.rs new file mode 100644 index 00000000000..90e521c90fa --- /dev/null +++ b/crates/fuel-core/src/query/tx.rs @@ -0,0 +1,64 @@ +use crate::{ + fuel_core_graphql_api::service::Database, + state::IterDirection, +}; +use fuel_core_storage::{ + not_found, + tables::{ + Receipts, + Transactions, + }, + Result as StorageResult, + StorageAsRef, +}; +use fuel_core_txpool::types::TxId; +use fuel_core_types::{ + fuel_tx::{ + Receipt, + Transaction, + TxPointer, + }, + fuel_types::Address, + services::txpool::TransactionStatus, +}; + +pub struct TransactionQueryContext<'a>(pub &'a Database); + +impl TransactionQueryContext<'_> { + pub fn transaction(&self, tx_id: &TxId) -> StorageResult { + self.0 + .as_ref() + .storage::() + .get(tx_id) + .and_then(|v| v.ok_or(not_found!(Transactions)).map(|tx| tx.into_owned())) + } + + pub fn receipts(&self, tx_id: &TxId) -> StorageResult> { + self.0 + .as_ref() + .storage::() + .get(tx_id) + .and_then(|v| v.ok_or(not_found!(Transactions)).map(|tx| tx.into_owned())) + } + + pub fn status(&self, tx_id: &TxId) -> StorageResult { + self.0.tx_status(tx_id) + } + + pub fn owned_transactions<'a>( + &'a self, + owner: &Address, + start: Option, + direction: IterDirection, + ) -> impl Iterator> + 'a { + self.0 + .owned_transactions_ids(owner, start, direction) + .map(|result| { + result.and_then(|(tx_pointer, tx_id)| { + let tx = self.transaction(&tx_id)?; + + Ok((tx_pointer, tx)) + }) + }) + } +} diff --git a/crates/fuel-core/src/resource_query.rs b/crates/fuel-core/src/resource_query.rs index fd499400c39..fb1bb58d9ae 100644 --- a/crates/fuel-core/src/resource_query.rs +++ b/crates/fuel-core/src/resource_query.rs @@ -1,19 +1,17 @@ -use crate::database::{ - resource::{ +use crate::{ + fuel_core_graphql_api::service::Database, + query::asset_query::{ AssetQuery, AssetSpendTarget, Exclude, - Resource, - ResourceId, }, - Database, }; use core::mem::swap; use fuel_core_storage::Error as StorageError; use fuel_core_types::{ - entities::{ - coin::Coin, - message::Message, + entities::resource::{ + Resource, + ResourceId, }, fuel_types::{ Address, @@ -115,9 +113,7 @@ impl SpendQuery { /// Returns the biggest inputs of the `owner` to satisfy the required `target` of the asset. The /// number of inputs for each asset can't exceed `max_inputs`, otherwise throw an error that query /// can't be satisfied. -pub fn largest_first( - query: &AssetQuery, -) -> Result>, ResourceQueryError> { +pub fn largest_first(query: &AssetQuery) -> Result, ResourceQueryError> { let mut inputs: Vec<_> = query.unspent_resources().try_collect()?; inputs.sort_by_key(|resource| Reverse(*resource.amount())); @@ -137,7 +133,7 @@ pub fn largest_first( // Add to list collected_amount = collected_amount.saturating_add(*resource.amount()); - resources.push(resource.into_owned()); + resources.push(resource); } if collected_amount < query.asset.target { @@ -154,7 +150,7 @@ pub fn largest_first( pub fn random_improve( db: &Database, spend_query: &SpendQuery, -) -> Result>>, ResourceQueryError> { +) -> Result>, ResourceQueryError> { let mut resources_per_asset = vec![]; for query in spend_query.asset_queries(db) { @@ -188,7 +184,7 @@ pub fn random_improve( // Add to list collected_amount = collected_amount.saturating_add(*resource.amount()); - resources.push(resource.into_owned()); + resources.push(resource); } // Fallback to largest_first if we can't fit more coins @@ -210,30 +206,53 @@ impl From for ResourceQueryError { #[cfg(test)] mod tests { - use crate::database::Database; + use crate::{ + database::Database, + fuel_core_graphql_api::service::Database as ServiceDatabase, + query::{ + asset_query::{ + AssetQuery, + AssetSpendTarget, + }, + CoinQueryContext, + MessageQueryContext, + }, + resource_query::{ + largest_first, + random_improve, + ResourceQueryError, + SpendQuery, + }, + state::IterDirection, + }; use assert_matches::assert_matches; use fuel_core_storage::{ tables::{ Coins, Messages, }, - StorageAsMut, - StorageAsRef, + StorageMutate, }; use fuel_core_types::{ blockchain::primitives::DaBlockHeight, - entities::coin::CoinStatus, + entities::{ + coin::{ + Coin, + CoinStatus, + CompressedCoin, + }, + message::Message, + }, fuel_asm::Word, fuel_tx::*, }; use itertools::Itertools; - - use super::*; + use std::cmp::Reverse; fn setup_coins() -> (Address, [AssetId; 2], TestDatabase) { let owner = Address::default(); let asset_ids = [AssetId::new([1u8; 32]), AssetId::new([2u8; 32])]; - let mut db = TestDatabase::default(); + let mut db = TestDatabase::new(); (0..5usize).for_each(|i| { db.make_coin(owner, (i + 1) as Word, asset_ids[0]); db.make_coin(owner, (i + 1) as Word, asset_ids[1]); @@ -245,7 +264,7 @@ mod tests { fn setup_messages() -> (Address, AssetId, TestDatabase) { let owner = Address::default(); let asset_id = AssetId::BASE; - let mut db = TestDatabase::default(); + let mut db = TestDatabase::new(); (0..5usize).for_each(|i| { db.make_message(owner, (i + 1) as Word); }); @@ -256,7 +275,7 @@ mod tests { fn setup_coins_and_messages() -> (Address, [AssetId; 2], TestDatabase) { let owner = Address::default(); let asset_ids = [AssetId::BASE, AssetId::new([1u8; 32])]; - let mut db = TestDatabase::default(); + let mut db = TestDatabase::new(); // 2 coins and 3 messages (0..2usize).for_each(|i| { db.make_coin(owner, (i + 1) as Word, asset_ids[0]); @@ -278,7 +297,7 @@ mod tests { fn query( spend_query: &[AssetSpendTarget], owner: &Address, - db: &Database, + db: &ServiceDatabase, ) -> Result>, ResourceQueryError> { let result: Vec<_> = spend_query .iter() @@ -306,7 +325,7 @@ mod tests { let resources = query( &[AssetSpendTarget::new(asset_id, target, u64::MAX)], &owner, - db.as_ref(), + &db.service_database(), ); // Transform result for convenience @@ -364,7 +383,7 @@ mod tests { let resources = query( &[AssetSpendTarget::new(asset_id, 6, 1)], &owner, - db.as_ref(), + &db.service_database(), ); assert_matches!(resources, Err(ResourceQueryError::MaxResourcesReached)); } @@ -395,7 +414,7 @@ mod tests { AssetSpendTarget::new(asset_ids[1], 6, u64::MAX), ], &owner, - db.as_ref(), + &db.service_database(), ); assert_matches!(resources, Ok(resources) if resources == vec![ @@ -423,7 +442,7 @@ mod tests { query_per_asset: Vec, owner: Address, asset_ids: &[AssetId], - db: &Database, + db: &ServiceDatabase, ) -> Result, ResourceQueryError> { let coins = random_improve(db, &SpendQuery::new(owner, &query_per_asset, None)?); @@ -455,7 +474,7 @@ mod tests { vec![AssetSpendTarget::new(asset_id, amount, u64::MAX)], owner, asset_ids, - db.as_ref(), + &db.service_database(), ); // Transform result for convenience @@ -506,7 +525,7 @@ mod tests { )], owner, asset_ids, - db.as_ref(), + &db.service_database(), ); assert_matches!(coins, Err(ResourceQueryError::MaxResourcesReached)); } @@ -547,7 +566,7 @@ mod tests { ], owner, asset_ids, - db.as_ref(), + &db.service_database(), ); assert_matches!(coins, Ok(ref coins) if coins.len() <= 6); let coins = coins.unwrap(); @@ -583,6 +602,7 @@ mod tests { mod exclusion { use super::*; + use fuel_core_types::entities::resource::ResourceId; fn exclusion_assert( owner: Address, @@ -596,7 +616,7 @@ mod tests { excluded_ids: Vec| -> Result, ResourceQueryError> { let coins = random_improve( - db.as_ref(), + &db.service_database(), &SpendQuery::new(owner, &query_per_asset, Some(excluded_ids))?, ); @@ -680,8 +700,8 @@ mod tests { let excluded_ids = db .owned_coins(&owner) .into_iter() - .filter(|(_, coin)| coin.amount == 5) - .map(|(utxo_id, _)| ResourceId::Utxo(utxo_id)) + .filter(|coin| coin.amount == 5) + .map(|coin| ResourceId::Utxo(coin.utxo_id)) .collect_vec(); exclusion_assert(owner, &asset_ids, db, excluded_ids); @@ -776,7 +796,7 @@ mod tests { } = case; let owner = Address::default(); let asset_ids = vec![AssetId::BASE]; - let mut db = TestDatabase::default(); + let mut db = TestDatabase::new(); for amount in db_amount { match resource_type { ResourceType::Coin => { @@ -789,7 +809,7 @@ mod tests { } let coins = random_improve( - db.as_ref(), + &db.service_database(), &SpendQuery::new( owner, &[AssetSpendTarget { @@ -812,25 +832,38 @@ mod tests { coin_result } - #[derive(Default)] pub struct TestDatabase { database: Database, last_coin_index: u64, last_message_index: u64, } + impl TestDatabase { + fn new() -> Self { + Self { + database: Default::default(), + last_coin_index: Default::default(), + last_message_index: Default::default(), + } + } + + fn service_database(&self) -> ServiceDatabase { + Box::new(self.database.clone()) + } + } + impl TestDatabase { pub fn make_coin( &mut self, owner: Address, amount: Word, asset_id: AssetId, - ) -> (UtxoId, Coin) { + ) -> Coin { let index = self.last_coin_index; self.last_coin_index += 1; let id = UtxoId::new(Bytes32::from([0u8; 32]), index.try_into().unwrap()); - let coin = Coin { + let coin = CompressedCoin { owner, amount, asset_id, @@ -840,16 +873,12 @@ mod tests { }; let db = &mut self.database; - db.storage::().insert(&id, &coin).unwrap(); + StorageMutate::::insert(db, &id, &coin).unwrap(); - (id, coin) + coin.uncompress(id) } - pub fn make_message( - &mut self, - owner: Address, - amount: Word, - ) -> (MessageId, Message) { + pub fn make_message(&mut self, owner: Address, amount: Word) -> Message { let nonce = self.last_message_index; self.last_message_index += 1; @@ -864,49 +893,29 @@ mod tests { }; let db = &mut self.database; - db.storage::() - .insert(&message.id(), &message) - .unwrap(); + StorageMutate::::insert(db, &message.id(), &message).unwrap(); - (message.id(), message) + message } - pub fn owned_coins(&self, owner: &Address) -> Vec<(UtxoId, Coin)> { - self.database - .owned_coins_ids(owner, None, None) - .map(|res| { - res.map(|id| { - let coin = - self.database.storage::().get(&id).unwrap().unwrap(); - (id, coin.into_owned()) - }) - }) + pub fn owned_coins(&self, owner: &Address) -> Vec { + let db = self.service_database(); + let query = CoinQueryContext(&db); + query + .owned_coins_ids(owner, None, IterDirection::Forward) + .map(|res| res.map(|id| query.coin(id).unwrap())) .try_collect() .unwrap() } pub fn owned_messages(&self, owner: &Address) -> Vec { - self.database - .owned_message_ids(owner, None, None) - .map(|res| { - res.map(|id| { - let message = self - .database - .storage::() - .get(&id) - .unwrap() - .unwrap(); - message.into_owned() - }) - }) + let db = self.service_database(); + let query = MessageQueryContext(&db); + query + .owned_message_ids(owner, None, IterDirection::Forward) + .map(|res| res.map(|id| query.message(&id).unwrap())) .try_collect() .unwrap() } } - - impl AsRef for TestDatabase { - fn as_ref(&self) -> &Database { - self.database.as_ref() - } - } } diff --git a/crates/fuel-core/src/schema.rs b/crates/fuel-core/src/schema.rs index 5171cb58fa7..9a701a4b780 100644 --- a/crates/fuel-core/src/schema.rs +++ b/crates/fuel-core/src/schema.rs @@ -53,8 +53,9 @@ pub struct Mutation(dap::DapMutation, tx::TxMutation, block::BlockMutation); pub struct Subscription(tx::TxStatusSubscription); pub type CoreSchema = Schema; +pub type CoreSchemaBuilder = SchemaBuilder; -pub fn build_schema() -> SchemaBuilder { +pub fn build_schema() -> CoreSchemaBuilder { Schema::build_with_ignore_name_conflicts( Query::default(), Mutation::default(), diff --git a/crates/fuel-core/src/schema/balance.rs b/crates/fuel-core/src/schema/balance.rs index 5fe6c6a8adf..0257c662fce 100644 --- a/crates/fuel-core/src/schema/balance.rs +++ b/crates/fuel-core/src/schema/balance.rs @@ -1,16 +1,10 @@ use crate::{ - database::resource::{ - AssetQuery, - AssetSpendTarget, - AssetsQuery, - }, - fuel_core_graphql_api::service::Database, + query::BalanceQueryContext, schema::scalars::{ Address, AssetId, U64, }, - state::IterDirection, }; use async_graphql::{ connection::{ @@ -21,32 +15,22 @@ use async_graphql::{ InputObject, Object, }; -use fuel_core_storage::Result as StorageResult; -use fuel_core_types::fuel_types; -use itertools::Itertools; -use std::{ - cmp::Ordering, - collections::HashMap, -}; +use fuel_core_types::services::graphql_api; -pub struct Balance { - owner: fuel_types::Address, - amount: u64, - asset_id: fuel_types::AssetId, -} +pub struct Balance(graphql_api::AddressBalance); #[Object] impl Balance { async fn owner(&self) -> Address { - self.owner.into() + self.0.owner.into() } async fn amount(&self) -> U64 { - self.amount.into() + self.0.amount.into() } async fn asset_id(&self) -> AssetId { - self.asset_id.into() + self.0.asset_id.into() } } @@ -67,34 +51,8 @@ impl BalanceQuery { #[graphql(desc = "address of the owner")] owner: Address, #[graphql(desc = "asset_id of the coin")] asset_id: AssetId, ) -> async_graphql::Result { - let db = ctx.data_unchecked::(); - let owner = owner.into(); - let asset_id = asset_id.into(); - - let balance = AssetQuery::new( - &owner, - &AssetSpendTarget::new(asset_id, u64::MAX, u64::MAX), - None, - db, - ) - .unspent_resources() - .map(|res| res.map(|resource| *resource.amount())) - .try_fold( - Balance { - owner, - amount: 0u64, - asset_id, - }, - |mut balance, res| -> StorageResult<_> { - let amount = res?; - - // Increase the balance - balance.amount += amount; - - Ok(balance) - }, - )?; - + let data = BalanceQueryContext(ctx.data_unchecked()); + let balance = data.balance(owner.0, asset_id.0)?.into(); Ok(balance) } @@ -110,43 +68,19 @@ impl BalanceQuery { before: Option, ) -> async_graphql::Result> { - let db = ctx.data_unchecked::(); + let query = BalanceQueryContext(ctx.data_unchecked()); crate::schema::query_pagination(after, before, first, last, |_, direction| { let owner = filter.owner.into(); - - let mut amounts_per_asset = HashMap::new(); - - for resource in AssetsQuery::new(&owner, None, None, db).unspent_resources() { - let resource = resource?; - *amounts_per_asset.entry(*resource.asset_id()).or_default() += - resource.amount(); - } - - let mut balances = amounts_per_asset - .into_iter() - .map(|(asset_id, amount)| Balance { - owner, - amount, - asset_id, - }) - .collect_vec(); - balances.sort_by(|l, r| { - if l.asset_id < r.asset_id { - Ordering::Less - } else { - Ordering::Greater - } - }); - if direction == IterDirection::Reverse { - balances.reverse(); - } - - let balances = balances - .into_iter() - .map(|balance| Ok((balance.asset_id.into(), balance))); - - Ok(balances) + Ok(query.balances(owner, direction).map(|result| { + result.map(|balance| (balance.asset_id.into(), balance.into())) + })) }) .await } } + +impl From for Balance { + fn from(balance: graphql_api::AddressBalance) -> Self { + Balance(balance) + } +} diff --git a/crates/fuel-core/src/schema/block.rs b/crates/fuel-core/src/schema/block.rs index f8586ef5664..f078fc82e4b 100644 --- a/crates/fuel-core/src/schema/block.rs +++ b/crates/fuel-core/src/schema/block.rs @@ -4,11 +4,13 @@ use super::scalars::{ }; use crate::{ fuel_core_graphql_api::{ - service::{ - Database, - Executor, - }, + service::Executor, Config as GraphQLConfig, + IntoApiResult, + }, + query::{ + BlockQueryContext, + TransactionQueryContext, }, schema::{ scalars::{ @@ -35,14 +37,8 @@ use async_graphql::{ use fuel_core_poa::service::seal_block; use fuel_core_producer::ports::Executor as ExecutorTrait; use fuel_core_storage::{ - not_found, - tables::{ - FuelBlocks, - SealedBlockConsensus, - Transactions, - }, + iter::IntoBoxedIter, Result as StorageResult, - StorageAsRef, }; use fuel_core_types::{ blockchain::{ @@ -64,13 +60,8 @@ use fuel_core_types::{ }, tai64::Tai64, }; -use itertools::Itertools; -use std::convert::TryInto; -pub struct Block { - pub(crate) header: Header, - pub(crate) transactions: Vec, -} +pub struct Block(pub(crate) CompressedBlock); pub struct Header(pub(crate) BlockHeader); @@ -103,40 +94,33 @@ pub struct PoAConsensus { #[Object] impl Block { async fn id(&self) -> BlockId { - let bytes: fuel_core_types::fuel_types::Bytes32 = self.header.0.id().into(); + let bytes: fuel_types::Bytes32 = self.0.header().id().into(); bytes.into() } - async fn header(&self) -> &Header { - &self.header + async fn header(&self) -> Header { + self.0.header().clone().into() } async fn consensus(&self, ctx: &Context<'_>) -> async_graphql::Result { - let db = ctx.data_unchecked::().clone(); - let id = self.header.0.id().into(); - let consensus = db - .storage::() - .get(&id) - .map(|c| c.map(|c| c.into_owned().into()))? - .ok_or(not_found!(SealedBlockConsensus))?; - - Ok(consensus) + let query = BlockQueryContext(ctx.data_unchecked()); + let id = self.0.header().id(); + let consensus = query.consensus(&id)?; + + Ok(consensus.into()) } async fn transactions( &self, ctx: &Context<'_>, ) -> async_graphql::Result> { - let db = ctx.data_unchecked::().clone(); - self.transactions + let query = TransactionQueryContext(ctx.data_unchecked()); + self.0 + .transactions() .iter() .map(|tx_id| { - Ok(Transaction( - db.storage::() - .get(tx_id) - .and_then(|v| v.ok_or(not_found!(Transactions)))? - .into_owned(), - )) + let tx = query.transaction(tx_id)?; + Ok(tx.into()) }) .collect() } @@ -215,29 +199,25 @@ impl BlockQuery { #[graphql(desc = "ID of the block")] id: Option, #[graphql(desc = "Height of the block")] height: Option, ) -> async_graphql::Result> { - let db = ctx.data_unchecked::(); + let data = BlockQueryContext(ctx.data_unchecked()); let id = match (id, height) { (Some(_), Some(_)) => { return Err(async_graphql::Error::new( "Can't provide both an id and a height", )) } - (Some(id), None) => id.into(), + (Some(id), None) => Ok(id.0.into()), (None, Some(height)) => { let height: u64 = height.into(); - db.get_block_id(height.try_into()?)? - .ok_or("Block height non-existent")? + let height: u32 = height.try_into()?; + data.block_id(height.into()) } (None, None) => { return Err(async_graphql::Error::new("Missing either id or height")) } }; - let block = db - .storage::() - .get(&id)? - .map(|b| Block::from(b.into_owned())); - Ok(block) + id.and_then(|id| data.block(&id)).into_api_result() } async fn blocks( @@ -248,9 +228,9 @@ impl BlockQuery { last: Option, before: Option, ) -> async_graphql::Result> { - let db = ctx.data_unchecked::(); + let db = BlockQueryContext(ctx.data_unchecked()); crate::schema::query_pagination(after, before, first, last, |start, direction| { - blocks_query(db, *start, direction) + Ok(blocks_query(&db, *start, direction)) }) .await } @@ -270,7 +250,7 @@ impl HeaderQuery { Ok(BlockQuery {} .block(ctx, id, height) .await? - .map(|b| b.header)) + .map(|b| b.0.header().clone().into())) } async fn headers( @@ -281,39 +261,31 @@ impl HeaderQuery { last: Option, before: Option, ) -> async_graphql::Result> { - let db = ctx.data_unchecked::(); + let db = BlockQueryContext(ctx.data_unchecked()); crate::schema::query_pagination(after, before, first, last, |start, direction| { - blocks_query(db, *start, direction) + Ok(blocks_query(&db, *start, direction)) }) .await } } -fn blocks_query( - db: &Database, +fn blocks_query<'a, T>( + query: &'a BlockQueryContext<'a>, start: Option, direction: IterDirection, -) -> StorageResult> + '_> +) -> impl Iterator> + 'a where T: async_graphql::OutputType, T: From, + T: 'a, { - // TODO: Remove `try_collect` - let blocks: Vec<_> = db - .all_block_ids(start.map(Into::into), Some(direction)) - .try_collect()?; - let blocks = blocks.into_iter().map(move |(height, id)| { - let value = db - .storage::() - .get(&id) - .transpose() - .ok_or(not_found!(FuelBlocks))?? - .into_owned(); - - Ok((height.to_usize(), value.into())) - }); - - Ok(blocks) + let blocks = query + .compressed_blocks(start.map(Into::into), direction) + .map(|result| { + result.map(|block| (block.header().height().as_usize(), block.into())) + }); + + blocks.into_boxed() } #[derive(Default)] @@ -335,7 +307,7 @@ impl BlockMutation { blocks_to_produce: U64, time: Option, ) -> async_graphql::Result { - let db = ctx.data_unchecked::(); + let query = BlockQueryContext(ctx.data_unchecked()); let executor = ctx.data_unchecked::().clone(); let config = ctx.data_unchecked::().clone(); @@ -346,10 +318,11 @@ impl BlockMutation { } // todo!("trigger block production manually"); - let block_time = get_time_closure(db, time, blocks_to_produce.0)?; + let latest_block = query.latest_block()?; + let block_time = get_time_closure(&latest_block, time, blocks_to_produce.0)?; for idx in 0..blocks_to_produce.0 { - let current_height = db.get_block_height()?.unwrap_or_default(); + let current_height = query.latest_block_height().unwrap_or_default(); let new_block_height = current_height + 1u32.into(); let block = PartialFuelBlock::new( @@ -381,19 +354,20 @@ impl BlockMutation { db_transaction.commit()?; } - db.get_block_height()? - .map(|new_height| Ok(new_height.into())) - .ok_or("Block height not found")? + query + .latest_block_height() + .map(Into::into) + .map_err(Into::into) } } fn get_time_closure( - db: &Database, + latest_block: &CompressedBlock, time_parameters: Option, blocks_to_produce: u64, ) -> anyhow::Result Tai64 + Send>> { if let Some(params) = time_parameters { - check_start_after_latest_block(db, params.start_time.0)?; + check_start_after_latest_block(latest_block, params.start_time.0)?; check_block_time_overflow(¶ms, blocks_to_produce)?; return Ok(Box::new(move |idx: u64| { @@ -408,17 +382,20 @@ fn get_time_closure( Ok(Box::new(|_| Tai64::now())) } -fn check_start_after_latest_block(db: &Database, start_time: u64) -> anyhow::Result<()> { - let current_height = db.get_block_height()?.unwrap_or_default(); +fn check_start_after_latest_block( + latest_block: &CompressedBlock, + start_time: u64, +) -> anyhow::Result<()> { + let current_height = *latest_block.header().height(); if current_height.as_usize() == 0 { return Ok(()) } - let latest_time = db.block_time(current_height.into())?.0; - if latest_time > start_time { + let latest_time = latest_block.header().time(); + if latest_time.0 > start_time { return Err(anyhow!( - "The start time must be set after the latest block time: {}", + "The start time must be set after the latest block time: {:?}", latest_time )) } @@ -445,11 +422,13 @@ fn check_block_time_overflow( impl From for Block { fn from(block: CompressedBlock) -> Self { - let (header, transactions) = block.into_inner(); - Block { - header: Header(header), - transactions, - } + Block(block) + } +} + +impl From for Header { + fn from(header: BlockHeader) -> Self { + Header(header) } } diff --git a/crates/fuel-core/src/schema/chain.rs b/crates/fuel-core/src/schema/chain.rs index 67a478204ed..3bc5cb1dbe4 100644 --- a/crates/fuel-core/src/schema/chain.rs +++ b/crates/fuel-core/src/schema/chain.rs @@ -3,6 +3,10 @@ use crate::{ service::Database, Config as GraphQLConfig, }, + query::{ + BlockQueryContext, + ChainQueryContext, + }, schema::{ block::Block, scalars::U64, @@ -12,15 +16,8 @@ use async_graphql::{ Context, Object, }; -use fuel_core_storage::{ - not_found, - tables::FuelBlocks, - StorageAsRef, -}; use fuel_core_types::fuel_tx; -pub const DEFAULT_NAME: &str = "Fuel.testnet"; - pub struct ChainInfo; pub struct ConsensusParameters(fuel_tx::ConsensusParameters); @@ -83,26 +80,23 @@ impl ConsensusParameters { #[Object] impl ChainInfo { async fn name(&self, ctx: &Context<'_>) -> async_graphql::Result { - let db = ctx.data_unchecked::().clone(); - let name = db - .get_chain_name()? - .unwrap_or_else(|| DEFAULT_NAME.to_string()); - Ok(name) + let data = ChainQueryContext(ctx.data_unchecked()); + Ok(data.name()?) } async fn latest_block(&self, ctx: &Context<'_>) -> async_graphql::Result { - let db = ctx.data_unchecked::().clone(); - let height = db.get_block_height()?.unwrap_or_default(); - let id = db.get_block_id(height)?.unwrap_or_default(); - let block = db - .storage::() - .get(&id)? - .ok_or(not_found!(FuelBlocks))?; - Ok(Block::from(block.into_owned())) - } - - async fn base_chain_height(&self) -> U64 { - 0.into() + let query = BlockQueryContext(ctx.data_unchecked()); + + let latest_block = query.latest_block()?.into(); + Ok(latest_block) + } + + async fn base_chain_height(&self, ctx: &Context<'_>) -> U64 { + let height = ctx + .data_unchecked::() + .base_chain_height() + .unwrap_or_default(); + height.0.into() } async fn peer_count(&self) -> u16 { diff --git a/crates/fuel-core/src/schema/coin.rs b/crates/fuel-core/src/schema/coin.rs index 898ca611bc5..8dde1eb9d9f 100644 --- a/crates/fuel-core/src/schema/coin.rs +++ b/crates/fuel-core/src/schema/coin.rs @@ -1,5 +1,6 @@ use crate::{ - fuel_core_graphql_api::service::Database, + fuel_core_graphql_api::IntoApiResult, + query::CoinQueryContext, schema::scalars::{ Address, AssetId, @@ -17,11 +18,6 @@ use async_graphql::{ InputObject, Object, }; -use fuel_core_storage::{ - not_found, - tables::Coins, - StorageAsRef, -}; use fuel_core_types::{ entities::coin::{ Coin as CoinModel, @@ -29,7 +25,6 @@ use fuel_core_types::{ }, fuel_tx, }; -use itertools::Itertools; #[derive(Enum, Copy, Clone, Eq, PartialEq)] #[graphql(remote = "CoinStatusModel")] @@ -38,36 +33,36 @@ pub enum CoinStatus { Spent, } -pub struct Coin(pub(crate) fuel_tx::UtxoId, pub(crate) CoinModel); +pub struct Coin(pub(crate) CoinModel); #[Object] impl Coin { async fn utxo_id(&self) -> UtxoId { - self.0.into() + self.0.utxo_id.into() } async fn owner(&self) -> Address { - self.1.owner.into() + self.0.owner.into() } async fn amount(&self) -> U64 { - self.1.amount.into() + self.0.amount.into() } async fn asset_id(&self) -> AssetId { - self.1.asset_id.into() + self.0.asset_id.into() } async fn maturity(&self) -> U64 { - self.1.maturity.into() + self.0.maturity.into() } async fn status(&self) -> CoinStatus { - self.1.status.into() + self.0.status.into() } async fn block_created(&self) -> U64 { - self.1.block_created.into() + self.0.block_created.into() } } @@ -90,13 +85,8 @@ impl CoinQuery { ctx: &Context<'_>, #[graphql(desc = "The ID of the coin")] utxo_id: UtxoId, ) -> async_graphql::Result> { - let utxo_id = utxo_id.0; - let db = ctx.data_unchecked::().clone(); - let block = db - .storage::() - .get(&utxo_id)? - .map(|coin| Coin(utxo_id, coin.into_owned())); - Ok(block) + let data = CoinQueryContext(ctx.data_unchecked()); + data.coin(utxo_id.0).into_api_result() } /// Gets all coins of some `owner` maybe filtered with by `asset_id` per page. @@ -110,39 +100,32 @@ impl CoinQuery { last: Option, before: Option, ) -> async_graphql::Result> { + let query = CoinQueryContext(ctx.data_unchecked()); crate::schema::query_pagination(after, before, first, last, |start, direction| { - let db = ctx.data_unchecked::(); let owner: fuel_tx::Address = filter.owner.into(); - let coin_ids: Vec<_> = db - .owned_coins_ids(&owner, (*start).map(Into::into), Some(direction)) - .try_collect()?; - let coins = coin_ids + let coins = query + .owned_coins(&owner, (*start).map(Into::into), direction) .into_iter() - .map(|id| { - let value = db - .storage::() - .get(&id) - .transpose() - .ok_or(not_found!(Coins))? - .map(|coin| Coin(id, coin.into_owned()))?; - let utxo_id: UtxoId = id.into(); - - Ok((utxo_id, value)) - }) .filter_map(|result| { - if let (Ok((_, coin)), Some(filter_asset_id)) = - (&result, &filter.asset_id) + if let (Ok(coin), Some(filter_asset_id)) = (&result, &filter.asset_id) { - if coin.1.asset_id != filter_asset_id.0 { + if coin.asset_id != filter_asset_id.0 { return None } } Some(result) - }); + }) + .map(|res| res.map(|coin| (coin.utxo_id.into(), coin.into()))); Ok(coins) }) .await } } + +impl From for Coin { + fn from(value: CoinModel) -> Self { + Coin(value) + } +} diff --git a/crates/fuel-core/src/schema/contract.rs b/crates/fuel-core/src/schema/contract.rs index b76f6066f56..3c0776dfb00 100644 --- a/crates/fuel-core/src/schema/contract.rs +++ b/crates/fuel-core/src/schema/contract.rs @@ -1,5 +1,6 @@ use crate::{ - fuel_core_graphql_api::service::Database, + fuel_core_graphql_api::IntoApiResult, + query::ContractQueryContext, schema::scalars::{ AssetId, ContractId, @@ -8,7 +9,6 @@ use crate::{ U64, }, }; -use anyhow::anyhow; use async_graphql::{ connection::{ Connection, @@ -18,16 +18,10 @@ use async_graphql::{ InputObject, Object, }; -use fuel_core_storage::{ - not_found, - tables::{ - ContractsAssets, - ContractsInfo, - ContractsRawCode, - }, - StorageAsRef, +use fuel_core_types::{ + fuel_types, + services::graphql_api, }; -use fuel_core_types::fuel_types; pub struct Contract(pub(crate) fuel_types::ContractId); @@ -44,27 +38,19 @@ impl Contract { } async fn bytecode(&self, ctx: &Context<'_>) -> async_graphql::Result { - let db = ctx.data_unchecked::().clone(); - let contract = db - .storage::() - .get(&self.0)? - .ok_or(not_found!(ContractsRawCode))? - .into_owned(); - Ok(HexString(contract.into())) + let context = ContractQueryContext(ctx.data_unchecked()); + context + .contract_bytecode(self.0) + .map(HexString) + .map_err(Into::into) } - async fn salt(&self, ctx: &Context<'_>) -> async_graphql::Result { - let contract_id = self.0; - - let db = ctx.data_unchecked::().clone(); - let (salt, _) = db - .storage::() - .get(&contract_id)? - .ok_or_else(|| anyhow!("Contract does not exist"))? - .into_owned(); - - let cleaned_salt: Salt = salt.into(); - Ok(cleaned_salt) + async fn salt(&self, ctx: &Context<'_>) -> async_graphql::Result { + let context = ContractQueryContext(ctx.data_unchecked()); + context + .contract_salt(self.0) + .map(Into::into) + .map_err(Into::into) } } @@ -78,35 +64,25 @@ impl ContractQuery { ctx: &Context<'_>, #[graphql(desc = "ID of the Contract")] id: ContractId, ) -> async_graphql::Result> { - let id: fuel_types::ContractId = id.0; - let db = ctx.data_unchecked::().clone(); - let contract_exists = db.storage::().contains_key(&id)?; - if !contract_exists { - return Ok(None) - } - let contract = Contract(id); - Ok(Some(contract)) + let data = ContractQueryContext(ctx.data_unchecked()); + data.contract_id(id.0).into_api_result() } } -pub struct ContractBalance { - contract: fuel_types::ContractId, - amount: u64, - asset_id: fuel_types::AssetId, -} +pub struct ContractBalance(graphql_api::ContractBalance); #[Object] impl ContractBalance { async fn contract(&self) -> ContractId { - self.contract.into() + self.0.owner.into() } async fn amount(&self) -> U64 { - self.amount.into() + self.0.amount.into() } async fn asset_id(&self) -> AssetId { - self.asset_id.into() + self.0.asset_id.into() } } @@ -127,22 +103,22 @@ impl ContractBalanceQuery { contract: ContractId, asset: AssetId, ) -> async_graphql::Result { - let contract_id: fuel_types::ContractId = contract.0; - - let db = ctx.data_unchecked::().clone(); - - let asset_id: fuel_types::AssetId = asset.into(); - - let result = db - .storage::() - .get(&(&contract_id, &asset_id))?; - let balance = result.unwrap_or_default().into_owned(); - - Ok(ContractBalance { - contract: contract.into(), - amount: balance, - asset_id, - }) + let contract_id = contract.into(); + let asset_id = asset.into(); + let context = ContractQueryContext(ctx.data_unchecked()); + context + .contract_balance(contract_id, asset_id) + .into_api_result() + .map(|result| { + result.unwrap_or_else(|| { + graphql_api::ContractBalance { + owner: contract_id, + amount: 0, + asset_id, + } + .into() + }) + }) } async fn contract_balances( @@ -156,26 +132,19 @@ impl ContractBalanceQuery { ) -> async_graphql::Result< Connection, > { - let db = ctx.data_unchecked::().clone(); + let query = ContractQueryContext(ctx.data_unchecked()); crate::schema::query_pagination(after, before, first, last, |start, direction| { - let balances = db + let balances = query .contract_balances( filter.contract.into(), (*start).map(Into::into), - Some(direction), + direction, ) .map(move |balance| { let balance = balance?; - let asset_id: AssetId = balance.0.into(); + let asset_id = balance.asset_id; - Ok(( - asset_id, - ContractBalance { - contract: filter.contract.into(), - amount: balance.1, - asset_id: balance.0, - }, - )) + Ok((asset_id.into(), balance.into())) }); Ok(balances) @@ -183,3 +152,9 @@ impl ContractBalanceQuery { .await } } + +impl From for ContractBalance { + fn from(balance: graphql_api::ContractBalance) -> Self { + ContractBalance(balance) + } +} diff --git a/crates/fuel-core/src/schema/dap.rs b/crates/fuel-core/src/schema/dap.rs index 8f10369c66a..2cb02679d7f 100644 --- a/crates/fuel-core/src/schema/dap.rs +++ b/crates/fuel-core/src/schema/dap.rs @@ -2,8 +2,8 @@ use crate::{ database::{ transactional::DatabaseTransaction, vm_database::VmDatabase, + Database, }, - fuel_core_graphql_api::service::Database, schema::scalars::U64, }; use async_graphql::{ @@ -12,7 +12,10 @@ use async_graphql::{ SchemaBuilder, ID, }; -use fuel_core_storage::not_found; +use fuel_core_storage::{ + not_found, + InterpreterStorage, +}; use fuel_core_types::{ fuel_asm::{ Opcode, @@ -88,7 +91,7 @@ impl ConcreteStorage { let vm_database = Self::vm_database(&storage)?; let tx = Script::default(); let checked_tx = - tx.into_checked_basic(vm_database.block_height() as Word, &self.params)?; + tx.into_checked_basic(vm_database.block_height()? as Word, &self.params)?; self.tx .get_mut(&id) .map(|tx| tx.extend_from_slice(txs)) @@ -120,7 +123,7 @@ impl ConcreteStorage { .unwrap_or_default(); let checked_tx = - tx.into_checked_basic(vm_database.block_height() as Word, &self.params)?; + tx.into_checked_basic(vm_database.block_height()? as Word, &self.params)?; let mut vm = Interpreter::with_storage(vm_database, self.params); vm.transact(checked_tx)?; @@ -349,7 +352,7 @@ impl DapMutation { let checked_tx = tx .into_checked_basic( - db.get_block_height()?.unwrap_or_default().into(), + db.latest_height()?.unwrap_or_default().into(), &locked.params, )? .into(); diff --git a/crates/fuel-core/src/schema/message.rs b/crates/fuel-core/src/schema/message.rs index 1b11b4e4428..fab5830f837 100644 --- a/crates/fuel-core/src/schema/message.rs +++ b/crates/fuel-core/src/schema/message.rs @@ -9,10 +9,7 @@ use super::{ U64, }, }; -use crate::{ - fuel_core_graphql_api::service::Database, - query::MessageProofData, -}; +use crate::query::MessageQueryContext; use async_graphql::{ connection::{ Connection, @@ -21,29 +18,8 @@ use async_graphql::{ Context, Object, }; -use fuel_core_storage::{ - not_found, - tables::{ - FuelBlocks, - Messages, - Receipts, - SealedBlockConsensus, - Transactions, - }, - Result as StorageResult, - StorageAsRef, -}; -use fuel_core_types::{ - blockchain::{ - block::CompressedBlock, - consensus::Consensus, - }, - entities, - fuel_tx, - services::txpool::TransactionStatus, -}; -use itertools::Itertools; -use std::borrow::Cow; +use fuel_core_storage::iter::IntoBoxedIter; +use fuel_core_types::entities; pub struct Message(pub(crate) entities::message::Message); @@ -97,42 +73,25 @@ impl MessageQuery { before: Option, ) -> async_graphql::Result> { - let db = ctx.data_unchecked::().clone(); + let query = MessageQueryContext(ctx.data_unchecked()); crate::schema::query_pagination(after, before, first, last, |start, direction| { let start = *start; - // TODO: Avoid the `collect_vec`. - let messages = if let Some(owner) = owner { - let message_ids: Vec<_> = db - .owned_message_ids(&owner.0, start.map(Into::into), Some(direction)) - .try_collect()?; - let messages = message_ids - .into_iter() - .map(|msg_id| { - let message = db - .storage::() - .get(&msg_id) - .transpose() - .ok_or(not_found!(Messages))?? - .into_owned(); - - Ok((msg_id.into(), message.into())) - }) - .collect_vec() - .into_iter(); - Ok::<_, anyhow::Error>(messages) + let messages = if let Some(owner) = owner { + query + .owned_messages(&owner.0, start.map(Into::into), direction) + .into_boxed() } else { - let messages = db - .all_messages(start.map(Into::into), Some(direction)) - .map(|result| { - result - .map(|message| (message.id().into(), message.into())) - .map_err(Into::into) - }) - .collect_vec() - .into_iter(); - Ok(messages) - }?; + query + .all_messages(start.map(Into::into), direction) + .into_boxed() + }; + + let messages = messages.map(|result| { + result + .map(|message| (message.id().into(), message.into())) + .map_err(Into::into) + }); Ok(messages) }) @@ -145,10 +104,9 @@ impl MessageQuery { transaction_id: TransactionId, message_id: MessageId, ) -> async_graphql::Result> { - let data = MessageProofContext(ctx.data_unchecked()); + let data = MessageQueryContext(ctx.data_unchecked()); Ok( - crate::query::message_proof(&data, transaction_id.into(), message_id.into()) - .await? + crate::query::message_proof(&data, transaction_id.into(), message_id.into())? .map(MessageProof), ) } @@ -200,80 +158,6 @@ impl MessageProof { } } -struct MessageProofContext<'a>(&'a Database); - -impl MessageProofData for MessageProofContext<'_> { - fn receipts( - &self, - transaction_id: &fuel_core_types::fuel_types::Bytes32, - ) -> StorageResult> { - Ok(self - .0 - .storage::() - .get(transaction_id)? - .map(Cow::into_owned) - .unwrap_or_else(|| Vec::with_capacity(0))) - } - - fn transaction( - &self, - transaction_id: &fuel_core_types::fuel_types::Bytes32, - ) -> StorageResult> { - Ok(self - .0 - .storage::() - .get(transaction_id)? - .map(Cow::into_owned)) - } - - fn transaction_status( - &self, - transaction_id: &fuel_core_types::fuel_types::Bytes32, - ) -> StorageResult> { - Ok(self.0.get_tx_status(transaction_id)?) - } - - fn transactions_on_block( - &self, - block_id: &fuel_core_types::fuel_types::Bytes32, - ) -> StorageResult> { - Ok(self - .0 - .storage::() - .get(block_id)? - .map(|block| block.into_owned().transactions().to_vec()) - .unwrap_or_else(|| Vec::with_capacity(0))) - } - - fn signature( - &self, - block_id: &fuel_core_types::fuel_types::Bytes32, - ) -> StorageResult> { - match self - .0 - .storage::() - .get(block_id)? - .map(Cow::into_owned) - { - // TODO: https://github.com/FuelLabs/fuel-core/issues/816 - Some(Consensus::Genesis(_)) => Ok(Default::default()), - Some(Consensus::PoA(c)) => Ok(Some(c.signature)), - None => Ok(None), - } - } - - fn block( - &self, - block_id: &fuel_core_types::fuel_types::Bytes32, - ) -> StorageResult> { - Ok(self - .0 - .storage::() - .get(block_id)? - .map(Cow::into_owned)) - } -} - impl From for Message { fn from(message: entities::message::Message) -> Self { Message(message) diff --git a/crates/fuel-core/src/schema/resource.rs b/crates/fuel-core/src/schema/resource.rs index 2532e34a162..8de59b8251b 100644 --- a/crates/fuel-core/src/schema/resource.rs +++ b/crates/fuel-core/src/schema/resource.rs @@ -1,9 +1,9 @@ use crate::{ - database::resource::AssetSpendTarget, fuel_core_graphql_api::{ service::Database, Config as GraphQLConfig, }, + query::asset_query::AssetSpendTarget, resource_query::{ random_improve, SpendQuery, @@ -26,7 +26,10 @@ use async_graphql::{ Object, Union, }; -use fuel_core_types::fuel_tx; +use fuel_core_types::{ + entities::resource, + fuel_tx, +}; use itertools::Itertools; #[derive(InputObject)] @@ -47,7 +50,7 @@ pub struct ExcludeInput { messages: Vec, } -/// The schema analog of the [`crate::database::utils::Resource`]. +/// The schema analog of the [`resource::Resource`]. #[derive(Union)] pub enum Resource { Coin(Coin), @@ -99,11 +102,11 @@ impl ResourceQuery { let utxos = exclude .utxos .into_iter() - .map(|utxo| crate::database::resource::ResourceId::Utxo(utxo.0)); + .map(|utxo| resource::ResourceId::Utxo(utxo.0)); let messages = exclude .messages .into_iter() - .map(|message| crate::database::resource::ResourceId::Message(message.0)); + .map(|message| resource::ResourceId::Message(message.0)); utxos.chain(messages).collect() }); @@ -117,12 +120,10 @@ impl ResourceQuery { resources .into_iter() .map(|resource| match resource { - crate::database::resource::Resource::Coin { id, fields } => { - Resource::Coin(Coin(id, fields)) + resource::Resource::Coin(coin) => Resource::Coin(coin.into()), + resource::Resource::Message(message) => { + Resource::Message(message.into()) } - crate::database::resource::Resource::Message { - fields, .. - } => Resource::Message(Message(fields)), }) .collect_vec() }) diff --git a/crates/fuel-core/src/schema/scalars.rs b/crates/fuel-core/src/schema/scalars.rs index 8c9d1365457..2ccc7cfef8f 100644 --- a/crates/fuel-core/src/schema/scalars.rs +++ b/crates/fuel-core/src/schema/scalars.rs @@ -1,4 +1,3 @@ -use crate::database::transaction::OwnedTransactionIndexCursor; use async_graphql::{ connection::CursorType, InputValueError, @@ -151,18 +150,6 @@ impl CursorType for HexString { } } -impl From for OwnedTransactionIndexCursor { - fn from(string: HexString) -> Self { - string.0.into() - } -} - -impl From for HexString { - fn from(cursor: OwnedTransactionIndexCursor) -> Self { - HexString(cursor.into()) - } -} - impl FromStr for HexString { type Err = String; diff --git a/crates/fuel-core/src/schema/scalars/tx_pointer.rs b/crates/fuel-core/src/schema/scalars/tx_pointer.rs index 1a3c1f4da0d..689cb7ed13d 100644 --- a/crates/fuel-core/src/schema/scalars/tx_pointer.rs +++ b/crates/fuel-core/src/schema/scalars/tx_pointer.rs @@ -15,7 +15,7 @@ use std::{ str::FromStr, }; -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct TxPointer(pub(crate) fuel_tx::TxPointer); #[Scalar(name = "TxPointer")] diff --git a/crates/fuel-core/src/schema/tx.rs b/crates/fuel-core/src/schema/tx.rs index c34d75ebabf..6f6c936437e 100644 --- a/crates/fuel-core/src/schema/tx.rs +++ b/crates/fuel-core/src/schema/tx.rs @@ -1,12 +1,16 @@ use crate::{ - database::transaction::OwnedTransactionIndexCursor, - fuel_core_graphql_api::service::{ - BlockProducer, - Database, - TxPool, + fuel_core_graphql_api::{ + service::{ + BlockProducer, + Database, + TxPool, + }, + IntoApiResult, }, query::{ transaction_status_change, + BlockQueryContext, + TransactionQueryContext, TxnStatusChangeState, }, schema::scalars::{ @@ -14,6 +18,7 @@ use crate::{ HexString, SortedTxCursor, TransactionId, + TxPointer, }, state::IterDirection, }; @@ -26,16 +31,7 @@ use async_graphql::{ Object, Subscription, }; -use fuel_core_storage::{ - not_found, - tables::{ - FuelBlocks, - Transactions, - }, - Error as StorageError, - Result as StorageResult, - StorageAsRef, -}; +use fuel_core_storage::Result as StorageResult; use fuel_core_types::{ fuel_tx::{ Cacheable, @@ -75,17 +71,14 @@ impl TxQuery { ctx: &Context<'_>, #[graphql(desc = "The ID of the transaction")] id: TransactionId, ) -> async_graphql::Result> { - let db = ctx.data_unchecked::(); + let query = TransactionQueryContext(ctx.data_unchecked()); let id = id.0; let txpool = ctx.data_unchecked::(); if let Some(transaction) = txpool.shared.find_one(id) { Ok(Some(Transaction(transaction.tx().clone().deref().into()))) } else { - Ok(db - .storage::() - .get(&id)? - .map(|tx| Transaction(tx.into_owned()))) + query.transaction(&id).into_api_result() } } @@ -99,7 +92,9 @@ impl TxQuery { ) -> async_graphql::Result< Connection, > { - let db = ctx.data_unchecked::(); + let db = ctx.data_unchecked(); + let db_query = BlockQueryContext(db); + let tx_query = TransactionQueryContext(db); crate::schema::query_pagination( after, before, @@ -108,28 +103,19 @@ impl TxQuery { |start: &Option, direction| { let start = *start; let block_id = start.map(|sorted| sorted.block_height); - let all_block_ids = db.all_block_ids(block_id, Some(direction)); + let all_block_ids = db_query.compressed_blocks(block_id, direction); let all_txs = all_block_ids - .flat_map(move |block| { - block.map_err(StorageError::from).map( - |(block_height, block_id)| { - db.storage::() - .get(&block_id) - .transpose() - .ok_or(not_found!(FuelBlocks))? - .map(|fuel_block| { - let mut txs = - fuel_block.into_owned().into_inner().1; + .map(move |block| { + block.map(|fuel_block| { + let (header, mut txs) = fuel_block.into_inner(); - if direction == IterDirection::Reverse { - txs.reverse(); - } + if direction == IterDirection::Reverse { + txs.reverse(); + } - txs.into_iter().zip(iter::repeat(block_height)) - }) - }, - ) + txs.into_iter().zip(iter::repeat(*header.height())) + }) }) .flatten_ok() .map(|result| { @@ -147,12 +133,7 @@ impl TxQuery { }); let all_txs = all_txs.map(|result: StorageResult| { result.and_then(|sorted| { - let tx = db - .storage::() - .get(&sorted.tx_id.0) - .transpose() - .ok_or(not_found!(Transactions))?? - .into_owned(); + let tx = tx_query.transaction(&sorted.tx_id.0)?; Ok((sorted, tx.into())) }) @@ -172,9 +153,9 @@ impl TxQuery { after: Option, last: Option, before: Option, - ) -> async_graphql::Result> + ) -> async_graphql::Result> { - let db = ctx.data_unchecked::(); + let query = TransactionQueryContext(ctx.data_unchecked()); let owner = fuel_types::Address::from(owner); crate::schema::query_pagination( @@ -182,23 +163,11 @@ impl TxQuery { before, first, last, - |start: &Option, direction| { - let start: Option = - start.clone().map(Into::into); - let txs = db - .owned_transactions(&owner, start.as_ref(), Some(direction)) - .map(|result| { - result - .map_err(StorageError::from) - .and_then(|(cursor, tx_id)| { - let tx = db - .storage::() - .get(&tx_id)? - .ok_or(not_found!(Transactions))? - .into_owned(); - Ok((cursor.into(), tx.into())) - }) - }); + |start: &Option, direction| { + let start = (*start).map(Into::into); + let txs = query + .owned_transactions(&owner, start, direction) + .map(|result| result.map(|(cursor, tx)| (cursor.into(), tx.into()))); Ok(txs) }, ) @@ -253,9 +222,9 @@ impl TxMutation { #[derive(Default)] pub struct TxStatusSubscription; -struct StreamState { +struct StreamState<'a> { txpool: TxPool, - db: Database, + db: &'a Database, } #[Subscription] @@ -272,15 +241,15 @@ impl TxStatusSubscription { /// then the updates arrive. In such a case the stream will close without /// a status. If this occurs the stream can simply be restarted to return /// the latest status. - async fn status_change( + async fn status_change<'a>( &self, - ctx: &Context<'_>, + ctx: &Context<'a>, #[graphql(desc = "The ID of the transaction")] id: TransactionId, - ) -> impl Stream> { + ) -> impl Stream> + 'a { let txpool = ctx.data_unchecked::().clone(); - let db = ctx.data_unchecked::().clone(); + let db = ctx.data_unchecked::(); let rx = BroadcastStream::new(txpool.shared.tx_update_subscribe()); - let state = Box::new(StreamState { txpool, db }); + let state = StreamState { txpool, db }; transaction_status_change(state, rx.boxed(), id.into()) .await @@ -289,13 +258,11 @@ impl TxStatusSubscription { } #[async_trait::async_trait] -impl TxnStatusChangeState for StreamState { +impl<'a> TxnStatusChangeState for StreamState<'a> { async fn get_tx_status( &self, - id: fuel_core_types::fuel_types::Bytes32, - ) -> anyhow::Result> { - Ok(types::get_tx_status(id, &self.db, &self.txpool) - .await - .map_err(|e| anyhow::anyhow!("Database lookup failed {:?}", e))?) + id: fuel_types::Bytes32, + ) -> StorageResult> { + types::get_tx_status(id, &TransactionQueryContext(self.db), &self.txpool).await } } diff --git a/crates/fuel-core/src/schema/tx/types.rs b/crates/fuel-core/src/schema/tx/types.rs index dd7ef7f95e0..0c2a421b1d9 100644 --- a/crates/fuel-core/src/schema/tx/types.rs +++ b/crates/fuel-core/src/schema/tx/types.rs @@ -4,9 +4,13 @@ use super::{ receipt::Receipt, }; use crate::{ - fuel_core_graphql_api::service::{ - Database, - TxPool, + fuel_core_graphql_api::{ + service::TxPool, + IntoApiResult, + }, + query::{ + BlockQueryContext, + TransactionQueryContext, }, schema::{ block::Block, @@ -29,14 +33,7 @@ use async_graphql::{ Object, Union, }; -use fuel_core_storage::{ - not_found, - tables::{ - FuelBlocks, - Receipts, - }, - StorageAsRef, -}; +use fuel_core_storage::Error as StorageError; use fuel_core_types::{ blockchain::primitives, fuel_tx::{ @@ -61,7 +58,10 @@ use fuel_core_types::{ }, fuel_types::bytes::SerializableVec, fuel_vm::ProgramState as VmProgramState, - services::txpool::TransactionStatus as TxStatus, + services::{ + txpool, + txpool::TransactionStatus as TxStatus, + }, tai64::Tai64, }; @@ -139,14 +139,9 @@ pub struct SuccessStatus { #[Object] impl SuccessStatus { async fn block(&self, ctx: &Context<'_>) -> async_graphql::Result { - let db = ctx.data_unchecked::(); - let block = db - .storage::() - .get(&self.block_id.into())? - .ok_or(not_found!(FuelBlocks))? - .into_owned(); - let block = Block::from(block); - Ok(block) + let query = BlockQueryContext(ctx.data_unchecked()); + let block = query.block(&self.block_id)?; + Ok(block.into()) } async fn time(&self) -> Tai64Timestamp { @@ -169,14 +164,9 @@ pub struct FailureStatus { #[Object] impl FailureStatus { async fn block(&self, ctx: &Context<'_>) -> async_graphql::Result { - let db = ctx.data_unchecked::(); - let block = db - .storage::() - .get(&self.block_id.into())? - .ok_or(not_found!(FuelBlocks))? - .into_owned(); - let block = Block::from(block); - Ok(block) + let query = BlockQueryContext(ctx.data_unchecked()); + let block = query.block(&self.block_id)?; + Ok(block.into()) } async fn time(&self) -> Tai64Timestamp { @@ -406,18 +396,20 @@ impl Transaction { ctx: &Context<'_>, ) -> async_graphql::Result> { let id = self.0.id(); - let db = ctx.data_unchecked::(); + let query = TransactionQueryContext(ctx.data_unchecked()); let txpool = ctx.data_unchecked::(); - get_tx_status(id, db, txpool).await + get_tx_status(id, &query, txpool).await.map_err(Into::into) } async fn receipts( &self, ctx: &Context<'_>, ) -> async_graphql::Result>> { - let db = ctx.data_unchecked::(); - let receipts = db.storage::().get(&self.0.id())?; - Ok(receipts.map(|receipts| receipts.iter().cloned().map(Receipt).collect())) + let query = TransactionQueryContext(ctx.data_unchecked()); + let receipts = query + .receipts(&self.0.id()) + .into_api_result::, async_graphql::Error>()?; + Ok(receipts.map(|receipts| receipts.into_iter().map(Receipt).collect())) } async fn script(&self) -> Option { @@ -499,10 +491,13 @@ impl Transaction { pub(super) async fn get_tx_status( id: fuel_core_types::fuel_types::Bytes32, - db: &Database, + query: &TransactionQueryContext<'_>, txpool: &TxPool, -) -> async_graphql::Result> { - match db.get_tx_status(&id)? { +) -> Result, StorageError> { + match query + .status(&id) + .into_api_result::()? + { Some(status) => Ok(Some(status.into())), None => match txpool.shared.find_one(id) { Some(transaction_in_pool) => { diff --git a/crates/fuel-core/src/service/adapters.rs b/crates/fuel-core/src/service/adapters.rs index a242a37e5da..c91df9b581d 100644 --- a/crates/fuel-core/src/service/adapters.rs +++ b/crates/fuel-core/src/service/adapters.rs @@ -7,6 +7,7 @@ use fuel_core_types::blockchain::SealedBlock; use std::sync::Arc; use tokio::sync::broadcast::Sender; +pub mod graphql_api; pub mod poa; pub mod producer; pub mod txpool; diff --git a/crates/fuel-core/src/service/adapters/graphql_api.rs b/crates/fuel-core/src/service/adapters/graphql_api.rs new file mode 100644 index 00000000000..323e9dc6c68 --- /dev/null +++ b/crates/fuel-core/src/service/adapters/graphql_api.rs @@ -0,0 +1,177 @@ +use crate::{ + database::{ + transactions::OwnedTransactionIndexCursor, + Database, + }, + fuel_core_graphql_api::ports::{ + DatabaseBlocks, + DatabaseChain, + DatabaseCoins, + DatabaseContracts, + DatabaseMessages, + DatabasePort, + DatabaseTransactions, + }, + state::IterDirection, +}; +use fuel_core_storage::{ + iter::{ + BoxedIter, + IntoBoxedIter, + }, + not_found, + Error as StorageError, + Result as StorageResult, +}; +use fuel_core_txpool::types::{ + ContractId, + TxId, +}; +use fuel_core_types::{ + blockchain::primitives::{ + BlockHeight, + BlockId, + DaBlockHeight, + }, + entities::message::Message, + fuel_tx::{ + Address, + AssetId, + MessageId, + TxPointer, + UtxoId, + }, + services::{ + graphql_api::ContractBalance, + txpool::TransactionStatus, + }, +}; + +impl DatabaseBlocks for Database { + fn block_id(&self, height: BlockHeight) -> StorageResult { + self.get_block_id(height) + .and_then(|heigh| heigh.ok_or(not_found!("BlockId"))) + } + + fn blocks_ids( + &self, + start: Option, + direction: IterDirection, + ) -> BoxedIter<'_, StorageResult<(BlockHeight, BlockId)>> { + self.all_block_ids(start, direction) + .map(|result| result.map_err(StorageError::from)) + .into_boxed() + } + + fn ids_of_latest_block(&self) -> StorageResult<(BlockHeight, BlockId)> { + Ok(self + .ids_of_latest_block() + .transpose() + .ok_or(not_found!("BlockId"))??) + } +} + +impl DatabaseTransactions for Database { + fn tx_status(&self, tx_id: &TxId) -> StorageResult { + Ok(self + .get_tx_status(tx_id) + .transpose() + .ok_or(not_found!("TransactionId"))??) + } + + fn owned_transactions_ids( + &self, + owner: &Address, + start: Option, + direction: IterDirection, + ) -> BoxedIter> { + let start = start.map(|tx_pointer| OwnedTransactionIndexCursor { + block_height: tx_pointer.block_height().into(), + tx_idx: tx_pointer.tx_index(), + }); + self.owned_transactions(owner, start, Some(direction)) + .map(|result| result.map_err(StorageError::from)) + .into_boxed() + } +} + +impl DatabaseMessages for Database { + fn owned_message_ids( + &self, + owner: &Address, + start_message_id: Option, + direction: IterDirection, + ) -> BoxedIter<'_, StorageResult> { + self.owned_message_ids(owner, start_message_id, Some(direction)) + .map(|result| result.map_err(StorageError::from)) + .into_boxed() + } + + fn all_messages( + &self, + start_message_id: Option, + direction: IterDirection, + ) -> BoxedIter<'_, StorageResult> { + self.all_messages(start_message_id, Some(direction)) + .map(|result| result.map_err(StorageError::from)) + .into_boxed() + } +} + +impl DatabaseCoins for Database { + fn owned_coins_ids( + &self, + owner: &Address, + start_coin: Option, + direction: IterDirection, + ) -> BoxedIter<'_, StorageResult> { + self.owned_coins_ids(owner, start_coin, Some(direction)) + .map(|res| res.map_err(StorageError::from)) + .into_boxed() + } +} + +impl DatabaseContracts for Database { + fn contract_balances( + &self, + contract: ContractId, + start_asset: Option, + direction: IterDirection, + ) -> BoxedIter> { + self.contract_balances(contract, start_asset, Some(direction)) + .map(move |result| { + result + .map_err(StorageError::from) + .map(|(asset_id, amount)| ContractBalance { + owner: contract, + amount, + asset_id, + }) + }) + .into_boxed() + } +} + +impl DatabaseChain for Database { + fn chain_name(&self) -> StorageResult { + pub const DEFAULT_NAME: &str = "Fuel.testnet"; + + Ok(self + .get_chain_name()? + .unwrap_or_else(|| DEFAULT_NAME.to_string())) + } + + fn base_chain_height(&self) -> StorageResult { + #[cfg(feature = "relayer")] + { + use fuel_core_relayer::ports::RelayerDb; + self.get_finalized_da_height() + } + #[cfg(not(feature = "relayer"))] + { + Ok(0u64.into()) + } + } +} + +impl DatabasePort for Database {} diff --git a/crates/fuel-core/src/service/adapters/producer.rs b/crates/fuel-core/src/service/adapters/producer.rs index 6fb81cbf8b9..29fa034ce32 100644 --- a/crates/fuel-core/src/service/adapters/producer.rs +++ b/crates/fuel-core/src/service/adapters/producer.rs @@ -77,11 +77,7 @@ impl fuel_core_producer::ports::Relayer for MaybeRelayerAdapter { sync.await_synced().await?; } - Ok(self - .database - .get_finalized_da_height() - .await - .unwrap_or_default()) + Ok(self.database.get_finalized_da_height().unwrap_or_default()) } #[cfg(not(feature = "relayer"))] { diff --git a/crates/fuel-core/src/service/genesis.rs b/crates/fuel-core/src/service/genesis.rs index b633ecb6a6c..a58559423f7 100644 --- a/crates/fuel-core/src/service/genesis.rs +++ b/crates/fuel-core/src/service/genesis.rs @@ -21,7 +21,7 @@ use fuel_core_storage::{ FuelBlocks, Messages, }, - transactional::Transactional, + transactional::Transaction, MerkleRoot, StorageAsMut, }; @@ -41,8 +41,8 @@ use fuel_core_types::{ }, entities::{ coin::{ - Coin, CoinStatus, + CompressedCoin, }, message::Message, }, @@ -130,7 +130,7 @@ fn add_genesis_block(config: &Config, database: &mut Database) -> anyhow::Result let block_id = block.id(); database .storage::() - .insert(&block_id.into(), &block.compress())?; + .insert(&block_id, &block.compress())?; database.seal_block(block_id, seal) } @@ -166,7 +166,7 @@ fn init_coin_state( }), ); - let mut coin = Coin { + let mut coin = CompressedCoin { owner: coin.owner, amount: coin.amount, asset_id: coin.asset_id, @@ -347,6 +347,7 @@ mod tests { BlockHeight, DaBlockHeight, }, + entities::coin::Coin, fuel_asm::Opcode, fuel_types::{ Address, @@ -354,7 +355,6 @@ mod tests { Salt, }, }; - use itertools::Itertools; use rand::{ rngs::StdRng, Rng, @@ -408,7 +408,7 @@ mod tests { assert_eq!( test_height, - db.get_block_height() + db.latest_height() .unwrap() .expect("Expected a block height to be set") ) @@ -477,21 +477,19 @@ mod tests { .unwrap(); let alice_coins = get_coins(&db, &alice); - let bob_coins = get_coins(&db, &bob) - .into_iter() - .map(|(_, coin)| coin) - .collect_vec(); + let bob_coins = get_coins(&db, &bob); assert!(matches!( alice_coins.as_slice(), - &[(utxo_id, Coin { + &[Coin { + utxo_id, owner, amount, asset_id, block_created, maturity, .. - })] if utxo_id == alice_utxo_id + }] if utxo_id == alice_utxo_id && owner == alice && amount == alice_value && asset_id == asset_id_alice @@ -623,7 +621,7 @@ mod tests { .unwrap(); let ret = db - .storage::>() + .storage::() .get(&(&id, &test_asset_id)) .unwrap() .expect("Expected a balance to be present") @@ -632,13 +630,13 @@ mod tests { assert_eq!(test_balance, ret) } - fn get_coins(db: &Database, owner: &Address) -> Vec<(UtxoId, Coin)> { + fn get_coins(db: &Database, owner: &Address) -> Vec { db.owned_coins_ids(owner, None, None) .map(|r| { let coin_id = r.unwrap(); db.storage::() .get(&coin_id) - .map(|v| (coin_id, v.unwrap().into_owned())) + .map(|v| v.unwrap().into_owned().uncompress(coin_id)) .unwrap() }) .collect() diff --git a/crates/fuel-core/src/service/sub_services.rs b/crates/fuel-core/src/service/sub_services.rs index b6aeaa72f18..f0a29c0949a 100644 --- a/crates/fuel-core/src/service/sub_services.rs +++ b/crates/fuel-core/src/service/sub_services.rs @@ -4,6 +4,10 @@ use crate::{ chain_config::BlockProduction, database::Database, fuel_core_graphql_api::Config as GraphQLConfig, + schema::{ + build_schema, + dap, + }, service::{ adapters::{ BlockImportAdapter, @@ -117,6 +121,9 @@ pub fn init_sub_services( ), }; + // TODO: Figure out on how to move it into `fuel-core-graphql-api`. + let schema = dap::init(build_schema(), config.chain_conf.transaction_parameters) + .data(database.clone()); let graph_ql = crate::fuel_core_graphql_api::service::new_service( GraphQLConfig { addr: config.addr, @@ -129,7 +136,8 @@ pub fn init_sub_services( transaction_parameters: config.chain_conf.transaction_parameters, consensus_key: config.consensus_key.clone(), }, - database.clone(), + Box::new(database.clone()), + schema, block_producer, txpool.clone(), executor, diff --git a/crates/fuel-core/src/state.rs b/crates/fuel-core/src/state.rs index 1a84cee8d8e..eb1273eadd8 100644 --- a/crates/fuel-core/src/state.rs +++ b/crates/fuel-core/src/state.rs @@ -5,6 +5,7 @@ use crate::{ }, state::in_memory::transaction::MemoryTransactionView, }; +use fuel_core_storage::iter::BoxedIter; use std::{ fmt::Debug, marker::PhantomData, @@ -71,7 +72,7 @@ pub trait KeyValueStore { prefix: Option>, start: Option>, direction: IterDirection, - ) -> Box + '_>; + ) -> BoxedIter; } #[derive(Copy, Clone, Debug, PartialOrd, Eq, PartialEq)] diff --git a/crates/fuel-core/src/state/in_memory/memory_store.rs b/crates/fuel-core/src/state/in_memory/memory_store.rs index b493eeca66e..dfde4376651 100644 --- a/crates/fuel-core/src/state/in_memory/memory_store.rs +++ b/crates/fuel-core/src/state/in_memory/memory_store.rs @@ -15,6 +15,10 @@ use crate::{ TransactableStorage, }, }; +use fuel_core_storage::iter::{ + BoxedIter, + IntoBoxedIter, +}; use itertools::Itertools; use std::{ collections::HashMap, @@ -73,7 +77,7 @@ impl KeyValueStore for MemoryStore { prefix: Option>, start: Option>, direction: IterDirection, - ) -> Box, Vec)>> + '_> { + ) -> BoxedIter, Vec)>> { // clone entire set so we can drop the lock let mut copy: Vec<(Vec, Vec)> = self .inner @@ -99,13 +103,12 @@ impl KeyValueStore for MemoryStore { } if let Some(start) = start { - Box::new( - copy.into_iter() - .skip_while(move |(key, _)| key.as_slice() != start.as_slice()) - .map(Ok), - ) + copy.into_iter() + .skip_while(move |(key, _)| key.as_slice() != start.as_slice()) + .map(Ok) + .into_boxed() } else { - Box::new(copy.into_iter().map(Ok)) + copy.into_iter().map(Ok).into_boxed() } } } diff --git a/crates/fuel-core/src/state/in_memory/transaction.rs b/crates/fuel-core/src/state/in_memory/transaction.rs index 960c1bce409..31f97eebc7d 100644 --- a/crates/fuel-core/src/state/in_memory/transaction.rs +++ b/crates/fuel-core/src/state/in_memory/transaction.rs @@ -19,6 +19,10 @@ use crate::{ WriteOperation, }, }; +use fuel_core_storage::iter::{ + BoxedIter, + IntoBoxedIter, +}; use itertools::{ EitherOrBoth, Itertools, @@ -128,11 +132,11 @@ impl KeyValueStore for MemoryTransactionView { prefix: Option>, start: Option>, direction: IterDirection, - ) -> Box, Vec)>> + '_> { + ) -> BoxedIter, Vec)>> { // iterate over inmemory + db while also filtering deleted entries let changes = self.changes.clone(); - Box::new( - self.view_layer + + self.view_layer // iter_all returns items in sorted order .iter_all(column, prefix.clone(), start.clone(), direction) // Merge two sorted iterators (our current view overlay + backing data source) @@ -177,8 +181,7 @@ impl KeyValueStore for MemoryTransactionView { // ensure errors are propagated true } - }), - ) + }).into_boxed() } } diff --git a/crates/fuel-core/src/state/rocks_db.rs b/crates/fuel-core/src/state/rocks_db.rs index ca76b10571b..c6e78d29504 100644 --- a/crates/fuel-core/src/state/rocks_db.rs +++ b/crates/fuel-core/src/state/rocks_db.rs @@ -19,6 +19,10 @@ use crate::{ }; #[cfg(feature = "metrics")] use fuel_core_metrics::core_metrics::DATABASE_METRICS; +use fuel_core_storage::iter::{ + BoxedIter, + IntoBoxedIter, +}; use rocksdb::{ BoundColumnFamily, ColumnFamilyDescriptor, @@ -192,7 +196,7 @@ impl KeyValueStore for RocksDb { prefix: Option>, start: Option>, direction: IterDirection, - ) -> Box + '_> { + ) -> BoxedIter { let iter_mode = start.as_ref().map_or_else( || { prefix.as_ref().map_or_else( @@ -242,15 +246,16 @@ impl KeyValueStore for RocksDb { if let Some(prefix) = prefix { let prefix = prefix.to_vec(); // end iterating when we've gone outside the prefix - Box::new(iter.take_while(move |item| { + iter.take_while(move |item| { if let Ok((key, _)) = item { key.starts_with(prefix.as_slice()) } else { true } - })) + }) + .into_boxed() } else { - Box::new(iter) + iter.into_boxed() } } } diff --git a/crates/services/consensus_module/poa/src/service_test.rs b/crates/services/consensus_module/poa/src/service_test.rs index bdcac288ca3..f24cc00730e 100644 --- a/crates/services/consensus_module/poa/src/service_test.rs +++ b/crates/services/consensus_module/poa/src/service_test.rs @@ -21,7 +21,7 @@ use fuel_core_services::{ use fuel_core_storage::{ transactional::{ StorageTransaction, - Transactional, + Transaction as StorageTransactionTrait, }, Result as StorageResult, }; @@ -272,7 +272,7 @@ mockall::mock! { ) -> anyhow::Result<()>; } - impl Transactional for Database { + impl StorageTransactionTrait for Database { fn commit(&mut self) -> StorageResult<()>; } diff --git a/crates/services/producer/src/mocks.rs b/crates/services/producer/src/mocks.rs index c23f2410150..44e2d36025a 100644 --- a/crates/services/producer/src/mocks.rs +++ b/crates/services/producer/src/mocks.rs @@ -7,7 +7,7 @@ use crate::ports::{ use fuel_core_storage::{ transactional::{ StorageTransaction, - Transactional, + Transaction, }, Result as StorageResult, }; @@ -81,7 +81,7 @@ struct DatabaseTransaction { database: MockDb, } -impl Transactional for DatabaseTransaction { +impl Transaction for DatabaseTransaction { fn commit(&mut self) -> StorageResult<()> { Ok(()) } @@ -99,7 +99,7 @@ impl AsRef for DatabaseTransaction { } } -impl Transactional for MockDb { +impl Transaction for MockDb { fn commit(&mut self) -> StorageResult<()> { Ok(()) } diff --git a/crates/services/relayer/src/mock_db.rs b/crates/services/relayer/src/mock_db.rs index cc4d7902cbd..54d570c7be2 100644 --- a/crates/services/relayer/src/mock_db.rs +++ b/crates/services/relayer/src/mock_db.rs @@ -1,7 +1,6 @@ #![allow(missing_docs)] use crate::ports::RelayerDb; -use async_trait::async_trait; use fuel_core_storage::{ not_found, Result as StorageResult, @@ -43,9 +42,8 @@ impl MockDb { } } -#[async_trait] impl RelayerDb for MockDb { - async fn insert_message( + fn insert_message( &mut self, message: &CheckedMessage, ) -> StorageResult> { @@ -58,15 +56,12 @@ impl RelayerDb for MockDb { .insert(message_id, message)) } - async fn set_finalized_da_height( - &mut self, - height: DaBlockHeight, - ) -> StorageResult<()> { + fn set_finalized_da_height(&mut self, height: DaBlockHeight) -> StorageResult<()> { self.data.lock().unwrap().finalized_da_height = Some(height); Ok(()) } - async fn get_finalized_da_height(&self) -> StorageResult { + fn get_finalized_da_height(&self) -> StorageResult { self.data .lock() .unwrap() diff --git a/crates/services/relayer/src/ports.rs b/crates/services/relayer/src/ports.rs index cb0bc9d50f4..c4c5ad99327 100644 --- a/crates/services/relayer/src/ports.rs +++ b/crates/services/relayer/src/ports.rs @@ -14,17 +14,14 @@ use fuel_core_types::{ #[async_trait] pub trait RelayerDb: Send + Sync { /// Add bridge message to database. Messages are not revertible. - async fn insert_message( + fn insert_message( &mut self, message: &CheckedMessage, ) -> StorageResult>; /// set finalized da height that represent last block from da layer that got finalized. - async fn set_finalized_da_height( - &mut self, - block: DaBlockHeight, - ) -> StorageResult<()>; + fn set_finalized_da_height(&mut self, block: DaBlockHeight) -> StorageResult<()>; /// Assume it is always set as initialization of database. - async fn get_finalized_da_height(&self) -> StorageResult; + fn get_finalized_da_height(&self) -> StorageResult; } diff --git a/crates/services/relayer/src/service.rs b/crates/services/relayer/src/service.rs index 6e3dcb98646..c887571b75f 100644 --- a/crates/services/relayer/src/service.rs +++ b/crates/services/relayer/src/service.rs @@ -98,11 +98,10 @@ where P: Middleware + 'static, D: RelayerDb + 'static, { - async fn set_deploy_height(&mut self) { - if self.finalized().await.unwrap_or_default() < *self.config.da_deploy_height { + fn set_deploy_height(&mut self) { + if self.finalized().unwrap_or_default() < *self.config.da_deploy_height { self.database .set_finalized_da_height(self.config.da_deploy_height) - .await .expect("Should be able to set the finalized da height"); } } @@ -136,11 +135,8 @@ where write_logs(&mut self.database, logs).await } - async fn set_finalized_da_height( - &mut self, - height: DaBlockHeight, - ) -> StorageResult<()> { - self.database.set_finalized_da_height(height).await + fn set_finalized_da_height(&mut self, height: DaBlockHeight) -> StorageResult<()> { + self.database.set_finalized_da_height(height) } fn update_synced(&self, state: &state::EthState) { @@ -166,7 +162,7 @@ where } async fn into_task(mut self, _watcher: &StateWatcher) -> anyhow::Result { - self.set_deploy_height().await; + self.set_deploy_height(); Ok(self) } } @@ -239,12 +235,8 @@ where P: Middleware, D: RelayerDb + 'static, { - async fn finalized(&self) -> Option { - self.database - .get_finalized_da_height() - .await - .map(|h| *h) - .ok() + fn finalized(&self) -> Option { + self.database.get_finalized_da_height().map(|h| *h).ok() } } diff --git a/crates/services/relayer/src/service/get_logs.rs b/crates/services/relayer/src/service/get_logs.rs index 4589a9463f5..1aed48bff89 100644 --- a/crates/services/relayer/src/service/get_logs.rs +++ b/crates/services/relayer/src/service/get_logs.rs @@ -67,7 +67,7 @@ where match event { EthEventLog::Message(m) => { let m = Message::from(&m).check(); - if database.insert_message(&m).await?.is_some() { + if database.insert_message(&m)?.is_some() { // TODO: https://github.com/FuelLabs/fuel-core/issues/681 return Err(anyhow!( "The message for {:?} already existed", @@ -79,7 +79,7 @@ where EthEventLog::Ignored => (), } } - database.set_finalized_da_height(to_block.into()).await?; + database.set_finalized_da_height(to_block.into())?; } Ok(()) } diff --git a/crates/services/relayer/src/service/get_logs/test.rs b/crates/services/relayer/src/service/get_logs/test.rs index a5ebf71ee8f..524b6fd685b 100644 --- a/crates/services/relayer/src/service/get_logs/test.rs +++ b/crates/services/relayer/src/service/get_logs/test.rs @@ -167,11 +167,11 @@ async fn test_da_height_updates( stream: Vec), ProviderError>>, ) -> u64 { let mut mock_db = crate::mock_db::MockDb::default(); - mock_db.set_finalized_da_height(0u64.into()).await.unwrap(); + mock_db.set_finalized_da_height(0u64.into()).unwrap(); let logs = futures::stream::iter(stream); let _ = write_logs(&mut mock_db, logs).await; - *mock_db.get_finalized_da_height().await.unwrap() + *mock_db.get_finalized_da_height().unwrap() } diff --git a/crates/services/relayer/src/service/run.rs b/crates/services/relayer/src/service/run.rs index 04f73e9a42e..93946600b0b 100644 --- a/crates/services/relayer/src/service/run.rs +++ b/crates/services/relayer/src/service/run.rs @@ -30,10 +30,7 @@ pub trait RelayerData: EthRemote + EthLocal { ) -> anyhow::Result<()>; /// Set the finalized DA block height. - async fn set_finalized_da_height( - &mut self, - height: DaBlockHeight, - ) -> StorageResult<()>; + fn set_finalized_da_height(&mut self, height: DaBlockHeight) -> StorageResult<()>; /// Update the synced state. fn update_synced(&self, state: &EthState); @@ -56,9 +53,7 @@ where relayer.download_logs(ð_sync_gap).await?; // Update finalized height in database. - relayer - .set_finalized_da_height(eth_sync_gap.latest().into()) - .await?; + relayer.set_finalized_da_height(eth_sync_gap.latest().into())?; } // Update the synced state. diff --git a/crates/services/relayer/src/service/run/test.rs b/crates/services/relayer/src/service/run/test.rs index 1deb4b05979..5228b3614dd 100644 --- a/crates/services/relayer/src/service/run/test.rs +++ b/crates/services/relayer/src/service/run/test.rs @@ -57,9 +57,8 @@ mockall::mock! { fn finalization_period(&self) -> u64; } - #[async_trait] impl EthLocal for RelayerData { - async fn finalized(&self) -> Option; + fn finalized(&self) -> Option; } #[async_trait] @@ -71,7 +70,7 @@ mockall::mock! { eth_sync_gap: &state::EthSyncGap, ) -> anyhow::Result<()>; - async fn set_finalized_da_height(&mut self, height: DaBlockHeight) -> StorageResult<()>; + fn set_finalized_da_height(&mut self, height: DaBlockHeight) -> StorageResult<()>; fn update_synced(&self, state: &EthState); } diff --git a/crates/services/relayer/src/service/state/state_builder.rs b/crates/services/relayer/src/service/state/state_builder.rs index 05bbc7627ce..cc463200585 100644 --- a/crates/services/relayer/src/service/state/state_builder.rs +++ b/crates/services/relayer/src/service/state/state_builder.rs @@ -15,7 +15,7 @@ pub trait EthRemote { #[async_trait] pub trait EthLocal { /// The current finalized eth block that the relayer has seen. - async fn finalized(&self) -> Option; + fn finalized(&self) -> Option; } /// Build the Ethereum state. @@ -25,7 +25,7 @@ where { Ok(EthState { remote: EthHeights::new(t.current().await?, t.finalization_period()), - local: t.finalized().await, + local: t.finalized(), }) } @@ -49,9 +49,8 @@ pub mod test_builder { } } - #[async_trait] impl EthLocal for TestDataSource { - async fn finalized(&self) -> Option { + fn finalized(&self) -> Option { self.eth_local_finalized } } diff --git a/crates/services/relayer/src/service/test.rs b/crates/services/relayer/src/service/test.rs index f63725ed81e..5b28a653f61 100644 --- a/crates/services/relayer/src/service/test.rs +++ b/crates/services/relayer/src/service/test.rs @@ -44,7 +44,7 @@ async fn can_download_logs() { #[tokio::test] async fn deploy_height_does_not_override() { let mut mock_db = crate::mock_db::MockDb::default(); - mock_db.set_finalized_da_height(50u64.into()).await.unwrap(); + mock_db.set_finalized_da_height(50u64.into()).unwrap(); let config = Config { da_deploy_height: 20u64.into(), da_finalization: 1u64.into(), @@ -53,15 +53,15 @@ async fn deploy_height_does_not_override() { let eth_node = MockMiddleware::default(); let (tx, _) = watch::channel(false); let mut relayer = Task::new(tx, eth_node, mock_db.clone(), config); - relayer.set_deploy_height().await; + relayer.set_deploy_height(); - assert_eq!(*mock_db.get_finalized_da_height().await.unwrap(), 50); + assert_eq!(*mock_db.get_finalized_da_height().unwrap(), 50); } #[tokio::test] async fn deploy_height_does_override() { let mut mock_db = crate::mock_db::MockDb::default(); - mock_db.set_finalized_da_height(50u64.into()).await.unwrap(); + mock_db.set_finalized_da_height(50u64.into()).unwrap(); let config = Config { da_deploy_height: 52u64.into(), da_finalization: 1u64.into(), @@ -70,7 +70,7 @@ async fn deploy_height_does_override() { let eth_node = MockMiddleware::default(); let (tx, _) = watch::channel(false); let mut relayer = Task::new(tx, eth_node, mock_db.clone(), config); - relayer.set_deploy_height().await; + relayer.set_deploy_height(); - assert_eq!(*mock_db.get_finalized_da_height().await.unwrap(), 52); + assert_eq!(*mock_db.get_finalized_da_height().unwrap(), 52); } diff --git a/crates/services/relayer/tests/integration.rs b/crates/services/relayer/tests/integration.rs index 4eb6272dc36..7354c00c908 100644 --- a/crates/services/relayer/tests/integration.rs +++ b/crates/services/relayer/tests/integration.rs @@ -26,7 +26,7 @@ async fn can_set_da_height() { relayer.shared.await_synced().await.unwrap(); - assert_eq!(*mock_db.get_finalized_da_height().await.unwrap(), 100); + assert_eq!(*mock_db.get_finalized_da_height().unwrap(), 100); } #[tokio::test(start_paused = true)] @@ -103,5 +103,5 @@ async fn deploy_height_is_set() { relayer.shared.await_synced().await.unwrap(); rx.await.unwrap(); - assert_eq!(*mock_db.get_finalized_da_height().await.unwrap(), 53); + assert_eq!(*mock_db.get_finalized_da_height().unwrap(), 53); } diff --git a/crates/services/txpool/src/containers/dependency.rs b/crates/services/txpool/src/containers/dependency.rs index 3414465104d..8356c0e43a7 100644 --- a/crates/services/txpool/src/containers/dependency.rs +++ b/crates/services/txpool/src/containers/dependency.rs @@ -6,8 +6,8 @@ use crate::{ use anyhow::anyhow; use fuel_core_types::{ entities::coin::{ - Coin, CoinStatus, + CompressedCoin, }, fuel_tx::{ Input, @@ -153,7 +153,7 @@ impl Dependency { } fn check_if_coin_input_can_spend_db_coin( - coin: &Coin, + coin: &CompressedCoin, input: &Input, ) -> anyhow::Result<()> { match input { diff --git a/crates/services/txpool/src/mock_db.rs b/crates/services/txpool/src/mock_db.rs index a1961a13fb3..2710630baf7 100644 --- a/crates/services/txpool/src/mock_db.rs +++ b/crates/services/txpool/src/mock_db.rs @@ -3,7 +3,10 @@ use fuel_core_storage::Result as StorageResult; use fuel_core_types::{ blockchain::primitives::BlockHeight, entities::{ - coin::Coin, + coin::{ + Coin, + CompressedCoin, + }, message::Message, }, fuel_tx::{ @@ -23,7 +26,7 @@ use std::{ #[derive(Default)] pub struct Data { - pub coins: HashMap, + pub coins: HashMap, pub contracts: HashMap, pub messages: HashMap, } @@ -34,8 +37,12 @@ pub struct MockDb { } impl MockDb { - pub fn insert_coin(&self, id: UtxoId, coin: Coin) { - self.data.lock().unwrap().coins.insert(id, coin); + pub fn insert_coin(&self, coin: Coin) { + self.data + .lock() + .unwrap() + .coins + .insert(coin.utxo_id, coin.compress()); } pub fn insert_message(&self, message: Message) { @@ -48,7 +55,7 @@ impl MockDb { } impl TxPoolDb for MockDb { - fn utxo(&self, utxo_id: &UtxoId) -> StorageResult> { + fn utxo(&self, utxo_id: &UtxoId) -> StorageResult> { Ok(self .data .lock() diff --git a/crates/services/txpool/src/ports.rs b/crates/services/txpool/src/ports.rs index 0a6c8a222ac..244a8d715e3 100644 --- a/crates/services/txpool/src/ports.rs +++ b/crates/services/txpool/src/ports.rs @@ -6,7 +6,7 @@ use fuel_core_types::{ SealedBlock, }, entities::{ - coin::Coin, + coin::CompressedCoin, message::Message, }, fuel_tx::{ @@ -47,7 +47,7 @@ pub trait BlockImport: Send + Sync { } pub trait TxPoolDb: Send + Sync { - fn utxo(&self, utxo_id: &UtxoId) -> StorageResult>; + fn utxo(&self, utxo_id: &UtxoId) -> StorageResult>; fn contract_exist(&self, contract_id: &ContractId) -> StorageResult; diff --git a/crates/services/txpool/src/test_helpers.rs b/crates/services/txpool/src/test_helpers.rs index fed9772db89..fb9ca3ff540 100644 --- a/crates/services/txpool/src/test_helpers.rs +++ b/crates/services/txpool/src/test_helpers.rs @@ -6,6 +6,7 @@ use fuel_core_types::{ entities::coin::{ Coin, CoinStatus, + CompressedCoin, }, fuel_asm::Opcode, fuel_crypto::rand::{ @@ -29,7 +30,7 @@ pub const TEST_COIN_AMOUNT: u64 = 100_000_000u64; pub(crate) fn setup_coin(rng: &mut StdRng, mock_db: Option<&MockDb>) -> (Coin, Input) { let input = random_predicate(rng, AssetId::BASE, TEST_COIN_AMOUNT, None); - let coin = Coin { + let coin = CompressedCoin { owner: *input.input_owner().unwrap(), amount: TEST_COIN_AMOUNT, asset_id: *input.asset_id().unwrap(), @@ -37,15 +38,16 @@ pub(crate) fn setup_coin(rng: &mut StdRng, mock_db: Option<&MockDb>) -> (Coin, I status: CoinStatus::Unspent, block_created: Default::default(), }; + let utxo_id = *input.utxo_id().unwrap(); if let Some(mock_db) = mock_db { mock_db .data .lock() .unwrap() .coins - .insert(*input.utxo_id().unwrap(), coin.clone()); + .insert(utxo_id, coin.clone()); } - (coin, input) + (coin.uncompress(utxo_id), input) } pub(crate) fn create_output_and_input( diff --git a/crates/services/txpool/src/txpool/tests.rs b/crates/services/txpool/src/txpool/tests.rs index e8a8b559b7c..120cbbe16f4 100644 --- a/crates/services/txpool/src/txpool/tests.rs +++ b/crates/services/txpool/src/txpool/tests.rs @@ -245,9 +245,8 @@ async fn tx_try_to_use_spent_coin() { // put a spent coin into the database let (mut coin, input) = setup_coin(&mut rng, None); - let utxo_id = *input.utxo_id().unwrap(); coin.status = CoinStatus::Spent; - txpool.database.insert_coin(utxo_id, coin); + txpool.database.insert_coin(coin.clone()); let tx = Arc::new( TransactionBuilder::script(vec![], vec![]) @@ -262,7 +261,7 @@ async fn tx_try_to_use_spent_coin() { .expect_err("Tx should be Err, got Ok"); assert!(matches!( err.downcast_ref::(), - Some(Error::NotInsertedInputUtxoIdSpent(id)) if id == &utxo_id + Some(Error::NotInsertedInputUtxoIdSpent(id)) if id == &coin.utxo_id )); } diff --git a/crates/storage/src/iter.rs b/crates/storage/src/iter.rs new file mode 100644 index 00000000000..bbd54681138 --- /dev/null +++ b/crates/storage/src/iter.rs @@ -0,0 +1,31 @@ +//! Iterators returned by the storage. + +/// A boxed variant of the iterator that can be used as a return type of the traits. +pub struct BoxedIter<'a, T> { + iter: Box + 'a>, +} + +impl<'a, T> Iterator for BoxedIter<'a, T> { + type Item = T; + + fn next(&mut self) -> Option { + self.iter.next() + } +} + +/// The traits simplifies conversion into `BoxedIter`. +pub trait IntoBoxedIter<'a, T> { + /// Converts `Self` iterator into `BoxedIter`. + fn into_boxed(self) -> BoxedIter<'a, T>; +} + +impl<'a, T, I> IntoBoxedIter<'a, T> for I +where + I: Iterator + 'a, +{ + fn into_boxed(self) -> BoxedIter<'a, T> { + BoxedIter { + iter: Box::new(self), + } + } +} diff --git a/crates/storage/src/lib.rs b/crates/storage/src/lib.rs index e9867437a76..3fab5ec4e55 100644 --- a/crates/storage/src/lib.rs +++ b/crates/storage/src/lib.rs @@ -10,8 +10,12 @@ use fuel_core_types::services::executor::Error as ExecutorError; use std::io::ErrorKind; -pub use fuel_vm_private::fuel_storage::*; +pub use fuel_vm_private::{ + fuel_storage::*, + storage::InterpreterStorage, +}; +pub mod iter; pub mod tables; pub mod transactional; @@ -88,7 +92,7 @@ mod test { #[rustfmt::skip] assert_eq!( format!("{}", not_found!(Coins)), - format!("resource of type `fuel_core_types::entities::coin::Coin` was not found at the: {}:{}", file!(), line!() - 1) + format!("resource of type `fuel_core_types::entities::coin::CompressedCoin` was not found at the: {}:{}", file!(), line!() - 1) ); } } diff --git a/crates/storage/src/tables.rs b/crates/storage/src/tables.rs index 628e46fb12d..410647624f6 100644 --- a/crates/storage/src/tables.rs +++ b/crates/storage/src/tables.rs @@ -6,14 +6,16 @@ use fuel_core_types::{ blockchain::{ block::CompressedBlock, consensus::Consensus, + primitives::BlockId, }, entities::{ - coin::Coin, + coin::CompressedCoin, message::Message, }, fuel_tx::{ Receipt, Transaction, + TxId, UtxoId, }, fuel_types::{ @@ -35,7 +37,7 @@ pub struct FuelBlocks; impl Mappable for FuelBlocks { /// Unique identifier of the fuel block. - type Key = Bytes32; + type Key = BlockId; type SetValue = CompressedBlock; type GetValue = Self::SetValue; } @@ -66,17 +68,19 @@ impl Mappable for Receipts { pub struct SealedBlockConsensus; impl Mappable for SealedBlockConsensus { - type Key = Bytes32; + type Key = BlockId; type SetValue = Consensus; type GetValue = Self::SetValue; } -/// The storage table of coins. Each [`Coin`](crate::model::Coin) is represented by unique `UtxoId`. +/// The storage table of coins. Each +/// [`CompressedCoin`](fuel_core_types::entities::coin::CompressedCoin) +/// is represented by unique `UtxoId`. pub struct Coins; impl Mappable for Coins { type Key = UtxoId; - type SetValue = Coin; + type SetValue = CompressedCoin; type GetValue = Self::SetValue; } @@ -93,7 +97,7 @@ impl Mappable for Messages { pub struct Transactions; impl Mappable for Transactions { - type Key = Bytes32; + type Key = TxId; type SetValue = Transaction; type GetValue = Self::SetValue; } diff --git a/crates/storage/src/transactional.rs b/crates/storage/src/transactional.rs index 4473b3d2170..b9d3f431858 100644 --- a/crates/storage/src/transactional.rs +++ b/crates/storage/src/transactional.rs @@ -2,53 +2,63 @@ use crate::Result as StorageResult; -/// The type is transactional and holds uncommitted state. -pub trait Transactional: AsRef + AsMut + Send + Sync { +/// The types is transactional and may create `StorageTransaction`. +pub trait Transactional { + /// Creates and returns the storage transaction. + fn transaction(&self) -> StorageTransaction; +} + +/// The type is storage transaction and holds uncommitted state. +pub trait Transaction: + AsRef + AsMut + Send + Sync +{ /// Commits the pending state changes into the storage. fn commit(&mut self) -> StorageResult<()>; } /// The storage transaction for the `Storage` type. -pub struct StorageTransaction { - transaction: Box>, +pub struct StorageTransaction { + transaction: Box>, } impl StorageTransaction { /// Create a new storage transaction. - pub fn new + 'static>(t: T) -> Self { + pub fn new + 'static>(t: T) -> Self { Self { transaction: Box::new(t), } } } -impl Transactional for StorageTransaction { +impl Transaction for StorageTransaction { fn commit(&mut self) -> StorageResult<()> { self.transaction.commit() } } -impl core::fmt::Debug for StorageTransaction { +impl core::fmt::Debug + for StorageTransaction +{ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.debug_struct("StorageTransaction") - .field("database", (*self.transaction).as_ref()) + .field("database", &self.transaction.as_ref().as_ref()) .finish() } } -impl AsRef for StorageTransaction { +impl AsRef for StorageTransaction { fn as_ref(&self) -> &Storage { (*self.transaction).as_ref() } } -impl AsMut for StorageTransaction { +impl AsMut for StorageTransaction { fn as_mut(&mut self) -> &mut Storage { (*self.transaction).as_mut() } } -impl StorageTransaction { +impl StorageTransaction { /// Committing of the state consumes `Self`. pub fn commit(mut self) -> StorageResult<()> { self.transaction.commit() diff --git a/crates/types/src/blockchain/block.rs b/crates/types/src/blockchain/block.rs index cee2f891d9e..08e2f7e30eb 100644 --- a/crates/types/src/blockchain/block.rs +++ b/crates/types/src/blockchain/block.rs @@ -16,11 +16,11 @@ use super::{ use crate::{ fuel_tx::{ Transaction, + TxId, UniqueIdentifier, }, fuel_types::{ bytes::SerializableVec, - Bytes32, MessageId, }, }; @@ -37,7 +37,7 @@ pub struct Block { } /// Compressed version of the fuel `Block`. -pub type CompressedBlock = Block; +pub type CompressedBlock = Block; /// Fuel block with all transaction data included /// but without any data generated. @@ -202,7 +202,7 @@ impl From for PartialFuelBlock { #[cfg(any(test, feature = "test-helpers"))] impl CompressedBlock { /// Create a compressed header for testing. This does not generate fields. - pub fn test(header: BlockHeader, transactions: Vec) -> Self { + pub fn test(header: BlockHeader, transactions: Vec) -> Self { Self { header, transactions, diff --git a/crates/types/src/blockchain/primitives.rs b/crates/types/src/blockchain/primitives.rs index dc7038c2d8b..804a0f90786 100644 --- a/crates/types/src/blockchain/primitives.rs +++ b/crates/types/src/blockchain/primitives.rs @@ -69,6 +69,11 @@ impl BlockId { // Without this, the signature would be using a hash of the id making it more // difficult to verify. } + + /// Represents `BlockId` as slice of bytes. + pub fn as_slice(&self) -> &[u8] { + self.0.as_slice() + } } /// Block height diff --git a/crates/types/src/entities.rs b/crates/types/src/entities.rs index 3ab8c88ab02..766e16a6088 100644 --- a/crates/types/src/entities.rs +++ b/crates/types/src/entities.rs @@ -2,3 +2,4 @@ pub mod coin; pub mod message; +pub mod resource; diff --git a/crates/types/src/entities/coin.rs b/crates/types/src/entities/coin.rs index 29fbe471a17..1d7e5ca3a8a 100644 --- a/crates/types/src/entities/coin.rs +++ b/crates/types/src/entities/coin.rs @@ -3,6 +3,7 @@ use crate::{ blockchain::primitives::BlockHeight, fuel_asm::Word, + fuel_tx::UtxoId, fuel_types::{ Address, AssetId, @@ -16,6 +17,8 @@ use crate::{ #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[derive(Debug, Clone)] pub struct Coin { + /// The coin utxo id. + pub utxo_id: UtxoId, /// The address with permission to spend this coin pub owner: Address, /// Amount of coins @@ -31,6 +34,56 @@ pub struct Coin { pub block_created: BlockHeight, } +impl Coin { + /// Compress the coin to minimize the serialized size. + pub fn compress(self) -> CompressedCoin { + CompressedCoin { + owner: self.owner, + amount: self.amount, + asset_id: self.asset_id, + maturity: self.maturity, + status: self.status, + block_created: self.block_created, + } + } +} + +/// The compressed version of the `Coin` with minimum fields required for +/// the proper work of the blockchain. +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Debug, Clone)] +pub struct CompressedCoin { + /// The address with permission to spend this coin + pub owner: Address, + /// Amount of coins + pub amount: Word, + /// Different incompatible coins can coexist with different asset ids. + /// This is the "color" of the coin. + pub asset_id: AssetId, + /// This coin cannot be spent until the given height + pub maturity: BlockHeight, + // TODO: Remove `status` when we will not use it for API functionality. + /// Whether a coin has been spent or not + pub status: CoinStatus, + /// Which block this coin was created in + pub block_created: BlockHeight, +} + +impl CompressedCoin { + /// Uncompress the coin. + pub fn uncompress(self, utxo_id: UtxoId) -> Coin { + Coin { + utxo_id, + owner: self.owner, + amount: self.amount, + asset_id: self.asset_id, + maturity: self.maturity, + status: self.status, + block_created: self.block_created, + } + } +} + /// Whether a coin has been spent or not #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[derive(Debug, Copy, Clone, Eq, PartialOrd, PartialEq)] diff --git a/crates/types/src/entities/resource.rs b/crates/types/src/entities/resource.rs new file mode 100644 index 00000000000..64931ca83a3 --- /dev/null +++ b/crates/types/src/entities/resource.rs @@ -0,0 +1,49 @@ +//! The resources module is an aggregation of all possible spendable entities(utxos, messages, etc). + +use crate::{ + entities::{ + coin::Coin, + message::Message, + }, + fuel_asm::Word, + fuel_tx::{ + AssetId, + MessageId, + UtxoId, + }, +}; + +/// The id of the resource. +#[allow(missing_docs)] +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum ResourceId { + Utxo(UtxoId), + Message(MessageId), +} + +/// The primary type of spent or not spent resources(coins, messages, etc). The not spent resource +/// can be used as a source of information for the creation of the transaction's input. +#[allow(missing_docs)] +#[derive(Debug)] +pub enum Resource { + Coin(Coin), + Message(Message), +} + +impl Resource { + /// Return the amount of the resource. + pub fn amount(&self) -> &Word { + match self { + Resource::Coin(coin) => &coin.amount, + Resource::Message(message) => &message.amount, + } + } + + /// Return the `AssetId` of the resource. + pub fn asset_id(&self) -> &AssetId { + match self { + Resource::Coin(coin) => &coin.asset_id, + Resource::Message(_) => &AssetId::BASE, + } + } +} diff --git a/crates/types/src/services.rs b/crates/types/src/services.rs index 87d85ef0ad3..a2cb62d622f 100644 --- a/crates/types/src/services.rs +++ b/crates/types/src/services.rs @@ -1,6 +1,7 @@ //! Types for specific services pub mod executor; +pub mod graphql_api; pub mod p2p; pub mod txpool; diff --git a/crates/types/src/services/graphql_api.rs b/crates/types/src/services/graphql_api.rs new file mode 100644 index 00000000000..b38d73e0e03 --- /dev/null +++ b/crates/types/src/services/graphql_api.rs @@ -0,0 +1,23 @@ +//! Types related to GraphQL API service. + +use crate::fuel_types::{ + Address, + AssetId, + ContractId, +}; + +/// The cumulative balance(`amount`) of the `Owner` of `asset_id`. +pub struct Balance { + /// Owner of the asset. + pub owner: Owner, + /// The cumulative amount of the asset. + pub amount: u64, + /// The identifier of the asset. + pub asset_id: AssetId, +} + +/// The alias for the `Balance` of the address. +pub type AddressBalance = Balance
; + +/// The alias for the `Balance` of the contract. +pub type ContractBalance = Balance; diff --git a/tests/tests/blocks.rs b/tests/tests/blocks.rs index 795912c3b15..4a8fe1eb814 100644 --- a/tests/tests/blocks.rs +++ b/tests/tests/blocks.rs @@ -29,7 +29,6 @@ use fuel_core_types::{ consensus::Consensus, }, fuel_tx::*, - fuel_types::Bytes32, secrecy::ExposeSecret, tai64::Tai64, }; @@ -46,11 +45,9 @@ async fn block() { let block = CompressedBlock::default(); let id = block.id(); let mut db = Database::default(); - db.storage::() - .insert(&id.into(), &block) - .unwrap(); + db.storage::().insert(&id, &block).unwrap(); db.storage::() - .insert(&id.into(), &Consensus::PoA(Default::default())) + .insert(&id, &Consensus::PoA(Default::default())) .unwrap(); // setup server & client @@ -89,6 +86,7 @@ async fn get_genesis_block() { #[tokio::test] async fn produce_block() { let config = Config::local_node(); + let srv = FuelService::from_database(Database::default(), config.clone()) .await .unwrap(); @@ -222,11 +220,11 @@ async fn produce_block_custom_time() { assert_eq!(5, new_height); - assert_eq!(db.block_time(1).unwrap().0, 100); - assert_eq!(db.block_time(2).unwrap().0, 110); - assert_eq!(db.block_time(3).unwrap().0, 120); - assert_eq!(db.block_time(4).unwrap().0, 130); - assert_eq!(db.block_time(5).unwrap().0, 140); + assert_eq!(db.block_time(1u32.into()).unwrap().0, 100); + assert_eq!(db.block_time(2u32.into()).unwrap().0, 110); + assert_eq!(db.block_time(3u32.into()).unwrap().0, 120); + assert_eq!(db.block_time(4u32.into()).unwrap().0, 130); + assert_eq!(db.block_time(5u32.into()).unwrap().0, 140); } #[tokio::test] @@ -318,11 +316,9 @@ async fn block_connection_5( let mut db = Database::default(); for block in blocks { let id = block.id(); - db.storage::() - .insert(&id.into(), &block) - .unwrap(); + db.storage::().insert(&id, &block).unwrap(); db.storage::() - .insert(&id.into(), &Consensus::PoA(Default::default())) + .insert(&id, &Consensus::PoA(Default::default())) .unwrap(); } diff --git a/tests/tests/coin.rs b/tests/tests/coin.rs index afc861add22..1e4af0775de 100644 --- a/tests/tests/coin.rs +++ b/tests/tests/coin.rs @@ -52,26 +52,22 @@ async fn first_5_coins( let owner = Address::default(); // setup test data in the node - let coins: Vec<(UtxoId, fuel_core_types::entities::coin::Coin)> = (1..10usize) - .map(|i| { - let coin = fuel_core_types::entities::coin::Coin { - owner, - amount: i as Word, - asset_id: Default::default(), - maturity: Default::default(), - status: fuel_core_types::entities::coin::CoinStatus::Unspent, - block_created: Default::default(), - }; - - let utxo_id = UtxoId::new(Bytes32::from([i as u8; 32]), 0); - (utxo_id, coin) + let coins: Vec<_> = (1..10usize) + .map(|i| Coin { + utxo_id: UtxoId::new(Bytes32::from([i as u8; 32]), 0), + owner, + amount: i as Word, + asset_id: Default::default(), + maturity: Default::default(), + status: fuel_core_types::entities::coin::CoinStatus::Unspent, + block_created: Default::default(), }) .collect(); let mut db = Database::default(); - for (utxo_id, coin) in coins { - db.storage::() - .insert(&utxo_id, &coin) + for coin in coins { + db.storage::() + .insert(&coin.utxo_id.clone(), &coin.compress()) .unwrap(); } @@ -104,25 +100,23 @@ async fn only_asset_id_filtered_coins() { let asset_id = AssetId::new([1u8; 32]); // setup test data in the node - let coins: Vec<(UtxoId, Coin)> = (1..10usize) - .map(|i| { - let coin = Coin { - owner, - amount: i as Word, - asset_id: if i <= 5 { asset_id } else { Default::default() }, - maturity: Default::default(), - status: CoinStatus::Unspent, - block_created: Default::default(), - }; - - let utxo_id = UtxoId::new(Bytes32::from([i as u8; 32]), 0); - (utxo_id, coin) + let coins: Vec<_> = (1..10usize) + .map(|i| Coin { + utxo_id: UtxoId::new(Bytes32::from([i as u8; 32]), 0), + owner, + amount: i as Word, + asset_id: if i <= 5 { asset_id } else { Default::default() }, + maturity: Default::default(), + status: CoinStatus::Unspent, + block_created: Default::default(), }) .collect(); let mut db = Database::default(); - for (id, coin) in coins { - db.storage::().insert(&id, &coin).unwrap(); + for coin in coins { + db.storage::() + .insert(&coin.utxo_id.clone(), &coin.compress()) + .unwrap(); } // setup server & client @@ -159,29 +153,27 @@ async fn get_unspent_and_spent_coins( #[values(AssetId::from([1u8; 32]), AssetId::from([32u8; 32]))] asset_id: AssetId, ) { // setup test data in the node - let coins: Vec<(UtxoId, Coin)> = (1..11usize) - .map(|i| { - let coin = Coin { - owner, - amount: i as Word, - asset_id, - maturity: Default::default(), - status: if i <= 5 { - CoinStatus::Unspent - } else { - CoinStatus::Spent - }, - block_created: Default::default(), - }; - - let utxo_id = UtxoId::new(Bytes32::from([i as u8; 32]), 0); - (utxo_id, coin) + let coins: Vec = (1..11usize) + .map(|i| Coin { + utxo_id: UtxoId::new(Bytes32::from([i as u8; 32]), 0), + owner, + amount: i as Word, + asset_id, + maturity: Default::default(), + status: if i <= 5 { + CoinStatus::Unspent + } else { + CoinStatus::Spent + }, + block_created: Default::default(), }) .collect(); let mut db = Database::default(); - for (id, coin) in coins { - db.storage::().insert(&id, &coin).unwrap(); + for coin in coins { + db.storage::() + .insert(&coin.utxo_id.clone(), &coin.compress()) + .unwrap(); } // setup server & client diff --git a/tests/tests/poa.rs b/tests/tests/poa.rs index f9ba1ea45ac..0be20cf157b 100644 --- a/tests/tests/poa.rs +++ b/tests/tests/poa.rs @@ -10,10 +10,12 @@ use fuel_core_client::client::{ FuelClient, }; use fuel_core_types::{ - blockchain::consensus::Consensus, + blockchain::{ + consensus::Consensus, + primitives::BlockId, + }, fuel_crypto::SecretKey, fuel_tx::Transaction, - fuel_types::Bytes32, secrecy::Secret, }; use rand::{ @@ -47,7 +49,7 @@ async fn can_get_sealed_block_from_poa_produced_block() { } }; - let block_id = Bytes32::from_str(&block_id).unwrap(); + let block_id = BlockId::from_str(&block_id).unwrap(); // check sealed block header is correct let sealed_block_header = db @@ -67,7 +69,7 @@ async fn can_get_sealed_block_from_poa_produced_block() { // check sealed block is correct let sealed_block = db - .get_sealed_block_by_id(&block_id.into()) + .get_sealed_block_by_id(&block_id) .unwrap() .expect("expected sealed header to be available");