From 82df7e2065fee76e98abe1278b6b8ebd01858191 Mon Sep 17 00:00:00 2001 From: xgreenx Date: Fri, 29 Dec 2023 18:17:22 +0100 Subject: [PATCH 01/11] Moved insertion of the blocks into the `BlockImporter` instead of the executor --- Cargo.lock | 1 + crates/fuel-core/src/service.rs | 3 + .../src/service/adapters/block_importer.rs | 46 +++-- .../service/adapters/consensus_module/poa.rs | 40 ++++- .../src/service/adapters/executor.rs | 9 +- .../src/service/adapters/producer.rs | 34 +++- crates/fuel-core/src/service/genesis.rs | 7 +- crates/fuel-core/src/service/sub_services.rs | 4 +- .../consensus_module/poa/src/ports.rs | 6 +- .../consensus_module/poa/src/service.rs | 55 ++++-- .../consensus_module/poa/src/service_test.rs | 6 +- .../service_test/manually_produce_tests.rs | 5 +- crates/services/executor/src/executor.rs | 12 -- crates/services/importer/Cargo.toml | 1 + crates/services/importer/src/importer.rs | 54 ++++-- crates/services/importer/src/importer/test.rs | 158 ++++++++++++------ crates/services/importer/src/ports.rs | 16 +- .../services/producer/src/block_producer.rs | 67 +++++++- .../producer/src/block_producer/tests.rs | 12 +- crates/services/producer/src/mocks.rs | 35 +--- crates/services/producer/src/ports.rs | 8 +- tests/Cargo.toml | 2 +- tests/tests/tx.rs | 115 ++++--------- 23 files changed, 423 insertions(+), 273 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 479ee9fa62..cc1e1924e7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2843,6 +2843,7 @@ version = "0.22.0" dependencies = [ "anyhow", "derive_more", + "fuel-core-chain-config", "fuel-core-metrics", "fuel-core-storage", "fuel-core-trace", diff --git a/crates/fuel-core/src/service.rs b/crates/fuel-core/src/service.rs index a6497bfc4a..3d5240cab2 100644 --- a/crates/fuel-core/src/service.rs +++ b/crates/fuel-core/src/service.rs @@ -19,6 +19,7 @@ pub use config::{ }; pub use fuel_core_services::Service as ServiceTrait; +use crate::service::adapters::PoAAdapter; pub use fuel_core_consensus_module::RelayerVerifierConfig; use self::adapters::BlockImporterAdapter; @@ -32,6 +33,8 @@ pub mod sub_services; #[derive(Clone)] pub struct SharedState { + /// The PoA adaptor around the shared state of the consensus module. + pub poa_adapter: PoAAdapter, /// The transaction pool shared state. pub txpool: fuel_core_txpool::service::SharedState, /// The P2P network shared state. diff --git a/crates/fuel-core/src/service/adapters/block_importer.rs b/crates/fuel-core/src/service/adapters/block_importer.rs index 3fc939a0f7..f44191d26c 100644 --- a/crates/fuel-core/src/service/adapters/block_importer.rs +++ b/crates/fuel-core/src/service/adapters/block_importer.rs @@ -6,6 +6,7 @@ use crate::{ VerifierAdapter, }, }; +use fuel_core_chain_config::ChainConfig; use fuel_core_importer::{ ports::{ BlockVerifier, @@ -18,14 +19,20 @@ use fuel_core_importer::{ }; use fuel_core_poa::ports::RelayerPort; use fuel_core_storage::{ - tables::SealedBlockConsensus, + tables::{ + FuelBlocks, + SealedBlockConsensus, + }, transactional::StorageTransaction, Result as StorageResult, StorageAsMut, }; use fuel_core_types::{ blockchain::{ - block::Block, + block::{ + Block, + CompressedBlock, + }, consensus::Consensus, primitives::{ BlockId, @@ -42,16 +49,20 @@ use fuel_core_types::{ }; use std::sync::Arc; -use super::MaybeRelayerAdapter; +use super::{ + MaybeRelayerAdapter, + TransactionsSource, +}; impl BlockImporterAdapter { pub fn new( config: Config, + chain_config: &ChainConfig, database: Database, executor: ExecutorAdapter, verifier: VerifierAdapter, ) -> Self { - let importer = Importer::new(config, database, executor, verifier); + let importer = Importer::new(config, chain_config, database, executor, verifier); importer.init_metrics(); Self { block_importer: Arc::new(importer), @@ -112,8 +123,8 @@ impl RelayerPort for MaybeRelayerAdapter { } impl ImporterDatabase for Database { - fn latest_block_height(&self) -> StorageResult { - self.latest_height() + fn latest_block_height(&self) -> StorageResult> { + Ok(self.ids_of_latest_block()?.map(|(height, _)| height)) } fn increase_tx_count(&self, new_txs_count: u64) -> StorageResult { @@ -126,10 +137,21 @@ impl ExecutorDatabase for Database { &mut self, block_id: &BlockId, consensus: &Consensus, - ) -> StorageResult> { - self.storage::() - .insert(block_id, consensus) - .map_err(Into::into) + ) -> StorageResult> { + Ok(self + .storage::() + .insert(block_id, consensus)? + .map(|_| ())) + } + fn block( + &mut self, + block_id: &BlockId, + block: &CompressedBlock, + ) -> StorageResult> { + Ok(self + .storage::() + .insert(block_id, block)? + .map(|_| ())) } } @@ -141,6 +163,8 @@ impl Executor for ExecutorAdapter { block: Block, ) -> ExecutorResult>> { - self._execute_without_commit(ExecutionTypes::Validation(block)) + self._execute_without_commit::(ExecutionTypes::Validation( + block, + )) } } diff --git a/crates/fuel-core/src/service/adapters/consensus_module/poa.rs b/crates/fuel-core/src/service/adapters/consensus_module/poa.rs index 46ed86fdc1..e53b37e11b 100644 --- a/crates/fuel-core/src/service/adapters/consensus_module/poa.rs +++ b/crates/fuel-core/src/service/adapters/consensus_module/poa.rs @@ -18,13 +18,19 @@ use fuel_core_poa::{ P2pPort, TransactionPool, }, - service::SharedState, + service::{ + Mode, + SharedState, + }, }; use fuel_core_services::stream::BoxStream; use fuel_core_storage::transactional::StorageTransaction; use fuel_core_types::{ fuel_asm::Word, - fuel_tx::TxId, + fuel_tx::{ + Transaction, + TxId, + }, fuel_types::BlockHeight, services::{ block_importer::{ @@ -45,6 +51,18 @@ impl PoAAdapter { pub fn new(shared_state: Option) -> Self { Self { shared_state } } + + pub async fn manually_produce_blocks( + &self, + start_time: Option, + mode: Mode, + ) -> anyhow::Result<()> { + self.shared_state + .as_ref() + .ok_or(anyhow!("The block production is disabled"))? + .manually_produce_block(start_time, mode) + .await + } } #[async_trait::async_trait] @@ -54,10 +72,7 @@ impl ConsensusModulePort for PoAAdapter { start_time: Option, number_of_blocks: u32, ) -> anyhow::Result<()> { - self.shared_state - .as_ref() - .ok_or(anyhow!("The block production is disabled"))? - .manually_produce_block(start_time, number_of_blocks) + self.manually_produce_blocks(start_time, Mode::Blocks { number_of_blocks }) .await } } @@ -91,11 +106,18 @@ impl fuel_core_poa::ports::BlockProducer for BlockProducerAdapter { &self, height: BlockHeight, block_time: Tai64, + txs: Option>, max_gas: Word, ) -> anyhow::Result>> { - self.block_producer - .produce_and_execute_block(height, block_time, max_gas) - .await + if let Some(txs) = txs { + self.block_producer + .produce_and_execute_block_transactions(height, block_time, txs, max_gas) + .await + } else { + self.block_producer + .produce_and_execute_block_txpool(height, block_time, max_gas) + .await + } } } diff --git a/crates/fuel-core/src/service/adapters/executor.rs b/crates/fuel-core/src/service/adapters/executor.rs index bb6f27083f..bb8e46042d 100644 --- a/crates/fuel-core/src/service/adapters/executor.rs +++ b/crates/fuel-core/src/service/adapters/executor.rs @@ -50,10 +50,13 @@ impl fuel_core_executor::ports::TransactionsSource for TransactionsSource { } impl ExecutorAdapter { - pub(crate) fn _execute_without_commit( + pub(crate) fn _execute_without_commit( &self, - block: ExecutionBlockWithSource, - ) -> ExecutorResult>> { + block: ExecutionBlockWithSource, + ) -> ExecutorResult>> + where + TxSource: fuel_core_executor::ports::TransactionsSource, + { let executor = Executor { database: self.relayer.database.clone(), relayer: self.relayer.clone(), diff --git a/crates/fuel-core/src/service/adapters/producer.rs b/crates/fuel-core/src/service/adapters/producer.rs index 5def3cc194..f966c48e33 100644 --- a/crates/fuel-core/src/service/adapters/producer.rs +++ b/crates/fuel-core/src/service/adapters/producer.rs @@ -11,6 +11,7 @@ use crate::{ sub_services::BlockProducerService, }, }; +use fuel_core_executor::executor::OnceTransactionsSource; use fuel_core_producer::ports::TxPool; use fuel_core_storage::{ not_found, @@ -25,7 +26,10 @@ use fuel_core_types::{ primitives, }, fuel_tx, - fuel_tx::Receipt, + fuel_tx::{ + Receipt, + Transaction, + }, fuel_types::{ BlockHeight, Bytes32, @@ -61,18 +65,38 @@ impl TxPool for TxPoolAdapter { } } -#[async_trait::async_trait] -impl fuel_core_producer::ports::Executor for ExecutorAdapter { +impl fuel_core_producer::ports::Executor for ExecutorAdapter { type Database = Database; - type TxSource = TransactionsSource; fn execute_without_commit( &self, - component: Components, + component: Components, ) -> ExecutorResult>> { self._execute_without_commit(ExecutionTypes::Production(component)) } +} + +impl fuel_core_producer::ports::Executor> for ExecutorAdapter { + type Database = Database; + + fn execute_without_commit( + &self, + component: Components>, + ) -> ExecutorResult>> { + let Components { + header_to_produce, + transactions_source, + gas_limit, + } = component; + self._execute_without_commit(ExecutionTypes::Production(Components { + header_to_produce, + transactions_source: OnceTransactionsSource::new(transactions_source), + gas_limit, + })) + } +} +impl fuel_core_producer::ports::DryRunner for ExecutorAdapter { fn dry_run( &self, block: Components, diff --git a/crates/fuel-core/src/service/genesis.rs b/crates/fuel-core/src/service/genesis.rs index 8da0fd4963..31c409b607 100644 --- a/crates/fuel-core/src/service/genesis.rs +++ b/crates/fuel-core/src/service/genesis.rs @@ -16,7 +16,6 @@ use fuel_core_storage::{ ContractsInfo, ContractsLatestUtxo, ContractsRawCode, - FuelBlocks, Messages, }, transactional::Transactional, @@ -125,11 +124,6 @@ fn import_genesis_block( &[], ); - let block_id = block.id(); - database.storage::().insert( - &block_id, - &block.compress(&config.chain_conf.consensus_parameters.chain_id), - )?; let consensus = Consensus::Genesis(genesis); let block = SealedBlock { entity: block, @@ -138,6 +132,7 @@ fn import_genesis_block( let importer = Importer::new( config.block_importer.clone(), + &config.chain_conf, original_database.clone(), (), (), diff --git a/crates/fuel-core/src/service/sub_services.rs b/crates/fuel-core/src/service/sub_services.rs index 36abbf6c54..0a027cc3d2 100644 --- a/crates/fuel-core/src/service/sub_services.rs +++ b/crates/fuel-core/src/service/sub_services.rs @@ -89,6 +89,7 @@ pub fn init_sub_services( let importer_adapter = BlockImporterAdapter::new( config.block_importer.clone(), + &config.chain_conf, database.clone(), executor.clone(), verifier.clone(), @@ -205,13 +206,14 @@ pub fn init_sub_services( Box::new(database.clone()), Box::new(tx_pool_adapter), Box::new(producer_adapter), - Box::new(poa_adapter), + Box::new(poa_adapter.clone()), Box::new(p2p_adapter), config.query_log_threshold_time, config.api_request_timeout, )?; let shared = SharedState { + poa_adapter, txpool: txpool.shared.clone(), #[cfg(feature = "p2p")] network: network.as_ref().map(|n| n.shared.clone()), diff --git a/crates/services/consensus_module/poa/src/ports.rs b/crates/services/consensus_module/poa/src/ports.rs index 967f64ef94..1866b871cb 100644 --- a/crates/services/consensus_module/poa/src/ports.rs +++ b/crates/services/consensus_module/poa/src/ports.rs @@ -9,7 +9,10 @@ use fuel_core_types::{ primitives::DaBlockHeight, }, fuel_asm::Word, - fuel_tx::TxId, + fuel_tx::{ + Transaction, + TxId, + }, fuel_types::{ BlockHeight, Bytes32, @@ -49,6 +52,7 @@ pub trait BlockProducer: Send + Sync { &self, height: BlockHeight, block_time: Tai64, + txs: Option>, max_gas: Word, ) -> anyhow::Result>>; } diff --git a/crates/services/consensus_module/poa/src/service.rs b/crates/services/consensus_module/poa/src/service.rs index 46b84e14a2..918304f2a1 100644 --- a/crates/services/consensus_module/poa/src/service.rs +++ b/crates/services/consensus_module/poa/src/service.rs @@ -42,7 +42,10 @@ use fuel_core_types::{ }, fuel_asm::Word, fuel_crypto::Signature, - fuel_tx::TxId, + fuel_tx::{ + Transaction, + TxId, + }, fuel_types::BlockHeight, secrecy::{ ExposeSecret, @@ -81,16 +84,13 @@ impl SharedState { pub async fn manually_produce_block( &self, start_time: Option, - number_of_blocks: u32, + mode: Mode, ) -> anyhow::Result<()> { let (sender, receiver) = oneshot::channel(); self.request_sender .send(Request::ManualBlocks(( - ManualProduction { - start_time, - number_of_blocks, - }, + ManualProduction { start_time, mode }, sender, ))) .await?; @@ -98,9 +98,16 @@ impl SharedState { } } +pub enum Mode { + /// Produces `number_of_blocks` blocks using `TxPool` as a source of transactions. + Blocks { number_of_blocks: u32 }, + /// Produces one block with the given transactions. + BlockWithTransactions(Vec), +} + struct ManualProduction { pub start_time: Option, - pub number_of_blocks: u32, + pub mode: Mode, } /// Requests accepted by the task. @@ -248,9 +255,10 @@ where &self, height: BlockHeight, block_time: Tai64, + txs: Option>, ) -> anyhow::Result>> { self.block_producer - .produce_and_execute_block(height, block_time, self.block_gas_limit) + .produce_and_execute_block(height, block_time, txs, self.block_gas_limit) .await } @@ -258,6 +266,7 @@ where self.produce_block( self.next_height(), self.next_time(RequestType::Trigger)?, + None, RequestType::Trigger, ) .await @@ -272,10 +281,28 @@ where } else { self.next_time(RequestType::Manual)? }; - for _ in 0..block_production.number_of_blocks { - self.produce_block(self.next_height(), block_time, RequestType::Manual) + match block_production.mode { + Mode::Blocks { number_of_blocks } => { + for _ in 0..number_of_blocks { + self.produce_block( + self.next_height(), + block_time, + None, + RequestType::Manual, + ) + .await?; + block_time = self.next_time(RequestType::Manual)?; + } + } + Mode::BlockWithTransactions(txs) => { + self.produce_block( + self.next_height(), + block_time, + Some(txs), + RequestType::Manual, + ) .await?; - block_time = self.next_time(RequestType::Manual)?; + } } Ok(()) } @@ -284,6 +311,7 @@ where &mut self, height: BlockHeight, block_time: Tai64, + txs: Option>, request_type: RequestType, ) -> anyhow::Result<()> { let last_block_created = Instant::now(); @@ -304,7 +332,10 @@ where tx_status, }, db_transaction, - ) = self.signal_produce_block(height, block_time).await?.into(); + ) = self + .signal_produce_block(height, block_time, txs) + .await? + .into(); let mut tx_ids_to_remove = Vec::with_capacity(skipped_transactions.len()); for (tx_id, err) in skipped_transactions { diff --git a/crates/services/consensus_module/poa/src/service_test.rs b/crates/services/consensus_module/poa/src/service_test.rs index 864a4e7d94..44525e3be6 100644 --- a/crates/services/consensus_module/poa/src/service_test.rs +++ b/crates/services/consensus_module/poa/src/service_test.rs @@ -123,7 +123,7 @@ impl TestContextBuilder { let mut producer = MockBlockProducer::default(); producer .expect_produce_and_execute_block() - .returning(|_, _, _| { + .returning(|_, _, _, _| { Ok(UncommittedResult::new( ExecutionResult { block: Default::default(), @@ -272,7 +272,7 @@ async fn remove_skipped_transactions() { block_producer .expect_produce_and_execute_block() .times(1) - .returning(move |_, _, _| { + .returning(move |_, _, _, _| { Ok(UncommittedResult::new( ExecutionResult { block: Default::default(), @@ -357,7 +357,7 @@ async fn does_not_produce_when_txpool_empty_in_instant_mode() { block_producer .expect_produce_and_execute_block() - .returning(|_, _, _| panic!("Block production should not be called")); + .returning(|_, _, _, _| panic!("Block production should not be called")); let mut block_importer = MockBlockImporter::default(); diff --git a/crates/services/consensus_module/poa/src/service_test/manually_produce_tests.rs b/crates/services/consensus_module/poa/src/service_test/manually_produce_tests.rs index 47bb8d5e30..3699fffb39 100644 --- a/crates/services/consensus_module/poa/src/service_test/manually_produce_tests.rs +++ b/crates/services/consensus_module/poa/src/service_test/manually_produce_tests.rs @@ -1,3 +1,4 @@ +use crate::service::Mode; use fuel_core_types::{ blockchain::block::Block, tai64::Tai64, @@ -82,7 +83,7 @@ async fn can_manually_produce_block( let mut producer = MockBlockProducer::default(); producer .expect_produce_and_execute_block() - .returning(|_, time, _| { + .returning(|_, time, _, _| { let mut block = Block::default(); block.header_mut().consensus.time = time; block.header_mut().recalculate_metadata(); @@ -101,7 +102,7 @@ async fn can_manually_produce_block( ctx.service .shared - .manually_produce_block(Some(start_time), number_of_blocks) + .manually_produce_block(Some(start_time), Mode::Blocks { number_of_blocks }) .await .unwrap(); for tx in txs { diff --git a/crates/services/executor/src/executor.rs b/crates/services/executor/src/executor.rs index 1570679341..1a9f5cdb27 100644 --- a/crates/services/executor/src/executor.rs +++ b/crates/services/executor/src/executor.rs @@ -12,7 +12,6 @@ use fuel_core_storage::{ Coins, ContractsInfo, ContractsLatestUtxo, - FuelBlocks, Messages, Receipts, SpentMessages, @@ -458,17 +457,6 @@ where // ------------ GraphQL API Functionality END ------------ - // insert block into database - block_st_transaction - .as_mut() - .storage::() - .insert( - &finalized_block_id, - &result - .block - .compress(&self.config.consensus_parameters.chain_id), - )?; - // Get the complete fuel block. Ok(UncommittedResult::new(result, block_st_transaction)) } diff --git a/crates/services/importer/Cargo.toml b/crates/services/importer/Cargo.toml index a8b359ceb7..7cd9384042 100644 --- a/crates/services/importer/Cargo.toml +++ b/crates/services/importer/Cargo.toml @@ -12,6 +12,7 @@ description = "Fuel Block Importer" [dependencies] anyhow = { workspace = true } derive_more = { workspace = true } +fuel-core-chain-config = { workspace = true } fuel-core-metrics = { workspace = true } fuel-core-storage = { workspace = true } fuel-core-types = { workspace = true } diff --git a/crates/services/importer/src/importer.rs b/crates/services/importer/src/importer.rs index ca1256005b..c3844cf235 100644 --- a/crates/services/importer/src/importer.rs +++ b/crates/services/importer/src/importer.rs @@ -7,11 +7,12 @@ use crate::{ }, Config, }; +use fuel_core_chain_config::ChainConfig; use fuel_core_metrics::importer::importer_metrics; use fuel_core_storage::{ + not_found, transactional::StorageTransaction, Error as StorageError, - IsNotFound, }; use fuel_core_types::{ blockchain::{ @@ -22,7 +23,10 @@ use fuel_core_types::{ primitives::BlockId, SealedBlock, }, - fuel_types::BlockHeight, + fuel_types::{ + BlockHeight, + ChainId, + }, services::{ block_importer::{ ImportResult, @@ -59,8 +63,8 @@ pub enum Error { )] InvalidUnderlyingDatabaseGenesisState, #[display(fmt = "The wrong state of database after execution of the block.\ - The actual height is {_1}, when the next expected height is {_0}.")] - InvalidDatabaseStateAfterExecution(BlockHeight, BlockHeight), + The actual height is {_1:?}, when the next expected height is {_0:?}.")] + InvalidDatabaseStateAfterExecution(Option, Option), #[display(fmt = "Got overflow during increasing the height.")] Overflow, #[display(fmt = "The non-generic block can't have zero height.")] @@ -96,7 +100,7 @@ impl From for anyhow::Error { #[cfg(test)] impl PartialEq for Error { fn eq(&self, other: &Self) -> bool { - format!("{self:?}") == format!("{other:?}") + format!("{self}") == format!("{other}") } } @@ -104,18 +108,26 @@ pub struct Importer { database: D, executor: E, verifier: V, + chain_id: ChainId, broadcast: broadcast::Sender>, guard: tokio::sync::Semaphore, } impl Importer { - pub fn new(config: Config, database: D, executor: E, verifier: V) -> Self { + pub fn new( + config: Config, + chain_config: &ChainConfig, + database: D, + executor: E, + verifier: V, + ) -> Self { let (broadcast, _) = broadcast::channel(config.max_block_notify_buffer); Self { database, executor, verifier, + chain_id: chain_config.consensus_parameters.chain_id, broadcast, guard: tokio::sync::Semaphore::new(1), } @@ -196,9 +208,9 @@ where // database height + 1. let expected_next_height = match consensus { Consensus::Genesis(_) => { - let result = self.database.latest_block_height(); - let found = !result.is_not_found(); - // Because the genesis block is not committed, it should return non found error. + let result = self.database.latest_block_height()?; + let found = result.is_some(); + // Because the genesis block is not committed, it should return `None`. // If we find the latest height, something is wrong with the state of the database. if found { return Err(Error::InvalidUnderlyingDatabaseGenesisState) @@ -210,7 +222,10 @@ where return Err(Error::ZeroNonGenericHeight) } - let last_db_height = self.database.latest_block_height()?; + let last_db_height = self + .database + .latest_block_height()? + .ok_or(not_found!("Latest block height"))?; last_db_height .checked_add(1u32) .ok_or(Error::Overflow)? @@ -228,15 +243,19 @@ where let db_after_execution = db_tx.as_mut(); // Importer expects that `UncommittedResult` contains the result of block - // execution(It includes the block itself). + // execution without block itself. + let expected_height = self.database.latest_block_height()?; let actual_height = db_after_execution.latest_block_height()?; - if expected_next_height != actual_height { + if expected_height != actual_height { return Err(Error::InvalidDatabaseStateAfterExecution( - expected_next_height, + expected_height, actual_height, )) } + db_after_execution + .block(&block_id, &block.compress(&self.chain_id))? + .should_be_unique(&expected_next_height)?; db_after_execution .seal_block(&block_id, &result.sealed_block.consensus)? .should_be_unique(&expected_next_height)?; @@ -252,7 +271,7 @@ where importer_metrics().total_txs_count.set(total_txs as i64); importer_metrics() .block_height - .set(*actual_height.deref() as i64); + .set(*actual_next_height.deref() as i64); let current_time = SystemTime::now() .duration_since(UNIX_EPOCH) .unwrap() @@ -273,8 +292,11 @@ where // Errors are optimistically handled via fallback to default values since the metrics // should get updated regularly anyways and these errors will be discovered and handled // correctly in more mission critical areas (such as _commit_result) - let current_block_height = - self.database.latest_block_height().unwrap_or_default(); + let current_block_height = self + .database + .latest_block_height() + .unwrap_or_default() + .unwrap_or_default(); let total_tx_count = self.database.increase_tx_count(0).unwrap_or_default(); importer_metrics() diff --git a/crates/services/importer/src/importer/test.rs b/crates/services/importer/src/importer/test.rs index 24db5d043c..fe5effb25a 100644 --- a/crates/services/importer/src/importer/test.rs +++ b/crates/services/importer/src/importer/test.rs @@ -10,7 +10,6 @@ use crate::{ }; use anyhow::anyhow; use fuel_core_storage::{ - not_found, transactional::{ StorageTransaction, Transaction as TransactionTrait, @@ -20,7 +19,10 @@ use fuel_core_storage::{ }; use fuel_core_types::{ blockchain::{ - block::Block, + block::{ + Block, + CompressedBlock, + }, consensus::Consensus, primitives::BlockId, SealedBlock, @@ -50,7 +52,7 @@ mockall::mock! { pub Database {} impl ImporterDatabase for Database { - fn latest_block_height(&self) -> StorageResult; + fn latest_block_height(&self) -> StorageResult>; fn increase_tx_count(&self, new_txs_count: u64) -> StorageResult; } @@ -59,7 +61,10 @@ mockall::mock! { &mut self, block_id: &BlockId, consensus: &Consensus, - ) -> StorageResult>; + ) -> StorageResult>; + + fn block(&mut self, block_id: &BlockId, block: &CompressedBlock) + -> StorageResult>; } impl TransactionTrait for Database { @@ -109,30 +114,38 @@ fn poa_block(height: u32) -> SealedBlock { fn underlying_db(result: R) -> impl Fn() -> MockDatabase where - R: Fn() -> StorageResult + Send + Clone + 'static, + R: Fn() -> StorageResult> + Send + Clone + 'static, { move || { let result = result.clone(); let mut db = MockDatabase::default(); db.expect_latest_block_height() - .returning(move || result().map(Into::into)); + .returning(move || result().map(|v| v.map(Into::into))); db.expect_increase_tx_count().returning(Ok); db } } -fn executor_db(height: H, seal: S, commits: usize) -> impl Fn() -> MockDatabase +fn executor_db( + height: H, + seal: S, + block: B, + commits: usize, +) -> impl Fn() -> MockDatabase where - H: Fn() -> StorageResult + Send + Clone + 'static, - S: Fn() -> StorageResult> + Send + Clone + 'static, + H: Fn() -> StorageResult> + Send + Clone + 'static, + S: Fn() -> StorageResult> + Send + Clone + 'static, + B: Fn() -> StorageResult> + Send + Clone + 'static, { move || { let height = height.clone(); let seal = seal.clone(); + let block = block.clone(); let mut db = MockDatabase::default(); db.expect_latest_block_height() - .returning(move || height().map(Into::into)); + .returning(move || height().map(|v| v.map(Into::into))); db.expect_seal_block().returning(move |_, _| seal()); + db.expect_block().returning(move |_, _| block()); db.expect_commit().times(commits).returning(|| Ok(())); db.expect_increase_tx_count().returning(Ok); db @@ -143,16 +156,12 @@ fn ok(entity: T) -> impl Fn() -> Result + Clone { move || Ok(entity.clone()) } -fn not_found() -> StorageResult { - Err(not_found!("Not found")) -} - fn storage_failure() -> StorageResult { Err(StorageError::Other(anyhow!("Some failure"))) } fn storage_failure_error() -> Error { - Error::StorageError(StorageError::Other(anyhow!("Some failure"))) + storage_failure::<()>().unwrap_err().into() } fn ex_result(height: u32, skipped_transactions: usize) -> MockExecutionResult { @@ -200,7 +209,7 @@ fn verification_failure() -> anyhow::Result { } fn verification_failure_error() -> Error { - Error::FailedVerification(anyhow!("Not verified")) + Error::FailedVerification(verification_failure::<()>().unwrap_err()) } fn verifier(result: R) -> MockBlockVerifier @@ -219,46 +228,53 @@ where //////////////// //////////// Genesis Block /////////// //////////////// #[test_case( genesis(0), - underlying_db(not_found), - executor_db(ok(0), ok(None), 1) + underlying_db(ok(None)), + executor_db(ok(None), ok(None), ok(None), 1) => Ok(()); "successfully imports genesis block when latest block not found" )] #[test_case( genesis(113), - underlying_db(not_found), - executor_db(ok(113), ok(None), 1) + underlying_db(ok(None)), + executor_db(ok(None), ok(None), ok(None), 1) => Ok(()); "successfully imports block at arbitrary height when executor db expects it and last block not found" )] #[test_case( genesis(0), underlying_db(storage_failure), - executor_db(ok(0), ok(None), 0) - => Err(Error::InvalidUnderlyingDatabaseGenesisState); + executor_db(ok(Some(0)), ok(None), ok(None), 0) + => Err(storage_failure_error()); "fails to import genesis when underlying database fails" )] #[test_case( genesis(0), - underlying_db(ok(0)), - executor_db(ok(0), ok(None), 0) + underlying_db(ok(Some(0))), + executor_db(ok(Some(0)), ok(None), ok(None), 0) => Err(Error::InvalidUnderlyingDatabaseGenesisState); "fails to import genesis block when already exists" )] #[test_case( genesis(1), - underlying_db(not_found), - executor_db(ok(0), ok(None), 0) - => Err(Error::InvalidDatabaseStateAfterExecution(1u32.into(), 0u32.into())); + underlying_db(ok(None)), + executor_db(ok(Some(0)), ok(None), ok(None), 0) + => Err(Error::InvalidDatabaseStateAfterExecution(None, Some(0u32.into()))); "fails to import genesis block when next height is not 0" )] #[test_case( genesis(0), - underlying_db(not_found), - executor_db(ok(0), ok(Some(Consensus::Genesis(Default::default()))), 0) + underlying_db(ok(None)), + executor_db(ok(None), ok(Some(())), ok(None), 0) => Err(Error::NotUnique(0u32.into())); "fails to import genesis block when consensus exists for height 0" )] +#[test_case( + genesis(0), + underlying_db(ok(None)), + executor_db(ok(None), ok(None), ok(Some(())), 0) + => Err(Error::NotUnique(0u32.into())); + "fails to import genesis block when block exists for height 0" +)] fn commit_result_genesis( sealed_block: SealedBlock, underlying_db: impl Fn() -> MockDatabase, @@ -270,67 +286,81 @@ fn commit_result_genesis( //////////////////////////// PoA Block //////////////////////////// #[test_case( poa_block(1), - underlying_db(ok(0)), - executor_db(ok(1), ok(None), 1) + underlying_db(ok(Some(0))), + executor_db(ok(Some(0)), ok(None), ok(None), 1) => Ok(()); "successfully imports block at height 1 when latest block is genesis" )] #[test_case( poa_block(113), - underlying_db(ok(112)), - executor_db(ok(113), ok(None), 1) + underlying_db(ok(Some(112))), + executor_db(ok(Some(112)), ok(None), ok(None), 1) => Ok(()); "successfully imports block at arbitrary height when latest block height is one fewer and executor db expects it" )] #[test_case( poa_block(0), - underlying_db(ok(0)), - executor_db(ok(1), ok(None), 0) + underlying_db(ok(Some(0))), + executor_db(ok(Some(1)), ok(None), ok(None), 0) => Err(Error::ZeroNonGenericHeight); "fails to import PoA block with height 0" )] #[test_case( poa_block(113), - underlying_db(ok(111)), - executor_db(ok(113), ok(None), 0) + underlying_db(ok(Some(111))), + executor_db(ok(Some(113)), ok(None), ok(None), 0) => Err(Error::IncorrectBlockHeight(112u32.into(), 113u32.into())); "fails to import block at height 113 when latest block height is 111" )] #[test_case( poa_block(113), - underlying_db(ok(114)), - executor_db(ok(113), ok(None), 0) + underlying_db(ok(Some(114))), + executor_db(ok(Some(113)), ok(None), ok(None), 0) => Err(Error::IncorrectBlockHeight(115u32.into(), 113u32.into())); "fails to import block at height 113 when latest block height is 114" )] #[test_case( poa_block(113), - underlying_db(ok(112)), - executor_db(ok(114), ok(None), 0) - => Err(Error::InvalidDatabaseStateAfterExecution(113u32.into(), 114u32.into())); + underlying_db(ok(Some(112))), + executor_db(ok(Some(114)), ok(None), ok(None), 0) + => Err(Error::InvalidDatabaseStateAfterExecution(Some(112u32.into()), Some(114u32.into()))); "fails to import block 113 when executor db expects height 114" )] #[test_case( poa_block(113), - underlying_db(ok(112)), - executor_db(storage_failure, ok(None), 0) + underlying_db(ok(Some(112))), + executor_db(storage_failure, ok(None), ok(None), 0) => Err(storage_failure_error()); "fails to import block when executor db fails to find latest block" )] #[test_case( poa_block(113), - underlying_db(ok(112)), - executor_db(ok(113), ok(Some(Consensus::PoA(Default::default()))), 0) + underlying_db(ok(Some(112))), + executor_db(ok(Some(112)), ok(Some(())), ok(None), 0) => Err(Error::NotUnique(113u32.into())); "fails to import block when consensus exists for block" )] #[test_case( poa_block(113), - underlying_db(ok(112)), - executor_db(ok(113), storage_failure, 0) + underlying_db(ok(Some(112))), + executor_db(ok(Some(112)), storage_failure, ok(None), 0) => Err(storage_failure_error()); "fails to import block when executor db fails to find consensus" )] +#[test_case( + poa_block(113), + underlying_db(ok(Some(112))), + executor_db(ok(Some(112)), ok(None), ok(Some(())), 0) + => Err(Error::NotUnique(113u32.into())); + "fails to import block when block exists" +)] +#[test_case( + poa_block(113), + underlying_db(ok(Some(112))), + executor_db(ok(Some(112)), ok(None), storage_failure, 0) + => Err(storage_failure_error()); + "fails to import block when executor db fails to find block" +)] fn commit_result_and_execute_and_commit_poa( sealed_block: SealedBlock, underlying_db: impl Fn() -> MockDatabase, @@ -357,7 +387,13 @@ fn commit_result_assert( executor_db: MockDatabase, ) -> Result<(), Error> { let expected_to_broadcast = sealed_block.clone(); - let importer = Importer::new(Default::default(), underlying_db, (), ()); + let importer = Importer::new( + Default::default(), + &Default::default(), + underlying_db, + (), + (), + ); let uncommitted_result = UncommittedResult::new( ImportResult::new_from_local(sealed_block, vec![]), StorageTransaction::new(executor_db), @@ -387,7 +423,13 @@ fn execute_and_commit_assert( verifier: MockBlockVerifier, ) -> Result<(), Error> { let expected_to_broadcast = sealed_block.clone(); - let importer = Importer::new(Default::default(), underlying_db, executor, verifier); + let importer = Importer::new( + Default::default(), + &Default::default(), + underlying_db, + executor, + verifier, + ); let mut imported_blocks = importer.subscribe(); let result = importer.execute_and_commit(sealed_block); @@ -408,7 +450,13 @@ fn execute_and_commit_assert( #[test] fn commit_result_fail_when_locked() { - let importer = Importer::new(Default::default(), MockDatabase::default(), (), ()); + let importer = Importer::new( + Default::default(), + &Default::default(), + MockDatabase::default(), + (), + (), + ); let uncommitted_result = UncommittedResult::new( ImportResult::default(), StorageTransaction::new(MockDatabase::default()), @@ -425,6 +473,7 @@ fn commit_result_fail_when_locked() { fn execute_and_commit_fail_when_locked() { let importer = Importer::new( Default::default(), + &Default::default(), MockDatabase::default(), MockExecutor::default(), MockBlockVerifier::default(), @@ -441,6 +490,7 @@ fn execute_and_commit_fail_when_locked() { fn one_lock_at_the_same_time() { let importer = Importer::new( Default::default(), + &Default::default(), MockDatabase::default(), MockExecutor::default(), MockBlockVerifier::default(), @@ -513,10 +563,10 @@ where let previous_height = expected_height.checked_sub(1).unwrap_or_default(); let execute_and_commit_result = execute_and_commit_assert( sealed_block, - underlying_db(ok(previous_height))(), + underlying_db(ok(Some(previous_height)))(), executor( block_after_execution, - executor_db(ok(expected_height), ok(None), commits)(), + executor_db(ok(Some(previous_height)), ok(None), ok(None), commits)(), ), verifier(verifier_result), ); @@ -535,6 +585,7 @@ where { let importer = Importer::new( Default::default(), + &Default::default(), MockDatabase::default(), executor(block_after_execution, MockDatabase::default()), verifier(verifier_result), @@ -547,6 +598,7 @@ where fn verify_and_execute_allowed_when_locked() { let importer = Importer::new( Default::default(), + &Default::default(), MockDatabase::default(), executor(ok(ex_result(13, 0)), MockDatabase::default()), verifier(ok(())), diff --git a/crates/services/importer/src/ports.rs b/crates/services/importer/src/ports.rs index ce0449a874..32b46cbc44 100644 --- a/crates/services/importer/src/ports.rs +++ b/crates/services/importer/src/ports.rs @@ -4,7 +4,10 @@ use fuel_core_storage::{ }; use fuel_core_types::{ blockchain::{ - block::Block, + block::{ + Block, + CompressedBlock, + }, consensus::Consensus, primitives::BlockId, }, @@ -32,7 +35,7 @@ pub trait Executor: Send + Sync { /// The database port used by the block importer. pub trait ImporterDatabase { /// Returns the latest block height. - fn latest_block_height(&self) -> StorageResult; + fn latest_block_height(&self) -> StorageResult>; /// Update metadata about the total number of transactions on the chain. /// Returns the total count after the update. fn increase_tx_count(&self, new_txs_count: u64) -> StorageResult; @@ -46,7 +49,14 @@ pub trait ExecutorDatabase: ImporterDatabase { &mut self, block_id: &BlockId, consensus: &Consensus, - ) -> StorageResult>; + ) -> StorageResult>; + + /// Inserts the `CompressedBlock` under the `block_id`. + fn block( + &mut self, + block_id: &BlockId, + block: &CompressedBlock, + ) -> StorageResult>; } #[cfg_attr(test, mockall::automock)] diff --git a/crates/services/producer/src/block_producer.rs b/crates/services/producer/src/block_producer.rs index 3e57c79419..93a5949c54 100644 --- a/crates/services/producer/src/block_producer.rs +++ b/crates/services/producer/src/block_producer.rs @@ -72,20 +72,21 @@ pub struct Producer { pub lock: Mutex<()>, } -impl - Producer +impl Producer where Database: ports::BlockProducerDatabase + 'static, - TxPool: ports::TxPool + 'static, - Executor: ports::Executor + 'static, { - /// Produces and execute block for the specified height - pub async fn produce_and_execute_block( + /// Produces and execute block for the specified height. + async fn produce_and_execute( &self, height: BlockHeight, block_time: Tai64, + tx_source: impl FnOnce(BlockHeight) -> TxSource, max_gas: Word, - ) -> anyhow::Result>> { + ) -> anyhow::Result>> + where + Executor: ports::Executor + 'static, + { // - get previous block info (hash, root, etc) // - select best da_height from relayer // - get available txs from txpool @@ -97,7 +98,7 @@ where // prevent simultaneous block production calls, the guard will drop at the end of this fn. let _production_guard = self.lock.lock().await; - let source = self.txpool.get_source(height); + let source = tx_source(height); let header = self.new_header(height, block_time).await?; @@ -107,7 +108,7 @@ where gas_limit: max_gas, }; - // Store the context string incase we error. + // Store the context string in case we error. let context_string = format!("Failed to produce block {height:?} due to execution failure"); let result = self @@ -119,7 +120,55 @@ where debug!("Produced block with result: {:?}", result.result()); Ok(result) } +} +impl + Producer +where + Database: ports::BlockProducerDatabase + 'static, + TxPool: ports::TxPool + 'static, + Executor: ports::Executor + 'static, +{ + /// Produces and execute block for the specified height with transactions from the `TxPool`. + pub async fn produce_and_execute_block_txpool( + &self, + height: BlockHeight, + block_time: Tai64, + max_gas: Word, + ) -> anyhow::Result>> { + self.produce_and_execute( + height, + block_time, + |height| self.txpool.get_source(height), + max_gas, + ) + .await + } +} + +impl Producer +where + Database: ports::BlockProducerDatabase + 'static, + Executor: ports::Executor, Database = ExecutorDB> + 'static, +{ + /// Produces and execute block for the specified height with `transactions`. + pub async fn produce_and_execute_block_transactions( + &self, + height: BlockHeight, + block_time: Tai64, + transactions: Vec, + max_gas: Word, + ) -> anyhow::Result>> { + self.produce_and_execute(height, block_time, |_| transactions, max_gas) + .await + } +} + +impl Producer +where + Database: ports::BlockProducerDatabase + 'static, + Executor: ports::DryRunner + 'static, +{ // TODO: Support custom `block_time` for `dry_run`. /// Simulate a transaction without altering any state. Does not aquire the production lock /// since it is basically a "read only" operation and shouldn't get in the way of normal diff --git a/crates/services/producer/src/block_producer/tests.rs b/crates/services/producer/src/block_producer/tests.rs index f9e959d16c..2263004c92 100644 --- a/crates/services/producer/src/block_producer/tests.rs +++ b/crates/services/producer/src/block_producer/tests.rs @@ -42,7 +42,7 @@ async fn cant_produce_at_genesis_height() { let producer = ctx.producer(); let err = producer - .produce_and_execute_block(0u32.into(), Tai64::now(), 1_000_000_000) + .produce_and_execute_block_txpool(0u32.into(), Tai64::now(), 1_000_000_000) .await .expect_err("expected failure"); @@ -58,7 +58,7 @@ async fn can_produce_initial_block() { let producer = ctx.producer(); let result = producer - .produce_and_execute_block(1u32.into(), Tai64::now(), 1_000_000_000) + .produce_and_execute_block_txpool(1u32.into(), Tai64::now(), 1_000_000_000) .await; assert!(result.is_ok()); @@ -93,7 +93,7 @@ async fn can_produce_next_block() { let ctx = TestContext::default_from_db(db); let producer = ctx.producer(); let result = producer - .produce_and_execute_block( + .produce_and_execute_block_txpool( prev_height .succ() .expect("The block height should be valid"), @@ -112,7 +112,7 @@ async fn cant_produce_if_no_previous_block() { let producer = ctx.producer(); let err = producer - .produce_and_execute_block(100u32.into(), Tai64::now(), 1_000_000_000) + .produce_and_execute_block_txpool(100u32.into(), Tai64::now(), 1_000_000_000) .await .expect_err("expected failure"); @@ -156,7 +156,7 @@ async fn cant_produce_if_previous_block_da_height_too_high() { let producer = ctx.producer(); let err = producer - .produce_and_execute_block( + .produce_and_execute_block_txpool( prev_height .succ() .expect("The block height should be valid"), @@ -187,7 +187,7 @@ async fn production_fails_on_execution_error() { let producer = ctx.producer(); let err = producer - .produce_and_execute_block(1u32.into(), Tai64::now(), 1_000_000_000) + .produce_and_execute_block_txpool(1u32.into(), Tai64::now(), 1_000_000_000) .await .expect_err("expected failure"); diff --git a/crates/services/producer/src/mocks.rs b/crates/services/producer/src/mocks.rs index 69ca3d482d..eadfcfed0d 100644 --- a/crates/services/producer/src/mocks.rs +++ b/crates/services/producer/src/mocks.rs @@ -20,8 +20,6 @@ use fuel_core_types::{ }, primitives::DaBlockHeight, }, - fuel_tx, - fuel_tx::Receipt, fuel_types::{ Address, BlockHeight, @@ -133,14 +131,12 @@ fn to_block(component: Components>) -> Block { Block::new(component.header_to_produce, transactions, &[]) } -impl Executor for MockExecutor { +impl Executor> for MockExecutor { type Database = MockDb; - /// The source of transaction used by the executor. - type TxSource = Vec; fn execute_without_commit( &self, - component: Components, + component: Components>, ) -> ExecutorResult>> { let block = to_block(component); // simulate executor inserting a block @@ -158,26 +154,16 @@ impl Executor for MockExecutor { StorageTransaction::new(self.0.clone()), )) } - - fn dry_run( - &self, - _block: Components, - _utxo_validation: Option, - ) -> ExecutorResult>> { - Ok(Default::default()) - } } pub struct FailingMockExecutor(pub Mutex>); -impl Executor for FailingMockExecutor { +impl Executor> for FailingMockExecutor { type Database = MockDb; - /// The source of transaction used by the executor. - type TxSource = Vec; fn execute_without_commit( &self, - component: Components, + component: Components>, ) -> ExecutorResult>> { // simulate an execution failure let mut err = self.0.lock().unwrap(); @@ -195,19 +181,6 @@ impl Executor for FailingMockExecutor { )) } } - - fn dry_run( - &self, - _block: Components, - _utxo_validation: Option, - ) -> ExecutorResult>> { - let mut err = self.0.lock().unwrap(); - if let Some(err) = err.take() { - Err(err) - } else { - Ok(Default::default()) - } - } } #[derive(Clone, Default, Debug)] diff --git a/crates/services/producer/src/ports.rs b/crates/services/producer/src/ports.rs index fb53df1934..1af44bc9d4 100644 --- a/crates/services/producer/src/ports.rs +++ b/crates/services/producer/src/ports.rs @@ -58,19 +58,19 @@ pub trait Relayer: Send + Sync { ) -> anyhow::Result; } -pub trait Executor: Send + Sync { +pub trait Executor: Send + Sync { /// The database used by the executor. type Database; - /// The source of transaction used by the executor. - type TxSource; /// Executes the block and returns the result of execution with uncommitted database /// transaction. fn execute_without_commit( &self, - component: Components, + component: Components, ) -> ExecutorResult>>; +} +pub trait DryRunner: Send + Sync { /// Executes the block without committing it to the database. During execution collects the /// receipts to return them. The `utxo_validation` field can be used to disable the validation /// of utxos during execution. diff --git a/tests/Cargo.toml b/tests/Cargo.toml index 9ed4728fb7..9faa23ec73 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -24,7 +24,7 @@ ethers = "2" fuel-core = { path = "../crates/fuel-core", default-features = false, features = ["test-helpers"] } fuel-core-benches = { path = "../benches" } fuel-core-client = { path = "../crates/client", features = ["test-helpers"] } -fuel-core-executor = { workspace = true, features = ["test-helpers"] } +fuel-core-executor = { workspace = true } fuel-core-p2p = { path = "../crates/services/p2p", features = ["test-helpers"], optional = true } fuel-core-poa = { path = "../crates/services/consensus_module/poa" } fuel-core-relayer = { path = "../crates/services/relayer", features = [ diff --git a/tests/tests/tx.rs b/tests/tests/tx.rs index 82948de6b0..da1db7b1be 100644 --- a/tests/tests/tx.rs +++ b/tests/tests/tx.rs @@ -1,9 +1,7 @@ use crate::helpers::TestContext; use fuel_core::{ - database::Database, schema::tx::receipt::all_receipts, service::{ - adapters::MaybeRelayerAdapter, Config, FuelService, }, @@ -16,20 +14,12 @@ use fuel_core_client::client::{ types::TransactionStatus, FuelClient, }; -use fuel_core_executor::executor::Executor; +use fuel_core_poa::service::Mode; use fuel_core_types::{ - blockchain::{ - block::PartialFuelBlock, - header::{ - ConsensusHeader, - PartialBlockHeader, - }, - }, fuel_asm::*, + fuel_crypto::SecretKey, fuel_tx::*, fuel_types::ChainId, - services::executor::ExecutionBlock, - tai64::Tai64, }; use itertools::Itertools; use rand::{ @@ -503,55 +493,30 @@ async fn get_transactions_by_owner_supports_cursor(direction: PageDirection) { #[tokio::test] async fn get_transactions_from_manual_blocks() { - let (executor, db) = get_executor_and_db(); - // get access to a client - let context = initialize_client(db).await; + let context = TestContext::new(100).await; // create 10 txs - let txs: Vec = (0..10).map(create_mock_tx).collect(); + let txs: Vec<_> = (0..10).map(create_mock_tx).collect(); // make 1st test block - let first_test_block = PartialFuelBlock { - header: PartialBlockHeader { - consensus: ConsensusHeader { - height: 1u32.into(), - time: Tai64::now(), - ..Default::default() - }, - ..Default::default() - }, - - // set the first 5 ids of the manually saved txs - transactions: txs.iter().take(5).cloned().collect(), - }; + let first_batch = txs.iter().take(5).cloned().collect(); + context + .srv + .shared + .poa_adapter + .manually_produce_blocks(None, Mode::BlockWithTransactions(first_batch)) + .await + .expect("Should produce first block with first 5 transactions."); // make 2nd test block - let second_test_block = PartialFuelBlock { - header: PartialBlockHeader { - consensus: ConsensusHeader { - height: 2u32.into(), - time: Tai64::now(), - ..Default::default() - }, - ..Default::default() - }, - // set the last 5 ids of the manually saved txs - transactions: txs.iter().skip(5).take(5).cloned().collect(), - }; - - // process blocks and save block height - executor - .execute_and_commit( - ExecutionBlock::Production(first_test_block), - Default::default(), - ) - .unwrap(); - executor - .execute_and_commit( - ExecutionBlock::Production(second_test_block), - Default::default(), - ) - .unwrap(); + let second_batch = txs.iter().skip(5).take(5).cloned().collect(); + context + .srv + .shared + .poa_adapter + .manually_produce_blocks(None, Mode::BlockWithTransactions(second_batch)) + .await + .expect("Should produce block with last 5 transactions."); // Query for first 4: [0, 1, 2, 3] let page_request_forwards = PaginationRequest { @@ -672,38 +637,18 @@ async fn get_owned_transactions() { assert_eq!(&charlie_txs, &[tx1, tx2, tx3]); } -fn get_executor_and_db() -> (Executor, Database) { - let db = Database::default(); - let relayer = MaybeRelayerAdapter { - database: db.clone(), - #[cfg(feature = "relayer")] - relayer_synced: None, - #[cfg(feature = "relayer")] - da_deploy_height: 0u64.into(), - }; - let executor = Executor { - relayer, - database: db.clone(), - config: Default::default(), - }; - - (executor, db) -} - -async fn initialize_client(db: Database) -> TestContext { - let config = Config::local_node(); - let srv = FuelService::from_database(db, config).await.unwrap(); - let client = FuelClient::from(srv.bound_address); - TestContext { - srv, - rng: StdRng::seed_from_u64(0x123), - client, - } -} - // add random val for unique tx fn create_mock_tx(val: u64) -> Transaction { + let mut rng = StdRng::seed_from_u64(val); + TransactionBuilder::script(val.to_be_bytes().to_vec(), Default::default()) - .add_random_fee_input() + .add_unsigned_coin_input( + SecretKey::random(&mut rng), + rng.gen(), + 1_000_000, + Default::default(), + Default::default(), + Default::default(), + ) .finalize_as_transaction() } From d4a4d0a1d26abe47270637c0d375fa1bac1c4747 Mon Sep 17 00:00:00 2001 From: xgreenx Date: Fri, 29 Dec 2023 18:44:14 +0100 Subject: [PATCH 02/11] Update CHANGELOG.md --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b2cf097802..e8ada0fcdd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/). Description of the upcoming release here. + +### Changed + +- [#1577](https://github.com/FuelLabs/fuel-core/pull/1577): Moved insertion of the blocks into the `BlockImporter` instead of the executor. + ## [Version 0.22.0] ### Added From 5033d8cd23b146069f35ed7ff678d11eb471a677 Mon Sep 17 00:00:00 2001 From: xgreenx Date: Fri, 29 Dec 2023 19:23:49 +0100 Subject: [PATCH 03/11] Cleanup --- crates/services/executor/src/ports.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/crates/services/executor/src/ports.rs b/crates/services/executor/src/ports.rs index 0c4c32a1de..dd221166a2 100644 --- a/crates/services/executor/src/ports.rs +++ b/crates/services/executor/src/ports.rs @@ -6,7 +6,6 @@ use fuel_core_storage::{ ContractsLatestUtxo, ContractsRawCode, ContractsState, - FuelBlocks, Messages, Receipts, SpentMessages, @@ -109,11 +108,9 @@ pub trait TxIdOwnerRecorder { // TODO: Remove `Clone` bound pub trait ExecutorDatabaseTrait: - StorageMutate - + StorageMutate + StorageMutate + StorageMutate - + MerkleRootStorage - + StorageInspect + + MerkleRootStorage + MessageIsSpent + StorageMutate + StorageMutate From ab88c67432d3dcda8ca0bc0edfad5dc20cf6f6b5 Mon Sep 17 00:00:00 2001 From: xgreenx Date: Fri, 29 Dec 2023 21:49:47 +0100 Subject: [PATCH 04/11] Moved insertion of the whole block with transactions --- CHANGELOG.md | 2 +- crates/fuel-core/src/database.rs | 2 + crates/fuel-core/src/database/storage.rs | 7 ++ crates/fuel-core/src/executor.rs | 37 +-------- .../src/service/adapters/block_importer.rs | 51 ++++++------ crates/services/executor/src/executor.rs | 12 +-- crates/services/executor/src/ports.rs | 4 +- crates/services/importer/src/importer.rs | 6 +- crates/services/importer/src/importer/test.rs | 83 ++++++------------- crates/services/importer/src/ports.rs | 28 +++---- crates/storage/src/tables.rs | 11 +++ 11 files changed, 96 insertions(+), 147 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e8ada0fcdd..5870b438e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ Description of the upcoming release here. ### Changed -- [#1577](https://github.com/FuelLabs/fuel-core/pull/1577): Moved insertion of the blocks into the `BlockImporter` instead of the executor. +- [#1577](https://github.com/FuelLabs/fuel-core/pull/1577): Moved insertion of sealed blocks into the `BlockImporter` instead of the executor. ## [Version 0.22.0] diff --git a/crates/fuel-core/src/database.rs b/crates/fuel-core/src/database.rs index d2fb65cfdd..29ace79dcd 100644 --- a/crates/fuel-core/src/database.rs +++ b/crates/fuel-core/src/database.rs @@ -150,6 +150,8 @@ pub enum Column { ContractsStateMerkleData = 23, /// See [`ContractsStateMerkleMetadata`](storage::ContractsStateMerkleMetadata) ContractsStateMerkleMetadata = 24, + /// See [`ProcessedTransactions`](storage::ProcessedTransactions) + ProcessedTransactions = 25, } impl Column { diff --git a/crates/fuel-core/src/database/storage.rs b/crates/fuel-core/src/database/storage.rs index 6ceab3a776..2c2c5333c6 100644 --- a/crates/fuel-core/src/database/storage.rs +++ b/crates/fuel-core/src/database/storage.rs @@ -3,6 +3,7 @@ use crate::database::{ Database, }; use fuel_core_storage::{ + tables::ProcessedTransactions, Error as StorageError, Mappable, MerkleRoot, @@ -160,6 +161,12 @@ impl DatabaseColumn for FuelBlockSecondaryKeyBlockHeights { } } +impl DatabaseColumn for ProcessedTransactions { + fn column() -> Column { + Column::ProcessedTransactions + } +} + impl DatabaseColumn for FuelBlockMerkleData { fn column() -> Column { Column::FuelBlockMerkleData diff --git a/crates/fuel-core/src/executor.rs b/crates/fuel-core/src/executor.rs index 36ece1ca9a..04a770582c 100644 --- a/crates/fuel-core/src/executor.rs +++ b/crates/fuel-core/src/executor.rs @@ -20,7 +20,6 @@ mod tests { ContractsRawCode, Messages, Receipts, - Transactions, }, StorageAsMut, }; @@ -1571,7 +1570,7 @@ mod tests { .into(); let db = &mut Database::default(); - let mut executor = create_executor( + let executor = create_executor( db.clone(), Config { utxo_validation_default: false, @@ -1607,16 +1606,6 @@ mod tests { assert_eq!(executed_tx.inputs()[0].balance_root(), Some(&empty_state)); assert_eq!(executed_tx.outputs()[0].state_root(), Some(&empty_state)); assert_eq!(executed_tx.outputs()[0].balance_root(), Some(&empty_state)); - - let expected_tx = block.transactions()[1].clone(); - let storage_tx = executor - .database - .storage::() - .get(&executed_tx.id(&ChainId::default())) - .unwrap() - .unwrap() - .into_owned(); - assert_eq!(storage_tx, expected_tx); } #[test] @@ -1638,7 +1627,7 @@ mod tests { .into(); let db = &mut Database::default(); - let mut executor = create_executor( + let executor = create_executor( db.clone(), Config { utxo_validation_default: false, @@ -1680,16 +1669,6 @@ mod tests { ); assert_eq!(executed_tx.inputs()[0].state_root(), Some(&empty_state)); assert_eq!(executed_tx.inputs()[0].balance_root(), Some(&empty_state)); - - let expected_tx = block.transactions()[1].clone(); - let storage_tx = executor - .database - .storage::() - .get(&expected_tx.id(&ChainId::default())) - .unwrap() - .unwrap() - .into_owned(); - assert_eq!(storage_tx, expected_tx); } #[test] @@ -1751,7 +1730,7 @@ mod tests { .clone(); let db = &mut Database::default(); - let mut executor = create_executor( + let executor = create_executor( db.clone(), Config { utxo_validation_default: false, @@ -1793,16 +1772,6 @@ mod tests { executed_tx.inputs()[0].balance_root(), executed_tx.outputs()[0].balance_root() ); - - let expected_tx = block.transactions()[1].clone(); - let storage_tx = executor - .database - .storage::() - .get(&expected_tx.id(&ChainId::default())) - .unwrap() - .unwrap() - .into_owned(); - assert_eq!(storage_tx, expected_tx); } #[test] diff --git a/crates/fuel-core/src/service/adapters/block_importer.rs b/crates/fuel-core/src/service/adapters/block_importer.rs index f44191d26c..399eade731 100644 --- a/crates/fuel-core/src/service/adapters/block_importer.rs +++ b/crates/fuel-core/src/service/adapters/block_importer.rs @@ -22,6 +22,7 @@ use fuel_core_storage::{ tables::{ FuelBlocks, SealedBlockConsensus, + Transactions, }, transactional::StorageTransaction, Result as StorageResult, @@ -29,18 +30,16 @@ use fuel_core_storage::{ }; use fuel_core_types::{ blockchain::{ - block::{ - Block, - CompressedBlock, - }, + block::Block, consensus::Consensus, - primitives::{ - BlockId, - DaBlockHeight, - }, + primitives::DaBlockHeight, SealedBlock, }, - fuel_types::BlockHeight, + fuel_tx::UniqueIdentifier, + fuel_types::{ + BlockHeight, + ChainId, + }, services::executor::{ ExecutionTypes, Result as ExecutorResult, @@ -133,25 +132,29 @@ impl ImporterDatabase for Database { } impl ExecutorDatabase for Database { - fn seal_block( - &mut self, - block_id: &BlockId, - consensus: &Consensus, - ) -> StorageResult> { - Ok(self - .storage::() - .insert(block_id, consensus)? - .map(|_| ())) - } fn block( &mut self, - block_id: &BlockId, - block: &CompressedBlock, + chain_id: &ChainId, + block: &SealedBlock, ) -> StorageResult> { - Ok(self + let block_id = block.entity.id(); + let mut found = self .storage::() - .insert(block_id, block)? - .map(|_| ())) + .insert(&block_id, &block.entity.compress(chain_id))? + .is_some(); + found |= self + .storage::() + .insert(&block_id, &block.consensus)? + .is_some(); + + // TODO: Use `batch_insert` from https://github.com/FuelLabs/fuel-core/pull/1576 + for tx in block.entity.transactions() { + found |= self + .storage::() + .insert(&tx.id(chain_id), tx)? + .is_some(); + } + Ok(found.then_some(())) } } diff --git a/crates/services/executor/src/executor.rs b/crates/services/executor/src/executor.rs index 1a9f5cdb27..6be1e94498 100644 --- a/crates/services/executor/src/executor.rs +++ b/crates/services/executor/src/executor.rs @@ -13,9 +13,9 @@ use fuel_core_storage::{ ContractsInfo, ContractsLatestUtxo, Messages, + ProcessedTransactions, Receipts, SpentMessages, - Transactions, }, transactional::{ StorageTransaction, @@ -617,7 +617,7 @@ where // Throw a clear error if the transaction id is a duplicate if tx_st_transaction .as_ref() - .storage::() + .storage::() .contains_key(tx_id)? { return Err(ExecutorError::TransactionIdCollision(*tx_id)) @@ -811,8 +811,8 @@ where if block_st_transaction .as_mut() - .storage::() - .insert(&coinbase_id, &tx)? + .storage::() + .insert(&coinbase_id, &())? .is_some() { return Err(ExecutorError::TransactionIdCollision(coinbase_id)) @@ -967,8 +967,8 @@ where // Store tx into the block db transaction tx_st_transaction .as_mut() - .storage::() - .insert(&tx_id, &final_tx)?; + .storage::() + .insert(&tx_id, &())?; // persist receipts self.persist_receipts(&tx_id, &receipts, tx_st_transaction.as_mut())?; diff --git a/crates/services/executor/src/ports.rs b/crates/services/executor/src/ports.rs index dd221166a2..1ca5a5058f 100644 --- a/crates/services/executor/src/ports.rs +++ b/crates/services/executor/src/ports.rs @@ -7,9 +7,9 @@ use fuel_core_storage::{ ContractsRawCode, ContractsState, Messages, + ProcessedTransactions, Receipts, SpentMessages, - Transactions, }, transactional::Transactional, vm_storage::VmStorageRequirements, @@ -109,7 +109,7 @@ pub trait TxIdOwnerRecorder { // TODO: Remove `Clone` bound pub trait ExecutorDatabaseTrait: StorageMutate - + StorageMutate + + StorageMutate + MerkleRootStorage + MessageIsSpent + StorageMutate diff --git a/crates/services/importer/src/importer.rs b/crates/services/importer/src/importer.rs index c3844cf235..b16a73725c 100644 --- a/crates/services/importer/src/importer.rs +++ b/crates/services/importer/src/importer.rs @@ -199,7 +199,6 @@ where let (result, mut db_tx) = result.into(); let block = &result.sealed_block.entity; let consensus = &result.sealed_block.consensus; - let block_id = block.id(); let actual_next_height = *block.header().height(); // During importing of the genesis block, the database should not be initialized @@ -254,10 +253,7 @@ where } db_after_execution - .block(&block_id, &block.compress(&self.chain_id))? - .should_be_unique(&expected_next_height)?; - db_after_execution - .seal_block(&block_id, &result.sealed_block.consensus)? + .block(&self.chain_id, &result.sealed_block)? .should_be_unique(&expected_next_height)?; // Update the total tx count in chain metadata diff --git a/crates/services/importer/src/importer/test.rs b/crates/services/importer/src/importer/test.rs index fe5effb25a..7d8bac9990 100644 --- a/crates/services/importer/src/importer/test.rs +++ b/crates/services/importer/src/importer/test.rs @@ -19,16 +19,15 @@ use fuel_core_storage::{ }; use fuel_core_types::{ blockchain::{ - block::{ - Block, - CompressedBlock, - }, + block::Block, consensus::Consensus, - primitives::BlockId, SealedBlock, }, fuel_tx::TxId, - fuel_types::BlockHeight, + fuel_types::{ + BlockHeight, + ChainId, + }, services::{ block_importer::{ ImportResult, @@ -57,14 +56,11 @@ mockall::mock! { } impl ExecutorDatabase for Database { - fn seal_block( + fn block( &mut self, - block_id: &BlockId, - consensus: &Consensus, + chain_id: &ChainId, + block: &SealedBlock, ) -> StorageResult>; - - fn block(&mut self, block_id: &BlockId, block: &CompressedBlock) - -> StorageResult>; } impl TransactionTrait for Database { @@ -126,25 +122,17 @@ where } } -fn executor_db( - height: H, - seal: S, - block: B, - commits: usize, -) -> impl Fn() -> MockDatabase +fn executor_db(height: H, block: B, commits: usize) -> impl Fn() -> MockDatabase where H: Fn() -> StorageResult> + Send + Clone + 'static, - S: Fn() -> StorageResult> + Send + Clone + 'static, B: Fn() -> StorageResult> + Send + Clone + 'static, { move || { let height = height.clone(); - let seal = seal.clone(); let block = block.clone(); let mut db = MockDatabase::default(); db.expect_latest_block_height() .returning(move || height().map(|v| v.map(Into::into))); - db.expect_seal_block().returning(move |_, _| seal()); db.expect_block().returning(move |_, _| block()); db.expect_commit().times(commits).returning(|| Ok(())); db.expect_increase_tx_count().returning(Ok); @@ -229,49 +217,42 @@ where #[test_case( genesis(0), underlying_db(ok(None)), - executor_db(ok(None), ok(None), ok(None), 1) + executor_db(ok(None), ok(None), 1) => Ok(()); "successfully imports genesis block when latest block not found" )] #[test_case( genesis(113), underlying_db(ok(None)), - executor_db(ok(None), ok(None), ok(None), 1) + executor_db(ok(None), ok(None), 1) => Ok(()); "successfully imports block at arbitrary height when executor db expects it and last block not found" )] #[test_case( genesis(0), underlying_db(storage_failure), - executor_db(ok(Some(0)), ok(None), ok(None), 0) + executor_db(ok(Some(0)), ok(None), 0) => Err(storage_failure_error()); "fails to import genesis when underlying database fails" )] #[test_case( genesis(0), underlying_db(ok(Some(0))), - executor_db(ok(Some(0)), ok(None), ok(None), 0) + executor_db(ok(Some(0)), ok(None), 0) => Err(Error::InvalidUnderlyingDatabaseGenesisState); "fails to import genesis block when already exists" )] #[test_case( genesis(1), underlying_db(ok(None)), - executor_db(ok(Some(0)), ok(None), ok(None), 0) + executor_db(ok(Some(0)), ok(None), 0) => Err(Error::InvalidDatabaseStateAfterExecution(None, Some(0u32.into()))); "fails to import genesis block when next height is not 0" )] #[test_case( genesis(0), underlying_db(ok(None)), - executor_db(ok(None), ok(Some(())), ok(None), 0) - => Err(Error::NotUnique(0u32.into())); - "fails to import genesis block when consensus exists for height 0" -)] -#[test_case( - genesis(0), - underlying_db(ok(None)), - executor_db(ok(None), ok(None), ok(Some(())), 0) + executor_db(ok(None), ok(Some(())), 0) => Err(Error::NotUnique(0u32.into())); "fails to import genesis block when block exists for height 0" )] @@ -287,77 +268,63 @@ fn commit_result_genesis( #[test_case( poa_block(1), underlying_db(ok(Some(0))), - executor_db(ok(Some(0)), ok(None), ok(None), 1) + executor_db(ok(Some(0)), ok(None), 1) => Ok(()); "successfully imports block at height 1 when latest block is genesis" )] #[test_case( poa_block(113), underlying_db(ok(Some(112))), - executor_db(ok(Some(112)), ok(None), ok(None), 1) + executor_db(ok(Some(112)), ok(None), 1) => Ok(()); "successfully imports block at arbitrary height when latest block height is one fewer and executor db expects it" )] #[test_case( poa_block(0), underlying_db(ok(Some(0))), - executor_db(ok(Some(1)), ok(None), ok(None), 0) + executor_db(ok(Some(1)), ok(None), 0) => Err(Error::ZeroNonGenericHeight); "fails to import PoA block with height 0" )] #[test_case( poa_block(113), underlying_db(ok(Some(111))), - executor_db(ok(Some(113)), ok(None), ok(None), 0) + executor_db(ok(Some(113)), ok(None), 0) => Err(Error::IncorrectBlockHeight(112u32.into(), 113u32.into())); "fails to import block at height 113 when latest block height is 111" )] #[test_case( poa_block(113), underlying_db(ok(Some(114))), - executor_db(ok(Some(113)), ok(None), ok(None), 0) + executor_db(ok(Some(113)), ok(None), 0) => Err(Error::IncorrectBlockHeight(115u32.into(), 113u32.into())); "fails to import block at height 113 when latest block height is 114" )] #[test_case( poa_block(113), underlying_db(ok(Some(112))), - executor_db(ok(Some(114)), ok(None), ok(None), 0) + executor_db(ok(Some(114)), ok(None), 0) => Err(Error::InvalidDatabaseStateAfterExecution(Some(112u32.into()), Some(114u32.into()))); "fails to import block 113 when executor db expects height 114" )] #[test_case( poa_block(113), underlying_db(ok(Some(112))), - executor_db(storage_failure, ok(None), ok(None), 0) + executor_db(storage_failure, ok(None), 0) => Err(storage_failure_error()); "fails to import block when executor db fails to find latest block" )] #[test_case( poa_block(113), underlying_db(ok(Some(112))), - executor_db(ok(Some(112)), ok(Some(())), ok(None), 0) - => Err(Error::NotUnique(113u32.into())); - "fails to import block when consensus exists for block" -)] -#[test_case( - poa_block(113), - underlying_db(ok(Some(112))), - executor_db(ok(Some(112)), storage_failure, ok(None), 0) - => Err(storage_failure_error()); - "fails to import block when executor db fails to find consensus" -)] -#[test_case( - poa_block(113), - underlying_db(ok(Some(112))), - executor_db(ok(Some(112)), ok(None), ok(Some(())), 0) + executor_db(ok(Some(112)), ok(Some(())), 0) => Err(Error::NotUnique(113u32.into())); "fails to import block when block exists" )] #[test_case( poa_block(113), underlying_db(ok(Some(112))), - executor_db(ok(Some(112)), ok(None), storage_failure, 0) + executor_db(ok(Some(112)), storage_failure, 0) => Err(storage_failure_error()); "fails to import block when executor db fails to find block" )] @@ -566,7 +533,7 @@ where underlying_db(ok(Some(previous_height)))(), executor( block_after_execution, - executor_db(ok(Some(previous_height)), ok(None), ok(None), commits)(), + executor_db(ok(Some(previous_height)), ok(None), commits)(), ), verifier(verifier_result), ); diff --git a/crates/services/importer/src/ports.rs b/crates/services/importer/src/ports.rs index 32b46cbc44..c26d4bab4a 100644 --- a/crates/services/importer/src/ports.rs +++ b/crates/services/importer/src/ports.rs @@ -4,14 +4,14 @@ use fuel_core_storage::{ }; use fuel_core_types::{ blockchain::{ - block::{ - Block, - CompressedBlock, - }, + block::Block, consensus::Consensus, - primitives::BlockId, + SealedBlock, + }, + fuel_types::{ + BlockHeight, + ChainId, }, - fuel_types::BlockHeight, services::executor::{ Result as ExecutorResult, UncommittedResult, @@ -43,19 +43,13 @@ pub trait ImporterDatabase { /// The port for returned database from the executor. pub trait ExecutorDatabase: ImporterDatabase { - /// Assigns the `Consensus` data to the block under the `block_id`. - /// Return the previous value at the `height`, if any. - fn seal_block( - &mut self, - block_id: &BlockId, - consensus: &Consensus, - ) -> StorageResult>; - - /// Inserts the `CompressedBlock` under the `block_id`. + /// Inserts the `SealedBlock` under the `block_id`. + // TODO: Remove `chain_id` from the signature, but for that transactions inside + // the block should have `cached_id`. We need to guarantee that from the Rust-type system. fn block( &mut self, - block_id: &BlockId, - block: &CompressedBlock, + chain_id: &ChainId, + block: &SealedBlock, ) -> StorageResult>; } diff --git a/crates/storage/src/tables.rs b/crates/storage/src/tables.rs index 2c2df585f1..27f5cb2fb2 100644 --- a/crates/storage/src/tables.rs +++ b/crates/storage/src/tables.rs @@ -121,5 +121,16 @@ impl Mappable for Transactions { type OwnedValue = Transaction; } +/// The storage table of processed transactions that were executed in the past. +/// The table helps to drop duplicated transactions. +pub struct ProcessedTransactions; + +impl Mappable for ProcessedTransactions { + type Key = Self::OwnedKey; + type OwnedKey = TxId; + type Value = Self::OwnedValue; + type OwnedValue = (); +} + // TODO: Add macro to define all common tables to avoid copy/paste of the code. // TODO: Add macro to define common unit tests. From a49d96d3fd0925278aab0e02d738a3452e218d98 Mon Sep 17 00:00:00 2001 From: xgreenx Date: Sat, 30 Dec 2023 15:18:34 +0100 Subject: [PATCH 05/11] Use `store_block` as a name instead of `block` --- .../fuel-core/src/service/adapters/block_importer.rs | 2 +- crates/services/importer/src/importer.rs | 2 +- crates/services/importer/src/importer/test.rs | 12 ++++++++---- crates/services/importer/src/ports.rs | 2 +- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/crates/fuel-core/src/service/adapters/block_importer.rs b/crates/fuel-core/src/service/adapters/block_importer.rs index 399eade731..1c439171df 100644 --- a/crates/fuel-core/src/service/adapters/block_importer.rs +++ b/crates/fuel-core/src/service/adapters/block_importer.rs @@ -132,7 +132,7 @@ impl ImporterDatabase for Database { } impl ExecutorDatabase for Database { - fn block( + fn store_block( &mut self, chain_id: &ChainId, block: &SealedBlock, diff --git a/crates/services/importer/src/importer.rs b/crates/services/importer/src/importer.rs index b16a73725c..42fe5f1d5f 100644 --- a/crates/services/importer/src/importer.rs +++ b/crates/services/importer/src/importer.rs @@ -253,7 +253,7 @@ where } db_after_execution - .block(&self.chain_id, &result.sealed_block)? + .store_block(&self.chain_id, &result.sealed_block)? .should_be_unique(&expected_next_height)?; // Update the total tx count in chain metadata diff --git a/crates/services/importer/src/importer/test.rs b/crates/services/importer/src/importer/test.rs index 7d8bac9990..f448af76e7 100644 --- a/crates/services/importer/src/importer/test.rs +++ b/crates/services/importer/src/importer/test.rs @@ -56,7 +56,7 @@ mockall::mock! { } impl ExecutorDatabase for Database { - fn block( + fn store_block( &mut self, chain_id: &ChainId, block: &SealedBlock, @@ -122,18 +122,22 @@ where } } -fn executor_db(height: H, block: B, commits: usize) -> impl Fn() -> MockDatabase +fn executor_db( + height: H, + store_block: B, + commits: usize, +) -> impl Fn() -> MockDatabase where H: Fn() -> StorageResult> + Send + Clone + 'static, B: Fn() -> StorageResult> + Send + Clone + 'static, { move || { let height = height.clone(); - let block = block.clone(); + let store_block = store_block.clone(); let mut db = MockDatabase::default(); db.expect_latest_block_height() .returning(move || height().map(|v| v.map(Into::into))); - db.expect_block().returning(move |_, _| block()); + db.expect_store_block().returning(move |_, _| store_block()); db.expect_commit().times(commits).returning(|| Ok(())); db.expect_increase_tx_count().returning(Ok); db diff --git a/crates/services/importer/src/ports.rs b/crates/services/importer/src/ports.rs index c26d4bab4a..9f188c472a 100644 --- a/crates/services/importer/src/ports.rs +++ b/crates/services/importer/src/ports.rs @@ -46,7 +46,7 @@ pub trait ExecutorDatabase: ImporterDatabase { /// Inserts the `SealedBlock` under the `block_id`. // TODO: Remove `chain_id` from the signature, but for that transactions inside // the block should have `cached_id`. We need to guarantee that from the Rust-type system. - fn block( + fn store_block( &mut self, chain_id: &ChainId, block: &SealedBlock, From f5c38f245f81d65195c1133964e076e280f1cbe5 Mon Sep 17 00:00:00 2001 From: xgreenx Date: Fri, 5 Jan 2024 16:09:20 +0100 Subject: [PATCH 06/11] Move `ChainId` to `fuel_core_importer::Config` --- bin/fuel-core/src/cli/run.rs | 6 ++-- .../src/service/adapters/block_importer.rs | 4 +-- crates/fuel-core/src/service/config.rs | 6 ++-- crates/fuel-core/src/service/genesis.rs | 1 - crates/fuel-core/src/service/sub_services.rs | 1 - crates/services/importer/src/config.rs | 16 +++++++++++ crates/services/importer/src/importer.rs | 11 ++------ crates/services/importer/src/importer/test.rs | 28 ++----------------- 8 files changed, 29 insertions(+), 44 deletions(-) diff --git a/bin/fuel-core/src/cli/run.rs b/bin/fuel-core/src/cli/run.rs index e6210d8330..9b1faeff4a 100644 --- a/bin/fuel-core/src/cli/run.rs +++ b/bin/fuel-core/src/cli/run.rs @@ -300,6 +300,9 @@ impl Command { max_wait_time: max_wait_time.into(), }; + let block_importer = + fuel_core::service::config::fuel_core_importer::Config::new(&chain_conf); + let config = Config { addr, api_request_timeout: api_request_timeout.into(), @@ -328,8 +331,7 @@ impl Command { coinbase_recipient, metrics, }, - block_executor: Default::default(), - block_importer: Default::default(), + block_importer, #[cfg(feature = "relayer")] relayer: relayer_cfg, #[cfg(feature = "p2p")] diff --git a/crates/fuel-core/src/service/adapters/block_importer.rs b/crates/fuel-core/src/service/adapters/block_importer.rs index 1c439171df..e286e0c2dd 100644 --- a/crates/fuel-core/src/service/adapters/block_importer.rs +++ b/crates/fuel-core/src/service/adapters/block_importer.rs @@ -6,7 +6,6 @@ use crate::{ VerifierAdapter, }, }; -use fuel_core_chain_config::ChainConfig; use fuel_core_importer::{ ports::{ BlockVerifier, @@ -56,12 +55,11 @@ use super::{ impl BlockImporterAdapter { pub fn new( config: Config, - chain_config: &ChainConfig, database: Database, executor: ExecutorAdapter, verifier: VerifierAdapter, ) -> Self { - let importer = Importer::new(config, chain_config, database, executor, verifier); + let importer = Importer::new(config, database, executor, verifier); importer.init_metrics(); Self { block_importer: Arc::new(importer), diff --git a/crates/fuel-core/src/service/config.rs b/crates/fuel-core/src/service/config.rs index f0cabfda03..5aafec6446 100644 --- a/crates/fuel-core/src/service/config.rs +++ b/crates/fuel-core/src/service/config.rs @@ -30,6 +30,7 @@ use fuel_core_p2p::config::{ #[cfg(feature = "relayer")] use fuel_core_relayer::Config as RelayerConfig; +pub use fuel_core_importer; pub use fuel_core_poa::Trigger; #[derive(Clone, Debug)] @@ -51,7 +52,6 @@ pub struct Config { pub vm: VMConfig, pub txpool: fuel_core_txpool::Config, pub block_producer: fuel_core_producer::Config, - pub block_executor: fuel_core_executor::Config, pub block_importer: fuel_core_importer::Config, #[cfg(feature = "relayer")] pub relayer: Option, @@ -73,6 +73,7 @@ pub struct Config { impl Config { pub fn local_node() -> Self { let chain_conf = ChainConfig::local_testnet(); + let block_importer = fuel_core_importer::Config::new(&chain_conf); let utxo_validation = false; let min_gas_price = 0; @@ -99,8 +100,7 @@ impl Config { ..fuel_core_txpool::Config::default() }, block_producer: Default::default(), - block_executor: Default::default(), - block_importer: Default::default(), + block_importer, #[cfg(feature = "relayer")] relayer: None, #[cfg(feature = "p2p")] diff --git a/crates/fuel-core/src/service/genesis.rs b/crates/fuel-core/src/service/genesis.rs index 31c409b607..8039f438d1 100644 --- a/crates/fuel-core/src/service/genesis.rs +++ b/crates/fuel-core/src/service/genesis.rs @@ -132,7 +132,6 @@ fn import_genesis_block( let importer = Importer::new( config.block_importer.clone(), - &config.chain_conf, original_database.clone(), (), (), diff --git a/crates/fuel-core/src/service/sub_services.rs b/crates/fuel-core/src/service/sub_services.rs index 0a027cc3d2..1523fe41c1 100644 --- a/crates/fuel-core/src/service/sub_services.rs +++ b/crates/fuel-core/src/service/sub_services.rs @@ -89,7 +89,6 @@ pub fn init_sub_services( let importer_adapter = BlockImporterAdapter::new( config.block_importer.clone(), - &config.chain_conf, database.clone(), executor.clone(), verifier.clone(), diff --git a/crates/services/importer/src/config.rs b/crates/services/importer/src/config.rs index ddb1739142..c551127c68 100644 --- a/crates/services/importer/src/config.rs +++ b/crates/services/importer/src/config.rs @@ -1,14 +1,30 @@ +use fuel_core_chain_config::ChainConfig; +use fuel_core_types::fuel_types::ChainId; + #[derive(Debug, Clone)] pub struct Config { pub max_block_notify_buffer: usize, pub metrics: bool, + pub chain_id: ChainId, +} + +impl Config { + pub fn new(chain_config: &ChainConfig) -> Self { + Self { + max_block_notify_buffer: 1 << 10, + metrics: false, + chain_id: chain_config.consensus_parameters.chain_id, + } + } } +#[cfg(test)] impl Default for Config { fn default() -> Self { Self { max_block_notify_buffer: 1 << 10, metrics: false, + chain_id: ChainId::default(), } } } diff --git a/crates/services/importer/src/importer.rs b/crates/services/importer/src/importer.rs index 42fe5f1d5f..daf129eb73 100644 --- a/crates/services/importer/src/importer.rs +++ b/crates/services/importer/src/importer.rs @@ -7,7 +7,6 @@ use crate::{ }, Config, }; -use fuel_core_chain_config::ChainConfig; use fuel_core_metrics::importer::importer_metrics; use fuel_core_storage::{ not_found, @@ -114,20 +113,14 @@ pub struct Importer { } impl Importer { - pub fn new( - config: Config, - chain_config: &ChainConfig, - database: D, - executor: E, - verifier: V, - ) -> Self { + pub fn new(config: Config, database: D, executor: E, verifier: V) -> Self { let (broadcast, _) = broadcast::channel(config.max_block_notify_buffer); Self { database, executor, verifier, - chain_id: chain_config.consensus_parameters.chain_id, + chain_id: config.chain_id, broadcast, guard: tokio::sync::Semaphore::new(1), } diff --git a/crates/services/importer/src/importer/test.rs b/crates/services/importer/src/importer/test.rs index f448af76e7..ad8e9aa340 100644 --- a/crates/services/importer/src/importer/test.rs +++ b/crates/services/importer/src/importer/test.rs @@ -358,13 +358,7 @@ fn commit_result_assert( executor_db: MockDatabase, ) -> Result<(), Error> { let expected_to_broadcast = sealed_block.clone(); - let importer = Importer::new( - Default::default(), - &Default::default(), - underlying_db, - (), - (), - ); + let importer = Importer::new(Default::default(), underlying_db, (), ()); let uncommitted_result = UncommittedResult::new( ImportResult::new_from_local(sealed_block, vec![]), StorageTransaction::new(executor_db), @@ -394,13 +388,7 @@ fn execute_and_commit_assert( verifier: MockBlockVerifier, ) -> Result<(), Error> { let expected_to_broadcast = sealed_block.clone(); - let importer = Importer::new( - Default::default(), - &Default::default(), - underlying_db, - executor, - verifier, - ); + let importer = Importer::new(Default::default(), underlying_db, executor, verifier); let mut imported_blocks = importer.subscribe(); let result = importer.execute_and_commit(sealed_block); @@ -421,13 +409,7 @@ fn execute_and_commit_assert( #[test] fn commit_result_fail_when_locked() { - let importer = Importer::new( - Default::default(), - &Default::default(), - MockDatabase::default(), - (), - (), - ); + let importer = Importer::new(Default::default(), MockDatabase::default(), (), ()); let uncommitted_result = UncommittedResult::new( ImportResult::default(), StorageTransaction::new(MockDatabase::default()), @@ -444,7 +426,6 @@ fn commit_result_fail_when_locked() { fn execute_and_commit_fail_when_locked() { let importer = Importer::new( Default::default(), - &Default::default(), MockDatabase::default(), MockExecutor::default(), MockBlockVerifier::default(), @@ -461,7 +442,6 @@ fn execute_and_commit_fail_when_locked() { fn one_lock_at_the_same_time() { let importer = Importer::new( Default::default(), - &Default::default(), MockDatabase::default(), MockExecutor::default(), MockBlockVerifier::default(), @@ -556,7 +536,6 @@ where { let importer = Importer::new( Default::default(), - &Default::default(), MockDatabase::default(), executor(block_after_execution, MockDatabase::default()), verifier(verifier_result), @@ -569,7 +548,6 @@ where fn verify_and_execute_allowed_when_locked() { let importer = Importer::new( Default::default(), - &Default::default(), MockDatabase::default(), executor(ok(ex_result(13, 0)), MockDatabase::default()), verifier(ok(())), From c1ee5f8c628bad3af0631cfea292302d336992f4 Mon Sep 17 00:00:00 2001 From: xgreenx Date: Fri, 5 Jan 2024 22:39:38 +0100 Subject: [PATCH 07/11] Apply suggestions from the PR --- .../src/service/adapters/block_importer.rs | 6 +-- .../service/adapters/consensus_module/poa.rs | 29 +++++++------- .../consensus_module/poa/src/ports.rs | 10 ++++- .../consensus_module/poa/src/service.rs | 15 +++---- crates/services/importer/src/importer.rs | 6 +-- crates/services/importer/src/importer/test.rs | 39 ++++++++++--------- crates/services/importer/src/ports.rs | 8 ++-- 7 files changed, 64 insertions(+), 49 deletions(-) diff --git a/crates/fuel-core/src/service/adapters/block_importer.rs b/crates/fuel-core/src/service/adapters/block_importer.rs index e286e0c2dd..89627483c8 100644 --- a/crates/fuel-core/src/service/adapters/block_importer.rs +++ b/crates/fuel-core/src/service/adapters/block_importer.rs @@ -130,11 +130,11 @@ impl ImporterDatabase for Database { } impl ExecutorDatabase for Database { - fn store_block( + fn store_new_block( &mut self, chain_id: &ChainId, block: &SealedBlock, - ) -> StorageResult> { + ) -> StorageResult { let block_id = block.entity.id(); let mut found = self .storage::() @@ -152,7 +152,7 @@ impl ExecutorDatabase for Database { .insert(&tx.id(chain_id), tx)? .is_some(); } - Ok(found.then_some(())) + Ok(!found) } } diff --git a/crates/fuel-core/src/service/adapters/consensus_module/poa.rs b/crates/fuel-core/src/service/adapters/consensus_module/poa.rs index e53b37e11b..ac446c7167 100644 --- a/crates/fuel-core/src/service/adapters/consensus_module/poa.rs +++ b/crates/fuel-core/src/service/adapters/consensus_module/poa.rs @@ -17,6 +17,7 @@ use fuel_core_poa::{ BlockImporter, P2pPort, TransactionPool, + TransactionsSource, }, service::{ Mode, @@ -27,10 +28,7 @@ use fuel_core_services::stream::BoxStream; use fuel_core_storage::transactional::StorageTransaction; use fuel_core_types::{ fuel_asm::Word, - fuel_tx::{ - Transaction, - TxId, - }, + fuel_tx::TxId, fuel_types::BlockHeight, services::{ block_importer::{ @@ -106,17 +104,22 @@ impl fuel_core_poa::ports::BlockProducer for BlockProducerAdapter { &self, height: BlockHeight, block_time: Tai64, - txs: Option>, + source: TransactionsSource, max_gas: Word, ) -> anyhow::Result>> { - if let Some(txs) = txs { - self.block_producer - .produce_and_execute_block_transactions(height, block_time, txs, max_gas) - .await - } else { - self.block_producer - .produce_and_execute_block_txpool(height, block_time, max_gas) - .await + match source { + TransactionsSource::TxPool => { + self.block_producer + .produce_and_execute_block_txpool(height, block_time, max_gas) + .await + } + TransactionsSource::SpecificTransactions(txs) => { + self.block_producer + .produce_and_execute_block_transactions( + height, block_time, txs, max_gas, + ) + .await + } } } } diff --git a/crates/services/consensus_module/poa/src/ports.rs b/crates/services/consensus_module/poa/src/ports.rs index 1866b871cb..fdb8a2d11d 100644 --- a/crates/services/consensus_module/poa/src/ports.rs +++ b/crates/services/consensus_module/poa/src/ports.rs @@ -43,6 +43,14 @@ pub trait TransactionPool: Send + Sync { #[cfg(test)] use fuel_core_storage::test_helpers::EmptyStorage; +/// The source of transactions for the block. +pub enum TransactionsSource { + /// The source of transactions for the block is the `TxPool`. + TxPool, + /// Use specific transactions for the block. + SpecificTransactions(Vec), +} + #[cfg_attr(test, mockall::automock(type Database=EmptyStorage;))] #[async_trait::async_trait] pub trait BlockProducer: Send + Sync { @@ -52,7 +60,7 @@ pub trait BlockProducer: Send + Sync { &self, height: BlockHeight, block_time: Tai64, - txs: Option>, + source: TransactionsSource, max_gas: Word, ) -> anyhow::Result>>; } diff --git a/crates/services/consensus_module/poa/src/service.rs b/crates/services/consensus_module/poa/src/service.rs index 918304f2a1..3ec7b8727d 100644 --- a/crates/services/consensus_module/poa/src/service.rs +++ b/crates/services/consensus_module/poa/src/service.rs @@ -8,6 +8,7 @@ use crate::{ BlockProducer, P2pPort, TransactionPool, + TransactionsSource, }, sync::{ SyncState, @@ -255,10 +256,10 @@ where &self, height: BlockHeight, block_time: Tai64, - txs: Option>, + source: TransactionsSource, ) -> anyhow::Result>> { self.block_producer - .produce_and_execute_block(height, block_time, txs, self.block_gas_limit) + .produce_and_execute_block(height, block_time, source, self.block_gas_limit) .await } @@ -266,7 +267,7 @@ where self.produce_block( self.next_height(), self.next_time(RequestType::Trigger)?, - None, + TransactionsSource::TxPool, RequestType::Trigger, ) .await @@ -287,7 +288,7 @@ where self.produce_block( self.next_height(), block_time, - None, + TransactionsSource::TxPool, RequestType::Manual, ) .await?; @@ -298,7 +299,7 @@ where self.produce_block( self.next_height(), block_time, - Some(txs), + TransactionsSource::SpecificTransactions(txs), RequestType::Manual, ) .await?; @@ -311,7 +312,7 @@ where &mut self, height: BlockHeight, block_time: Tai64, - txs: Option>, + source: TransactionsSource, request_type: RequestType, ) -> anyhow::Result<()> { let last_block_created = Instant::now(); @@ -333,7 +334,7 @@ where }, db_transaction, ) = self - .signal_produce_block(height, block_time, txs) + .signal_produce_block(height, block_time, source) .await? .into(); diff --git a/crates/services/importer/src/importer.rs b/crates/services/importer/src/importer.rs index daf129eb73..056c401041 100644 --- a/crates/services/importer/src/importer.rs +++ b/crates/services/importer/src/importer.rs @@ -245,9 +245,9 @@ where )) } - db_after_execution - .store_block(&self.chain_id, &result.sealed_block)? - .should_be_unique(&expected_next_height)?; + if !db_after_execution.store_new_block(&self.chain_id, &result.sealed_block)? { + return Err(Error::NotUnique(expected_next_height)) + } // Update the total tx count in chain metadata let total_txs = db_after_execution diff --git a/crates/services/importer/src/importer/test.rs b/crates/services/importer/src/importer/test.rs index ad8e9aa340..897be9f994 100644 --- a/crates/services/importer/src/importer/test.rs +++ b/crates/services/importer/src/importer/test.rs @@ -56,11 +56,11 @@ mockall::mock! { } impl ExecutorDatabase for Database { - fn store_block( + fn store_new_block( &mut self, chain_id: &ChainId, block: &SealedBlock, - ) -> StorageResult>; + ) -> StorageResult; } impl TransactionTrait for Database { @@ -129,7 +129,7 @@ fn executor_db( ) -> impl Fn() -> MockDatabase where H: Fn() -> StorageResult> + Send + Clone + 'static, - B: Fn() -> StorageResult> + Send + Clone + 'static, + B: Fn() -> StorageResult + Send + Clone + 'static, { move || { let height = height.clone(); @@ -137,7 +137,8 @@ where let mut db = MockDatabase::default(); db.expect_latest_block_height() .returning(move || height().map(|v| v.map(Into::into))); - db.expect_store_block().returning(move |_, _| store_block()); + db.expect_store_new_block() + .returning(move |_, _| store_block()); db.expect_commit().times(commits).returning(|| Ok(())); db.expect_increase_tx_count().returning(Ok); db @@ -221,42 +222,42 @@ where #[test_case( genesis(0), underlying_db(ok(None)), - executor_db(ok(None), ok(None), 1) + executor_db(ok(None), ok(true), 1) => Ok(()); "successfully imports genesis block when latest block not found" )] #[test_case( genesis(113), underlying_db(ok(None)), - executor_db(ok(None), ok(None), 1) + executor_db(ok(None), ok(true), 1) => Ok(()); "successfully imports block at arbitrary height when executor db expects it and last block not found" )] #[test_case( genesis(0), underlying_db(storage_failure), - executor_db(ok(Some(0)), ok(None), 0) + executor_db(ok(Some(0)), ok(true), 0) => Err(storage_failure_error()); "fails to import genesis when underlying database fails" )] #[test_case( genesis(0), underlying_db(ok(Some(0))), - executor_db(ok(Some(0)), ok(None), 0) + executor_db(ok(Some(0)), ok(true), 0) => Err(Error::InvalidUnderlyingDatabaseGenesisState); "fails to import genesis block when already exists" )] #[test_case( genesis(1), underlying_db(ok(None)), - executor_db(ok(Some(0)), ok(None), 0) + executor_db(ok(Some(0)), ok(true), 0) => Err(Error::InvalidDatabaseStateAfterExecution(None, Some(0u32.into()))); "fails to import genesis block when next height is not 0" )] #[test_case( genesis(0), underlying_db(ok(None)), - executor_db(ok(None), ok(Some(())), 0) + executor_db(ok(None), ok(false), 0) => Err(Error::NotUnique(0u32.into())); "fails to import genesis block when block exists for height 0" )] @@ -272,56 +273,56 @@ fn commit_result_genesis( #[test_case( poa_block(1), underlying_db(ok(Some(0))), - executor_db(ok(Some(0)), ok(None), 1) + executor_db(ok(Some(0)), ok(true), 1) => Ok(()); "successfully imports block at height 1 when latest block is genesis" )] #[test_case( poa_block(113), underlying_db(ok(Some(112))), - executor_db(ok(Some(112)), ok(None), 1) + executor_db(ok(Some(112)), ok(true), 1) => Ok(()); "successfully imports block at arbitrary height when latest block height is one fewer and executor db expects it" )] #[test_case( poa_block(0), underlying_db(ok(Some(0))), - executor_db(ok(Some(1)), ok(None), 0) + executor_db(ok(Some(1)), ok(true), 0) => Err(Error::ZeroNonGenericHeight); "fails to import PoA block with height 0" )] #[test_case( poa_block(113), underlying_db(ok(Some(111))), - executor_db(ok(Some(113)), ok(None), 0) + executor_db(ok(Some(113)), ok(true), 0) => Err(Error::IncorrectBlockHeight(112u32.into(), 113u32.into())); "fails to import block at height 113 when latest block height is 111" )] #[test_case( poa_block(113), underlying_db(ok(Some(114))), - executor_db(ok(Some(113)), ok(None), 0) + executor_db(ok(Some(113)), ok(true), 0) => Err(Error::IncorrectBlockHeight(115u32.into(), 113u32.into())); "fails to import block at height 113 when latest block height is 114" )] #[test_case( poa_block(113), underlying_db(ok(Some(112))), - executor_db(ok(Some(114)), ok(None), 0) + executor_db(ok(Some(114)), ok(true), 0) => Err(Error::InvalidDatabaseStateAfterExecution(Some(112u32.into()), Some(114u32.into()))); "fails to import block 113 when executor db expects height 114" )] #[test_case( poa_block(113), underlying_db(ok(Some(112))), - executor_db(storage_failure, ok(None), 0) + executor_db(storage_failure, ok(true), 0) => Err(storage_failure_error()); "fails to import block when executor db fails to find latest block" )] #[test_case( poa_block(113), underlying_db(ok(Some(112))), - executor_db(ok(Some(112)), ok(Some(())), 0) + executor_db(ok(Some(112)), ok(false), 0) => Err(Error::NotUnique(113u32.into())); "fails to import block when block exists" )] @@ -517,7 +518,7 @@ where underlying_db(ok(Some(previous_height)))(), executor( block_after_execution, - executor_db(ok(Some(previous_height)), ok(None), commits)(), + executor_db(ok(Some(previous_height)), ok(true), commits)(), ), verifier(verifier_result), ); diff --git a/crates/services/importer/src/ports.rs b/crates/services/importer/src/ports.rs index 9f188c472a..51c14e5085 100644 --- a/crates/services/importer/src/ports.rs +++ b/crates/services/importer/src/ports.rs @@ -43,14 +43,16 @@ pub trait ImporterDatabase { /// The port for returned database from the executor. pub trait ExecutorDatabase: ImporterDatabase { - /// Inserts the `SealedBlock` under the `block_id`. + /// Inserts the `SealedBlock`. + /// + /// The method returns `true` if the block is a new, otherwise `false`. // TODO: Remove `chain_id` from the signature, but for that transactions inside // the block should have `cached_id`. We need to guarantee that from the Rust-type system. - fn store_block( + fn store_new_block( &mut self, chain_id: &ChainId, block: &SealedBlock, - ) -> StorageResult>; + ) -> StorageResult; } #[cfg_attr(test, mockall::automock)] From c594d45366ba59a76248663589df65537bf11313 Mon Sep 17 00:00:00 2001 From: xgreenx Date: Thu, 4 Jan 2024 12:09:14 +0100 Subject: [PATCH 08/11] Extract off chain logic from the executor --- Cargo.lock | 1 + crates/fuel-core/src/coins_query.rs | 55 ++-- crates/fuel-core/src/executor.rs | 37 +-- crates/fuel-core/src/graphql_api.rs | 5 +- .../{service.rs => api_service.rs} | 38 ++- crates/fuel-core/src/graphql_api/database.rs | 223 ++++++++++++++ crates/fuel-core/src/graphql_api/ports.rs | 125 +++++--- .../src/graphql_api/view_extension.rs | 44 +++ .../src/graphql_api/worker_service.rs | 277 ++++++++++++++++++ crates/fuel-core/src/query/balance.rs | 4 +- .../src/query/balance/asset_query.rs | 10 +- crates/fuel-core/src/query/block.rs | 6 +- crates/fuel-core/src/query/chain.rs | 4 +- crates/fuel-core/src/query/coin.rs | 7 +- crates/fuel-core/src/query/contract.rs | 4 +- crates/fuel-core/src/query/message.rs | 10 +- crates/fuel-core/src/query/tx.rs | 15 +- crates/fuel-core/src/schema/balance.rs | 8 +- crates/fuel-core/src/schema/block.rs | 30 +- crates/fuel-core/src/schema/chain.rs | 10 +- crates/fuel-core/src/schema/coins.rs | 12 +- crates/fuel-core/src/schema/contract.rs | 20 +- crates/fuel-core/src/schema/message.rs | 19 +- crates/fuel-core/src/schema/node_info.rs | 2 +- crates/fuel-core/src/schema/tx.rs | 45 +-- crates/fuel-core/src/schema/tx/types.rs | 24 +- crates/fuel-core/src/service.rs | 6 +- .../src/service/adapters/block_importer.rs | 6 +- .../service/adapters/consensus_module/poa.rs | 8 +- .../src/service/adapters/executor.rs | 39 +-- .../src/service/adapters/graphql_api.rs | 210 ++----------- .../service/adapters/graphql_api/off_chain.rs | 116 ++++++++ .../service/adapters/graphql_api/on_chain.rs | 139 +++++++++ crates/fuel-core/src/service/adapters/sync.rs | 1 + .../fuel-core/src/service/adapters/txpool.rs | 14 +- crates/fuel-core/src/service/genesis.rs | 3 +- crates/fuel-core/src/service/sub_services.rs | 39 ++- .../consensus_module/poa/src/ports.rs | 3 +- .../consensus_module/poa/src/service.rs | 10 +- crates/services/executor/src/executor.rs | 180 ++---------- crates/services/executor/src/ports.rs | 40 +-- crates/services/importer/Cargo.toml | 1 + crates/services/importer/src/config.rs | 2 +- crates/services/importer/src/importer.rs | 152 ++++++++-- crates/services/importer/src/importer/test.rs | 62 ++-- crates/services/importer/src/ports.rs | 4 +- crates/services/txpool/src/mock_db.rs | 7 - crates/services/txpool/src/ports.rs | 8 +- crates/services/txpool/src/service.rs | 13 +- .../txpool/src/service/test_helpers.rs | 9 +- crates/services/txpool/src/txpool.rs | 27 +- crates/storage/src/transactional.rs | 11 + crates/types/src/services/block_importer.rs | 17 +- crates/types/src/services/executor.rs | 3 + crates/types/src/services/txpool.rs | 30 +- 55 files changed, 1428 insertions(+), 767 deletions(-) rename crates/fuel-core/src/graphql_api/{service.rs => api_service.rs} (89%) create mode 100644 crates/fuel-core/src/graphql_api/database.rs create mode 100644 crates/fuel-core/src/graphql_api/view_extension.rs create mode 100644 crates/fuel-core/src/graphql_api/worker_service.rs create mode 100644 crates/fuel-core/src/service/adapters/graphql_api/off_chain.rs create mode 100644 crates/fuel-core/src/service/adapters/graphql_api/on_chain.rs diff --git a/Cargo.lock b/Cargo.lock index 264c9099a8..9ae03c137d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2850,6 +2850,7 @@ dependencies = [ "mockall", "test-case", "tokio", + "tokio-rayon", "tracing", ] diff --git a/crates/fuel-core/src/coins_query.rs b/crates/fuel-core/src/coins_query.rs index 254e5f1b7f..7314889b40 100644 --- a/crates/fuel-core/src/coins_query.rs +++ b/crates/fuel-core/src/coins_query.rs @@ -1,5 +1,5 @@ use crate::{ - fuel_core_graphql_api::service::Database, + fuel_core_graphql_api::database::ReadView, query::asset_query::{ AssetQuery, AssetSpendTarget, @@ -95,7 +95,7 @@ impl SpendQuery { } /// Return [`AssetQuery`]s. - pub fn asset_queries<'a>(&'a self, db: &'a Database) -> Vec> { + pub fn asset_queries<'a>(&'a self, db: &'a ReadView) -> Vec> { self.query_per_asset .iter() .map(|asset| { @@ -159,7 +159,7 @@ pub fn largest_first(query: &AssetQuery) -> Result, CoinsQueryErro // An implementation of the method described on: https://iohk.io/en/blog/posts/2018/07/03/self-organisation-in-coin-selection/ pub fn random_improve( - db: &Database, + db: &ReadView, spend_query: &SpendQuery, ) -> Result>, CoinsQueryError> { let mut coins_per_asset = vec![]; @@ -229,7 +229,7 @@ mod tests { SpendQuery, }, database::Database, - fuel_core_graphql_api::service::Database as ServiceDatabase, + fuel_core_graphql_api::api_service::ReadDatabase as ServiceDatabase, query::asset_query::{ AssetQuery, AssetSpendTarget, @@ -323,15 +323,19 @@ mod tests { let result: Vec<_> = spend_query .iter() .map(|asset| { - largest_first(&AssetQuery::new(owner, asset, base_asset_id, None, db)) - .map(|coins| { - coins - .iter() - .map(|coin| { - (*coin.asset_id(base_asset_id), coin.amount()) - }) - .collect() - }) + largest_first(&AssetQuery::new( + owner, + asset, + base_asset_id, + None, + &db.view(), + )) + .map(|coins| { + coins + .iter() + .map(|coin| (*coin.asset_id(base_asset_id), coin.amount())) + .collect() + }) }) .try_collect()?; Ok(result) @@ -484,7 +488,7 @@ mod tests { db: &ServiceDatabase, ) -> Result, CoinsQueryError> { let coins = random_improve( - db, + &db.view(), &SpendQuery::new(owner, &query_per_asset, None, base_asset_id)?, ); @@ -682,7 +686,7 @@ mod tests { Some(excluded_ids), base_asset_id, )?; - let coins = random_improve(&db.service_database(), &spend_query); + let coins = random_improve(&db.service_database().view(), &spend_query); // Transform result for convenience coins.map(|coins| { @@ -840,7 +844,7 @@ mod tests { } let coins = random_improve( - &db.service_database(), + &db.service_database().view(), &SpendQuery::new( owner, &[AssetSpendTarget { @@ -930,7 +934,8 @@ mod tests { } fn service_database(&self) -> ServiceDatabase { - Box::new(self.database.clone()) + let database = self.database.clone(); + ServiceDatabase::new(database.clone(), database) } } @@ -980,18 +985,22 @@ mod tests { pub fn owned_coins(&self, owner: &Address) -> Vec { use crate::query::CoinQueryData; - let db = self.service_database(); - db.owned_coins_ids(owner, None, IterDirection::Forward) - .map(|res| res.map(|id| db.coin(id).unwrap())) + let query = self.service_database(); + let query = query.view(); + 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 { use crate::query::MessageQueryData; - let db = self.service_database(); - db.owned_message_ids(owner, None, IterDirection::Forward) - .map(|res| res.map(|id| db.message(&id).unwrap())) + let query = self.service_database(); + let query = query.view(); + query + .owned_message_ids(owner, None, IterDirection::Forward) + .map(|res| res.map(|id| query.message(&id).unwrap())) .try_collect() .unwrap() } diff --git a/crates/fuel-core/src/executor.rs b/crates/fuel-core/src/executor.rs index 04a770582c..c719aadeb5 100644 --- a/crates/fuel-core/src/executor.rs +++ b/crates/fuel-core/src/executor.rs @@ -19,7 +19,6 @@ mod tests { Coins, ContractsRawCode, Messages, - Receipts, }, StorageAsMut, }; @@ -662,23 +661,18 @@ mod tests { coinbase_recipient: config_coinbase, ..Default::default() }; - let mut producer = create_executor(Default::default(), config); + let producer = create_executor(Default::default(), config); let mut block = Block::default(); *block.transactions_mut() = vec![script.clone().into()]; - assert!(producer + let ExecutionResult { tx_status, .. } = producer .execute_and_commit( ExecutionBlock::Production(block.into()), - Default::default() + Default::default(), ) - .is_ok()); - let receipts = producer - .database - .storage::() - .get(&script.id(&producer.config.consensus_parameters.chain_id)) - .unwrap() - .unwrap(); + .expect("Should execute the block"); + let receipts = &tx_status[0].receipts; if let Some(Receipt::Return { val, .. }) = receipts.get(0) { *val == 1 @@ -2756,20 +2750,16 @@ mod tests { }, ); - executor + let ExecutionResult { tx_status, .. } = executor .execute_and_commit( ExecutionBlock::Production(block), ExecutionOptions { utxo_validation: true, }, ) - .unwrap(); + .expect("Should execute the block"); - let receipts = database - .storage::() - .get(&tx.id(&ChainId::default())) - .unwrap() - .unwrap(); + let receipts = &tx_status[0].receipts; assert_eq!(block_height as u64, receipts[0].val().unwrap()); } @@ -2835,21 +2825,16 @@ mod tests { }, ); - executor + let ExecutionResult { tx_status, .. } = executor .execute_and_commit( ExecutionBlock::Production(block), ExecutionOptions { utxo_validation: true, }, ) - .unwrap(); - - let receipts = database - .storage::() - .get(&tx.id(&ChainId::default())) - .unwrap() - .unwrap(); + .expect("Should execute the block"); + let receipts = &tx_status[0].receipts; assert_eq!(time.0, receipts[0].val().unwrap()); } } diff --git a/crates/fuel-core/src/graphql_api.rs b/crates/fuel-core/src/graphql_api.rs index 3fd27a3c19..12603d964a 100644 --- a/crates/fuel-core/src/graphql_api.rs +++ b/crates/fuel-core/src/graphql_api.rs @@ -9,9 +9,12 @@ use fuel_core_types::{ }; use std::net::SocketAddr; +pub mod api_service; +pub mod database; pub(crate) mod metrics_extension; pub mod ports; -pub mod service; +pub(crate) mod view_extension; +pub mod worker_service; #[derive(Clone, Debug)] pub struct Config { diff --git a/crates/fuel-core/src/graphql_api/service.rs b/crates/fuel-core/src/graphql_api/api_service.rs similarity index 89% rename from crates/fuel-core/src/graphql_api/service.rs rename to crates/fuel-core/src/graphql_api/api_service.rs index 6c6879ae30..15023a5995 100644 --- a/crates/fuel-core/src/graphql_api/service.rs +++ b/crates/fuel-core/src/graphql_api/api_service.rs @@ -1,13 +1,17 @@ use crate::{ - fuel_core_graphql_api::ports::{ - BlockProducerPort, - ConsensusModulePort, - DatabasePort, - P2pPort, - TxPoolPort, - }, - graphql_api::{ + fuel_core_graphql_api::{ + database::{ + OffChainView, + OnChainView, + }, metrics_extension::MetricsExtension, + ports::{ + BlockProducerPort, + ConsensusModulePort, + P2pPort, + TxPoolPort, + }, + view_extension::ViewExtension, Config, }, schema::{ @@ -55,6 +59,7 @@ use fuel_core_services::{ RunnableTask, StateWatcher, }; +use fuel_core_storage::transactional::AtomicView; use futures::Stream; use serde_json::json; use std::{ @@ -75,7 +80,7 @@ use tower_http::{ pub type Service = fuel_core_services::ServiceRunner; -pub type Database = Box; +pub use super::database::ReadDatabase; pub type BlockProducer = Box; // In the future GraphQL should not be aware of `TxPool`. It should @@ -160,28 +165,35 @@ impl RunnableTask for Task { // Need a seperate Data Object for each Query endpoint, cannot be avoided #[allow(clippy::too_many_arguments)] -pub fn new_service( +pub fn new_service( config: Config, schema: CoreSchemaBuilder, - database: Database, + on_database: OnChain, + off_database: OffChain, txpool: TxPool, producer: BlockProducer, consensus_module: ConsensusModule, p2p_service: P2pService, log_threshold_ms: Duration, request_timeout: Duration, -) -> anyhow::Result { +) -> anyhow::Result +where + OnChain: AtomicView + 'static, + OffChain: AtomicView + 'static, +{ let network_addr = config.addr; + let combined_read_database = ReadDatabase::new(on_database, off_database); let schema = schema .data(config) - .data(database) + .data(combined_read_database) .data(txpool) .data(producer) .data(consensus_module) .data(p2p_service) .extension(async_graphql::extensions::Tracing) .extension(MetricsExtension::new(log_threshold_ms)) + .extension(ViewExtension::new()) .finish(); let router = Router::new() diff --git a/crates/fuel-core/src/graphql_api/database.rs b/crates/fuel-core/src/graphql_api/database.rs new file mode 100644 index 0000000000..175f0610f1 --- /dev/null +++ b/crates/fuel-core/src/graphql_api/database.rs @@ -0,0 +1,223 @@ +use crate::fuel_core_graphql_api::ports::{ + DatabaseBlocks, + DatabaseChain, + DatabaseContracts, + DatabaseMessageProof, + DatabaseMessages, + OffChainDatabase, + OnChainDatabase, +}; +use fuel_core_storage::{ + iter::{ + BoxedIter, + IterDirection, + }, + tables::Receipts, + transactional::AtomicView, + Error as StorageError, + Mappable, + Result as StorageResult, + StorageInspect, +}; +use fuel_core_txpool::types::{ + ContractId, + TxId, +}; +use fuel_core_types::{ + blockchain::primitives::{ + BlockId, + DaBlockHeight, + }, + entities::message::{ + MerkleProof, + Message, + }, + fuel_tx::{ + Address, + AssetId, + TxPointer, + UtxoId, + }, + fuel_types::{ + BlockHeight, + Nonce, + }, + services::{ + graphql_api::ContractBalance, + txpool::TransactionStatus, + }, +}; +use std::{ + borrow::Cow, + sync::Arc, +}; + +pub type OnChainView = Arc; +pub type OffChainView = Arc; + +pub struct ReadDatabase { + on_chain: Box>, + off_chain: Box>, +} + +impl ReadDatabase { + pub fn new(on_chain: OnChain, off_chain: OffChain) -> Self + where + OnChain: AtomicView + 'static, + OffChain: AtomicView + 'static, + { + Self { + on_chain: Box::new(on_chain), + off_chain: Box::new(off_chain), + } + } + + pub fn view(&self) -> ReadView { + ReadView { + on_chain: self.on_chain.latest_view(), + off_chain: self.off_chain.latest_view(), + } + } +} + +pub struct ReadView { + on_chain: OnChainView, + off_chain: OffChainView, +} + +impl DatabaseBlocks for ReadView { + fn block_id(&self, height: &BlockHeight) -> StorageResult { + self.on_chain.block_id(height) + } + + fn blocks_ids( + &self, + start: Option, + direction: IterDirection, + ) -> BoxedIter<'_, StorageResult<(BlockHeight, BlockId)>> { + self.on_chain.blocks_ids(start, direction) + } + + fn ids_of_latest_block(&self) -> StorageResult<(BlockHeight, BlockId)> { + self.on_chain.ids_of_latest_block() + } +} + +impl StorageInspect for ReadView +where + M: Mappable, + dyn OnChainDatabase: StorageInspect, +{ + type Error = StorageError; + + fn get(&self, key: &M::Key) -> StorageResult>> { + self.on_chain.get(key) + } + + fn contains_key(&self, key: &M::Key) -> StorageResult { + self.on_chain.contains_key(key) + } +} + +impl DatabaseMessages for ReadView { + fn all_messages( + &self, + start_message_id: Option, + direction: IterDirection, + ) -> BoxedIter<'_, StorageResult> { + self.on_chain.all_messages(start_message_id, direction) + } + + fn message_is_spent(&self, nonce: &Nonce) -> StorageResult { + self.on_chain.message_is_spent(nonce) + } + + fn message_exists(&self, nonce: &Nonce) -> StorageResult { + self.on_chain.message_exists(nonce) + } +} + +impl DatabaseContracts for ReadView { + fn contract_balances( + &self, + contract: ContractId, + start_asset: Option, + direction: IterDirection, + ) -> BoxedIter> { + self.on_chain + .contract_balances(contract, start_asset, direction) + } +} + +impl DatabaseChain for ReadView { + fn chain_name(&self) -> StorageResult { + self.on_chain.chain_name() + } + + fn da_height(&self) -> StorageResult { + self.on_chain.da_height() + } +} + +impl DatabaseMessageProof for ReadView { + fn block_history_proof( + &self, + message_block_height: &BlockHeight, + commit_block_height: &BlockHeight, + ) -> StorageResult { + self.on_chain + .block_history_proof(message_block_height, commit_block_height) + } +} + +impl OnChainDatabase for ReadView {} + +impl StorageInspect for ReadView { + type Error = StorageError; + + fn get( + &self, + key: &::Key, + ) -> StorageResult::OwnedValue>>> { + self.off_chain.get(key) + } + + fn contains_key(&self, key: &::Key) -> StorageResult { + self.off_chain.contains_key(key) + } +} + +impl OffChainDatabase for ReadView { + fn owned_message_ids( + &self, + owner: &Address, + start_message_id: Option, + direction: IterDirection, + ) -> BoxedIter<'_, StorageResult> { + self.off_chain + .owned_message_ids(owner, start_message_id, direction) + } + + fn owned_coins_ids( + &self, + owner: &Address, + start_coin: Option, + direction: IterDirection, + ) -> BoxedIter<'_, StorageResult> { + self.off_chain.owned_coins_ids(owner, start_coin, direction) + } + + fn tx_status(&self, tx_id: &TxId) -> StorageResult { + self.off_chain.tx_status(tx_id) + } + + fn owned_transactions_ids( + &self, + owner: Address, + start: Option, + direction: IterDirection, + ) -> BoxedIter> { + self.off_chain + .owned_transactions_ids(owner, start, direction) + } +} diff --git a/crates/fuel-core/src/graphql_api/ports.rs b/crates/fuel-core/src/graphql_api/ports.rs index b897acb248..44ff62b79b 100644 --- a/crates/fuel-core/src/graphql_api/ports.rs +++ b/crates/fuel-core/src/graphql_api/ports.rs @@ -14,7 +14,6 @@ use fuel_core_storage::{ Messages, Receipts, SealedBlockConsensus, - SpentMessages, Transactions, }, Error as StorageError, @@ -57,14 +56,41 @@ use fuel_core_types::{ }; use std::sync::Arc; -/// The database port expected by GraphQL API service. -pub trait DatabasePort: +pub trait OffChainDatabase: + Send + Sync + StorageInspect +{ + fn owned_message_ids( + &self, + owner: &Address, + start_message_id: Option, + direction: IterDirection, + ) -> BoxedIter<'_, StorageResult>; + + fn owned_coins_ids( + &self, + owner: &Address, + start_coin: Option, + direction: IterDirection, + ) -> BoxedIter<'_, StorageResult>; + + fn tx_status(&self, tx_id: &TxId) -> StorageResult; + + fn owned_transactions_ids( + &self, + owner: Address, + start: Option, + direction: IterDirection, + ) -> BoxedIter>; +} + +/// The on chain database port expected by GraphQL API service. +pub trait OnChainDatabase: Send + Sync + DatabaseBlocks - + DatabaseTransactions + + StorageInspect + DatabaseMessages - + DatabaseCoins + + StorageInspect + DatabaseContracts + DatabaseChain + DatabaseMessageProof @@ -87,33 +113,8 @@ pub trait DatabaseBlocks: 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 - + StorageInspect -{ - fn owned_message_ids( - &self, - owner: &Address, - start_message_id: Option, - direction: IterDirection, - ) -> BoxedIter<'_, StorageResult>; - +pub trait DatabaseMessages: StorageInspect { fn all_messages( &self, start_message_id: Option, @@ -125,16 +126,6 @@ pub trait DatabaseMessages: fn message_exists(&self, nonce: &Nonce) -> 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 @@ -174,7 +165,7 @@ pub trait TxPoolPort: Send + Sync { } #[async_trait] -pub trait DryRunExecution { +pub trait BlockProducerPort: Send + Sync { async fn dry_run_tx( &self, transaction: Transaction, @@ -183,8 +174,6 @@ pub trait DryRunExecution { ) -> anyhow::Result>; } -pub trait BlockProducerPort: Send + Sync + DryRunExecution {} - #[async_trait::async_trait] pub trait ConsensusModulePort: Send + Sync { async fn manually_produce_blocks( @@ -209,3 +198,51 @@ pub trait DatabaseMessageProof: Send + Sync { pub trait P2pPort: Send + Sync { async fn all_peer_info(&self) -> anyhow::Result>; } + +pub mod worker { + use fuel_core_services::stream::BoxStream; + use fuel_core_storage::{ + tables::Receipts, + transactional::Transactional, + Error as StorageError, + Result as StorageResult, + StorageMutate, + }; + use fuel_core_types::{ + fuel_tx::{ + Address, + Bytes32, + }, + fuel_types::BlockHeight, + services::{ + block_importer::SharedImportResult, + txpool::TransactionStatus, + }, + }; + + pub trait OffChainDatabase: + Send + + Sync + + StorageMutate + + Transactional + { + fn record_tx_id_owner( + &mut self, + owner: &Address, + block_height: BlockHeight, + tx_idx: u16, + tx_id: &Bytes32, + ) -> StorageResult>; + + fn update_tx_status( + &mut self, + id: &Bytes32, + status: TransactionStatus, + ) -> StorageResult>; + } + + pub trait BlockImporter { + /// Returns a stream of imported block. + fn block_events(&self) -> BoxStream; + } +} diff --git a/crates/fuel-core/src/graphql_api/view_extension.rs b/crates/fuel-core/src/graphql_api/view_extension.rs new file mode 100644 index 0000000000..ca482fe987 --- /dev/null +++ b/crates/fuel-core/src/graphql_api/view_extension.rs @@ -0,0 +1,44 @@ +use crate::graphql_api::database::ReadDatabase; +use async_graphql::{ + extensions::{ + Extension, + ExtensionContext, + ExtensionFactory, + NextPrepareRequest, + }, + Request, + ServerResult, +}; +use std::sync::Arc; + +/// The extension that adds the `ReadView` to the request context. +/// It guarantees that the request works with the one view of the database, +/// and external database modification cannot affect the result. +pub(crate) struct ViewExtension; + +impl ViewExtension { + pub fn new() -> Self { + Self + } +} + +impl ExtensionFactory for ViewExtension { + fn create(&self) -> Arc { + Arc::new(ViewExtension::new()) + } +} + +#[async_trait::async_trait] +impl Extension for ViewExtension { + async fn prepare_request( + &self, + ctx: &ExtensionContext<'_>, + request: Request, + next: NextPrepareRequest<'_>, + ) -> ServerResult { + let database: &ReadDatabase = ctx.data_unchecked(); + let view = database.view(); + let request = request.data(view); + next.run(ctx, request).await + } +} diff --git a/crates/fuel-core/src/graphql_api/worker_service.rs b/crates/fuel-core/src/graphql_api/worker_service.rs new file mode 100644 index 0000000000..fe904d7f7d --- /dev/null +++ b/crates/fuel-core/src/graphql_api/worker_service.rs @@ -0,0 +1,277 @@ +use crate::fuel_core_graphql_api::ports; +use fuel_core_services::{ + stream::BoxStream, + EmptyShared, + RunnableService, + RunnableTask, + ServiceRunner, + StateWatcher, +}; +use fuel_core_storage::{ + tables::Receipts, + Result as StorageResult, + StorageAsMut, +}; +use fuel_core_types::{ + blockchain::block::Block, + fuel_tx::{ + field::{ + Inputs, + Outputs, + }, + input::coin::{ + CoinPredicate, + CoinSigned, + }, + Input, + Output, + Receipt, + Transaction, + TxId, + UniqueIdentifier, + }, + fuel_types::{ + BlockHeight, + Bytes32, + }, + services::{ + block_importer::{ + ImportResult, + SharedImportResult, + }, + executor::TransactionExecutionStatus, + txpool::from_executor_to_status, + }, +}; +use futures::{ + FutureExt, + StreamExt, +}; + +pub struct Task { + block_importer: BoxStream, + database: D, +} + +impl Task +where + D: ports::worker::OffChainDatabase, +{ + fn process_block(&mut self, result: SharedImportResult) -> anyhow::Result<()> { + let mut transaction = self.database.transaction(); + // save the status for every transaction using the finalized block id + self.persist_transaction_status(&result, transaction.as_mut())?; + + // save the associated owner for each transaction in the block + self.index_tx_owners_for_block( + &result.sealed_block.entity, + transaction.as_mut(), + )?; + transaction.commit()?; + + Ok(()) + } + + /// Associate all transactions within a block to their respective UTXO owners + fn index_tx_owners_for_block( + &self, + block: &Block, + block_st_transaction: &mut D, + ) -> anyhow::Result<()> { + for (tx_idx, tx) in block.transactions().iter().enumerate() { + let block_height = *block.header().height(); + let inputs; + let outputs; + let tx_idx = u16::try_from(tx_idx).map_err(|e| { + anyhow::anyhow!("The block has more than `u16::MAX` transactions, {}", e) + })?; + let tx_id = tx.cached_id().expect( + "The imported block should contains only transactions with cached id", + ); + match tx { + Transaction::Script(tx) => { + inputs = tx.inputs().as_slice(); + outputs = tx.outputs().as_slice(); + } + Transaction::Create(tx) => { + inputs = tx.inputs().as_slice(); + outputs = tx.outputs().as_slice(); + } + Transaction::Mint(_) => continue, + } + self.persist_owners_index( + block_height, + inputs, + outputs, + &tx_id, + tx_idx, + block_st_transaction, + )?; + } + Ok(()) + } + + /// Index the tx id by owner for all of the inputs and outputs + fn persist_owners_index( + &self, + block_height: BlockHeight, + inputs: &[Input], + outputs: &[Output], + tx_id: &Bytes32, + tx_idx: u16, + db: &mut D, + ) -> StorageResult<()> { + let mut owners = vec![]; + for input in inputs { + if let Input::CoinSigned(CoinSigned { owner, .. }) + | Input::CoinPredicate(CoinPredicate { owner, .. }) = input + { + owners.push(owner); + } + } + + for output in outputs { + match output { + Output::Coin { to, .. } + | Output::Change { to, .. } + | Output::Variable { to, .. } => { + owners.push(to); + } + Output::Contract(_) | Output::ContractCreated { .. } => {} + } + } + + // dedupe owners from inputs and outputs prior to indexing + owners.sort(); + owners.dedup(); + + for owner in owners { + db.record_tx_id_owner(owner, block_height, tx_idx, tx_id)?; + } + + Ok(()) + } + + fn persist_transaction_status( + &self, + import_result: &ImportResult, + db: &mut D, + ) -> StorageResult<()> { + for TransactionExecutionStatus { + id, + result, + receipts, + } in import_result.tx_status.iter() + { + let status = from_executor_to_status( + &import_result.sealed_block.entity, + result.clone(), + ); + + if db.update_tx_status(id, status)?.is_some() { + return Err(anyhow::anyhow!( + "Transaction status already exists for tx {}", + id + ) + .into()); + } + + self.persist_receipts(id, receipts, db)?; + } + Ok(()) + } + + fn persist_receipts( + &self, + tx_id: &TxId, + receipts: &[Receipt], + db: &mut D, + ) -> StorageResult<()> { + if db.storage::().insert(tx_id, receipts)?.is_some() { + return Err(anyhow::anyhow!("Receipts already exist for tx {}", tx_id).into()); + } + Ok(()) + } +} + +#[async_trait::async_trait] +impl RunnableService for Task +where + D: ports::worker::OffChainDatabase, +{ + const NAME: &'static str = "GraphQL_Off_Chain_Worker"; + type SharedData = EmptyShared; + type Task = Self; + type TaskParams = (); + + fn shared_data(&self) -> Self::SharedData { + EmptyShared + } + + async fn into_task( + self, + _: &StateWatcher, + _: Self::TaskParams, + ) -> anyhow::Result { + // TODO: It is possible that the node was shut down before we processed all imported blocks. + // It could lead to some missed blocks and the database's inconsistent state. + // Because the result of block execution is not stored on the chain, it is impossible + // to actualize the database without executing the block at the previous state + // of the blockchain. When `AtomicView::view_at` is implemented, we can + // process all missed blocks and actualize the database here. + Ok(self) + } +} + +#[async_trait::async_trait] +impl RunnableTask for Task +where + D: ports::worker::OffChainDatabase, +{ + async fn run(&mut self, watcher: &mut StateWatcher) -> anyhow::Result { + let should_continue; + tokio::select! { + biased; + + _ = watcher.while_started() => { + should_continue = false; + } + + result = self.block_importer.next() => { + if let Some(block) = result { + self.process_block(block)?; + + should_continue = true + } else { + should_continue = false + } + } + } + Ok(should_continue) + } + + async fn shutdown(mut self) -> anyhow::Result<()> { + loop { + let result = self.block_importer.next().now_or_never(); + + if let Some(Some(block)) = result { + self.process_block(block)?; + } else { + break; + } + } + Ok(()) + } +} + +pub fn new_service(block_importer: I, database: D) -> ServiceRunner> +where + I: ports::worker::BlockImporter, + D: ports::worker::OffChainDatabase, +{ + let block_importer = block_importer.block_events(); + ServiceRunner::new(Task { + block_importer, + database, + }) +} diff --git a/crates/fuel-core/src/query/balance.rs b/crates/fuel-core/src/query/balance.rs index c597742225..ecbc47620b 100644 --- a/crates/fuel-core/src/query/balance.rs +++ b/crates/fuel-core/src/query/balance.rs @@ -1,4 +1,4 @@ -use crate::fuel_core_graphql_api::service::Database; +use crate::fuel_core_graphql_api::database::ReadView; use asset_query::{ AssetQuery, AssetSpendTarget, @@ -43,7 +43,7 @@ pub trait BalanceQueryData: Send + Sync { ) -> BoxedIter>; } -impl BalanceQueryData for Database { +impl BalanceQueryData for ReadView { fn balance( &self, owner: Address, diff --git a/crates/fuel-core/src/query/balance/asset_query.rs b/crates/fuel-core/src/query/balance/asset_query.rs index e93c9d0f30..ee0266b124 100644 --- a/crates/fuel-core/src/query/balance/asset_query.rs +++ b/crates/fuel-core/src/query/balance/asset_query.rs @@ -1,5 +1,5 @@ use crate::{ - graphql_api::service::Database, + graphql_api::database::ReadView, query::{ CoinQueryData, MessageQueryData, @@ -58,7 +58,7 @@ pub struct AssetsQuery<'a> { pub owner: &'a Address, pub assets: Option>, pub exclude: Option<&'a Exclude>, - pub database: &'a Database, + pub database: &'a ReadView, pub base_asset_id: &'a AssetId, } @@ -67,7 +67,7 @@ impl<'a> AssetsQuery<'a> { owner: &'a Address, assets: Option>, exclude: Option<&'a Exclude>, - database: &'a Database, + database: &'a ReadView, base_asset_id: &'a AssetId, ) -> Self { Self { @@ -171,7 +171,7 @@ pub struct AssetQuery<'a> { pub owner: &'a Address, pub asset: &'a AssetSpendTarget, pub exclude: Option<&'a Exclude>, - pub database: &'a Database, + pub database: &'a ReadView, query: AssetsQuery<'a>, } @@ -181,7 +181,7 @@ impl<'a> AssetQuery<'a> { asset: &'a AssetSpendTarget, base_asset_id: &'a AssetId, exclude: Option<&'a Exclude>, - database: &'a Database, + database: &'a ReadView, ) -> Self { let mut allowed = HashSet::new(); allowed.insert(&asset.id); diff --git a/crates/fuel-core/src/query/block.rs b/crates/fuel-core/src/query/block.rs index 66cba1f941..8aeed56f76 100644 --- a/crates/fuel-core/src/query/block.rs +++ b/crates/fuel-core/src/query/block.rs @@ -1,4 +1,4 @@ -use crate::graphql_api::ports::DatabasePort; +use crate::fuel_core_graphql_api::ports::OnChainDatabase; use fuel_core_storage::{ iter::{ BoxedIter, @@ -26,7 +26,7 @@ pub trait SimpleBlockData: Send + Sync { fn block(&self, id: &BlockId) -> StorageResult; } -impl SimpleBlockData for D { +impl SimpleBlockData for D { fn block(&self, id: &BlockId) -> StorageResult { let block = self .storage::() @@ -56,7 +56,7 @@ pub trait BlockQueryData: Send + Sync + SimpleBlockData { fn consensus(&self, id: &BlockId) -> StorageResult; } -impl BlockQueryData for D { +impl BlockQueryData for D { fn block_id(&self, height: &BlockHeight) -> StorageResult { self.block_id(height) } diff --git a/crates/fuel-core/src/query/chain.rs b/crates/fuel-core/src/query/chain.rs index 88ce035ba1..b9408ddfcd 100644 --- a/crates/fuel-core/src/query/chain.rs +++ b/crates/fuel-core/src/query/chain.rs @@ -1,4 +1,4 @@ -use crate::graphql_api::ports::DatabasePort; +use crate::fuel_core_graphql_api::ports::OnChainDatabase; use fuel_core_storage::Result as StorageResult; use fuel_core_types::blockchain::primitives::DaBlockHeight; @@ -8,7 +8,7 @@ pub trait ChainQueryData: Send + Sync { fn da_height(&self) -> StorageResult; } -impl ChainQueryData for D { +impl ChainQueryData for D { fn name(&self) -> StorageResult { self.chain_name() } diff --git a/crates/fuel-core/src/query/coin.rs b/crates/fuel-core/src/query/coin.rs index d31b60690e..427379a728 100644 --- a/crates/fuel-core/src/query/coin.rs +++ b/crates/fuel-core/src/query/coin.rs @@ -1,4 +1,7 @@ -use crate::graphql_api::ports::DatabasePort; +use crate::fuel_core_graphql_api::ports::{ + OffChainDatabase, + OnChainDatabase, +}; use fuel_core_storage::{ iter::{ BoxedIter, @@ -34,7 +37,7 @@ pub trait CoinQueryData: Send + Sync { ) -> BoxedIter>; } -impl CoinQueryData for D { +impl CoinQueryData for D { fn coin(&self, utxo_id: UtxoId) -> StorageResult { let coin = self .storage::() diff --git a/crates/fuel-core/src/query/contract.rs b/crates/fuel-core/src/query/contract.rs index d05d90999b..d4bbb8b5d6 100644 --- a/crates/fuel-core/src/query/contract.rs +++ b/crates/fuel-core/src/query/contract.rs @@ -1,4 +1,4 @@ -use crate::graphql_api::ports::DatabasePort; +use crate::fuel_core_graphql_api::ports::OnChainDatabase; use fuel_core_storage::{ iter::{ BoxedIter, @@ -43,7 +43,7 @@ pub trait ContractQueryData: Send + Sync { ) -> BoxedIter>; } -impl ContractQueryData for D { +impl ContractQueryData for D { fn contract_id(&self, id: ContractId) -> StorageResult { let contract_exists = self.storage::().contains_key(&id)?; if contract_exists { diff --git a/crates/fuel-core/src/query/message.rs b/crates/fuel-core/src/query/message.rs index b1ce17e4bb..334c24dc0d 100644 --- a/crates/fuel-core/src/query/message.rs +++ b/crates/fuel-core/src/query/message.rs @@ -3,7 +3,8 @@ use crate::{ ports::{ DatabaseMessageProof, DatabaseMessages, - DatabasePort, + OffChainDatabase, + OnChainDatabase, }, IntoApiResult, }, @@ -80,7 +81,7 @@ pub trait MessageQueryData: Send + Sync { ) -> BoxedIter>; } -impl MessageQueryData for D { +impl MessageQueryData for D { fn message(&self, id: &Nonce) -> StorageResult { self.storage::() .get(id)? @@ -128,7 +129,10 @@ pub trait MessageProofData: ) -> StorageResult; } -impl MessageProofData for D { +impl MessageProofData for D +where + D: OnChainDatabase + OffChainDatabase + ?Sized, +{ fn transaction_status( &self, transaction_id: &TxId, diff --git a/crates/fuel-core/src/query/tx.rs b/crates/fuel-core/src/query/tx.rs index 74d325e33a..09994be55f 100644 --- a/crates/fuel-core/src/query/tx.rs +++ b/crates/fuel-core/src/query/tx.rs @@ -1,4 +1,7 @@ -use crate::graphql_api::ports::DatabasePort; +use crate::fuel_core_graphql_api::ports::{ + OffChainDatabase, + OnChainDatabase, +}; use fuel_core_storage::{ iter::{ BoxedIter, @@ -32,7 +35,10 @@ pub trait SimpleTransactionData: Send + Sync { fn transaction(&self, transaction_id: &TxId) -> StorageResult; } -impl SimpleTransactionData for D { +impl SimpleTransactionData for D +where + D: OffChainDatabase + OnChainDatabase + ?Sized, +{ fn transaction(&self, tx_id: &TxId) -> StorageResult { self.storage::() .get(tx_id) @@ -57,7 +63,10 @@ pub trait TransactionQueryData: Send + Sync + SimpleTransactionData { ) -> BoxedIter>; } -impl TransactionQueryData for D { +impl TransactionQueryData for D +where + D: OnChainDatabase + OffChainDatabase + ?Sized, +{ fn status(&self, tx_id: &TxId) -> StorageResult { self.tx_status(tx_id) } diff --git a/crates/fuel-core/src/schema/balance.rs b/crates/fuel-core/src/schema/balance.rs index 9188696a89..da5a72ada5 100644 --- a/crates/fuel-core/src/schema/balance.rs +++ b/crates/fuel-core/src/schema/balance.rs @@ -1,6 +1,6 @@ use crate::{ fuel_core_graphql_api::{ - service::Database, + database::ReadView, Config, }, query::BalanceQueryData, @@ -56,12 +56,12 @@ impl BalanceQuery { #[graphql(desc = "address of the owner")] owner: Address, #[graphql(desc = "asset_id of the coin")] asset_id: AssetId, ) -> async_graphql::Result { - let data: &Database = ctx.data_unchecked(); + let query: &ReadView = ctx.data_unchecked(); let base_asset_id = *ctx .data_unchecked::() .consensus_parameters .base_asset_id(); - let balance = data.balance(owner.0, asset_id.0, base_asset_id)?.into(); + let balance = query.balance(owner.0, asset_id.0, base_asset_id)?.into(); Ok(balance) } @@ -82,7 +82,7 @@ impl BalanceQuery { if before.is_some() || after.is_some() { return Err(anyhow!("pagination is not yet supported").into()) } - let query: &Database = ctx.data_unchecked(); + let query: &ReadView = ctx.data_unchecked(); crate::schema::query_pagination(after, before, first, last, |_, direction| { let owner = filter.owner.into(); let base_asset_id = *ctx diff --git a/crates/fuel-core/src/schema/block.rs b/crates/fuel-core/src/schema/block.rs index 5d503f281b..a092600c07 100644 --- a/crates/fuel-core/src/schema/block.rs +++ b/crates/fuel-core/src/schema/block.rs @@ -4,13 +4,11 @@ use super::scalars::{ }; use crate::{ fuel_core_graphql_api::{ - service::{ - ConsensusModule, - Database, - }, + api_service::ConsensusModule, + database::ReadView, Config as GraphQLConfig, + IntoApiResult, }, - graphql_api::IntoApiResult, query::{ BlockQueryData, SimpleBlockData, @@ -96,7 +94,7 @@ impl Block { } async fn consensus(&self, ctx: &Context<'_>) -> async_graphql::Result { - let query: &Database = ctx.data_unchecked(); + let query: &ReadView = ctx.data_unchecked(); let id = self.0.header().id(); let consensus = query.consensus(&id)?; @@ -107,7 +105,7 @@ impl Block { &self, ctx: &Context<'_>, ) -> async_graphql::Result> { - let query: &Database = ctx.data_unchecked(); + let query: &ReadView = ctx.data_unchecked(); self.0 .transactions() .iter() @@ -192,7 +190,7 @@ impl BlockQuery { #[graphql(desc = "ID of the block")] id: Option, #[graphql(desc = "Height of the block")] height: Option, ) -> async_graphql::Result> { - let data: &Database = ctx.data_unchecked(); + let query: &ReadView = ctx.data_unchecked(); let id = match (id, height) { (Some(_), Some(_)) => { return Err(async_graphql::Error::new( @@ -202,14 +200,14 @@ impl BlockQuery { (Some(id), None) => Ok(id.0.into()), (None, Some(height)) => { let height: u32 = height.into(); - data.block_id(&height.into()) + query.block_id(&height.into()) } (None, None) => { return Err(async_graphql::Error::new("Missing either id or height")) } }; - id.and_then(|id| data.block(&id)).into_api_result() + id.and_then(|id| query.block(&id)).into_api_result() } async fn blocks( @@ -220,9 +218,9 @@ impl BlockQuery { last: Option, before: Option, ) -> async_graphql::Result> { - let db: &Database = ctx.data_unchecked(); + let query: &ReadView = ctx.data_unchecked(); crate::schema::query_pagination(after, before, first, last, |start, direction| { - Ok(blocks_query(db, start.map(Into::into), direction)) + Ok(blocks_query(query, start.map(Into::into), direction)) }) .await } @@ -253,16 +251,16 @@ impl HeaderQuery { last: Option, before: Option, ) -> async_graphql::Result> { - let db: &Database = ctx.data_unchecked(); + let query: &ReadView = ctx.data_unchecked(); crate::schema::query_pagination(after, before, first, last, |start, direction| { - Ok(blocks_query(db, start.map(Into::into), direction)) + Ok(blocks_query(query, start.map(Into::into), direction)) }) .await } } fn blocks_query( - query: &Database, + query: &ReadView, start: Option, direction: IterDirection, ) -> BoxedIter> @@ -292,7 +290,7 @@ impl BlockMutation { start_timestamp: Option, blocks_to_produce: U32, ) -> async_graphql::Result { - let query: &Database = ctx.data_unchecked(); + let query: &ReadView = ctx.data_unchecked(); let consensus_module = ctx.data_unchecked::(); let config = ctx.data_unchecked::().clone(); diff --git a/crates/fuel-core/src/schema/chain.rs b/crates/fuel-core/src/schema/chain.rs index e1df56c7eb..7c8bb918aa 100644 --- a/crates/fuel-core/src/schema/chain.rs +++ b/crates/fuel-core/src/schema/chain.rs @@ -1,6 +1,6 @@ use crate::{ fuel_core_graphql_api::{ - service::Database, + database::ReadView, Config as GraphQLConfig, }, query::{ @@ -683,19 +683,19 @@ impl HeavyOperation { #[Object] impl ChainInfo { async fn name(&self, ctx: &Context<'_>) -> async_graphql::Result { - let data: &Database = ctx.data_unchecked(); - Ok(data.name()?) + let query: &ReadView = ctx.data_unchecked(); + Ok(query.name()?) } async fn latest_block(&self, ctx: &Context<'_>) -> async_graphql::Result { - let query: &Database = ctx.data_unchecked(); + let query: &ReadView = ctx.data_unchecked(); let latest_block = query.latest_block()?.into(); Ok(latest_block) } async fn da_height(&self, ctx: &Context<'_>) -> U64 { - let query: &Database = ctx.data_unchecked(); + let query: &ReadView = ctx.data_unchecked(); let height = query .da_height() diff --git a/crates/fuel-core/src/schema/coins.rs b/crates/fuel-core/src/schema/coins.rs index 60a75add8f..476058016b 100644 --- a/crates/fuel-core/src/schema/coins.rs +++ b/crates/fuel-core/src/schema/coins.rs @@ -4,10 +4,10 @@ use crate::{ SpendQuery, }, fuel_core_graphql_api::{ + database::ReadView, Config as GraphQLConfig, IntoApiResult, }, - graphql_api::service::Database, query::{ asset_query::AssetSpendTarget, CoinQueryData, @@ -152,8 +152,8 @@ impl CoinQuery { ctx: &Context<'_>, #[graphql(desc = "The ID of the coin")] utxo_id: UtxoId, ) -> async_graphql::Result> { - let data: &Database = ctx.data_unchecked(); - data.coin(utxo_id.0).into_api_result() + let query: &ReadView = ctx.data_unchecked(); + query.coin(utxo_id.0).into_api_result() } /// Gets all unspent coins of some `owner` maybe filtered with by `asset_id` per page. @@ -166,7 +166,7 @@ impl CoinQuery { last: Option, before: Option, ) -> async_graphql::Result> { - let query: &Database = ctx.data_unchecked(); + let query: &ReadView = ctx.data_unchecked(); crate::schema::query_pagination(after, before, first, last, |start, direction| { let owner: fuel_tx::Address = filter.owner.into(); let coins = query @@ -240,9 +240,9 @@ impl CoinQuery { let spend_query = SpendQuery::new(owner, &query_per_asset, excluded_ids, *base_asset_id)?; - let db = ctx.data_unchecked::(); + let query: &ReadView = ctx.data_unchecked(); - let coins = random_improve(db, &spend_query)? + let coins = random_improve(query, &spend_query)? .into_iter() .map(|coins| { coins diff --git a/crates/fuel-core/src/schema/contract.rs b/crates/fuel-core/src/schema/contract.rs index 2409041925..16a26b8770 100644 --- a/crates/fuel-core/src/schema/contract.rs +++ b/crates/fuel-core/src/schema/contract.rs @@ -1,6 +1,6 @@ use crate::{ fuel_core_graphql_api::{ - service::Database, + database::ReadView, IntoApiResult, }, query::ContractQueryData, @@ -41,16 +41,16 @@ impl Contract { } async fn bytecode(&self, ctx: &Context<'_>) -> async_graphql::Result { - let context: &Database = ctx.data_unchecked(); - context + let query: &ReadView = ctx.data_unchecked(); + query .contract_bytecode(self.0) .map(HexString) .map_err(Into::into) } async fn salt(&self, ctx: &Context<'_>) -> async_graphql::Result { - let context: &Database = ctx.data_unchecked(); - context + let query: &ReadView = ctx.data_unchecked(); + query .contract_salt(self.0) .map(Into::into) .map_err(Into::into) @@ -67,8 +67,8 @@ impl ContractQuery { ctx: &Context<'_>, #[graphql(desc = "ID of the Contract")] id: ContractId, ) -> async_graphql::Result> { - let data: &Database = ctx.data_unchecked(); - data.contract_id(id.0).into_api_result() + let query: &ReadView = ctx.data_unchecked(); + query.contract_id(id.0).into_api_result() } } @@ -108,8 +108,8 @@ impl ContractBalanceQuery { ) -> async_graphql::Result { let contract_id = contract.into(); let asset_id = asset.into(); - let context: &Database = ctx.data_unchecked(); - context + let query: &ReadView = ctx.data_unchecked(); + query .contract_balance(contract_id, asset_id) .into_api_result() .map(|result| { @@ -135,7 +135,7 @@ impl ContractBalanceQuery { ) -> async_graphql::Result< Connection, > { - let query: &Database = ctx.data_unchecked(); + let query: &ReadView = ctx.data_unchecked(); crate::schema::query_pagination(after, before, first, last, |start, direction| { let balances = query diff --git a/crates/fuel-core/src/schema/message.rs b/crates/fuel-core/src/schema/message.rs index 75707190e2..dfc1760686 100644 --- a/crates/fuel-core/src/schema/message.rs +++ b/crates/fuel-core/src/schema/message.rs @@ -1,5 +1,3 @@ -use std::ops::Deref; - use super::{ block::Header, scalars::{ @@ -12,7 +10,10 @@ use super::{ }, }; use crate::{ - fuel_core_graphql_api::service::Database, + fuel_core_graphql_api::{ + database::ReadView, + ports::DatabaseBlocks, + }, query::MessageQueryData, schema::scalars::{ BlockId, @@ -75,7 +76,7 @@ impl MessageQuery { before: Option, ) -> async_graphql::Result> { - let query: &Database = ctx.data_unchecked(); + let query: &ReadView = ctx.data_unchecked(); crate::schema::query_pagination( after, before, @@ -114,12 +115,12 @@ impl MessageQuery { commit_block_id: Option, commit_block_height: Option, ) -> async_graphql::Result> { - let data: &Database = ctx.data_unchecked(); + let query: &ReadView = ctx.data_unchecked(); let block_id = match (commit_block_id, commit_block_height) { (Some(commit_block_id), None) => commit_block_id.0.into(), (None, Some(commit_block_height)) => { let block_height = commit_block_height.0.into(); - data.block_id(&block_height)? + query.block_id(&block_height)? } _ => Err(anyhow::anyhow!( "Either `commit_block_id` or `commit_block_height` must be provided exclusively" @@ -127,7 +128,7 @@ impl MessageQuery { }; Ok(crate::query::message_proof( - data.deref(), + query, transaction_id.into(), nonce.into(), block_id, @@ -140,8 +141,8 @@ impl MessageQuery { ctx: &Context<'_>, nonce: Nonce, ) -> async_graphql::Result { - let data: &Database = ctx.data_unchecked(); - let status = crate::query::message_status(data.deref(), nonce.into())?; + let query: &ReadView = ctx.data_unchecked(); + let status = crate::query::message_status(query, nonce.into())?; Ok(status.into()) } } diff --git a/crates/fuel-core/src/schema/node_info.rs b/crates/fuel-core/src/schema/node_info.rs index 97ef85167c..647b0c4215 100644 --- a/crates/fuel-core/src/schema/node_info.rs +++ b/crates/fuel-core/src/schema/node_info.rs @@ -47,7 +47,7 @@ impl NodeInfo { async fn peers(&self, _ctx: &Context<'_>) -> async_graphql::Result> { #[cfg(feature = "p2p")] { - let p2p: &crate::fuel_core_graphql_api::service::P2pService = + let p2p: &crate::fuel_core_graphql_api::api_service::P2pService = _ctx.data_unchecked(); let peer_info = p2p.all_peer_info().await?; let peers = peer_info.into_iter().map(PeerInfo).collect(); diff --git a/crates/fuel-core/src/schema/tx.rs b/crates/fuel-core/src/schema/tx.rs index 0d772b8685..19a8599b10 100644 --- a/crates/fuel-core/src/schema/tx.rs +++ b/crates/fuel-core/src/schema/tx.rs @@ -1,25 +1,29 @@ use crate::{ fuel_core_graphql_api::{ - service::{ + api_service::{ BlockProducer, - Database, TxPool, }, + database::ReadView, + ports::OffChainDatabase, + Config, IntoApiResult, }, - graphql_api::Config, query::{ transaction_status_change, BlockQueryData, SimpleTransactionData, TransactionQueryData, }, - schema::scalars::{ - Address, - HexString, - SortedTxCursor, - TransactionId, - TxPointer, + schema::{ + scalars::{ + Address, + HexString, + SortedTxCursor, + TransactionId, + TxPointer, + }, + tx::types::TransactionStatus, }, }; use async_graphql::{ @@ -48,7 +52,10 @@ use fuel_core_types::{ }, fuel_types, fuel_types::canonical::Deserialize, - fuel_vm::checked_transaction::EstimatePredicates, + fuel_vm::checked_transaction::{ + CheckPredicateParams, + EstimatePredicates, + }, services::txpool, }; use futures::{ @@ -63,9 +70,6 @@ use std::{ use tokio_stream::StreamExt; use types::Transaction; -use self::types::TransactionStatus; -use fuel_core_types::fuel_vm::checked_transaction::CheckPredicateParams; - pub mod input; pub mod output; pub mod receipt; @@ -81,7 +85,7 @@ impl TxQuery { ctx: &Context<'_>, #[graphql(desc = "The ID of the transaction")] id: TransactionId, ) -> async_graphql::Result> { - let query: &Database = ctx.data_unchecked(); + let query: &ReadView = ctx.data_unchecked(); let id = id.0; let txpool = ctx.data_unchecked::(); @@ -105,8 +109,7 @@ impl TxQuery { ) -> async_graphql::Result< Connection, > { - let db_query: &Database = ctx.data_unchecked(); - let tx_query: &Database = ctx.data_unchecked(); + let query: &ReadView = ctx.data_unchecked(); crate::schema::query_pagination( after, before, @@ -115,7 +118,7 @@ impl TxQuery { |start: &Option, direction| { let start = *start; let block_id = start.map(|sorted| sorted.block_height); - let all_block_ids = db_query.compressed_blocks(block_id, direction); + let all_block_ids = query.compressed_blocks(block_id, direction); let all_txs = all_block_ids .map(move |block| { @@ -145,7 +148,7 @@ impl TxQuery { }); let all_txs = all_txs.map(|result: StorageResult| { result.and_then(|sorted| { - let tx = tx_query.transaction(&sorted.tx_id.0)?; + let tx = query.transaction(&sorted.tx_id.0)?; Ok((sorted, Transaction::from_tx(sorted.tx_id.0, tx))) }) @@ -167,7 +170,7 @@ impl TxQuery { before: Option, ) -> async_graphql::Result> { - let query: &Database = ctx.data_unchecked(); + let query: &ReadView = ctx.data_unchecked(); let config = ctx.data_unchecked::(); let owner = fuel_types::Address::from(owner); @@ -298,11 +301,11 @@ impl TxStatusSubscription { ) -> anyhow::Result> + 'a> { let txpool = ctx.data_unchecked::(); - let db = ctx.data_unchecked::(); + let query: &ReadView = ctx.data_unchecked(); let rx = txpool.tx_update_subscribe(id.into())?; Ok(transaction_status_change( - move |id| match db.tx_status(&id) { + move |id| match query.tx_status(&id) { Ok(status) => Ok(Some(status)), Err(StorageError::NotFound(_, _)) => Ok(txpool .submission_time(id) diff --git a/crates/fuel-core/src/schema/tx/types.rs b/crates/fuel-core/src/schema/tx/types.rs index 41b06f5cb3..fcd0e110ff 100644 --- a/crates/fuel-core/src/schema/tx/types.rs +++ b/crates/fuel-core/src/schema/tx/types.rs @@ -5,10 +5,8 @@ use super::{ }; use crate::{ fuel_core_graphql_api::{ - service::{ - Database, - TxPool, - }, + api_service::TxPool, + database::ReadView, Config, IntoApiResult, }, @@ -160,7 +158,7 @@ impl SuccessStatus { } async fn block(&self, ctx: &Context<'_>) -> async_graphql::Result { - let query: &Database = ctx.data_unchecked(); + let query: &ReadView = ctx.data_unchecked(); let block = query.block(&self.block_id)?; Ok(block.into()) } @@ -174,8 +172,8 @@ impl SuccessStatus { } async fn receipts(&self, ctx: &Context<'_>) -> async_graphql::Result> { - let db = ctx.data_unchecked::(); - let receipts = db + let query: &ReadView = ctx.data_unchecked(); + let receipts = query .receipts(&self.tx_id) .unwrap_or_default() .into_iter() @@ -201,7 +199,7 @@ impl FailureStatus { } async fn block(&self, ctx: &Context<'_>) -> async_graphql::Result { - let query: &Database = ctx.data_unchecked(); + let query: &ReadView = ctx.data_unchecked(); let block = query.block(&self.block_id)?; Ok(block.into()) } @@ -219,8 +217,8 @@ impl FailureStatus { } async fn receipts(&self, ctx: &Context<'_>) -> async_graphql::Result> { - let db = ctx.data_unchecked::(); - let receipts = db + let query: &ReadView = ctx.data_unchecked(); + let receipts = query .receipts(&self.tx_id) .unwrap_or_default() .into_iter() @@ -526,7 +524,7 @@ impl Transaction { ctx: &Context<'_>, ) -> async_graphql::Result> { let id = self.1; - let query: &Database = ctx.data_unchecked(); + let query: &ReadView = ctx.data_unchecked(); let txpool = ctx.data_unchecked::(); get_tx_status(id, query, txpool).map_err(Into::into) } @@ -535,7 +533,7 @@ impl Transaction { &self, ctx: &Context<'_>, ) -> async_graphql::Result>> { - let query: &Database = ctx.data_unchecked(); + let query: &ReadView = ctx.data_unchecked(); let receipts = query .receipts(&self.1) .into_api_result::, async_graphql::Error>()?; @@ -622,7 +620,7 @@ impl Transaction { #[tracing::instrument(level = "debug", skip(query, txpool), ret, err)] pub(crate) fn get_tx_status( id: fuel_core_types::fuel_types::Bytes32, - query: &Database, + query: &ReadView, txpool: &TxPool, ) -> Result, StorageError> { match query diff --git a/crates/fuel-core/src/service.rs b/crates/fuel-core/src/service.rs index 3d5240cab2..70004f2fba 100644 --- a/crates/fuel-core/src/service.rs +++ b/crates/fuel-core/src/service.rs @@ -44,7 +44,7 @@ pub struct SharedState { /// The Relayer shared state. pub relayer: Option>, /// The GraphQL shared state. - pub graph_ql: crate::fuel_core_graphql_api::service::SharedState, + pub graph_ql: crate::fuel_core_graphql_api::api_service::SharedState, /// The underlying database. pub database: Database, /// Subscribe to new block production. @@ -305,9 +305,9 @@ mod tests { i += 1; } - // current services: graphql, txpool, PoA + // current services: graphql, graphql worker, txpool, PoA #[allow(unused_mut)] - let mut expected_services = 3; + let mut expected_services = 4; // Relayer service is disabled with `Config::local_node`. // #[cfg(feature = "relayer")] diff --git a/crates/fuel-core/src/service/adapters/block_importer.rs b/crates/fuel-core/src/service/adapters/block_importer.rs index 89627483c8..7fdfb2c303 100644 --- a/crates/fuel-core/src/service/adapters/block_importer.rs +++ b/crates/fuel-core/src/service/adapters/block_importer.rs @@ -70,11 +70,7 @@ impl BlockImporterAdapter { &self, sealed_block: SealedBlock, ) -> anyhow::Result<()> { - tokio::task::spawn_blocking({ - let importer = self.block_importer.clone(); - move || importer.execute_and_commit(sealed_block) - }) - .await??; + self.block_importer.execute_and_commit(sealed_block).await?; Ok(()) } } diff --git a/crates/fuel-core/src/service/adapters/consensus_module/poa.rs b/crates/fuel-core/src/service/adapters/consensus_module/poa.rs index ac446c7167..9e57c2cf0e 100644 --- a/crates/fuel-core/src/service/adapters/consensus_module/poa.rs +++ b/crates/fuel-core/src/service/adapters/consensus_module/poa.rs @@ -1,5 +1,3 @@ -use std::ops::Deref; - use crate::{ database::Database, fuel_core_graphql_api::ports::ConsensusModulePort, @@ -124,15 +122,17 @@ impl fuel_core_poa::ports::BlockProducer for BlockProducerAdapter { } } +#[async_trait::async_trait] impl BlockImporter for BlockImporterAdapter { type Database = Database; - fn commit_result( + async fn commit_result( &self, result: UncommittedImporterResult>, ) -> anyhow::Result<()> { self.block_importer .commit_result(result) + .await .map_err(Into::into) } @@ -140,7 +140,7 @@ impl BlockImporter for BlockImporterAdapter { Box::pin( BroadcastStream::new(self.block_importer.subscribe()) .filter_map(|result| result.ok()) - .map(|r| r.deref().into()), + .map(BlockImportInfo::from), ) } } diff --git a/crates/fuel-core/src/service/adapters/executor.rs b/crates/fuel-core/src/service/adapters/executor.rs index bb8e46042d..dbeece6c73 100644 --- a/crates/fuel-core/src/service/adapters/executor.rs +++ b/crates/fuel-core/src/service/adapters/executor.rs @@ -16,26 +16,19 @@ use fuel_core_executor::{ use fuel_core_storage::{ transactional::StorageTransaction, Error as StorageError, - Result as StorageResult, }; use fuel_core_types::{ blockchain::primitives::DaBlockHeight, entities::message::Message, fuel_tx, fuel_tx::Receipt, - fuel_types::{ - Address, - BlockHeight, - Bytes32, - Nonce, - }, + fuel_types::Nonce, services::{ block_producer::Components, executor::{ Result as ExecutorResult, UncommittedResult, }, - txpool::TransactionStatus, }, }; @@ -84,36 +77,6 @@ impl fuel_core_executor::refs::ContractStorageTrait for Database { type InnerError = StorageError; } -impl fuel_core_executor::ports::MessageIsSpent for Database { - type Error = StorageError; - - fn message_is_spent(&self, nonce: &Nonce) -> StorageResult { - self.message_is_spent(nonce) - } -} - -impl fuel_core_executor::ports::TxIdOwnerRecorder for Database { - type Error = StorageError; - - fn record_tx_id_owner( - &self, - owner: &Address, - block_height: BlockHeight, - tx_idx: u16, - tx_id: &Bytes32, - ) -> Result, Self::Error> { - self.record_tx_id_owner(owner, block_height, tx_idx, tx_id) - } - - fn update_tx_status( - &self, - id: &Bytes32, - status: TransactionStatus, - ) -> Result, Self::Error> { - self.update_tx_status(id, status) - } -} - impl fuel_core_executor::ports::ExecutorDatabaseTrait for Database {} impl fuel_core_executor::ports::RelayerPort for MaybeRelayerAdapter { diff --git a/crates/fuel-core/src/service/adapters/graphql_api.rs b/crates/fuel-core/src/service/adapters/graphql_api.rs index 4faea60040..e83efc44e0 100644 --- a/crates/fuel-core/src/service/adapters/graphql_api.rs +++ b/crates/fuel-core/src/service/adapters/graphql_api.rs @@ -1,20 +1,13 @@ -use super::BlockProducerAdapter; +use super::{ + BlockImporterAdapter, + BlockProducerAdapter, +}; use crate::{ - database::{ - transactions::OwnedTransactionIndexCursor, - Database, - }, + database::Database, fuel_core_graphql_api::ports::{ + worker, BlockProducerPort, - DatabaseBlocks, - DatabaseChain, - DatabaseCoins, - DatabaseContracts, DatabaseMessageProof, - DatabaseMessages, - DatabasePort, - DatabaseTransactions, - DryRunExecution, P2pPort, TxPoolPort, }, @@ -25,51 +18,22 @@ use crate::{ }; use async_trait::async_trait; use fuel_core_services::stream::BoxStream; -use fuel_core_storage::{ - iter::{ - BoxedIter, - IntoBoxedIter, - IterDirection, - }, - not_found, - Error as StorageError, - Result as StorageResult, -}; +use fuel_core_storage::Result as StorageResult; use fuel_core_txpool::{ service::TxStatusMessage, - types::{ - ContractId, - TxId, - }, + types::TxId, }; use fuel_core_types::{ - blockchain::primitives::{ - BlockId, - DaBlockHeight, - }, - entities::message::{ - MerkleProof, - Message, - }, + entities::message::MerkleProof, fuel_tx::{ - Address, - AssetId, Receipt as TxReceipt, Transaction, - TxPointer, - UtxoId, - }, - fuel_types::{ - BlockHeight, - Nonce, }, + fuel_types::BlockHeight, services::{ - graphql_api::ContractBalance, + block_importer::SharedImportResult, p2p::PeerInfo, - txpool::{ - InsertionResult, - TransactionStatus, - }, + txpool::InsertionResult, }, tai64::Tai64, }; @@ -78,140 +42,8 @@ use std::{ sync::Arc, }; -impl DatabaseBlocks for Database { - fn block_id(&self, height: &BlockHeight) -> StorageResult { - self.get_block_id(height) - .and_then(|height| height.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)> { - self.ids_of_latest_block() - .transpose() - .ok_or(not_found!("BlockId"))? - } -} - -impl DatabaseTransactions for Database { - fn tx_status(&self, tx_id: &TxId) -> StorageResult { - 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(), - 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() - } - - fn message_is_spent(&self, nonce: &Nonce) -> StorageResult { - self.message_is_spent(nonce) - } - - fn message_exists(&self, nonce: &Nonce) -> StorageResult { - self.message_exists(nonce) - } -} - -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 da_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 {} +mod off_chain; +mod on_chain; #[async_trait] impl TxPoolPort for TxPoolAdapter { @@ -253,7 +85,7 @@ impl DatabaseMessageProof for Database { } #[async_trait] -impl DryRunExecution for BlockProducerAdapter { +impl BlockProducerPort for BlockProducerAdapter { async fn dry_run_tx( &self, transaction: Transaction, @@ -266,8 +98,6 @@ impl DryRunExecution for BlockProducerAdapter { } } -impl BlockProducerPort for BlockProducerAdapter {} - #[async_trait::async_trait] impl P2pPort for P2PAdapter { async fn all_peer_info(&self) -> anyhow::Result> { @@ -305,3 +135,13 @@ impl P2pPort for P2PAdapter { } } } + +impl worker::BlockImporter for BlockImporterAdapter { + fn block_events(&self) -> BoxStream { + use futures::StreamExt; + fuel_core_services::stream::IntoBoxStream::into_boxed( + tokio_stream::wrappers::BroadcastStream::new(self.block_importer.subscribe()) + .filter_map(|r| futures::future::ready(r.ok())), + ) + } +} diff --git a/crates/fuel-core/src/service/adapters/graphql_api/off_chain.rs b/crates/fuel-core/src/service/adapters/graphql_api/off_chain.rs new file mode 100644 index 0000000000..ba23d77bd5 --- /dev/null +++ b/crates/fuel-core/src/service/adapters/graphql_api/off_chain.rs @@ -0,0 +1,116 @@ +use crate::{ + database::{ + transactions::OwnedTransactionIndexCursor, + Database, + }, + fuel_core_graphql_api::{ + database::OffChainView, + ports::{ + worker, + OffChainDatabase, + }, + }, +}; +use fuel_core_storage::{ + iter::{ + BoxedIter, + IntoBoxedIter, + IterDirection, + }, + not_found, + transactional::AtomicView, + Error as StorageError, + Result as StorageResult, +}; +use fuel_core_txpool::types::TxId; +use fuel_core_types::{ + fuel_tx::{ + Address, + Bytes32, + TxPointer, + UtxoId, + }, + fuel_types::{ + BlockHeight, + Nonce, + }, + services::txpool::TransactionStatus, +}; +use std::sync::Arc; + +impl OffChainDatabase 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 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() + } + + fn tx_status(&self, tx_id: &TxId) -> StorageResult { + 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(), + tx_idx: tx_pointer.tx_index(), + }); + self.owned_transactions(owner, start, Some(direction)) + .map(|result| result.map_err(StorageError::from)) + .into_boxed() + } +} + +impl AtomicView for Database { + fn view_at(&self, _: BlockHeight) -> StorageResult { + unimplemented!( + "Unimplemented until of the https://github.com/FuelLabs/fuel-core/issues/451" + ) + } + + fn latest_view(&self) -> OffChainView { + Arc::new(self.clone()) + } +} + +impl worker::OffChainDatabase for Database { + fn record_tx_id_owner( + &mut self, + owner: &Address, + block_height: BlockHeight, + tx_idx: u16, + tx_id: &Bytes32, + ) -> StorageResult> { + Database::record_tx_id_owner(self, owner, block_height, tx_idx, tx_id) + } + + fn update_tx_status( + &mut self, + id: &Bytes32, + status: TransactionStatus, + ) -> StorageResult> { + Database::update_tx_status(self, id, status) + } +} diff --git a/crates/fuel-core/src/service/adapters/graphql_api/on_chain.rs b/crates/fuel-core/src/service/adapters/graphql_api/on_chain.rs new file mode 100644 index 0000000000..d0bcaf3eeb --- /dev/null +++ b/crates/fuel-core/src/service/adapters/graphql_api/on_chain.rs @@ -0,0 +1,139 @@ +use crate::{ + database::Database, + fuel_core_graphql_api::{ + database::OnChainView, + ports::{ + DatabaseBlocks, + DatabaseChain, + DatabaseContracts, + DatabaseMessages, + OnChainDatabase, + }, + }, +}; +use fuel_core_storage::{ + iter::{ + BoxedIter, + IntoBoxedIter, + IterDirection, + }, + not_found, + transactional::AtomicView, + Error as StorageError, + Result as StorageResult, +}; +use fuel_core_txpool::types::ContractId; +use fuel_core_types::{ + blockchain::primitives::{ + BlockId, + DaBlockHeight, + }, + entities::message::Message, + fuel_tx::AssetId, + fuel_types::{ + BlockHeight, + Nonce, + }, + services::graphql_api::ContractBalance, +}; +use std::sync::Arc; + +impl DatabaseBlocks for Database { + fn block_id(&self, height: &BlockHeight) -> StorageResult { + self.get_block_id(height) + .and_then(|height| height.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)> { + self.ids_of_latest_block() + .transpose() + .ok_or(not_found!("BlockId"))? + } +} + +impl DatabaseMessages for Database { + 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() + } + + fn message_is_spent(&self, nonce: &Nonce) -> StorageResult { + self.message_is_spent(nonce) + } + + fn message_exists(&self, nonce: &Nonce) -> StorageResult { + self.message_exists(nonce) + } +} + +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 da_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 OnChainDatabase for Database {} + +impl AtomicView for Database { + fn view_at(&self, _: BlockHeight) -> StorageResult { + unimplemented!( + "Unimplemented until of the https://github.com/FuelLabs/fuel-core/issues/451" + ) + } + + fn latest_view(&self) -> OnChainView { + Arc::new(self.clone()) + } +} diff --git a/crates/fuel-core/src/service/adapters/sync.rs b/crates/fuel-core/src/service/adapters/sync.rs index 1b63c8c25e..ddbcd2ed6d 100644 --- a/crates/fuel-core/src/service/adapters/sync.rs +++ b/crates/fuel-core/src/service/adapters/sync.rs @@ -137,6 +137,7 @@ impl BlockImporterPort for BlockImporterAdapter { }), ) } + async fn execute_and_commit(&self, block: SealedBlock) -> anyhow::Result<()> { self.execute_and_commit(block).await } diff --git a/crates/fuel-core/src/service/adapters/txpool.rs b/crates/fuel-core/src/service/adapters/txpool.rs index 6f1593f6d7..ccd33474df 100644 --- a/crates/fuel-core/src/service/adapters/txpool.rs +++ b/crates/fuel-core/src/service/adapters/txpool.rs @@ -7,7 +7,6 @@ use crate::{ }; use fuel_core_services::stream::BoxStream; use fuel_core_storage::{ - not_found, tables::{ Coins, ContractsRawCode, @@ -33,7 +32,7 @@ use fuel_core_types::{ Nonce, }, services::{ - block_importer::ImportResult, + block_importer::SharedImportResult, p2p::{ GossipsubMessageAcceptance, GossipsubMessageInfo, @@ -44,7 +43,7 @@ use fuel_core_types::{ use std::sync::Arc; impl BlockImporter for BlockImporterAdapter { - fn block_events(&self) -> BoxStream> { + fn block_events(&self) -> BoxStream { use tokio_stream::{ wrappers::BroadcastStream, StreamExt, @@ -144,13 +143,4 @@ impl fuel_core_txpool::ports::TxPoolDb for Database { fn current_block_height(&self) -> StorageResult { self.latest_height() } - - fn transaction_status( - &self, - tx_id: &fuel_core_types::fuel_types::Bytes32, - ) -> StorageResult { - self.get_tx_status(tx_id) - .transpose() - .ok_or(not_found!("TransactionId"))? - } } diff --git a/crates/fuel-core/src/service/genesis.rs b/crates/fuel-core/src/service/genesis.rs index 8039f438d1..9942df0a81 100644 --- a/crates/fuel-core/src/service/genesis.rs +++ b/crates/fuel-core/src/service/genesis.rs @@ -136,7 +136,8 @@ fn import_genesis_block( (), (), ); - importer.commit_result(UncommittedImportResult::new( + // We commit Genesis block before start of any service, so there is no listeners. + importer.commit_result_without_awaiting_listeners(UncommittedImportResult::new( ImportResult::new_from_local(block, vec![]), database_transaction, ))?; diff --git a/crates/fuel-core/src/service/sub_services.rs b/crates/fuel-core/src/service/sub_services.rs index 1523fe41c1..ba8dc05e93 100644 --- a/crates/fuel-core/src/service/sub_services.rs +++ b/crates/fuel-core/src/service/sub_services.rs @@ -3,6 +3,7 @@ use super::adapters::P2PAdapter; use crate::{ database::Database, + fuel_core_graphql_api, fuel_core_graphql_api::Config as GraphQLConfig, schema::build_schema, service::{ @@ -41,7 +42,7 @@ pub type BlockProducerService = fuel_core_producer::block_producer::Producer< TxPoolAdapter, ExecutorAdapter, >; -pub type GraphQL = crate::fuel_core_graphql_api::service::Service; +pub type GraphQL = crate::fuel_core_graphql_api::api_service::Service; pub fn init_sub_services( config: &Config, @@ -189,20 +190,28 @@ pub fn init_sub_services( ) .data(database.clone()); - let graph_ql = crate::fuel_core_graphql_api::service::new_service( - GraphQLConfig { - addr: config.addr, - utxo_validation: config.utxo_validation, - debug: config.debug, - vm_backtrace: config.vm.backtrace, - min_gas_price: config.txpool.min_gas_price, - max_tx: config.txpool.max_tx, - max_depth: config.txpool.max_depth, - consensus_parameters: config.chain_conf.consensus_parameters.clone(), - consensus_key: config.consensus_key.clone(), - }, + let graphql_worker = fuel_core_graphql_api::worker_service::new_service( + importer_adapter.clone(), + database.clone(), + ); + + let graphql_config = GraphQLConfig { + addr: config.addr, + utxo_validation: config.utxo_validation, + debug: config.debug, + vm_backtrace: config.vm.backtrace, + min_gas_price: config.txpool.min_gas_price, + max_tx: config.txpool.max_tx, + max_depth: config.txpool.max_depth, + consensus_parameters: config.chain_conf.consensus_parameters.clone(), + consensus_key: config.consensus_key.clone(), + }; + + let graph_ql = fuel_core_graphql_api::api_service::new_service( + graphql_config, schema, - Box::new(database.clone()), + database.clone(), + database.clone(), Box::new(tx_pool_adapter), Box::new(producer_adapter), Box::new(poa_adapter.clone()), @@ -249,5 +258,7 @@ pub fn init_sub_services( } } + services.push(Box::new(graphql_worker)); + Ok((services, shared)) } diff --git a/crates/services/consensus_module/poa/src/ports.rs b/crates/services/consensus_module/poa/src/ports.rs index fdb8a2d11d..c93180645b 100644 --- a/crates/services/consensus_module/poa/src/ports.rs +++ b/crates/services/consensus_module/poa/src/ports.rs @@ -66,10 +66,11 @@ pub trait BlockProducer: Send + Sync { } #[cfg_attr(test, mockall::automock(type Database=EmptyStorage;))] +#[async_trait::async_trait] pub trait BlockImporter: Send + Sync { type Database; - fn commit_result( + async fn commit_result( &self, result: UncommittedImportResult>, ) -> anyhow::Result<()>; diff --git a/crates/services/consensus_module/poa/src/service.rs b/crates/services/consensus_module/poa/src/service.rs index 3ec7b8727d..4fd65a220e 100644 --- a/crates/services/consensus_module/poa/src/service.rs +++ b/crates/services/consensus_module/poa/src/service.rs @@ -356,10 +356,12 @@ where consensus: seal, }; // Import the sealed block - self.block_importer.commit_result(Uncommitted::new( - ImportResult::new_from_local(block, tx_status), - db_transaction, - ))?; + self.block_importer + .commit_result(Uncommitted::new( + ImportResult::new_from_local(block, tx_status), + db_transaction, + )) + .await?; // Update last block time self.last_height = height; diff --git a/crates/services/executor/src/executor.rs b/crates/services/executor/src/executor.rs index 6be1e94498..a2041c56f4 100644 --- a/crates/services/executor/src/executor.rs +++ b/crates/services/executor/src/executor.rs @@ -14,7 +14,6 @@ use fuel_core_storage::{ ContractsLatestUtxo, Messages, ProcessedTransactions, - Receipts, SpentMessages, }, transactional::{ @@ -23,7 +22,6 @@ use fuel_core_storage::{ }, StorageAsMut, StorageAsRef, - StorageInspect, }; use fuel_core_types::{ blockchain::{ @@ -45,11 +43,9 @@ use fuel_core_types::{ fuel_tx::{ field::{ InputContract, - Inputs, MintAmount, MintAssetId, OutputContract, - Outputs, TxPointer as TxPointerField, }, input, @@ -79,7 +75,6 @@ use fuel_core_types::{ Transaction, TxId, TxPointer, - UniqueIdentifier, UtxoId, }, fuel_types::{ @@ -123,7 +118,6 @@ use fuel_core_types::{ TransactionValidityError, UncommittedResult, }, - txpool::TransactionStatus, }, }; use parking_lot::Mutex as ParkingMutex; @@ -267,11 +261,11 @@ where let ( ExecutionResult { - block, skipped_transactions, + tx_status, .. }, - temporary_db, + _temporary_db, ) = self .execute_without_commit(ExecutionTypes::DryRun(component), options)? .into(); @@ -281,19 +275,11 @@ where return Err(err) } - block - .transactions() - .iter() - .map(|tx| { - let id = tx.id(&self.config.consensus_parameters.chain_id); - StorageInspect::::get(temporary_db.as_ref(), &id) - .transpose() - .unwrap_or_else(|| Ok(Default::default())) - .map(|v| v.into_owned()) - }) - .collect::>, _>>() - .map_err(Into::into) - // drop `temporary_db` without committing to avoid altering state. + Ok(tx_status + .into_iter() + .map(|tx| tx.receipts) + .collect::>>()) + // drop `_temporary_db` without committing to avoid altering state. } } @@ -447,16 +433,6 @@ where tx_status, }; - // ------------ GraphQL API Functionality BEGIN ------------ - - // save the status for every transaction using the finalized block id - self.persist_transaction_status(&result, block_st_transaction.as_mut())?; - - // save the associated owner for each transaction in the block - self.index_tx_owners_for_block(&result.block, block_st_transaction.as_mut())?; - - // ------------ GraphQL API Functionality END ------------ - // Get the complete fuel block. Ok(UncommittedResult::new(result, block_st_transaction)) } @@ -807,6 +783,7 @@ where execution_data.tx_status.push(TransactionExecutionStatus { id: coinbase_id, result: TransactionExecutionResult::Success { result: None }, + receipts: vec![], }); if block_st_transaction @@ -895,7 +872,10 @@ where debug_assert_eq!(tx.id(&self.config.consensus_parameters.chain_id), tx_id); } - // Wrap inputs in the execution kind. + // TODO: We need to call this function before `vm.transact` but we can't do that because of + // `Checked` immutability requirements. So we do it here after its execution for now. + // But it should be fixed in the future. + // https://github.com/FuelLabs/fuel-vm/issues/651 self.compute_inputs( match execution_kind { ExecutionKind::DryRun => ExecutionTypes::DryRun(tx.inputs_mut()), @@ -970,9 +950,6 @@ where .storage::() .insert(&tx_id, &())?; - // persist receipts - self.persist_receipts(&tx_id, &receipts, tx_st_transaction.as_mut())?; - let status = if reverted { self.log_backtrace(&vm, &receipts); // get reason for revert @@ -1004,14 +981,15 @@ where .checked_add(tx_fee) .ok_or(ExecutorError::FeeOverflow)?; execution_data.used_gas = execution_data.used_gas.saturating_add(used_gas); + execution_data + .message_ids + .extend(receipts.iter().filter_map(|r| r.message_id())); // queue up status for this tx to be stored once block id is finalized. execution_data.tx_status.push(TransactionExecutionStatus { id: tx_id, result: status, + receipts, }); - execution_data - .message_ids - .extend(receipts.iter().filter_map(|r| r.message_id())); Ok(final_tx) } @@ -1070,7 +1048,7 @@ where | Input::MessageDataSigned(MessageDataSigned { nonce, .. }) | Input::MessageDataPredicate(MessageDataPredicate { nonce, .. }) => { // Eagerly return already spent if status is known. - if db.message_is_spent(nonce)? { + if db.storage::().contains_key(nonce)? { return Err( TransactionValidityError::MessageAlreadySpent(*nonce).into() ) @@ -1545,130 +1523,6 @@ where Ok(()) } - - fn persist_receipts( - &self, - tx_id: &TxId, - receipts: &[Receipt], - db: &mut D, - ) -> ExecutorResult<()> { - if db.storage::().insert(tx_id, receipts)?.is_some() { - return Err(ExecutorError::OutputAlreadyExists) - } - Ok(()) - } - - /// Associate all transactions within a block to their respective UTXO owners - fn index_tx_owners_for_block( - &self, - block: &Block, - block_st_transaction: &mut D, - ) -> ExecutorResult<()> { - for (tx_idx, tx) in block.transactions().iter().enumerate() { - let block_height = *block.header().height(); - let inputs; - let outputs; - let tx_idx = - u16::try_from(tx_idx).map_err(|_| ExecutorError::TooManyTransactions)?; - let tx_id = tx.id(&self.config.consensus_parameters.chain_id); - match tx { - Transaction::Script(tx) => { - inputs = tx.inputs().as_slice(); - outputs = tx.outputs().as_slice(); - } - Transaction::Create(tx) => { - inputs = tx.inputs().as_slice(); - outputs = tx.outputs().as_slice(); - } - Transaction::Mint(_) => continue, - } - self.persist_owners_index( - block_height, - inputs, - outputs, - &tx_id, - tx_idx, - block_st_transaction, - )?; - } - Ok(()) - } - - /// Index the tx id by owner for all of the inputs and outputs - fn persist_owners_index( - &self, - block_height: BlockHeight, - inputs: &[Input], - outputs: &[Output], - tx_id: &Bytes32, - tx_idx: u16, - db: &mut D, - ) -> ExecutorResult<()> { - let mut owners = vec![]; - for input in inputs { - if let Input::CoinSigned(CoinSigned { owner, .. }) - | Input::CoinPredicate(CoinPredicate { owner, .. }) = input - { - owners.push(owner); - } - } - - for output in outputs { - match output { - Output::Coin { to, .. } - | Output::Change { to, .. } - | Output::Variable { to, .. } => { - owners.push(to); - } - Output::Contract(_) | Output::ContractCreated { .. } => {} - } - } - - // dedupe owners from inputs and outputs prior to indexing - owners.sort(); - owners.dedup(); - - for owner in owners { - db.record_tx_id_owner(owner, block_height, tx_idx, tx_id)?; - } - - Ok(()) - } - - fn persist_transaction_status( - &self, - result: &ExecutionResult, - db: &D, - ) -> ExecutorResult<()> { - let time = result.block.header().time(); - let block_id = result.block.id(); - for TransactionExecutionStatus { id, result } in result.tx_status.iter() { - match result { - TransactionExecutionResult::Success { result } => { - db.update_tx_status( - id, - TransactionStatus::Success { - block_id, - time, - result: *result, - }, - )?; - } - TransactionExecutionResult::Failed { result, reason } => { - db.update_tx_status( - id, - TransactionStatus::Failed { - block_id, - time, - result: *result, - reason: reason.clone(), - }, - )?; - } - } - } - Ok(()) - } } trait Fee { diff --git a/crates/services/executor/src/ports.rs b/crates/services/executor/src/ports.rs index 1ca5a5058f..e9c5b1b9b4 100644 --- a/crates/services/executor/src/ports.rs +++ b/crates/services/executor/src/ports.rs @@ -8,14 +8,12 @@ use fuel_core_storage::{ ContractsState, Messages, ProcessedTransactions, - Receipts, SpentMessages, }, transactional::Transactional, vm_storage::VmStorageRequirements, Error as StorageError, MerkleRootStorage, - StorageInspect, StorageMutate, StorageRead, }; @@ -25,18 +23,14 @@ use fuel_core_types::{ entities::message::Message, fuel_tx, fuel_tx::{ - Address, - Bytes32, TxId, UniqueIdentifier, }, fuel_types::{ - BlockHeight, ChainId, Nonce, }, fuel_vm::checked_transaction::CheckedTransaction, - services::txpool::TransactionStatus, }; use fuel_core_types::fuel_tx::ContractId; @@ -79,50 +73,20 @@ pub trait RelayerPort { ) -> anyhow::Result>; } -pub trait MessageIsSpent: - StorageInspect - + StorageInspect -{ - type Error; - - fn message_is_spent(&self, nonce: &Nonce) -> Result; -} - -pub trait TxIdOwnerRecorder { - type Error; - - fn record_tx_id_owner( - &self, - owner: &Address, - block_height: BlockHeight, - tx_idx: u16, - tx_id: &Bytes32, - ) -> Result, Self::Error>; - - fn update_tx_status( - &self, - id: &Bytes32, - status: TransactionStatus, - ) -> Result, Self::Error>; -} - // TODO: Remove `Clone` bound pub trait ExecutorDatabaseTrait: - StorageMutate + StorageMutate + StorageMutate + MerkleRootStorage - + MessageIsSpent + StorageMutate + StorageMutate + StorageMutate - + StorageMutate + StorageMutate - + StorageRead + + StorageRead + StorageMutate + MerkleRootStorage + VmStorageRequirements + Transactional - + TxIdOwnerRecorder + Clone { } diff --git a/crates/services/importer/Cargo.toml b/crates/services/importer/Cargo.toml index 7cd9384042..6b47a8272f 100644 --- a/crates/services/importer/Cargo.toml +++ b/crates/services/importer/Cargo.toml @@ -17,6 +17,7 @@ fuel-core-metrics = { workspace = true } fuel-core-storage = { workspace = true } fuel-core-types = { workspace = true } tokio = { workspace = true, features = ["full"] } +tokio-rayon = { workspace = true } tracing = { workspace = true } [dev-dependencies] diff --git a/crates/services/importer/src/config.rs b/crates/services/importer/src/config.rs index c551127c68..0e9d938be9 100644 --- a/crates/services/importer/src/config.rs +++ b/crates/services/importer/src/config.rs @@ -22,7 +22,7 @@ impl Config { impl Default for Config { fn default() -> Self { Self { - max_block_notify_buffer: 1 << 10, + max_block_notify_buffer: 1, metrics: false, chain_id: ChainId::default(), } diff --git a/crates/services/importer/src/importer.rs b/crates/services/importer/src/importer.rs index 056c401041..d75709e1c9 100644 --- a/crates/services/importer/src/importer.rs +++ b/crates/services/importer/src/importer.rs @@ -29,6 +29,7 @@ use fuel_core_types::{ services::{ block_importer::{ ImportResult, + SharedImportResult, UncommittedResult, }, executor, @@ -38,7 +39,10 @@ use fuel_core_types::{ }; use std::{ ops::Deref, - sync::Arc, + sync::{ + Arc, + Mutex, + }, time::{ Instant, SystemTime, @@ -47,6 +51,7 @@ use std::{ }; use tokio::sync::{ broadcast, + oneshot, TryAcquireError, }; @@ -105,10 +110,14 @@ impl PartialEq for Error { pub struct Importer { database: D, - executor: E, - verifier: V, + executor: Arc, + verifier: Arc, chain_id: ChainId, - broadcast: broadcast::Sender>, + broadcast: broadcast::Sender, + /// The channel to notify about the end of the processing of the previous block by all listeners. + /// It is used to await until all receivers of the notification process the `SharedImportResult` + /// before starting committing a new block. + prev_block_process_result: Mutex>>, guard: tokio::sync::Semaphore, } @@ -118,15 +127,16 @@ impl Importer { Self { database, - executor, - verifier, + executor: Arc::new(executor), + verifier: Arc::new(verifier), chain_id: config.chain_id, broadcast, + prev_block_process_result: Default::default(), guard: tokio::sync::Semaphore::new(1), } } - pub fn subscribe(&self) -> broadcast::Receiver> { + pub fn subscribe(&self) -> broadcast::Receiver { self.broadcast.subscribe() } @@ -162,7 +172,7 @@ where /// /// Only one commit may be in progress at the time. All other calls will fail. /// Returns an error if called while another call is in progress. - pub fn commit_result( + pub async fn commit_result( &self, result: UncommittedResult>, ) -> Result<(), Error> @@ -170,9 +180,36 @@ where ExecutorDatabase: ports::ExecutorDatabase, { let _guard = self.lock()?; + // It is safe to unwrap the channel because we have the `_guard`. + let previous_block_result = self + .prev_block_process_result + .lock() + .expect("poisoned") + .take(); + + // Await until all receivers of the notification process the result. + if let Some(channel) = previous_block_result { + let _ = channel.await; + } + self._commit_result(result) } + /// The method works in the same way as [`Importer::commit_result`], but it doesn't + /// wait for listeners to process the result. + pub fn commit_result_without_awaiting_listeners( + &self, + result: UncommittedResult>, + ) -> Result<(), Error> + where + ExecutorDatabase: ports::ExecutorDatabase, + { + let _guard = self.lock()?; + self._commit_result(result)?; + Ok(()) + } + + /// The method commits the result of the block execution and notifies about a new imported block. #[tracing::instrument( skip_all, fields( @@ -270,7 +307,13 @@ where .set(current_time); tracing::info!("Committed block {:#x}", result.sealed_block.entity.id()); - let _ = self.broadcast.send(Arc::new(result)); + + // The `tokio::sync::oneshot::Sender` is used to notify about the end + // of the processing of a new block by all listeners. + let (sender, receiver) = oneshot::channel(); + let _ = self.broadcast.send(Arc::new(Awaiter::new(result, sender))); + *self.prev_block_process_result.lock().expect("poisoned") = Some(receiver); + Ok(()) } @@ -324,13 +367,24 @@ where pub fn verify_and_execute_block( &self, sealed_block: SealedBlock, + ) -> Result>, Error> { + Self::verify_and_execute_block_inner( + self.executor.clone(), + self.verifier.clone(), + sealed_block, + ) + } + + fn verify_and_execute_block_inner( + executor: Arc, + verifier: Arc, + sealed_block: SealedBlock, ) -> Result>, Error> { let consensus = sealed_block.consensus; let block = sealed_block.entity; let sealed_block_id = block.id(); - let result_of_verification = - self.verifier.verify_block_fields(&consensus, &block); + let result_of_verification = verifier.verify_block_fields(&consensus, &block); if let Err(err) = result_of_verification { return Err(Error::FailedVerification(err)) } @@ -350,8 +404,7 @@ where tx_status, }, db_tx, - ) = self - .executor + ) = executor .execute_without_commit(block) .map_err(Error::FailedExecution)? .into(); @@ -380,19 +433,47 @@ where impl Importer where - IDatabase: ImporterDatabase, - E: Executor, - V: BlockVerifier, + IDatabase: ImporterDatabase + 'static, + E: Executor + 'static, + V: BlockVerifier + 'static, { /// The method validates the `Block` fields and commits the `SealedBlock`. /// It is a combination of the [`Importer::verify_and_execute_block`] and [`Importer::commit_result`]. - pub fn execute_and_commit(&self, sealed_block: SealedBlock) -> Result<(), Error> { + pub async fn execute_and_commit( + &self, + sealed_block: SealedBlock, + ) -> Result<(), Error> { let _guard = self.lock()?; + + let executor = self.executor.clone(); + let verifier = self.verifier.clone(); + let (result, execute_time) = tokio_rayon::spawn_fifo(|| { + let start = Instant::now(); + let result = + Self::verify_and_execute_block_inner(executor, verifier, sealed_block); + let execute_time = start.elapsed().as_secs_f64(); + (result, execute_time) + }) + .await; + + let result = result?; + + // It is safe to unwrap the channel because we have the `_guard`. + let previous_block_result = self + .prev_block_process_result + .lock() + .expect("poisoned") + .take(); + + // Await until all receivers of the notification process the result. + if let Some(channel) = previous_block_result { + let _ = channel.await; + } + let start = Instant::now(); - let result = self.verify_and_execute_block(sealed_block)?; let commit_result = self._commit_result(result); - // record the execution time to prometheus - let time = start.elapsed().as_secs_f64(); + let commit_time = start.elapsed().as_secs_f64(); + let time = execute_time + commit_time; importer_metrics().execute_and_commit_duration.observe(time); // return execution result commit_result @@ -412,3 +493,34 @@ impl ShouldBeUnique for Option { } } } + +/// The wrapper around `ImportResult` to notify about the end of the processing of a new block. +struct Awaiter { + result: ImportResult, + release_channel: Option>, +} + +impl Drop for Awaiter { + fn drop(&mut self) { + if let Some(release_channel) = core::mem::take(&mut self.release_channel) { + let _ = release_channel.send(()); + } + } +} + +impl Deref for Awaiter { + type Target = ImportResult; + + fn deref(&self) -> &Self::Target { + &self.result + } +} + +impl Awaiter { + fn new(result: ImportResult, channel: oneshot::Sender<()>) -> Self { + Self { + result, + release_channel: Some(channel), + } + } +} diff --git a/crates/services/importer/src/importer/test.rs b/crates/services/importer/src/importer/test.rs index 897be9f994..717271093f 100644 --- a/crates/services/importer/src/importer/test.rs +++ b/crates/services/importer/src/importer/test.rs @@ -261,12 +261,13 @@ where => Err(Error::NotUnique(0u32.into())); "fails to import genesis block when block exists for height 0" )] -fn commit_result_genesis( +#[tokio::test] +async fn commit_result_genesis( sealed_block: SealedBlock, underlying_db: impl Fn() -> MockDatabase, executor_db: impl Fn() -> MockDatabase, ) -> Result<(), Error> { - commit_result_assert(sealed_block, underlying_db(), executor_db()) + commit_result_assert(sealed_block, underlying_db(), executor_db()).await } //////////////////////////// PoA Block //////////////////////////// @@ -333,7 +334,8 @@ fn commit_result_genesis( => Err(storage_failure_error()); "fails to import block when executor db fails to find block" )] -fn commit_result_and_execute_and_commit_poa( +#[tokio::test] +async fn commit_result_and_execute_and_commit_poa( sealed_block: SealedBlock, underlying_db: impl Fn() -> MockDatabase, executor_db: impl Fn() -> MockDatabase, @@ -342,18 +344,19 @@ fn commit_result_and_execute_and_commit_poa( // validation rules(-> test cases) during committing the result. let height = *sealed_block.entity.header().height(); let commit_result = - commit_result_assert(sealed_block.clone(), underlying_db(), executor_db()); + commit_result_assert(sealed_block.clone(), underlying_db(), executor_db()).await; let execute_and_commit_result = execute_and_commit_assert( sealed_block, underlying_db(), executor(ok(ex_result(height.into(), 0)), executor_db()), verifier(ok(())), - ); + ) + .await; assert_eq!(commit_result, execute_and_commit_result); commit_result } -fn commit_result_assert( +async fn commit_result_assert( sealed_block: SealedBlock, underlying_db: MockDatabase, executor_db: MockDatabase, @@ -366,23 +369,22 @@ fn commit_result_assert( ); let mut imported_blocks = importer.subscribe(); - let result = importer.commit_result(uncommitted_result); + let result = importer.commit_result(uncommitted_result).await; if result.is_ok() { let actual_sealed_block = imported_blocks.try_recv().unwrap(); assert_eq!(actual_sealed_block.sealed_block, expected_to_broadcast); - assert_eq!( - imported_blocks - .try_recv() - .expect_err("We should broadcast only one block"), - TryRecvError::Empty - ) + if let Err(err) = imported_blocks.try_recv() { + assert_eq!(err, TryRecvError::Empty); + } else { + panic!("We should broadcast only one block"); + } } result } -fn execute_and_commit_assert( +async fn execute_and_commit_assert( sealed_block: SealedBlock, underlying_db: MockDatabase, executor: MockExecutor, @@ -392,24 +394,24 @@ fn execute_and_commit_assert( let importer = Importer::new(Default::default(), underlying_db, executor, verifier); let mut imported_blocks = importer.subscribe(); - let result = importer.execute_and_commit(sealed_block); + let result = importer.execute_and_commit(sealed_block).await; if result.is_ok() { let actual_sealed_block = imported_blocks.try_recv().unwrap(); assert_eq!(actual_sealed_block.sealed_block, expected_to_broadcast); - assert_eq!( - imported_blocks - .try_recv() - .expect_err("We should broadcast only one block"), - TryRecvError::Empty - ) + + if let Err(err) = imported_blocks.try_recv() { + assert_eq!(err, TryRecvError::Empty); + } else { + panic!("We should broadcast only one block"); + } } result } -#[test] -fn commit_result_fail_when_locked() { +#[tokio::test] +async fn commit_result_fail_when_locked() { let importer = Importer::new(Default::default(), MockDatabase::default(), (), ()); let uncommitted_result = UncommittedResult::new( ImportResult::default(), @@ -418,13 +420,13 @@ fn commit_result_fail_when_locked() { let _guard = importer.lock(); assert_eq!( - importer.commit_result(uncommitted_result), + importer.commit_result(uncommitted_result).await, Err(Error::SemaphoreError(TryAcquireError::NoPermits)) ); } -#[test] -fn execute_and_commit_fail_when_locked() { +#[tokio::test] +async fn execute_and_commit_fail_when_locked() { let importer = Importer::new( Default::default(), MockDatabase::default(), @@ -434,7 +436,7 @@ fn execute_and_commit_fail_when_locked() { let _guard = importer.lock(); assert_eq!( - importer.execute_and_commit(Default::default()), + importer.execute_and_commit(Default::default()).await, Err(Error::SemaphoreError(TryAcquireError::NoPermits)) ); } @@ -491,7 +493,8 @@ fn one_lock_at_the_same_time() { => Err(verification_failure_error()); "commit fails if verification fails" )] -fn execute_and_commit_and_verify_and_execute_block_poa( +#[tokio::test] +async fn execute_and_commit_and_verify_and_execute_block_poa( sealed_block: SealedBlock, block_after_execution: P, verifier_result: V, @@ -521,7 +524,8 @@ where executor_db(ok(Some(previous_height)), ok(true), commits)(), ), verifier(verifier_result), - ); + ) + .await; assert_eq!(verify_and_execute_result, execute_and_commit_result); execute_and_commit_result } diff --git a/crates/services/importer/src/ports.rs b/crates/services/importer/src/ports.rs index 51c14e5085..99f097fefe 100644 --- a/crates/services/importer/src/ports.rs +++ b/crates/services/importer/src/ports.rs @@ -33,7 +33,7 @@ pub trait Executor: Send + Sync { } /// The database port used by the block importer. -pub trait ImporterDatabase { +pub trait ImporterDatabase: Send + Sync { /// Returns the latest block height. fn latest_block_height(&self) -> StorageResult>; /// Update metadata about the total number of transactions on the chain. @@ -57,7 +57,7 @@ pub trait ExecutorDatabase: ImporterDatabase { #[cfg_attr(test, mockall::automock)] /// The verifier of the block. -pub trait BlockVerifier { +pub trait BlockVerifier: Send + Sync { /// Verifies the consistency of the block fields for the block's height. /// It includes the verification of **all** fields, it includes the consensus rules for /// the corresponding height. diff --git a/crates/services/txpool/src/mock_db.rs b/crates/services/txpool/src/mock_db.rs index 157e5e7f27..5435585a3f 100644 --- a/crates/services/txpool/src/mock_db.rs +++ b/crates/services/txpool/src/mock_db.rs @@ -95,11 +95,4 @@ impl TxPoolDb for MockDb { fn current_block_height(&self) -> StorageResult { Ok(Default::default()) } - - fn transaction_status( - &self, - _tx_id: &fuel_core_types::fuel_types::Bytes32, - ) -> StorageResult { - unimplemented!() - } } diff --git a/crates/services/txpool/src/ports.rs b/crates/services/txpool/src/ports.rs index de51f429e9..375d706698 100644 --- a/crates/services/txpool/src/ports.rs +++ b/crates/services/txpool/src/ports.rs @@ -11,18 +11,16 @@ use fuel_core_types::{ }, fuel_types::{ BlockHeight, - Bytes32, ContractId, Nonce, }, services::{ - block_importer::ImportResult, + block_importer::SharedImportResult, p2p::{ GossipsubMessageAcceptance, GossipsubMessageInfo, NetworkData, }, - txpool::TransactionStatus, }, }; use std::sync::Arc; @@ -46,7 +44,7 @@ pub trait PeerToPeer: Send + Sync { pub trait BlockImporter: Send + Sync { /// Wait until the next block is available - fn block_events(&self) -> BoxStream>; + fn block_events(&self) -> BoxStream; } pub trait TxPoolDb: Send + Sync { @@ -59,6 +57,4 @@ pub trait TxPoolDb: Send + Sync { fn is_message_spent(&self, message_id: &Nonce) -> StorageResult; fn current_block_height(&self) -> StorageResult; - - fn transaction_status(&self, tx_id: &Bytes32) -> StorageResult; } diff --git a/crates/services/txpool/src/service.rs b/crates/services/txpool/src/service.rs index e247e196a7..38ac9b7592 100644 --- a/crates/services/txpool/src/service.rs +++ b/crates/services/txpool/src/service.rs @@ -34,7 +34,6 @@ use fuel_core_types::{ Bytes32, }, services::{ - block_importer::ImportResult, p2p::{ GossipData, GossipsubMessageAcceptance, @@ -52,6 +51,7 @@ use fuel_core_types::{ }; use anyhow::anyhow; +use fuel_core_types::services::block_importer::SharedImportResult; use parking_lot::Mutex as ParkingMutex; use std::{ sync::Arc, @@ -143,7 +143,7 @@ impl Clone for SharedState { pub struct Task { gossiped_tx_stream: BoxStream, - committed_block_stream: BoxStream>, + committed_block_stream: BoxStream, shared: SharedState, ttl_timer: tokio::time::Interval, } @@ -201,14 +201,13 @@ where result = self.committed_block_stream.next() => { if let Some(result) = result { - let block = result + let block = &result .sealed_block - .entity - .compress(&self.shared.consensus_params.chain_id); + .entity; self.shared.txpool.lock().block_update( &self.shared.tx_status_sender, - block.header().height(), - block.transactions() + block, + &result.tx_status, ); should_continue = true; } else { diff --git a/crates/services/txpool/src/service/test_helpers.rs b/crates/services/txpool/src/service/test_helpers.rs index decaf2f98d..3cf532bfa8 100644 --- a/crates/services/txpool/src/service/test_helpers.rs +++ b/crates/services/txpool/src/service/test_helpers.rs @@ -21,7 +21,10 @@ use fuel_core_types::{ TransactionBuilder, Word, }, - services::p2p::GossipsubMessageAcceptance, + services::{ + block_importer::ImportResult, + p2p::GossipsubMessageAcceptance, + }, }; use std::cell::RefCell; @@ -103,7 +106,7 @@ mockall::mock! { pub Importer {} impl BlockImporter for Importer { - fn block_events(&self) -> BoxStream>; + fn block_events(&self) -> BoxStream; } } @@ -115,7 +118,7 @@ impl MockImporter { let stream = fuel_core_services::stream::unfold(blocks, |mut blocks| async { let block = blocks.pop(); if let Some(sealed_block) = block { - let result = + let result: SharedImportResult = Arc::new(ImportResult::new_from_local(sealed_block, vec![])); Some((result, blocks)) diff --git a/crates/services/txpool/src/txpool.rs b/crates/services/txpool/src/txpool.rs index 50c7d2484e..1c3c0376e8 100644 --- a/crates/services/txpool/src/txpool.rs +++ b/crates/services/txpool/src/txpool.rs @@ -35,8 +35,16 @@ use fuel_core_types::{ tai64::Tai64, }; +use crate::service::TxStatusMessage; use fuel_core_metrics::txpool_metrics::txpool_metrics; -use fuel_core_types::fuel_vm::checked_transaction::CheckPredicateParams; +use fuel_core_types::{ + blockchain::block::Block, + fuel_vm::checked_transaction::CheckPredicateParams, + services::{ + executor::TransactionExecutionStatus, + txpool::from_executor_to_status, + }, +}; use std::{ cmp::Reverse, collections::HashMap, @@ -315,14 +323,19 @@ where pub fn block_update( &mut self, tx_status_sender: &TxStatusChange, - height: &BlockHeight, - transactions: &[TxId], + block: &Block, + tx_status: &[TransactionExecutionStatus], // spend_outputs: [Input], added_outputs: [AddedOutputs] ) { - for tx_id in transactions { - let tx_id = *tx_id; - let result = self.database.transaction_status(&tx_id); - tx_status_sender.send_complete(tx_id, height, result); + let height = block.header().height(); + for status in tx_status { + let tx_id = status.id; + let status = from_executor_to_status(block, status.result.clone()); + tx_status_sender.send_complete( + tx_id, + height, + TxStatusMessage::Status(status), + ); self.remove_committed_tx(&tx_id); } } diff --git a/crates/storage/src/transactional.rs b/crates/storage/src/transactional.rs index d44041113b..854557bd11 100644 --- a/crates/storage/src/transactional.rs +++ b/crates/storage/src/transactional.rs @@ -1,6 +1,7 @@ //! The primitives to work with storage in transactional mode. use crate::Result as StorageResult; +use fuel_core_types::fuel_types::BlockHeight; #[cfg_attr(feature = "test-helpers", mockall::automock(type Storage = crate::test_helpers::EmptyStorage;))] /// The types is transactional and may create `StorageTransaction`. @@ -75,3 +76,13 @@ impl StorageTransaction { self.transaction.commit() } } + +/// Provides a view of the storage at the given height. +/// It guarantees to be atomic, meaning the view is immutable to outside modifications. +pub trait AtomicView: Send + Sync { + /// Returns the view of the storage at the given `height`. + fn view_at(&self, height: BlockHeight) -> StorageResult; + + /// Returns the view of the storage for the latest block height. + fn latest_view(&self) -> View; +} diff --git a/crates/types/src/services/block_importer.rs b/crates/types/src/services/block_importer.rs index 494abb8b57..276a305b96 100644 --- a/crates/types/src/services/block_importer.rs +++ b/crates/types/src/services/block_importer.rs @@ -10,11 +10,16 @@ use crate::{ Uncommitted, }, }; +use core::ops::Deref; +use std::sync::Arc; /// The uncommitted result of the block importing. pub type UncommittedResult = Uncommitted; +/// The alias for the `ImportResult` that can be shared between threads. +pub type SharedImportResult = Arc + Send + Sync>; + /// The result of the block import. #[derive(Debug)] #[cfg_attr(any(test, feature = "test-helpers"), derive(Default))] @@ -27,6 +32,14 @@ pub struct ImportResult { pub source: Source, } +impl Deref for ImportResult { + type Target = Self; + + fn deref(&self) -> &Self::Target { + self + } +} + /// The source producer of the block. #[derive(Debug, Clone, Copy, PartialEq, Default)] pub enum Source { @@ -87,8 +100,8 @@ impl BlockImportInfo { } } -impl From<&ImportResult> for BlockImportInfo { - fn from(result: &ImportResult) -> Self { +impl From for BlockImportInfo { + fn from(result: SharedImportResult) -> Self { Self { block_header: result.sealed_block.entity.header().clone(), source: result.source, diff --git a/crates/types/src/services/executor.rs b/crates/types/src/services/executor.rs index 8f48c815e7..a51c5b564d 100644 --- a/crates/types/src/services/executor.rs +++ b/crates/types/src/services/executor.rs @@ -9,6 +9,7 @@ use crate::{ primitives::BlockId, }, fuel_tx::{ + Receipt, TxId, UtxoId, ValidityError, @@ -53,6 +54,8 @@ pub struct TransactionExecutionStatus { pub id: Bytes32, /// The result of the executed transaction. pub result: TransactionExecutionResult, + /// The receipts generated by the executed transaction. + pub receipts: Vec, } /// The result of transaction execution. diff --git a/crates/types/src/services/txpool.rs b/crates/types/src/services/txpool.rs index c323761ec8..4cc483e6c7 100644 --- a/crates/types/src/services/txpool.rs +++ b/crates/types/src/services/txpool.rs @@ -1,7 +1,10 @@ //! Types for interoperability with the txpool service use crate::{ - blockchain::primitives::BlockId, + blockchain::{ + block::Block, + primitives::BlockId, + }, fuel_asm::Word, fuel_tx::{ field::{ @@ -27,6 +30,7 @@ use crate::{ checked_transaction::Checked, ProgramState, }, + services::executor::TransactionExecutionResult, }; use fuel_vm_private::checked_transaction::CheckedTransaction; use std::{ @@ -199,6 +203,30 @@ pub enum TransactionStatus { }, } +/// Converts the transaction execution result to the transaction status. +pub fn from_executor_to_status( + block: &Block, + result: TransactionExecutionResult, +) -> TransactionStatus { + let time = block.header().time(); + let block_id = block.id(); + match result { + TransactionExecutionResult::Success { result } => TransactionStatus::Success { + block_id, + time, + result, + }, + TransactionExecutionResult::Failed { result, reason } => { + TransactionStatus::Failed { + block_id, + time, + result, + reason: reason.clone(), + } + } + } +} + #[allow(missing_docs)] #[derive(thiserror::Error, Debug, PartialEq, Eq, Clone)] #[non_exhaustive] From bd60793f0a5451676a4bfea01ee42787c8a5bd4b Mon Sep 17 00:00:00 2001 From: xgreenx Date: Fri, 5 Jan 2024 15:48:10 +0100 Subject: [PATCH 09/11] Added comments and linked todos --- crates/fuel-core/src/graphql_api/database.rs | 11 +++++++++++ crates/fuel-core/src/graphql_api/worker_service.rs | 7 +++++++ crates/fuel-core/src/query/coin.rs | 2 +- crates/fuel-core/src/query/tx.rs | 2 +- .../src/service/adapters/graphql_api/off_chain.rs | 1 + .../src/service/adapters/graphql_api/on_chain.rs | 1 + crates/fuel-core/src/service/adapters/sync.rs | 1 - crates/storage/src/tables.rs | 1 + 8 files changed, 23 insertions(+), 3 deletions(-) diff --git a/crates/fuel-core/src/graphql_api/database.rs b/crates/fuel-core/src/graphql_api/database.rs index 175f0610f1..5aa4ba0f97 100644 --- a/crates/fuel-core/src/graphql_api/database.rs +++ b/crates/fuel-core/src/graphql_api/database.rs @@ -52,15 +52,22 @@ use std::{ sync::Arc, }; +/// The on-chain view of the database used by the [`ReadView`] to fetch on-chain data. pub type OnChainView = Arc; +/// The off-chain view of the database used by the [`ReadView`] to fetch off-chain data. pub type OffChainView = Arc; +/// The container of the on-chain and off-chain database view provides. +/// It is used only by [`ViewExtension`](super::view_extension::ViewExtension) to create a [`ReadView`]. pub struct ReadDatabase { + /// The on-chain database view provider. on_chain: Box>, + /// The off-chain database view provider. off_chain: Box>, } impl ReadDatabase { + /// Creates a new [`ReadDatabase`] with the given on-chain and off-chain database view providers. pub fn new(on_chain: OnChain, off_chain: OffChain) -> Self where OnChain: AtomicView + 'static, @@ -72,7 +79,11 @@ impl ReadDatabase { } } + /// Creates a consistent view of the database. pub fn view(&self) -> ReadView { + // TODO: Use the same height for both views to guarantee consistency. + // It is not possible to implement until `view_at` is implemented for the `AtomicView`. + // https://github.com/FuelLabs/fuel-core/issues/1582 ReadView { on_chain: self.on_chain.latest_view(), off_chain: self.off_chain.latest_view(), diff --git a/crates/fuel-core/src/graphql_api/worker_service.rs b/crates/fuel-core/src/graphql_api/worker_service.rs index fe904d7f7d..22f5471922 100644 --- a/crates/fuel-core/src/graphql_api/worker_service.rs +++ b/crates/fuel-core/src/graphql_api/worker_service.rs @@ -48,6 +48,8 @@ use futures::{ StreamExt, }; +/// The off-chain GraphQL API worker task processes the imported blocks +/// and actualize the information used by the GraphQL service. pub struct Task { block_importer: BoxStream, database: D, @@ -58,6 +60,9 @@ where D: ports::worker::OffChainDatabase, { fn process_block(&mut self, result: SharedImportResult) -> anyhow::Result<()> { + // TODO: Implement the creation of indexes for the messages and coins. + // Implement table `BlockId -> BlockHeight` to get the block height by block id. + // https://github.com/FuelLabs/fuel-core/issues/1583 let mut transaction = self.database.transaction(); // save the status for every transaction using the finalized block id self.persist_transaction_status(&result, transaction.as_mut())?; @@ -219,6 +224,7 @@ where // to actualize the database without executing the block at the previous state // of the blockchain. When `AtomicView::view_at` is implemented, we can // process all missed blocks and actualize the database here. + // https://github.com/FuelLabs/fuel-core/issues/1584 Ok(self) } } @@ -251,6 +257,7 @@ where } async fn shutdown(mut self) -> anyhow::Result<()> { + // Process all remaining blocks before shutdown to not lose any data. loop { let result = self.block_importer.next().now_or_never(); diff --git a/crates/fuel-core/src/query/coin.rs b/crates/fuel-core/src/query/coin.rs index 427379a728..171a88168b 100644 --- a/crates/fuel-core/src/query/coin.rs +++ b/crates/fuel-core/src/query/coin.rs @@ -37,7 +37,7 @@ pub trait CoinQueryData: Send + Sync { ) -> BoxedIter>; } -impl CoinQueryData for D { +impl CoinQueryData for D { fn coin(&self, utxo_id: UtxoId) -> StorageResult { let coin = self .storage::() diff --git a/crates/fuel-core/src/query/tx.rs b/crates/fuel-core/src/query/tx.rs index 09994be55f..ebc2531f27 100644 --- a/crates/fuel-core/src/query/tx.rs +++ b/crates/fuel-core/src/query/tx.rs @@ -37,7 +37,7 @@ pub trait SimpleTransactionData: Send + Sync { impl SimpleTransactionData for D where - D: OffChainDatabase + OnChainDatabase + ?Sized, + D: OnChainDatabase + OffChainDatabase + ?Sized, { fn transaction(&self, tx_id: &TxId) -> StorageResult { self.storage::() diff --git a/crates/fuel-core/src/service/adapters/graphql_api/off_chain.rs b/crates/fuel-core/src/service/adapters/graphql_api/off_chain.rs index ba23d77bd5..86fc7002a0 100644 --- a/crates/fuel-core/src/service/adapters/graphql_api/off_chain.rs +++ b/crates/fuel-core/src/service/adapters/graphql_api/off_chain.rs @@ -91,6 +91,7 @@ impl AtomicView for Database { } fn latest_view(&self) -> OffChainView { + // TODO: https://github.com/FuelLabs/fuel-core/issues/1581 Arc::new(self.clone()) } } diff --git a/crates/fuel-core/src/service/adapters/graphql_api/on_chain.rs b/crates/fuel-core/src/service/adapters/graphql_api/on_chain.rs index d0bcaf3eeb..dd9c9937ff 100644 --- a/crates/fuel-core/src/service/adapters/graphql_api/on_chain.rs +++ b/crates/fuel-core/src/service/adapters/graphql_api/on_chain.rs @@ -134,6 +134,7 @@ impl AtomicView for Database { } fn latest_view(&self) -> OnChainView { + // TODO: https://github.com/FuelLabs/fuel-core/issues/1581 Arc::new(self.clone()) } } diff --git a/crates/fuel-core/src/service/adapters/sync.rs b/crates/fuel-core/src/service/adapters/sync.rs index ddbcd2ed6d..1b63c8c25e 100644 --- a/crates/fuel-core/src/service/adapters/sync.rs +++ b/crates/fuel-core/src/service/adapters/sync.rs @@ -137,7 +137,6 @@ impl BlockImporterPort for BlockImporterAdapter { }), ) } - async fn execute_and_commit(&self, block: SealedBlock) -> anyhow::Result<()> { self.execute_and_commit(block).await } diff --git a/crates/storage/src/tables.rs b/crates/storage/src/tables.rs index 27f5cb2fb2..5e7762ea76 100644 --- a/crates/storage/src/tables.rs +++ b/crates/storage/src/tables.rs @@ -40,6 +40,7 @@ impl Mappable for FuelBlocks { /// Unique identifier of the fuel block. type Key = Self::OwnedKey; // TODO: Seems it would be faster to use `BlockHeight` as primary key. + // https://github.com/FuelLabs/fuel-core/issues/1580. type OwnedKey = BlockId; type Value = Self::OwnedValue; type OwnedValue = CompressedBlock; From 8221443f1e4418b7419afaa27343ab7614a8adb7 Mon Sep 17 00:00:00 2001 From: xgreenx Date: Fri, 5 Jan 2024 15:49:34 +0100 Subject: [PATCH 10/11] Updated CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5870b438e5..2227d4e107 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ Description of the upcoming release here. ### Changed +- [#1579](https://github.com/FuelLabs/fuel-core/pull/1579): The change extracts the off-chain-related logic from the executor and moves it to the GraphQL off-chain worker. It creates two new concepts - Off-chain and On-chain databases where the GraphQL worker has exclusive ownership of the database and may modify it without intersecting with the On-chain database. - [#1577](https://github.com/FuelLabs/fuel-core/pull/1577): Moved insertion of sealed blocks into the `BlockImporter` instead of the executor. ## [Version 0.22.0] From c7bce203dabe92cd4024f7b1cd3681132bec1b1b Mon Sep 17 00:00:00 2001 From: xgreenx Date: Fri, 19 Jan 2024 05:31:10 +0100 Subject: [PATCH 11/11] Fix conflicts --- crates/fuel-core/src/graphql_api/database.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/fuel-core/src/graphql_api/database.rs b/crates/fuel-core/src/graphql_api/database.rs index 5aa4ba0f97..feb9a638c1 100644 --- a/crates/fuel-core/src/graphql_api/database.rs +++ b/crates/fuel-core/src/graphql_api/database.rs @@ -58,7 +58,7 @@ pub type OnChainView = Arc; pub type OffChainView = Arc; /// The container of the on-chain and off-chain database view provides. -/// It is used only by [`ViewExtension`](super::view_extension::ViewExtension) to create a [`ReadView`]. +/// It is used only by `ViewExtension` to create a [`ReadView`]. pub struct ReadDatabase { /// The on-chain database view provider. on_chain: Box>,