diff --git a/CHANGELOG.md b/CHANGELOG.md index 5fee0162125..431818fea7f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ Description of the upcoming release here. ### Changed +- [#1833](https://github.com/FuelLabs/fuel-core/pull/1833): Regenesis of `SpentMessages` and `ProcessedTransactions`. - [#1830](https://github.com/FuelLabs/fuel-core/pull/1830): Use versioning enum for WASM executor input and output. - [#1816](https://github.com/FuelLabs/fuel-core/pull/1816): Updated the upgradable executor to fetch the state transition bytecode from the database when the version doesn't match a native one. This change enables the WASM executor in the "production" build and requires a `wasm32-unknown-unknown` target. - [#1812](https://github.com/FuelLabs/fuel-core/pull/1812): Follow-up PR to simplify the logic around parallel snapshot creation. diff --git a/bin/fuel-core/chainspec/dev-testnet/state_transition_bytecode.wasm b/bin/fuel-core/chainspec/dev-testnet/state_transition_bytecode.wasm index 92f57ec2709..df2e7bbb6a4 100755 Binary files a/bin/fuel-core/chainspec/dev-testnet/state_transition_bytecode.wasm and b/bin/fuel-core/chainspec/dev-testnet/state_transition_bytecode.wasm differ diff --git a/bin/fuel-core/chainspec/testnet/state_transition_bytecode.wasm b/bin/fuel-core/chainspec/testnet/state_transition_bytecode.wasm index 92f57ec2709..df2e7bbb6a4 100755 Binary files a/bin/fuel-core/chainspec/testnet/state_transition_bytecode.wasm and b/bin/fuel-core/chainspec/testnet/state_transition_bytecode.wasm differ diff --git a/ci_checks.sh b/ci_checks.sh index ce5175bfe6a..d2fb5297857 100755 --- a/ci_checks.sh +++ b/ci_checks.sh @@ -25,7 +25,8 @@ cargo check -p fuel-core-storage --target wasm32-unknown-unknown --no-default-fe cargo check -p fuel-core-client --target wasm32-unknown-unknown --no-default-features && cargo check -p fuel-core-chain-config --target wasm32-unknown-unknown --no-default-features && cargo check -p fuel-core-executor --target wasm32-unknown-unknown --no-default-features && -OVERRIDE_CHAIN_CONFIGS=true cargo test --workspace && +OVERRIDE_CHAIN_CONFIGS=true cargo test --test integration_tests deployment && +cargo test --workspace && FUEL_ALWAYS_USE_WASM=true cargo test --all-features --workspace && cargo test -p fuel-core --no-default-features && cargo test -p fuel-core-client --no-default-features && diff --git a/crates/chain-config/src/config/state.rs b/crates/chain-config/src/config/state.rs index 11e9313ce9e..ffef804f907 100644 --- a/crates/chain-config/src/config/state.rs +++ b/crates/chain-config/src/config/state.rs @@ -18,6 +18,7 @@ use fuel_core_storage::{ ContractsState, FuelBlocks, Messages, + ProcessedTransactions, SealedBlockConsensus, Transactions, }, @@ -433,6 +434,16 @@ impl AddTable for StateConfigBuilder { fn add(&mut self, _entries: Vec>) {} } +impl AddTable for StateConfigBuilder { + fn add(&mut self, _: Vec>) {} +} + +impl AsTable for StateConfig { + fn as_table(&self) -> Vec> { + Vec::new() // Do not include these for now + } +} + impl StateConfig { pub fn sorted(mut self) -> Self { self.coins = self diff --git a/crates/chain-config/src/genesis.rs b/crates/chain-config/src/genesis.rs index 203ca78e4f9..c55cd5fce2a 100644 --- a/crates/chain-config/src/genesis.rs +++ b/crates/chain-config/src/genesis.rs @@ -18,6 +18,7 @@ impl GenesisCommitment for Genesis { .chain(self.coins_root) .chain(self.contracts_root) .chain(self.messages_root) + .chain(self.transactions_root) .finalize(); Ok(genesis_hash) diff --git a/crates/client/assets/schema.sdl b/crates/client/assets/schema.sdl index d2f10e5356c..19a1389f24a 100644 --- a/crates/client/assets/schema.sdl +++ b/crates/client/assets/schema.sdl @@ -460,6 +460,10 @@ type Genesis { The Binary Merkle Tree root of all genesis messages. """ messagesRoot: Bytes32! + """ + The Binary Merkle Tree root of all processed transaction ids. + """ + transactionsRoot: Bytes32! } type Header { diff --git a/crates/client/src/client/schema/block.rs b/crates/client/src/client/schema/block.rs index 4fecdb0e4bc..f6280e1512d 100644 --- a/crates/client/src/client/schema/block.rs +++ b/crates/client/src/client/schema/block.rs @@ -148,6 +148,7 @@ pub struct Genesis { pub coins_root: Bytes32, pub contracts_root: Bytes32, pub messages_root: Bytes32, + pub transactions_root: Bytes32, } #[derive(cynic::QueryFragment, Clone, Debug)] diff --git a/crates/client/src/client/schema/snapshots/fuel_core_client__client__schema__block__tests__block_by_height_query_gql_output.snap b/crates/client/src/client/schema/snapshots/fuel_core_client__client__schema__block__tests__block_by_height_query_gql_output.snap index 99321c4eb66..e707ee45212 100644 --- a/crates/client/src/client/schema/snapshots/fuel_core_client__client__schema__block__tests__block_by_height_query_gql_output.snap +++ b/crates/client/src/client/schema/snapshots/fuel_core_client__client__schema__block__tests__block_by_height_query_gql_output.snap @@ -27,6 +27,7 @@ query($height: U32) { coinsRoot contractsRoot messagesRoot + transactionsRoot } ... on PoAConsensus { signature diff --git a/crates/client/src/client/schema/snapshots/fuel_core_client__client__schema__block__tests__block_by_id_query_gql_output.snap b/crates/client/src/client/schema/snapshots/fuel_core_client__client__schema__block__tests__block_by_id_query_gql_output.snap index 5d4d9dbd8ca..38f86c43561 100644 --- a/crates/client/src/client/schema/snapshots/fuel_core_client__client__schema__block__tests__block_by_id_query_gql_output.snap +++ b/crates/client/src/client/schema/snapshots/fuel_core_client__client__schema__block__tests__block_by_id_query_gql_output.snap @@ -27,6 +27,7 @@ query($id: BlockId) { coinsRoot contractsRoot messagesRoot + transactionsRoot } ... on PoAConsensus { signature diff --git a/crates/client/src/client/schema/snapshots/fuel_core_client__client__schema__block__tests__blocks_connection_query_gql_output.snap b/crates/client/src/client/schema/snapshots/fuel_core_client__client__schema__block__tests__blocks_connection_query_gql_output.snap index 67034b77090..42b672009f7 100644 --- a/crates/client/src/client/schema/snapshots/fuel_core_client__client__schema__block__tests__blocks_connection_query_gql_output.snap +++ b/crates/client/src/client/schema/snapshots/fuel_core_client__client__schema__block__tests__blocks_connection_query_gql_output.snap @@ -30,6 +30,7 @@ query($after: String, $before: String, $first: Int, $last: Int) { coinsRoot contractsRoot messagesRoot + transactionsRoot } ... on PoAConsensus { signature diff --git a/crates/client/src/client/schema/snapshots/fuel_core_client__client__schema__chain__tests__chain_gql_query_output.snap b/crates/client/src/client/schema/snapshots/fuel_core_client__client__schema__chain__tests__chain_gql_query_output.snap index f2e6925e909..7ca35e72dba 100644 --- a/crates/client/src/client/schema/snapshots/fuel_core_client__client__schema__chain__tests__chain_gql_query_output.snap +++ b/crates/client/src/client/schema/snapshots/fuel_core_client__client__schema__chain__tests__chain_gql_query_output.snap @@ -30,6 +30,7 @@ query { coinsRoot contractsRoot messagesRoot + transactionsRoot } ... on PoAConsensus { signature diff --git a/crates/client/src/client/types/block.rs b/crates/client/src/client/types/block.rs index c663c4c582f..4ffb8bfc9cd 100644 --- a/crates/client/src/client/types/block.rs +++ b/crates/client/src/client/types/block.rs @@ -57,6 +57,7 @@ pub struct Genesis { pub coins_root: MerkleRoot, pub contracts_root: MerkleRoot, pub messages_root: MerkleRoot, + pub transactions_root: MerkleRoot, } #[derive(Clone, Debug, PartialEq, Eq)] @@ -109,6 +110,7 @@ impl From for Genesis { coins_root: value.coins_root.into(), contracts_root: value.contracts_root.into(), messages_root: value.messages_root.into(), + transactions_root: value.transactions_root.into(), } } } diff --git a/crates/fuel-core/src/database/genesis_progress.rs b/crates/fuel-core/src/database/genesis_progress.rs index d7e5269368c..81823d571ba 100644 --- a/crates/fuel-core/src/database/genesis_progress.rs +++ b/crates/fuel-core/src/database/genesis_progress.rs @@ -20,6 +20,7 @@ use fuel_core_storage::{ Coins, ContractsLatestUtxo, Messages, + ProcessedTransactions, }, Error as StorageError, Mappable, @@ -138,4 +139,16 @@ impl Database { Ok(root_calculator.root()) } + + pub fn processed_transactions_root(&self) -> Result { + let txs = self.iter_all::(None); + + let mut root_calculator = MerkleRootCalculator::new(); + for tx in txs { + let (tx_id, _) = tx?; + root_calculator.push(tx_id.as_slice()); + } + + Ok(root_calculator.root()) + } } diff --git a/crates/fuel-core/src/database/message.rs b/crates/fuel-core/src/database/message.rs index 270ce465382..8b01de979bd 100644 --- a/crates/fuel-core/src/database/message.rs +++ b/crates/fuel-core/src/database/message.rs @@ -6,6 +6,7 @@ use crate::{ fuel_core_graphql_api::storage::messages::{ OwnedMessageIds, OwnedMessageKey, + SpentMessages, }, }; use fuel_core_chain_config::TableEntry; @@ -14,11 +15,7 @@ use fuel_core_storage::{ IterDirection, IteratorOverTable, }, - tables::{ - Messages, - SpentMessages, - }, - Error as StorageError, + tables::Messages, Result as StorageResult, }; use fuel_core_types::{ @@ -46,6 +43,10 @@ impl Database { ) .map(|res| res.map(|(key, _)| *key.nonce())) } + + pub fn message_is_spent(&self, id: &Nonce) -> StorageResult { + fuel_core_storage::StorageAsRef::storage::(&self).contains_key(id) + } } impl Database { @@ -62,25 +63,9 @@ impl Database { &self, ) -> impl Iterator>> + '_ { self.iter_all_by_start::(None, None) - .filter_map(|msg| { - // Return only unspent messages - if let Ok(msg) = msg { - match self.message_is_spent(msg.1.id()) { - Ok(false) => Some(Ok(msg)), - Ok(true) => None, - Err(e) => Some(Err(e)), - } - } else { - Some(msg.map_err(StorageError::from)) - } - }) .map_ok(|(key, value)| TableEntry { key, value }) } - pub fn message_is_spent(&self, id: &Nonce) -> StorageResult { - fuel_core_storage::StorageAsRef::storage::(&self).contains_key(id) - } - pub fn message_exists(&self, id: &Nonce) -> StorageResult { fuel_core_storage::StorageAsRef::storage::(&self).contains_key(id) } diff --git a/crates/fuel-core/src/executor.rs b/crates/fuel-core/src/executor.rs index 2fa3844c618..b7a90ed4af5 100644 --- a/crates/fuel-core/src/executor.rs +++ b/crates/fuel-core/src/executor.rs @@ -2373,8 +2373,8 @@ mod tests { let mut exec = make_executor(&messages); let view = exec.storage_view_provider.latest_view(); - assert!(!view.message_is_spent(message_coin.nonce()).unwrap()); - assert!(!view.message_is_spent(message_data.nonce()).unwrap()); + assert!(view.message_exists(message_coin.nonce()).unwrap()); + assert!(view.message_exists(message_data.nonce()).unwrap()); let ExecutionResult { skipped_transactions, @@ -2386,8 +2386,8 @@ mod tests { // Successful execution consumes `message_coin` and `message_data`. let view = exec.storage_view_provider.latest_view(); - assert!(view.message_is_spent(message_coin.nonce()).unwrap()); - assert!(view.message_is_spent(message_data.nonce()).unwrap()); + assert!(!view.message_exists(message_coin.nonce()).unwrap()); + assert!(!view.message_exists(message_data.nonce()).unwrap()); assert_eq!( *view.coin(&UtxoId::new(tx_id, 0)).unwrap().amount(), amount + amount @@ -2422,8 +2422,8 @@ mod tests { let mut exec = make_executor(&messages); let view = exec.storage_view_provider.latest_view(); - assert!(!view.message_is_spent(message_coin.nonce()).unwrap()); - assert!(!view.message_is_spent(message_data.nonce()).unwrap()); + assert!(view.message_exists(message_coin.nonce()).unwrap()); + assert!(view.message_exists(message_data.nonce()).unwrap()); let ExecutionResult { skipped_transactions, @@ -2435,8 +2435,8 @@ mod tests { // We should spend only `message_coin`. The `message_data` should be unspent. let view = exec.storage_view_provider.latest_view(); - assert!(view.message_is_spent(message_coin.nonce()).unwrap()); - assert!(!view.message_is_spent(message_data.nonce()).unwrap()); + assert!(!view.message_exists(message_coin.nonce()).unwrap()); + assert!(view.message_exists(message_data.nonce()).unwrap()); assert_eq!(*view.coin(&UtxoId::new(tx_id, 0)).unwrap().amount(), amount); } @@ -2580,10 +2580,11 @@ mod tests { // One of two transactions is skipped. assert_eq!(skipped_transactions.len(), 1); let err = &skipped_transactions[0].1; + dbg!(err); assert!(matches!( err, &ExecutorError::TransactionValidity( - TransactionValidityError::MessageAlreadySpent(_) + TransactionValidityError::MessageDoesNotExist(_) ) )); @@ -2602,7 +2603,7 @@ mod tests { assert!(matches!( res, Err(ExecutorError::TransactionValidity( - TransactionValidityError::MessageAlreadySpent(_) + TransactionValidityError::MessageDoesNotExist(_) )) )); } @@ -2830,10 +2831,7 @@ mod tests { use fuel_core_relayer::storage::EventsHistory; use fuel_core_storage::{ iter::IteratorOverTable, - tables::{ - FuelBlocks, - SpentMessages, - }, + tables::FuelBlocks, StorageAsMut, }; use fuel_core_types::{ @@ -3604,7 +3602,6 @@ mod tests { // Given assert_eq!(on_chain_db.iter_all::(None).count(), 0); - assert_eq!(on_chain_db.iter_all::(None).count(), 0); let tx = TransactionBuilder::script(vec![], vec![]) .script_gas_limit(10) .add_unsigned_message_input( @@ -3629,8 +3626,6 @@ mod tests { let view = ChangesIterator::::new(&changes); assert!(result.skipped_transactions.is_empty()); assert_eq!(view.iter_all::(None).count() as u64, 0); - // Message added during this block immediately became spent. - assert_eq!(view.iter_all::(None).count(), 1); assert_eq!(result.events.len(), 2); assert!(matches!( result.events[0], diff --git a/crates/fuel-core/src/graphql_api/database.rs b/crates/fuel-core/src/graphql_api/database.rs index 566e0f43f41..316bf4dca60 100644 --- a/crates/fuel-core/src/graphql_api/database.rs +++ b/crates/fuel-core/src/graphql_api/database.rs @@ -196,10 +196,6 @@ impl DatabaseMessages for ReadView { 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) } @@ -313,4 +309,8 @@ impl OffChainDatabase for ReadView { ) -> StorageResult> { self.off_chain.relayed_tx_status(id) } + + fn message_is_spent(&self, nonce: &Nonce) -> StorageResult { + self.off_chain.message_is_spent(nonce) + } } diff --git a/crates/fuel-core/src/graphql_api/ports.rs b/crates/fuel-core/src/graphql_api/ports.rs index 9eb978d6fb4..7d0d3e0fb3c 100644 --- a/crates/fuel-core/src/graphql_api/ports.rs +++ b/crates/fuel-core/src/graphql_api/ports.rs @@ -106,6 +106,8 @@ pub trait OffChainDatabase: Send + Sync { &self, id: Bytes32, ) -> StorageResult>; + + fn message_is_spent(&self, nonce: &Nonce) -> StorageResult; } /// The on chain database port expected by GraphQL API service. @@ -147,8 +149,6 @@ pub trait DatabaseMessages: StorageInspect { direction: IterDirection, ) -> BoxedIter<'_, StorageResult>; - fn message_is_spent(&self, nonce: &Nonce) -> StorageResult; - fn message_exists(&self, nonce: &Nonce) -> StorageResult; } @@ -242,7 +242,10 @@ pub mod worker { fuel_core_graphql_api::storage::{ coins::OwnedCoins, contracts::ContractsInfo, - messages::OwnedMessageIds, + messages::{ + OwnedMessageIds, + SpentMessages, + }, }, graphql_api::storage::{ old::{ @@ -288,6 +291,7 @@ pub mod worker { + StorageMutate + StorageMutate + StorageMutate + + StorageMutate + StorageMutate { fn record_tx_id_owner( diff --git a/crates/fuel-core/src/graphql_api/storage.rs b/crates/fuel-core/src/graphql_api/storage.rs index d2b61fc2e81..d97e2363f2f 100644 --- a/crates/fuel-core/src/graphql_api/storage.rs +++ b/crates/fuel-core/src/graphql_api/storage.rs @@ -89,6 +89,10 @@ pub enum Column { OldTransactions = 11, /// Relayed Tx ID to Layer 1 Relayed Transaction status RelayedTransactionStatus = 12, + /// Messages that have been spent. + /// Existence of a key in this column means that the message has been spent. + /// See [`SpentMessages`](messages::SpentMessages) + SpentMessages = 13, } impl Column { diff --git a/crates/fuel-core/src/graphql_api/storage/messages.rs b/crates/fuel-core/src/graphql_api/storage/messages.rs index 951cb3a2a7c..102ce20b84d 100644 --- a/crates/fuel-core/src/graphql_api/storage/messages.rs +++ b/crates/fuel-core/src/graphql_api/storage/messages.rs @@ -1,3 +1,10 @@ +use fuel_core_chain_config::{ + AddTable, + AsTable, + StateConfig, + StateConfigBuilder, + TableEntry, +}; use fuel_core_storage::{ blueprint::plain::Plain, codec::{ @@ -56,3 +63,41 @@ fuel_core_storage::basic_storage_tests!( ::Key::default(), ::Value::default() ); + +/// The storage table that indicates if the message is spent or not. +pub struct SpentMessages; + +impl Mappable for SpentMessages { + type Key = Self::OwnedKey; + type OwnedKey = Nonce; + type Value = Self::OwnedValue; + type OwnedValue = (); +} + +impl TableWithBlueprint for SpentMessages { + type Blueprint = Plain; + type Column = super::Column; + + fn column() -> Self::Column { + Self::Column::SpentMessages + } +} + +impl AsTable for StateConfig { + fn as_table(&self) -> Vec> { + Vec::new() // Do not include these for now + } +} + +impl AddTable for StateConfigBuilder { + fn add(&mut self, _entries: Vec>) { + // Do not include these for now + } +} + +#[cfg(test)] +fuel_core_storage::basic_storage_tests!( + SpentMessages, + ::Key::default(), + ::Value::default() +); diff --git a/crates/fuel-core/src/graphql_api/worker_service.rs b/crates/fuel-core/src/graphql_api/worker_service.rs index 7fd33ac8b5a..ad76fda9f3b 100644 --- a/crates/fuel-core/src/graphql_api/worker_service.rs +++ b/crates/fuel-core/src/graphql_api/worker_service.rs @@ -12,6 +12,7 @@ use crate::{ messages::{ OwnedMessageIds, OwnedMessageKey, + SpentMessages, }, }, }, @@ -172,6 +173,9 @@ where message.recipient(), message.nonce(), ))?; + block_st_transaction + .storage::() + .insert(message.nonce(), &())?; } Event::CoinCreated(coin) => { let coin_by_owner = owner_coin_id_key(&coin.owner, &coin.utxo_id); diff --git a/crates/fuel-core/src/p2p_test_helpers.rs b/crates/fuel-core/src/p2p_test_helpers.rs index 6df978de991..a3e1912171a 100644 --- a/crates/fuel-core/src/p2p_test_helpers.rs +++ b/crates/fuel-core/src/p2p_test_helpers.rs @@ -380,7 +380,7 @@ pub fn make_config(name: String, mut node_config: Config) -> Config { pub async fn make_node(node_config: Config, test_txs: Vec) -> Node { let db = Database::in_memory(); - let time_limit = Duration::from_secs(10); + let time_limit = Duration::from_secs(4); let node = tokio::time::timeout( time_limit, FuelService::from_database(db.clone(), node_config), @@ -430,7 +430,12 @@ impl Node { .block_stream() .take(number_of_blocks); while let Some(block) = stream.next().await { - assert_eq!(block.is_locally_produced(), is_local); + if block.is_locally_produced() != is_local { + panic!( + "Block produced by the wrong node while was \ + waiting for `{number_of_blocks}` and is_local=`{is_local}`" + ); + } } } diff --git a/crates/fuel-core/src/query/message.rs b/crates/fuel-core/src/query/message.rs index b9e527272b0..2fb43928737 100644 --- a/crates/fuel-core/src/query/message.rs +++ b/crates/fuel-core/src/query/message.rs @@ -281,10 +281,13 @@ fn message_receipts_proof( } } -pub fn message_status( +pub fn message_status( database: &T, message_nonce: Nonce, -) -> StorageResult { +) -> StorageResult +where + T: OffChainDatabase + DatabaseMessages + ?Sized, +{ if database.message_is_spent(&message_nonce)? { Ok(MessageStatus::spent()) } else if database.message_exists(&message_nonce)? { diff --git a/crates/fuel-core/src/schema/block.rs b/crates/fuel-core/src/schema/block.rs index c7e90e93ebe..fe108497a05 100644 --- a/crates/fuel-core/src/schema/block.rs +++ b/crates/fuel-core/src/schema/block.rs @@ -78,6 +78,8 @@ pub struct Genesis { pub contracts_root: Bytes32, /// The Binary Merkle Tree root of all genesis messages. pub messages_root: Bytes32, + /// The Binary Merkle Tree root of all processed transaction ids. + pub transactions_root: Bytes32, } pub struct PoAConsensus { @@ -365,6 +367,7 @@ impl From for Genesis { coins_root: genesis.coins_root.into(), contracts_root: genesis.contracts_root.into(), messages_root: genesis.messages_root.into(), + transactions_root: genesis.transactions_root.into(), } } } 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 c1acfb0cb7b..af4a4e69864 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 @@ -156,6 +156,10 @@ impl OffChainDatabase for Database { .map(|cow| cow.into_owned()); Ok(status) } + + fn message_is_spent(&self, nonce: &Nonce) -> StorageResult { + self.message_is_spent(nonce) + } } impl Transactional for Database { 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 4f05ec58fbe..ee20dc8de8a 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 @@ -72,10 +72,6 @@ impl DatabaseMessages for Database { .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) } diff --git a/crates/fuel-core/src/service/adapters/txpool.rs b/crates/fuel-core/src/service/adapters/txpool.rs index ee241fafbfd..2caebd15619 100644 --- a/crates/fuel-core/src/service/adapters/txpool.rs +++ b/crates/fuel-core/src/service/adapters/txpool.rs @@ -13,7 +13,6 @@ use fuel_core_storage::{ Coins, ContractsRawCode, Messages, - SpentMessages, }, Result as StorageResult, StorageAsRef, @@ -135,10 +134,6 @@ impl fuel_core_txpool::ports::TxPoolDb for Database { .get(id) .map(|t| t.map(|t| t.as_ref().clone())) } - - fn is_message_spent(&self, id: &Nonce) -> StorageResult { - self.storage::().contains_key(id) - } } impl GasPriceProvider for StaticGasPrice { diff --git a/crates/fuel-core/src/service/genesis.rs b/crates/fuel-core/src/service/genesis.rs index c85315b2d53..0bd08cd910f 100644 --- a/crates/fuel-core/src/service/genesis.rs +++ b/crates/fuel-core/src/service/genesis.rs @@ -95,6 +95,7 @@ pub async fn execute_genesis_block( coins_root: db.on_chain().genesis_coins_root()?.into(), messages_root: db.on_chain().genesis_messages_root()?.into(), contracts_root: db.on_chain().genesis_contracts_root()?.into(), + transactions_root: db.on_chain().processed_transactions_root()?.into(), }; let consensus = Consensus::Genesis(genesis); diff --git a/crates/fuel-core/src/service/genesis/exporter.rs b/crates/fuel-core/src/service/genesis/exporter.rs index e004c220f71..badbb01a471 100644 --- a/crates/fuel-core/src/service/genesis/exporter.rs +++ b/crates/fuel-core/src/service/genesis/exporter.rs @@ -4,9 +4,12 @@ use crate::{ database_description::DatabaseDescription, Database, }, - fuel_core_graphql_api::storage::transactions::{ - OwnedTransactions, - TransactionStatuses, + fuel_core_graphql_api::storage::{ + messages::SpentMessages, + transactions::{ + OwnedTransactions, + TransactionStatuses, + }, }, graphql_api::storage::old::{ OldFuelBlockConsensus, @@ -36,6 +39,7 @@ use fuel_core_storage::{ ContractsState, FuelBlocks, Messages, + ProcessedTransactions, SealedBlockConsensus, Transactions, }, @@ -91,9 +95,10 @@ where ContractsLatestUtxo, ContractsState, ContractsAssets, - Transactions, FuelBlocks, - SealedBlockConsensus + Transactions, + SealedBlockConsensus, + ProcessedTransactions ); export!( @@ -102,7 +107,8 @@ where OwnedTransactions, OldFuelBlocks, OldFuelBlockConsensus, - OldTransactions + OldTransactions, + SpentMessages ); self.finalize().await?; diff --git a/crates/fuel-core/src/service/genesis/importer.rs b/crates/fuel-core/src/service/genesis/importer.rs index 728ccc73612..72867f16d80 100644 --- a/crates/fuel-core/src/service/genesis/importer.rs +++ b/crates/fuel-core/src/service/genesis/importer.rs @@ -9,23 +9,14 @@ use self::{ Target, }, }; - use super::task_manager::TaskManager; -mod import_task; -mod off_chain; -mod on_chain; -mod progress; -use std::{ - io::IsTerminal, - marker::PhantomData, -}; - use crate::{ combined_database::CombinedDatabase, database::database_description::{ off_chain::OffChain, on_chain::OnChain, }, + fuel_core_graphql_api::storage::messages::SpentMessages, graphql_api::storage::{ coins::OwnedCoins, contracts::ContractsInfo, @@ -41,6 +32,7 @@ use crate::{ }, }, }; +use core::marker::PhantomData; use fuel_core_chain_config::{ AsTable, SnapshotReader, @@ -59,6 +51,7 @@ use fuel_core_storage::{ ContractsState, FuelBlocks, Messages, + ProcessedTransactions, SealedBlockConsensus, Transactions, }, @@ -70,8 +63,16 @@ use fuel_core_types::{ }, fuel_types::BlockHeight, }; +use std::io::IsTerminal; use tracing::Level; +mod import_task; +mod off_chain; +mod on_chain; +mod progress; + +const GROUPS_NUMBER_FOR_PARALLELIZATION: usize = 10; + pub struct SnapshotImporter { db: CombinedDatabase, task_manager: TaskManager<()>, @@ -117,9 +118,11 @@ impl SnapshotImporter { self.spawn_worker_on_chain::()?; self.spawn_worker_on_chain::()?; self.spawn_worker_on_chain::()?; + self.spawn_worker_on_chain::()?; self.spawn_worker_off_chain::()?; self.spawn_worker_off_chain::()?; + self.spawn_worker_off_chain::()?; self.spawn_worker_off_chain::()?; self.spawn_worker_off_chain::()?; self.spawn_worker_off_chain::()?; @@ -147,6 +150,10 @@ impl SnapshotImporter { let groups = self.snapshot_reader.read::()?; let num_groups = groups.len(); + if num_groups == 0 { + return Ok(()); + } + let block_height = *self.genesis_block.header().height(); let da_block_height = self.genesis_block.header().da_height; let db = self.db.on_chain().clone(); @@ -163,7 +170,14 @@ impl SnapshotImporter { db, progress_reporter, ); - tokio_rayon::spawn(move || task.run()) + async move { + if num_groups >= GROUPS_NUMBER_FOR_PARALLELIZATION { + tokio_rayon::spawn(move || task.run()).await?; + } else { + task.run()?; + } + Ok(()) + } }); Ok(()) @@ -182,6 +196,11 @@ impl SnapshotImporter { { let groups = self.snapshot_reader.read::()?; let num_groups = groups.len(); + + if num_groups == 0 { + return Ok(()); + } + let block_height = *self.genesis_block.header().height(); let da_block_height = self.genesis_block.header().da_height; @@ -200,7 +219,14 @@ impl SnapshotImporter { db, progress_reporter, ); - tokio_rayon::spawn(move || task.run()) + async move { + if num_groups >= GROUPS_NUMBER_FOR_PARALLELIZATION { + tokio_rayon::spawn(move || task.run()).await?; + } else { + task.run()?; + } + Ok(()) + } }); Ok(()) diff --git a/crates/fuel-core/src/service/genesis/importer/off_chain.rs b/crates/fuel-core/src/service/genesis/importer/off_chain.rs index 9eef8479621..f73d7b95b23 100644 --- a/crates/fuel-core/src/service/genesis/importer/off_chain.rs +++ b/crates/fuel-core/src/service/genesis/importer/off_chain.rs @@ -5,6 +5,7 @@ use crate::{ database_description::off_chain::OffChain, Database, }, + fuel_core_graphql_api::storage::messages::SpentMessages, graphql_api::{ storage::{ blocks::FuelBlockIdsToHeights, @@ -272,3 +273,21 @@ impl ImportTable for Handler { Ok(()) } } + +impl ImportTable for Handler { + type TableInSnapshot = SpentMessages; + type TableBeingWritten = SpentMessages; + type DbDesc = OffChain; + + fn process( + &mut self, + group: Vec>, + tx: &mut StorageTransaction<&mut Database>, + ) -> anyhow::Result<()> { + for entry in group { + tx.storage_as_mut::() + .insert(&entry.key, &entry.value)?; + } + Ok(()) + } +} diff --git a/crates/fuel-core/src/service/genesis/importer/on_chain.rs b/crates/fuel-core/src/service/genesis/importer/on_chain.rs index d3e5605ca16..eac8ee71699 100644 --- a/crates/fuel-core/src/service/genesis/importer/on_chain.rs +++ b/crates/fuel-core/src/service/genesis/importer/on_chain.rs @@ -18,6 +18,7 @@ use fuel_core_storage::{ ContractsRawCode, ContractsState, Messages, + ProcessedTransactions, }, transactional::StorageTransaction, StorageAsMut, @@ -65,6 +66,25 @@ impl ImportTable for Handler { } } +impl ImportTable for Handler { + type TableInSnapshot = ProcessedTransactions; + type TableBeingWritten = ProcessedTransactions; + type DbDesc = OnChain; + + fn process( + &mut self, + group: Vec>, + tx: &mut StorageTransaction<&mut Database>, + ) -> anyhow::Result<()> { + group.into_iter().try_for_each(|transaction| { + tx.storage_as_mut::() + .insert(&transaction.key, &transaction.value) + .map(|_| ()) + })?; + Ok(()) + } +} + impl ImportTable for Handler { type TableInSnapshot = ContractsRawCode; type TableBeingWritten = ContractsRawCode; diff --git a/crates/services/consensus_module/poa/src/service.rs b/crates/services/consensus_module/poa/src/service.rs index 0266de90631..531d80903cf 100644 --- a/crates/services/consensus_module/poa/src/service.rs +++ b/crates/services/consensus_module/poa/src/service.rs @@ -459,26 +459,37 @@ where { async fn run(&mut self, watcher: &mut StateWatcher) -> anyhow::Result { let should_continue; + let mut state = self.sync_task_handle.shared.clone(); // make sure we're synced first - while *self.sync_task_handle.shared.borrow() == SyncState::NotSynced { + while *state.borrow_and_update() == SyncState::NotSynced { tokio::select! { biased; result = watcher.while_started() => { should_continue = result?.started(); return Ok(should_continue); } - _ = self.sync_task_handle.shared.changed() => { - if let SyncState::Synced(block_header) = &*self.sync_task_handle.shared.borrow() { - let (last_height, last_timestamp, last_block_created) = - Self::extract_block_info(block_header); - self.last_height = last_height; - self.last_timestamp = last_timestamp; - self.last_block_created = last_block_created; - } + _ = state.changed() => { + break; + } + _ = self.tx_status_update_stream.next() => { + // ignore txpool events while syncing + } + _ = self.timer.wait() => { + // ignore timer events while syncing } } } + if let SyncState::Synced(block_header) = &*state.borrow_and_update() { + let (last_height, last_timestamp, last_block_created) = + Self::extract_block_info(block_header); + if last_height > self.last_height { + self.last_height = last_height; + self.last_timestamp = last_timestamp; + self.last_block_created = last_block_created; + } + } + tokio::select! { biased; _ = watcher.while_started() => { diff --git a/crates/services/consensus_module/poa/src/sync.rs b/crates/services/consensus_module/poa/src/sync.rs index 8086493b610..2e26d529ea4 100644 --- a/crates/services/consensus_module/poa/src/sync.rs +++ b/crates/services/consensus_module/poa/src/sync.rs @@ -136,6 +136,7 @@ impl RunnableTask for SyncTask { let mut should_continue = true; tokio::select! { + biased; _ = watcher.while_started() => { should_continue = false; } @@ -174,9 +175,10 @@ impl RunnableTask for SyncTask { InnerSyncState::Synced { block_header, has_sufficient_peers } if new_block_height > block_header.height() => { if block_info.is_locally_produced() { self.inner_state = InnerSyncState::Synced { - block_header: block_info.block_header, + block_header: block_info.block_header.clone(), has_sufficient_peers: *has_sufficient_peers }; + self.update_sync_state(SyncState::Synced(Arc::new(block_info.block_header))); } else { // we considered to be synced but we're obviously not! if *has_sufficient_peers { diff --git a/crates/services/executor/src/executor.rs b/crates/services/executor/src/executor.rs index 03bab4010d6..2bb9bc4e275 100644 --- a/crates/services/executor/src/executor.rs +++ b/crates/services/executor/src/executor.rs @@ -18,7 +18,6 @@ use fuel_core_storage::{ FuelBlocks, Messages, ProcessedTransactions, - SpentMessages, }, transactional::{ Changes, @@ -1285,12 +1284,6 @@ where | Input::MessageCoinPredicate(MessageCoinPredicate { nonce, .. }) | Input::MessageDataSigned(MessageDataSigned { nonce, .. }) | Input::MessageDataPredicate(MessageDataPredicate { nonce, .. }) => { - // Eagerly return already spent if status is known. - if db.storage::().contains_key(nonce)? { - return Err( - TransactionValidityError::MessageAlreadySpent(*nonce).into() - ); - } if let Some(message) = db.storage::().get(nonce)? { if message.da_height() > block_da_height { return Err(TransactionValidityError::MessageSpendTooEarly( @@ -1374,19 +1367,12 @@ where | Input::MessageCoinPredicate(MessageCoinPredicate { nonce, .. }) | Input::MessageDataSigned(MessageDataSigned { nonce, .. }) | Input::MessageDataPredicate(MessageDataPredicate { nonce, .. }) => { - // `MessageDataSigned` and `MessageDataPredicate` are spent only if tx is not reverted - // mark message id as spent - let was_already_spent = - db.storage::().insert(nonce, &())?; - // ensure message wasn't already marked as spent - if was_already_spent.is_some() { - return Err(ExecutorError::MessageAlreadySpent(*nonce)) - } - // cleanup message contents + // ensure message wasn't already marked as spent, + // and cleanup message contents let message = db .storage::() .remove(nonce)? - .ok_or(ExecutorError::MessageAlreadySpent(*nonce))?; + .ok_or(ExecutorError::MessageDoesNotExist(*nonce))?; execution_data .events .push(ExecutorEvent::MessageConsumed(message)); diff --git a/crates/services/txpool/src/containers/dependency.rs b/crates/services/txpool/src/containers/dependency.rs index 84d53cec3fc..9801a16bcf7 100644 --- a/crates/services/txpool/src/containers/dependency.rs +++ b/crates/services/txpool/src/containers/dependency.rs @@ -372,13 +372,6 @@ impl Dependency { { return Err(Error::NotInsertedIoMessageMismatch) } - // return an error if spent block is set - if db - .is_message_spent(nonce) - .map_err(|e| Error::Database(format!("{:?}", e)))? - { - return Err(Error::NotInsertedInputMessageSpent(*nonce)) - } } else { return Err(Error::NotInsertedInputMessageUnknown(*nonce)) } diff --git a/crates/services/txpool/src/mock_db.rs b/crates/services/txpool/src/mock_db.rs index 962a59ed9c0..a7b8406b485 100644 --- a/crates/services/txpool/src/mock_db.rs +++ b/crates/services/txpool/src/mock_db.rs @@ -84,10 +84,6 @@ impl TxPoolDb for MockDb { fn message(&self, id: &Nonce) -> StorageResult> { Ok(self.data.lock().unwrap().messages.get(id).cloned()) } - - fn is_message_spent(&self, id: &Nonce) -> StorageResult { - Ok(self.data.lock().unwrap().spent_messages.contains(id)) - } } pub struct MockDBProvider(pub MockDb); diff --git a/crates/services/txpool/src/ports.rs b/crates/services/txpool/src/ports.rs index c24e4c9eb71..3a2a5f4ff24 100644 --- a/crates/services/txpool/src/ports.rs +++ b/crates/services/txpool/src/ports.rs @@ -58,8 +58,6 @@ pub trait TxPoolDb: Send + Sync { fn contract_exist(&self, contract_id: &ContractId) -> StorageResult; fn message(&self, message_id: &Nonce) -> StorageResult>; - - fn is_message_spent(&self, message_id: &Nonce) -> StorageResult; } /// Trait for getting gas price for the Tx Pool code to look up the gas price for a given block height diff --git a/crates/services/txpool/src/txpool/tests.rs b/crates/services/txpool/src/txpool/tests.rs index d3d23c64cb4..10121890d60 100644 --- a/crates/services/txpool/src/txpool/tests.rs +++ b/crates/services/txpool/src/txpool/tests.rs @@ -976,30 +976,6 @@ async fn tx_inserted_into_pool_when_input_message_id_exists_in_db() { assert_eq!(tx_info.tx().id(), tx1_id); } -#[tokio::test] -async fn tx_rejected_when_input_message_id_is_spent() { - let mut context = TextContext::default(); - let (message, input) = create_message_predicate_from_message(5_000, 0); - - let tx = TransactionBuilder::script(vec![], vec![]) - .script_gas_limit(GAS_LIMIT) - .add_input(input) - .finalize_as_transaction(); - - context.database_mut().insert_message(message.clone()); - context.database_mut().spend_message(*message.id()); - let mut txpool = context.build(); - - let tx = check_unwrap_tx(tx, &txpool.config).await; - let err = txpool.insert_single(tx).expect_err("should fail"); - - // check error - assert!(matches!( - err, - Error::NotInsertedInputMessageSpent(msg_id) if msg_id == *message.id() - )); -} - #[tokio::test] async fn tx_rejected_from_pool_when_input_message_id_does_not_exist_in_db() { let context = TextContext::default(); diff --git a/crates/storage/src/column.rs b/crates/storage/src/column.rs index ea9a1fd703c..dde05777863 100644 --- a/crates/storage/src/column.rs +++ b/crates/storage/src/column.rs @@ -39,34 +39,30 @@ pub enum Column { FuelBlockMerkleData = 8, /// See [`FuelBlockMerkleMetadata`](crate::tables::merkle::FuelBlockMerkleMetadata) FuelBlockMerkleMetadata = 9, - /// Messages that have been spent. - /// Existence of a key in this column means that the message has been spent. - /// See [`SpentMessages`](crate::tables::SpentMessages) - SpentMessages = 10, /// See [`ContractsAssetsMerkleData`](crate::tables::merkle::ContractsAssetsMerkleData) - ContractsAssetsMerkleData = 11, + ContractsAssetsMerkleData = 10, /// See [`ContractsAssetsMerkleMetadata`](crate::tables::merkle::ContractsAssetsMerkleMetadata) - ContractsAssetsMerkleMetadata = 12, + ContractsAssetsMerkleMetadata = 11, /// See [`ContractsStateMerkleData`](crate::tables::merkle::ContractsStateMerkleData) - ContractsStateMerkleData = 13, + ContractsStateMerkleData = 12, /// See [`ContractsStateMerkleMetadata`](crate::tables::merkle::ContractsStateMerkleMetadata) - ContractsStateMerkleMetadata = 14, + ContractsStateMerkleMetadata = 13, /// See [`Messages`](crate::tables::Messages) - Messages = 15, + Messages = 14, /// See [`ProcessedTransactions`](crate::tables::ProcessedTransactions) - ProcessedTransactions = 16, + ProcessedTransactions = 15, /// See [`SealedBlockConsensus`](crate::tables::SealedBlockConsensus) - FuelBlockConsensus = 17, + FuelBlockConsensus = 16, /// See [`ConsensusParametersVersions`](crate::tables::ConsensusParametersVersions) - ConsensusParametersVersions = 18, + ConsensusParametersVersions = 17, /// See [`StateTransitionBytecodeVersions`](crate::tables::StateTransitionBytecodeVersions) - StateTransitionBytecodeVersions = 19, + StateTransitionBytecodeVersions = 18, /// See [`UploadedBytecodes`](crate::tables::UploadedBytecodes) - UploadedBytecodes = 20, + UploadedBytecodes = 19, // TODO: Remove this column and use `Metadata` column instead. /// Table for genesis state import progress tracking. - GenesisMetadata = 21, + GenesisMetadata = 20, } impl Column { diff --git a/crates/storage/src/structured_storage/messages.rs b/crates/storage/src/structured_storage/messages.rs index 78d92c66a17..04f4b67647d 100644 --- a/crates/storage/src/structured_storage/messages.rs +++ b/crates/storage/src/structured_storage/messages.rs @@ -8,10 +8,7 @@ use crate::{ }, column::Column, structured_storage::TableWithBlueprint, - tables::{ - Messages, - SpentMessages, - }, + tables::Messages, }; impl TableWithBlueprint for Messages { @@ -23,15 +20,6 @@ impl TableWithBlueprint for Messages { } } -impl TableWithBlueprint for SpentMessages { - type Blueprint = Plain; - type Column = Column; - - fn column() -> Column { - Column::SpentMessages - } -} - #[cfg(test)] mod test { use super::*; @@ -41,10 +29,4 @@ mod test { ::Key::default(), ::Value::default() ); - - crate::basic_storage_tests!( - SpentMessages, - ::Key::default(), - ::Value::default() - ); } diff --git a/crates/storage/src/tables.rs b/crates/storage/src/tables.rs index a3b95a79266..9daef59ff6e 100644 --- a/crates/storage/src/tables.rs +++ b/crates/storage/src/tables.rs @@ -92,16 +92,6 @@ impl Mappable for Messages { type OwnedValue = Message; } -/// The storage table that indicates if the message is spent or not. -pub struct SpentMessages; - -impl Mappable for SpentMessages { - type Key = Self::OwnedKey; - type OwnedKey = Nonce; - type Value = Self::OwnedValue; - type OwnedValue = (); -} - /// The storage table of confirmed transactions. pub struct Transactions; diff --git a/crates/types/src/blockchain/consensus.rs b/crates/types/src/blockchain/consensus.rs index 408e0373476..2a3ddcdec72 100644 --- a/crates/types/src/blockchain/consensus.rs +++ b/crates/types/src/blockchain/consensus.rs @@ -83,4 +83,6 @@ pub struct Genesis { pub contracts_root: Bytes32, /// The Binary Merkle Tree root of all genesis messages. pub messages_root: Bytes32, + /// The Binary Merkle Tree root of all processed transaction ids. + pub transactions_root: Bytes32, } diff --git a/crates/types/src/services/executor.rs b/crates/types/src/services/executor.rs index 89dc45d3231..468ddaa7066 100644 --- a/crates/types/src/services/executor.rs +++ b/crates/types/src/services/executor.rs @@ -397,7 +397,7 @@ pub enum Error { #[display(fmt = "No matching utxo for contract id ${_0:#x}")] ContractUtxoMissing(ContractId), #[display(fmt = "message already spent {_0:#x}")] - MessageAlreadySpent(Nonce), + MessageDoesNotExist(Nonce), #[display(fmt = "Expected input of type {_0}")] InputTypeMismatch(String), #[display(fmt = "Executing of the genesis block is not allowed")] @@ -444,13 +444,11 @@ pub enum TransactionValidityError { CoinMismatch(UtxoId), #[error("The specified coin({0:#x}) doesn't exist")] CoinDoesNotExist(UtxoId), - #[error("The specified message({0:#x}) was already spent")] - MessageAlreadySpent(Nonce), #[error( "Message({0:#x}) is not yet spendable, as it's DA height is newer than this block allows" )] MessageSpendTooEarly(Nonce), - #[error("The specified message({0:#x}) doesn't exist")] + #[error("The specified message({0:#x}) doesn't exist, possibly because it was already spent")] MessageDoesNotExist(Nonce), #[error("The input message({0:#x}) doesn't match the relayer message")] MessageMismatch(Nonce), diff --git a/crates/types/src/services/txpool.rs b/crates/types/src/services/txpool.rs index 86c72d7767c..fb918ecf8b4 100644 --- a/crates/types/src/services/txpool.rs +++ b/crates/types/src/services/txpool.rs @@ -317,8 +317,6 @@ pub enum Error { NotInsertedInputUtxoIdNotDoesNotExist(UtxoId), #[error("Transaction is not inserted. UTXO is spent: {0:#x}")] NotInsertedInputUtxoIdSpent(UtxoId), - #[error("Transaction is not inserted. Message is spent: {0:#x}")] - NotInsertedInputMessageSpent(Nonce), #[error("Transaction is not inserted. Message id {0:#x} does not match any received message from the DA layer.")] NotInsertedInputMessageUnknown(Nonce), #[error( diff --git a/tests/tests/poa.rs b/tests/tests/poa.rs index bd628b0b563..bc6d93bd55f 100644 --- a/tests/tests/poa.rs +++ b/tests/tests/poa.rs @@ -94,7 +94,10 @@ mod p2p { }, service::ServiceTrait, }; - use fuel_core_poa::Trigger; + use fuel_core_poa::{ + service::Mode, + Trigger, + }; use fuel_core_types::{ fuel_tx::Input, fuel_types::Address, @@ -107,8 +110,8 @@ mod p2p { // after the first_producer stops, second_producer should start producing blocks #[tokio::test(flavor = "multi_thread")] async fn test_poa_multiple_producers() { - const INTERVAL: u64 = 2; - const TIMEOUT: u64 = 5 * INTERVAL; + const SYNC_TIMEOUT: u64 = 30; + const TIME_UNTIL_SYNCED: u64 = SYNC_TIMEOUT + 10; let mut rng = StdRng::seed_from_u64(2222); @@ -124,14 +127,14 @@ mod p2p { let make_node_config = |name: &str| { let mut config = make_config(name.to_string(), config.clone()); - config.block_production = Trigger::Interval { - block_time: Duration::from_secs(INTERVAL), - }; + config.debug = true; + config.block_production = Trigger::Never; config.consensus_key = Some(Secret::new(secret.into())); config.p2p.as_mut().unwrap().bootstrap_nodes = bootstrap.listeners(); config.p2p.as_mut().unwrap().reserved_nodes = bootstrap.listeners(); + config.p2p.as_mut().unwrap().info_interval = Some(Duration::from_millis(100)); config.min_connected_reserved_peers = 1; - config.time_until_synced = Duration::from_secs(2 * INTERVAL); + config.time_until_synced = Duration::from_secs(TIME_UNTIL_SYNCED); config }; @@ -140,46 +143,61 @@ mod p2p { let first_producer = make_node(first_producer_config, vec![]).await; - // The first producer should produce 2 blocks. - tokio::time::timeout( - Duration::from_secs(TIMEOUT), - first_producer.wait_for_blocks(2, true /* is_local */), - ) - .await - .expect("The first should produce 2 blocks"); - - // Start the second producer after 2 blocks. + // The first producer should produce 3 blocks. + first_producer + .node + .shared + .poa_adapter + .manually_produce_blocks( + None, + Mode::Blocks { + number_of_blocks: 3, + }, + ) + .await + .expect("The first should produce 3 blocks"); + + // Start the second producer after 3 blocks. // The second producer should synchronize 3 blocks produced by the first producer. let second_producer = make_node(second_producer_config, vec![]).await; tokio::time::timeout( - Duration::from_secs(TIMEOUT), + Duration::from_secs(SYNC_TIMEOUT), second_producer.wait_for_blocks(3, false /* is_local */), ) .await .expect("The second should sync with the first"); + let start_time = tokio::time::Instant::now(); // Stop the first producer. - // The second should start produce new blocks after 2 * `INTERVAL` tokio::time::timeout( - Duration::from_secs(TIMEOUT), + Duration::from_secs(1), first_producer.node.stop_and_await(), ) .await .expect("Should stop services before timeout") .expect("Should stop without any error"); - tokio::time::timeout( - Duration::from_secs(TIMEOUT), - second_producer.wait_for_blocks(1, true /* is_local */), - ) - .await - .expect("The second should produce one block after the first stopped"); + + // The second should start produce new blocks after `TIMEOUT` + second_producer + .node + .shared + .poa_adapter + .manually_produce_blocks( + None, + Mode::Blocks { + number_of_blocks: 2, + }, + ) + .await + .expect("The second should produce 2 blocks"); + assert!(start_time.elapsed() >= Duration::from_secs(TIME_UNTIL_SYNCED)); // Restart fresh first producer. // it should sync remotely 5 blocks. let first_producer = make_node(make_node_config("First Producer reborn"), vec![]).await; tokio::time::timeout( - Duration::from_secs(TIMEOUT), + Duration::from_secs(SYNC_TIMEOUT), first_producer.wait_for_blocks(5, false /* is_local */), ) .await diff --git a/tests/tests/regenesis.rs b/tests/tests/regenesis.rs index 26b259dd577..31ba99bb586 100644 --- a/tests/tests/regenesis.rs +++ b/tests/tests/regenesis.rs @@ -1,7 +1,13 @@ use clap::Parser; -use fuel_core::service::{ - FuelService, - ServiceTrait, +use fuel_core::{ + chain_config::{ + ChainConfig, + SnapshotWriter, + }, + service::{ + FuelService, + ServiceTrait, + }, }; use fuel_core_bin::cli::snapshot; use fuel_core_client::client::{ @@ -9,6 +15,10 @@ use fuel_core_client::client::{ PageDirection, PaginationRequest, }, + types::{ + message::MessageStatus, + TransactionStatus, + }, FuelClient, }; use fuel_core_types::{ @@ -36,7 +46,13 @@ impl FuelCoreDriver { // Generate temp params let db_dir = tempdir()?; - let mut args = vec!["_IGNORED_", "--db-path", db_dir.path().to_str().unwrap()]; + let mut args = vec![ + "_IGNORED_", + "--db-path", + db_dir.path().to_str().unwrap(), + "--port", + "0", + ]; args.extend(extra_args); let node = fuel_core_bin::cli::run::get_service( @@ -200,3 +216,157 @@ async fn test_regenesis_old_blocks_are_preserved() -> anyhow::Result<()> { Ok(()) } + +#[tokio::test(flavor = "multi_thread")] +async fn test_regenesis_spent_messages_are_preserved() -> anyhow::Result<()> { + let mut rng = StdRng::seed_from_u64(1234); + let state_config_dir = tempdir().expect("Failed to create temp dir"); + let nonce = [123; 32].into(); + let state_config = fuel_core::chain_config::StateConfig { + messages: vec![fuel_core::chain_config::MessageConfig { + sender: rng.gen(), + recipient: rng.gen(), + nonce, + amount: 123, + data: vec![], + da_height: Default::default(), + }], + ..Default::default() + }; + let writer = SnapshotWriter::json(state_config_dir.path()); + writer + .write_state_config(state_config, &ChainConfig::local_testnet()) + .unwrap(); + + let core = FuelCoreDriver::spawn(&[ + "--debug", + "--poa-instant", + "true", + "--snapshot", + state_config_dir.path().to_str().unwrap(), + ]) + .await?; + + // Add some blocks + let secret = SecretKey::random(&mut rng); + let tx_with_message = TransactionBuilder::script(vec![], vec![]) + .add_unsigned_message_input( + secret, + rng.gen(), + nonce, + Default::default(), + Default::default(), + ) + .add_output(Output::change( + Default::default(), + Default::default(), + Default::default(), + )) + .finalize_as_transaction(); + core.client + .submit_and_await_commit(&tx_with_message) + .await + .unwrap(); + + let status = core + .client + .message_status(&nonce) + .await + .expect("Failed to get message status"); + assert_eq!(status, MessageStatus::Spent); + + // Stop the node, keep the db + let db_dir = core.kill().await; + + // ------------------------- The genesis node is stopped ------------------------- + + // Take a snapshot + let snapshot_dir = tempdir().expect("Failed to create temp dir"); + take_snapshot(&db_dir, &snapshot_dir) + .await + .expect("Failed to take first snapshot"); + + // ------------------------- Start a node with the regenesis ------------------------- + + // Start a new node with the snapshot + let core = FuelCoreDriver::spawn(&[ + "--debug", + "--poa-instant", + "true", + "--snapshot", + snapshot_dir.path().to_str().unwrap(), + ]) + .await?; + + let status = core + .client + .message_status(&nonce) + .await + .expect("Failed to get message status"); + assert_eq!(status, MessageStatus::Spent); + + Ok(()) +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_regenesis_processed_transactions_are_preserved() -> anyhow::Result<()> { + let mut rng = StdRng::seed_from_u64(1234); + let core = FuelCoreDriver::spawn(&["--debug", "--poa-instant", "true"]).await?; + + // Add some blocks + let secret = SecretKey::random(&mut rng); + let tx = TransactionBuilder::script(vec![], vec![]) + .add_unsigned_coin_input( + secret, + rng.gen(), + 1234, + Default::default(), + Default::default(), + ) + .add_output(Output::change( + Default::default(), + Default::default(), + Default::default(), + )) + .finalize_as_transaction(); + core.client.submit_and_await_commit(&tx).await.unwrap(); + + let TransactionStatus::SqueezedOut { reason } = + core.client.submit_and_await_commit(&tx).await.unwrap() + else { + panic!("Expected transaction to be squeezed out") + }; + assert!(reason.contains("Transaction id was already used")); + + // Stop the node, keep the db + let db_dir = core.kill().await; + + // ------------------------- The genesis node is stopped ------------------------- + + // Take a snapshot + let snapshot_dir = tempdir().expect("Failed to create temp dir"); + take_snapshot(&db_dir, &snapshot_dir) + .await + .expect("Failed to take first snapshot"); + + // ------------------------- Start a node with the regenesis ------------------------- + + // Start a new node with the snapshot + let core = FuelCoreDriver::spawn(&[ + "--debug", + "--poa-instant", + "true", + "--snapshot", + snapshot_dir.path().to_str().unwrap(), + ]) + .await?; + + let TransactionStatus::SqueezedOut { reason } = + core.client.submit_and_await_commit(&tx).await.unwrap() + else { + panic!("Expected transaction to be squeezed out") + }; + assert!(reason.contains("Transaction id was already used")); + + Ok(()) +}