From 66291e278870001dee6cd4c043bbfc3d6caac38d Mon Sep 17 00:00:00 2001 From: AurelienFT Date: Tue, 11 Mar 2025 13:55:30 +0100 Subject: [PATCH 01/76] Add preconfirmations integrations tests --- crates/fuel-core/src/service/config.rs | 2 +- tests/tests/lib.rs | 4 + tests/tests/preconfirmations.rs | 387 +++++++++++++++++++++ tests/tests/preconfirmations_gossip.rs | 458 +++++++++++++++++++++++++ 4 files changed, 850 insertions(+), 1 deletion(-) create mode 100644 tests/tests/preconfirmations.rs create mode 100644 tests/tests/preconfirmations_gossip.rs diff --git a/crates/fuel-core/src/service/config.rs b/crates/fuel-core/src/service/config.rs index c78b864a177..1cc43d6160c 100644 --- a/crates/fuel-core/src/service/config.rs +++ b/crates/fuel-core/src/service/config.rs @@ -226,7 +226,7 @@ impl Config { self.utxo_validation = true; } - if self.txpool.utxo_validation != self.utxo_validation { + if !self.debug && self.txpool.utxo_validation != self.utxo_validation { tracing::warn!("The `utxo_validation` of `TxPool` was inconsistent"); self.txpool.utxo_validation = self.utxo_validation; } diff --git a/tests/tests/lib.rs b/tests/tests/lib.rs index 7b2fbb72115..8b30407c329 100644 --- a/tests/tests/lib.rs +++ b/tests/tests/lib.rs @@ -47,6 +47,8 @@ mod node_info; #[cfg(not(feature = "only-p2p"))] mod poa; #[cfg(not(feature = "only-p2p"))] +mod preconfirmations; +#[cfg(not(feature = "only-p2p"))] mod recovery; #[cfg(not(feature = "only-p2p"))] mod regenesis; @@ -69,6 +71,8 @@ mod tx; #[cfg(not(feature = "only-p2p"))] mod vm_storage; +#[cfg(feature = "only-p2p")] +mod preconfirmations_gossip; #[cfg(feature = "only-p2p")] mod sync; #[cfg(feature = "only-p2p")] diff --git a/tests/tests/preconfirmations.rs b/tests/tests/preconfirmations.rs new file mode 100644 index 00000000000..9634110853e --- /dev/null +++ b/tests/tests/preconfirmations.rs @@ -0,0 +1,387 @@ +use std::time::Duration; + +use fuel_core::service::Config; +use fuel_core_bin::FuelService; +use fuel_core_client::client::{ + types::TransactionStatus, + FuelClient, +}; +use fuel_core_poa::Trigger; +use fuel_core_types::{ + fuel_asm::{ + op, + RegId, + }, + fuel_tx::{ + Address, + AssetId, + Output, + Receipt, + TransactionBuilder, + TxPointer, + }, + fuel_types::BlockHeight, + fuel_vm::SecretKey, +}; +use futures::StreamExt; +use rand::Rng; + +#[tokio::test] +async fn preconfirmation__received_after_execution() { + let mut config = Config::local_node(); + let block_production_period = Duration::from_secs(1); + let address = Address::new([0; 32]); + + config.block_production = Trigger::Open { + period: block_production_period, + }; + let srv = FuelService::new_node(config).await.unwrap(); + let client = FuelClient::from(srv.bound_address); + + let gas_limit = 1_000_000; + let maturity = Default::default(); + + // Given + let script = [ + op::addi(0x10, RegId::ZERO, 0xca), + op::addi(0x11, RegId::ZERO, 0xba), + op::log(0x10, 0x11, RegId::ZERO, RegId::ZERO), + op::ret(RegId::ONE), + ]; + let script: Vec = script + .iter() + .flat_map(|op| u32::from(*op).to_be_bytes()) + .collect(); + + let tx = TransactionBuilder::script(script, vec![]) + .script_gas_limit(gas_limit) + .maturity(maturity) + .add_fee_input() + .add_output(Output::variable(address, 0, AssetId::default())) + .finalize_as_transaction(); + + let tx_id = client.submit(&tx).await.unwrap(); + let mut tx_statuses_subscriber = + client.subscribe_transaction_status(&tx_id).await.unwrap(); + + // When + assert!(matches!( + tx_statuses_subscriber.next().await.unwrap().unwrap(), + TransactionStatus::Submitted { .. } + )); + if let TransactionStatus::PreconfirmationSuccess { + tx_pointer, + total_fee, + total_gas, + transaction_id, + receipts, + resolved_outputs, + } = tx_statuses_subscriber.next().await.unwrap().unwrap() + { + // Then + assert_eq!(tx_pointer, TxPointer::new(BlockHeight::new(1), 0)); + assert_eq!(total_fee, 0); + assert_eq!(total_gas, 4450); + assert_eq!(transaction_id, tx_id); + let receipts = receipts.unwrap(); + assert_eq!(receipts.len(), 2); + assert!(matches!(receipts[0], + Receipt::Log { + ra, rb, .. + } if ra == 0xca && rb == 0xba)); + + assert!(matches!(receipts[1], + Receipt::Return { + val, .. + } if val == 1)); + let outputs = resolved_outputs.unwrap(); + assert_eq!(outputs.len(), 1); + assert_eq!( + outputs[0], + Output::Coin { + to: address, + amount: 2, + asset_id: AssetId::default() + } + ); + } else { + panic!("Expected preconfirmation status"); + } + assert!(matches!( + tx_statuses_subscriber.next().await.unwrap().unwrap(), + TransactionStatus::Success { .. } + )); +} + +#[tokio::test] +async fn preconfirmation__received_after_failed_execution() { + let mut config = Config::local_node(); + let block_production_period = Duration::from_secs(1); + let address = Address::new([0; 32]); + + config.block_production = Trigger::Open { + period: block_production_period, + }; + let srv = FuelService::new_node(config).await.unwrap(); + let client = FuelClient::from(srv.bound_address); + + let gas_limit = 1_000_000; + let maturity = Default::default(); + + // Given + let script = [ + op::addi(0x10, RegId::ZERO, 0xca), + op::addi(0x11, RegId::ZERO, 0xba), + op::log(0x10, 0x11, RegId::ZERO, RegId::ZERO), + op::rvrt(RegId::ONE), + op::ret(RegId::ONE), + ]; + let script: Vec = script + .iter() + .flat_map(|op| u32::from(*op).to_be_bytes()) + .collect(); + + let tx = TransactionBuilder::script(script, vec![]) + .script_gas_limit(gas_limit) + .maturity(maturity) + .add_fee_input() + .add_output(Output::variable(address, 0, AssetId::default())) + .finalize_as_transaction(); + + let tx_id = client.submit(&tx).await.unwrap(); + let mut tx_statuses_subscriber = + client.subscribe_transaction_status(&tx_id).await.unwrap(); + + // When + assert!(matches!( + tx_statuses_subscriber.next().await.unwrap().unwrap(), + TransactionStatus::Submitted { .. } + )); + + if let TransactionStatus::PreconfirmationFailure { + tx_pointer, + total_fee, + total_gas, + transaction_id, + receipts, + resolved_outputs, + reason: _, + } = tx_statuses_subscriber.next().await.unwrap().unwrap() + { + // Then + assert_eq!(tx_pointer, TxPointer::new(BlockHeight::new(1), 0)); + assert_eq!(total_fee, 0); + assert_eq!(total_gas, 4450); + assert_eq!(transaction_id, tx_id); + let receipts = receipts.unwrap(); + assert_eq!(receipts.len(), 2); + assert!(matches!(receipts[0], + Receipt::Log { + ra, rb, .. + } if ra == 0xca && rb == 0xba)); + + assert!(matches!(receipts[1], + Receipt::Revert { + ra, .. + } if ra == 1)); + let outputs = resolved_outputs.unwrap(); + assert_eq!(outputs.len(), 1); + assert_eq!( + outputs[0], + Output::Coin { + to: address, + amount: 2, + asset_id: AssetId::default() + } + ); + } else { + panic!("Expected preconfirmation status"); + } + + assert!(matches!( + tx_statuses_subscriber.next().await.unwrap().unwrap(), + TransactionStatus::Failure { .. } + )); +} + +#[tokio::test] +async fn preconfirmation__received_after_squeezed_out() { + let mut config = Config::local_node(); + let mut rng = rand::thread_rng(); + let block_production_period = Duration::from_secs(1); + + config.block_production = Trigger::Open { + period: block_production_period, + }; + // Given + // Disable UTXO validation in TxPool so that the transaction is squeezed out by + // block production + config.debug = true; + config.txpool.utxo_validation = false; + config.utxo_validation = true; + let srv = FuelService::new_node(config).await.unwrap(); + let client = FuelClient::from(srv.bound_address); + + let gas_limit = 1_000_000; + let tx = TransactionBuilder::script( + vec![op::ret(RegId::ONE)].into_iter().collect(), + vec![], + ) + .script_gas_limit(gas_limit) + .add_unsigned_coin_input( + SecretKey::random(&mut rng), + rng.gen(), + 10, + Default::default(), + Default::default(), + ) + .add_output(Output::Change { + to: Default::default(), + amount: 0, + asset_id: Default::default(), + }) + .finalize_as_transaction(); + + let tx_id = client.submit(&tx).await.unwrap(); + let mut tx_statuses_subscriber = + client.subscribe_transaction_status(&tx_id).await.unwrap(); + + // When + assert!(matches!( + tx_statuses_subscriber.next().await.unwrap().unwrap(), + TransactionStatus::Submitted { .. } + )); + if let TransactionStatus::PreconfirmationSqueezedOut { + transaction_id, + reason: _, + } = tx_statuses_subscriber.next().await.unwrap().unwrap() + { + // Then + assert_eq!(transaction_id, tx_id); + } else { + panic!("Expected preconfirmation status"); + } + + assert!(matches!( + tx_statuses_subscriber.next().await.unwrap().unwrap(), + TransactionStatus::SqueezedOut { .. } + )); +} + +#[tokio::test] +async fn preconfirmation__received_tx_inserted_end_block_open_period() { + let mut config = Config::local_node(); + let block_production_period = Duration::from_secs(1); + let address = Address::new([0; 32]); + + config.block_production = Trigger::Open { + period: block_production_period, + }; + let srv = FuelService::new_node(config).await.unwrap(); + let client = FuelClient::from(srv.bound_address); + + // Given + let tx = TransactionBuilder::script( + vec![op::ret(RegId::ONE)].into_iter().collect(), + vec![], + ) + .script_gas_limit(1_000_000) + .add_fee_input() + .add_output(Output::variable(address, 0, AssetId::default())) + .finalize_as_transaction(); + + // Given + tokio::time::sleep(block_production_period.checked_div(2).unwrap()).await; + + let tx_id = client.submit(&tx).await.unwrap(); + let mut tx_statuses_subscriber = + client.subscribe_transaction_status(&tx_id).await.unwrap(); + + // When + assert!(matches!( + tx_statuses_subscriber.next().await.unwrap().unwrap(), + TransactionStatus::Submitted { .. } + )); + assert!(matches!( + tx_statuses_subscriber.next().await.unwrap().unwrap(), + TransactionStatus::PreconfirmationSuccess { .. } + )); + // Then + assert!(matches!( + tx_statuses_subscriber.next().await.unwrap().unwrap(), + TransactionStatus::Success { block_height, .. } if block_height == BlockHeight::new(1) + )); +} + +#[tokio::test] +async fn preconfirmation__received_after_execution__multiple_txs() { + let mut config = Config::local_node(); + let block_production_period = Duration::from_secs(1); + + config.block_production = Trigger::Open { + period: block_production_period, + }; + let srv = FuelService::new_node(config).await.unwrap(); + let client = FuelClient::from(srv.bound_address); + + // Given + let tx1 = TransactionBuilder::script( + vec![op::ret(RegId::ONE)].into_iter().collect(), + vec![], + ) + .script_gas_limit(1_000_000) + .add_fee_input() + .add_output(Output::variable( + Address::new([0; 32]), + 0, + AssetId::default(), + )) + .finalize_as_transaction(); + let tx2 = TransactionBuilder::script( + vec![op::ret(RegId::ONE)].into_iter().collect(), + vec![], + ) + .script_gas_limit(1_000_000) + .add_fee_input() + .add_output(Output::variable( + Address::new([1; 32]), + 0, + AssetId::default(), + )) + .finalize_as_transaction(); + + // Given + let tx_id1 = client.submit(&tx1).await.unwrap(); + let mut tx_statuses_subscriber1 = + client.subscribe_transaction_status(&tx_id1).await.unwrap(); + let tx_id2 = client.submit(&tx2).await.unwrap(); + let mut tx_statuses_subscriber2 = + client.subscribe_transaction_status(&tx_id2).await.unwrap(); + + // When + assert!(matches!( + tx_statuses_subscriber1.next().await.unwrap().unwrap(), + TransactionStatus::Submitted { .. } + )); + assert!(matches!( + tx_statuses_subscriber2.next().await.unwrap().unwrap(), + TransactionStatus::Submitted { .. } + )); + assert!(matches!( + tx_statuses_subscriber1.next().await.unwrap().unwrap(), + TransactionStatus::PreconfirmationSuccess { .. } + )); + assert!(matches!( + tx_statuses_subscriber2.next().await.unwrap().unwrap(), + TransactionStatus::PreconfirmationSuccess { .. } + )); + // Then + assert!(matches!( + tx_statuses_subscriber1.next().await.unwrap().unwrap(), + TransactionStatus::Success { block_height, .. } if block_height == BlockHeight::new(1) + )); + assert!(matches!( + tx_statuses_subscriber2.next().await.unwrap().unwrap(), + TransactionStatus::Success { block_height, .. } if block_height == BlockHeight::new(1) + )); +} diff --git a/tests/tests/preconfirmations_gossip.rs b/tests/tests/preconfirmations_gossip.rs new file mode 100644 index 00000000000..15837ed39ea --- /dev/null +++ b/tests/tests/preconfirmations_gossip.rs @@ -0,0 +1,458 @@ +use std::hash::{ + DefaultHasher, + Hash, + Hasher, +}; + +use fuel_core::p2p_test_helpers::{ + make_nodes, + BootstrapSetup, + Nodes, + ProducerSetup, + ValidatorSetup, +}; +use fuel_core_client::client::{ + types::TransactionStatus, + FuelClient, +}; +use fuel_core_types::{ + fuel_asm::{ + op, + RegId, + }, + fuel_tx::{ + Address, + AssetId, + Input, + Output, + Receipt, + TransactionBuilder, + TxPointer, + UniqueIdentifier, + }, + fuel_types::BlockHeight, + fuel_vm::SecretKey, +}; +use futures::StreamExt; +use rand::{ + rngs::StdRng, + SeedableRng, +}; + +#[tokio::test(flavor = "multi_thread")] +async fn preconfirmation__propagate_p2p_after_execution() { + let address = Address::new([0; 32]); + let gas_limit = 1_000_000; + let maturity = Default::default(); + + // Given + let script = [ + op::addi(0x10, RegId::ZERO, 0xca), + op::addi(0x11, RegId::ZERO, 0xba), + op::log(0x10, 0x11, RegId::ZERO, RegId::ZERO), + op::ret(RegId::ONE), + ]; + let script: Vec = script + .iter() + .flat_map(|op| u32::from(*op).to_be_bytes()) + .collect(); + + // Given + let tx = TransactionBuilder::script(script, vec![]) + .script_gas_limit(gas_limit) + .maturity(maturity) + .add_fee_input() + .add_output(Output::variable(address, 0, AssetId::default())) + .finalize_as_transaction(); + let tx_id = tx.id(&Default::default()); + // Create a random seed based on the test parameters. + let mut hasher = DefaultHasher::new(); + let num_txs = 1; + let num_validators = 1; + let num_partitions = 1; + (num_txs, num_validators, num_partitions, line!()).hash(&mut hasher); + let mut rng = StdRng::seed_from_u64(hasher.finish()); + + // Create a set of key pairs. + let secrets: Vec<_> = (0..1).map(|_| SecretKey::random(&mut rng)).collect(); + let pub_keys: Vec<_> = secrets + .clone() + .into_iter() + .map(|secret| Input::owner(&secret.public_key())) + .collect(); + + // Create a producer for each key pair and a set of validators that share + // the same key pair. + let Nodes { + producers: _dont_drop1, + validators, + bootstrap_nodes: _dont_drop2, + } = make_nodes( + pub_keys + .iter() + .map(|pub_key| Some(BootstrapSetup::new(*pub_key))), + secrets.clone().into_iter().enumerate().map(|(i, secret)| { + Some( + ProducerSetup::new(secret) + .with_name(format!("{}:producer", pub_keys[i])) + .utxo_validation(false), + ) + }), + pub_keys.iter().flat_map(|pub_key| { + (0..num_validators).map(move |i| { + Some( + ValidatorSetup::new(*pub_key) + .with_name(format!("{pub_key}:{i}")) + .utxo_validation(false), + ) + }) + }), + None, + ) + .await; + + let sentry = &validators[0]; + let authority = &validators[0]; + + // When + let client_sentry = FuelClient::from(sentry.node.bound_address); + sentry + .node + .submit(tx) + .await + .expect("Should accept invalid transaction because `utxo_validation = false`."); + let mut tx_statuses_subscriber = client_sentry + .subscribe_transaction_status(&tx_id) + .await + .expect("Should be able to subscribe for events"); + authority + .node + .shared + .poa_adapter + .manually_produce_blocks( + None, + fuel_core_poa::service::Mode::Blocks { + number_of_blocks: 1, + }, + ) + .await + .unwrap(); + + // Then + assert!(matches!( + tx_statuses_subscriber.next().await.unwrap().unwrap(), + TransactionStatus::Submitted { .. } + )); + if let TransactionStatus::PreconfirmationSuccess { + tx_pointer, + total_fee, + total_gas, + transaction_id, + receipts, + resolved_outputs, + } = tx_statuses_subscriber.next().await.unwrap().unwrap() + { + // Then + assert_eq!(tx_pointer, TxPointer::new(BlockHeight::new(1), 0)); + assert_eq!(total_fee, 0); + assert_eq!(total_gas, 4450); + assert_eq!(transaction_id, tx_id); + let receipts = receipts.unwrap(); + assert_eq!(receipts.len(), 2); + assert!(matches!(receipts[0], + Receipt::Log { + ra, rb, .. + } if ra == 0xca && rb == 0xba)); + + assert!(matches!(receipts[1], + Receipt::Return { + val, .. + } if val == 1)); + let outputs = resolved_outputs.unwrap(); + assert_eq!(outputs.len(), 1); + assert_eq!( + outputs[0], + Output::Coin { + to: address, + amount: 2, + asset_id: AssetId::default() + } + ); + } else { + panic!("Expected preconfirmation status"); + } + assert!(matches!( + tx_statuses_subscriber.next().await.unwrap().unwrap(), + TransactionStatus::Success { .. } + )); +} + +#[tokio::test] +async fn preconfirmation__propagate_p2p_after_failed_execution() { + let address = Address::new([0; 32]); + let gas_limit = 1_000_000; + let maturity = Default::default(); + + // Given + let script = [ + op::addi(0x10, RegId::ZERO, 0xca), + op::addi(0x11, RegId::ZERO, 0xba), + op::log(0x10, 0x11, RegId::ZERO, RegId::ZERO), + op::rvrt(RegId::ONE), + op::ret(RegId::ONE), + ]; + let script: Vec = script + .iter() + .flat_map(|op| u32::from(*op).to_be_bytes()) + .collect(); + + // Given + let tx = TransactionBuilder::script(script, vec![]) + .script_gas_limit(gas_limit) + .maturity(maturity) + .add_fee_input() + .add_output(Output::variable(address, 0, AssetId::default())) + .finalize_as_transaction(); + let tx_id = tx.id(&Default::default()); + // Create a random seed based on the test parameters. + let mut hasher = DefaultHasher::new(); + let num_txs = 1; + let num_validators = 1; + let num_partitions = 1; + (num_txs, num_validators, num_partitions, line!()).hash(&mut hasher); + let mut rng = StdRng::seed_from_u64(hasher.finish()); + + // Create a set of key pairs. + let secrets: Vec<_> = (0..1).map(|_| SecretKey::random(&mut rng)).collect(); + let pub_keys: Vec<_> = secrets + .clone() + .into_iter() + .map(|secret| Input::owner(&secret.public_key())) + .collect(); + + // Create a producer for each key pair and a set of validators that share + // the same key pair. + let Nodes { + producers: _dont_drop1, + validators, + bootstrap_nodes: _dont_drop2, + } = make_nodes( + pub_keys + .iter() + .map(|pub_key| Some(BootstrapSetup::new(*pub_key))), + secrets.clone().into_iter().enumerate().map(|(i, secret)| { + Some( + ProducerSetup::new(secret) + .with_name(format!("{}:producer", pub_keys[i])) + .utxo_validation(false), + ) + }), + pub_keys.iter().flat_map(|pub_key| { + (0..num_validators).map(move |i| { + Some( + ValidatorSetup::new(*pub_key) + .with_name(format!("{pub_key}:{i}")) + .utxo_validation(false), + ) + }) + }), + None, + ) + .await; + + let sentry = &validators[0]; + let authority = &validators[0]; + + // When + let client_sentry = FuelClient::from(sentry.node.bound_address); + sentry + .node + .submit(tx) + .await + .expect("Should accept invalid transaction because `utxo_validation = false`."); + let mut tx_statuses_subscriber = client_sentry + .subscribe_transaction_status(&tx_id) + .await + .expect("Should be able to subscribe for events"); + authority + .node + .shared + .poa_adapter + .manually_produce_blocks( + None, + fuel_core_poa::service::Mode::Blocks { + number_of_blocks: 1, + }, + ) + .await + .unwrap(); + + // Then + assert!(matches!( + tx_statuses_subscriber.next().await.unwrap().unwrap(), + TransactionStatus::Submitted { .. } + )); + if let TransactionStatus::PreconfirmationFailure { + tx_pointer, + total_fee, + total_gas, + transaction_id, + receipts, + resolved_outputs, + reason: _, + } = tx_statuses_subscriber.next().await.unwrap().unwrap() + { + // Then + assert_eq!(tx_pointer, TxPointer::new(BlockHeight::new(1), 0)); + assert_eq!(total_fee, 0); + assert_eq!(total_gas, 4450); + assert_eq!(transaction_id, tx_id); + let receipts = receipts.unwrap(); + assert_eq!(receipts.len(), 2); + assert!(matches!(receipts[0], + Receipt::Log { + ra, rb, .. + } if ra == 0xca && rb == 0xba)); + + assert!(matches!(receipts[1], + Receipt::Revert { + ra, .. + } if ra == 1)); + let outputs = resolved_outputs.unwrap(); + assert_eq!(outputs.len(), 1); + assert_eq!( + outputs[0], + Output::Coin { + to: address, + amount: 2, + asset_id: AssetId::default() + } + ); + } else { + panic!("Expected preconfirmation status"); + } + + assert!(matches!( + tx_statuses_subscriber.next().await.unwrap().unwrap(), + TransactionStatus::Failure { .. } + )); +} + +#[tokio::test] +async fn preconfirmation__propagate_p2p_after_squeezed_out() { + let address = Address::new([0; 32]); + let gas_limit = 1_000_000; + let maturity = Default::default(); + + // Given + let script = [ + op::addi(0x10, RegId::ZERO, 0xca), + op::addi(0x11, RegId::ZERO, 0xba), + op::log(0x10, 0x11, RegId::ZERO, RegId::ZERO), + op::ret(RegId::ONE), + ]; + let script: Vec = script + .iter() + .flat_map(|op| u32::from(*op).to_be_bytes()) + .collect(); + + // Given + let tx = TransactionBuilder::script(script, vec![]) + .script_gas_limit(gas_limit) + .maturity(maturity) + .add_fee_input() + .add_output(Output::variable(address, 0, AssetId::default())) + .finalize_as_transaction(); + let tx_id = tx.id(&Default::default()); + // Create a random seed based on the test parameters. + let mut hasher = DefaultHasher::new(); + let num_txs = 1; + let num_validators = 1; + let num_partitions = 1; + (num_txs, num_validators, num_partitions, line!()).hash(&mut hasher); + let mut rng = StdRng::seed_from_u64(hasher.finish()); + + // Create a set of key pairs. + let secrets: Vec<_> = (0..1).map(|_| SecretKey::random(&mut rng)).collect(); + let pub_keys: Vec<_> = secrets + .clone() + .into_iter() + .map(|secret| Input::owner(&secret.public_key())) + .collect(); + + // Create a producer for each key pair and a set of validators that share + // the same key pair. + let Nodes { + producers: _dont_drop1, + validators, + bootstrap_nodes: _dont_drop2, + } = make_nodes( + pub_keys + .iter() + .map(|pub_key| Some(BootstrapSetup::new(*pub_key))), + secrets.clone().into_iter().enumerate().map(|(i, secret)| { + Some( + ProducerSetup::new(secret).with_name(format!("{}:producer", pub_keys[i])), + ) + }), + pub_keys.iter().flat_map(|pub_key| { + (0..num_validators).map(move |i| { + Some( + ValidatorSetup::new(*pub_key) + .with_name(format!("{pub_key}:{i}")) + .utxo_validation(false), + ) + }) + }), + None, + ) + .await; + + let sentry = &validators[0]; + let authority = &validators[0]; + + // When + let client_sentry = FuelClient::from(sentry.node.bound_address); + sentry + .node + .submit(tx) + .await + .expect("Should accept invalid transaction because `utxo_validation = false`."); + let mut tx_statuses_subscriber = client_sentry + .subscribe_transaction_status(&tx_id) + .await + .expect("Should be able to subscribe for events"); + authority + .node + .shared + .poa_adapter + .manually_produce_blocks( + None, + fuel_core_poa::service::Mode::Blocks { + number_of_blocks: 1, + }, + ) + .await + .unwrap(); + + // Then + assert!(matches!( + tx_statuses_subscriber.next().await.unwrap().unwrap(), + TransactionStatus::Submitted { .. } + )); + if let TransactionStatus::PreconfirmationSqueezedOut { + reason, + transaction_id, + } = tx_statuses_subscriber.next().await.unwrap().unwrap() + { + assert_eq!(transaction_id, tx_id); + assert_eq!(reason, "Transaction has been skipped during block insertion: \ + The specified coin(0xc49d65de61cf04588a764b557d25cc6c6b4bc0d7429227e2a21e61c213b3a3e28212) doesn't exist".to_string()) + } else { + panic!("Expected preconfirmation status"); + } + assert!(matches!( + tx_statuses_subscriber.next().await.unwrap().unwrap(), + TransactionStatus::SqueezedOut { .. } + )); +} From 78767065bed6d9426aacbf8bbf0250f86513109d Mon Sep 17 00:00:00 2001 From: Mitch Turner Date: Mon, 10 Mar 2025 15:59:18 -0600 Subject: [PATCH 02/76] WIP --- .../services/tx_status_manager/src/service.rs | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/crates/services/tx_status_manager/src/service.rs b/crates/services/tx_status_manager/src/service.rs index 734db4801e1..ea1699e518e 100644 --- a/crates/services/tx_status_manager/src/service.rs +++ b/crates/services/tx_status_manager/src/service.rs @@ -24,8 +24,10 @@ use fuel_core_types::{ }, services::{ p2p::{ + DelegatePublicKey, GossipData, PreConfirmationMessage, + ProtocolSignature, Sealed, }, preconfirmation::Preconfirmation, @@ -102,6 +104,14 @@ pub struct Task { shared_data: SharedData, } +pub trait SignatureVerification { + fn add_new_delegate( + &mut self, + delegate: DelegatePublicKey, + protocol_signature: ProtocolSignature, + ) -> bool; +} + impl Task { // TODO: Implement signatures verifications logic for preconfirmation logic. // https://github.com/FuelLabs/fuel-core/issues/2823 @@ -235,3 +245,13 @@ where shared_data, }) } + +#[cfg(test)] +mod tests { + use super::*; + + #[tokio::test] + async fn run__when_receive_pre_confirmation_delegations_message_updates_delegate() { + todo!() + } +} From 1e27960cdb4f4c5ac929d0577de3b051eab792bf Mon Sep 17 00:00:00 2001 From: Mitch Turner Date: Tue, 11 Mar 2025 16:04:21 -0600 Subject: [PATCH 03/76] Add basic trait for adding delegate key --- Cargo.lock | 1 + crates/services/tx_status_manager/Cargo.toml | 1 + .../services/tx_status_manager/src/service.rs | 131 ++++++++++++++++-- .../tx_status_manager/src/tests/mod.rs | 40 ++++++ .../tx_status_manager/src/tests/universe.rs | 13 +- crates/types/src/services/p2p.rs | 2 +- 6 files changed, 172 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0b344e63891..a2670b4f212 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4077,6 +4077,7 @@ dependencies = [ "tokio", "tokio-stream", "tracing", + "tracing-subscriber", ] [[package]] diff --git a/crates/services/tx_status_manager/Cargo.toml b/crates/services/tx_status_manager/Cargo.toml index fbdcb5ccb30..6921c087950 100644 --- a/crates/services/tx_status_manager/Cargo.toml +++ b/crates/services/tx_status_manager/Cargo.toml @@ -29,6 +29,7 @@ proptest = { workspace = true } test-strategy = { workspace = true } tokio = { workspace = true, features = ["test-util", "macros"] } tracing = { workspace = true } +tracing-subscriber = { workspace = true } [features] test-helpers = ["fuel-core-types/test-helpers"] diff --git a/crates/services/tx_status_manager/src/service.rs b/crates/services/tx_status_manager/src/service.rs index ea1699e518e..0f492263909 100644 --- a/crates/services/tx_status_manager/src/service.rs +++ b/crates/services/tx_status_manager/src/service.rs @@ -35,6 +35,7 @@ use fuel_core_types::{ }, }; use futures::StreamExt; +use std::future::Future; use tokio::sync::{ mpsc, oneshot, @@ -96,32 +97,48 @@ impl SharedData { } } -pub struct Task { +pub struct Task { manager: TxStatusManager, subscriptions: Subscriptions, read_requests_receiver: mpsc::Receiver, write_requests_receiver: mpsc::UnboundedReceiver, shared_data: SharedData, + signature_verification: T, } -pub trait SignatureVerification { +#[allow(dead_code)] +/// Interface for signature verification of preconfirmations +pub trait SignatureVerification: Send { + /// Adds a new delegate signature to verify the preconfirmations fn add_new_delegate( &mut self, delegate: DelegatePublicKey, protocol_signature: ProtocolSignature, - ) -> bool; + ) -> impl Future + Send; } -impl Task { +impl Task { // TODO: Implement signatures verifications logic for preconfirmation logic. // https://github.com/FuelLabs/fuel-core/issues/2823 - fn new_preconfirmations_from_p2p( + async fn new_preconfirmations_from_p2p( &mut self, preconfirmations: P2PPreConfirmationMessage, ) { match preconfirmations { - PreConfirmationMessage::Delegate(_) => {} + PreConfirmationMessage::Delegate(sealed) => { + tracing::debug!( + "Received new delegate signature from peer: {:?}", + sealed.entity.public_key + ); + let Sealed { signature, entity } = sealed; + let delegate_key = entity.public_key; + let _ = self + .signature_verification + .add_new_delegate(delegate_key, signature) + .await; + } PreConfirmationMessage::Preconfirmations(sealed) => { + tracing::debug!("Received new preconfirmations from peer"); let Sealed { signature: _, entity, @@ -138,7 +155,7 @@ impl Task { } #[async_trait::async_trait] -impl RunnableService for Task { +impl RunnableService for Task { const NAME: &'static str = "TxStatusManagerTask"; type SharedData = SharedData; type Task = Self; @@ -157,7 +174,7 @@ impl RunnableService for Task { } } -impl RunnableTask for Task { +impl RunnableTask for Task { async fn run(&mut self, watcher: &mut StateWatcher) -> TaskNextAction { tokio::select! { biased; @@ -169,7 +186,7 @@ impl RunnableTask for Task { tx_status_from_p2p = self.subscriptions.new_tx_status.next() => { if let Some(GossipData { data, .. }) = tx_status_from_p2p { if let Some(msg) = data { - self.new_preconfirmations_from_p2p(msg); + self.new_preconfirmations_from_p2p(msg).await; } TaskNextAction::Continue } else { @@ -214,7 +231,11 @@ impl RunnableTask for Task { } } -pub fn new_service(p2p: P2P, config: Config) -> ServiceRunner +pub fn new_service( + p2p: P2P, + signature_verification: Sign, + config: Config, +) -> ServiceRunner> where P2P: P2PSubscriptions, { @@ -243,15 +264,103 @@ where read_requests_receiver, write_requests_receiver, shared_data, + signature_verification, }) } #[cfg(test)] mod tests { + #![allow(non_snake_case)] + use super::*; + use crate::tests::FakeSignatureVerification; + use fuel_core_types::services::p2p::{ + DelegatePreConfirmationKey, + Tai64, + }; + use std::time::Duration; + use tokio_stream::wrappers::ReceiverStream; + + const TTL: Duration = Duration::from_secs(4); + + fn new_task_with_signature_verification( + signature_verification: T, + ) -> (Task, mpsc::Sender) { + let (read_requests_sender, read_requests_receiver) = mpsc::channel(1); + let (write_requests_sender, write_requests_receiver) = mpsc::unbounded_channel(); + let shared_data = SharedData { + read_requests_sender, + write_requests_sender, + }; + let (sender, receiver) = mpsc::channel(1_000); + let new_tx_status = Box::pin(ReceiverStream::new(receiver)); + let subscriptions = Subscriptions { new_tx_status }; + + let tx_status_change = TxStatusChange::new(100, Duration::from_secs(360)); + let tx_status_manager = TxStatusManager::new(tx_status_change, TTL); + let task = Task { + manager: tx_status_manager, + subscriptions, + read_requests_receiver, + write_requests_receiver, + shared_data, + signature_verification, + }; + (task, sender) + } + + fn arbitrary_delegate_signatures_message() -> P2PPreConfirmationGossipData { + let signature = ProtocolSignature::from_bytes([1u8; 64]); + let delegate_key = DelegatePublicKey::default(); + let entity = DelegatePreConfirmationKey { + public_key: delegate_key, + expiration: Tai64(1234u64), + }; + let sealed = Sealed { signature, entity }; + let inner = P2PPreConfirmationMessage::Delegate(sealed); + GossipData { + data: Some(inner), + peer_id: Default::default(), + message_id: vec![], + } + } #[tokio::test] async fn run__when_receive_pre_confirmation_delegations_message_updates_delegate() { - todo!() + let _ = tracing_subscriber::fmt() + .with_max_level(tracing::Level::DEBUG) + .try_init(); + + // given + let (signature_verification, mut new_delegate_handle) = + FakeSignatureVerification::new_with_handles(true); + let (mut task, sender) = + new_task_with_signature_verification(signature_verification); + let delegate_signature_message = arbitrary_delegate_signatures_message(); + let mut state_watcher = StateWatcher::started(); + + // when + tokio::task::spawn(async move { + let _ = task.run(&mut state_watcher).await; + }); + sender + .send(delegate_signature_message.clone()) + .await + .unwrap(); + tokio::time::sleep(Duration::from_millis(100)).await; + + // then + let (actual_delegate_key, actual_protocol_signature) = + new_delegate_handle.recv().await.unwrap(); + let (expected_delegate_key, expected_protocol_signature) = + if let P2PPreConfirmationMessage::Delegate(sealed) = + delegate_signature_message.data.unwrap() + { + (sealed.entity.public_key, sealed.signature) + } else { + panic!("Expected Delegate message"); + }; + assert_eq!(actual_delegate_key, expected_delegate_key); + assert_eq!(actual_protocol_signature, expected_protocol_signature); } } diff --git a/crates/services/tx_status_manager/src/tests/mod.rs b/crates/services/tx_status_manager/src/tests/mod.rs index 931124d580b..3577ffe93da 100644 --- a/crates/services/tx_status_manager/src/tests/mod.rs +++ b/crates/services/tx_status_manager/src/tests/mod.rs @@ -1,5 +1,12 @@ #![allow(non_snake_case)] +use crate::service::SignatureVerification; +use fuel_core_types::services::p2p::{ + DelegatePublicKey, + ProtocolSignature, +}; +use tokio::sync::mpsc; + mod mocks; mod tests_e2e; mod tests_permits; @@ -9,3 +16,36 @@ mod tests_subscribe; mod tests_update_stream_state; mod universe; mod utils; + +pub(crate) struct FakeSignatureVerification { + new_delegate_sender: mpsc::Sender<(DelegatePublicKey, ProtocolSignature)>, + new_delegate_response: bool, +} + +impl FakeSignatureVerification { + pub fn new_with_handles( + new_delegate_response: bool, + ) -> (Self, mpsc::Receiver<(DelegatePublicKey, ProtocolSignature)>) { + let (new_delegate_sender, new_delegate_receiver) = mpsc::channel(1_000); + let adapter = Self { + new_delegate_sender, + new_delegate_response, + }; + (adapter, new_delegate_receiver) + } +} + +impl SignatureVerification for FakeSignatureVerification { + async fn add_new_delegate( + &mut self, + _delegate: DelegatePublicKey, + _protocol_signature: ProtocolSignature, + ) -> bool { + tracing::debug!("FakeSignatureVerification::add_new_delegate"); + self.new_delegate_sender + .send((_delegate, _protocol_signature)) + .await + .unwrap(); + self.new_delegate_response + } +} diff --git a/crates/services/tx_status_manager/src/tests/universe.rs b/crates/services/tx_status_manager/src/tests/universe.rs index 00c36bdf37a..c2147400246 100644 --- a/crates/services/tx_status_manager/src/tests/universe.rs +++ b/crates/services/tx_status_manager/src/tests/universe.rs @@ -8,16 +8,16 @@ use fuel_core_types::fuel_crypto::rand::{ use parking_lot::Mutex; use std::time::Duration; +use super::mocks::MockP2P; use crate::{ config::Config, new_service, + tests::FakeSignatureVerification, update_sender::TxStatusChange, Task, TxStatusManager, }; -use super::mocks::MockP2P; - const TX_STATUS_MANAGER_TTL: Duration = Duration::from_secs(5); // TODO[RC]: Remove "dead code" when the universe is fully implemented to support new tests @@ -60,9 +60,14 @@ impl TestTxStatusManagerUniverse { self.tx_status_manager = Some(tx_status_manager.clone()); } - pub fn build_service(&self, p2p: Option) -> ServiceRunner { + pub fn build_service( + &self, + p2p: Option, + ) -> ServiceRunner> { let p2p = p2p.unwrap_or_else(|| MockP2P::new_with_statuses(vec![])); + let (signature_verification, _) = + FakeSignatureVerification::new_with_handles(true); - new_service(p2p, self.config.clone()) + new_service(p2p, signature_verification, self.config.clone()) } } diff --git a/crates/types/src/services/p2p.rs b/crates/types/src/services/p2p.rs index 1cb037139ea..f1f884f479c 100644 --- a/crates/types/src/services/p2p.rs +++ b/crates/types/src/services/p2p.rs @@ -29,7 +29,7 @@ use std::{ time::SystemTime, }; -use tai64::Tai64; +pub use tai64::Tai64; /// Contains types and logic for Peer Reputation pub mod peer_reputation; From 8b3eb35018f747ae5231c54a0509c1ff54208598 Mon Sep 17 00:00:00 2001 From: Mitch Turner Date: Tue, 11 Mar 2025 17:15:05 -0600 Subject: [PATCH 04/76] Add tests for verifying incoming preconfirmation signatures --- .../services/tx_status_manager/src/service.rs | 203 ++++++++++++++++-- .../tx_status_manager/src/tests/mod.rs | 23 +- .../tx_status_manager/src/tests/universe.rs | 2 +- 3 files changed, 202 insertions(+), 26 deletions(-) diff --git a/crates/services/tx_status_manager/src/service.rs b/crates/services/tx_status_manager/src/service.rs index 0f492263909..7f539dd6768 100644 --- a/crates/services/tx_status_manager/src/service.rs +++ b/crates/services/tx_status_manager/src/service.rs @@ -20,6 +20,7 @@ use fuel_core_services::{ use fuel_core_types::{ fuel_tx::{ Bytes32, + Bytes64, TxId, }, services::{ @@ -30,7 +31,10 @@ use fuel_core_types::{ ProtocolSignature, Sealed, }, - preconfirmation::Preconfirmation, + preconfirmation::{ + Preconfirmation, + Preconfirmations, + }, txpool::TransactionStatus, }, }; @@ -115,6 +119,12 @@ pub trait SignatureVerification: Send { delegate: DelegatePublicKey, protocol_signature: ProtocolSignature, ) -> impl Future + Send; + + /// Checks pre-confirmation signature + fn check_preconfirmation_signature( + &mut self, + sealed: &Sealed, + ) -> impl Future + Send; } impl Task { @@ -139,16 +149,23 @@ impl Task { } PreConfirmationMessage::Preconfirmations(sealed) => { tracing::debug!("Received new preconfirmations from peer"); - let Sealed { - signature: _, - entity, - } = sealed; - entity.preconfirmations.into_iter().for_each( - |Preconfirmation { tx_id, status }| { - let status: TransactionStatus = status.into(); - self.manager.status_update(tx_id, status); - }, - ); + if self + .signature_verification + .check_preconfirmation_signature(&sealed) + .await + { + tracing::debug!("Preconfirmation signature verified"); + let Sealed { entity, .. } = sealed; + entity.preconfirmations.into_iter().for_each( + |Preconfirmation { tx_id, status }| { + let status: TransactionStatus = status.into(); + self.manager.status_update(tx_id, status); + }, + ); + } else { + // TODO: Allow to retry later when a new delegate key is added + tracing::warn!("Preconfirmation signature verification failed"); + } } } } @@ -273,19 +290,37 @@ mod tests { #![allow(non_snake_case)] use super::*; - use crate::tests::FakeSignatureVerification; - use fuel_core_types::services::p2p::{ - DelegatePreConfirmationKey, - Tai64, + use crate::{ + tests::FakeSignatureVerification, + update_sender::{ + MpscChannel, + UpdateSender, + }, + TxStatusMessage, + }; + use fuel_core_types::services::{ + p2p::{ + DelegatePreConfirmationKey, + Tai64, + }, + preconfirmation::{ + PreconfirmationStatus, + Preconfirmations, + }, }; use std::time::Duration; use tokio_stream::wrappers::ReceiverStream; const TTL: Duration = Duration::from_secs(4); - fn new_task_with_signature_verification( + struct Handles { + pub new_delegate_handle: mpsc::Sender, + pub update_sender: UpdateSender, + } + + fn new_task_with_handles( signature_verification: T, - ) -> (Task, mpsc::Sender) { + ) -> (Task, Handles) { let (read_requests_sender, read_requests_receiver) = mpsc::channel(1); let (write_requests_sender, write_requests_receiver) = mpsc::unbounded_channel(); let shared_data = SharedData { @@ -297,6 +332,7 @@ mod tests { let subscriptions = Subscriptions { new_tx_status }; let tx_status_change = TxStatusChange::new(100, Duration::from_secs(360)); + let updater_sender = tx_status_change.update_sender.clone(); let tx_status_manager = TxStatusManager::new(tx_status_change, TTL); let task = Task { manager: tx_status_manager, @@ -306,7 +342,11 @@ mod tests { shared_data, signature_verification, }; - (task, sender) + let handles = Handles { + new_delegate_handle: sender, + update_sender: updater_sender, + }; + (task, handles) } fn arbitrary_delegate_signatures_message() -> P2PPreConfirmationGossipData { @@ -325,6 +365,36 @@ mod tests { } } + fn arbitrary_pre_confirmation_message( + tx_ids: &[TxId], + ) -> P2PPreConfirmationGossipData { + let preconfirmations = tx_ids + .iter() + .map(|tx_id| Preconfirmation { + tx_id: *tx_id, + status: PreconfirmationStatus::Success { + tx_pointer: Default::default(), + total_gas: 0, + total_fee: 0, + receipts: vec![], + outputs: vec![], + }, + }) + .collect::>(); + let entity = Preconfirmations { + preconfirmations, + expiration: Tai64(1234u64), + }; + let signature = Bytes64::from([1u8; 64]); + let sealed = Sealed { signature, entity }; + let inner = P2PPreConfirmationMessage::Preconfirmations(sealed); + GossipData { + data: Some(inner), + peer_id: Default::default(), + message_id: vec![], + } + } + #[tokio::test] async fn run__when_receive_pre_confirmation_delegations_message_updates_delegate() { let _ = tracing_subscriber::fmt() @@ -333,9 +403,8 @@ mod tests { // given let (signature_verification, mut new_delegate_handle) = - FakeSignatureVerification::new_with_handles(true); - let (mut task, sender) = - new_task_with_signature_verification(signature_verification); + FakeSignatureVerification::new_with_handles(true, true); + let (mut task, handles) = new_task_with_handles(signature_verification); let delegate_signature_message = arbitrary_delegate_signatures_message(); let mut state_watcher = StateWatcher::started(); @@ -343,7 +412,8 @@ mod tests { tokio::task::spawn(async move { let _ = task.run(&mut state_watcher).await; }); - sender + handles + .new_delegate_handle .send(delegate_signature_message.clone()) .await .unwrap(); @@ -363,4 +433,93 @@ mod tests { assert_eq!(actual_delegate_key, expected_delegate_key); assert_eq!(actual_protocol_signature, expected_protocol_signature); } + + #[tokio::test] + async fn run__when_pre_confirmations_pass_verification_then_send() { + // given + let (signature_verification, _) = + FakeSignatureVerification::new_with_handles(true, true); + let (mut task, handles) = new_task_with_handles(signature_verification); + + let tx_ids = vec![[3u8; 32].into(), [4u8; 32].into()]; + let pre_confirmation_message = arbitrary_pre_confirmation_message(&tx_ids); + let mut state_watcher = StateWatcher::started(); + + let streams = tx_ids + .iter() + .map(|tx_id| { + handles + .update_sender + .try_subscribe::(*tx_id) + .unwrap() + }) + .collect::>(); + + // when + tokio::task::spawn(async move { + let _ = task.run(&mut state_watcher).await; + }); + handles + .new_delegate_handle + .send(pre_confirmation_message.clone()) + .await + .unwrap(); + tokio::time::sleep(Duration::from_millis(100)).await; + + // then + // pub type TxStatusStream = Pin + Send + Sync>>; + for mut stream in streams { + let msg = stream.next().await.unwrap(); + match msg { + TxStatusMessage::Status(_) => { + // should be good if we get this + } + _ => panic!("Expected Status message"), + } + } + } + + #[tokio::test] + async fn run__when_pre_confirmations_fail_then_do_not_send() { + let _ = tracing_subscriber::fmt() + .with_max_level(tracing::Level::DEBUG) + .try_init(); + + // given + let (signature_verification, _) = + FakeSignatureVerification::new_with_handles(false, false); + let (mut task, handles) = new_task_with_handles(signature_verification); + + let tx_ids = vec![[3u8; 32].into(), [4u8; 32].into()]; + let pre_confirmation_message = arbitrary_pre_confirmation_message(&tx_ids); + let mut state_watcher = StateWatcher::started(); + + let streams = tx_ids + .iter() + .map(|tx_id| { + handles + .update_sender + .try_subscribe::(*tx_id) + .unwrap() + }) + .collect::>(); + + // when + tokio::task::spawn(async move { + let _ = task.run(&mut state_watcher).await; + }); + handles + .new_delegate_handle + .send(pre_confirmation_message.clone()) + .await + .unwrap(); + tokio::time::sleep(Duration::from_millis(100)).await; + + // then + for mut stream in streams { + let res = + tokio::time::timeout(Duration::from_millis(100), stream.next()).await; + assert!(res.is_err()); + } + } } diff --git a/crates/services/tx_status_manager/src/tests/mod.rs b/crates/services/tx_status_manager/src/tests/mod.rs index 3577ffe93da..55b0c977b5c 100644 --- a/crates/services/tx_status_manager/src/tests/mod.rs +++ b/crates/services/tx_status_manager/src/tests/mod.rs @@ -1,9 +1,16 @@ #![allow(non_snake_case)] use crate::service::SignatureVerification; -use fuel_core_types::services::p2p::{ - DelegatePublicKey, - ProtocolSignature, +use fuel_core_types::{ + fuel_tx::Bytes64, + services::{ + p2p::{ + DelegatePublicKey, + ProtocolSignature, + Sealed, + }, + preconfirmation::Preconfirmations, + }, }; use tokio::sync::mpsc; @@ -20,16 +27,19 @@ mod utils; pub(crate) struct FakeSignatureVerification { new_delegate_sender: mpsc::Sender<(DelegatePublicKey, ProtocolSignature)>, new_delegate_response: bool, + preconfirmation_signature_success: bool, } impl FakeSignatureVerification { pub fn new_with_handles( new_delegate_response: bool, + preconfirmation_signature_success: bool, ) -> (Self, mpsc::Receiver<(DelegatePublicKey, ProtocolSignature)>) { let (new_delegate_sender, new_delegate_receiver) = mpsc::channel(1_000); let adapter = Self { new_delegate_sender, new_delegate_response, + preconfirmation_signature_success, }; (adapter, new_delegate_receiver) } @@ -48,4 +58,11 @@ impl SignatureVerification for FakeSignatureVerification { .unwrap(); self.new_delegate_response } + + async fn check_preconfirmation_signature( + &mut self, + _sealed: &Sealed, + ) -> bool { + self.preconfirmation_signature_success + } } diff --git a/crates/services/tx_status_manager/src/tests/universe.rs b/crates/services/tx_status_manager/src/tests/universe.rs index c2147400246..b5ae0ee0335 100644 --- a/crates/services/tx_status_manager/src/tests/universe.rs +++ b/crates/services/tx_status_manager/src/tests/universe.rs @@ -66,7 +66,7 @@ impl TestTxStatusManagerUniverse { ) -> ServiceRunner> { let p2p = p2p.unwrap_or_else(|| MockP2P::new_with_statuses(vec![])); let (signature_verification, _) = - FakeSignatureVerification::new_with_handles(true); + FakeSignatureVerification::new_with_handles(true, true); new_service(p2p, signature_verification, self.config.clone()) } From 136ff89454a116a731d0de3ea4a84c29966274e4 Mon Sep 17 00:00:00 2001 From: Mitch Turner Date: Tue, 11 Mar 2025 18:56:18 -0600 Subject: [PATCH 05/76] Include test for tracking preconfirmations that return too early for their delegate key --- .../services/tx_status_manager/src/service.rs | 130 +++++++++++++++--- .../tx_status_manager/src/tests/mod.rs | 33 ++++- .../tx_status_manager/src/tests/universe.rs | 2 +- 3 files changed, 135 insertions(+), 30 deletions(-) diff --git a/crates/services/tx_status_manager/src/service.rs b/crates/services/tx_status_manager/src/service.rs index 7f539dd6768..a603744eca8 100644 --- a/crates/services/tx_status_manager/src/service.rs +++ b/crates/services/tx_status_manager/src/service.rs @@ -108,6 +108,7 @@ pub struct Task { write_requests_receiver: mpsc::UnboundedReceiver, shared_data: SharedData, signature_verification: T, + early_preconfirmations: Vec>, } #[allow(dead_code)] @@ -128,8 +129,20 @@ pub trait SignatureVerification: Send { } impl Task { - // TODO: Implement signatures verifications logic for preconfirmation logic. - // https://github.com/FuelLabs/fuel-core/issues/2823 + fn handle_verified_preconfirmation( + &mut self, + sealed: Sealed, + ) { + tracing::debug!("Preconfirmation signature verified"); + let Sealed { entity, .. } = sealed; + entity.preconfirmations.into_iter().for_each( + |Preconfirmation { tx_id, status }| { + let status: TransactionStatus = status.into(); + self.manager.status_update(tx_id, status); + }, + ); + } + async fn new_preconfirmations_from_p2p( &mut self, preconfirmations: P2PPreConfirmationMessage, @@ -146,6 +159,18 @@ impl Task { .signature_verification .add_new_delegate(delegate_key, signature) .await; + let drained = std::mem::take(&mut self.early_preconfirmations); + for sealed in drained { + if self + .signature_verification + .check_preconfirmation_signature(&sealed) + .await + { + self.handle_verified_preconfirmation(sealed); + } else { + tracing::warn!("Preconfirmation signature verification failed for early preconfirmation, removing it"); + } + } } PreConfirmationMessage::Preconfirmations(sealed) => { tracing::debug!("Received new preconfirmations from peer"); @@ -154,17 +179,11 @@ impl Task { .check_preconfirmation_signature(&sealed) .await { - tracing::debug!("Preconfirmation signature verified"); - let Sealed { entity, .. } = sealed; - entity.preconfirmations.into_iter().for_each( - |Preconfirmation { tx_id, status }| { - let status: TransactionStatus = status.into(); - self.manager.status_update(tx_id, status); - }, - ); + self.handle_verified_preconfirmation(sealed); } else { // TODO: Allow to retry later when a new delegate key is added - tracing::warn!("Preconfirmation signature verification failed"); + tracing::warn!("Preconfirmation signature verification failed, will try once more when a new delegate key is added"); + self.early_preconfirmations.push(sealed); } } } @@ -282,6 +301,7 @@ where write_requests_receiver, shared_data, signature_verification, + early_preconfirmations: Vec::new(), }) } @@ -298,6 +318,7 @@ mod tests { }, TxStatusMessage, }; + use fuel_core_services::Service; use fuel_core_types::services::{ p2p::{ DelegatePreConfirmationKey, @@ -314,7 +335,7 @@ mod tests { const TTL: Duration = Duration::from_secs(4); struct Handles { - pub new_delegate_handle: mpsc::Sender, + pub pre_confirmation_updates: mpsc::Sender, pub update_sender: UpdateSender, } @@ -341,9 +362,10 @@ mod tests { write_requests_receiver, shared_data, signature_verification, + early_preconfirmations: Vec::new(), }; let handles = Handles { - new_delegate_handle: sender, + pre_confirmation_updates: sender, update_sender: updater_sender, }; (task, handles) @@ -403,7 +425,7 @@ mod tests { // given let (signature_verification, mut new_delegate_handle) = - FakeSignatureVerification::new_with_handles(true, true); + FakeSignatureVerification::new_with_handles(true); let (mut task, handles) = new_task_with_handles(signature_verification); let delegate_signature_message = arbitrary_delegate_signatures_message(); let mut state_watcher = StateWatcher::started(); @@ -413,15 +435,18 @@ mod tests { let _ = task.run(&mut state_watcher).await; }); handles - .new_delegate_handle + .pre_confirmation_updates .send(delegate_signature_message.clone()) .await .unwrap(); tokio::time::sleep(Duration::from_millis(100)).await; // then - let (actual_delegate_key, actual_protocol_signature) = - new_delegate_handle.recv().await.unwrap(); + let (actual_delegate_key, actual_protocol_signature) = new_delegate_handle + .new_delegate_receiver + .recv() + .await + .unwrap(); let (expected_delegate_key, expected_protocol_signature) = if let P2PPreConfirmationMessage::Delegate(sealed) = delegate_signature_message.data.unwrap() @@ -438,7 +463,7 @@ mod tests { async fn run__when_pre_confirmations_pass_verification_then_send() { // given let (signature_verification, _) = - FakeSignatureVerification::new_with_handles(true, true); + FakeSignatureVerification::new_with_handles(true); let (mut task, handles) = new_task_with_handles(signature_verification); let tx_ids = vec![[3u8; 32].into(), [4u8; 32].into()]; @@ -460,7 +485,7 @@ mod tests { let _ = task.run(&mut state_watcher).await; }); handles - .new_delegate_handle + .pre_confirmation_updates .send(pre_confirmation_message.clone()) .await .unwrap(); @@ -480,14 +505,14 @@ mod tests { } #[tokio::test] - async fn run__when_pre_confirmations_fail_then_do_not_send() { + async fn run__when_pre_confirmations_fail_verification_then_do_not_send() { let _ = tracing_subscriber::fmt() .with_max_level(tracing::Level::DEBUG) .try_init(); // given let (signature_verification, _) = - FakeSignatureVerification::new_with_handles(false, false); + FakeSignatureVerification::new_with_handles(false); let (mut task, handles) = new_task_with_handles(signature_verification); let tx_ids = vec![[3u8; 32].into(), [4u8; 32].into()]; @@ -509,7 +534,7 @@ mod tests { let _ = task.run(&mut state_watcher).await; }); handles - .new_delegate_handle + .pre_confirmation_updates .send(pre_confirmation_message.clone()) .await .unwrap(); @@ -522,4 +547,65 @@ mod tests { assert!(res.is_err()); } } + + #[tokio::test] + async fn run__when_pre_confirmations_fail_verification_they_can_be_retried_on_next_delegate_update( + ) { + let _ = tracing_subscriber::fmt() + .with_max_level(tracing::Level::DEBUG) + .try_init(); + + // given + let (signature_verification, mut fake_signature_handles) = + FakeSignatureVerification::new_with_handles(false); + let (task, handles) = new_task_with_handles(signature_verification); + + let tx_ids = vec![[3u8; 32].into(), [4u8; 32].into()]; + let pre_confirmation_message = arbitrary_pre_confirmation_message(&tx_ids); + + let mut streams = tx_ids + .iter() + .map(|tx_id| { + handles + .update_sender + .try_subscribe::(*tx_id) + .unwrap() + }) + .collect::>(); + + let service = ServiceRunner::new(task); + service.start_and_await().await.unwrap(); + handles + .pre_confirmation_updates + .send(pre_confirmation_message.clone()) + .await + .unwrap(); + tokio::time::sleep(Duration::from_millis(100)).await; + + for stream in &mut streams { + let res = + tokio::time::timeout(Duration::from_millis(100), stream.next()).await; + assert!(res.is_err()); + } + + // when + let new_delegate_message = arbitrary_delegate_signatures_message(); + fake_signature_handles.update_signature_verification_result(true); + handles + .pre_confirmation_updates + .send(new_delegate_message) + .await + .unwrap(); + + // then + for mut stream in streams { + let msg = stream.next().await.unwrap(); + match msg { + TxStatusMessage::Status(_) => { + // should be good if we get this + } + _ => panic!("Expected Status message"), + } + } + } } diff --git a/crates/services/tx_status_manager/src/tests/mod.rs b/crates/services/tx_status_manager/src/tests/mod.rs index 55b0c977b5c..f28f6be8cf3 100644 --- a/crates/services/tx_status_manager/src/tests/mod.rs +++ b/crates/services/tx_status_manager/src/tests/mod.rs @@ -12,6 +12,7 @@ use fuel_core_types::{ preconfirmation::Preconfirmations, }, }; +use std::sync::Arc; use tokio::sync::mpsc; mod mocks; @@ -26,22 +27,39 @@ mod utils; pub(crate) struct FakeSignatureVerification { new_delegate_sender: mpsc::Sender<(DelegatePublicKey, ProtocolSignature)>, - new_delegate_response: bool, - preconfirmation_signature_success: bool, + preconfirmation_signature_success: Arc, +} + +pub(crate) struct FakeSignatureVerificationHandles { + pub new_delegate_receiver: mpsc::Receiver<(DelegatePublicKey, ProtocolSignature)>, + pub verification_result: Arc, +} + +impl FakeSignatureVerificationHandles { + pub fn update_signature_verification_result(&mut self, result: bool) { + self.verification_result + .store(result, std::sync::atomic::Ordering::Relaxed); + } } impl FakeSignatureVerification { pub fn new_with_handles( - new_delegate_response: bool, preconfirmation_signature_success: bool, - ) -> (Self, mpsc::Receiver<(DelegatePublicKey, ProtocolSignature)>) { + ) -> (Self, FakeSignatureVerificationHandles) { let (new_delegate_sender, new_delegate_receiver) = mpsc::channel(1_000); + let preconfirmation_signature_success = Arc::new( + std::sync::atomic::AtomicBool::new(preconfirmation_signature_success), + ); + let verification_result = preconfirmation_signature_success.clone(); let adapter = Self { new_delegate_sender, - new_delegate_response, preconfirmation_signature_success, }; - (adapter, new_delegate_receiver) + let handles = FakeSignatureVerificationHandles { + new_delegate_receiver, + verification_result, + }; + (adapter, handles) } } @@ -56,7 +74,7 @@ impl SignatureVerification for FakeSignatureVerification { .send((_delegate, _protocol_signature)) .await .unwrap(); - self.new_delegate_response + true } async fn check_preconfirmation_signature( @@ -64,5 +82,6 @@ impl SignatureVerification for FakeSignatureVerification { _sealed: &Sealed, ) -> bool { self.preconfirmation_signature_success + .load(std::sync::atomic::Ordering::Relaxed) } } diff --git a/crates/services/tx_status_manager/src/tests/universe.rs b/crates/services/tx_status_manager/src/tests/universe.rs index b5ae0ee0335..c2147400246 100644 --- a/crates/services/tx_status_manager/src/tests/universe.rs +++ b/crates/services/tx_status_manager/src/tests/universe.rs @@ -66,7 +66,7 @@ impl TestTxStatusManagerUniverse { ) -> ServiceRunner> { let p2p = p2p.unwrap_or_else(|| MockP2P::new_with_statuses(vec![])); let (signature_verification, _) = - FakeSignatureVerification::new_with_handles(true, true); + FakeSignatureVerification::new_with_handles(true); new_service(p2p, signature_verification, self.config.clone()) } From 37579b620d11cd40efcbc0b1ad5897bb2315934d Mon Sep 17 00:00:00 2001 From: Mitch Turner Date: Tue, 11 Mar 2025 18:59:55 -0600 Subject: [PATCH 06/76] Cleanup --- .../services/tx_status_manager/src/service.rs | 46 ++++++------------- .../tx_status_manager/src/tests/mod.rs | 2 + 2 files changed, 17 insertions(+), 31 deletions(-) diff --git a/crates/services/tx_status_manager/src/service.rs b/crates/services/tx_status_manager/src/service.rs index a603744eca8..1d4c29eebdf 100644 --- a/crates/services/tx_status_manager/src/service.rs +++ b/crates/services/tx_status_manager/src/service.rs @@ -419,10 +419,6 @@ mod tests { #[tokio::test] async fn run__when_receive_pre_confirmation_delegations_message_updates_delegate() { - let _ = tracing_subscriber::fmt() - .with_max_level(tracing::Level::DEBUG) - .try_init(); - // given let (signature_verification, mut new_delegate_handle) = FakeSignatureVerification::new_with_handles(true); @@ -459,6 +455,19 @@ mod tests { assert_eq!(actual_protocol_signature, expected_protocol_signature); } + async fn all_streams_return_success(streams: Vec) -> bool { + for mut stream in streams { + let msg = stream.next().await.unwrap(); + match msg { + TxStatusMessage::Status(_) => { + // should be good if we get this + } + _ => return false, + } + } + true + } + #[tokio::test] async fn run__when_pre_confirmations_pass_verification_then_send() { // given @@ -492,24 +501,11 @@ mod tests { tokio::time::sleep(Duration::from_millis(100)).await; // then - // pub type TxStatusStream = Pin + Send + Sync>>; - for mut stream in streams { - let msg = stream.next().await.unwrap(); - match msg { - TxStatusMessage::Status(_) => { - // should be good if we get this - } - _ => panic!("Expected Status message"), - } - } + all_streams_return_success(streams).await; } #[tokio::test] async fn run__when_pre_confirmations_fail_verification_then_do_not_send() { - let _ = tracing_subscriber::fmt() - .with_max_level(tracing::Level::DEBUG) - .try_init(); - // given let (signature_verification, _) = FakeSignatureVerification::new_with_handles(false); @@ -551,10 +547,6 @@ mod tests { #[tokio::test] async fn run__when_pre_confirmations_fail_verification_they_can_be_retried_on_next_delegate_update( ) { - let _ = tracing_subscriber::fmt() - .with_max_level(tracing::Level::DEBUG) - .try_init(); - // given let (signature_verification, mut fake_signature_handles) = FakeSignatureVerification::new_with_handles(false); @@ -598,14 +590,6 @@ mod tests { .unwrap(); // then - for mut stream in streams { - let msg = stream.next().await.unwrap(); - match msg { - TxStatusMessage::Status(_) => { - // should be good if we get this - } - _ => panic!("Expected Status message"), - } - } + all_streams_return_success(streams).await; } } diff --git a/crates/services/tx_status_manager/src/tests/mod.rs b/crates/services/tx_status_manager/src/tests/mod.rs index f28f6be8cf3..2997b6fc649 100644 --- a/crates/services/tx_status_manager/src/tests/mod.rs +++ b/crates/services/tx_status_manager/src/tests/mod.rs @@ -25,6 +25,8 @@ mod tests_update_stream_state; mod universe; mod utils; +use tracing_subscriber as _; + pub(crate) struct FakeSignatureVerification { new_delegate_sender: mpsc::Sender<(DelegatePublicKey, ProtocolSignature)>, preconfirmation_signature_success: Arc, From af74f22050524b8dea0d5fb3b3f8b96c4dc55104 Mon Sep 17 00:00:00 2001 From: Mitch Turner Date: Tue, 11 Mar 2025 19:01:09 -0600 Subject: [PATCH 07/76] Update CHANGELOG --- .changes/added/2856.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 .changes/added/2856.md diff --git a/.changes/added/2856.md b/.changes/added/2856.md new file mode 100644 index 00000000000..953d3c7b944 --- /dev/null +++ b/.changes/added/2856.md @@ -0,0 +1 @@ +Add generic logic for managing the signatures and delegate keys for pre-confirmations signatures \ No newline at end of file From b35f3ab679e64b801a57fd7d5fab4077f5430daf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Chabowski?= Date: Wed, 12 Mar 2025 12:46:43 +0100 Subject: [PATCH 08/76] Introduce the 'final transaction status' concept --- crates/client/assets/schema.sdl | 6 +++--- crates/client/src/client.rs | 2 +- crates/client/src/client/types.rs | 15 +++++++++++++++ crates/fuel-core/src/query/subscriptions.rs | 9 +++------ crates/fuel-core/src/schema/tx.rs | 12 ++++++------ crates/fuel-core/src/schema/tx/types.rs | 12 ++++++++++++ crates/fuel-core/src/service/query.rs | 4 +++- .../tx_status_manager/src/tx_status_stream.rs | 9 +++++++++ crates/types/src/services/txpool.rs | 13 +++++++++++++ 9 files changed, 65 insertions(+), 17 deletions(-) diff --git a/crates/client/assets/schema.sdl b/crates/client/assets/schema.sdl index d974add1173..54e6fa961aa 100644 --- a/crates/client/assets/schema.sdl +++ b/crates/client/assets/schema.sdl @@ -1320,8 +1320,8 @@ type Subscription { Returns a stream of status updates for the given transaction id. If the current status is [`TransactionStatus::Success`], [`TransactionStatus::SqueezedOut`] or [`TransactionStatus::Failed`] the stream will return that and end immediately. - If the current status is [`TransactionStatus::Submitted`] this will be returned - and the stream will wait for a future update. + Other, intermediate statuses will also be returned but the stream + will remain active and wait for a future updates. This stream will wait forever so it's advised to use within a timeout. @@ -1337,7 +1337,7 @@ type Subscription { id: TransactionId! ): TransactionStatus! """ - Submits transaction to the `TxPool` and await either confirmation or failure. + Submits transaction to the `TxPool` and await either success or failure. """ submitAndAwait(tx: HexString!): TransactionStatus! """ diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index 11038bb867d..953a1768b3d 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -1190,7 +1190,7 @@ impl FuelClient { .subscribe_transaction_status(id) .await? .skip_while(|status| { - future::ready(matches!(status, Ok(TransactionStatus::Submitted { .. }))) + future::ready(status.as_ref().map_or(true, |status| !status.is_final())) }) .next() .await; diff --git a/crates/client/src/client/types.rs b/crates/client/src/client/types.rs index 8345bcf91a7..535a9c952a9 100644 --- a/crates/client/src/client/types.rs +++ b/crates/client/src/client/types.rs @@ -153,6 +153,21 @@ pub enum TransactionStatus { }, } +impl TransactionStatus { + #[cfg(feature = "subscriptions")] + pub(super) fn is_final(&self) -> bool { + match self { + TransactionStatus::Success { .. } + | TransactionStatus::Failure { .. } + | TransactionStatus::SqueezedOut { .. } => true, + TransactionStatus::Submitted { .. } + | TransactionStatus::PreconfirmationSuccess { .. } + | TransactionStatus::PreconfirmationSqueezedOut { .. } + | TransactionStatus::PreconfirmationFailure { .. } => false, + } + } +} + impl TryFrom for TransactionStatus { type Error = ConversionError; diff --git a/crates/fuel-core/src/query/subscriptions.rs b/crates/fuel-core/src/query/subscriptions.rs index 0a71f1536aa..53afa8f28d2 100644 --- a/crates/fuel-core/src/query/subscriptions.rs +++ b/crates/fuel-core/src/query/subscriptions.rs @@ -51,12 +51,9 @@ where // Keep taking the stream until the oneshot channel is closed. .take_until(closed) .map(move |status| { - // Close the stream if the transaction is anything other than - // `Submitted`. - if !matches!( - status, - TxStatusMessage::Status(TransactionStatus::Submitted(_)) - ) { + // Close the stream if the transaction is in final status. + if status.is_final() + { if let Some(close) = close.take() { let _ = close.send(()); } diff --git a/crates/fuel-core/src/schema/tx.rs b/crates/fuel-core/src/schema/tx.rs index 349ebd7cad4..d1b946c6b97 100644 --- a/crates/fuel-core/src/schema/tx.rs +++ b/crates/fuel-core/src/schema/tx.rs @@ -609,8 +609,8 @@ impl TxStatusSubscription { /// Returns a stream of status updates for the given transaction id. /// If the current status is [`TransactionStatus::Success`], [`TransactionStatus::SqueezedOut`] /// or [`TransactionStatus::Failed`] the stream will return that and end immediately. - /// If the current status is [`TransactionStatus::Submitted`] this will be returned - /// and the stream will wait for a future update. + /// Other, intermediate statuses will also be returned but the stream + /// will remain active and wait for a future updates. /// /// This stream will wait forever so it's advised to use within a timeout. /// @@ -640,7 +640,7 @@ impl TxStatusSubscription { ) } - /// Submits transaction to the `TxPool` and await either confirmation or failure. + /// Submits transaction to the `TxPool` and await either success or failure. #[graphql(complexity = "query_costs().submit_and_await + child_complexity")] async fn submit_and_await<'a>( &self, @@ -653,13 +653,13 @@ impl TxStatusSubscription { let subscription = submit_and_await_status(ctx, tx).await?; Ok(subscription - .skip_while(|event| matches!(event, Ok(TransactionStatus::Submitted(..)))) + .skip_while(|event| event.as_ref().map_or(true, |status| !status.is_final())) .take(1)) } /// Submits the transaction to the `TxPool` and returns a stream of events. - /// Compared to the `submitAndAwait`, the stream also contains ` - /// SubmittedStatus` as an intermediate state. + /// Compared to the `submitAndAwait`, the stream also contains + /// `SubmittedStatus` as an intermediate state. #[graphql(complexity = "query_costs().submit_and_await + child_complexity")] async fn submit_and_await_status<'a>( &self, diff --git a/crates/fuel-core/src/schema/tx/types.rs b/crates/fuel-core/src/schema/tx/types.rs index 1a6e5e203c4..fbe77d91258 100644 --- a/crates/fuel-core/src/schema/tx/types.rs +++ b/crates/fuel-core/src/schema/tx/types.rs @@ -450,6 +450,18 @@ impl TransactionStatus { } } } + + pub fn is_final(&self) -> bool { + match self { + TransactionStatus::Success(_) + | TransactionStatus::Failure(_) + | TransactionStatus::SqueezedOut(_) => true, + TransactionStatus::Submitted(_) + | TransactionStatus::PreconfirmationSuccess(_) + | TransactionStatus::PreconfirmationSqueezedOut(_) + | TransactionStatus::PreconfirmationFailure(_) => false, + } + } } impl From for TxStatus { diff --git a/crates/fuel-core/src/service/query.rs b/crates/fuel-core/src/service/query.rs index 4d0b382b5a2..ccf0cea4063 100644 --- a/crates/fuel-core/src/service/query.rs +++ b/crates/fuel-core/src/service/query.rs @@ -65,7 +65,9 @@ impl FuelService { .consensus_parameters .chain_id()); let stream = self.transaction_status_change(id).await?.filter(|status| { - futures::future::ready(!matches!(status, Ok(TransactionStatus::Submitted(_)))) + futures::future::ready( + status.as_ref().map_or(false, |status| status.is_final()), + ) }); futures::pin_mut!(stream); self.submit(tx).await?; diff --git a/crates/services/tx_status_manager/src/tx_status_stream.rs b/crates/services/tx_status_manager/src/tx_status_stream.rs index b533a0edb2a..68400a6ac2e 100644 --- a/crates/services/tx_status_manager/src/tx_status_stream.rs +++ b/crates/services/tx_status_manager/src/tx_status_stream.rs @@ -31,6 +31,15 @@ pub enum TxStatusMessage { FailedStatus, } +impl TxStatusMessage { + pub fn is_final(&self) -> bool { + match self { + TxStatusMessage::Status(transaction_status) => transaction_status.is_final(), + TxStatusMessage::FailedStatus => true, + } + } +} + impl From> for TxStatusMessage { fn from(result: Result) -> Self { match result { diff --git a/crates/types/src/services/txpool.rs b/crates/types/src/services/txpool.rs index b3068ef2204..a312323042d 100644 --- a/crates/types/src/services/txpool.rs +++ b/crates/types/src/services/txpool.rs @@ -452,6 +452,19 @@ pub enum TransactionStatus { } impl TransactionStatus { + /// Returns `true` if the status is considered "final". + pub fn is_final(&self) -> bool { + match self { + TransactionStatus::Success(_) + | TransactionStatus::Failure(_) + | TransactionStatus::SqueezedOut(_) => true, + TransactionStatus::Submitted(_) + | TransactionStatus::PreConfirmationSuccess(_) + | TransactionStatus::PreConfirmationSqueezedOut(_) + | TransactionStatus::PreConfirmationFailure(_) => false, + } + } + /// Returns `true` if the status is `Submitted`. pub fn is_submitted(&self) -> bool { matches!(self, Self::Submitted { .. }) From a9306a85a15c8f96d4ed5bd4c95d32abd2b61d7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Chabowski?= Date: Wed, 12 Mar 2025 14:15:33 +0100 Subject: [PATCH 09/76] Do not close status change stream for preconfirmation statuses --- crates/fuel-core/src/lib.rs | 2 +- crates/fuel-core/src/query/subscriptions.rs | 12 +++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/crates/fuel-core/src/lib.rs b/crates/fuel-core/src/lib.rs index 9ea4c45d726..7bb9d0aef87 100644 --- a/crates/fuel-core/src/lib.rs +++ b/crates/fuel-core/src/lib.rs @@ -1,7 +1,7 @@ #![deny(clippy::arithmetic_side_effects)] #![deny(clippy::cast_possible_truncation)] #![deny(unused_crate_dependencies)] -#![deny(warnings)] +//#![deny(warnings)] #[cfg(test)] use tracing_subscriber as _; diff --git a/crates/fuel-core/src/query/subscriptions.rs b/crates/fuel-core/src/query/subscriptions.rs index 53afa8f28d2..e7971a64aea 100644 --- a/crates/fuel-core/src/query/subscriptions.rs +++ b/crates/fuel-core/src/query/subscriptions.rs @@ -51,9 +51,15 @@ where // Keep taking the stream until the oneshot channel is closed. .take_until(closed) .map(move |status| { - // Close the stream if the transaction is in final status. - if status.is_final() - { + let should_close_stream = status.is_final(); + + // TODO[RC]: This also closes the stream for Preconfirmation* statuses. + let _prev_impl = !matches!( + status, + TxStatusMessage::Status(TransactionStatus::Submitted(_)) + ) ; + + if should_close_stream { if let Some(close) = close.take() { let _ = close.send(()); } From 986aab9da9134dad351d1e2bb725156d8e11cd03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Chabowski?= Date: Wed, 12 Mar 2025 14:26:42 +0100 Subject: [PATCH 10/76] Update tx status flow test --- .../fuel-core/src/query/subscriptions/test.rs | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/crates/fuel-core/src/query/subscriptions/test.rs b/crates/fuel-core/src/query/subscriptions/test.rs index bb446270e1d..d8d063da31e 100644 --- a/crates/fuel-core/src/query/subscriptions/test.rs +++ b/crates/fuel-core/src/query/subscriptions/test.rs @@ -85,6 +85,8 @@ fn squeezed_during_block_production() -> TransactionStatus { enum TxStatus { /// The transaction has been submitted Submitted, + /// The transaction has been preconfirmed, but the final status could still be different + Preconfirmed, /// The transaction has reached a final status Final(FinalTxStatus), } @@ -94,19 +96,11 @@ enum TxStatus { enum FinalTxStatus { /// The transaction was successfully included in a block. Success, - /// The transaction has been executed, but the full block has not been produced yet. - PreconfirmationSuccess, /// The transaction was squeezed out of the txpool (or block) /// because it was not valid to include in the block. Squeezed, - /// Transaction was eligible for execution and inclusion in the block but - /// it was squeezed out by block producer. - PreconfirmationSqueezedOut, /// The transaction failed to execute and was included in a block. Failed, - /// Transaction was eligible for execution and inclusion in the block but - /// it failed during the execution. - PreconfirmationFailure, } /// Strategy to generate an Option @@ -151,10 +145,13 @@ struct Error; /// Struct representing a submitted transaction. #[derive(Debug, Clone, Copy, PartialEq, Eq, Arbitrary)] -struct Submitted; +enum NotFinal { + Submitted, + Preconfirmation, +} /// A model of the transaction status change functions control flow. -type Flow = ControlFlow; +type Flow = ControlFlow; /// The `transaction_status_change_model` function is a simplified version of the real /// `transaction_status_change` function. It takes an `Option` and an `Iterator` as input @@ -182,8 +179,11 @@ fn transaction_status_change_model( .try_fold(Vec::new(), |mut out, state| match state { TxStatusMessage::Status(status) => match next_state(status) { // If the next state is "Continue" with "Submitted" status, push it to the output vector - Flow::Continue(Submitted) => { - out.push(Ok(TxStatus::Submitted)); + Flow::Continue(not_final_status) => { + match not_final_status { + NotFinal::Submitted => out.push(Ok(TxStatus::Submitted)), + NotFinal::Preconfirmation => out.push(Ok(TxStatus::Preconfirmed)), + } ControlFlow::Continue(out) } // If the next state is "Break" with a final status, push it to the output vector @@ -210,18 +210,18 @@ fn transaction_status_change_model( /// The flow continues only for `Submitted` status, otherwise it breaks with appropriate status. fn next_state(state: TransactionStatus) -> Flow { match state { - TransactionStatus::Submitted { .. } => Flow::Continue(Submitted), + TransactionStatus::Submitted { .. } => Flow::Continue(NotFinal::Submitted), TransactionStatus::Success { .. } => Flow::Break(FinalTxStatus::Success), TransactionStatus::PreConfirmationSuccess { .. } => { - Flow::Break(FinalTxStatus::PreconfirmationSuccess) + Flow::Continue(NotFinal::Preconfirmation) } TransactionStatus::Failure { .. } => Flow::Break(FinalTxStatus::Failed), TransactionStatus::PreConfirmationFailure { .. } => { - Flow::Break(FinalTxStatus::PreconfirmationFailure) + Flow::Continue(NotFinal::Preconfirmation) } TransactionStatus::SqueezedOut { .. } => Flow::Break(FinalTxStatus::Squeezed), TransactionStatus::PreConfirmationSqueezedOut { .. } => { - Flow::Break(FinalTxStatus::PreconfirmationSqueezedOut) + Flow::Continue(NotFinal::Preconfirmation) } } } @@ -289,19 +289,19 @@ impl From for TxStatus { TxStatus::Final(FinalTxStatus::Success) } crate::schema::tx::types::TransactionStatus::PreconfirmationSuccess(_) => { - TxStatus::Final(FinalTxStatus::PreconfirmationSuccess) + TxStatus::Preconfirmed } crate::schema::tx::types::TransactionStatus::SqueezedOut(_) => { TxStatus::Final(FinalTxStatus::Squeezed) } crate::schema::tx::types::TransactionStatus::PreconfirmationSqueezedOut( _, - ) => TxStatus::Final(FinalTxStatus::PreconfirmationSqueezedOut), + ) => TxStatus::Preconfirmed, crate::schema::tx::types::TransactionStatus::Failure(_) => { TxStatus::Final(FinalTxStatus::Failed) } crate::schema::tx::types::TransactionStatus::PreconfirmationFailure(_) => { - TxStatus::Final(FinalTxStatus::PreconfirmationFailure) + TxStatus::Preconfirmed } } } From e9c332276cc20b9cff488e8dd1af05c906a25b07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Chabowski?= Date: Wed, 12 Mar 2025 14:31:04 +0100 Subject: [PATCH 11/76] Cleanup --- crates/fuel-core/src/lib.rs | 2 +- crates/fuel-core/src/query/subscriptions.rs | 10 +--------- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/crates/fuel-core/src/lib.rs b/crates/fuel-core/src/lib.rs index 7bb9d0aef87..9ea4c45d726 100644 --- a/crates/fuel-core/src/lib.rs +++ b/crates/fuel-core/src/lib.rs @@ -1,7 +1,7 @@ #![deny(clippy::arithmetic_side_effects)] #![deny(clippy::cast_possible_truncation)] #![deny(unused_crate_dependencies)] -//#![deny(warnings)] +#![deny(warnings)] #[cfg(test)] use tracing_subscriber as _; diff --git a/crates/fuel-core/src/query/subscriptions.rs b/crates/fuel-core/src/query/subscriptions.rs index e7971a64aea..9904e37612b 100644 --- a/crates/fuel-core/src/query/subscriptions.rs +++ b/crates/fuel-core/src/query/subscriptions.rs @@ -51,15 +51,7 @@ where // Keep taking the stream until the oneshot channel is closed. .take_until(closed) .map(move |status| { - let should_close_stream = status.is_final(); - - // TODO[RC]: This also closes the stream for Preconfirmation* statuses. - let _prev_impl = !matches!( - status, - TxStatusMessage::Status(TransactionStatus::Submitted(_)) - ) ; - - if should_close_stream { + if status.is_final() { if let Some(close) = close.take() { let _ = close.send(()); } From 99238850c673ca1801b30f033e22046b0858bad2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Chabowski?= Date: Wed, 12 Mar 2025 14:54:07 +0100 Subject: [PATCH 12/76] Update changelog --- .changes/changed/2865.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 .changes/changed/2865.md diff --git a/.changes/changed/2865.md b/.changes/changed/2865.md new file mode 100644 index 00000000000..c3328fd192a --- /dev/null +++ b/.changes/changed/2865.md @@ -0,0 +1 @@ +Classify the transactions statuses related to preconfirmations as interim, similarly to `Submitted`, treating only `Failure`, `Success`, and `SqueezedOut` as final. \ No newline at end of file From 932b524d83879f627c419840e35f86b6a6a207f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Chabowski?= Date: Wed, 12 Mar 2025 15:09:37 +0100 Subject: [PATCH 13/76] Update schema --- crates/client/assets/schema.sdl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/client/assets/schema.sdl b/crates/client/assets/schema.sdl index 54e6fa961aa..60b2914e78c 100644 --- a/crates/client/assets/schema.sdl +++ b/crates/client/assets/schema.sdl @@ -1342,8 +1342,8 @@ type Subscription { submitAndAwait(tx: HexString!): TransactionStatus! """ Submits the transaction to the `TxPool` and returns a stream of events. - Compared to the `submitAndAwait`, the stream also contains ` - SubmittedStatus` as an intermediate state. + Compared to the `submitAndAwait`, the stream also contains + `SubmittedStatus` as an intermediate state. """ submitAndAwaitStatus(tx: HexString!): TransactionStatus! contractStorageSlots(contractId: ContractId!): StorageSlot! From a0b531fc9a34d5710a76ad8a9e9124f7e198c9eb Mon Sep 17 00:00:00 2001 From: Mitch Turner Date: Wed, 12 Mar 2025 10:11:50 -0600 Subject: [PATCH 14/76] Add basic impl --- .../src/service/adapters/tx_status_manager.rs | 2 ++ .../signature_verification.rs | 31 +++++++++++++++++++ crates/fuel-core/src/service/sub_services.rs | 23 ++++++++------ crates/services/tx_status_manager/src/lib.rs | 2 +- 4 files changed, 47 insertions(+), 11 deletions(-) create mode 100644 crates/fuel-core/src/service/adapters/tx_status_manager/signature_verification.rs diff --git a/crates/fuel-core/src/service/adapters/tx_status_manager.rs b/crates/fuel-core/src/service/adapters/tx_status_manager.rs index 7030ae502fb..e7585e03e09 100644 --- a/crates/fuel-core/src/service/adapters/tx_status_manager.rs +++ b/crates/fuel-core/src/service/adapters/tx_status_manager.rs @@ -3,6 +3,8 @@ use fuel_core_tx_status_manager::ports::P2PPreConfirmationGossipData; use super::P2PAdapter; +pub mod signature_verification; + #[cfg(feature = "p2p")] impl fuel_core_tx_status_manager::ports::P2PSubscriptions for P2PAdapter { type GossipedStatuses = P2PPreConfirmationGossipData; diff --git a/crates/fuel-core/src/service/adapters/tx_status_manager/signature_verification.rs b/crates/fuel-core/src/service/adapters/tx_status_manager/signature_verification.rs new file mode 100644 index 00000000000..5b2e49c3c20 --- /dev/null +++ b/crates/fuel-core/src/service/adapters/tx_status_manager/signature_verification.rs @@ -0,0 +1,31 @@ +use fuel_core_tx_status_manager::service::SignatureVerification; +use fuel_core_types::{ + fuel_tx::Bytes64, + services::{ + p2p::{ + DelegatePublicKey, + ProtocolSignature, + Sealed, + }, + preconfirmation::Preconfirmations, + }, +}; + +pub struct PreconfirmationSignatureVerification; + +impl SignatureVerification for PreconfirmationSignatureVerification { + async fn add_new_delegate( + &mut self, + _delegate: DelegatePublicKey, + _protocol_signature: ProtocolSignature, + ) -> bool { + true + } + + async fn check_preconfirmation_signature( + &mut self, + _sealed: &Sealed, + ) -> bool { + true + } +} diff --git a/crates/fuel-core/src/service/sub_services.rs b/crates/fuel-core/src/service/sub_services.rs index 38564a8b76d..48bbfaa48b8 100644 --- a/crates/fuel-core/src/service/sub_services.rs +++ b/crates/fuel-core/src/service/sub_services.rs @@ -41,6 +41,15 @@ use crate::service::adapters::consensus_module::poa::pre_confirmation_signature: tx_receiver::PreconfirmationsReceiver, }; +use super::{ + adapters::{ + FuelBlockSigner, + P2PAdapter, + TxStatusManagerAdapter, + }, + genesis::create_genesis_block, + DbType, +}; use crate::{ combined_database::CombinedDatabase, database::Database, @@ -58,6 +67,7 @@ use crate::{ graphql_api::GraphQLBlockImporter, import_result_provider::ImportResultProvider, ready_signal::ReadySignal, + tx_status_manager::signature_verification::PreconfirmationSignatureVerification, BlockImporterAdapter, BlockProducerAdapter, ChainStateInfoProvider, @@ -76,16 +86,6 @@ use crate::{ }, }; -use super::{ - adapters::{ - FuelBlockSigner, - P2PAdapter, - TxStatusManagerAdapter, - }, - genesis::create_genesis_block, - DbType, -}; - pub type PoAService = fuel_core_poa::Service< TxPoolAdapter, BlockProducerAdapter, @@ -257,8 +257,11 @@ pub fn init_sub_services( universal_gas_price_provider.clone(), ); + let signature_verification = PreconfirmationSignatureVerification; + let tx_status_manager = fuel_core_tx_status_manager::new_service( p2p_adapter.clone(), + signature_verification, config.tx_status_manager.clone(), ); let tx_status_manager_adapter = diff --git a/crates/services/tx_status_manager/src/lib.rs b/crates/services/tx_status_manager/src/lib.rs index 76da0b1aba5..f577c27b942 100644 --- a/crates/services/tx_status_manager/src/lib.rs +++ b/crates/services/tx_status_manager/src/lib.rs @@ -9,7 +9,7 @@ pub mod config; mod error; mod manager; pub mod ports; -mod service; +pub mod service; mod subscriptions; mod tx_status_stream; mod update_sender; From c056e362f834e48c75b591a185f0a78ffe23996d Mon Sep 17 00:00:00 2001 From: Mitch Turner Date: Wed, 12 Mar 2025 10:26:13 -0600 Subject: [PATCH 15/76] Fix trait method signature --- .../signature_verification.rs | 7 +++-- .../services/tx_status_manager/src/service.rs | 31 +++++++------------ .../tx_status_manager/src/tests/mod.rs | 17 +++++----- 3 files changed, 26 insertions(+), 29 deletions(-) diff --git a/crates/fuel-core/src/service/adapters/tx_status_manager/signature_verification.rs b/crates/fuel-core/src/service/adapters/tx_status_manager/signature_verification.rs index 5b2e49c3c20..0b9f6652eaf 100644 --- a/crates/fuel-core/src/service/adapters/tx_status_manager/signature_verification.rs +++ b/crates/fuel-core/src/service/adapters/tx_status_manager/signature_verification.rs @@ -3,6 +3,7 @@ use fuel_core_types::{ fuel_tx::Bytes64, services::{ p2p::{ + DelegatePreConfirmationKey, DelegatePublicKey, ProtocolSignature, Sealed, @@ -16,8 +17,10 @@ pub struct PreconfirmationSignatureVerification; impl SignatureVerification for PreconfirmationSignatureVerification { async fn add_new_delegate( &mut self, - _delegate: DelegatePublicKey, - _protocol_signature: ProtocolSignature, + _sealed: &Sealed< + DelegatePreConfirmationKey, + ProtocolSignature, + >, ) -> bool { true } diff --git a/crates/services/tx_status_manager/src/service.rs b/crates/services/tx_status_manager/src/service.rs index 1d4c29eebdf..fe7acbed389 100644 --- a/crates/services/tx_status_manager/src/service.rs +++ b/crates/services/tx_status_manager/src/service.rs @@ -25,6 +25,7 @@ use fuel_core_types::{ }, services::{ p2p::{ + DelegatePreConfirmationKey, DelegatePublicKey, GossipData, PreConfirmationMessage, @@ -117,8 +118,7 @@ pub trait SignatureVerification: Send { /// Adds a new delegate signature to verify the preconfirmations fn add_new_delegate( &mut self, - delegate: DelegatePublicKey, - protocol_signature: ProtocolSignature, + sealed: &Sealed, ProtocolSignature>, ) -> impl Future + Send; /// Checks pre-confirmation signature @@ -153,12 +153,7 @@ impl Task { "Received new delegate signature from peer: {:?}", sealed.entity.public_key ); - let Sealed { signature, entity } = sealed; - let delegate_key = entity.public_key; - let _ = self - .signature_verification - .add_new_delegate(delegate_key, signature) - .await; + let _ = self.signature_verification.add_new_delegate(&sealed).await; let drained = std::mem::take(&mut self.early_preconfirmations); for sealed in drained { if self @@ -438,21 +433,19 @@ mod tests { tokio::time::sleep(Duration::from_millis(100)).await; // then - let (actual_delegate_key, actual_protocol_signature) = new_delegate_handle + let actual = new_delegate_handle .new_delegate_receiver .recv() .await .unwrap(); - let (expected_delegate_key, expected_protocol_signature) = - if let P2PPreConfirmationMessage::Delegate(sealed) = - delegate_signature_message.data.unwrap() - { - (sealed.entity.public_key, sealed.signature) - } else { - panic!("Expected Delegate message"); - }; - assert_eq!(actual_delegate_key, expected_delegate_key); - assert_eq!(actual_protocol_signature, expected_protocol_signature); + let expected = if let PreConfirmationMessage::Delegate(delegate_info) = + delegate_signature_message.data.unwrap() + { + delegate_info + } else { + panic!("Expected Delegate message"); + }; + assert_eq!(actual, expected); } async fn all_streams_return_success(streams: Vec) -> bool { diff --git a/crates/services/tx_status_manager/src/tests/mod.rs b/crates/services/tx_status_manager/src/tests/mod.rs index 2997b6fc649..b6aab80dbdf 100644 --- a/crates/services/tx_status_manager/src/tests/mod.rs +++ b/crates/services/tx_status_manager/src/tests/mod.rs @@ -25,15 +25,20 @@ mod tests_update_stream_state; mod universe; mod utils; +use fuel_core_types::services::p2p::DelegatePreConfirmationKey; use tracing_subscriber as _; pub(crate) struct FakeSignatureVerification { - new_delegate_sender: mpsc::Sender<(DelegatePublicKey, ProtocolSignature)>, + new_delegate_sender: mpsc::Sender< + Sealed, ProtocolSignature>, + >, preconfirmation_signature_success: Arc, } pub(crate) struct FakeSignatureVerificationHandles { - pub new_delegate_receiver: mpsc::Receiver<(DelegatePublicKey, ProtocolSignature)>, + pub new_delegate_receiver: mpsc::Receiver< + Sealed, ProtocolSignature>, + >, pub verification_result: Arc, } @@ -68,14 +73,10 @@ impl FakeSignatureVerification { impl SignatureVerification for FakeSignatureVerification { async fn add_new_delegate( &mut self, - _delegate: DelegatePublicKey, - _protocol_signature: ProtocolSignature, + sealed: &Sealed, ProtocolSignature>, ) -> bool { tracing::debug!("FakeSignatureVerification::add_new_delegate"); - self.new_delegate_sender - .send((_delegate, _protocol_signature)) - .await - .unwrap(); + self.new_delegate_sender.send(sealed.clone()).await.unwrap(); true } From d03be7a4ee4768b2c138a6cd64b6cdc4ab396593 Mon Sep 17 00:00:00 2001 From: Mitch Turner Date: Wed, 12 Mar 2025 17:30:37 -0600 Subject: [PATCH 16/76] Add three tests and implementations --- .../signature_verification.rs | 195 +++++++++++++++++- crates/fuel-core/src/service/sub_services.rs | 9 +- 2 files changed, 194 insertions(+), 10 deletions(-) diff --git a/crates/fuel-core/src/service/adapters/tx_status_manager/signature_verification.rs b/crates/fuel-core/src/service/adapters/tx_status_manager/signature_verification.rs index 0b9f6652eaf..2e5f0ffd21c 100644 --- a/crates/fuel-core/src/service/adapters/tx_status_manager/signature_verification.rs +++ b/crates/fuel-core/src/service/adapters/tx_status_manager/signature_verification.rs @@ -1,5 +1,11 @@ use fuel_core_tx_status_manager::service::SignatureVerification; use fuel_core_types::{ + // ed25519::Signature, + // ed25519_dalek::Verifier, + fuel_crypto::{ + Message, + PublicKey, + }, fuel_tx::Bytes64, services::{ p2p::{ @@ -10,25 +16,198 @@ use fuel_core_types::{ }, preconfirmation::Preconfirmations, }, + tai64::Tai64, }; +use std::collections::VecDeque; + +pub struct PreconfirmationSignatureVerification { + protocol_pubkey: PublicKey, + delegate_keys: VecDeque<(Tai64, DelegatePublicKey)>, +} -pub struct PreconfirmationSignatureVerification; +impl PreconfirmationSignatureVerification { + pub fn new(protocol_pubkey: PublicKey) -> Self { + Self { + protocol_pubkey, + delegate_keys: VecDeque::new(), + } + } +} impl SignatureVerification for PreconfirmationSignatureVerification { async fn add_new_delegate( &mut self, - _sealed: &Sealed< - DelegatePreConfirmationKey, - ProtocolSignature, - >, + sealed: &Sealed, ProtocolSignature>, ) -> bool { - true + let Sealed { entity, signature } = sealed; + let bytes = postcard::to_allocvec(&entity).unwrap(); + let message = Message::new(&bytes); + let verified = signature.verify(&self.protocol_pubkey, &message); + match verified { + Ok(_) => { + self.delegate_keys + .push_front((entity.expiration, entity.public_key)); + true + } + Err(_) => false, + } } async fn check_preconfirmation_signature( &mut self, - _sealed: &Sealed, + sealed: &Sealed, ) -> bool { - true + let expiration = sealed.entity.expiration; + let delegate_key = self + .delegate_keys + .iter() + .find(|(exp, _)| exp == &expiration) + .map(|(_, key)| key); + if let Some(_delegate_key) = delegate_key { + // let bytes = postcard::to_allocvec(&sealed.entity).unwrap(); + // let signature = Signature::from_bytes(&sealed.signature); + // let verified = delegate_key.verify(&bytes, &signature); + // match verified { + // Ok(_) => true, + // Err(_) => false, + // } + true + } else { + false + } + } +} + +#[cfg(test)] +mod tests { + #![allow(non_snake_case)] + + use super::*; + use fuel_core_types::{ + ed25519_dalek::{ + Signer, + SigningKey as DalekSigningKey, + VerifyingKey as DalekVerifyingKey, + }, + fuel_crypto::{ + Message, + SecretKey, + Signature, + }, + tai64::Tai64, + }; + + fn valid_sealed_delegate_signature( + protocol_secret_key: SecretKey, + delegate_public_key: DelegatePublicKey, + expiration: Tai64, + ) -> Sealed, ProtocolSignature> { + let entity = DelegatePreConfirmationKey { + public_key: delegate_public_key, + expiration, + }; + let bytes = postcard::to_allocvec(&entity).unwrap(); + let message = Message::new(&bytes); + let signature = Signature::sign(&protocol_secret_key, &message); + Sealed { entity, signature } + } + + fn arb_valid_sealed_delegate_signature( + protocol_secret_key: SecretKey, + ) -> Sealed, ProtocolSignature> { + let delegate_public_key = DelegatePublicKey::default(); + let expiration = Tai64(u64::MAX); + valid_sealed_delegate_signature( + protocol_secret_key, + delegate_public_key, + expiration, + ) + } + + fn valid_pre_confirmation_signature( + delegate_private_key: DalekSigningKey, + expiration: Tai64, + ) -> Sealed { + let entity = Preconfirmations { + expiration, + preconfirmations: vec![], + }; + let bytes = postcard::to_allocvec(&entity).unwrap(); + let typed_signature = delegate_private_key.sign(&bytes); + let signature = Bytes64::new(typed_signature.to_bytes()); + Sealed { entity, signature } + } + + fn protocol_key_pair() -> (SecretKey, PublicKey) { + let secret_key = SecretKey::default(); + let public_key = secret_key.public_key(); + (secret_key, public_key) + } + + fn delegate_key_pair() -> (DalekSigningKey, DalekVerifyingKey) { + let secret_key = [99u8; 32]; + let secret_key = DalekSigningKey::from_bytes(&secret_key); + let public_key = secret_key.verifying_key(); + (secret_key, public_key) + } + + #[tokio::test] + async fn add_new_delegate__includes_delegate_if_protocol_signature_verified() { + // given + let (secret_key, public_key) = protocol_key_pair(); + let mut adapter = PreconfirmationSignatureVerification::new(public_key); + let valid_delegate_signature = arb_valid_sealed_delegate_signature(secret_key); + + // when + let added = adapter.add_new_delegate(&valid_delegate_signature).await; + + // then + assert!(added); + } + + #[tokio::test] + async fn check_preconfirmation_signature__can_verify_valid_pre_confirmations_signature_for_known_key( + ) { + // given + let (protocol_secret_key, protocol_public_key) = protocol_key_pair(); + let (delegate_secret_key, delegate_public_key) = delegate_key_pair(); + let mut adapter = PreconfirmationSignatureVerification::new(protocol_public_key); + let expiration = Tai64(100u64); + let valid_delegate_signature = valid_sealed_delegate_signature( + protocol_secret_key, + delegate_public_key, + expiration, + ); + let valid_pre_confirmation_signature = + valid_pre_confirmation_signature(delegate_secret_key, expiration); + let _ = adapter.add_new_delegate(&valid_delegate_signature).await; + + // when + let verified = adapter + .check_preconfirmation_signature(&valid_pre_confirmation_signature) + .await; + + // then + assert!(verified); + } + + #[tokio::test] + async fn check_preconfirmation_signature__will_not_verify_pre_confirmations_signature_for_unknown_delegate_key( + ) { + // given + let (_, protocol_public_key) = protocol_key_pair(); + let (delegate_secret_key, _) = delegate_key_pair(); + let mut adapter = PreconfirmationSignatureVerification::new(protocol_public_key); + let expiration = Tai64(100u64); + let valid_pre_confirmation_signature = + valid_pre_confirmation_signature(delegate_secret_key, expiration); + + // when + let verified = adapter + .check_preconfirmation_signature(&valid_pre_confirmation_signature) + .await; + + // then + assert!(!verified); } } diff --git a/crates/fuel-core/src/service/sub_services.rs b/crates/fuel-core/src/service/sub_services.rs index 48bbfaa48b8..ebc64c40976 100644 --- a/crates/fuel-core/src/service/sub_services.rs +++ b/crates/fuel-core/src/service/sub_services.rs @@ -25,7 +25,10 @@ use fuel_core_storage::{ }; #[cfg(feature = "relayer")] use fuel_core_types::blockchain::primitives::DaBlockHeight; -use fuel_core_types::signer::SignMode; +use fuel_core_types::{ + fuel_crypto::PublicKey, + signer::SignMode, +}; #[cfg(feature = "relayer")] use crate::relayer::Config as RelayerConfig; @@ -257,7 +260,9 @@ pub fn init_sub_services( universal_gas_price_provider.clone(), ); - let signature_verification = PreconfirmationSignatureVerification; + // TODO: Use real values + let signature_verification = + PreconfirmationSignatureVerification::new(PublicKey::default()); let tx_status_manager = fuel_core_tx_status_manager::new_service( p2p_adapter.clone(), From 88f80a5d07f51be4656e7563d7522b07b3219390 Mon Sep 17 00:00:00 2001 From: Mitch Turner Date: Wed, 12 Mar 2025 22:30:04 -0600 Subject: [PATCH 17/76] Add test for signature verification --- .../signature_verification.rs | 90 +++++++++++++++---- 1 file changed, 73 insertions(+), 17 deletions(-) diff --git a/crates/fuel-core/src/service/adapters/tx_status_manager/signature_verification.rs b/crates/fuel-core/src/service/adapters/tx_status_manager/signature_verification.rs index 2e5f0ffd21c..4ada9ada2a0 100644 --- a/crates/fuel-core/src/service/adapters/tx_status_manager/signature_verification.rs +++ b/crates/fuel-core/src/service/adapters/tx_status_manager/signature_verification.rs @@ -1,7 +1,7 @@ use fuel_core_tx_status_manager::service::SignatureVerification; use fuel_core_types::{ - // ed25519::Signature, - // ed25519_dalek::Verifier, + ed25519::Signature, + ed25519_dalek::Verifier, fuel_crypto::{ Message, PublicKey, @@ -32,6 +32,28 @@ impl PreconfirmationSignatureVerification { delegate_keys: VecDeque::new(), } } + + fn verify_preconfirmation( + delegate_key: &DelegatePublicKey, + sealed: &Sealed, + ) -> bool { + let bytes = match postcard::to_allocvec(&sealed.entity) { + Ok(bytes) => bytes, + Err(e) => { + tracing::warn!("Failed to serialize preconfirmation: {e:?}"); + return false; + } + }; + + let signature = Signature::from_bytes(&sealed.signature); + match delegate_key.verify(&bytes, &signature) { + Ok(_) => true, + Err(e) => { + tracing::warn!("Failed to verify preconfirmation signature: {e:?}"); + false + } + } + } } impl SignatureVerification for PreconfirmationSignatureVerification { @@ -58,23 +80,11 @@ impl SignatureVerification for PreconfirmationSignatureVerification { sealed: &Sealed, ) -> bool { let expiration = sealed.entity.expiration; - let delegate_key = self - .delegate_keys + self.delegate_keys .iter() .find(|(exp, _)| exp == &expiration) - .map(|(_, key)| key); - if let Some(_delegate_key) = delegate_key { - // let bytes = postcard::to_allocvec(&sealed.entity).unwrap(); - // let signature = Signature::from_bytes(&sealed.signature); - // let verified = delegate_key.verify(&bytes, &signature); - // match verified { - // Ok(_) => true, - // Err(_) => false, - // } - true - } else { - false - } + .map(|(_, delegate_key)| Self::verify_preconfirmation(delegate_key, sealed)) + .unwrap_or(false) } } @@ -138,6 +148,26 @@ mod tests { Sealed { entity, signature } } + fn bad_pre_confirmation_signature( + delegate_private_key: DalekSigningKey, + expiration: Tai64, + ) -> Sealed { + let mut mutated_private_key = delegate_private_key.to_bytes(); + for byte in mutated_private_key.iter_mut() { + *byte = byte.wrapping_add(1); + } + let mutated_delegate_private_key = + DalekSigningKey::from_bytes(&mutated_private_key); + let entity = Preconfirmations { + expiration, + preconfirmations: vec![], + }; + let bytes = postcard::to_allocvec(&entity).unwrap(); + let typed_signature = mutated_delegate_private_key.sign(&bytes); + let signature = Bytes64::new(typed_signature.to_bytes()); + Sealed { entity, signature } + } + fn protocol_key_pair() -> (SecretKey, PublicKey) { let secret_key = SecretKey::default(); let public_key = secret_key.public_key(); @@ -210,4 +240,30 @@ mod tests { // then assert!(!verified); } + + #[tokio::test] + async fn check_preconfirmation_signature__will_not_verify_pre_confirmations_signature_with_invalid_signature( + ) { + // given + let (protocol_secret_key, protocol_public_key) = protocol_key_pair(); + let (delegate_secret_key, delegate_public_key) = delegate_key_pair(); + let mut adapter = PreconfirmationSignatureVerification::new(protocol_public_key); + let expiration = Tai64(100u64); + let valid_delegate_signature = valid_sealed_delegate_signature( + protocol_secret_key, + delegate_public_key, + expiration, + ); + let invalid_pre_confirmation_signature = + bad_pre_confirmation_signature(delegate_secret_key, expiration); + let _ = adapter.add_new_delegate(&valid_delegate_signature).await; + + // when + let verified = adapter + .check_preconfirmation_signature(&invalid_pre_confirmation_signature) + .await; + + // then + assert!(!verified); + } } From 19368a91aec0dce9dee5e5055af9ac155a2e0a2f Mon Sep 17 00:00:00 2001 From: Mitch Turner Date: Wed, 12 Mar 2025 23:03:59 -0600 Subject: [PATCH 18/76] add expiration checks --- .../signature_verification.rs | 48 +++++++++++++++++-- 1 file changed, 43 insertions(+), 5 deletions(-) diff --git a/crates/fuel-core/src/service/adapters/tx_status_manager/signature_verification.rs b/crates/fuel-core/src/service/adapters/tx_status_manager/signature_verification.rs index 4ada9ada2a0..7f954bd66ae 100644 --- a/crates/fuel-core/src/service/adapters/tx_status_manager/signature_verification.rs +++ b/crates/fuel-core/src/service/adapters/tx_status_manager/signature_verification.rs @@ -18,18 +18,17 @@ use fuel_core_types::{ }, tai64::Tai64, }; -use std::collections::VecDeque; pub struct PreconfirmationSignatureVerification { protocol_pubkey: PublicKey, - delegate_keys: VecDeque<(Tai64, DelegatePublicKey)>, + delegate_keys: Vec<(Tai64, DelegatePublicKey)>, } impl PreconfirmationSignatureVerification { pub fn new(protocol_pubkey: PublicKey) -> Self { Self { protocol_pubkey, - delegate_keys: VecDeque::new(), + delegate_keys: Vec::new(), } } @@ -54,6 +53,11 @@ impl PreconfirmationSignatureVerification { } } } + + fn remove_expired_delegates(&mut self) { + let now = Tai64::now(); + self.delegate_keys.retain(|(exp, _)| exp > &now); + } } impl SignatureVerification for PreconfirmationSignatureVerification { @@ -65,10 +69,11 @@ impl SignatureVerification for PreconfirmationSignatureVerification { let bytes = postcard::to_allocvec(&entity).unwrap(); let message = Message::new(&bytes); let verified = signature.verify(&self.protocol_pubkey, &message); + self.remove_expired_delegates(); match verified { Ok(_) => { self.delegate_keys - .push_front((entity.expiration, entity.public_key)); + .push((entity.expiration, entity.public_key)); true } Err(_) => false, @@ -80,6 +85,11 @@ impl SignatureVerification for PreconfirmationSignatureVerification { sealed: &Sealed, ) -> bool { let expiration = sealed.entity.expiration; + let now = Tai64::now(); + if now > expiration { + tracing::warn!("Preconfirmation signature expired: {now:?} > {expiration:?}"); + return false; + } self.delegate_keys .iter() .find(|(exp, _)| exp == &expiration) @@ -202,7 +212,7 @@ mod tests { let (protocol_secret_key, protocol_public_key) = protocol_key_pair(); let (delegate_secret_key, delegate_public_key) = delegate_key_pair(); let mut adapter = PreconfirmationSignatureVerification::new(protocol_public_key); - let expiration = Tai64(100u64); + let expiration = Tai64(u64::MAX); let valid_delegate_signature = valid_sealed_delegate_signature( protocol_secret_key, delegate_public_key, @@ -266,4 +276,32 @@ mod tests { // then assert!(!verified); } + + #[tokio::test] + async fn check_preconfirmation_signature__will_not_verify_pre_confirmations_signature_expired_delegate_key( + ) { + // given + let (protocol_secret_key, protocol_public_key) = protocol_key_pair(); + let (delegate_secret_key, delegate_public_key) = delegate_key_pair(); + let mut adapter = PreconfirmationSignatureVerification::new(protocol_public_key); + let expiration = Tai64::now(); + let valid_delegate_signature = valid_sealed_delegate_signature( + protocol_secret_key, + delegate_public_key, + expiration, + ); + let valid_pre_confirmation_signature = + valid_pre_confirmation_signature(delegate_secret_key, expiration); + let _ = adapter.add_new_delegate(&valid_delegate_signature).await; + + // when + // `Tai64` only has granularity of seconds, so we need to wait for the next second + tokio::time::sleep(std::time::Duration::from_secs(1)).await; + let verified = adapter + .check_preconfirmation_signature(&valid_pre_confirmation_signature) + .await; + + // then + assert!(!verified); + } } From 4021a6d45ae2c230daffb32108f44cc02f7ed114 Mon Sep 17 00:00:00 2001 From: Mitch Turner Date: Wed, 12 Mar 2025 23:10:02 -0600 Subject: [PATCH 19/76] add test for allowing multiple delegate keys --- .../signature_verification.rs | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/crates/fuel-core/src/service/adapters/tx_status_manager/signature_verification.rs b/crates/fuel-core/src/service/adapters/tx_status_manager/signature_verification.rs index 7f954bd66ae..2fb53c38f31 100644 --- a/crates/fuel-core/src/service/adapters/tx_status_manager/signature_verification.rs +++ b/crates/fuel-core/src/service/adapters/tx_status_manager/signature_verification.rs @@ -304,4 +304,32 @@ mod tests { // then assert!(!verified); } + + #[tokio::test] + async fn check_preconfirmation_signature__can_track_multipl_delegate_keys() { + // given + let (protocol_secret_key, protocol_public_key) = protocol_key_pair(); + let (delegate_secret_key, delegate_public_key) = delegate_key_pair(); + let mut adapter = PreconfirmationSignatureVerification::new(protocol_public_key); + let first_expiration = Tai64(u64::MAX - 200); + for expiration_modifier in 0..100u64 { + let expiration = first_expiration + expiration_modifier; + let valid_delegate_signature = valid_sealed_delegate_signature( + protocol_secret_key, + delegate_public_key, + expiration, + ); + let _ = adapter.add_new_delegate(&valid_delegate_signature).await; + } + let valid_pre_confirmation_signature = + valid_pre_confirmation_signature(delegate_secret_key, first_expiration); + + // when + let verified = adapter + .check_preconfirmation_signature(&valid_pre_confirmation_signature) + .await; + + // then + assert!(verified); + } } From d92672bf8801d9783f90205061b1902840b6051c Mon Sep 17 00:00:00 2001 From: Mitch Turner Date: Wed, 12 Mar 2025 23:13:21 -0600 Subject: [PATCH 20/76] Convert to hashmap --- .../tx_status_manager/signature_verification.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/crates/fuel-core/src/service/adapters/tx_status_manager/signature_verification.rs b/crates/fuel-core/src/service/adapters/tx_status_manager/signature_verification.rs index 2fb53c38f31..dcb18be96f7 100644 --- a/crates/fuel-core/src/service/adapters/tx_status_manager/signature_verification.rs +++ b/crates/fuel-core/src/service/adapters/tx_status_manager/signature_verification.rs @@ -18,17 +18,18 @@ use fuel_core_types::{ }, tai64::Tai64, }; +use std::collections::HashMap; pub struct PreconfirmationSignatureVerification { protocol_pubkey: PublicKey, - delegate_keys: Vec<(Tai64, DelegatePublicKey)>, + delegate_keys: HashMap, } impl PreconfirmationSignatureVerification { pub fn new(protocol_pubkey: PublicKey) -> Self { Self { protocol_pubkey, - delegate_keys: Vec::new(), + delegate_keys: HashMap::new(), } } @@ -56,7 +57,7 @@ impl PreconfirmationSignatureVerification { fn remove_expired_delegates(&mut self) { let now = Tai64::now(); - self.delegate_keys.retain(|(exp, _)| exp > &now); + self.delegate_keys.retain(|exp, _| exp > &now); } } @@ -73,7 +74,7 @@ impl SignatureVerification for PreconfirmationSignatureVerification { match verified { Ok(_) => { self.delegate_keys - .push((entity.expiration, entity.public_key)); + .insert(entity.expiration, entity.public_key); true } Err(_) => false, @@ -91,9 +92,8 @@ impl SignatureVerification for PreconfirmationSignatureVerification { return false; } self.delegate_keys - .iter() - .find(|(exp, _)| exp == &expiration) - .map(|(_, delegate_key)| Self::verify_preconfirmation(delegate_key, sealed)) + .get(&expiration) + .map(|delegate_key| Self::verify_preconfirmation(delegate_key, sealed)) .unwrap_or(false) } } From e8975e49100d3e583c896d7aa6cd61ffdf5076ed Mon Sep 17 00:00:00 2001 From: Mitch Turner Date: Wed, 12 Mar 2025 23:26:13 -0600 Subject: [PATCH 21/76] Update CHANGELOG --- .changes/added/2866.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 .changes/added/2866.md diff --git a/.changes/added/2866.md b/.changes/added/2866.md new file mode 100644 index 00000000000..c57063830a2 --- /dev/null +++ b/.changes/added/2866.md @@ -0,0 +1 @@ +Introduce adapter for signature verification in the tx status manager service \ No newline at end of file From 345209a42d04d03c7e886ea563e026a31a23435e Mon Sep 17 00:00:00 2001 From: Mitch Turner Date: Wed, 12 Mar 2025 23:28:53 -0600 Subject: [PATCH 22/76] Fix spelling --- .../adapters/tx_status_manager/signature_verification.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/fuel-core/src/service/adapters/tx_status_manager/signature_verification.rs b/crates/fuel-core/src/service/adapters/tx_status_manager/signature_verification.rs index dcb18be96f7..9f65f880adb 100644 --- a/crates/fuel-core/src/service/adapters/tx_status_manager/signature_verification.rs +++ b/crates/fuel-core/src/service/adapters/tx_status_manager/signature_verification.rs @@ -306,7 +306,7 @@ mod tests { } #[tokio::test] - async fn check_preconfirmation_signature__can_track_multipl_delegate_keys() { + async fn check_preconfirmation_signature__can_track_multiple_delegate_keys() { // given let (protocol_secret_key, protocol_public_key) = protocol_key_pair(); let (delegate_secret_key, delegate_public_key) = delegate_key_pair(); From b26bbd3d083702ec61694cc0e629a93456ab2197 Mon Sep 17 00:00:00 2001 From: Mitchell Turner Date: Wed, 12 Mar 2025 23:29:12 -0600 Subject: [PATCH 23/76] Update crates/services/tx_status_manager/src/service.rs Co-authored-by: Green Baneling --- crates/services/tx_status_manager/src/service.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/services/tx_status_manager/src/service.rs b/crates/services/tx_status_manager/src/service.rs index e4e40d96945..c110e9f2736 100644 --- a/crates/services/tx_status_manager/src/service.rs +++ b/crates/services/tx_status_manager/src/service.rs @@ -112,7 +112,6 @@ pub struct Task { early_preconfirmations: Vec>, } -#[allow(dead_code)] /// Interface for signature verification of preconfirmations pub trait SignatureVerification: Send { /// Adds a new delegate signature to verify the preconfirmations From f72b1ac7743cd188139df95f2bbf0f436c11f586 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Chabowski?= Date: Thu, 13 Mar 2025 09:34:11 +0100 Subject: [PATCH 24/76] `PreConfirmationSqueezedOut` is considered final --- .changes/changed/2865.md | 2 +- crates/client/assets/schema.sdl | 8 ++++---- crates/client/src/client/types.rs | 4 ++-- crates/fuel-core/src/query/subscriptions/test.rs | 8 ++++---- crates/fuel-core/src/schema/tx.rs | 8 ++++---- crates/fuel-core/src/schema/tx/types.rs | 4 ++-- crates/types/src/services/txpool.rs | 4 ++-- 7 files changed, 19 insertions(+), 19 deletions(-) diff --git a/.changes/changed/2865.md b/.changes/changed/2865.md index c3328fd192a..f489f8f83be 100644 --- a/.changes/changed/2865.md +++ b/.changes/changed/2865.md @@ -1 +1 @@ -Classify the transactions statuses related to preconfirmations as interim, similarly to `Submitted`, treating only `Failure`, `Success`, and `SqueezedOut` as final. \ No newline at end of file +Consider the following transaction statuses as final: `Success`, `Failure`, `SqueezedOut`, `PreConfirmationSqueezedOut`. All other statuses will be considered transient. diff --git a/crates/client/assets/schema.sdl b/crates/client/assets/schema.sdl index c22ce943ac6..3752e2e99ab 100644 --- a/crates/client/assets/schema.sdl +++ b/crates/client/assets/schema.sdl @@ -1318,10 +1318,10 @@ type SubmittedStatus { type Subscription { """ Returns a stream of status updates for the given transaction id. - If the current status is [`TransactionStatus::Success`], [`TransactionStatus::SqueezedOut`] - or [`TransactionStatus::Failed`] the stream will return that and end immediately. - Other, intermediate statuses will also be returned but the stream - will remain active and wait for a future updates. + If the current status is [`TransactionStatus::Success`], [`TransactionStatus::Failed`], + [`TransactionStatus::SqueezedOut`] or [`TransactionStatus::PreconfirmationSqueezedOut`] + the stream will return that and end immediately. Other, intermediate statuses + will also be returned but the stream will remain active and wait for a future updates. This stream will wait forever so it's advised to use within a timeout. diff --git a/crates/client/src/client/types.rs b/crates/client/src/client/types.rs index 535a9c952a9..fbbc536b771 100644 --- a/crates/client/src/client/types.rs +++ b/crates/client/src/client/types.rs @@ -159,10 +159,10 @@ impl TransactionStatus { match self { TransactionStatus::Success { .. } | TransactionStatus::Failure { .. } - | TransactionStatus::SqueezedOut { .. } => true, + | TransactionStatus::SqueezedOut { .. } + | TransactionStatus::PreconfirmationSqueezedOut { .. } => true, TransactionStatus::Submitted { .. } | TransactionStatus::PreconfirmationSuccess { .. } - | TransactionStatus::PreconfirmationSqueezedOut { .. } | TransactionStatus::PreconfirmationFailure { .. } => false, } } diff --git a/crates/fuel-core/src/query/subscriptions/test.rs b/crates/fuel-core/src/query/subscriptions/test.rs index d8d063da31e..1bdb6117e16 100644 --- a/crates/fuel-core/src/query/subscriptions/test.rs +++ b/crates/fuel-core/src/query/subscriptions/test.rs @@ -219,9 +219,9 @@ fn next_state(state: TransactionStatus) -> Flow { TransactionStatus::PreConfirmationFailure { .. } => { Flow::Continue(NotFinal::Preconfirmation) } - TransactionStatus::SqueezedOut { .. } => Flow::Break(FinalTxStatus::Squeezed), - TransactionStatus::PreConfirmationSqueezedOut { .. } => { - Flow::Continue(NotFinal::Preconfirmation) + TransactionStatus::SqueezedOut { .. } + | TransactionStatus::PreConfirmationSqueezedOut { .. } => { + Flow::Break(FinalTxStatus::Squeezed) } } } @@ -296,7 +296,7 @@ impl From for TxStatus { } crate::schema::tx::types::TransactionStatus::PreconfirmationSqueezedOut( _, - ) => TxStatus::Preconfirmed, + ) => TxStatus::Final(FinalTxStatus::Squeezed), crate::schema::tx::types::TransactionStatus::Failure(_) => { TxStatus::Final(FinalTxStatus::Failed) } diff --git a/crates/fuel-core/src/schema/tx.rs b/crates/fuel-core/src/schema/tx.rs index 7c2ade10231..adedbb231fd 100644 --- a/crates/fuel-core/src/schema/tx.rs +++ b/crates/fuel-core/src/schema/tx.rs @@ -608,10 +608,10 @@ pub struct TxStatusSubscription; #[Subscription] impl TxStatusSubscription { /// Returns a stream of status updates for the given transaction id. - /// If the current status is [`TransactionStatus::Success`], [`TransactionStatus::SqueezedOut`] - /// or [`TransactionStatus::Failed`] the stream will return that and end immediately. - /// Other, intermediate statuses will also be returned but the stream - /// will remain active and wait for a future updates. + /// If the current status is [`TransactionStatus::Success`], [`TransactionStatus::Failed`], + /// [`TransactionStatus::SqueezedOut`] or [`TransactionStatus::PreconfirmationSqueezedOut`] + /// the stream will return that and end immediately. Other, intermediate statuses + /// will also be returned but the stream will remain active and wait for a future updates. /// /// This stream will wait forever so it's advised to use within a timeout. /// diff --git a/crates/fuel-core/src/schema/tx/types.rs b/crates/fuel-core/src/schema/tx/types.rs index fbe77d91258..c2f482a6e7a 100644 --- a/crates/fuel-core/src/schema/tx/types.rs +++ b/crates/fuel-core/src/schema/tx/types.rs @@ -455,10 +455,10 @@ impl TransactionStatus { match self { TransactionStatus::Success(_) | TransactionStatus::Failure(_) - | TransactionStatus::SqueezedOut(_) => true, + | TransactionStatus::SqueezedOut(_) + | TransactionStatus::PreconfirmationSqueezedOut(_) => true, TransactionStatus::Submitted(_) | TransactionStatus::PreconfirmationSuccess(_) - | TransactionStatus::PreconfirmationSqueezedOut(_) | TransactionStatus::PreconfirmationFailure(_) => false, } } diff --git a/crates/types/src/services/txpool.rs b/crates/types/src/services/txpool.rs index a312323042d..2b853d72573 100644 --- a/crates/types/src/services/txpool.rs +++ b/crates/types/src/services/txpool.rs @@ -457,10 +457,10 @@ impl TransactionStatus { match self { TransactionStatus::Success(_) | TransactionStatus::Failure(_) - | TransactionStatus::SqueezedOut(_) => true, + | TransactionStatus::SqueezedOut(_) + | TransactionStatus::PreConfirmationSqueezedOut(_) => true, TransactionStatus::Submitted(_) | TransactionStatus::PreConfirmationSuccess(_) - | TransactionStatus::PreConfirmationSqueezedOut(_) | TransactionStatus::PreConfirmationFailure(_) => false, } } From 05e56723d7835eda6b654f763dd71464b7e288e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Chabowski?= <88321181+rafal-ch@users.noreply.github.com> Date: Thu, 13 Mar 2025 09:36:09 +0100 Subject: [PATCH 25/76] Update crates/client/src/client/types.rs Co-authored-by: Green Baneling --- crates/client/src/client/types.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/client/src/client/types.rs b/crates/client/src/client/types.rs index fbbc536b771..13368557da4 100644 --- a/crates/client/src/client/types.rs +++ b/crates/client/src/client/types.rs @@ -154,8 +154,7 @@ pub enum TransactionStatus { } impl TransactionStatus { - #[cfg(feature = "subscriptions")] - pub(super) fn is_final(&self) -> bool { + pub fn is_final(&self) -> bool { match self { TransactionStatus::Success { .. } | TransactionStatus::Failure { .. } From 310a9d1c25e7e8fa6bf97a1c9c36c2f6f205abb0 Mon Sep 17 00:00:00 2001 From: AurelienFT Date: Thu, 13 Mar 2025 14:23:01 +0100 Subject: [PATCH 26/76] Add compiling versions of the links --- Cargo.lock | 1 + crates/fuel-core/src/service/adapters.rs | 8 +-- .../pre_confirmation_signature/broadcast.rs | 8 +-- .../parent_signature.rs | 7 +-- .../src/service/adapters/executor.rs | 9 +-- crates/fuel-core/src/service/config.rs | 11 ++++ crates/fuel-core/src/service/sub_services.rs | 27 ++++++-- .../services/consensus_module/poa/Cargo.toml | 1 + .../src/pre_confirmation_signature_service.rs | 62 ++++++++++++++++++- .../config.rs | 8 +++ .../trigger.rs | 1 + crates/services/executor/src/executor.rs | 25 +++++--- crates/services/executor/src/ports.rs | 9 +-- .../src/preconfirmation_sender.rs | 6 +- tests/tests/preconfirmations.rs | 1 + 15 files changed, 141 insertions(+), 43 deletions(-) create mode 100644 crates/services/consensus_module/poa/src/pre_confirmation_signature_service/config.rs diff --git a/Cargo.lock b/Cargo.lock index acbf40354d8..49bc53c59a5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3851,6 +3851,7 @@ dependencies = [ "fuel-core-storage", "fuel-core-trace", "fuel-core-types 0.41.7", + "futures", "mockall", "rand 0.8.5", "serde", diff --git a/crates/fuel-core/src/service/adapters.rs b/crates/fuel-core/src/service/adapters.rs index 7074610d41b..0298d83c591 100644 --- a/crates/fuel-core/src/service/adapters.rs +++ b/crates/fuel-core/src/service/adapters.rs @@ -38,7 +38,7 @@ use fuel_core_types::{ Result as ExecutorResult, UncommittedResult, }, - preconfirmation::PreconfirmationStatus, + preconfirmation::Preconfirmation, }, signer::SignMode, tai64::Tai64, @@ -341,11 +341,11 @@ impl NewTxWaiter { #[derive(Clone)] pub struct PreconfirmationSender { - pub sender: tokio::sync::mpsc::Sender>, + pub sender: tokio::sync::mpsc::Sender>, } impl PreconfirmationSender { - pub fn new(sender: tokio::sync::mpsc::Sender>) -> Self { + pub fn new(sender: tokio::sync::mpsc::Sender>) -> Self { Self { sender } } } @@ -363,7 +363,7 @@ impl ExecutorAdapter { relayer_database: Database, config: fuel_core_upgradable_executor::config::Config, new_txs_watcher: tokio::sync::watch::Receiver<()>, - preconfirmation_sender: tokio::sync::mpsc::Sender>, + preconfirmation_sender: tokio::sync::mpsc::Sender>, ) -> Self { let executor = Executor::new(database, relayer_database, config); let preconfirmation_sender = PreconfirmationSender::new(preconfirmation_sender); diff --git a/crates/fuel-core/src/service/adapters/consensus_module/poa/pre_confirmation_signature/broadcast.rs b/crates/fuel-core/src/service/adapters/consensus_module/poa/pre_confirmation_signature/broadcast.rs index 5ed1340e7a8..22b4c023589 100644 --- a/crates/fuel-core/src/service/adapters/consensus_module/poa/pre_confirmation_signature/broadcast.rs +++ b/crates/fuel-core/src/service/adapters/consensus_module/poa/pre_confirmation_signature/broadcast.rs @@ -1,8 +1,6 @@ use crate::service::adapters::{ - consensus_module::poa::pre_confirmation_signature::{ - key_generator::Ed25519Key, - parent_signature::FuelParentSigner, - }, + consensus_module::poa::pre_confirmation_signature::key_generator::Ed25519Key, + FuelBlockSigner, P2PAdapter, }; use fuel_core_poa::pre_confirmation_signature_service::{ @@ -36,7 +34,7 @@ use fuel_core_types::{ use std::sync::Arc; impl Broadcast for P2PAdapter { - type ParentKey = FuelParentSigner; + type ParentKey = FuelBlockSigner; type DelegateKey = Ed25519Key; type Preconfirmations = Vec; diff --git a/crates/fuel-core/src/service/adapters/consensus_module/poa/pre_confirmation_signature/parent_signature.rs b/crates/fuel-core/src/service/adapters/consensus_module/poa/pre_confirmation_signature/parent_signature.rs index 9da3b2425e0..551fce0805f 100644 --- a/crates/fuel-core/src/service/adapters/consensus_module/poa/pre_confirmation_signature/parent_signature.rs +++ b/crates/fuel-core/src/service/adapters/consensus_module/poa/pre_confirmation_signature/parent_signature.rs @@ -8,15 +8,12 @@ use fuel_core_poa::pre_confirmation_signature_service::{ use fuel_core_types::{ fuel_crypto, fuel_vm::Signature, - signer::SignMode, }; use serde::Serialize; -pub struct FuelParentSigner { - mode: SignMode, -} +use crate::service::adapters::FuelBlockSigner; -impl ParentSignature for FuelParentSigner { +impl ParentSignature for FuelBlockSigner { type Signature = Signature; async fn sign(&self, data: &T) -> PoAResult diff --git a/crates/fuel-core/src/service/adapters/executor.rs b/crates/fuel-core/src/service/adapters/executor.rs index f8e8755441a..15a40ebb024 100644 --- a/crates/fuel-core/src/service/adapters/executor.rs +++ b/crates/fuel-core/src/service/adapters/executor.rs @@ -17,7 +17,7 @@ use fuel_core_txpool::Constraints; use fuel_core_types::{ blockchain::primitives::DaBlockHeight, services::{ - preconfirmation::PreconfirmationStatus, + preconfirmation::Preconfirmation, relayer::Event, }, }; @@ -103,16 +103,13 @@ impl NewTxWaiterPort for NewTxWaiter { } impl PreconfirmationSenderPort for PreconfirmationSender { - async fn send(&self, preconfirmations: Vec) { + async fn send(&self, preconfirmations: Vec) { // If the receiver is closed, it means no one is listening to the preconfirmations and so we can drop them. // We don't consider this an error. let _ = self.sender.send(preconfirmations).await; } - fn try_send( - &self, - preconfirmations: Vec, - ) -> Vec { + fn try_send(&self, preconfirmations: Vec) -> Vec { match self.sender.try_send(preconfirmations) { Ok(()) => vec![], // If the receiver is closed, it means no one is listening to the preconfirmations and so we can drop them. diff --git a/crates/fuel-core/src/service/config.rs b/crates/fuel-core/src/service/config.rs index f036cc950eb..e83a93d0c3b 100644 --- a/crates/fuel-core/src/service/config.rs +++ b/crates/fuel-core/src/service/config.rs @@ -262,6 +262,17 @@ impl From<&Config> for fuel_core_poa::Config { } } +impl From<&Config> for fuel_core_poa::pre_confirmation_signature_service::config::Config { + fn from(_value: &Config) -> Self { + fuel_core_poa::pre_confirmation_signature_service::config::Config { + // TODO + echo_delegation_interval: Duration::from_secs(1), + key_rotation_interval: Duration::from_secs(10), + key_expiration_interval: Duration::from_secs(1), + } + } +} + #[derive( Clone, Copy, Debug, Display, Eq, PartialEq, EnumString, EnumVariantNames, ValueEnum, )] diff --git a/crates/fuel-core/src/service/sub_services.rs b/crates/fuel-core/src/service/sub_services.rs index ebc64c40976..48e5d47878c 100644 --- a/crates/fuel-core/src/service/sub_services.rs +++ b/crates/fuel-core/src/service/sub_services.rs @@ -39,7 +39,6 @@ use crate::service::adapters::consensus_module::poa::pre_confirmation_signature: Ed25519Key, Ed25519KeyGenerator, }, - parent_signature::FuelParentSigner, trigger::TimeBasedTrigger, tx_receiver::PreconfirmationsReceiver, }; @@ -124,6 +123,10 @@ pub fn init_sub_services( let chain_name = chain_config.chain_name.clone(); let on_chain_view = database.on_chain().latest_view()?; let (new_txs_updater, new_txs_watcher) = tokio::sync::watch::channel(()); + #[cfg(feature = "p2p")] + let (preconfirmation_sender, preconfirmation_receiver) = + tokio::sync::mpsc::channel(1024); + #[cfg(not(feature = "p2p"))] let (preconfirmation_sender, _preconfirmation_receiver) = tokio::sync::mpsc::channel(1024); @@ -340,16 +343,31 @@ pub fn init_sub_services( InDirectoryPredefinedBlocks::new(config.predefined_blocks_path.clone()); #[cfg(feature = "p2p")] - let _pre_confirmation_service: ServiceRunner< + let config_preconfirmation: fuel_core_poa::pre_confirmation_signature_service::config::Config = + config.into(); + + #[cfg(feature = "p2p")] + let pre_confirmation_service: ServiceRunner< PreConfirmationSignatureTask< PreconfirmationsReceiver, P2PAdapter, - FuelParentSigner, + FuelBlockSigner, Ed25519KeyGenerator, Ed25519Key, TimeBasedTrigger, >, - >; + > = fuel_core_poa::pre_confirmation_signature_service::new_service( + config_preconfirmation.clone(), + PreconfirmationsReceiver::new(preconfirmation_receiver), + p2p_adapter.clone(), + signer.clone(), + Ed25519KeyGenerator, + TimeBasedTrigger::new( + SystemTime, + config_preconfirmation.key_rotation_interval, + config_preconfirmation.key_expiration_interval, + ), + )?; let poa = production_enabled.then(|| { fuel_core_poa::new_service( @@ -462,6 +480,7 @@ pub fn init_sub_services( if let Some(network) = network.take() { services.push(Box::new(network)); services.push(Box::new(sync)); + services.push(Box::new(pre_confirmation_service)) } } #[cfg(feature = "shared-sequencer")] diff --git a/crates/services/consensus_module/poa/Cargo.toml b/crates/services/consensus_module/poa/Cargo.toml index c2f90000e6c..ff6e9a8b51a 100644 --- a/crates/services/consensus_module/poa/Cargo.toml +++ b/crates/services/consensus_module/poa/Cargo.toml @@ -16,6 +16,7 @@ fuel-core-chain-config = { workspace = true } fuel-core-services = { workspace = true } fuel-core-storage = { workspace = true, features = ["std"] } fuel-core-types = { workspace = true, features = ["std"] } +futures = { workspace = true } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } thiserror = { workspace = true } diff --git a/crates/services/consensus_module/poa/src/pre_confirmation_signature_service.rs b/crates/services/consensus_module/poa/src/pre_confirmation_signature_service.rs index 09a2ee7f946..a4a8d1020a7 100644 --- a/crates/services/consensus_module/poa/src/pre_confirmation_signature_service.rs +++ b/crates/services/consensus_module/poa/src/pre_confirmation_signature_service.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use error::Result; use fuel_core_services::{ try_or_continue, @@ -5,6 +7,7 @@ use fuel_core_services::{ EmptyShared, RunnableService, RunnableTask, + ServiceRunner, StateWatcher, TaskNextAction, }; @@ -31,6 +34,7 @@ use crate::pre_confirmation_signature_service::{ }; pub mod broadcast; +pub mod config; pub mod error; pub mod key_generator; pub mod parent_signature; @@ -54,7 +58,7 @@ pub struct PreConfirmationSignatureTask< { tx_receiver: TxReceiver, broadcast: Broadcast, - parent_signature: Parent, + parent_signature: Arc, key_generator: KeyGenerator, current_delegate_key: ExpiringKey, sealed_delegate_message: @@ -99,7 +103,7 @@ where async fn create_delegate_key( key_generator: &mut Gen, - parent_signature: &Parent, + parent_signature: &Arc, expiration: Tai64, ) -> Result<( ExpiringKey, @@ -219,3 +223,57 @@ where Ok(()) } } + +pub type Service = ServiceRunner< + PreConfirmationSignatureTask, +>; + +pub fn new_service( + config: config::Config, + preconfirmation_receiver: TxRcv, + mut p2p_adapter: Brdcst, + parent_signature: Arc, + mut key_generator: Gen, + mut key_rotation_trigger: Trigger, +) -> anyhow::Result> +where + TxRcv: TxReceiver, + Brdcst: Broadcast< + DelegateKey = DelegateKey, + ParentKey = Parent, + Preconfirmations = Preconfirmations, + >, + Gen: KeyGenerator, + Trigger: KeyRotationTrigger, + DelegateKey: SigningKey, + Parent: ParentSignature, + Preconfirmations: serde::Serialize + Send, +{ + // It's ok to wait because the first rotation is expected to be immediate + let expiration = futures::executor::block_on(key_rotation_trigger.next_rotation()) + .map_err(|e| anyhow::anyhow!(e))?; + let (new_delegate_key, sealed) = futures::executor::block_on(create_delegate_key( + &mut key_generator, + &parent_signature, + expiration, + )) + .map_err(|e| anyhow::anyhow!(e))?; + + if let Err(e) = futures::executor::block_on( + p2p_adapter + .broadcast_delegate_key(sealed.entity.clone(), sealed.signature.clone()), + ) { + tracing::error!("Failed to broadcast delegate key: {:?}", e); + } + + Ok(Service::new(PreConfirmationSignatureTask { + tx_receiver: preconfirmation_receiver, + broadcast: p2p_adapter, + parent_signature, + key_generator, + current_delegate_key: new_delegate_key, + sealed_delegate_message: sealed, + key_rotation_trigger, + echo_delegation_trigger: tokio::time::interval(config.echo_delegation_interval), + })) +} diff --git a/crates/services/consensus_module/poa/src/pre_confirmation_signature_service/config.rs b/crates/services/consensus_module/poa/src/pre_confirmation_signature_service/config.rs new file mode 100644 index 00000000000..a27c8a082bc --- /dev/null +++ b/crates/services/consensus_module/poa/src/pre_confirmation_signature_service/config.rs @@ -0,0 +1,8 @@ +use std::time::Duration; + +#[derive(Debug, Clone)] +pub struct Config { + pub key_rotation_interval: Duration, + pub key_expiration_interval: Duration, + pub echo_delegation_interval: Duration, +} diff --git a/crates/services/consensus_module/poa/src/pre_confirmation_signature_service/trigger.rs b/crates/services/consensus_module/poa/src/pre_confirmation_signature_service/trigger.rs index 4f0c6803abf..9cfa63808f0 100644 --- a/crates/services/consensus_module/poa/src/pre_confirmation_signature_service/trigger.rs +++ b/crates/services/consensus_module/poa/src/pre_confirmation_signature_service/trigger.rs @@ -3,5 +3,6 @@ use std::future::Future; /// Defines the behavior for when the `PreconfirmationSignatureTask` should rotate the delegate key pub trait KeyRotationTrigger: Send { + /// First rotation must be immediate fn next_rotation(&mut self) -> impl Future> + Send; } diff --git a/crates/services/executor/src/executor.rs b/crates/services/executor/src/executor.rs index a0a32d87fca..1ede2b1e56e 100644 --- a/crates/services/executor/src/executor.rs +++ b/crates/services/executor/src/executor.rs @@ -136,7 +136,10 @@ use fuel_core_types::{ UncommittedValidationResult, ValidationResult, }, - preconfirmation::PreconfirmationStatus, + preconfirmation::{ + Preconfirmation, + PreconfirmationStatus, + }, relayer::Event, }, }; @@ -239,19 +242,20 @@ impl NewTxWaiterPort for TimeoutOnlyTxWaiter { pub struct TransparentPreconfirmationSender; impl PreconfirmationSenderPort for TransparentPreconfirmationSender { - fn try_send(&self, _: Vec) -> Vec { + fn try_send(&self, _: Vec) -> Vec { vec![] } - async fn send(&self, _: Vec) {} + async fn send(&self, _: Vec) {} } fn convert_tx_execution_result_to_preconfirmation( tx: &Transaction, + tx_id: TxId, tx_exec_result: &TransactionExecutionResult, block_height: BlockHeight, tx_index: u16, -) -> PreconfirmationStatus { +) -> Preconfirmation { let tx_pointer = TxPointer::new(block_height, tx_index); let dynamic_outputs = tx .outputs() @@ -265,7 +269,7 @@ fn convert_tx_execution_result_to_preconfirmation( }) .collect(); - match tx_exec_result { + let status = match tx_exec_result { TransactionExecutionResult::Success { receipts, total_gas, @@ -290,7 +294,8 @@ fn convert_tx_execution_result_to_preconfirmation( receipts: receipts.clone(), outputs: dynamic_outputs, }, - } + }; + Preconfirmation { tx_id, status } } /// Data that is generated after executing all transactions. @@ -808,6 +813,7 @@ where let preconfirmation_status = convert_tx_execution_result_to_preconfirmation( tx, + tx_id, &latest_executed_tx.result, *block.header.height(), data.tx_count, @@ -815,8 +821,11 @@ where status.push(preconfirmation_status); } Err(err) => { - status.push(PreconfirmationStatus::SqueezedOut { - reason: err.to_string(), + status.push(Preconfirmation { + tx_id, + status: PreconfirmationStatus::SqueezedOut { + reason: err.to_string(), + }, }); data.skipped_transactions.push((tx_id, err)); } diff --git a/crates/services/executor/src/ports.rs b/crates/services/executor/src/ports.rs index 4d18fc51752..0534f40f2f7 100644 --- a/crates/services/executor/src/ports.rs +++ b/crates/services/executor/src/ports.rs @@ -21,7 +21,7 @@ use fuel_core_types::{ fuel_vm::checked_transaction::CheckedTransaction, services::{ executor::Result as ExecutorResult, - preconfirmation::PreconfirmationStatus, + preconfirmation::Preconfirmation, relayer::Event, }, }; @@ -181,14 +181,11 @@ pub trait NewTxWaiterPort: Send { pub trait PreconfirmationSenderPort { /// Try to send a batch of pre-confirmations. Will succeed only if /// it can be directly sent. Otherwise it will return the batch. - fn try_send( - &self, - preconfirmations: Vec, - ) -> Vec; + fn try_send(&self, preconfirmations: Vec) -> Vec; /// Send a batch of pre-confirmations, awaiting for the send to be successful. fn send( &self, - preconfirmations: Vec, + preconfirmations: Vec, ) -> impl Future + Send; } diff --git a/crates/services/upgradable-executor/wasm-executor/src/preconfirmation_sender.rs b/crates/services/upgradable-executor/wasm-executor/src/preconfirmation_sender.rs index 95755eb56c5..2940f3edd05 100644 --- a/crates/services/upgradable-executor/wasm-executor/src/preconfirmation_sender.rs +++ b/crates/services/upgradable-executor/wasm-executor/src/preconfirmation_sender.rs @@ -1,12 +1,12 @@ use fuel_core_executor::ports::PreconfirmationSenderPort; -use fuel_core_types::services::preconfirmation::PreconfirmationStatus; +use fuel_core_types::services::preconfirmation::Preconfirmation; pub struct PreconfirmationSender; impl PreconfirmationSenderPort for PreconfirmationSender { - fn try_send(&self, _: Vec) -> Vec { + fn try_send(&self, _: Vec) -> Vec { vec![] } - async fn send(&self, _: Vec) {} + async fn send(&self, _: Vec) {} } diff --git a/tests/tests/preconfirmations.rs b/tests/tests/preconfirmations.rs index 9634110853e..ac0bcce91de 100644 --- a/tests/tests/preconfirmations.rs +++ b/tests/tests/preconfirmations.rs @@ -28,6 +28,7 @@ use rand::Rng; #[tokio::test] async fn preconfirmation__received_after_execution() { + tracing_subscriber::fmt::init(); let mut config = Config::local_node(); let block_production_period = Duration::from_secs(1); let address = Address::new([0; 32]); From 84146181c76e1253d597384dffc96d5a69863dd1 Mon Sep 17 00:00:00 2001 From: AurelienFT Date: Thu, 13 Mar 2025 15:38:22 +0100 Subject: [PATCH 27/76] Add connection between tx status manager and executor --- Cargo.lock | 1 + bin/fuel-core/Cargo.toml | 1 + bin/fuel-core/src/cli/run.rs | 7 ++ crates/fuel-core/Cargo.toml | 1 + crates/fuel-core/src/service/adapters.rs | 17 +++- .../poa/pre_confirmation_signature/trigger.rs | 4 +- .../src/service/adapters/executor.rs | 11 ++- crates/fuel-core/src/service/config.rs | 7 ++ crates/fuel-core/src/service/sub_services.rs | 81 ++++++++++--------- .../src/pre_confirmation_signature_service.rs | 9 ++- .../config.rs | 11 +++ tests/tests/preconfirmations.rs | 39 ++++----- 12 files changed, 121 insertions(+), 68 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 49bc53c59a5..eaa5d7992dc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3482,6 +3482,7 @@ dependencies = [ "fuel-core-chain-config", "fuel-core-compression", "fuel-core-metrics", + "fuel-core-poa", "fuel-core-shared-sequencer", "fuel-core-storage", "fuel-core-types 0.41.7", diff --git a/bin/fuel-core/Cargo.toml b/bin/fuel-core/Cargo.toml index aadd89f5c52..7224fe4bd50 100644 --- a/bin/fuel-core/Cargo.toml +++ b/bin/fuel-core/Cargo.toml @@ -29,6 +29,7 @@ fuel-core = { workspace = true, features = ["wasm-executor"] } fuel-core-chain-config = { workspace = true } fuel-core-compression = { workspace = true } fuel-core-metrics = { workspace = true } +fuel-core-poa = { workspace = true } fuel-core-shared-sequencer = { workspace = true, optional = true } fuel-core-types = { workspace = true, features = ["std"] } hex = { workspace = true } diff --git a/bin/fuel-core/src/cli/run.rs b/bin/fuel-core/src/cli/run.rs index 25e3d9083be..ceef876a214 100644 --- a/bin/fuel-core/src/cli/run.rs +++ b/bin/fuel-core/src/cli/run.rs @@ -701,6 +701,13 @@ impl Command { p2p: p2p_cfg, #[cfg(feature = "p2p")] sync: sync_args.into(), + #[cfg(feature = "p2p")] + pre_confirmation_signature_service: + fuel_core_poa::pre_confirmation_signature_service::config::Config { + key_rotation_interval: Duration::from_secs(10), + key_expiration_interval: Duration::from_secs(30), + echo_delegation_interval: Duration::from_secs(5), + }, #[cfg(feature = "shared-sequencer")] shared_sequencer: shared_sequencer_args.try_into()?, consensus_signer, diff --git a/crates/fuel-core/Cargo.toml b/crates/fuel-core/Cargo.toml index 0616da56643..d20eedd201b 100644 --- a/crates/fuel-core/Cargo.toml +++ b/crates/fuel-core/Cargo.toml @@ -116,6 +116,7 @@ test-helpers = [ "fuel-core-services/test-helpers", "fuel-core-shared-sequencer?/test-helpers", "fuel-core-importer/test-helpers", + "fuel-core-poa/test-helpers", "dep:mockall", ] # features to enable in production, but increase build times diff --git a/crates/fuel-core/src/service/adapters.rs b/crates/fuel-core/src/service/adapters.rs index 0298d83c591..a5b20b33809 100644 --- a/crates/fuel-core/src/service/adapters.rs +++ b/crates/fuel-core/src/service/adapters.rs @@ -341,12 +341,19 @@ impl NewTxWaiter { #[derive(Clone)] pub struct PreconfirmationSender { - pub sender: tokio::sync::mpsc::Sender>, + pub sender_signature_service: tokio::sync::mpsc::Sender>, + pub tx_status_manager_adapter: TxStatusManagerAdapter, } impl PreconfirmationSender { - pub fn new(sender: tokio::sync::mpsc::Sender>) -> Self { - Self { sender } + pub fn new( + sender_signature_service: tokio::sync::mpsc::Sender>, + tx_status_manager_adapter: TxStatusManagerAdapter, + ) -> Self { + Self { + sender_signature_service, + tx_status_manager_adapter, + } } } @@ -364,9 +371,11 @@ impl ExecutorAdapter { config: fuel_core_upgradable_executor::config::Config, new_txs_watcher: tokio::sync::watch::Receiver<()>, preconfirmation_sender: tokio::sync::mpsc::Sender>, + tx_status_manager_adapter: TxStatusManagerAdapter, ) -> Self { let executor = Executor::new(database, relayer_database, config); - let preconfirmation_sender = PreconfirmationSender::new(preconfirmation_sender); + let preconfirmation_sender = + PreconfirmationSender::new(preconfirmation_sender, tx_status_manager_adapter); Self { executor: Arc::new(executor), new_txs_watcher, diff --git a/crates/fuel-core/src/service/adapters/consensus_module/poa/pre_confirmation_signature/trigger.rs b/crates/fuel-core/src/service/adapters/consensus_module/poa/pre_confirmation_signature/trigger.rs index 7d5e8f62598..03412799376 100644 --- a/crates/fuel-core/src/service/adapters/consensus_module/poa/pre_confirmation_signature/trigger.rs +++ b/crates/fuel-core/src/service/adapters/consensus_module/poa/pre_confirmation_signature/trigger.rs @@ -17,9 +17,11 @@ pub struct TimeBasedTrigger { impl TimeBasedTrigger { pub fn new(time: T, rotation_interval: Duration, expiration: Duration) -> Self { + let mut interval = tokio::time::interval(rotation_interval); + interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); Self { time, - interval: tokio::time::interval(rotation_interval), + interval, expiration, } } diff --git a/crates/fuel-core/src/service/adapters/executor.rs b/crates/fuel-core/src/service/adapters/executor.rs index 15a40ebb024..5d2d780229c 100644 --- a/crates/fuel-core/src/service/adapters/executor.rs +++ b/crates/fuel-core/src/service/adapters/executor.rs @@ -106,11 +106,18 @@ impl PreconfirmationSenderPort for PreconfirmationSender { async fn send(&self, preconfirmations: Vec) { // If the receiver is closed, it means no one is listening to the preconfirmations and so we can drop them. // We don't consider this an error. - let _ = self.sender.send(preconfirmations).await; + let _ = self.sender_signature_service.send(preconfirmations).await; } fn try_send(&self, preconfirmations: Vec) -> Vec { - match self.sender.try_send(preconfirmations) { + for preconfirmation in &preconfirmations { + let tx_id = preconfirmation.tx_id; + let status = preconfirmation.status.clone(); + self.tx_status_manager_adapter + .tx_status_manager_shared_data + .update_status(tx_id, status.into()); + } + match self.sender_signature_service.try_send(preconfirmations) { Ok(()) => vec![], // If the receiver is closed, it means no one is listening to the preconfirmations and so we can drop them. // We don't consider this an error. diff --git a/crates/fuel-core/src/service/config.rs b/crates/fuel-core/src/service/config.rs index e83a93d0c3b..eed04eadd72 100644 --- a/crates/fuel-core/src/service/config.rs +++ b/crates/fuel-core/src/service/config.rs @@ -80,6 +80,9 @@ pub struct Config { pub p2p: Option>, #[cfg(feature = "p2p")] pub sync: fuel_core_sync::Config, + #[cfg(feature = "p2p")] + pub pre_confirmation_signature_service: + fuel_core_poa::pre_confirmation_signature_service::config::Config, #[cfg(feature = "shared-sequencer")] pub shared_sequencer: fuel_core_shared_sequencer::Config, pub consensus_signer: SignMode, @@ -204,6 +207,10 @@ impl Config { p2p: Some(P2PConfig::::default(network_name.as_str())), #[cfg(feature = "p2p")] sync: fuel_core_sync::Config::default(), + #[cfg(feature = "p2p")] + pre_confirmation_signature_service: + fuel_core_poa::pre_confirmation_signature_service::config::Config::default( + ), #[cfg(feature = "shared-sequencer")] shared_sequencer: fuel_core_shared_sequencer::Config::local_node(), consensus_signer: SignMode::Key(fuel_core_types::secrecy::Secret::new( diff --git a/crates/fuel-core/src/service/sub_services.rs b/crates/fuel-core/src/service/sub_services.rs index 48e5d47878c..d52aff4b224 100644 --- a/crates/fuel-core/src/service/sub_services.rs +++ b/crates/fuel-core/src/service/sub_services.rs @@ -148,6 +148,46 @@ pub fn init_sub_services( )); } + #[cfg(feature = "p2p")] + let p2p_externals = config + .p2p + .clone() + .map(fuel_core_p2p::service::build_shared_state); + + #[cfg(feature = "p2p")] + let p2p_adapter = { + use crate::service::adapters::PeerReportConfig; + + // Hardcoded for now, but left here to be configurable in the future. + // TODO: https://github.com/FuelLabs/fuel-core/issues/1340 + let peer_report_config = PeerReportConfig { + successful_block_import: 5., + missing_block_headers: -100., + bad_block_header: -100., + missing_transactions: -100., + invalid_transactions: -100., + }; + P2PAdapter::new( + p2p_externals.as_ref().map(|ext| ext.0.clone()), + peer_report_config, + ) + }; + + #[cfg(not(feature = "p2p"))] + let p2p_adapter = P2PAdapter::new(); + + // TODO: Use real values + let signature_verification = + PreconfirmationSignatureVerification::new(PublicKey::default()); + + let tx_status_manager = fuel_core_tx_status_manager::new_service( + p2p_adapter.clone(), + signature_verification, + config.tx_status_manager.clone(), + ); + let tx_status_manager_adapter = + TxStatusManagerAdapter::new(tx_status_manager.shared.clone()); + let upgradable_executor_config = fuel_core_upgradable_executor::config::Config { forbid_fake_coins_default: config.utxo_validation, native_executor_version: config.native_executor_version, @@ -159,6 +199,7 @@ pub fn init_sub_services( upgradable_executor_config, new_txs_watcher, preconfirmation_sender, + tx_status_manager_adapter.clone(), ); let import_result_provider = ImportResultProvider::new(database.on_chain().clone(), executor.clone()); @@ -206,34 +247,6 @@ pub fn init_sub_services( ), }; - #[cfg(feature = "p2p")] - let p2p_externals = config - .p2p - .clone() - .map(fuel_core_p2p::service::build_shared_state); - - #[cfg(feature = "p2p")] - let p2p_adapter = { - use crate::service::adapters::PeerReportConfig; - - // Hardcoded for now, but left here to be configurable in the future. - // TODO: https://github.com/FuelLabs/fuel-core/issues/1340 - let peer_report_config = PeerReportConfig { - successful_block_import: 5., - missing_block_headers: -100., - bad_block_header: -100., - missing_transactions: -100., - invalid_transactions: -100., - }; - P2PAdapter::new( - p2p_externals.as_ref().map(|ext| ext.0.clone()), - peer_report_config, - ) - }; - - #[cfg(not(feature = "p2p"))] - let p2p_adapter = P2PAdapter::new(); - let genesis_block_height = *genesis_block.header().height(); let settings = chain_state_info_provider.clone(); let block_stream = importer_adapter.events_shared_result(); @@ -263,18 +276,6 @@ pub fn init_sub_services( universal_gas_price_provider.clone(), ); - // TODO: Use real values - let signature_verification = - PreconfirmationSignatureVerification::new(PublicKey::default()); - - let tx_status_manager = fuel_core_tx_status_manager::new_service( - p2p_adapter.clone(), - signature_verification, - config.tx_status_manager.clone(), - ); - let tx_status_manager_adapter = - TxStatusManagerAdapter::new(tx_status_manager.shared.clone()); - let txpool = fuel_core_txpool::new_service( chain_id, config.txpool.clone(), diff --git a/crates/services/consensus_module/poa/src/pre_confirmation_signature_service.rs b/crates/services/consensus_module/poa/src/pre_confirmation_signature_service.rs index a4a8d1020a7..673111ae272 100644 --- a/crates/services/consensus_module/poa/src/pre_confirmation_signature_service.rs +++ b/crates/services/consensus_module/poa/src/pre_confirmation_signature_service.rs @@ -234,7 +234,7 @@ pub fn new_service, mut key_generator: Gen, - mut key_rotation_trigger: Trigger, + key_rotation_trigger: Trigger, ) -> anyhow::Result> where TxRcv: TxReceiver, @@ -250,8 +250,11 @@ where Preconfirmations: serde::Serialize + Send, { // It's ok to wait because the first rotation is expected to be immediate - let expiration = futures::executor::block_on(key_rotation_trigger.next_rotation()) - .map_err(|e| anyhow::anyhow!(e))?; + let expiration = Tai64( + Tai64::now() + .0 + .saturating_add(config.key_expiration_interval.as_secs()), + ); let (new_delegate_key, sealed) = futures::executor::block_on(create_delegate_key( &mut key_generator, &parent_signature, diff --git a/crates/services/consensus_module/poa/src/pre_confirmation_signature_service/config.rs b/crates/services/consensus_module/poa/src/pre_confirmation_signature_service/config.rs index a27c8a082bc..eb5e3dcb9a0 100644 --- a/crates/services/consensus_module/poa/src/pre_confirmation_signature_service/config.rs +++ b/crates/services/consensus_module/poa/src/pre_confirmation_signature_service/config.rs @@ -6,3 +6,14 @@ pub struct Config { pub key_expiration_interval: Duration, pub echo_delegation_interval: Duration, } + +#[cfg(any(test, feature = "test-helpers"))] +impl Default for Config { + fn default() -> Self { + Self { + key_rotation_interval: Duration::from_secs(10), + key_expiration_interval: Duration::from_secs(30), + echo_delegation_interval: Duration::from_secs(5), + } + } +} diff --git a/tests/tests/preconfirmations.rs b/tests/tests/preconfirmations.rs index ac0bcce91de..efc071c7148 100644 --- a/tests/tests/preconfirmations.rs +++ b/tests/tests/preconfirmations.rs @@ -19,6 +19,7 @@ use fuel_core_types::{ Receipt, TransactionBuilder, TxPointer, + UniqueIdentifier, }, fuel_types::BlockHeight, fuel_vm::SecretKey, @@ -26,9 +27,9 @@ use fuel_core_types::{ use futures::StreamExt; use rand::Rng; -#[tokio::test] +#[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn preconfirmation__received_after_execution() { - tracing_subscriber::fmt::init(); + // tracing_subscriber::fmt::init(); let mut config = Config::local_node(); let block_production_period = Duration::from_secs(1); let address = Address::new([0; 32]); @@ -61,9 +62,11 @@ async fn preconfirmation__received_after_execution() { .add_output(Output::variable(address, 0, AssetId::default())) .finalize_as_transaction(); - let tx_id = client.submit(&tx).await.unwrap(); + let tx_id = tx.id(&Default::default()); let mut tx_statuses_subscriber = client.subscribe_transaction_status(&tx_id).await.unwrap(); + tokio::time::sleep(Duration::from_secs(1)).await; + client.submit(&tx).await.unwrap(); // When assert!(matches!( @@ -73,19 +76,18 @@ async fn preconfirmation__received_after_execution() { if let TransactionStatus::PreconfirmationSuccess { tx_pointer, total_fee, - total_gas, + total_gas: _, transaction_id, receipts, - resolved_outputs, + resolved_outputs: _, } = tx_statuses_subscriber.next().await.unwrap().unwrap() { // Then - assert_eq!(tx_pointer, TxPointer::new(BlockHeight::new(1), 0)); + assert_eq!(tx_pointer, TxPointer::new(BlockHeight::new(2), 1)); assert_eq!(total_fee, 0); - assert_eq!(total_gas, 4450); assert_eq!(transaction_id, tx_id); let receipts = receipts.unwrap(); - assert_eq!(receipts.len(), 2); + assert_eq!(receipts.len(), 3); assert!(matches!(receipts[0], Receipt::Log { ra, rb, .. @@ -95,16 +97,17 @@ async fn preconfirmation__received_after_execution() { Receipt::Return { val, .. } if val == 1)); - let outputs = resolved_outputs.unwrap(); - assert_eq!(outputs.len(), 1); - assert_eq!( - outputs[0], - Output::Coin { - to: address, - amount: 2, - asset_id: AssetId::default() - } - ); + // TODO: Fix + // let outputs = resolved_outputs.unwrap(); + // assert_eq!(outputs.len(), 1); + // assert_eq!( + // outputs[0], + // Output::Coin { + // to: address, + // amount: 2, + // asset_id: AssetId::default() + // } + // ); } else { panic!("Expected preconfirmation status"); } From 8605d95f680ace2b86d76632d328354e87336755 Mon Sep 17 00:00:00 2001 From: AurelienFT Date: Thu, 13 Mar 2025 15:56:00 +0100 Subject: [PATCH 28/76] Update some tests, some are passing now. --- tests/tests/preconfirmations.rs | 86 +++++++++++++++++++-------------- 1 file changed, 50 insertions(+), 36 deletions(-) diff --git a/tests/tests/preconfirmations.rs b/tests/tests/preconfirmations.rs index efc071c7148..6b31fa04d76 100644 --- a/tests/tests/preconfirmations.rs +++ b/tests/tests/preconfirmations.rs @@ -29,10 +29,11 @@ use rand::Rng; #[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn preconfirmation__received_after_execution() { - // tracing_subscriber::fmt::init(); + let mut rng = rand::thread_rng(); let mut config = Config::local_node(); let block_production_period = Duration::from_secs(1); let address = Address::new([0; 32]); + let amount = 10; config.block_production = Trigger::Open { period: block_production_period, @@ -58,8 +59,14 @@ async fn preconfirmation__received_after_execution() { let tx = TransactionBuilder::script(script, vec![]) .script_gas_limit(gas_limit) .maturity(maturity) - .add_fee_input() - .add_output(Output::variable(address, 0, AssetId::default())) + .add_unsigned_coin_input( + SecretKey::random(&mut rng), + rng.gen(), + amount, + AssetId::default(), + Default::default(), + ) + .add_output(Output::change(address, 0, AssetId::default())) .finalize_as_transaction(); let tx_id = tx.id(&Default::default()); @@ -79,7 +86,7 @@ async fn preconfirmation__received_after_execution() { total_gas: _, transaction_id, receipts, - resolved_outputs: _, + resolved_outputs, } = tx_statuses_subscriber.next().await.unwrap().unwrap() { // Then @@ -97,17 +104,16 @@ async fn preconfirmation__received_after_execution() { Receipt::Return { val, .. } if val == 1)); - // TODO: Fix - // let outputs = resolved_outputs.unwrap(); - // assert_eq!(outputs.len(), 1); - // assert_eq!( - // outputs[0], - // Output::Coin { - // to: address, - // amount: 2, - // asset_id: AssetId::default() - // } - // ); + let outputs = resolved_outputs.unwrap(); + assert_eq!(outputs.len(), 1); + assert_eq!( + outputs[0], + Output::Change { + to: address, + amount, + asset_id: AssetId::default() + } + ); } else { panic!("Expected preconfirmation status"); } @@ -117,11 +123,13 @@ async fn preconfirmation__received_after_execution() { )); } -#[tokio::test] +#[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn preconfirmation__received_after_failed_execution() { + let mut rng = rand::thread_rng(); let mut config = Config::local_node(); let block_production_period = Duration::from_secs(1); let address = Address::new([0; 32]); + let amount = 10; config.block_production = Trigger::Open { period: block_production_period, @@ -149,12 +157,21 @@ async fn preconfirmation__received_after_failed_execution() { .script_gas_limit(gas_limit) .maturity(maturity) .add_fee_input() - .add_output(Output::variable(address, 0, AssetId::default())) + .add_unsigned_coin_input( + SecretKey::random(&mut rng), + rng.gen(), + amount, + AssetId::default(), + Default::default(), + ) + .add_output(Output::change(address, 0, AssetId::default())) .finalize_as_transaction(); - let tx_id = client.submit(&tx).await.unwrap(); + let tx_id = tx.id(&Default::default()); let mut tx_statuses_subscriber = client.subscribe_transaction_status(&tx_id).await.unwrap(); + tokio::time::sleep(block_production_period).await; + client.submit(&tx).await.unwrap(); // When assert!(matches!( @@ -165,7 +182,7 @@ async fn preconfirmation__received_after_failed_execution() { if let TransactionStatus::PreconfirmationFailure { tx_pointer, total_fee, - total_gas, + total_gas: _, transaction_id, receipts, resolved_outputs, @@ -173,12 +190,11 @@ async fn preconfirmation__received_after_failed_execution() { } = tx_statuses_subscriber.next().await.unwrap().unwrap() { // Then - assert_eq!(tx_pointer, TxPointer::new(BlockHeight::new(1), 0)); + assert_eq!(tx_pointer, TxPointer::new(BlockHeight::new(2), 1)); assert_eq!(total_fee, 0); - assert_eq!(total_gas, 4450); assert_eq!(transaction_id, tx_id); let receipts = receipts.unwrap(); - assert_eq!(receipts.len(), 2); + assert_eq!(receipts.len(), 3); assert!(matches!(receipts[0], Receipt::Log { ra, rb, .. @@ -192,7 +208,7 @@ async fn preconfirmation__received_after_failed_execution() { assert_eq!(outputs.len(), 1); assert_eq!( outputs[0], - Output::Coin { + Output::Change { to: address, amount: 2, asset_id: AssetId::default() @@ -208,7 +224,7 @@ async fn preconfirmation__received_after_failed_execution() { )); } -#[tokio::test] +#[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn preconfirmation__received_after_squeezed_out() { let mut config = Config::local_node(); let mut rng = rand::thread_rng(); @@ -246,9 +262,11 @@ async fn preconfirmation__received_after_squeezed_out() { }) .finalize_as_transaction(); - let tx_id = client.submit(&tx).await.unwrap(); + let tx_id = tx.id(&Default::default()); let mut tx_statuses_subscriber = client.subscribe_transaction_status(&tx_id).await.unwrap(); + tokio::time::sleep(block_production_period).await; + client.submit(&tx).await.unwrap(); // When assert!(matches!( @@ -265,14 +283,9 @@ async fn preconfirmation__received_after_squeezed_out() { } else { panic!("Expected preconfirmation status"); } - - assert!(matches!( - tx_statuses_subscriber.next().await.unwrap().unwrap(), - TransactionStatus::SqueezedOut { .. } - )); } -#[tokio::test] +#[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn preconfirmation__received_tx_inserted_end_block_open_period() { let mut config = Config::local_node(); let block_production_period = Duration::from_secs(1); @@ -295,15 +308,16 @@ async fn preconfirmation__received_tx_inserted_end_block_open_period() { .finalize_as_transaction(); // Given - tokio::time::sleep(block_production_period.checked_div(2).unwrap()).await; - - let tx_id = client.submit(&tx).await.unwrap(); + let tx_id = tx.id(&Default::default()); let mut tx_statuses_subscriber = client.subscribe_transaction_status(&tx_id).await.unwrap(); + tokio::time::sleep(block_production_period).await; + tokio::time::sleep(block_production_period.checked_div(2).unwrap()).await; + client.submit(&tx).await.unwrap(); // When assert!(matches!( - tx_statuses_subscriber.next().await.unwrap().unwrap(), + dbg!(tx_statuses_subscriber.next().await.unwrap().unwrap()), TransactionStatus::Submitted { .. } )); assert!(matches!( @@ -313,7 +327,7 @@ async fn preconfirmation__received_tx_inserted_end_block_open_period() { // Then assert!(matches!( tx_statuses_subscriber.next().await.unwrap().unwrap(), - TransactionStatus::Success { block_height, .. } if block_height == BlockHeight::new(1) + TransactionStatus::Success { block_height, .. } if block_height == BlockHeight::new(2) )); } From 2aa4cb6ecddfb71edc0cfd2a8e962166a018dec7 Mon Sep 17 00:00:00 2001 From: AurelienFT Date: Thu, 13 Mar 2025 16:46:41 +0100 Subject: [PATCH 29/76] Simplify some tests --- tests/tests/preconfirmations.rs | 36 ++++++++++++--------------------- 1 file changed, 13 insertions(+), 23 deletions(-) diff --git a/tests/tests/preconfirmations.rs b/tests/tests/preconfirmations.rs index 6b31fa04d76..803e15c2f8b 100644 --- a/tests/tests/preconfirmations.rs +++ b/tests/tests/preconfirmations.rs @@ -27,17 +27,15 @@ use fuel_core_types::{ use futures::StreamExt; use rand::Rng; -#[tokio::test(flavor = "multi_thread", worker_threads = 4)] +#[tokio::test] async fn preconfirmation__received_after_execution() { + tracing_subscriber::fmt::init(); let mut rng = rand::thread_rng(); let mut config = Config::local_node(); - let block_production_period = Duration::from_secs(1); + config.block_production = Trigger::Never; let address = Address::new([0; 32]); let amount = 10; - config.block_production = Trigger::Open { - period: block_production_period, - }; let srv = FuelService::new_node(config).await.unwrap(); let client = FuelClient::from(srv.bound_address); @@ -72,7 +70,6 @@ async fn preconfirmation__received_after_execution() { let tx_id = tx.id(&Default::default()); let mut tx_statuses_subscriber = client.subscribe_transaction_status(&tx_id).await.unwrap(); - tokio::time::sleep(Duration::from_secs(1)).await; client.submit(&tx).await.unwrap(); // When @@ -80,6 +77,7 @@ async fn preconfirmation__received_after_execution() { tx_statuses_subscriber.next().await.unwrap().unwrap(), TransactionStatus::Submitted { .. } )); + client.produce_blocks(1, None).await.unwrap(); if let TransactionStatus::PreconfirmationSuccess { tx_pointer, total_fee, @@ -90,7 +88,7 @@ async fn preconfirmation__received_after_execution() { } = tx_statuses_subscriber.next().await.unwrap().unwrap() { // Then - assert_eq!(tx_pointer, TxPointer::new(BlockHeight::new(2), 1)); + assert_eq!(tx_pointer, TxPointer::new(BlockHeight::new(1), 1)); assert_eq!(total_fee, 0); assert_eq!(transaction_id, tx_id); let receipts = receipts.unwrap(); @@ -123,17 +121,14 @@ async fn preconfirmation__received_after_execution() { )); } -#[tokio::test(flavor = "multi_thread", worker_threads = 4)] +#[tokio::test] async fn preconfirmation__received_after_failed_execution() { let mut rng = rand::thread_rng(); let mut config = Config::local_node(); - let block_production_period = Duration::from_secs(1); + config.block_production = Trigger::Never; let address = Address::new([0; 32]); let amount = 10; - config.block_production = Trigger::Open { - period: block_production_period, - }; let srv = FuelService::new_node(config).await.unwrap(); let client = FuelClient::from(srv.bound_address); @@ -156,7 +151,6 @@ async fn preconfirmation__received_after_failed_execution() { let tx = TransactionBuilder::script(script, vec![]) .script_gas_limit(gas_limit) .maturity(maturity) - .add_fee_input() .add_unsigned_coin_input( SecretKey::random(&mut rng), rng.gen(), @@ -170,7 +164,6 @@ async fn preconfirmation__received_after_failed_execution() { let tx_id = tx.id(&Default::default()); let mut tx_statuses_subscriber = client.subscribe_transaction_status(&tx_id).await.unwrap(); - tokio::time::sleep(block_production_period).await; client.submit(&tx).await.unwrap(); // When @@ -178,7 +171,7 @@ async fn preconfirmation__received_after_failed_execution() { tx_statuses_subscriber.next().await.unwrap().unwrap(), TransactionStatus::Submitted { .. } )); - + client.produce_blocks(1, None).await.unwrap(); if let TransactionStatus::PreconfirmationFailure { tx_pointer, total_fee, @@ -190,7 +183,7 @@ async fn preconfirmation__received_after_failed_execution() { } = tx_statuses_subscriber.next().await.unwrap().unwrap() { // Then - assert_eq!(tx_pointer, TxPointer::new(BlockHeight::new(2), 1)); + assert_eq!(tx_pointer, TxPointer::new(BlockHeight::new(1), 1)); assert_eq!(total_fee, 0); assert_eq!(transaction_id, tx_id); let receipts = receipts.unwrap(); @@ -210,7 +203,7 @@ async fn preconfirmation__received_after_failed_execution() { outputs[0], Output::Change { to: address, - amount: 2, + amount, asset_id: AssetId::default() } ); @@ -224,15 +217,12 @@ async fn preconfirmation__received_after_failed_execution() { )); } -#[tokio::test(flavor = "multi_thread", worker_threads = 4)] +#[tokio::test] async fn preconfirmation__received_after_squeezed_out() { let mut config = Config::local_node(); + config.block_production = Trigger::Never; let mut rng = rand::thread_rng(); - let block_production_period = Duration::from_secs(1); - config.block_production = Trigger::Open { - period: block_production_period, - }; // Given // Disable UTXO validation in TxPool so that the transaction is squeezed out by // block production @@ -265,7 +255,6 @@ async fn preconfirmation__received_after_squeezed_out() { let tx_id = tx.id(&Default::default()); let mut tx_statuses_subscriber = client.subscribe_transaction_status(&tx_id).await.unwrap(); - tokio::time::sleep(block_production_period).await; client.submit(&tx).await.unwrap(); // When @@ -273,6 +262,7 @@ async fn preconfirmation__received_after_squeezed_out() { tx_statuses_subscriber.next().await.unwrap().unwrap(), TransactionStatus::Submitted { .. } )); + client.produce_blocks(1, None).await.unwrap(); if let TransactionStatus::PreconfirmationSqueezedOut { transaction_id, reason: _, From 06f851f4be2304bd01306a5ae73cbf1d8bbc87bc Mon Sep 17 00:00:00 2001 From: AurelienFT Date: Thu, 13 Mar 2025 17:07:16 +0100 Subject: [PATCH 30/76] All graphql tests pass. --- tests/tests/preconfirmations.rs | 81 +++++++++++++++++++++------------ 1 file changed, 51 insertions(+), 30 deletions(-) diff --git a/tests/tests/preconfirmations.rs b/tests/tests/preconfirmations.rs index 803e15c2f8b..239a3ea32ac 100644 --- a/tests/tests/preconfirmations.rs +++ b/tests/tests/preconfirmations.rs @@ -29,7 +29,6 @@ use rand::Rng; #[tokio::test] async fn preconfirmation__received_after_execution() { - tracing_subscriber::fmt::init(); let mut rng = rand::thread_rng(); let mut config = Config::local_node(); config.block_production = Trigger::Never; @@ -297,38 +296,45 @@ async fn preconfirmation__received_tx_inserted_end_block_open_period() { .add_output(Output::variable(address, 0, AssetId::default())) .finalize_as_transaction(); - // Given let tx_id = tx.id(&Default::default()); - let mut tx_statuses_subscriber = - client.subscribe_transaction_status(&tx_id).await.unwrap(); - tokio::time::sleep(block_production_period).await; - tokio::time::sleep(block_production_period.checked_div(2).unwrap()).await; + let jh = tokio::spawn({ + let client = client.clone(); + async move { + let _ = client + .subscribe_transaction_status(&tx_id) + .await + .unwrap() + .enumerate() + .take(3) + .for_each(|(event_idx, r)| async move { + let r = r.unwrap(); + // Then + match (event_idx, r) { + (0, TransactionStatus::Submitted { .. }) => {} + (1, TransactionStatus::PreconfirmationSuccess { .. }) => {} + (2, TransactionStatus::Success { block_height, .. }) => { + assert_eq!(block_height, BlockHeight::new(1)); + } + (_, r) => panic!("Unexpected event: {:?}", r), + } + }) + .await; + } + }); - client.submit(&tx).await.unwrap(); // When - assert!(matches!( - dbg!(tx_statuses_subscriber.next().await.unwrap().unwrap()), - TransactionStatus::Submitted { .. } - )); - assert!(matches!( - tx_statuses_subscriber.next().await.unwrap().unwrap(), - TransactionStatus::PreconfirmationSuccess { .. } - )); - // Then - assert!(matches!( - tx_statuses_subscriber.next().await.unwrap().unwrap(), - TransactionStatus::Success { block_height, .. } if block_height == BlockHeight::new(2) - )); + tokio::time::sleep(block_production_period.checked_div(2).unwrap()).await; + client.submit(&tx).await.unwrap(); + + jh.await.unwrap(); } #[tokio::test] async fn preconfirmation__received_after_execution__multiple_txs() { + let mut rng = rand::thread_rng(); let mut config = Config::local_node(); - let block_production_period = Duration::from_secs(1); + config.block_production = Trigger::Never; - config.block_production = Trigger::Open { - period: block_production_period, - }; let srv = FuelService::new_node(config).await.unwrap(); let client = FuelClient::from(srv.bound_address); @@ -338,7 +344,13 @@ async fn preconfirmation__received_after_execution__multiple_txs() { vec![], ) .script_gas_limit(1_000_000) - .add_fee_input() + .add_unsigned_coin_input( + SecretKey::random(&mut rng), + rng.gen(), + 10, + AssetId::default(), + Default::default(), + ) .add_output(Output::variable( Address::new([0; 32]), 0, @@ -347,24 +359,32 @@ async fn preconfirmation__received_after_execution__multiple_txs() { .finalize_as_transaction(); let tx2 = TransactionBuilder::script( vec![op::ret(RegId::ONE)].into_iter().collect(), - vec![], + vec![1, 2, 3], ) .script_gas_limit(1_000_000) - .add_fee_input() + .add_unsigned_coin_input( + SecretKey::random(&mut rng), + rng.gen(), + 10, + AssetId::default(), + Default::default(), + ) .add_output(Output::variable( - Address::new([1; 32]), + Address::new([0; 32]), 0, AssetId::default(), )) .finalize_as_transaction(); // Given - let tx_id1 = client.submit(&tx1).await.unwrap(); + let tx_id1 = tx1.id(&Default::default()); + let tx_id2 = tx2.id(&Default::default()); let mut tx_statuses_subscriber1 = client.subscribe_transaction_status(&tx_id1).await.unwrap(); - let tx_id2 = client.submit(&tx2).await.unwrap(); let mut tx_statuses_subscriber2 = client.subscribe_transaction_status(&tx_id2).await.unwrap(); + client.submit(&tx1).await.unwrap(); + client.submit(&tx2).await.unwrap(); // When assert!(matches!( @@ -375,6 +395,7 @@ async fn preconfirmation__received_after_execution__multiple_txs() { tx_statuses_subscriber2.next().await.unwrap().unwrap(), TransactionStatus::Submitted { .. } )); + client.produce_blocks(1, None).await.unwrap(); assert!(matches!( tx_statuses_subscriber1.next().await.unwrap().unwrap(), TransactionStatus::PreconfirmationSuccess { .. } From ddec644be94d92c2049a2e47ddc92943139e50b0 Mon Sep 17 00:00:00 2001 From: AurelienFT Date: Thu, 13 Mar 2025 17:50:03 +0100 Subject: [PATCH 31/76] Change txpool submitted status emission to be sure it's before the executed one. --- .changes/added/2848.md | 1 + .../tests.rs | 2 +- crates/services/txpool_v2/src/pool.rs | 47 +++++++++++++++---- crates/services/txpool_v2/src/pool_worker.rs | 17 ++++--- crates/services/txpool_v2/src/service.rs | 20 ++------ .../services/txpool_v2/src/tests/universe.rs | 5 +- 6 files changed, 60 insertions(+), 32 deletions(-) create mode 100644 .changes/added/2848.md diff --git a/.changes/added/2848.md b/.changes/added/2848.md new file mode 100644 index 00000000000..e0a2667cf55 --- /dev/null +++ b/.changes/added/2848.md @@ -0,0 +1 @@ +Link all components of preconfirmations and add E2E tests. \ No newline at end of file diff --git a/crates/services/consensus_module/poa/src/pre_confirmation_signature_service/tests.rs b/crates/services/consensus_module/poa/src/pre_confirmation_signature_service/tests.rs index 9aef6df5f3a..76dafd4884a 100644 --- a/crates/services/consensus_module/poa/src/pre_confirmation_signature_service/tests.rs +++ b/crates/services/consensus_module/poa/src/pre_confirmation_signature_service/tests.rs @@ -238,7 +238,7 @@ impl TaskBuilder { let task = PreConfirmationSignatureTask { tx_receiver, broadcast, - parent_signature, + parent_signature: Arc::new(parent_signature), key_generator, current_delegate_key, sealed_delegate_message: Sealed { entity, signature }, diff --git a/crates/services/txpool_v2/src/pool.rs b/crates/services/txpool_v2/src/pool.rs index 43f5be5d346..dd0ae8df314 100644 --- a/crates/services/txpool_v2/src/pool.rs +++ b/crates/services/txpool_v2/src/pool.rs @@ -3,6 +3,7 @@ mod collisions; use std::{ collections::HashMap, iter, + sync::Arc, time::{ Instant, SystemTime, @@ -19,7 +20,9 @@ use fuel_core_types::{ services::txpool::{ ArcPoolTx, PoolTransaction, + TransactionStatus, }, + tai64::Tai64, }; use num_rational::Ratio; @@ -36,7 +39,10 @@ use crate::{ InsertionErrorType, }, extracted_outputs::ExtractedOutputs, - ports::TxPoolPersistentStorage, + ports::{ + TxPoolPersistentStorage, + TxStatusManager as TxStatusManagerTrait, + }, selection_algorithms::{ Constraints, SelectionAlgorithm, @@ -60,7 +66,7 @@ pub struct TxPoolStats { /// The pool is the main component of the txpool service. It is responsible for storing transactions /// and allowing the selection of transactions for inclusion in a block. -pub struct Pool { +pub struct Pool { /// Configuration of the pool. pub(crate) config: Config, /// The storage of the pool. @@ -81,9 +87,11 @@ pub struct Pool { pub(crate) pool_stats_sender: tokio::sync::watch::Sender, /// New executable transactions notifier. pub(crate) new_executable_txs_notifier: tokio::sync::watch::Sender<()>, + /// Transaction status manager. + pub(crate) tx_status_manager: Arc, } -impl Pool { +impl Pool { /// Create a new pool. pub fn new( storage: S, @@ -92,6 +100,7 @@ impl Pool { config: Config, pool_stats_sender: tokio::sync::watch::Sender, new_executable_txs_notifier: tokio::sync::watch::Sender<()>, + tx_status_manager: Arc, ) -> Self { Pool { storage, @@ -104,6 +113,7 @@ impl Pool { current_bytes_size: 0, pool_stats_sender, new_executable_txs_notifier, + tx_status_manager, } } @@ -113,11 +123,13 @@ impl Pool { } } -impl Pool +impl + Pool where S: Storage, CM: CollisionManager, SA: SelectionAlgorithm, + TxStatusManager: TxStatusManagerTrait, { /// Insert transactions into the pool. /// Returns a list of results for each transaction. @@ -128,16 +140,36 @@ where tx: ArcPoolTx, persistent_storage: &impl TxPoolPersistentStorage, ) -> Result, InsertionErrorType> { + let tx_id = tx.id(); let insertion_result = self.insert_inner(tx, persistent_storage); self.register_transaction_counts(); - insertion_result + insertion_result.map(|(removed_transactions, creation_instant, executable)| { + if executable { + self.new_executable_txs_notifier.send_replace(()); + } + let duration = i64::try_from( + creation_instant + .duration_since(SystemTime::UNIX_EPOCH) + .expect("Time can't be less than UNIX EPOCH") + .as_secs(), + ) + .expect("Duration is less than i64::MAX"); + self.tx_status_manager.status_update( + tx_id, + TransactionStatus::submitted(Tai64::from_unix(duration)), + ); + removed_transactions + }) } fn insert_inner( &mut self, tx: std::sync::Arc, persistent_storage: &impl TxPoolPersistentStorage, - ) -> Result>, InsertionErrorType> { + ) -> Result< + (Vec>, SystemTime, bool), + InsertionErrorType, + > { let CanStoreTransaction { checked_transaction, transactions_to_remove, @@ -190,7 +222,6 @@ where if !has_dependencies { self.selection_algorithm .new_executable_transaction(storage_id, tx); - self.new_executable_txs_notifier.send_replace(()); } let removed_transactions = removed_transactions @@ -198,7 +229,7 @@ where .map(|data| data.transaction) .collect::>(); self.update_stats(); - Ok(removed_transactions) + Ok((removed_transactions, creation_instant, !has_dependencies)) } fn update_stats(&self) { diff --git a/crates/services/txpool_v2/src/pool_worker.rs b/crates/services/txpool_v2/src/pool_worker.rs index a0fc9bffe25..0bc4774d769 100644 --- a/crates/services/txpool_v2/src/pool_worker.rs +++ b/crates/services/txpool_v2/src/pool_worker.rs @@ -39,7 +39,10 @@ use crate::{ RemovedReason, }, pending_pool::PendingPool, - ports::TxPoolPersistentStorage, + ports::{ + TxPoolPersistentStorage, + TxStatusManager as TxStatusManagerTrait, + }, service::{ TxInfo, TxPool, @@ -66,13 +69,14 @@ pub(super) struct PoolWorkerInterface { } impl PoolWorkerInterface { - pub fn new( - tx_pool: TxPool, + pub fn new( + tx_pool: TxPool, view_provider: Arc>, limits: &ServiceChannelLimits, ) -> Self where View: TxPoolPersistentStorage, + TxStatusManager: TxStatusManagerTrait, { let (request_read_sender, request_read_receiver) = mpsc::channel(limits.max_pending_read_pool_requests); @@ -252,22 +256,23 @@ pub(super) enum PoolNotification { }, } -pub(super) struct PoolWorker { +pub(super) struct PoolWorker { tx_insert_from_pending_sender: Sender, thread_management_receiver: Receiver, request_remove_receiver: Receiver, request_read_receiver: Receiver, extract_block_transactions_receiver: Receiver, request_insert_receiver: Receiver, - pool: TxPool, + pool: TxPool, pending_pool: PendingPool, view_provider: Arc>, notification_sender: Sender, } -impl PoolWorker +impl PoolWorker where View: TxPoolPersistentStorage, + TxStatusManager: TxStatusManagerTrait, { pub async fn run(&mut self, pending_pool_expiration_check: &mut Interval) -> bool { let mut remove_buffer = vec![]; diff --git a/crates/services/txpool_v2/src/service.rs b/crates/services/txpool_v2/src/service.rs index 14b83f5347f..4cab0c2fdf6 100644 --- a/crates/services/txpool_v2/src/service.rs +++ b/crates/services/txpool_v2/src/service.rs @@ -116,11 +116,12 @@ mod pruner; mod subscriptions; pub(crate) mod verifications; -pub type TxPool = Pool< +pub type TxPool = Pool< GraphStorage, ::StorageIndex, BasicCollisionManager<::StorageIndex>, RatioTipGasSelection, + TxStatusManager, >; pub(crate) type Shared = Arc>; @@ -372,15 +373,6 @@ where expiration, source, } => { - let duration = time - .duration_since(SystemTime::UNIX_EPOCH) - .expect("Time can't be less than UNIX EPOCH"); - // We do it at the top of the function to avoid any inconsistency in case of error - let Ok(duration) = i64::try_from(duration.as_secs()) else { - tracing::error!("Failed to convert the duration to i64"); - return - }; - match source { ExtendedInsertionSource::P2P { from_peer_info } => { let _ = self.p2p.notify_gossip_transaction_validity( @@ -404,10 +396,6 @@ where } self.pruner.time_txs_submitted.push_front((time, tx_id)); - self.tx_status_manager.status_update( - tx_id, - TransactionStatus::submitted(Tai64::from_unix(duration)), - ); if expiration < u32::MAX.into() { let block_height_expiration = self @@ -803,6 +791,7 @@ where let service_channel_limits = config.service_channel_limits; let utxo_validation = config.utxo_validation; + let tx_status_manager = Arc::new(tx_status_manager); let txpool = Pool::new( GraphStorage::new(GraphConfig { max_txs_chain_count: config.max_txs_chain_count, @@ -812,6 +801,7 @@ where config, pool_stats_sender, new_txs_notifier.clone(), + tx_status_manager.clone(), ); // BlockHeight is < 64 bytes, so we can use SeqLock @@ -847,6 +837,6 @@ where shared_state, metrics, tx_sync_history: Default::default(), - tx_status_manager: Arc::new(tx_status_manager), + tx_status_manager, }) } diff --git a/crates/services/txpool_v2/src/tests/universe.rs b/crates/services/txpool_v2/src/tests/universe.rs index 702615a30ec..628e71caa55 100644 --- a/crates/services/txpool_v2/src/tests/universe.rs +++ b/crates/services/txpool_v2/src/tests/universe.rs @@ -127,7 +127,7 @@ pub struct TestPoolUniverse { mock_db: MockDb, rng: StdRng, pub config: Config, - pool: Option>, + pool: Option>>, mock_tx_status_manager: MockTxStatusManager, tx_status_manager_receiver: mpsc::Receiver<(TxId, TransactionStatus)>, stats_receiver: Option>, @@ -185,6 +185,7 @@ impl TestPoolUniverse { self.config.clone(), tx, tx_new_executable_txs, + Arc::new(MockTxStatusManager::new(mpsc::channel(100).0)), ))); self.stats_receiver = Some(rx); self.pool = Some(pool.clone()); @@ -393,7 +394,7 @@ impl TestPoolUniverse { pool.assert_integrity(txs); } - pub fn get_pool(&self) -> Shared { + pub fn get_pool(&self) -> Shared> { self.pool.clone().unwrap() } From b954b9ed4b334759a6111678cb8e85bb91c13151 Mon Sep 17 00:00:00 2001 From: AurelienFT Date: Thu, 13 Mar 2025 17:52:20 +0100 Subject: [PATCH 32/76] improve move of submitted --- crates/services/txpool_v2/src/pool.rs | 38 +++++++++++---------------- 1 file changed, 15 insertions(+), 23 deletions(-) diff --git a/crates/services/txpool_v2/src/pool.rs b/crates/services/txpool_v2/src/pool.rs index dd0ae8df314..ad5acd339f3 100644 --- a/crates/services/txpool_v2/src/pool.rs +++ b/crates/services/txpool_v2/src/pool.rs @@ -140,36 +140,16 @@ where tx: ArcPoolTx, persistent_storage: &impl TxPoolPersistentStorage, ) -> Result, InsertionErrorType> { - let tx_id = tx.id(); let insertion_result = self.insert_inner(tx, persistent_storage); self.register_transaction_counts(); - insertion_result.map(|(removed_transactions, creation_instant, executable)| { - if executable { - self.new_executable_txs_notifier.send_replace(()); - } - let duration = i64::try_from( - creation_instant - .duration_since(SystemTime::UNIX_EPOCH) - .expect("Time can't be less than UNIX EPOCH") - .as_secs(), - ) - .expect("Duration is less than i64::MAX"); - self.tx_status_manager.status_update( - tx_id, - TransactionStatus::submitted(Tai64::from_unix(duration)), - ); - removed_transactions - }) + insertion_result } fn insert_inner( &mut self, tx: std::sync::Arc, persistent_storage: &impl TxPoolPersistentStorage, - ) -> Result< - (Vec>, SystemTime, bool), - InsertionErrorType, - > { + ) -> Result>, InsertionErrorType> { let CanStoreTransaction { checked_transaction, transactions_to_remove, @@ -218,10 +198,22 @@ where Storage::get(&self.storage, &storage_id).expect("Transaction is set above"); self.collision_manager.on_stored_transaction(storage_id, tx); + let duration = i64::try_from( + creation_instant + .duration_since(SystemTime::UNIX_EPOCH) + .expect("Time can't be less than UNIX EPOCH") + .as_secs(), + ) + .expect("Duration is less than i64::MAX"); + self.tx_status_manager.status_update( + tx_id, + TransactionStatus::submitted(Tai64::from_unix(duration)), + ); // No dependencies directly in the graph and the sorted transactions if !has_dependencies { self.selection_algorithm .new_executable_transaction(storage_id, tx); + self.new_executable_txs_notifier.send_replace(()); } let removed_transactions = removed_transactions @@ -229,7 +221,7 @@ where .map(|data| data.transaction) .collect::>(); self.update_stats(); - Ok((removed_transactions, creation_instant, !has_dependencies)) + Ok(removed_transactions) } fn update_stats(&self) { From 329f7ddb129a383cfa18c1026c871c53552aa1bb Mon Sep 17 00:00:00 2001 From: Mitchell Turner Date: Thu, 13 Mar 2025 22:02:37 -0600 Subject: [PATCH 33/76] remove trait and replace with concrete impl (#2869) ## Linked Issues/PRs ## Description ## Checklist - [ ] Breaking changes are clearly marked as such in the PR description and changelog - [ ] New behavior is reflected in tests - [ ] [The specification](https://github.com/FuelLabs/fuel-specs/) matches the implemented behavior (link update PR if changes are needed) ### Before requesting review - [ ] I have reviewed the code myself - [ ] I have created follow-up issues caused by this PR and linked them here ### After merging, notify other teams [Add or remove entries as needed] - [ ] [Rust SDK](https://github.com/FuelLabs/fuels-rs/) - [ ] [Sway compiler](https://github.com/FuelLabs/sway/) - [ ] [Platform documentation](https://github.com/FuelLabs/devrel-requests/issues/new?assignees=&labels=new+request&projects=&template=NEW-REQUEST.yml&title=%5BRequest%5D%3A+) (for out-of-organization contributors, the person merging the PR will do this) - [ ] Someone else? --- Cargo.lock | 1 + bin/fuel-core/src/cli/run.rs | 2 + .../src/service/adapters/tx_status_manager.rs | 2 - .../signature_verification.rs | 34 - crates/fuel-core/src/service/sub_services.rs | 4 - crates/services/tx_status_manager/Cargo.toml | 3 +- .../services/tx_status_manager/src/config.rs | 4 + .../services/tx_status_manager/src/service.rs | 603 ++++++++++++++---- .../tx_status_manager/src/tests/mod.rs | 77 --- .../tx_status_manager/src/tests/universe.rs | 10 +- 10 files changed, 484 insertions(+), 256 deletions(-) delete mode 100644 crates/fuel-core/src/service/adapters/tx_status_manager/signature_verification.rs diff --git a/Cargo.lock b/Cargo.lock index acbf40354d8..9c94c688638 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4071,6 +4071,7 @@ dependencies = [ "futures", "mockall", "parking_lot", + "postcard", "proptest", "test-case", "test-strategy", diff --git a/bin/fuel-core/src/cli/run.rs b/bin/fuel-core/src/cli/run.rs index 25e3d9083be..341f800d640 100644 --- a/bin/fuel-core/src/cli/run.rs +++ b/bin/fuel-core/src/cli/run.rs @@ -714,6 +714,8 @@ impl Command { max_tx_update_subscriptions: tx_number_active_subscriptions, subscription_ttl, status_cache_ttl: status_cache_ttl.into(), + // TODO: Where can I get this value? + protocol_public_key: Default::default(), }, }; Ok(config) diff --git a/crates/fuel-core/src/service/adapters/tx_status_manager.rs b/crates/fuel-core/src/service/adapters/tx_status_manager.rs index e7585e03e09..7030ae502fb 100644 --- a/crates/fuel-core/src/service/adapters/tx_status_manager.rs +++ b/crates/fuel-core/src/service/adapters/tx_status_manager.rs @@ -3,8 +3,6 @@ use fuel_core_tx_status_manager::ports::P2PPreConfirmationGossipData; use super::P2PAdapter; -pub mod signature_verification; - #[cfg(feature = "p2p")] impl fuel_core_tx_status_manager::ports::P2PSubscriptions for P2PAdapter { type GossipedStatuses = P2PPreConfirmationGossipData; diff --git a/crates/fuel-core/src/service/adapters/tx_status_manager/signature_verification.rs b/crates/fuel-core/src/service/adapters/tx_status_manager/signature_verification.rs deleted file mode 100644 index 0b9f6652eaf..00000000000 --- a/crates/fuel-core/src/service/adapters/tx_status_manager/signature_verification.rs +++ /dev/null @@ -1,34 +0,0 @@ -use fuel_core_tx_status_manager::service::SignatureVerification; -use fuel_core_types::{ - fuel_tx::Bytes64, - services::{ - p2p::{ - DelegatePreConfirmationKey, - DelegatePublicKey, - ProtocolSignature, - Sealed, - }, - preconfirmation::Preconfirmations, - }, -}; - -pub struct PreconfirmationSignatureVerification; - -impl SignatureVerification for PreconfirmationSignatureVerification { - async fn add_new_delegate( - &mut self, - _sealed: &Sealed< - DelegatePreConfirmationKey, - ProtocolSignature, - >, - ) -> bool { - true - } - - async fn check_preconfirmation_signature( - &mut self, - _sealed: &Sealed, - ) -> bool { - true - } -} diff --git a/crates/fuel-core/src/service/sub_services.rs b/crates/fuel-core/src/service/sub_services.rs index 48bbfaa48b8..26616af2b53 100644 --- a/crates/fuel-core/src/service/sub_services.rs +++ b/crates/fuel-core/src/service/sub_services.rs @@ -67,7 +67,6 @@ use crate::{ graphql_api::GraphQLBlockImporter, import_result_provider::ImportResultProvider, ready_signal::ReadySignal, - tx_status_manager::signature_verification::PreconfirmationSignatureVerification, BlockImporterAdapter, BlockProducerAdapter, ChainStateInfoProvider, @@ -257,11 +256,8 @@ pub fn init_sub_services( universal_gas_price_provider.clone(), ); - let signature_verification = PreconfirmationSignatureVerification; - let tx_status_manager = fuel_core_tx_status_manager::new_service( p2p_adapter.clone(), - signature_verification, config.tx_status_manager.clone(), ); let tx_status_manager_adapter = diff --git a/crates/services/tx_status_manager/Cargo.toml b/crates/services/tx_status_manager/Cargo.toml index 7876e51e8e5..7f067942beb 100644 --- a/crates/services/tx_status_manager/Cargo.toml +++ b/crates/services/tx_status_manager/Cargo.toml @@ -13,9 +13,10 @@ anyhow = { workspace = true } async-trait = { workspace = true } derive_more = { workspace = true } fuel-core-services = { workspace = true } -fuel-core-types = { workspace = true, features = ["std"] } +fuel-core-types = { workspace = true, features = ["std", "serde"] } futures = { workspace = true } parking_lot = { workspace = true } +postcard = { workspace = true } tokio = { workspace = true } tokio-stream = { workspace = true } tracing = { workspace = true } diff --git a/crates/services/tx_status_manager/src/config.rs b/crates/services/tx_status_manager/src/config.rs index 2621d622c82..d0300e3dfa4 100644 --- a/crates/services/tx_status_manager/src/config.rs +++ b/crates/services/tx_status_manager/src/config.rs @@ -1,3 +1,4 @@ +use fuel_core_types::fuel_crypto::PublicKey; use std::time::Duration; #[derive(Clone, Debug)] @@ -8,6 +9,8 @@ pub struct Config { pub subscription_ttl: Duration, /// Maximum time to keep the status in the cache of the manager. pub status_cache_ttl: Duration, + /// Protocol Signing Key, i.e. the block signer's public key + pub protocol_public_key: PublicKey, } #[cfg(feature = "test-helpers")] @@ -17,6 +20,7 @@ impl Default for Config { max_tx_update_subscriptions: 1000, subscription_ttl: Duration::from_secs(60 * 10), status_cache_ttl: Duration::from_secs(5), + protocol_public_key: PublicKey::default(), } } } diff --git a/crates/services/tx_status_manager/src/service.rs b/crates/services/tx_status_manager/src/service.rs index c110e9f2736..0796c96fa12 100644 --- a/crates/services/tx_status_manager/src/service.rs +++ b/crates/services/tx_status_manager/src/service.rs @@ -18,6 +18,12 @@ use fuel_core_services::{ TaskNextAction, }; use fuel_core_types::{ + ed25519::Signature, + ed25519_dalek::Verifier, + fuel_crypto::{ + Message, + PublicKey, + }, fuel_tx::{ Bytes32, Bytes64, @@ -38,9 +44,10 @@ use fuel_core_types::{ }, txpool::TransactionStatus, }, + tai64::Tai64, }; use futures::StreamExt; -use std::future::Future; +use std::collections::HashMap; use tokio::sync::{ mpsc, oneshot, @@ -102,32 +109,93 @@ impl SharedData { } } -pub struct Task { +pub struct Task { manager: TxStatusManager, subscriptions: Subscriptions, read_requests_receiver: mpsc::Receiver, write_requests_receiver: mpsc::UnboundedReceiver, shared_data: SharedData, - signature_verification: T, + signature_verification: SignatureVerification, early_preconfirmations: Vec>, } -/// Interface for signature verification of preconfirmations -pub trait SignatureVerification: Send { - /// Adds a new delegate signature to verify the preconfirmations - fn add_new_delegate( +struct SignatureVerification { + protocol_pubkey: PublicKey, + delegate_keys: HashMap, +} + +impl SignatureVerification { + pub fn new(protocol_pubkey: PublicKey) -> Self { + Self { + protocol_pubkey, + delegate_keys: HashMap::new(), + } + } + + fn verify_preconfirmation( + delegate_key: &DelegatePublicKey, + sealed: &Sealed, + ) -> bool { + let bytes = match postcard::to_allocvec(&sealed.entity) { + Ok(bytes) => bytes, + Err(e) => { + tracing::warn!("Failed to serialize preconfirmation: {e:?}"); + return false; + } + }; + + let signature = Signature::from_bytes(&sealed.signature); + match delegate_key.verify(&bytes, &signature) { + Ok(_) => true, + Err(e) => { + tracing::warn!("Failed to verify preconfirmation signature: {e:?}"); + false + } + } + } + + fn remove_expired_delegates(&mut self) { + let now = Tai64::now(); + self.delegate_keys.retain(|exp, _| exp > &now); + } + + async fn add_new_delegate( &mut self, sealed: &Sealed, ProtocolSignature>, - ) -> impl Future + Send; + ) -> bool { + let Sealed { entity, signature } = sealed; + let bytes = postcard::to_allocvec(&entity).unwrap(); + let message = Message::new(&bytes); + let verified = signature.verify(&self.protocol_pubkey, &message); + self.remove_expired_delegates(); + match verified { + Ok(_) => { + self.delegate_keys + .insert(entity.expiration, entity.public_key); + true + } + Err(_) => false, + } + } - /// Checks pre-confirmation signature - fn check_preconfirmation_signature( + async fn check_preconfirmation_signature( &mut self, sealed: &Sealed, - ) -> impl Future + Send; + ) -> bool { + let expiration = sealed.entity.expiration; + let now = Tai64::now(); + if now > expiration { + tracing::warn!("Preconfirmation signature expired: {now:?} > {expiration:?}"); + return false; + } + self.delegate_keys + .get(&expiration) + .map(|delegate_key| Self::verify_preconfirmation(delegate_key, sealed)) + .unwrap_or(false) + } } -impl Task { +impl Task { fn handle_verified_preconfirmation( &mut self, sealed: Sealed, @@ -185,7 +253,7 @@ impl Task { } #[async_trait::async_trait] -impl RunnableService for Task { +impl RunnableService for Task { const NAME: &'static str = "TxStatusManagerTask"; type SharedData = SharedData; type Task = Self; @@ -204,7 +272,7 @@ impl RunnableService for Task { } } -impl RunnableTask for Task { +impl RunnableTask for Task { async fn run(&mut self, watcher: &mut StateWatcher) -> TaskNextAction { tokio::select! { biased; @@ -261,11 +329,7 @@ impl RunnableTask for Task { } } -pub fn new_service( - p2p: P2P, - signature_verification: Sign, - config: Config, -) -> ServiceRunner> +pub fn new_service(p2p: P2P, config: Config) -> ServiceRunner where P2P: P2PSubscriptions, { @@ -287,6 +351,7 @@ where read_requests_sender, write_requests_sender, }; + let signature_verification = SignatureVerification::new(config.protocol_public_key); ServiceRunner::new(Task { subscriptions, @@ -305,7 +370,6 @@ mod tests { use super::*; use crate::{ - tests::FakeSignatureVerification, update_sender::{ MpscChannel, UpdateSender, @@ -313,14 +377,26 @@ mod tests { TxStatusMessage, }; use fuel_core_services::Service; - use fuel_core_types::services::{ - p2p::{ - DelegatePreConfirmationKey, - Tai64, + use fuel_core_types::{ + ed25519_dalek::{ + Signer, + SigningKey as DalekSigningKey, + VerifyingKey as DalekVerifyingKey, }, - preconfirmation::{ - PreconfirmationStatus, - Preconfirmations, + fuel_crypto::{ + Message, + SecretKey, + Signature, + }, + services::{ + p2p::{ + DelegatePreConfirmationKey, + Tai64, + }, + preconfirmation::{ + PreconfirmationStatus, + Preconfirmations, + }, }, }; use std::time::Duration; @@ -331,11 +407,10 @@ mod tests { struct Handles { pub pre_confirmation_updates: mpsc::Sender, pub update_sender: UpdateSender, + pub protocol_signing_key: SecretKey, } - fn new_task_with_handles( - signature_verification: T, - ) -> (Task, Handles) { + fn new_task_with_handles() -> (Task, Handles) { let (read_requests_sender, read_requests_receiver) = mpsc::channel(1); let (write_requests_sender, write_requests_receiver) = mpsc::unbounded_channel(); let shared_data = SharedData { @@ -349,6 +424,9 @@ mod tests { let tx_status_change = TxStatusChange::new(100, Duration::from_secs(360)); let updater_sender = tx_status_change.update_sender.clone(); let tx_status_manager = TxStatusManager::new(tx_status_change, TTL); + let signing_key = SecretKey::default(); + let protocol_public_key = signing_key.public_key(); + let signature_verification = SignatureVerification::new(protocol_public_key); let task = Task { manager: tx_status_manager, subscriptions, @@ -361,18 +439,52 @@ mod tests { let handles = Handles { pre_confirmation_updates: sender, update_sender: updater_sender, + protocol_signing_key: signing_key, }; (task, handles) } - fn arbitrary_delegate_signatures_message() -> P2PPreConfirmationGossipData { - let signature = ProtocolSignature::from_bytes([1u8; 64]); - let delegate_key = DelegatePublicKey::default(); + async fn all_streams_return_success(streams: Vec) -> bool { + for mut stream in streams { + let timeout = Duration::from_millis(100); + let msg = tokio::time::timeout(timeout, stream.next()) + .await + .unwrap_or_else(|_| panic!("This should not timeout: {timeout:?}")) + .unwrap(); + match msg { + TxStatusMessage::Status(_) => { + // should be good if we get this + } + _ => return false, + } + } + true + } + + async fn all_streams_timeout(streams: &mut Vec) -> bool { + for stream in streams { + let timeout = Duration::from_millis(100); + let res = tokio::time::timeout(timeout, stream.next()).await; + if res.is_ok() { + return false; + } + } + true + } + + fn valid_sealed_delegate_signature( + protocol_secret_key: SecretKey, + delegate_public_key: DelegatePublicKey, + expiration: Tai64, + ) -> P2PPreConfirmationGossipData { let entity = DelegatePreConfirmationKey { - public_key: delegate_key, - expiration: Tai64(1234u64), + public_key: delegate_public_key, + expiration, }; - let sealed = Sealed { signature, entity }; + let bytes = postcard::to_allocvec(&entity).unwrap(); + let message = Message::new(&bytes); + let signature = Signature::sign(&protocol_secret_key, &message); + let sealed = Sealed { entity, signature }; let inner = P2PPreConfirmationMessage::Delegate(sealed); GossipData { data: Some(inner), @@ -381,28 +493,46 @@ mod tests { } } - fn arbitrary_pre_confirmation_message( - tx_ids: &[TxId], + fn valid_pre_confirmation_signature( + preconfirmations: Vec, + delegate_private_key: DalekSigningKey, + expiration: Tai64, ) -> P2PPreConfirmationGossipData { - let preconfirmations = tx_ids - .iter() - .map(|tx_id| Preconfirmation { - tx_id: *tx_id, - status: PreconfirmationStatus::Success { - tx_pointer: Default::default(), - total_gas: 0, - total_fee: 0, - receipts: vec![], - outputs: vec![], - }, - }) - .collect::>(); let entity = Preconfirmations { + expiration, + preconfirmations, + }; + let bytes = postcard::to_allocvec(&entity).unwrap(); + let typed_signature = delegate_private_key.sign(&bytes); + let signature = Bytes64::new(typed_signature.to_bytes()); + let sealed = Sealed { entity, signature }; + let inner = P2PPreConfirmationMessage::Preconfirmations(sealed); + GossipData { + data: Some(inner), + peer_id: Default::default(), + message_id: vec![], + } + } + + fn bad_pre_confirmation_signature( + preconfirmations: Vec, + delegate_private_key: DalekSigningKey, + expiration: Tai64, + ) -> P2PPreConfirmationGossipData { + let mut mutated_private_key = delegate_private_key.to_bytes(); + for byte in mutated_private_key.iter_mut() { + *byte = byte.wrapping_add(1); + } + let mutated_delegate_private_key = + DalekSigningKey::from_bytes(&mutated_private_key); + let entity = Preconfirmations { + expiration, preconfirmations, - expiration: Tai64(1234u64), }; - let signature = Bytes64::from([1u8; 64]); - let sealed = Sealed { signature, entity }; + let bytes = postcard::to_allocvec(&entity).unwrap(); + let typed_signature = mutated_delegate_private_key.sign(&bytes); + let signature = Bytes64::new(typed_signature.to_bytes()); + let sealed = Sealed { entity, signature }; let inner = P2PPreConfirmationMessage::Preconfirmations(sealed); GossipData { data: Some(inner), @@ -411,67 +541,163 @@ mod tests { } } + fn delegate_key_pair() -> (DalekSigningKey, DalekVerifyingKey) { + let secret_key = [99u8; 32]; + let secret_key = DalekSigningKey::from_bytes(&secret_key); + let public_key = secret_key.verifying_key(); + (secret_key, public_key) + } + #[tokio::test] - async fn run__when_receive_pre_confirmation_delegations_message_updates_delegate() { + async fn run__when_pre_confirmations_pass_verification_then_send() { // given - let (signature_verification, mut new_delegate_handle) = - FakeSignatureVerification::new_with_handles(true); - let (mut task, handles) = new_task_with_handles(signature_verification); - let delegate_signature_message = arbitrary_delegate_signatures_message(); - let mut state_watcher = StateWatcher::started(); + let (task, handles) = new_task_with_handles(); + + let tx_ids = vec![[3u8; 32].into(), [4u8; 32].into()]; + let preconfirmations = tx_ids + .clone() + .into_iter() + .map(|tx_id| Preconfirmation { + tx_id, + status: PreconfirmationStatus::Success { + tx_pointer: Default::default(), + total_gas: 0, + total_fee: 0, + receipts: vec![], + outputs: vec![], + }, + }) + .collect(); + let (delegate_signing_key, delegate_verifying_key) = delegate_key_pair(); + let expiration = Tai64(u64::MAX); + let pre_confirmation_message = valid_pre_confirmation_signature( + preconfirmations, + delegate_signing_key, + expiration, + ); + let delegate_signature_message = valid_sealed_delegate_signature( + handles.protocol_signing_key, + delegate_verifying_key, + expiration, + ); + + let streams = tx_ids + .iter() + .map(|tx_id| { + handles + .update_sender + .try_subscribe::(*tx_id) + .unwrap() + }) + .collect::>(); + handles + .pre_confirmation_updates + .send(delegate_signature_message) + .await + .unwrap(); + + let service = ServiceRunner::new(task); + service.start_and_await().await.unwrap(); // when - tokio::task::spawn(async move { - let _ = task.run(&mut state_watcher).await; - }); handles .pre_confirmation_updates - .send(delegate_signature_message.clone()) + .send(pre_confirmation_message.clone()) .await .unwrap(); tokio::time::sleep(Duration::from_millis(100)).await; // then - let actual = new_delegate_handle - .new_delegate_receiver - .recv() + assert!(all_streams_return_success(streams).await); + } + + #[tokio::test] + async fn run__when_pre_confirmations_unknown_delegate_key_then_do_not_send() { + // given + let (task, handles) = new_task_with_handles(); + + let tx_ids = vec![[3u8; 32].into(), [4u8; 32].into()]; + let preconfirmations = tx_ids + .clone() + .into_iter() + .map(|tx_id| Preconfirmation { + tx_id, + status: PreconfirmationStatus::Success { + tx_pointer: Default::default(), + total_gas: 0, + total_fee: 0, + receipts: vec![], + outputs: vec![], + }, + }) + .collect(); + let (delegate_signing_key, _) = delegate_key_pair(); + let expiration = Tai64(u64::MAX); + let invalid_pre_confirmation_message = bad_pre_confirmation_signature( + preconfirmations, + delegate_signing_key, + expiration, + ); + + let mut streams = tx_ids + .iter() + .map(|tx_id| { + handles + .update_sender + .try_subscribe::(*tx_id) + .unwrap() + }) + .collect::>(); + + let service = ServiceRunner::new(task); + service.start_and_await().await.unwrap(); + + // when + handles + .pre_confirmation_updates + .send(invalid_pre_confirmation_message) .await .unwrap(); - let expected = if let PreConfirmationMessage::Delegate(delegate_info) = - delegate_signature_message.data.unwrap() - { - delegate_info - } else { - panic!("Expected Delegate message"); - }; - assert_eq!(actual, expected); - } + tokio::time::sleep(Duration::from_millis(100)).await; - async fn all_streams_return_success(streams: Vec) -> bool { - for mut stream in streams { - let msg = stream.next().await.unwrap(); - match msg { - TxStatusMessage::Status(_) => { - // should be good if we get this - } - _ => return false, - } - } - true + // then + assert!(all_streams_timeout(&mut streams).await); } #[tokio::test] - async fn run__when_pre_confirmations_pass_verification_then_send() { + async fn run__when_pre_confirmations_bad_signature_then_do_not_send() { // given - let (signature_verification, _) = - FakeSignatureVerification::new_with_handles(true); - let (mut task, handles) = new_task_with_handles(signature_verification); + let (task, handles) = new_task_with_handles(); let tx_ids = vec![[3u8; 32].into(), [4u8; 32].into()]; - let pre_confirmation_message = arbitrary_pre_confirmation_message(&tx_ids); - let mut state_watcher = StateWatcher::started(); + let preconfirmations = tx_ids + .clone() + .into_iter() + .map(|tx_id| Preconfirmation { + tx_id, + status: PreconfirmationStatus::Success { + tx_pointer: Default::default(), + total_gas: 0, + total_fee: 0, + receipts: vec![], + outputs: vec![], + }, + }) + .collect(); + let (delegate_signing_key, delegate_verifying_key) = delegate_key_pair(); + let expiration = Tai64(u64::MAX); + let invalid_pre_confirmation_message = bad_pre_confirmation_signature( + preconfirmations, + delegate_signing_key, + expiration, + ); + let delegate_signature_message = valid_sealed_delegate_signature( + handles.protocol_signing_key, + delegate_verifying_key, + expiration, + ); - let streams = tx_ids + let mut streams = tx_ids .iter() .map(|tx_id| { handles @@ -480,34 +706,62 @@ mod tests { .unwrap() }) .collect::>(); + handles + .pre_confirmation_updates + .send(delegate_signature_message) + .await + .unwrap(); + + let service = ServiceRunner::new(task); + service.start_and_await().await.unwrap(); // when - tokio::task::spawn(async move { - let _ = task.run(&mut state_watcher).await; - }); handles .pre_confirmation_updates - .send(pre_confirmation_message.clone()) + .send(invalid_pre_confirmation_message) .await .unwrap(); tokio::time::sleep(Duration::from_millis(100)).await; // then - all_streams_return_success(streams).await; + assert!(all_streams_timeout(&mut streams).await); } #[tokio::test] - async fn run__when_pre_confirmations_fail_verification_then_do_not_send() { + async fn run__when_pre_confirmations_fail_verification_they_can_be_retried_on_next_delegate_update( + ) { // given - let (signature_verification, _) = - FakeSignatureVerification::new_with_handles(false); - let (mut task, handles) = new_task_with_handles(signature_verification); + let (task, handles) = new_task_with_handles(); let tx_ids = vec![[3u8; 32].into(), [4u8; 32].into()]; - let pre_confirmation_message = arbitrary_pre_confirmation_message(&tx_ids); - let mut state_watcher = StateWatcher::started(); + let preconfirmations = tx_ids + .clone() + .into_iter() + .map(|tx_id| Preconfirmation { + tx_id, + status: PreconfirmationStatus::Success { + tx_pointer: Default::default(), + total_gas: 0, + total_fee: 0, + receipts: vec![], + outputs: vec![], + }, + }) + .collect(); + let (delegate_signing_key, delegate_verifying_key) = delegate_key_pair(); + let expiration = Tai64(u64::MAX); + let pre_confirmation_message = valid_pre_confirmation_signature( + preconfirmations, + delegate_signing_key, + expiration, + ); + let delegate_signature_message = valid_sealed_delegate_signature( + handles.protocol_signing_key, + delegate_verifying_key, + expiration, + ); - let streams = tx_ids + let mut streams = tx_ids .iter() .map(|tx_id| { handles @@ -516,36 +770,62 @@ mod tests { .unwrap() }) .collect::>(); + handles + .pre_confirmation_updates + .send(pre_confirmation_message) + .await + .unwrap(); + + let service = ServiceRunner::new(task); + service.start_and_await().await.unwrap(); + + assert!(all_streams_timeout(&mut streams).await); // when - tokio::task::spawn(async move { - let _ = task.run(&mut state_watcher).await; - }); handles .pre_confirmation_updates - .send(pre_confirmation_message.clone()) + .send(delegate_signature_message) .await .unwrap(); tokio::time::sleep(Duration::from_millis(100)).await; // then - for mut stream in streams { - let res = - tokio::time::timeout(Duration::from_millis(100), stream.next()).await; - assert!(res.is_err()); - } + assert!(all_streams_return_success(streams).await); } #[tokio::test] - async fn run__when_pre_confirmations_fail_verification_they_can_be_retried_on_next_delegate_update( - ) { + async fn run__if_preconfirmation_signature_is_expired_do_not_send() { // given - let (signature_verification, mut fake_signature_handles) = - FakeSignatureVerification::new_with_handles(false); - let (task, handles) = new_task_with_handles(signature_verification); + let (task, handles) = new_task_with_handles(); let tx_ids = vec![[3u8; 32].into(), [4u8; 32].into()]; - let pre_confirmation_message = arbitrary_pre_confirmation_message(&tx_ids); + let preconfirmations = tx_ids + .clone() + .into_iter() + .map(|tx_id| Preconfirmation { + tx_id, + status: PreconfirmationStatus::Success { + tx_pointer: Default::default(), + total_gas: 0, + total_fee: 0, + receipts: vec![], + outputs: vec![], + }, + }) + .collect(); + let (delegate_signing_key, delegate_verifying_key) = delegate_key_pair(); + let expiration = Tai64::now(); + let pre_confirmation_message = valid_pre_confirmation_signature( + preconfirmations, + delegate_signing_key, + expiration, + ); + + let delegate_signature_message = valid_sealed_delegate_signature( + handles.protocol_signing_key, + delegate_verifying_key, + expiration, + ); let mut streams = tx_ids .iter() @@ -559,29 +839,92 @@ mod tests { let service = ServiceRunner::new(task); service.start_and_await().await.unwrap(); + handles .pre_confirmation_updates - .send(pre_confirmation_message.clone()) + .send(delegate_signature_message) + .await + .unwrap(); + + tokio::time::sleep(Duration::from_secs(1)).await; + + // when + handles + .pre_confirmation_updates + .send(pre_confirmation_message) .await .unwrap(); tokio::time::sleep(Duration::from_millis(100)).await; - for stream in &mut streams { - let res = - tokio::time::timeout(Duration::from_millis(100), stream.next()).await; - assert!(res.is_err()); + // then + assert!(all_streams_timeout(&mut streams).await); + } + + #[tokio::test] + async fn run__can_verify_preconfirmation_signature_while_tracking_multiple_delegate_keys( + ) { + // given + let (task, handles) = new_task_with_handles(); + let (delegate_secret_key, delegate_public_key) = delegate_key_pair(); + let first_expiration = Tai64(u64::MAX - 200); + + let tx_ids = vec![[3u8; 32].into(), [4u8; 32].into()]; + let preconfirmations = tx_ids + .clone() + .into_iter() + .map(|tx_id| Preconfirmation { + tx_id, + status: PreconfirmationStatus::Success { + tx_pointer: Default::default(), + total_gas: 0, + total_fee: 0, + receipts: vec![], + outputs: vec![], + }, + }) + .collect(); + let valid_pre_confirmation_message = valid_pre_confirmation_signature( + preconfirmations, + delegate_secret_key, + first_expiration, + ); + + let streams = tx_ids + .iter() + .map(|tx_id| { + handles + .update_sender + .try_subscribe::(*tx_id) + .unwrap() + }) + .collect::>(); + + let service = ServiceRunner::new(task); + service.start_and_await().await.unwrap(); + + for expiration_modifier in 0..100u64 { + let expiration = first_expiration + expiration_modifier; + let valid_delegate_signature = valid_sealed_delegate_signature( + handles.protocol_signing_key, + delegate_public_key, + expiration, + ); + handles + .pre_confirmation_updates + .send(valid_delegate_signature) + .await + .unwrap(); } // when - let new_delegate_message = arbitrary_delegate_signatures_message(); - fake_signature_handles.update_signature_verification_result(true); handles .pre_confirmation_updates - .send(new_delegate_message) + .send(valid_pre_confirmation_message) .await .unwrap(); + tokio::time::sleep(Duration::from_millis(100)).await; // then - all_streams_return_success(streams).await; + assert!(all_streams_return_success(streams).await); } } diff --git a/crates/services/tx_status_manager/src/tests/mod.rs b/crates/services/tx_status_manager/src/tests/mod.rs index b6aab80dbdf..b1ef04ad8ba 100644 --- a/crates/services/tx_status_manager/src/tests/mod.rs +++ b/crates/services/tx_status_manager/src/tests/mod.rs @@ -1,20 +1,5 @@ #![allow(non_snake_case)] -use crate::service::SignatureVerification; -use fuel_core_types::{ - fuel_tx::Bytes64, - services::{ - p2p::{ - DelegatePublicKey, - ProtocolSignature, - Sealed, - }, - preconfirmation::Preconfirmations, - }, -}; -use std::sync::Arc; -use tokio::sync::mpsc; - mod mocks; mod tests_e2e; mod tests_permits; @@ -25,66 +10,4 @@ mod tests_update_stream_state; mod universe; mod utils; -use fuel_core_types::services::p2p::DelegatePreConfirmationKey; use tracing_subscriber as _; - -pub(crate) struct FakeSignatureVerification { - new_delegate_sender: mpsc::Sender< - Sealed, ProtocolSignature>, - >, - preconfirmation_signature_success: Arc, -} - -pub(crate) struct FakeSignatureVerificationHandles { - pub new_delegate_receiver: mpsc::Receiver< - Sealed, ProtocolSignature>, - >, - pub verification_result: Arc, -} - -impl FakeSignatureVerificationHandles { - pub fn update_signature_verification_result(&mut self, result: bool) { - self.verification_result - .store(result, std::sync::atomic::Ordering::Relaxed); - } -} - -impl FakeSignatureVerification { - pub fn new_with_handles( - preconfirmation_signature_success: bool, - ) -> (Self, FakeSignatureVerificationHandles) { - let (new_delegate_sender, new_delegate_receiver) = mpsc::channel(1_000); - let preconfirmation_signature_success = Arc::new( - std::sync::atomic::AtomicBool::new(preconfirmation_signature_success), - ); - let verification_result = preconfirmation_signature_success.clone(); - let adapter = Self { - new_delegate_sender, - preconfirmation_signature_success, - }; - let handles = FakeSignatureVerificationHandles { - new_delegate_receiver, - verification_result, - }; - (adapter, handles) - } -} - -impl SignatureVerification for FakeSignatureVerification { - async fn add_new_delegate( - &mut self, - sealed: &Sealed, ProtocolSignature>, - ) -> bool { - tracing::debug!("FakeSignatureVerification::add_new_delegate"); - self.new_delegate_sender.send(sealed.clone()).await.unwrap(); - true - } - - async fn check_preconfirmation_signature( - &mut self, - _sealed: &Sealed, - ) -> bool { - self.preconfirmation_signature_success - .load(std::sync::atomic::Ordering::Relaxed) - } -} diff --git a/crates/services/tx_status_manager/src/tests/universe.rs b/crates/services/tx_status_manager/src/tests/universe.rs index c2147400246..5dc6b2b89dd 100644 --- a/crates/services/tx_status_manager/src/tests/universe.rs +++ b/crates/services/tx_status_manager/src/tests/universe.rs @@ -12,7 +12,6 @@ use super::mocks::MockP2P; use crate::{ config::Config, new_service, - tests::FakeSignatureVerification, update_sender::TxStatusChange, Task, TxStatusManager, @@ -60,14 +59,9 @@ impl TestTxStatusManagerUniverse { self.tx_status_manager = Some(tx_status_manager.clone()); } - pub fn build_service( - &self, - p2p: Option, - ) -> ServiceRunner> { + pub fn build_service(&self, p2p: Option) -> ServiceRunner { let p2p = p2p.unwrap_or_else(|| MockP2P::new_with_statuses(vec![])); - let (signature_verification, _) = - FakeSignatureVerification::new_with_handles(true); - new_service(p2p, signature_verification, self.config.clone()) + new_service(p2p, self.config.clone()) } } From c1d432c0fd76f5664e04774d1d60915520b9311b Mon Sep 17 00:00:00 2001 From: AurelienFT Date: Fri, 14 Mar 2025 11:09:07 +0100 Subject: [PATCH 34/76] Update cargo lock --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 98ccff8726d..ddf9f8a20a3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3799,8 +3799,8 @@ dependencies = [ "fuel-core-services", "fuel-core-storage", "fuel-core-trace", - "futures", "fuel-core-types 0.41.9", + "futures", "mockall", "rand 0.8.5", "serde", From 50ccea7575b2ffd9071e24529d9340f344185254 Mon Sep 17 00:00:00 2001 From: AurelienFT Date: Fri, 14 Mar 2025 11:48:27 +0100 Subject: [PATCH 35/76] Fix double creation of tx status manager --- crates/fuel-core/src/service/sub_services.rs | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/crates/fuel-core/src/service/sub_services.rs b/crates/fuel-core/src/service/sub_services.rs index b5dbdeafdd1..5c99256541f 100644 --- a/crates/fuel-core/src/service/sub_services.rs +++ b/crates/fuel-core/src/service/sub_services.rs @@ -25,10 +25,7 @@ use fuel_core_storage::{ }; #[cfg(feature = "relayer")] use fuel_core_types::blockchain::primitives::DaBlockHeight; -use fuel_core_types::{ - fuel_crypto::PublicKey, - signer::SignMode, -}; +use fuel_core_types::signer::SignMode; #[cfg(feature = "relayer")] use crate::relayer::Config as RelayerConfig; @@ -175,13 +172,8 @@ pub fn init_sub_services( #[cfg(not(feature = "p2p"))] let p2p_adapter = P2PAdapter::new(); - // TODO: Use real values - let signature_verification = - PreconfirmationSignatureVerification::new(PublicKey::default()); - let tx_status_manager = fuel_core_tx_status_manager::new_service( p2p_adapter.clone(), - signature_verification, config.tx_status_manager.clone(), ); let tx_status_manager_adapter = @@ -275,13 +267,6 @@ pub fn init_sub_services( universal_gas_price_provider.clone(), ); - let tx_status_manager = fuel_core_tx_status_manager::new_service( - p2p_adapter.clone(), - config.tx_status_manager.clone(), - ); - let tx_status_manager_adapter = - TxStatusManagerAdapter::new(tx_status_manager.shared.clone()); - let txpool = fuel_core_txpool::new_service( chain_id, config.txpool.clone(), From 9cec229c539e120762ddf3097c2c43a6fe3cd899 Mon Sep 17 00:00:00 2001 From: AurelienFT Date: Fri, 14 Mar 2025 12:34:37 +0100 Subject: [PATCH 36/76] Update test and config --- bin/fuel-core/src/cli/run.rs | 2 +- crates/fuel-core/src/service/config.rs | 15 +- .../src/pre_confirmation_signature_service.rs | 12 +- crates/services/p2p/src/config.rs | 2 +- tests/tests/preconfirmations_gossip.rs | 215 +++++------------- 5 files changed, 76 insertions(+), 170 deletions(-) diff --git a/bin/fuel-core/src/cli/run.rs b/bin/fuel-core/src/cli/run.rs index 7c771c89014..67a7becf44b 100644 --- a/bin/fuel-core/src/cli/run.rs +++ b/bin/fuel-core/src/cli/run.rs @@ -706,7 +706,7 @@ impl Command { fuel_core_poa::pre_confirmation_signature_service::config::Config { key_rotation_interval: Duration::from_secs(10), key_expiration_interval: Duration::from_secs(30), - echo_delegation_interval: Duration::from_secs(5), + echo_delegation_interval: Duration::from_secs(1), }, #[cfg(feature = "shared-sequencer")] shared_sequencer: shared_sequencer_args.try_into()?, diff --git a/crates/fuel-core/src/service/config.rs b/crates/fuel-core/src/service/config.rs index eed04eadd72..6ed3ec9ce8b 100644 --- a/crates/fuel-core/src/service/config.rs +++ b/crates/fuel-core/src/service/config.rs @@ -270,12 +270,17 @@ impl From<&Config> for fuel_core_poa::Config { } impl From<&Config> for fuel_core_poa::pre_confirmation_signature_service::config::Config { - fn from(_value: &Config) -> Self { + fn from(value: &Config) -> Self { fuel_core_poa::pre_confirmation_signature_service::config::Config { - // TODO - echo_delegation_interval: Duration::from_secs(1), - key_rotation_interval: Duration::from_secs(10), - key_expiration_interval: Duration::from_secs(1), + echo_delegation_interval: value + .pre_confirmation_signature_service + .echo_delegation_interval, + key_expiration_interval: value + .pre_confirmation_signature_service + .key_expiration_interval, + key_rotation_interval: value + .pre_confirmation_signature_service + .key_rotation_interval, } } } diff --git a/crates/services/consensus_module/poa/src/pre_confirmation_signature_service.rs b/crates/services/consensus_module/poa/src/pre_confirmation_signature_service.rs index 673111ae272..c5c8ec9c057 100644 --- a/crates/services/consensus_module/poa/src/pre_confirmation_signature_service.rs +++ b/crates/services/consensus_module/poa/src/pre_confirmation_signature_service.rs @@ -1,4 +1,7 @@ -use std::sync::Arc; +use std::{ + fmt::Debug, + sync::Arc, +}; use error::Result; use fuel_core_services::{ @@ -81,7 +84,7 @@ where Trigger: KeyRotationTrigger, DelegateKey: SigningKey, Parent: ParentSignature, - Preconfirmations: serde::Serialize + Send, + Preconfirmations: serde::Serialize + Send + Debug, { const NAME: &'static str = "PreconfirmationSignatureTask"; type SharedData = EmptyShared; @@ -168,7 +171,7 @@ where Trigger: KeyRotationTrigger, DelegateKey: SigningKey, Parent: ParentSignature, - Preconfirmations: serde::Serialize + Send, + Preconfirmations: serde::Serialize + Send + Debug, { async fn run(&mut self, watcher: &mut StateWatcher) -> TaskNextAction { tracing::debug!("Running pre-confirmation task"); @@ -177,6 +180,7 @@ where TaskNextAction::Stop } res = self.tx_receiver.receive() => { + tracing::debug!("AURELIEN: Res is {:?}", &res); tracing::debug!("Received transactions"); let pre_confirmations = try_or_stop!(res); let signature = try_or_stop!(self.current_delegate_key.sign(&pre_confirmations)); @@ -247,7 +251,7 @@ where Trigger: KeyRotationTrigger, DelegateKey: SigningKey, Parent: ParentSignature, - Preconfirmations: serde::Serialize + Send, + Preconfirmations: serde::Serialize + Send + Debug, { // It's ok to wait because the first rotation is expected to be immediate let expiration = Tai64( diff --git a/crates/services/p2p/src/config.rs b/crates/services/p2p/src/config.rs index 6492c3c1fb2..97c213c8dd2 100644 --- a/crates/services/p2p/src/config.rs +++ b/crates/services/p2p/src/config.rs @@ -253,7 +253,7 @@ impl Config { database_read_threads: 0, tx_pool_threads: 0, state: NotInitialized, - subscribe_to_pre_confirmations: false, + subscribe_to_pre_confirmations: true, } } } diff --git a/tests/tests/preconfirmations_gossip.rs b/tests/tests/preconfirmations_gossip.rs index 15837ed39ea..e5380a0293e 100644 --- a/tests/tests/preconfirmations_gossip.rs +++ b/tests/tests/preconfirmations_gossip.rs @@ -36,14 +36,16 @@ use fuel_core_types::{ use futures::StreamExt; use rand::{ rngs::StdRng, + Rng, SeedableRng, }; -#[tokio::test(flavor = "multi_thread")] +#[tokio::test] async fn preconfirmation__propagate_p2p_after_execution() { + let mut rng = rand::thread_rng(); let address = Address::new([0; 32]); let gas_limit = 1_000_000; - let maturity = Default::default(); + let amount = 10; // Given let script = [ @@ -60,9 +62,14 @@ async fn preconfirmation__propagate_p2p_after_execution() { // Given let tx = TransactionBuilder::script(script, vec![]) .script_gas_limit(gas_limit) - .maturity(maturity) - .add_fee_input() - .add_output(Output::variable(address, 0, AssetId::default())) + .add_unsigned_coin_input( + SecretKey::random(&mut rng), + rng.gen(), + amount, + AssetId::default(), + Default::default(), + ) + .add_output(Output::change(address, 0, AssetId::default())) .finalize_as_transaction(); let tx_id = tx.id(&Default::default()); // Create a random seed based on the test parameters. @@ -116,15 +123,23 @@ async fn preconfirmation__propagate_p2p_after_execution() { // When let client_sentry = FuelClient::from(sentry.node.bound_address); + let mut tx_statuses_subscriber = client_sentry + .subscribe_transaction_status(&tx_id) + .await + .expect("Should be able to subscribe for events"); + sentry .node .submit(tx) .await .expect("Should accept invalid transaction because `utxo_validation = false`."); - let mut tx_statuses_subscriber = client_sentry - .subscribe_transaction_status(&tx_id) - .await - .expect("Should be able to subscribe for events"); + + // Then + assert!(matches!( + tx_statuses_subscriber.next().await.unwrap().unwrap(), + TransactionStatus::Submitted { .. } + )); + authority .node .shared @@ -138,27 +153,21 @@ async fn preconfirmation__propagate_p2p_after_execution() { .await .unwrap(); - // Then - assert!(matches!( - tx_statuses_subscriber.next().await.unwrap().unwrap(), - TransactionStatus::Submitted { .. } - )); if let TransactionStatus::PreconfirmationSuccess { tx_pointer, total_fee, - total_gas, + total_gas: _, transaction_id, receipts, resolved_outputs, } = tx_statuses_subscriber.next().await.unwrap().unwrap() { // Then - assert_eq!(tx_pointer, TxPointer::new(BlockHeight::new(1), 0)); + assert_eq!(tx_pointer, TxPointer::new(BlockHeight::new(1), 1)); assert_eq!(total_fee, 0); - assert_eq!(total_gas, 4450); assert_eq!(transaction_id, tx_id); let receipts = receipts.unwrap(); - assert_eq!(receipts.len(), 2); + assert_eq!(receipts.len(), 3); assert!(matches!(receipts[0], Receipt::Log { ra, rb, .. @@ -172,9 +181,9 @@ async fn preconfirmation__propagate_p2p_after_execution() { assert_eq!(outputs.len(), 1); assert_eq!( outputs[0], - Output::Coin { + Output::Change { to: address, - amount: 2, + amount, asset_id: AssetId::default() } ); @@ -189,9 +198,10 @@ async fn preconfirmation__propagate_p2p_after_execution() { #[tokio::test] async fn preconfirmation__propagate_p2p_after_failed_execution() { + let mut rng = rand::thread_rng(); let address = Address::new([0; 32]); let gas_limit = 1_000_000; - let maturity = Default::default(); + let amount = 10; // Given let script = [ @@ -209,9 +219,14 @@ async fn preconfirmation__propagate_p2p_after_failed_execution() { // Given let tx = TransactionBuilder::script(script, vec![]) .script_gas_limit(gas_limit) - .maturity(maturity) - .add_fee_input() - .add_output(Output::variable(address, 0, AssetId::default())) + .add_unsigned_coin_input( + SecretKey::random(&mut rng), + rng.gen(), + amount, + AssetId::default(), + Default::default(), + ) + .add_output(Output::change(address, 0, AssetId::default())) .finalize_as_transaction(); let tx_id = tx.id(&Default::default()); // Create a random seed based on the test parameters. @@ -265,15 +280,23 @@ async fn preconfirmation__propagate_p2p_after_failed_execution() { // When let client_sentry = FuelClient::from(sentry.node.bound_address); + let mut tx_statuses_subscriber = client_sentry + .subscribe_transaction_status(&tx_id) + .await + .expect("Should be able to subscribe for events"); + sentry .node .submit(tx) .await .expect("Should accept invalid transaction because `utxo_validation = false`."); - let mut tx_statuses_subscriber = client_sentry - .subscribe_transaction_status(&tx_id) - .await - .expect("Should be able to subscribe for events"); + + // Then + assert!(matches!( + tx_statuses_subscriber.next().await.unwrap().unwrap(), + TransactionStatus::Submitted { .. } + )); + authority .node .shared @@ -286,16 +309,10 @@ async fn preconfirmation__propagate_p2p_after_failed_execution() { ) .await .unwrap(); - - // Then - assert!(matches!( - tx_statuses_subscriber.next().await.unwrap().unwrap(), - TransactionStatus::Submitted { .. } - )); if let TransactionStatus::PreconfirmationFailure { tx_pointer, total_fee, - total_gas, + total_gas: _, transaction_id, receipts, resolved_outputs, @@ -303,12 +320,11 @@ async fn preconfirmation__propagate_p2p_after_failed_execution() { } = tx_statuses_subscriber.next().await.unwrap().unwrap() { // Then - assert_eq!(tx_pointer, TxPointer::new(BlockHeight::new(1), 0)); + assert_eq!(tx_pointer, TxPointer::new(BlockHeight::new(1), 1)); assert_eq!(total_fee, 0); - assert_eq!(total_gas, 4450); assert_eq!(transaction_id, tx_id); let receipts = receipts.unwrap(); - assert_eq!(receipts.len(), 2); + assert_eq!(receipts.len(), 3); assert!(matches!(receipts[0], Receipt::Log { ra, rb, .. @@ -322,9 +338,9 @@ async fn preconfirmation__propagate_p2p_after_failed_execution() { assert_eq!(outputs.len(), 1); assert_eq!( outputs[0], - Output::Coin { + Output::Change { to: address, - amount: 2, + amount, asset_id: AssetId::default() } ); @@ -337,122 +353,3 @@ async fn preconfirmation__propagate_p2p_after_failed_execution() { TransactionStatus::Failure { .. } )); } - -#[tokio::test] -async fn preconfirmation__propagate_p2p_after_squeezed_out() { - let address = Address::new([0; 32]); - let gas_limit = 1_000_000; - let maturity = Default::default(); - - // Given - let script = [ - op::addi(0x10, RegId::ZERO, 0xca), - op::addi(0x11, RegId::ZERO, 0xba), - op::log(0x10, 0x11, RegId::ZERO, RegId::ZERO), - op::ret(RegId::ONE), - ]; - let script: Vec = script - .iter() - .flat_map(|op| u32::from(*op).to_be_bytes()) - .collect(); - - // Given - let tx = TransactionBuilder::script(script, vec![]) - .script_gas_limit(gas_limit) - .maturity(maturity) - .add_fee_input() - .add_output(Output::variable(address, 0, AssetId::default())) - .finalize_as_transaction(); - let tx_id = tx.id(&Default::default()); - // Create a random seed based on the test parameters. - let mut hasher = DefaultHasher::new(); - let num_txs = 1; - let num_validators = 1; - let num_partitions = 1; - (num_txs, num_validators, num_partitions, line!()).hash(&mut hasher); - let mut rng = StdRng::seed_from_u64(hasher.finish()); - - // Create a set of key pairs. - let secrets: Vec<_> = (0..1).map(|_| SecretKey::random(&mut rng)).collect(); - let pub_keys: Vec<_> = secrets - .clone() - .into_iter() - .map(|secret| Input::owner(&secret.public_key())) - .collect(); - - // Create a producer for each key pair and a set of validators that share - // the same key pair. - let Nodes { - producers: _dont_drop1, - validators, - bootstrap_nodes: _dont_drop2, - } = make_nodes( - pub_keys - .iter() - .map(|pub_key| Some(BootstrapSetup::new(*pub_key))), - secrets.clone().into_iter().enumerate().map(|(i, secret)| { - Some( - ProducerSetup::new(secret).with_name(format!("{}:producer", pub_keys[i])), - ) - }), - pub_keys.iter().flat_map(|pub_key| { - (0..num_validators).map(move |i| { - Some( - ValidatorSetup::new(*pub_key) - .with_name(format!("{pub_key}:{i}")) - .utxo_validation(false), - ) - }) - }), - None, - ) - .await; - - let sentry = &validators[0]; - let authority = &validators[0]; - - // When - let client_sentry = FuelClient::from(sentry.node.bound_address); - sentry - .node - .submit(tx) - .await - .expect("Should accept invalid transaction because `utxo_validation = false`."); - let mut tx_statuses_subscriber = client_sentry - .subscribe_transaction_status(&tx_id) - .await - .expect("Should be able to subscribe for events"); - authority - .node - .shared - .poa_adapter - .manually_produce_blocks( - None, - fuel_core_poa::service::Mode::Blocks { - number_of_blocks: 1, - }, - ) - .await - .unwrap(); - - // Then - assert!(matches!( - tx_statuses_subscriber.next().await.unwrap().unwrap(), - TransactionStatus::Submitted { .. } - )); - if let TransactionStatus::PreconfirmationSqueezedOut { - reason, - transaction_id, - } = tx_statuses_subscriber.next().await.unwrap().unwrap() - { - assert_eq!(transaction_id, tx_id); - assert_eq!(reason, "Transaction has been skipped during block insertion: \ - The specified coin(0xc49d65de61cf04588a764b557d25cc6c6b4bc0d7429227e2a21e61c213b3a3e28212) doesn't exist".to_string()) - } else { - panic!("Expected preconfirmation status"); - } - assert!(matches!( - tx_statuses_subscriber.next().await.unwrap().unwrap(), - TransactionStatus::SqueezedOut { .. } - )); -} From e09788c97ed3a1bedade4fd23bef8c9b8cc5cd55 Mon Sep 17 00:00:00 2001 From: AurelienFT Date: Fri, 14 Mar 2025 12:45:03 +0100 Subject: [PATCH 37/76] Add args for preconfirmation signature service --- bin/fuel-core/src/cli/run.rs | 26 ++++++++++++++----- .../run/preconfirmation_signature_service.rs | 24 +++++++++++++++++ 2 files changed, 44 insertions(+), 6 deletions(-) create mode 100644 bin/fuel-core/src/cli/run/preconfirmation_signature_service.rs diff --git a/bin/fuel-core/src/cli/run.rs b/bin/fuel-core/src/cli/run.rs index 67a7becf44b..830b92dc897 100644 --- a/bin/fuel-core/src/cli/run.rs +++ b/bin/fuel-core/src/cli/run.rs @@ -108,6 +108,8 @@ mod shared_sequencer; mod consensus; mod gas_price; mod graphql; +#[cfg(feature = "p2p")] +mod preconfirmation_signature_service; mod profiling; #[cfg(feature = "relayer")] mod relayer; @@ -272,6 +274,10 @@ pub struct Command { #[cfg(feature = "p2p")] pub sync_args: p2p::SyncArgs, + #[cfg_attr(feature = "p2p", clap(flatten))] + #[cfg(feature = "p2p")] + pub pre_confirmation_signature_service_args: preconfirmation_signature_service::Args, + #[cfg_attr(feature = "shared-sequencer", clap(flatten))] #[cfg(feature = "shared-sequencer")] pub shared_sequencer_args: shared_sequencer::Args, @@ -341,6 +347,8 @@ impl Command { p2p_args, #[cfg(feature = "p2p")] sync_args, + #[cfg(feature = "p2p")] + pre_confirmation_signature_service_args, #[cfg(feature = "shared-sequencer")] shared_sequencer_args, disabled_metrics, @@ -404,6 +412,17 @@ impl Command { disabled_metrics.is_enabled(Module::P2P), )?; + #[cfg(feature = "p2p")] + let preconfirmation_signature_service_config = + fuel_core_poa::pre_confirmation_signature_service::config::Config { + key_rotation_interval: *pre_confirmation_signature_service_args + .key_rotation_interval, + key_expiration_interval: *pre_confirmation_signature_service_args + .key_expiration_interval, + echo_delegation_interval: *pre_confirmation_signature_service_args + .echo_delegation_interval, + }; + let trigger: Trigger = poa_trigger.into(); if trigger != Trigger::Never { @@ -702,12 +721,7 @@ impl Command { #[cfg(feature = "p2p")] sync: sync_args.into(), #[cfg(feature = "p2p")] - pre_confirmation_signature_service: - fuel_core_poa::pre_confirmation_signature_service::config::Config { - key_rotation_interval: Duration::from_secs(10), - key_expiration_interval: Duration::from_secs(30), - echo_delegation_interval: Duration::from_secs(1), - }, + pre_confirmation_signature_service: preconfirmation_signature_service_config, #[cfg(feature = "shared-sequencer")] shared_sequencer: shared_sequencer_args.try_into()?, consensus_signer, diff --git a/bin/fuel-core/src/cli/run/preconfirmation_signature_service.rs b/bin/fuel-core/src/cli/run/preconfirmation_signature_service.rs new file mode 100644 index 00000000000..8b9d39b1686 --- /dev/null +++ b/bin/fuel-core/src/cli/run/preconfirmation_signature_service.rs @@ -0,0 +1,24 @@ +#[derive(Debug, Clone, clap::Args)] +pub struct Args { + /// The frequency at which we rotate the preconfirmation sub-key + #[clap( + long = "preconfirmation-key-rotation-frequency", + env, + default_value = "10m" + )] + pub key_rotation_interval: humantime::Duration, + /// The frequency at which the preconfirmation sub-key expires + #[clap( + long = "preconfirmation-key-expiration-frequency", + env, + default_value = "12m" + )] + pub key_expiration_interval: humantime::Duration, + /// The frequency at which the preconfirmation sub-key is echoed to the network + #[clap( + long = "preconfirmation-echo-delegation-frequency", + env, + default_value = "10s" + )] + pub echo_delegation_interval: humantime::Duration, +} From 00c56271b57215d75e04405591e1c85f0b61b0b6 Mon Sep 17 00:00:00 2001 From: AurelienFT Date: Fri, 14 Mar 2025 13:11:21 +0100 Subject: [PATCH 38/76] Update test and feature gate --- crates/fuel-core/src/service.rs | 4 ++-- crates/fuel-core/src/service/config.rs | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/fuel-core/src/service.rs b/crates/fuel-core/src/service.rs index 4ef0df9cf9a..9ddb1b33fbb 100644 --- a/crates/fuel-core/src/service.rs +++ b/crates/fuel-core/src/service.rs @@ -545,8 +545,8 @@ mod tests { // } #[cfg(feature = "p2p")] { - // p2p & sync - expected_services += 2; + // p2p & sync & preconfirmation signature service + expected_services += 3; } #[cfg(feature = "shared-sequencer")] { diff --git a/crates/fuel-core/src/service/config.rs b/crates/fuel-core/src/service/config.rs index 6ed3ec9ce8b..ff1bec6a66b 100644 --- a/crates/fuel-core/src/service/config.rs +++ b/crates/fuel-core/src/service/config.rs @@ -269,6 +269,7 @@ impl From<&Config> for fuel_core_poa::Config { } } +#[cfg(feature = "p2p")] impl From<&Config> for fuel_core_poa::pre_confirmation_signature_service::config::Config { fn from(value: &Config) -> Self { fuel_core_poa::pre_confirmation_signature_service::config::Config { From 3a5544422454a12e3c6f140d83b207b68fad6106 Mon Sep 17 00:00:00 2001 From: AurelienFT Date: Fri, 14 Mar 2025 14:55:27 +0100 Subject: [PATCH 39/76] Fix non P2P fuel core bin --- bin/fuel-core/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/fuel-core/Cargo.toml b/bin/fuel-core/Cargo.toml index 7224fe4bd50..cf3468a1ec9 100644 --- a/bin/fuel-core/Cargo.toml +++ b/bin/fuel-core/Cargo.toml @@ -29,7 +29,7 @@ fuel-core = { workspace = true, features = ["wasm-executor"] } fuel-core-chain-config = { workspace = true } fuel-core-compression = { workspace = true } fuel-core-metrics = { workspace = true } -fuel-core-poa = { workspace = true } +fuel-core-poa = { workspace = true, optional = true } fuel-core-shared-sequencer = { workspace = true, optional = true } fuel-core-types = { workspace = true, features = ["std"] } hex = { workspace = true } @@ -64,7 +64,7 @@ test-case = { workspace = true } default = ["env", "relayer", "rocksdb"] aws-kms = ["dep:aws-config", "dep:aws-sdk-kms", "fuel-core-types/aws-kms"] env = ["dep:dotenvy"] -p2p = ["fuel-core/p2p", "const_format"] +p2p = ["fuel-core/p2p", "const_format", "dep:fuel-core-poa"] shared-sequencer = [ "dep:fuel-core-shared-sequencer", "fuel-core/shared-sequencer", From 2608505e630a1686e00e7189a91c219d685b36c5 Mon Sep 17 00:00:00 2001 From: AurelienFT Date: Fri, 14 Mar 2025 15:10:55 +0100 Subject: [PATCH 40/76] Update preconfirmation args --- bin/fuel-core/src/cli/run.rs | 2 +- bin/fuel-core/src/cli/run/preconfirmation_signature_service.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/fuel-core/src/cli/run.rs b/bin/fuel-core/src/cli/run.rs index 830b92dc897..540fba01a81 100644 --- a/bin/fuel-core/src/cli/run.rs +++ b/bin/fuel-core/src/cli/run.rs @@ -276,7 +276,7 @@ pub struct Command { #[cfg_attr(feature = "p2p", clap(flatten))] #[cfg(feature = "p2p")] - pub pre_confirmation_signature_service_args: preconfirmation_signature_service::Args, + pub pre_confirmation_signature_service_args: preconfirmation_signature_service::PreconfirmationArgs, #[cfg_attr(feature = "shared-sequencer", clap(flatten))] #[cfg(feature = "shared-sequencer")] diff --git a/bin/fuel-core/src/cli/run/preconfirmation_signature_service.rs b/bin/fuel-core/src/cli/run/preconfirmation_signature_service.rs index 8b9d39b1686..f895c78d597 100644 --- a/bin/fuel-core/src/cli/run/preconfirmation_signature_service.rs +++ b/bin/fuel-core/src/cli/run/preconfirmation_signature_service.rs @@ -1,5 +1,5 @@ #[derive(Debug, Clone, clap::Args)] -pub struct Args { +pub struct PreconfirmationArgs { /// The frequency at which we rotate the preconfirmation sub-key #[clap( long = "preconfirmation-key-rotation-frequency", From 90e63a559c721c91c3805b4e9be9838829106c39 Mon Sep 17 00:00:00 2001 From: AurelienFT Date: Fri, 14 Mar 2025 15:16:35 +0100 Subject: [PATCH 41/76] format and fix test compilation --- bin/fuel-core/src/cli/run.rs | 3 ++- crates/fuel-core/src/executor.rs | 15 +++++++++------ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/bin/fuel-core/src/cli/run.rs b/bin/fuel-core/src/cli/run.rs index 540fba01a81..8a92fa42c7a 100644 --- a/bin/fuel-core/src/cli/run.rs +++ b/bin/fuel-core/src/cli/run.rs @@ -276,7 +276,8 @@ pub struct Command { #[cfg_attr(feature = "p2p", clap(flatten))] #[cfg(feature = "p2p")] - pub pre_confirmation_signature_service_args: preconfirmation_signature_service::PreconfirmationArgs, + pub pre_confirmation_signature_service_args: + preconfirmation_signature_service::PreconfirmationArgs, #[cfg_attr(feature = "shared-sequencer", clap(flatten))] #[cfg(feature = "shared-sequencer")] diff --git a/crates/fuel-core/src/executor.rs b/crates/fuel-core/src/executor.rs index 80555fbd934..2da3806cc06 100644 --- a/crates/fuel-core/src/executor.rs +++ b/crates/fuel-core/src/executor.rs @@ -17,7 +17,10 @@ mod tests { }; #[cfg(not(feature = "wasm-executor"))] - use fuel_core_types::services::preconfirmation::PreconfirmationStatus; + use fuel_core_types::services::preconfirmation::{ + Preconfirmation, + PreconfirmationStatus, + }; use crate as fuel_core; use fuel_core::database::Database; @@ -3117,19 +3120,19 @@ mod tests { async fn execute_block__send_preconfirmations() { // Given struct MockPreconfirmationsSender { - sender: tokio::sync::mpsc::Sender>, + sender: tokio::sync::mpsc::Sender>, } impl PreconfirmationSenderPort for MockPreconfirmationsSender { fn try_send( &self, - preconfirmations: Vec, - ) -> Vec { + preconfirmations: Vec, + ) -> Vec { preconfirmations } /// Send a batch of pre-confirmations, awaiting for the send to be successful. - async fn send(&self, preconfirmations: Vec) { + async fn send(&self, preconfirmations: Vec) { self.sender.send(preconfirmations).await.unwrap(); } } @@ -3173,7 +3176,7 @@ mod tests { let preconfirmations = receiver.recv().await.unwrap(); assert_eq!(preconfirmations.len(), 1); assert!(matches!( - preconfirmations[0], + preconfirmations[0].status, PreconfirmationStatus::Success { .. } )); assert_eq!(res.skipped_transactions.len(), 0); From d2c48f3ed8c3a4ca2eb0f04c2932acc1f6391371 Mon Sep 17 00:00:00 2001 From: AurelienFT Date: Fri, 14 Mar 2025 15:49:41 +0100 Subject: [PATCH 42/76] Update taking of status in subscription --- crates/fuel-core/src/schema/tx.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/fuel-core/src/schema/tx.rs b/crates/fuel-core/src/schema/tx.rs index adedbb231fd..8dc0ae903d4 100644 --- a/crates/fuel-core/src/schema/tx.rs +++ b/crates/fuel-core/src/schema/tx.rs @@ -712,7 +712,7 @@ async fn submit_and_await_status<'a>( Err(anyhow::anyhow!("Failed to get transaction status").into()) } }) - .take(2)) + .take(3)) } struct StatusChangeState<'a> { From 8e151a4f63086cd9b65567b9e626757e8c93a409 Mon Sep 17 00:00:00 2001 From: AurelienFT Date: Fri, 14 Mar 2025 16:17:39 +0100 Subject: [PATCH 43/76] Update P2P tests --- crates/fuel-core/src/p2p_test_helpers.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/fuel-core/src/p2p_test_helpers.rs b/crates/fuel-core/src/p2p_test_helpers.rs index dd58beb1444..28a1b34b5ab 100644 --- a/crates/fuel-core/src/p2p_test_helpers.rs +++ b/crates/fuel-core/src/p2p_test_helpers.rs @@ -396,6 +396,7 @@ pub async fn make_nodes( } node_config.utxo_validation = utxo_validation; + node_config.txpool.utxo_validation = utxo_validation; update_signing_key(&mut node_config, Input::owner(&secret.public_key())); node_config.consensus_signer = SignMode::Key(Secret::new(secret.into())); From b7b3d6b724fe1437f9a08411d266c8ecda1a1e87 Mon Sep 17 00:00:00 2001 From: AurelienFT Date: Fri, 14 Mar 2025 16:37:54 +0100 Subject: [PATCH 44/76] fix aws test --- tests/tests/aws_kms.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/tests/aws_kms.rs b/tests/tests/aws_kms.rs index e832a4afc2c..57efe959b90 100644 --- a/tests/tests/aws_kms.rs +++ b/tests/tests/aws_kms.rs @@ -6,7 +6,7 @@ use fuel_core_storage::transactional::AtomicView; use fuel_core_types::blockchain::consensus::Consensus; use test_helpers::fuel_core_driver::FuelCoreDriver; -#[tokio::test] +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn can_get_sealed_block_from_poa_produced_block_when_signing_with_kms() { use fuel_core_types::fuel_crypto::PublicKey; use k256::pkcs8::DecodePublicKey; From 7acb94e5d51815ed75d8663991421286f5aa3f8f Mon Sep 17 00:00:00 2001 From: AurelienFT Date: Fri, 14 Mar 2025 16:54:09 +0100 Subject: [PATCH 45/76] Don't create signature service if signer is not available --- crates/fuel-core/src/service/sub_services.rs | 54 ++++++++++++-------- tests/test-helpers/src/builder.rs | 1 + 2 files changed, 34 insertions(+), 21 deletions(-) diff --git a/crates/fuel-core/src/service/sub_services.rs b/crates/fuel-core/src/service/sub_services.rs index 5c99256541f..5023a9155cc 100644 --- a/crates/fuel-core/src/service/sub_services.rs +++ b/crates/fuel-core/src/service/sub_services.rs @@ -317,6 +317,10 @@ pub fn init_sub_services( production_enabled = true; tracing::info!("Enabled manual block production because of `debug` flag"); } + if production_enabled && matches!(poa_config.signer, SignMode::Unavailable) { + production_enabled = false; + tracing::warn!("Disabled block production because of unavailable signer"); + } let signer = Arc::new(FuelBlockSigner::new(config.consensus_signer.clone())); @@ -339,27 +343,33 @@ pub fn init_sub_services( config.into(); #[cfg(feature = "p2p")] - let pre_confirmation_service: ServiceRunner< - PreConfirmationSignatureTask< - PreconfirmationsReceiver, - P2PAdapter, - FuelBlockSigner, - Ed25519KeyGenerator, - Ed25519Key, - TimeBasedTrigger, + let pre_confirmation_service: Option< + ServiceRunner< + PreConfirmationSignatureTask< + PreconfirmationsReceiver, + P2PAdapter, + FuelBlockSigner, + Ed25519KeyGenerator, + Ed25519Key, + TimeBasedTrigger, + >, >, - > = fuel_core_poa::pre_confirmation_signature_service::new_service( - config_preconfirmation.clone(), - PreconfirmationsReceiver::new(preconfirmation_receiver), - p2p_adapter.clone(), - signer.clone(), - Ed25519KeyGenerator, - TimeBasedTrigger::new( - SystemTime, - config_preconfirmation.key_rotation_interval, - config_preconfirmation.key_expiration_interval, - ), - )?; + > = production_enabled + .then(|| { + fuel_core_poa::pre_confirmation_signature_service::new_service( + config_preconfirmation.clone(), + PreconfirmationsReceiver::new(preconfirmation_receiver), + p2p_adapter.clone(), + signer.clone(), + Ed25519KeyGenerator, + TimeBasedTrigger::new( + SystemTime, + config_preconfirmation.key_rotation_interval, + config_preconfirmation.key_expiration_interval, + ), + ) + }) + .transpose()?; let poa = production_enabled.then(|| { fuel_core_poa::new_service( @@ -472,7 +482,9 @@ pub fn init_sub_services( if let Some(network) = network.take() { services.push(Box::new(network)); services.push(Box::new(sync)); - services.push(Box::new(pre_confirmation_service)) + if let Some(pre_confirmation_service) = pre_confirmation_service { + services.push(Box::new(pre_confirmation_service)); + } } } #[cfg(feature = "shared-sequencer")] diff --git a/tests/test-helpers/src/builder.rs b/tests/test-helpers/src/builder.rs index e653045737b..17754bfd0d1 100644 --- a/tests/test-helpers/src/builder.rs +++ b/tests/test-helpers/src/builder.rs @@ -253,6 +253,7 @@ impl TestSetupBuilder { txpool.heavy_work.size_of_verification_queue = self.max_txs; txpool.heavy_work.number_threads_to_verify_transactions = self.number_threads_pool_verif; + txpool.utxo_validation = self.utxo_validation; let gas_price_config = GasPriceConfig { starting_exec_gas_price: self.starting_gas_price, From 9375190bc37d871f2b4e3a3c47dbcc470d4032f0 Mon Sep 17 00:00:00 2001 From: AurelienFT Date: Fri, 14 Mar 2025 17:08:36 +0100 Subject: [PATCH 46/76] fix clippy --- crates/fuel-core/src/service/sub_services.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/fuel-core/src/service/sub_services.rs b/crates/fuel-core/src/service/sub_services.rs index 5023a9155cc..4f53ec56e57 100644 --- a/crates/fuel-core/src/service/sub_services.rs +++ b/crates/fuel-core/src/service/sub_services.rs @@ -343,6 +343,7 @@ pub fn init_sub_services( config.into(); #[cfg(feature = "p2p")] + #[allow(clippy::type_complexity)] let pre_confirmation_service: Option< ServiceRunner< PreConfirmationSignatureTask< From 8b6fb3dec75398bd625fe9c59003ae8665108007 Mon Sep 17 00:00:00 2001 From: AurelienFT Date: Fri, 14 Mar 2025 17:17:13 +0100 Subject: [PATCH 47/76] change mock tx status manager in txpool --- crates/services/txpool_v2/src/tests/mocks.rs | 4 +--- crates/services/txpool_v2/src/tests/universe.rs | 4 +++- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/services/txpool_v2/src/tests/mocks.rs b/crates/services/txpool_v2/src/tests/mocks.rs index 18872c089a7..304c9d2fcba 100644 --- a/crates/services/txpool_v2/src/tests/mocks.rs +++ b/crates/services/txpool_v2/src/tests/mocks.rs @@ -97,9 +97,7 @@ impl MockTxStatusManager { impl ports::TxStatusManager for MockTxStatusManager { fn status_update(&self, tx_id: TxId, tx_status: TransactionStatus) { let tx = self.tx.clone(); - tokio::spawn(async move { - tx.send((tx_id, tx_status)).await.unwrap(); - }); + tx.try_send((tx_id, tx_status)).unwrap(); } } diff --git a/crates/services/txpool_v2/src/tests/universe.rs b/crates/services/txpool_v2/src/tests/universe.rs index 628e71caa55..950c049cc6b 100644 --- a/crates/services/txpool_v2/src/tests/universe.rs +++ b/crates/services/txpool_v2/src/tests/universe.rs @@ -175,6 +175,7 @@ impl TestPoolUniverse { pub fn build_pool(&mut self) { let (tx_new_executable_txs, _) = tokio::sync::watch::channel(()); + let (status_sender, status_receiver) = mpsc::channel(1_000_000); let (tx, rx) = tokio::sync::watch::channel(TxPoolStats::default()); let pool = Arc::new(RwLock::new(Pool::new( GraphStorage::new(GraphConfig { @@ -185,9 +186,10 @@ impl TestPoolUniverse { self.config.clone(), tx, tx_new_executable_txs, - Arc::new(MockTxStatusManager::new(mpsc::channel(100).0)), + Arc::new(MockTxStatusManager::new(status_sender)), ))); self.stats_receiver = Some(rx); + self.tx_status_manager_receiver = status_receiver; self.pool = Some(pool.clone()); } From 296883a4381c04ccee0bd6304c564e403b983e5a Mon Sep 17 00:00:00 2001 From: AurelienFT Date: Fri, 14 Mar 2025 17:48:07 +0100 Subject: [PATCH 48/76] Fix review comments --- .../poa/src/pre_confirmation_signature_service.rs | 1 - tests/tests/preconfirmations.rs | 3 +-- tests/tests/preconfirmations_gossip.rs | 4 +--- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/crates/services/consensus_module/poa/src/pre_confirmation_signature_service.rs b/crates/services/consensus_module/poa/src/pre_confirmation_signature_service.rs index c5c8ec9c057..d7635809d96 100644 --- a/crates/services/consensus_module/poa/src/pre_confirmation_signature_service.rs +++ b/crates/services/consensus_module/poa/src/pre_confirmation_signature_service.rs @@ -180,7 +180,6 @@ where TaskNextAction::Stop } res = self.tx_receiver.receive() => { - tracing::debug!("AURELIEN: Res is {:?}", &res); tracing::debug!("Received transactions"); let pre_confirmations = try_or_stop!(res); let signature = try_or_stop!(self.current_delegate_key.sign(&pre_confirmations)); diff --git a/tests/tests/preconfirmations.rs b/tests/tests/preconfirmations.rs index 239a3ea32ac..f564f30ed04 100644 --- a/tests/tests/preconfirmations.rs +++ b/tests/tests/preconfirmations.rs @@ -28,7 +28,7 @@ use futures::StreamExt; use rand::Rng; #[tokio::test] -async fn preconfirmation__received_after_execution() { +async fn preconfirmation__received_after_successful_execution() { let mut rng = rand::thread_rng(); let mut config = Config::local_node(); config.block_production = Trigger::Never; @@ -305,7 +305,6 @@ async fn preconfirmation__received_tx_inserted_end_block_open_period() { .await .unwrap() .enumerate() - .take(3) .for_each(|(event_idx, r)| async move { let r = r.unwrap(); // Then diff --git a/tests/tests/preconfirmations_gossip.rs b/tests/tests/preconfirmations_gossip.rs index e5380a0293e..fadad495f4a 100644 --- a/tests/tests/preconfirmations_gossip.rs +++ b/tests/tests/preconfirmations_gossip.rs @@ -41,7 +41,7 @@ use rand::{ }; #[tokio::test] -async fn preconfirmation__propagate_p2p_after_execution() { +async fn preconfirmation__propagate_p2p_after_successful_execution() { let mut rng = rand::thread_rng(); let address = Address::new([0; 32]); let gas_limit = 1_000_000; @@ -203,7 +203,6 @@ async fn preconfirmation__propagate_p2p_after_failed_execution() { let gas_limit = 1_000_000; let amount = 10; - // Given let script = [ op::addi(0x10, RegId::ZERO, 0xca), op::addi(0x11, RegId::ZERO, 0xba), @@ -291,7 +290,6 @@ async fn preconfirmation__propagate_p2p_after_failed_execution() { .await .expect("Should accept invalid transaction because `utxo_validation = false`."); - // Then assert!(matches!( tx_statuses_subscriber.next().await.unwrap().unwrap(), TransactionStatus::Submitted { .. } From 6b3f9e007af437b9c98020d367bc6076c5e1a73a Mon Sep 17 00:00:00 2001 From: AurelienFT Date: Fri, 14 Mar 2025 18:06:45 +0100 Subject: [PATCH 49/76] Fix expected status when squeezed out in tests --- tests/tests/regenesis.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/tests/regenesis.rs b/tests/tests/regenesis.rs index 2d025c9a4e5..36b867ff00a 100644 --- a/tests/tests/regenesis.rs +++ b/tests/tests/regenesis.rs @@ -296,7 +296,7 @@ async fn test_regenesis_processed_transactions_are_preserved() -> anyhow::Result .finalize_as_transaction(); core.client.submit_and_await_commit(&tx).await.unwrap(); - let TransactionStatus::SqueezedOut { reason } = + let TransactionStatus::PreconfirmationSqueezedOut { reason, .. } = core.client.submit_and_await_commit(&tx).await.unwrap() else { panic!("Expected transaction to be squeezed out") From ca8b9393c601a61acfa08b11e3c2993fcd898e0a Mon Sep 17 00:00:00 2001 From: AurelienFT Date: Fri, 14 Mar 2025 18:33:59 +0100 Subject: [PATCH 50/76] Change preconfirmation key expiration frequency --- bin/fuel-core/src/cli/run/preconfirmation_signature_service.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/fuel-core/src/cli/run/preconfirmation_signature_service.rs b/bin/fuel-core/src/cli/run/preconfirmation_signature_service.rs index f895c78d597..528149ac03d 100644 --- a/bin/fuel-core/src/cli/run/preconfirmation_signature_service.rs +++ b/bin/fuel-core/src/cli/run/preconfirmation_signature_service.rs @@ -11,7 +11,7 @@ pub struct PreconfirmationArgs { #[clap( long = "preconfirmation-key-expiration-frequency", env, - default_value = "12m" + default_value = "20m" )] pub key_expiration_interval: humantime::Duration, /// The frequency at which the preconfirmation sub-key is echoed to the network From c159f569530821d563901c0bc952692195bf8963 Mon Sep 17 00:00:00 2001 From: AurelienFT Date: Fri, 14 Mar 2025 19:03:56 +0100 Subject: [PATCH 51/76] Fix Tx status --- tests/test-helpers/src/counter_contract.rs | 5 +++++ tests/test-helpers/src/lib.rs | 1 + tests/tests/tx.rs | 5 +++++ tests/tests/tx/txn_status_subscription.rs | 6 ++++-- 4 files changed, 15 insertions(+), 2 deletions(-) diff --git a/tests/test-helpers/src/counter_contract.rs b/tests/test-helpers/src/counter_contract.rs index 962d6b47958..10a2b586eb5 100644 --- a/tests/test-helpers/src/counter_contract.rs +++ b/tests/test-helpers/src/counter_contract.rs @@ -84,6 +84,11 @@ pub async fn deploy( intermediate_status, TransactionStatus::Submitted { .. } )); + let preconfirmation_status = status_stream.next().await.unwrap().unwrap(); + match preconfirmation_status { + TransactionStatus::PreconfirmationSuccess { .. } => {} + _ => panic!("Tx wasn't preconfirmed: {:?}", preconfirmation_status), + }; let final_status = status_stream.next().await.unwrap().unwrap(); let TransactionStatus::Success { block_height, .. } = final_status else { panic!("Tx wasn't included in a block: {:?}", final_status); diff --git a/tests/test-helpers/src/lib.rs b/tests/test-helpers/src/lib.rs index d8636fbee17..b19d0f8728b 100644 --- a/tests/test-helpers/src/lib.rs +++ b/tests/test-helpers/src/lib.rs @@ -139,6 +139,7 @@ pub async fn produce_block_with_tx(rng: &mut StdRng, client: &FuelClient) { pub fn config_with_fee() -> Config { let mut config = Config::local_node(); config.utxo_validation = true; + config.txpool.utxo_validation = true; config.gas_price_config.min_exec_gas_price = 1000; config } diff --git a/tests/tests/tx.rs b/tests/tests/tx.rs index 9a46d14c0e8..2bb26922758 100644 --- a/tests/tests/tx.rs +++ b/tests/tests/tx.rs @@ -390,6 +390,11 @@ async fn submit_and_await_status() { intermediate_status, TransactionStatus::Submitted { .. } )); + let preconfirmation_status = status_stream.next().await.unwrap().unwrap(); + assert!(matches!( + preconfirmation_status, + TransactionStatus::PreconfirmationSuccess { .. } + )); let final_status = status_stream.next().await.unwrap().unwrap(); assert!(matches!(final_status, TransactionStatus::Success { .. })); } diff --git a/tests/tests/tx/txn_status_subscription.rs b/tests/tests/tx/txn_status_subscription.rs index e01b60b60de..b7e56b64489 100644 --- a/tests/tests/tx/txn_status_subscription.rs +++ b/tests/tests/tx/txn_status_subscription.rs @@ -150,9 +150,11 @@ async fn subscribe_txn_status() { (0, 0) => assert!(matches!(r, fuel_core_client::client::types::TransactionStatus::Submitted{ .. }), "{r:?}"), (0, 1) => assert!(matches!(r, fuel_core_client::client::types::TransactionStatus::SqueezedOut{ .. }), "{r:?}"), (1, 0) => assert!(matches!(r, fuel_core_client::client::types::TransactionStatus::Submitted{ .. }), "{r:?}"), - (1, 1) => assert!(matches!(r, fuel_core_client::client::types::TransactionStatus::Success{ .. }), "{r:?}"), + (1, 1) => assert!(matches!(r, fuel_core_client::client::types::TransactionStatus::PreconfirmationSuccess{ .. }), "{r:?}"), + (1, 2) => assert!(matches!(r, fuel_core_client::client::types::TransactionStatus::Success{ .. }), "{r:?}"), (2, 0) => assert!(matches!(r, fuel_core_client::client::types::TransactionStatus::Submitted{ .. }), "{r:?}"), - (2, 1) => assert!(matches!(r, fuel_core_client::client::types::TransactionStatus::Failure{ .. }), "{r:?}"), + (2, 1) => assert!(matches!(r, fuel_core_client::client::types::TransactionStatus::PreconfirmationFailure{ .. }), "{r:?}"), + (2, 2) => assert!(matches!(r, fuel_core_client::client::types::TransactionStatus::Failure{ .. }), "{r:?}"), _ => unreachable!("{} {} {:?}", txn_idx, event_idx, r), } }) From a63358cdba395b2ad3b471db312765356041ae38 Mon Sep 17 00:00:00 2001 From: AurelienFT Date: Fri, 14 Mar 2025 19:21:51 +0100 Subject: [PATCH 52/76] fix compil --- crates/services/tx_status_manager/src/service.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/services/tx_status_manager/src/service.rs b/crates/services/tx_status_manager/src/service.rs index a3c797bb37a..045d2d94ea8 100644 --- a/crates/services/tx_status_manager/src/service.rs +++ b/crates/services/tx_status_manager/src/service.rs @@ -423,7 +423,7 @@ mod tests { let tx_status_change = TxStatusChange::new(100, Duration::from_secs(360)); let updater_sender = tx_status_change.update_sender.clone(); - let tx_status_manager = TxStatusManager::new(tx_status_change, TTL); + let tx_status_manager = TxStatusManager::new(tx_status_change, TTL, false); let signing_key = SecretKey::default(); let protocol_public_key = signing_key.public_key(); let signature_verification = SignatureVerification::new(protocol_public_key); From f60f79382d6220846e04a8b291785252e7da6e40 Mon Sep 17 00:00:00 2001 From: Mitch Turner Date: Fri, 14 Mar 2025 13:44:37 -0600 Subject: [PATCH 53/76] Use `ConsensusConfig` to get `PublicKey` (`Address` actually) --- Cargo.lock | 1 - bin/fuel-core/src/cli/run.rs | 2 - crates/chain-config/src/config/consensus.rs | 43 ++++++++----- .../src/service/adapters/tx_status_manager.rs | 29 ++++++++- crates/fuel-core/src/service/sub_services.rs | 5 ++ .../consensus_module/poa/src/verifier.rs | 2 +- crates/services/tx_status_manager/Cargo.toml | 1 - .../services/tx_status_manager/src/config.rs | 4 -- .../services/tx_status_manager/src/service.rs | 63 +++++++++++-------- .../tx_status_manager/src/tests/mod.rs | 15 ++++- .../tx_status_manager/src/tests/universe.rs | 6 +- 11 files changed, 113 insertions(+), 58 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0439dcb2b53..8322dda6731 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4034,7 +4034,6 @@ dependencies = [ "tokio", "tokio-stream", "tracing", - "tracing-subscriber", ] [[package]] diff --git a/bin/fuel-core/src/cli/run.rs b/bin/fuel-core/src/cli/run.rs index 99acb1ef6e5..f7400ad0a81 100644 --- a/bin/fuel-core/src/cli/run.rs +++ b/bin/fuel-core/src/cli/run.rs @@ -714,8 +714,6 @@ impl Command { max_tx_update_subscriptions: tx_number_active_subscriptions, subscription_ttl, status_cache_ttl: status_cache_ttl.into(), - // TODO: Where can I get this value? - protocol_public_key: Default::default(), metrics: metrics.is_enabled(Module::TxStatusManager), }, }; diff --git a/crates/chain-config/src/config/consensus.rs b/crates/chain-config/src/config/consensus.rs index f6dff8998c8..1a606e54e20 100644 --- a/crates/chain-config/src/config/consensus.rs +++ b/crates/chain-config/src/config/consensus.rs @@ -37,17 +37,17 @@ pub struct PoAV2 { impl PoAV2 { pub fn new( - genesis_signing_key: Address, - signing_key_overrides: BTreeMap, + genesis_signing_key_address: Address, + signing_key_address_overrides: BTreeMap, ) -> Self { PoAV2 { - genesis_signing_key, - signing_key_overrides, + genesis_signing_key: genesis_signing_key_address, + signing_key_overrides: signing_key_address_overrides, } } - /// Returns the signing key for the given block height. - pub fn signing_key_at(&self, height: BlockHeight) -> Address { + /// Returns the address for the signing key at block height. + pub fn address_for_height(&self, height: BlockHeight) -> Address { if self.signing_key_overrides.is_empty() { self.genesis_signing_key } else { @@ -60,6 +60,15 @@ impl PoAV2 { } } + /// Returns the address of the latest signing key for the given block height. + pub fn latest_address(&self) -> Address { + self.signing_key_overrides + .last_key_value() + .map(|(_, key)| key) + .cloned() + .unwrap_or(self.genesis_signing_key) + } + /// Returns overrides for all the signing keys. pub fn get_all_overrides(&self) -> &BTreeMap { &self.signing_key_overrides @@ -73,10 +82,12 @@ impl PoAV2 { #[cfg(test)] mod tests { + #![allow(non_snake_case)] + use super::*; #[test] - fn signing_key_at_works() { + fn address_at_height__returns_expected_values() { // Given let genesis_signing_key = Address::from([1; 32]); let signing_key_after_10 = Address::from([2; 32]); @@ -95,16 +106,16 @@ mod tests { }; // When/Then - assert_eq!(poa.signing_key_at(0u32.into()), genesis_signing_key); - assert_eq!(poa.signing_key_at(9u32.into()), genesis_signing_key); - assert_eq!(poa.signing_key_at(10u32.into()), signing_key_after_10); - assert_eq!(poa.signing_key_at(19u32.into()), signing_key_after_10); - assert_eq!(poa.signing_key_at(20u32.into()), signing_key_after_20); - assert_eq!(poa.signing_key_at(29u32.into()), signing_key_after_20); - assert_eq!(poa.signing_key_at(30u32.into()), signing_key_after_30); - assert_eq!(poa.signing_key_at(40u32.into()), signing_key_after_30); + assert_eq!(poa.address_for_height(0u32.into()), genesis_signing_key); + assert_eq!(poa.address_for_height(9u32.into()), genesis_signing_key); + assert_eq!(poa.address_for_height(10u32.into()), signing_key_after_10); + assert_eq!(poa.address_for_height(19u32.into()), signing_key_after_10); + assert_eq!(poa.address_for_height(20u32.into()), signing_key_after_20); + assert_eq!(poa.address_for_height(29u32.into()), signing_key_after_20); + assert_eq!(poa.address_for_height(30u32.into()), signing_key_after_30); + assert_eq!(poa.address_for_height(40u32.into()), signing_key_after_30); assert_eq!( - poa.signing_key_at(4_000_000u32.into()), + poa.address_for_height(4_000_000u32.into()), signing_key_after_30 ); } diff --git a/crates/fuel-core/src/service/adapters/tx_status_manager.rs b/crates/fuel-core/src/service/adapters/tx_status_manager.rs index 7030ae502fb..7ad09b93fae 100644 --- a/crates/fuel-core/src/service/adapters/tx_status_manager.rs +++ b/crates/fuel-core/src/service/adapters/tx_status_manager.rs @@ -1,7 +1,11 @@ -use fuel_core_services::stream::BoxStream; -use fuel_core_tx_status_manager::ports::P2PPreConfirmationGossipData; - use super::P2PAdapter; +use fuel_core_chain_config::ConsensusConfig; +use fuel_core_services::stream::BoxStream; +use fuel_core_tx_status_manager::{ + ports::P2PPreConfirmationGossipData, + service::ProtocolPublicKey, +}; +use fuel_core_types::fuel_tx::Address; #[cfg(feature = "p2p")] impl fuel_core_tx_status_manager::ports::P2PSubscriptions for P2PAdapter { @@ -32,3 +36,22 @@ impl fuel_core_tx_status_manager::ports::P2PSubscriptions for P2PAdapter { Box::pin(fuel_core_services::stream::pending()) } } + +pub struct ConsensusConfigProtocolPublicKey { + inner: ConsensusConfig, +} + +impl ConsensusConfigProtocolPublicKey { + pub fn new(inner: ConsensusConfig) -> Self { + Self { inner } + } +} + +impl ProtocolPublicKey for ConsensusConfigProtocolPublicKey { + fn latest_address(&self) -> Address { + match &self.inner { + ConsensusConfig::PoA { signing_key } => *signing_key, + ConsensusConfig::PoAV2(poa_v2) => poa_v2.latest_address(), + } + } +} diff --git a/crates/fuel-core/src/service/sub_services.rs b/crates/fuel-core/src/service/sub_services.rs index 26616af2b53..a5860b144b8 100644 --- a/crates/fuel-core/src/service/sub_services.rs +++ b/crates/fuel-core/src/service/sub_services.rs @@ -67,6 +67,7 @@ use crate::{ graphql_api::GraphQLBlockImporter, import_result_provider::ImportResultProvider, ready_signal::ReadySignal, + tx_status_manager::ConsensusConfigProtocolPublicKey, BlockImporterAdapter, BlockProducerAdapter, ChainStateInfoProvider, @@ -256,9 +257,13 @@ pub fn init_sub_services( universal_gas_price_provider.clone(), ); + let protocol_pubkey = + ConsensusConfigProtocolPublicKey::new(chain_config.consensus.clone()); + let tx_status_manager = fuel_core_tx_status_manager::new_service( p2p_adapter.clone(), config.tx_status_manager.clone(), + protocol_pubkey, ); let tx_status_manager_adapter = TxStatusManagerAdapter::new(tx_status_manager.shared.clone()); diff --git a/crates/services/consensus_module/poa/src/verifier.rs b/crates/services/consensus_module/poa/src/verifier.rs index 6e34f606719..04ab99f7b2b 100644 --- a/crates/services/consensus_module/poa/src/verifier.rs +++ b/crates/services/consensus_module/poa/src/verifier.rs @@ -31,7 +31,7 @@ pub fn verify_consensus( ConsensusConfig::PoAV2(poa) => { let id = header.id(); let m = id.as_message(); - let signing_key = poa.signing_key_at(*header.height()); + let signing_key = poa.address_for_height(*header.height()); consensus .signature .recover(m) diff --git a/crates/services/tx_status_manager/Cargo.toml b/crates/services/tx_status_manager/Cargo.toml index 8b788781758..88258b194b5 100644 --- a/crates/services/tx_status_manager/Cargo.toml +++ b/crates/services/tx_status_manager/Cargo.toml @@ -32,7 +32,6 @@ test-case = { workspace = true } test-strategy = { workspace = true } tokio = { workspace = true, features = ["test-util", "macros"] } tracing = { workspace = true } -tracing-subscriber = { workspace = true } [features] test-helpers = ["fuel-core-types/test-helpers"] diff --git a/crates/services/tx_status_manager/src/config.rs b/crates/services/tx_status_manager/src/config.rs index d84f580ba49..a4667a64e82 100644 --- a/crates/services/tx_status_manager/src/config.rs +++ b/crates/services/tx_status_manager/src/config.rs @@ -1,4 +1,3 @@ -use fuel_core_types::fuel_crypto::PublicKey; use std::time::Duration; #[derive(Clone, Debug)] @@ -9,8 +8,6 @@ pub struct Config { pub subscription_ttl: Duration, /// Maximum time to keep the status in the cache of the manager. pub status_cache_ttl: Duration, - /// Protocol Signing Key, i.e. the block signer's public key - pub protocol_public_key: PublicKey, /// Enable metrics when set to true pub metrics: bool, } @@ -22,7 +19,6 @@ impl Default for Config { max_tx_update_subscriptions: 1000, subscription_ttl: Duration::from_secs(60 * 10), status_cache_ttl: Duration::from_secs(5), - protocol_public_key: PublicKey::default(), metrics: false, } } diff --git a/crates/services/tx_status_manager/src/service.rs b/crates/services/tx_status_manager/src/service.rs index a3c797bb37a..caebe66a1f9 100644 --- a/crates/services/tx_status_manager/src/service.rs +++ b/crates/services/tx_status_manager/src/service.rs @@ -20,13 +20,12 @@ use fuel_core_services::{ use fuel_core_types::{ ed25519::Signature, ed25519_dalek::Verifier, - fuel_crypto::{ - Message, - PublicKey, - }, + fuel_crypto::Message, fuel_tx::{ + Address, Bytes32, Bytes64, + Input, TxId, }, services::{ @@ -109,23 +108,27 @@ impl SharedData { } } -pub struct Task { +pub struct Task { manager: TxStatusManager, subscriptions: Subscriptions, read_requests_receiver: mpsc::Receiver, write_requests_receiver: mpsc::UnboundedReceiver, shared_data: SharedData, - signature_verification: SignatureVerification, + signature_verification: SignatureVerification, early_preconfirmations: Vec>, } -struct SignatureVerification { - protocol_pubkey: PublicKey, +pub trait ProtocolPublicKey: Send { + fn latest_address(&self) -> Address; +} + +struct SignatureVerification { + protocol_pubkey: Pubkey, delegate_keys: HashMap, } -impl SignatureVerification { - pub fn new(protocol_pubkey: PublicKey) -> Self { +impl SignatureVerification { + pub fn new(protocol_pubkey: Pubkey) -> Self { Self { protocol_pubkey, delegate_keys: HashMap::new(), @@ -166,16 +169,17 @@ impl SignatureVerification { let Sealed { entity, signature } = sealed; let bytes = postcard::to_allocvec(&entity).unwrap(); let message = Message::new(&bytes); - let verified = signature.verify(&self.protocol_pubkey, &message); + let expected_address = self.protocol_pubkey.latest_address(); + // let verified = signature.verify(&pubkey, &message); + let verified = signature + .recover(&message) + .map_or(false, |pubkey| Input::owner(&pubkey) == expected_address); self.remove_expired_delegates(); - match verified { - Ok(_) => { - self.delegate_keys - .insert(entity.expiration, entity.public_key); - true - } - Err(_) => false, - } + if verified { + self.delegate_keys + .insert(entity.expiration, entity.public_key); + }; + verified } async fn check_preconfirmation_signature( @@ -195,7 +199,7 @@ impl SignatureVerification { } } -impl Task { +impl Task { fn handle_verified_preconfirmation( &mut self, sealed: Sealed, @@ -243,7 +247,6 @@ impl Task { { self.handle_verified_preconfirmation(sealed); } else { - // TODO: Allow to retry later when a new delegate key is added tracing::warn!("Preconfirmation signature verification failed, will try once more when a new delegate key is added"); self.early_preconfirmations.push(sealed); } @@ -253,7 +256,7 @@ impl Task { } #[async_trait::async_trait] -impl RunnableService for Task { +impl RunnableService for Task { const NAME: &'static str = "TxStatusManagerTask"; type SharedData = SharedData; type Task = Self; @@ -272,7 +275,7 @@ impl RunnableService for Task { } } -impl RunnableTask for Task { +impl RunnableTask for Task { async fn run(&mut self, watcher: &mut StateWatcher) -> TaskNextAction { tokio::select! { biased; @@ -329,9 +332,14 @@ impl RunnableTask for Task { } } -pub fn new_service(p2p: P2P, config: Config) -> ServiceRunner +pub fn new_service( + p2p: P2P, + config: Config, + protocol_pubkey: Pubkey, +) -> ServiceRunner> where P2P: P2PSubscriptions, + Pubkey: ProtocolPublicKey, { let tx_status_from_p2p_stream = p2p.gossiped_tx_statuses(); let subscriptions = Subscriptions { @@ -351,7 +359,7 @@ where read_requests_sender, write_requests_sender, }; - let signature_verification = SignatureVerification::new(config.protocol_public_key); + let signature_verification = SignatureVerification::new(protocol_pubkey); ServiceRunner::new(Task { subscriptions, @@ -385,6 +393,7 @@ mod tests { }, fuel_crypto::{ Message, + PublicKey, SecretKey, Signature, }, @@ -410,7 +419,7 @@ mod tests { pub protocol_signing_key: SecretKey, } - fn new_task_with_handles() -> (Task, Handles) { + fn new_task_with_handles() -> (Task, Handles) { let (read_requests_sender, read_requests_receiver) = mpsc::channel(1); let (write_requests_sender, write_requests_receiver) = mpsc::unbounded_channel(); let shared_data = SharedData { @@ -423,7 +432,7 @@ mod tests { let tx_status_change = TxStatusChange::new(100, Duration::from_secs(360)); let updater_sender = tx_status_change.update_sender.clone(); - let tx_status_manager = TxStatusManager::new(tx_status_change, TTL); + let tx_status_manager = TxStatusManager::new(tx_status_change, TTL, false); let signing_key = SecretKey::default(); let protocol_public_key = signing_key.public_key(); let signature_verification = SignatureVerification::new(protocol_public_key); diff --git a/crates/services/tx_status_manager/src/tests/mod.rs b/crates/services/tx_status_manager/src/tests/mod.rs index b1ef04ad8ba..5a5c5b65b9a 100644 --- a/crates/services/tx_status_manager/src/tests/mod.rs +++ b/crates/services/tx_status_manager/src/tests/mod.rs @@ -10,4 +10,17 @@ mod tests_update_stream_state; mod universe; mod utils; -use tracing_subscriber as _; +use crate::service::ProtocolPublicKey; +use fuel_core_types::{ + fuel_crypto::PublicKey, + fuel_tx::{ + Address, + Input, + }, +}; + +impl ProtocolPublicKey for PublicKey { + fn latest_address(&self) -> Address { + Input::owner(self) + } +} diff --git a/crates/services/tx_status_manager/src/tests/universe.rs b/crates/services/tx_status_manager/src/tests/universe.rs index 4a3f2109a64..67af589e92c 100644 --- a/crates/services/tx_status_manager/src/tests/universe.rs +++ b/crates/services/tx_status_manager/src/tests/universe.rs @@ -1,3 +1,4 @@ +use crate::tests::PublicKey; use std::sync::Arc; use fuel_core_services::ServiceRunner; @@ -60,9 +61,10 @@ impl TestTxStatusManagerUniverse { self.tx_status_manager = Some(tx_status_manager.clone()); } - pub fn build_service(&self, p2p: Option) -> ServiceRunner { + pub fn build_service(&self, p2p: Option) -> ServiceRunner> { let p2p = p2p.unwrap_or_else(|| MockP2P::new_with_statuses(vec![])); + let arb_pubkey = Default::default(); - new_service(p2p, self.config.clone()) + new_service(p2p, self.config.clone(), arb_pubkey) } } From 7d44a661b6d241857c2f798924ddf048d01c2880 Mon Sep 17 00:00:00 2001 From: Mitch Turner Date: Fri, 14 Mar 2025 15:57:25 -0600 Subject: [PATCH 54/76] Avoid DoS vector by removing `early_preconfirmations` concept and test --- .../services/tx_status_manager/src/service.rs | 86 +------------------ 1 file changed, 3 insertions(+), 83 deletions(-) diff --git a/crates/services/tx_status_manager/src/service.rs b/crates/services/tx_status_manager/src/service.rs index caebe66a1f9..5211f7bb732 100644 --- a/crates/services/tx_status_manager/src/service.rs +++ b/crates/services/tx_status_manager/src/service.rs @@ -115,7 +115,6 @@ pub struct Task { write_requests_receiver: mpsc::UnboundedReceiver, shared_data: SharedData, signature_verification: SignatureVerification, - early_preconfirmations: Vec>, } pub trait ProtocolPublicKey: Send { @@ -225,18 +224,6 @@ impl Task { sealed.entity.public_key ); let _ = self.signature_verification.add_new_delegate(&sealed).await; - let drained = std::mem::take(&mut self.early_preconfirmations); - for sealed in drained { - if self - .signature_verification - .check_preconfirmation_signature(&sealed) - .await - { - self.handle_verified_preconfirmation(sealed); - } else { - tracing::warn!("Preconfirmation signature verification failed for early preconfirmation, removing it"); - } - } } PreConfirmationMessage::Preconfirmations(sealed) => { tracing::debug!("Received new preconfirmations from peer"); @@ -247,8 +234,9 @@ impl Task { { self.handle_verified_preconfirmation(sealed); } else { - tracing::warn!("Preconfirmation signature verification failed, will try once more when a new delegate key is added"); - self.early_preconfirmations.push(sealed); + // There is a chance that this is a signature for whom the delegate key hasn't + // arrived yet, in which case the pre-confirmation will be lost + tracing::warn!("Preconfirmation signature verification failed"); } } } @@ -368,7 +356,6 @@ where write_requests_receiver, shared_data, signature_verification, - early_preconfirmations: Vec::new(), }) } @@ -443,7 +430,6 @@ mod tests { write_requests_receiver, shared_data, signature_verification, - early_preconfirmations: Vec::new(), }; let handles = Handles { pre_confirmation_updates: sender, @@ -736,72 +722,6 @@ mod tests { assert!(all_streams_timeout(&mut streams).await); } - #[tokio::test] - async fn run__when_pre_confirmations_fail_verification_they_can_be_retried_on_next_delegate_update( - ) { - // given - let (task, handles) = new_task_with_handles(); - - let tx_ids = vec![[3u8; 32].into(), [4u8; 32].into()]; - let preconfirmations = tx_ids - .clone() - .into_iter() - .map(|tx_id| Preconfirmation { - tx_id, - status: PreconfirmationStatus::Success { - tx_pointer: Default::default(), - total_gas: 0, - total_fee: 0, - receipts: vec![], - outputs: vec![], - }, - }) - .collect(); - let (delegate_signing_key, delegate_verifying_key) = delegate_key_pair(); - let expiration = Tai64(u64::MAX); - let pre_confirmation_message = valid_pre_confirmation_signature( - preconfirmations, - delegate_signing_key, - expiration, - ); - let delegate_signature_message = valid_sealed_delegate_signature( - handles.protocol_signing_key, - delegate_verifying_key, - expiration, - ); - - let mut streams = tx_ids - .iter() - .map(|tx_id| { - handles - .update_sender - .try_subscribe::(*tx_id) - .unwrap() - }) - .collect::>(); - handles - .pre_confirmation_updates - .send(pre_confirmation_message) - .await - .unwrap(); - - let service = ServiceRunner::new(task); - service.start_and_await().await.unwrap(); - - assert!(all_streams_timeout(&mut streams).await); - - // when - handles - .pre_confirmation_updates - .send(delegate_signature_message) - .await - .unwrap(); - tokio::time::sleep(Duration::from_millis(100)).await; - - // then - assert!(all_streams_return_success(streams).await); - } - #[tokio::test] async fn run__if_preconfirmation_signature_is_expired_do_not_send() { // given From f1b7209f0694bfa25c5dbb0742ad58788a743ed7 Mon Sep 17 00:00:00 2001 From: AurelienFT Date: Mon, 17 Mar 2025 12:03:05 +0100 Subject: [PATCH 55/76] Try to make tests more stable --- .changes/added/2866.md | 1 - crates/fuel-core/src/schema/tx.rs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) delete mode 100644 .changes/added/2866.md diff --git a/.changes/added/2866.md b/.changes/added/2866.md deleted file mode 100644 index c57063830a2..00000000000 --- a/.changes/added/2866.md +++ /dev/null @@ -1 +0,0 @@ -Introduce adapter for signature verification in the tx status manager service \ No newline at end of file diff --git a/crates/fuel-core/src/schema/tx.rs b/crates/fuel-core/src/schema/tx.rs index cb61ddfb770..af0ba1760a2 100644 --- a/crates/fuel-core/src/schema/tx.rs +++ b/crates/fuel-core/src/schema/tx.rs @@ -712,7 +712,7 @@ async fn submit_and_await_status<'a>( Err(anyhow::anyhow!("Failed to get transaction status").into()) } }) - .take(3)) + .take(10)) } struct StatusChangeState<'a> { From 165519d50356c7475defcf465bd84cae866f1623 Mon Sep 17 00:00:00 2001 From: AurelienFT Date: Mon, 17 Mar 2025 15:55:31 +0100 Subject: [PATCH 56/76] Try without multi thread flavor --- bin/e2e-test-client/tests/integration_tests.rs | 4 ++-- tests/tests/state_rewind.rs | 18 +++++++++--------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/bin/e2e-test-client/tests/integration_tests.rs b/bin/e2e-test-client/tests/integration_tests.rs index 13f66893370..5e7b0bdc657 100644 --- a/bin/e2e-test-client/tests/integration_tests.rs +++ b/bin/e2e-test-client/tests/integration_tests.rs @@ -25,7 +25,7 @@ use tempfile::TempDir; // Used for writing assertions // Run programs #[global_allocator] static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; -#[tokio::test(flavor = "multi_thread")] +#[tokio::test] async fn works_in_local_env() { // setup a local node let srv = setup_dev_node().await; @@ -36,7 +36,7 @@ async fn works_in_local_env() { } // Spins up a node for each wallet and verifies that the suite works across multiple nodes -#[tokio::test(flavor = "multi_thread")] +#[tokio::test] async fn works_in_multinode_local_env() { use fuel_core::p2p_test_helpers::*; use fuel_core_types::{ diff --git a/tests/tests/state_rewind.rs b/tests/tests/state_rewind.rs index 623b472e171..a84c80632fa 100644 --- a/tests/tests/state_rewind.rs +++ b/tests/tests/state_rewind.rs @@ -86,7 +86,7 @@ fn transfer_transaction(min_amount: u64, rng: &mut StdRng) -> Transaction { builder.finalize_as_transaction() } -#[tokio::test(flavor = "multi_thread")] +#[tokio::test] async fn validate_block_at_any_height__only_transfers() -> anyhow::Result<()> { let mut rng = StdRng::seed_from_u64(1234); let driver = FuelCoreDriver::spawn_feeless(&[ @@ -239,7 +239,7 @@ async fn rollback_existing_chain_to_target_height_and_verify( Ok(()) } -#[tokio::test(flavor = "multi_thread")] +#[tokio::test] async fn rollback_chain_to_genesis() -> anyhow::Result<()> { let genesis_block_height = 0; let blocks_in_the_chain = 100; @@ -250,7 +250,7 @@ async fn rollback_chain_to_genesis() -> anyhow::Result<()> { .await } -#[tokio::test(flavor = "multi_thread")] +#[tokio::test] async fn rollback_chain_to_middle() -> anyhow::Result<()> { let target_rollback_block_height = 50; let blocks_in_the_chain = 100; @@ -261,7 +261,7 @@ async fn rollback_chain_to_middle() -> anyhow::Result<()> { .await } -#[tokio::test(flavor = "multi_thread")] +#[tokio::test] async fn rollback_chain_to_same_height() -> anyhow::Result<()> { let target_rollback_block_height = 100; let blocks_in_the_chain = 100; @@ -272,7 +272,7 @@ async fn rollback_chain_to_same_height() -> anyhow::Result<()> { .await } -#[tokio::test(flavor = "multi_thread")] +#[tokio::test] async fn rollback_chain_to_same_height_1000() -> anyhow::Result<()> { let target_rollback_block_height = 800; let blocks_in_the_chain = 1000; @@ -283,7 +283,7 @@ async fn rollback_chain_to_same_height_1000() -> anyhow::Result<()> { .await } -#[tokio::test(flavor = "multi_thread")] +#[tokio::test] async fn rollback_to__should_work_with_empty_gas_price_database() -> anyhow::Result<()> { let mut rng = StdRng::seed_from_u64(1234); let driver = FuelCoreDriver::spawn_feeless(&[ @@ -320,7 +320,7 @@ async fn rollback_to__should_work_with_empty_gas_price_database() -> anyhow::Res Ok(()) } -#[tokio::test(flavor = "multi_thread")] +#[tokio::test] async fn backup_and_restore__should_work_with_state_rewind() -> anyhow::Result<()> { let mut rng = StdRng::seed_from_u64(1234); let driver = FuelCoreDriver::spawn_feeless(&[ @@ -402,7 +402,7 @@ async fn backup_and_restore__should_work_with_state_rewind() -> anyhow::Result<( Ok(()) } -#[tokio::test(flavor = "multi_thread")] +#[tokio::test] async fn dry_run__correct_utxoid_state_in_past_blocks() -> anyhow::Result<()> { let mut rng = StdRng::seed_from_u64(1234); let poa_secret = SecretKey::random(&mut rng); @@ -510,7 +510,7 @@ async fn dry_run__correct_utxoid_state_in_past_blocks() -> anyhow::Result<()> { Ok(()) } -#[tokio::test(flavor = "multi_thread")] +#[tokio::test] async fn dry_run__correct_contract_state_in_past_blocks() -> anyhow::Result<()> { let mut rng = StdRng::seed_from_u64(1234); let driver = FuelCoreDriver::spawn_feeless(&[ From f30544363fafea17642ebed5b0f644d2484cd006 Mon Sep 17 00:00:00 2001 From: AurelienFT Date: Mon, 17 Mar 2025 19:20:41 +0100 Subject: [PATCH 57/76] Revert tests changes and change max buffer size --- bin/e2e-test-client/tests/integration_tests.rs | 4 ++-- .../tx_status_manager/src/update_sender.rs | 6 +++--- tests/tests/state_rewind.rs | 18 +++++++++--------- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/bin/e2e-test-client/tests/integration_tests.rs b/bin/e2e-test-client/tests/integration_tests.rs index 5e7b0bdc657..13f66893370 100644 --- a/bin/e2e-test-client/tests/integration_tests.rs +++ b/bin/e2e-test-client/tests/integration_tests.rs @@ -25,7 +25,7 @@ use tempfile::TempDir; // Used for writing assertions // Run programs #[global_allocator] static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; -#[tokio::test] +#[tokio::test(flavor = "multi_thread")] async fn works_in_local_env() { // setup a local node let srv = setup_dev_node().await; @@ -36,7 +36,7 @@ async fn works_in_local_env() { } // Spins up a node for each wallet and verifies that the suite works across multiple nodes -#[tokio::test] +#[tokio::test(flavor = "multi_thread")] async fn works_in_multinode_local_env() { use fuel_core::p2p_test_helpers::*; use fuel_core_types::{ diff --git a/crates/services/tx_status_manager/src/update_sender.rs b/crates/services/tx_status_manager/src/update_sender.rs index 009980ff63d..8878e0de08d 100644 --- a/crates/services/tx_status_manager/src/update_sender.rs +++ b/crates/services/tx_status_manager/src/update_sender.rs @@ -27,9 +27,9 @@ use crate::tx_status_stream::{ }; /// Subscriber channel buffer size. -/// Subscribers will only ever get at most a submitted -/// and final transaction status update. -const BUFFER_SIZE: usize = 2; +/// Subscribers will only ever get at most a submitted, +/// a preconfirmation and final transaction status update. +const BUFFER_SIZE: usize = 3; #[derive(Clone)] pub struct TxStatusChange { diff --git a/tests/tests/state_rewind.rs b/tests/tests/state_rewind.rs index a84c80632fa..623b472e171 100644 --- a/tests/tests/state_rewind.rs +++ b/tests/tests/state_rewind.rs @@ -86,7 +86,7 @@ fn transfer_transaction(min_amount: u64, rng: &mut StdRng) -> Transaction { builder.finalize_as_transaction() } -#[tokio::test] +#[tokio::test(flavor = "multi_thread")] async fn validate_block_at_any_height__only_transfers() -> anyhow::Result<()> { let mut rng = StdRng::seed_from_u64(1234); let driver = FuelCoreDriver::spawn_feeless(&[ @@ -239,7 +239,7 @@ async fn rollback_existing_chain_to_target_height_and_verify( Ok(()) } -#[tokio::test] +#[tokio::test(flavor = "multi_thread")] async fn rollback_chain_to_genesis() -> anyhow::Result<()> { let genesis_block_height = 0; let blocks_in_the_chain = 100; @@ -250,7 +250,7 @@ async fn rollback_chain_to_genesis() -> anyhow::Result<()> { .await } -#[tokio::test] +#[tokio::test(flavor = "multi_thread")] async fn rollback_chain_to_middle() -> anyhow::Result<()> { let target_rollback_block_height = 50; let blocks_in_the_chain = 100; @@ -261,7 +261,7 @@ async fn rollback_chain_to_middle() -> anyhow::Result<()> { .await } -#[tokio::test] +#[tokio::test(flavor = "multi_thread")] async fn rollback_chain_to_same_height() -> anyhow::Result<()> { let target_rollback_block_height = 100; let blocks_in_the_chain = 100; @@ -272,7 +272,7 @@ async fn rollback_chain_to_same_height() -> anyhow::Result<()> { .await } -#[tokio::test] +#[tokio::test(flavor = "multi_thread")] async fn rollback_chain_to_same_height_1000() -> anyhow::Result<()> { let target_rollback_block_height = 800; let blocks_in_the_chain = 1000; @@ -283,7 +283,7 @@ async fn rollback_chain_to_same_height_1000() -> anyhow::Result<()> { .await } -#[tokio::test] +#[tokio::test(flavor = "multi_thread")] async fn rollback_to__should_work_with_empty_gas_price_database() -> anyhow::Result<()> { let mut rng = StdRng::seed_from_u64(1234); let driver = FuelCoreDriver::spawn_feeless(&[ @@ -320,7 +320,7 @@ async fn rollback_to__should_work_with_empty_gas_price_database() -> anyhow::Res Ok(()) } -#[tokio::test] +#[tokio::test(flavor = "multi_thread")] async fn backup_and_restore__should_work_with_state_rewind() -> anyhow::Result<()> { let mut rng = StdRng::seed_from_u64(1234); let driver = FuelCoreDriver::spawn_feeless(&[ @@ -402,7 +402,7 @@ async fn backup_and_restore__should_work_with_state_rewind() -> anyhow::Result<( Ok(()) } -#[tokio::test] +#[tokio::test(flavor = "multi_thread")] async fn dry_run__correct_utxoid_state_in_past_blocks() -> anyhow::Result<()> { let mut rng = StdRng::seed_from_u64(1234); let poa_secret = SecretKey::random(&mut rng); @@ -510,7 +510,7 @@ async fn dry_run__correct_utxoid_state_in_past_blocks() -> anyhow::Result<()> { Ok(()) } -#[tokio::test] +#[tokio::test(flavor = "multi_thread")] async fn dry_run__correct_contract_state_in_past_blocks() -> anyhow::Result<()> { let mut rng = StdRng::seed_from_u64(1234); let driver = FuelCoreDriver::spawn_feeless(&[ From 90513c85b931a7dc2df5860838a2ae3e99177fd3 Mon Sep 17 00:00:00 2001 From: AurelienFT <32803821+AurelienFT@users.noreply.github.com> Date: Tue, 18 Mar 2025 23:36:21 +0100 Subject: [PATCH 58/76] Update crates/fuel-core/src/schema/tx.rs Co-authored-by: Green Baneling --- crates/fuel-core/src/schema/tx.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/fuel-core/src/schema/tx.rs b/crates/fuel-core/src/schema/tx.rs index af0ba1760a2..cb61ddfb770 100644 --- a/crates/fuel-core/src/schema/tx.rs +++ b/crates/fuel-core/src/schema/tx.rs @@ -712,7 +712,7 @@ async fn submit_and_await_status<'a>( Err(anyhow::anyhow!("Failed to get transaction status").into()) } }) - .take(10)) + .take(3)) } struct StatusChangeState<'a> { From 37c6339fc2668d0eb41aaf2d47facb1b73a983fa Mon Sep 17 00:00:00 2001 From: AurelienFT Date: Wed, 19 Mar 2025 12:13:29 +0100 Subject: [PATCH 59/76] Change creation of sub service signature to avoid block_on and follow the general guidelines --- Cargo.lock | 1 - crates/fuel-core/src/service/adapters.rs | 1 + crates/fuel-core/src/service/sub_services.rs | 28 +---- .../services/consensus_module/poa/Cargo.toml | 1 - .../src/pre_confirmation_signature_service.rs | 101 +++++++++++------- .../tests.rs | 2 +- tests/tests/aws_kms.rs | 2 +- 7 files changed, 71 insertions(+), 65 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ea610893b5e..0713be9e3ba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3811,7 +3811,6 @@ dependencies = [ "fuel-core-storage", "fuel-core-trace", "fuel-core-types 0.42.0", - "futures", "mockall", "rand 0.8.5", "serde", diff --git a/crates/fuel-core/src/service/adapters.rs b/crates/fuel-core/src/service/adapters.rs index a5b20b33809..ae36bbd0674 100644 --- a/crates/fuel-core/src/service/adapters.rs +++ b/crates/fuel-core/src/service/adapters.rs @@ -487,6 +487,7 @@ impl TxStatusManagerAdapter { } } +#[derive(Clone)] pub struct FuelBlockSigner { mode: SignMode, } diff --git a/crates/fuel-core/src/service/sub_services.rs b/crates/fuel-core/src/service/sub_services.rs index c7fc71ecf1c..a472a6dc5ad 100644 --- a/crates/fuel-core/src/service/sub_services.rs +++ b/crates/fuel-core/src/service/sub_services.rs @@ -14,11 +14,7 @@ use fuel_core_gas_price_service::v1::{ uninitialized_task::new_gas_price_service_v1, }; -#[cfg(feature = "p2p")] -use fuel_core_poa::pre_confirmation_signature_service::PreConfirmationSignatureTask; use fuel_core_poa::Trigger; -#[cfg(feature = "p2p")] -use fuel_core_services::ServiceRunner; use fuel_core_storage::{ self, transactional::AtomicView, @@ -32,10 +28,7 @@ use crate::relayer::Config as RelayerConfig; #[cfg(feature = "p2p")] use crate::service::adapters::consensus_module::poa::pre_confirmation_signature::{ - key_generator::{ - Ed25519Key, - Ed25519KeyGenerator, - }, + key_generator::Ed25519KeyGenerator, trigger::TimeBasedTrigger, tx_receiver::PreconfirmationsReceiver, }; @@ -327,7 +320,7 @@ pub fn init_sub_services( tracing::warn!("Disabled block production because of unavailable signer"); } - let signer = Arc::new(FuelBlockSigner::new(config.consensus_signer.clone())); + let signer = FuelBlockSigner::new(config.consensus_signer.clone()); #[cfg(feature = "shared-sequencer")] let shared_sequencer = { @@ -336,7 +329,7 @@ pub fn init_sub_services( fuel_core_shared_sequencer::service::new_service( importer_adapter.clone(), config, - signer.clone(), + Arc::new(signer.clone()), )? }; @@ -349,18 +342,7 @@ pub fn init_sub_services( #[cfg(feature = "p2p")] #[allow(clippy::type_complexity)] - let pre_confirmation_service: Option< - ServiceRunner< - PreConfirmationSignatureTask< - PreconfirmationsReceiver, - P2PAdapter, - FuelBlockSigner, - Ed25519KeyGenerator, - Ed25519Key, - TimeBasedTrigger, - >, - >, - > = production_enabled + let pre_confirmation_service = production_enabled .then(|| { fuel_core_poa::pre_confirmation_signature_service::new_service( config_preconfirmation.clone(), @@ -386,7 +368,7 @@ pub fn init_sub_services( producer_adapter.clone(), importer_adapter.clone(), p2p_adapter.clone(), - signer, + Arc::new(signer), predefined_blocks, SystemTime, block_production_ready_signal, diff --git a/crates/services/consensus_module/poa/Cargo.toml b/crates/services/consensus_module/poa/Cargo.toml index ff6e9a8b51a..c2f90000e6c 100644 --- a/crates/services/consensus_module/poa/Cargo.toml +++ b/crates/services/consensus_module/poa/Cargo.toml @@ -16,7 +16,6 @@ fuel-core-chain-config = { workspace = true } fuel-core-services = { workspace = true } fuel-core-storage = { workspace = true, features = ["std"] } fuel-core-types = { workspace = true, features = ["std"] } -futures = { workspace = true } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } thiserror = { workspace = true } diff --git a/crates/services/consensus_module/poa/src/pre_confirmation_signature_service.rs b/crates/services/consensus_module/poa/src/pre_confirmation_signature_service.rs index d7635809d96..e9b0d4526f1 100644 --- a/crates/services/consensus_module/poa/src/pre_confirmation_signature_service.rs +++ b/crates/services/consensus_module/poa/src/pre_confirmation_signature_service.rs @@ -1,7 +1,4 @@ -use std::{ - fmt::Debug, - sync::Arc, -}; +use std::fmt::Debug; use error::Result; use fuel_core_services::{ @@ -48,6 +45,23 @@ pub mod tx_receiver; #[cfg(test)] pub mod tests; +pub struct UninitializedPreConfirmationSignatureTask< + TxReceiver, + Broadcast, + Parent, + KeyGenerator, + KeyRotationTrigger, +> where + Parent: ParentSignature, +{ + tx_receiver: TxReceiver, + broadcast: Broadcast, + parent_signature: Parent, + key_generator: KeyGenerator, + key_rotation_trigger: KeyRotationTrigger, + echo_delegation_trigger: Interval, +} + pub struct PreConfirmationSignatureTask< TxReceiver, Broadcast, @@ -61,7 +75,7 @@ pub struct PreConfirmationSignatureTask< { tx_receiver: TxReceiver, broadcast: Broadcast, - parent_signature: Arc, + parent_signature: Parent, key_generator: KeyGenerator, current_delegate_key: ExpiringKey, sealed_delegate_message: @@ -72,7 +86,7 @@ pub struct PreConfirmationSignatureTask< #[async_trait::async_trait] impl RunnableService - for PreConfirmationSignatureTask + for UninitializedPreConfirmationSignatureTask where TxRcv: TxReceiver, Brdcst: Broadcast< @@ -88,7 +102,8 @@ where { const NAME: &'static str = "PreconfirmationSignatureTask"; type SharedData = EmptyShared; - type Task = Self; + type Task = + PreConfirmationSignatureTask; type TaskParams = (); fn shared_data(&self) -> Self::SharedData { @@ -100,13 +115,45 @@ where _: &StateWatcher, _: Self::TaskParams, ) -> anyhow::Result { - Ok(self) + let Self { + tx_receiver, + mut broadcast, + parent_signature, + mut key_generator, + mut key_rotation_trigger, + echo_delegation_trigger, + } = self; + + // The first key rotation is triggered immediately + let expiration = key_rotation_trigger.next_rotation().await?; + let (new_delegate_key, sealed) = + create_delegate_key(&mut key_generator, &parent_signature, expiration) + .await + .map_err(|e| anyhow::anyhow!(e))?; + + if let Err(e) = broadcast + .broadcast_delegate_key(sealed.entity.clone(), sealed.signature.clone()) + .await + { + tracing::error!("Failed to broadcast delegate key: {:?}", e); + } + let initialized_task = PreConfirmationSignatureTask { + tx_receiver, + broadcast, + parent_signature, + key_generator, + current_delegate_key: new_delegate_key, + sealed_delegate_message: sealed, + key_rotation_trigger, + echo_delegation_trigger, + }; + Ok(initialized_task) } } async fn create_delegate_key( key_generator: &mut Gen, - parent_signature: &Arc, + parent_signature: &Parent, expiration: Tai64, ) -> Result<( ExpiringKey, @@ -227,18 +274,18 @@ where } } -pub type Service = ServiceRunner< - PreConfirmationSignatureTask, +pub type Service = ServiceRunner< + UninitializedPreConfirmationSignatureTask, >; pub fn new_service( config: config::Config, preconfirmation_receiver: TxRcv, - mut p2p_adapter: Brdcst, - parent_signature: Arc, - mut key_generator: Gen, + p2p_adapter: Brdcst, + parent_signature: Parent, + key_generator: Gen, key_rotation_trigger: Trigger, -) -> anyhow::Result> +) -> anyhow::Result> where TxRcv: TxReceiver, Brdcst: Broadcast< @@ -252,33 +299,11 @@ where Parent: ParentSignature, Preconfirmations: serde::Serialize + Send + Debug, { - // It's ok to wait because the first rotation is expected to be immediate - let expiration = Tai64( - Tai64::now() - .0 - .saturating_add(config.key_expiration_interval.as_secs()), - ); - let (new_delegate_key, sealed) = futures::executor::block_on(create_delegate_key( - &mut key_generator, - &parent_signature, - expiration, - )) - .map_err(|e| anyhow::anyhow!(e))?; - - if let Err(e) = futures::executor::block_on( - p2p_adapter - .broadcast_delegate_key(sealed.entity.clone(), sealed.signature.clone()), - ) { - tracing::error!("Failed to broadcast delegate key: {:?}", e); - } - - Ok(Service::new(PreConfirmationSignatureTask { + Ok(Service::new(UninitializedPreConfirmationSignatureTask { tx_receiver: preconfirmation_receiver, broadcast: p2p_adapter, parent_signature, key_generator, - current_delegate_key: new_delegate_key, - sealed_delegate_message: sealed, key_rotation_trigger, echo_delegation_trigger: tokio::time::interval(config.echo_delegation_interval), })) diff --git a/crates/services/consensus_module/poa/src/pre_confirmation_signature_service/tests.rs b/crates/services/consensus_module/poa/src/pre_confirmation_signature_service/tests.rs index 76dafd4884a..9aef6df5f3a 100644 --- a/crates/services/consensus_module/poa/src/pre_confirmation_signature_service/tests.rs +++ b/crates/services/consensus_module/poa/src/pre_confirmation_signature_service/tests.rs @@ -238,7 +238,7 @@ impl TaskBuilder { let task = PreConfirmationSignatureTask { tx_receiver, broadcast, - parent_signature: Arc::new(parent_signature), + parent_signature, key_generator, current_delegate_key, sealed_delegate_message: Sealed { entity, signature }, diff --git a/tests/tests/aws_kms.rs b/tests/tests/aws_kms.rs index 57efe959b90..e832a4afc2c 100644 --- a/tests/tests/aws_kms.rs +++ b/tests/tests/aws_kms.rs @@ -6,7 +6,7 @@ use fuel_core_storage::transactional::AtomicView; use fuel_core_types::blockchain::consensus::Consensus; use test_helpers::fuel_core_driver::FuelCoreDriver; -#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +#[tokio::test] async fn can_get_sealed_block_from_poa_produced_block_when_signing_with_kms() { use fuel_core_types::fuel_crypto::PublicKey; use k256::pkcs8::DecodePublicKey; From d99d8afe937f84b4c4b2a1b25b0cf3f1ed172305 Mon Sep 17 00:00:00 2001 From: AurelienFT Date: Wed, 19 Mar 2025 12:52:09 +0100 Subject: [PATCH 60/76] Commit --- crates/services/executor/src/executor.rs | 33 +++++------- crates/types/src/services/p2p.rs | 14 +++-- crates/types/src/services/preconfirmation.rs | 57 ++++++++------------ crates/types/src/services/txpool.rs | 44 ++------------- 4 files changed, 47 insertions(+), 101 deletions(-) diff --git a/crates/services/executor/src/executor.rs b/crates/services/executor/src/executor.rs index 1ede2b1e56e..dbc2fee6ded 100644 --- a/crates/services/executor/src/executor.rs +++ b/crates/services/executor/src/executor.rs @@ -53,7 +53,11 @@ use fuel_core_types::{ contract::ContractUtxoInfo, RelayedTransaction, }, - fuel_asm::Word, + fuel_asm::{ + op, + PanicInstruction, + Word + }, fuel_merkle::binary::root_calculator::MerkleRootCalculator, fuel_tx::{ field::{ @@ -94,6 +98,7 @@ use fuel_core_types::{ TxPointer, UniqueIdentifier, UtxoId, + PanicReason }, fuel_types::{ canonical::Deserialize, @@ -116,7 +121,9 @@ use fuel_core_types::{ ExecutableTransaction, InterpreterParams, MemoryInstance, + NotSupportedEcal, }, + verification, state::StateTransition, Interpreter, ProgramState, @@ -162,17 +169,8 @@ use alloc::{ vec, vec::Vec, }; -use fuel_core_types::{ - fuel_asm::{ - op, - PanicInstruction, - }, - fuel_tx::PanicReason, - fuel_vm::{ - interpreter::NotSupportedEcal, - verification, - }, -}; + + /// The maximum amount of transactions that can be included in a block, /// excluding the mint transaction. @@ -275,13 +273,10 @@ fn convert_tx_execution_result_to_preconfirmation( total_gas, total_fee, .. - } => PreconfirmationStatus::Success { - total_gas: *total_gas, - total_fee: *total_fee, - tx_pointer, - receipts: receipts.clone(), - outputs: dynamic_outputs, - }, + } => { + #[cfg(feature = "std")] + PreconfirmationStatus::Success() + } TransactionExecutionResult::Failed { receipts, total_gas, diff --git a/crates/types/src/services/p2p.rs b/crates/types/src/services/p2p.rs index f1f884f479c..11a35066c2f 100644 --- a/crates/types/src/services/p2p.rs +++ b/crates/types/src/services/p2p.rs @@ -16,17 +16,14 @@ use super::{ use crate::services::preconfirmation::PreconfirmationStatus; use crate::{ fuel_tx::Transaction, - fuel_types::BlockHeight, + fuel_types::BlockHeight, services::txpool::statuses::PreConfirmationSqueezedOut, }; use std::{ - collections::HashSet, - fmt::{ + collections::HashSet, fmt::{ Debug, Display, Formatter, - }, - str::FromStr, - time::SystemTime, + }, str::FromStr, time::SystemTime }; pub use tai64::Tai64; @@ -129,14 +126,15 @@ impl PreConfirmationMessage { fuel_tx::TxId, services::preconfirmation::Preconfirmation, }; + use std::sync::Arc; Self::Preconfirmations(SignedPreconfirmationByDelegate { entity: Preconfirmations { expiration: Tai64::UNIX_EPOCH, preconfirmations: vec![Preconfirmation { tx_id: TxId::default(), - status: PreconfirmationStatus::SqueezedOut { + status: PreconfirmationStatus::SqueezedOut(Arc::new(PreConfirmationSqueezedOut { reason: "Dummy reason".to_string(), - }, + })), }], }, signature: crate::fuel_tx::Bytes64::default(), diff --git a/crates/types/src/services/preconfirmation.rs b/crates/types/src/services/preconfirmation.rs index 6a236eee70d..13182386413 100644 --- a/crates/types/src/services/preconfirmation.rs +++ b/crates/types/src/services/preconfirmation.rs @@ -1,11 +1,9 @@ //! The module containing all types related to the preconfirmation service. -use crate::fuel_tx::{ - Receipt, - TxId, - TxPointer, -}; -use fuel_vm_private::fuel_tx::Output; +#[cfg(feature = "std")] +use std::sync::Arc; + +use crate::fuel_tx::TxId; use tai64::Tai64; #[cfg(not(feature = "std"))] @@ -13,6 +11,8 @@ use alloc::{ string::String, vec::Vec, }; + +use super::txpool; /// A collection of pre-confirmations that have been signed by a delegate #[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] @@ -34,39 +34,28 @@ pub struct Preconfirmation { pub status: PreconfirmationStatus, } +#[cfg(feature = "std")] /// Status of a transaction that has been pre-confirmed by block producer #[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum PreconfirmationStatus { /// Transaction was squeezed out by the tx pool - SqueezedOut { - /// Reason the transaction was squeezed out - reason: String, - }, + SqueezedOut(Arc), /// Transaction has been confirmed and will be included in block_height - Success { - /// Transaction pointer within the block. - tx_pointer: TxPointer, - /// The total gas used by the transaction. - total_gas: u64, - /// The total fee paid by the transaction. - total_fee: u64, - /// Receipts produced by the transaction during execution. - receipts: Vec, - /// Dynamic outputs produced by the transaction during execution. - outputs: Vec, - }, + Success(Arc), /// Transaction will not be included in a block, rejected at `block_height` - Failure { - /// Transaction pointer within the block. - tx_pointer: TxPointer, - /// The total gas used by the transaction. - total_gas: u64, - /// The total fee paid by the transaction. - total_fee: u64, - /// Receipts produced by the transaction during execution. - receipts: Vec, - /// Dynamic outputs produced by the transaction during execution. - outputs: Vec, - }, + Failure(Arc), } + +#[cfg(not(feature = "std"))] +/// Status of a transaction that has been pre-confirmed by block producer +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum PreconfirmationStatus { + /// Transaction was squeezed out by the tx pool + SqueezedOut(txpool::statuses::PreConfirmationSqueezedOut), + /// Transaction has been confirmed and will be included in block_height + Success(txpool::statuses::PreConfirmationSuccess), + /// Transaction will not be included in a block, rejected at `block_height` + Failure(txpool::statuses::PreConfirmationFailure), +} \ No newline at end of file diff --git a/crates/types/src/services/txpool.rs b/crates/types/src/services/txpool.rs index 130c1087c65..7ed2a7974ab 100644 --- a/crates/types/src/services/txpool.rs +++ b/crates/types/src/services/txpool.rs @@ -491,47 +491,11 @@ impl TransactionStatus { impl From for TransactionStatus { fn from(value: PreconfirmationStatus) -> Self { match value { - PreconfirmationStatus::SqueezedOut { reason } => { - TransactionStatus::PreConfirmationSqueezedOut( - statuses::PreConfirmationSqueezedOut { reason }.into(), - ) - } - PreconfirmationStatus::Success { - tx_pointer, - total_gas, - total_fee, - receipts, - outputs, - } => TransactionStatus::PreConfirmationSuccess( - statuses::PreConfirmationSuccess { - tx_pointer, - total_gas, - total_fee, - receipts: Some(receipts), - outputs: Some(outputs), - } - .into(), - ), - PreconfirmationStatus::Failure { - tx_pointer, - total_gas, - total_fee, - receipts, - outputs, - } => { - let reason = TransactionExecutionResult::reason(&receipts, &None); - TransactionStatus::PreConfirmationFailure( - statuses::PreConfirmationFailure { - tx_pointer, - total_gas, - total_fee, - receipts: Some(receipts), - outputs: Some(outputs), - reason, - } - .into(), - ) + PreconfirmationStatus::SqueezedOut(s) => { + TransactionStatus::PreConfirmationSqueezedOut(s) } + PreconfirmationStatus::Success(s) => TransactionStatus::PreConfirmationSuccess(s), + PreconfirmationStatus::Failure(s) => TransactionStatus::PreConfirmationFailure(s), } } } From 4837e7d424a53767f340d4ae0a4d3e61f24a1df6 Mon Sep 17 00:00:00 2001 From: AurelienFT Date: Wed, 19 Mar 2025 13:38:12 +0100 Subject: [PATCH 61/76] Move transaction status to a specific file to make it no-std compliant --- bin/fuel-core/src/cli/snapshot.rs | 2 +- crates/fuel-core/src/database/transactions.rs | 2 +- crates/fuel-core/src/graphql_api/database.rs | 2 +- crates/fuel-core/src/graphql_api/ports.rs | 10 +- crates/fuel-core/src/graphql_api/storage.rs | 2 +- .../src/graphql_api/storage/transactions.rs | 2 +- .../src/graphql_api/worker_service/tests.rs | 2 +- crates/fuel-core/src/query/message.rs | 4 +- crates/fuel-core/src/query/subscriptions.rs | 2 +- .../fuel-core/src/query/subscriptions/test.rs | 2 +- crates/fuel-core/src/query/tx.rs | 2 +- crates/fuel-core/src/schema/tx.rs | 4 +- crates/fuel-core/src/schema/tx/types.rs | 14 +- .../pre_confirmation_signature/broadcast.rs | 10 +- .../pre_confirmation_signature/tx_receiver.rs | 34 +- .../src/service/adapters/graphql_api.rs | 2 +- .../service/adapters/graphql_api/off_chain.rs | 4 +- .../fuel-core/src/service/adapters/txpool.rs | 2 +- crates/fuel-core/src/service/query.rs | 2 +- crates/services/executor/src/executor.rs | 66 ++- .../services/tx_status_manager/src/manager.rs | 4 +- .../services/tx_status_manager/src/service.rs | 88 ++-- .../tx_status_manager/src/tests/tests_e2e.rs | 2 +- .../src/tests/tests_sending.rs | 2 +- .../tx_status_manager/src/tests/utils.rs | 2 +- .../tx_status_manager/src/tx_status_stream.rs | 2 +- .../services/tx_status_manager/src/utils.rs | 2 +- crates/services/txpool_v2/src/pool.rs | 10 +- crates/services/txpool_v2/src/ports.rs | 2 +- crates/services/txpool_v2/src/service.rs | 6 +- crates/services/txpool_v2/src/tests/mocks.rs | 2 +- .../txpool_v2/src/tests/tests_pending_pool.rs | 2 +- .../txpool_v2/src/tests/tests_service.rs | 2 +- .../services/txpool_v2/src/tests/universe.rs | 6 +- crates/types/src/services.rs | 1 + crates/types/src/services/p2p.rs | 18 +- crates/types/src/services/preconfirmation.rs | 24 +- .../types/src/services/transaction_status.rs | 384 ++++++++++++++++++ crates/types/src/services/txpool.rs | 354 +--------------- 39 files changed, 580 insertions(+), 503 deletions(-) create mode 100644 crates/types/src/services/transaction_status.rs diff --git a/bin/fuel-core/src/cli/snapshot.rs b/bin/fuel-core/src/cli/snapshot.rs index d009f9d6a21..02a5b3b39ea 100644 --- a/bin/fuel-core/src/cli/snapshot.rs +++ b/bin/fuel-core/src/cli/snapshot.rs @@ -290,7 +290,7 @@ mod tests { UtxoId, }, fuel_types::ChainId, - services::txpool::TransactionExecutionStatus, + services::transaction_status::TransactionExecutionStatus, tai64::Tai64, }; use itertools::Itertools; diff --git a/crates/fuel-core/src/database/transactions.rs b/crates/fuel-core/src/database/transactions.rs index 24f630c2a56..8eed97ddff8 100644 --- a/crates/fuel-core/src/database/transactions.rs +++ b/crates/fuel-core/src/database/transactions.rs @@ -21,7 +21,7 @@ use fuel_core_types::{ TxPointer, }, fuel_types::Address, - services::txpool::TransactionExecutionStatus, + services::transaction_status::TransactionExecutionStatus, }; #[cfg(feature = "test-helpers")] diff --git a/crates/fuel-core/src/graphql_api/database.rs b/crates/fuel-core/src/graphql_api/database.rs index 762a9852939..5fb9e5d9d83 100644 --- a/crates/fuel-core/src/graphql_api/database.rs +++ b/crates/fuel-core/src/graphql_api/database.rs @@ -64,7 +64,7 @@ use fuel_core_types::{ fuel_vm::BlobData, services::{ graphql_api::ContractBalance, - txpool::TransactionExecutionStatus, + transaction_status::TransactionExecutionStatus, }, }; use futures::Stream; diff --git a/crates/fuel-core/src/graphql_api/ports.rs b/crates/fuel-core/src/graphql_api/ports.rs index 8680177179f..6e05e10d834 100644 --- a/crates/fuel-core/src/graphql_api/ports.rs +++ b/crates/fuel-core/src/graphql_api/ports.rs @@ -70,7 +70,7 @@ use fuel_core_types::{ }, graphql_api::ContractBalance, p2p::PeerInfo, - txpool::{ + transaction_status::{ self, TransactionStatus, }, @@ -92,7 +92,7 @@ pub trait OffChainDatabase: Send + Sync { fn tx_status( &self, tx_id: &TxId, - ) -> StorageResult; + ) -> StorageResult; fn balance( &self, @@ -368,7 +368,7 @@ pub mod worker { fuel_types::BlockHeight, services::{ block_importer::SharedImportResult, - txpool::{ + transaction_status::{ self, TransactionStatus, }, @@ -446,8 +446,8 @@ pub mod worker { fn update_tx_status( &mut self, id: &Bytes32, - status: txpool::TransactionExecutionStatus, - ) -> StorageResult>; + status: transaction_status::TransactionExecutionStatus, + ) -> StorageResult>; /// Update metadata about the total number of transactions on the chain. /// Returns the total count after the update. diff --git a/crates/fuel-core/src/graphql_api/storage.rs b/crates/fuel-core/src/graphql_api/storage.rs index 7bb311dd5bc..8719286b2f8 100644 --- a/crates/fuel-core/src/graphql_api/storage.rs +++ b/crates/fuel-core/src/graphql_api/storage.rs @@ -32,7 +32,7 @@ use fuel_core_types::{ Bytes32, }, fuel_types::BlockHeight, - services::txpool::TransactionExecutionStatus, + services::transaction_status::TransactionExecutionStatus, }; use statistic::StatisticTable; diff --git a/crates/fuel-core/src/graphql_api/storage/transactions.rs b/crates/fuel-core/src/graphql_api/storage/transactions.rs index fb1cf1c2cda..56e8000c1d3 100644 --- a/crates/fuel-core/src/graphql_api/storage/transactions.rs +++ b/crates/fuel-core/src/graphql_api/storage/transactions.rs @@ -23,7 +23,7 @@ use fuel_core_types::{ Bytes32, }, fuel_types::BlockHeight, - services::txpool::TransactionExecutionStatus, + services::transaction_status::TransactionExecutionStatus, }; use std::{ array::TryFromSliceError, diff --git a/crates/fuel-core/src/graphql_api/worker_service/tests.rs b/crates/fuel-core/src/graphql_api/worker_service/tests.rs index c34f69a8867..13693b7478d 100644 --- a/crates/fuel-core/src/graphql_api/worker_service/tests.rs +++ b/crates/fuel-core/src/graphql_api/worker_service/tests.rs @@ -10,7 +10,7 @@ use fuel_core_storage::StorageAsRef; use fuel_core_types::{ fuel_tx::Bytes32, fuel_types::BlockHeight, - services::txpool::TransactionStatus, + services::transaction_status::TransactionStatus, }; use std::sync::Arc; diff --git a/crates/fuel-core/src/query/message.rs b/crates/fuel-core/src/query/message.rs index a7868d57cb9..b95c6447006 100644 --- a/crates/fuel-core/src/query/message.rs +++ b/crates/fuel-core/src/query/message.rs @@ -31,7 +31,7 @@ use fuel_core_types::{ MessageId, Nonce, }, - services::txpool::TransactionExecutionStatus, + services::transaction_status::TransactionExecutionStatus, }; use futures::{ Stream, @@ -354,7 +354,7 @@ mod tests { BlockHeight, Nonce, }, - services::txpool::TransactionExecutionStatus, + services::transaction_status::TransactionExecutionStatus, tai64::Tai64, }; diff --git a/crates/fuel-core/src/query/subscriptions.rs b/crates/fuel-core/src/query/subscriptions.rs index 9904e37612b..4c94c0d243e 100644 --- a/crates/fuel-core/src/query/subscriptions.rs +++ b/crates/fuel-core/src/query/subscriptions.rs @@ -3,7 +3,7 @@ use fuel_core_storage::Result as StorageResult; use fuel_core_tx_status_manager::TxStatusMessage; use fuel_core_types::{ fuel_types::Bytes32, - services::txpool::TransactionStatus, + services::transaction_status::TransactionStatus, }; use futures::{ stream::BoxStream, diff --git a/crates/fuel-core/src/query/subscriptions/test.rs b/crates/fuel-core/src/query/subscriptions/test.rs index 9a3046eec06..eb033d69091 100644 --- a/crates/fuel-core/src/query/subscriptions/test.rs +++ b/crates/fuel-core/src/query/subscriptions/test.rs @@ -24,7 +24,7 @@ use fuel_core_tx_status_manager::TxStatusMessage; use fuel_core_txpool::error::RemovedReason; use fuel_core_types::{ fuel_types::Bytes32, - services::txpool::TransactionStatus, + services::transaction_status::TransactionStatus, tai64::Tai64, }; use futures::StreamExt; diff --git a/crates/fuel-core/src/query/tx.rs b/crates/fuel-core/src/query/tx.rs index de76ca0b288..1661c2065c8 100644 --- a/crates/fuel-core/src/query/tx.rs +++ b/crates/fuel-core/src/query/tx.rs @@ -14,7 +14,7 @@ use fuel_core_types::{ TxPointer, }, fuel_types::Address, - services::txpool::TransactionExecutionStatus, + services::transaction_status::TransactionExecutionStatus, }; use futures::{ Stream, diff --git a/crates/fuel-core/src/schema/tx.rs b/crates/fuel-core/src/schema/tx.rs index cb61ddfb770..55999a03a27 100644 --- a/crates/fuel-core/src/schema/tx.rs +++ b/crates/fuel-core/src/schema/tx.rs @@ -84,7 +84,7 @@ use fuel_core_types::{ CheckPredicateParams, EstimatePredicates, }, - services::txpool, + services::transaction_status, }; use futures::{ Stream, @@ -724,7 +724,7 @@ impl<'a> TxnStatusChangeState for StatusChangeState<'a> { async fn get_tx_status( &self, id: Bytes32, - ) -> StorageResult> { + ) -> StorageResult> { match self.query.tx_status(&id) { Ok(status) => Ok(Some(status.into())), Err(StorageError::NotFound(_, _)) => { diff --git a/crates/fuel-core/src/schema/tx/types.rs b/crates/fuel-core/src/schema/tx/types.rs index c88a30b2f65..40b78c2a851 100644 --- a/crates/fuel-core/src/schema/tx/types.rs +++ b/crates/fuel-core/src/schema/tx/types.rs @@ -83,7 +83,7 @@ use fuel_core_types::{ TransactionExecutionResult, TransactionExecutionStatus, }, - txpool::{ + transaction_status::{ self, TransactionStatus as TxStatus, }, @@ -163,7 +163,7 @@ impl SubmittedStatus { #[derive(Debug)] pub struct SuccessStatus { tx_id: TxId, - status: Arc, + status: Arc, } #[Object] @@ -214,7 +214,7 @@ impl SuccessStatus { #[derive(Debug)] pub struct PreconfirmationSuccessStatus { pub tx_id: TxId, - pub status: Arc, + pub status: Arc, } #[Object] @@ -268,7 +268,7 @@ impl PreconfirmationSuccessStatus { #[derive(Debug)] pub struct FailureStatus { tx_id: TxId, - status: Arc, + status: Arc, } #[Object] @@ -326,7 +326,7 @@ impl FailureStatus { #[derive(Debug)] pub struct PreconfirmationFailureStatus { pub tx_id: TxId, - pub status: Arc, + pub status: Arc, } #[Object] @@ -384,7 +384,7 @@ impl PreconfirmationFailureStatus { #[derive(Debug)] pub struct SqueezedOutStatus { pub tx_id: TxId, - pub status: Arc, + pub status: Arc, } #[Object] @@ -1095,7 +1095,7 @@ pub(crate) async fn get_tx_status( ) -> Result, StorageError> { let api_result = query .tx_status(&id) - .into_api_result::()?; + .into_api_result::()?; match api_result { Some(status) => { let status = TransactionStatus::new(id, status); diff --git a/crates/fuel-core/src/service/adapters/consensus_module/poa/pre_confirmation_signature/broadcast.rs b/crates/fuel-core/src/service/adapters/consensus_module/poa/pre_confirmation_signature/broadcast.rs index 22b4c023589..8287b1f2ef9 100644 --- a/crates/fuel-core/src/service/adapters/consensus_module/poa/pre_confirmation_signature/broadcast.rs +++ b/crates/fuel-core/src/service/adapters/consensus_module/poa/pre_confirmation_signature/broadcast.rs @@ -101,6 +101,7 @@ mod tests { services::{ p2p::ProtocolSignature, preconfirmation::PreconfirmationStatus, + transaction_status::statuses::PreConfirmationFailure, }, }; @@ -114,13 +115,14 @@ mod tests { let mut adapter = P2PAdapter::new(service, peer_report_config); let preconfirmations = vec![Preconfirmation { tx_id: Default::default(), - status: PreconfirmationStatus::Failure { + status: PreconfirmationStatus::Failure(Arc::new(PreConfirmationFailure { tx_pointer: Default::default(), total_gas: 0, total_fee: 0, - receipts: vec![], - outputs: vec![], - }, + receipts: Some(vec![]), + outputs: Some(vec![]), + reason: "Dummy reason".to_string(), + })), }]; let signature = ed25519::Signature::from_bytes(&[5u8; 64]); let expiration = Tai64::UNIX_EPOCH; diff --git a/crates/fuel-core/src/service/adapters/consensus_module/poa/pre_confirmation_signature/tx_receiver.rs b/crates/fuel-core/src/service/adapters/consensus_module/poa/pre_confirmation_signature/tx_receiver.rs index 5d4b55c724d..4d6c084be5a 100644 --- a/crates/fuel-core/src/service/adapters/consensus_module/poa/pre_confirmation_signature/tx_receiver.rs +++ b/crates/fuel-core/src/service/adapters/consensus_module/poa/pre_confirmation_signature/tx_receiver.rs @@ -51,10 +51,18 @@ impl TxReceiver for PreconfirmationsReceiver { mod tests { #![allow(non_snake_case)] + use std::sync::Arc; + use super::*; use fuel_core_types::{ fuel_tx::TxId, - services::preconfirmation::PreconfirmationStatus, + services::{ + preconfirmation::PreconfirmationStatus, + transaction_status::statuses::{ + PreConfirmationSqueezedOut, + PreConfirmationSuccess, + }, + }, }; #[tokio::test] @@ -63,19 +71,23 @@ mod tests { let txs = vec![ Preconfirmation { tx_id: TxId::default(), - status: PreconfirmationStatus::SqueezedOut { - reason: "Dummy reason".to_string(), - }, + status: PreconfirmationStatus::SqueezedOut(Arc::new( + PreConfirmationSqueezedOut { + reason: "Dummy reason".to_string(), + }, + )), }, Preconfirmation { tx_id: TxId::default(), - status: PreconfirmationStatus::Success { - tx_pointer: Default::default(), - total_gas: 0, - total_fee: 0, - receipts: vec![], - outputs: vec![], - }, + status: PreconfirmationStatus::Success(Arc::new( + PreConfirmationSuccess { + tx_pointer: Default::default(), + total_gas: 0, + total_fee: 0, + receipts: Some(vec![]), + outputs: Some(vec![]), + }, + )), }, ]; diff --git a/crates/fuel-core/src/service/adapters/graphql_api.rs b/crates/fuel-core/src/service/adapters/graphql_api.rs index 0f7d187eab5..e5166501f38 100644 --- a/crates/fuel-core/src/service/adapters/graphql_api.rs +++ b/crates/fuel-core/src/service/adapters/graphql_api.rs @@ -58,7 +58,7 @@ use fuel_core_types::{ TransactionExecutionStatus, }, p2p::PeerInfo, - txpool::TransactionStatus, + transaction_status::TransactionStatus, }, tai64::Tai64, }; 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 a280451faf2..4466f4efa15 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 @@ -87,7 +87,7 @@ use fuel_core_types::{ BlockHeight, Nonce, }, - services::txpool, + services::transaction_status, }; use std::iter; @@ -113,7 +113,7 @@ impl OffChainDatabase for OffChainIterableKeyValueView { fn tx_status( &self, tx_id: &TxId, - ) -> StorageResult { + ) -> StorageResult { self.get_tx_status(tx_id) .transpose() .ok_or(not_found!("TransactionId"))? diff --git a/crates/fuel-core/src/service/adapters/txpool.rs b/crates/fuel-core/src/service/adapters/txpool.rs index e133ad7143e..8fe17be857f 100644 --- a/crates/fuel-core/src/service/adapters/txpool.rs +++ b/crates/fuel-core/src/service/adapters/txpool.rs @@ -50,7 +50,7 @@ use fuel_core_types::{ PeerId, TransactionGossipData, }, - txpool::TransactionStatus, + transaction_status::TransactionStatus, }, }; use std::sync::Arc; diff --git a/crates/fuel-core/src/service/query.rs b/crates/fuel-core/src/service/query.rs index ccf0cea4063..2aa3a64a402 100644 --- a/crates/fuel-core/src/service/query.rs +++ b/crates/fuel-core/src/service/query.rs @@ -7,7 +7,7 @@ use fuel_core_types::{ UniqueIdentifier, }, fuel_types::Bytes32, - services::txpool::TransactionStatus as TxPoolTxStatus, + services::transaction_status::TransactionStatus as TxPoolTxStatus, }; use futures::{ Stream, diff --git a/crates/services/executor/src/executor.rs b/crates/services/executor/src/executor.rs index dbc2fee6ded..12730864222 100644 --- a/crates/services/executor/src/executor.rs +++ b/crates/services/executor/src/executor.rs @@ -56,7 +56,7 @@ use fuel_core_types::{ fuel_asm::{ op, PanicInstruction, - Word + Word, }, fuel_merkle::binary::root_calculator::MerkleRootCalculator, fuel_tx::{ @@ -92,13 +92,13 @@ use fuel_core_types::{ Input, Mint, Output, + PanicReason, Receipt, Transaction, TxId, TxPointer, UniqueIdentifier, UtxoId, - PanicReason }, fuel_types::{ canonical::Deserialize, @@ -123,8 +123,8 @@ use fuel_core_types::{ MemoryInstance, NotSupportedEcal, }, - verification, state::StateTransition, + verification, Interpreter, ProgramState, }, @@ -148,6 +148,7 @@ use fuel_core_types::{ PreconfirmationStatus, }, relayer::Event, + transaction_status, }, }; use parking_lot::Mutex as ParkingMutex; @@ -157,10 +158,16 @@ use tracing::{ }; #[cfg(feature = "std")] -use std::borrow::Cow; +use std::{ + borrow::Cow, + sync::Arc, +}; #[cfg(not(feature = "std"))] -use alloc::borrow::Cow; +use alloc::{ + borrow::Cow, + sync::Arc, +}; #[cfg(feature = "alloc")] use alloc::{ @@ -170,8 +177,6 @@ use alloc::{ vec::Vec, }; - - /// The maximum amount of transactions that can be included in a block, /// excluding the mint transaction. #[cfg(not(feature = "limited-tx-count"))] @@ -273,22 +278,39 @@ fn convert_tx_execution_result_to_preconfirmation( total_gas, total_fee, .. - } => { - #[cfg(feature = "std")] - PreconfirmationStatus::Success() - } + } => PreconfirmationStatus::Success(Arc::new( + transaction_status::statuses::PreConfirmationSuccess { + total_gas: *total_gas, + total_fee: *total_fee, + tx_pointer, + receipts: Some(receipts.clone()), + outputs: Some(dynamic_outputs), + }, + )), TransactionExecutionResult::Failed { receipts, total_gas, total_fee, + #[cfg(feature = "std")] + result, .. - } => PreconfirmationStatus::Failure { - total_gas: *total_gas, - total_fee: *total_fee, - tx_pointer, - receipts: receipts.clone(), - outputs: dynamic_outputs, - }, + } => { + #[cfg(feature = "std")] + let reason = TransactionExecutionResult::reason(receipts, result); + #[cfg(not(feature = "std"))] + let reason = "Transaction execution failed".to_string(); + + PreconfirmationStatus::Failure(Arc::new( + transaction_status::statuses::PreConfirmationFailure { + total_gas: *total_gas, + total_fee: *total_fee, + tx_pointer, + receipts: Some(receipts.clone()), + outputs: Some(dynamic_outputs), + reason, + }, + )) + } }; Preconfirmation { tx_id, status } } @@ -818,9 +840,11 @@ where Err(err) => { status.push(Preconfirmation { tx_id, - status: PreconfirmationStatus::SqueezedOut { - reason: err.to_string(), - }, + status: PreconfirmationStatus::SqueezedOut(Arc::new( + transaction_status::statuses::PreConfirmationSqueezedOut { + reason: err.to_string(), + }, + )), }); data.skipped_transactions.push((tx_id, err)); } diff --git a/crates/services/tx_status_manager/src/manager.rs b/crates/services/tx_status_manager/src/manager.rs index aee9e0febba..c8181a4b1d6 100644 --- a/crates/services/tx_status_manager/src/manager.rs +++ b/crates/services/tx_status_manager/src/manager.rs @@ -14,7 +14,7 @@ use fuel_core_types::{ Bytes32, TxId, }, - services::txpool::TransactionStatus, + services::transaction_status::TransactionStatus, }; use crate::{ @@ -232,7 +232,7 @@ mod tests { SeedableRng, }, fuel_tx::Bytes32, - services::txpool::TransactionStatus, + services::transaction_status::TransactionStatus, tai64::Tai64, }; diff --git a/crates/services/tx_status_manager/src/service.rs b/crates/services/tx_status_manager/src/service.rs index d0de771b824..242e0cd04e0 100644 --- a/crates/services/tx_status_manager/src/service.rs +++ b/crates/services/tx_status_manager/src/service.rs @@ -41,7 +41,7 @@ use fuel_core_types::{ Preconfirmation, Preconfirmations, }, - txpool::TransactionStatus, + transaction_status::TransactionStatus, }, tai64::Tai64, }; @@ -396,9 +396,13 @@ mod tests { PreconfirmationStatus, Preconfirmations, }, + transaction_status, }, }; - use std::time::Duration; + use std::{ + sync::Arc, + time::Duration, + }; use tokio_stream::wrappers::ReceiverStream; const TTL: Duration = Duration::from_secs(4); @@ -548,13 +552,15 @@ mod tests { .into_iter() .map(|tx_id| Preconfirmation { tx_id, - status: PreconfirmationStatus::Success { - tx_pointer: Default::default(), - total_gas: 0, - total_fee: 0, - receipts: vec![], - outputs: vec![], - }, + status: PreconfirmationStatus::Success(Arc::new( + transaction_status::statuses::PreConfirmationSuccess { + tx_pointer: Default::default(), + total_gas: 0, + total_fee: 0, + receipts: Some(vec![]), + outputs: Some(vec![]), + }, + )), }) .collect(); let (delegate_signing_key, delegate_verifying_key) = delegate_key_pair(); @@ -611,13 +617,15 @@ mod tests { .into_iter() .map(|tx_id| Preconfirmation { tx_id, - status: PreconfirmationStatus::Success { - tx_pointer: Default::default(), - total_gas: 0, - total_fee: 0, - receipts: vec![], - outputs: vec![], - }, + status: PreconfirmationStatus::Success(Arc::new( + transaction_status::statuses::PreConfirmationSuccess { + tx_pointer: Default::default(), + total_gas: 0, + total_fee: 0, + receipts: Some(vec![]), + outputs: Some(vec![]), + }, + )), }) .collect(); let (delegate_signing_key, _) = delegate_key_pair(); @@ -664,13 +672,15 @@ mod tests { .into_iter() .map(|tx_id| Preconfirmation { tx_id, - status: PreconfirmationStatus::Success { - tx_pointer: Default::default(), - total_gas: 0, - total_fee: 0, - receipts: vec![], - outputs: vec![], - }, + status: PreconfirmationStatus::Success(Arc::new( + transaction_status::statuses::PreConfirmationSuccess { + tx_pointer: Default::default(), + total_gas: 0, + total_fee: 0, + receipts: Some(vec![]), + outputs: Some(vec![]), + }, + )), }) .collect(); let (delegate_signing_key, delegate_verifying_key) = delegate_key_pair(); @@ -727,13 +737,15 @@ mod tests { .into_iter() .map(|tx_id| Preconfirmation { tx_id, - status: PreconfirmationStatus::Success { - tx_pointer: Default::default(), - total_gas: 0, - total_fee: 0, - receipts: vec![], - outputs: vec![], - }, + status: PreconfirmationStatus::Success(Arc::new( + transaction_status::statuses::PreConfirmationSuccess { + tx_pointer: Default::default(), + total_gas: 0, + total_fee: 0, + receipts: Some(vec![]), + outputs: Some(vec![]), + }, + )), }) .collect(); let (delegate_signing_key, delegate_verifying_key) = delegate_key_pair(); @@ -797,13 +809,15 @@ mod tests { .into_iter() .map(|tx_id| Preconfirmation { tx_id, - status: PreconfirmationStatus::Success { - tx_pointer: Default::default(), - total_gas: 0, - total_fee: 0, - receipts: vec![], - outputs: vec![], - }, + status: PreconfirmationStatus::Success(Arc::new( + transaction_status::statuses::PreConfirmationSuccess { + tx_pointer: Default::default(), + total_gas: 0, + total_fee: 0, + receipts: Some(vec![]), + outputs: Some(vec![]), + }, + )), }) .collect(); let valid_pre_confirmation_message = valid_pre_confirmation_signature( diff --git a/crates/services/tx_status_manager/src/tests/tests_e2e.rs b/crates/services/tx_status_manager/src/tests/tests_e2e.rs index 11499abddbe..e1d72339b54 100644 --- a/crates/services/tx_status_manager/src/tests/tests_e2e.rs +++ b/crates/services/tx_status_manager/src/tests/tests_e2e.rs @@ -6,7 +6,7 @@ use fuel_core_types::{ fuel_tx::Bytes32, - services::txpool::TransactionStatus, + services::transaction_status::TransactionStatus, }; use proptest::prelude::*; use test_strategy::{ diff --git a/crates/services/tx_status_manager/src/tests/tests_sending.rs b/crates/services/tx_status_manager/src/tests/tests_sending.rs index e8982184001..3a8d93d3345 100644 --- a/crates/services/tx_status_manager/src/tests/tests_sending.rs +++ b/crates/services/tx_status_manager/src/tests/tests_sending.rs @@ -6,7 +6,7 @@ use std::{ use fuel_core_types::{ fuel_tx::Bytes32, - services::txpool::TransactionStatus, + services::transaction_status::TransactionStatus, }; use parking_lot::lock_api::Mutex; use test_strategy::proptest; diff --git a/crates/services/tx_status_manager/src/tests/utils.rs b/crates/services/tx_status_manager/src/tests/utils.rs index f5580be54f4..08cb59ac161 100644 --- a/crates/services/tx_status_manager/src/tests/utils.rs +++ b/crates/services/tx_status_manager/src/tests/utils.rs @@ -5,7 +5,7 @@ use std::{ use fuel_core_types::{ fuel_tx::Bytes32, - services::txpool::TransactionStatus, + services::transaction_status::TransactionStatus, }; use proptest::{ prelude::*, diff --git a/crates/services/tx_status_manager/src/tx_status_stream.rs b/crates/services/tx_status_manager/src/tx_status_stream.rs index d9d6682edcb..d85243ac9ad 100644 --- a/crates/services/tx_status_manager/src/tx_status_stream.rs +++ b/crates/services/tx_status_manager/src/tx_status_stream.rs @@ -1,6 +1,6 @@ use fuel_core_types::{ fuel_tx::Bytes32, - services::txpool::TransactionStatus, + services::transaction_status::TransactionStatus, }; use std::pin::Pin; use tokio_stream::Stream; diff --git a/crates/services/tx_status_manager/src/utils.rs b/crates/services/tx_status_manager/src/utils.rs index e0939e24b7d..3e301be460c 100644 --- a/crates/services/tx_status_manager/src/utils.rs +++ b/crates/services/tx_status_manager/src/utils.rs @@ -2,7 +2,7 @@ use fuel_core_types::{ blockchain::block::Block, services::{ executor::TransactionExecutionResult, - txpool::TransactionExecutionStatus, + transaction_status::TransactionExecutionStatus, }, }; diff --git a/crates/services/txpool_v2/src/pool.rs b/crates/services/txpool_v2/src/pool.rs index ad5acd339f3..7fa0ddfde7d 100644 --- a/crates/services/txpool_v2/src/pool.rs +++ b/crates/services/txpool_v2/src/pool.rs @@ -17,10 +17,12 @@ use fuel_core_types::{ field::BlobId, TxId, }, - services::txpool::{ - ArcPoolTx, - PoolTransaction, - TransactionStatus, + services::{ + transaction_status::TransactionStatus, + txpool::{ + ArcPoolTx, + PoolTransaction, + }, }, tai64::Tai64, }; diff --git a/crates/services/txpool_v2/src/ports.rs b/crates/services/txpool_v2/src/ports.rs index dc992e920bf..dc3dff0e3d9 100644 --- a/crates/services/txpool_v2/src/ports.rs +++ b/crates/services/txpool_v2/src/ports.rs @@ -29,7 +29,7 @@ use fuel_core_types::{ NetworkData, PeerId, }, - txpool::TransactionStatus, + transaction_status::TransactionStatus, }, }; diff --git a/crates/services/txpool_v2/src/service.rs b/crates/services/txpool_v2/src/service.rs index 4cab0c2fdf6..7fd14ccbff8 100644 --- a/crates/services/txpool_v2/src/service.rs +++ b/crates/services/txpool_v2/src/service.rs @@ -81,10 +81,8 @@ use fuel_core_types::{ PeerId, TransactionGossipData, }, - txpool::{ - ArcPoolTx, - TransactionStatus, - }, + transaction_status::TransactionStatus, + txpool::ArcPoolTx, }, tai64::Tai64, }; diff --git a/crates/services/txpool_v2/src/tests/mocks.rs b/crates/services/txpool_v2/src/tests/mocks.rs index 304c9d2fcba..8ef79743892 100644 --- a/crates/services/txpool_v2/src/tests/mocks.rs +++ b/crates/services/txpool_v2/src/tests/mocks.rs @@ -58,7 +58,7 @@ use fuel_core_types::{ GossipsubMessageInfo, PeerId, }, - txpool::TransactionStatus, + transaction_status::TransactionStatus, }, }; use std::{ diff --git a/crates/services/txpool_v2/src/tests/tests_pending_pool.rs b/crates/services/txpool_v2/src/tests/tests_pending_pool.rs index 43d953bf791..f5ca849cac7 100644 --- a/crates/services/txpool_v2/src/tests/tests_pending_pool.rs +++ b/crates/services/txpool_v2/src/tests/tests_pending_pool.rs @@ -4,7 +4,7 @@ use fuel_core_types::{ UniqueIdentifier, UtxoId, }, - services::txpool::TransactionStatus, + services::transaction_status::TransactionStatus, }; use crate::tests::universe::TestPoolUniverse; diff --git a/crates/services/txpool_v2/src/tests/tests_service.rs b/crates/services/txpool_v2/src/tests/tests_service.rs index f2d8d5ae8db..459caa8256f 100644 --- a/crates/services/txpool_v2/src/tests/tests_service.rs +++ b/crates/services/txpool_v2/src/tests/tests_service.rs @@ -11,7 +11,7 @@ use fuel_core_types::{ fuel_types::ChainId, services::{ block_importer::ImportResult, - txpool::TransactionStatus, + transaction_status::TransactionStatus, }, }; use std::{ diff --git a/crates/services/txpool_v2/src/tests/universe.rs b/crates/services/txpool_v2/src/tests/universe.rs index 950c049cc6b..d5d5d4e3284 100644 --- a/crates/services/txpool_v2/src/tests/universe.rs +++ b/crates/services/txpool_v2/src/tests/universe.rs @@ -54,9 +54,9 @@ use fuel_core_types::{ interpreter::MemoryInstance, predicate::EmptyStorage, }, - services::txpool::{ - ArcPoolTx, - TransactionStatus, + services::{ + transaction_status::TransactionStatus, + txpool::ArcPoolTx, }, }; use parking_lot::RwLock; diff --git a/crates/types/src/services.rs b/crates/types/src/services.rs index 0a6e3f2e787..1fcfd7295fd 100644 --- a/crates/types/src/services.rs +++ b/crates/types/src/services.rs @@ -9,6 +9,7 @@ pub mod p2p; pub mod preconfirmation; pub mod relayer; pub mod shared_sequencer; +pub mod transaction_status; #[cfg(feature = "std")] pub mod txpool; diff --git a/crates/types/src/services/p2p.rs b/crates/types/src/services/p2p.rs index 11a35066c2f..e01c500613d 100644 --- a/crates/types/src/services/p2p.rs +++ b/crates/types/src/services/p2p.rs @@ -16,14 +16,18 @@ use super::{ use crate::services::preconfirmation::PreconfirmationStatus; use crate::{ fuel_tx::Transaction, - fuel_types::BlockHeight, services::txpool::statuses::PreConfirmationSqueezedOut, + fuel_types::BlockHeight, + services::transaction_status::statuses::PreConfirmationSqueezedOut, }; use std::{ - collections::HashSet, fmt::{ + collections::HashSet, + fmt::{ Debug, Display, Formatter, - }, str::FromStr, time::SystemTime + }, + str::FromStr, + time::SystemTime, }; pub use tai64::Tai64; @@ -132,9 +136,11 @@ impl PreConfirmationMessage { expiration: Tai64::UNIX_EPOCH, preconfirmations: vec![Preconfirmation { tx_id: TxId::default(), - status: PreconfirmationStatus::SqueezedOut(Arc::new(PreConfirmationSqueezedOut { - reason: "Dummy reason".to_string(), - })), + status: PreconfirmationStatus::SqueezedOut(Arc::new( + PreConfirmationSqueezedOut { + reason: "Dummy reason".to_string(), + }, + )), }], }, signature: crate::fuel_tx::Bytes64::default(), diff --git a/crates/types/src/services/preconfirmation.rs b/crates/types/src/services/preconfirmation.rs index 13182386413..2036abba789 100644 --- a/crates/types/src/services/preconfirmation.rs +++ b/crates/types/src/services/preconfirmation.rs @@ -8,11 +8,11 @@ use tai64::Tai64; #[cfg(not(feature = "std"))] use alloc::{ - string::String, + sync::Arc, vec::Vec, }; -use super::txpool; +use super::transaction_status; /// A collection of pre-confirmations that have been signed by a delegate #[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] @@ -34,28 +34,14 @@ pub struct Preconfirmation { pub status: PreconfirmationStatus, } -#[cfg(feature = "std")] /// Status of a transaction that has been pre-confirmed by block producer #[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum PreconfirmationStatus { /// Transaction was squeezed out by the tx pool - SqueezedOut(Arc), + SqueezedOut(Arc), /// Transaction has been confirmed and will be included in block_height - Success(Arc), + Success(Arc), /// Transaction will not be included in a block, rejected at `block_height` - Failure(Arc), + Failure(Arc), } - -#[cfg(not(feature = "std"))] -/// Status of a transaction that has been pre-confirmed by block producer -#[derive(Debug, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub enum PreconfirmationStatus { - /// Transaction was squeezed out by the tx pool - SqueezedOut(txpool::statuses::PreConfirmationSqueezedOut), - /// Transaction has been confirmed and will be included in block_height - Success(txpool::statuses::PreConfirmationSuccess), - /// Transaction will not be included in a block, rejected at `block_height` - Failure(txpool::statuses::PreConfirmationFailure), -} \ No newline at end of file diff --git a/crates/types/src/services/transaction_status.rs b/crates/types/src/services/transaction_status.rs new file mode 100644 index 00000000000..acf7a9dcd23 --- /dev/null +++ b/crates/types/src/services/transaction_status.rs @@ -0,0 +1,384 @@ +//! The status of the transaction during its lifecycle. + +use crate::{ + fuel_tx::{ + Output, + Receipt, + TxPointer, + }, + fuel_vm::ProgramState, + services::preconfirmation::PreconfirmationStatus, +}; +use fuel_vm_private::fuel_types::BlockHeight; +use tai64::Tai64; + +#[cfg(feature = "std")] +use crate::services::executor::TransactionExecutionResult; + +#[cfg(feature = "std")] +use std::sync::Arc; + +#[cfg(not(feature = "std"))] +use alloc::sync::Arc; + +#[cfg(feature = "alloc")] +use alloc::{ + string::{ + String, + ToString, + }, + vec::Vec, +}; + +/// The status of the transaction during its life from the tx pool until the block. +// TODO: This type needs to be updated: https://github.com/FuelLabs/fuel-core/issues/2794 +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum TransactionExecutionStatus { + /// Transaction was submitted into the txpool + Submitted { + /// Timestamp of submission into the txpool + time: Tai64, + }, + /// Transaction was successfully included in a block + Success { + /// Included in this block + block_height: BlockHeight, + /// Time when the block was generated + time: Tai64, + /// Result of executing the transaction for scripts + result: Option, + /// The receipts generated during execution of the transaction. + receipts: Vec, + /// The total gas used by the transaction. + total_gas: u64, + /// The total fee paid by the transaction. + total_fee: u64, + }, + /// Transaction was squeezed of the txpool + SqueezedOut { + /// Why this happened + reason: String, + }, + /// Transaction was included in a block, but the execution was reverted + Failed { + /// Included in this block + block_height: BlockHeight, + /// Time when the block was generated + time: Tai64, + /// Result of executing the transaction for scripts + result: Option, + /// The receipts generated during execution of the transaction. + receipts: Vec, + /// The total gas used by the transaction. + total_gas: u64, + /// The total fee paid by the transaction. + total_fee: u64, + }, +} + +impl From for TransactionStatus { + fn from(value: TransactionExecutionStatus) -> Self { + match value { + TransactionExecutionStatus::Submitted { time } => { + TransactionStatus::Submitted( + statuses::Submitted { timestamp: time }.into(), + ) + } + TransactionExecutionStatus::Success { + block_height, + time, + result, + receipts, + total_gas, + total_fee, + } => TransactionStatus::Success( + statuses::Success { + block_height, + block_timestamp: time, + program_state: result, + receipts, + total_gas, + total_fee, + } + .into(), + ), + // TODO: Removed this variant as part of the + // https://github.com/FuelLabs/fuel-core/issues/2794 + TransactionExecutionStatus::SqueezedOut { reason } => { + TransactionStatus::SqueezedOut(statuses::SqueezedOut { reason }.into()) + } + TransactionExecutionStatus::Failed { + block_height, + time, + result, + receipts, + total_gas, + total_fee, + } => { + #[cfg(feature = "std")] + let reason = TransactionExecutionResult::reason(&receipts, &result); + #[cfg(not(feature = "std"))] + let reason = "Unknown reason".to_string(); + TransactionStatus::Failure( + statuses::Failure { + reason, + block_height, + block_timestamp: time, + program_state: result, + receipts, + total_gas, + total_fee, + } + .into(), + ) + } + } + } +} + +/// The status of the transaction during its life from the TxPool until the inclusion in the block. +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum TransactionStatus { + /// Transaction was submitted into the TxPool + Submitted(Arc), + /// Transaction was successfully included in a block + Success(Arc), + /// Transaction was successfully executed by block producer + PreConfirmationSuccess(Arc), + /// Transaction was squeezed out of the TxPool + SqueezedOut(Arc), + /// Transaction was squeezed out + PreConfirmationSqueezedOut(Arc), + /// Transaction was included in a block, but the execution has failed + Failure(Arc), + /// Transaction was included in a block by block producer, but the execution has failed + PreConfirmationFailure(Arc), +} + +impl TransactionStatus { + /// Returns `true` if the status is considered "final". + pub fn is_final(&self) -> bool { + match self { + TransactionStatus::Success(_) + | TransactionStatus::Failure(_) + | TransactionStatus::SqueezedOut(_) + | TransactionStatus::PreConfirmationSqueezedOut(_) => true, + TransactionStatus::Submitted(_) + | TransactionStatus::PreConfirmationSuccess(_) + | TransactionStatus::PreConfirmationFailure(_) => false, + } + } + + /// Returns `true` if the status is `Submitted`. + pub fn is_submitted(&self) -> bool { + matches!(self, Self::Submitted { .. }) + } + + /// Creates a new `TransactionStatus::Submitted` variant. + pub fn submitted(timestamp: Tai64) -> Self { + Self::Submitted(statuses::Submitted { timestamp }.into()) + } + + /// Creates a new `TransactionStatus::SqueezedOut` variant. + pub fn squeezed_out(reason: String) -> Self { + Self::SqueezedOut(statuses::SqueezedOut { reason }.into()) + } + + /// Creates a new `TransactionStatus::PreConfirmationSqueezedOut` variant. + pub fn preconfirmation_squeezed_out(reason: String) -> Self { + Self::PreConfirmationSqueezedOut( + statuses::PreConfirmationSqueezedOut { reason }.into(), + ) + } +} + +impl From for TransactionStatus { + fn from(value: PreconfirmationStatus) -> Self { + match value { + PreconfirmationStatus::SqueezedOut(s) => { + TransactionStatus::PreConfirmationSqueezedOut(s) + } + PreconfirmationStatus::Success(s) => { + TransactionStatus::PreConfirmationSuccess(s) + } + PreconfirmationStatus::Failure(s) => { + TransactionStatus::PreConfirmationFailure(s) + } + } + } +} + +/// The status of the transaction during its lifecycle. +pub mod statuses { + use super::*; + + /// Transaction was submitted into the TxPool + #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] + #[derive(Clone, Debug, PartialEq, Eq)] + pub struct Submitted { + /// Timestamp of submission into the TxPool + pub timestamp: Tai64, + } + + impl Default for Submitted { + fn default() -> Self { + Self { + timestamp: Tai64::UNIX_EPOCH, + } + } + } + + /// Transaction was successfully included in a block + #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] + #[derive(Clone, Debug, PartialEq, Eq)] + pub struct Success { + /// Included in this block + pub block_height: BlockHeight, + /// Timestamp of the block + pub block_timestamp: Tai64, + /// Result of executing the transaction for scripts + pub program_state: Option, + /// The receipts generated during execution of the transaction + pub receipts: Vec, + /// The total gas used by the transaction + pub total_gas: u64, + /// The total fee paid by the transaction + pub total_fee: u64, + } + + impl Default for Success { + fn default() -> Self { + Self { + block_height: Default::default(), + block_timestamp: Tai64::UNIX_EPOCH, + program_state: None, + receipts: Vec::new(), + total_gas: 0, + total_fee: 0, + } + } + } + + /// Transaction was successfully executed by block producer + #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] + #[derive(Default, Clone, Debug, PartialEq, Eq)] + pub struct PreConfirmationSuccess { + /// Transaction pointer within the block. + pub tx_pointer: TxPointer, + /// The total gas used by the transaction. + pub total_gas: u64, + /// The total fee paid by the transaction. + pub total_fee: u64, + /// Receipts produced by the transaction during execution. + pub receipts: Option>, + /// Dynamic outputs produced by the transaction during execution. + pub outputs: Option>, + } + + /// Transaction was squeezed out of the TxPool + #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] + #[derive(Clone, Debug, PartialEq, Eq)] + pub struct SqueezedOut { + /// The reason why the transaction was squeezed out + pub reason: String, + } + + impl Default for SqueezedOut { + fn default() -> Self { + Self { + reason: "Default reason".to_string(), + } + } + } + + impl From<&PreConfirmationSqueezedOut> for SqueezedOut { + fn from(value: &PreConfirmationSqueezedOut) -> Self { + Self { + reason: value.reason.clone(), + } + } + } + + /// Transaction was squeezed out from block producer + #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] + #[derive(Clone, Debug, PartialEq, Eq)] + pub struct PreConfirmationSqueezedOut { + /// The reason why the transaction was squeezed out + pub reason: String, + } + + impl Default for PreConfirmationSqueezedOut { + fn default() -> Self { + Self { + reason: "Default reason".to_string(), + } + } + } + + /// Transaction was included in a block, but the execution has failed + #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] + #[derive(Clone, Debug, PartialEq, Eq)] + pub struct Failure { + /// Included in this block + pub block_height: BlockHeight, + /// Timestamp of the block + pub block_timestamp: Tai64, + /// The reason why the transaction has failed + pub reason: String, + /// Result of executing the transaction for scripts + pub program_state: Option, + /// The receipts generated during execution of the transaction + pub receipts: Vec, + /// The total gas used by the transaction + pub total_gas: u64, + /// The total fee paid by the transaction + pub total_fee: u64, + } + + impl Default for Failure { + fn default() -> Self { + Self { + block_height: Default::default(), + block_timestamp: Tai64::UNIX_EPOCH, + reason: "Dummy reason".to_string(), + program_state: None, + receipts: Vec::new(), + total_gas: 0, + total_fee: 0, + } + } + } + + /// Transaction was included in a block by block producer, but the execution has failed + #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] + #[derive(Clone, Debug, PartialEq, Eq)] + pub struct PreConfirmationFailure { + /// Transaction pointer within the block. + pub tx_pointer: TxPointer, + /// The total gas used by the transaction. + pub total_gas: u64, + /// The total fee paid by the transaction. + pub total_fee: u64, + /// Receipts produced by the transaction during execution. + pub receipts: Option>, + /// Dynamic outputs produced by the transaction during execution. + pub outputs: Option>, + /// The reason why the transaction has failed + pub reason: String, + } + + impl Default for PreConfirmationFailure { + fn default() -> Self { + Self { + tx_pointer: Default::default(), + total_gas: 0, + total_fee: 0, + receipts: None, + outputs: None, + reason: "Dummy reason".to_string(), + } + } + } +} diff --git a/crates/types/src/services/txpool.rs b/crates/types/src/services/txpool.rs index 7ed2a7974ab..6217570bf76 100644 --- a/crates/types/src/services/txpool.rs +++ b/crates/types/src/services/txpool.rs @@ -16,19 +16,13 @@ use crate::{ Create, Input, Output, - Receipt, Script, Transaction, TxId, - TxPointer, Upgrade, Upload, }, - fuel_vm::{ - checked_transaction::Checked, - ProgramState, - }, - services::preconfirmation::PreconfirmationStatus, + fuel_vm::checked_transaction::Checked, }; use fuel_vm_private::{ checked_transaction::CheckedTransaction, @@ -36,9 +30,6 @@ use fuel_vm_private::{ prelude::field::Expiration, }; use std::sync::Arc; -use tai64::Tai64; - -use super::executor::TransactionExecutionResult; /// Pool transaction wrapped in an Arc for thread-safe sharing pub type ArcPoolTx = Arc; @@ -329,346 +320,3 @@ impl From<&PoolTransaction> for CheckedTransaction { } } } - -/// The status of the transaction during its life from the tx pool until the block. -// TODO: This type needs to be updated: https://github.com/FuelLabs/fuel-core/issues/2794 -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum TransactionExecutionStatus { - /// Transaction was submitted into the txpool - Submitted { - /// Timestamp of submission into the txpool - time: Tai64, - }, - /// Transaction was successfully included in a block - Success { - /// Included in this block - block_height: BlockHeight, - /// Time when the block was generated - time: Tai64, - /// Result of executing the transaction for scripts - result: Option, - /// The receipts generated during execution of the transaction. - receipts: Vec, - /// The total gas used by the transaction. - total_gas: u64, - /// The total fee paid by the transaction. - total_fee: u64, - }, - /// Transaction was squeezed of the txpool - SqueezedOut { - /// Why this happened - reason: String, - }, - /// Transaction was included in a block, but the execution was reverted - Failed { - /// Included in this block - block_height: BlockHeight, - /// Time when the block was generated - time: Tai64, - /// Result of executing the transaction for scripts - result: Option, - /// The receipts generated during execution of the transaction. - receipts: Vec, - /// The total gas used by the transaction. - total_gas: u64, - /// The total fee paid by the transaction. - total_fee: u64, - }, -} - -impl From for TransactionStatus { - fn from(value: TransactionExecutionStatus) -> Self { - match value { - TransactionExecutionStatus::Submitted { time } => { - TransactionStatus::Submitted( - statuses::Submitted { timestamp: time }.into(), - ) - } - TransactionExecutionStatus::Success { - block_height, - time, - result, - receipts, - total_gas, - total_fee, - } => TransactionStatus::Success( - statuses::Success { - block_height, - block_timestamp: time, - program_state: result, - receipts, - total_gas, - total_fee, - } - .into(), - ), - // TODO: Removed this variant as part of the - // https://github.com/FuelLabs/fuel-core/issues/2794 - TransactionExecutionStatus::SqueezedOut { reason } => { - TransactionStatus::SqueezedOut(statuses::SqueezedOut { reason }.into()) - } - TransactionExecutionStatus::Failed { - block_height, - time, - result, - receipts, - total_gas, - total_fee, - } => TransactionStatus::Failure( - statuses::Failure { - reason: TransactionExecutionResult::reason(&receipts, &result), - block_height, - block_timestamp: time, - program_state: result, - receipts, - total_gas, - total_fee, - } - .into(), - ), - } - } -} - -/// The status of the transaction during its life from the TxPool until the inclusion in the block. -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum TransactionStatus { - /// Transaction was submitted into the TxPool - Submitted(Arc), - /// Transaction was successfully included in a block - Success(Arc), - /// Transaction was successfully executed by block producer - PreConfirmationSuccess(Arc), - /// Transaction was squeezed out of the TxPool - SqueezedOut(Arc), - /// Transaction was squeezed out - PreConfirmationSqueezedOut(Arc), - /// Transaction was included in a block, but the execution has failed - Failure(Arc), - /// Transaction was included in a block by block producer, but the execution has failed - PreConfirmationFailure(Arc), -} - -impl TransactionStatus { - /// Returns `true` if the status is considered "final". - pub fn is_final(&self) -> bool { - match self { - TransactionStatus::Success(_) - | TransactionStatus::Failure(_) - | TransactionStatus::SqueezedOut(_) - | TransactionStatus::PreConfirmationSqueezedOut(_) => true, - TransactionStatus::Submitted(_) - | TransactionStatus::PreConfirmationSuccess(_) - | TransactionStatus::PreConfirmationFailure(_) => false, - } - } - - /// Returns `true` if the status is `Submitted`. - pub fn is_submitted(&self) -> bool { - matches!(self, Self::Submitted { .. }) - } - - /// Creates a new `TransactionStatus::Submitted` variant. - pub fn submitted(timestamp: Tai64) -> Self { - Self::Submitted(statuses::Submitted { timestamp }.into()) - } - - /// Creates a new `TransactionStatus::SqueezedOut` variant. - pub fn squeezed_out(reason: String) -> Self { - Self::SqueezedOut(statuses::SqueezedOut { reason }.into()) - } - - /// Creates a new `TransactionStatus::PreConfirmationSqueezedOut` variant. - pub fn preconfirmation_squeezed_out(reason: String) -> Self { - Self::PreConfirmationSqueezedOut( - statuses::PreConfirmationSqueezedOut { reason }.into(), - ) - } -} - -impl From for TransactionStatus { - fn from(value: PreconfirmationStatus) -> Self { - match value { - PreconfirmationStatus::SqueezedOut(s) => { - TransactionStatus::PreConfirmationSqueezedOut(s) - } - PreconfirmationStatus::Success(s) => TransactionStatus::PreConfirmationSuccess(s), - PreconfirmationStatus::Failure(s) => TransactionStatus::PreConfirmationFailure(s), - } - } -} - -/// The status of the transaction during its lifecycle. -pub mod statuses { - use super::*; - - /// Transaction was submitted into the TxPool - #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] - #[derive(Clone, Debug, PartialEq, Eq)] - pub struct Submitted { - /// Timestamp of submission into the TxPool - pub timestamp: Tai64, - } - - impl Default for Submitted { - fn default() -> Self { - Self { - timestamp: Tai64::UNIX_EPOCH, - } - } - } - - /// Transaction was successfully included in a block - #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] - #[derive(Clone, Debug, PartialEq, Eq)] - pub struct Success { - /// Included in this block - pub block_height: BlockHeight, - /// Timestamp of the block - pub block_timestamp: Tai64, - /// Result of executing the transaction for scripts - pub program_state: Option, - /// The receipts generated during execution of the transaction - pub receipts: Vec, - /// The total gas used by the transaction - pub total_gas: u64, - /// The total fee paid by the transaction - pub total_fee: u64, - } - - impl Default for Success { - fn default() -> Self { - Self { - block_height: Default::default(), - block_timestamp: Tai64::UNIX_EPOCH, - program_state: None, - receipts: vec![], - total_gas: 0, - total_fee: 0, - } - } - } - - /// Transaction was successfully executed by block producer - #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] - #[derive(Default, Clone, Debug, PartialEq, Eq)] - pub struct PreConfirmationSuccess { - /// Transaction pointer within the block. - pub tx_pointer: TxPointer, - /// The total gas used by the transaction. - pub total_gas: u64, - /// The total fee paid by the transaction. - pub total_fee: u64, - /// Receipts produced by the transaction during execution. - pub receipts: Option>, - /// Dynamic outputs produced by the transaction during execution. - pub outputs: Option>, - } - - /// Transaction was squeezed out of the TxPool - #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] - #[derive(Clone, Debug, PartialEq, Eq)] - pub struct SqueezedOut { - /// The reason why the transaction was squeezed out - pub reason: String, - } - - impl Default for SqueezedOut { - fn default() -> Self { - Self { - reason: "Default reason".to_string(), - } - } - } - - impl From<&PreConfirmationSqueezedOut> for SqueezedOut { - fn from(value: &PreConfirmationSqueezedOut) -> Self { - Self { - reason: value.reason.clone(), - } - } - } - - /// Transaction was squeezed out from block producer - #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] - #[derive(Clone, Debug, PartialEq, Eq)] - pub struct PreConfirmationSqueezedOut { - /// The reason why the transaction was squeezed out - pub reason: String, - } - - impl Default for PreConfirmationSqueezedOut { - fn default() -> Self { - Self { - reason: "Default reason".to_string(), - } - } - } - - /// Transaction was included in a block, but the execution has failed - #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] - #[derive(Clone, Debug, PartialEq, Eq)] - pub struct Failure { - /// Included in this block - pub block_height: BlockHeight, - /// Timestamp of the block - pub block_timestamp: Tai64, - /// The reason why the transaction has failed - pub reason: String, - /// Result of executing the transaction for scripts - pub program_state: Option, - /// The receipts generated during execution of the transaction - pub receipts: Vec, - /// The total gas used by the transaction - pub total_gas: u64, - /// The total fee paid by the transaction - pub total_fee: u64, - } - - impl Default for Failure { - fn default() -> Self { - Self { - block_height: Default::default(), - block_timestamp: Tai64::UNIX_EPOCH, - reason: "Dummy reason".to_string(), - program_state: None, - receipts: vec![], - total_gas: 0, - total_fee: 0, - } - } - } - - /// Transaction was included in a block by block producer, but the execution has failed - #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] - #[derive(Clone, Debug, PartialEq, Eq)] - pub struct PreConfirmationFailure { - /// Transaction pointer within the block. - pub tx_pointer: TxPointer, - /// The total gas used by the transaction. - pub total_gas: u64, - /// The total fee paid by the transaction. - pub total_fee: u64, - /// Receipts produced by the transaction during execution. - pub receipts: Option>, - /// Dynamic outputs produced by the transaction during execution. - pub outputs: Option>, - /// The reason why the transaction has failed - pub reason: String, - } - - impl Default for PreConfirmationFailure { - fn default() -> Self { - Self { - tx_pointer: Default::default(), - total_gas: 0, - total_fee: 0, - receipts: None, - outputs: None, - reason: "Dummy reason".to_string(), - } - } - } -} From d7d366c4b71b3c19e21ebff587dc75886ab90e06 Mon Sep 17 00:00:00 2001 From: AurelienFT Date: Wed, 19 Mar 2025 14:08:43 +0100 Subject: [PATCH 62/76] Improve preconfirmation notification to the consensus --- .../src/service/adapters/executor.rs | 16 +++---- .../src/service/adapters/graphql_api.rs | 3 +- .../fuel-core/src/service/adapters/txpool.rs | 2 +- .../services/tx_status_manager/src/service.rs | 47 +++++++++++++++++-- 4 files changed, 53 insertions(+), 15 deletions(-) diff --git a/crates/fuel-core/src/service/adapters/executor.rs b/crates/fuel-core/src/service/adapters/executor.rs index 5d2d780229c..60a769670d1 100644 --- a/crates/fuel-core/src/service/adapters/executor.rs +++ b/crates/fuel-core/src/service/adapters/executor.rs @@ -106,17 +106,17 @@ impl PreconfirmationSenderPort for PreconfirmationSender { async fn send(&self, preconfirmations: Vec) { // If the receiver is closed, it means no one is listening to the preconfirmations and so we can drop them. // We don't consider this an error. - let _ = self.sender_signature_service.send(preconfirmations).await; + let _ = self + .sender_signature_service + .send(preconfirmations.clone()) + .await; + self.tx_status_manager_adapter + .tx_status_manager_shared_data + .update_preconfirmations(preconfirmations) + .await; } fn try_send(&self, preconfirmations: Vec) -> Vec { - for preconfirmation in &preconfirmations { - let tx_id = preconfirmation.tx_id; - let status = preconfirmation.status.clone(); - self.tx_status_manager_adapter - .tx_status_manager_shared_data - .update_status(tx_id, status.into()); - } match self.sender_signature_service.try_send(preconfirmations) { Ok(()) => vec![], // If the receiver is closed, it means no one is listening to the preconfirmations and so we can drop them. diff --git a/crates/fuel-core/src/service/adapters/graphql_api.rs b/crates/fuel-core/src/service/adapters/graphql_api.rs index e5166501f38..07f7d46f3ce 100644 --- a/crates/fuel-core/src/service/adapters/graphql_api.rs +++ b/crates/fuel-core/src/service/adapters/graphql_api.rs @@ -186,7 +186,8 @@ impl worker::TxStatusCompletion for TxStatusManagerAdapter { status: TransactionStatus, ) { tracing::info!("Transaction {id} successfully included in block {block_height}"); - self.tx_status_manager_shared_data.update_status(id, status); + self.tx_status_manager_shared_data + .blocking_update_status(id, status); } } diff --git a/crates/fuel-core/src/service/adapters/txpool.rs b/crates/fuel-core/src/service/adapters/txpool.rs index 8fe17be857f..107370ff8ac 100644 --- a/crates/fuel-core/src/service/adapters/txpool.rs +++ b/crates/fuel-core/src/service/adapters/txpool.rs @@ -234,6 +234,6 @@ impl ChainStateInfoProviderTrait for ChainStateInfoProvider { impl TxStatusManager for TxStatusManagerAdapter { fn status_update(&self, tx_id: TxId, tx_status: TransactionStatus) { self.tx_status_manager_shared_data - .update_status(tx_id, tx_status); + .blocking_update_status(tx_id, tx_status); } } diff --git a/crates/services/tx_status_manager/src/service.rs b/crates/services/tx_status_manager/src/service.rs index 242e0cd04e0..ab0a10a206a 100644 --- a/crates/services/tx_status_manager/src/service.rs +++ b/crates/services/tx_status_manager/src/service.rs @@ -68,6 +68,9 @@ enum WriteRequest { tx_id: TxId, status: TransactionStatus, }, + UpdatePreconfirmations { + preconfirmations: Vec, + }, NotifySkipped { tx_ids_and_reason: Vec<(Bytes32, String)>, }, @@ -76,7 +79,7 @@ enum WriteRequest { #[derive(Clone)] pub struct SharedData { read_requests_sender: mpsc::Sender, - write_requests_sender: mpsc::UnboundedSender, + write_requests_sender: mpsc::Sender, } impl SharedData { @@ -97,8 +100,35 @@ impl SharedData { receiver.await? } - pub fn update_status(&self, tx_id: TxId, status: TransactionStatus) { + pub fn blocking_update_status(&self, tx_id: TxId, status: TransactionStatus) { let request = WriteRequest::UpdateStatus { tx_id, status }; + let _ = self.write_requests_sender.blocking_send(request); + } + + pub fn try_update_preconfirmations( + &self, + preconfirmations: Vec, + ) -> Result<(), Vec> { + let request = WriteRequest::UpdatePreconfirmations { preconfirmations }; + match self.write_requests_sender.try_send(request) { + Ok(_) => Ok(()), + Err(e) => { + if let WriteRequest::UpdatePreconfirmations { preconfirmations } = + e.into_inner() + { + Err(preconfirmations) + } else { + // SAFETY: Building the WriteRequest::UpdatePreconfirmation variant just above + panic!( + "Wrong WriteRequest variant it should be UpdatePreconfirmations" + ); + } + } + } + } + + pub async fn update_preconfirmations(&self, preconfirmations: Vec) { + let request = WriteRequest::UpdatePreconfirmations { preconfirmations }; let _ = self.write_requests_sender.send(request); } @@ -112,7 +142,7 @@ pub struct Task { manager: TxStatusManager, subscriptions: Subscriptions, read_requests_receiver: mpsc::Receiver, - write_requests_receiver: mpsc::UnboundedReceiver, + write_requests_receiver: mpsc::Receiver, shared_data: SharedData, signature_verification: SignatureVerification, } @@ -292,6 +322,12 @@ impl RunnableTask for Task { self.manager.status_update(tx_id, status); TaskNextAction::Continue } + Some(WriteRequest::UpdatePreconfirmations { preconfirmations }) => { + for preconfirmation in preconfirmations { + self.manager.status_update(preconfirmation.tx_id, preconfirmation.status.into()); + } + TaskNextAction::Continue + } Some(WriteRequest::NotifySkipped { tx_ids_and_reason }) => { self.manager.notify_skipped_txs(tx_ids_and_reason); TaskNextAction::Continue @@ -344,7 +380,8 @@ where let (read_requests_sender, read_requests_receiver) = mpsc::channel(config.max_tx_update_subscriptions); - let (write_requests_sender, write_requests_receiver) = mpsc::unbounded_channel(); + // TODO: Configurable buffer size + let (write_requests_sender, write_requests_receiver) = mpsc::channel(100_000); let shared_data = SharedData { read_requests_sender, @@ -415,7 +452,7 @@ mod tests { fn new_task_with_handles() -> (Task, Handles) { let (read_requests_sender, read_requests_receiver) = mpsc::channel(1); - let (write_requests_sender, write_requests_receiver) = mpsc::unbounded_channel(); + let (write_requests_sender, write_requests_receiver) = mpsc::channel(100_000); let shared_data = SharedData { read_requests_sender, write_requests_sender, From 7d37bd0473a0180e865e05d48fb95ff5f75cfb2a Mon Sep 17 00:00:00 2001 From: AurelienFT Date: Wed, 19 Mar 2025 15:38:54 +0100 Subject: [PATCH 63/76] Fix send usage --- .../service/adapters/consensus_module/poa.rs | 3 ++- .../consensus_module/poa/src/ports.rs | 5 +++- .../consensus_module/poa/src/service.rs | 4 +++- .../consensus_module/poa/src/service_test.rs | 2 +- .../services/tx_status_manager/src/service.rs | 23 +++++++++++++++---- 5 files changed, 29 insertions(+), 8 deletions(-) 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 ab7d65ade36..9d6be342adc 100644 --- a/crates/fuel-core/src/service/adapters/consensus_module/poa.rs +++ b/crates/fuel-core/src/service/adapters/consensus_module/poa.rs @@ -95,9 +95,10 @@ impl TransactionPool for TxPoolAdapter { } impl TxStatusManager for TxStatusManagerAdapter { - fn notify_skipped_txs(&self, tx_ids_and_reasons: Vec<(Bytes32, String)>) { + async fn notify_skipped_txs(&self, tx_ids_and_reasons: Vec<(Bytes32, String)>) { self.tx_status_manager_shared_data .notify_skipped(tx_ids_and_reasons) + .await } } diff --git a/crates/services/consensus_module/poa/src/ports.rs b/crates/services/consensus_module/poa/src/ports.rs index a53eec10139..2f65a2abb0f 100644 --- a/crates/services/consensus_module/poa/src/ports.rs +++ b/crates/services/consensus_module/poa/src/ports.rs @@ -36,7 +36,10 @@ pub trait TransactionPool: Send + Sync { #[cfg_attr(test, mockall::automock)] pub trait TxStatusManager: Send + Sync { - fn notify_skipped_txs(&self, tx_ids_and_reasons: Vec<(Bytes32, String)>); + fn notify_skipped_txs( + &self, + tx_ids_and_reasons: Vec<(Bytes32, String)>, + ) -> impl std::future::Future + Send; } /// The source of transactions for the block. diff --git a/crates/services/consensus_module/poa/src/service.rs b/crates/services/consensus_module/poa/src/service.rs index 375ef6c1865..cba2d503044 100644 --- a/crates/services/consensus_module/poa/src/service.rs +++ b/crates/services/consensus_module/poa/src/service.rs @@ -394,7 +394,9 @@ where tx_ids_to_remove.push((tx_id, err.to_string())); } self.txpool.notify_skipped_txs(tx_ids_to_remove.clone()); - self.tx_status_manager.notify_skipped_txs(tx_ids_to_remove); + self.tx_status_manager + .notify_skipped_txs(tx_ids_to_remove) + .await; } // Sign the block and seal it diff --git a/crates/services/consensus_module/poa/src/service_test.rs b/crates/services/consensus_module/poa/src/service_test.rs index cc0a9f04587..ac5ab4746fc 100644 --- a/crates/services/consensus_module/poa/src/service_test.rs +++ b/crates/services/consensus_module/poa/src/service_test.rs @@ -304,7 +304,7 @@ impl MockTxStatusManager { let mut tx_status_manager = MockTxStatusManager::default(); tx_status_manager .expect_notify_skipped_txs() - .returning(|_| {}); + .returning(|_| Box::pin(async move {})); tx_status_manager } } diff --git a/crates/services/tx_status_manager/src/service.rs b/crates/services/tx_status_manager/src/service.rs index ed3271469f0..244a9747467 100644 --- a/crates/services/tx_status_manager/src/service.rs +++ b/crates/services/tx_status_manager/src/service.rs @@ -129,12 +129,16 @@ impl SharedData { pub async fn update_preconfirmations(&self, preconfirmations: Vec) { let request = WriteRequest::UpdatePreconfirmations { preconfirmations }; - let _ = self.write_requests_sender.send(request); + if let Err(e) = self.write_requests_sender.send(request).await { + tracing::error!("Failed to send preconfirmations: {:?}", e); + } } - pub fn notify_skipped(&self, tx_ids_and_reason: Vec<(Bytes32, String)>) { + pub async fn notify_skipped(&self, tx_ids_and_reason: Vec<(Bytes32, String)>) { let request = WriteRequest::NotifySkipped { tx_ids_and_reason }; - let _ = self.write_requests_sender.send(request); + if let Err(e) = self.write_requests_sender.send(request).await { + tracing::error!("Failed to send skipped txs: {:?}", e); + } } } @@ -533,7 +537,18 @@ mod tests { use std::sync::Arc; use fuel_core_types::{ - fuel_crypto::rand::{rngs::StdRng, seq::SliceRandom}, services::transaction_status::{statuses::{PreConfirmationFailure, PreConfirmationSuccess}, TransactionStatus}, tai64::Tai64 + fuel_crypto::rand::{ + rngs::StdRng, + seq::SliceRandom, + }, + services::transaction_status::{ + statuses::{ + PreConfirmationFailure, + PreConfirmationSuccess, + }, + TransactionStatus, + }, + tai64::Tai64, }; use crate::manager::TxStatusManager; From 30ee449ceedb4e54b512fe1f338ebc96e1910ac9 Mon Sep 17 00:00:00 2001 From: green Date: Thu, 20 Mar 2025 03:49:37 -0400 Subject: [PATCH 64/76] Revert back the change with the fix for `clone` of `Preconfirmation`. We can try to think about it in a separate PR. Added a new integration test with squeezed out that we need make to work. This test instead of the test that required the hack with `!self.debug`. Fixed the error with not broadcasted delegation messages because of duplicate message id. Reworked TxPool to use `TxStatusManager` more, it simplified many functions. Now we don't need to return the list of removed transactions. Returned back the unbounded channel for cross service communication. Found the reason of the issues with wrongly deserialzied p2p responses. But I think it will break tests and we need to cover one more case. Maybe we need to do it in another PR, if it breaks a lto of stuff. Corrected gossip sub tests, now they setup correctly, but they fail=D So we need to investigate --- Cargo.lock | 1 + crates/fuel-core/src/p2p_test_helpers.rs | 1 + crates/fuel-core/src/service/adapters.rs | 18 +- .../service/adapters/consensus_module/poa.rs | 5 +- .../pre_confirmation_signature/broadcast.rs | 12 +- .../pre_confirmation_signature/tx_receiver.rs | 34 ++-- .../src/service/adapters/executor.rs | 34 ++-- .../src/service/adapters/graphql_api.rs | 3 +- .../fuel-core/src/service/adapters/txpool.rs | 6 +- crates/fuel-core/src/service/config.rs | 2 +- crates/fuel-core/src/service/sub_services.rs | 13 +- .../consensus_module/poa/src/ports.rs | 7 +- .../src/pre_confirmation_signature_service.rs | 2 + .../tests.rs | 3 + .../trigger.rs | 1 - .../consensus_module/poa/src/service.rs | 8 +- .../consensus_module/poa/src/service_test.rs | 3 +- crates/services/executor/src/executor.rs | 61 ++---- crates/services/p2p/src/codecs/postcard.rs | 39 +++- crates/services/sync/src/import/tests.rs | 6 - .../services/tx_status_manager/src/service.rs | 88 ++++----- crates/services/txpool_v2/src/pool.rs | 41 ++-- crates/services/txpool_v2/src/pool_worker.rs | 56 +----- crates/services/txpool_v2/src/ports.rs | 2 + crates/services/txpool_v2/src/service.rs | 14 +- crates/services/txpool_v2/src/shared_state.rs | 10 +- crates/services/txpool_v2/src/tests/mocks.rs | 6 + .../txpool_v2/src/tests/tests_pool.rs | 93 ++++----- .../services/txpool_v2/src/tests/universe.rs | 37 ++-- crates/types/Cargo.toml | 3 +- crates/types/src/services/executor.rs | 3 +- crates/types/src/services/p2p.rs | 89 +++++++-- crates/types/src/services/preconfirmation.rs | 44 ++++- .../types/src/services/transaction_status.rs | 54 ++++-- tests/tests/preconfirmations.rs | 55 ------ tests/tests/preconfirmations_gossip.rs | 177 ++++++++++++------ tests/tests/regenesis.rs | 2 +- 37 files changed, 562 insertions(+), 471 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0713be9e3ba..a0bd73f7a35 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4096,6 +4096,7 @@ dependencies = [ "fuel-core-types 0.42.0", "fuel-vm 0.60.0", "k256", + "postcard", "rand 0.8.5", "secrecy", "serde", diff --git a/crates/fuel-core/src/p2p_test_helpers.rs b/crates/fuel-core/src/p2p_test_helpers.rs index 28a1b34b5ab..adfc1bed685 100644 --- a/crates/fuel-core/src/p2p_test_helpers.rs +++ b/crates/fuel-core/src/p2p_test_helpers.rs @@ -423,6 +423,7 @@ pub async fn make_nodes( overrides, ); node_config.block_production = Trigger::Never; + node_config.consensus_signer = SignMode::Unavailable; if let Some(ValidatorSetup { pub_key, diff --git a/crates/fuel-core/src/service/adapters.rs b/crates/fuel-core/src/service/adapters.rs index ae36bbd0674..d4c1eb41d5d 100644 --- a/crates/fuel-core/src/service/adapters.rs +++ b/crates/fuel-core/src/service/adapters.rs @@ -1,4 +1,8 @@ use std::sync::Arc; +use tokio::sync::{ + mpsc, + watch, +}; use fuel_core_consensus_module::{ block_verifier::Verifier, @@ -329,25 +333,25 @@ impl TransactionsSource { } pub struct NewTxWaiter { - pub receiver: tokio::sync::watch::Receiver<()>, + pub receiver: watch::Receiver<()>, pub timeout: Instant, } impl NewTxWaiter { - pub fn new(receiver: tokio::sync::watch::Receiver<()>, timeout: Instant) -> Self { + pub fn new(receiver: watch::Receiver<()>, timeout: Instant) -> Self { Self { receiver, timeout } } } #[derive(Clone)] pub struct PreconfirmationSender { - pub sender_signature_service: tokio::sync::mpsc::Sender>, + pub sender_signature_service: mpsc::Sender>, pub tx_status_manager_adapter: TxStatusManagerAdapter, } impl PreconfirmationSender { pub fn new( - sender_signature_service: tokio::sync::mpsc::Sender>, + sender_signature_service: mpsc::Sender>, tx_status_manager_adapter: TxStatusManagerAdapter, ) -> Self { Self { @@ -360,7 +364,7 @@ impl PreconfirmationSender { #[derive(Clone)] pub struct ExecutorAdapter { pub(crate) executor: Arc>>, - pub new_txs_watcher: tokio::sync::watch::Receiver<()>, + pub new_txs_watcher: watch::Receiver<()>, pub preconfirmation_sender: PreconfirmationSender, } @@ -369,8 +373,8 @@ impl ExecutorAdapter { database: Database, relayer_database: Database, config: fuel_core_upgradable_executor::config::Config, - new_txs_watcher: tokio::sync::watch::Receiver<()>, - preconfirmation_sender: tokio::sync::mpsc::Sender>, + new_txs_watcher: watch::Receiver<()>, + preconfirmation_sender: mpsc::Sender>, tx_status_manager_adapter: TxStatusManagerAdapter, ) -> Self { let executor = Executor::new(database, relayer_database, config); 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 9d6be342adc..dcc81a28f40 100644 --- a/crates/fuel-core/src/service/adapters/consensus_module/poa.rs +++ b/crates/fuel-core/src/service/adapters/consensus_module/poa.rs @@ -89,16 +89,15 @@ impl TransactionPool for TxPoolAdapter { self.service.get_new_executable_txs_notifier() } - fn notify_skipped_txs(&self, tx_ids_and_reasons: Vec<(Bytes32, String)>) { + fn notify_skipped_txs(&self, tx_ids_and_reasons: Vec) { self.service.notify_skipped_txs(tx_ids_and_reasons) } } impl TxStatusManager for TxStatusManagerAdapter { - async fn notify_skipped_txs(&self, tx_ids_and_reasons: Vec<(Bytes32, String)>) { + fn notify_skipped_txs(&self, tx_ids_and_reasons: Vec<(Bytes32, String)>) { self.tx_status_manager_shared_data .notify_skipped(tx_ids_and_reasons) - .await } } diff --git a/crates/fuel-core/src/service/adapters/consensus_module/poa/pre_confirmation_signature/broadcast.rs b/crates/fuel-core/src/service/adapters/consensus_module/poa/pre_confirmation_signature/broadcast.rs index 8287b1f2ef9..5bc2808abe4 100644 --- a/crates/fuel-core/src/service/adapters/consensus_module/poa/pre_confirmation_signature/broadcast.rs +++ b/crates/fuel-core/src/service/adapters/consensus_module/poa/pre_confirmation_signature/broadcast.rs @@ -101,7 +101,6 @@ mod tests { services::{ p2p::ProtocolSignature, preconfirmation::PreconfirmationStatus, - transaction_status::statuses::PreConfirmationFailure, }, }; @@ -115,14 +114,13 @@ mod tests { let mut adapter = P2PAdapter::new(service, peer_report_config); let preconfirmations = vec![Preconfirmation { tx_id: Default::default(), - status: PreconfirmationStatus::Failure(Arc::new(PreConfirmationFailure { + status: PreconfirmationStatus::Failure { tx_pointer: Default::default(), total_gas: 0, total_fee: 0, - receipts: Some(vec![]), - outputs: Some(vec![]), - reason: "Dummy reason".to_string(), - })), + receipts: vec![], + outputs: vec![], + }, }]; let signature = ed25519::Signature::from_bytes(&[5u8; 64]); let expiration = Tai64::UNIX_EPOCH; @@ -178,6 +176,7 @@ mod tests { let delegate = DelegatePreConfirmationKey { public_key: Default::default(), expiration, + nonce: 0, }; let signature = ProtocolSignature::from_bytes([5u8; 64]); @@ -210,6 +209,7 @@ mod tests { let entity = DelegatePreConfirmationKey { public_key: delegate_key, expiration, + nonce: 0, }; match &**inner { PreConfirmationMessage::Delegate(signed_by_block_producer_delegation) => { diff --git a/crates/fuel-core/src/service/adapters/consensus_module/poa/pre_confirmation_signature/tx_receiver.rs b/crates/fuel-core/src/service/adapters/consensus_module/poa/pre_confirmation_signature/tx_receiver.rs index 4d6c084be5a..5d4b55c724d 100644 --- a/crates/fuel-core/src/service/adapters/consensus_module/poa/pre_confirmation_signature/tx_receiver.rs +++ b/crates/fuel-core/src/service/adapters/consensus_module/poa/pre_confirmation_signature/tx_receiver.rs @@ -51,18 +51,10 @@ impl TxReceiver for PreconfirmationsReceiver { mod tests { #![allow(non_snake_case)] - use std::sync::Arc; - use super::*; use fuel_core_types::{ fuel_tx::TxId, - services::{ - preconfirmation::PreconfirmationStatus, - transaction_status::statuses::{ - PreConfirmationSqueezedOut, - PreConfirmationSuccess, - }, - }, + services::preconfirmation::PreconfirmationStatus, }; #[tokio::test] @@ -71,23 +63,19 @@ mod tests { let txs = vec![ Preconfirmation { tx_id: TxId::default(), - status: PreconfirmationStatus::SqueezedOut(Arc::new( - PreConfirmationSqueezedOut { - reason: "Dummy reason".to_string(), - }, - )), + status: PreconfirmationStatus::SqueezedOut { + reason: "Dummy reason".to_string(), + }, }, Preconfirmation { tx_id: TxId::default(), - status: PreconfirmationStatus::Success(Arc::new( - PreConfirmationSuccess { - tx_pointer: Default::default(), - total_gas: 0, - total_fee: 0, - receipts: Some(vec![]), - outputs: Some(vec![]), - }, - )), + status: PreconfirmationStatus::Success { + tx_pointer: Default::default(), + total_gas: 0, + total_fee: 0, + receipts: vec![], + outputs: vec![], + }, }, ]; diff --git a/crates/fuel-core/src/service/adapters/executor.rs b/crates/fuel-core/src/service/adapters/executor.rs index 60a769670d1..803eebb9b1d 100644 --- a/crates/fuel-core/src/service/adapters/executor.rs +++ b/crates/fuel-core/src/service/adapters/executor.rs @@ -104,25 +104,35 @@ impl NewTxWaiterPort for NewTxWaiter { impl PreconfirmationSenderPort for PreconfirmationSender { async fn send(&self, preconfirmations: Vec) { - // If the receiver is closed, it means no one is listening to the preconfirmations and so we can drop them. - // We don't consider this an error. - let _ = self - .sender_signature_service - .send(preconfirmations.clone()) - .await; + // TODO: Avoid cloning of the `preconfirmations` self.tx_status_manager_adapter .tx_status_manager_shared_data - .update_preconfirmations(preconfirmations) - .await; + .update_preconfirmations(preconfirmations.clone()); + + // If the receiver is closed, it means no one is listening to the preconfirmations and so we can drop them. + // We don't consider this an error. + let _ = self.sender_signature_service.send(preconfirmations).await; } fn try_send(&self, preconfirmations: Vec) -> Vec { - match self.sender_signature_service.try_send(preconfirmations) { - Ok(()) => vec![], + match self.sender_signature_service.try_reserve() { + Ok(permit) => { + // TODO: Avoid cloning of the `preconfirmations` + self.tx_status_manager_adapter + .tx_status_manager_shared_data + .update_preconfirmations(preconfirmations.clone()); + permit.send(preconfirmations); + vec![] + } // If the receiver is closed, it means no one is listening to the preconfirmations and so we can drop them. // We don't consider this an error. - Err(TrySendError::Closed(_)) => vec![], - Err(TrySendError::Full(preconfirmations)) => preconfirmations, + Err(TrySendError::Closed(_)) => { + self.tx_status_manager_adapter + .tx_status_manager_shared_data + .update_preconfirmations(preconfirmations); + vec![] + } + Err(TrySendError::Full(_)) => preconfirmations, } } } diff --git a/crates/fuel-core/src/service/adapters/graphql_api.rs b/crates/fuel-core/src/service/adapters/graphql_api.rs index 07f7d46f3ce..e5166501f38 100644 --- a/crates/fuel-core/src/service/adapters/graphql_api.rs +++ b/crates/fuel-core/src/service/adapters/graphql_api.rs @@ -186,8 +186,7 @@ impl worker::TxStatusCompletion for TxStatusManagerAdapter { status: TransactionStatus, ) { tracing::info!("Transaction {id} successfully included in block {block_height}"); - self.tx_status_manager_shared_data - .blocking_update_status(id, status); + self.tx_status_manager_shared_data.update_status(id, status); } } diff --git a/crates/fuel-core/src/service/adapters/txpool.rs b/crates/fuel-core/src/service/adapters/txpool.rs index 107370ff8ac..36ed5f8a572 100644 --- a/crates/fuel-core/src/service/adapters/txpool.rs +++ b/crates/fuel-core/src/service/adapters/txpool.rs @@ -234,6 +234,10 @@ impl ChainStateInfoProviderTrait for ChainStateInfoProvider { impl TxStatusManager for TxStatusManagerAdapter { fn status_update(&self, tx_id: TxId, tx_status: TransactionStatus) { self.tx_status_manager_shared_data - .blocking_update_status(tx_id, tx_status); + .update_status(tx_id, tx_status); + } + + fn statuses_update(&self, statuses: Vec<(TxId, TransactionStatus)>) { + self.tx_status_manager_shared_data.update_statuses(statuses); } } diff --git a/crates/fuel-core/src/service/config.rs b/crates/fuel-core/src/service/config.rs index ff1bec6a66b..c99695a2274 100644 --- a/crates/fuel-core/src/service/config.rs +++ b/crates/fuel-core/src/service/config.rs @@ -234,7 +234,7 @@ impl Config { self.utxo_validation = true; } - if !self.debug && self.txpool.utxo_validation != self.utxo_validation { + if self.txpool.utxo_validation != self.utxo_validation { tracing::warn!("The `utxo_validation` of `TxPool` was inconsistent"); self.txpool.utxo_validation = self.utxo_validation; } diff --git a/crates/fuel-core/src/service/sub_services.rs b/crates/fuel-core/src/service/sub_services.rs index a472a6dc5ad..69764bc85dc 100644 --- a/crates/fuel-core/src/service/sub_services.rs +++ b/crates/fuel-core/src/service/sub_services.rs @@ -117,8 +117,7 @@ pub fn init_sub_services( let (preconfirmation_sender, preconfirmation_receiver) = tokio::sync::mpsc::channel(1024); #[cfg(not(feature = "p2p"))] - let (preconfirmation_sender, _preconfirmation_receiver) = - tokio::sync::mpsc::channel(1024); + let (preconfirmation_sender, _) = tokio::sync::mpsc::channel(1024); let genesis_block = on_chain_view .genesis_block()? @@ -311,14 +310,13 @@ pub fn init_sub_services( let poa_config: fuel_core_poa::Config = config.into(); let mut production_enabled = !matches!(poa_config.trigger, Trigger::Never); - if !production_enabled && config.debug { + if !production_enabled + && config.debug + && !matches!(poa_config.signer, SignMode::Unavailable) + { production_enabled = true; tracing::info!("Enabled manual block production because of `debug` flag"); } - if production_enabled && matches!(poa_config.signer, SignMode::Unavailable) { - production_enabled = false; - tracing::warn!("Disabled block production because of unavailable signer"); - } let signer = FuelBlockSigner::new(config.consensus_signer.clone()); @@ -341,7 +339,6 @@ pub fn init_sub_services( config.into(); #[cfg(feature = "p2p")] - #[allow(clippy::type_complexity)] let pre_confirmation_service = production_enabled .then(|| { fuel_core_poa::pre_confirmation_signature_service::new_service( diff --git a/crates/services/consensus_module/poa/src/ports.rs b/crates/services/consensus_module/poa/src/ports.rs index 2f65a2abb0f..1165ba81ae0 100644 --- a/crates/services/consensus_module/poa/src/ports.rs +++ b/crates/services/consensus_module/poa/src/ports.rs @@ -31,15 +31,12 @@ use tokio::time::Instant; pub trait TransactionPool: Send + Sync { fn new_txs_watcher(&self) -> tokio::sync::watch::Receiver<()>; - fn notify_skipped_txs(&self, tx_ids_and_reasons: Vec<(Bytes32, String)>); + fn notify_skipped_txs(&self, tx_ids_and_reasons: Vec); } #[cfg_attr(test, mockall::automock)] pub trait TxStatusManager: Send + Sync { - fn notify_skipped_txs( - &self, - tx_ids_and_reasons: Vec<(Bytes32, String)>, - ) -> impl std::future::Future + Send; + fn notify_skipped_txs(&self, tx_ids_and_reasons: Vec<(Bytes32, String)>); } /// The source of transactions for the block. diff --git a/crates/services/consensus_module/poa/src/pre_confirmation_signature_service.rs b/crates/services/consensus_module/poa/src/pre_confirmation_signature_service.rs index e9b0d4526f1..8d17f61465e 100644 --- a/crates/services/consensus_module/poa/src/pre_confirmation_signature_service.rs +++ b/crates/services/consensus_module/poa/src/pre_confirmation_signature_service.rs @@ -170,6 +170,7 @@ where let message = DelegatePreConfirmationKey { public_key, expiration, + nonce: 0, }; const MAX_ATTEMPTS: usize = 5; @@ -258,6 +259,7 @@ where } _ = self.echo_delegation_trigger.tick() => { tracing::debug!("Echo delegation trigger"); + self.sealed_delegate_message.entity.nonce = self.sealed_delegate_message.entity.nonce.saturating_add(1); let sealed = self.sealed_delegate_message.clone(); try_or_continue!( diff --git a/crates/services/consensus_module/poa/src/pre_confirmation_signature_service/tests.rs b/crates/services/consensus_module/poa/src/pre_confirmation_signature_service/tests.rs index 9aef6df5f3a..e42db3ba907 100644 --- a/crates/services/consensus_module/poa/src/pre_confirmation_signature_service/tests.rs +++ b/crates/services/consensus_module/poa/src/pre_confirmation_signature_service/tests.rs @@ -229,6 +229,7 @@ impl TaskBuilder { let entity = DelegatePreConfirmationKey { public_key: Bytes32::zeroed(), expiration: Tai64::UNIX_EPOCH, + nonce: 0, }; let signature = parent_signature.dummy_signature.clone(); let period = self.period.unwrap_or(Duration::from_secs(60 * 60)); @@ -374,6 +375,7 @@ async fn run__key_rotation_trigger_will_broadcast_generated_key_with_correct_sig data: DelegatePreConfirmationKey { public_key: Bytes32::zeroed(), expiration: expiration_time, + nonce: 0, }, dummy_signature: dummy_signature.into(), }; @@ -420,6 +422,7 @@ async fn run__will_rebroadcast_generated_key_with_correct_signature_after_1_seco data: DelegatePreConfirmationKey { public_key: Bytes32::zeroed(), expiration: expiration_time, + nonce: 0, }, dummy_signature: dummy_signature.into(), }; diff --git a/crates/services/consensus_module/poa/src/pre_confirmation_signature_service/trigger.rs b/crates/services/consensus_module/poa/src/pre_confirmation_signature_service/trigger.rs index 9cfa63808f0..4f0c6803abf 100644 --- a/crates/services/consensus_module/poa/src/pre_confirmation_signature_service/trigger.rs +++ b/crates/services/consensus_module/poa/src/pre_confirmation_signature_service/trigger.rs @@ -3,6 +3,5 @@ use std::future::Future; /// Defines the behavior for when the `PreconfirmationSignatureTask` should rotate the delegate key pub trait KeyRotationTrigger: Send { - /// First rotation must be immediate fn next_rotation(&mut self) -> impl Future> + Send; } diff --git a/crates/services/consensus_module/poa/src/service.rs b/crates/services/consensus_module/poa/src/service.rs index cba2d503044..2022c32359d 100644 --- a/crates/services/consensus_module/poa/src/service.rs +++ b/crates/services/consensus_module/poa/src/service.rs @@ -393,10 +393,10 @@ where ); tx_ids_to_remove.push((tx_id, err.to_string())); } - self.txpool.notify_skipped_txs(tx_ids_to_remove.clone()); - self.tx_status_manager - .notify_skipped_txs(tx_ids_to_remove) - .await; + self.txpool.notify_skipped_txs( + tx_ids_to_remove.iter().map(|(tx_id, _)| *tx_id).collect(), + ); + self.tx_status_manager.notify_skipped_txs(tx_ids_to_remove); } // Sign the block and seal it diff --git a/crates/services/consensus_module/poa/src/service_test.rs b/crates/services/consensus_module/poa/src/service_test.rs index ac5ab4746fc..ec08d5bdf7f 100644 --- a/crates/services/consensus_module/poa/src/service_test.rs +++ b/crates/services/consensus_module/poa/src/service_test.rs @@ -304,7 +304,7 @@ impl MockTxStatusManager { let mut tx_status_manager = MockTxStatusManager::default(); tx_status_manager .expect_notify_skipped_txs() - .returning(|_| Box::pin(async move {})); + .returning(|_| {}); tx_status_manager } } @@ -369,7 +369,6 @@ async fn remove_skipped_transactions() { txpool .expect_notify_skipped_txs() .returning(move |skipped_ids| { - let skipped_ids: Vec<_> = skipped_ids.into_iter().map(|(id, _)| id).collect(); // Transform transactions into ids. let skipped_transactions: Vec<_> = skipped_transactions .iter() diff --git a/crates/services/executor/src/executor.rs b/crates/services/executor/src/executor.rs index 12730864222..9a6499418b9 100644 --- a/crates/services/executor/src/executor.rs +++ b/crates/services/executor/src/executor.rs @@ -148,7 +148,6 @@ use fuel_core_types::{ PreconfirmationStatus, }, relayer::Event, - transaction_status, }, }; use parking_lot::Mutex as ParkingMutex; @@ -158,16 +157,10 @@ use tracing::{ }; #[cfg(feature = "std")] -use std::{ - borrow::Cow, - sync::Arc, -}; +use std::borrow::Cow; #[cfg(not(feature = "std"))] -use alloc::{ - borrow::Cow, - sync::Arc, -}; +use alloc::borrow::Cow; #[cfg(feature = "alloc")] use alloc::{ @@ -278,39 +271,25 @@ fn convert_tx_execution_result_to_preconfirmation( total_gas, total_fee, .. - } => PreconfirmationStatus::Success(Arc::new( - transaction_status::statuses::PreConfirmationSuccess { - total_gas: *total_gas, - total_fee: *total_fee, - tx_pointer, - receipts: Some(receipts.clone()), - outputs: Some(dynamic_outputs), - }, - )), + } => PreconfirmationStatus::Success { + total_gas: *total_gas, + total_fee: *total_fee, + tx_pointer, + receipts: receipts.clone(), + outputs: dynamic_outputs, + }, TransactionExecutionResult::Failed { receipts, total_gas, total_fee, - #[cfg(feature = "std")] - result, .. - } => { - #[cfg(feature = "std")] - let reason = TransactionExecutionResult::reason(receipts, result); - #[cfg(not(feature = "std"))] - let reason = "Transaction execution failed".to_string(); - - PreconfirmationStatus::Failure(Arc::new( - transaction_status::statuses::PreConfirmationFailure { - total_gas: *total_gas, - total_fee: *total_fee, - tx_pointer, - receipts: Some(receipts.clone()), - outputs: Some(dynamic_outputs), - reason, - }, - )) - } + } => PreconfirmationStatus::Failure { + total_gas: *total_gas, + total_fee: *total_fee, + tx_pointer, + receipts: receipts.clone(), + outputs: dynamic_outputs, + }, }; Preconfirmation { tx_id, status } } @@ -840,11 +819,9 @@ where Err(err) => { status.push(Preconfirmation { tx_id, - status: PreconfirmationStatus::SqueezedOut(Arc::new( - transaction_status::statuses::PreConfirmationSqueezedOut { - reason: err.to_string(), - }, - )), + status: PreconfirmationStatus::SqueezedOut { + reason: err.to_string(), + }, }); data.skipped_transactions.push((tx_id, err)); } diff --git a/crates/services/p2p/src/codecs/postcard.rs b/crates/services/p2p/src/codecs/postcard.rs index 296deb34590..07070d2dacc 100644 --- a/crates/services/p2p/src/codecs/postcard.rs +++ b/crates/services/p2p/src/codecs/postcard.rs @@ -63,7 +63,11 @@ where #[cfg(test)] #[allow(non_snake_case)] mod tests { - use fuel_core_types::blockchain::SealedBlockHeader; + use fuel_core_types::{ + blockchain::SealedBlockHeader, + fuel_tx::Transaction, + services::p2p::NetworkableTransactionPool, + }; use libp2p::request_response::Codec; use super::*; @@ -92,7 +96,7 @@ mod tests { } #[tokio::test] - async fn codec__serialization_roundtrip_using_v2_on_successful_response_returns_original_value( + async fn codec__serialization_roundtrip_using_v2_on_successful_response_returns_original_value__sealed_headers( ) { // Given let sealed_block_headers = vec![SealedBlockHeader::default()]; @@ -119,6 +123,37 @@ mod tests { )); } + #[tokio::test] + async fn codec__serialization_roundtrip_using_v2_on_successful_response_returns_original_value__full_transactions( + ) { + // Given + let full_transactions = vec![Some(NetworkableTransactionPool::Transaction( + Transaction::default_test_tx(), + ))]; + let response = + V2ResponseMessage::TxPoolFullTransactions(Ok(full_transactions.clone())); + let mut codec: RequestResponseMessageHandler = + RequestResponseMessageHandler::new(MAX_REQUEST_SIZE); + let mut buf = Vec::with_capacity(1024); + + // When + codec + .write_response(&RequestResponseProtocol::V2, &mut buf, response) + .await + .expect("Valid full transactions should be serialized using v2"); + + let deserialized = codec + .read_response(&RequestResponseProtocol::V2, &mut buf.as_slice()) + .await + .expect("Valid full transactions should be deserialized using v2"); + + // Then + assert!(matches!( + deserialized, + V2ResponseMessage::TxPoolFullTransactions(Ok(actual)) if actual == full_transactions + )); + } + #[tokio::test] async fn codec__serialization_roundtrip_using_v1_on_successful_response_returns_original_value( ) { diff --git a/crates/services/sync/src/import/tests.rs b/crates/services/sync/src/import/tests.rs index 6cd2c0f0e04..a32ee67d691 100644 --- a/crates/services/sync/src/import/tests.rs +++ b/crates/services/sync/src/import/tests.rs @@ -1495,12 +1495,6 @@ impl PeerReportTestBuilder { } } - #[allow(dead_code)] - pub fn debug(mut self) -> Self { - self.debug = true; - self - } - pub fn with_get_sealed_block_headers( mut self, get_headers: Option>, diff --git a/crates/services/tx_status_manager/src/service.rs b/crates/services/tx_status_manager/src/service.rs index 244a9747467..d35063c5826 100644 --- a/crates/services/tx_status_manager/src/service.rs +++ b/crates/services/tx_status_manager/src/service.rs @@ -68,6 +68,9 @@ enum WriteRequest { tx_id: TxId, status: TransactionStatus, }, + UpdateStatuses { + statuses: Vec<(TxId, TransactionStatus)>, + }, UpdatePreconfirmations { preconfirmations: Vec, }, @@ -79,7 +82,7 @@ enum WriteRequest { #[derive(Clone)] pub struct SharedData { read_requests_sender: mpsc::Sender, - write_requests_sender: mpsc::Sender, + write_requests_sender: mpsc::UnboundedSender, } impl SharedData { @@ -100,43 +103,26 @@ impl SharedData { receiver.await? } - pub fn blocking_update_status(&self, tx_id: TxId, status: TransactionStatus) { + pub fn update_status(&self, tx_id: TxId, status: TransactionStatus) { let request = WriteRequest::UpdateStatus { tx_id, status }; - let _ = self.write_requests_sender.blocking_send(request); + let _ = self.write_requests_sender.send(request); } - pub fn try_update_preconfirmations( - &self, - preconfirmations: Vec, - ) -> Result<(), Vec> { - let request = WriteRequest::UpdatePreconfirmations { preconfirmations }; - match self.write_requests_sender.try_send(request) { - Ok(_) => Ok(()), - Err(e) => { - if let WriteRequest::UpdatePreconfirmations { preconfirmations } = - e.into_inner() - { - Err(preconfirmations) - } else { - // SAFETY: Building the WriteRequest::UpdatePreconfirmation variant just above - panic!( - "Wrong WriteRequest variant it should be UpdatePreconfirmations" - ); - } - } - } + pub fn update_statuses(&self, statuses: Vec<(TxId, TransactionStatus)>) { + let request = WriteRequest::UpdateStatuses { statuses }; + let _ = self.write_requests_sender.send(request); } - pub async fn update_preconfirmations(&self, preconfirmations: Vec) { + pub fn update_preconfirmations(&self, preconfirmations: Vec) { let request = WriteRequest::UpdatePreconfirmations { preconfirmations }; - if let Err(e) = self.write_requests_sender.send(request).await { + if let Err(e) = self.write_requests_sender.send(request) { tracing::error!("Failed to send preconfirmations: {:?}", e); } } - pub async fn notify_skipped(&self, tx_ids_and_reason: Vec<(Bytes32, String)>) { + pub fn notify_skipped(&self, tx_ids_and_reason: Vec<(Bytes32, String)>) { let request = WriteRequest::NotifySkipped { tx_ids_and_reason }; - if let Err(e) = self.write_requests_sender.send(request).await { + if let Err(e) = self.write_requests_sender.send(request) { tracing::error!("Failed to send skipped txs: {:?}", e); } } @@ -146,7 +132,7 @@ pub struct Task { manager: TxStatusManager, subscriptions: Subscriptions, read_requests_receiver: mpsc::Receiver, - write_requests_receiver: mpsc::Receiver, + write_requests_receiver: mpsc::UnboundedReceiver, shared_data: SharedData, signature_verification: SignatureVerification, } @@ -326,6 +312,12 @@ impl RunnableTask for Task { self.manager.status_update(tx_id, status); TaskNextAction::Continue } + Some(WriteRequest::UpdateStatuses { statuses }) => { + for (tx_id, status) in statuses { + self.manager.status_update(tx_id, status); + } + TaskNextAction::Continue + } Some(WriteRequest::UpdatePreconfirmations { preconfirmations }) => { for preconfirmation in preconfirmations { self.manager.status_update(preconfirmation.tx_id, preconfirmation.status.into()); @@ -390,8 +382,7 @@ where let (read_requests_sender, read_requests_receiver) = mpsc::channel(config.max_tx_update_subscriptions); - // TODO: Configurable buffer size - let (write_requests_sender, write_requests_receiver) = mpsc::channel(100_000); + let (write_requests_sender, write_requests_receiver) = mpsc::unbounded_channel(); let shared_data = SharedData { read_requests_sender, @@ -504,7 +495,7 @@ mod tests { struct Handles { pub pre_confirmation_updates: mpsc::Sender>, - pub write_requests_sender: mpsc::Sender, + pub write_requests_sender: mpsc::UnboundedSender, pub read_requests_sender: mpsc::Sender, pub tx_status_change: TxStatusChange, pub update_sender: UpdateSender, @@ -513,23 +504,17 @@ mod tests { pub(super) mod status { pub(super) mod preconfirmation { - use std::sync::Arc; - use fuel_core_types::services::{ - preconfirmation::PreconfirmationStatus, - transaction_status, - }; + use fuel_core_types::services::preconfirmation::PreconfirmationStatus; pub fn success() -> PreconfirmationStatus { - PreconfirmationStatus::Success(Arc::new( - transaction_status::statuses::PreConfirmationSuccess { - tx_pointer: Default::default(), - total_gas: Default::default(), - total_fee: Default::default(), - receipts: Some(vec![]), - outputs: Some(vec![]), - }, - )) + PreconfirmationStatus::Success { + tx_pointer: Default::default(), + total_gas: Default::default(), + total_fee: Default::default(), + receipts: vec![], + outputs: vec![], + } } } @@ -628,7 +613,7 @@ mod tests { fn new_task_with_handles(ttl: Duration) -> (Task, Handles) { let (read_requests_sender, read_requests_receiver) = mpsc::channel(1); - let (write_requests_sender, write_requests_receiver) = mpsc::channel(100_000); + let (write_requests_sender, write_requests_receiver) = mpsc::unbounded_channel(); let shared_data = SharedData { read_requests_sender: read_requests_sender.clone(), write_requests_sender: write_requests_sender.clone(), @@ -701,6 +686,7 @@ mod tests { let entity = DelegatePreConfirmationKey { public_key: delegate_public_key, expiration, + nonce: 0, }; let bytes = postcard::to_allocvec(&entity).unwrap(); let message = Message::new(&bytes); @@ -762,11 +748,11 @@ mod tests { async fn send_status_updates( updates: &[(Bytes32, TransactionStatus)], - sender: &mpsc::Sender, + sender: &mpsc::UnboundedSender, ) { updates.iter().for_each(|(tx_id, status)| { sender - .try_send(WriteRequest::UpdateStatus { + .send(WriteRequest::UpdateStatus { tx_id: *tx_id, status: status.clone(), }) @@ -782,10 +768,10 @@ mod tests { id.into() } - async fn force_pruning(sender: &mpsc::Sender) { + async fn force_pruning(sender: &mpsc::UnboundedSender) { let id = pruning_tx_id(); sender - .try_send(WriteRequest::UpdateStatus { + .send(WriteRequest::UpdateStatus { tx_id: id, status: status::transaction::failure(), }) @@ -1086,7 +1072,7 @@ mod tests { status_updates.iter().for_each(|(tx_id, status)| { handles .write_requests_sender - .try_send(WriteRequest::UpdateStatus { + .send(WriteRequest::UpdateStatus { tx_id: *tx_id, status: status.clone(), }) diff --git a/crates/services/txpool_v2/src/pool.rs b/crates/services/txpool_v2/src/pool.rs index 7fa0ddfde7d..3b230080b70 100644 --- a/crates/services/txpool_v2/src/pool.rs +++ b/crates/services/txpool_v2/src/pool.rs @@ -56,6 +56,7 @@ use crate::{ }, }; +use crate::error::RemovedReason; #[cfg(test)] use std::collections::HashSet; @@ -135,13 +136,11 @@ where { /// Insert transactions into the pool. /// Returns a list of results for each transaction. - /// Each result is a list of transactions that were removed from the pool - /// because of the insertion of the new transaction. pub fn insert( &mut self, tx: ArcPoolTx, persistent_storage: &impl TxPoolPersistentStorage, - ) -> Result, InsertionErrorType> { + ) -> Result<(), InsertionErrorType> { let insertion_result = self.insert_inner(tx, persistent_storage); self.register_transaction_counts(); insertion_result @@ -149,9 +148,9 @@ where fn insert_inner( &mut self, - tx: std::sync::Arc, + tx: Arc, persistent_storage: &impl TxPoolPersistentStorage, - ) -> Result>, InsertionErrorType> { + ) -> Result<(), InsertionErrorType> { let CanStoreTransaction { checked_transaction, transactions_to_remove, @@ -218,12 +217,21 @@ where self.new_executable_txs_notifier.send_replace(()); } + let error = TransactionStatus::squeezed_out( + Error::Removed(RemovedReason::LessWorth(tx_id)).to_string(), + ); let removed_transactions = removed_transactions .into_iter() - .map(|data| data.transaction) + .map(|data| { + let removed_tx_id = data.transaction.id(); + + (removed_tx_id, error.clone()) + }) .collect::>(); + self.tx_status_manager.statuses_update(removed_transactions); + self.update_stats(); - Ok(removed_transactions) + Ok(()) } fn update_stats(&self) { @@ -576,7 +584,8 @@ where pub fn remove_transaction_and_dependents( &mut self, tx_ids: Vec, - ) -> Vec { + tx_status: TransactionStatus, + ) { let mut removed_transactions = vec![]; for tx_id in tx_ids { if let Some(storage_id) = self.tx_id_to_storage_id.remove(&tx_id) { @@ -584,31 +593,29 @@ where .storage .remove_transaction_and_dependents_subtree(storage_id); self.update_components_and_caches_on_removal(removed.iter()); - removed_transactions - .extend(removed.into_iter().map(|data| data.transaction)); + removed_transactions.extend(removed.into_iter().map(|data| { + let tx_id = data.transaction.id(); + + (tx_id, tx_status.clone()) + })); } } + self.tx_status_manager.statuses_update(removed_transactions); self.update_stats(); - - removed_transactions } - pub fn remove_skipped_transaction(&mut self, tx_id: TxId) -> Vec { + pub fn remove_skipped_transaction(&mut self, tx_id: TxId) { self.extracted_outputs.new_skipped_transaction(&tx_id); - let mut txs_removed = vec![]; let coin_dependents = self.collision_manager.get_coins_spenders(&tx_id); for dependent in coin_dependents { let removed = self .storage .remove_transaction_and_dependents_subtree(dependent); self.update_components_and_caches_on_removal(removed.iter()); - txs_removed.extend(removed.into_iter().map(|data| data.transaction)); } self.update_stats(); - - txs_removed } fn check_blob_does_not_exist( diff --git a/crates/services/txpool_v2/src/pool_worker.rs b/crates/services/txpool_v2/src/pool_worker.rs index 0bc4774d769..d9f0ee14e2c 100644 --- a/crates/services/txpool_v2/src/pool_worker.rs +++ b/crates/services/txpool_v2/src/pool_worker.rs @@ -8,6 +8,7 @@ use fuel_core_types::{ services::{ block_importer::SharedImportResult, p2p::GossipsubMessageInfo, + transaction_status::TransactionStatus, txpool::ArcPoolTx, }, }; @@ -36,7 +37,6 @@ use crate::{ error::{ Error, InsertionErrorType, - RemovedReason, }, pending_pool::PendingPool, ports::{ @@ -206,7 +206,7 @@ pub(super) enum PoolRemoveRequest { block_result: SharedImportResult, }, SkippedTransactions { - dependents_ids: Vec<(TxId, Error)>, + dependents_ids: Vec, }, TxAndCoinDependents { tx_and_dependents_ids: (Vec, Error), @@ -250,10 +250,6 @@ pub(super) enum PoolNotification { error: Error, source: InsertionSource, }, - Removed { - tx_id: TxId, - error: Error, - }, } pub(super) struct PoolWorker { @@ -378,7 +374,7 @@ where let res = self.pool.insert(tx.clone(), &view); match res { - Ok(removed_txs) => { + Ok(()) => { let extended_source = match source { InsertionSource::P2P { from_peer_info } => { ExtendedInsertionSource::P2P { from_peer_info } @@ -409,19 +405,6 @@ where { tracing::error!("Failed to send inserted notification: {}", e); } - - for tx in removed_txs { - let removed_tx_id = tx.id(); - if let Err(e) = - self.notification_sender - .try_send(PoolNotification::Removed { - tx_id: removed_tx_id, - error: Error::Removed(RemovedReason::LessWorth(tx_id)), - }) - { - tracing::error!("Failed to send removed notification: {}", e); - } - } let resolved_txs = self.pending_pool.new_known_tx(tx); for (tx, source) in resolved_txs { @@ -504,38 +487,17 @@ where } } - fn remove_skipped_transactions(&mut self, parent_txs: Vec<(TxId, Error)>) { - for (tx_id, reason) in parent_txs { - let removed = self.pool.remove_skipped_transaction(tx_id); - for tx in removed { - let tx_id = tx.id(); - if let Err(e) = self.notification_sender.try_send(PoolNotification::Removed { - tx_id, - error: Error::SkippedTransaction( - format!("Parent transaction with id: {tx_id}, was removed because of: {reason}") - ) - }) { - tracing::error!("Failed to send removed notification: {}", e); - } - } + fn remove_skipped_transactions(&mut self, parent_txs: Vec) { + for tx_id in parent_txs { + self.pool.remove_skipped_transaction(tx_id); } } fn remove_and_coin_dependents(&mut self, tx_ids: (Vec, Error)) { let (tx_ids, error) = tx_ids; - let removed = self.pool.remove_transaction_and_dependents(tx_ids); - for tx in removed { - let tx_id = tx.id(); - if let Err(e) = self - .notification_sender - .try_send(PoolNotification::Removed { - tx_id, - error: error.clone(), - }) - { - tracing::error!("Failed to send removed notification: {}", e); - } - } + let tx_status = TransactionStatus::squeezed_out(error.to_string()); + self.pool + .remove_transaction_and_dependents(tx_ids, tx_status); } fn get_tx_ids(&mut self, max_txs: usize, tx_ids_sender: oneshot::Sender>) { diff --git a/crates/services/txpool_v2/src/ports.rs b/crates/services/txpool_v2/src/ports.rs index dc3dff0e3d9..6c3d7250d8c 100644 --- a/crates/services/txpool_v2/src/ports.rs +++ b/crates/services/txpool_v2/src/ports.rs @@ -39,6 +39,8 @@ pub use fuel_core_storage::transactional::AtomicView; pub trait TxStatusManager: Send + Sync + 'static { fn status_update(&self, tx_id: TxId, tx_status: TransactionStatus); + + fn statuses_update(&self, statuses: Vec<(TxId, TransactionStatus)>); } pub trait BlockImporter { diff --git a/crates/services/txpool_v2/src/service.rs b/crates/services/txpool_v2/src/service.rs index 7fd14ccbff8..8bb03681bcc 100644 --- a/crates/services/txpool_v2/src/service.rs +++ b/crates/services/txpool_v2/src/service.rs @@ -409,6 +409,7 @@ where error, source, } => { + let tx_status = TransactionStatus::squeezed_out(error.to_string()); match source { InsertionSource::P2P { from_peer_info } => { let _ = self.p2p.notify_gossip_transaction_validity( @@ -418,21 +419,12 @@ where } InsertionSource::RPC { response_channel } => { if let Some(channel) = response_channel { - let _ = channel.send(Err(error.clone())); + let _ = channel.send(Err(error)); } } } - self.tx_status_manager.status_update( - tx_id, - TransactionStatus::squeezed_out(error.to_string()), - ); - } - PoolNotification::Removed { tx_id, error } => { - self.tx_status_manager.status_update( - tx_id, - TransactionStatus::squeezed_out(error.to_string()), - ); + self.tx_status_manager.status_update(tx_id, tx_status); } } } diff --git a/crates/services/txpool_v2/src/shared_state.rs b/crates/services/txpool_v2/src/shared_state.rs index 1130d630e83..25f669c57cc 100644 --- a/crates/services/txpool_v2/src/shared_state.rs +++ b/crates/services/txpool_v2/src/shared_state.rs @@ -147,15 +147,7 @@ impl SharedState { /// Notify the txpool that some transactions were skipped during block production. /// This is used to update the status of the skipped transactions internally and in subscriptions - pub fn notify_skipped_txs(&self, tx_ids_and_reason: Vec<(Bytes32, String)>) { - let dependents_ids = tx_ids_and_reason - .into_iter() - .map(|(tx_id, reason)| { - let error = Error::SkippedTransaction(reason); - (tx_id, error) - }) - .collect(); - + pub fn notify_skipped_txs(&self, dependents_ids: Vec) { if let Err(e) = self .request_remove_sender .try_send(PoolRemoveRequest::SkippedTransactions { dependents_ids }) diff --git a/crates/services/txpool_v2/src/tests/mocks.rs b/crates/services/txpool_v2/src/tests/mocks.rs index 8ef79743892..c4aa8ccbdbb 100644 --- a/crates/services/txpool_v2/src/tests/mocks.rs +++ b/crates/services/txpool_v2/src/tests/mocks.rs @@ -99,6 +99,12 @@ impl ports::TxStatusManager for MockTxStatusManager { let tx = self.tx.clone(); tx.try_send((tx_id, tx_status)).unwrap(); } + + fn statuses_update(&self, statuses: Vec<(TxId, TransactionStatus)>) { + for (tx_id, tx_status) in statuses { + self.status_update(tx_id, tx_status); + } + } } #[derive(Clone, Default)] diff --git a/crates/services/txpool_v2/src/tests/tests_pool.rs b/crates/services/txpool_v2/src/tests/tests_pool.rs index fc8c1032644..f2580b00b63 100644 --- a/crates/services/txpool_v2/src/tests/tests_pool.rs +++ b/crates/services/txpool_v2/src/tests/tests_pool.rs @@ -76,7 +76,7 @@ fn insert_one_tx_succeeds() { // Then assert!(result.is_ok()); - let tx = result.unwrap().0; + let tx = result.unwrap(); universe.assert_pool_integrity(&[tx]); } @@ -194,7 +194,7 @@ fn insert__tx2_succeeds_after_dependent_tx1() { // Then assert!(result1.is_ok()); assert!(result2.is_ok()); - universe.assert_pool_integrity(&[result1.unwrap().0, result2.unwrap().0]); + universe.assert_pool_integrity(&[result1.unwrap(), result2.unwrap()]); } #[test] @@ -232,7 +232,7 @@ fn insert__tx2_collided_on_contract_id() { .add_input(gas_coin) .add_output(create_contract_output(contract_id)) .finalize_as_transaction(); - let tx = universe.verify_and_insert(tx).unwrap().0; + let tx = universe.verify_and_insert(tx).unwrap(); // When let result2 = universe.verify_and_insert(tx_faulty); @@ -269,7 +269,7 @@ fn insert__tx_with_dependency_on_invalid_utxo_type() { universe.random_predicate(AssetId::BASE, TEST_COIN_AMOUNT, Some(utxo_id)); let tx_faulty = universe.build_script_transaction(Some(vec![random_predicate]), None, 0); - let tx = universe.verify_and_insert(tx).unwrap().0; + let tx = universe.verify_and_insert(tx).unwrap(); // When let result2 = universe.verify_and_insert(tx_faulty); @@ -293,7 +293,7 @@ fn insert__already_known_tx_returns_error() { // Given let tx = universe.build_script_transaction(None, None, 0); - let pool_tx = universe.verify_and_insert(tx.clone()).unwrap().0; + let pool_tx = universe.verify_and_insert(tx.clone()).unwrap(); // When let result2 = universe.verify_and_insert(tx.clone()); @@ -327,8 +327,8 @@ fn insert__unknown_utxo_returns_error() { universe.assert_pool_integrity(&[]); } -#[test] -fn insert__higher_priced_tx_removes_lower_priced_tx() { +#[tokio::test] +async fn insert__higher_priced_tx_removes_lower_priced_tx() { let mut universe = TestPoolUniverse::default(); universe.build_pool(); @@ -344,8 +344,10 @@ fn insert__higher_priced_tx_removes_lower_priced_tx() { let result = universe.verify_and_insert(tx2).unwrap(); // Then - assert_eq!(result.1[0].id(), tx_id); - universe.assert_pool_integrity(&[result.0]); + universe + .await_expected_tx_statuses_squeeze_out(vec![tx_id]) + .await; + universe.assert_pool_integrity(&[result]); } #[test] @@ -361,8 +363,8 @@ fn insert__colliding_dependent_and_underpriced_returns_error() { // Given let tx2 = universe.build_script_transaction(Some(vec![input.clone()]), None, 20); let tx3 = universe.build_script_transaction(Some(vec![input]), None, 10); - let tx1 = universe.verify_and_insert(tx1).unwrap().0; - let tx2 = universe.verify_and_insert(tx2).unwrap().0; + let tx1 = universe.verify_and_insert(tx1).unwrap(); + let tx2 = universe.verify_and_insert(tx2).unwrap(); // When let result3 = universe.verify_and_insert(tx3); @@ -413,11 +415,11 @@ fn insert_dependent_contract_creation() { // Then assert!(result1.is_ok()); assert!(result2.is_ok()); - universe.assert_pool_integrity(&[result1.unwrap().0, result2.unwrap().0]); + universe.assert_pool_integrity(&[result1.unwrap(), result2.unwrap()]); } -#[test] -fn insert_more_priced_tx3_removes_tx1_and_dependent_tx2() { +#[tokio::test] +async fn insert_more_priced_tx3_removes_tx1_and_dependent_tx2() { let mut universe = TestPoolUniverse::default(); universe.build_pool(); @@ -444,15 +446,15 @@ fn insert_more_priced_tx3_removes_tx1_and_dependent_tx2() { let result3 = universe.verify_and_insert(tx3); // Then - let (pool_tx, removed_txs) = result3.unwrap(); - assert_eq!(removed_txs.len(), 2); - assert_eq!(removed_txs[0].id(), tx1_id); - assert_eq!(removed_txs[1].id(), tx2_id); + let pool_tx = result3.unwrap(); + universe + .await_expected_tx_statuses_squeeze_out(vec![tx1_id, tx2_id]) + .await; universe.assert_pool_integrity(&[pool_tx]); } -#[test] -fn insert_more_priced_tx2_removes_tx1_and_more_priced_tx3_removes_tx2() { +#[tokio::test] +async fn insert_more_priced_tx2_removes_tx1_and_more_priced_tx3_removes_tx2() { let mut universe = TestPoolUniverse::default(); universe.build_pool(); @@ -477,13 +479,11 @@ fn insert_more_priced_tx2_removes_tx1_and_more_priced_tx3_removes_tx2() { // Then assert!(result2.is_ok()); - let removed_txs = result2.unwrap().1; - assert_eq!(removed_txs.len(), 1); - assert_eq!(removed_txs[0].id(), tx1_id); assert!(result3.is_ok()); - let (pool_tx, removed_txs) = result3.unwrap(); - assert_eq!(removed_txs.len(), 1); - assert_eq!(removed_txs[0].id(), tx2_id); + universe + .await_expected_tx_statuses_squeeze_out(vec![tx1_id, tx2_id]) + .await; + let pool_tx = result3.unwrap(); universe.assert_pool_integrity(&[pool_tx]); } @@ -502,7 +502,7 @@ fn insert__tx_limit_hit() { // Given let tx1 = universe.build_script_transaction(None, None, 10); let tx2 = universe.build_script_transaction(None, None, 0); - let pool_tx = universe.verify_and_insert(tx1).unwrap().0; + let pool_tx = universe.verify_and_insert(tx1).unwrap(); // When let result2 = universe.verify_and_insert(tx2); @@ -537,7 +537,7 @@ fn insert__tx_gas_limit() { ..Default::default() }); universe.build_pool(); - let pool_tx = universe.verify_and_insert(tx1).unwrap().0; + let pool_tx = universe.verify_and_insert(tx1).unwrap(); // When let result2 = universe.verify_and_insert(tx2); @@ -572,7 +572,7 @@ fn insert__tx_bytes_limit() { ..Default::default() }); universe.build_pool(); - let pool_tx = universe.verify_and_insert(tx1).unwrap().0; + let pool_tx = universe.verify_and_insert(tx1).unwrap(); // When let result2 = universe.verify_and_insert(tx2); @@ -601,8 +601,8 @@ fn insert__dependency_chain_length_hit() { let input = unset_input.into_input(UtxoId::new(tx2.id(&Default::default()), 0)); let tx3 = universe.build_script_transaction(Some(vec![input]), None, 0); - let tx1 = universe.verify_and_insert(tx1).unwrap().0; - let tx2 = universe.verify_and_insert(tx2).unwrap().0; + let tx1 = universe.verify_and_insert(tx1).unwrap(); + let tx2 = universe.verify_and_insert(tx2).unwrap(); // When let result3 = universe.verify_and_insert(tx3); @@ -921,7 +921,7 @@ fn insert_tx_when_input_message_id_exists_in_db() { // When let pool_tx = universe.verify_and_insert(tx) // Then - .unwrap().0; + .unwrap(); universe.assert_pool_integrity(&[pool_tx]); } @@ -969,7 +969,7 @@ fn insert__tx_tip_lower_than_another_tx_with_same_message_id() { ); // When - let pool_tx = universe.verify_and_insert(tx_high).unwrap().0; + let pool_tx = universe.verify_and_insert(tx_high).unwrap(); let err = universe.verify_and_insert(tx_low).unwrap_err(); // Then @@ -979,8 +979,8 @@ fn insert__tx_tip_lower_than_another_tx_with_same_message_id() { universe.assert_pool_integrity(&[pool_tx]); } -#[test] -fn insert_tx_tip_higher_than_another_tx_with_same_message_id() { +#[tokio::test] +async fn insert_tx_tip_higher_than_another_tx_with_same_message_id() { let mut universe = TestPoolUniverse::default(); universe.build_pool(); @@ -1010,9 +1010,10 @@ fn insert_tx_tip_higher_than_another_tx_with_same_message_id() { // Then assert!(result1.is_ok()); assert!(result2.is_ok()); - let (pool_tx, removed_txs) = result2.unwrap(); - assert_eq!(removed_txs.len(), 1); - assert_eq!(removed_txs[0].id(), tx_high_id); + let pool_tx = result2.unwrap(); + universe + .await_expected_tx_statuses_squeeze_out(vec![tx_high_id]) + .await; universe.assert_pool_integrity(&[pool_tx]); } @@ -1049,7 +1050,7 @@ fn insert_again_message_after_squeeze_with_even_lower_tip() { assert!(result1.is_ok()); assert!(result2.is_ok()); assert!(result3.is_ok()); - universe.assert_pool_integrity(&[result2.unwrap().0, result3.unwrap().0]); + universe.assert_pool_integrity(&[result2.unwrap(), result3.unwrap()]); } #[test] @@ -1179,7 +1180,7 @@ fn insert_tx_with_blob() { // When let pool_tx = universe.verify_and_insert(tx) // Then - .unwrap().0; + .unwrap(); universe.assert_pool_integrity(&[pool_tx]); } @@ -1202,7 +1203,7 @@ fn insert__tx_with_blob_already_inserted_at_higher_tip() { .add_fee_input() .finalize_as_transaction(); - let pool_tx = universe.verify_and_insert(tx).unwrap().0; + let pool_tx = universe.verify_and_insert(tx).unwrap(); let same_blob_tx = TransactionBuilder::blob(BlobBody { id: blob_id, @@ -1256,7 +1257,7 @@ fn insert_tx_with_blob_already_insert_at_lower_tip() { // Then assert!(result.is_ok()); - universe.assert_pool_integrity(&[result.unwrap().0]); + universe.assert_pool_integrity(&[result.unwrap()]); } #[test] @@ -1269,7 +1270,7 @@ fn verify_and_insert__when_dependent_tx_is_extracted_new_tx_still_accepted() { let (output_a, unset_input) = universe.create_output_and_input(); let dependency_tx = universe.build_script_transaction(inputs.clone(), Some(vec![output_a]), 1); - let mut pool_dependency_tx = universe.verify_and_insert(dependency_tx).unwrap().0; + let mut pool_dependency_tx = universe.verify_and_insert(dependency_tx).unwrap(); inputs = Some(vec![ unset_input.into_input(UtxoId::new(pool_dependency_tx.id(), 0)) ]); @@ -1293,7 +1294,7 @@ fn verify_and_insert__when_dependent_tx_is_extracted_new_tx_still_accepted() { assert_eq!(pool_dependency_tx.id(), txs[0].id()); // Then - pool_dependency_tx = universe.verify_and_insert(dependent_tx).unwrap().0; + pool_dependency_tx = universe.verify_and_insert(dependent_tx).unwrap(); let input_a = new_unset_input.into_input(UtxoId::new(pool_dependency_tx.id(), 0)); inputs = Some(vec![input_a.clone()]); } @@ -1349,8 +1350,8 @@ fn insert__if_tx3_depends_and_collides_with_tx2() { // Given // tx3 {inputs: {coinA, coinB}, outputs:{}, tip: 20} let input_b = unset_input.into_input(UtxoId::new(tx2.id(&Default::default()), 0)); - let tx1 = universe.verify_and_insert(tx1).unwrap().0; - let tx2 = universe.verify_and_insert(tx2).unwrap().0; + let tx1 = universe.verify_and_insert(tx1).unwrap(); + let tx2 = universe.verify_and_insert(tx2).unwrap(); let tx3 = universe.build_script_transaction(Some(vec![input_a, input_b]), None, 20); diff --git a/crates/services/txpool_v2/src/tests/universe.rs b/crates/services/txpool_v2/src/tests/universe.rs index d5d5d4e3284..34030a0a9f2 100644 --- a/crates/services/txpool_v2/src/tests/universe.rs +++ b/crates/services/txpool_v2/src/tests/universe.rs @@ -257,10 +257,7 @@ impl TestPoolUniverse { } // Returns the added transaction and the list of transactions that were removed from the pool - pub fn verify_and_insert( - &mut self, - tx: Transaction, - ) -> Result<(ArcPoolTx, Vec), Error> { + pub fn verify_and_insert(&mut self, tx: Transaction) -> Result { if let Some(pool) = &self.pool { let mut mock_chain_state_info_provider = MockChainStateInfoProvider::default(); @@ -280,15 +277,14 @@ impl TestPoolUniverse { let tx = verification.perform_all_verifications(tx, Default::default(), true)?; let tx = Arc::new(tx); - Ok(( - tx.clone(), - pool.write() - .insert(tx, &self.mock_db) - .map_err(|e| match e { - InsertionErrorType::Error(e) => e, - InsertionErrorType::MissingInputs(e) => e.first().unwrap().into(), - })?, - )) + pool.write() + .insert(tx.clone(), &self.mock_db) + .map_err(|e| match e { + InsertionErrorType::Error(e) => e, + InsertionErrorType::MissingInputs(e) => e.first().unwrap().into(), + })?; + + Ok(tx) } else { panic!("Pool needs to be built first"); } @@ -298,7 +294,7 @@ impl TestPoolUniverse { &mut self, tx: Transaction, gas_price: GasPrice, - ) -> Result, Error> { + ) -> Result<(), Error> { if let Some(pool) = &self.pool { let mut mock_chain_state_info = MockChainStateInfoProvider::default(); mock_chain_state_info @@ -332,7 +328,7 @@ impl TestPoolUniverse { tx: Transaction, consensus_params: ConsensusParameters, wasm_checker: MockWasmChecker, - ) -> Result, Error> { + ) -> Result<(), Error> { if let Some(pool) = &self.pool { let mut mock_chain_state_info_provider = MockChainStateInfoProvider::default(); @@ -479,6 +475,17 @@ impl TestPoolUniverse { .unwrap(); } + pub(crate) async fn await_expected_tx_statuses_squeeze_out( + &mut self, + tx_ids: Vec, + ) { + self.await_expected_tx_statuses(tx_ids, |status| { + matches!(status, TransactionStatus::SqueezedOut { .. }) + }) + .await + .unwrap(); + } + pub(crate) async fn await_expected_tx_statuses( &mut self, tx_ids: Vec, diff --git a/crates/types/Cargo.toml b/crates/types/Cargo.toml index 19ece14167a..3baac2a29ff 100644 --- a/crates/types/Cargo.toml +++ b/crates/types/Cargo.toml @@ -38,8 +38,9 @@ zeroize = "1.5" [dev-dependencies] aws-config = { version = "1.1.7", features = ["behavior-version-latest"] } -fuel-core-types = { path = ".", features = ["test-helpers"] } +fuel-core-types = { path = ".", features = ["test-helpers", "serde"] } tokio = { workspace = true, features = ["macros"] } +postcard = { workspace = true } [features] default = ["std"] diff --git a/crates/types/src/services/executor.rs b/crates/types/src/services/executor.rs index 2eafbb1fb36..f05a4a3ac1d 100644 --- a/crates/types/src/services/executor.rs +++ b/crates/types/src/services/executor.rs @@ -242,9 +242,10 @@ impl TransactionExecutionResult { } } - #[cfg(feature = "std")] /// Get the reason of the failed transaction execution. pub fn reason(receipts: &[Receipt], state: &Option) -> String { + use alloc::format; + receipts .iter() .find_map(|receipt| match receipt { diff --git a/crates/types/src/services/p2p.rs b/crates/types/src/services/p2p.rs index e01c500613d..8766dfe5a08 100644 --- a/crates/types/src/services/p2p.rs +++ b/crates/types/src/services/p2p.rs @@ -17,7 +17,6 @@ use crate::services::preconfirmation::PreconfirmationStatus; use crate::{ fuel_tx::Transaction, fuel_types::BlockHeight, - services::transaction_status::statuses::PreConfirmationSqueezedOut, }; use std::{ collections::HashSet, @@ -104,6 +103,8 @@ pub struct DelegatePreConfirmationKey

{ /// to use to verify the pre-confirmations--serves the second purpose of being a nonce of /// each key pub expiration: Tai64, + /// The nonce of the p2p message to make it unique. + pub nonce: u64, } /// A signed key delegation @@ -130,17 +131,14 @@ impl PreConfirmationMessage { fuel_tx::TxId, services::preconfirmation::Preconfirmation, }; - use std::sync::Arc; Self::Preconfirmations(SignedPreconfirmationByDelegate { entity: Preconfirmations { expiration: Tai64::UNIX_EPOCH, preconfirmations: vec![Preconfirmation { tx_id: TxId::default(), - status: PreconfirmationStatus::SqueezedOut(Arc::new( - PreConfirmationSqueezedOut { - reason: "Dummy reason".to_string(), - }, - )), + status: PreconfirmationStatus::SqueezedOut { + reason: "Dummy reason".to_string(), + }, }], }, signature: crate::fuel_tx::Bytes64::default(), @@ -293,22 +291,57 @@ impl Serialize for NetworkableTransactionPool { where S: serde::Serializer, { + use crate::fuel_tx::{ + Blob, + Create, + Script, + Upgrade, + Upload, + }; + + #[cfg(debug_assertions)] + // When a new variant is added to `Transaction`, the `TransactionRef` + // must also be updated to match the new variant. + // This match statement will trigger a compilation error if a new variant is added. + // Don't add a `_` wildcard to this match statement, instead handle new variant. + { + match Transaction::default_test_tx() { + Transaction::Script(_) => {} + Transaction::Create(_) => {} + Transaction::Mint(_) => {} + Transaction::Upgrade(_) => {} + Transaction::Upload(_) => {} + Transaction::Blob(_) => {} + } + } + + #[derive(serde::Serialize)] + enum TransactionRef<'a> { + Script(&'a Script), + Create(&'a Create), + #[allow(dead_code)] + Mint, + Upgrade(&'a Upgrade), + Upload(&'a Upload), + Blob(&'a Blob), + } + match self { NetworkableTransactionPool::PoolTransaction(tx) => match (*tx).as_ref() { - PoolTransaction::Script(script, _) => { - script.transaction().serialize(serializer) + PoolTransaction::Script(tx, _) => { + TransactionRef::Script(tx.transaction()).serialize(serializer) } - PoolTransaction::Create(create, _) => { - create.transaction().serialize(serializer) + PoolTransaction::Create(tx, _) => { + TransactionRef::Create(tx.transaction()).serialize(serializer) } - PoolTransaction::Blob(blob, _) => { - blob.transaction().serialize(serializer) + PoolTransaction::Blob(tx, _) => { + TransactionRef::Blob(tx.transaction()).serialize(serializer) } - PoolTransaction::Upgrade(upgrade, _) => { - upgrade.transaction().serialize(serializer) + PoolTransaction::Upgrade(tx, _) => { + TransactionRef::Upgrade(tx.transaction()).serialize(serializer) } - PoolTransaction::Upload(upload, _) => { - upload.transaction().serialize(serializer) + PoolTransaction::Upload(tx, _) => { + TransactionRef::Upload(tx.transaction()).serialize(serializer) } }, NetworkableTransactionPool::Transaction(tx) => tx.serialize(serializer), @@ -339,3 +372,25 @@ impl TryFrom for Transaction { } } } + +#[cfg(test)] +mod tests { + use crate::{ + fuel_tx::Transaction, + services::p2p::NetworkableTransactionPool, + }; + + #[test] + fn ser_der() { + // Given + let transaction = Transaction::default_test_tx(); + let expected = NetworkableTransactionPool::Transaction(transaction.clone()); + let bytes = postcard::to_allocvec(&expected).unwrap(); + + // When + let actual: NetworkableTransactionPool = postcard::from_bytes(&bytes).unwrap(); + + // Then + assert_eq!(actual, expected); + } +} diff --git a/crates/types/src/services/preconfirmation.rs b/crates/types/src/services/preconfirmation.rs index 2036abba789..745c181417f 100644 --- a/crates/types/src/services/preconfirmation.rs +++ b/crates/types/src/services/preconfirmation.rs @@ -1,18 +1,19 @@ //! The module containing all types related to the preconfirmation service. -#[cfg(feature = "std")] -use std::sync::Arc; - -use crate::fuel_tx::TxId; +use crate::fuel_tx::{ + Receipt, + TxId, + TxPointer, +}; +use fuel_vm_private::fuel_tx::Output; use tai64::Tai64; #[cfg(not(feature = "std"))] use alloc::{ - sync::Arc, + string::String, vec::Vec, }; -use super::transaction_status; /// A collection of pre-confirmations that have been signed by a delegate #[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] @@ -39,9 +40,34 @@ pub struct Preconfirmation { #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum PreconfirmationStatus { /// Transaction was squeezed out by the tx pool - SqueezedOut(Arc), + SqueezedOut { + /// Reason the transaction was squeezed out + reason: String, + }, /// Transaction has been confirmed and will be included in block_height - Success(Arc), + Success { + /// Transaction pointer within the block. + tx_pointer: TxPointer, + /// The total gas used by the transaction. + total_gas: u64, + /// The total fee paid by the transaction. + total_fee: u64, + /// Receipts produced by the transaction during execution. + receipts: Vec, + /// Dynamic outputs produced by the transaction during execution. + outputs: Vec, + }, /// Transaction will not be included in a block, rejected at `block_height` - Failure(Arc), + Failure { + /// Transaction pointer within the block. + tx_pointer: TxPointer, + /// The total gas used by the transaction. + total_gas: u64, + /// The total fee paid by the transaction. + total_fee: u64, + /// Receipts produced by the transaction during execution. + receipts: Vec, + /// Dynamic outputs produced by the transaction during execution. + outputs: Vec, + }, } diff --git a/crates/types/src/services/transaction_status.rs b/crates/types/src/services/transaction_status.rs index acf7a9dcd23..88b46d202a7 100644 --- a/crates/types/src/services/transaction_status.rs +++ b/crates/types/src/services/transaction_status.rs @@ -7,14 +7,14 @@ use crate::{ TxPointer, }, fuel_vm::ProgramState, - services::preconfirmation::PreconfirmationStatus, + services::{ + executor::TransactionExecutionResult, + preconfirmation::PreconfirmationStatus, + }, }; use fuel_vm_private::fuel_types::BlockHeight; use tai64::Tai64; -#[cfg(feature = "std")] -use crate::services::executor::TransactionExecutionResult; - #[cfg(feature = "std")] use std::sync::Arc; @@ -197,14 +197,46 @@ impl TransactionStatus { impl From for TransactionStatus { fn from(value: PreconfirmationStatus) -> Self { match value { - PreconfirmationStatus::SqueezedOut(s) => { - TransactionStatus::PreConfirmationSqueezedOut(s) - } - PreconfirmationStatus::Success(s) => { - TransactionStatus::PreConfirmationSuccess(s) + PreconfirmationStatus::SqueezedOut { reason } => { + TransactionStatus::PreConfirmationSqueezedOut( + statuses::PreConfirmationSqueezedOut { reason }.into(), + ) } - PreconfirmationStatus::Failure(s) => { - TransactionStatus::PreConfirmationFailure(s) + PreconfirmationStatus::Success { + tx_pointer, + total_gas, + total_fee, + receipts, + outputs, + } => TransactionStatus::PreConfirmationSuccess( + statuses::PreConfirmationSuccess { + tx_pointer, + total_gas, + total_fee, + receipts: Some(receipts), + outputs: Some(outputs), + } + .into(), + ), + PreconfirmationStatus::Failure { + tx_pointer, + total_gas, + total_fee, + receipts, + outputs, + } => { + let reason = TransactionExecutionResult::reason(&receipts, &None); + TransactionStatus::PreConfirmationFailure( + statuses::PreConfirmationFailure { + tx_pointer, + total_gas, + total_fee, + receipts: Some(receipts), + outputs: Some(outputs), + reason, + } + .into(), + ) } } } diff --git a/tests/tests/preconfirmations.rs b/tests/tests/preconfirmations.rs index 70fb6b91e1a..5817a250690 100644 --- a/tests/tests/preconfirmations.rs +++ b/tests/tests/preconfirmations.rs @@ -216,61 +216,6 @@ async fn preconfirmation__received_after_failed_execution() { )); } -#[tokio::test] -async fn preconfirmation__received_after_squeezed_out() { - let mut config = Config::local_node(); - config.block_production = Trigger::Never; - let mut rng = rand::thread_rng(); - - // Given - // Disable UTXO validation in TxPool so that the transaction is squeezed out by - // block production - config.debug = true; - config.txpool.utxo_validation = false; - config.utxo_validation = true; - let srv = FuelService::new_node(config).await.unwrap(); - let client = FuelClient::from(srv.bound_address); - - let gas_limit = 1_000_000; - let tx = TransactionBuilder::script( - vec![op::ret(RegId::ONE)].into_iter().collect(), - vec![], - ) - .script_gas_limit(gas_limit) - .add_unsigned_coin_input( - SecretKey::random(&mut rng), - rng.gen(), - 10, - Default::default(), - Default::default(), - ) - .add_output(Output::Change { - to: Default::default(), - amount: 0, - asset_id: Default::default(), - }) - .finalize_as_transaction(); - - let tx_id = tx.id(&Default::default()); - let mut tx_statuses_subscriber = - client.subscribe_transaction_status(&tx_id).await.unwrap(); - client.submit(&tx).await.unwrap(); - - // When - assert!(matches!( - tx_statuses_subscriber.next().await.unwrap().unwrap(), - TransactionStatus::Submitted { .. } - )); - client.produce_blocks(1, None).await.unwrap(); - if let TransactionStatus::SqueezedOut { reason: _ } = - tx_statuses_subscriber.next().await.unwrap().unwrap() - { - // Then - } else { - panic!("Expected preconfirmation status"); - } -} - #[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn preconfirmation__received_tx_inserted_end_block_open_period() { let mut config = Config::local_node(); diff --git a/tests/tests/preconfirmations_gossip.rs b/tests/tests/preconfirmations_gossip.rs index fadad495f4a..b4bbecb6513 100644 --- a/tests/tests/preconfirmations_gossip.rs +++ b/tests/tests/preconfirmations_gossip.rs @@ -4,12 +4,15 @@ use std::hash::{ Hasher, }; -use fuel_core::p2p_test_helpers::{ - make_nodes, - BootstrapSetup, - Nodes, - ProducerSetup, - ValidatorSetup, +use fuel_core::{ + p2p_test_helpers::{ + make_nodes, + BootstrapSetup, + Nodes, + ProducerSetup, + ValidatorSetup, + }, + service::Config, }; use fuel_core_client::client::{ types::TransactionStatus, @@ -40,6 +43,14 @@ use rand::{ SeedableRng, }; +fn config_with_preconfirmations() -> Config { + let mut config = Config::local_node(); + + config.p2p.as_mut().unwrap().subscribe_to_pre_confirmations = true; + + config +} + #[tokio::test] async fn preconfirmation__propagate_p2p_after_successful_execution() { let mut rng = rand::thread_rng(); @@ -91,9 +102,9 @@ async fn preconfirmation__propagate_p2p_after_successful_execution() { // Create a producer for each key pair and a set of validators that share // the same key pair. let Nodes { - producers: _dont_drop1, + producers: _producers, validators, - bootstrap_nodes: _dont_drop2, + bootstrap_nodes: _dont_drop, } = make_nodes( pub_keys .iter() @@ -114,45 +125,26 @@ async fn preconfirmation__propagate_p2p_after_successful_execution() { ) }) }), - None, + Some(config_with_preconfirmations()), ) .await; let sentry = &validators[0]; - let authority = &validators[0]; // When let client_sentry = FuelClient::from(sentry.node.bound_address); let mut tx_statuses_subscriber = client_sentry - .subscribe_transaction_status(&tx_id) + .submit_and_await_status(&tx) .await .expect("Should be able to subscribe for events"); - sentry - .node - .submit(tx) - .await - .expect("Should accept invalid transaction because `utxo_validation = false`."); - // Then assert!(matches!( tx_statuses_subscriber.next().await.unwrap().unwrap(), TransactionStatus::Submitted { .. } )); - authority - .node - .shared - .poa_adapter - .manually_produce_blocks( - None, - fuel_core_poa::service::Mode::Blocks { - number_of_blocks: 1, - }, - ) - .await - .unwrap(); - + let status = tx_statuses_subscriber.next().await.unwrap().unwrap(); if let TransactionStatus::PreconfirmationSuccess { tx_pointer, total_fee, @@ -160,7 +152,7 @@ async fn preconfirmation__propagate_p2p_after_successful_execution() { transaction_id, receipts, resolved_outputs, - } = tx_statuses_subscriber.next().await.unwrap().unwrap() + } = status.clone() { // Then assert_eq!(tx_pointer, TxPointer::new(BlockHeight::new(1), 1)); @@ -188,7 +180,7 @@ async fn preconfirmation__propagate_p2p_after_successful_execution() { } ); } else { - panic!("Expected preconfirmation status"); + panic!("Expected preconfirmation status, got {status:?}"); } assert!(matches!( tx_statuses_subscriber.next().await.unwrap().unwrap(), @@ -247,9 +239,9 @@ async fn preconfirmation__propagate_p2p_after_failed_execution() { // Create a producer for each key pair and a set of validators that share // the same key pair. let Nodes { - producers: _dont_drop1, + producers: _producers, validators, - bootstrap_nodes: _dont_drop2, + bootstrap_nodes: _dont_drop, } = make_nodes( pub_keys .iter() @@ -270,43 +262,24 @@ async fn preconfirmation__propagate_p2p_after_failed_execution() { ) }) }), - None, + Some(config_with_preconfirmations()), ) .await; let sentry = &validators[0]; - let authority = &validators[0]; // When let client_sentry = FuelClient::from(sentry.node.bound_address); let mut tx_statuses_subscriber = client_sentry - .subscribe_transaction_status(&tx_id) + .submit_and_await_status(&tx) .await .expect("Should be able to subscribe for events"); - sentry - .node - .submit(tx) - .await - .expect("Should accept invalid transaction because `utxo_validation = false`."); - assert!(matches!( tx_statuses_subscriber.next().await.unwrap().unwrap(), TransactionStatus::Submitted { .. } )); - authority - .node - .shared - .poa_adapter - .manually_produce_blocks( - None, - fuel_core_poa::service::Mode::Blocks { - number_of_blocks: 1, - }, - ) - .await - .unwrap(); if let TransactionStatus::PreconfirmationFailure { tx_pointer, total_fee, @@ -351,3 +324,97 @@ async fn preconfirmation__propagate_p2p_after_failed_execution() { TransactionStatus::Failure { .. } )); } + +#[tokio::test] +async fn preconfirmation__propagate_p2p_after_squeezed_out_on_producer() { + let mut rng = rand::thread_rng(); + + let gas_limit = 1_000_000; + let tx = TransactionBuilder::script( + vec![op::ret(RegId::ONE)].into_iter().collect(), + vec![], + ) + .script_gas_limit(gas_limit) + .add_unsigned_coin_input( + SecretKey::random(&mut rng), + rng.gen(), + 10, + Default::default(), + Default::default(), + ) + .add_output(Output::Change { + to: Default::default(), + amount: 0, + asset_id: Default::default(), + }) + .finalize_as_transaction(); + + // Create a random seed based on the test parameters. + let mut hasher = DefaultHasher::new(); + let num_txs = 1; + let num_validators = 1; + let num_partitions = 1; + (num_txs, num_validators, num_partitions, line!()).hash(&mut hasher); + let mut rng = StdRng::seed_from_u64(hasher.finish()); + + // Create a set of key pairs. + let secrets: Vec<_> = (0..1).map(|_| SecretKey::random(&mut rng)).collect(); + let pub_keys: Vec<_> = secrets + .clone() + .into_iter() + .map(|secret| Input::owner(&secret.public_key())) + .collect(); + + // Create a producer for each key pair and a set of validators that share + // the same key pair. + + // Given + // Disable UTXO validation in TxPool so that the transaction is squeezed out by + // block production + let Nodes { + producers: _producers, + validators, + bootstrap_nodes: _dont_drop, + } = make_nodes( + pub_keys + .iter() + .map(|pub_key| Some(BootstrapSetup::new(*pub_key))), + secrets.clone().into_iter().enumerate().map(|(i, secret)| { + Some( + ProducerSetup::new(secret) + .with_name(format!("{}:producer", pub_keys[i])) + .utxo_validation(true), + ) + }), + pub_keys.iter().flat_map(|pub_key| { + (0..num_validators).map(move |i| { + Some( + ValidatorSetup::new(*pub_key) + .with_name(format!("{pub_key}:{i}")) + .utxo_validation(false), + ) + }) + }), + Some(config_with_preconfirmations()), + ) + .await; + + let sentry = &validators[0]; + + // When + let client_sentry = FuelClient::from(sentry.node.bound_address); + let mut tx_statuses_subscriber = client_sentry + .submit_and_await_status(&tx) + .await + .expect("Should be able to subscribe for events"); + + assert!(matches!( + tx_statuses_subscriber.next().await.unwrap().unwrap(), + TransactionStatus::Submitted { .. } + )); + + assert!(matches!( + tx_statuses_subscriber.next().await.unwrap().unwrap(), + TransactionStatus::SqueezedOut { .. } + )); +} diff --git a/tests/tests/regenesis.rs b/tests/tests/regenesis.rs index a9811dc314a..2d025c9a4e5 100644 --- a/tests/tests/regenesis.rs +++ b/tests/tests/regenesis.rs @@ -296,7 +296,7 @@ async fn test_regenesis_processed_transactions_are_preserved() -> anyhow::Result .finalize_as_transaction(); core.client.submit_and_await_commit(&tx).await.unwrap(); - let TransactionStatus::SqueezedOut { reason, .. } = + let TransactionStatus::SqueezedOut { reason } = core.client.submit_and_await_commit(&tx).await.unwrap() else { panic!("Expected transaction to be squeezed out") From 94c0aae64d52b9ea23aebcff7157620950a00549 Mon Sep 17 00:00:00 2001 From: green Date: Thu, 20 Mar 2025 03:58:27 -0400 Subject: [PATCH 65/76] Only inform subscribers about squeezed out tx if the source is the user --- crates/services/txpool_v2/src/service.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/services/txpool_v2/src/service.rs b/crates/services/txpool_v2/src/service.rs index 8bb03681bcc..0f39516b1d5 100644 --- a/crates/services/txpool_v2/src/service.rs +++ b/crates/services/txpool_v2/src/service.rs @@ -420,11 +420,10 @@ where InsertionSource::RPC { response_channel } => { if let Some(channel) = response_channel { let _ = channel.send(Err(error)); + self.tx_status_manager.status_update(tx_id, tx_status); } } } - - self.tx_status_manager.status_update(tx_id, tx_status); } } } From fc5f4c20bf188cfb10b95fcdd7a94ff513c5155e Mon Sep 17 00:00:00 2001 From: green Date: Thu, 20 Mar 2025 04:01:58 -0400 Subject: [PATCH 66/76] Use Open block production for the preconfigmration gossipsub tests --- crates/fuel-core/src/p2p_test_helpers.rs | 1 - tests/tests/preconfirmations_gossip.rs | 15 +++++++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/crates/fuel-core/src/p2p_test_helpers.rs b/crates/fuel-core/src/p2p_test_helpers.rs index adfc1bed685..924fdfbd76f 100644 --- a/crates/fuel-core/src/p2p_test_helpers.rs +++ b/crates/fuel-core/src/p2p_test_helpers.rs @@ -364,7 +364,6 @@ pub async fn make_nodes( ); let mut test_txs = Vec::with_capacity(0); - node_config.block_production = Trigger::Instant; if let Some(( ProducerSetup { diff --git a/tests/tests/preconfirmations_gossip.rs b/tests/tests/preconfirmations_gossip.rs index b4bbecb6513..29dafc05538 100644 --- a/tests/tests/preconfirmations_gossip.rs +++ b/tests/tests/preconfirmations_gossip.rs @@ -1,7 +1,10 @@ -use std::hash::{ - DefaultHasher, - Hash, - Hasher, +use std::{ + hash::{ + DefaultHasher, + Hash, + Hasher, + }, + time::Duration, }; use fuel_core::{ @@ -18,6 +21,7 @@ use fuel_core_client::client::{ types::TransactionStatus, FuelClient, }; +use fuel_core_poa::Trigger; use fuel_core_types::{ fuel_asm::{ op, @@ -47,6 +51,9 @@ fn config_with_preconfirmations() -> Config { let mut config = Config::local_node(); config.p2p.as_mut().unwrap().subscribe_to_pre_confirmations = true; + config.block_production = Trigger::Open { + period: Duration::from_secs(5), + }; config } From d03eff72d6069d022b31bf87aa71e487f2bdcd26 Mon Sep 17 00:00:00 2001 From: AurelienFT Date: Thu, 20 Mar 2025 13:59:26 +0100 Subject: [PATCH 67/76] sort toml --- crates/types/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/types/Cargo.toml b/crates/types/Cargo.toml index 3baac2a29ff..547a1002a98 100644 --- a/crates/types/Cargo.toml +++ b/crates/types/Cargo.toml @@ -39,8 +39,8 @@ zeroize = "1.5" [dev-dependencies] aws-config = { version = "1.1.7", features = ["behavior-version-latest"] } fuel-core-types = { path = ".", features = ["test-helpers", "serde"] } -tokio = { workspace = true, features = ["macros"] } postcard = { workspace = true } +tokio = { workspace = true, features = ["macros"] } [features] default = ["std"] From 458a5c3b4f18cc4429447fc46205fad8e21dc704 Mon Sep 17 00:00:00 2001 From: AurelienFT Date: Thu, 20 Mar 2025 14:15:12 +0100 Subject: [PATCH 68/76] Update status update to work when transaction comes from p2p but not p2p sync. Useful if you are listening through a sentry that is not the same you submitted tx (can happens with load balancing) --- crates/services/txpool_v2/src/pool_worker.rs | 3 +++ crates/services/txpool_v2/src/service.rs | 25 +++++++++++++++----- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/crates/services/txpool_v2/src/pool_worker.rs b/crates/services/txpool_v2/src/pool_worker.rs index d9f0ee14e2c..0096f09f2a0 100644 --- a/crates/services/txpool_v2/src/pool_worker.rs +++ b/crates/services/txpool_v2/src/pool_worker.rs @@ -182,6 +182,7 @@ pub(super) enum InsertionSource { P2P { from_peer_info: GossipsubMessageInfo, }, + P2PSync, RPC { response_channel: Option>>, }, @@ -232,6 +233,7 @@ pub(super) enum ExtendedInsertionSource { P2P { from_peer_info: GossipsubMessageInfo, }, + P2PSync, RPC { tx: Arc, response_channel: Option>>, @@ -379,6 +381,7 @@ where InsertionSource::P2P { from_peer_info } => { ExtendedInsertionSource::P2P { from_peer_info } } + InsertionSource::P2PSync => ExtendedInsertionSource::P2PSync, InsertionSource::RPC { response_channel } => { let tx: Transaction = self .pool diff --git a/crates/services/txpool_v2/src/service.rs b/crates/services/txpool_v2/src/service.rs index 0f39516b1d5..7b426c09581 100644 --- a/crates/services/txpool_v2/src/service.rs +++ b/crates/services/txpool_v2/src/service.rs @@ -164,6 +164,9 @@ pub enum WritePoolRequest { InsertTxs { transactions: Vec>, }, + InsertTxsSync { + transactions: Vec>, + }, InsertTx { transaction: Arc, response_channel: oneshot::Sender>, @@ -340,7 +343,10 @@ where fn process_write(&self, write_pool_request: WritePoolRequest) { match write_pool_request { WritePoolRequest::InsertTxs { transactions } => { - self.insert_transactions(transactions); + self.insert_transactions(transactions, false); + } + WritePoolRequest::InsertTxsSync { transactions } => { + self.insert_transactions(transactions, true); } WritePoolRequest::InsertTx { transaction, @@ -351,6 +357,7 @@ where transaction, None, Some(response_channel), + false ); self.transaction_verifier_process @@ -378,6 +385,7 @@ where GossipsubMessageAcceptance::Accept, ); } + ExtendedInsertionSource::P2PSync => {}, ExtendedInsertionSource::RPC { response_channel, tx, @@ -416,25 +424,27 @@ where from_peer_info, GossipsubMessageAcceptance::Ignore, ); + self.tx_status_manager.status_update(tx_id, tx_status); } + InsertionSource::P2PSync => {}, InsertionSource::RPC { response_channel } => { if let Some(channel) = response_channel { let _ = channel.send(Err(error)); - self.tx_status_manager.status_update(tx_id, tx_status); } + self.tx_status_manager.status_update(tx_id, tx_status); } } } } } - fn insert_transactions(&self, transactions: Vec>) { + fn insert_transactions(&self, transactions: Vec>, from_p2p_sync: bool) { for transaction in transactions { let Ok(reservation) = self.transaction_verifier_process.reserve() else { tracing::error!("Failed to insert transactions: Out of capacity"); continue }; - let op = self.insert_transaction(transaction, None, None); + let op = self.insert_transaction(transaction, None, None, from_p2p_sync); self.transaction_verifier_process .spawn_reserved(reservation, op); @@ -446,6 +456,7 @@ where transaction: Arc, from_peer_info: Option, response_channel: Option>>, + from_p2p_sync: bool, ) -> impl FnOnce() + Send + 'static { let metrics = self.metrics; if metrics { @@ -516,6 +527,8 @@ where let source = if let Some(from_peer_info) = from_peer_info { InsertionSource::P2P { from_peer_info } + } else if from_p2p_sync { + InsertionSource::P2PSync } else { InsertionSource::RPC { response_channel } }; @@ -557,7 +570,7 @@ where message_id, peer_id, }); - let op = self.insert_transaction(Arc::new(tx), info, None); + let op = self.insert_transaction(Arc::new(tx), info, None, false); self.transaction_verifier_process .spawn_reserved(reservation, op); } @@ -645,7 +658,7 @@ where // Verifying and insert them, not a big deal if we fail to insert them let _ = txs_insert_sender - .try_send(WritePoolRequest::InsertTxs { transactions: txs }); + .try_send(WritePoolRequest::InsertTxsSync { transactions: txs }); } }); } From 027ec2879437983339353a97ff2d90f6d83eb72f Mon Sep 17 00:00:00 2001 From: AurelienFT Date: Thu, 20 Mar 2025 14:21:06 +0100 Subject: [PATCH 69/76] Update consensus module tests --- .../src/pre_confirmation_signature_service/tests.rs | 8 ++++++-- crates/services/txpool_v2/src/service.rs | 12 ++++++++---- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/crates/services/consensus_module/poa/src/pre_confirmation_signature_service/tests.rs b/crates/services/consensus_module/poa/src/pre_confirmation_signature_service/tests.rs index e42db3ba907..5f12cc11c4b 100644 --- a/crates/services/consensus_module/poa/src/pre_confirmation_signature_service/tests.rs +++ b/crates/services/consensus_module/poa/src/pre_confirmation_signature_service/tests.rs @@ -384,7 +384,8 @@ async fn run__key_rotation_trigger_will_broadcast_generated_key_with_correct_sig } #[tokio::test] -async fn run__will_rebroadcast_generated_key_with_correct_signature_after_1_second() { +async fn run__will_rebroadcast_generated_key_with_correct_signature_after_1_second_with_nonce_increased( +) { // Given let period = Duration::from_secs(1); let generated_key = "some generated key"; @@ -427,7 +428,10 @@ async fn run__will_rebroadcast_generated_key_with_correct_signature_after_1_seco dummy_signature: dummy_signature.into(), }; - assert_eq!(actual_1, actual_2); + assert_eq!(actual_1.data.expiration, actual_2.data.expiration); + assert_eq!(actual_1.data.public_key, actual_2.data.public_key); + assert_eq!(actual_1.data.nonce.saturating_add(1), actual_2.data.nonce); + assert_eq!(actual_1.dummy_signature, actual_2.dummy_signature); assert_eq!(expected, actual_1); } diff --git a/crates/services/txpool_v2/src/service.rs b/crates/services/txpool_v2/src/service.rs index 7b426c09581..fac1eb5581a 100644 --- a/crates/services/txpool_v2/src/service.rs +++ b/crates/services/txpool_v2/src/service.rs @@ -357,7 +357,7 @@ where transaction, None, Some(response_channel), - false + false, ); self.transaction_verifier_process @@ -385,7 +385,7 @@ where GossipsubMessageAcceptance::Accept, ); } - ExtendedInsertionSource::P2PSync => {}, + ExtendedInsertionSource::P2PSync => {} ExtendedInsertionSource::RPC { response_channel, tx, @@ -426,7 +426,7 @@ where ); self.tx_status_manager.status_update(tx_id, tx_status); } - InsertionSource::P2PSync => {}, + InsertionSource::P2PSync => {} InsertionSource::RPC { response_channel } => { if let Some(channel) = response_channel { let _ = channel.send(Err(error)); @@ -438,7 +438,11 @@ where } } - fn insert_transactions(&self, transactions: Vec>, from_p2p_sync: bool) { + fn insert_transactions( + &self, + transactions: Vec>, + from_p2p_sync: bool, + ) { for transaction in transactions { let Ok(reservation) = self.transaction_verifier_process.reserve() else { tracing::error!("Failed to insert transactions: Out of capacity"); From 83cb172701d9f59fbd1f4265a456e2fa149a39fc Mon Sep 17 00:00:00 2001 From: AurelienFT Date: Thu, 20 Mar 2025 15:44:09 +0100 Subject: [PATCH 70/76] Update signature verification to sign what is actually being verified --- .../services/tx_status_manager/src/service.rs | 2 +- tests/tests/preconfirmations_gossip.rs | 23 +++++++++++++++---- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/crates/services/tx_status_manager/src/service.rs b/crates/services/tx_status_manager/src/service.rs index d35063c5826..31da4a6be92 100644 --- a/crates/services/tx_status_manager/src/service.rs +++ b/crates/services/tx_status_manager/src/service.rs @@ -158,7 +158,7 @@ impl SignatureVerification { delegate_key: &DelegatePublicKey, sealed: &Sealed, ) -> bool { - let bytes = match postcard::to_allocvec(&sealed.entity) { + let bytes = match postcard::to_allocvec(&sealed.entity.preconfirmations) { Ok(bytes) => bytes, Err(e) => { tracing::warn!("Failed to serialize preconfirmation: {e:?}"); diff --git a/tests/tests/preconfirmations_gossip.rs b/tests/tests/preconfirmations_gossip.rs index 29dafc05538..29cbea3d259 100644 --- a/tests/tests/preconfirmations_gossip.rs +++ b/tests/tests/preconfirmations_gossip.rs @@ -47,12 +47,13 @@ use rand::{ SeedableRng, }; -fn config_with_preconfirmations() -> Config { +fn config_with_preconfirmations(block_production_period: Duration) -> Config { let mut config = Config::local_node(); config.p2p.as_mut().unwrap().subscribe_to_pre_confirmations = true; + config.pre_confirmation_signature_service.key_rotation_interval = block_production_period.checked_div(4).unwrap(); config.block_production = Trigger::Open { - period: Duration::from_secs(5), + period: block_production_period }; config @@ -62,6 +63,7 @@ fn config_with_preconfirmations() -> Config { async fn preconfirmation__propagate_p2p_after_successful_execution() { let mut rng = rand::thread_rng(); let address = Address::new([0; 32]); + let block_production_period = Duration::from_secs(8); let gas_limit = 1_000_000; let amount = 10; @@ -132,12 +134,15 @@ async fn preconfirmation__propagate_p2p_after_successful_execution() { ) }) }), - Some(config_with_preconfirmations()), + Some(config_with_preconfirmations(block_production_period)), ) .await; let sentry = &validators[0]; + // Sleep to let time for exchange preconfirmations delegate public keys + tokio::time::sleep(block_production_period.checked_div(2).unwrap()).await; + // When let client_sentry = FuelClient::from(sentry.node.bound_address); let mut tx_statuses_subscriber = client_sentry @@ -199,6 +204,7 @@ async fn preconfirmation__propagate_p2p_after_successful_execution() { async fn preconfirmation__propagate_p2p_after_failed_execution() { let mut rng = rand::thread_rng(); let address = Address::new([0; 32]); + let block_production_period = Duration::from_secs(8); let gas_limit = 1_000_000; let amount = 10; @@ -269,12 +275,15 @@ async fn preconfirmation__propagate_p2p_after_failed_execution() { ) }) }), - Some(config_with_preconfirmations()), + Some(config_with_preconfirmations(block_production_period)), ) .await; let sentry = &validators[0]; + // Sleep to let time for exchange preconfirmations delegate public keys + tokio::time::sleep(block_production_period.checked_div(2).unwrap()).await; + // When let client_sentry = FuelClient::from(sentry.node.bound_address); let mut tx_statuses_subscriber = client_sentry @@ -336,6 +345,7 @@ async fn preconfirmation__propagate_p2p_after_failed_execution() { async fn preconfirmation__propagate_p2p_after_squeezed_out_on_producer() { let mut rng = rand::thread_rng(); + let block_production_period = Duration::from_secs(8); let gas_limit = 1_000_000; let tx = TransactionBuilder::script( vec![op::ret(RegId::ONE)].into_iter().collect(), @@ -402,12 +412,15 @@ async fn preconfirmation__propagate_p2p_after_squeezed_out_on_producer() { ) }) }), - Some(config_with_preconfirmations()), + Some(config_with_preconfirmations(block_production_period)), ) .await; let sentry = &validators[0]; + // Sleep to let time for exchange preconfirmations delegate public keys + tokio::time::sleep(block_production_period.checked_div(2).unwrap()).await; + // When let client_sentry = FuelClient::from(sentry.node.bound_address); let mut tx_statuses_subscriber = client_sentry From be4d66778ceede7c9549759a71627a15e082fefb Mon Sep 17 00:00:00 2001 From: AurelienFT Date: Thu, 20 Mar 2025 16:38:20 +0100 Subject: [PATCH 71/76] Update preconfirmation with concrete type to allow creation of the correct type for p2p --- .../pre_confirmation_signature/broadcast.rs | 46 ++++---- .../src/pre_confirmation_signature_service.rs | 39 ++++--- .../broadcast.rs | 6 +- .../tests.rs | 51 +++++---- .../services/tx_status_manager/src/service.rs | 2 +- tests/tests/preconfirmations_gossip.rs | 104 +----------------- 6 files changed, 74 insertions(+), 174 deletions(-) diff --git a/crates/fuel-core/src/service/adapters/consensus_module/poa/pre_confirmation_signature/broadcast.rs b/crates/fuel-core/src/service/adapters/consensus_module/poa/pre_confirmation_signature/broadcast.rs index 5bc2808abe4..fe6373f0336 100644 --- a/crates/fuel-core/src/service/adapters/consensus_module/poa/pre_confirmation_signature/broadcast.rs +++ b/crates/fuel-core/src/service/adapters/consensus_module/poa/pre_confirmation_signature/broadcast.rs @@ -24,35 +24,29 @@ use fuel_core_types::{ SignedByBlockProducerDelegation, SignedPreconfirmationByDelegate, }, - preconfirmation::{ - Preconfirmation, - Preconfirmations, - }, + preconfirmation::Preconfirmations, }, - tai64::Tai64, }; use std::sync::Arc; impl Broadcast for P2PAdapter { type ParentKey = FuelBlockSigner; type DelegateKey = Ed25519Key; - type Preconfirmations = Vec; + type Preconfirmations = Preconfirmations; async fn broadcast_preconfirmations( &mut self, preconfirmations: Self::Preconfirmations, signature: ::Signature, - expiration: Tai64, ) -> PreConfServiceResult<()> { if let Some(p2p) = &self.service { - let entity = Preconfirmations { - expiration, - preconfirmations, - }; let signature_bytes = signature.to_bytes(); let signature = Bytes64::new(signature_bytes); let preconfirmations = Arc::new(PreConfirmationMessage::Preconfirmations( - SignedPreconfirmationByDelegate { entity, signature }, + SignedPreconfirmationByDelegate { + entity: preconfirmations, + signature, + }, )); p2p.broadcast_preconfirmations(preconfirmations) .map_err(|e| PreConfServiceError::Broadcast(format!("{e:?}")))?; @@ -112,22 +106,24 @@ mod tests { let peer_report_config = PeerReportConfig::default(); let service = Some(shared_state); let mut adapter = P2PAdapter::new(service, peer_report_config); - let preconfirmations = vec![Preconfirmation { - tx_id: Default::default(), - status: PreconfirmationStatus::Failure { - tx_pointer: Default::default(), - total_gas: 0, - total_fee: 0, - receipts: vec![], - outputs: vec![], - }, - }]; + let preconfirmations = Preconfirmations { + preconfirmations: vec![Preconfirmation { + tx_id: Default::default(), + status: PreconfirmationStatus::Failure { + tx_pointer: Default::default(), + total_gas: 0, + total_fee: 0, + receipts: vec![], + outputs: vec![], + }, + }], + expiration: Tai64::UNIX_EPOCH, + }; let signature = ed25519::Signature::from_bytes(&[5u8; 64]); - let expiration = Tai64::UNIX_EPOCH; // when adapter - .broadcast_preconfirmations(preconfirmations.clone(), signature, expiration) + .broadcast_preconfirmations(preconfirmations.clone(), signature) .await .unwrap(); @@ -138,7 +134,7 @@ mod tests { TaskRequest::BroadcastPreConfirmations(inner) if pre_conf_matches_expected_values( &inner, - &preconfirmations, + &preconfirmations.preconfirmations, &Bytes64::new(signature.to_bytes()), &expiration, ) diff --git a/crates/services/consensus_module/poa/src/pre_confirmation_signature_service.rs b/crates/services/consensus_module/poa/src/pre_confirmation_signature_service.rs index 8d17f61465e..a04d1d7ca21 100644 --- a/crates/services/consensus_module/poa/src/pre_confirmation_signature_service.rs +++ b/crates/services/consensus_module/poa/src/pre_confirmation_signature_service.rs @@ -1,5 +1,3 @@ -use std::fmt::Debug; - use error::Result; use fuel_core_services::{ try_or_continue, @@ -12,9 +10,15 @@ use fuel_core_services::{ TaskNextAction, }; use fuel_core_types::{ - services::p2p::{ - DelegatePreConfirmationKey, - Sealed, + services::{ + p2p::{ + DelegatePreConfirmationKey, + Sealed, + }, + preconfirmation::{ + Preconfirmation, + Preconfirmations, + }, }, tai64::Tai64, }; @@ -85,10 +89,10 @@ pub struct PreConfirmationSignatureTask< } #[async_trait::async_trait] -impl RunnableService +impl RunnableService for UninitializedPreConfirmationSignatureTask where - TxRcv: TxReceiver, + TxRcv: TxReceiver>, Brdcst: Broadcast< DelegateKey = DelegateKey, ParentKey = Parent, @@ -98,7 +102,6 @@ where Trigger: KeyRotationTrigger, DelegateKey: SigningKey, Parent: ParentSignature, - Preconfirmations: serde::Serialize + Send + Debug, { const NAME: &'static str = "PreconfirmationSignatureTask"; type SharedData = EmptyShared; @@ -206,10 +209,10 @@ where Ok((new_delegate_key, sealed)) } -impl RunnableTask +impl RunnableTask for PreConfirmationSignatureTask where - TxRcv: TxReceiver, + TxRcv: TxReceiver>, Brdcst: Broadcast< DelegateKey = DelegateKey, ParentKey = Parent, @@ -219,7 +222,6 @@ where Trigger: KeyRotationTrigger, DelegateKey: SigningKey, Parent: ParentSignature, - Preconfirmations: serde::Serialize + Send + Debug, { async fn run(&mut self, watcher: &mut StateWatcher) -> TaskNextAction { tracing::debug!("Running pre-confirmation task"); @@ -229,11 +231,15 @@ where } res = self.tx_receiver.receive() => { tracing::debug!("Received transactions"); - let pre_confirmations = try_or_stop!(res); - let signature = try_or_stop!(self.current_delegate_key.sign(&pre_confirmations)); + let preconfirmations = try_or_stop!(res); let expiration = self.current_delegate_key.expiration(); + let pre_confirmations = Preconfirmations { + expiration, + preconfirmations, + }; + let signature = try_or_stop!(self.current_delegate_key.sign(&pre_confirmations)); try_or_continue!( - self.broadcast.broadcast_preconfirmations(pre_confirmations, signature, expiration).await, + self.broadcast.broadcast_preconfirmations(pre_confirmations, signature).await, |err| tracing::error!("Failed to broadcast pre-confirmations: {:?}", err) ); TaskNextAction::Continue @@ -280,7 +286,7 @@ pub type Service = ServiceRunner< UninitializedPreConfirmationSignatureTask, >; -pub fn new_service( +pub fn new_service( config: config::Config, preconfirmation_receiver: TxRcv, p2p_adapter: Brdcst, @@ -289,7 +295,7 @@ pub fn new_service anyhow::Result> where - TxRcv: TxReceiver, + TxRcv: TxReceiver>, Brdcst: Broadcast< DelegateKey = DelegateKey, ParentKey = Parent, @@ -299,7 +305,6 @@ where Trigger: KeyRotationTrigger, DelegateKey: SigningKey, Parent: ParentSignature, - Preconfirmations: serde::Serialize + Send + Debug, { Ok(Service::new(UninitializedPreConfirmationSignatureTask { tx_receiver: preconfirmation_receiver, diff --git a/crates/services/consensus_module/poa/src/pre_confirmation_signature_service/broadcast.rs b/crates/services/consensus_module/poa/src/pre_confirmation_signature_service/broadcast.rs index 6a605460bed..72d9d3ab25c 100644 --- a/crates/services/consensus_module/poa/src/pre_confirmation_signature_service/broadcast.rs +++ b/crates/services/consensus_module/poa/src/pre_confirmation_signature_service/broadcast.rs @@ -4,10 +4,7 @@ use crate::pre_confirmation_signature_service::{ signing_key::SigningKey, }; use core::future::Future; -use fuel_core_types::{ - services::p2p::DelegatePreConfirmationKey, - tai64::Tai64, -}; +use fuel_core_types::services::p2p::DelegatePreConfirmationKey; #[allow(type_alias_bounds)] pub type PublicKey @@ -31,7 +28,6 @@ pub trait Broadcast: Send { &mut self, message: Self::Preconfirmations, signature: Signature, - expiration: Tai64, ) -> impl Future> + Send; fn broadcast_delegate_key( diff --git a/crates/services/consensus_module/poa/src/pre_confirmation_signature_service/tests.rs b/crates/services/consensus_module/poa/src/pre_confirmation_signature_service/tests.rs index 5f12cc11c4b..bbe9c458731 100644 --- a/crates/services/consensus_module/poa/src/pre_confirmation_signature_service/tests.rs +++ b/crates/services/consensus_module/poa/src/pre_confirmation_signature_service/tests.rs @@ -12,9 +12,10 @@ use fuel_core_services::StateWatcher; use fuel_core_types::{ fuel_tx::{ Bytes32, - Transaction, + TxId, }, fuel_types::BlockHeight, + services::preconfirmation::PreconfirmationStatus, }; use std::time::Duration; @@ -26,13 +27,13 @@ pub enum Status { } pub struct FakeTxReceiver { - recv: tokio::sync::mpsc::Receiver>, + recv: tokio::sync::mpsc::Receiver>, } impl TxReceiver for FakeTxReceiver { - type Txs = Vec<(Transaction, Status)>; + type Txs = Vec; - async fn receive(&mut self) -> Result> { + async fn receive(&mut self) -> Result> { let txs = self.recv.recv().await; match txs { Some(txs) => Ok(txs), @@ -45,25 +46,22 @@ pub struct FakeBroadcast { delegation_key_sender: tokio::sync::mpsc::Sender< FakeParentSignedData>, >, - tx_sender: - tokio::sync::mpsc::Sender>>, + tx_sender: tokio::sync::mpsc::Sender>, } impl Broadcast for FakeBroadcast { type DelegateKey = FakeSigningKey; type ParentKey = FakeParentSignature; - type Preconfirmations = Vec<(Transaction, Status)>; + type Preconfirmations = Preconfirmations; async fn broadcast_preconfirmations( &mut self, message: Self::Preconfirmations, signature: Signature, - expiration: Tai64, ) -> Result<()> { let txs = FakeDelegateSignedData { data: message, dummy_signature: signature, - key_expiration: expiration, }; self.tx_sender.send(txs).await.map_err(|error| { Error::Broadcast(format!("Could not send {:?} over channel", error)) @@ -100,7 +98,6 @@ pub struct FakeParentSignature { pub struct FakeDelegateSignedData { data: T, dummy_signature: String, - key_expiration: Tai64, } #[derive(Debug, Clone, Eq, PartialEq)] @@ -192,8 +189,8 @@ pub struct TestImplHandles { FakeParentSignedData>, >, pub broadcast_tx_handle: - tokio::sync::mpsc::Receiver>>, - pub tx_sender_handle: tokio::sync::mpsc::Sender>, + tokio::sync::mpsc::Receiver>, + pub tx_sender_handle: tokio::sync::mpsc::Sender>, } #[derive(Default)] @@ -292,7 +289,7 @@ impl TaskBuilder { tokio::sync::mpsc::Receiver< FakeParentSignedData>, >, - tokio::sync::mpsc::Receiver>>, + tokio::sync::mpsc::Receiver>, ) { let (delegation_key_sender, delegation_key_receiver) = tokio::sync::mpsc::channel(10); @@ -308,7 +305,7 @@ impl TaskBuilder { &self, ) -> ( FakeTxReceiver, - tokio::sync::mpsc::Sender>, + tokio::sync::mpsc::Sender>, ) { let (tx_sender, tx_receiver) = tokio::sync::mpsc::channel(1); let tx_receiver = FakeTxReceiver { recv: tx_receiver }; @@ -470,18 +467,18 @@ async fn run__received_tx_will_be_broadcast_with_current_delegate_key_signature( // when let txs = vec![ - ( - Transaction::default(), - Status::Success { - height: BlockHeight::from(1), + Preconfirmation { + tx_id: TxId::zeroed(), + status: PreconfirmationStatus::SqueezedOut { + reason: "foo".into(), }, - ), - ( - Transaction::default(), - Status::Fail { - height: BlockHeight::from(2), + }, + Preconfirmation { + tx_id: TxId::zeroed(), + status: PreconfirmationStatus::SqueezedOut { + reason: "bar".into(), }, - ), + }, ]; tokio::task::spawn(async move { let _ = task.run(&mut state_watcher).await; @@ -498,9 +495,11 @@ async fn run__received_tx_will_be_broadcast_with_current_delegate_key_signature( let actual = handles.broadcast_tx_handle.try_recv().unwrap(); let dummy_signature = current_delegate_key.to_string(); let expected = FakeDelegateSignedData { - data: txs, + data: Preconfirmations { + expiration: Tai64::UNIX_EPOCH, + preconfirmations: txs, + }, dummy_signature, - key_expiration: Tai64::UNIX_EPOCH, }; assert_eq!(expected, actual); } diff --git a/crates/services/tx_status_manager/src/service.rs b/crates/services/tx_status_manager/src/service.rs index 31da4a6be92..d35063c5826 100644 --- a/crates/services/tx_status_manager/src/service.rs +++ b/crates/services/tx_status_manager/src/service.rs @@ -158,7 +158,7 @@ impl SignatureVerification { delegate_key: &DelegatePublicKey, sealed: &Sealed, ) -> bool { - let bytes = match postcard::to_allocvec(&sealed.entity.preconfirmations) { + let bytes = match postcard::to_allocvec(&sealed.entity) { Ok(bytes) => bytes, Err(e) => { tracing::warn!("Failed to serialize preconfirmation: {e:?}"); diff --git a/tests/tests/preconfirmations_gossip.rs b/tests/tests/preconfirmations_gossip.rs index 29cbea3d259..1d45155f873 100644 --- a/tests/tests/preconfirmations_gossip.rs +++ b/tests/tests/preconfirmations_gossip.rs @@ -51,9 +51,11 @@ fn config_with_preconfirmations(block_production_period: Duration) -> Config { let mut config = Config::local_node(); config.p2p.as_mut().unwrap().subscribe_to_pre_confirmations = true; - config.pre_confirmation_signature_service.key_rotation_interval = block_production_period.checked_div(4).unwrap(); + config + .pre_confirmation_signature_service + .key_rotation_interval = block_production_period.checked_div(4).unwrap(); config.block_production = Trigger::Open { - period: block_production_period + period: block_production_period, }; config @@ -340,101 +342,3 @@ async fn preconfirmation__propagate_p2p_after_failed_execution() { TransactionStatus::Failure { .. } )); } - -#[tokio::test] -async fn preconfirmation__propagate_p2p_after_squeezed_out_on_producer() { - let mut rng = rand::thread_rng(); - - let block_production_period = Duration::from_secs(8); - let gas_limit = 1_000_000; - let tx = TransactionBuilder::script( - vec![op::ret(RegId::ONE)].into_iter().collect(), - vec![], - ) - .script_gas_limit(gas_limit) - .add_unsigned_coin_input( - SecretKey::random(&mut rng), - rng.gen(), - 10, - Default::default(), - Default::default(), - ) - .add_output(Output::Change { - to: Default::default(), - amount: 0, - asset_id: Default::default(), - }) - .finalize_as_transaction(); - - // Create a random seed based on the test parameters. - let mut hasher = DefaultHasher::new(); - let num_txs = 1; - let num_validators = 1; - let num_partitions = 1; - (num_txs, num_validators, num_partitions, line!()).hash(&mut hasher); - let mut rng = StdRng::seed_from_u64(hasher.finish()); - - // Create a set of key pairs. - let secrets: Vec<_> = (0..1).map(|_| SecretKey::random(&mut rng)).collect(); - let pub_keys: Vec<_> = secrets - .clone() - .into_iter() - .map(|secret| Input::owner(&secret.public_key())) - .collect(); - - // Create a producer for each key pair and a set of validators that share - // the same key pair. - - // Given - // Disable UTXO validation in TxPool so that the transaction is squeezed out by - // block production - let Nodes { - producers: _producers, - validators, - bootstrap_nodes: _dont_drop, - } = make_nodes( - pub_keys - .iter() - .map(|pub_key| Some(BootstrapSetup::new(*pub_key))), - secrets.clone().into_iter().enumerate().map(|(i, secret)| { - Some( - ProducerSetup::new(secret) - .with_name(format!("{}:producer", pub_keys[i])) - .utxo_validation(true), - ) - }), - pub_keys.iter().flat_map(|pub_key| { - (0..num_validators).map(move |i| { - Some( - ValidatorSetup::new(*pub_key) - .with_name(format!("{pub_key}:{i}")) - .utxo_validation(false), - ) - }) - }), - Some(config_with_preconfirmations(block_production_period)), - ) - .await; - - let sentry = &validators[0]; - - // Sleep to let time for exchange preconfirmations delegate public keys - tokio::time::sleep(block_production_period.checked_div(2).unwrap()).await; - - // When - let client_sentry = FuelClient::from(sentry.node.bound_address); - let mut tx_statuses_subscriber = client_sentry - .submit_and_await_status(&tx) - .await - .expect("Should be able to subscribe for events"); - - assert!(matches!( - tx_statuses_subscriber.next().await.unwrap().unwrap(), - TransactionStatus::Submitted { .. } - )); - - assert!(matches!( - tx_statuses_subscriber.next().await.unwrap().unwrap(), - TransactionStatus::SqueezedOut { .. } - )); -} From 5c90e7e08c0997a05bed2b1a983aaf8c656f9e55 Mon Sep 17 00:00:00 2001 From: AurelienFT Date: Thu, 20 Mar 2025 17:00:20 +0100 Subject: [PATCH 72/76] Update broadcast tests compilation --- .../poa/pre_confirmation_signature/broadcast.rs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/crates/fuel-core/src/service/adapters/consensus_module/poa/pre_confirmation_signature/broadcast.rs b/crates/fuel-core/src/service/adapters/consensus_module/poa/pre_confirmation_signature/broadcast.rs index fe6373f0336..0f1d2a26f77 100644 --- a/crates/fuel-core/src/service/adapters/consensus_module/poa/pre_confirmation_signature/broadcast.rs +++ b/crates/fuel-core/src/service/adapters/consensus_module/poa/pre_confirmation_signature/broadcast.rs @@ -94,8 +94,12 @@ mod tests { ed25519_dalek::VerifyingKey, services::{ p2p::ProtocolSignature, - preconfirmation::PreconfirmationStatus, + preconfirmation::{ + Preconfirmation, + PreconfirmationStatus, + }, }, + tai64::Tai64, }; #[tokio::test] @@ -136,7 +140,7 @@ mod tests { &inner, &preconfirmations.preconfirmations, &Bytes64::new(signature.to_bytes()), - &expiration, + preconfirmations.expiration, ) )); } @@ -145,15 +149,12 @@ mod tests { inner: &Arc, preconfirmations: &[Preconfirmation], signature: &Bytes64, - expiration: &Tai64, + expiration: Tai64, ) -> bool { - let entity = Preconfirmations { - expiration: *expiration, - preconfirmations: preconfirmations.to_vec(), - }; match &**inner { PreConfirmationMessage::Preconfirmations(signed_preconfirmation) => { - signed_preconfirmation.entity == entity + signed_preconfirmation.entity.preconfirmations == preconfirmations + && signed_preconfirmation.entity.expiration == expiration && signed_preconfirmation.signature == *signature } _ => false, From 767cb543a0f137001b0adee60af2ca898a746743 Mon Sep 17 00:00:00 2001 From: AurelienFT Date: Thu, 20 Mar 2025 17:40:08 +0100 Subject: [PATCH 73/76] fix compil --- crates/services/txpool_v2/src/service.rs | 1 + crates/types/src/services/p2p.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/services/txpool_v2/src/service.rs b/crates/services/txpool_v2/src/service.rs index fac1eb5581a..53dc476cab9 100644 --- a/crates/services/txpool_v2/src/service.rs +++ b/crates/services/txpool_v2/src/service.rs @@ -160,6 +160,7 @@ impl TryFrom for TransactionStatus { } } +#[allow(clippy::enum_variant_names)] pub enum WritePoolRequest { InsertTxs { transactions: Vec>, diff --git a/crates/types/src/services/p2p.rs b/crates/types/src/services/p2p.rs index 8766dfe5a08..5266a9a51c3 100644 --- a/crates/types/src/services/p2p.rs +++ b/crates/types/src/services/p2p.rs @@ -299,7 +299,7 @@ impl Serialize for NetworkableTransactionPool { Upload, }; - #[cfg(debug_assertions)] + #[cfg(all(debug_assertions, feature = "test-helpers"))] // When a new variant is added to `Transaction`, the `TransactionRef` // must also be updated to match the new variant. // This match statement will trigger a compilation error if a new variant is added. From bf4446856d097ea0441535a2a213d0559679abcc Mon Sep 17 00:00:00 2001 From: green Date: Fri, 21 Mar 2025 03:17:52 -0400 Subject: [PATCH 74/76] Putting `nonce` into delegation body invalidates signature, so moved that upper. Simplified tests, added test for the case of sqeuuze out. Removed `InsertTxsSync` from TxPool for simplicity. Instead, in the case of duplicated insertion error we just don't report new transaction status. When TxPool reports about squeeze out transaction status, we also try to send it to the pre confirmation service. In the case of the block producer it allows to broadcast squeeze out pre confirmation from TxPool as well. Fixed the wrong block time and trigger time for `Open` block production mode. Before it used old timestamp(the timestamp at the moment of start production instead of when it ends). THe trigger logic would trigger two blocks in a row, because we used start of the block production as a relative point, instead of end of the block production for `Open` mode. --- crates/fuel-core/src/service/adapters.rs | 18 ++- .../pre_confirmation_signature/broadcast.rs | 14 +- .../fuel-core/src/service/adapters/txpool.rs | 46 ++++++- crates/fuel-core/src/service/sub_services.rs | 10 +- .../src/pre_confirmation_signature_service.rs | 19 ++- .../broadcast.rs | 1 + .../tests.rs | 9 +- .../consensus_module/poa/src/service.rs | 26 +++- .../services/tx_status_manager/src/service.rs | 22 +-- crates/services/txpool_v2/src/error.rs | 9 ++ crates/services/txpool_v2/src/pool.rs | 18 ++- crates/services/txpool_v2/src/pool_worker.rs | 9 +- crates/services/txpool_v2/src/ports.rs | 3 +- crates/services/txpool_v2/src/service.rs | 38 +++--- crates/services/txpool_v2/src/tests/mocks.rs | 9 +- crates/types/src/services/p2p.rs | 9 +- .../types/src/services/transaction_status.rs | 6 + tests/tests/preconfirmations.rs | 65 +++------ tests/tests/preconfirmations_gossip.rs | 127 +++++++++++++++++- 19 files changed, 320 insertions(+), 138 deletions(-) diff --git a/crates/fuel-core/src/service/adapters.rs b/crates/fuel-core/src/service/adapters.rs index d4c1eb41d5d..406b7aff5b5 100644 --- a/crates/fuel-core/src/service/adapters.rs +++ b/crates/fuel-core/src/service/adapters.rs @@ -1,4 +1,7 @@ -use std::sync::Arc; +use std::{ + ops::Deref, + sync::Arc, +}; use tokio::sync::{ mpsc, watch, @@ -374,12 +377,9 @@ impl ExecutorAdapter { relayer_database: Database, config: fuel_core_upgradable_executor::config::Config, new_txs_watcher: watch::Receiver<()>, - preconfirmation_sender: mpsc::Sender>, - tx_status_manager_adapter: TxStatusManagerAdapter, + preconfirmation_sender: PreconfirmationSender, ) -> Self { let executor = Executor::new(database, relayer_database, config); - let preconfirmation_sender = - PreconfirmationSender::new(preconfirmation_sender, tx_status_manager_adapter); Self { executor: Arc::new(executor), new_txs_watcher, @@ -475,6 +475,14 @@ pub struct TxStatusManagerAdapter { tx_status_manager_shared_data: SharedData, } +impl Deref for TxStatusManagerAdapter { + type Target = SharedData; + + fn deref(&self) -> &Self::Target { + &self.tx_status_manager_shared_data + } +} + impl TxStatusManagerAdapter { pub fn new(tx_status_manager_shared_data: SharedData) -> Self { Self { diff --git a/crates/fuel-core/src/service/adapters/consensus_module/poa/pre_confirmation_signature/broadcast.rs b/crates/fuel-core/src/service/adapters/consensus_module/poa/pre_confirmation_signature/broadcast.rs index 0f1d2a26f77..c989f1d3a90 100644 --- a/crates/fuel-core/src/service/adapters/consensus_module/poa/pre_confirmation_signature/broadcast.rs +++ b/crates/fuel-core/src/service/adapters/consensus_module/poa/pre_confirmation_signature/broadcast.rs @@ -58,14 +58,15 @@ impl Broadcast for P2PAdapter { async fn broadcast_delegate_key( &mut self, delegate: DelegatePreConfirmationKey>, + nonce: u64, signature: ::Signature, ) -> PreConfServiceResult<()> { if let Some(p2p) = &self.service { - let sealed = SignedByBlockProducerDelegation { + let seal = SignedByBlockProducerDelegation { entity: delegate, signature, }; - let delegate_key = Arc::new(PreConfirmationMessage::Delegate(sealed)); + let delegate_key = Arc::new(PreConfirmationMessage::Delegate { seal, nonce }); p2p.broadcast_preconfirmations(delegate_key) .map_err(|e| PreConfServiceError::Broadcast(format!("{e:?}")))?; } @@ -173,13 +174,12 @@ mod tests { let delegate = DelegatePreConfirmationKey { public_key: Default::default(), expiration, - nonce: 0, }; let signature = ProtocolSignature::from_bytes([5u8; 64]); // when adapter - .broadcast_delegate_key(delegate.clone(), signature) + .broadcast_delegate_key(delegate.clone(), 0, signature) .await .unwrap(); @@ -206,10 +206,12 @@ mod tests { let entity = DelegatePreConfirmationKey { public_key: delegate_key, expiration, - nonce: 0, }; match &**inner { - PreConfirmationMessage::Delegate(signed_by_block_producer_delegation) => { + PreConfirmationMessage::Delegate { + seal: signed_by_block_producer_delegation, + .. + } => { signed_by_block_producer_delegation.entity == entity && signed_by_block_producer_delegation.signature == *signature } diff --git a/crates/fuel-core/src/service/adapters/txpool.rs b/crates/fuel-core/src/service/adapters/txpool.rs index 36ed5f8a572..d64931a028b 100644 --- a/crates/fuel-core/src/service/adapters/txpool.rs +++ b/crates/fuel-core/src/service/adapters/txpool.rs @@ -4,8 +4,8 @@ use crate::{ BlockImporterAdapter, ChainStateInfoProvider, P2PAdapter, + PreconfirmationSender, StaticGasPrice, - TxStatusManagerAdapter, }, }; use fuel_core_services::stream::BoxStream; @@ -50,7 +50,14 @@ use fuel_core_types::{ PeerId, TransactionGossipData, }, - transaction_status::TransactionStatus, + preconfirmation::{ + Preconfirmation, + PreconfirmationStatus, + }, + transaction_status::{ + statuses, + TransactionStatus, + }, }, }; use std::sync::Arc; @@ -231,13 +238,40 @@ impl ChainStateInfoProviderTrait for ChainStateInfoProvider { } } -impl TxStatusManager for TxStatusManagerAdapter { +impl TxStatusManager for PreconfirmationSender { fn status_update(&self, tx_id: TxId, tx_status: TransactionStatus) { - self.tx_status_manager_shared_data + let permit = self.sender_signature_service.try_reserve(); + + if let Ok(permit) = permit { + if let TransactionStatus::SqueezedOut(status) = &tx_status { + let preconfirmation = Preconfirmation { + tx_id, + status: PreconfirmationStatus::SqueezedOut { + reason: status.reason.clone(), + }, + }; + permit.send(vec![preconfirmation]); + } + } + + self.tx_status_manager_adapter .update_status(tx_id, tx_status); } - fn statuses_update(&self, statuses: Vec<(TxId, TransactionStatus)>) { - self.tx_status_manager_shared_data.update_statuses(statuses); + fn squeezed_out_txs(&self, statuses: Vec<(TxId, statuses::SqueezedOut)>) { + let permit = self.sender_signature_service.try_reserve(); + if let Ok(permit) = permit { + let preconfirmations = statuses + .iter() + .map(|(tx_id, status)| Preconfirmation { + tx_id: *tx_id, + status: PreconfirmationStatus::SqueezedOut { + reason: status.reason.clone(), + }, + }) + .collect(); + permit.send(preconfirmations); + } + self.tx_status_manager_adapter.update_statuses(statuses); } } diff --git a/crates/fuel-core/src/service/sub_services.rs b/crates/fuel-core/src/service/sub_services.rs index 69764bc85dc..1f3be74a39e 100644 --- a/crates/fuel-core/src/service/sub_services.rs +++ b/crates/fuel-core/src/service/sub_services.rs @@ -66,6 +66,7 @@ use crate::{ ExecutorAdapter, MaybeRelayerAdapter, PoAAdapter, + PreconfirmationSender, SharedMemoryPool, SystemTime, TxPoolAdapter, @@ -175,6 +176,10 @@ pub fn init_sub_services( ); let tx_status_manager_adapter = TxStatusManagerAdapter::new(tx_status_manager.shared.clone()); + let preconfirmation_sender = PreconfirmationSender::new( + preconfirmation_sender, + tx_status_manager_adapter.clone(), + ); let upgradable_executor_config = fuel_core_upgradable_executor::config::Config { forbid_fake_coins_default: config.utxo_validation, @@ -186,8 +191,7 @@ pub fn init_sub_services( database.relayer().clone(), upgradable_executor_config, new_txs_watcher, - preconfirmation_sender, - tx_status_manager_adapter.clone(), + preconfirmation_sender.clone(), ); let import_result_provider = ImportResultProvider::new(database.on_chain().clone(), executor.clone()); @@ -275,7 +279,7 @@ pub fn init_sub_services( universal_gas_price_provider.clone(), executor.clone(), new_txs_updater, - tx_status_manager_adapter.clone(), + preconfirmation_sender, ); let tx_pool_adapter = TxPoolAdapter::new(txpool.shared.clone()); diff --git a/crates/services/consensus_module/poa/src/pre_confirmation_signature_service.rs b/crates/services/consensus_module/poa/src/pre_confirmation_signature_service.rs index a04d1d7ca21..990ac304166 100644 --- a/crates/services/consensus_module/poa/src/pre_confirmation_signature_service.rs +++ b/crates/services/consensus_module/poa/src/pre_confirmation_signature_service.rs @@ -84,6 +84,7 @@ pub struct PreConfirmationSignatureTask< current_delegate_key: ExpiringKey, sealed_delegate_message: Sealed, Parent::Signature>, + nonce: u64, key_rotation_trigger: KeyRotationTrigger, echo_delegation_trigger: Interval, } @@ -134,12 +135,18 @@ where .await .map_err(|e| anyhow::anyhow!(e))?; + let mut nonce = 0; if let Err(e) = broadcast - .broadcast_delegate_key(sealed.entity.clone(), sealed.signature.clone()) + .broadcast_delegate_key( + sealed.entity.clone(), + nonce, + sealed.signature.clone(), + ) .await { tracing::error!("Failed to broadcast delegate key: {:?}", e); } + nonce = nonce.saturating_add(1); let initialized_task = PreConfirmationSignatureTask { tx_receiver, broadcast, @@ -147,6 +154,7 @@ where key_generator, current_delegate_key: new_delegate_key, sealed_delegate_message: sealed, + nonce, key_rotation_trigger, echo_delegation_trigger, }; @@ -173,7 +181,6 @@ where let message = DelegatePreConfirmationKey { public_key, expiration, - nonce: 0, }; const MAX_ATTEMPTS: usize = 5; @@ -256,22 +263,24 @@ where self.current_delegate_key = new_delegate_key; self.sealed_delegate_message = sealed.clone(); + self.nonce = 0; try_or_continue!( - self.broadcast.broadcast_delegate_key(sealed.entity, sealed.signature).await, + self.broadcast.broadcast_delegate_key(sealed.entity, self.nonce, sealed.signature).await, |err| tracing::error!("Failed to broadcast newly generated delegate key: {:?}", err) ); + self.nonce = self.nonce.saturating_add(1); TaskNextAction::Continue } _ = self.echo_delegation_trigger.tick() => { tracing::debug!("Echo delegation trigger"); - self.sealed_delegate_message.entity.nonce = self.sealed_delegate_message.entity.nonce.saturating_add(1); let sealed = self.sealed_delegate_message.clone(); try_or_continue!( - self.broadcast.broadcast_delegate_key(sealed.entity, sealed.signature).await, + self.broadcast.broadcast_delegate_key(sealed.entity, self.nonce, sealed.signature).await, |err| tracing::error!("Failed to re-broadcast delegate key: {:?}", err) ); + self.nonce = self.nonce.saturating_add(1); TaskNextAction::Continue } } diff --git a/crates/services/consensus_module/poa/src/pre_confirmation_signature_service/broadcast.rs b/crates/services/consensus_module/poa/src/pre_confirmation_signature_service/broadcast.rs index 72d9d3ab25c..770dfd59ca0 100644 --- a/crates/services/consensus_module/poa/src/pre_confirmation_signature_service/broadcast.rs +++ b/crates/services/consensus_module/poa/src/pre_confirmation_signature_service/broadcast.rs @@ -33,6 +33,7 @@ pub trait Broadcast: Send { fn broadcast_delegate_key( &mut self, delegate: DelegatePreConfirmationKey>, + nonce: u64, signature: ::Signature, ) -> impl Future> + Send; } diff --git a/crates/services/consensus_module/poa/src/pre_confirmation_signature_service/tests.rs b/crates/services/consensus_module/poa/src/pre_confirmation_signature_service/tests.rs index bbe9c458731..ab79eadf4a1 100644 --- a/crates/services/consensus_module/poa/src/pre_confirmation_signature_service/tests.rs +++ b/crates/services/consensus_module/poa/src/pre_confirmation_signature_service/tests.rs @@ -72,6 +72,7 @@ impl Broadcast for FakeBroadcast { async fn broadcast_delegate_key( &mut self, data: DelegatePreConfirmationKey>, + _: u64, signature: ::Signature, ) -> Result<()> { let delegate_key = FakeParentSignedData { @@ -226,7 +227,6 @@ impl TaskBuilder { let entity = DelegatePreConfirmationKey { public_key: Bytes32::zeroed(), expiration: Tai64::UNIX_EPOCH, - nonce: 0, }; let signature = parent_signature.dummy_signature.clone(); let period = self.period.unwrap_or(Duration::from_secs(60 * 60)); @@ -240,6 +240,7 @@ impl TaskBuilder { key_generator, current_delegate_key, sealed_delegate_message: Sealed { entity, signature }, + nonce: 0, key_rotation_trigger, echo_delegation_trigger: tokio::time::interval(period), }; @@ -372,7 +373,6 @@ async fn run__key_rotation_trigger_will_broadcast_generated_key_with_correct_sig data: DelegatePreConfirmationKey { public_key: Bytes32::zeroed(), expiration: expiration_time, - nonce: 0, }, dummy_signature: dummy_signature.into(), }; @@ -381,8 +381,7 @@ async fn run__key_rotation_trigger_will_broadcast_generated_key_with_correct_sig } #[tokio::test] -async fn run__will_rebroadcast_generated_key_with_correct_signature_after_1_second_with_nonce_increased( -) { +async fn run__will_rebroadcast_generated_key_with_correct_signature_after_1_second() { // Given let period = Duration::from_secs(1); let generated_key = "some generated key"; @@ -420,14 +419,12 @@ async fn run__will_rebroadcast_generated_key_with_correct_signature_after_1_seco data: DelegatePreConfirmationKey { public_key: Bytes32::zeroed(), expiration: expiration_time, - nonce: 0, }, dummy_signature: dummy_signature.into(), }; assert_eq!(actual_1.data.expiration, actual_2.data.expiration); assert_eq!(actual_1.data.public_key, actual_2.data.public_key); - assert_eq!(actual_1.data.nonce.saturating_add(1), actual_2.data.nonce); assert_eq!(actual_1.dummy_signature, actual_2.dummy_signature); assert_eq!(expected, actual_1); } diff --git a/crates/services/consensus_module/poa/src/service.rs b/crates/services/consensus_module/poa/src/service.rs index 2022c32359d..dd964f52a2c 100644 --- a/crates/services/consensus_module/poa/src/service.rs +++ b/crates/services/consensus_module/poa/src/service.rs @@ -252,10 +252,23 @@ where }, RequestType::Trigger => { let now = self.clock.now(); - if now > self.last_timestamp { - Ok(now) - } else { - self.next_time(RequestType::Manual) + match self.trigger { + Trigger::Open { period } => { + let expected_timestamp = + increase_time(self.last_timestamp, period)?; + if now > expected_timestamp { + Ok(now) + } else { + Ok(expected_timestamp) + } + } + _ => { + if now > self.last_timestamp { + Ok(now) + } else { + self.next_time(RequestType::Manual) + } + } } } } @@ -417,7 +430,10 @@ where // Update last block time self.last_height = height; self.last_timestamp = block_time; - self.last_block_created = last_block_created; + self.last_block_created = match self.trigger { + Trigger::Open { .. } => deadline.max(last_block_created), + _ => last_block_created, + }; Ok(()) } diff --git a/crates/services/tx_status_manager/src/service.rs b/crates/services/tx_status_manager/src/service.rs index d35063c5826..b9d76115104 100644 --- a/crates/services/tx_status_manager/src/service.rs +++ b/crates/services/tx_status_manager/src/service.rs @@ -41,7 +41,10 @@ use fuel_core_types::{ Preconfirmation, Preconfirmations, }, - transaction_status::TransactionStatus, + transaction_status::{ + statuses, + TransactionStatus, + }, }, tai64::Tai64, }; @@ -69,7 +72,7 @@ enum WriteRequest { status: TransactionStatus, }, UpdateStatuses { - statuses: Vec<(TxId, TransactionStatus)>, + statuses: Vec<(TxId, statuses::SqueezedOut)>, }, UpdatePreconfirmations { preconfirmations: Vec, @@ -108,7 +111,7 @@ impl SharedData { let _ = self.write_requests_sender.send(request); } - pub fn update_statuses(&self, statuses: Vec<(TxId, TransactionStatus)>) { + pub fn update_statuses(&self, statuses: Vec<(TxId, statuses::SqueezedOut)>) { let request = WriteRequest::UpdateStatuses { statuses }; let _ = self.write_requests_sender.send(request); } @@ -237,14 +240,14 @@ impl Task { preconfirmations: P2PPreConfirmationMessage, ) { match preconfirmations { - PreConfirmationMessage::Delegate(sealed) => { + PreConfirmationMessage::Delegate { seal, .. } => { tracing::debug!( "Received new delegate signature from peer: {:?}", - sealed.entity.public_key + seal.entity.public_key ); // TODO: Report peer for sending invalid delegation // https://github.com/FuelLabs/fuel-core/issues/2872 - let _ = self.signature_verification.add_new_delegate(&sealed); + let _ = self.signature_verification.add_new_delegate(&seal); } PreConfirmationMessage::Preconfirmations(sealed) => { tracing::debug!("Received new preconfirmations from peer"); @@ -314,7 +317,7 @@ impl RunnableTask for Task { } Some(WriteRequest::UpdateStatuses { statuses }) => { for (tx_id, status) in statuses { - self.manager.status_update(tx_id, status); + self.manager.status_update(tx_id, status.into()); } TaskNextAction::Continue } @@ -686,13 +689,12 @@ mod tests { let entity = DelegatePreConfirmationKey { public_key: delegate_public_key, expiration, - nonce: 0, }; let bytes = postcard::to_allocvec(&entity).unwrap(); let message = Message::new(&bytes); let signature = Signature::sign(&protocol_secret_key, &message); - let sealed = Sealed { entity, signature }; - let inner = P2PPreConfirmationMessage::Delegate(sealed); + let seal = Sealed { entity, signature }; + let inner = P2PPreConfirmationMessage::Delegate { seal, nonce: 0 }; GossipData { data: Some(inner), peer_id: Default::default(), diff --git a/crates/services/txpool_v2/src/error.rs b/crates/services/txpool_v2/src/error.rs index c731d1a3c04..9eecce52939 100644 --- a/crates/services/txpool_v2/src/error.rs +++ b/crates/services/txpool_v2/src/error.rs @@ -61,6 +61,15 @@ pub enum Error { }, } +impl Error { + pub fn is_duplicate_tx(&self) -> bool { + matches!( + self, + Error::InputValidation(InputValidationError::DuplicateTxId(_)) + ) + } +} + #[derive(Clone, Debug, derive_more::Display)] pub enum RemovedReason { #[display( diff --git a/crates/services/txpool_v2/src/pool.rs b/crates/services/txpool_v2/src/pool.rs index 3b230080b70..708e27e017e 100644 --- a/crates/services/txpool_v2/src/pool.rs +++ b/crates/services/txpool_v2/src/pool.rs @@ -57,6 +57,7 @@ use crate::{ }; use crate::error::RemovedReason; +use fuel_core_types::services::transaction_status::statuses; #[cfg(test)] use std::collections::HashSet; @@ -217,18 +218,20 @@ where self.new_executable_txs_notifier.send_replace(()); } - let error = TransactionStatus::squeezed_out( - Error::Removed(RemovedReason::LessWorth(tx_id)).to_string(), - ); + let status = statuses::SqueezedOut { + reason: Error::Removed(RemovedReason::LessWorth(tx_id)).to_string(), + }; + let removed_transactions = removed_transactions .into_iter() .map(|data| { let removed_tx_id = data.transaction.id(); - (removed_tx_id, error.clone()) + (removed_tx_id, status.clone()) }) .collect::>(); - self.tx_status_manager.statuses_update(removed_transactions); + self.tx_status_manager + .squeezed_out_txs(removed_transactions); self.update_stats(); Ok(()) @@ -584,7 +587,7 @@ where pub fn remove_transaction_and_dependents( &mut self, tx_ids: Vec, - tx_status: TransactionStatus, + tx_status: statuses::SqueezedOut, ) { let mut removed_transactions = vec![]; for tx_id in tx_ids { @@ -601,7 +604,8 @@ where } } - self.tx_status_manager.statuses_update(removed_transactions); + self.tx_status_manager + .squeezed_out_txs(removed_transactions); self.update_stats(); } diff --git a/crates/services/txpool_v2/src/pool_worker.rs b/crates/services/txpool_v2/src/pool_worker.rs index 0096f09f2a0..7ef51c07383 100644 --- a/crates/services/txpool_v2/src/pool_worker.rs +++ b/crates/services/txpool_v2/src/pool_worker.rs @@ -8,7 +8,7 @@ use fuel_core_types::{ services::{ block_importer::SharedImportResult, p2p::GossipsubMessageInfo, - transaction_status::TransactionStatus, + transaction_status::statuses, txpool::ArcPoolTx, }, }; @@ -182,7 +182,6 @@ pub(super) enum InsertionSource { P2P { from_peer_info: GossipsubMessageInfo, }, - P2PSync, RPC { response_channel: Option>>, }, @@ -233,7 +232,6 @@ pub(super) enum ExtendedInsertionSource { P2P { from_peer_info: GossipsubMessageInfo, }, - P2PSync, RPC { tx: Arc, response_channel: Option>>, @@ -381,7 +379,6 @@ where InsertionSource::P2P { from_peer_info } => { ExtendedInsertionSource::P2P { from_peer_info } } - InsertionSource::P2PSync => ExtendedInsertionSource::P2PSync, InsertionSource::RPC { response_channel } => { let tx: Transaction = self .pool @@ -498,7 +495,9 @@ where fn remove_and_coin_dependents(&mut self, tx_ids: (Vec, Error)) { let (tx_ids, error) = tx_ids; - let tx_status = TransactionStatus::squeezed_out(error.to_string()); + let tx_status = statuses::SqueezedOut { + reason: error.to_string(), + }; self.pool .remove_transaction_and_dependents(tx_ids, tx_status); } diff --git a/crates/services/txpool_v2/src/ports.rs b/crates/services/txpool_v2/src/ports.rs index 6c3d7250d8c..9eec852436f 100644 --- a/crates/services/txpool_v2/src/ports.rs +++ b/crates/services/txpool_v2/src/ports.rs @@ -36,11 +36,12 @@ use fuel_core_types::{ use crate::GasPrice; pub use fuel_core_storage::transactional::AtomicView; +use fuel_core_types::services::transaction_status::statuses; pub trait TxStatusManager: Send + Sync + 'static { fn status_update(&self, tx_id: TxId, tx_status: TransactionStatus); - fn statuses_update(&self, statuses: Vec<(TxId, TransactionStatus)>); + fn squeezed_out_txs(&self, statuses: Vec<(TxId, statuses::SqueezedOut)>); } pub trait BlockImporter { diff --git a/crates/services/txpool_v2/src/service.rs b/crates/services/txpool_v2/src/service.rs index 53dc476cab9..b94229546d1 100644 --- a/crates/services/txpool_v2/src/service.rs +++ b/crates/services/txpool_v2/src/service.rs @@ -165,9 +165,6 @@ pub enum WritePoolRequest { InsertTxs { transactions: Vec>, }, - InsertTxsSync { - transactions: Vec>, - }, InsertTx { transaction: Arc, response_channel: oneshot::Sender>, @@ -344,10 +341,7 @@ where fn process_write(&self, write_pool_request: WritePoolRequest) { match write_pool_request { WritePoolRequest::InsertTxs { transactions } => { - self.insert_transactions(transactions, false); - } - WritePoolRequest::InsertTxsSync { transactions } => { - self.insert_transactions(transactions, true); + self.insert_transactions(transactions); } WritePoolRequest::InsertTx { transaction, @@ -358,7 +352,6 @@ where transaction, None, Some(response_channel), - false, ); self.transaction_verifier_process @@ -386,7 +379,6 @@ where GossipsubMessageAcceptance::Accept, ); } - ExtendedInsertionSource::P2PSync => {} ExtendedInsertionSource::RPC { response_channel, tx, @@ -418,6 +410,7 @@ where error, source, } => { + let is_duplicate = error.is_duplicate_tx(); let tx_status = TransactionStatus::squeezed_out(error.to_string()); match source { InsertionSource::P2P { from_peer_info } => { @@ -425,31 +418,33 @@ where from_peer_info, GossipsubMessageAcceptance::Ignore, ); - self.tx_status_manager.status_update(tx_id, tx_status); } - InsertionSource::P2PSync => {} InsertionSource::RPC { response_channel } => { if let Some(channel) = response_channel { let _ = channel.send(Err(error)); } - self.tx_status_manager.status_update(tx_id, tx_status); } } + + // If the transaction is a duplicate, we don't want to update + // the status with useless information for the end user. + // Transaction already is inserted into the pool, + // so main goal of the user is already achieved - + // his transaction is accepted by the pool. + if !is_duplicate { + self.tx_status_manager.status_update(tx_id, tx_status); + } } } } - fn insert_transactions( - &self, - transactions: Vec>, - from_p2p_sync: bool, - ) { + fn insert_transactions(&self, transactions: Vec>) { for transaction in transactions { let Ok(reservation) = self.transaction_verifier_process.reserve() else { tracing::error!("Failed to insert transactions: Out of capacity"); continue }; - let op = self.insert_transaction(transaction, None, None, from_p2p_sync); + let op = self.insert_transaction(transaction, None, None); self.transaction_verifier_process .spawn_reserved(reservation, op); @@ -461,7 +456,6 @@ where transaction: Arc, from_peer_info: Option, response_channel: Option>>, - from_p2p_sync: bool, ) -> impl FnOnce() + Send + 'static { let metrics = self.metrics; if metrics { @@ -532,8 +526,6 @@ where let source = if let Some(from_peer_info) = from_peer_info { InsertionSource::P2P { from_peer_info } - } else if from_p2p_sync { - InsertionSource::P2PSync } else { InsertionSource::RPC { response_channel } }; @@ -575,7 +567,7 @@ where message_id, peer_id, }); - let op = self.insert_transaction(Arc::new(tx), info, None, false); + let op = self.insert_transaction(Arc::new(tx), info, None); self.transaction_verifier_process .spawn_reserved(reservation, op); } @@ -663,7 +655,7 @@ where // Verifying and insert them, not a big deal if we fail to insert them let _ = txs_insert_sender - .try_send(WritePoolRequest::InsertTxsSync { transactions: txs }); + .try_send(WritePoolRequest::InsertTxs { transactions: txs }); } }); } diff --git a/crates/services/txpool_v2/src/tests/mocks.rs b/crates/services/txpool_v2/src/tests/mocks.rs index c4aa8ccbdbb..790e2d7da28 100644 --- a/crates/services/txpool_v2/src/tests/mocks.rs +++ b/crates/services/txpool_v2/src/tests/mocks.rs @@ -58,7 +58,10 @@ use fuel_core_types::{ GossipsubMessageInfo, PeerId, }, - transaction_status::TransactionStatus, + transaction_status::{ + statuses, + TransactionStatus, + }, }, }; use std::{ @@ -100,9 +103,9 @@ impl ports::TxStatusManager for MockTxStatusManager { tx.try_send((tx_id, tx_status)).unwrap(); } - fn statuses_update(&self, statuses: Vec<(TxId, TransactionStatus)>) { + fn squeezed_out_txs(&self, statuses: Vec<(TxId, statuses::SqueezedOut)>) { for (tx_id, tx_status) in statuses { - self.status_update(tx_id, tx_status); + self.status_update(tx_id, tx_status.into()); } } } diff --git a/crates/types/src/services/p2p.rs b/crates/types/src/services/p2p.rs index 5266a9a51c3..fc17a862e96 100644 --- a/crates/types/src/services/p2p.rs +++ b/crates/types/src/services/p2p.rs @@ -103,8 +103,6 @@ pub struct DelegatePreConfirmationKey

{ /// to use to verify the pre-confirmations--serves the second purpose of being a nonce of /// each key pub expiration: Tai64, - /// The nonce of the p2p message to make it unique. - pub nonce: u64, } /// A signed key delegation @@ -118,7 +116,12 @@ pub type SignedPreconfirmationByDelegate = Sealed; #[derive(Debug, Clone, PartialEq, Eq)] pub enum PreConfirmationMessage { /// Notification of key delegation - Delegate(SignedByBlockProducerDelegation), + Delegate { + /// The sealed key delegation. + seal: SignedByBlockProducerDelegation, + /// The nonce of the p2p message to make it unique. + nonce: u64, + }, /// Notification of pre-confirmations Preconfirmations(SignedPreconfirmationByDelegate), } diff --git a/crates/types/src/services/transaction_status.rs b/crates/types/src/services/transaction_status.rs index 88b46d202a7..d68eeccf1c4 100644 --- a/crates/types/src/services/transaction_status.rs +++ b/crates/types/src/services/transaction_status.rs @@ -333,6 +333,12 @@ pub mod statuses { } } + impl From for TransactionStatus { + fn from(value: SqueezedOut) -> Self { + TransactionStatus::SqueezedOut(value.into()) + } + } + /// Transaction was squeezed out from block producer #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[derive(Clone, Debug, PartialEq, Eq)] diff --git a/tests/tests/preconfirmations.rs b/tests/tests/preconfirmations.rs index 5817a250690..21f67d9dd6b 100644 --- a/tests/tests/preconfirmations.rs +++ b/tests/tests/preconfirmations.rs @@ -67,9 +67,7 @@ async fn preconfirmation__received_after_successful_execution() { .finalize_as_transaction(); let tx_id = tx.id(&Default::default()); - let mut tx_statuses_subscriber = - client.subscribe_transaction_status(&tx_id).await.unwrap(); - client.submit(&tx).await.unwrap(); + let mut tx_statuses_subscriber = client.submit_and_await_status(&tx).await.unwrap(); // When assert!(matches!( @@ -161,9 +159,7 @@ async fn preconfirmation__received_after_failed_execution() { .finalize_as_transaction(); let tx_id = tx.id(&Default::default()); - let mut tx_statuses_subscriber = - client.subscribe_transaction_status(&tx_id).await.unwrap(); - client.submit(&tx).await.unwrap(); + let mut tx_statuses_subscriber = client.submit_and_await_status(&tx).await.unwrap(); // When assert!(matches!( @@ -238,36 +234,25 @@ async fn preconfirmation__received_tx_inserted_end_block_open_period() { .add_output(Output::variable(address, 0, AssetId::default())) .finalize_as_transaction(); - let tx_id = tx.id(&Default::default()); - let jh = tokio::spawn({ - let client = client.clone(); - async move { - let _ = client - .subscribe_transaction_status(&tx_id) - .await - .unwrap() - .enumerate() - .for_each(|(event_idx, r)| async move { - let r = r.unwrap(); - // Then - match (event_idx, r) { - (0, TransactionStatus::Submitted { .. }) => {} - (1, TransactionStatus::PreconfirmationSuccess { .. }) => {} - (2, TransactionStatus::Success { block_height, .. }) => { - assert_eq!(block_height, BlockHeight::new(1)); - } - (_, r) => panic!("Unexpected event: {:?}", r), - } - }) - .await; - } - }); - // When - tokio::time::sleep(block_production_period.checked_div(2).unwrap()).await; - client.submit(&tx).await.unwrap(); - - jh.await.unwrap(); + client + .submit_and_await_status(&tx) + .await + .unwrap() + .enumerate() + .for_each(|(event_idx, r)| async move { + let r = r.unwrap(); + // Then + match (event_idx, r) { + (0, TransactionStatus::Submitted { .. }) => {} + (1, TransactionStatus::PreconfirmationSuccess { .. }) => {} + (2, TransactionStatus::Success { block_height, .. }) => { + assert_eq!(block_height, BlockHeight::new(1)); + } + (_, r) => panic!("Unexpected event: {:?}", r), + } + }) + .await; } #[tokio::test] @@ -318,14 +303,8 @@ async fn preconfirmation__received_after_execution__multiple_txs() { .finalize_as_transaction(); // Given - let tx_id1 = tx1.id(&Default::default()); - let tx_id2 = tx2.id(&Default::default()); - let mut tx_statuses_subscriber1 = - client.subscribe_transaction_status(&tx_id1).await.unwrap(); - let mut tx_statuses_subscriber2 = - client.subscribe_transaction_status(&tx_id2).await.unwrap(); - client.submit(&tx1).await.unwrap(); - client.submit(&tx2).await.unwrap(); + let mut tx_statuses_subscriber1 = client.submit_and_await_status(&tx1).await.unwrap(); + let mut tx_statuses_subscriber2 = client.submit_and_await_status(&tx2).await.unwrap(); // When assert!(matches!( diff --git a/tests/tests/preconfirmations_gossip.rs b/tests/tests/preconfirmations_gossip.rs index 1d45155f873..3309a9b5314 100644 --- a/tests/tests/preconfirmations_gossip.rs +++ b/tests/tests/preconfirmations_gossip.rs @@ -51,9 +51,10 @@ fn config_with_preconfirmations(block_production_period: Duration) -> Config { let mut config = Config::local_node(); config.p2p.as_mut().unwrap().subscribe_to_pre_confirmations = true; + config.txpool.pending_pool_tx_ttl = Duration::from_secs(1); config .pre_confirmation_signature_service - .key_rotation_interval = block_production_period.checked_div(4).unwrap(); + .echo_delegation_interval = Duration::from_millis(100); config.block_production = Trigger::Open { period: block_production_period, }; @@ -61,7 +62,7 @@ fn config_with_preconfirmations(block_production_period: Duration) -> Config { config } -#[tokio::test] +#[tokio::test(flavor = "multi_thread")] async fn preconfirmation__propagate_p2p_after_successful_execution() { let mut rng = rand::thread_rng(); let address = Address::new([0; 32]); @@ -143,7 +144,7 @@ async fn preconfirmation__propagate_p2p_after_successful_execution() { let sentry = &validators[0]; // Sleep to let time for exchange preconfirmations delegate public keys - tokio::time::sleep(block_production_period.checked_div(2).unwrap()).await; + tokio::time::sleep(Duration::from_secs(1)).await; // When let client_sentry = FuelClient::from(sentry.node.bound_address); @@ -158,7 +159,12 @@ async fn preconfirmation__propagate_p2p_after_successful_execution() { TransactionStatus::Submitted { .. } )); - let status = tx_statuses_subscriber.next().await.unwrap().unwrap(); + let status = + tokio::time::timeout(Duration::from_secs(1), tx_statuses_subscriber.next()) + .await + .unwrap() + .unwrap() + .unwrap(); if let TransactionStatus::PreconfirmationSuccess { tx_pointer, total_fee, @@ -202,7 +208,7 @@ async fn preconfirmation__propagate_p2p_after_successful_execution() { )); } -#[tokio::test] +#[tokio::test(flavor = "multi_thread")] async fn preconfirmation__propagate_p2p_after_failed_execution() { let mut rng = rand::thread_rng(); let address = Address::new([0; 32]); @@ -284,7 +290,7 @@ async fn preconfirmation__propagate_p2p_after_failed_execution() { let sentry = &validators[0]; // Sleep to let time for exchange preconfirmations delegate public keys - tokio::time::sleep(block_production_period.checked_div(2).unwrap()).await; + tokio::time::sleep(Duration::from_secs(1)).await; // When let client_sentry = FuelClient::from(sentry.node.bound_address); @@ -298,6 +304,13 @@ async fn preconfirmation__propagate_p2p_after_failed_execution() { TransactionStatus::Submitted { .. } )); + let status = + tokio::time::timeout(Duration::from_secs(1), tx_statuses_subscriber.next()) + .await + .unwrap() + .unwrap() + .unwrap(); + if let TransactionStatus::PreconfirmationFailure { tx_pointer, total_fee, @@ -306,7 +319,7 @@ async fn preconfirmation__propagate_p2p_after_failed_execution() { receipts, resolved_outputs, reason: _, - } = tx_statuses_subscriber.next().await.unwrap().unwrap() + } = status { // Then assert_eq!(tx_pointer, TxPointer::new(BlockHeight::new(1), 1)); @@ -342,3 +355,103 @@ async fn preconfirmation__propagate_p2p_after_failed_execution() { TransactionStatus::Failure { .. } )); } + +#[tokio::test(flavor = "multi_thread")] +async fn preconfirmation__propagate_p2p_after_squeezed_out_on_producer() { + let mut rng = rand::thread_rng(); + + let block_production_period = Duration::from_secs(8); + let gas_limit = 1_000_000; + let tx = TransactionBuilder::script( + vec![op::ret(RegId::ONE)].into_iter().collect(), + vec![], + ) + .script_gas_limit(gas_limit) + .add_unsigned_coin_input( + SecretKey::random(&mut rng), + rng.gen(), + 10, + Default::default(), + Default::default(), + ) + .add_output(Output::Change { + to: Default::default(), + amount: 0, + asset_id: Default::default(), + }) + .finalize_as_transaction(); + + // Create a random seed based on the test parameters. + let mut hasher = DefaultHasher::new(); + let num_txs = 1; + let num_validators = 1; + let num_partitions = 1; + (num_txs, num_validators, num_partitions, line!()).hash(&mut hasher); + let mut rng = StdRng::seed_from_u64(hasher.finish()); + + // Create a set of key pairs. + let secrets: Vec<_> = (0..1).map(|_| SecretKey::random(&mut rng)).collect(); + let pub_keys: Vec<_> = secrets + .clone() + .into_iter() + .map(|secret| Input::owner(&secret.public_key())) + .collect(); + + // Create a producer for each key pair and a set of validators that share + // the same key pair. + + // Given + // Disable UTXO validation in TxPool so that the transaction is squeezed out by + // block production + let Nodes { + producers: _producers, + validators, + bootstrap_nodes: _dont_drop, + } = make_nodes( + pub_keys + .iter() + .map(|pub_key| Some(BootstrapSetup::new(*pub_key))), + secrets.clone().into_iter().enumerate().map(|(i, secret)| { + Some( + ProducerSetup::new(secret) + .with_name(format!("{}:producer", pub_keys[i])) + .utxo_validation(true), + ) + }), + pub_keys.iter().flat_map(|pub_key| { + (0..num_validators).map(move |i| { + Some( + ValidatorSetup::new(*pub_key) + .with_name(format!("{pub_key}:{i}")) + .utxo_validation(false), + ) + }) + }), + Some(config_with_preconfirmations(block_production_period)), + ) + .await; + + let sentry = &validators[0]; + + // Sleep to let time for exchange preconfirmations delegate public keys + tokio::time::sleep(Duration::from_secs(1)).await; + + // When + let client_sentry = FuelClient::from(sentry.node.bound_address); + let mut tx_statuses_subscriber = client_sentry + .submit_and_await_status(&tx) + .await + .expect("Should be able to subscribe for events"); + + assert!(matches!( + tx_statuses_subscriber.next().await.unwrap().unwrap(), + TransactionStatus::Submitted { .. } + )); + tracing::info!("Submitted"); + + assert!(matches!( + tx_statuses_subscriber.next().await.unwrap().unwrap(), + TransactionStatus::SqueezedOut { .. } + )); + tracing::info!("SqueezedOut"); +} From 95899dd3fe14e0f45dff8ceac4b646aed992f3ca Mon Sep 17 00:00:00 2001 From: AurelienFT Date: Fri, 21 Mar 2025 10:09:34 +0100 Subject: [PATCH 75/76] added a test open block production --- .../poa/src/service_test/trigger_tests.rs | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/crates/services/consensus_module/poa/src/service_test/trigger_tests.rs b/crates/services/consensus_module/poa/src/service_test/trigger_tests.rs index eed104810dc..9cdd3ddd5f0 100644 --- a/crates/services/consensus_module/poa/src/service_test/trigger_tests.rs +++ b/crates/services/consensus_module/poa/src/service_test/trigger_tests.rs @@ -506,3 +506,32 @@ async fn interval_trigger_even_if_queued_tx_events() { }); block_creation_waiter.notified().await; } + +#[tokio::test] +async fn open_trigger__produce_blocks_in_time() { + // Given + let open_time = Duration::from_secs(10); + let offset = Duration::from_secs(1); + let mut ctx = DefaultContext::new(Config { + trigger: Trigger::Open { period: open_time }, + signer: SignMode::Key(test_signing_key()), + metrics: false, + ..Default::default() + }) + .await; + let expected_block_time = ctx.now().0.checked_add(open_time.as_secs()).unwrap(); + + // When + time::sleep(offset).await; + ctx.advance_time_with_tokio(); + let receive_before_closed = ctx.block_import.try_recv(); + + time::sleep(open_time).await; + let receive_after_open = ctx.block_import.try_recv(); + + // Then + assert!(receive_before_closed.is_err()); + assert!(receive_after_open.is_ok()); + let block_time = receive_after_open.unwrap().entity.header().time(); + assert_eq!(block_time.0, expected_block_time); +} From f9f728eedca3b2f8f4b63f37119a2b95a6458525 Mon Sep 17 00:00:00 2001 From: green Date: Fri, 21 Mar 2025 13:00:29 -0400 Subject: [PATCH 76/76] Add another test --- .../poa/src/service_test/trigger_tests.rs | 62 ++++++++++++++++--- 1 file changed, 55 insertions(+), 7 deletions(-) diff --git a/crates/services/consensus_module/poa/src/service_test/trigger_tests.rs b/crates/services/consensus_module/poa/src/service_test/trigger_tests.rs index 9cdd3ddd5f0..810ef87a2a2 100644 --- a/crates/services/consensus_module/poa/src/service_test/trigger_tests.rs +++ b/crates/services/consensus_module/poa/src/service_test/trigger_tests.rs @@ -511,6 +511,7 @@ async fn interval_trigger_even_if_queued_tx_events() { async fn open_trigger__produce_blocks_in_time() { // Given let open_time = Duration::from_secs(10); + let quarter_of_open_time = open_time / 4; let offset = Duration::from_secs(1); let mut ctx = DefaultContext::new(Config { trigger: Trigger::Open { period: open_time }, @@ -519,19 +520,66 @@ async fn open_trigger__produce_blocks_in_time() { ..Default::default() }) .await; - let expected_block_time = ctx.now().0.checked_add(open_time.as_secs()).unwrap(); + time::sleep(offset).await; + + for _ in 0..10 { + // When + ctx.advance_time_with_tokio(); + time::sleep(quarter_of_open_time).await; + let first_quarter = ctx.block_import.try_recv(); + + ctx.advance_time_with_tokio(); + time::sleep(quarter_of_open_time).await; + let second_quarter = ctx.block_import.try_recv(); + + ctx.advance_time_with_tokio(); + time::sleep(quarter_of_open_time).await; + let third_quarter = ctx.block_import.try_recv(); + + ctx.advance_time_with_tokio(); + time::sleep(quarter_of_open_time).await; + let forth_quarter = ctx.block_import.try_recv(); + + // Then + assert!(first_quarter.is_err()); + assert!(second_quarter.is_err()); + assert!(third_quarter.is_err()); + assert!(forth_quarter.is_ok()); + } +} + +#[tokio::test] +async fn open_trigger__produce_blocks_with_correct_time() { + // Given + let open_time = Duration::from_secs(10); + let offset = Duration::from_secs(1); + let mut ctx = DefaultContext::new(Config { + trigger: Trigger::Open { period: open_time }, + signer: SignMode::Key(test_signing_key()), + metrics: false, + ..Default::default() + }) + .await; + let expected_first_block_time = ctx.now().0.checked_add(open_time.as_secs()).unwrap(); + let expected_second_block_time = expected_first_block_time + .checked_add(open_time.as_secs()) + .unwrap(); // When time::sleep(offset).await; ctx.advance_time_with_tokio(); - let receive_before_closed = ctx.block_import.try_recv(); + time::sleep(open_time).await; + let first_block = ctx.block_import.try_recv(); + ctx.advance_time_with_tokio(); time::sleep(open_time).await; - let receive_after_open = ctx.block_import.try_recv(); + let second_block = ctx.block_import.try_recv(); // Then - assert!(receive_before_closed.is_err()); - assert!(receive_after_open.is_ok()); - let block_time = receive_after_open.unwrap().entity.header().time(); - assert_eq!(block_time.0, expected_block_time); + assert!(first_block.is_ok()); + assert!(second_block.is_ok()); + let first_block_time = first_block.unwrap().entity.header().time(); + let second_block_time = second_block.unwrap().entity.header().time(); + assert_eq!(first_block_time.0, expected_first_block_time); + assert_eq!(second_block_time.0, expected_second_block_time); }