From 0cd5b20051c54ed27724d38f70e42c902b9ba04c Mon Sep 17 00:00:00 2001 From: Mitch Turner Date: Fri, 21 Mar 2025 15:35:07 -0600 Subject: [PATCH 01/37] Include new data coin input/output types from vm --- Cargo.lock | 18 ----- Cargo.toml | 4 + crates/compression/src/decompress.rs | 36 +++++++++ crates/fuel-core/src/schema/tx/assemble_tx.rs | 2 + crates/services/executor/src/executor.rs | 60 ++++++++++++++- .../txpool_v2/src/collision_manager/basic.rs | 34 ++++++--- crates/services/txpool_v2/src/config.rs | 11 ++- .../txpool_v2/src/extracted_outputs.rs | 13 +++- crates/services/txpool_v2/src/pending_pool.rs | 1 + .../services/txpool_v2/src/storage/graph.rs | 75 +++++++++++++++---- 10 files changed, 205 insertions(+), 49 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c614a10ecfb..00f60d995ad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3332,8 +3332,6 @@ dependencies = [ [[package]] name = "fuel-asm" version = "0.60.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4702e63db53a308a4bb57ba21e8f7dcb08f1146a9b34fce06d964306ed3e1497" dependencies = [ "bitflags 2.9.0", "fuel-types 0.60.0", @@ -3344,8 +3342,6 @@ dependencies = [ [[package]] name = "fuel-compression" version = "0.60.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aad1694f1c4f53309056d5343aae7f1944408b5c426d52d34027f4005778e54c" dependencies = [ "fuel-derive 0.60.0", "fuel-types 0.60.0", @@ -4156,8 +4152,6 @@ dependencies = [ [[package]] name = "fuel-crypto" version = "0.60.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b478d21160fa528667ca477b82e2426efa5ac226a7b6fce67987d62f1b041cac" dependencies = [ "base64ct", "coins-bip32", @@ -4189,8 +4183,6 @@ dependencies = [ [[package]] name = "fuel-derive" version = "0.60.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e0b24c724f47a73f021dae315e5d2b5aec03ba6b9ef9d66377106a20a5fdcde" dependencies = [ "proc-macro2", "quote", @@ -4227,8 +4219,6 @@ dependencies = [ [[package]] name = "fuel-merkle" version = "0.60.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d3a023169ea7231b6bd107c513b8b9a7ff849df5ca023c120b348da234ec20a" dependencies = [ "derive_more 0.99.19", "digest 0.10.7", @@ -4260,8 +4250,6 @@ checksum = "4c1b711f28553ddc5f3546711bd220e144ce4c1af7d9e9a1f70b2f20d9f5b791" [[package]] name = "fuel-storage" version = "0.60.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8425dc32f86e2d6082126ee34998a9556306c6a79d95b85a62a05b439ff9bb19" [[package]] name = "fuel-tx" @@ -4288,8 +4276,6 @@ dependencies = [ [[package]] name = "fuel-tx" version = "0.60.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db32cb7845a7462b02a8406303bfc5387589387418b5f4817a01bd5d3d16b287" dependencies = [ "bitflags 2.9.0", "derive_more 1.0.0", @@ -4322,8 +4308,6 @@ dependencies = [ [[package]] name = "fuel-types" version = "0.60.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "284f4e00bb184c4d24e7fe12f5a698612c384f2b9354eba4478bb022b4fb0a33" dependencies = [ "fuel-derive 0.60.0", "hex", @@ -4365,8 +4349,6 @@ dependencies = [ [[package]] name = "fuel-vm" version = "0.60.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a07bfdb498244dc1e82de4e396a6c1f4183fd05355454e4847a727c9b5fb3331" dependencies = [ "anyhow", "async-trait", diff --git a/Cargo.toml b/Cargo.toml index bec263a5f6a..64505a8bde8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -171,3 +171,7 @@ insta = "1.8" tempfile = "3.4" tikv-jemallocator = "0.5" url = "2.2" + +# add patch for fuel-vm +[patch.crates-io] +fuel-vm-private = { path = "../fuel-vm/fuel-vm", version = "0.60.0", package = "fuel-vm" } diff --git a/crates/compression/src/decompress.rs b/crates/compression/src/decompress.rs index bb4f66d651a..00ddebd9fbd 100644 --- a/crates/compression/src/decompress.rs +++ b/crates/compression/src/decompress.rs @@ -23,6 +23,7 @@ use fuel_core_types::{ coin::{ Coin, CoinSpecification, + DataCoin, }, message::{ Message, @@ -182,6 +183,41 @@ where } } +impl DecompressibleBy> for DataCoin +where + D: DecompressDb, + Specification: CoinSpecification, + Specification::Predicate: DecompressibleBy>, + Specification::PredicateData: DecompressibleBy>, + Specification::PredicateGasUsed: DecompressibleBy>, + Specification::Witness: DecompressibleBy>, +{ + async fn decompress_with( + c: as Compressible>::Compressed, + ctx: &DecompressCtx, + ) -> anyhow::Result> { + let utxo_id = UtxoId::decompress_with(c.utxo_id, ctx).await?; + let coin_info = ctx.db.coin(utxo_id)?; + let witness_index = c.witness_index.decompress(ctx).await?; + let predicate_gas_used = c.predicate_gas_used.decompress(ctx).await?; + let predicate = c.predicate.decompress(ctx).await?; + let predicate_data = c.predicate_data.decompress(ctx).await?; + let data = c.data.decompress(ctx).await?; + Ok(Self { + utxo_id, + owner: coin_info.owner, + amount: coin_info.amount, + asset_id: coin_info.asset_id, + tx_pointer: Default::default(), + witness_index, + predicate_gas_used, + predicate, + predicate_data, + data, + }) + } +} + impl DecompressibleBy> for Message where D: DecompressDb, diff --git a/crates/fuel-core/src/schema/tx/assemble_tx.rs b/crates/fuel-core/src/schema/tx/assemble_tx.rs index 44e14d275dd..9df6aa6bd86 100644 --- a/crates/fuel-core/src/schema/tx/assemble_tx.rs +++ b/crates/fuel-core/src/schema/tx/assemble_tx.rs @@ -736,7 +736,9 @@ where let has_spendable_input = script.inputs().iter().any(|input| match input { Input::CoinSigned(_) + | Input::DataCoinSigned(_) | Input::CoinPredicate(_) + | Input::DataCoinPredicate(_) | Input::MessageCoinSigned(_) | Input::MessageCoinPredicate(_) => true, Input::MessageDataSigned(_) diff --git a/crates/services/executor/src/executor.rs b/crates/services/executor/src/executor.rs index 9a6499418b9..d549bac9ae3 100644 --- a/crates/services/executor/src/executor.rs +++ b/crates/services/executor/src/executor.rs @@ -169,6 +169,10 @@ use alloc::{ vec, vec::Vec, }; +use fuel_core_types::fuel_tx::input::coin::{ + DataCoinPredicate, + DataCoinSigned, +}; /// The maximum amount of transactions that can be included in a block, /// excluding the mint transaction. @@ -1948,7 +1952,9 @@ where for input in inputs { match input { Input::CoinSigned(CoinSigned { utxo_id, .. }) - | Input::CoinPredicate(CoinPredicate { utxo_id, .. }) => { + | Input::DataCoinSigned(DataCoinSigned { utxo_id, .. }) + | Input::CoinPredicate(CoinPredicate { utxo_id, .. }) + | Input::DataCoinPredicate(DataCoinPredicate { utxo_id, .. }) => { if let Some(coin) = db.storage::().get(utxo_id)? { if !coin.matches_input(input).unwrap_or_default() { return Err( @@ -2267,6 +2273,21 @@ where to, db, )?, + Output::DataCoin { + amount, + asset_id, + to, + data_hash, + } => Self::insert_data_coin( + block_height, + execution_data, + utxo_id, + amount, + asset_id, + to, + data_hash, + db, + )?, Output::Contract(contract) => { if let Some(Input::Contract(input::contract::Contract { contract_id, @@ -2356,4 +2377,41 @@ where Ok(()) } + + #[allow(clippy::too_many_arguments)] + fn insert_data_coin( + block_height: BlockHeight, + execution_data: &mut ExecutionData, + utxo_id: UtxoId, + amount: &Word, + asset_id: &AssetId, + to: &Address, + _data_hash: &Bytes32, + db: &mut TxStorageTransaction, + ) -> ExecutorResult<()> + where + T: KeyValueInspect, + { + // Only insert a coin output if it has some amount. + // This is because variable or transfer outputs won't have any value + // if there's a revert or panic and shouldn't be added to the utxo set. + if *amount > Word::MIN { + let coin = CompressedCoinV1 { + owner: *to, + amount: *amount, + asset_id: *asset_id, + tx_pointer: TxPointer::new(block_height, execution_data.tx_count), + } + .into(); + + if db.storage::().replace(&utxo_id, &coin)?.is_some() { + return Err(ExecutorError::OutputAlreadyExists) + } + execution_data + .events + .push(ExecutorEvent::CoinCreated(coin.uncompress(utxo_id))); + } + + Ok(()) + } } diff --git a/crates/services/txpool_v2/src/collision_manager/basic.rs b/crates/services/txpool_v2/src/collision_manager/basic.rs index bf971d34756..b0a716e2e0e 100644 --- a/crates/services/txpool_v2/src/collision_manager/basic.rs +++ b/crates/services/txpool_v2/src/collision_manager/basic.rs @@ -7,6 +7,14 @@ use std::{ hash::Hash, }; +use crate::{ + error::{ + CollisionReason, + Error, + InputValidationError, + }, + storage::StorageData, +}; use fuel_core_types::{ fuel_tx::{ field::BlobId as _, @@ -14,6 +22,8 @@ use fuel_core_types::{ coin::{ CoinPredicate, CoinSigned, + DataCoinPredicate, + DataCoinSigned, }, message::{ MessageCoinPredicate, @@ -33,15 +43,6 @@ use fuel_core_types::{ services::txpool::PoolTransaction, }; -use crate::{ - error::{ - CollisionReason, - Error, - InputValidationError, - }, - storage::StorageData, -}; - use super::{ CollisionManager, Collisions, @@ -100,6 +101,10 @@ impl BasicCollisionManager { | Input::CoinPredicate(CoinPredicate { utxo_id, .. }) => { coins_spenders.insert(*utxo_id, tx.id()); } + Input::DataCoinSigned(DataCoinSigned { utxo_id, .. }) + | Input::DataCoinPredicate(DataCoinPredicate { utxo_id, .. }) => { + coins_spenders.insert(*utxo_id, tx.id()); + } Input::MessageCoinSigned(MessageCoinSigned { nonce, .. }) | Input::MessageCoinPredicate(MessageCoinPredicate { nonce, .. @@ -219,7 +224,10 @@ where entry.push(CollisionReason::ContractCreation(*contract_id)); } } - Output::Coin { .. } | Output::Change { .. } | Output::Variable { .. } => { + Output::Coin { .. } + | Output::DataCoin { .. } + | Output::Change { .. } + | Output::Variable { .. } => { let utxo_id = UtxoId::new( transaction.id(), u16::try_from(i) @@ -251,7 +259,9 @@ where for input in store_entry.transaction.inputs() { match input { Input::CoinSigned(CoinSigned { utxo_id, .. }) - | Input::CoinPredicate(CoinPredicate { utxo_id, .. }) => { + | Input::DataCoinSigned(DataCoinSigned { utxo_id, .. }) + | Input::CoinPredicate(CoinPredicate { utxo_id, .. }) + | Input::DataCoinPredicate(DataCoinPredicate { utxo_id, .. }) => { // insert coin self.coins_spenders.insert(*utxo_id, storage_id); } @@ -268,6 +278,7 @@ where for output in store_entry.transaction.outputs().iter() { match output { Output::Coin { .. } + | Output::DataCoin { .. } | Output::Change { .. } | Output::Variable { .. } | Output::Contract(_) => {} @@ -304,6 +315,7 @@ where for output in transaction.outputs().iter() { match output { Output::Coin { .. } + | Output::DataCoin { .. } | Output::Change { .. } | Output::Variable { .. } | Output::Contract(_) => {} diff --git a/crates/services/txpool_v2/src/config.rs b/crates/services/txpool_v2/src/config.rs index a28f3243756..ef0c644067a 100644 --- a/crates/services/txpool_v2/src/config.rs +++ b/crates/services/txpool_v2/src/config.rs @@ -3,12 +3,15 @@ use std::{ time::Duration, }; +use crate::error::BlacklistedError; use fuel_core_types::{ fuel_tx::{ input::{ coin::{ CoinPredicate, CoinSigned, + DataCoinPredicate, + DataCoinSigned, }, message::{ MessageCoinPredicate, @@ -26,8 +29,6 @@ use fuel_core_types::{ services::txpool::PoolTransaction, }; -use crate::error::BlacklistedError; - #[derive(Default, Debug, Clone, PartialEq, Eq)] pub struct BlackList { /// Blacklisted addresses. @@ -64,7 +65,11 @@ impl BlackList { for input in tx.inputs() { match input { Input::CoinSigned(CoinSigned { utxo_id, owner, .. }) - | Input::CoinPredicate(CoinPredicate { utxo_id, owner, .. }) => { + | Input::DataCoinSigned(DataCoinSigned { utxo_id, owner, .. }) + | Input::CoinPredicate(CoinPredicate { utxo_id, owner, .. }) + | Input::DataCoinPredicate(DataCoinPredicate { + utxo_id, owner, .. + }) => { if self.coins.contains(utxo_id) { return Err(BlacklistedError::BlacklistedUTXO(*utxo_id)); } diff --git a/crates/services/txpool_v2/src/extracted_outputs.rs b/crates/services/txpool_v2/src/extracted_outputs.rs index 2bcb24beff9..57c005033f3 100644 --- a/crates/services/txpool_v2/src/extracted_outputs.rs +++ b/crates/services/txpool_v2/src/extracted_outputs.rs @@ -7,6 +7,8 @@ use fuel_core_types::{ input::coin::{ CoinPredicate, CoinSigned, + DataCoinPredicate, + DataCoinSigned, }, Address, AssetId, @@ -57,12 +59,19 @@ impl ExtractedOutputs { to, amount, asset_id, + } + | Output::DataCoin { + to, + amount, + asset_id, + .. } => { self.coins_created.entry(tx_id).or_default().insert( u16::try_from(idx).expect("Outputs count is less than u16::MAX"), (*to, *amount, *asset_id), ); } + Output::Contract { .. } | Output::Change { .. } | Output::Variable { .. } => { @@ -73,7 +82,9 @@ impl ExtractedOutputs { for input in tx.inputs() { match input { Input::CoinSigned(CoinSigned { utxo_id, .. }) - | Input::CoinPredicate(CoinPredicate { utxo_id, .. }) => { + | Input::DataCoinSigned(DataCoinSigned { utxo_id, .. }) + | Input::CoinPredicate(CoinPredicate { utxo_id, .. }) + | Input::DataCoinPredicate(DataCoinPredicate { utxo_id, .. }) => { self.coins_created .entry(*utxo_id.tx_id()) .and_modify(|coins| { diff --git a/crates/services/txpool_v2/src/pending_pool.rs b/crates/services/txpool_v2/src/pending_pool.rs index 2b0e11dab22..1f4a0b1c396 100644 --- a/crates/services/txpool_v2/src/pending_pool.rs +++ b/crates/services/txpool_v2/src/pending_pool.rs @@ -148,6 +148,7 @@ impl PendingPool { ) { let missing_input = match output { Output::Coin { .. } => MissingInput::Utxo(utxo_id), + Output::DataCoin { .. } => MissingInput::Utxo(utxo_id), Output::ContractCreated { contract_id, .. } => { MissingInput::Contract(*contract_id) } diff --git a/crates/services/txpool_v2/src/storage/graph.rs b/crates/services/txpool_v2/src/storage/graph.rs index 938aa127ce5..9672aa1aef6 100644 --- a/crates/services/txpool_v2/src/storage/graph.rs +++ b/crates/services/txpool_v2/src/storage/graph.rs @@ -7,12 +7,27 @@ use std::{ time::SystemTime, }; +use crate::{ + error::{ + DependencyError, + Error, + InputValidationError, + InputValidationErrorType, + }, + extracted_outputs::ExtractedOutputs, + pending_pool::MissingInput, + ports::TxPoolPersistentStorage, + selection_algorithms::ratio_tip_gas::RatioTipGasSelectionAlgorithmStorage, + storage::checked_collision::CheckedTransaction, +}; use fuel_core_types::{ fuel_tx::{ input::{ coin::{ CoinPredicate, CoinSigned, + DataCoinPredicate, + DataCoinSigned, }, contract::Contract, message::{ @@ -38,20 +53,6 @@ use petgraph::{ prelude::StableDiGraph, }; -use crate::{ - error::{ - DependencyError, - Error, - InputValidationError, - InputValidationErrorType, - }, - extracted_outputs::ExtractedOutputs, - pending_pool::MissingInput, - ports::TxPoolPersistentStorage, - selection_algorithms::ratio_tip_gas::RatioTipGasSelectionAlgorithmStorage, - storage::checked_collision::CheckedTransaction, -}; - use super::{ RemovedTransactions, Storage, @@ -231,6 +232,29 @@ impl GraphStorage { )); } } + Output::DataCoin { + to, + amount, + asset_id, + data_hash: _data_hash, + } => { + if to != i_owner { + return Err(Error::InputValidation( + InputValidationError::NotInsertedIoWrongOwner, + )); + } + if amount != i_amount { + return Err(Error::InputValidation( + InputValidationError::NotInsertedIoWrongAmount, + )); + } + if asset_id != i_asset_id { + return Err(Error::InputValidation( + InputValidationError::NotInsertedIoWrongAssetId, + )); + } + todo!("Data hash matches data") + } Output::Contract(_) => { return Err(Error::InputValidation( InputValidationError::NotInsertedIoContractOutput, @@ -337,7 +361,9 @@ impl GraphStorage { for input in transaction.inputs() { match input { Input::CoinSigned(CoinSigned { utxo_id, .. }) - | Input::CoinPredicate(CoinPredicate { utxo_id, .. }) => { + | Input::DataCoinSigned(DataCoinSigned { utxo_id, .. }) + | Input::CoinPredicate(CoinPredicate { utxo_id, .. }) + | Input::DataCoinPredicate(DataCoinPredicate { utxo_id, .. }) => { if let Some(node_id) = self.coins_creators.get(utxo_id) { direct_dependencies.insert(*node_id); @@ -416,6 +442,11 @@ impl GraphStorage { UtxoId::new(expected_tx.id(), i.try_into().unwrap()); coins_creators.insert(utxo_id, expected_tx.id()); } + Output::DataCoin { .. } => { + let utxo_id = + UtxoId::new(expected_tx.id(), i.try_into().unwrap()); + coins_creators.insert(utxo_id, expected_tx.id()); + } Output::ContractCreated { contract_id, .. } => { contracts_creators.insert(*contract_id, expected_tx.id()); } @@ -632,12 +663,26 @@ impl Storage for GraphStorage { asset_id, .. }) + | Input::DataCoinSigned(DataCoinSigned { + utxo_id, + owner, + amount, + asset_id, + .. + }) | Input::CoinPredicate(CoinPredicate { utxo_id, owner, amount, asset_id, .. + }) + | Input::DataCoinPredicate(DataCoinPredicate { + utxo_id, + owner, + amount, + asset_id, + .. }) => { if let Some(node_id) = self.coins_creators.get(utxo_id) { let Some(node) = self.graph.node_weight(*node_id) else { From 3d22f4619fbc6cecd5cdf66a552b62127695fd8d Mon Sep 17 00:00:00 2001 From: Mitch Turner Date: Fri, 21 Mar 2025 16:00:59 -0600 Subject: [PATCH 02/37] Fix the remaining compilation errors --- .../src/graphql_api/worker_service.rs | 1 + crates/fuel-core/src/schema/tx/assemble_tx.rs | 11 +- crates/fuel-core/src/schema/tx/input.rs | 113 +++++++++++++++++- crates/fuel-core/src/schema/tx/output.rs | 55 +++++++-- 4 files changed, 166 insertions(+), 14 deletions(-) diff --git a/crates/fuel-core/src/graphql_api/worker_service.rs b/crates/fuel-core/src/graphql_api/worker_service.rs index a0272cd6959..fd2f8890487 100644 --- a/crates/fuel-core/src/graphql_api/worker_service.rs +++ b/crates/fuel-core/src/graphql_api/worker_service.rs @@ -407,6 +407,7 @@ where for output in outputs { match output { Output::Coin { to, .. } + | Output::DataCoin { to, .. } | Output::Change { to, .. } | Output::Variable { to, .. } => { owners.push(to); diff --git a/crates/fuel-core/src/schema/tx/assemble_tx.rs b/crates/fuel-core/src/schema/tx/assemble_tx.rs index 9df6aa6bd86..51fd0b4e5dd 100644 --- a/crates/fuel-core/src/schema/tx/assemble_tx.rs +++ b/crates/fuel-core/src/schema/tx/assemble_tx.rs @@ -38,7 +38,10 @@ use fuel_core_types::{ WitnessLimit, }, input::{ - coin::CoinSigned, + coin::{ + CoinSigned, + DataCoinSigned, + }, message::{ MessageCoinSigned, MessageDataSigned, @@ -222,6 +225,11 @@ where witness_index, .. }) + | Input::DataCoinSigned(DataCoinSigned { + owner, + witness_index, + .. + }) | Input::MessageCoinSigned(MessageCoinSigned { recipient: owner, witness_index, @@ -239,6 +247,7 @@ where } Input::CoinPredicate(_) + | Input::DataCoinPredicate(_) | Input::MessageCoinPredicate(_) | Input::MessageDataPredicate(_) => { has_predicates = true; diff --git a/crates/fuel-core/src/schema/tx/input.rs b/crates/fuel-core/src/schema/tx/input.rs index 1f274403f92..08808dbfae1 100644 --- a/crates/fuel-core/src/schema/tx/input.rs +++ b/crates/fuel-core/src/schema/tx/input.rs @@ -1,3 +1,10 @@ +use async_graphql::{ + Object, + Union, +}; + +use fuel_core_types::fuel_tx; + use crate::schema::scalars::{ Address, AssetId, @@ -10,15 +17,11 @@ use crate::schema::scalars::{ U16, U64, }; -use async_graphql::{ - Object, - Union, -}; -use fuel_core_types::fuel_tx; #[derive(Union)] pub enum Input { Coin(InputCoin), + DataCoin(InputDataCoin), Contract(InputContract), Message(InputMessage), } @@ -74,6 +77,62 @@ impl InputCoin { } } +pub struct InputDataCoin { + utxo_id: UtxoId, + owner: Address, + amount: U64, + asset_id: AssetId, + tx_pointer: TxPointer, + witness_index: u16, + predicate_gas_used: U64, + predicate: HexString, + predicate_data: HexString, + data: HexString, +} + +#[Object] +impl InputDataCoin { + async fn utxo_id(&self) -> UtxoId { + self.utxo_id + } + + async fn owner(&self) -> Address { + self.owner + } + + async fn amount(&self) -> U64 { + self.amount + } + + async fn asset_id(&self) -> AssetId { + self.asset_id + } + + async fn tx_pointer(&self) -> TxPointer { + self.tx_pointer + } + + async fn witness_index(&self) -> U16 { + self.witness_index.into() + } + + async fn predicate_gas_used(&self) -> U64 { + self.predicate_gas_used + } + + async fn predicate(&self) -> HexString { + self.predicate.clone() + } + + async fn predicate_data(&self) -> HexString { + self.predicate_data.clone() + } + + async fn data(&self) -> HexString { + self.data.clone() + } +} + pub struct InputContract { utxo_id: UtxoId, balance_root: Bytes32, @@ -178,6 +237,26 @@ impl From<&fuel_tx::Input> for Input { predicate: HexString(Default::default()), predicate_data: HexString(Default::default()), }), + fuel_tx::Input::DataCoinSigned(fuel_tx::input::coin::DataCoinSigned { + utxo_id, + owner, + amount, + asset_id, + tx_pointer, + witness_index, + .. + }) => Input::DataCoin(InputDataCoin { + utxo_id: UtxoId(*utxo_id), + owner: Address(*owner), + amount: (*amount).into(), + asset_id: AssetId(*asset_id), + tx_pointer: TxPointer(*tx_pointer), + witness_index: *witness_index, + predicate_gas_used: 0.into(), + predicate: HexString(Default::default()), + predicate_data: HexString(Default::default()), + data: HexString(Default::default()), + }), fuel_tx::Input::CoinPredicate(fuel_tx::input::coin::CoinPredicate { utxo_id, owner, @@ -199,6 +278,30 @@ impl From<&fuel_tx::Input> for Input { predicate: HexString(predicate.to_vec()), predicate_data: HexString(predicate_data.clone()), }), + fuel_tx::Input::DataCoinPredicate( + fuel_tx::input::coin::DataCoinPredicate { + utxo_id, + owner, + amount, + asset_id, + tx_pointer, + predicate, + predicate_data, + predicate_gas_used, + .. + }, + ) => Input::DataCoin(InputDataCoin { + utxo_id: UtxoId(*utxo_id), + owner: Address(*owner), + amount: (*amount).into(), + asset_id: AssetId(*asset_id), + tx_pointer: TxPointer(*tx_pointer), + witness_index: Default::default(), + predicate_gas_used: (*predicate_gas_used).into(), + predicate: HexString(predicate.to_vec()), + predicate_data: HexString(predicate_data.clone()), + data: HexString(Default::default()), + }), fuel_tx::Input::Contract(contract) => Input::Contract(contract.into()), fuel_tx::Input::MessageCoinSigned( fuel_tx::input::message::MessageCoinSigned { diff --git a/crates/fuel-core/src/schema/tx/output.rs b/crates/fuel-core/src/schema/tx/output.rs index 8a5e2ed543b..72f71e9ab7d 100644 --- a/crates/fuel-core/src/schema/tx/output.rs +++ b/crates/fuel-core/src/schema/tx/output.rs @@ -1,15 +1,8 @@ -use crate::schema::scalars::{ - Address, - AssetId, - Bytes32, - ContractId, - U16, - U64, -}; use async_graphql::{ Object, Union, }; + use fuel_core_types::{ fuel_asm::Word, fuel_tx, @@ -17,9 +10,19 @@ use fuel_core_types::{ fuel_types, }; +use crate::schema::scalars::{ + Address, + AssetId, + Bytes32, + ContractId, + U16, + U64, +}; + #[derive(Union)] pub enum Output { Coin(CoinOutput), + DataCoin(DataCoinOutput), Contract(ContractOutput), Change(ChangeOutput), Variable(VariableOutput), @@ -47,6 +50,31 @@ impl CoinOutput { } } +pub struct DataCoinOutput { + to: fuel_types::Address, + amount: Word, + asset_id: fuel_types::AssetId, + data_hash: fuel_types::Bytes32, +} + +#[Object] +impl DataCoinOutput { + async fn to(&self) -> Address { + self.to.into() + } + + async fn amount(&self) -> U64 { + self.amount.into() + } + + async fn asset_id(&self) -> AssetId { + self.asset_id.into() + } + + async fn data(&self) -> Bytes32 { + self.data_hash.into() + } +} pub struct ChangeOutput(CoinOutput); #[Object] @@ -130,6 +158,17 @@ impl From<&fuel_tx::Output> for Output { amount: *amount, asset_id: *asset_id, }), + fuel_tx::Output::DataCoin { + to, + amount, + asset_id, + data_hash, + } => Output::DataCoin(DataCoinOutput { + to: *to, + amount: *amount, + asset_id: *asset_id, + data_hash: *data_hash, + }), fuel_tx::Output::Contract(contract) => Output::Contract(contract.into()), fuel_tx::Output::Change { to, From 7ac89febac8e6e6bf474dffba1d3719644c5bdbe Mon Sep 17 00:00:00 2001 From: Mitch Turner Date: Thu, 27 Mar 2025 17:18:01 -0600 Subject: [PATCH 03/37] Change output to use `Vec`--but it is not compiling :/// --- crates/fuel-core/src/executor.rs | 9 +++ crates/services/executor/src/executor.rs | 8 +-- crates/services/txpool_v2/src/error.rs | 4 ++ .../services/txpool_v2/src/storage/graph.rs | 61 ++++++++++++++++++- 4 files changed, 76 insertions(+), 6 deletions(-) diff --git a/crates/fuel-core/src/executor.rs b/crates/fuel-core/src/executor.rs index 2da3806cc06..6498d840f35 100644 --- a/crates/fuel-core/src/executor.rs +++ b/crates/fuel-core/src/executor.rs @@ -2965,6 +2965,15 @@ mod tests { assert!(result.is_ok(), "{result:?}") } + fn predicate_where_predicate_data_matches_input_data_coin() -> Vec { + vec![].into_iter().collect() + } + + #[test] + fn validate__predicate_can_find_data_coin_data() { + todo!() + } + #[test] fn verifying_during_production_consensus_parameters_version_works() { let mut rng = StdRng::seed_from_u64(2322u64); diff --git a/crates/services/executor/src/executor.rs b/crates/services/executor/src/executor.rs index d549bac9ae3..88d5542af4b 100644 --- a/crates/services/executor/src/executor.rs +++ b/crates/services/executor/src/executor.rs @@ -262,7 +262,7 @@ fn convert_tx_execution_result_to_preconfirmation( .iter() .filter_map(|output| { if output.is_change() || output.is_variable() && output.amount() != Some(0) { - Some(*output) + Some(output.clone()) } else { None } @@ -2277,7 +2277,7 @@ where amount, asset_id, to, - data_hash, + data, } => Self::insert_data_coin( block_height, execution_data, @@ -2285,7 +2285,7 @@ where amount, asset_id, to, - data_hash, + data, db, )?, Output::Contract(contract) => { @@ -2386,7 +2386,7 @@ where amount: &Word, asset_id: &AssetId, to: &Address, - _data_hash: &Bytes32, + _data: &[u8], db: &mut TxStorageTransaction, ) -> ExecutorResult<()> where diff --git a/crates/services/txpool_v2/src/error.rs b/crates/services/txpool_v2/src/error.rs index 9eecce52939..4c4ef2f0ece 100644 --- a/crates/services/txpool_v2/src/error.rs +++ b/crates/services/txpool_v2/src/error.rs @@ -162,6 +162,10 @@ pub enum InputValidationError { NotInsertedBlobIdAlreadyTaken(BlobId), #[display(fmt = "Input coin does not match the values from database")] NotInsertedIoCoinMismatch, + #[display(fmt = "Input dependent on a Coin output")] + NotInsertedIoCoinOutput, + #[display(fmt = "Input dependent on a DataCoin output")] + NotInsertedIoDataCoinOutput, #[display(fmt = "Wrong number of outputs: {_0}")] WrongOutputNumber(String), #[display(fmt = "UTXO (id: {_0}) does not exist")] diff --git a/crates/services/txpool_v2/src/storage/graph.rs b/crates/services/txpool_v2/src/storage/graph.rs index 9672aa1aef6..d1916a6dbd4 100644 --- a/crates/services/txpool_v2/src/storage/graph.rs +++ b/crates/services/txpool_v2/src/storage/graph.rs @@ -232,11 +232,58 @@ impl GraphStorage { )); } } + Output::DataCoin { .. } => { + return Err(Error::InputValidation( + InputValidationError::NotInsertedIoDataCoinOutput, + )) + } + Output::Contract(_) => { + return Err(Error::InputValidation( + InputValidationError::NotInsertedIoContractOutput, + )) + } + Output::Change { .. } => { + return Err(Error::InputValidation( + InputValidationError::NotInsertedInputDependentOnChangeOrVariable, + )) + } + Output::Variable { .. } => { + return Err(Error::InputValidation( + InputValidationError::NotInsertedInputDependentOnChangeOrVariable, + )) + } + Output::ContractCreated { .. } => { + return Err(Error::InputValidation( + InputValidationError::NotInsertedIoContractOutput, + )) + } + } + } else if let Input::DataCoinSigned(DataCoinSigned { + owner, + amount, + asset_id, + data, + .. + }) + | Input::DataCoinPredicate(DataCoinPredicate { + owner, + amount, + asset_id, + data, + .. + }) = input + { + let i_owner = owner; + let i_amount = amount; + let i_asset_id = asset_id; + let i_data = data; + + match output { Output::DataCoin { to, amount, asset_id, - data_hash: _data_hash, + data, } => { if to != i_owner { return Err(Error::InputValidation( @@ -253,7 +300,16 @@ impl GraphStorage { InputValidationError::NotInsertedIoWrongAssetId, )); } - todo!("Data hash matches data") + if data != i_data { + return Err(Error::InputValidation( + InputValidationError::NotInsertedIoDataCoinOutput, + )); + } + } + Output::Coin { .. } => { + return Err(Error::InputValidation( + InputValidationError::NotInsertedIoCoinOutput, + )) } Output::Contract(_) => { return Err(Error::InputValidation( @@ -277,6 +333,7 @@ impl GraphStorage { } }; } + Ok(()) } From 6c3fbacdb396b07aa0e10c305a507ba33e6b1d90 Mon Sep 17 00:00:00 2001 From: Mitch Turner Date: Thu, 27 Mar 2025 17:29:06 -0600 Subject: [PATCH 04/37] Update data type --- benches/benches/block_target_gas.rs | 2 +- crates/fuel-core/src/executor.rs | 6 +++--- crates/fuel-core/src/schema/tx/output.rs | 10 +++++----- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/benches/benches/block_target_gas.rs b/benches/benches/block_target_gas.rs index 0a7da601888..48221c6c1f1 100644 --- a/benches/benches/block_target_gas.rs +++ b/benches/benches/block_target_gas.rs @@ -428,7 +428,7 @@ fn run_with_service_with_extra_inputs( } for output in &extra_outputs { - tx_builder.add_output(*output); + tx_builder.add_output(output.clone()); } let mut tx = tx_builder.finalize_as_transaction(); let chain_config = shared.config.snapshot_reader.chain_config().clone(); diff --git a/crates/fuel-core/src/executor.rs b/crates/fuel-core/src/executor.rs index 6498d840f35..da3c4df2b9c 100644 --- a/crates/fuel-core/src/executor.rs +++ b/crates/fuel-core/src/executor.rs @@ -2965,9 +2965,9 @@ mod tests { assert!(result.is_ok(), "{result:?}") } - fn predicate_where_predicate_data_matches_input_data_coin() -> Vec { - vec![].into_iter().collect() - } + // fn predicate_where_predicate_data_matches_input_data_coin() -> Vec { + // Vec::new() + // } #[test] fn validate__predicate_can_find_data_coin_data() { diff --git a/crates/fuel-core/src/schema/tx/output.rs b/crates/fuel-core/src/schema/tx/output.rs index 72f71e9ab7d..b8bb2ae6d9c 100644 --- a/crates/fuel-core/src/schema/tx/output.rs +++ b/crates/fuel-core/src/schema/tx/output.rs @@ -54,7 +54,7 @@ pub struct DataCoinOutput { to: fuel_types::Address, amount: Word, asset_id: fuel_types::AssetId, - data_hash: fuel_types::Bytes32, + data: Vec, } #[Object] @@ -71,8 +71,8 @@ impl DataCoinOutput { self.asset_id.into() } - async fn data(&self) -> Bytes32 { - self.data_hash.into() + async fn data(&self) -> &Vec { + &self.data } } pub struct ChangeOutput(CoinOutput); @@ -162,12 +162,12 @@ impl From<&fuel_tx::Output> for Output { to, amount, asset_id, - data_hash, + data, } => Output::DataCoin(DataCoinOutput { to: *to, amount: *amount, asset_id: *asset_id, - data_hash: *data_hash, + data: data.to_vec(), }), fuel_tx::Output::Contract(contract) => Output::Contract(contract.into()), fuel_tx::Output::Change { From d5ff3bffd256165b7df195504c9a163348e5f5b6 Mon Sep 17 00:00:00 2001 From: Mitch Turner Date: Fri, 28 Mar 2025 12:39:22 -0600 Subject: [PATCH 05/37] WIP add data coins to fuel-core level concepts --- crates/chain-config/src/config/coin.rs | 28 ++- crates/fuel-core/src/coins_query.rs | 2 +- .../src/database/genesis_progress.rs | 12 +- crates/fuel-core/src/executor.rs | 121 ++++++++++++- .../src/query/balance/asset_query.rs | 2 +- crates/fuel-core/src/query/coin.rs | 9 +- crates/fuel-core/src/service/genesis.rs | 2 +- .../src/service/genesis/importer/off_chain.rs | 22 ++- crates/services/executor/src/executor.rs | 56 ++++-- .../services/txpool_v2/src/tests/universe.rs | 2 +- crates/types/src/entities/coins.rs | 15 +- crates/types/src/entities/coins/coin.rs | 167 +++++++++++++++++- crates/types/src/services/executor.rs | 5 + 13 files changed, 398 insertions(+), 45 deletions(-) diff --git a/crates/chain-config/src/config/coin.rs b/crates/chain-config/src/config/coin.rs index d38717e7d53..85f059d930c 100644 --- a/crates/chain-config/src/config/coin.rs +++ b/crates/chain-config/src/config/coin.rs @@ -1,3 +1,4 @@ +use super::table_entry::TableEntry; use crate::GenesisCommitment; use fuel_core_storage::{ tables::Coins, @@ -8,6 +9,7 @@ use fuel_core_types::{ Coin, CompressedCoin, CompressedCoinV1, + DataCoin, }, fuel_crypto::Hasher, fuel_tx::{ @@ -26,8 +28,6 @@ use serde::{ Serialize, }; -use super::table_entry::TableEntry; - #[derive(Default, Clone, Debug, Deserialize, Serialize, Eq, PartialEq)] pub struct CoinConfig { /// auto-generated if None @@ -121,6 +121,30 @@ impl GenesisCommitment for Coin { } } +impl GenesisCommitment for DataCoin { + fn root(&self) -> anyhow::Result { + let owner = self.owner; + let amount = self.amount; + let asset_id = self.asset_id; + let tx_pointer = self.tx_pointer; + let utxo_id = self.utxo_id; + let data = &self.data; + + let coin_hash = *Hasher::default() + .chain(owner) + .chain(amount.to_be_bytes()) + .chain(asset_id) + .chain(tx_pointer.block_height().to_be_bytes()) + .chain(tx_pointer.tx_index().to_be_bytes()) + .chain(utxo_id.tx_id()) + .chain(utxo_id.output_index().to_be_bytes()) + .chain(data) + .finalize(); + + Ok(coin_hash) + } +} + #[cfg(feature = "test-helpers")] pub mod coin_config_helpers { use crate::CoinConfig; diff --git a/crates/fuel-core/src/coins_query.rs b/crates/fuel-core/src/coins_query.rs index e14ff47ddaa..59c7b4ed606 100644 --- a/crates/fuel-core/src/coins_query.rs +++ b/crates/fuel-core/src/coins_query.rs @@ -1653,7 +1653,7 @@ mod tests { let coin_by_owner = owner_coin_id_key(&owner, &id); StorageMutate::::insert(db, &coin_by_owner, &()).unwrap(); - coin.uncompress(id) + coin.uncompress_coin(id) } pub fn make_message(&mut self, owner: Address, amount: Word) -> Message { diff --git a/crates/fuel-core/src/database/genesis_progress.rs b/crates/fuel-core/src/database/genesis_progress.rs index 9e847f38dba..a8dc0103a6e 100644 --- a/crates/fuel-core/src/database/genesis_progress.rs +++ b/crates/fuel-core/src/database/genesis_progress.rs @@ -30,7 +30,10 @@ use fuel_core_storage::{ StorageInspect, StorageMutate, }; -use fuel_core_types::fuel_merkle::binary::root_calculator::MerkleRootCalculator; +use fuel_core_types::{ + entities::coins::coin::UncompressedCoin, + fuel_merkle::binary::root_calculator::MerkleRootCalculator, +}; pub struct GenesisMetadata(core::marker::PhantomData); @@ -109,7 +112,12 @@ impl GenesisDatabase { let mut root_calculator = MerkleRootCalculator::new(); for coin in coins { let (utxo_id, coin) = coin?; - root_calculator.push(coin.uncompress(utxo_id).root()?.as_slice()); + // root_calculator.push(coin.uncompress_coin(utxo_id).root()?.as_slice()); + let root = match coin.uncompress(utxo_id) { + UncompressedCoin::Coin(coin) => coin.root()?.as_slice(), + UncompressedCoin::DataCoin(data_coin) => data_coin.root()?.as_slice(), + }; + root_calculator.push(root); } Ok(root_calculator.root()) diff --git a/crates/fuel-core/src/executor.rs b/crates/fuel-core/src/executor.rs index da3c4df2b9c..0eddb36578a 100644 --- a/crates/fuel-core/src/executor.rs +++ b/crates/fuel-core/src/executor.rs @@ -95,6 +95,7 @@ mod tests { coin::{ CoinPredicate, CoinSigned, + DataCoinPredicate, }, contract, Input, @@ -2965,13 +2966,125 @@ mod tests { assert!(result.is_ok(), "{result:?}") } - // fn predicate_where_predicate_data_matches_input_data_coin() -> Vec { - // Vec::new() - // } + fn predicate_checking_predicate_data_matches_input_data_coin() -> Vec { + let len_reg = 0x13; + let res_reg = 0x10; + let input_index = 0; + let expected_data_mem_location = 0x22; + let actual_data_mem_location = 0x23; + vec![ + op::gtf_args(len_reg, input_index, GTFArgs::InputDataCoinDataLength), + // get expected data from predicate data + op::gtf_args( + expected_data_mem_location, + input_index, + GTFArgs::InputCoinPredicateData, + ), + // get actual data + op::gtf_args( + actual_data_mem_location, + input_index, + GTFArgs::InputDataCoinData, + ), + // compare + op::meq( + res_reg, + expected_data_mem_location, + actual_data_mem_location, + len_reg, + ), + op::ret(res_reg), + ] + .into_iter() + .collect() + } #[test] fn validate__predicate_can_find_data_coin_data() { - todo!() + let mut rng = StdRng::seed_from_u64(2322u64); + let predicate: Vec = + predicate_checking_predicate_data_matches_input_data_coin(); + let owner = Input::predicate_owner(&predicate); + let amount = 1000; + + let consensus_parameters = ConsensusParameters::default(); + let config = Config { + forbid_fake_coins_default: true, + consensus_parameters: consensus_parameters.clone(), + }; + let data = vec![99u8; 100]; + let predicate_data = data.clone(); + + let mut tx = TransactionBuilder::script( + vec![op::ret(RegId::ONE)].into_iter().collect(), + vec![], + ) + .max_fee_limit(amount) + .add_input(Input::data_coin_predicate( + rng.gen(), + owner, + amount, + AssetId::BASE, + rng.gen(), + 0, + predicate, + predicate_data, + data, + )) + .add_output(Output::Change { + to: Default::default(), + amount: 0, + asset_id: Default::default(), + }) + .finalize(); + tx.estimate_predicates( + &consensus_parameters.clone().into(), + MemoryInstance::new(), + &EmptyStorage, + ) + .unwrap(); + let db = &mut Database::default(); + + // insert coin into state + if let Input::DataCoinPredicate(DataCoinPredicate { + utxo_id, + owner, + amount, + asset_id, + tx_pointer, + .. + }) = tx.inputs()[0] + { + let mut coin = CompressedCoin::default(); + coin.set_owner(owner); + coin.set_amount(amount); + coin.set_asset_id(asset_id); + coin.set_tx_pointer(tx_pointer); + db.storage::().insert(&utxo_id, &coin).unwrap(); + } else { + panic!("Expected a DataCoinPredicate"); + } + + let producer = create_executor(db.clone(), config.clone()); + + let ExecutionResult { + block, + skipped_transactions, + .. + } = producer + .produce_without_commit_with_source_direct_resolve(Components { + header_to_produce: PartialBlockHeader::default(), + transactions_source: OnceTransactionsSource::new(vec![tx.into()]), + coinbase_recipient: Default::default(), + gas_price: 1, + }) + .unwrap() + .into_result(); + assert!(skipped_transactions.is_empty()); + + let validator = create_executor(db.clone(), config); + let result = validator.validate(&block); + assert!(result.is_ok(), "{result:?}") } #[test] diff --git a/crates/fuel-core/src/query/balance/asset_query.rs b/crates/fuel-core/src/query/balance/asset_query.rs index 9f4f244305d..add403c37a5 100644 --- a/crates/fuel-core/src/query/balance/asset_query.rs +++ b/crates/fuel-core/src/query/balance/asset_query.rs @@ -139,7 +139,7 @@ impl<'a> AssetsQuery<'a> { .try_flatten() .filter(move |result| { if let Ok(CoinType::Coin(coin)) = result { - allowed_asset(&allowed_assets, &coin.asset_id) + allowed_asset(&allowed_assets, coin.asset_id()) } else { true } diff --git a/crates/fuel-core/src/query/coin.rs b/crates/fuel-core/src/query/coin.rs index c487bdba23c..be35ca1d85d 100644 --- a/crates/fuel-core/src/query/coin.rs +++ b/crates/fuel-core/src/query/coin.rs @@ -8,7 +8,10 @@ use fuel_core_storage::{ StorageAsRef, }; use fuel_core_types::{ - entities::coins::coin::Coin, + entities::coins::coin::{ + Coin, + UncompressedCoin, + }, fuel_tx::UtxoId, fuel_types::Address, }; @@ -19,7 +22,7 @@ use futures::{ }; impl ReadView { - pub fn coin(&self, utxo_id: UtxoId) -> StorageResult { + pub fn coin(&self, utxo_id: UtxoId) -> StorageResult { let coin = self .on_chain .as_ref() @@ -34,7 +37,7 @@ impl ReadView { pub async fn coins( &self, utxo_ids: Vec, - ) -> impl Iterator> + '_ { + ) -> impl Iterator> + '_ { // TODO: Use multiget when it's implemented. // https://github.com/FuelLabs/fuel-core/issues/2344 let coins = utxo_ids.into_iter().map(|id| self.coin(id)); diff --git a/crates/fuel-core/src/service/genesis.rs b/crates/fuel-core/src/service/genesis.rs index 462be780de8..ea7421379fa 100644 --- a/crates/fuel-core/src/service/genesis.rs +++ b/crates/fuel-core/src/service/genesis.rs @@ -691,7 +691,7 @@ mod tests { db.on_chain() .storage::() .get(&coin_id) - .map(|v| v.unwrap().into_owned().uncompress(coin_id)) + .map(|v| v.unwrap().into_owned().uncompress_coin(coin_id)) .unwrap() }) .collect() diff --git a/crates/fuel-core/src/service/genesis/importer/off_chain.rs b/crates/fuel-core/src/service/genesis/importer/off_chain.rs index 42c9c320f3b..e55e6c1eaa2 100644 --- a/crates/fuel-core/src/service/genesis/importer/off_chain.rs +++ b/crates/fuel-core/src/service/genesis/importer/off_chain.rs @@ -1,3 +1,7 @@ +use super::{ + import_task::ImportTable, + Handler, +}; use crate::{ database::{ database_description::off_chain::OffChain, @@ -35,13 +39,11 @@ use fuel_core_storage::{ transactional::StorageTransaction, StorageAsMut, }; -use fuel_core_types::services::executor::Event; -use std::borrow::Cow; - -use super::{ - import_task::ImportTable, - Handler, +use fuel_core_types::{ + entities::coins::coin::UncompressedCoin, + services::executor::Event, }; +use std::borrow::Cow; fn balances_indexation_enabled() -> bool { use std::sync::OnceLock; @@ -167,7 +169,13 @@ impl ImportTable for Handler { tx: &mut StorageTransaction<&mut GenesisDatabase>, ) -> anyhow::Result<()> { let events = group.into_iter().map(|TableEntry { value, key }| { - Cow::Owned(Event::CoinCreated(value.uncompress(key))) + // Cow::Owned(Event::CoinCreated(value.uncompress_coin(key))) + match value.uncompress(key) { + UncompressedCoin::Coin(coin) => Cow::Owned(Event::CoinCreated(coin)), + UncompressedCoin::DataCoin(data_coin) => { + Cow::Owned(Event::DataCoinCreated(data_coin)) + } + } }); worker_service::process_executor_events( events, diff --git a/crates/services/executor/src/executor.rs b/crates/services/executor/src/executor.rs index 88d5542af4b..2235ed4036f 100644 --- a/crates/services/executor/src/executor.rs +++ b/crates/services/executor/src/executor.rs @@ -169,9 +169,12 @@ use alloc::{ vec, vec::Vec, }; -use fuel_core_types::fuel_tx::input::coin::{ - DataCoinPredicate, - DataCoinSigned, +use fuel_core_types::{ + entities::coins::coin::UncompressedCoin, + fuel_tx::input::coin::{ + DataCoinPredicate, + DataCoinSigned, + }, }; /// The maximum amount of transactions that can be included in a block, @@ -2048,9 +2051,18 @@ where ) })?; - execution_data - .events - .push(ExecutorEvent::CoinConsumed(coin.uncompress(*utxo_id))); + match coin.uncompress(*utxo_id) { + UncompressedCoin::Coin(coin) => { + execution_data + .events + .push(ExecutorEvent::CoinConsumed(coin)); + } + UncompressedCoin::DataCoin(data_coin) => { + execution_data + .events + .push(ExecutorEvent::DataCoinConsumed(data_coin)); + } + } } Input::MessageDataSigned(_) | Input::MessageDataPredicate(_) if reverted => @@ -2370,9 +2382,19 @@ where if db.storage::().replace(&utxo_id, &coin)?.is_some() { return Err(ExecutorError::OutputAlreadyExists) } - execution_data - .events - .push(ExecutorEvent::CoinCreated(coin.uncompress(utxo_id))); + // execution_data + // .events + // .push(ExecutorEvent::CoinCreated(coin.uncompress_coin(utxo_id))); + match coin.uncompress(utxo_id) { + UncompressedCoin::Coin(coin) => { + execution_data.events.push(ExecutorEvent::CoinCreated(coin)); + } + UncompressedCoin::DataCoin(data_coin) => { + execution_data + .events + .push(ExecutorEvent::DataCoinCreated(data_coin)); + } + } } Ok(()) @@ -2407,9 +2429,19 @@ where if db.storage::().replace(&utxo_id, &coin)?.is_some() { return Err(ExecutorError::OutputAlreadyExists) } - execution_data - .events - .push(ExecutorEvent::CoinCreated(coin.uncompress(utxo_id))); + // execution_data + // .events + // .push(ExecutorEvent::CoinCreated(coin.uncompress_coin(utxo_id))); + match coin.uncompress(utxo_id) { + UncompressedCoin::Coin(coin) => { + execution_data.events.push(ExecutorEvent::CoinCreated(coin)); + } + UncompressedCoin::DataCoin(data_coin) => { + execution_data + .events + .push(ExecutorEvent::DataCoinCreated(data_coin)); + } + } } Ok(()) diff --git a/crates/services/txpool_v2/src/tests/universe.rs b/crates/services/txpool_v2/src/tests/universe.rs index 34030a0a9f2..7626a757de5 100644 --- a/crates/services/txpool_v2/src/tests/universe.rs +++ b/crates/services/txpool_v2/src/tests/universe.rs @@ -411,7 +411,7 @@ impl TestPoolUniverse { .unwrap() .coins .insert(utxo_id, coin.clone()); - (coin.uncompress(utxo_id), input) + (coin.uncompress_coin(utxo_id), input) } pub fn create_output_and_input(&mut self) -> (Output, UnsetInput) { diff --git a/crates/types/src/entities/coins.rs b/crates/types/src/entities/coins.rs index a1db13fc8a0..3a968179726 100644 --- a/crates/types/src/entities/coins.rs +++ b/crates/types/src/entities/coins.rs @@ -1,6 +1,7 @@ //! The module for all possible coins. use crate::{ + entities::coins::coin::UncompressedCoin, fuel_asm::Word, fuel_tx::Address, fuel_types::{ @@ -39,10 +40,10 @@ impl From for CoinId { /// The enum of all kind of coins. #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[derive(Debug, Clone, Eq, PartialEq)] pub enum CoinType { /// The regular coins generated by the transaction output. - Coin(Coin), + Coin(UncompressedCoin), /// The bridged coin from the DA layer. MessageCoin(MessageCoin), } @@ -51,7 +52,7 @@ impl CoinType { /// Returns the coin unique identifier. pub fn coin_id(&self) -> CoinId { match self { - CoinType::Coin(coin) => CoinId::Utxo(coin.utxo_id), + CoinType::Coin(coin) => CoinId::Utxo(*coin.utxo_id()), CoinType::MessageCoin(coin) => CoinId::Message(coin.nonce), } } @@ -59,7 +60,7 @@ impl CoinType { /// Returns the owner of the coin. pub fn owner(&self) -> &Address { match self { - CoinType::Coin(coin) => &coin.owner, + CoinType::Coin(coin) => &coin.owner(), CoinType::MessageCoin(coin) => &coin.recipient, } } @@ -67,7 +68,7 @@ impl CoinType { /// Returns the amount of the asset held by the coin. pub fn amount(&self) -> Word { match self { - CoinType::Coin(coin) => coin.amount, + CoinType::Coin(coin) => *coin.amount(), CoinType::MessageCoin(coin) => coin.amount, } } @@ -75,7 +76,7 @@ impl CoinType { /// Returns the asset held by the coin. pub fn asset_id<'a>(&'a self, base_asset_id: &'a AssetId) -> &'a AssetId { match self { - CoinType::Coin(coin) => &coin.asset_id, + CoinType::Coin(coin) => coin.asset_id(), CoinType::MessageCoin(_) => base_asset_id, } } @@ -83,7 +84,7 @@ impl CoinType { impl From for CoinType { fn from(coin: Coin) -> Self { - CoinType::Coin(coin) + CoinType::Coin(UncompressedCoin::Coin(coin)) } } diff --git a/crates/types/src/entities/coins/coin.rs b/crates/types/src/entities/coins/coin.rs index 283eb7f25c4..484295aaca1 100644 --- a/crates/types/src/entities/coins/coin.rs +++ b/crates/types/src/entities/coins/coin.rs @@ -6,6 +6,8 @@ use crate::{ input::coin::{ CoinPredicate, CoinSigned, + DataCoinPredicate, + DataCoinSigned, }, Input, TxPointer, @@ -46,6 +48,38 @@ impl Coin { } } +/// Represents the user's coin for some asset with `asset_id`. +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Debug, Clone, Eq, PartialOrd, PartialEq)] +pub struct DataCoin { + /// The coin utxo id. + pub utxo_id: UtxoId, + /// The address with permission to spend this coin + pub owner: Address, + /// Amount of coins + pub amount: Word, + /// Different incompatible coins can coexist with different asset ids. + /// This is the "color" of the coin. + pub asset_id: AssetId, + /// Indexes the block and transaction this coin originated from + pub tx_pointer: TxPointer, + /// A data field that can be used to store arbitrary data. + pub data: Vec, +} + +impl DataCoin { + /// Compress the coin to minimize the serialized size. + pub fn compress(self) -> CompressedCoin { + CompressedCoin::V2(CompressedCoinV2 { + owner: self.owner, + amount: self.amount, + asset_id: self.asset_id, + tx_pointer: self.tx_pointer, + data: self.data, + }) + } +} + /// The compressed version of the `Coin` with minimum fields required for /// the proper work of the blockchain. #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] @@ -54,6 +88,8 @@ impl Coin { pub enum CompressedCoin { /// CompressedCoin Version 1 V1(CompressedCoinV1), + /// CompressedCoin Version 2 + V2(CompressedCoinV2), } #[cfg(any(test, feature = "test-helpers"))] @@ -78,6 +114,75 @@ pub struct CompressedCoinV1 { pub tx_pointer: TxPointer, } +/// CompressedCoin Version 2 +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Default, Debug, Clone, PartialEq, Eq)] +pub struct CompressedCoinV2 { + /// The address with permission to spend this coin + pub owner: Address, + /// Amount of coins + pub amount: Word, + /// Different incompatible coins can coexist with different asset ids. + /// This is the "color" of the coin. + pub asset_id: AssetId, + /// Indexes the block and transaction this coin originated from + pub tx_pointer: TxPointer, + /// Data + pub data: Vec, +} + +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Debug, Clone, Eq, PartialEq)] +/// The uncompressed version of `CompressedCoin` +pub enum UncompressedCoin { + /// Coin derived from `V1` + Coin(Coin), + /// Coin with data field derived from `V2` + DataCoin(DataCoin), +} + +impl UncompressedCoin { + /// Get the UTXO ID of the coin + pub fn utxo_id(&self) -> &UtxoId { + match self { + UncompressedCoin::Coin(coin) => &coin.utxo_id, + UncompressedCoin::DataCoin(coin) => &coin.utxo_id, + } + } + + /// Get the owner of the coin + pub fn owner(&self) -> &Address { + match self { + UncompressedCoin::Coin(coin) => &coin.owner, + UncompressedCoin::DataCoin(coin) => &coin.owner, + } + } + + /// Get the amount of the coin + pub fn amount(&self) -> &Word { + match self { + UncompressedCoin::Coin(coin) => &coin.amount, + UncompressedCoin::DataCoin(coin) => &coin.amount, + } + } + + /// Get the asset ID of the coin + pub fn asset_id(&self) -> &AssetId { + match self { + UncompressedCoin::Coin(coin) => &coin.asset_id, + UncompressedCoin::DataCoin(coin) => &coin.asset_id, + } + } + + /// Get the TX Pointer of the coin + pub fn tx_pointer(&self) -> &TxPointer { + match self { + UncompressedCoin::Coin(coin) => &coin.tx_pointer, + UncompressedCoin::DataCoin(coin) => &coin.tx_pointer, + } + } +} + impl From for CompressedCoin { fn from(value: CompressedCoinV1) -> Self { Self::V1(value) @@ -86,22 +191,49 @@ impl From for CompressedCoin { impl CompressedCoin { /// Uncompress the coin. - pub fn uncompress(self, utxo_id: UtxoId) -> Coin { + pub fn uncompress_coin(self, utxo_id: UtxoId) -> Option { match self { - CompressedCoin::V1(coin) => Coin { + CompressedCoin::V1(coin) => Some(Coin { utxo_id, owner: coin.owner, amount: coin.amount, asset_id: coin.asset_id, tx_pointer: coin.tx_pointer, - }, + }), + CompressedCoin::V2(_coin) => None, + } + } + + /// Uncompress the coin to `UncompressedCoin` enum + pub fn uncompress(self, utxo_id: UtxoId) -> UncompressedCoin { + match self { + CompressedCoin::V1(coin) => UncompressedCoin::Coin(Coin { + utxo_id, + owner: coin.owner, + amount: coin.amount, + asset_id: coin.asset_id, + tx_pointer: coin.tx_pointer, + }), + CompressedCoin::V2(coin) => UncompressedCoin::DataCoin(DataCoin { + utxo_id, + owner: coin.owner, + amount: coin.amount, + asset_id: coin.asset_id, + tx_pointer: coin.tx_pointer, + data: coin.data, + }), } } + // pub fn uncompress_data_coin(self, _utxo_id: UtxoId) -> Option { + // None + // } + /// Get the owner of the coin pub fn owner(&self) -> &Address { match self { CompressedCoin::V1(coin) => &coin.owner, + CompressedCoin::V2(coin) => &coin.owner, } } @@ -109,6 +241,7 @@ impl CompressedCoin { pub fn set_owner(&mut self, owner: Address) { match self { CompressedCoin::V1(coin) => coin.owner = owner, + CompressedCoin::V2(coin) => coin.owner = owner, } } @@ -116,6 +249,7 @@ impl CompressedCoin { pub fn amount(&self) -> &Word { match self { CompressedCoin::V1(coin) => &coin.amount, + CompressedCoin::V2(coin) => &coin.amount, } } @@ -123,6 +257,7 @@ impl CompressedCoin { pub fn set_amount(&mut self, amount: Word) { match self { CompressedCoin::V1(coin) => coin.amount = amount, + CompressedCoin::V2(coin) => coin.amount = amount, } } @@ -130,6 +265,7 @@ impl CompressedCoin { pub fn asset_id(&self) -> &AssetId { match self { CompressedCoin::V1(coin) => &coin.asset_id, + CompressedCoin::V2(coin) => &coin.asset_id, } } @@ -137,6 +273,7 @@ impl CompressedCoin { pub fn set_asset_id(&mut self, asset_id: AssetId) { match self { CompressedCoin::V1(coin) => coin.asset_id = asset_id, + CompressedCoin::V2(coin) => coin.asset_id = asset_id, } } @@ -144,6 +281,7 @@ impl CompressedCoin { pub fn tx_pointer(&self) -> &TxPointer { match self { CompressedCoin::V1(coin) => &coin.tx_pointer, + CompressedCoin::V2(coin) => &coin.tx_pointer, } } @@ -151,12 +289,13 @@ impl CompressedCoin { pub fn set_tx_pointer(&mut self, tx_pointer: TxPointer) { match self { CompressedCoin::V1(coin) => coin.tx_pointer = tx_pointer, + CompressedCoin::V2(coin) => coin.tx_pointer = tx_pointer, } } /// Verifies the integrity of the coin. /// - /// Returns `None`, if the `input` is not a coin. + /// Returns `None`, if the `input` is a different type of input. /// Otherwise, returns the result of the field comparison. pub fn matches_input(&self, input: &Input) -> Option { match input { @@ -177,6 +316,26 @@ impl CompressedCoin { && amount == &coin.amount && asset_id == &coin.asset_id, ), + _ => None, + }, + Input::DataCoinSigned(DataCoinSigned { + owner, + amount, + asset_id, + .. + }) + | Input::DataCoinPredicate(DataCoinPredicate { + owner, + amount, + asset_id, + .. + }) => match self { + CompressedCoin::V2(coin) => Some( + owner == &coin.owner + && amount == &coin.amount + && asset_id == &coin.asset_id, + ), + _ => None, }, _ => None, } diff --git a/crates/types/src/services/executor.rs b/crates/types/src/services/executor.rs index f05a4a3ac1d..08169c28e71 100644 --- a/crates/types/src/services/executor.rs +++ b/crates/types/src/services/executor.rs @@ -36,6 +36,7 @@ use crate::{ services::Uncommitted, }; +use crate::entities::coins::coin::DataCoin; #[cfg(feature = "alloc")] use alloc::{ string::String, @@ -137,8 +138,12 @@ pub enum Event { MessageConsumed(Message), /// Created a new spendable coin, produced by the transaction. CoinCreated(Coin), + /// Created a new spendable data coin, produced by the transaction. + DataCoinCreated(DataCoin), /// The coin was consumed by the transaction. CoinConsumed(Coin), + /// The data coin was consumed by the transaction. + DataCoinConsumed(DataCoin), /// Failed transaction inclusion ForcedTransactionFailed { /// The hash of the relayed transaction From f6686d3ae885a33e676200efd95bf775b12bdbd1 Mon Sep 17 00:00:00 2001 From: Mitch Turner Date: Sat, 29 Mar 2025 15:35:54 -0600 Subject: [PATCH 06/37] Finish adding data coin and fixing helpers --- crates/fuel-core/src/coins_query.rs | 13 ++-- .../src/database/genesis_progress.rs | 7 +- .../src/graphql_api/indexation/balances.rs | 70 ++++++++++++++++--- .../graphql_api/indexation/coins_to_spend.rs | 68 +++++++++++++++--- .../src/graphql_api/storage/coins.rs | 26 ++++++- .../src/graphql_api/storage/coins/codecs.rs | 33 +++++++++ .../src/graphql_api/worker_service.rs | 13 ++++ crates/fuel-core/src/query/coin.rs | 7 +- crates/fuel-core/src/schema/coins.rs | 32 +++++---- crates/fuel-core/src/schema/tx/assemble_tx.rs | 18 ++--- crates/fuel-core/src/service/genesis.rs | 17 +++-- crates/types/src/entities/coins/coin.rs | 8 +++ 12 files changed, 249 insertions(+), 63 deletions(-) diff --git a/crates/fuel-core/src/coins_query.rs b/crates/fuel-core/src/coins_query.rs index 59c7b4ed606..18f399e535b 100644 --- a/crates/fuel-core/src/coins_query.rs +++ b/crates/fuel-core/src/coins_query.rs @@ -375,6 +375,7 @@ where fn is_excluded(key: &CoinsToSpendIndexKey, exclude: &Exclude) -> bool { match key { CoinsToSpendIndexKey::Coin { utxo_id, .. } => exclude.contains_coin(utxo_id), + CoinsToSpendIndexKey::DataCoin { utxo_id, .. } => exclude.contains_coin(utxo_id), CoinsToSpendIndexKey::Message { nonce, .. } => exclude.contains_message(nonce), } } @@ -462,8 +463,8 @@ mod tests { blockchain::primitives::DaBlockHeight, entities::{ coins::coin::{ - Coin, CompressedCoin, + UncompressedCoin, }, relayer::message::{ Message, @@ -1025,8 +1026,8 @@ mod tests { .owned_coins(&owner) .await .into_iter() - .filter(|coin| coin.amount == 5) - .map(|coin| CoinId::Utxo(coin.utxo_id)) + .filter(|coin| *coin.amount() == 5) + .map(|coin| CoinId::Utxo(*coin.utxo_id())) .collect_vec(); exclusion_assert(owner, &asset_ids, base_asset_id, db, excluded_ids).await; @@ -1637,7 +1638,7 @@ mod tests { owner: Address, amount: Word, asset_id: AssetId, - ) -> Coin { + ) -> UncompressedCoin { let index = self.last_coin_index; self.last_coin_index += 1; @@ -1653,7 +1654,7 @@ mod tests { let coin_by_owner = owner_coin_id_key(&owner, &id); StorageMutate::::insert(db, &coin_by_owner, &()).unwrap(); - coin.uncompress_coin(id) + coin.uncompress(id) } pub fn make_message(&mut self, owner: Address, amount: Word) -> Message { @@ -1680,7 +1681,7 @@ mod tests { message } - pub async fn owned_coins(&self, owner: &Address) -> Vec { + pub async fn owned_coins(&self, owner: &Address) -> Vec { let query = self.service_database(); let query = query.test_view(); query diff --git a/crates/fuel-core/src/database/genesis_progress.rs b/crates/fuel-core/src/database/genesis_progress.rs index a8dc0103a6e..3008f5be724 100644 --- a/crates/fuel-core/src/database/genesis_progress.rs +++ b/crates/fuel-core/src/database/genesis_progress.rs @@ -112,12 +112,11 @@ impl GenesisDatabase { let mut root_calculator = MerkleRootCalculator::new(); for coin in coins { let (utxo_id, coin) = coin?; - // root_calculator.push(coin.uncompress_coin(utxo_id).root()?.as_slice()); let root = match coin.uncompress(utxo_id) { - UncompressedCoin::Coin(coin) => coin.root()?.as_slice(), - UncompressedCoin::DataCoin(data_coin) => data_coin.root()?.as_slice(), + UncompressedCoin::Coin(coin) => coin.root()?, + UncompressedCoin::DataCoin(data_coin) => data_coin.root()?, }; - root_calculator.push(root); + root_calculator.push(&root); } Ok(root_calculator.root()) diff --git a/crates/fuel-core/src/graphql_api/indexation/balances.rs b/crates/fuel-core/src/graphql_api/indexation/balances.rs index 29bc22bc3d9..cc24341ea98 100644 --- a/crates/fuel-core/src/graphql_api/indexation/balances.rs +++ b/crates/fuel-core/src/graphql_api/indexation/balances.rs @@ -1,12 +1,3 @@ -use fuel_core_storage::StorageAsMut; -use fuel_core_types::{ - entities::{ - coins::coin::Coin, - Message, - }, - services::executor::Event, -}; - use crate::graphql_api::{ ports::worker::OffChainDatabaseTransaction, storage::balances::{ @@ -16,6 +7,17 @@ use crate::graphql_api::{ MessageBalances, }, }; +use fuel_core_storage::StorageAsMut; +use fuel_core_types::{ + entities::{ + coins::coin::{ + Coin, + DataCoin, + }, + Message, + }, + services::executor::Event, +}; use super::error::IndexationError; @@ -112,6 +114,24 @@ where .map_err(Into::into) } +fn increase_data_coin_balance( + block_st_transaction: &mut T, + coin: &DataCoin, +) -> Result<(), IndexationError> +where + T: OffChainDatabaseTransaction, +{ + let key = CoinBalancesKey::new(&coin.owner, &coin.asset_id); + let storage = block_st_transaction.storage::(); + let current_amount = storage.get(&key)?.unwrap_or_default().into_owned(); + let new_amount = current_amount.saturating_add(u128::from(coin.amount)); + + block_st_transaction + .storage::() + .insert(&key, &new_amount) + .map_err(Into::into) +} + fn decrease_coin_balance( block_st_transaction: &mut T, coin: &Coin, @@ -138,6 +158,32 @@ where .map_err(Into::into) } +fn decrease_data_coin_balance( + block_st_transaction: &mut T, + coin: &DataCoin, +) -> Result<(), IndexationError> +where + T: OffChainDatabaseTransaction, +{ + let key = CoinBalancesKey::new(&coin.owner, &coin.asset_id); + let storage = block_st_transaction.storage::(); + let current_amount = storage.get(&key)?.unwrap_or_default().into_owned(); + + let new_amount = current_amount + .checked_sub(u128::from(coin.amount)) + .ok_or_else(|| IndexationError::CoinBalanceWouldUnderflow { + owner: coin.owner, + asset_id: coin.asset_id, + current_amount, + requested_deduction: u128::from(coin.amount), + })?; + + block_st_transaction + .storage::() + .insert(&key, &new_amount) + .map_err(Into::into) +} + pub(crate) fn update( event: &Event, block_st_transaction: &mut T, @@ -159,6 +205,12 @@ where } Event::CoinCreated(coin) => increase_coin_balance(block_st_transaction, coin), Event::CoinConsumed(coin) => decrease_coin_balance(block_st_transaction, coin), + Event::DataCoinCreated(coin) => { + increase_data_coin_balance(block_st_transaction, coin) + } + Event::DataCoinConsumed(coin) => { + decrease_data_coin_balance(block_st_transaction, coin) + } Event::ForcedTransactionFailed { .. } => Ok(()), } } diff --git a/crates/fuel-core/src/graphql_api/indexation/coins_to_spend.rs b/crates/fuel-core/src/graphql_api/indexation/coins_to_spend.rs index c6e853c5ca2..55a6d30a9e7 100644 --- a/crates/fuel-core/src/graphql_api/indexation/coins_to_spend.rs +++ b/crates/fuel-core/src/graphql_api/indexation/coins_to_spend.rs @@ -1,14 +1,5 @@ use fuel_core_storage::StorageAsMut; -use fuel_core_types::{ - entities::{ - coins::coin::Coin, - Message, - }, - fuel_tx::AssetId, - services::executor::Event, -}; - use crate::graphql_api::{ ports::worker::OffChainDatabaseTransaction, storage::coins::{ @@ -16,6 +7,17 @@ use crate::graphql_api::{ CoinsToSpendIndexKey, }, }; +use fuel_core_types::{ + entities::{ + coins::coin::{ + Coin, + DataCoin, + }, + Message, + }, + fuel_tx::AssetId, + services::executor::Event, +}; use super::error::IndexationError; @@ -43,6 +45,27 @@ where Ok(()) } +fn add_data_coin( + block_st_transaction: &mut T, + coin: &DataCoin, +) -> Result<(), IndexationError> +where + T: OffChainDatabaseTransaction, +{ + let key = CoinsToSpendIndexKey::from_data_coin(coin); + let storage = block_st_transaction.storage::(); + let maybe_old_value = storage.replace(&key, &())?; + if maybe_old_value.is_some() { + return Err(IndexationError::CoinToSpendAlreadyIndexed { + owner: coin.owner, + asset_id: coin.asset_id, + amount: coin.amount, + utxo_id: coin.utxo_id, + }); + } + Ok(()) +} + fn remove_coin( block_st_transaction: &mut T, coin: &Coin, @@ -64,6 +87,27 @@ where Ok(()) } +fn remove_data_coin( + block_st_transaction: &mut T, + coin: &DataCoin, +) -> Result<(), IndexationError> +where + T: OffChainDatabaseTransaction, +{ + let key = CoinsToSpendIndexKey::from_data_coin(coin); + let storage = block_st_transaction.storage::(); + let maybe_old_value = storage.take(&key)?; + if maybe_old_value.is_none() { + return Err(IndexationError::CoinToSpendNotFound { + owner: coin.owner, + asset_id: coin.asset_id, + amount: coin.amount, + utxo_id: coin.utxo_id, + }); + } + Ok(()) +} + fn add_message( block_st_transaction: &mut T, message: &Message, @@ -128,6 +172,12 @@ where } Event::CoinCreated(coin) => add_coin(block_st_transaction, coin), Event::CoinConsumed(coin) => remove_coin(block_st_transaction, coin), + Event::DataCoinCreated(data_coin) => { + add_data_coin(block_st_transaction, data_coin) + } + Event::DataCoinConsumed(data_coin) => { + remove_data_coin(block_st_transaction, data_coin) + } Event::ForcedTransactionFailed { .. } => Ok(()), } } diff --git a/crates/fuel-core/src/graphql_api/storage/coins.rs b/crates/fuel-core/src/graphql_api/storage/coins.rs index c963ce5b463..60ebd3583cd 100644 --- a/crates/fuel-core/src/graphql_api/storage/coins.rs +++ b/crates/fuel-core/src/graphql_api/storage/coins.rs @@ -20,7 +20,10 @@ use fuel_core_storage::{ }; use fuel_core_types::{ entities::{ - coins::coin::Coin, + coins::coin::{ + Coin, + DataCoin, + }, Message, }, fuel_tx::{ @@ -67,6 +70,13 @@ pub enum CoinsToSpendIndexKey { amount: u64, utxo_id: UtxoId, }, + DataCoin { + owner: Address, + asset_id: AssetId, + amount: u64, + utxo_id: UtxoId, + data: Vec, + }, Message { retryable_flag: u8, owner: Address, @@ -99,6 +109,16 @@ impl CoinsToSpendIndexKey { } } + pub fn from_data_coin(data_coin: &DataCoin) -> Self { + Self::DataCoin { + owner: data_coin.owner, + asset_id: data_coin.asset_id, + amount: data_coin.amount, + utxo_id: data_coin.utxo_id, + data: data_coin.data.clone(), + } + } + pub fn from_message(message: &Message, base_asset_id: &AssetId) -> Self { let retryable_flag_bytes = if message.is_retryable_message() { RETRYABLE_BYTE @@ -117,6 +137,7 @@ impl CoinsToSpendIndexKey { pub fn owner(&self) -> &Address { match self { CoinsToSpendIndexKey::Coin { owner, .. } => owner, + CoinsToSpendIndexKey::DataCoin { owner, .. } => owner, CoinsToSpendIndexKey::Message { owner, .. } => owner, } } @@ -124,6 +145,7 @@ impl CoinsToSpendIndexKey { pub fn asset_id(&self) -> &AssetId { match self { CoinsToSpendIndexKey::Coin { asset_id, .. } => asset_id, + CoinsToSpendIndexKey::DataCoin { asset_id, .. } => asset_id, CoinsToSpendIndexKey::Message { asset_id, .. } => asset_id, } } @@ -131,6 +153,7 @@ impl CoinsToSpendIndexKey { pub fn retryable_flag(&self) -> u8 { match self { CoinsToSpendIndexKey::Coin { .. } => NON_RETRYABLE_BYTE[0], + CoinsToSpendIndexKey::DataCoin { .. } => NON_RETRYABLE_BYTE[0], CoinsToSpendIndexKey::Message { retryable_flag, .. } => *retryable_flag, } } @@ -138,6 +161,7 @@ impl CoinsToSpendIndexKey { pub fn amount(&self) -> u64 { match self { CoinsToSpendIndexKey::Coin { amount, .. } => *amount, + CoinsToSpendIndexKey::DataCoin { amount, .. } => *amount, CoinsToSpendIndexKey::Message { amount, .. } => *amount, } } diff --git a/crates/fuel-core/src/graphql_api/storage/coins/codecs.rs b/crates/fuel-core/src/graphql_api/storage/coins/codecs.rs index 67b278c640a..68bf4db3257 100644 --- a/crates/fuel-core/src/graphql_api/storage/coins/codecs.rs +++ b/crates/fuel-core/src/graphql_api/storage/coins/codecs.rs @@ -126,6 +126,39 @@ impl Encode for Manual { SerializedCoinsToSpendIndexKey::Message(serialized_coin) } + CoinsToSpendIndexKey::DataCoin { + owner, + asset_id, + amount, + utxo_id, + data: _data, + } => { + // retryable_flag | address | asset_id | amount | utxo_id | coin_type + let retryable_flag_bytes = NON_RETRYABLE_BYTE; + + // retryable_flag | address | asset_id | amount | utxo_id | coin_type + let mut serialized_coin = [0u8; COIN_VARIANT_SIZE]; + let mut start = 0; + let mut end = RETRYABLE_FLAG_SIZE; + serialized_coin[start] = retryable_flag_bytes[0]; + start = end; + end = end.saturating_add(Address::LEN); + serialized_coin[start..end].copy_from_slice(owner.as_ref()); + start = end; + end = end.saturating_add(AssetId::LEN); + serialized_coin[start..end].copy_from_slice(asset_id.as_ref()); + start = end; + end = end.saturating_add(AMOUNT_SIZE); + serialized_coin[start..end].copy_from_slice(&amount.to_be_bytes()); + start = end; + end = end.saturating_add(UTXO_ID_SIZE); + serialized_coin[start..end].copy_from_slice(&utxo_id_to_bytes(utxo_id)); + start = end; + serialized_coin[start] = CoinType::Coin as u8; + + todo!("Need to add data"); + // SerializedCoinsToSpendIndexKey::Coin(serialized_coin) + } } } } diff --git a/crates/fuel-core/src/graphql_api/worker_service.rs b/crates/fuel-core/src/graphql_api/worker_service.rs index fd2f8890487..94cf32597c9 100644 --- a/crates/fuel-core/src/graphql_api/worker_service.rs +++ b/crates/fuel-core/src/graphql_api/worker_service.rs @@ -269,6 +269,19 @@ where .storage_as_mut::() .remove(&key)?; } + Event::DataCoinCreated(data_coin) => { + let coin_by_owner = + owner_coin_id_key(&data_coin.owner, &data_coin.utxo_id); + block_st_transaction + .storage_as_mut::() + .insert(&coin_by_owner, &())?; + } + Event::DataCoinConsumed(data_coin) => { + let key = owner_coin_id_key(&data_coin.owner, &data_coin.utxo_id); + block_st_transaction + .storage_as_mut::() + .remove(&key)?; + } Event::ForcedTransactionFailed { id, block_height, diff --git a/crates/fuel-core/src/query/coin.rs b/crates/fuel-core/src/query/coin.rs index be35ca1d85d..ce88f58c53a 100644 --- a/crates/fuel-core/src/query/coin.rs +++ b/crates/fuel-core/src/query/coin.rs @@ -8,10 +8,7 @@ use fuel_core_storage::{ StorageAsRef, }; use fuel_core_types::{ - entities::coins::coin::{ - Coin, - UncompressedCoin, - }, + entities::coins::coin::UncompressedCoin, fuel_tx::UtxoId, fuel_types::Address, }; @@ -51,7 +48,7 @@ impl ReadView { owner: &Address, start_coin: Option, direction: IterDirection, - ) -> impl Stream> + '_ { + ) -> impl Stream> + '_ { self.owned_coins_ids(owner, start_coin, direction) .chunks(self.batch_size) .map(|chunk| { diff --git a/crates/fuel-core/src/schema/coins.rs b/crates/fuel-core/src/schema/coins.rs index 65154863d8b..fc121989a2c 100644 --- a/crates/fuel-core/src/schema/coins.rs +++ b/crates/fuel-core/src/schema/coins.rs @@ -28,6 +28,7 @@ use crate::{ scalars::{ Address, AssetId, + HexString, Nonce, UtxoId, U128, @@ -48,7 +49,7 @@ use async_graphql::{ use fuel_core_types::{ entities::coins::{ self, - coin::Coin as CoinModel, + coin::UncompressedCoin, message_coin::{ self, MessageCoin as MessageCoinModel, @@ -63,39 +64,43 @@ use fuel_core_types::{ use itertools::Itertools; use tokio_stream::StreamExt; -pub struct Coin(pub(crate) CoinModel); +pub struct Coin(pub(crate) UncompressedCoin); #[async_graphql::Object] impl Coin { async fn utxo_id(&self) -> UtxoId { - self.0.utxo_id.into() + (*self.0.utxo_id()).into() } async fn owner(&self) -> Address { - self.0.owner.into() + (*self.0.owner()).into() } async fn amount(&self) -> U64 { - self.0.amount.into() + (*self.0.amount()).into() } async fn asset_id(&self) -> AssetId { - self.0.asset_id.into() + (*self.0.asset_id()).into() } /// TxPointer - the height of the block this coin was created in async fn block_created(&self) -> U32 { - u32::from(self.0.tx_pointer.block_height()).into() + u32::from(self.0.tx_pointer().block_height()).into() } /// TxPointer - the index of the transaction that created this coin async fn tx_created_idx(&self) -> U16 { - self.0.tx_pointer.tx_index().into() + self.0.tx_pointer().tx_index().into() + } + + async fn data(&self) -> Option { + self.0.data().map(|data| HexString(data.clone())) } } -impl From for Coin { - fn from(value: CoinModel) -> Self { +impl From for Coin { + fn from(value: UncompressedCoin) -> Self { Coin(value) } } @@ -153,7 +158,7 @@ pub enum CoinType { impl CoinType { pub fn amount(&self) -> u64 { match self { - CoinType::Coin(coin) => coin.0.amount, + CoinType::Coin(coin) => *coin.0.amount(), CoinType::MessageCoin(coin) => coin.0.amount, } } @@ -251,14 +256,14 @@ impl CoinQuery { .filter_map(|result| { if let (Ok(coin), Some(filter_asset_id)) = (&result, &filter.asset_id) { - if coin.asset_id != filter_asset_id.0 { + if *coin.asset_id() != filter_asset_id.0 { return None } } Some(result) }) - .map(|res| res.map(|coin| (coin.utxo_id.into(), coin.into()))); + .map(|res| res.map(|coin| ((*coin.utxo_id()).into(), coin.into()))); Ok(coins) }) @@ -463,6 +468,7 @@ fn into_coin_id(selected: &[CoinsToSpendIndexKey]) -> Vec { for coin in selected { let coin = match coin { CoinsToSpendIndexKey::Coin { utxo_id, .. } => CoinId::Utxo(*utxo_id), + CoinsToSpendIndexKey::DataCoin { utxo_id, .. } => CoinId::Utxo(*utxo_id), CoinsToSpendIndexKey::Message { nonce, .. } => CoinId::Message(*nonce), }; coins.push(coin); diff --git a/crates/fuel-core/src/schema/tx/assemble_tx.rs b/crates/fuel-core/src/schema/tx/assemble_tx.rs index 51fd0b4e5dd..e01210860fb 100644 --- a/crates/fuel-core/src/schema/tx/assemble_tx.rs +++ b/crates/fuel-core/src/schema/tx/assemble_tx.rs @@ -475,11 +475,11 @@ where match coin { CoinType::Coin(coin) => Input::coin_signed( - coin.0.utxo_id, - coin.0.owner, - coin.0.amount, - coin.0.asset_id, - coin.0.tx_pointer, + *coin.0.utxo_id(), + *coin.0.owner(), + *coin.0.amount(), + *coin.0.asset_id(), + *coin.0.tx_pointer(), signature_index, ), CoinType::MessageCoin(message) => Input::message_coin_signed( @@ -496,11 +496,11 @@ where let predicate_gas_used = 0; match coin { CoinType::Coin(coin) => Input::coin_predicate( - coin.0.utxo_id, + *coin.0.utxo_id(), predicate.predicate_address, - coin.0.amount, - coin.0.asset_id, - coin.0.tx_pointer, + *coin.0.amount(), + *coin.0.asset_id(), + *coin.0.tx_pointer(), predicate_gas_used, predicate.predicate.clone(), predicate.predicate_data.clone(), diff --git a/crates/fuel-core/src/service/genesis.rs b/crates/fuel-core/src/service/genesis.rs index ea7421379fa..461691e8584 100644 --- a/crates/fuel-core/src/service/genesis.rs +++ b/crates/fuel-core/src/service/genesis.rs @@ -345,7 +345,10 @@ mod tests { }; use fuel_core_types::{ blockchain::primitives::DaBlockHeight, - entities::coins::coin::Coin, + entities::coins::coin::{ + Coin, + UncompressedCoin, + }, fuel_tx::UtxoId, fuel_types::{ Address, @@ -500,14 +503,14 @@ mod tests { assert!(matches!( alice_coins.as_slice(), - &[Coin { + &[UncompressedCoin::Coin(Coin { utxo_id, owner, amount, asset_id, tx_pointer, .. - }] if utxo_id == alice_utxo_id + })] if utxo_id == alice_utxo_id && owner == alice && amount == alice_value && asset_id == asset_id_alice @@ -515,12 +518,12 @@ mod tests { )); assert!(matches!( bob_coins.as_slice(), - &[Coin { + &[UncompressedCoin::Coin(Coin { owner, amount, asset_id, .. - }] if owner == bob + })] if owner == bob && amount == bob_value && asset_id == asset_id_bob )); @@ -681,7 +684,7 @@ mod tests { assert!(init_result.is_err()) } - fn get_coins(db: &CombinedDatabase, owner: &Address) -> Vec { + fn get_coins(db: &CombinedDatabase, owner: &Address) -> Vec { db.off_chain() .latest_view() .unwrap() @@ -691,7 +694,7 @@ mod tests { db.on_chain() .storage::() .get(&coin_id) - .map(|v| v.unwrap().into_owned().uncompress_coin(coin_id)) + .map(|v| v.unwrap().into_owned().uncompress(coin_id)) .unwrap() }) .collect() diff --git a/crates/types/src/entities/coins/coin.rs b/crates/types/src/entities/coins/coin.rs index 484295aaca1..5617637edef 100644 --- a/crates/types/src/entities/coins/coin.rs +++ b/crates/types/src/entities/coins/coin.rs @@ -181,6 +181,14 @@ impl UncompressedCoin { UncompressedCoin::DataCoin(coin) => &coin.tx_pointer, } } + + /// Returns the data attached to coin if it exists + pub fn data(&self) -> Option<&Vec> { + match self { + UncompressedCoin::Coin(_) => None, + UncompressedCoin::DataCoin(coin) => Some(&coin.data), + } + } } impl From for CompressedCoin { From 31a64c5b144bce0b27b97d89f4c7c1dcb92a0b5f Mon Sep 17 00:00:00 2001 From: Mitch Turner Date: Sat, 29 Mar 2025 16:11:39 -0600 Subject: [PATCH 07/37] Fix test that shows executor running tx with data coin and predicate that checks it! --- crates/fuel-core/src/executor.rs | 22 ++++++++++++++-------- crates/services/executor/src/executor.rs | 4 ++++ 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/crates/fuel-core/src/executor.rs b/crates/fuel-core/src/executor.rs index 0eddb36578a..575e95ca145 100644 --- a/crates/fuel-core/src/executor.rs +++ b/crates/fuel-core/src/executor.rs @@ -61,7 +61,10 @@ mod tests { primitives::DaBlockHeight, }, entities::{ - coins::coin::CompressedCoin, + coins::coin::{ + CompressedCoin, + CompressedCoinV2, + }, relayer::message::{ Message, MessageV1, @@ -3029,12 +3032,12 @@ mod tests { 0, predicate, predicate_data, - data, + data.clone(), )) .add_output(Output::Change { to: Default::default(), amount: 0, - asset_id: Default::default(), + asset_id: AssetId::BASE, }) .finalize(); tx.estimate_predicates( @@ -3055,11 +3058,13 @@ mod tests { .. }) = tx.inputs()[0] { - let mut coin = CompressedCoin::default(); - coin.set_owner(owner); - coin.set_amount(amount); - coin.set_asset_id(asset_id); - coin.set_tx_pointer(tx_pointer); + let coin = CompressedCoin::V2(CompressedCoinV2 { + owner, + amount, + asset_id, + tx_pointer, + data, + }); db.storage::().insert(&utxo_id, &coin).unwrap(); } else { panic!("Expected a DataCoinPredicate"); @@ -3080,6 +3085,7 @@ mod tests { }) .unwrap() .into_result(); + dbg!(&skipped_transactions); assert!(skipped_transactions.is_empty()); let validator = create_executor(db.clone(), config); diff --git a/crates/services/executor/src/executor.rs b/crates/services/executor/src/executor.rs index 2235ed4036f..f5778337838 100644 --- a/crates/services/executor/src/executor.rs +++ b/crates/services/executor/src/executor.rs @@ -1745,6 +1745,10 @@ where Input::CoinPredicate(CoinPredicate { predicate_gas_used, .. }) + | Input::DataCoinPredicate(DataCoinPredicate { + predicate_gas_used, + .. + }) | Input::MessageCoinPredicate(MessageCoinPredicate { predicate_gas_used, .. From 4eba976d8b3a8ff71bc8cd84339ded13a195842f Mon Sep 17 00:00:00 2001 From: Mitch Turner Date: Sun, 30 Mar 2025 15:12:35 -0600 Subject: [PATCH 08/37] Add new unit test for unhappy path --- crates/fuel-core/src/executor.rs | 103 ++++++++++++++++++++++++++++++- 1 file changed, 101 insertions(+), 2 deletions(-) diff --git a/crates/fuel-core/src/executor.rs b/crates/fuel-core/src/executor.rs index 575e95ca145..568eadec3f3 100644 --- a/crates/fuel-core/src/executor.rs +++ b/crates/fuel-core/src/executor.rs @@ -63,6 +63,7 @@ mod tests { entities::{ coins::coin::{ CompressedCoin, + CompressedCoinV1, CompressedCoinV2, }, relayer::message::{ @@ -3001,10 +3002,11 @@ mod tests { .into_iter() .collect() } - #[test] fn validate__predicate_can_find_data_coin_data() { let mut rng = StdRng::seed_from_u64(2322u64); + + // given let predicate: Vec = predicate_checking_predicate_data_matches_input_data_coin(); let owner = Input::predicate_owner(&predicate); @@ -3072,6 +3074,7 @@ mod tests { let producer = create_executor(db.clone(), config.clone()); + // when let ExecutionResult { block, skipped_transactions, @@ -3085,7 +3088,8 @@ mod tests { }) .unwrap() .into_result(); - dbg!(&skipped_transactions); + + // then assert!(skipped_transactions.is_empty()); let validator = create_executor(db.clone(), config); @@ -3093,6 +3097,101 @@ mod tests { assert!(result.is_ok(), "{result:?}") } + #[test] + fn validate__predicate_fails_if_data_less_coin() { + let mut rng = StdRng::seed_from_u64(2322u64); + + // given + let predicate: Vec = + predicate_checking_predicate_data_matches_input_data_coin(); + let owner = Input::predicate_owner(&predicate); + let amount = 1000; + + let consensus_parameters = ConsensusParameters::default(); + let config = Config { + forbid_fake_coins_default: true, + consensus_parameters: consensus_parameters.clone(), + }; + let data = vec![99u8; 100]; + let predicate_data = data.clone(); + + let mut tx = TransactionBuilder::script( + vec![op::ret(RegId::ONE)].into_iter().collect(), + vec![], + ) + .max_fee_limit(amount) + .add_input(Input::coin_predicate( + // not a data coin + rng.gen(), + owner, + amount, + AssetId::BASE, + rng.gen(), + 0, + predicate, + predicate_data, + )) + .add_output(Output::Change { + to: Default::default(), + amount: 0, + asset_id: AssetId::BASE, + }) + .finalize(); + tx.estimate_predicates( + &consensus_parameters.clone().into(), + MemoryInstance::new(), + &EmptyStorage, + ) + .unwrap(); + let db = &mut Database::default(); + + // insert coin into state + if let Input::CoinPredicate(CoinPredicate { + utxo_id, + owner, + amount, + asset_id, + tx_pointer, + .. + }) = tx.inputs()[0] + { + let coin = CompressedCoin::V1(CompressedCoinV1 { + owner, + amount, + asset_id, + tx_pointer, + }); + db.storage::().insert(&utxo_id, &coin).unwrap(); + } else { + panic!("Expected a DataCoinPredicate"); + } + + let producer = create_executor(db.clone(), config.clone()); + + // when + let ExecutionResult { + block, + skipped_transactions, + tx_status, + .. + } = producer + .produce_without_commit_with_source_direct_resolve(Components { + header_to_produce: PartialBlockHeader::default(), + transactions_source: OnceTransactionsSource::new(vec![tx.into()]), + coinbase_recipient: Default::default(), + gas_price: 1, + }) + .unwrap() + .into_result(); + // then + assert_eq!(skipped_transactions.len(), 1); + assert_eq!(tx_status.len(), 1); + + let validator = create_executor(db.clone(), config); + let result = validator.validate(&block); + assert!(result.is_ok(), "{result:?}") + } + #[test] fn verifying_during_production_consensus_parameters_version_works() { let mut rng = StdRng::seed_from_u64(2322u64); From b0a4336fd5507b46c74b9dd393b56eeeb28df0aa Mon Sep 17 00:00:00 2001 From: Mitch Turner Date: Sun, 30 Mar 2025 16:49:08 -0600 Subject: [PATCH 09/37] WIP add integ test for data coin in predicate --- Cargo.lock | 1 + crates/chain-config/src/config/coin.rs | 190 ++++++++++++++++++---- crates/fuel-core/src/p2p_test_helpers.rs | 17 +- crates/types/Cargo.toml | 1 + crates/types/src/entities/coins/coin.rs | 9 +- tests/test-helpers/src/builder.rs | 57 ++++++- tests/tests/assemble_tx.rs | 16 +- tests/tests/balances.rs | 43 +++-- tests/tests/blob.rs | 4 +- tests/tests/chain.rs | 12 +- tests/tests/coin.rs | 45 +++-- tests/tests/coins.rs | 33 ++-- tests/tests/contract.rs | 12 +- tests/tests/tx/predicates.rs | 114 +++++++++++++ tests/tests/tx/tx_pointer.rs | 23 +-- tests/tests/tx/txn_status_subscription.rs | 2 +- 16 files changed, 452 insertions(+), 127 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 61143569db1..7ba7e77c06b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4096,6 +4096,7 @@ dependencies = [ "serde", "tai64", "tokio", + "tracing", "zeroize", ] diff --git a/crates/chain-config/src/config/coin.rs b/crates/chain-config/src/config/coin.rs index 85f059d930c..03e143cf4dd 100644 --- a/crates/chain-config/src/config/coin.rs +++ b/crates/chain-config/src/config/coin.rs @@ -1,5 +1,8 @@ -use super::table_entry::TableEntry; -use crate::GenesisCommitment; +use serde::{ + Deserialize, + Serialize, +}; + use fuel_core_storage::{ tables::Coins, MerkleRoot, @@ -23,29 +26,144 @@ use fuel_core_types::{ Bytes32, }, }; -use serde::{ - Deserialize, - Serialize, -}; + +use crate::GenesisCommitment; + +use super::table_entry::TableEntry; + +#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)] +pub enum CoinConfig { + Coin(ConfigCoin), + DataCoin(ConfigDataCoin), +} #[derive(Default, Clone, Debug, Deserialize, Serialize, Eq, PartialEq)] -pub struct CoinConfig { - /// auto-generated if None +pub struct ConfigCoin { pub tx_id: Bytes32, pub output_index: u16, - /// used if coin is forked from another chain to preserve id & tx_pointer pub tx_pointer_block_height: BlockHeight, - /// used if coin is forked from another chain to preserve id & tx_pointer - /// The index of the originating tx within `tx_pointer_block_height` pub tx_pointer_tx_idx: u16, pub owner: Address, pub amount: u64, pub asset_id: AssetId, } +#[derive(Default, Clone, Debug, Deserialize, Serialize, Eq, PartialEq)] +pub struct ConfigDataCoin { + pub tx_id: Bytes32, + pub output_index: u16, + pub tx_pointer_block_height: BlockHeight, + pub tx_pointer_tx_idx: u16, + pub owner: Address, + pub amount: u64, + pub asset_id: AssetId, + pub data: Vec, +} + +impl CoinConfig { + pub fn tx_id(&self) -> Bytes32 { + match self { + CoinConfig::Coin(ConfigCoin { tx_id, .. }) => *tx_id, + CoinConfig::DataCoin(ConfigDataCoin { tx_id, .. }) => *tx_id, + } + } + + pub fn output_index(&self) -> u16 { + match self { + CoinConfig::Coin(ConfigCoin { output_index, .. }) => *output_index, + CoinConfig::DataCoin(ConfigDataCoin { output_index, .. }) => *output_index, + } + } + + pub fn mut_output_index(&mut self) -> &mut u16 { + match self { + CoinConfig::Coin(ConfigCoin { output_index, .. }) => output_index, + CoinConfig::DataCoin(ConfigDataCoin { output_index, .. }) => output_index, + } + } + + pub fn tx_pointer_block_height(&self) -> BlockHeight { + match self { + CoinConfig::Coin(ConfigCoin { + tx_pointer_block_height, + .. + }) => *tx_pointer_block_height, + CoinConfig::DataCoin(ConfigDataCoin { + tx_pointer_block_height, + .. + }) => *tx_pointer_block_height, + } + } + + pub fn tx_pointer_tx_idx(&self) -> u16 { + match self { + CoinConfig::Coin(ConfigCoin { + tx_pointer_tx_idx, .. + }) => *tx_pointer_tx_idx, + CoinConfig::DataCoin(ConfigDataCoin { + tx_pointer_tx_idx, .. + }) => *tx_pointer_tx_idx, + } + } + + pub fn owner(&self) -> Address { + match self { + CoinConfig::Coin(ConfigCoin { owner, .. }) => *owner, + CoinConfig::DataCoin(ConfigDataCoin { owner, .. }) => *owner, + } + } + + pub fn mut_owner(&mut self) -> &mut Address { + match self { + CoinConfig::Coin(ConfigCoin { owner, .. }) => owner, + CoinConfig::DataCoin(ConfigDataCoin { owner, .. }) => owner, + } + } + + pub fn amount(&self) -> u64 { + match self { + CoinConfig::Coin(ConfigCoin { amount, .. }) => *amount, + CoinConfig::DataCoin(ConfigDataCoin { amount, .. }) => *amount, + } + } + + pub fn asset_id(&self) -> AssetId { + match self { + CoinConfig::Coin(ConfigCoin { asset_id, .. }) => *asset_id, + CoinConfig::DataCoin(ConfigDataCoin { asset_id, .. }) => *asset_id, + } + } + + pub fn mut_asset_id(&mut self) -> &mut AssetId { + match self { + CoinConfig::Coin(ConfigCoin { asset_id, .. }) => asset_id, + CoinConfig::DataCoin(ConfigDataCoin { asset_id, .. }) => asset_id, + } + } + + pub fn data(&self) -> Option<&Vec> { + match self { + CoinConfig::Coin(_) => None, + CoinConfig::DataCoin(ConfigDataCoin { data, .. }) => Some(data), + } + } +} + +impl From for CoinConfig { + fn from(value: ConfigCoin) -> Self { + CoinConfig::Coin(value) + } +} + +impl From for CoinConfig { + fn from(value: ConfigDataCoin) -> Self { + CoinConfig::DataCoin(value) + } +} + impl From> for CoinConfig { fn from(value: TableEntry) -> Self { - CoinConfig { + ConfigCoin { tx_id: *value.key.tx_id(), output_index: value.key.output_index(), tx_pointer_block_height: value.value.tx_pointer().block_height(), @@ -54,20 +172,21 @@ impl From> for CoinConfig { amount: *value.value.amount(), asset_id: *value.value.asset_id(), } + .into() } } impl From for TableEntry { fn from(config: CoinConfig) -> Self { Self { - key: UtxoId::new(config.tx_id, config.output_index), + key: UtxoId::new(config.tx_id(), config.output_index()), value: CompressedCoin::V1(CompressedCoinV1 { - owner: config.owner, - amount: config.amount, - asset_id: config.asset_id, + owner: config.owner(), + amount: config.amount(), + asset_id: config.asset_id(), tx_pointer: TxPointer::new( - config.tx_pointer_block_height, - config.tx_pointer_tx_idx, + config.tx_pointer_block_height(), + config.tx_pointer_tx_idx(), ), }), } @@ -76,18 +195,18 @@ impl From for TableEntry { impl CoinConfig { pub fn utxo_id(&self) -> UtxoId { - UtxoId::new(self.tx_id, self.output_index) + UtxoId::new(self.tx_id(), self.output_index()) } pub fn tx_pointer(&self) -> TxPointer { - TxPointer::new(self.tx_pointer_block_height, self.tx_pointer_tx_idx) + TxPointer::new(self.tx_pointer_block_height(), self.tx_pointer_tx_idx()) } } #[cfg(feature = "test-helpers")] impl crate::Randomize for CoinConfig { fn randomize(mut rng: impl ::rand::Rng) -> Self { - Self { + ConfigCoin { tx_id: crate::Randomize::randomize(&mut rng), output_index: rng.gen(), tx_pointer_block_height: rng.gen(), @@ -96,6 +215,7 @@ impl crate::Randomize for CoinConfig { amount: rng.gen(), asset_id: crate::Randomize::randomize(&mut rng), } + .into() } } @@ -147,7 +267,6 @@ impl GenesisCommitment for DataCoin { #[cfg(feature = "test-helpers")] pub mod coin_config_helpers { - use crate::CoinConfig; use fuel_core_types::{ fuel_types::{ Address, @@ -156,6 +275,11 @@ pub mod coin_config_helpers { fuel_vm::SecretKey, }; + use crate::{ + CoinConfig, + ConfigCoin, + }; + type CoinCount = u16; /// Generates a new coin config with a unique utxo id for testing @@ -175,13 +299,17 @@ pub mod coin_config_helpers { Self { count: 0 } } - pub fn generate(&mut self) -> CoinConfig { + pub fn generate(&mut self) -> ConfigCoin { let tx_id = tx_id(self.count); - let config = CoinConfig { + let config = ConfigCoin { tx_id, output_index: self.count, - ..Default::default() + tx_pointer_block_height: Default::default(), + tx_pointer_tx_idx: Default::default(), + owner: Default::default(), + amount: Default::default(), + asset_id: Default::default(), }; self.count = self.count.checked_add(1).expect("Max coin count reached"); @@ -192,23 +320,29 @@ pub mod coin_config_helpers { pub fn generate_with(&mut self, secret: SecretKey, amount: u64) -> CoinConfig { let owner = Address::from(*secret.public_key().hash()); - CoinConfig { + ConfigCoin { + tx_id: Default::default(), + output_index: Default::default(), + tx_pointer_block_height: Default::default(), amount, owner, - ..self.generate() + tx_pointer_tx_idx: Default::default(), + asset_id: Default::default(), } + .into() } } } #[cfg(test)] mod tests { - use super::*; use fuel_core_types::{ fuel_types::Address, fuel_vm::SecretKey, }; + use super::*; + #[test] fn test_generate_unique_utxo_id() { let mut generator = coin_config_helpers::CoinConfigGenerator::new(); diff --git a/crates/fuel-core/src/p2p_test_helpers.rs b/crates/fuel-core/src/p2p_test_helpers.rs index 924fdfbd76f..e356f1a70cd 100644 --- a/crates/fuel-core/src/p2p_test_helpers.rs +++ b/crates/fuel-core/src/p2p_test_helpers.rs @@ -1,10 +1,7 @@ //! # Helpers for creating networks of nodes use crate::{ - chain_config::{ - coin_config_helpers::CoinConfigGenerator, - CoinConfig, - }, + chain_config::coin_config_helpers::CoinConfigGenerator, combined_database::CombinedDatabase, database::{ database_description::off_chain::OffChain, @@ -268,12 +265,8 @@ pub async fn make_nodes( let all: Vec<_> = (0..num_test_txs) .map(|_| { let secret = SecretKey::random(&mut rng); - let initial_coin = CoinConfig { - // set idx to prevent overlapping utxo_ids when - // merging with existing coins from config - output_index: 10, - ..coin_generator.generate_with(secret, 10000) - }; + let mut initial_coin = coin_generator.generate_with(secret, 10000); + *initial_coin.mut_output_index() = 10; let tx = TransactionBuilder::script( vec![op::ret(RegId::ONE)].into_iter().collect(), vec![], @@ -282,8 +275,8 @@ pub async fn make_nodes( .add_unsigned_coin_input( secret, initial_coin.utxo_id(), - initial_coin.amount, - initial_coin.asset_id, + initial_coin.amount(), + initial_coin.asset_id(), Default::default(), ) .finalize_as_transaction(); diff --git a/crates/types/Cargo.toml b/crates/types/Cargo.toml index 547a1002a98..e0acb871749 100644 --- a/crates/types/Cargo.toml +++ b/crates/types/Cargo.toml @@ -35,6 +35,7 @@ serde = { workspace = true, features = ["derive"], optional = true } # We force the version because 4.1.0 update leap seconds that breaks our timestamps tai64 = { version = "=4.0.0", features = ["serde"] } zeroize = "1.5" +tracing = { workspace = true } [dev-dependencies] aws-config = { version = "1.1.7", features = ["behavior-version-latest"] } diff --git a/crates/types/src/entities/coins/coin.rs b/crates/types/src/entities/coins/coin.rs index 5617637edef..2f03bbabbca 100644 --- a/crates/types/src/entities/coins/coin.rs +++ b/crates/types/src/entities/coins/coin.rs @@ -18,6 +18,7 @@ use crate::{ AssetId, }, }; +use alloc::vec::Vec; /// Represents the user's coin for some asset with `asset_id`. #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] @@ -306,6 +307,7 @@ impl CompressedCoin { /// Returns `None`, if the `input` is a different type of input. /// Otherwise, returns the result of the field comparison. pub fn matches_input(&self, input: &Input) -> Option { + tracing::debug!("Checking if CompressedCoin matches Input: {:?}", input); match input { Input::CoinSigned(CoinSigned { owner, @@ -343,7 +345,12 @@ impl CompressedCoin { && amount == &coin.amount && asset_id == &coin.asset_id, ), - _ => None, + _ => { + tracing::debug!( + "Invalid type for CompressedCoin: expected V2 for DataCoin but found V1. \ + "); + None + } }, _ => None, } diff --git a/tests/test-helpers/src/builder.rs b/tests/test-helpers/src/builder.rs index 17754bfd0d1..6a73471ca33 100644 --- a/tests/test-helpers/src/builder.rs +++ b/tests/test-helpers/src/builder.rs @@ -2,6 +2,8 @@ use fuel_core::{ chain_config::{ ChainConfig, CoinConfig, + ConfigCoin, + ConfigDataCoin, ContractBalanceConfig, ContractConfig, LastBlockConfig, @@ -26,6 +28,8 @@ use fuel_core_types::{ input::coin::{ CoinPredicate, CoinSigned, + DataCoinPredicate, + DataCoinSigned, }, policies::Policies, *, @@ -183,15 +187,50 @@ impl TestSetupBuilder { .. }) = input { - Some(CoinConfig { - tx_id: *utxo_id.tx_id(), - output_index: utxo_id.output_index(), - tx_pointer_block_height: tx_pointer.block_height(), - tx_pointer_tx_idx: tx_pointer.tx_index(), - owner: *owner, - amount: *amount, - asset_id: *asset_id, - }) + Some( + ConfigCoin { + tx_id: *utxo_id.tx_id(), + output_index: utxo_id.output_index(), + tx_pointer_block_height: tx_pointer.block_height(), + tx_pointer_tx_idx: tx_pointer.tx_index(), + owner: *owner, + amount: *amount, + asset_id: *asset_id, + } + .into(), + ) + } else if let Input::DataCoinSigned(DataCoinSigned { + amount, + owner, + asset_id, + utxo_id, + tx_pointer, + data, + .. + }) + | Input::DataCoinPredicate(DataCoinPredicate { + amount, + owner, + asset_id, + utxo_id, + tx_pointer, + data, + .. + }) = input + { + Some( + ConfigDataCoin { + tx_id: *utxo_id.tx_id(), + output_index: utxo_id.output_index(), + tx_pointer_block_height: tx_pointer.block_height(), + tx_pointer_tx_idx: tx_pointer.tx_index(), + owner: *owner, + amount: *amount, + asset_id: *asset_id, + data: data.clone(), + } + .into(), + ) } else { None } diff --git a/tests/tests/assemble_tx.rs b/tests/tests/assemble_tx.rs index 5a138432202..be2e67dcc74 100644 --- a/tests/tests/assemble_tx.rs +++ b/tests/tests/assemble_tx.rs @@ -254,10 +254,10 @@ async fn assemble_transaction__transfer_non_based_asset() { assert_ne!(base_asset_id, non_base_asset_id); // Given - state_config.coins[0].owner = owner; - state_config.coins[0].asset_id = base_asset_id; - state_config.coins[1].owner = owner; - state_config.coins[1].asset_id = non_base_asset_id; + *state_config.coins[0].mut_owner() = owner; + *state_config.coins[0].mut_asset_id() = base_asset_id; + *state_config.coins[1].mut_owner() = owner; + *state_config.coins[1].mut_asset_id() = non_base_asset_id; let mut config = Config::local_node_with_configs(chain_config, state_config); config.utxo_validation = true; @@ -314,10 +314,10 @@ async fn assemble_transaction__adds_change_output_for_non_required_non_base_bala assert_ne!(base_asset_id, non_base_asset_id); // Given - state_config.coins[0].owner = owner; - state_config.coins[0].asset_id = base_asset_id; - state_config.coins[1].owner = owner; - state_config.coins[1].asset_id = non_base_asset_id; + *state_config.coins[0].mut_owner() = owner; + *state_config.coins[0].mut_asset_id() = base_asset_id; + *state_config.coins[1].mut_owner() = owner; + *state_config.coins[1].mut_asset_id() = non_base_asset_id; let mut config = Config::local_node_with_configs(chain_config, state_config); config.utxo_validation = true; diff --git a/tests/tests/balances.rs b/tests/tests/balances.rs index 16b16d1b71d..290c5731d52 100644 --- a/tests/tests/balances.rs +++ b/tests/tests/balances.rs @@ -1,7 +1,7 @@ use fuel_core::{ chain_config::{ coin_config_helpers::CoinConfigGenerator, - CoinConfig, + ConfigCoin, MessageConfig, StateConfig, }, @@ -56,11 +56,14 @@ async fn balance() { (owner, 150, asset_id), ] .into_iter() - .map(|(owner, amount, asset_id)| CoinConfig { - owner, - amount, - asset_id, - ..coin_generator.generate() + .map(|(owner, amount, asset_id)| { + ConfigCoin { + owner, + amount, + asset_id, + ..coin_generator.generate() + } + .into() }) .collect(), messages: vec![ @@ -220,11 +223,14 @@ async fn first_5_balances() { (owner, 150, asset_id), ] }) - .map(|(owner, amount, asset_id)| CoinConfig { - owner: *owner, - amount, - asset_id, - ..coin_generator.generate() + .map(|(owner, amount, asset_id)| { + ConfigCoin { + owner: *owner, + amount, + asset_id, + ..coin_generator.generate() + } + .into() }), ); } @@ -295,11 +301,11 @@ async fn first_5_balances() { } mod pagination { + use crate::balances::ConfigCoin; use fuel_core::{ chain_config::{ coin_config_helpers::CoinConfigGenerator, ChainConfig, - CoinConfig, MessageConfig, StateConfig, }, @@ -336,11 +342,14 @@ mod pagination { coins.extend( coin.iter() .flat_map(|(asset_id, amount)| vec![(owner, amount, asset_id)]) - .map(|(owner, amount, asset_id)| CoinConfig { - owner: *owner, - amount: *amount as u64, - asset_id: *asset_id, - ..coin_generator.generate() + .map(|(owner, amount, asset_id)| { + ConfigCoin { + owner: *owner, + amount: *amount as u64, + asset_id: *asset_id, + ..coin_generator.generate() + } + .into() }), ); coins diff --git a/tests/tests/blob.rs b/tests/tests/blob.rs index 503248f72b8..b4c0de1071e 100644 --- a/tests/tests/blob.rs +++ b/tests/tests/blob.rs @@ -289,8 +289,8 @@ async fn predicate_can_load_blob() { let mut state = StateConfig::local_testnet(); - state.coins[0].owner = blob_owner; - state.coins[1].owner = predicate_with_blob_owner; + *state.coins[0].mut_owner() = blob_owner; + *state.coins[1].mut_owner() = predicate_with_blob_owner; let blob_predicate_account = SigningAccount::Predicate { predicate: blob_predicate, diff --git a/tests/tests/chain.rs b/tests/tests/chain.rs index 8096e14f314..2ed81fa6174 100644 --- a/tests/tests/chain.rs +++ b/tests/tests/chain.rs @@ -1,7 +1,7 @@ use fuel_core::{ chain_config::{ ChainConfig, - CoinConfig, + ConfigCoin, StateConfig, }, service::{ @@ -62,14 +62,15 @@ async fn network_operates_with_non_zero_chain_id() { let utxo_id = UtxoId::new([1; 32].into(), 0); let state_config = StateConfig { - coins: vec![CoinConfig { + coins: vec![ConfigCoin { tx_id: *utxo_id.tx_id(), output_index: utxo_id.output_index(), owner, amount, asset_id: AssetId::BASE, ..Default::default() - }], + } + .into()], ..Default::default() }; let mut chain_config = ChainConfig::local_testnet(); @@ -110,14 +111,15 @@ async fn network_operates_with_non_zero_base_asset_id() { let new_base_asset_id = AssetId::new([6; 32]); let state_config = StateConfig { - coins: vec![CoinConfig { + coins: vec![ConfigCoin { tx_id: *utxo_id.tx_id(), output_index: utxo_id.output_index(), owner, amount, asset_id: new_base_asset_id, ..Default::default() - }], + } + .into()], ..Default::default() }; let mut chain_config = ChainConfig::local_testnet(); diff --git a/tests/tests/coin.rs b/tests/tests/coin.rs index 9057ca8355c..293e193d41e 100644 --- a/tests/tests/coin.rs +++ b/tests/tests/coin.rs @@ -2,6 +2,7 @@ use fuel_core::{ chain_config::{ coin_config_helpers::CoinConfigGenerator, CoinConfig, + ConfigCoin, StateConfig, }, database::Database, @@ -45,11 +46,12 @@ async fn coin() { // setup test data in the node let output_index = 5; let tx_id = TxId::new([1u8; 32]); - let coin = CoinConfig { + let coin = ConfigCoin { output_index, tx_id, ..Default::default() - }; + } + .into(); // setup server & client let srv = setup_service(vec![coin]).await; @@ -72,10 +74,13 @@ async fn first_5_coins( // setup test data in the node let mut coin_generator = CoinConfigGenerator::new(); let coins: Vec<_> = (1..10usize) - .map(|i| CoinConfig { - owner, - amount: i as Word, - ..coin_generator.generate() + .map(|i| { + ConfigCoin { + owner, + amount: i as Word, + ..coin_generator.generate() + } + .into() }) .collect(); @@ -108,11 +113,14 @@ async fn only_asset_id_filtered_coins() { // setup test data in the node let mut coin_generator = CoinConfigGenerator::new(); let coins: Vec<_> = (1..10usize) - .map(|i| CoinConfig { - owner, - amount: i as Word, - asset_id: if i <= 5 { asset_id } else { Default::default() }, - ..coin_generator.generate() + .map(|i| { + ConfigCoin { + owner, + amount: i as Word, + asset_id: if i <= 5 { asset_id } else { Default::default() }, + ..coin_generator.generate() + } + .into() }) .collect(); @@ -146,12 +154,15 @@ async fn get_coins_forwards_backwards( ) { // setup test data in the node let coins: Vec<_> = (1..11usize) - .map(|i| CoinConfig { - owner, - amount: i as Word, - asset_id, - output_index: i as u16, - ..Default::default() + .map(|i| { + ConfigCoin { + owner, + amount: i as Word, + asset_id, + output_index: i as u16, + ..Default::default() + } + .into() }) .collect(); diff --git a/tests/tests/coins.rs b/tests/tests/coins.rs index 12dccf15cb5..eb49cc27fd0 100644 --- a/tests/tests/coins.rs +++ b/tests/tests/coins.rs @@ -1,6 +1,5 @@ use fuel_core::{ chain_config::{ - CoinConfig, MessageConfig, StateConfig, }, @@ -29,6 +28,7 @@ mod coin { use fuel_core::chain_config::{ coin_config_helpers::CoinConfigGenerator, ChainConfig, + ConfigCoin, }; use fuel_core_client::client::types::CoinType; use fuel_core_types::{ @@ -57,11 +57,14 @@ mod coin { (owner, 150, asset_id_b), ] .into_iter() - .map(|(owner, amount, asset_id)| CoinConfig { - owner, - amount, - asset_id, - ..coin_generator.generate() + .map(|(owner, amount, asset_id)| { + ConfigCoin { + owner, + amount, + asset_id, + ..coin_generator.generate() + } + .into() }) .collect(), messages: vec![], @@ -512,7 +515,10 @@ mod message_coin { // It is combination of coins and deposit coins test cases. mod all_coins { - use fuel_core::chain_config::coin_config_helpers::CoinConfigGenerator; + use fuel_core::chain_config::{ + coin_config_helpers::CoinConfigGenerator, + ConfigCoin, + }; use fuel_core_client::client::types::CoinType; use fuel_core_types::blockchain::primitives::DaBlockHeight; @@ -532,11 +538,14 @@ mod all_coins { (owner, 150, asset_id_b), ] .into_iter() - .map(|(owner, amount, asset_id)| CoinConfig { - owner, - amount, - asset_id, - ..coin_generator.generate() + .map(|(owner, amount, asset_id)| { + ConfigCoin { + owner, + amount, + asset_id, + ..coin_generator.generate() + } + .into() }) .collect(), messages: vec![(owner, 50), (owner, 150)] diff --git a/tests/tests/contract.rs b/tests/tests/contract.rs index b3e91552315..cb6cf5834fe 100644 --- a/tests/tests/contract.rs +++ b/tests/tests/contract.rs @@ -33,7 +33,7 @@ use fuel_core_types::{ use rand::SeedableRng; use fuel_core::chain_config::{ - CoinConfig, + ConfigCoin, ContractConfig, StateConfig, }; @@ -52,22 +52,24 @@ async fn calling_the_contract_with_enabled_utxo_validation_is_successful() { let state_config = StateConfig { coins: vec![ - CoinConfig { + ConfigCoin { tx_id: *utxo_id_1.tx_id(), output_index: utxo_id_1.output_index(), owner, amount, asset_id: AssetId::BASE, ..Default::default() - }, - CoinConfig { + } + .into(), + ConfigCoin { tx_id: *utxo_id_2.tx_id(), output_index: utxo_id_2.output_index(), owner, amount, asset_id: AssetId::BASE, ..Default::default() - }, + } + .into(), ], ..Default::default() }; diff --git a/tests/tests/tx/predicates.rs b/tests/tests/tx/predicates.rs index 508a87b1cca..330b54a56b6 100644 --- a/tests/tests/tx/predicates.rs +++ b/tests/tests/tx/predicates.rs @@ -179,3 +179,117 @@ async fn transaction_with_predicates_that_exhaust_gas_limit_are_rejected() { "got unexpected error {err}" ) } + +fn predicate_checking_predicate_data_matches_input_data_coin() -> Vec { + let len_reg = 0x13; + let res_reg = 0x10; + let input_index = 0; + let expected_data_mem_location = 0x22; + let actual_data_mem_location = 0x23; + vec![ + op::gtf_args(len_reg, input_index, GTFArgs::InputDataCoinDataLength), + // get expected data from predicate data + op::gtf_args( + expected_data_mem_location, + input_index, + GTFArgs::InputCoinPredicateData, + ), + // get actual data + op::gtf_args( + actual_data_mem_location, + input_index, + GTFArgs::InputDataCoinData, + ), + // compare + op::meq( + res_reg, + expected_data_mem_location, + actual_data_mem_location, + len_reg, + ), + op::ret(res_reg), + ] + .into_iter() + .collect() +} + +#[tokio::test] +async fn submit__tx_with_predicate_can_check_data_coin() { + let _ = tracing_subscriber::fmt() + .with_max_level(tracing::Level::DEBUG) + .try_init(); + let mut rng = StdRng::seed_from_u64(2322); + + // given + let amount = 500; + let limit = 1000; + let asset_id = rng.gen(); + let predicate = predicate_checking_predicate_data_matches_input_data_coin(); + let coin_data = vec![123; 100]; + let predicate_data = coin_data.clone(); + let owner = Input::predicate_owner(&predicate); + let mut predicate_tx = + TransactionBuilder::script(Default::default(), Default::default()) + .add_input(Input::data_coin_predicate( + rng.gen(), + owner, + amount, + asset_id, + Default::default(), + Default::default(), + predicate, + predicate_data, + coin_data, + )) + .add_output(Output::change(rng.gen(), 0, asset_id)) + .script_gas_limit(limit) + .finalize(); + + // create test context with predicates disabled + let context = TestSetupBuilder::default() + .config_coin_inputs_from_transactions(&[&predicate_tx]) + .finalize() + .await; + + assert_eq!(predicate_tx.inputs()[0].predicate_gas_used().unwrap(), 0); + + predicate_tx + .estimate_predicates( + &CheckPredicateParams::from( + &context + .srv + .shared + .config + .snapshot_reader + .chain_config() + .consensus_parameters, + ), + MemoryInstance::new(), + &EmptyStorage, + ) + .expect("Predicate check failed"); + + assert_ne!(predicate_tx.inputs()[0].predicate_gas_used().unwrap(), 0); + + let predicate_tx = predicate_tx.into(); + context + .client + .submit_and_await_commit(&predicate_tx) + .await + .unwrap(); + + // check transaction change amount to see if predicate was spent + let transaction: Transaction = context + .client + .transaction(&predicate_tx.id(&ChainId::default())) + .await + .unwrap() + .unwrap() + .transaction + .try_into() + .unwrap(); + + assert!( + matches!(transaction.as_script().unwrap().outputs()[0], Output::Change { amount: change_amount, .. } if change_amount == amount) + ) +} diff --git a/tests/tests/tx/tx_pointer.rs b/tests/tests/tx/tx_pointer.rs index 6d82b2ea6f7..3d9bbc4689d 100644 --- a/tests/tests/tx/tx_pointer.rs +++ b/tests/tests/tx/tx_pointer.rs @@ -2,7 +2,7 @@ use crate::helpers::{ TestContext, TestSetupBuilder, }; -use fuel_core::chain_config::CoinConfig; +use fuel_core::chain_config::ConfigCoin; use fuel_core_types::{ fuel_crypto::SecretKey, fuel_tx::{ @@ -52,15 +52,18 @@ async fn tx_pointer_set_from_genesis_for_coin_and_contract_inputs() { let amount = 1000; // add coin to genesis block - test_builder.initial_coins.push(CoinConfig { - tx_id: *coin_utxo_id.tx_id(), - output_index: coin_utxo_id.output_index(), - tx_pointer_block_height: coin_tx_pointer.block_height(), - tx_pointer_tx_idx: coin_tx_pointer.tx_index(), - owner, - amount, - asset_id: Default::default(), - }); + test_builder.initial_coins.push( + ConfigCoin { + tx_id: *coin_utxo_id.tx_id(), + output_index: coin_utxo_id.output_index(), + tx_pointer_block_height: coin_tx_pointer.block_height(), + tx_pointer_tx_idx: coin_tx_pointer.tx_index(), + owner, + amount, + asset_id: Default::default(), + } + .into(), + ); // set starting block >= tx_pointer.block_height() test_builder.starting_block = Some(starting_block); diff --git a/tests/tests/tx/txn_status_subscription.rs b/tests/tests/tx/txn_status_subscription.rs index b7e56b64489..1625cc6ec25 100644 --- a/tests/tests/tx/txn_status_subscription.rs +++ b/tests/tests/tx/txn_status_subscription.rs @@ -212,7 +212,7 @@ async fn test_regression_in_subscribe() { empty_create .clone() .add_input(contract.clone()) - .add_output(contract_created) + .add_output(contract_created.clone()) .finalize() .into(), empty_create From 79e1e19fab03be43010f6e659be33818d5f9e697 Mon Sep 17 00:00:00 2001 From: Mitch Turner Date: Mon, 31 Mar 2025 15:07:42 -0600 Subject: [PATCH 10/37] WIP troubleshooting integ test --- Cargo.lock | 1 + crates/chain-config/src/config/coin.rs | 88 ++++++++++++++----- crates/chain-config/src/config/state.rs | 4 + .../chain-config/src/config/state/reader.rs | 9 ++ .../src/graphql_api/storage/coins/codecs.rs | 85 +++++++++++++----- crates/fuel-core/src/service/sub_services.rs | 19 +++- crates/services/executor/src/executor.rs | 2 + .../services/txpool_v2/src/storage/graph.rs | 12 ++- crates/types/src/entities/coins/coin.rs | 5 +- tests/test-helpers/Cargo.toml | 1 + tests/test-helpers/src/builder.rs | 9 ++ tests/tests/tx/predicates.rs | 14 +++ 12 files changed, 200 insertions(+), 49 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7ba7e77c06b..67d68841c7c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9809,6 +9809,7 @@ dependencies = [ "serde_json", "tempfile", "tokio", + "tracing", ] [[package]] diff --git a/crates/chain-config/src/config/coin.rs b/crates/chain-config/src/config/coin.rs index 03e143cf4dd..ede740e6faf 100644 --- a/crates/chain-config/src/config/coin.rs +++ b/crates/chain-config/src/config/coin.rs @@ -3,6 +3,7 @@ use serde::{ Serialize, }; +use crate::GenesisCommitment; use fuel_core_storage::{ tables::Coins, MerkleRoot, @@ -12,6 +13,7 @@ use fuel_core_types::{ Coin, CompressedCoin, CompressedCoinV1, + CompressedCoinV2, DataCoin, }, fuel_crypto::Hasher, @@ -27,8 +29,6 @@ use fuel_core_types::{ }, }; -use crate::GenesisCommitment; - use super::table_entry::TableEntry; #[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)] @@ -162,33 +162,73 @@ impl From for CoinConfig { } impl From> for CoinConfig { - fn from(value: TableEntry) -> Self { - ConfigCoin { - tx_id: *value.key.tx_id(), - output_index: value.key.output_index(), - tx_pointer_block_height: value.value.tx_pointer().block_height(), - tx_pointer_tx_idx: value.value.tx_pointer().tx_index(), - owner: *value.value.owner(), - amount: *value.value.amount(), - asset_id: *value.value.asset_id(), - } - .into() + fn from(_value: TableEntry) -> Self { + // ConfigCoin { + // tx_id: *value.key.tx_id(), + // output_index: value.key.output_index(), + // tx_pointer_block_height: value.value.tx_pointer().block_height(), + // tx_pointer_tx_idx: value.value.tx_pointer().tx_index(), + // owner: *value.value.owner(), + // amount: *value.value.amount(), + // asset_id: *value.value.asset_id(), + // } + // .into() + todo!() } } impl From for TableEntry { fn from(config: CoinConfig) -> Self { - Self { - key: UtxoId::new(config.tx_id(), config.output_index()), - value: CompressedCoin::V1(CompressedCoinV1 { - owner: config.owner(), - amount: config.amount(), - asset_id: config.asset_id(), - tx_pointer: TxPointer::new( - config.tx_pointer_block_height(), - config.tx_pointer_tx_idx(), - ), - }), + match config { + CoinConfig::Coin(config) => { + tracing::debug!( + "Creating TableEntry for CoinConfig: tx_id={:?}, output_index={}, owner={:?}, amount={}, asset_id={:?}", + config.tx_id, + config.output_index, + config.owner, + config.amount, + config.asset_id + ); + let value = CompressedCoin::V1(CompressedCoinV1 { + owner: config.owner, + amount: config.amount, + asset_id: config.asset_id, + tx_pointer: TxPointer::new( + config.tx_pointer_block_height, + config.tx_pointer_tx_idx, + ), + }); + Self { + key: UtxoId::new(config.tx_id, config.output_index), + value, + } + } + CoinConfig::DataCoin(config) => { + tracing::debug!( + "Creating TableEntry for DataCoinConfig: tx_id={:?}, output_index={}, owner={:?}, amount={}, asset_id={:?}, data_len={}", + config.tx_id, + config.output_index, + config.owner, + config.amount, + config.asset_id, + config.data.len() + ); + let value = CompressedCoin::V2(CompressedCoinV2 { + owner: config.owner, + amount: config.amount, + asset_id: config.asset_id, + tx_pointer: TxPointer::new( + config.tx_pointer_block_height, + config.tx_pointer_tx_idx, + ), + data: config.data, + }); + + Self { + key: UtxoId::new(config.tx_id, config.output_index), + value, + } + } } } } diff --git a/crates/chain-config/src/config/state.rs b/crates/chain-config/src/config/state.rs index a1ac6a25e9c..dd05bf12d2c 100644 --- a/crates/chain-config/src/config/state.rs +++ b/crates/chain-config/src/config/state.rs @@ -266,6 +266,10 @@ impl AddTable for StateConfigBuilder { impl AsTable for StateConfig { fn as_table(&self) -> Vec> { + tracing::debug!( + "Converting StateConfig to Coins table with {} entries", + self.coins.len() + ); self.coins .clone() .into_iter() diff --git a/crates/chain-config/src/config/state/reader.rs b/crates/chain-config/src/config/state/reader.rs index 3ea058809b4..4ac2380bfa4 100644 --- a/crates/chain-config/src/config/state/reader.rs +++ b/crates/chain-config/src/config/state/reader.rs @@ -139,6 +139,15 @@ impl SnapshotReader { Self::new_in_memory(chain_config, state) } + #[cfg(feature = "test-helpers")] + pub fn get_in_memory_data_source_state(&self) -> Option<&StateConfig> { + match &self.data_source { + DataSource::InMemory { state, .. } => Some(state), + #[cfg(feature = "parquet")] + DataSource::Parquet { .. } => None, + } + } + pub fn with_chain_config(self, chain_config: ChainConfig) -> Self { Self { chain_config, diff --git a/crates/fuel-core/src/graphql_api/storage/coins/codecs.rs b/crates/fuel-core/src/graphql_api/storage/coins/codecs.rs index 68bf4db3257..c4e0120670c 100644 --- a/crates/fuel-core/src/graphql_api/storage/coins/codecs.rs +++ b/crates/fuel-core/src/graphql_api/storage/coins/codecs.rs @@ -28,6 +28,7 @@ pub const RETRYABLE_FLAG_SIZE: usize = size_of::(); pub enum CoinType { Coin, Message, + DataCoin, } pub const COIN_TYPE_SIZE: usize = size_of::(); @@ -50,6 +51,8 @@ pub const MESSAGE_VARIANT_SIZE: usize = RETRYABLE_FLAG_SIZE pub enum SerializedCoinsToSpendIndexKey { Coin([u8; COIN_VARIANT_SIZE]), + // TODO: Is this okay as a `Vec`? + DataCoin(Vec), Message([u8; MESSAGE_VARIANT_SIZE]), } @@ -57,6 +60,7 @@ impl Encoder for SerializedCoinsToSpendIndexKey { fn as_bytes(&self) -> Cow<[u8]> { match self { SerializedCoinsToSpendIndexKey::Coin(bytes) => Cow::Borrowed(bytes), + SerializedCoinsToSpendIndexKey::DataCoin(bytes) => Cow::Borrowed(bytes), SerializedCoinsToSpendIndexKey::Message(bytes) => Cow::Borrowed(bytes), } } @@ -131,33 +135,43 @@ impl Encode for Manual { asset_id, amount, utxo_id, - data: _data, + data, } => { // retryable_flag | address | asset_id | amount | utxo_id | coin_type let retryable_flag_bytes = NON_RETRYABLE_BYTE; // retryable_flag | address | asset_id | amount | utxo_id | coin_type - let mut serialized_coin = [0u8; COIN_VARIANT_SIZE]; - let mut start = 0; - let mut end = RETRYABLE_FLAG_SIZE; - serialized_coin[start] = retryable_flag_bytes[0]; - start = end; - end = end.saturating_add(Address::LEN); - serialized_coin[start..end].copy_from_slice(owner.as_ref()); - start = end; - end = end.saturating_add(AssetId::LEN); - serialized_coin[start..end].copy_from_slice(asset_id.as_ref()); - start = end; - end = end.saturating_add(AMOUNT_SIZE); - serialized_coin[start..end].copy_from_slice(&amount.to_be_bytes()); - start = end; - end = end.saturating_add(UTXO_ID_SIZE); - serialized_coin[start..end].copy_from_slice(&utxo_id_to_bytes(utxo_id)); - start = end; - serialized_coin[start] = CoinType::Coin as u8; + let mut serialized_coin = + Vec::with_capacity(COIN_VARIANT_SIZE + data.len()); + // let mut start = 0; + // let mut end = RETRYABLE_FLAG_SIZE; + // serialized_coin[start] = retryable_flag_bytes[0]; + // start = end; + // end = end.saturating_add(Address::LEN); + // serialized_coin[start..end].copy_from_slice(owner.as_ref()); + // start = end; + // end = end.saturating_add(AssetId::LEN); + // serialized_coin[start..end].copy_from_slice(asset_id.as_ref()); + // start = end; + // end = end.saturating_add(AMOUNT_SIZE); + // serialized_coin[start..end].copy_from_slice(&amount.to_be_bytes()); + // start = end; + // end = end.saturating_add(UTXO_ID_SIZE); + // serialized_coin[start..end].copy_from_slice(&utxo_id_to_bytes(utxo_id)); + // start = end; + // end = end.saturating_add(data.len()); + // serialized_coin[start..end].copy_from_slice(&data); + // serialized_coin[start] = CoinType::DataCoin as u8; + // TODO: Check that this works! + serialized_coin.push(retryable_flag_bytes[0]); + serialized_coin.extend_from_slice(owner.as_ref()); + serialized_coin.extend_from_slice(asset_id.as_ref()); + serialized_coin.extend_from_slice(&amount.to_be_bytes()); + serialized_coin.extend_from_slice(&utxo_id_to_bytes(utxo_id)); + serialized_coin.extend_from_slice(&data); + serialized_coin.push(CoinType::DataCoin as u8); - todo!("Need to add data"); - // SerializedCoinsToSpendIndexKey::Coin(serialized_coin) + SerializedCoinsToSpendIndexKey::DataCoin(serialized_coin) } } } @@ -201,6 +215,35 @@ impl Decode for Manual { utxo_id, } } + CoinType::DataCoin => { + let bytes: Vec = bytes.to_vec(); + let mut end = RETRYABLE_FLAG_SIZE; + let mut start = end; + end = end.saturating_add(Address::LEN); + let owner = Address::try_from(&bytes[start..end])?; + start = end; + end = end.saturating_add(AssetId::LEN); + let asset_id = AssetId::try_from(&bytes[start..end])?; + start = end; + end = end.saturating_add(AMOUNT_SIZE); + let amount = u64::from_be_bytes(bytes[start..end].try_into()?); + start = end; + end = bytes.len() - 1; // Exclude the last byte which is coin type + + let utxo_id_bytes = &bytes[start..end]; + let (tx_id_bytes, output_index_bytes) = utxo_id_bytes.split_at(TxId::LEN); + let tx_id = TxId::try_from(tx_id_bytes)?; + let output_index = u16::from_be_bytes(output_index_bytes.try_into()?); + let utxo_id = UtxoId::new(tx_id, output_index); + + CoinsToSpendIndexKey::DataCoin { + owner, + asset_id, + amount, + utxo_id, + data: bytes[end..].to_vec(), + } + } CoinType::Message => { let bytes: [u8; MESSAGE_VARIANT_SIZE] = bytes.try_into()?; let mut start = 0; diff --git a/crates/fuel-core/src/service/sub_services.rs b/crates/fuel-core/src/service/sub_services.rs index 778249b483a..01ef2389d0e 100644 --- a/crates/fuel-core/src/service/sub_services.rs +++ b/crates/fuel-core/src/service/sub_services.rs @@ -23,10 +23,16 @@ use fuel_core_storage::{ use fuel_core_types::blockchain::primitives::DaBlockHeight; use fuel_core_types::signer::SignMode; -use fuel_core_compression_service::service::new_service as new_compression_service; - #[cfg(feature = "relayer")] use crate::relayer::Config as RelayerConfig; +use fuel_core_compression_service::service::new_service as new_compression_service; +use fuel_core_storage::{ + iter::{ + IterDirection, + IteratorOverTable, + }, + tables::Coins, +}; #[cfg(feature = "p2p")] use crate::service::adapters::consensus_module::poa::pre_confirmation_signature::{ @@ -124,6 +130,7 @@ pub fn init_sub_services( #[cfg(not(feature = "p2p"))] let (preconfirmation_sender, _) = tokio::sync::mpsc::channel(1024); + // 1 let genesis_block = on_chain_view .genesis_block()? .unwrap_or(create_genesis_block(config).compress(&chain_id)); @@ -458,6 +465,14 @@ pub fn init_sub_services( Box::new(compression_service_adapter), )?; + // 2 + let coins: Vec<_> = database + .on_chain() + .iter_all::(Some(IterDirection::Forward)) + .collect(); + + tracing::debug!("Initialized coins table with: {:?}", coins); + let shared = SharedState { poa_adapter, txpool_shared_state: txpool.shared.clone(), diff --git a/crates/services/executor/src/executor.rs b/crates/services/executor/src/executor.rs index 009436cd166..8e96197d77f 100644 --- a/crates/services/executor/src/executor.rs +++ b/crates/services/executor/src/executor.rs @@ -2249,6 +2249,8 @@ where )) .map(Cow::into_owned) } else { + debug!("vvvvvv creating new coin for utxo_id: {:?} owner: {:?} amount: {} asset_id: {:?} vvvvvv", + utxo_id, owner, amount, asset_id); // if utxo validation is disabled, just assign this new input to the original block let coin = CompressedCoinV1 { owner, diff --git a/crates/services/txpool_v2/src/storage/graph.rs b/crates/services/txpool_v2/src/storage/graph.rs index d1916a6dbd4..634e226f0f6 100644 --- a/crates/services/txpool_v2/src/storage/graph.rs +++ b/crates/services/txpool_v2/src/storage/graph.rs @@ -760,9 +760,19 @@ impl Storage for GraphStorage { } else if utxo_validation { match persistent_storage.utxo(utxo_id) { Ok(Some(coin)) => { + tracing::debug!( + "Validating input \n{:?} \nagainst persistent storage coin: \n{:?}", + input, + coin + ); if !coin .matches_input(input) - .expect("The input is coin above") + .expect("Input is a coin above") + // .ok_or(InputValidationErrorType::Inconsistency( + // Error::InputValidation( + // InputValidationError::NotInsertedIoCoinMismatch, + // ), + // ))? { return Err(InputValidationErrorType::Inconsistency(Error::InputValidation( InputValidationError::NotInsertedIoCoinMismatch, diff --git a/crates/types/src/entities/coins/coin.rs b/crates/types/src/entities/coins/coin.rs index 2f03bbabbca..94fa569a841 100644 --- a/crates/types/src/entities/coins/coin.rs +++ b/crates/types/src/entities/coins/coin.rs @@ -332,18 +332,21 @@ impl CompressedCoin { owner, amount, asset_id, + data, .. }) | Input::DataCoinPredicate(DataCoinPredicate { owner, amount, asset_id, + data, .. }) => match self { CompressedCoin::V2(coin) => Some( owner == &coin.owner && amount == &coin.amount - && asset_id == &coin.asset_id, + && asset_id == &coin.asset_id + && data == data, ), _ => { tracing::debug!( diff --git a/tests/test-helpers/Cargo.toml b/tests/test-helpers/Cargo.toml index 26cc26a88e0..ce5d9700fc5 100644 --- a/tests/test-helpers/Cargo.toml +++ b/tests/test-helpers/Cargo.toml @@ -37,3 +37,4 @@ reqwest = { workspace = true } serde_json = { workspace = true } tokio = { workspace = true } tempfile = { workspace = true } +tracing = "0.1.41" diff --git a/tests/test-helpers/src/builder.rs b/tests/test-helpers/src/builder.rs index 6a73471ca33..4addb57b8e2 100644 --- a/tests/test-helpers/src/builder.rs +++ b/tests/test-helpers/src/builder.rs @@ -218,6 +218,8 @@ impl TestSetupBuilder { .. }) = input { + tracing::debug!("Adding data coin input: tx_id: {:?}, output_index: {}, owner: {:?}, amount: {}, asset_id: {:?}, data: {:?}", + utxo_id.tx_id(), utxo_id.output_index(), owner, amount, asset_id, data); Some( ConfigDataCoin { tx_id: *utxo_id.tx_id(), @@ -306,6 +308,13 @@ impl TestSetupBuilder { gas_price_config, ..Config::local_node_with_configs(chain_conf, state) }; + + let state = config + .snapshot_reader + .get_in_memory_data_source_state() + .unwrap(); + + tracing::debug!("outputs to add to state: {:?}", state.coins); config.combined_db_config.database_config = self.database_config; let srv = FuelService::new_node(config).await.unwrap(); diff --git a/tests/tests/tx/predicates.rs b/tests/tests/tx/predicates.rs index 330b54a56b6..b3710827e8d 100644 --- a/tests/tests/tx/predicates.rs +++ b/tests/tests/tx/predicates.rs @@ -1,6 +1,7 @@ // Tests related to the predicate execution feature use crate::helpers::TestSetupBuilder; +use fuel_core_storage::tables::Coins; use fuel_core_types::{ fuel_asm::*, fuel_tx::{ @@ -251,6 +252,19 @@ async fn submit__tx_with_predicate_can_check_data_coin() { .finalize() .await; + use fuel_core_storage::StorageAsRef; + + let coin = context + .srv + .shared + .database + .on_chain() + .storage::() + .get(&predicate_tx.inputs()[0].utxo_id().unwrap()) + .expect("Failed to get coin from db"); + + tracing::debug!("zzzzzz {:?}", coin); + assert_eq!(predicate_tx.inputs()[0].predicate_gas_used().unwrap(), 0); predicate_tx From ea66242dcf5e34f806bdccb66c487ff5cde92e0b Mon Sep 17 00:00:00 2001 From: Mitch Turner Date: Mon, 31 Mar 2025 16:31:44 -0600 Subject: [PATCH 11/37] Get the table entry to work properly for genesis block --- crates/chain-config/src/config/coin.rs | 10 ++++-- .../chain-config/src/config/state/reader.rs | 1 + crates/fuel-core/src/service/genesis.rs | 18 ++++++++++ .../src/service/genesis/importer/on_chain.rs | 36 ++++++++++++++----- 4 files changed, 54 insertions(+), 11 deletions(-) diff --git a/crates/chain-config/src/config/coin.rs b/crates/chain-config/src/config/coin.rs index ede740e6faf..28203eb385d 100644 --- a/crates/chain-config/src/config/coin.rs +++ b/crates/chain-config/src/config/coin.rs @@ -179,7 +179,7 @@ impl From> for CoinConfig { impl From for TableEntry { fn from(config: CoinConfig) -> Self { - match config { + let entry = match config { CoinConfig::Coin(config) => { tracing::debug!( "Creating TableEntry for CoinConfig: tx_id={:?}, output_index={}, owner={:?}, amount={}, asset_id={:?}", @@ -229,7 +229,13 @@ impl From for TableEntry { value, } } - } + }; + tracing::debug!( + "Created TableEntry: key={:?}, value={:?}", + &entry.key, + &entry.value, + ); + entry } } diff --git a/crates/chain-config/src/config/state/reader.rs b/crates/chain-config/src/config/state/reader.rs index 4ac2380bfa4..2b0ae78646d 100644 --- a/crates/chain-config/src/config/state/reader.rs +++ b/crates/chain-config/src/config/state/reader.rs @@ -278,6 +278,7 @@ impl SnapshotReader { } } DataSource::InMemory { state, group_size } => { + tracing::debug!("Creating in-memory data source"); let collection = state .as_table() .into_iter() diff --git a/crates/fuel-core/src/service/genesis.rs b/crates/fuel-core/src/service/genesis.rs index 461691e8584..be3b7e515d7 100644 --- a/crates/fuel-core/src/service/genesis.rs +++ b/crates/fuel-core/src/service/genesis.rs @@ -61,6 +61,10 @@ use fuel_core_types::{ use itertools::Itertools; pub use exporter::Exporter; +use fuel_core_storage::{ + iter::IterDirection, + tables::Coins, +}; pub use task_manager::NotifyCancel; mod exporter; @@ -92,6 +96,13 @@ pub async fn execute_genesis_block( off_chain, }; + let coins: Vec<_> = db + .on_chain() + .iter_all::(Some(IterDirection::Forward)) + .collect(); + + tracing::debug!("pppppppppp: {:?}", coins); + SnapshotImporter::import( genesis_db.clone(), genesis_block.clone(), @@ -100,6 +111,13 @@ pub async fn execute_genesis_block( ) .await?; + let coins: Vec<_> = db + .on_chain() + .iter_all::(Some(IterDirection::Forward)) + .collect(); + + tracing::debug!("qqqqqqqqq: {:?}", coins); + let genesis_progress_on_chain: Vec = db .on_chain() .iter_all_keys::>(None) diff --git a/crates/fuel-core/src/service/genesis/importer/on_chain.rs b/crates/fuel-core/src/service/genesis/importer/on_chain.rs index c5ed0d5cca6..0c2b0a2e084 100644 --- a/crates/fuel-core/src/service/genesis/importer/on_chain.rs +++ b/crates/fuel-core/src/service/genesis/importer/on_chain.rs @@ -31,7 +31,11 @@ use fuel_core_types::{ self, blockchain::primitives::DaBlockHeight, entities::{ - coins::coin::Coin, + coins::coin::{ + Coin, + CompressedCoin, + DataCoin, + }, Message, }, fuel_types::BlockHeight, @@ -218,14 +222,28 @@ fn init_coin( ) -> anyhow::Result<()> { let utxo_id = coin.key; - let compressed_coin = Coin { - utxo_id, - owner: *coin.value.owner(), - amount: *coin.value.amount(), - asset_id: *coin.value.asset_id(), - tx_pointer: *coin.value.tx_pointer(), - } - .compress(); + let compressed_coin = match &coin.value { + CompressedCoin::V1(coin) => Coin { + utxo_id, + owner: coin.owner, + amount: coin.amount, + asset_id: coin.asset_id, + tx_pointer: coin.tx_pointer, + } + .compress(), + CompressedCoin::V2(data_coin) => DataCoin { + utxo_id, + owner: data_coin.owner, + amount: data_coin.amount, + asset_id: data_coin.asset_id, + tx_pointer: data_coin.tx_pointer, + data: data_coin.data.clone(), + } + .compress(), + _ => { + unreachable!("We have covered the two cases") + } + }; // ensure coin can't point to blocks in the future let coin_height = coin.value.tx_pointer().block_height(); From 3a27f6939c45f689cf6768f69f458aab9562100a Mon Sep 17 00:00:00 2001 From: Mitchell Turner Date: Mon, 31 Mar 2025 16:42:40 -0600 Subject: [PATCH 12/37] Update crates/chain-config/src/config/state.rs --- crates/chain-config/src/config/state.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/crates/chain-config/src/config/state.rs b/crates/chain-config/src/config/state.rs index dd05bf12d2c..a1ac6a25e9c 100644 --- a/crates/chain-config/src/config/state.rs +++ b/crates/chain-config/src/config/state.rs @@ -266,10 +266,6 @@ impl AddTable for StateConfigBuilder { impl AsTable for StateConfig { fn as_table(&self) -> Vec> { - tracing::debug!( - "Converting StateConfig to Coins table with {} entries", - self.coins.len() - ); self.coins .clone() .into_iter() From 670beeb965fa3a4da553bd09f159eb6386f931ca Mon Sep 17 00:00:00 2001 From: Mitchell Turner Date: Mon, 31 Mar 2025 16:42:47 -0600 Subject: [PATCH 13/37] Update crates/chain-config/src/config/state/reader.rs --- crates/chain-config/src/config/state/reader.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/chain-config/src/config/state/reader.rs b/crates/chain-config/src/config/state/reader.rs index 2b0ae78646d..4ac2380bfa4 100644 --- a/crates/chain-config/src/config/state/reader.rs +++ b/crates/chain-config/src/config/state/reader.rs @@ -278,7 +278,6 @@ impl SnapshotReader { } } DataSource::InMemory { state, group_size } => { - tracing::debug!("Creating in-memory data source"); let collection = state .as_table() .into_iter() From 897ecc1ac18f1ebded1981ba8d1cd65ab72601f0 Mon Sep 17 00:00:00 2001 From: Mitchell Turner Date: Mon, 31 Mar 2025 16:42:53 -0600 Subject: [PATCH 14/37] Update crates/fuel-core/src/graphql_api/storage/coins/codecs.rs --- .../src/graphql_api/storage/coins/codecs.rs | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/crates/fuel-core/src/graphql_api/storage/coins/codecs.rs b/crates/fuel-core/src/graphql_api/storage/coins/codecs.rs index c4e0120670c..647eb4ba308 100644 --- a/crates/fuel-core/src/graphql_api/storage/coins/codecs.rs +++ b/crates/fuel-core/src/graphql_api/storage/coins/codecs.rs @@ -143,25 +143,6 @@ impl Encode for Manual { // retryable_flag | address | asset_id | amount | utxo_id | coin_type let mut serialized_coin = Vec::with_capacity(COIN_VARIANT_SIZE + data.len()); - // let mut start = 0; - // let mut end = RETRYABLE_FLAG_SIZE; - // serialized_coin[start] = retryable_flag_bytes[0]; - // start = end; - // end = end.saturating_add(Address::LEN); - // serialized_coin[start..end].copy_from_slice(owner.as_ref()); - // start = end; - // end = end.saturating_add(AssetId::LEN); - // serialized_coin[start..end].copy_from_slice(asset_id.as_ref()); - // start = end; - // end = end.saturating_add(AMOUNT_SIZE); - // serialized_coin[start..end].copy_from_slice(&amount.to_be_bytes()); - // start = end; - // end = end.saturating_add(UTXO_ID_SIZE); - // serialized_coin[start..end].copy_from_slice(&utxo_id_to_bytes(utxo_id)); - // start = end; - // end = end.saturating_add(data.len()); - // serialized_coin[start..end].copy_from_slice(&data); - // serialized_coin[start] = CoinType::DataCoin as u8; // TODO: Check that this works! serialized_coin.push(retryable_flag_bytes[0]); serialized_coin.extend_from_slice(owner.as_ref()); From 62f1843b4e2ed3b89abe441255016c8f73ddeeff Mon Sep 17 00:00:00 2001 From: Mitchell Turner Date: Mon, 31 Mar 2025 16:43:01 -0600 Subject: [PATCH 15/37] Update crates/fuel-core/src/graphql_api/storage/coins/codecs.rs --- crates/fuel-core/src/graphql_api/storage/coins/codecs.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/fuel-core/src/graphql_api/storage/coins/codecs.rs b/crates/fuel-core/src/graphql_api/storage/coins/codecs.rs index 647eb4ba308..48333091511 100644 --- a/crates/fuel-core/src/graphql_api/storage/coins/codecs.rs +++ b/crates/fuel-core/src/graphql_api/storage/coins/codecs.rs @@ -137,7 +137,6 @@ impl Encode for Manual { utxo_id, data, } => { - // retryable_flag | address | asset_id | amount | utxo_id | coin_type let retryable_flag_bytes = NON_RETRYABLE_BYTE; // retryable_flag | address | asset_id | amount | utxo_id | coin_type From d09d0e2a19eb7cb695198039d8d05ab9b0dad813 Mon Sep 17 00:00:00 2001 From: Mitchell Turner Date: Mon, 31 Mar 2025 16:43:10 -0600 Subject: [PATCH 16/37] Update crates/fuel-core/src/graphql_api/storage/coins/codecs.rs --- crates/fuel-core/src/graphql_api/storage/coins/codecs.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/fuel-core/src/graphql_api/storage/coins/codecs.rs b/crates/fuel-core/src/graphql_api/storage/coins/codecs.rs index 48333091511..56511dc2544 100644 --- a/crates/fuel-core/src/graphql_api/storage/coins/codecs.rs +++ b/crates/fuel-core/src/graphql_api/storage/coins/codecs.rs @@ -139,7 +139,6 @@ impl Encode for Manual { } => { let retryable_flag_bytes = NON_RETRYABLE_BYTE; - // retryable_flag | address | asset_id | amount | utxo_id | coin_type let mut serialized_coin = Vec::with_capacity(COIN_VARIANT_SIZE + data.len()); // TODO: Check that this works! From e2a9da6e784992bbde5e7a359d977bfd1ac171aa Mon Sep 17 00:00:00 2001 From: Mitchell Turner Date: Mon, 31 Mar 2025 16:43:19 -0600 Subject: [PATCH 17/37] Update crates/types/src/entities/coins/coin.rs --- crates/types/src/entities/coins/coin.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/crates/types/src/entities/coins/coin.rs b/crates/types/src/entities/coins/coin.rs index 94fa569a841..f3d6e213efc 100644 --- a/crates/types/src/entities/coins/coin.rs +++ b/crates/types/src/entities/coins/coin.rs @@ -234,10 +234,6 @@ impl CompressedCoin { } } - // pub fn uncompress_data_coin(self, _utxo_id: UtxoId) -> Option { - // None - // } - /// Get the owner of the coin pub fn owner(&self) -> &Address { match self { From 4b8ad3b66727ab2fc55d4d9cb769d08667183f80 Mon Sep 17 00:00:00 2001 From: Mitchell Turner Date: Mon, 31 Mar 2025 16:43:29 -0600 Subject: [PATCH 18/37] Update crates/services/txpool_v2/src/storage/graph.rs --- crates/services/txpool_v2/src/storage/graph.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/crates/services/txpool_v2/src/storage/graph.rs b/crates/services/txpool_v2/src/storage/graph.rs index 634e226f0f6..5e43e3214aa 100644 --- a/crates/services/txpool_v2/src/storage/graph.rs +++ b/crates/services/txpool_v2/src/storage/graph.rs @@ -768,11 +768,6 @@ impl Storage for GraphStorage { if !coin .matches_input(input) .expect("Input is a coin above") - // .ok_or(InputValidationErrorType::Inconsistency( - // Error::InputValidation( - // InputValidationError::NotInsertedIoCoinMismatch, - // ), - // ))? { return Err(InputValidationErrorType::Inconsistency(Error::InputValidation( InputValidationError::NotInsertedIoCoinMismatch, From 9a623bfa2677d92abfda97d7a6d71548664f25d4 Mon Sep 17 00:00:00 2001 From: Mitchell Turner Date: Mon, 31 Mar 2025 16:43:38 -0600 Subject: [PATCH 19/37] Update crates/services/executor/src/executor.rs --- crates/services/executor/src/executor.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/crates/services/executor/src/executor.rs b/crates/services/executor/src/executor.rs index 8e96197d77f..f07ac60b670 100644 --- a/crates/services/executor/src/executor.rs +++ b/crates/services/executor/src/executor.rs @@ -2438,9 +2438,6 @@ where if db.storage::().replace(&utxo_id, &coin)?.is_some() { return Err(ExecutorError::OutputAlreadyExists) } - // execution_data - // .events - // .push(ExecutorEvent::CoinCreated(coin.uncompress_coin(utxo_id))); match coin.uncompress(utxo_id) { UncompressedCoin::Coin(coin) => { execution_data.events.push(ExecutorEvent::CoinCreated(coin)); From 66f0e293215b16f0d47404a1e27377024af99631 Mon Sep 17 00:00:00 2001 From: Mitchell Turner Date: Mon, 31 Mar 2025 16:43:46 -0600 Subject: [PATCH 20/37] Update crates/services/executor/src/executor.rs --- crates/services/executor/src/executor.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/crates/services/executor/src/executor.rs b/crates/services/executor/src/executor.rs index f07ac60b670..f5908259ff4 100644 --- a/crates/services/executor/src/executor.rs +++ b/crates/services/executor/src/executor.rs @@ -2391,9 +2391,6 @@ where if db.storage::().replace(&utxo_id, &coin)?.is_some() { return Err(ExecutorError::OutputAlreadyExists) } - // execution_data - // .events - // .push(ExecutorEvent::CoinCreated(coin.uncompress_coin(utxo_id))); match coin.uncompress(utxo_id) { UncompressedCoin::Coin(coin) => { execution_data.events.push(ExecutorEvent::CoinCreated(coin)); From a339f97bb1de9ab58248adb2e66a8b565460f390 Mon Sep 17 00:00:00 2001 From: Mitchell Turner Date: Mon, 31 Mar 2025 16:43:52 -0600 Subject: [PATCH 21/37] Update crates/services/executor/src/executor.rs --- crates/services/executor/src/executor.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/services/executor/src/executor.rs b/crates/services/executor/src/executor.rs index f5908259ff4..488845fed04 100644 --- a/crates/services/executor/src/executor.rs +++ b/crates/services/executor/src/executor.rs @@ -2249,8 +2249,6 @@ where )) .map(Cow::into_owned) } else { - debug!("vvvvvv creating new coin for utxo_id: {:?} owner: {:?} amount: {} asset_id: {:?} vvvvvv", - utxo_id, owner, amount, asset_id); // if utxo validation is disabled, just assign this new input to the original block let coin = CompressedCoinV1 { owner, From 1c23c18be75e9d2364c10f9e2a2fc51994055973 Mon Sep 17 00:00:00 2001 From: Mitchell Turner Date: Mon, 31 Mar 2025 16:44:03 -0600 Subject: [PATCH 22/37] Update crates/fuel-core/src/service/sub_services.rs --- crates/fuel-core/src/service/sub_services.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/fuel-core/src/service/sub_services.rs b/crates/fuel-core/src/service/sub_services.rs index 01ef2389d0e..9c73a9e53cb 100644 --- a/crates/fuel-core/src/service/sub_services.rs +++ b/crates/fuel-core/src/service/sub_services.rs @@ -130,7 +130,6 @@ pub fn init_sub_services( #[cfg(not(feature = "p2p"))] let (preconfirmation_sender, _) = tokio::sync::mpsc::channel(1024); - // 1 let genesis_block = on_chain_view .genesis_block()? .unwrap_or(create_genesis_block(config).compress(&chain_id)); From c21b437d1c152146cccfa730fc1bdd2688e04fb5 Mon Sep 17 00:00:00 2001 From: Mitchell Turner Date: Mon, 31 Mar 2025 16:44:15 -0600 Subject: [PATCH 23/37] Update crates/fuel-core/src/service/sub_services.rs --- crates/fuel-core/src/service/sub_services.rs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/crates/fuel-core/src/service/sub_services.rs b/crates/fuel-core/src/service/sub_services.rs index 9c73a9e53cb..5c5267a6b7c 100644 --- a/crates/fuel-core/src/service/sub_services.rs +++ b/crates/fuel-core/src/service/sub_services.rs @@ -464,14 +464,6 @@ pub fn init_sub_services( Box::new(compression_service_adapter), )?; - // 2 - let coins: Vec<_> = database - .on_chain() - .iter_all::(Some(IterDirection::Forward)) - .collect(); - - tracing::debug!("Initialized coins table with: {:?}", coins); - let shared = SharedState { poa_adapter, txpool_shared_state: txpool.shared.clone(), From f08558e5a38a6cadfe483937ec79bb0459dd597e Mon Sep 17 00:00:00 2001 From: Mitchell Turner Date: Mon, 31 Mar 2025 16:44:24 -0600 Subject: [PATCH 24/37] Update crates/fuel-core/src/service/genesis.rs --- crates/fuel-core/src/service/genesis.rs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/crates/fuel-core/src/service/genesis.rs b/crates/fuel-core/src/service/genesis.rs index be3b7e515d7..d3fcd24b0b0 100644 --- a/crates/fuel-core/src/service/genesis.rs +++ b/crates/fuel-core/src/service/genesis.rs @@ -96,13 +96,6 @@ pub async fn execute_genesis_block( off_chain, }; - let coins: Vec<_> = db - .on_chain() - .iter_all::(Some(IterDirection::Forward)) - .collect(); - - tracing::debug!("pppppppppp: {:?}", coins); - SnapshotImporter::import( genesis_db.clone(), genesis_block.clone(), From a6beef57582f74b06a896b24284f13902a1da074 Mon Sep 17 00:00:00 2001 From: Mitchell Turner Date: Mon, 31 Mar 2025 16:44:35 -0600 Subject: [PATCH 25/37] Update crates/fuel-core/src/service/genesis.rs --- crates/fuel-core/src/service/genesis.rs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/crates/fuel-core/src/service/genesis.rs b/crates/fuel-core/src/service/genesis.rs index d3fcd24b0b0..a0210faa0c9 100644 --- a/crates/fuel-core/src/service/genesis.rs +++ b/crates/fuel-core/src/service/genesis.rs @@ -104,13 +104,6 @@ pub async fn execute_genesis_block( ) .await?; - let coins: Vec<_> = db - .on_chain() - .iter_all::(Some(IterDirection::Forward)) - .collect(); - - tracing::debug!("qqqqqqqqq: {:?}", coins); - let genesis_progress_on_chain: Vec = db .on_chain() .iter_all_keys::>(None) From 45a48298cdf841c465278b6cc2eb84b766fc27f8 Mon Sep 17 00:00:00 2001 From: Mitchell Turner Date: Mon, 31 Mar 2025 16:44:42 -0600 Subject: [PATCH 26/37] Update crates/fuel-core/src/service/genesis/importer/off_chain.rs --- crates/fuel-core/src/service/genesis/importer/off_chain.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/fuel-core/src/service/genesis/importer/off_chain.rs b/crates/fuel-core/src/service/genesis/importer/off_chain.rs index e55e6c1eaa2..7b9e78f487d 100644 --- a/crates/fuel-core/src/service/genesis/importer/off_chain.rs +++ b/crates/fuel-core/src/service/genesis/importer/off_chain.rs @@ -169,7 +169,6 @@ impl ImportTable for Handler { tx: &mut StorageTransaction<&mut GenesisDatabase>, ) -> anyhow::Result<()> { let events = group.into_iter().map(|TableEntry { value, key }| { - // Cow::Owned(Event::CoinCreated(value.uncompress_coin(key))) match value.uncompress(key) { UncompressedCoin::Coin(coin) => Cow::Owned(Event::CoinCreated(coin)), UncompressedCoin::DataCoin(data_coin) => { From 43c4d774f5044ca04e055b150a7bafd78c475ac1 Mon Sep 17 00:00:00 2001 From: Mitch Turner Date: Mon, 31 Mar 2025 22:43:40 -0600 Subject: [PATCH 27/37] Appease Clippy-sama --- benches/src/bin/tps_bench.rs | 23 +++++++------- crates/chain-config/src/config/coin.rs | 16 ++++++---- .../src/graphql_api/storage/coins/codecs.rs | 17 ++++++----- crates/fuel-core/src/service/genesis.rs | 30 ++++++++++--------- crates/fuel-core/src/service/sub_services.rs | 7 ----- .../services/txpool_v2/src/tests/universe.rs | 2 +- crates/types/src/entities/coins.rs | 2 +- crates/types/src/entities/coins/coin.rs | 2 +- tests/tests/tx_gossip.rs | 6 ++-- 9 files changed, 56 insertions(+), 49 deletions(-) diff --git a/benches/src/bin/tps_bench.rs b/benches/src/bin/tps_bench.rs index 262791a5346..9d2a42ca0d5 100644 --- a/benches/src/bin/tps_bench.rs +++ b/benches/src/bin/tps_bench.rs @@ -3,7 +3,7 @@ use fuel_core::service::config::Trigger; use fuel_core_chain_config::{ ChainConfig, - CoinConfig, + ConfigCoin, SnapshotMetadata, }; use fuel_core_storage::transactional::AtomicView; @@ -169,15 +169,18 @@ fn main() { .. }) = input { - Some(CoinConfig { - tx_id: *utxo_id.tx_id(), - output_index: utxo_id.output_index(), - tx_pointer_block_height: tx_pointer.block_height(), - tx_pointer_tx_idx: tx_pointer.tx_index(), - owner: *owner, - amount: *amount, - asset_id: *asset_id, - }) + Some( + ConfigCoin { + tx_id: *utxo_id.tx_id(), + output_index: utxo_id.output_index(), + tx_pointer_block_height: tx_pointer.block_height(), + tx_pointer_tx_idx: tx_pointer.tx_index(), + owner: *owner, + amount: *amount, + asset_id: *asset_id, + } + .into(), + ) } else { None } diff --git a/crates/chain-config/src/config/coin.rs b/crates/chain-config/src/config/coin.rs index 28203eb385d..b4dc736831d 100644 --- a/crates/chain-config/src/config/coin.rs +++ b/crates/chain-config/src/config/coin.rs @@ -250,7 +250,7 @@ impl CoinConfig { } #[cfg(feature = "test-helpers")] -impl crate::Randomize for CoinConfig { +impl crate::Randomize for ConfigCoin { fn randomize(mut rng: impl ::rand::Rng) -> Self { ConfigCoin { tx_id: crate::Randomize::randomize(&mut rng), @@ -261,7 +261,13 @@ impl crate::Randomize for CoinConfig { amount: rng.gen(), asset_id: crate::Randomize::randomize(&mut rng), } - .into() + } +} + +#[cfg(feature = "test-helpers")] +impl crate::Randomize for CoinConfig { + fn randomize(rng: impl ::rand::Rng) -> Self { + ConfigCoin::randomize(rng).into() } } @@ -395,7 +401,7 @@ mod tests { let config1 = generator.generate(); let config2 = generator.generate(); - assert_ne!(config1.utxo_id(), config2.utxo_id()); + assert_ne!(config1.tx_id, config2.tx_id); } #[test] @@ -407,7 +413,7 @@ mod tests { let mut generator = coin_config_helpers::CoinConfigGenerator::new(); let config = generator.generate_with(secret, amount); - assert_eq!(config.owner, Address::from(*secret.public_key().hash())); - assert_eq!(config.amount, amount); + assert_eq!(config.owner(), Address::from(*secret.public_key().hash())); + assert_eq!(config.amount(), amount); } } diff --git a/crates/fuel-core/src/graphql_api/storage/coins/codecs.rs b/crates/fuel-core/src/graphql_api/storage/coins/codecs.rs index 56511dc2544..c841a053cda 100644 --- a/crates/fuel-core/src/graphql_api/storage/coins/codecs.rs +++ b/crates/fuel-core/src/graphql_api/storage/coins/codecs.rs @@ -140,14 +140,14 @@ impl Encode for Manual { let retryable_flag_bytes = NON_RETRYABLE_BYTE; let mut serialized_coin = - Vec::with_capacity(COIN_VARIANT_SIZE + data.len()); + Vec::with_capacity(COIN_VARIANT_SIZE.saturating_add(data.len())); // TODO: Check that this works! serialized_coin.push(retryable_flag_bytes[0]); serialized_coin.extend_from_slice(owner.as_ref()); serialized_coin.extend_from_slice(asset_id.as_ref()); serialized_coin.extend_from_slice(&amount.to_be_bytes()); serialized_coin.extend_from_slice(&utxo_id_to_bytes(utxo_id)); - serialized_coin.extend_from_slice(&data); + serialized_coin.extend_from_slice(data); serialized_coin.push(CoinType::DataCoin as u8); SerializedCoinsToSpendIndexKey::DataCoin(serialized_coin) @@ -207,20 +207,23 @@ impl Decode for Manual { end = end.saturating_add(AMOUNT_SIZE); let amount = u64::from_be_bytes(bytes[start..end].try_into()?); start = end; - end = bytes.len() - 1; // Exclude the last byte which is coin type - - let utxo_id_bytes = &bytes[start..end]; - let (tx_id_bytes, output_index_bytes) = utxo_id_bytes.split_at(TxId::LEN); + end = start.saturating_add(TxId::LEN); + let tx_id_bytes = &bytes[start..end]; let tx_id = TxId::try_from(tx_id_bytes)?; + start = end; + end = start.saturating_add(UTXO_ID_SIZE); + let output_index_bytes = &bytes[start..end]; let output_index = u16::from_be_bytes(output_index_bytes.try_into()?); let utxo_id = UtxoId::new(tx_id, output_index); + start = end; + let data = bytes[start..].to_vec(); CoinsToSpendIndexKey::DataCoin { owner, asset_id, amount, utxo_id, - data: bytes[end..].to_vec(), + data, } } CoinType::Message => { diff --git a/crates/fuel-core/src/service/genesis.rs b/crates/fuel-core/src/service/genesis.rs index a0210faa0c9..ad60fcfb797 100644 --- a/crates/fuel-core/src/service/genesis.rs +++ b/crates/fuel-core/src/service/genesis.rs @@ -61,10 +61,6 @@ use fuel_core_types::{ use itertools::Itertools; pub use exporter::Exporter; -use fuel_core_storage::{ - iter::IterDirection, - tables::Coins, -}; pub use task_manager::NotifyCancel; mod exporter; @@ -327,7 +323,7 @@ mod tests { }; use fuel_core_chain_config::{ BlobConfig, - CoinConfig, + ConfigCoin, ContractConfig, LastBlockConfig, MessageConfig, @@ -399,9 +395,12 @@ mod tests { async fn genesis_columns_are_cleared_after_import() { let mut rng = StdRng::seed_from_u64(10); - let coins = std::iter::repeat_with(|| CoinConfig { - tx_pointer_block_height: 0.into(), - ..Randomize::randomize(&mut rng) + let coins = std::iter::repeat_with(|| { + ConfigCoin { + tx_pointer_block_height: 0.into(), + ..Randomize::randomize(&mut rng) + } + .into() }) .take(1000) .collect_vec(); @@ -471,7 +470,7 @@ mod tests { }; let state = StateConfig { coins: vec![ - CoinConfig { + ConfigCoin { tx_id: alice_tx_id, output_index: alice_output_index, tx_pointer_block_height: alice_block_created, @@ -479,13 +478,15 @@ mod tests { owner: alice, amount: alice_value, asset_id: asset_id_alice, - }, - CoinConfig { + } + .into(), + ConfigCoin { owner: bob, amount: bob_value, asset_id: asset_id_bob, ..Default::default() - }, + } + .into(), ], last_block: Some(LastBlockConfig { block_height: starting_height, @@ -638,12 +639,13 @@ mod tests { #[tokio::test] async fn coin_tx_pointer_cant_exceed_genesis_height() { let state = StateConfig { - coins: vec![CoinConfig { + coins: vec![ConfigCoin { // set txpointer height > genesis height tx_pointer_block_height: BlockHeight::from(11u32), amount: 10, ..Default::default() - }], + } + .into()], last_block: Some(LastBlockConfig { block_height: BlockHeight::from(9u32), state_transition_version: 0, diff --git a/crates/fuel-core/src/service/sub_services.rs b/crates/fuel-core/src/service/sub_services.rs index 5c5267a6b7c..980b96069c1 100644 --- a/crates/fuel-core/src/service/sub_services.rs +++ b/crates/fuel-core/src/service/sub_services.rs @@ -26,13 +26,6 @@ use fuel_core_types::signer::SignMode; #[cfg(feature = "relayer")] use crate::relayer::Config as RelayerConfig; use fuel_core_compression_service::service::new_service as new_compression_service; -use fuel_core_storage::{ - iter::{ - IterDirection, - IteratorOverTable, - }, - tables::Coins, -}; #[cfg(feature = "p2p")] use crate::service::adapters::consensus_module::poa::pre_confirmation_signature::{ diff --git a/crates/services/txpool_v2/src/tests/universe.rs b/crates/services/txpool_v2/src/tests/universe.rs index 421fccc4231..53ebff462b5 100644 --- a/crates/services/txpool_v2/src/tests/universe.rs +++ b/crates/services/txpool_v2/src/tests/universe.rs @@ -416,7 +416,7 @@ impl TestPoolUniverse { .unwrap() .coins .insert(utxo_id, coin.clone()); - (coin.uncompress_coin(utxo_id), input) + (coin.uncompress_coin(utxo_id).unwrap(), input) } pub fn create_output_and_input(&mut self) -> (Output, UnsetInput) { diff --git a/crates/types/src/entities/coins.rs b/crates/types/src/entities/coins.rs index 3a968179726..6865ae18fa5 100644 --- a/crates/types/src/entities/coins.rs +++ b/crates/types/src/entities/coins.rs @@ -60,7 +60,7 @@ impl CoinType { /// Returns the owner of the coin. pub fn owner(&self) -> &Address { match self { - CoinType::Coin(coin) => &coin.owner(), + CoinType::Coin(coin) => coin.owner(), CoinType::MessageCoin(coin) => &coin.recipient, } } diff --git a/crates/types/src/entities/coins/coin.rs b/crates/types/src/entities/coins/coin.rs index f3d6e213efc..a09abe23b79 100644 --- a/crates/types/src/entities/coins/coin.rs +++ b/crates/types/src/entities/coins/coin.rs @@ -342,7 +342,7 @@ impl CompressedCoin { owner == &coin.owner && amount == &coin.amount && asset_id == &coin.asset_id - && data == data, + && data == &coin.data, ), _ => { tracing::debug!( diff --git a/tests/tests/tx_gossip.rs b/tests/tests/tx_gossip.rs index 1c11d180ff5..6f452ac3165 100644 --- a/tests/tests/tx_gossip.rs +++ b/tests/tests/tx_gossip.rs @@ -172,9 +172,9 @@ async fn test_tx_gossiping_invalid_txs( let invalid_tx = TransactionBuilder::script(vec![], vec![]) .add_input(Input::CoinSigned(CoinSigned { utxo_id: coin.utxo_id(), - owner: coin.owner, - amount: coin.amount, - asset_id: coin.asset_id, + owner: coin.owner(), + amount: coin.amount(), + asset_id: coin.asset_id(), tx_pointer: Default::default(), witness_index: 0, predicate_gas_used: Empty::new(), From 9074c108b9be19d1e243d393c0d7eab133d1f064 Mon Sep 17 00:00:00 2001 From: Mitch Turner Date: Mon, 31 Mar 2025 22:46:41 -0600 Subject: [PATCH 28/37] Add GWT to test --- tests/tests/tx/predicates.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/tests/tx/predicates.rs b/tests/tests/tx/predicates.rs index b3710827e8d..195414a7d8f 100644 --- a/tests/tests/tx/predicates.rs +++ b/tests/tests/tx/predicates.rs @@ -285,6 +285,7 @@ async fn submit__tx_with_predicate_can_check_data_coin() { assert_ne!(predicate_tx.inputs()[0].predicate_gas_used().unwrap(), 0); + // when let predicate_tx = predicate_tx.into(); context .client @@ -292,7 +293,7 @@ async fn submit__tx_with_predicate_can_check_data_coin() { .await .unwrap(); - // check transaction change amount to see if predicate was spent + // then let transaction: Transaction = context .client .transaction(&predicate_tx.id(&ChainId::default())) From afc99067b41465450ab83ac40fe4d7d398f31d6b Mon Sep 17 00:00:00 2001 From: Mitch Turner Date: Mon, 31 Mar 2025 23:10:51 -0600 Subject: [PATCH 29/37] add new test --- crates/fuel-core/src/executor.rs | 101 ++++++++++++++++++++++++++++++- 1 file changed, 99 insertions(+), 2 deletions(-) diff --git a/crates/fuel-core/src/executor.rs b/crates/fuel-core/src/executor.rs index 568eadec3f3..0cc0311d483 100644 --- a/crates/fuel-core/src/executor.rs +++ b/crates/fuel-core/src/executor.rs @@ -3097,6 +3097,105 @@ mod tests { assert!(result.is_ok(), "{result:?}") } + #[test] + fn validate__predicate_fails_if_data_wrong() { + let mut rng = StdRng::seed_from_u64(2322u64); + + // given + let predicate: Vec = + predicate_checking_predicate_data_matches_input_data_coin(); + let owner = Input::predicate_owner(&predicate); + let amount = 1000; + + let consensus_parameters = ConsensusParameters::default(); + let config = Config { + forbid_fake_coins_default: true, + consensus_parameters: consensus_parameters.clone(), + }; + let data = vec![99u8; 100]; + let predicate_data = data + .clone() + .iter() + .map(|x| x.wrapping_add(1)) + .collect::>(); + + let mut tx = TransactionBuilder::script( + vec![op::ret(RegId::ONE)].into_iter().collect(), + vec![], + ) + .max_fee_limit(amount) + .add_input(Input::data_coin_predicate( + rng.gen(), + owner, + amount, + AssetId::BASE, + rng.gen(), + 0, + predicate, + predicate_data, + data.clone(), + )) + .add_output(Output::Change { + to: Default::default(), + amount: 0, + asset_id: AssetId::BASE, + }) + .finalize(); + tx.estimate_predicates( + &consensus_parameters.clone().into(), + MemoryInstance::new(), + &EmptyStorage, + ) + .unwrap(); + let db = &mut Database::default(); + + // insert coin into state + if let Input::DataCoinPredicate(DataCoinPredicate { + utxo_id, + owner, + amount, + asset_id, + tx_pointer, + .. + }) = tx.inputs()[0] + { + let coin = CompressedCoin::V2(CompressedCoinV2 { + owner, + amount, + asset_id, + tx_pointer, + data, + }); + db.storage::().insert(&utxo_id, &coin).unwrap(); + } else { + panic!("Expected a DataCoinPredicate"); + } + + let producer = create_executor(db.clone(), config.clone()); + + // when + let ExecutionResult { + block, + skipped_transactions, + .. + } = producer + .produce_without_commit_with_source_direct_resolve(Components { + header_to_produce: PartialBlockHeader::default(), + transactions_source: OnceTransactionsSource::new(vec![tx.into()]), + coinbase_recipient: Default::default(), + gas_price: 1, + }) + .unwrap() + .into_result(); + + // then + assert_eq!(skipped_transactions.len(), 1); + + let validator = create_executor(db.clone(), config); + let result = validator.validate(&block); + assert!(result.is_ok(), "{result:?}") + } + #[test] fn validate__predicate_fails_if_data_less_coin() { let mut rng = StdRng::seed_from_u64(2322u64); @@ -3172,7 +3271,6 @@ mod tests { let ExecutionResult { block, skipped_transactions, - tx_status, .. } = producer .produce_without_commit_with_source_direct_resolve(Components { @@ -3185,7 +3283,6 @@ mod tests { .into_result(); // then assert_eq!(skipped_transactions.len(), 1); - assert_eq!(tx_status.len(), 1); let validator = create_executor(db.clone(), config); let result = validator.validate(&block); From 5aa12e47a6afcfc5d5a216eefd3dbc5702b07311 Mon Sep 17 00:00:00 2001 From: Mitch Turner Date: Mon, 31 Mar 2025 23:48:59 -0600 Subject: [PATCH 30/37] fix tests --- benches/src/bin/collect.rs | 3 +- crates/chain-config/src/config/coin.rs | 68 ++++++++----------- ...ests__json_snapshot_is_human_readable.snap | 32 +++++---- 3 files changed, 49 insertions(+), 54 deletions(-) diff --git a/benches/src/bin/collect.rs b/benches/src/bin/collect.rs index 4fb2a697eb8..a1908271768 100644 --- a/benches/src/bin/collect.rs +++ b/benches/src/bin/collect.rs @@ -886,7 +886,8 @@ mod tests { .unwrap(); println!("{}", String::from_utf8(output.stderr).unwrap()); - assert!(output.status.success()); + panic!("{:?}", output.status); + // assert!(output.status.success()); } #[test] diff --git a/crates/chain-config/src/config/coin.rs b/crates/chain-config/src/config/coin.rs index b4dc736831d..da227b7b747 100644 --- a/crates/chain-config/src/config/coin.rs +++ b/crates/chain-config/src/config/coin.rs @@ -162,18 +162,33 @@ impl From for CoinConfig { } impl From> for CoinConfig { - fn from(_value: TableEntry) -> Self { - // ConfigCoin { - // tx_id: *value.key.tx_id(), - // output_index: value.key.output_index(), - // tx_pointer_block_height: value.value.tx_pointer().block_height(), - // tx_pointer_tx_idx: value.value.tx_pointer().tx_index(), - // owner: *value.value.owner(), - // amount: *value.value.amount(), - // asset_id: *value.value.asset_id(), - // } - // .into() - todo!() + fn from(value: TableEntry) -> Self { + match value.value { + CompressedCoin::V1(coin) => CoinConfig::Coin(ConfigCoin { + tx_id: *value.key.tx_id(), + output_index: value.key.output_index(), + tx_pointer_block_height: coin.tx_pointer.block_height(), + tx_pointer_tx_idx: coin.tx_pointer.tx_index(), + owner: coin.owner, + amount: coin.amount, + asset_id: coin.asset_id, + }), + CompressedCoin::V2(data_coin) => { + CoinConfig::DataCoin(ConfigDataCoin { + tx_id: *value.key.tx_id(), + output_index: value.key.output_index(), + tx_pointer_block_height: data_coin.tx_pointer.block_height(), + tx_pointer_tx_idx: data_coin.tx_pointer.tx_index(), + owner: data_coin.owner, + amount: data_coin.amount, + asset_id: data_coin.asset_id, + data: data_coin.data.clone(), // Clone the data for the new struct + }) + } + _ => { + unreachable!("Covered both variants") + } + } } } @@ -181,14 +196,6 @@ impl From for TableEntry { fn from(config: CoinConfig) -> Self { let entry = match config { CoinConfig::Coin(config) => { - tracing::debug!( - "Creating TableEntry for CoinConfig: tx_id={:?}, output_index={}, owner={:?}, amount={}, asset_id={:?}", - config.tx_id, - config.output_index, - config.owner, - config.amount, - config.asset_id - ); let value = CompressedCoin::V1(CompressedCoinV1 { owner: config.owner, amount: config.amount, @@ -204,15 +211,6 @@ impl From for TableEntry { } } CoinConfig::DataCoin(config) => { - tracing::debug!( - "Creating TableEntry for DataCoinConfig: tx_id={:?}, output_index={}, owner={:?}, amount={}, asset_id={:?}, data_len={}", - config.tx_id, - config.output_index, - config.owner, - config.amount, - config.asset_id, - config.data.len() - ); let value = CompressedCoin::V2(CompressedCoinV2 { owner: config.owner, amount: config.amount, @@ -357,11 +355,7 @@ pub mod coin_config_helpers { let config = ConfigCoin { tx_id, output_index: self.count, - tx_pointer_block_height: Default::default(), - tx_pointer_tx_idx: Default::default(), - owner: Default::default(), - amount: Default::default(), - asset_id: Default::default(), + ..Default::default() }; self.count = self.count.checked_add(1).expect("Max coin count reached"); @@ -373,13 +367,9 @@ pub mod coin_config_helpers { let owner = Address::from(*secret.public_key().hash()); ConfigCoin { - tx_id: Default::default(), - output_index: Default::default(), - tx_pointer_block_height: Default::default(), amount, owner, - tx_pointer_tx_idx: Default::default(), - asset_id: Default::default(), + ..self.generate() } .into() } diff --git a/crates/chain-config/src/config/state/snapshots/fuel_core_chain_config__config__state__writer__tests__json_snapshot_is_human_readable.snap b/crates/chain-config/src/config/state/snapshots/fuel_core_chain_config__config__state__writer__tests__json_snapshot_is_human_readable.snap index 5199d07a9cd..2e2df47b0d9 100644 --- a/crates/chain-config/src/config/state/snapshots/fuel_core_chain_config__config__state__writer__tests__json_snapshot_is_human_readable.snap +++ b/crates/chain-config/src/config/state/snapshots/fuel_core_chain_config__config__state__writer__tests__json_snapshot_is_human_readable.snap @@ -5,22 +5,26 @@ expression: encoded_json { "coins": [ { - "tx_id": "9b07815f04497e2e05d22cac3aa061410b20868cc619154c42a1c61be9902717", - "output_index": 3250, - "tx_pointer_block_height": 1405935179, - "tx_pointer_tx_idx": 35726, - "owner": "c168fe4896f5b70ccb10f1a705e061d0fdfa189618d28b0d44efef92cbf44b58", - "amount": 16206947779870322207, - "asset_id": "222c10916b17a369b4da039d075952b58036f2a7b561446592403c672f0dbe12" + "Coin": { + "tx_id": "9b07815f04497e2e05d22cac3aa061410b20868cc619154c42a1c61be9902717", + "output_index": 3250, + "tx_pointer_block_height": 1405935179, + "tx_pointer_tx_idx": 35726, + "owner": "c168fe4896f5b70ccb10f1a705e061d0fdfa189618d28b0d44efef92cbf44b58", + "amount": 16206947779870322207, + "asset_id": "222c10916b17a369b4da039d075952b58036f2a7b561446592403c672f0dbe12" + } }, { - "tx_id": "9f1e39113f9f6164ea2867f8deccf3c3c518f146de9554f03f27a83d5b1185f3", - "output_index": 10142, - "tx_pointer_block_height": 476225137, - "tx_pointer_tx_idx": 52207, - "owner": "357e2bd9e13962516b0a41403276437b25f0ffbe897cf476c0777e9d246ff397", - "amount": 901131989421222986, - "asset_id": "f1b350aa16c42ea0d7117b30c8c8436ce7a9254b30d253e4567ccafa5f36ce84" + "Coin": { + "tx_id": "9f1e39113f9f6164ea2867f8deccf3c3c518f146de9554f03f27a83d5b1185f3", + "output_index": 10142, + "tx_pointer_block_height": 476225137, + "tx_pointer_tx_idx": 52207, + "owner": "357e2bd9e13962516b0a41403276437b25f0ffbe897cf476c0777e9d246ff397", + "amount": 901131989421222986, + "asset_id": "f1b350aa16c42ea0d7117b30c8c8436ce7a9254b30d253e4567ccafa5f36ce84" + } } ], "messages": [ From d568ae953fb231f2a3948ea7f07c9ecd5396ea9b Mon Sep 17 00:00:00 2001 From: Mitch Turner Date: Mon, 31 Mar 2025 23:51:16 -0600 Subject: [PATCH 31/37] Remove panic in benches --- benches/src/bin/collect.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/benches/src/bin/collect.rs b/benches/src/bin/collect.rs index a1908271768..4fb2a697eb8 100644 --- a/benches/src/bin/collect.rs +++ b/benches/src/bin/collect.rs @@ -886,8 +886,7 @@ mod tests { .unwrap(); println!("{}", String::from_utf8(output.stderr).unwrap()); - panic!("{:?}", output.status); - // assert!(output.status.success()); + assert!(output.status.success()); } #[test] From d6d09b41c7782a3c75307a32752c200a709e1d24 Mon Sep 17 00:00:00 2001 From: Mitch Turner Date: Thu, 3 Apr 2025 13:54:21 -0600 Subject: [PATCH 32/37] Add test for outputs to executor --- crates/fuel-core/src/executor.rs | 330 +++++++++++++++++++++++++++++++ 1 file changed, 330 insertions(+) diff --git a/crates/fuel-core/src/executor.rs b/crates/fuel-core/src/executor.rs index 0cc0311d483..e6965e1c606 100644 --- a/crates/fuel-core/src/executor.rs +++ b/crates/fuel-core/src/executor.rs @@ -3002,6 +3002,33 @@ mod tests { .into_iter() .collect() } + + fn predicates_checking_input_data_coin_data_matches_output_data_coin_data() -> Vec + { + let len_reg = 0x13; + let res_reg = 0x10; + let input_index = 0; + let output_index = 0; + let output_data_location = 0x22; + let input_data_location = 0x23; + vec![ + op::gtf_args(len_reg, input_index, GTFArgs::InputDataCoinDataLength), + // get output data + op::gtf_args( + output_data_location, + output_index, + GTFArgs::OutputDataCoinData, + ), + // get actual data + op::gtf_args(input_data_location, input_index, GTFArgs::InputDataCoinData), + // compare + op::meq(res_reg, output_data_location, input_data_location, len_reg), + op::ret(res_reg), + ] + .into_iter() + .collect() + } + #[test] fn validate__predicate_can_find_data_coin_data() { let mut rng = StdRng::seed_from_u64(2322u64); @@ -3289,6 +3316,309 @@ mod tests { assert!(result.is_ok(), "{result:?}") } + #[test] + fn validate__predicate_succeeds_if_input_data_matches_output_data() { + let mut rng = StdRng::seed_from_u64(2322u64); + + // given + let predicate: Vec = + predicates_checking_input_data_coin_data_matches_output_data_coin_data(); + let owner = Input::predicate_owner(&predicate); + let amount = 1000; + + let consensus_parameters = ConsensusParameters::default(); + let config = Config { + forbid_fake_coins_default: true, + consensus_parameters: consensus_parameters.clone(), + }; + let data = vec![99u8; 100]; + let predicate_data = data.clone(); + + let mut tx = TransactionBuilder::script( + vec![op::ret(RegId::ONE)].into_iter().collect(), + vec![], + ) + .max_fee_limit(amount) + .add_input(Input::data_coin_predicate( + rng.gen(), + owner, + amount, + AssetId::BASE, + rng.gen(), + 0, + predicate, + predicate_data, + data.clone(), + )) + .add_output(Output::DataCoin { + to: Default::default(), + amount: 100, + asset_id: AssetId::BASE, + data: data.clone(), + }) + .add_output(Output::Change { + to: Default::default(), + amount: 0, + asset_id: AssetId::BASE, + }) + .finalize(); + tx.estimate_predicates( + &consensus_parameters.clone().into(), + MemoryInstance::new(), + &EmptyStorage, + ) + .unwrap(); + let db = &mut Database::default(); + + // insert coin into state + if let Input::DataCoinPredicate(DataCoinPredicate { + utxo_id, + owner, + amount, + asset_id, + tx_pointer, + .. + }) = tx.inputs()[0] + { + let coin = CompressedCoin::V2(CompressedCoinV2 { + owner, + amount, + asset_id, + tx_pointer, + data, + }); + db.storage::().insert(&utxo_id, &coin).unwrap(); + } else { + panic!("Expected a DataCoinPredicate"); + } + + let producer = create_executor(db.clone(), config.clone()); + + // when + let ExecutionResult { + block, + skipped_transactions, + .. + } = producer + .produce_without_commit_with_source_direct_resolve(Components { + header_to_produce: PartialBlockHeader::default(), + transactions_source: OnceTransactionsSource::new(vec![tx.into()]), + coinbase_recipient: Default::default(), + gas_price: 1, + }) + .unwrap() + .into_result(); + + // then + assert!(skipped_transactions.is_empty()); + + let validator = create_executor(db.clone(), config); + let result = validator.validate(&block); + assert!(result.is_ok(), "{result:?}") + } + + #[test] + fn validate__predicate_fails_if_input_data_does_not_match_output_data() { + let mut rng = StdRng::seed_from_u64(2322u64); + + // given + let predicate: Vec = + predicates_checking_input_data_coin_data_matches_output_data_coin_data(); + let owner = Input::predicate_owner(&predicate); + let amount = 1000; + + let consensus_parameters = ConsensusParameters::default(); + let config = Config { + forbid_fake_coins_default: true, + consensus_parameters: consensus_parameters.clone(), + }; + let input_data = vec![99u8; 100]; + let output_data = vec![100u8; 100]; + let predicate_data = vec![1, 2, 3, 4, 5]; + + let mut tx = TransactionBuilder::script( + vec![op::ret(RegId::ONE)].into_iter().collect(), + vec![], + ) + .max_fee_limit(amount) + .add_input(Input::data_coin_predicate( + rng.gen(), + owner, + amount, + AssetId::BASE, + rng.gen(), + 0, + predicate, + predicate_data, + input_data.clone(), + )) + .add_output(Output::DataCoin { + to: Default::default(), + amount: 100, + asset_id: AssetId::BASE, + data: output_data, + }) + .add_output(Output::Change { + to: Default::default(), + amount: 0, + asset_id: AssetId::BASE, + }) + .finalize(); + tx.estimate_predicates( + &consensus_parameters.clone().into(), + MemoryInstance::new(), + &EmptyStorage, + ) + .unwrap(); + let db = &mut Database::default(); + + // insert coin into state + if let Input::DataCoinPredicate(DataCoinPredicate { + utxo_id, + owner, + amount, + asset_id, + tx_pointer, + .. + }) = tx.inputs()[0] + { + let coin = CompressedCoin::V2(CompressedCoinV2 { + owner, + amount, + asset_id, + tx_pointer, + data: input_data, + }); + db.storage::().insert(&utxo_id, &coin).unwrap(); + } else { + panic!("Expected a DataCoinPredicate"); + } + + let producer = create_executor(db.clone(), config.clone()); + + // when + let ExecutionResult { + block, + skipped_transactions, + .. + } = producer + .produce_without_commit_with_source_direct_resolve(Components { + header_to_produce: PartialBlockHeader::default(), + transactions_source: OnceTransactionsSource::new(vec![tx.into()]), + coinbase_recipient: Default::default(), + gas_price: 1, + }) + .unwrap() + .into_result(); + + // then + assert_eq!(skipped_transactions.len(), 1); + + let validator = create_executor(db.clone(), config); + let result = validator.validate(&block); + assert!(result.is_ok(), "{result:?}") + } + + #[test] + fn validate__predicate_fails_if_output_coin_not_data_coin() { + let mut rng = StdRng::seed_from_u64(2322u64); + + // given + let predicate: Vec = + predicates_checking_input_data_coin_data_matches_output_data_coin_data(); + let owner = Input::predicate_owner(&predicate); + let amount = 1000; + + let consensus_parameters = ConsensusParameters::default(); + let config = Config { + forbid_fake_coins_default: true, + consensus_parameters: consensus_parameters.clone(), + }; + let input_data = vec![99u8; 100]; + let predicate_data = vec![1, 2, 3, 4, 5]; + + let mut tx = TransactionBuilder::script( + vec![op::ret(RegId::ONE)].into_iter().collect(), + vec![], + ) + .max_fee_limit(amount) + .add_input(Input::data_coin_predicate( + rng.gen(), + owner, + amount, + AssetId::BASE, + rng.gen(), + 0, + predicate, + predicate_data, + input_data.clone(), + )) + .add_output(Output::Coin { + to: Default::default(), + amount: 100, + asset_id: AssetId::BASE, + }) + .add_output(Output::Change { + to: Default::default(), + amount: 0, + asset_id: AssetId::BASE, + }) + .finalize(); + tx.estimate_predicates( + &consensus_parameters.clone().into(), + MemoryInstance::new(), + &EmptyStorage, + ) + .unwrap(); + let db = &mut Database::default(); + + // insert coin into state + if let Input::DataCoinPredicate(DataCoinPredicate { + utxo_id, + owner, + amount, + asset_id, + tx_pointer, + .. + }) = tx.inputs()[0] + { + let coin = CompressedCoin::V2(CompressedCoinV2 { + owner, + amount, + asset_id, + tx_pointer, + data: input_data, + }); + db.storage::().insert(&utxo_id, &coin).unwrap(); + } else { + panic!("Expected a DataCoinPredicate"); + } + + let producer = create_executor(db.clone(), config.clone()); + + // when + let ExecutionResult { + block, + skipped_transactions, + .. + } = producer + .produce_without_commit_with_source_direct_resolve(Components { + header_to_produce: PartialBlockHeader::default(), + transactions_source: OnceTransactionsSource::new(vec![tx.into()]), + coinbase_recipient: Default::default(), + gas_price: 1, + }) + .unwrap() + .into_result(); + + // then + assert_eq!(skipped_transactions.len(), 1); + + let validator = create_executor(db.clone(), config); + let result = validator.validate(&block); + assert!(result.is_ok(), "{result:?}") + } + #[test] fn verifying_during_production_consensus_parameters_version_works() { let mut rng = StdRng::seed_from_u64(2322u64); From 2f96a614bd732c0b818d9d25bef14d21c8e600f9 Mon Sep 17 00:00:00 2001 From: Mitch Turner Date: Thu, 3 Apr 2025 15:22:11 -0600 Subject: [PATCH 33/37] Add test that checks data outputs --- crates/services/executor/src/executor.rs | 60 ++++---- tests/tests/tx/predicates.rs | 166 +++++++++++++++++++++++ 2 files changed, 194 insertions(+), 32 deletions(-) diff --git a/crates/services/executor/src/executor.rs b/crates/services/executor/src/executor.rs index 488845fed04..42169b932a8 100644 --- a/crates/services/executor/src/executor.rs +++ b/crates/services/executor/src/executor.rs @@ -1,13 +1,21 @@ -use crate::{ - ports::{ - MaybeCheckedTransaction, - NewTxWaiterPort, - PreconfirmationSenderPort, - RelayerPort, - TransactionsSource, - }, - refs::ContractRef, +#[cfg(not(feature = "std"))] +use alloc::borrow::Cow; +#[cfg(feature = "alloc")] +use alloc::{ + format, + string::ToString, + vec, + vec::Vec, }; +#[cfg(feature = "std")] +use std::borrow::Cow; + +use parking_lot::Mutex as ParkingMutex; +use tracing::{ + debug, + warn, +}; + use fuel_core_storage::{ column::Column, kv_store::KeyValueInspect, @@ -49,6 +57,7 @@ use fuel_core_types::{ coins::coin::{ CompressedCoin, CompressedCoinV1, + UncompressedCoin, }, contract::ContractUtxoInfo, RelayedTransaction, @@ -74,6 +83,8 @@ use fuel_core_types::{ coin::{ CoinPredicate, CoinSigned, + DataCoinPredicate, + DataCoinSigned, }, message::{ MessageCoinPredicate, @@ -150,31 +161,16 @@ use fuel_core_types::{ relayer::Event, }, }; -use parking_lot::Mutex as ParkingMutex; -use tracing::{ - debug, - warn, -}; - -#[cfg(feature = "std")] -use std::borrow::Cow; -#[cfg(not(feature = "std"))] -use alloc::borrow::Cow; - -#[cfg(feature = "alloc")] -use alloc::{ - format, - string::ToString, - vec, - vec::Vec, -}; -use fuel_core_types::{ - entities::coins::coin::UncompressedCoin, - fuel_tx::input::coin::{ - DataCoinPredicate, - DataCoinSigned, +use crate::{ + ports::{ + MaybeCheckedTransaction, + NewTxWaiterPort, + PreconfirmationSenderPort, + RelayerPort, + TransactionsSource, }, + refs::ContractRef, }; /// The maximum amount of transactions that can be included in a block, diff --git a/tests/tests/tx/predicates.rs b/tests/tests/tx/predicates.rs index 195414a7d8f..a882458d52f 100644 --- a/tests/tests/tx/predicates.rs +++ b/tests/tests/tx/predicates.rs @@ -214,6 +214,50 @@ fn predicate_checking_predicate_data_matches_input_data_coin() -> Vec { .collect() } +fn predicate_checking_output_data_matches_input_data_coin() -> Vec { + // let output_len_reg = 0x13; + // let input_len_reg = 0x14; + // let res_reg = 0x10; + // let lengths_match_reg = 0x11; + // let data_match_reg = 0x12; + // let input_index = 0; + // let output_index = 0; + // let output_data_mem_location = 0x22; + // let input_data_mem_location = 0x23; + // vec![ + // op::gtf_args(input_len_reg, input_index, GTFArgs::InputDataCoinDataLength), + // op::gtf_args( + // output_len_reg, + // output_index, + // GTFArgs::OutputDataCoinDataLength, + // ), + // // get expected data from predicate data + // op::gtf_args( + // output_data_mem_location, + // output_index, + // GTFArgs::OutputDataCoinData, + // ), + // // get actual data + // op::gtf_args( + // input_data_mem_location, + // input_index, + // GTFArgs::InputDataCoinData, + // ), + // // compare + // op::eq(lengths_match_reg, input_len_reg, output_len_reg), + // op::meq( + // data_match_reg, + // output_data_mem_location, + // input_data_mem_location, + // output_len_reg, + // ), + // op::and(res_reg, lengths_match_reg, data_match_reg), + // op::ret(res_reg), + // ] + // .into_iter() + // .collect() + vec![op::ret(0x01)].into_iter().collect() +} #[tokio::test] async fn submit__tx_with_predicate_can_check_data_coin() { let _ = tracing_subscriber::fmt() @@ -308,3 +352,125 @@ async fn submit__tx_with_predicate_can_check_data_coin() { matches!(transaction.as_script().unwrap().outputs()[0], Output::Change { amount: change_amount, .. } if change_amount == amount) ) } + +#[tokio::test] +async fn submit__tx_with_predicate_can_check_input_and_output_data_coins() { + let _ = tracing_subscriber::fmt() + .with_max_level(tracing::Level::DEBUG) + .try_init(); + let mut rng = StdRng::seed_from_u64(2322); + + // given + let input_amount = 500; + let output_amount = 200; + let change_amount = input_amount - output_amount; + let limit = 1000; + let asset_id = rng.gen(); + let predicate = predicate_checking_output_data_matches_input_data_coin(); + let coin_data = vec![123; 100]; + let predicate_data = vec![1, 2, 3, 4, 5]; + let owner = Input::predicate_owner(&predicate); + let mut predicate_tx = + TransactionBuilder::script(Default::default(), Default::default()) + .add_input(Input::data_coin_predicate( + rng.gen(), + owner, + input_amount, + asset_id, + Default::default(), + Default::default(), + predicate, + predicate_data, + coin_data.clone(), + )) + .add_output(Output::data_coin( + rng.gen(), + output_amount, + asset_id, + coin_data, + )) + .add_output(Output::change(rng.gen(), 0, asset_id)) + .script_gas_limit(limit) + .finalize(); + + // create test context with predicates disabled + let context = TestSetupBuilder::default() + .config_coin_inputs_from_transactions(&[&predicate_tx]) + .finalize() + .await; + + use fuel_core_storage::StorageAsRef; + + let coin = context + .srv + .shared + .database + .on_chain() + .storage::() + .get(&predicate_tx.inputs()[0].utxo_id().unwrap()) + .expect("Failed to get coin from db"); + + tracing::debug!("zzzzzz {:?}", coin); + + assert_eq!(predicate_tx.inputs()[0].predicate_gas_used().unwrap(), 0); + + predicate_tx + .estimate_predicates( + &CheckPredicateParams::from( + &context + .srv + .shared + .config + .snapshot_reader + .chain_config() + .consensus_parameters, + ), + MemoryInstance::new(), + &EmptyStorage, + ) + .expect("Predicate check failed"); + + assert_ne!(predicate_tx.inputs()[0].predicate_gas_used().unwrap(), 0); + + // when + let predicate_tx = predicate_tx.into(); + context + .client + .submit_and_await_commit(&predicate_tx) + .await + .unwrap(); + + // then + let transaction: Transaction = context + .client + .transaction(&predicate_tx.id(&ChainId::default())) + .await + .unwrap() + .unwrap() + .transaction + .try_into() + .unwrap(); + + if let Output::DataCoin { amount, .. } = transaction.as_script().unwrap().outputs()[0] + { + assert!( + amount == output_amount, + "Expected output amount to be {}, but got {}", + amount, + output_amount + ); + } else { + panic!("Expected output data coin"); + } + + if let Output::Change { amount, .. } = transaction.as_script().unwrap().outputs()[1] { + assert!( + amount == change_amount, + "Expected change amount to be {}, but got {}", + change_amount, + amount + ); + } else { + panic!("Expected change output"); + } +} From cefd63398ed59dbe25d1c16bf07dbad646ea591d Mon Sep 17 00:00:00 2001 From: Mitch Turner Date: Fri, 4 Apr 2025 11:55:45 -0600 Subject: [PATCH 34/37] Switch on actual predicate code --- tests/tests/tx/predicates.rs | 83 ++++++++++++++++++------------------ 1 file changed, 41 insertions(+), 42 deletions(-) diff --git a/tests/tests/tx/predicates.rs b/tests/tests/tx/predicates.rs index a882458d52f..0d5f9f4dffa 100644 --- a/tests/tests/tx/predicates.rs +++ b/tests/tests/tx/predicates.rs @@ -215,48 +215,47 @@ fn predicate_checking_predicate_data_matches_input_data_coin() -> Vec { } fn predicate_checking_output_data_matches_input_data_coin() -> Vec { - // let output_len_reg = 0x13; - // let input_len_reg = 0x14; - // let res_reg = 0x10; - // let lengths_match_reg = 0x11; - // let data_match_reg = 0x12; - // let input_index = 0; - // let output_index = 0; - // let output_data_mem_location = 0x22; - // let input_data_mem_location = 0x23; - // vec![ - // op::gtf_args(input_len_reg, input_index, GTFArgs::InputDataCoinDataLength), - // op::gtf_args( - // output_len_reg, - // output_index, - // GTFArgs::OutputDataCoinDataLength, - // ), - // // get expected data from predicate data - // op::gtf_args( - // output_data_mem_location, - // output_index, - // GTFArgs::OutputDataCoinData, - // ), - // // get actual data - // op::gtf_args( - // input_data_mem_location, - // input_index, - // GTFArgs::InputDataCoinData, - // ), - // // compare - // op::eq(lengths_match_reg, input_len_reg, output_len_reg), - // op::meq( - // data_match_reg, - // output_data_mem_location, - // input_data_mem_location, - // output_len_reg, - // ), - // op::and(res_reg, lengths_match_reg, data_match_reg), - // op::ret(res_reg), - // ] - // .into_iter() - // .collect() - vec![op::ret(0x01)].into_iter().collect() + let output_len_reg = 0x13; + let input_len_reg = 0x14; + let res_reg = 0x10; + let lengths_match_reg = 0x11; + let data_match_reg = 0x12; + let input_index = 0; + let output_index = 0; + let output_data_mem_location = 0x22; + let input_data_mem_location = 0x23; + vec![ + op::gtf_args(input_len_reg, input_index, GTFArgs::InputDataCoinDataLength), + op::gtf_args( + output_len_reg, + output_index, + GTFArgs::OutputDataCoinDataLength, + ), + // get expected data from predicate data + op::gtf_args( + output_data_mem_location, + output_index, + GTFArgs::OutputDataCoinData, + ), + // get actual data + op::gtf_args( + input_data_mem_location, + input_index, + GTFArgs::InputDataCoinData, + ), + // compare + op::eq(lengths_match_reg, input_len_reg, output_len_reg), + op::meq( + data_match_reg, + output_data_mem_location, + input_data_mem_location, + output_len_reg, + ), + op::and(res_reg, lengths_match_reg, data_match_reg), + op::ret(res_reg), + ] + .into_iter() + .collect() } #[tokio::test] async fn submit__tx_with_predicate_can_check_data_coin() { From 59edc6021b8c738e0335d6314c6e889eee8f1c7f Mon Sep 17 00:00:00 2001 From: Mitch Turner Date: Fri, 4 Apr 2025 16:42:06 -0600 Subject: [PATCH 35/37] Restore test now that it is passing --- Cargo.lock | 2 + tests/tests/tx/predicates.rs | 95 +++++++++++++++++++----------------- 2 files changed, 51 insertions(+), 46 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 79ab2ea7617..d891d6bc56e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4294,6 +4294,7 @@ dependencies = [ "serde", "strum 0.24.1", "strum_macros 0.24.3", + "tracing", ] [[package]] @@ -4380,6 +4381,7 @@ dependencies = [ "strum 0.24.1", "substrate-bn", "tai64", + "tracing", ] [[package]] diff --git a/tests/tests/tx/predicates.rs b/tests/tests/tx/predicates.rs index 0d5f9f4dffa..e61cd357f96 100644 --- a/tests/tests/tx/predicates.rs +++ b/tests/tests/tx/predicates.rs @@ -214,49 +214,6 @@ fn predicate_checking_predicate_data_matches_input_data_coin() -> Vec { .collect() } -fn predicate_checking_output_data_matches_input_data_coin() -> Vec { - let output_len_reg = 0x13; - let input_len_reg = 0x14; - let res_reg = 0x10; - let lengths_match_reg = 0x11; - let data_match_reg = 0x12; - let input_index = 0; - let output_index = 0; - let output_data_mem_location = 0x22; - let input_data_mem_location = 0x23; - vec![ - op::gtf_args(input_len_reg, input_index, GTFArgs::InputDataCoinDataLength), - op::gtf_args( - output_len_reg, - output_index, - GTFArgs::OutputDataCoinDataLength, - ), - // get expected data from predicate data - op::gtf_args( - output_data_mem_location, - output_index, - GTFArgs::OutputDataCoinData, - ), - // get actual data - op::gtf_args( - input_data_mem_location, - input_index, - GTFArgs::InputDataCoinData, - ), - // compare - op::eq(lengths_match_reg, input_len_reg, output_len_reg), - op::meq( - data_match_reg, - output_data_mem_location, - input_data_mem_location, - output_len_reg, - ), - op::and(res_reg, lengths_match_reg, data_match_reg), - op::ret(res_reg), - ] - .into_iter() - .collect() -} #[tokio::test] async fn submit__tx_with_predicate_can_check_data_coin() { let _ = tracing_subscriber::fmt() @@ -352,11 +309,57 @@ async fn submit__tx_with_predicate_can_check_data_coin() { ) } +#[allow(unused_variables)] +fn predicate_checking_output_data_matches_input_data_coin() -> Vec { + let output_data_len_reg = 0x13; + let input_data_len_reg = 0x14; + let res_reg = 0x10; + let lengths_match_reg = 0x11; + let data_match_reg = 0x12; + let input_index = 0; + let output_index = 0; + let output_data_mem_location = 0x22; + let input_data_mem_location = 0x23; + vec![ + op::gtf_args( + input_data_len_reg, + input_index, + GTFArgs::InputDataCoinDataLength, + ), + op::gtf_args( + output_data_len_reg, + output_index, + GTFArgs::OutputDataCoinDataLength, + ), + // output data location + op::gtf_args( + output_data_mem_location, + output_index, + GTFArgs::OutputDataCoinData, + ), + // input data location + op::gtf_args( + input_data_mem_location, + input_index, + GTFArgs::InputDataCoinData, + ), + // compare + op::eq(lengths_match_reg, input_data_len_reg, output_data_len_reg), + op::meq( + data_match_reg, + input_data_mem_location, + output_data_mem_location, + output_data_len_reg, + ), + op::and(res_reg, lengths_match_reg, data_match_reg), + op::ret(res_reg), + ] + .into_iter() + .collect() +} + #[tokio::test] async fn submit__tx_with_predicate_can_check_input_and_output_data_coins() { - let _ = tracing_subscriber::fmt() - .with_max_level(tracing::Level::DEBUG) - .try_init(); let mut rng = StdRng::seed_from_u64(2322); // given From 709202461727f1b258b4591dafbbb7f6a3a19f4e Mon Sep 17 00:00:00 2001 From: Mitch Turner Date: Mon, 14 Apr 2025 14:29:50 -0600 Subject: [PATCH 36/37] Fix compilation --- Cargo.lock | 1 - benches/benches/block_target_gas.rs | 3 +- benches/benches/vm_set/blockchain.rs | 2 +- benches/src/bin/tps_bench.rs | 21 ++++++----- .../client/schema/tx/transparent_receipt.rs | 2 ++ .../graphql_api/indexation/asset_metadata.rs | 35 +++++++++---------- .../src/graphql_api/storage/assets.rs | 12 ++++--- crates/fuel-core/src/p2p_test_helpers.rs | 6 ++-- crates/fuel-core/src/schema/assets.rs | 12 +++---- crates/fuel-core/src/schema/tx/receipt.rs | 11 ++++-- .../services/txpool_v2/src/storage/graph.rs | 27 ++++---------- .../txpool_v2/src/tests/tests_pool.rs | 4 +-- tests/test-helpers/src/mint_contract.rs | 5 +-- tests/tests/balances.rs | 3 +- 14 files changed, 70 insertions(+), 74 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 043e6c388ad..b005c96f876 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4389,7 +4389,6 @@ dependencies = [ "strum 0.24.1", "substrate-bn", "tai64", - "tracing", ] [[package]] diff --git a/benches/benches/block_target_gas.rs b/benches/benches/block_target_gas.rs index 1f1d40022ae..2538b691510 100644 --- a/benches/benches/block_target_gas.rs +++ b/benches/benches/block_target_gas.rs @@ -85,6 +85,7 @@ use fuel_core_types::{ AssetId, Bytes32, ContractId, + SubAssetId, }, fuel_vm::{ checked_transaction::EstimatePredicates, @@ -332,7 +333,7 @@ fn service_with_many_contracts( .unwrap(); let mut storage_key = primitive_types::U256::zero(); - let mut sub_id = Bytes32::zeroed(); + let mut sub_id = SubAssetId::zeroed(); database .init_contract_balances( contract_id, diff --git a/benches/benches/vm_set/blockchain.rs b/benches/benches/vm_set/blockchain.rs index 04ad9851cc8..57531930e86 100644 --- a/benches/benches/vm_set/blockchain.rs +++ b/benches/benches/vm_set/blockchain.rs @@ -108,7 +108,7 @@ impl BenchDb { )?; let mut storage_key = primitive_types::U256::zero(); - let mut sub_id = Bytes32::zeroed(); + let mut sub_id = SubAssetId::zeroed(); database.init_contract_balances( contract_id, (0..state_size).map(|k| { diff --git a/benches/src/bin/tps_bench.rs b/benches/src/bin/tps_bench.rs index 0f3527673be..d930bb1189d 100644 --- a/benches/src/bin/tps_bench.rs +++ b/benches/src/bin/tps_bench.rs @@ -169,15 +169,18 @@ fn main() { .. }) = input { - Some(CoinConfig { - tx_id: *utxo_id.tx_id(), - output_index: utxo_id.output_index(), - tx_pointer_block_height: tx_pointer.block_height(), - tx_pointer_tx_idx: tx_pointer.tx_index(), - owner, - amount, - asset_id, - }) + Some( + ConfigCoin { + tx_id: *utxo_id.tx_id(), + output_index: utxo_id.output_index(), + tx_pointer_block_height: tx_pointer.block_height(), + tx_pointer_tx_idx: tx_pointer.tx_index(), + owner, + amount, + asset_id, + } + .into(), + ) } else { None } diff --git a/crates/client/src/client/schema/tx/transparent_receipt.rs b/crates/client/src/client/schema/tx/transparent_receipt.rs index d8500a7ebb2..57297cd63be 100644 --- a/crates/client/src/client/schema/tx/transparent_receipt.rs +++ b/crates/client/src/client/schema/tx/transparent_receipt.rs @@ -365,6 +365,7 @@ impl TryFrom for fuel_tx::Receipt { ReceiptType::Mint => fuel_tx::Receipt::Mint { sub_id: schema .sub_id + .map(|bytes| *bytes.0 .0) .ok_or_else(|| MissingField("sub_id".to_string()))? .into(), contract_id: schema.id.map(|id| id.into()).unwrap_or_default(), @@ -384,6 +385,7 @@ impl TryFrom for fuel_tx::Receipt { ReceiptType::Burn => fuel_tx::Receipt::Burn { sub_id: schema .sub_id + .map(|bytes| *bytes.0 .0) .ok_or_else(|| MissingField("sub_id".to_string()))? .into(), contract_id: schema.id.map(|id| id.into()).unwrap_or_default(), diff --git a/crates/fuel-core/src/graphql_api/indexation/asset_metadata.rs b/crates/fuel-core/src/graphql_api/indexation/asset_metadata.rs index b9fe18a1bc5..c416d391320 100644 --- a/crates/fuel-core/src/graphql_api/indexation/asset_metadata.rs +++ b/crates/fuel-core/src/graphql_api/indexation/asset_metadata.rs @@ -3,8 +3,7 @@ use fuel_core_types::fuel_tx::{ Receipt, }; -use fuel_core_storage::StorageAsMut; - +use super::error::IndexationError; use crate::graphql_api::{ ports::worker::OffChainDatabaseTransaction, storage::assets::{ @@ -12,8 +11,7 @@ use crate::graphql_api::{ AssetsInfo, }, }; - -use super::error::IndexationError; +use fuel_core_storage::StorageAsMut; pub(crate) fn update( receipts: &[Receipt], @@ -39,7 +37,7 @@ where contract_id, .. } => { - let asset_id = contract_id.asset_id(sub_id); + let asset_id = contract_id.asset_id(&sub_id); let new_supply = current_supply(block_st_transaction, receipt, asset_id)?; block_st_transaction.storage::().insert( @@ -105,17 +103,6 @@ where #[cfg(test)] mod tests { - use fuel_core_storage::{ - transactional::WriteTransaction, - StorageAsMut, - }; - use fuel_core_types::fuel_tx::{ - Bytes32, - ContractId, - ContractIdExt, - Receipt, - }; - use crate::{ database::{ database_description::off_chain::OffChain, @@ -130,6 +117,18 @@ mod tests { }, state::rocks_db::DatabaseConfig, }; + use fuel_core_storage::{ + transactional::WriteTransaction, + StorageAsMut, + }; + use fuel_core_types::{ + fuel_tx::{ + ContractId, + ContractIdExt, + Receipt, + }, + fuel_types::SubAssetId, + }; #[test] fn asset_metadata_index_is_correctly_updated() { @@ -145,7 +144,7 @@ mod tests { const ASSET_METADATA_IS_ENABLED: bool = true; - let sub_id: Bytes32 = Bytes32::from([1u8; 32]); + let sub_id = SubAssetId::from([1u8; 32]); let contract_id: ContractId = ContractId::from([2u8; 32]); let contract_asset_id = contract_id.asset_id(&sub_id); const MINT_AMOUNT: u64 = 3; @@ -189,7 +188,7 @@ mod tests { const ASSET_METADATA_IS_DISABLED: bool = false; - let sub_id: Bytes32 = Bytes32::from([1u8; 32]); + let sub_id = SubAssetId::from([1u8; 32]); let contract_id: ContractId = ContractId::from([2u8; 32]); let contract_asset_id = contract_id.asset_id(&sub_id); const MINT_AMOUNT: u64 = 3; diff --git a/crates/fuel-core/src/graphql_api/storage/assets.rs b/crates/fuel-core/src/graphql_api/storage/assets.rs index a51cc68e06f..4af8eda739d 100644 --- a/crates/fuel-core/src/graphql_api/storage/assets.rs +++ b/crates/fuel-core/src/graphql_api/storage/assets.rs @@ -7,10 +7,12 @@ use fuel_core_storage::{ structured_storage::TableWithBlueprint, Mappable, }; -use fuel_core_types::fuel_tx::{ - AssetId, - Bytes32, - ContractId, +use fuel_core_types::{ + fuel_tx::{ + AssetId, + ContractId, + }, + fuel_types::SubAssetId, }; /// Asset info table to store information about the asset like total minted amounts, @@ -20,7 +22,7 @@ pub struct AssetsInfo; #[derive(Default, Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub struct AssetDetails { pub contract_id: ContractId, - pub sub_id: Bytes32, + pub sub_id: SubAssetId, pub total_supply: u128, } diff --git a/crates/fuel-core/src/p2p_test_helpers.rs b/crates/fuel-core/src/p2p_test_helpers.rs index c2d453667e6..22c96d7fb5e 100644 --- a/crates/fuel-core/src/p2p_test_helpers.rs +++ b/crates/fuel-core/src/p2p_test_helpers.rs @@ -266,12 +266,10 @@ pub async fn make_nodes( let all: Vec<_> = (0..num_test_txs) .map(|_| { let secret = SecretKey::random(&mut rng); - let mut initial_coin = CoinConfig { - ..coin_generator.generate_with(secret, 10000) - }; + let mut initial_coin = coin_generator.generate_with(secret, 10000); // Shift idx to prevent overlapping utxo_ids when // merging with existing coins from config - initial_coin.output_index += 100; + *initial_coin.mut_output_index() += 100; let tx = TransactionBuilder::script( vec![op::ret(RegId::ONE)].into_iter().collect(), vec![], diff --git a/crates/fuel-core/src/schema/assets.rs b/crates/fuel-core/src/schema/assets.rs index 60b07d98a12..14067684bdb 100644 --- a/crates/fuel-core/src/schema/assets.rs +++ b/crates/fuel-core/src/schema/assets.rs @@ -1,8 +1,3 @@ -use async_graphql::{ - Context, - Object, -}; - use crate::{ fuel_core_graphql_api::query_costs, graphql_api::storage::assets::AssetDetails, @@ -16,6 +11,11 @@ use crate::{ ReadViewProvider, }, }; +use async_graphql::{ + Context, + Object, +}; +use fuel_core_types::fuel_types::Bytes32; #[derive(Default)] pub struct AssetInfoQuery; @@ -47,7 +47,7 @@ impl From for AssetInfoDetails { fn from(details: AssetDetails) -> Self { AssetInfoDetails { contract_id: details.contract_id.into(), - sub_id: details.sub_id.into(), + sub_id: SubId::from(Bytes32::from(*details.sub_id)), total_supply: details.total_supply.into(), } } diff --git a/crates/fuel-core/src/schema/tx/receipt.rs b/crates/fuel-core/src/schema/tx/receipt.rs index 003680d0d61..aabef434af4 100644 --- a/crates/fuel-core/src/schema/tx/receipt.rs +++ b/crates/fuel-core/src/schema/tx/receipt.rs @@ -14,6 +14,8 @@ use async_graphql::{ use fuel_core_types::{ fuel_asm::Word, fuel_tx, + fuel_types, + fuel_types::SubAssetId, }; #[derive( @@ -143,7 +145,10 @@ impl Receipt { self.0.contract_id().map(|id| ContractId(*id)) } async fn sub_id(&self) -> Option { - self.0.sub_id().copied().map(Into::into) + self.0 + .sub_id() + .copied() + .map(|sub_id| fuel_types::Bytes32::from(*sub_id).into()) } } @@ -246,14 +251,14 @@ pub fn all_receipts() -> Vec { vec![5; 30], ), ReceiptType::Mint => fuel_tx::Receipt::mint( - fuel_tx::Bytes32::from([1u8; 32]), + SubAssetId::from([1u8; 32]), fuel_tx::ContractId::from([2u8; 32]), 3, 4, 5, ), ReceiptType::Burn => fuel_tx::Receipt::burn( - fuel_tx::Bytes32::from([1u8; 32]), + SubAssetId::from([1u8; 32]), fuel_tx::ContractId::from([2u8; 32]), 3, 4, diff --git a/crates/services/txpool_v2/src/storage/graph.rs b/crates/services/txpool_v2/src/storage/graph.rs index b8a6e6d35fe..52d377d56cb 100644 --- a/crates/services/txpool_v2/src/storage/graph.rs +++ b/crates/services/txpool_v2/src/storage/graph.rs @@ -7,6 +7,11 @@ use std::{ time::SystemTime, }; +use super::{ + RemovedTransactions, + Storage, + StorageData, +}; use crate::{ error::{ DependencyError, @@ -18,6 +23,7 @@ use crate::{ pending_pool::MissingInput, ports::TxPoolPersistentStorage, selection_algorithms::ratio_tip_gas::RatioTipGasSelectionAlgorithmStorage, + spent_inputs::SpentInputs, storage::checked_collision::CheckedTransaction, }; use fuel_core_types::{ @@ -53,27 +59,6 @@ use petgraph::{ prelude::StableDiGraph, }; -use crate::{ - error::{ - DependencyError, - Error, - InputValidationError, - InputValidationErrorType, - }, - extracted_outputs::ExtractedOutputs, - pending_pool::MissingInput, - ports::TxPoolPersistentStorage, - selection_algorithms::ratio_tip_gas::RatioTipGasSelectionAlgorithmStorage, - spent_inputs::SpentInputs, - storage::checked_collision::CheckedTransaction, -}; - -use super::{ - RemovedTransactions, - Storage, - StorageData, -}; - pub struct GraphStorage { /// The configuration of the graph config: GraphConfig, diff --git a/crates/services/txpool_v2/src/tests/tests_pool.rs b/crates/services/txpool_v2/src/tests/tests_pool.rs index f2580b00b63..aee633003fb 100644 --- a/crates/services/txpool_v2/src/tests/tests_pool.rs +++ b/crates/services/txpool_v2/src/tests/tests_pool.rs @@ -1119,7 +1119,7 @@ fn insert__tx_with_predicate_without_enough_gas() { assert!(matches!( err, Error::ConsensusValidity(CheckError::PredicateVerificationFailed( - PredicateVerificationFailed::OutOfGas + PredicateVerificationFailed::OutOfGas { .. } )) )); universe.assert_pool_integrity(&[]); @@ -1153,7 +1153,7 @@ fn insert__tx_with_predicate_that_returns_false() { assert!(matches!( err, Error::ConsensusValidity(CheckError::PredicateVerificationFailed( - PredicateVerificationFailed::Panic(PanicReason::PredicateReturnedNonOne) + PredicateVerificationFailed::Panic { reason: PanicReason::PredicateReturnedNonOne, ..} )) )); universe.assert_pool_integrity(&[]); diff --git a/tests/test-helpers/src/mint_contract.rs b/tests/test-helpers/src/mint_contract.rs index 0b761b1035d..7bfca172d2b 100644 --- a/tests/test-helpers/src/mint_contract.rs +++ b/tests/test-helpers/src/mint_contract.rs @@ -26,6 +26,7 @@ use fuel_core_types::{ bytes::WORD_SIZE, canonical::Serialize, BlockHeight, + SubAssetId, }, fuel_vm::{ Call, @@ -81,7 +82,7 @@ pub async fn deploy( pub fn mint_tx( rng: &mut rand::rngs::StdRng, contract_id: ContractId, - sub_asset_id: Bytes32, + sub_asset_id: SubAssetId, amount: u64, ) -> Transaction { let script = [ @@ -128,7 +129,7 @@ pub async fn mint( client: &FuelClient, rng: &mut rand::rngs::StdRng, contract_id: ContractId, - sub_asset_id: Bytes32, + sub_asset_id: SubAssetId, amount: u64, ) -> BlockHeight { let tx = mint_tx(rng, contract_id, sub_asset_id, amount); diff --git a/tests/tests/balances.rs b/tests/tests/balances.rs index 290c5731d52..fef7f692822 100644 --- a/tests/tests/balances.rs +++ b/tests/tests/balances.rs @@ -29,6 +29,7 @@ use fuel_core_types::{ Bytes32, ContractIdExt, }, + fuel_types::SubAssetId, }; use rand::SeedableRng; use test_helpers::{ @@ -571,7 +572,7 @@ async fn contract_balances_in_the_past() { let client = FuelClient::from(srv.bound_address); // Given - let sub_asset_id = Bytes32::new([1u8; 32]); + let sub_asset_id = SubAssetId::new([1u8; 32]); let amount = 1234; let (deployed_height, contract_id) = mint_contract::deploy(&client, &mut rng).await; From 7e76efd14e6255f77363027982d52689145faec4 Mon Sep 17 00:00:00 2001 From: Mitchell Turner Date: Thu, 17 Apr 2025 17:23:56 -0600 Subject: [PATCH 37/37] Prototype: DataCoins api endpoints (#2948) ## 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.toml | 2 +- crates/client/assets/schema.sdl | 78 +++++++- crates/client/src/client.rs | 39 +++- crates/client/src/client/schema/coins.rs | 126 +++++++++++++ crates/client/src/client/types.rs | 1 + crates/client/src/client/types/coins.rs | 43 ++++- crates/compression/Cargo.toml | 1 + crates/fuel-core/src/schema/coins.rs | 142 ++++++++++++++- crates/fuel-core/src/schema/tx/assemble_tx.rs | 20 +++ .../txpool_v2/src/tests/tests_pool.rs | 5 +- crates/storage/src/blueprint/sparse.rs | 4 +- tests/tests/assets.rs | 9 +- tests/tests/balances.rs | 5 +- tests/tests/coins.rs | 170 ++++++++++++++++++ 14 files changed, 616 insertions(+), 29 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index bd37e6429c4..3f10c14dbe4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -175,4 +175,4 @@ url = "2.2" # add patch for fuel-vm [patch.crates-io] -fuel-vm-private = { path = "../fuel-vm/fuel-vm", version = "0.60.0", package = "fuel-vm" } +fuel-vm-private = { path = "../fuel-vm/fuel-vm", version = "0.60.0", package = "fuel-vm" } diff --git a/crates/client/assets/schema.sdl b/crates/client/assets/schema.sdl index 089633e3474..8fd63d5d091 100644 --- a/crates/client/assets/schema.sdl +++ b/crates/client/assets/schema.sdl @@ -215,7 +215,7 @@ type CoinOutput { """ The schema analog of the [`coins::CoinType`]. """ -union CoinType = Coin | MessageCoin +union CoinType = Coin | MessageCoin | DataCoin union Consensus = Genesis | PoAConsensus @@ -318,6 +318,58 @@ type DaCompressedBlock { bytes: HexString! } +type DataCoin { + utxoId: UtxoId! + owner: Address! + amount: U64! + assetId: AssetId! + """ + TxPointer - the height of the block this coin was created in + """ + blockCreated: U32! + """ + TxPointer - the index of the transaction that created this coin + """ + txCreatedIdx: U16! + data: HexString +} + +type DataCoinConnection { + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + """ + A list of edges. + """ + edges: [DataCoinEdge!]! + """ + A list of nodes. + """ + nodes: [DataCoin!]! +} + +""" +An edge in a connection. +""" +type DataCoinEdge { + """ + The item at the end of the edge + """ + node: DataCoin! + """ + A cursor for use in pagination + """ + cursor: String! +} + +type DataCoinOutput { + to: Address! + amount: U64! + assetId: AssetId! + data: [Int!]! +} + union DependentCost = LightOperation | HeavyOperation enum Destroy { @@ -632,7 +684,7 @@ type IndexationFlags { assetMetadata: Boolean! } -union Input = InputCoin | InputContract | InputMessage +union Input = InputCoin | InputDataCoin | InputContract | InputMessage type InputCoin { utxoId: UtxoId! @@ -654,6 +706,19 @@ type InputContract { contractId: ContractId! } +type InputDataCoin { + utxoId: UtxoId! + owner: Address! + amount: U64! + assetId: AssetId! + txPointer: TxPointer! + witnessIndex: U16! + predicateGasUsed: U64! + predicate: HexString! + predicateData: HexString! + data: HexString! +} + type InputMessage { sender: Address! recipient: Address! @@ -823,7 +888,7 @@ type NodeInfo { scalar Nonce -union Output = CoinOutput | ContractOutput | ChangeOutput | VariableOutput | ContractCreated +union Output = CoinOutput | DataCoinOutput | ContractOutput | ChangeOutput | VariableOutput | ContractCreated """ A separate `Breakpoint` type to be used as an output, as a single @@ -1081,10 +1146,17 @@ type Query { """ utxoId: UtxoId! ): Coin + dataCoin( + """ + The ID of the coin + """ + utxoId: UtxoId! + ): DataCoin """ Gets all unspent coins of some `owner` maybe filtered with by `asset_id` per page. """ coins(filter: CoinFilterInput!, first: Int, after: String, last: Int, before: String): CoinConnection! + dataCoins(filter: CoinFilterInput!, first: Int, after: String, last: Int, before: String): DataCoinConnection! """ For each `query_per_asset`, get some spendable coins(of asset specified by the query) owned by `owner` that add up at least the query amount. The returned coins can be spent. diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index 193b6e5e38a..4ed357963a7 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -1,3 +1,10 @@ +use self::schema::{ + block::ProduceBlockArgs, + message::{ + MessageProofArgs, + NonceArgs, + }, +}; #[cfg(feature = "subscriptions")] use crate::client::types::StatusWithTransaction; use crate::{ @@ -5,6 +12,8 @@ use crate::{ schema::{ block::BlockByHeightArgs, coins::{ + DataCoinByIdArgs, + DataCoinsConnectionArgs, ExcludeInput, SpendQueryElementInput, }, @@ -165,14 +174,6 @@ use types::{ TransactionStatus, }; -use self::schema::{ - block::ProduceBlockArgs, - message::{ - MessageProofArgs, - NonceArgs, - }, -}; - pub mod pagination; pub mod schema; pub mod types; @@ -1478,6 +1479,14 @@ impl FuelClient { Ok(coin) } + pub async fn data_coin(&self, id: &UtxoId) -> io::Result> { + let query = schema::coins::DataCoinByIdQuery::build(DataCoinByIdArgs { + utxo_id: (*id).into(), + }); + let coin = self.query(query).await?.data_coin.map(Into::into); + Ok(coin) + } + /// Retrieve a page of coins by their owner pub async fn coins( &self, @@ -1497,6 +1506,20 @@ impl FuelClient { Ok(coins) } + pub async fn data_coins( + &self, + owner: &Address, + asset_id: Option<&AssetId>, + request: PaginationRequest, + ) -> io::Result> { + let owner: schema::Address = (*owner).into(); + let args = DataCoinsConnectionArgs::from((owner, asset_id.copied(), request)); + let query = schema::coins::DataCoinsQuery::build(args); + + let coins = self.query(query).await?.data_coins.into(); + Ok(coins) + } + /// Retrieve coins to spend in a transaction pub async fn coins_to_spend( &self, diff --git a/crates/client/src/client/schema/coins.rs b/crates/client/src/client/schema/coins.rs index ef4bf7f2637..edc0a80cb99 100644 --- a/crates/client/src/client/schema/coins.rs +++ b/crates/client/src/client/schema/coins.rs @@ -3,6 +3,7 @@ use crate::client::{ schema, Address, AssetId, + HexString, Nonce, PageInfo, UtxoId, @@ -24,6 +25,11 @@ pub struct CoinByIdArgs { pub utxo_id: UtxoId, } +#[derive(cynic::QueryVariables, Debug)] +pub struct DataCoinByIdArgs { + pub utxo_id: UtxoId, +} + #[derive(cynic::QueryFragment, Clone, Debug)] #[cynic( schema_path = "./assets/schema.sdl", @@ -35,6 +41,17 @@ pub struct CoinByIdQuery { pub coin: Option, } +#[derive(cynic::QueryFragment, Clone, Debug)] +#[cynic( + schema_path = "./assets/schema.sdl", + graphql_type = "Query", + variables = "DataCoinByIdArgs" +)] +pub struct DataCoinByIdQuery { + #[arguments(utxoId: $ utxo_id)] + pub data_coin: Option, +} + #[derive(cynic::InputObject, Clone, Debug)] #[cynic(schema_path = "./assets/schema.sdl")] pub struct CoinFilterInput { @@ -59,6 +76,54 @@ pub struct CoinsConnectionArgs { pub last: Option, } +#[derive(cynic::QueryVariables, Debug)] +pub struct DataCoinsConnectionArgs { + /// Filter coins based on a filter + filter: CoinFilterInput, + /// Skip until coin id (forward pagination) + pub after: Option, + /// Skip until coin id (backward pagination) + pub before: Option, + /// Retrieve the first n coins in order (forward pagination) + pub first: Option, + /// Retrieve the last n coins in order (backward pagination). + /// Can't be used at the same time as `first`. + pub last: Option, +} + +impl From<(Address, Option, PaginationRequest)> + for DataCoinsConnectionArgs +{ + fn from(r: (Address, Option, PaginationRequest)) -> Self { + let (owner, asset_id, pagination) = r; + let cursor = pagination.cursor; + let results = pagination.results; + let schema_asset = asset_id.map(|a| AssetId::from(a)); + match pagination.direction { + PageDirection::Forward => DataCoinsConnectionArgs { + filter: CoinFilterInput { + owner, + asset_id: schema_asset, + }, + after: cursor, + before: None, + first: Some(results), + last: None, + }, + PageDirection::Backward => DataCoinsConnectionArgs { + filter: CoinFilterInput { + owner, + asset_id: schema_asset, + }, + after: None, + before: cursor, + first: None, + last: Some(results), + }, + } + } +} + impl From<(Address, AssetId, PaginationRequest)> for CoinsConnectionArgs { fn from(r: (Address, AssetId, PaginationRequest)) -> Self { match r.2.direction { @@ -97,6 +162,17 @@ pub struct CoinsQuery { pub coins: CoinConnection, } +#[derive(cynic::QueryFragment, Clone, Debug)] +#[cynic( + schema_path = "./assets/schema.sdl", + graphql_type = "Query", + variables = "DataCoinsConnectionArgs" +)] +pub struct DataCoinsQuery { + #[arguments(filter: $ filter, after: $ after, before: $ before, first: $ first, last: $ last)] + pub data_coins: DataCoinConnection, +} + #[derive(cynic::QueryFragment, Clone, Debug)] #[cynic(schema_path = "./assets/schema.sdl")] pub struct CoinConnection { @@ -104,6 +180,13 @@ pub struct CoinConnection { pub page_info: PageInfo, } +#[derive(cynic::QueryFragment, Clone, Debug)] +#[cynic(schema_path = "./assets/schema.sdl")] +pub struct DataCoinConnection { + pub edges: Vec, + pub page_info: PageInfo, +} + #[derive(cynic::QueryFragment, Clone, Debug)] #[cynic(schema_path = "./assets/schema.sdl")] pub struct CoinEdge { @@ -111,6 +194,13 @@ pub struct CoinEdge { pub node: Coin, } +#[derive(cynic::QueryFragment, Clone, Debug)] +#[cynic(schema_path = "./assets/schema.sdl")] +pub struct DataCoinEdge { + pub cursor: String, + pub node: DataCoin, +} + #[derive(cynic::QueryFragment, Debug, Clone)] #[cynic(schema_path = "./assets/schema.sdl")] pub struct Coin { @@ -122,6 +212,18 @@ pub struct Coin { pub owner: Address, } +#[derive(cynic::QueryFragment, Debug, Clone)] +#[cynic(schema_path = "./assets/schema.sdl")] +pub struct DataCoin { + pub amount: U64, + pub block_created: U32, + pub tx_created_idx: U16, + pub asset_id: AssetId, + pub utxo_id: UtxoId, + pub owner: Address, + pub data: Option, +} + #[derive(cynic::QueryFragment, Clone, Debug)] #[cynic(schema_path = "./assets/schema.sdl", graphql_type = "Coin")] pub struct CoinIdFragment { @@ -180,6 +282,7 @@ pub struct MessageCoin { #[cynic(schema_path = "./assets/schema.sdl")] pub enum CoinType { Coin(Coin), + DataCoin(DataCoin), MessageCoin(MessageCoin), #[cynic(fallback)] Unknown, @@ -189,12 +292,23 @@ impl CoinType { pub fn amount(&self) -> u64 { match self { CoinType::Coin(c) => c.amount.0, + CoinType::DataCoin(c) => c.amount.0, CoinType::MessageCoin(m) => m.amount.0, CoinType::Unknown => 0, } } } +#[derive(cynic::QueryVariables, Debug)] +pub struct DataCoinsToSpendArgs { + /// The `Address` of the assets' coins owner. + owner: Address, + /// The total amount of each asset type to spend. + query_per_asset: Vec, + /// A list of ids to exclude from the selection. + excluded_ids: Option, +} + #[derive(cynic::QueryVariables, Debug)] pub struct CoinsToSpendArgs { /// The `Address` of the assets' coins owner. @@ -207,6 +321,8 @@ pub struct CoinsToSpendArgs { pub(crate) type CoinsToSpendArgsTuple = (Address, Vec, Option); +pub(crate) type DataCoinsToSpendArgsTuple = + (Address, Vec, Option); impl From for CoinsToSpendArgs { fn from(r: CoinsToSpendArgsTuple) -> Self { @@ -218,6 +334,16 @@ impl From for CoinsToSpendArgs { } } +impl From for DataCoinsToSpendArgs { + fn from(r: DataCoinsToSpendArgsTuple) -> Self { + DataCoinsToSpendArgs { + owner: r.0, + query_per_asset: r.1, + excluded_ids: r.2, + } + } +} + #[derive(cynic::QueryFragment, Clone, Debug)] #[cynic( schema_path = "./assets/schema.sdl", diff --git a/crates/client/src/client/types.rs b/crates/client/src/client/types.rs index 204300e8ab1..99600174662 100644 --- a/crates/client/src/client/types.rs +++ b/crates/client/src/client/types.rs @@ -24,6 +24,7 @@ pub use chain_info::ChainInfo; pub use coins::{ Coin, CoinType, + DataCoin, MessageCoin, }; pub use contract::{ diff --git a/crates/client/src/client/types/coins.rs b/crates/client/src/client/types/coins.rs index 5e5e8f08789..3d142384cb5 100644 --- a/crates/client/src/client/types/coins.rs +++ b/crates/client/src/client/types/coins.rs @@ -9,9 +9,10 @@ use crate::client::{ PaginatedResult, }; -#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub enum CoinType { Coin(Coin), + DataCoin(DataCoin), MessageCoin(MessageCoin), Unknown, } @@ -20,6 +21,7 @@ impl CoinType { pub fn amount(&self) -> u64 { match self { CoinType::Coin(c) => c.amount, + CoinType::DataCoin(c) => c.amount, CoinType::MessageCoin(m) => m.amount, CoinType::Unknown => 0, } @@ -36,6 +38,17 @@ pub struct Coin { pub owner: Address, } +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct DataCoin { + pub amount: u64, + pub block_created: u32, + pub tx_created_idx: u16, + pub asset_id: AssetId, + pub utxo_id: UtxoId, + pub owner: Address, + pub data: Vec, +} + #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct MessageCoin { pub amount: u64, @@ -51,6 +64,9 @@ impl From for CoinType { fn from(value: schema::coins::CoinType) -> Self { match value { schema::coins::CoinType::Coin(coin) => Self::Coin(coin.into()), + schema::coins::CoinType::DataCoin(data_coin) => { + Self::DataCoin(data_coin.into()) + } schema::coins::CoinType::MessageCoin(message_coin) => { Self::MessageCoin(message_coin.into()) } @@ -72,6 +88,20 @@ impl From for Coin { } } +impl From for DataCoin { + fn from(value: schema::coins::DataCoin) -> Self { + Self { + amount: value.amount.into(), + block_created: value.block_created.into(), + tx_created_idx: value.tx_created_idx.into(), + asset_id: value.asset_id.into(), + utxo_id: value.utxo_id.into(), + owner: value.owner.into(), + data: value.data.map(|data| data.into()).unwrap_or_default(), + } + } +} + impl From for MessageCoin { fn from(value: schema::coins::MessageCoin) -> Self { Self { @@ -94,3 +124,14 @@ impl From for PaginatedResult { } } } + +impl From for PaginatedResult { + fn from(conn: schema::coins::DataCoinConnection) -> Self { + PaginatedResult { + cursor: conn.page_info.end_cursor, + has_next_page: conn.page_info.has_next_page, + has_previous_page: conn.page_info.has_previous_page, + results: conn.edges.into_iter().map(|e| e.node.into()).collect(), + } + } +} diff --git a/crates/compression/Cargo.toml b/crates/compression/Cargo.toml index 60ff0d8b78a..0977645d70e 100644 --- a/crates/compression/Cargo.toml +++ b/crates/compression/Cargo.toml @@ -41,6 +41,7 @@ tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } test-helpers = [ "dep:rand", "fuel-core-types/test-helpers", + "fuel-core-types/da-compression", "fuel-core-types/random", "fuel-core-types/std", ] diff --git a/crates/fuel-core/src/schema/coins.rs b/crates/fuel-core/src/schema/coins.rs index 45145155297..37daab88497 100644 --- a/crates/fuel-core/src/schema/coins.rs +++ b/crates/fuel-core/src/schema/coins.rs @@ -93,10 +93,6 @@ impl Coin { async fn tx_created_idx(&self) -> U16 { self.0.tx_pointer().tx_index().into() } - - async fn data(&self) -> Option { - self.0.data().map(|data| HexString(data.clone())) - } } impl From for Coin { @@ -105,6 +101,12 @@ impl From for Coin { } } +impl From for Coin { + fn from(value: fuel_core_types::entities::coins::coin::Coin) -> Self { + Coin(UncompressedCoin::Coin(value)) + } +} + pub struct MessageCoin(pub(crate) MessageCoinModel); #[async_graphql::Object] @@ -146,6 +148,53 @@ impl From for MessageCoin { } } +pub struct DataCoin(pub(crate) UncompressedCoin); + +#[async_graphql::Object] +impl DataCoin { + async fn utxo_id(&self) -> UtxoId { + (*self.0.utxo_id()).into() + } + + async fn owner(&self) -> Address { + (*self.0.owner()).into() + } + + async fn amount(&self) -> U64 { + (*self.0.amount()).into() + } + + async fn asset_id(&self) -> AssetId { + (*self.0.asset_id()).into() + } + + /// TxPointer - the height of the block this coin was created in + async fn block_created(&self) -> U32 { + u32::from(self.0.tx_pointer().block_height()).into() + } + + /// TxPointer - the index of the transaction that created this coin + async fn tx_created_idx(&self) -> U16 { + self.0.tx_pointer().tx_index().into() + } + + async fn data(&self) -> Option { + self.0.data().map(|data| HexString(data.clone())) + } +} + +impl From for DataCoin { + fn from(value: UncompressedCoin) -> Self { + DataCoin(value) + } +} + +impl From for DataCoin { + fn from(value: fuel_core_types::entities::coins::coin::DataCoin) -> Self { + DataCoin(UncompressedCoin::DataCoin(value)) + } +} + /// The schema analog of the [`coins::CoinType`]. #[derive(async_graphql::Union)] pub enum CoinType { @@ -153,6 +202,8 @@ pub enum CoinType { Coin(Coin), /// The bridged coin from the DA layer. MessageCoin(MessageCoin), + /// A coin that contains additional data. + DataCoin(DataCoin), } impl CoinType { @@ -160,6 +211,7 @@ impl CoinType { match self { CoinType::Coin(coin) => *coin.0.amount(), CoinType::MessageCoin(coin) => coin.0.amount, + CoinType::DataCoin(coin) => *coin.0.amount(), } } } @@ -167,7 +219,10 @@ impl CoinType { impl From for CoinType { fn from(value: coins::CoinType) -> Self { match value { - coins::CoinType::Coin(coin) => CoinType::Coin(coin.into()), + coins::CoinType::Coin(coin) => match coin { + UncompressedCoin::Coin(c) => CoinType::Coin(c.into()), + UncompressedCoin::DataCoin(c) => CoinType::DataCoin(c.into()), + }, coins::CoinType::MessageCoin(coin) => CoinType::MessageCoin(coin.into()), } } @@ -235,6 +290,16 @@ impl CoinQuery { query.coin(utxo_id.0).into_api_result() } + #[graphql(complexity = "query_costs().storage_read + child_complexity")] + async fn data_coin( + &self, + ctx: &Context<'_>, + #[graphql(desc = "The ID of the coin")] utxo_id: UtxoId, + ) -> async_graphql::Result> { + let query = ctx.read_view()?; + query.coin(utxo_id.0).into_api_result() + } + /// Gets all unspent coins of some `owner` maybe filtered with by `asset_id` per page. #[graphql(complexity = "{\ query_costs().storage_iterator\ @@ -272,6 +337,38 @@ impl CoinQuery { .await } + async fn data_coins( + &self, + ctx: &Context<'_>, + filter: CoinFilterInput, + first: Option, + after: Option, + last: Option, + before: Option, + ) -> async_graphql::Result> + { + let query = ctx.read_view()?; + let owner: fuel_tx::Address = filter.owner.into(); + crate::schema::query_pagination(after, before, first, last, |start, direction| { + let coins = query + .owned_coins(&owner, (*start).map(Into::into), direction) + .filter_map(|result| { + if let (Ok(coin), Some(filter_asset_id)) = (&result, &filter.asset_id) + { + if *coin.asset_id() != filter_asset_id.0 { + return None + } + } + + Some(result) + }) + .map(|res| res.map(|coin| ((*coin.utxo_id()).into(), coin.into()))); + + Ok(coins) + }) + .await + } + /// For each `query_per_asset`, get some spendable coins(of asset specified by the query) owned by /// `owner` that add up at least the query amount. The returned coins can be spent. /// The number of coins is optimized to prevent dust accumulation. @@ -369,6 +466,34 @@ impl ReadView { .await } } + + pub async fn data_coins_to_spend( + &self, + owner: fuel_tx::Address, + query_per_asset: &[SpendQueryElementInput], + excluded: &Exclude, + params: &ConsensusParameters, + max_input: u16, + ) -> Result>, CoinsQueryError> { + let indexation_available = self + .indexation_flags + .contains(&IndexationKind::CoinsToSpend); + if indexation_available { + coins_to_spend_with_cache(owner, query_per_asset, excluded, max_input, self) + .await + } else { + let base_asset_id = params.base_asset_id(); + coins_to_spend_without_cache( + owner, + query_per_asset, + excluded, + max_input, + base_asset_id, + self, + ) + .await + } + } } async fn coins_to_spend_without_cache( @@ -405,7 +530,12 @@ async fn coins_to_spend_without_cache( coins .into_iter() .map(|coin| match coin { - coins::CoinType::Coin(coin) => CoinType::Coin(coin.into()), + coins::CoinType::Coin(coin) => match coin { + UncompressedCoin::Coin(coin) => CoinType::Coin(coin.into()), + UncompressedCoin::DataCoin(coin) => { + CoinType::DataCoin(coin.into()) + } + }, coins::CoinType::MessageCoin(coin) => { CoinType::MessageCoin(coin.into()) } diff --git a/crates/fuel-core/src/schema/tx/assemble_tx.rs b/crates/fuel-core/src/schema/tx/assemble_tx.rs index 3212e5c7a1e..70dff8a10d1 100644 --- a/crates/fuel-core/src/schema/tx/assemble_tx.rs +++ b/crates/fuel-core/src/schema/tx/assemble_tx.rs @@ -492,6 +492,15 @@ where message.0.nonce, signature_index, ), + CoinType::DataCoin(data_coin) => Input::data_coin_signed( + *data_coin.0.utxo_id(), + *data_coin.0.owner(), + *data_coin.0.amount(), + *data_coin.0.asset_id(), + *data_coin.0.tx_pointer(), + signature_index, + data_coin.0.data().cloned().unwrap_or_default(), + ), } } Account::Predicate(predicate) => { @@ -517,6 +526,17 @@ where predicate.predicate.clone(), predicate.predicate_data.clone(), ), + CoinType::DataCoin(data_coin) => Input::data_coin_predicate( + *data_coin.0.utxo_id(), + predicate.predicate_address, + *data_coin.0.amount(), + *data_coin.0.asset_id(), + *data_coin.0.tx_pointer(), + predicate_gas_used, + predicate.predicate.clone(), + predicate.predicate_data.clone(), + data_coin.0.data().cloned().unwrap_or_default(), + ), } } }; diff --git a/crates/services/txpool_v2/src/tests/tests_pool.rs b/crates/services/txpool_v2/src/tests/tests_pool.rs index aee633003fb..aedd1e9d903 100644 --- a/crates/services/txpool_v2/src/tests/tests_pool.rs +++ b/crates/services/txpool_v2/src/tests/tests_pool.rs @@ -1153,7 +1153,10 @@ fn insert__tx_with_predicate_that_returns_false() { assert!(matches!( err, Error::ConsensusValidity(CheckError::PredicateVerificationFailed( - PredicateVerificationFailed::Panic { reason: PanicReason::PredicateReturnedNonOne, ..} + PredicateVerificationFailed::Panic { + reason: PanicReason::PredicateReturnedNonOne, + .. + } )) )); universe.assert_pool_integrity(&[]); diff --git a/crates/storage/src/blueprint/sparse.rs b/crates/storage/src/blueprint/sparse.rs index e5f9de1fceb..e04c3c4064e 100644 --- a/crates/storage/src/blueprint/sparse.rs +++ b/crates/storage/src/blueprint/sparse.rs @@ -110,7 +110,7 @@ where let mut tree: MerkleTree = MerkleTree::load(storage, &root) .map_err(|err| StorageError::Other(anyhow::anyhow!("{err:?}")))?; - tree.update(MerkleTreeKey::new(key_bytes), value_bytes) + tree.insert(MerkleTreeKey::new(key_bytes), value_bytes) .map_err(|err| StorageError::Other(anyhow::anyhow!("{err:?}")))?; // Generate new metadata for the updated tree @@ -402,7 +402,7 @@ where .collect_vec(); for (key_bytes, value_bytes) in encoded_set.iter() { - tree.update(MerkleTreeKey::new(key_bytes), value_bytes) + tree.insert(MerkleTreeKey::new(key_bytes), value_bytes) .map_err(|err| StorageError::Other(anyhow::anyhow!("{err:?}")))?; } let root = tree.root(); diff --git a/tests/tests/assets.rs b/tests/tests/assets.rs index 420fe444528..8dc4625afb2 100644 --- a/tests/tests/assets.rs +++ b/tests/tests/assets.rs @@ -20,7 +20,10 @@ use fuel_core_types::{ UtxoId, Witness, }, - fuel_types::canonical::Serialize, + fuel_types::{ + canonical::Serialize, + SubAssetId, + }, fuel_vm::{ Call, Contract, @@ -126,7 +129,7 @@ async fn asset_info_mint_burn() { // When // Query asset info before burn let initial_supply = client - .asset_info(&contract_id.asset_id(&Bytes32::zeroed())) + .asset_info(&contract_id.asset_id(&SubAssetId::zeroed())) .await .unwrap() .total_supply; @@ -173,7 +176,7 @@ async fn asset_info_mint_burn() { // When // Query asset info after burn let final_supply = client - .asset_info(&contract_id.asset_id(&Bytes32::zeroed())) + .asset_info(&contract_id.asset_id(&SubAssetId::zeroed())) .await .unwrap() .total_supply; diff --git a/tests/tests/balances.rs b/tests/tests/balances.rs index fef7f692822..7ed3004c343 100644 --- a/tests/tests/balances.rs +++ b/tests/tests/balances.rs @@ -25,10 +25,7 @@ use fuel_core_client::client::{ use fuel_core_poa::Trigger; use fuel_core_types::{ blockchain::primitives::DaBlockHeight, - fuel_tx::{ - Bytes32, - ContractIdExt, - }, + fuel_tx::ContractIdExt, fuel_types::SubAssetId, }; use rand::SeedableRng; diff --git a/tests/tests/coins.rs b/tests/tests/coins.rs index 3683964ebdb..cfd5c82fec0 100644 --- a/tests/tests/coins.rs +++ b/tests/tests/coins.rs @@ -219,6 +219,7 @@ mod coin { .flat_map(|coins| { coins.iter().filter_map(|b| match b { CoinType::Coin(c) => Some(c.utxo_id), + CoinType::DataCoin(d) => Some(d.utxo_id), CoinType::MessageCoin(_) => None, CoinType::Unknown => None, }) @@ -442,6 +443,7 @@ mod message_coin { .flat_map(|coins| { coins.iter().filter_map(|b| match b { CoinType::Coin(_) => None, + CoinType::DataCoin(_) => None, CoinType::MessageCoin(m) => Some(m.nonce), CoinType::Unknown => None, }) @@ -651,6 +653,7 @@ mod all_coins { .flat_map(|coins| { coins.iter().filter_map(|b| match b { CoinType::Coin(_) => None, + CoinType::DataCoin(_) => None, CoinType::MessageCoin(m) => Some(m.nonce), CoinType::Unknown => None, }) @@ -661,6 +664,7 @@ mod all_coins { .flat_map(|coins| { coins.iter().filter_map(|b| match b { CoinType::Coin(c) => Some(c.utxo_id), + CoinType::DataCoin(d) => Some(d.utxo_id), CoinType::MessageCoin(_) => None, CoinType::Unknown => None, }) @@ -742,6 +746,172 @@ mod all_coins { } } +mod data_coin { + use super::*; + use fuel_core::chain_config::{ + ChainConfig, + ConfigDataCoin, + }; + use fuel_core_client::client::{ + pagination::{ + PageDirection, + PaginationRequest, + }, + types::DataCoin, + }; + use fuel_core_types::{ + fuel_crypto::SecretKey, + fuel_tx::Address, + }; + use rand::Rng; + + async fn setup( + tx_id: TxId, + output_index: u16, + owner: Address, + asset_id: AssetId, + amount: u64, + data: Vec, + consensus_parameters: &ConsensusParameters, + ) -> TestContext { + let data_coin = ConfigDataCoin { + tx_id, + output_index, + owner, + amount, + asset_id, + data, + ..Default::default() + } + .into(); + let state = StateConfig { + contracts: vec![], + coins: vec![data_coin], + messages: vec![], + ..Default::default() + }; + let chain = + ChainConfig::local_testnet_with_consensus_parameters(consensus_parameters); + let config = Config::local_node_with_configs(chain, state); + + let srv = FuelService::new_node(config).await.unwrap(); + let client = FuelClient::from(srv.bound_address); + + TestContext { + srv, + rng: StdRng::seed_from_u64(0x123), + client, + } + } + + #[tokio::test] + async fn data_coin__can_get_data_coin_from_storage() { + // Given + let mut rng = StdRng::seed_from_u64(1234); + let expected_asset_id: AssetId = rng.gen(); + let expected_amount = 123; + let secret_key: SecretKey = SecretKey::random(&mut rng); + let pk = secret_key.public_key(); + let expected_owner = Input::owner(&pk); + let expected_data = vec![1, 2, 3, 4, 5]; + let cp = ConsensusParameters::default(); + let tx_id = rng.gen(); + let index = 0; + let context = setup( + tx_id, + index, + expected_owner, + expected_asset_id, + expected_amount, + expected_data.clone(), + &cp, + ) + .await; + + // When + let expected_utxo_id = UtxoId::new(tx_id, index); + let data_coin = context + .client + .data_coin(&expected_utxo_id) + .await + .unwrap() + .unwrap(); + + // Then + let DataCoin { + utxo_id, + amount, + asset_id, + owner, + data, + .. + } = &data_coin; + assert_eq!(expected_utxo_id, *utxo_id); + assert_eq!(expected_amount, *amount); + assert_eq!(expected_owner, *owner); + assert_eq!(expected_asset_id, *asset_id); + assert_eq!(expected_data, *data); + } + + #[tokio::test] + async fn data_coins__can_get_data_coin_from_storage() { + // Given + let mut rng = StdRng::seed_from_u64(1234); + let expected_asset_id: AssetId = rng.gen(); + let expected_amount = 123; + let secret_key: SecretKey = SecretKey::random(&mut rng); + let pk = secret_key.public_key(); + let expected_owner = Input::owner(&pk); + let expected_data = vec![1, 2, 3, 4, 5]; + let cp = ConsensusParameters::default(); + let tx_id = rng.gen(); + let index = 0; + let context = setup( + tx_id, + index, + expected_owner, + expected_asset_id, + expected_amount, + expected_data.clone(), + &cp, + ) + .await; + + // When + let data_coins = context + .client + .data_coins( + &expected_owner, + None, + // Some(&expected_asset_id), + PaginationRequest { + cursor: None, + results: 5, + direction: PageDirection::Forward, + }, + ) + .await + .unwrap() + .results; + + // Then + assert_eq!(data_coins.len(), 1); + let DataCoin { + utxo_id, + amount, + asset_id, + owner, + data, + .. + } = &data_coins[0]; + assert_eq!(UtxoId::new(tx_id, index), *utxo_id); + assert_eq!(expected_amount, *amount); + assert_eq!(expected_owner, *owner); + assert_eq!(expected_asset_id, *asset_id); + assert_eq!(expected_data, *data); + } +} + async fn empty_setup() -> TestContext { // setup config let config = Config::local_node_with_state_config(StateConfig::default());