From 676a08baa076b8fd723264e3566d63bfbede7e7a Mon Sep 17 00:00:00 2001 From: Hannes Karppila Date: Wed, 11 Dec 2024 08:39:34 +0200 Subject: [PATCH 01/25] Initial support for historical execution tracing --- Cargo.lock | 147 ++++++++---- Cargo.toml | 3 +- crates/client/assets/schema.sdl | 68 ++++++ crates/fuel-core/src/graphql_api/database.rs | 9 +- crates/fuel-core/src/graphql_api/ports.rs | 11 +- crates/fuel-core/src/schema/chain.rs | 3 +- crates/fuel-core/src/schema/dap.rs | 6 +- crates/fuel-core/src/schema/tx.rs | 35 +++ crates/fuel-core/src/schema/tx/types.rs | 214 +++++++++++++++++- .../src/service/adapters/graphql_api.rs | 11 + .../src/service/adapters/producer.rs | 25 +- crates/fuel-core/src/state/data_source.rs | 3 +- .../fuel-core/src/state/generic_database.rs | 12 +- .../fuel-core/src/state/historical_rocksdb.rs | 4 +- .../src/state/iterable_key_value_view.rs | 3 +- crates/fuel-core/src/state/key_value_view.rs | 3 +- crates/fuel-core/src/state/rocks_db.rs | 9 +- crates/services/executor/src/executor.rs | 28 ++- .../services/producer/src/block_producer.rs | 31 +++ crates/services/producer/src/ports.rs | 18 +- .../upgradable-executor/src/config.rs | 1 + .../upgradable-executor/src/executor.rs | 13 ++ crates/storage/src/kv_store.rs | 15 +- crates/storage/src/structured_storage.rs | 12 +- crates/storage/src/transactional.rs | 14 +- crates/storage/src/vm_storage.rs | 9 +- crates/types/Cargo.toml | 2 +- crates/types/src/services/executor.rs | 5 + crates/types/src/services/txpool.rs | 2 + 29 files changed, 623 insertions(+), 93 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0bc9f38389d..1da357f05b9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2292,6 +2292,7 @@ dependencies = [ "proc-macro2", "quote", "syn 2.0.90", + "unicode-xid", ] [[package]] @@ -2488,6 +2489,18 @@ dependencies = [ "zeroize", ] +[[package]] +name = "educe" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7bc049e1bd8cdeb31b68bbd586a9464ecf9f3944af3958a7a9d0f8b9799417" +dependencies = [ + "enum-ordinalize", + "proc-macro2", + "quote", + "syn 2.0.90", +] + [[package]] name = "either" version = "1.13.0" @@ -2599,6 +2612,26 @@ dependencies = [ "syn 2.0.90", ] +[[package]] +name = "enum-ordinalize" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea0dcfa4e54eeb516fe454635a95753ddd39acda650ce703031c6973e315dd5" +dependencies = [ + "enum-ordinalize-derive", +] + +[[package]] +name = "enum-ordinalize-derive" +version = "4.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d28318a75d4aead5c4db25382e8ef717932d0346600cacae6357eb5941bc5ff" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + [[package]] name = "equivalent" version = "1.0.1" @@ -3130,24 +3163,22 @@ dependencies = [ [[package]] name = "fuel-asm" -version = "0.58.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f325971bf9047ec70004f80a989e03456316bc19cbef3ff3a39a38b192ab56e" +version = "0.59.0" +source = "git+https://github.com/FuelLabs/fuel-vm?branch=dento/execution-trace#2cd645d73e30feae620f545d02af1986375f8478" dependencies = [ "bitflags 2.6.0", - "fuel-types 0.58.2", + "fuel-types 0.59.0", "serde", "strum 0.24.1", ] [[package]] name = "fuel-compression" -version = "0.58.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24e42841f56f76ed759b3f516e5188d5c42de47015bee951651660c13b6dfa6c" +version = "0.59.0" +source = "git+https://github.com/FuelLabs/fuel-vm?branch=dento/execution-trace#2cd645d73e30feae620f545d02af1986375f8478" dependencies = [ - "fuel-derive 0.58.2", - "fuel-types 0.58.2", + "fuel-derive 0.59.0", + "fuel-types 0.59.0", "serde", ] @@ -3658,7 +3689,7 @@ dependencies = [ "enum-iterator", "fuel-core-storage", "fuel-core-types 0.40.0", - "fuel-vm 0.58.2", + "fuel-vm 0.59.0", "impl-tools", "itertools 0.12.1", "mockall", @@ -3800,7 +3831,7 @@ dependencies = [ "bs58", "derivative", "derive_more 0.99.18", - "fuel-vm 0.58.2", + "fuel-vm 0.59.0", "rand", "secrecy", "serde", @@ -3857,15 +3888,14 @@ dependencies = [ [[package]] name = "fuel-crypto" -version = "0.58.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65e318850ca64890ff123a99b6b866954ef49da94ab9bc6827cf6ee045568585" +version = "0.59.0" +source = "git+https://github.com/FuelLabs/fuel-vm?branch=dento/execution-trace#2cd645d73e30feae620f545d02af1986375f8478" dependencies = [ "coins-bip32", "coins-bip39", "ecdsa", "ed25519-dalek", - "fuel-types 0.58.2", + "fuel-types 0.59.0", "k256", "lazy_static", "p256", @@ -3890,9 +3920,8 @@ dependencies = [ [[package]] name = "fuel-derive" -version = "0.58.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab0bc46a3552964bae5169e79b383761a54bd115ea66951a1a7a229edcefa55a" +version = "0.59.0" +source = "git+https://github.com/FuelLabs/fuel-vm?branch=dento/execution-trace#2cd645d73e30feae620f545d02af1986375f8478" dependencies = [ "proc-macro2", "quote", @@ -3928,13 +3957,12 @@ dependencies = [ [[package]] name = "fuel-merkle" -version = "0.58.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c79eca6a452311c70978a5df796c0f99f27e474b69719e0db4c1d82e68800d07" +version = "0.59.0" +source = "git+https://github.com/FuelLabs/fuel-vm?branch=dento/execution-trace#2cd645d73e30feae620f545d02af1986375f8478" dependencies = [ "derive_more 0.99.18", "digest 0.10.7", - "fuel-storage 0.58.2", + "fuel-storage 0.59.0", "hashbrown 0.13.2", "hex", "serde", @@ -3949,9 +3977,8 @@ checksum = "4c1b711f28553ddc5f3546711bd220e144ce4c1af7d9e9a1f70b2f20d9f5b791" [[package]] name = "fuel-storage" -version = "0.58.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d0c46b5d76b3e11197bd31e036cd8b1cb46c4d822cacc48836638080c6d2b76" +version = "0.59.0" +source = "git+https://github.com/FuelLabs/fuel-vm?branch=dento/execution-trace#2cd645d73e30feae620f545d02af1986375f8478" [[package]] name = "fuel-tx" @@ -3977,18 +4004,17 @@ dependencies = [ [[package]] name = "fuel-tx" -version = "0.58.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6723bb8710ba2b70516ac94d34459593225870c937670fb3afaf82e0354667ac" +version = "0.59.0" +source = "git+https://github.com/FuelLabs/fuel-vm?branch=dento/execution-trace#2cd645d73e30feae620f545d02af1986375f8478" dependencies = [ "bitflags 2.6.0", - "derivative", - "derive_more 0.99.18", - "fuel-asm 0.58.2", + "derive_more 1.0.0", + "educe", + "fuel-asm 0.59.0", "fuel-compression", - "fuel-crypto 0.58.2", - "fuel-merkle 0.58.2", - "fuel-types 0.58.2", + "fuel-crypto 0.59.0", + "fuel-merkle 0.59.0", + "fuel-types 0.59.0", "hashbrown 0.14.5", "itertools 0.10.5", "postcard", @@ -4011,11 +4037,10 @@ dependencies = [ [[package]] name = "fuel-types" -version = "0.58.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "982265415a99b5bd6277bc24194a233bb2e18764df11c937b3dbb11a02c9e545" +version = "0.59.0" +source = "git+https://github.com/FuelLabs/fuel-vm?branch=dento/execution-trace#2cd645d73e30feae620f545d02af1986375f8478" dependencies = [ - "fuel-derive 0.58.2", + "fuel-derive 0.59.0", "hex", "rand", "serde", @@ -4054,24 +4079,23 @@ dependencies = [ [[package]] name = "fuel-vm" -version = "0.58.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54b5362d7d072c72eec20581f67fc5400090c356a7f3ae77c79880b3b177b667" +version = "0.59.0" +source = "git+https://github.com/FuelLabs/fuel-vm?branch=dento/execution-trace#2cd645d73e30feae620f545d02af1986375f8478" dependencies = [ "anyhow", "async-trait", "backtrace", "bitflags 2.6.0", - "derivative", "derive_more 0.99.18", + "educe", "ethnum", - "fuel-asm 0.58.2", + "fuel-asm 0.59.0", "fuel-compression", - "fuel-crypto 0.58.2", - "fuel-merkle 0.58.2", - "fuel-storage 0.58.2", - "fuel-tx 0.58.2", - "fuel-types 0.58.2", + "fuel-crypto 0.59.0", + "fuel-merkle 0.59.0", + "fuel-storage 0.59.0", + "fuel-tx 0.59.0", + "fuel-types 0.59.0", "hashbrown 0.14.5", "itertools 0.10.5", "libm", @@ -4080,10 +4104,12 @@ dependencies = [ "primitive-types", "rand", "serde", + "serde-big-array", "serde_with", "sha3", "static_assertions", "strum 0.24.1", + "substrate-bn", "tai64", ] @@ -5404,6 +5430,9 @@ name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin 0.9.8", +] [[package]] name = "lazycell" @@ -8491,6 +8520,15 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-big-array" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11fc7cc2c76d73e0f27ee52abbd64eec84d46f370c88371120433196934e4b7f" +dependencies = [ + "serde", +] + [[package]] name = "serde_derive" version = "1.0.215" @@ -8972,6 +9010,19 @@ dependencies = [ "syn 2.0.90", ] +[[package]] +name = "substrate-bn" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b5bbfa79abbae15dd642ea8176a21a635ff3c00059961d1ea27ad04e5b441c" +dependencies = [ + "byteorder", + "crunchy", + "lazy_static", + "rand", + "rustc-hex", +] + [[package]] name = "subtle" version = "2.6.1" diff --git a/Cargo.toml b/Cargo.toml index 0a711e83ad3..e0e9a688744 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -87,7 +87,8 @@ fuel-core-xtask = { version = "0.0.0", path = "./xtask" } fuel-gas-price-algorithm = { version = "0.40.0", path = "crates/fuel-gas-price-algorithm" } # Fuel dependencies -fuel-vm-private = { version = "0.58.2", package = "fuel-vm", default-features = false } +fuel-vm-private = { git = "https://github.com/FuelLabs/fuel-vm", branch = "dento/execution-trace", package = "fuel-vm", default-features = false } +# fuel-vm-private = { version = "0.58.2", package = "fuel-vm", default-features = false } # Common dependencies anyhow = "1.0" diff --git a/crates/client/assets/schema.sdl b/crates/client/assets/schema.sdl index 1c291a567fd..ff521f3aba2 100644 --- a/crates/client/assets/schema.sdl +++ b/crates/client/assets/schema.sdl @@ -734,6 +734,10 @@ type Mutation { """ dryRun(txs: [HexString!]!, utxoValidation: Boolean, gasPrice: U64): [DryRunTransactionExecutionStatus!]! """ + Get execution trace for an already-executed transaction. + """ + traceTx(txId: HexString!, trigger: TraceTrigger!): [TraceTransactionExecutionStatus!]! + """ Submits transaction to the `TxPool`. Returns submitted transaction if the transaction is included in the `TxPool` without problems. @@ -1170,6 +1174,70 @@ type SuccessStatus { scalar Tai64Timestamp +type TraceFailureStatus { + programState: ProgramState + reason: String! + receipts: [Receipt!]! + totalGas: U64! + totalFee: U64! +} + +type TraceFrame { + """ + Register values + """ + registers: [U64!]! + """ + Changes to memory `(address, bytes)` that occurred since the last frame + """ + memoryDiff: [TraceFrameMemoryPatch!]! + """ + How many of the original receipts have been produced by this point + """ + receiptCount: Int! +} + +type TraceFrameMemoryPatch { + """ + Start address + """ + start: Int! + """ + Bytes of data + """ + bytes: HexString! +} + +type TraceSuccessStatus { + programState: ProgramState + receipts: [Receipt!]! + executionTrace: [TraceFrame!] + totalGas: U64! + totalFee: U64! +} + +type TraceTransactionExecutionStatus { + id: TransactionId! + status: TraceTransactionStatus! + receipts: [Receipt!]! +} + +union TraceTransactionStatus = TraceSuccessStatus | TraceFailureStatus + +""" +One of the films in the Star Wars Trilogy +""" +enum TraceTrigger { + """ + After each instruction + """ + ON_INSTRUCTION + """ + After an instruction has created a receipt + """ + ON_RECEIPT +} + type Transaction { id: TransactionId! inputAssetIds: [AssetId!] diff --git a/crates/fuel-core/src/graphql_api/database.rs b/crates/fuel-core/src/graphql_api/database.rs index 6feaaabc5dc..36f02562ca6 100644 --- a/crates/fuel-core/src/graphql_api/database.rs +++ b/crates/fuel-core/src/graphql_api/database.rs @@ -261,8 +261,13 @@ impl StorageSize for ReadView { } impl StorageRead for ReadView { - fn read(&self, key: &BlobId, buf: &mut [u8]) -> Result, Self::Error> { - StorageRead::::read(self.on_chain.as_ref(), key, buf) + fn read( + &self, + key: &BlobId, + offset: usize, + buf: &mut [u8], + ) -> Result, Self::Error> { + StorageRead::::read(self.on_chain.as_ref(), key, offset, buf) } fn read_alloc(&self, key: &BlobId) -> Result>, Self::Error> { diff --git a/crates/fuel-core/src/graphql_api/ports.rs b/crates/fuel-core/src/graphql_api/ports.rs index 0463ee3992e..ec174e4dc86 100644 --- a/crates/fuel-core/src/graphql_api/ports.rs +++ b/crates/fuel-core/src/graphql_api/ports.rs @@ -53,7 +53,10 @@ use fuel_core_types::{ ContractId, Nonce, }, - fuel_vm::interpreter::Memory, + fuel_vm::interpreter::{ + trace::Trigger, + Memory, + }, services::{ executor::TransactionExecutionStatus, graphql_api::ContractBalance, @@ -234,6 +237,12 @@ pub trait BlockProducerPort: Send + Sync { utxo_validation: Option, gas_price: Option, ) -> anyhow::Result>; + + async fn exection_trace_block( + &self, + height: BlockHeight, + trigger: Trigger, + ) -> anyhow::Result>; } #[async_trait::async_trait] diff --git a/crates/fuel-core/src/schema/chain.rs b/crates/fuel-core/src/schema/chain.rs index 9acdbb01940..17b7c464ea8 100644 --- a/crates/fuel-core/src/schema/chain.rs +++ b/crates/fuel-core/src/schema/chain.rs @@ -280,7 +280,8 @@ impl GasCosts { GasCostsValues::V1(_) | GasCostsValues::V2(_) | GasCostsValues::V3(_) - | GasCostsValues::V4(_) => GasCostsVersion::V1, + | GasCostsValues::V4(_) + | GasCostsValues::V5(_) => GasCostsVersion::V1, } } diff --git a/crates/fuel-core/src/schema/dap.rs b/crates/fuel-core/src/schema/dap.rs index 7e920f3ead2..d554c3aae00 100644 --- a/crates/fuel-core/src/schema/dap.rs +++ b/crates/fuel-core/src/schema/dap.rs @@ -132,7 +132,7 @@ impl ConcreteStorage { let fee_params = params.fee_params(); let ready_tx = checked_tx - .into_ready(GAS_PRICE, gas_costs, fee_params) + .into_ready(GAS_PRICE, gas_costs, fee_params, None) .map_err(|e| { anyhow!("Failed to apply dynamic values to checked tx: {:?}", e) })?; @@ -176,7 +176,7 @@ impl ConcreteStorage { let fee_params = params.fee_params(); let ready_tx = checked_tx - .into_ready(GAS_PRICE, gas_costs, fee_params) + .into_ready(GAS_PRICE, gas_costs, fee_params, None) .map_err(|e| { anyhow!("Failed to apply dynamic values to checked tx: {:?}", e) })?; @@ -456,7 +456,7 @@ impl DapMutation { match checked_tx { CheckedTransaction::Script(script) => { let ready_tx = script - .into_ready(GAS_PRICE, gas_costs, fee_params) + .into_ready(GAS_PRICE, gas_costs, fee_params, None) .map_err(|e| { anyhow!("Failed to apply dynamic values to checked tx: {:?}", e) })?; diff --git a/crates/fuel-core/src/schema/tx.rs b/crates/fuel-core/src/schema/tx.rs index 8ee21edf972..799e0f51db1 100644 --- a/crates/fuel-core/src/schema/tx.rs +++ b/crates/fuel-core/src/schema/tx.rs @@ -51,6 +51,7 @@ use fuel_core_types::{ Bytes32, Cacheable, Transaction as FuelTx, + TxId, UniqueIdentifier, }, fuel_types::{ @@ -73,6 +74,8 @@ use std::{ }; use types::{ DryRunTransactionExecutionStatus, + TraceTransactionExecutionStatus, + TraceTrigger, Transaction, }; @@ -331,6 +334,38 @@ impl TxMutation { Ok(tx_statuses) } + /// Get execution trace for an already-executed transaction. + #[graphql(complexity = "query_costs().dry_run + child_complexity")] + async fn trace_tx( + &self, + ctx: &Context<'_>, + tx_id: HexString, + trigger: TraceTrigger, + ) -> async_graphql::Result> { + let tx_id = TxId::try_from(tx_id.0.as_slice()).expect("TOOD: handle this"); + + // Get the block height of the transaction + let fuel_core_types::services::txpool::TransactionStatus::Success { + block_height, + .. + } = ctx.read_view()?.tx_status(&tx_id)? + else { + return Err(async_graphql::Error::new( + "The transaction is not part of any block (yet)", + )); + }; + + let block_producer = ctx.data_unchecked::(); + let status = block_producer + .exection_trace_block(block_height, trigger.into()) + .await?; + dbg!(&status); + Ok(status + .into_iter() + .map(TraceTransactionExecutionStatus) + .collect()) + } + /// Submits transaction to the `TxPool`. /// /// Returns submitted transaction if the transaction is included in the `TxPool` without problems. diff --git a/crates/fuel-core/src/schema/tx/types.rs b/crates/fuel-core/src/schema/tx/types.rs index effbc463d0c..cba7165460b 100644 --- a/crates/fuel-core/src/schema/tx/types.rs +++ b/crates/fuel-core/src/schema/tx/types.rs @@ -78,7 +78,14 @@ use fuel_core_types::{ TxId, }, fuel_types::canonical::Serialize, - fuel_vm::ProgramState as VmProgramState, + fuel_vm::{ + consts::VM_REGISTER_COUNT, + interpreter::trace::{ + Frame, + Trigger, + }, + ProgramState as VmProgramState, + }, services::{ executor::{ TransactionExecutionResult, @@ -864,6 +871,7 @@ impl DryRunTransactionStatus { receipts, total_gas, total_fee, + execution_trace: _, // Discard, as dry run doesn't support this } => DryRunTransactionStatus::Success(DryRunSuccessStatus { result, receipts, @@ -875,6 +883,7 @@ impl DryRunTransactionStatus { receipts, total_gas, total_fee, + execution_trace: _, // Discard, as dry run doesn't support this } => DryRunTransactionStatus::Failed(DryRunFailureStatus { result, receipts, @@ -885,6 +894,62 @@ impl DryRunTransactionStatus { } } +/// One of the films in the Star Wars Trilogy +#[derive(Enum, Copy, Clone, Eq, PartialEq)] +pub enum TraceTrigger { + /// After each instruction + OnInstruction, + /// After an instruction has created a receipt + OnReceipt, +} +impl From for Trigger { + fn from(value: TraceTrigger) -> Self { + match value { + TraceTrigger::OnInstruction => Trigger::OnInstruction, + TraceTrigger::OnReceipt => Trigger::OnReceipt, + } + } +} + +#[derive(Union, Debug)] +pub enum TraceTransactionStatus { + Success(TraceSuccessStatus), + Failed(TraceFailureStatus), +} + +impl TraceTransactionStatus { + pub fn new(tx_status: TransactionExecutionResult) -> Self { + match tx_status { + TransactionExecutionResult::Success { + result, + receipts, + total_gas, + total_fee, + execution_trace, + } => TraceTransactionStatus::Success(TraceSuccessStatus { + result, + receipts, + execution_trace, + total_gas, + total_fee, + }), + TransactionExecutionResult::Failed { + result, + receipts, + execution_trace, + total_gas, + total_fee, + } => TraceTransactionStatus::Failed(TraceFailureStatus { + result, + receipts, + execution_trace, + total_gas, + total_fee, + }), + } + } +} + fn input_contracts(tx: &Tx) -> IntoIter<&fuel_core_types::fuel_types::ContractId> where Tx: Inputs, @@ -980,6 +1045,153 @@ impl DryRunTransactionExecutionStatus { } } +#[derive(Debug)] +pub struct TraceFrame(Frame); + +impl From for TraceFrame { + fn from(frame: Frame) -> Self { + Self(frame) + } +} + +#[Object] +impl TraceFrame { + /// Register values + async fn registers(&self) -> [U64; VM_REGISTER_COUNT] { + self.0.registers.map(|r| r.into()) + } + + /// Changes to memory `(address, bytes)` that occurred since the last frame + async fn memory_diff(&self) -> Vec { + self.0 + .memory_diff + .clone() + .into_iter().map(|c| c.into()).collect() + } + + /// How many of the original receipts have been produced by this point + async fn receipt_count(&self) -> usize { + self.0.receipt_count + } +} + +#[derive(Debug)] +pub struct TraceFrameMemoryPatch { + start: usize, + bytes: Vec, +} +impl From for TraceFrameMemoryPatch { + fn from(change: fuel_core_types::fuel_vm::interpreter::MemorySliceChange) -> Self { + Self { start: change.global_start, bytes: change.data } + } +} + +#[Object] +impl TraceFrameMemoryPatch { + /// Start address + async fn start(&self) -> u32 { + self.start as u32 + } + + /// Bytes of data + async fn bytes(&self) -> HexString { + self.bytes.clone().into() + } +} + +#[derive(Debug)] +pub struct TraceSuccessStatus { + result: Option, + receipts: Vec, + execution_trace: Vec, + total_gas: u64, + total_fee: u64, +} + +#[Object] +impl TraceSuccessStatus { + async fn program_state(&self) -> Option { + self.result.map(Into::into) + } + + async fn receipts(&self) -> Vec { + self.receipts.iter().map(Into::into).collect() + } + + async fn execution_trace(&self) -> Vec { + self.execution_trace + .clone() + .into_iter() + .map(TraceFrame::from) + .collect() + } + + async fn total_gas(&self) -> U64 { + self.total_gas.into() + } + + async fn total_fee(&self) -> U64 { + self.total_fee.into() + } +} + +#[derive(Debug)] +pub struct TraceFailureStatus { + result: Option, + receipts: Vec, + execution_trace: Vec, + total_gas: u64, + total_fee: u64, +} + +#[Object] +impl TraceFailureStatus { + async fn program_state(&self) -> Option { + self.result.map(Into::into) + } + + async fn reason(&self) -> String { + TransactionExecutionResult::reason(&self.receipts, &self.result) + } + + async fn receipts(&self) -> Vec { + self.receipts.iter().map(Into::into).collect() + } + + async fn execution_trace(&self) -> Vec { + self.execution_trace + .clone() + .into_iter() + .map(TraceFrame::from) + .collect() + } + + async fn total_gas(&self) -> U64 { + self.total_gas.into() + } + + async fn total_fee(&self) -> U64 { + self.total_fee.into() + } +} + +pub struct TraceTransactionExecutionStatus(pub TransactionExecutionStatus); + +#[Object] +impl TraceTransactionExecutionStatus { + async fn id(&self) -> TransactionId { + TransactionId(self.0.id) + } + + async fn status(&self) -> TraceTransactionStatus { + TraceTransactionStatus::new(self.0.result.clone()) + } + + async fn receipts(&self) -> Vec { + self.0.result.receipts().iter().map(Into::into).collect() + } +} + #[tracing::instrument(level = "debug", skip(query, txpool), ret, err)] pub(crate) async fn get_tx_status( id: fuel_core_types::fuel_types::Bytes32, diff --git a/crates/fuel-core/src/service/adapters/graphql_api.rs b/crates/fuel-core/src/service/adapters/graphql_api.rs index 43cd0471794..b9af08535a8 100644 --- a/crates/fuel-core/src/service/adapters/graphql_api.rs +++ b/crates/fuel-core/src/service/adapters/graphql_api.rs @@ -41,6 +41,7 @@ use fuel_core_types::{ TxId, }, fuel_types::BlockHeight, + fuel_vm::interpreter::trace::Trigger, services::{ block_importer::SharedImportResult, executor::TransactionExecutionStatus, @@ -123,6 +124,16 @@ impl BlockProducerPort for BlockProducerAdapter { .dry_run(transactions, height, time, utxo_validation, gas_price) .await } + + async fn exection_trace_block( + &self, + height: BlockHeight, + trigger: Trigger, + ) -> anyhow::Result> { + self.block_producer + .exection_trace_block(height, trigger) + .await + } } #[async_trait::async_trait] diff --git a/crates/fuel-core/src/service/adapters/producer.rs b/crates/fuel-core/src/service/adapters/producer.rs index 7a977f69228..e1965c4755f 100644 --- a/crates/fuel-core/src/service/adapters/producer.rs +++ b/crates/fuel-core/src/service/adapters/producer.rs @@ -33,6 +33,7 @@ use fuel_core_storage::{ ConsensusParametersVersions, FuelBlocks, StateTransitionBytecodeVersions, + Transactions, }, transactional::Changes, Result as StorageResult, @@ -40,15 +41,18 @@ use fuel_core_storage::{ }; use fuel_core_types::{ blockchain::{ - block::CompressedBlock, + block::{ + Block, + CompressedBlock, + }, header::{ ConsensusParametersVersion, StateTransitionBytecodeVersion, }, primitives::DaBlockHeight, }, - fuel_tx, fuel_tx::{ + self, ConsensusParameters, Transaction, }, @@ -56,6 +60,7 @@ use fuel_core_types::{ BlockHeight, Bytes32, }, + fuel_vm::interpreter::trace::Trigger, services::{ block_producer::Components, executor::{ @@ -124,6 +129,16 @@ impl fuel_core_producer::ports::DryRunner for ExecutorAdapter { } } +impl fuel_core_producer::ports::BlockExecutionTracer for ExecutorAdapter { + fn execution_trace( + &self, + block: &Block, + trigger: Trigger, + ) -> ExecutorResult> { + self.executor.execution_traces(block, trigger) + } +} + #[async_trait::async_trait] impl fuel_core_producer::ports::Relayer for MaybeRelayerAdapter { async fn wait_for_at_least_height( @@ -221,6 +236,12 @@ impl fuel_core_producer::ports::BlockProducerDatabase for OnChainIterableKeyValu .ok_or(not_found!(FuelBlocks)) } + fn get_transaction(&self, id: &fuel_tx::TxId) -> StorageResult> { + self.storage::() + .get(id)? + .ok_or(not_found!(Transactions)) + } + fn block_header_merkle_root(&self, height: &BlockHeight) -> StorageResult { self.storage::().root(height).map(Into::into) } diff --git a/crates/fuel-core/src/state/data_source.rs b/crates/fuel-core/src/state/data_source.rs index cf107d0e1b9..f30f3ee8268 100644 --- a/crates/fuel-core/src/state/data_source.rs +++ b/crates/fuel-core/src/state/data_source.rs @@ -67,9 +67,10 @@ where &self, key: &[u8], column: Self::Column, + offset: usize, buf: &mut [u8], ) -> StorageResult> { - self.data.read(key, column, buf) + self.data.read(key, column, offset, buf) } } diff --git a/crates/fuel-core/src/state/generic_database.rs b/crates/fuel-core/src/state/generic_database.rs index b6f5f2ea464..5a31591268f 100644 --- a/crates/fuel-core/src/state/generic_database.rs +++ b/crates/fuel-core/src/state/generic_database.rs @@ -79,8 +79,13 @@ where M: Mappable, StructuredStorage: StorageRead, { - fn read(&self, key: &M::Key, buf: &mut [u8]) -> Result, Self::Error> { - self.storage.storage::().read(key, buf) + fn read( + &self, + key: &M::Key, + offset: usize, + buf: &mut [u8], + ) -> Result, Self::Error> { + self.storage.storage::().read(key, offset, buf) } fn read_alloc(&self, key: &M::Key) -> Result>, Self::Error> { @@ -124,9 +129,10 @@ where &self, key: &[u8], column: Self::Column, + offset: usize, buf: &mut [u8], ) -> StorageResult> { - KeyValueInspect::read(&self.storage, key, column, buf) + KeyValueInspect::read(&self.storage, key, column, offset, buf) } } diff --git a/crates/fuel-core/src/state/historical_rocksdb.rs b/crates/fuel-core/src/state/historical_rocksdb.rs index a0bd39b2801..059fb35f12b 100644 --- a/crates/fuel-core/src/state/historical_rocksdb.rs +++ b/crates/fuel-core/src/state/historical_rocksdb.rs @@ -534,9 +534,11 @@ where &self, key: &[u8], column: Self::Column, + offset: usize, buf: &mut [u8], ) -> StorageResult> { - self.db.read(key, Column::OriginalColumn(column), buf) + self.db + .read(key, Column::OriginalColumn(column), offset, buf) } } diff --git a/crates/fuel-core/src/state/iterable_key_value_view.rs b/crates/fuel-core/src/state/iterable_key_value_view.rs index 40ab3483ede..1d8fd02cbbb 100644 --- a/crates/fuel-core/src/state/iterable_key_value_view.rs +++ b/crates/fuel-core/src/state/iterable_key_value_view.rs @@ -67,9 +67,10 @@ where &self, key: &[u8], column: Self::Column, + offset: usize, buf: &mut [u8], ) -> StorageResult> { - self.0.read(key, column, buf) + self.0.read(key, column, offset, buf) } } diff --git a/crates/fuel-core/src/state/key_value_view.rs b/crates/fuel-core/src/state/key_value_view.rs index 9e70037fc21..e507e83329a 100644 --- a/crates/fuel-core/src/state/key_value_view.rs +++ b/crates/fuel-core/src/state/key_value_view.rs @@ -54,8 +54,9 @@ where &self, key: &[u8], column: Self::Column, + offset: usize, buf: &mut [u8], ) -> StorageResult> { - self.0.read(key, column, buf) + self.0.read(key, column, offset, buf) } } diff --git a/crates/fuel-core/src/state/rocks_db.rs b/crates/fuel-core/src/state/rocks_db.rs index e69c6289bed..cf76517959c 100644 --- a/crates/fuel-core/src/state/rocks_db.rs +++ b/crates/fuel-core/src/state/rocks_db.rs @@ -758,6 +758,7 @@ where &self, key: &[u8], column: Self::Column, + offset: usize, mut buf: &mut [u8], ) -> StorageResult> { self.metrics.read_meter.inc(); @@ -769,10 +770,12 @@ where .get_pinned_cf_opt(&self.cf(column), key, &self.read_options) .map_err(|e| DatabaseError::Other(e.into()))? .map(|value| { - let read = value.len(); - std::io::Write::write_all(&mut buf, value.as_ref()) + if offset >= value.len() { + return Ok(0); + } + std::io::Write::write_all(&mut buf, &value.as_ref()[offset..]) .map_err(|e| DatabaseError::Other(anyhow::anyhow!(e)))?; - StorageResult::Ok(read) + StorageResult::Ok(value.len() - offset) }) .transpose()?; diff --git a/crates/services/executor/src/executor.rs b/crates/services/executor/src/executor.rs index bc690c52d3d..14fb0094290 100644 --- a/crates/services/executor/src/executor.rs +++ b/crates/services/executor/src/executor.rs @@ -112,11 +112,7 @@ use fuel_core_types::{ IntoChecked, }, interpreter::{ - CheckedMetadata as CheckedMetadataTrait, - ExecutableTransaction, - InterpreterParams, - Memory, - MemoryInstance, + trace::Frame, CheckedMetadata as CheckedMetadataTrait, ExecutableTransaction, InterpreterParams, Memory, MemoryInstance }, state::StateTransition, Backtrace as FuelBacktrace, @@ -253,6 +249,9 @@ pub struct ExecutionOptions { pub extra_tx_checks: bool, /// Print execution backtraces if transaction execution reverts. pub backtrace: bool, + /// Record execution traces for each transaction with the given trigger + #[serde(default)] + pub execution_trace: Option, } /// The executor instance performs block production and validation. Given a block, it will execute all @@ -1252,7 +1251,7 @@ where checked_tx = self.extra_tx_checks(checked_tx, header, storage_tx, memory)?; } - let (reverted, state, tx, receipts) = self.attempt_tx_execution_with_vm( + let (reverted, state, tx, receipts, trace_frames) = self.attempt_tx_execution_with_vm( checked_tx, header, coinbase_contract_id, @@ -1280,6 +1279,7 @@ where &tx, execution_data, receipts, + trace_frames, gas_price, reverted, state, @@ -1352,6 +1352,7 @@ where result: TransactionExecutionResult::Success { result: None, receipts: vec![], + execution_trace: Vec::new(), total_gas: 0, total_fee: 0, }, @@ -1459,6 +1460,7 @@ where tx: &Tx, execution_data: &mut ExecutionData, receipts: Vec, + execution_trace: Vec, gas_price: Word, reverted: bool, state: ProgramState, @@ -1493,6 +1495,7 @@ where TransactionExecutionResult::Failed { result: Some(state), receipts, + execution_trace, total_gas: used_gas, total_fee: tx_fee, } @@ -1501,6 +1504,7 @@ where TransactionExecutionResult::Success { result: Some(state), receipts, + execution_trace, total_gas: used_gas, total_fee: tx_fee, } @@ -1598,7 +1602,7 @@ where gas_price: Word, storage_tx: &mut TxStorageTransaction, memory: &mut MemoryInstance, - ) -> ExecutorResult<(bool, ProgramState, Tx, Vec)> + ) -> ExecutorResult<(bool, ProgramState, Tx, Vec, Vec)> where Tx: ExecutableTransaction + Cacheable, ::Metadata: CheckedMetadataTrait + Send + Sync, @@ -1626,7 +1630,7 @@ where .iter() .map(|input| input.predicate_gas_used()) .collect(); - let ready_tx = checked_tx.into_ready(gas_price, gas_costs, fee_params)?; + let ready_tx = checked_tx.into_ready(gas_price, gas_costs, fee_params, None)?; // TODO: block_height argument? let mut vm = Interpreter::with_storage( memory, @@ -1634,6 +1638,11 @@ where InterpreterParams::new(gas_price, &self.consensus_params), ); + let mut trace_tmp_memory = MemoryInstance::new(); + if let Some(trigger) = self.options.execution_trace { + vm = vm.with_trace_recording(trigger, &mut trace_tmp_memory); + } + let vm_result: StateTransition<_> = vm .transact(ready_tx) .map_err(|error| ExecutorError::VmExecution { @@ -1643,6 +1652,7 @@ where .into(); let reverted = vm_result.should_revert(); + let trace_frames = vm.trace_frames().to_vec(); let (state, mut tx, receipts): (_, Tx, _) = vm_result.into_inner(); #[cfg(debug_assertions)] { @@ -1664,7 +1674,7 @@ where } self.update_tx_outputs(storage_tx, tx_id, &mut tx)?; - Ok((reverted, state, tx, receipts)) + Ok((reverted, state, tx, receipts, trace_frames)) } fn verify_inputs_exist_and_values_match( diff --git a/crates/services/producer/src/block_producer.rs b/crates/services/producer/src/block_producer.rs index f5ad44be901..ca1b4f9d21b 100644 --- a/crates/services/producer/src/block_producer.rs +++ b/crates/services/producer/src/block_producer.rs @@ -17,6 +17,7 @@ use anyhow::{ use fuel_core_storage::transactional::{ AtomicView, Changes, + HistoricalView, }; use fuel_core_types::{ blockchain::{ @@ -39,6 +40,7 @@ use fuel_core_types::{ BlockHeight, Bytes32, }, + fuel_vm::interpreter::trace::Trigger, services::{ block_producer::Components, executor::{ @@ -377,6 +379,35 @@ where } } +impl + Producer +where + ViewProvider: HistoricalView + 'static, + ViewProvider::LatestView: BlockProducerDatabase, + Executor: ports::BlockExecutionTracer + 'static, + GasPriceProvider: GasPriceProviderConstraint, + ConsensusProvider: ConsensusParametersProvider, +{ + /// Re-executes an old block, getting full execution traces. + pub async fn exection_trace_block( + &self, + height: BlockHeight, + trigger: Trigger, + ) -> anyhow::Result> { + let view = self.view_provider.latest_view()?; + + let block = view.get_block(&height)?; + let transactions = block + .transactions() + .iter() + .map(|id| view.get_transaction(id).map(|tx| tx.into_owned())) + .collect::, _>>()?; + let block = block.into_owned().uncompress(transactions); + + Ok(self.executor.execution_trace(&block, trigger)?) + } +} + pub const NO_NEW_DA_HEIGHT_FOUND: &str = "No new da_height found"; impl diff --git a/crates/services/producer/src/ports.rs b/crates/services/producer/src/ports.rs index 6875099951a..1b26f4045e9 100644 --- a/crates/services/producer/src/ports.rs +++ b/crates/services/producer/src/ports.rs @@ -4,7 +4,10 @@ use fuel_core_storage::{ }; use fuel_core_types::{ blockchain::{ - block::CompressedBlock, + block::{ + Block, + CompressedBlock, + }, header::{ ConsensusParametersVersion, StateTransitionBytecodeVersion, @@ -14,8 +17,10 @@ use fuel_core_types::{ fuel_tx::{ Bytes32, Transaction, + TxId, }, fuel_types::BlockHeight, + fuel_vm::interpreter::trace::Trigger, services::{ block_producer::Components, executor::{ @@ -34,6 +39,9 @@ pub trait BlockProducerDatabase: Send + Sync { /// Gets the committed block at the `height`. fn get_block(&self, height: &BlockHeight) -> StorageResult>; + /// Gets the transaction by id + fn get_transaction(&self, id: &TxId) -> StorageResult>; + /// Gets the block header BMT MMR root at `height`. fn block_header_merkle_root(&self, height: &BlockHeight) -> StorageResult; @@ -101,3 +109,11 @@ pub trait DryRunner: Send + Sync { utxo_validation: Option, ) -> ExecutorResult>; } + +pub trait BlockExecutionTracer: Send + Sync { + fn execution_trace( + &self, + block: &Block, + trigger: Trigger, + ) -> ExecutorResult>; +} diff --git a/crates/services/upgradable-executor/src/config.rs b/crates/services/upgradable-executor/src/config.rs index 7367cf318e3..3ba9de4f7ea 100644 --- a/crates/services/upgradable-executor/src/config.rs +++ b/crates/services/upgradable-executor/src/config.rs @@ -20,6 +20,7 @@ impl From<&Config> for ExecutionOptions { Self { extra_tx_checks: value.utxo_validation_default, backtrace: value.backtrace, + execution_trace: None, } } } diff --git a/crates/services/upgradable-executor/src/executor.rs b/crates/services/upgradable-executor/src/executor.rs index 2b8c72fa5af..a144375ca07 100644 --- a/crates/services/upgradable-executor/src/executor.rs +++ b/crates/services/upgradable-executor/src/executor.rs @@ -35,6 +35,7 @@ use fuel_core_types::{ }, fuel_tx::Transaction, fuel_types::BlockHeight, + fuel_vm::interpreter::trace::Trigger, services::{ block_producer::Components, executor::{ @@ -341,6 +342,7 @@ where let options = ExecutionOptions { extra_tx_checks: utxo_validation, backtrace: self.config.backtrace, + execution_trace: None, }; let component = Components { @@ -374,6 +376,17 @@ where self.validate_inner(block, options) } + pub fn execution_traces( + &self, + block: &Block, + trigger: Trigger, + ) -> ExecutorResult> { + let mut options: ExecutionOptions = self.config.as_ref().into(); + options.execution_trace = Some(trigger); + self.validate_inner(block, options) + .map(|v| v.into_result().tx_status) + } + #[cfg(feature = "wasm-executor")] fn produce_inner( &self, diff --git a/crates/storage/src/kv_store.rs b/crates/storage/src/kv_store.rs index 3e7c1359114..fe9e94d86f2 100644 --- a/crates/storage/src/kv_store.rs +++ b/crates/storage/src/kv_store.rs @@ -67,17 +67,23 @@ pub trait KeyValueInspect { &self, key: &[u8], column: Self::Column, + offset: usize, buf: &mut [u8], ) -> StorageResult> { self.get(key, column)? .map(|value| { let read = value.len(); - if read != buf.len() { + if offset + read != buf.len() { return Err(StorageError::Other(anyhow::anyhow!( - "Buffer size is not equal to the value size" + "Buffer size is not equal to the value size after offset" ))); } - buf.copy_from_slice(value.as_ref()); + if offset >= buf.len() { + return Err(StorageError::Other(anyhow::anyhow!( + "Offset too large for the buffer" + ))); + } + buf.copy_from_slice(&value.as_ref()[offset..]); Ok(read) }) .transpose() @@ -111,9 +117,10 @@ where &self, key: &[u8], column: Self::Column, + offset: usize, buf: &mut [u8], ) -> StorageResult> { - self.deref().read(key, column, buf) + self.deref().read(key, column, offset, buf) } } diff --git a/crates/storage/src/structured_storage.rs b/crates/storage/src/structured_storage.rs index 122792cbf8f..4e1239c5982 100644 --- a/crates/storage/src/structured_storage.rs +++ b/crates/storage/src/structured_storage.rs @@ -147,9 +147,10 @@ where &self, key: &[u8], column: Self::Column, + offset: usize, buf: &mut [u8], ) -> StorageResult> { - self.inner.read(key, column, buf) + self.inner.read(key, column, offset, buf) } } @@ -358,6 +359,7 @@ where fn read( &self, key: &::Key, + offset: usize, buf: &mut [u8], ) -> Result, Self::Error> { let key_encoder = @@ -365,8 +367,12 @@ where key, ); let key_bytes = key_encoder.as_bytes(); - self.inner - .read(key_bytes.as_ref(), ::column(), buf) + self.inner.read( + key_bytes.as_ref(), + ::column(), + offset, + buf, + ) } fn read_alloc( diff --git a/crates/storage/src/transactional.rs b/crates/storage/src/transactional.rs index 4494393ad33..e43ad3b77e9 100644 --- a/crates/storage/src/transactional.rs +++ b/crates/storage/src/transactional.rs @@ -392,24 +392,30 @@ where &self, key: &[u8], column: Self::Column, + offset: usize, buf: &mut [u8], ) -> StorageResult> { if let Some(operation) = self.get_from_changes(key, column) { match operation { WriteOperation::Insert(value) => { let read = value.len(); - if read != buf.len() { + if offset + read != buf.len() { return Err(crate::Error::Other(anyhow::anyhow!( - "Buffer size is not equal to the value size" + "Buffer size is not equal to the value size after offset" ))); } - buf.copy_from_slice(value.as_ref()); + if offset >= buf.len() { + return Err(crate::Error::Other(anyhow::anyhow!( + "Offset too large for the buffer" + ))); + } + buf.copy_from_slice(&value.as_ref()[offset..]); Ok(Some(read)) } WriteOperation::Remove => Ok(None), } } else { - self.storage.read(key, column, buf) + self.storage.read(key, column, offset, buf) } } } diff --git a/crates/storage/src/vm_storage.rs b/crates/storage/src/vm_storage.rs index 92ab65ffdaf..99f6c360628 100644 --- a/crates/storage/src/vm_storage.rs +++ b/crates/storage/src/vm_storage.rs @@ -190,8 +190,13 @@ impl StorageRead for VmStorage where D: StorageRead, { - fn read(&self, key: &M::Key, buf: &mut [u8]) -> Result, Self::Error> { - StorageRead::::read(&self.database, key, buf) + fn read( + &self, + key: &M::Key, + offset: usize, + buf: &mut [u8], + ) -> Result, Self::Error> { + StorageRead::::read(&self.database, key, offset, buf) } fn read_alloc( diff --git a/crates/types/Cargo.toml b/crates/types/Cargo.toml index 897869db7c1..10e5922ca3b 100644 --- a/crates/types/Cargo.toml +++ b/crates/types/Cargo.toml @@ -19,7 +19,7 @@ version = { workspace = true } [dependencies] anyhow = { workspace = true } bs58 = { version = "0.5", optional = true } -derivative = { version = "2" } +derivative = { version = "2", features = [ "use_core"]} derive_more = { version = "0.99" } fuel-vm-private = { workspace = true, default-features = false, features = [ "alloc", diff --git a/crates/types/src/services/executor.rs b/crates/types/src/services/executor.rs index 357e3697638..cfec902a23a 100644 --- a/crates/types/src/services/executor.rs +++ b/crates/types/src/services/executor.rs @@ -39,6 +39,7 @@ use alloc::{ string::String, vec::Vec, }; +use fuel_vm_private::interpreter::trace::Frame; /// The alias for executor result. pub type Result = core::result::Result; @@ -197,6 +198,8 @@ pub enum TransactionExecutionResult { result: Option, /// The receipts generated by the executed transaction. receipts: Vec, + /// Full execution trace, if recorded. + execution_trace: Vec, /// The total gas used by the transaction. total_gas: u64, /// The total fee paid by the transaction. @@ -208,6 +211,8 @@ pub enum TransactionExecutionResult { result: Option, /// The receipts generated by the executed transaction. receipts: Vec, + /// Full execution trace, if recorded. + execution_trace: Vec, /// The total gas used by the transaction. total_gas: u64, /// The total fee paid by the transaction. diff --git a/crates/types/src/services/txpool.rs b/crates/types/src/services/txpool.rs index c684a78b092..dd0ee5c835f 100644 --- a/crates/types/src/services/txpool.rs +++ b/crates/types/src/services/txpool.rs @@ -377,6 +377,7 @@ pub fn from_executor_to_status( receipts, total_gas, total_fee, + execution_trace: _, // Discarded, txpool doesn't use these } => TransactionStatus::Success { block_height, time, @@ -390,6 +391,7 @@ pub fn from_executor_to_status( receipts, total_gas, total_fee, + execution_trace: _, // Discarded, txpool doesn't use these } => TransactionStatus::Failed { block_height, time, From 1e018f101a475df9a04c1123efae3da3ff73f600 Mon Sep 17 00:00:00 2001 From: Hannes Karppila Date: Wed, 11 Dec 2024 08:46:47 +0200 Subject: [PATCH 02/25] Update schema.sdl --- crates/client/assets/schema.sdl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/client/assets/schema.sdl b/crates/client/assets/schema.sdl index ff521f3aba2..85272ff654b 100644 --- a/crates/client/assets/schema.sdl +++ b/crates/client/assets/schema.sdl @@ -1178,6 +1178,7 @@ type TraceFailureStatus { programState: ProgramState reason: String! receipts: [Receipt!]! + executionTrace: [TraceFrame!]! totalGas: U64! totalFee: U64! } @@ -1211,7 +1212,7 @@ type TraceFrameMemoryPatch { type TraceSuccessStatus { programState: ProgramState receipts: [Receipt!]! - executionTrace: [TraceFrame!] + executionTrace: [TraceFrame!]! totalGas: U64! totalFee: U64! } From 5f6c07913fc59828d6e3a328c4ead8b982a25572 Mon Sep 17 00:00:00 2001 From: Hannes Karppila Date: Wed, 11 Dec 2024 08:50:09 +0200 Subject: [PATCH 03/25] Fix typo exection => execution --- crates/fuel-core/src/graphql_api/ports.rs | 2 +- crates/fuel-core/src/schema/tx.rs | 2 +- crates/fuel-core/src/service/adapters/graphql_api.rs | 4 ++-- crates/services/producer/src/block_producer.rs | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/fuel-core/src/graphql_api/ports.rs b/crates/fuel-core/src/graphql_api/ports.rs index ec174e4dc86..07afd93874e 100644 --- a/crates/fuel-core/src/graphql_api/ports.rs +++ b/crates/fuel-core/src/graphql_api/ports.rs @@ -238,7 +238,7 @@ pub trait BlockProducerPort: Send + Sync { gas_price: Option, ) -> anyhow::Result>; - async fn exection_trace_block( + async fn execution_trace_block( &self, height: BlockHeight, trigger: Trigger, diff --git a/crates/fuel-core/src/schema/tx.rs b/crates/fuel-core/src/schema/tx.rs index 799e0f51db1..ef46afbf951 100644 --- a/crates/fuel-core/src/schema/tx.rs +++ b/crates/fuel-core/src/schema/tx.rs @@ -357,7 +357,7 @@ impl TxMutation { let block_producer = ctx.data_unchecked::(); let status = block_producer - .exection_trace_block(block_height, trigger.into()) + .execution_trace_block(block_height, trigger.into()) .await?; dbg!(&status); Ok(status diff --git a/crates/fuel-core/src/service/adapters/graphql_api.rs b/crates/fuel-core/src/service/adapters/graphql_api.rs index b9af08535a8..ac36379b05c 100644 --- a/crates/fuel-core/src/service/adapters/graphql_api.rs +++ b/crates/fuel-core/src/service/adapters/graphql_api.rs @@ -125,13 +125,13 @@ impl BlockProducerPort for BlockProducerAdapter { .await } - async fn exection_trace_block( + async fn execution_trace_block( &self, height: BlockHeight, trigger: Trigger, ) -> anyhow::Result> { self.block_producer - .exection_trace_block(height, trigger) + .execution_trace_block(height, trigger) .await } } diff --git a/crates/services/producer/src/block_producer.rs b/crates/services/producer/src/block_producer.rs index ca1b4f9d21b..47e9a6643d8 100644 --- a/crates/services/producer/src/block_producer.rs +++ b/crates/services/producer/src/block_producer.rs @@ -389,7 +389,7 @@ where ConsensusProvider: ConsensusParametersProvider, { /// Re-executes an old block, getting full execution traces. - pub async fn exection_trace_block( + pub async fn execution_trace_block( &self, height: BlockHeight, trigger: Trigger, From 7c8548f57085fda982da31fee9af1a5ce72083f2 Mon Sep 17 00:00:00 2001 From: Hannes Karppila Date: Thu, 12 Dec 2024 14:49:55 +0200 Subject: [PATCH 04/25] Make execution trace endpoint to work on block level --- crates/client/assets/schema.sdl | 2 +- crates/fuel-core/src/graphql_api.rs | 2 ++ crates/fuel-core/src/schema/tx.rs | 25 +++++++----------------- crates/fuel-core/src/schema/tx/types.rs | 13 +++++++++--- crates/services/executor/src/executor.rs | 24 ++++++++++++++--------- 5 files changed, 35 insertions(+), 31 deletions(-) diff --git a/crates/client/assets/schema.sdl b/crates/client/assets/schema.sdl index 85272ff654b..b3805d795ec 100644 --- a/crates/client/assets/schema.sdl +++ b/crates/client/assets/schema.sdl @@ -736,7 +736,7 @@ type Mutation { """ Get execution trace for an already-executed transaction. """ - traceTx(txId: HexString!, trigger: TraceTrigger!): [TraceTransactionExecutionStatus!]! + executionTraceBlock(height: U32!, trigger: TraceTrigger!): [TraceTransactionExecutionStatus!]! """ Submits transaction to the `TxPool`. diff --git a/crates/fuel-core/src/graphql_api.rs b/crates/fuel-core/src/graphql_api.rs index 63a6efcf0de..bf7cf4011ee 100644 --- a/crates/fuel-core/src/graphql_api.rs +++ b/crates/fuel-core/src/graphql_api.rs @@ -56,6 +56,7 @@ pub struct Costs { pub get_peers: usize, pub estimate_predicates: usize, pub dry_run: usize, + pub execution_trace_block: usize, pub submit: usize, pub submit_and_await: usize, pub status_change: usize, @@ -88,6 +89,7 @@ pub const DEFAULT_QUERY_COSTS: Costs = Costs { get_peers: 40001, estimate_predicates: 40001, dry_run: 12000, + execution_trace_block: 1_000_000, submit: 40001, submit_and_await: 40001, status_change: 40001, diff --git a/crates/fuel-core/src/schema/tx.rs b/crates/fuel-core/src/schema/tx.rs index ef46afbf951..f555afb966f 100644 --- a/crates/fuel-core/src/schema/tx.rs +++ b/crates/fuel-core/src/schema/tx.rs @@ -1,4 +1,7 @@ -use super::scalars::U64; +use super::scalars::{ + U32, + U64, +}; use crate::{ fuel_core_graphql_api::{ api_service::{ @@ -51,7 +54,6 @@ use fuel_core_types::{ Bytes32, Cacheable, Transaction as FuelTx, - TxId, UniqueIdentifier, }, fuel_types::{ @@ -336,30 +338,17 @@ impl TxMutation { /// Get execution trace for an already-executed transaction. #[graphql(complexity = "query_costs().dry_run + child_complexity")] - async fn trace_tx( + async fn execution_trace_block( &self, ctx: &Context<'_>, - tx_id: HexString, + height: U32, trigger: TraceTrigger, ) -> async_graphql::Result> { - let tx_id = TxId::try_from(tx_id.0.as_slice()).expect("TOOD: handle this"); - - // Get the block height of the transaction - let fuel_core_types::services::txpool::TransactionStatus::Success { - block_height, - .. - } = ctx.read_view()?.tx_status(&tx_id)? - else { - return Err(async_graphql::Error::new( - "The transaction is not part of any block (yet)", - )); - }; - + let block_height = height.into(); let block_producer = ctx.data_unchecked::(); let status = block_producer .execution_trace_block(block_height, trigger.into()) .await?; - dbg!(&status); Ok(status .into_iter() .map(TraceTransactionExecutionStatus) diff --git a/crates/fuel-core/src/schema/tx/types.rs b/crates/fuel-core/src/schema/tx/types.rs index cba7165460b..ef327f435a4 100644 --- a/crates/fuel-core/src/schema/tx/types.rs +++ b/crates/fuel-core/src/schema/tx/types.rs @@ -1066,7 +1066,9 @@ impl TraceFrame { self.0 .memory_diff .clone() - .into_iter().map(|c| c.into()).collect() + .into_iter() + .map(|c| c.into()) + .collect() } /// How many of the original receipts have been produced by this point @@ -1080,9 +1082,14 @@ pub struct TraceFrameMemoryPatch { start: usize, bytes: Vec, } -impl From for TraceFrameMemoryPatch { +impl From + for TraceFrameMemoryPatch +{ fn from(change: fuel_core_types::fuel_vm::interpreter::MemorySliceChange) -> Self { - Self { start: change.global_start, bytes: change.data } + Self { + start: change.global_start, + bytes: change.data, + } } } diff --git a/crates/services/executor/src/executor.rs b/crates/services/executor/src/executor.rs index 14fb0094290..7b7468a2a98 100644 --- a/crates/services/executor/src/executor.rs +++ b/crates/services/executor/src/executor.rs @@ -112,7 +112,12 @@ use fuel_core_types::{ IntoChecked, }, interpreter::{ - trace::Frame, CheckedMetadata as CheckedMetadataTrait, ExecutableTransaction, InterpreterParams, Memory, MemoryInstance + trace::Frame, + CheckedMetadata as CheckedMetadataTrait, + ExecutableTransaction, + InterpreterParams, + Memory, + MemoryInstance, }, state::StateTransition, Backtrace as FuelBacktrace, @@ -1251,14 +1256,15 @@ where checked_tx = self.extra_tx_checks(checked_tx, header, storage_tx, memory)?; } - let (reverted, state, tx, receipts, trace_frames) = self.attempt_tx_execution_with_vm( - checked_tx, - header, - coinbase_contract_id, - gas_price, - storage_tx, - memory, - )?; + let (reverted, state, tx, receipts, trace_frames) = self + .attempt_tx_execution_with_vm( + checked_tx, + header, + coinbase_contract_id, + gas_price, + storage_tx, + memory, + )?; self.spend_input_utxos(tx.inputs(), storage_tx, reverted, execution_data)?; From 6ebe050cce4821d1f067ebf649eee31a99dc3faf Mon Sep 17 00:00:00 2001 From: Hannes Karppila Date: Thu, 12 Dec 2024 15:31:30 +0200 Subject: [PATCH 05/25] Fix cli arguments --- bin/fuel-core/src/cli/run.rs | 1 + bin/fuel-core/src/cli/run/graphql.rs | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/bin/fuel-core/src/cli/run.rs b/bin/fuel-core/src/cli/run.rs index 06d00219b50..485f0ee740d 100644 --- a/bin/fuel-core/src/cli/run.rs +++ b/bin/fuel-core/src/cli/run.rs @@ -543,6 +543,7 @@ impl Command { get_peers: graphql.costs.get_peers, estimate_predicates: graphql.costs.estimate_predicates, dry_run: graphql.costs.dry_run, + execution_trace_block: graphql.costs.execution_trace_block, submit: graphql.costs.submit, submit_and_await: graphql.costs.submit_and_await, status_change: graphql.costs.status_change, diff --git a/bin/fuel-core/src/cli/run/graphql.rs b/bin/fuel-core/src/cli/run/graphql.rs index 4abce8922a1..427e53006c6 100644 --- a/bin/fuel-core/src/cli/run/graphql.rs +++ b/bin/fuel-core/src/cli/run/graphql.rs @@ -112,6 +112,14 @@ pub struct QueryCosts { )] pub dry_run: usize, + /// Query costs for generating execution trace for a block. + #[clap( + long = "query-cost-execution-trace-block", + default_value = DEFAULT_QUERY_COSTS.execution_trace_block.to_string(), + env + )] + pub execution_trace_block: usize, + /// Query costs for submitting a transaction. #[clap( long = "query-cost-submit", From cd0704a1ecf9fef7826e5289f692443f8ecb321c Mon Sep 17 00:00:00 2001 From: Hannes Karppila Date: Fri, 13 Dec 2024 01:53:11 +0200 Subject: [PATCH 06/25] Various fixes and work towards Rust client support --- Cargo.lock | 20 +- benches/src/lib.rs | 2 +- bin/fuel-core/src/cli/run/graphql.rs | 2 +- crates/client/src/client.rs | 31 ++- crates/client/src/client/schema.rs | 1 + .../src/client/schema/execution_trace.rs | 207 ++++++++++++++++++ crates/client/src/client/schema/tx.rs | 4 +- crates/fuel-core/src/schema/tx/types.rs | 6 +- crates/fuel-core/src/state/rocks_db.rs | 6 +- crates/services/executor/src/executor.rs | 1 + crates/services/producer/src/mocks.rs | 7 + crates/services/txpool_v2/src/tests/mocks.rs | 4 +- crates/storage/src/kv_store.rs | 2 +- crates/storage/src/transactional.rs | 2 +- 14 files changed, 267 insertions(+), 28 deletions(-) create mode 100644 crates/client/src/client/schema/execution_trace.rs diff --git a/Cargo.lock b/Cargo.lock index 1da357f05b9..1ce1bbf72cb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3164,7 +3164,7 @@ dependencies = [ [[package]] name = "fuel-asm" version = "0.59.0" -source = "git+https://github.com/FuelLabs/fuel-vm?branch=dento/execution-trace#2cd645d73e30feae620f545d02af1986375f8478" +source = "git+https://github.com/FuelLabs/fuel-vm?branch=dento/execution-trace#16f8c326e3f6cb31a7e527aa65987a3dd5603e0d" dependencies = [ "bitflags 2.6.0", "fuel-types 0.59.0", @@ -3175,7 +3175,7 @@ dependencies = [ [[package]] name = "fuel-compression" version = "0.59.0" -source = "git+https://github.com/FuelLabs/fuel-vm?branch=dento/execution-trace#2cd645d73e30feae620f545d02af1986375f8478" +source = "git+https://github.com/FuelLabs/fuel-vm?branch=dento/execution-trace#16f8c326e3f6cb31a7e527aa65987a3dd5603e0d" dependencies = [ "fuel-derive 0.59.0", "fuel-types 0.59.0", @@ -3889,7 +3889,7 @@ dependencies = [ [[package]] name = "fuel-crypto" version = "0.59.0" -source = "git+https://github.com/FuelLabs/fuel-vm?branch=dento/execution-trace#2cd645d73e30feae620f545d02af1986375f8478" +source = "git+https://github.com/FuelLabs/fuel-vm?branch=dento/execution-trace#16f8c326e3f6cb31a7e527aa65987a3dd5603e0d" dependencies = [ "coins-bip32", "coins-bip39", @@ -3921,7 +3921,7 @@ dependencies = [ [[package]] name = "fuel-derive" version = "0.59.0" -source = "git+https://github.com/FuelLabs/fuel-vm?branch=dento/execution-trace#2cd645d73e30feae620f545d02af1986375f8478" +source = "git+https://github.com/FuelLabs/fuel-vm?branch=dento/execution-trace#16f8c326e3f6cb31a7e527aa65987a3dd5603e0d" dependencies = [ "proc-macro2", "quote", @@ -3958,7 +3958,7 @@ dependencies = [ [[package]] name = "fuel-merkle" version = "0.59.0" -source = "git+https://github.com/FuelLabs/fuel-vm?branch=dento/execution-trace#2cd645d73e30feae620f545d02af1986375f8478" +source = "git+https://github.com/FuelLabs/fuel-vm?branch=dento/execution-trace#16f8c326e3f6cb31a7e527aa65987a3dd5603e0d" dependencies = [ "derive_more 0.99.18", "digest 0.10.7", @@ -3978,7 +3978,7 @@ checksum = "4c1b711f28553ddc5f3546711bd220e144ce4c1af7d9e9a1f70b2f20d9f5b791" [[package]] name = "fuel-storage" version = "0.59.0" -source = "git+https://github.com/FuelLabs/fuel-vm?branch=dento/execution-trace#2cd645d73e30feae620f545d02af1986375f8478" +source = "git+https://github.com/FuelLabs/fuel-vm?branch=dento/execution-trace#16f8c326e3f6cb31a7e527aa65987a3dd5603e0d" [[package]] name = "fuel-tx" @@ -4005,7 +4005,7 @@ dependencies = [ [[package]] name = "fuel-tx" version = "0.59.0" -source = "git+https://github.com/FuelLabs/fuel-vm?branch=dento/execution-trace#2cd645d73e30feae620f545d02af1986375f8478" +source = "git+https://github.com/FuelLabs/fuel-vm?branch=dento/execution-trace#16f8c326e3f6cb31a7e527aa65987a3dd5603e0d" dependencies = [ "bitflags 2.6.0", "derive_more 1.0.0", @@ -4038,7 +4038,7 @@ dependencies = [ [[package]] name = "fuel-types" version = "0.59.0" -source = "git+https://github.com/FuelLabs/fuel-vm?branch=dento/execution-trace#2cd645d73e30feae620f545d02af1986375f8478" +source = "git+https://github.com/FuelLabs/fuel-vm?branch=dento/execution-trace#16f8c326e3f6cb31a7e527aa65987a3dd5603e0d" dependencies = [ "fuel-derive 0.59.0", "hex", @@ -4080,7 +4080,7 @@ dependencies = [ [[package]] name = "fuel-vm" version = "0.59.0" -source = "git+https://github.com/FuelLabs/fuel-vm?branch=dento/execution-trace#2cd645d73e30feae620f545d02af1986375f8478" +source = "git+https://github.com/FuelLabs/fuel-vm?branch=dento/execution-trace#16f8c326e3f6cb31a7e527aa65987a3dd5603e0d" dependencies = [ "anyhow", "async-trait", @@ -5479,7 +5479,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" dependencies = [ "cfg-if", - "windows-targets 0.52.6", + "windows-targets 0.48.5", ] [[package]] diff --git a/benches/src/lib.rs b/benches/src/lib.rs index c84fb4e6418..fe2f7e6782e 100644 --- a/benches/src/lib.rs +++ b/benches/src/lib.rs @@ -546,7 +546,7 @@ impl TryFrom for VmBenchPrepared { } let storage_diff = vm.storage_diff(); let mut vm = vm.remove_recording(); - let mut diff = start_vm.diff(&vm); + let mut diff = start_vm.rollback_to(&vm); diff += storage_diff; let diff: diff::Diff = diff.into(); vm.reset_vm_state(&diff); diff --git a/bin/fuel-core/src/cli/run/graphql.rs b/bin/fuel-core/src/cli/run/graphql.rs index 427e53006c6..78e4933c7c7 100644 --- a/bin/fuel-core/src/cli/run/graphql.rs +++ b/bin/fuel-core/src/cli/run/graphql.rs @@ -112,7 +112,7 @@ pub struct QueryCosts { )] pub dry_run: usize, - /// Query costs for generating execution trace for a block. + /// Query costs for generating execution trace for a block.® #[clap( long = "query-cost-execution-trace-block", default_value = DEFAULT_QUERY_COSTS.execution_trace_block.to_string(), diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index 48973564e17..58cb2242a3d 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -49,22 +49,19 @@ use fuel_core_types::{ fuel_asm::{ Instruction, Word, - }, - fuel_tx::{ + }, fuel_tx::{ BlobId, Bytes32, ConsensusParameters, Receipt, Transaction, TxId, - }, - fuel_types::{ + }, fuel_types::{ self, canonical::Serialize, BlockHeight, Nonce, - }, - services::executor::TransactionExecutionStatus, + }, fuel_vm::interpreter::trace::Trigger, services::executor::TransactionExecutionStatus }; #[cfg(feature = "subscriptions")] use futures::{ @@ -506,6 +503,28 @@ impl FuelClient { .collect() } + /// Exectuion trace for a block + pub async fn execution_trace_block( + &self, + height: &BlockHeight, + trigger: Trigger + ) -> io::Result> { + let query: Operation< + schema::execution_trace::ExectionTraceBlock, + schema::execution_trace::ExectionTraceBlockArgs, + > = schema::execution_trace::ExectionTraceBlock::build( + schema::execution_trace::ExectionTraceBlockArgs { + height: (*height).into(), + trigger: trigger.into(), + }, + ); + let tx_statuses = self.query(query).await.map(|r| r.execution_trace_block)?; + tx_statuses + .into_iter() + .map(|tx_status| tx_status.try_into().map_err(Into::into)) + .collect() + } + /// Estimate predicates for the transaction pub async fn estimate_predicates(&self, tx: &mut Transaction) -> io::Result<()> { let serialized_tx = tx.to_bytes(); diff --git a/crates/client/src/client/schema.rs b/crates/client/src/client/schema.rs index 7930d66c1ab..d28fb73f773 100644 --- a/crates/client/src/client/schema.rs +++ b/crates/client/src/client/schema.rs @@ -33,6 +33,7 @@ pub mod chain; pub mod coins; pub mod contract; pub mod da_compressed; +pub mod execution_trace; pub mod message; pub mod node_info; pub mod upgrades; diff --git a/crates/client/src/client/schema/execution_trace.rs b/crates/client/src/client/schema/execution_trace.rs new file mode 100644 index 00000000000..adb05894da3 --- /dev/null +++ b/crates/client/src/client/schema/execution_trace.rs @@ -0,0 +1,207 @@ +use crate::client::{ + schema::{ + schema, + tx::transparent_receipt::Receipt, + Address, + ConversionError, + TransactionId, + U32, + U64, + }, + PageDirection, + PaginationRequest, +}; +use fuel_core_types::{ + fuel_tx, fuel_vm::interpreter::trace::Trigger, services::executor::{ + TransactionExecutionResult, + TransactionExecutionStatus, + } +}; +use std::convert::{ + TryFrom, + TryInto, +}; + +use super::tx::{ + ProgramState, +}; + +#[allow(clippy::enum_variant_names)] +#[derive(cynic::InlineFragments, Clone, Debug)] +#[cynic(schema_path = "./assets/schema.sdl")] +pub enum TraceTransactionStatus { + SuccessStatus(TraceSuccessStatus), + FailureStatus(TraceFailureStatus), + #[cynic(fallback)] + Unknown, +} + +impl TryFrom for TransactionExecutionResult { + type Error = ConversionError; + + fn try_from(status: TraceTransactionStatus) -> Result { + Ok(match status { + TraceTransactionStatus::SuccessStatus(s) => { + let receipts = s + .receipts + .into_iter() + .map(|receipt| receipt.try_into()) + .collect::, _>>()?; + TransactionExecutionResult::Success { + result: s.program_state.map(TryInto::try_into).transpose()?, + receipts, + execution_trace: Vec::new(), // Not produced by dry-run + total_gas: s.total_gas.0, + total_fee: s.total_fee.0, + } + } + TraceTransactionStatus::FailureStatus(s) => { + let receipts = s + .receipts + .into_iter() + .map(|receipt| receipt.try_into()) + .collect::, _>>()?; + TransactionExecutionResult::Failed { + result: s.program_state.map(TryInto::try_into).transpose()?, + receipts, + execution_trace: Vec::new(), // Not produced by dry-run + total_gas: s.total_gas.0, + total_fee: s.total_fee.0, + } + } + TraceTransactionStatus::Unknown => { + return Err(Self::Error::UnknownVariant("ExecutionTraceTxStatus")) + } + }) + } +} + +#[derive(cynic::QueryFragment, Clone, Debug)] +#[cynic(schema_path = "./assets/schema.sdl")] +pub struct TraceSuccessStatus { + pub program_state: Option, + pub receipts: Vec, + pub total_gas: U64, + pub total_fee: U64, +} + +#[derive(cynic::QueryFragment, Clone, Debug)] +#[cynic(schema_path = "./assets/schema.sdl")] +pub struct TraceFailureStatus { + pub program_state: Option, + pub receipts: Vec, + pub total_gas: U64, + pub total_fee: U64, +} + +#[derive(cynic::QueryFragment, Clone, Debug)] +#[cynic(schema_path = "./assets/schema.sdl")] +pub struct TraceTransactionExecutionStatus { + pub id: TransactionId, + pub status: TraceTransactionStatus, +} + +impl TryFrom for TransactionExecutionStatus { + type Error = ConversionError; + + fn try_from(schema: TraceTransactionExecutionStatus) -> Result { + let id = schema.id.into(); + let status = schema.status.try_into()?; + + Ok(TransactionExecutionStatus { id, result: status }) + } +} + +#[derive(cynic::QueryVariables, Debug)] +pub struct TransactionsByOwnerConnectionArgs { + /// Select transactions based on related `owner`s + pub owner: Address, + /// Skip until cursor (forward pagination) + pub after: Option, + /// Skip until cursor (backward pagination) + pub before: Option, + /// Retrieve the first n transactions in order (forward pagination) + pub first: Option, + /// Retrieve the last n transactions in order (backward pagination). + /// Can't be used at the same time as `first`. + pub last: Option, +} + +impl From<(Address, PaginationRequest)> for TransactionsByOwnerConnectionArgs { + fn from(r: (Address, PaginationRequest)) -> Self { + match r.1.direction { + PageDirection::Forward => TransactionsByOwnerConnectionArgs { + owner: r.0, + after: r.1.cursor, + before: None, + first: Some(r.1.results), + last: None, + }, + PageDirection::Backward => TransactionsByOwnerConnectionArgs { + owner: r.0, + after: None, + before: r.1.cursor, + first: None, + last: Some(r.1.results), + }, + } + } +} + +// mutations + + +/// When to record a trace frames during execution +#[derive(cynic::Enum, Debug, Copy, Clone, Eq, PartialEq)] +#[cynic( + schema_path = "./assets/schema.sdl", + graphql_type = "TraceTrigger", +)] +pub enum TraceTrigger { + /// After each instruction + OnInstruction, + /// After an instruction has created a receipt + OnReceipt, +} +impl From for TraceTrigger { + fn from(value: Trigger) -> Self { + match value { + Trigger::OnInstruction => TraceTrigger::OnInstruction, + Trigger::OnReceipt => TraceTrigger::OnReceipt, + } + } +} + +#[derive(cynic::QueryVariables, Debug)] +pub struct ExectionTraceBlockArgs { + pub height: U32, + pub trigger: TraceTrigger, +} + +/// Retrieves the transaction in opaque form +#[derive(cynic::QueryFragment, Clone, Debug)] +#[cynic( + schema_path = "./assets/schema.sdl", + graphql_type = "Mutation", + variables = "ExectionTraceBlockArgs" +)] +pub struct ExectionTraceBlock { + #[arguments(height: $height, trigger: $trigger)] + pub execution_trace_block: Vec, +} + +#[cfg(test)] +pub mod tests { + use super::*; + use fuel_core_types::fuel_types::BlockHeight; + + #[test] + fn execution_trace_block_tx_gql_output() { + use cynic::MutationBuilder; + let query = ExectionTraceBlock::build(ExectionTraceBlockArgs { + height: BlockHeight::new(1234).into(), + trigger: TraceTrigger::OnReceipt, + }); + insta::assert_snapshot!(query.query) + } +} diff --git a/crates/client/src/client/schema/tx.rs b/crates/client/src/client/schema/tx.rs index 01f9184ff8d..144f63b1992 100644 --- a/crates/client/src/client/schema/tx.rs +++ b/crates/client/src/client/schema/tx.rs @@ -292,6 +292,7 @@ impl TryFrom for TransactionExecutionResult { TransactionExecutionResult::Success { result: s.program_state.map(TryInto::try_into).transpose()?, receipts, + execution_trace: Vec::new(), // Not produced by dry-run total_gas: s.total_gas.0, total_fee: s.total_fee.0, } @@ -305,12 +306,13 @@ impl TryFrom for TransactionExecutionResult { TransactionExecutionResult::Failed { result: s.program_state.map(TryInto::try_into).transpose()?, receipts, + execution_trace: Vec::new(), // Not produced by dry-run total_gas: s.total_gas.0, total_fee: s.total_fee.0, } } DryRunTransactionStatus::Unknown => { - return Err(Self::Error::UnknownVariant("DryRuynTxStatus")) + return Err(Self::Error::UnknownVariant("DryRuyTxStatus")) } }) } diff --git a/crates/fuel-core/src/schema/tx/types.rs b/crates/fuel-core/src/schema/tx/types.rs index ef327f435a4..86cc6a7420c 100644 --- a/crates/fuel-core/src/schema/tx/types.rs +++ b/crates/fuel-core/src/schema/tx/types.rs @@ -894,8 +894,8 @@ impl DryRunTransactionStatus { } } -/// One of the films in the Star Wars Trilogy -#[derive(Enum, Copy, Clone, Eq, PartialEq)] +/// When to record a trace frames during execution +#[derive(Enum, Debug, Copy, Clone, Eq, PartialEq)] pub enum TraceTrigger { /// After each instruction OnInstruction, @@ -1097,7 +1097,7 @@ impl From impl TraceFrameMemoryPatch { /// Start address async fn start(&self) -> u32 { - self.start as u32 + u32::try_from(self.start).expect("Invalid memory address") } /// Bytes of data diff --git a/crates/fuel-core/src/state/rocks_db.rs b/crates/fuel-core/src/state/rocks_db.rs index cf76517959c..7f0d31d05e2 100644 --- a/crates/fuel-core/src/state/rocks_db.rs +++ b/crates/fuel-core/src/state/rocks_db.rs @@ -770,12 +770,12 @@ where .get_pinned_cf_opt(&self.cf(column), key, &self.read_options) .map_err(|e| DatabaseError::Other(e.into()))? .map(|value| { - if offset >= value.len() { + let Some(read) = value.len().checked_sub(offset) else { return Ok(0); - } + }; std::io::Write::write_all(&mut buf, &value.as_ref()[offset..]) .map_err(|e| DatabaseError::Other(anyhow::anyhow!(e)))?; - StorageResult::Ok(value.len() - offset) + StorageResult::Ok(read) }) .transpose()?; diff --git a/crates/services/executor/src/executor.rs b/crates/services/executor/src/executor.rs index 7b7468a2a98..2510b65c9d1 100644 --- a/crates/services/executor/src/executor.rs +++ b/crates/services/executor/src/executor.rs @@ -1600,6 +1600,7 @@ where Ok(checked_tx) } + #[allow(clippy::type_complexity)] fn attempt_tx_execution_with_vm( &self, checked_tx: Checked, diff --git a/crates/services/producer/src/mocks.rs b/crates/services/producer/src/mocks.rs index 797035866af..9d1c2c01e76 100644 --- a/crates/services/producer/src/mocks.rs +++ b/crates/services/producer/src/mocks.rs @@ -248,6 +248,13 @@ impl BlockProducerDatabase for MockDb { .ok_or(not_found!("Didn't find block for test")) } + fn get_transaction( + &self, + _id: &fuel_core_types::fuel_tx::TxId, + ) -> StorageResult> { + todo!(); + } + fn block_header_merkle_root(&self, height: &BlockHeight) -> StorageResult { Ok(Bytes32::new( [u8::try_from(*height.deref()).expect("Test use small values"); 32], diff --git a/crates/services/txpool_v2/src/tests/mocks.rs b/crates/services/txpool_v2/src/tests/mocks.rs index 1dca65bc9d3..da6b5a3b412 100644 --- a/crates/services/txpool_v2/src/tests/mocks.rs +++ b/crates/services/txpool_v2/src/tests/mocks.rs @@ -126,13 +126,15 @@ impl StorageRead for MockDb { fn read( &self, key: &::Key, + offset: usize, buf: &mut [u8], ) -> Result, Self::Error> { let table = self.data.lock().unwrap(); let bytes = table.blobs.get(key); let len = bytes.map(|bytes| { - buf.copy_from_slice(bytes.0.as_slice()); + assert!(offset < bytes.0.len()); + buf.copy_from_slice(&bytes.0.as_slice()[offset..]); bytes.0.len() }); Ok(len) diff --git a/crates/storage/src/kv_store.rs b/crates/storage/src/kv_store.rs index fe9e94d86f2..214b46fe51b 100644 --- a/crates/storage/src/kv_store.rs +++ b/crates/storage/src/kv_store.rs @@ -73,7 +73,7 @@ pub trait KeyValueInspect { self.get(key, column)? .map(|value| { let read = value.len(); - if offset + read != buf.len() { + if offset.saturating_add(read) != buf.len() { return Err(StorageError::Other(anyhow::anyhow!( "Buffer size is not equal to the value size after offset" ))); diff --git a/crates/storage/src/transactional.rs b/crates/storage/src/transactional.rs index e43ad3b77e9..7740828f110 100644 --- a/crates/storage/src/transactional.rs +++ b/crates/storage/src/transactional.rs @@ -399,7 +399,7 @@ where match operation { WriteOperation::Insert(value) => { let read = value.len(); - if offset + read != buf.len() { + if offset.saturating_add(read) != buf.len() { return Err(crate::Error::Other(anyhow::anyhow!( "Buffer size is not equal to the value size after offset" ))); From f76c96fd5ed2e745949d1ab156d665af05565830 Mon Sep 17 00:00:00 2001 From: Hannes Karppila Date: Wed, 18 Dec 2024 11:21:02 +0200 Subject: [PATCH 07/25] WIP --- Cargo.lock | 13 +- Cargo.toml | 4 +- crates/client/src/client.rs | 16 +- .../src/client/schema/execution_trace.rs | 29 ++- crates/fuel-core/src/graphql_api/ports.rs | 5 +- crates/fuel-core/src/schema/tx/types.rs | 12 -- .../src/service/adapters/graphql_api.rs | 1 - .../src/service/adapters/producer.rs | 1 - crates/services/executor/src/executor.rs | 82 +++++--- crates/services/executor/src/lib.rs | 1 + crates/services/executor/src/trace.rs | 181 ++++++++++++++++++ .../services/producer/src/block_producer.rs | 4 +- crates/services/producer/src/ports.rs | 4 +- .../upgradable-executor/src/executor.rs | 4 +- crates/types/Cargo.toml | 1 + crates/types/src/services/executor.rs | 29 ++- 16 files changed, 304 insertions(+), 83 deletions(-) create mode 100644 crates/services/executor/src/trace.rs diff --git a/Cargo.lock b/Cargo.lock index 1ce1bbf72cb..9219e50eb2d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3164,7 +3164,6 @@ dependencies = [ [[package]] name = "fuel-asm" version = "0.59.0" -source = "git+https://github.com/FuelLabs/fuel-vm?branch=dento/execution-trace#16f8c326e3f6cb31a7e527aa65987a3dd5603e0d" dependencies = [ "bitflags 2.6.0", "fuel-types 0.59.0", @@ -3175,7 +3174,6 @@ dependencies = [ [[package]] name = "fuel-compression" version = "0.59.0" -source = "git+https://github.com/FuelLabs/fuel-vm?branch=dento/execution-trace#16f8c326e3f6cb31a7e527aa65987a3dd5603e0d" dependencies = [ "fuel-derive 0.59.0", "fuel-types 0.59.0", @@ -3835,6 +3833,7 @@ dependencies = [ "rand", "secrecy", "serde", + "serde-big-array", "tai64", "zeroize", ] @@ -3889,7 +3888,6 @@ dependencies = [ [[package]] name = "fuel-crypto" version = "0.59.0" -source = "git+https://github.com/FuelLabs/fuel-vm?branch=dento/execution-trace#16f8c326e3f6cb31a7e527aa65987a3dd5603e0d" dependencies = [ "coins-bip32", "coins-bip39", @@ -3921,7 +3919,6 @@ dependencies = [ [[package]] name = "fuel-derive" version = "0.59.0" -source = "git+https://github.com/FuelLabs/fuel-vm?branch=dento/execution-trace#16f8c326e3f6cb31a7e527aa65987a3dd5603e0d" dependencies = [ "proc-macro2", "quote", @@ -3958,7 +3955,6 @@ dependencies = [ [[package]] name = "fuel-merkle" version = "0.59.0" -source = "git+https://github.com/FuelLabs/fuel-vm?branch=dento/execution-trace#16f8c326e3f6cb31a7e527aa65987a3dd5603e0d" dependencies = [ "derive_more 0.99.18", "digest 0.10.7", @@ -3978,7 +3974,6 @@ checksum = "4c1b711f28553ddc5f3546711bd220e144ce4c1af7d9e9a1f70b2f20d9f5b791" [[package]] name = "fuel-storage" version = "0.59.0" -source = "git+https://github.com/FuelLabs/fuel-vm?branch=dento/execution-trace#16f8c326e3f6cb31a7e527aa65987a3dd5603e0d" [[package]] name = "fuel-tx" @@ -4005,7 +4000,6 @@ dependencies = [ [[package]] name = "fuel-tx" version = "0.59.0" -source = "git+https://github.com/FuelLabs/fuel-vm?branch=dento/execution-trace#16f8c326e3f6cb31a7e527aa65987a3dd5603e0d" dependencies = [ "bitflags 2.6.0", "derive_more 1.0.0", @@ -4038,7 +4032,6 @@ dependencies = [ [[package]] name = "fuel-types" version = "0.59.0" -source = "git+https://github.com/FuelLabs/fuel-vm?branch=dento/execution-trace#16f8c326e3f6cb31a7e527aa65987a3dd5603e0d" dependencies = [ "fuel-derive 0.59.0", "hex", @@ -4080,7 +4073,6 @@ dependencies = [ [[package]] name = "fuel-vm" version = "0.59.0" -source = "git+https://github.com/FuelLabs/fuel-vm?branch=dento/execution-trace#16f8c326e3f6cb31a7e527aa65987a3dd5603e0d" dependencies = [ "anyhow", "async-trait", @@ -4104,7 +4096,6 @@ dependencies = [ "primitive-types", "rand", "serde", - "serde-big-array", "serde_with", "sha3", "static_assertions", @@ -5479,7 +5470,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" dependencies = [ "cfg-if", - "windows-targets 0.48.5", + "windows-targets 0.52.6", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index e0e9a688744..bcb0108b548 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -87,7 +87,8 @@ fuel-core-xtask = { version = "0.0.0", path = "./xtask" } fuel-gas-price-algorithm = { version = "0.40.0", path = "crates/fuel-gas-price-algorithm" } # Fuel dependencies -fuel-vm-private = { git = "https://github.com/FuelLabs/fuel-vm", branch = "dento/execution-trace", package = "fuel-vm", default-features = false } +fuel-vm-private = { path = "../fuel-vm/fuel-vm", package = "fuel-vm", default-features = false } +# fuel-vm-private = { git = "https://github.com/FuelLabs/fuel-vm", branch = "dento/execution-trace", package = "fuel-vm", default-features = false } # fuel-vm-private = { version = "0.58.2", package = "fuel-vm", default-features = false } # Common dependencies @@ -115,6 +116,7 @@ postcard = "1.0" tracing-attributes = "0.1" tracing-subscriber = "0.3" serde = "1.0" +serde-big-array = { version = "0.5", default-features = false } serde_json = { version = "1.0", default-features = false, features = ["alloc"] } serde_with = { version = "3.4", default-features = false } strum = { version = "0.25" } diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index 58cb2242a3d..941740f88c2 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -49,19 +49,25 @@ use fuel_core_types::{ fuel_asm::{ Instruction, Word, - }, fuel_tx::{ + }, + fuel_tx::{ BlobId, Bytes32, ConsensusParameters, Receipt, Transaction, TxId, - }, fuel_types::{ + }, + fuel_types::{ self, canonical::Serialize, BlockHeight, Nonce, - }, fuel_vm::interpreter::trace::Trigger, services::executor::TransactionExecutionStatus + }, + services::executor::{ + TraceTrigger, + TransactionExecutionStatus, + }, }; #[cfg(feature = "subscriptions")] use futures::{ @@ -503,11 +509,11 @@ impl FuelClient { .collect() } - /// Exectuion trace for a block + /// Execution trace for a block pub async fn execution_trace_block( &self, height: &BlockHeight, - trigger: Trigger + trigger: TraceTrigger, ) -> io::Result> { let query: Operation< schema::execution_trace::ExectionTraceBlock, diff --git a/crates/client/src/client/schema/execution_trace.rs b/crates/client/src/client/schema/execution_trace.rs index adb05894da3..6bb090af8c2 100644 --- a/crates/client/src/client/schema/execution_trace.rs +++ b/crates/client/src/client/schema/execution_trace.rs @@ -12,19 +12,18 @@ use crate::client::{ PaginationRequest, }; use fuel_core_types::{ - fuel_tx, fuel_vm::interpreter::trace::Trigger, services::executor::{ + fuel_tx, + services::executor::{ TransactionExecutionResult, TransactionExecutionStatus, - } + }, }; use std::convert::{ TryFrom, TryInto, }; -use super::tx::{ - ProgramState, -}; +use super::tx::ProgramState; #[allow(clippy::enum_variant_names)] #[derive(cynic::InlineFragments, Clone, Debug)] @@ -150,24 +149,24 @@ impl From<(Address, PaginationRequest)> for TransactionsByOwnerConnectio // mutations - /// When to record a trace frames during execution #[derive(cynic::Enum, Debug, Copy, Clone, Eq, PartialEq)] -#[cynic( - schema_path = "./assets/schema.sdl", - graphql_type = "TraceTrigger", -)] +#[cynic(schema_path = "./assets/schema.sdl", graphql_type = "TraceTrigger")] pub enum TraceTrigger { /// After each instruction OnInstruction, /// After an instruction has created a receipt OnReceipt, } -impl From for TraceTrigger { - fn from(value: Trigger) -> Self { - match value { - Trigger::OnInstruction => TraceTrigger::OnInstruction, - Trigger::OnReceipt => TraceTrigger::OnReceipt, +impl From for TraceTrigger { + fn from(trigger: fuel_core_types::services::executor::TraceTrigger) -> Self { + match trigger { + fuel_core_types::services::executor::TraceTrigger::OnInstruction => { + TraceTrigger::OnInstruction + } + fuel_core_types::services::executor::TraceTrigger::OnReceipt => { + TraceTrigger::OnReceipt + } } } } diff --git a/crates/fuel-core/src/graphql_api/ports.rs b/crates/fuel-core/src/graphql_api/ports.rs index 07afd93874e..7489ed2a213 100644 --- a/crates/fuel-core/src/graphql_api/ports.rs +++ b/crates/fuel-core/src/graphql_api/ports.rs @@ -53,10 +53,7 @@ use fuel_core_types::{ ContractId, Nonce, }, - fuel_vm::interpreter::{ - trace::Trigger, - Memory, - }, + fuel_vm::interpreter::Memory, services::{ executor::TransactionExecutionStatus, graphql_api::ContractBalance, diff --git a/crates/fuel-core/src/schema/tx/types.rs b/crates/fuel-core/src/schema/tx/types.rs index 86cc6a7420c..f333f087905 100644 --- a/crates/fuel-core/src/schema/tx/types.rs +++ b/crates/fuel-core/src/schema/tx/types.rs @@ -80,10 +80,6 @@ use fuel_core_types::{ fuel_types::canonical::Serialize, fuel_vm::{ consts::VM_REGISTER_COUNT, - interpreter::trace::{ - Frame, - Trigger, - }, ProgramState as VmProgramState, }, services::{ @@ -902,14 +898,6 @@ pub enum TraceTrigger { /// After an instruction has created a receipt OnReceipt, } -impl From for Trigger { - fn from(value: TraceTrigger) -> Self { - match value { - TraceTrigger::OnInstruction => Trigger::OnInstruction, - TraceTrigger::OnReceipt => Trigger::OnReceipt, - } - } -} #[derive(Union, Debug)] pub enum TraceTransactionStatus { diff --git a/crates/fuel-core/src/service/adapters/graphql_api.rs b/crates/fuel-core/src/service/adapters/graphql_api.rs index ac36379b05c..1fd260fbc44 100644 --- a/crates/fuel-core/src/service/adapters/graphql_api.rs +++ b/crates/fuel-core/src/service/adapters/graphql_api.rs @@ -41,7 +41,6 @@ use fuel_core_types::{ TxId, }, fuel_types::BlockHeight, - fuel_vm::interpreter::trace::Trigger, services::{ block_importer::SharedImportResult, executor::TransactionExecutionStatus, diff --git a/crates/fuel-core/src/service/adapters/producer.rs b/crates/fuel-core/src/service/adapters/producer.rs index e1965c4755f..1ac4453d91b 100644 --- a/crates/fuel-core/src/service/adapters/producer.rs +++ b/crates/fuel-core/src/service/adapters/producer.rs @@ -60,7 +60,6 @@ use fuel_core_types::{ BlockHeight, Bytes32, }, - fuel_vm::interpreter::trace::Trigger, services::{ block_producer::Components, executor::{ diff --git a/crates/services/executor/src/executor.rs b/crates/services/executor/src/executor.rs index 2510b65c9d1..915b10973af 100644 --- a/crates/services/executor/src/executor.rs +++ b/crates/services/executor/src/executor.rs @@ -5,6 +5,10 @@ use crate::{ TransactionsSource, }, refs::ContractRef, + trace::{ + TraceOnInstruction, + TraceOnReceipt, + }, }; use fuel_core_storage::{ column::Column, @@ -112,12 +116,14 @@ use fuel_core_types::{ IntoChecked, }, interpreter::{ - trace::Frame, + trace::ExecutionTraceHooks, CheckedMetadata as CheckedMetadataTrait, ExecutableTransaction, InterpreterParams, Memory, MemoryInstance, + NoTrace, + NotSupportedEcal, }, state::StateTransition, Backtrace as FuelBacktrace, @@ -132,6 +138,8 @@ use fuel_core_types::{ ExecutionResult, ForcedTransactionFailure, Result as ExecutorResult, + TraceFrame, + TraceTrigger, TransactionExecutionResult, TransactionExecutionStatus, TransactionValidityError, @@ -256,7 +264,7 @@ pub struct ExecutionOptions { pub backtrace: bool, /// Record execution traces for each transaction with the given trigger #[serde(default)] - pub execution_trace: Option, + pub execution_trace: Option, } /// The executor instance performs block production and validation. Given a block, it will execute all @@ -1256,15 +1264,45 @@ where checked_tx = self.extra_tx_checks(checked_tx, header, storage_tx, memory)?; } - let (reverted, state, tx, receipts, trace_frames) = self - .attempt_tx_execution_with_vm( - checked_tx, - header, - coinbase_contract_id, - gas_price, - storage_tx, - memory, - )?; + let (reverted, state, tx, receipts, trace_frames) = + match self.options.execution_trace { + Some(TraceTrigger::OnInstruction) => { + let (reverted, state, tx, receipts, trace) = self + .attempt_tx_execution_with_vm::<_, _, TraceOnInstruction>( + checked_tx, + header, + coinbase_contract_id, + gas_price, + storage_tx, + memory, + )?; + (reverted, state, tx, receipts, trace.frames) + } + Some(TraceTrigger::OnReceipt) => { + let (reverted, state, tx, receipts, trace) = self + .attempt_tx_execution_with_vm::<_, _, TraceOnReceipt>( + checked_tx, + header, + coinbase_contract_id, + gas_price, + storage_tx, + memory, + )?; + (reverted, state, tx, receipts, trace.frames) + } + None => { + let (reverted, state, tx, receipts, _) = self + .attempt_tx_execution_with_vm::<_, _, NoTrace>( + checked_tx, + header, + coinbase_contract_id, + gas_price, + storage_tx, + memory, + )?; + (reverted, state, tx, receipts, Vec::new()) + } + }; self.spend_input_utxos(tx.inputs(), storage_tx, reverted, execution_data)?; @@ -1466,7 +1504,7 @@ where tx: &Tx, execution_data: &mut ExecutionData, receipts: Vec, - execution_trace: Vec, + execution_trace: Vec, gas_price: Word, reverted: bool, state: ProgramState, @@ -1601,7 +1639,7 @@ where } #[allow(clippy::type_complexity)] - fn attempt_tx_execution_with_vm( + fn attempt_tx_execution_with_vm( &self, checked_tx: Checked, header: &PartialBlockHeader, @@ -1609,11 +1647,12 @@ where gas_price: Word, storage_tx: &mut TxStorageTransaction, memory: &mut MemoryInstance, - ) -> ExecutorResult<(bool, ProgramState, Tx, Vec, Vec)> + ) -> ExecutorResult<(bool, ProgramState, Tx, Vec, Trace)> where Tx: ExecutableTransaction + Cacheable, ::Metadata: CheckedMetadataTrait + Send + Sync, T: KeyValueInspect, + Trace: ExecutionTraceHooks + Clone + Default, { let tx_id = checked_tx.id(); @@ -1639,17 +1678,12 @@ where .collect(); let ready_tx = checked_tx.into_ready(gas_price, gas_costs, fee_params, None)?; // TODO: block_height argument? - let mut vm = Interpreter::with_storage( + let mut vm = Interpreter::<_, _, _, NotSupportedEcal, Trace>::with_storage( memory, vm_db, InterpreterParams::new(gas_price, &self.consensus_params), ); - let mut trace_tmp_memory = MemoryInstance::new(); - if let Some(trigger) = self.options.execution_trace { - vm = vm.with_trace_recording(trigger, &mut trace_tmp_memory); - } - let vm_result: StateTransition<_> = vm .transact(ready_tx) .map_err(|error| ExecutorError::VmExecution { @@ -1659,7 +1693,7 @@ where .into(); let reverted = vm_result.should_revert(); - let trace_frames = vm.trace_frames().to_vec(); + let trace = vm.trace().clone(); let (state, mut tx, receipts): (_, Tx, _) = vm_result.into_inner(); #[cfg(debug_assertions)] { @@ -1681,7 +1715,7 @@ where } self.update_tx_outputs(storage_tx, tx_id, &mut tx)?; - Ok((reverted, state, tx, receipts, trace_frames)) + Ok((reverted, state, tx, receipts.to_vec(), trace)) } fn verify_inputs_exist_and_values_match( @@ -1986,9 +2020,9 @@ where } /// Log a VM backtrace if configured to do so - fn log_backtrace( + fn log_backtrace( &self, - vm: &Interpreter, Tx>, + vm: &Interpreter, Tx, Ecal, Trace>, receipts: &[Receipt], ) where M: Memory, diff --git a/crates/services/executor/src/lib.rs b/crates/services/executor/src/lib.rs index d28e5dbb612..75fa2332973 100644 --- a/crates/services/executor/src/lib.rs +++ b/crates/services/executor/src/lib.rs @@ -10,6 +10,7 @@ extern crate alloc; pub mod executor; pub mod ports; pub mod refs; +pub mod trace; #[cfg(test)] fuel_core_trace::enable_tracing!(); diff --git a/crates/services/executor/src/trace.rs b/crates/services/executor/src/trace.rs new file mode 100644 index 00000000000..7150a4b54d3 --- /dev/null +++ b/crates/services/executor/src/trace.rs @@ -0,0 +1,181 @@ +use alloc::vec::Vec; +use fuel_core_types::{ + fuel_vm::{ + consts::VM_REGISTER_COUNT, + interpreter::{ + trace::ExecutionTraceHooks, + Memory, + MemoryInstance, + }, + }, + services::executor::TraceFrame, +}; + +/// Used to capture an execution trace for every instruction. +#[derive(Debug, Clone, Default)] +pub struct TraceOnInstruction { + /// Append-only set of frames + pub frames: Vec, + /// Memory at the time of the previous snapshot + acc_memory: AccVec, +} + +impl ExecutionTraceHooks for TraceOnInstruction { + fn after_instruction( + vm: &mut fuel_core_types::fuel_vm::Interpreter, + ) where + M: Memory, + { + let memory_diff = vm.trace().acc_memory.diff(vm.memory().as_ref()); + for patch in memory_diff.parts.iter() { + vm.trace_mut().acc_memory.update(patch.clone()); + } + + let mut registers = [0; VM_REGISTER_COUNT]; + registers.copy_from_slice(vm.registers()); + + let receipt_count = vm.receipts().len(); + + vm.trace_mut().frames.push(TraceFrame { + memory_diff: memory_diff + .parts + .into_iter() + .map(|p| (p.start, p.data)) + .collect(), + registers, + receipt_count, + }) + } +} + +/// Used to capture an execution trace for after each receipt. +#[derive(Debug, Clone, Default)] +pub struct TraceOnReceipt { + /// Append-only set of frames + pub frames: Vec, + /// Memory at the time of the previous snapshot + acc_memory: AccVec, +} + +impl ExecutionTraceHooks for TraceOnReceipt { + fn after_instruction( + vm: &mut fuel_core_types::fuel_vm::Interpreter, + ) where + M: Memory, + { + if vm + .trace() + .frames + .last() + .map(|s| s.receipt_count) + .unwrap_or(0) + < vm.receipts().len() + { + let memory_diff = vm.trace().acc_memory.diff(vm.memory().as_ref()); + for patch in memory_diff.parts.iter() { + vm.trace_mut().acc_memory.update(patch.clone()); + } + + let mut registers = [0; VM_REGISTER_COUNT]; + registers.copy_from_slice(vm.registers()); + + let receipt_count = vm.receipts().len(); + + vm.trace_mut().frames.push(TraceFrame { + memory_diff: memory_diff + .parts + .into_iter() + .map(|p| (p.start, p.data)) + .collect(), + registers, + receipt_count, + }) + } + } +} + +#[derive(Debug, Clone, Default)] +struct AccVec { + parts: Vec, +} + +#[derive(Debug, Clone)] +struct AccVecPart { + start: usize, + data: Vec, +} +impl AccVecPart { + #[allow(clippy::arithmetic_side_effects)] // VM memory is always within bounds + fn end(&self) -> usize { + self.start + self.data.len() + } +} + +impl AccVec { + #[allow(clippy::arithmetic_side_effects)] // All operations stay within array bounds + pub fn update(&mut self, mut new: AccVecPart) { + let new_end = new.end(); + let start = self.parts.binary_search_by_key(&new.start, |p| p.start); + + // Figure out the number of overlapping with the new part + let mut end = match start { + Ok(start) => start, + Err(start) => start, + }; + while end < self.parts.len() && self.parts[end].start < new.end() { + end += 1; + } + + // Actually insert the new part + match start { + Ok(start) => { + // Found a part that starts at exact same address. Figure out how many pieces we need to merge. + if start == end { + // We only have one item. + if self.parts[start].data.len() <= new.data.len() { + // Replace a prefix + self.parts[start].data[..new.data.len()] + .copy_from_slice(&new.data); + } else { + // Replace and extend + self.parts[start] = new; + } + return; + } + + // Multiple items to merge + self.parts[start].data = new.data; + + // Remove the now-unnecessary parts, but keep the last one in case we need to keep some of it. + if let Some(last) = self.parts.drain(start + 1..end).last() { + // How many bytes of the last item we need to keep? + let keep_last = last.end() - new_end; + self.parts[start] + .data + .extend_from_slice(&last.data[keep_last..]); + } + } + Err(start) => { + // No exact match for start address found. + + if start == self.parts.len() { + // This is the last item, so we can just append it. + self.parts.push(new); + return; + } + + // Remove the now-unnecessary parts, but keep the last one in case we need to keep some of it. + if let Some(last) = self.parts.drain(start..end).last() { + let keep_last = last.end() - new_end; + new.data.extend_from_slice(&last.data[keep_last..]); + } + + self.parts.insert(start, new); + } + } + } + + pub fn diff(&self, _new: &MemoryInstance) -> Self { + todo!(); + } +} diff --git a/crates/services/producer/src/block_producer.rs b/crates/services/producer/src/block_producer.rs index 47e9a6643d8..f82a05b96b0 100644 --- a/crates/services/producer/src/block_producer.rs +++ b/crates/services/producer/src/block_producer.rs @@ -40,10 +40,10 @@ use fuel_core_types::{ BlockHeight, Bytes32, }, - fuel_vm::interpreter::trace::Trigger, services::{ block_producer::Components, executor::{ + TraceTrigger, TransactionExecutionStatus, UncommittedResult, }, @@ -392,7 +392,7 @@ where pub async fn execution_trace_block( &self, height: BlockHeight, - trigger: Trigger, + trigger: TraceTrigger, ) -> anyhow::Result> { let view = self.view_provider.latest_view()?; diff --git a/crates/services/producer/src/ports.rs b/crates/services/producer/src/ports.rs index 1b26f4045e9..9bc3a50bded 100644 --- a/crates/services/producer/src/ports.rs +++ b/crates/services/producer/src/ports.rs @@ -20,11 +20,11 @@ use fuel_core_types::{ TxId, }, fuel_types::BlockHeight, - fuel_vm::interpreter::trace::Trigger, services::{ block_producer::Components, executor::{ Result as ExecutorResult, + TraceTrigger, TransactionExecutionStatus, UncommittedResult, }, @@ -114,6 +114,6 @@ pub trait BlockExecutionTracer: Send + Sync { fn execution_trace( &self, block: &Block, - trigger: Trigger, + trigger: TraceTrigger, ) -> ExecutorResult>; } diff --git a/crates/services/upgradable-executor/src/executor.rs b/crates/services/upgradable-executor/src/executor.rs index a144375ca07..8e8696b7a4b 100644 --- a/crates/services/upgradable-executor/src/executor.rs +++ b/crates/services/upgradable-executor/src/executor.rs @@ -35,13 +35,13 @@ use fuel_core_types::{ }, fuel_tx::Transaction, fuel_types::BlockHeight, - fuel_vm::interpreter::trace::Trigger, services::{ block_producer::Components, executor::{ Error as ExecutorError, ExecutionResult, Result as ExecutorResult, + TraceTrigger, TransactionExecutionStatus, ValidationResult, }, @@ -379,7 +379,7 @@ where pub fn execution_traces( &self, block: &Block, - trigger: Trigger, + trigger: TraceTrigger, ) -> ExecutorResult> { let mut options: ExecutionOptions = self.config.as_ref().into(); options.execution_trace = Some(trigger); diff --git a/crates/types/Cargo.toml b/crates/types/Cargo.toml index 10e5922ca3b..f0587443416 100644 --- a/crates/types/Cargo.toml +++ b/crates/types/Cargo.toml @@ -27,6 +27,7 @@ fuel-vm-private = { workspace = true, default-features = false, features = [ rand = { workspace = true, optional = true } secrecy = "0.8" serde = { workspace = true, features = ["derive"], optional = true } +serde-big-array = { version = "0.5", default-features = false} # 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" diff --git a/crates/types/src/services/executor.rs b/crates/types/src/services/executor.rs index cfec902a23a..a5168382f3a 100644 --- a/crates/types/src/services/executor.rs +++ b/crates/types/src/services/executor.rs @@ -26,9 +26,11 @@ use crate::{ Bytes32, ContractId, Nonce, + Word, }, fuel_vm::{ checked_transaction::CheckError, + consts::VM_REGISTER_COUNT, ProgramState, }, services::Uncommitted, @@ -39,7 +41,6 @@ use alloc::{ string::String, vec::Vec, }; -use fuel_vm_private::interpreter::trace::Frame; /// The alias for executor result. pub type Result = core::result::Result; @@ -199,7 +200,7 @@ pub enum TransactionExecutionResult { /// The receipts generated by the executed transaction. receipts: Vec, /// Full execution trace, if recorded. - execution_trace: Vec, + execution_trace: Vec, /// The total gas used by the transaction. total_gas: u64, /// The total fee paid by the transaction. @@ -212,7 +213,7 @@ pub enum TransactionExecutionResult { /// The receipts generated by the executed transaction. receipts: Vec, /// Full execution trace, if recorded. - execution_trace: Vec, + execution_trace: Vec, /// The total gas used by the transaction. total_gas: u64, /// The total fee paid by the transaction. @@ -404,3 +405,25 @@ impl From for TransactionValidityError { Self::Validation(CheckError::Validity(e)) } } + +/// Snapshot of the execution state, with some delta compression applied +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct TraceFrame { + /// Registers at this point + #[cfg_attr(feature = "serde", serde(with = "serde_big_array::BigArray"))] + pub registers: [Word; VM_REGISTER_COUNT], + /// Memory delta from the previous snapshot + pub memory_diff: Vec<(usize, Vec)>, + /// How many of the receipts have been added by now + pub receipt_count: usize, +} + +/// When to record a new snapshot +#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)] +pub enum TraceTrigger { + /// Capture state after an instruction adds a new receipt + OnReceipt, + /// Capture state after each instruction + OnInstruction, +} From 3ba2bc79d9bbbef42deadec2c4c44dc35d883c63 Mon Sep 17 00:00:00 2001 From: Hannes Karppila Date: Thu, 19 Dec 2024 17:18:07 +0200 Subject: [PATCH 08/25] Change from tracing to storage read replay recording --- Cargo.lock | 138 ++++-------- Cargo.toml | 4 +- bin/fuel-core/src/cli/run.rs | 2 +- bin/fuel-core/src/cli/run/graphql.rs | 6 +- crates/client/assets/schema.sdl | 73 +----- crates/client/src/client.rs | 28 +-- crates/client/src/client/schema.rs | 2 +- .../src/client/schema/execution_trace.rs | 206 ----------------- .../src/client/schema/storage_read_replay.rs | 58 +++++ crates/client/src/client/schema/tx.rs | 2 - crates/fuel-core/src/graphql_api.rs | 4 +- crates/fuel-core/src/graphql_api/database.rs | 3 +- crates/fuel-core/src/graphql_api/ports.rs | 10 +- crates/fuel-core/src/schema/chain.rs | 3 +- crates/fuel-core/src/schema/dap.rs | 6 +- crates/fuel-core/src/schema/tx.rs | 22 +- crates/fuel-core/src/schema/tx/types.rs | 210 ++---------------- .../src/service/adapters/graphql_api.rs | 14 +- .../src/service/adapters/producer.rs | 10 +- crates/fuel-core/src/state/data_source.rs | 3 +- .../fuel-core/src/state/generic_database.rs | 12 +- .../fuel-core/src/state/historical_rocksdb.rs | 4 +- .../src/state/iterable_key_value_view.rs | 3 +- crates/fuel-core/src/state/key_value_view.rs | 3 +- crates/fuel-core/src/state/rocks_db.rs | 7 +- crates/services/executor/src/executor.rs | 197 ++++++++++------ crates/services/executor/src/lib.rs | 1 - crates/services/executor/src/trace.rs | 181 --------------- .../services/producer/src/block_producer.rs | 13 +- crates/services/producer/src/ports.rs | 9 +- .../services/upgradable-executor/Cargo.toml | 5 +- .../upgradable-executor/src/config.rs | 1 - .../upgradable-executor/src/executor.rs | 58 ++++- .../services/upgradable-executor/src/lib.rs | 3 + .../src/relayer_recorder.rs | 50 +++++ .../src/storage_access_recorder.rs | 51 +++++ crates/storage/src/kv_store.rs | 15 +- crates/storage/src/structured_storage.rs | 12 +- crates/storage/src/transactional.rs | 14 +- crates/storage/src/vm_storage.rs | 3 +- crates/types/Cargo.toml | 1 - crates/types/src/services/executor.rs | 40 +--- crates/types/src/services/txpool.rs | 2 - 43 files changed, 518 insertions(+), 971 deletions(-) delete mode 100644 crates/client/src/client/schema/execution_trace.rs create mode 100644 crates/client/src/client/schema/storage_read_replay.rs delete mode 100644 crates/services/executor/src/trace.rs create mode 100644 crates/services/upgradable-executor/src/relayer_recorder.rs create mode 100644 crates/services/upgradable-executor/src/storage_access_recorder.rs diff --git a/Cargo.lock b/Cargo.lock index 9219e50eb2d..0bc9f38389d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2292,7 +2292,6 @@ dependencies = [ "proc-macro2", "quote", "syn 2.0.90", - "unicode-xid", ] [[package]] @@ -2489,18 +2488,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "educe" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d7bc049e1bd8cdeb31b68bbd586a9464ecf9f3944af3958a7a9d0f8b9799417" -dependencies = [ - "enum-ordinalize", - "proc-macro2", - "quote", - "syn 2.0.90", -] - [[package]] name = "either" version = "1.13.0" @@ -2612,26 +2599,6 @@ dependencies = [ "syn 2.0.90", ] -[[package]] -name = "enum-ordinalize" -version = "4.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fea0dcfa4e54eeb516fe454635a95753ddd39acda650ce703031c6973e315dd5" -dependencies = [ - "enum-ordinalize-derive", -] - -[[package]] -name = "enum-ordinalize-derive" -version = "4.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d28318a75d4aead5c4db25382e8ef717932d0346600cacae6357eb5941bc5ff" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.90", -] - [[package]] name = "equivalent" version = "1.0.1" @@ -3163,20 +3130,24 @@ dependencies = [ [[package]] name = "fuel-asm" -version = "0.59.0" +version = "0.58.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f325971bf9047ec70004f80a989e03456316bc19cbef3ff3a39a38b192ab56e" dependencies = [ "bitflags 2.6.0", - "fuel-types 0.59.0", + "fuel-types 0.58.2", "serde", "strum 0.24.1", ] [[package]] name = "fuel-compression" -version = "0.59.0" +version = "0.58.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24e42841f56f76ed759b3f516e5188d5c42de47015bee951651660c13b6dfa6c" dependencies = [ - "fuel-derive 0.59.0", - "fuel-types 0.59.0", + "fuel-derive 0.58.2", + "fuel-types 0.58.2", "serde", ] @@ -3687,7 +3658,7 @@ dependencies = [ "enum-iterator", "fuel-core-storage", "fuel-core-types 0.40.0", - "fuel-vm 0.59.0", + "fuel-vm 0.58.2", "impl-tools", "itertools 0.12.1", "mockall", @@ -3829,11 +3800,10 @@ dependencies = [ "bs58", "derivative", "derive_more 0.99.18", - "fuel-vm 0.59.0", + "fuel-vm 0.58.2", "rand", "secrecy", "serde", - "serde-big-array", "tai64", "zeroize", ] @@ -3887,13 +3857,15 @@ dependencies = [ [[package]] name = "fuel-crypto" -version = "0.59.0" +version = "0.58.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65e318850ca64890ff123a99b6b866954ef49da94ab9bc6827cf6ee045568585" dependencies = [ "coins-bip32", "coins-bip39", "ecdsa", "ed25519-dalek", - "fuel-types 0.59.0", + "fuel-types 0.58.2", "k256", "lazy_static", "p256", @@ -3918,7 +3890,9 @@ dependencies = [ [[package]] name = "fuel-derive" -version = "0.59.0" +version = "0.58.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab0bc46a3552964bae5169e79b383761a54bd115ea66951a1a7a229edcefa55a" dependencies = [ "proc-macro2", "quote", @@ -3954,11 +3928,13 @@ dependencies = [ [[package]] name = "fuel-merkle" -version = "0.59.0" +version = "0.58.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c79eca6a452311c70978a5df796c0f99f27e474b69719e0db4c1d82e68800d07" dependencies = [ "derive_more 0.99.18", "digest 0.10.7", - "fuel-storage 0.59.0", + "fuel-storage 0.58.2", "hashbrown 0.13.2", "hex", "serde", @@ -3973,7 +3949,9 @@ checksum = "4c1b711f28553ddc5f3546711bd220e144ce4c1af7d9e9a1f70b2f20d9f5b791" [[package]] name = "fuel-storage" -version = "0.59.0" +version = "0.58.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d0c46b5d76b3e11197bd31e036cd8b1cb46c4d822cacc48836638080c6d2b76" [[package]] name = "fuel-tx" @@ -3999,16 +3977,18 @@ dependencies = [ [[package]] name = "fuel-tx" -version = "0.59.0" +version = "0.58.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6723bb8710ba2b70516ac94d34459593225870c937670fb3afaf82e0354667ac" dependencies = [ "bitflags 2.6.0", - "derive_more 1.0.0", - "educe", - "fuel-asm 0.59.0", + "derivative", + "derive_more 0.99.18", + "fuel-asm 0.58.2", "fuel-compression", - "fuel-crypto 0.59.0", - "fuel-merkle 0.59.0", - "fuel-types 0.59.0", + "fuel-crypto 0.58.2", + "fuel-merkle 0.58.2", + "fuel-types 0.58.2", "hashbrown 0.14.5", "itertools 0.10.5", "postcard", @@ -4031,9 +4011,11 @@ dependencies = [ [[package]] name = "fuel-types" -version = "0.59.0" +version = "0.58.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "982265415a99b5bd6277bc24194a233bb2e18764df11c937b3dbb11a02c9e545" dependencies = [ - "fuel-derive 0.59.0", + "fuel-derive 0.58.2", "hex", "rand", "serde", @@ -4072,22 +4054,24 @@ dependencies = [ [[package]] name = "fuel-vm" -version = "0.59.0" +version = "0.58.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54b5362d7d072c72eec20581f67fc5400090c356a7f3ae77c79880b3b177b667" dependencies = [ "anyhow", "async-trait", "backtrace", "bitflags 2.6.0", + "derivative", "derive_more 0.99.18", - "educe", "ethnum", - "fuel-asm 0.59.0", + "fuel-asm 0.58.2", "fuel-compression", - "fuel-crypto 0.59.0", - "fuel-merkle 0.59.0", - "fuel-storage 0.59.0", - "fuel-tx 0.59.0", - "fuel-types 0.59.0", + "fuel-crypto 0.58.2", + "fuel-merkle 0.58.2", + "fuel-storage 0.58.2", + "fuel-tx 0.58.2", + "fuel-types 0.58.2", "hashbrown 0.14.5", "itertools 0.10.5", "libm", @@ -4100,7 +4084,6 @@ dependencies = [ "sha3", "static_assertions", "strum 0.24.1", - "substrate-bn", "tai64", ] @@ -5421,9 +5404,6 @@ name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" -dependencies = [ - "spin 0.9.8", -] [[package]] name = "lazycell" @@ -8511,15 +8491,6 @@ dependencies = [ "serde_derive", ] -[[package]] -name = "serde-big-array" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11fc7cc2c76d73e0f27ee52abbd64eec84d46f370c88371120433196934e4b7f" -dependencies = [ - "serde", -] - [[package]] name = "serde_derive" version = "1.0.215" @@ -9001,19 +8972,6 @@ dependencies = [ "syn 2.0.90", ] -[[package]] -name = "substrate-bn" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b5bbfa79abbae15dd642ea8176a21a635ff3c00059961d1ea27ad04e5b441c" -dependencies = [ - "byteorder", - "crunchy", - "lazy_static", - "rand", - "rustc-hex", -] - [[package]] name = "subtle" version = "2.6.1" diff --git a/Cargo.toml b/Cargo.toml index bcb0108b548..05f8048b908 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -87,9 +87,7 @@ fuel-core-xtask = { version = "0.0.0", path = "./xtask" } fuel-gas-price-algorithm = { version = "0.40.0", path = "crates/fuel-gas-price-algorithm" } # Fuel dependencies -fuel-vm-private = { path = "../fuel-vm/fuel-vm", package = "fuel-vm", default-features = false } -# fuel-vm-private = { git = "https://github.com/FuelLabs/fuel-vm", branch = "dento/execution-trace", package = "fuel-vm", default-features = false } -# fuel-vm-private = { version = "0.58.2", package = "fuel-vm", default-features = false } +fuel-vm-private = { version = "0.58.2", package = "fuel-vm", default-features = false } # Common dependencies anyhow = "1.0" diff --git a/bin/fuel-core/src/cli/run.rs b/bin/fuel-core/src/cli/run.rs index 485f0ee740d..49bc0c94750 100644 --- a/bin/fuel-core/src/cli/run.rs +++ b/bin/fuel-core/src/cli/run.rs @@ -543,7 +543,7 @@ impl Command { get_peers: graphql.costs.get_peers, estimate_predicates: graphql.costs.estimate_predicates, dry_run: graphql.costs.dry_run, - execution_trace_block: graphql.costs.execution_trace_block, + storage_read_replay: graphql.costs.storage_read_replay, submit: graphql.costs.submit, submit_and_await: graphql.costs.submit_and_await, status_change: graphql.costs.status_change, diff --git a/bin/fuel-core/src/cli/run/graphql.rs b/bin/fuel-core/src/cli/run/graphql.rs index 78e4933c7c7..57c511c7a63 100644 --- a/bin/fuel-core/src/cli/run/graphql.rs +++ b/bin/fuel-core/src/cli/run/graphql.rs @@ -114,11 +114,11 @@ pub struct QueryCosts { /// Query costs for generating execution trace for a block.® #[clap( - long = "query-cost-execution-trace-block", - default_value = DEFAULT_QUERY_COSTS.execution_trace_block.to_string(), + long = "query-cost-storage-read-replay", + default_value = DEFAULT_QUERY_COSTS.storage_read_replay.to_string(), env )] - pub execution_trace_block: usize, + pub storage_read_replay: usize, /// Query costs for submitting a transaction. #[clap( diff --git a/crates/client/assets/schema.sdl b/crates/client/assets/schema.sdl index b3805d795ec..dd42c07b0fa 100644 --- a/crates/client/assets/schema.sdl +++ b/crates/client/assets/schema.sdl @@ -736,7 +736,7 @@ type Mutation { """ Get execution trace for an already-executed transaction. """ - executionTraceBlock(height: U32!, trigger: TraceTrigger!): [TraceTransactionExecutionStatus!]! + storageReadReplay(height: U32!): [[StorageReadReplayEvent!]!]! """ Submits transaction to the `TxPool`. @@ -1122,6 +1122,12 @@ type StateTransitionPurpose { root: Bytes32! } +type StorageReadReplayEvent { + column: String! + key: HexString! + value: HexString +} + type SubmittedStatus { time: Tai64Timestamp! @@ -1174,71 +1180,6 @@ type SuccessStatus { scalar Tai64Timestamp -type TraceFailureStatus { - programState: ProgramState - reason: String! - receipts: [Receipt!]! - executionTrace: [TraceFrame!]! - totalGas: U64! - totalFee: U64! -} - -type TraceFrame { - """ - Register values - """ - registers: [U64!]! - """ - Changes to memory `(address, bytes)` that occurred since the last frame - """ - memoryDiff: [TraceFrameMemoryPatch!]! - """ - How many of the original receipts have been produced by this point - """ - receiptCount: Int! -} - -type TraceFrameMemoryPatch { - """ - Start address - """ - start: Int! - """ - Bytes of data - """ - bytes: HexString! -} - -type TraceSuccessStatus { - programState: ProgramState - receipts: [Receipt!]! - executionTrace: [TraceFrame!]! - totalGas: U64! - totalFee: U64! -} - -type TraceTransactionExecutionStatus { - id: TransactionId! - status: TraceTransactionStatus! - receipts: [Receipt!]! -} - -union TraceTransactionStatus = TraceSuccessStatus | TraceFailureStatus - -""" -One of the films in the Star Wars Trilogy -""" -enum TraceTrigger { - """ - After each instruction - """ - ON_INSTRUCTION - """ - After an instruction has created a receipt - """ - ON_RECEIPT -} - type Transaction { id: TransactionId! inputAssetIds: [AssetId!] diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index 941740f88c2..e14827e6a38 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -65,7 +65,7 @@ use fuel_core_types::{ Nonce, }, services::executor::{ - TraceTrigger, + StorageReadReplayEvent, TransactionExecutionStatus, }, }; @@ -509,26 +509,26 @@ impl FuelClient { .collect() } - /// Execution trace for a block - pub async fn execution_trace_block( + /// Get storage read replay for a block + pub async fn storage_read_replay( &self, height: &BlockHeight, - trigger: TraceTrigger, - ) -> io::Result> { + ) -> io::Result>> { let query: Operation< - schema::execution_trace::ExectionTraceBlock, - schema::execution_trace::ExectionTraceBlockArgs, - > = schema::execution_trace::ExectionTraceBlock::build( - schema::execution_trace::ExectionTraceBlockArgs { + schema::storage_read_replay::StorageReadReplay, + schema::storage_read_replay::StorageReadReplayArgs, + > = schema::storage_read_replay::StorageReadReplay::build( + schema::storage_read_replay::StorageReadReplayArgs { height: (*height).into(), - trigger: trigger.into(), }, ); - let tx_statuses = self.query(query).await.map(|r| r.execution_trace_block)?; - tx_statuses + Ok(self + .query(query) + .await + .map(|r| r.storage_read_replay)? .into_iter() - .map(|tx_status| tx_status.try_into().map_err(Into::into)) - .collect() + .map(|events| events.into_iter().map(Into::into).collect()) + .collect()) } /// Estimate predicates for the transaction diff --git a/crates/client/src/client/schema.rs b/crates/client/src/client/schema.rs index d28fb73f773..5d758bd5a39 100644 --- a/crates/client/src/client/schema.rs +++ b/crates/client/src/client/schema.rs @@ -33,9 +33,9 @@ pub mod chain; pub mod coins; pub mod contract; pub mod da_compressed; -pub mod execution_trace; pub mod message; pub mod node_info; +pub mod storage_read_replay; pub mod upgrades; pub mod gas_price; diff --git a/crates/client/src/client/schema/execution_trace.rs b/crates/client/src/client/schema/execution_trace.rs deleted file mode 100644 index 6bb090af8c2..00000000000 --- a/crates/client/src/client/schema/execution_trace.rs +++ /dev/null @@ -1,206 +0,0 @@ -use crate::client::{ - schema::{ - schema, - tx::transparent_receipt::Receipt, - Address, - ConversionError, - TransactionId, - U32, - U64, - }, - PageDirection, - PaginationRequest, -}; -use fuel_core_types::{ - fuel_tx, - services::executor::{ - TransactionExecutionResult, - TransactionExecutionStatus, - }, -}; -use std::convert::{ - TryFrom, - TryInto, -}; - -use super::tx::ProgramState; - -#[allow(clippy::enum_variant_names)] -#[derive(cynic::InlineFragments, Clone, Debug)] -#[cynic(schema_path = "./assets/schema.sdl")] -pub enum TraceTransactionStatus { - SuccessStatus(TraceSuccessStatus), - FailureStatus(TraceFailureStatus), - #[cynic(fallback)] - Unknown, -} - -impl TryFrom for TransactionExecutionResult { - type Error = ConversionError; - - fn try_from(status: TraceTransactionStatus) -> Result { - Ok(match status { - TraceTransactionStatus::SuccessStatus(s) => { - let receipts = s - .receipts - .into_iter() - .map(|receipt| receipt.try_into()) - .collect::, _>>()?; - TransactionExecutionResult::Success { - result: s.program_state.map(TryInto::try_into).transpose()?, - receipts, - execution_trace: Vec::new(), // Not produced by dry-run - total_gas: s.total_gas.0, - total_fee: s.total_fee.0, - } - } - TraceTransactionStatus::FailureStatus(s) => { - let receipts = s - .receipts - .into_iter() - .map(|receipt| receipt.try_into()) - .collect::, _>>()?; - TransactionExecutionResult::Failed { - result: s.program_state.map(TryInto::try_into).transpose()?, - receipts, - execution_trace: Vec::new(), // Not produced by dry-run - total_gas: s.total_gas.0, - total_fee: s.total_fee.0, - } - } - TraceTransactionStatus::Unknown => { - return Err(Self::Error::UnknownVariant("ExecutionTraceTxStatus")) - } - }) - } -} - -#[derive(cynic::QueryFragment, Clone, Debug)] -#[cynic(schema_path = "./assets/schema.sdl")] -pub struct TraceSuccessStatus { - pub program_state: Option, - pub receipts: Vec, - pub total_gas: U64, - pub total_fee: U64, -} - -#[derive(cynic::QueryFragment, Clone, Debug)] -#[cynic(schema_path = "./assets/schema.sdl")] -pub struct TraceFailureStatus { - pub program_state: Option, - pub receipts: Vec, - pub total_gas: U64, - pub total_fee: U64, -} - -#[derive(cynic::QueryFragment, Clone, Debug)] -#[cynic(schema_path = "./assets/schema.sdl")] -pub struct TraceTransactionExecutionStatus { - pub id: TransactionId, - pub status: TraceTransactionStatus, -} - -impl TryFrom for TransactionExecutionStatus { - type Error = ConversionError; - - fn try_from(schema: TraceTransactionExecutionStatus) -> Result { - let id = schema.id.into(); - let status = schema.status.try_into()?; - - Ok(TransactionExecutionStatus { id, result: status }) - } -} - -#[derive(cynic::QueryVariables, Debug)] -pub struct TransactionsByOwnerConnectionArgs { - /// Select transactions based on related `owner`s - pub owner: Address, - /// Skip until cursor (forward pagination) - pub after: Option, - /// Skip until cursor (backward pagination) - pub before: Option, - /// Retrieve the first n transactions in order (forward pagination) - pub first: Option, - /// Retrieve the last n transactions in order (backward pagination). - /// Can't be used at the same time as `first`. - pub last: Option, -} - -impl From<(Address, PaginationRequest)> for TransactionsByOwnerConnectionArgs { - fn from(r: (Address, PaginationRequest)) -> Self { - match r.1.direction { - PageDirection::Forward => TransactionsByOwnerConnectionArgs { - owner: r.0, - after: r.1.cursor, - before: None, - first: Some(r.1.results), - last: None, - }, - PageDirection::Backward => TransactionsByOwnerConnectionArgs { - owner: r.0, - after: None, - before: r.1.cursor, - first: None, - last: Some(r.1.results), - }, - } - } -} - -// mutations - -/// When to record a trace frames during execution -#[derive(cynic::Enum, Debug, Copy, Clone, Eq, PartialEq)] -#[cynic(schema_path = "./assets/schema.sdl", graphql_type = "TraceTrigger")] -pub enum TraceTrigger { - /// After each instruction - OnInstruction, - /// After an instruction has created a receipt - OnReceipt, -} -impl From for TraceTrigger { - fn from(trigger: fuel_core_types::services::executor::TraceTrigger) -> Self { - match trigger { - fuel_core_types::services::executor::TraceTrigger::OnInstruction => { - TraceTrigger::OnInstruction - } - fuel_core_types::services::executor::TraceTrigger::OnReceipt => { - TraceTrigger::OnReceipt - } - } - } -} - -#[derive(cynic::QueryVariables, Debug)] -pub struct ExectionTraceBlockArgs { - pub height: U32, - pub trigger: TraceTrigger, -} - -/// Retrieves the transaction in opaque form -#[derive(cynic::QueryFragment, Clone, Debug)] -#[cynic( - schema_path = "./assets/schema.sdl", - graphql_type = "Mutation", - variables = "ExectionTraceBlockArgs" -)] -pub struct ExectionTraceBlock { - #[arguments(height: $height, trigger: $trigger)] - pub execution_trace_block: Vec, -} - -#[cfg(test)] -pub mod tests { - use super::*; - use fuel_core_types::fuel_types::BlockHeight; - - #[test] - fn execution_trace_block_tx_gql_output() { - use cynic::MutationBuilder; - let query = ExectionTraceBlock::build(ExectionTraceBlockArgs { - height: BlockHeight::new(1234).into(), - trigger: TraceTrigger::OnReceipt, - }); - insta::assert_snapshot!(query.query) - } -} diff --git a/crates/client/src/client/schema/storage_read_replay.rs b/crates/client/src/client/schema/storage_read_replay.rs new file mode 100644 index 00000000000..075f4952bb9 --- /dev/null +++ b/crates/client/src/client/schema/storage_read_replay.rs @@ -0,0 +1,58 @@ +use super::HexString; +use crate::client::schema::{ + schema, + U32, +}; + +#[derive(cynic::QueryFragment, Clone, Debug)] +#[cynic(schema_path = "./assets/schema.sdl")] +pub struct StorageReadReplayEvent { + pub column: String, + pub key: HexString, + pub value: Option, +} +impl From + for fuel_core_types::services::executor::StorageReadReplayEvent +{ + fn from(event: StorageReadReplayEvent) -> Self { + fuel_core_types::services::executor::StorageReadReplayEvent { + column: event.column, + key: event.key.into(), + value: event.value.map(Into::into), + } + } +} + +// mutations + +#[derive(cynic::QueryVariables, Debug)] +pub struct StorageReadReplayArgs { + pub height: U32, +} + +/// Retrieves the transaction in opaque form +#[derive(cynic::QueryFragment, Clone, Debug)] +#[cynic( + schema_path = "./assets/schema.sdl", + graphql_type = "Mutation", + variables = "StorageReadReplayArgs" +)] +pub struct StorageReadReplay { + #[arguments(height: $height)] + pub storage_read_replay: Vec>, +} + +#[cfg(test)] +pub mod tests { + use super::*; + use fuel_core_types::fuel_types::BlockHeight; + + #[test] + fn execution_trace_block_tx_gql_output() { + use cynic::MutationBuilder; + let query = StorageReadReplay::build(StorageReadReplayArgs { + height: BlockHeight::new(1234).into(), + }); + insta::assert_snapshot!(query.query) + } +} diff --git a/crates/client/src/client/schema/tx.rs b/crates/client/src/client/schema/tx.rs index 144f63b1992..f67152f9db3 100644 --- a/crates/client/src/client/schema/tx.rs +++ b/crates/client/src/client/schema/tx.rs @@ -292,7 +292,6 @@ impl TryFrom for TransactionExecutionResult { TransactionExecutionResult::Success { result: s.program_state.map(TryInto::try_into).transpose()?, receipts, - execution_trace: Vec::new(), // Not produced by dry-run total_gas: s.total_gas.0, total_fee: s.total_fee.0, } @@ -306,7 +305,6 @@ impl TryFrom for TransactionExecutionResult { TransactionExecutionResult::Failed { result: s.program_state.map(TryInto::try_into).transpose()?, receipts, - execution_trace: Vec::new(), // Not produced by dry-run total_gas: s.total_gas.0, total_fee: s.total_fee.0, } diff --git a/crates/fuel-core/src/graphql_api.rs b/crates/fuel-core/src/graphql_api.rs index bf7cf4011ee..313cfc7b43f 100644 --- a/crates/fuel-core/src/graphql_api.rs +++ b/crates/fuel-core/src/graphql_api.rs @@ -56,7 +56,7 @@ pub struct Costs { pub get_peers: usize, pub estimate_predicates: usize, pub dry_run: usize, - pub execution_trace_block: usize, + pub storage_read_replay: usize, pub submit: usize, pub submit_and_await: usize, pub status_change: usize, @@ -89,7 +89,7 @@ pub const DEFAULT_QUERY_COSTS: Costs = Costs { get_peers: 40001, estimate_predicates: 40001, dry_run: 12000, - execution_trace_block: 1_000_000, + storage_read_replay: 1_000_000, submit: 40001, submit_and_await: 40001, status_change: 40001, diff --git a/crates/fuel-core/src/graphql_api/database.rs b/crates/fuel-core/src/graphql_api/database.rs index 36f02562ca6..7ef9b976bf4 100644 --- a/crates/fuel-core/src/graphql_api/database.rs +++ b/crates/fuel-core/src/graphql_api/database.rs @@ -264,10 +264,9 @@ impl StorageRead for ReadView { fn read( &self, key: &BlobId, - offset: usize, buf: &mut [u8], ) -> Result, Self::Error> { - StorageRead::::read(self.on_chain.as_ref(), key, offset, buf) + StorageRead::::read(self.on_chain.as_ref(), key, buf) } fn read_alloc(&self, key: &BlobId) -> Result>, Self::Error> { diff --git a/crates/fuel-core/src/graphql_api/ports.rs b/crates/fuel-core/src/graphql_api/ports.rs index 7489ed2a213..57f1006f3a5 100644 --- a/crates/fuel-core/src/graphql_api/ports.rs +++ b/crates/fuel-core/src/graphql_api/ports.rs @@ -55,7 +55,10 @@ use fuel_core_types::{ }, fuel_vm::interpreter::Memory, services::{ - executor::TransactionExecutionStatus, + executor::{ + StorageReadReplayEvent, + TransactionExecutionStatus, + }, graphql_api::ContractBalance, p2p::PeerInfo, txpool::TransactionStatus, @@ -235,11 +238,10 @@ pub trait BlockProducerPort: Send + Sync { gas_price: Option, ) -> anyhow::Result>; - async fn execution_trace_block( + async fn storage_read_replay( &self, height: BlockHeight, - trigger: Trigger, - ) -> anyhow::Result>; + ) -> anyhow::Result>>; } #[async_trait::async_trait] diff --git a/crates/fuel-core/src/schema/chain.rs b/crates/fuel-core/src/schema/chain.rs index 17b7c464ea8..9acdbb01940 100644 --- a/crates/fuel-core/src/schema/chain.rs +++ b/crates/fuel-core/src/schema/chain.rs @@ -280,8 +280,7 @@ impl GasCosts { GasCostsValues::V1(_) | GasCostsValues::V2(_) | GasCostsValues::V3(_) - | GasCostsValues::V4(_) - | GasCostsValues::V5(_) => GasCostsVersion::V1, + | GasCostsValues::V4(_) => GasCostsVersion::V1, } } diff --git a/crates/fuel-core/src/schema/dap.rs b/crates/fuel-core/src/schema/dap.rs index d554c3aae00..7e920f3ead2 100644 --- a/crates/fuel-core/src/schema/dap.rs +++ b/crates/fuel-core/src/schema/dap.rs @@ -132,7 +132,7 @@ impl ConcreteStorage { let fee_params = params.fee_params(); let ready_tx = checked_tx - .into_ready(GAS_PRICE, gas_costs, fee_params, None) + .into_ready(GAS_PRICE, gas_costs, fee_params) .map_err(|e| { anyhow!("Failed to apply dynamic values to checked tx: {:?}", e) })?; @@ -176,7 +176,7 @@ impl ConcreteStorage { let fee_params = params.fee_params(); let ready_tx = checked_tx - .into_ready(GAS_PRICE, gas_costs, fee_params, None) + .into_ready(GAS_PRICE, gas_costs, fee_params) .map_err(|e| { anyhow!("Failed to apply dynamic values to checked tx: {:?}", e) })?; @@ -456,7 +456,7 @@ impl DapMutation { match checked_tx { CheckedTransaction::Script(script) => { let ready_tx = script - .into_ready(GAS_PRICE, gas_costs, fee_params, None) + .into_ready(GAS_PRICE, gas_costs, fee_params) .map_err(|e| { anyhow!("Failed to apply dynamic values to checked tx: {:?}", e) })?; diff --git a/crates/fuel-core/src/schema/tx.rs b/crates/fuel-core/src/schema/tx.rs index f555afb966f..9a14aabb3cf 100644 --- a/crates/fuel-core/src/schema/tx.rs +++ b/crates/fuel-core/src/schema/tx.rs @@ -76,8 +76,7 @@ use std::{ }; use types::{ DryRunTransactionExecutionStatus, - TraceTransactionExecutionStatus, - TraceTrigger, + StorageReadReplayEvent, Transaction, }; @@ -338,20 +337,23 @@ impl TxMutation { /// Get execution trace for an already-executed transaction. #[graphql(complexity = "query_costs().dry_run + child_complexity")] - async fn execution_trace_block( + async fn storage_read_replay( &self, ctx: &Context<'_>, height: U32, - trigger: TraceTrigger, - ) -> async_graphql::Result> { + ) -> async_graphql::Result>> { let block_height = height.into(); let block_producer = ctx.data_unchecked::(); - let status = block_producer - .execution_trace_block(block_height, trigger.into()) - .await?; - Ok(status + Ok(block_producer + .storage_read_replay(block_height) + .await? .into_iter() - .map(TraceTransactionExecutionStatus) + .map(|items| { + items + .into_iter() + .map(StorageReadReplayEvent::from) + .collect() + }) .collect()) } diff --git a/crates/fuel-core/src/schema/tx/types.rs b/crates/fuel-core/src/schema/tx/types.rs index f333f087905..0d313930cd2 100644 --- a/crates/fuel-core/src/schema/tx/types.rs +++ b/crates/fuel-core/src/schema/tx/types.rs @@ -78,10 +78,7 @@ use fuel_core_types::{ TxId, }, fuel_types::canonical::Serialize, - fuel_vm::{ - consts::VM_REGISTER_COUNT, - ProgramState as VmProgramState, - }, + fuel_vm::ProgramState as VmProgramState, services::{ executor::{ TransactionExecutionResult, @@ -867,7 +864,6 @@ impl DryRunTransactionStatus { receipts, total_gas, total_fee, - execution_trace: _, // Discard, as dry run doesn't support this } => DryRunTransactionStatus::Success(DryRunSuccessStatus { result, receipts, @@ -879,7 +875,6 @@ impl DryRunTransactionStatus { receipts, total_gas, total_fee, - execution_trace: _, // Discard, as dry run doesn't support this } => DryRunTransactionStatus::Failed(DryRunFailureStatus { result, receipts, @@ -890,54 +885,6 @@ impl DryRunTransactionStatus { } } -/// When to record a trace frames during execution -#[derive(Enum, Debug, Copy, Clone, Eq, PartialEq)] -pub enum TraceTrigger { - /// After each instruction - OnInstruction, - /// After an instruction has created a receipt - OnReceipt, -} - -#[derive(Union, Debug)] -pub enum TraceTransactionStatus { - Success(TraceSuccessStatus), - Failed(TraceFailureStatus), -} - -impl TraceTransactionStatus { - pub fn new(tx_status: TransactionExecutionResult) -> Self { - match tx_status { - TransactionExecutionResult::Success { - result, - receipts, - total_gas, - total_fee, - execution_trace, - } => TraceTransactionStatus::Success(TraceSuccessStatus { - result, - receipts, - execution_trace, - total_gas, - total_fee, - }), - TransactionExecutionResult::Failed { - result, - receipts, - execution_trace, - total_gas, - total_fee, - } => TraceTransactionStatus::Failed(TraceFailureStatus { - result, - receipts, - execution_trace, - total_gas, - total_fee, - }), - } - } -} - fn input_contracts(tx: &Tx) -> IntoIter<&fuel_core_types::fuel_types::ContractId> where Tx: Inputs, @@ -1033,157 +980,36 @@ impl DryRunTransactionExecutionStatus { } } -#[derive(Debug)] -pub struct TraceFrame(Frame); - -impl From for TraceFrame { - fn from(frame: Frame) -> Self { - Self(frame) - } -} - -#[Object] -impl TraceFrame { - /// Register values - async fn registers(&self) -> [U64; VM_REGISTER_COUNT] { - self.0.registers.map(|r| r.into()) - } - - /// Changes to memory `(address, bytes)` that occurred since the last frame - async fn memory_diff(&self) -> Vec { - self.0 - .memory_diff - .clone() - .into_iter() - .map(|c| c.into()) - .collect() - } - - /// How many of the original receipts have been produced by this point - async fn receipt_count(&self) -> usize { - self.0.receipt_count - } +pub struct StorageReadReplayEvent { + column: String, + key: HexString, + value: Option, } -#[derive(Debug)] -pub struct TraceFrameMemoryPatch { - start: usize, - bytes: Vec, -} -impl From - for TraceFrameMemoryPatch +impl From + for StorageReadReplayEvent { - fn from(change: fuel_core_types::fuel_vm::interpreter::MemorySliceChange) -> Self { + fn from(event: fuel_core_types::services::executor::StorageReadReplayEvent) -> Self { Self { - start: change.global_start, - bytes: change.data, + column: event.column, + key: HexString(event.key), + value: event.value.map(HexString), } } } #[Object] -impl TraceFrameMemoryPatch { - /// Start address - async fn start(&self) -> u32 { - u32::try_from(self.start).expect("Invalid memory address") +impl StorageReadReplayEvent { + async fn column(&self) -> String { + self.column.clone() } - /// Bytes of data - async fn bytes(&self) -> HexString { - self.bytes.clone().into() + async fn key(&self) -> HexString { + self.key.clone() } -} - -#[derive(Debug)] -pub struct TraceSuccessStatus { - result: Option, - receipts: Vec, - execution_trace: Vec, - total_gas: u64, - total_fee: u64, -} -#[Object] -impl TraceSuccessStatus { - async fn program_state(&self) -> Option { - self.result.map(Into::into) - } - - async fn receipts(&self) -> Vec { - self.receipts.iter().map(Into::into).collect() - } - - async fn execution_trace(&self) -> Vec { - self.execution_trace - .clone() - .into_iter() - .map(TraceFrame::from) - .collect() - } - - async fn total_gas(&self) -> U64 { - self.total_gas.into() - } - - async fn total_fee(&self) -> U64 { - self.total_fee.into() - } -} - -#[derive(Debug)] -pub struct TraceFailureStatus { - result: Option, - receipts: Vec, - execution_trace: Vec, - total_gas: u64, - total_fee: u64, -} - -#[Object] -impl TraceFailureStatus { - async fn program_state(&self) -> Option { - self.result.map(Into::into) - } - - async fn reason(&self) -> String { - TransactionExecutionResult::reason(&self.receipts, &self.result) - } - - async fn receipts(&self) -> Vec { - self.receipts.iter().map(Into::into).collect() - } - - async fn execution_trace(&self) -> Vec { - self.execution_trace - .clone() - .into_iter() - .map(TraceFrame::from) - .collect() - } - - async fn total_gas(&self) -> U64 { - self.total_gas.into() - } - - async fn total_fee(&self) -> U64 { - self.total_fee.into() - } -} - -pub struct TraceTransactionExecutionStatus(pub TransactionExecutionStatus); - -#[Object] -impl TraceTransactionExecutionStatus { - async fn id(&self) -> TransactionId { - TransactionId(self.0.id) - } - - async fn status(&self) -> TraceTransactionStatus { - TraceTransactionStatus::new(self.0.result.clone()) - } - - async fn receipts(&self) -> Vec { - self.0.result.receipts().iter().map(Into::into).collect() + async fn value(&self) -> Option { + self.value.clone() } } diff --git a/crates/fuel-core/src/service/adapters/graphql_api.rs b/crates/fuel-core/src/service/adapters/graphql_api.rs index 1fd260fbc44..667a12b5cba 100644 --- a/crates/fuel-core/src/service/adapters/graphql_api.rs +++ b/crates/fuel-core/src/service/adapters/graphql_api.rs @@ -43,7 +43,10 @@ use fuel_core_types::{ fuel_types::BlockHeight, services::{ block_importer::SharedImportResult, - executor::TransactionExecutionStatus, + executor::{ + StorageReadReplayEvent, + TransactionExecutionStatus, + }, p2p::PeerInfo, txpool::TransactionStatus, }, @@ -124,14 +127,11 @@ impl BlockProducerPort for BlockProducerAdapter { .await } - async fn execution_trace_block( + async fn storage_read_replay( &self, height: BlockHeight, - trigger: Trigger, - ) -> anyhow::Result> { - self.block_producer - .execution_trace_block(height, trigger) - .await + ) -> anyhow::Result>> { + self.block_producer.storage_read_replay(height).await } } diff --git a/crates/fuel-core/src/service/adapters/producer.rs b/crates/fuel-core/src/service/adapters/producer.rs index 1ac4453d91b..d5763192957 100644 --- a/crates/fuel-core/src/service/adapters/producer.rs +++ b/crates/fuel-core/src/service/adapters/producer.rs @@ -64,6 +64,7 @@ use fuel_core_types::{ block_producer::Components, executor::{ Result as ExecutorResult, + StorageReadReplayEvent, TransactionExecutionStatus, UncommittedResult, }, @@ -128,13 +129,12 @@ impl fuel_core_producer::ports::DryRunner for ExecutorAdapter { } } -impl fuel_core_producer::ports::BlockExecutionTracer for ExecutorAdapter { - fn execution_trace( +impl fuel_core_producer::ports::StorageReadReplayRecorder for ExecutorAdapter { + fn storage_read_replay( &self, block: &Block, - trigger: Trigger, - ) -> ExecutorResult> { - self.executor.execution_traces(block, trigger) + ) -> ExecutorResult>> { + self.executor.storage_read_replay(block) } } diff --git a/crates/fuel-core/src/state/data_source.rs b/crates/fuel-core/src/state/data_source.rs index f30f3ee8268..cf107d0e1b9 100644 --- a/crates/fuel-core/src/state/data_source.rs +++ b/crates/fuel-core/src/state/data_source.rs @@ -67,10 +67,9 @@ where &self, key: &[u8], column: Self::Column, - offset: usize, buf: &mut [u8], ) -> StorageResult> { - self.data.read(key, column, offset, buf) + self.data.read(key, column, buf) } } diff --git a/crates/fuel-core/src/state/generic_database.rs b/crates/fuel-core/src/state/generic_database.rs index 5a31591268f..b6f5f2ea464 100644 --- a/crates/fuel-core/src/state/generic_database.rs +++ b/crates/fuel-core/src/state/generic_database.rs @@ -79,13 +79,8 @@ where M: Mappable, StructuredStorage: StorageRead, { - fn read( - &self, - key: &M::Key, - offset: usize, - buf: &mut [u8], - ) -> Result, Self::Error> { - self.storage.storage::().read(key, offset, buf) + fn read(&self, key: &M::Key, buf: &mut [u8]) -> Result, Self::Error> { + self.storage.storage::().read(key, buf) } fn read_alloc(&self, key: &M::Key) -> Result>, Self::Error> { @@ -129,10 +124,9 @@ where &self, key: &[u8], column: Self::Column, - offset: usize, buf: &mut [u8], ) -> StorageResult> { - KeyValueInspect::read(&self.storage, key, column, offset, buf) + KeyValueInspect::read(&self.storage, key, column, buf) } } diff --git a/crates/fuel-core/src/state/historical_rocksdb.rs b/crates/fuel-core/src/state/historical_rocksdb.rs index 059fb35f12b..a0bd39b2801 100644 --- a/crates/fuel-core/src/state/historical_rocksdb.rs +++ b/crates/fuel-core/src/state/historical_rocksdb.rs @@ -534,11 +534,9 @@ where &self, key: &[u8], column: Self::Column, - offset: usize, buf: &mut [u8], ) -> StorageResult> { - self.db - .read(key, Column::OriginalColumn(column), offset, buf) + self.db.read(key, Column::OriginalColumn(column), buf) } } diff --git a/crates/fuel-core/src/state/iterable_key_value_view.rs b/crates/fuel-core/src/state/iterable_key_value_view.rs index 1d8fd02cbbb..40ab3483ede 100644 --- a/crates/fuel-core/src/state/iterable_key_value_view.rs +++ b/crates/fuel-core/src/state/iterable_key_value_view.rs @@ -67,10 +67,9 @@ where &self, key: &[u8], column: Self::Column, - offset: usize, buf: &mut [u8], ) -> StorageResult> { - self.0.read(key, column, offset, buf) + self.0.read(key, column, buf) } } diff --git a/crates/fuel-core/src/state/key_value_view.rs b/crates/fuel-core/src/state/key_value_view.rs index e507e83329a..9e70037fc21 100644 --- a/crates/fuel-core/src/state/key_value_view.rs +++ b/crates/fuel-core/src/state/key_value_view.rs @@ -54,9 +54,8 @@ where &self, key: &[u8], column: Self::Column, - offset: usize, buf: &mut [u8], ) -> StorageResult> { - self.0.read(key, column, offset, buf) + self.0.read(key, column, buf) } } diff --git a/crates/fuel-core/src/state/rocks_db.rs b/crates/fuel-core/src/state/rocks_db.rs index 7f0d31d05e2..e69c6289bed 100644 --- a/crates/fuel-core/src/state/rocks_db.rs +++ b/crates/fuel-core/src/state/rocks_db.rs @@ -758,7 +758,6 @@ where &self, key: &[u8], column: Self::Column, - offset: usize, mut buf: &mut [u8], ) -> StorageResult> { self.metrics.read_meter.inc(); @@ -770,10 +769,8 @@ where .get_pinned_cf_opt(&self.cf(column), key, &self.read_options) .map_err(|e| DatabaseError::Other(e.into()))? .map(|value| { - let Some(read) = value.len().checked_sub(offset) else { - return Ok(0); - }; - std::io::Write::write_all(&mut buf, &value.as_ref()[offset..]) + let read = value.len(); + std::io::Write::write_all(&mut buf, value.as_ref()) .map_err(|e| DatabaseError::Other(anyhow::anyhow!(e)))?; StorageResult::Ok(read) }) diff --git a/crates/services/executor/src/executor.rs b/crates/services/executor/src/executor.rs index 915b10973af..a6b548ca024 100644 --- a/crates/services/executor/src/executor.rs +++ b/crates/services/executor/src/executor.rs @@ -5,10 +5,6 @@ use crate::{ TransactionsSource, }, refs::ContractRef, - trace::{ - TraceOnInstruction, - TraceOnReceipt, - }, }; use fuel_core_storage::{ column::Column, @@ -116,14 +112,11 @@ use fuel_core_types::{ IntoChecked, }, interpreter::{ - trace::ExecutionTraceHooks, CheckedMetadata as CheckedMetadataTrait, ExecutableTransaction, InterpreterParams, Memory, MemoryInstance, - NoTrace, - NotSupportedEcal, }, state::StateTransition, Backtrace as FuelBacktrace, @@ -138,8 +131,6 @@ use fuel_core_types::{ ExecutionResult, ForcedTransactionFailure, Result as ExecutorResult, - TraceFrame, - TraceTrigger, TransactionExecutionResult, TransactionExecutionStatus, TransactionValidityError, @@ -262,9 +253,6 @@ pub struct ExecutionOptions { pub extra_tx_checks: bool, /// Print execution backtraces if transaction execution reverts. pub backtrace: bool, - /// Record execution traces for each transaction with the given trigger - #[serde(default)] - pub execution_trace: Option, } /// The executor instance performs block production and validation. Given a block, it will execute all @@ -342,6 +330,19 @@ where Ok(UncommittedResult::new(result, changes)) } + pub fn record_storage_reads_for( + self, + block: &Block, + tx_separator_callback: &mut dyn FnMut(usize) -> (), + ) -> ExecutorResult> { + let consensus_params_version = block.header().consensus_parameters_version; + let (block_executor, storage_tx) = + self.into_executor(consensus_params_version)?; + Ok(block_executor + .record_storage_reads_for(block, storage_tx, tx_separator_callback)? + .tx_status) + } + pub fn validate_without_commit( self, block: &Block, @@ -714,6 +715,109 @@ where Ok(()) } + #[tracing::instrument(skip_all)] + fn record_storage_reads_for( + mut self, + block: &Block, + mut block_storage_tx: StorageTransaction, + tx_separator_callback: &mut dyn FnMut(usize) -> (), + ) -> ExecutorResult + where + D: KeyValueInspect, + { + let mut data = ExecutionData::new(); + + let partial_header = PartialBlockHeader::from(block.header()); + let mut partial_block = PartialFuelBlock::new(partial_header, vec![]); + let transactions = block.transactions(); + let mut memory = MemoryInstance::new(); + + let (gas_price, coinbase_contract_id) = + Self::get_coinbase_info_from_mint_tx(transactions)?; + + // self.process_l1_txs( + // &mut partial_block, + // coinbase_contract_id, + // &mut block_storage_tx, + // &mut data, + // &mut memory, + // )?; + + let block_height = *partial_block.header.height(); + let da_block_height = partial_block.header.da_height; + let relayed_txs = self.get_relayed_txs( + block_height, + da_block_height, + &mut data, + &mut block_storage_tx, + &mut memory, + )?; + + // self.process_relayed_txs( + // relayed_txs, + // block, + // storage_tx, + // data, + // coinbase_contract_id, + // memory, + // )?; + + let consensus_parameters_version = + partial_block.header.consensus_parameters_version; + let relayed_tx_iter = relayed_txs.into_iter(); + for (i, checked) in relayed_tx_iter.enumerate() { + tx_separator_callback(i); + let maybe_checked_transaction = MaybeCheckedTransaction::CheckedTransaction( + checked, + consensus_parameters_version, + ); + let tx_id = maybe_checked_transaction.id(&self.consensus_params.chain_id()); + match self.execute_transaction_and_commit( + &mut partial_block, + &mut block_storage_tx, + &mut data, + maybe_checked_transaction, + Self::RELAYED_GAS_PRICE, + coinbase_contract_id, + &mut memory, + ) { + Ok(_) => {} + Err(err) => { + let event = ExecutorEvent::ForcedTransactionFailed { + id: tx_id.into(), + block_height, + failure: err.to_string(), + }; + data.events.push(event); + } + } + } + + let processed_l1_tx_count = partial_block.transactions.len(); + + for (i, transaction) in + transactions.iter().enumerate().skip(processed_l1_tx_count) + { + tx_separator_callback(i); + let maybe_checked_tx = + MaybeCheckedTransaction::Transaction(transaction.clone()); + self.execute_transaction_and_commit( + &mut partial_block, + &mut block_storage_tx, + &mut data, + maybe_checked_tx, + gas_price, + coinbase_contract_id, + &mut memory, + )?; + } + + self.check_block_matches(partial_block, block, &data)?; + + data.changes = block_storage_tx.into_changes(); + Ok(data) + } + #[tracing::instrument(skip_all)] fn validate_block( mut self, @@ -1264,45 +1368,15 @@ where checked_tx = self.extra_tx_checks(checked_tx, header, storage_tx, memory)?; } - let (reverted, state, tx, receipts, trace_frames) = - match self.options.execution_trace { - Some(TraceTrigger::OnInstruction) => { - let (reverted, state, tx, receipts, trace) = self - .attempt_tx_execution_with_vm::<_, _, TraceOnInstruction>( - checked_tx, - header, - coinbase_contract_id, - gas_price, - storage_tx, - memory, - )?; - (reverted, state, tx, receipts, trace.frames) - } - Some(TraceTrigger::OnReceipt) => { - let (reverted, state, tx, receipts, trace) = self - .attempt_tx_execution_with_vm::<_, _, TraceOnReceipt>( - checked_tx, - header, - coinbase_contract_id, - gas_price, - storage_tx, - memory, - )?; - (reverted, state, tx, receipts, trace.frames) - } - None => { - let (reverted, state, tx, receipts, _) = self - .attempt_tx_execution_with_vm::<_, _, NoTrace>( - checked_tx, - header, - coinbase_contract_id, - gas_price, - storage_tx, - memory, - )?; - (reverted, state, tx, receipts, Vec::new()) - } - }; + let (reverted, state, tx, receipts) = self + .attempt_tx_execution_with_vm::<_, _>( + checked_tx, + header, + coinbase_contract_id, + gas_price, + storage_tx, + memory, + )?; self.spend_input_utxos(tx.inputs(), storage_tx, reverted, execution_data)?; @@ -1323,7 +1397,6 @@ where &tx, execution_data, receipts, - trace_frames, gas_price, reverted, state, @@ -1396,7 +1469,6 @@ where result: TransactionExecutionResult::Success { result: None, receipts: vec![], - execution_trace: Vec::new(), total_gas: 0, total_fee: 0, }, @@ -1504,7 +1576,6 @@ where tx: &Tx, execution_data: &mut ExecutionData, receipts: Vec, - execution_trace: Vec, gas_price: Word, reverted: bool, state: ProgramState, @@ -1539,7 +1610,6 @@ where TransactionExecutionResult::Failed { result: Some(state), receipts, - execution_trace, total_gas: used_gas, total_fee: tx_fee, } @@ -1548,7 +1618,6 @@ where TransactionExecutionResult::Success { result: Some(state), receipts, - execution_trace, total_gas: used_gas, total_fee: tx_fee, } @@ -1639,7 +1708,7 @@ where } #[allow(clippy::type_complexity)] - fn attempt_tx_execution_with_vm( + fn attempt_tx_execution_with_vm( &self, checked_tx: Checked, header: &PartialBlockHeader, @@ -1647,12 +1716,11 @@ where gas_price: Word, storage_tx: &mut TxStorageTransaction, memory: &mut MemoryInstance, - ) -> ExecutorResult<(bool, ProgramState, Tx, Vec, Trace)> + ) -> ExecutorResult<(bool, ProgramState, Tx, Vec)> where Tx: ExecutableTransaction + Cacheable, ::Metadata: CheckedMetadataTrait + Send + Sync, T: KeyValueInspect, - Trace: ExecutionTraceHooks + Clone + Default, { let tx_id = checked_tx.id(); @@ -1676,9 +1744,9 @@ where .iter() .map(|input| input.predicate_gas_used()) .collect(); - let ready_tx = checked_tx.into_ready(gas_price, gas_costs, fee_params, None)?; // TODO: block_height argument? + let ready_tx = checked_tx.into_ready(gas_price, gas_costs, fee_params)?; - let mut vm = Interpreter::<_, _, _, NotSupportedEcal, Trace>::with_storage( + let mut vm = Interpreter::<_, _, _>::with_storage( memory, vm_db, InterpreterParams::new(gas_price, &self.consensus_params), @@ -1693,7 +1761,6 @@ where .into(); let reverted = vm_result.should_revert(); - let trace = vm.trace().clone(); let (state, mut tx, receipts): (_, Tx, _) = vm_result.into_inner(); #[cfg(debug_assertions)] { @@ -1715,7 +1782,7 @@ where } self.update_tx_outputs(storage_tx, tx_id, &mut tx)?; - Ok((reverted, state, tx, receipts.to_vec(), trace)) + Ok((reverted, state, tx, receipts.to_vec())) } fn verify_inputs_exist_and_values_match( @@ -2020,9 +2087,9 @@ where } /// Log a VM backtrace if configured to do so - fn log_backtrace( + fn log_backtrace( &self, - vm: &Interpreter, Tx, Ecal, Trace>, + vm: &Interpreter, Tx, Ecal>, receipts: &[Receipt], ) where M: Memory, diff --git a/crates/services/executor/src/lib.rs b/crates/services/executor/src/lib.rs index 75fa2332973..d28e5dbb612 100644 --- a/crates/services/executor/src/lib.rs +++ b/crates/services/executor/src/lib.rs @@ -10,7 +10,6 @@ extern crate alloc; pub mod executor; pub mod ports; pub mod refs; -pub mod trace; #[cfg(test)] fuel_core_trace::enable_tracing!(); diff --git a/crates/services/executor/src/trace.rs b/crates/services/executor/src/trace.rs deleted file mode 100644 index 7150a4b54d3..00000000000 --- a/crates/services/executor/src/trace.rs +++ /dev/null @@ -1,181 +0,0 @@ -use alloc::vec::Vec; -use fuel_core_types::{ - fuel_vm::{ - consts::VM_REGISTER_COUNT, - interpreter::{ - trace::ExecutionTraceHooks, - Memory, - MemoryInstance, - }, - }, - services::executor::TraceFrame, -}; - -/// Used to capture an execution trace for every instruction. -#[derive(Debug, Clone, Default)] -pub struct TraceOnInstruction { - /// Append-only set of frames - pub frames: Vec, - /// Memory at the time of the previous snapshot - acc_memory: AccVec, -} - -impl ExecutionTraceHooks for TraceOnInstruction { - fn after_instruction( - vm: &mut fuel_core_types::fuel_vm::Interpreter, - ) where - M: Memory, - { - let memory_diff = vm.trace().acc_memory.diff(vm.memory().as_ref()); - for patch in memory_diff.parts.iter() { - vm.trace_mut().acc_memory.update(patch.clone()); - } - - let mut registers = [0; VM_REGISTER_COUNT]; - registers.copy_from_slice(vm.registers()); - - let receipt_count = vm.receipts().len(); - - vm.trace_mut().frames.push(TraceFrame { - memory_diff: memory_diff - .parts - .into_iter() - .map(|p| (p.start, p.data)) - .collect(), - registers, - receipt_count, - }) - } -} - -/// Used to capture an execution trace for after each receipt. -#[derive(Debug, Clone, Default)] -pub struct TraceOnReceipt { - /// Append-only set of frames - pub frames: Vec, - /// Memory at the time of the previous snapshot - acc_memory: AccVec, -} - -impl ExecutionTraceHooks for TraceOnReceipt { - fn after_instruction( - vm: &mut fuel_core_types::fuel_vm::Interpreter, - ) where - M: Memory, - { - if vm - .trace() - .frames - .last() - .map(|s| s.receipt_count) - .unwrap_or(0) - < vm.receipts().len() - { - let memory_diff = vm.trace().acc_memory.diff(vm.memory().as_ref()); - for patch in memory_diff.parts.iter() { - vm.trace_mut().acc_memory.update(patch.clone()); - } - - let mut registers = [0; VM_REGISTER_COUNT]; - registers.copy_from_slice(vm.registers()); - - let receipt_count = vm.receipts().len(); - - vm.trace_mut().frames.push(TraceFrame { - memory_diff: memory_diff - .parts - .into_iter() - .map(|p| (p.start, p.data)) - .collect(), - registers, - receipt_count, - }) - } - } -} - -#[derive(Debug, Clone, Default)] -struct AccVec { - parts: Vec, -} - -#[derive(Debug, Clone)] -struct AccVecPart { - start: usize, - data: Vec, -} -impl AccVecPart { - #[allow(clippy::arithmetic_side_effects)] // VM memory is always within bounds - fn end(&self) -> usize { - self.start + self.data.len() - } -} - -impl AccVec { - #[allow(clippy::arithmetic_side_effects)] // All operations stay within array bounds - pub fn update(&mut self, mut new: AccVecPart) { - let new_end = new.end(); - let start = self.parts.binary_search_by_key(&new.start, |p| p.start); - - // Figure out the number of overlapping with the new part - let mut end = match start { - Ok(start) => start, - Err(start) => start, - }; - while end < self.parts.len() && self.parts[end].start < new.end() { - end += 1; - } - - // Actually insert the new part - match start { - Ok(start) => { - // Found a part that starts at exact same address. Figure out how many pieces we need to merge. - if start == end { - // We only have one item. - if self.parts[start].data.len() <= new.data.len() { - // Replace a prefix - self.parts[start].data[..new.data.len()] - .copy_from_slice(&new.data); - } else { - // Replace and extend - self.parts[start] = new; - } - return; - } - - // Multiple items to merge - self.parts[start].data = new.data; - - // Remove the now-unnecessary parts, but keep the last one in case we need to keep some of it. - if let Some(last) = self.parts.drain(start + 1..end).last() { - // How many bytes of the last item we need to keep? - let keep_last = last.end() - new_end; - self.parts[start] - .data - .extend_from_slice(&last.data[keep_last..]); - } - } - Err(start) => { - // No exact match for start address found. - - if start == self.parts.len() { - // This is the last item, so we can just append it. - self.parts.push(new); - return; - } - - // Remove the now-unnecessary parts, but keep the last one in case we need to keep some of it. - if let Some(last) = self.parts.drain(start..end).last() { - let keep_last = last.end() - new_end; - new.data.extend_from_slice(&last.data[keep_last..]); - } - - self.parts.insert(start, new); - } - } - } - - pub fn diff(&self, _new: &MemoryInstance) -> Self { - todo!(); - } -} diff --git a/crates/services/producer/src/block_producer.rs b/crates/services/producer/src/block_producer.rs index f82a05b96b0..d013dc4d405 100644 --- a/crates/services/producer/src/block_producer.rs +++ b/crates/services/producer/src/block_producer.rs @@ -43,7 +43,7 @@ use fuel_core_types::{ services::{ block_producer::Components, executor::{ - TraceTrigger, + StorageReadReplayEvent, TransactionExecutionStatus, UncommittedResult, }, @@ -384,16 +384,15 @@ impl where ViewProvider: HistoricalView + 'static, ViewProvider::LatestView: BlockProducerDatabase, - Executor: ports::BlockExecutionTracer + 'static, + Executor: ports::StorageReadReplayRecorder + 'static, GasPriceProvider: GasPriceProviderConstraint, ConsensusProvider: ConsensusParametersProvider, { - /// Re-executes an old block, getting full execution traces. - pub async fn execution_trace_block( + /// Re-executes an old block, getting the storage read events. + pub async fn storage_read_replay( &self, height: BlockHeight, - trigger: TraceTrigger, - ) -> anyhow::Result> { + ) -> anyhow::Result>> { let view = self.view_provider.latest_view()?; let block = view.get_block(&height)?; @@ -404,7 +403,7 @@ where .collect::, _>>()?; let block = block.into_owned().uncompress(transactions); - Ok(self.executor.execution_trace(&block, trigger)?) + Ok(self.executor.storage_read_replay(&block)?) } } diff --git a/crates/services/producer/src/ports.rs b/crates/services/producer/src/ports.rs index 9bc3a50bded..c60cbf8435d 100644 --- a/crates/services/producer/src/ports.rs +++ b/crates/services/producer/src/ports.rs @@ -24,7 +24,7 @@ use fuel_core_types::{ block_producer::Components, executor::{ Result as ExecutorResult, - TraceTrigger, + StorageReadReplayEvent, TransactionExecutionStatus, UncommittedResult, }, @@ -110,10 +110,9 @@ pub trait DryRunner: Send + Sync { ) -> ExecutorResult>; } -pub trait BlockExecutionTracer: Send + Sync { - fn execution_trace( +pub trait StorageReadReplayRecorder: Send + Sync { + fn storage_read_replay( &self, block: &Block, - trigger: TraceTrigger, - ) -> ExecutorResult>; + ) -> ExecutorResult>>; } diff --git a/crates/services/upgradable-executor/Cargo.toml b/crates/services/upgradable-executor/Cargo.toml index 16eeb0fc391..c93cf1e3d42 100644 --- a/crates/services/upgradable-executor/Cargo.toml +++ b/crates/services/upgradable-executor/Cargo.toml @@ -11,7 +11,8 @@ description = "Fuel Block Upgradable Executor" build = "build.rs" [dependencies] -anyhow = { workspace = true, optional = true } +anyhow = { workspace = true } +# anyhow = { workspace = true, optional = true } derive_more = { workspace = true, optional = true } fuel-core-executor = { workspace = true } fuel-core-storage = { workspace = true, features = ["std"] } @@ -47,7 +48,7 @@ smt = [ "fuel-core-wasm-executor?/smt", ] wasm-executor = [ - "dep:anyhow", + # "dep:anyhow", "dep:derive_more", "dep:parking_lot", "dep:postcard", diff --git a/crates/services/upgradable-executor/src/config.rs b/crates/services/upgradable-executor/src/config.rs index 3ba9de4f7ea..7367cf318e3 100644 --- a/crates/services/upgradable-executor/src/config.rs +++ b/crates/services/upgradable-executor/src/config.rs @@ -20,7 +20,6 @@ impl From<&Config> for ExecutionOptions { Self { extra_tx_checks: value.utxo_validation_default, backtrace: value.backtrace, - execution_trace: None, } } } diff --git a/crates/services/upgradable-executor/src/executor.rs b/crates/services/upgradable-executor/src/executor.rs index 8e8696b7a4b..88312abe283 100644 --- a/crates/services/upgradable-executor/src/executor.rs +++ b/crates/services/upgradable-executor/src/executor.rs @@ -1,6 +1,10 @@ -use crate::config::Config; #[cfg(feature = "wasm-executor")] use crate::error::UpgradableError; +use crate::{ + config::Config, + relayer_recorder::RelayerRecorder, + storage_access_recorder::StorageAccessRecorder, +}; use fuel_core_executor::{ executor::{ @@ -41,7 +45,7 @@ use fuel_core_types::{ Error as ExecutorError, ExecutionResult, Result as ExecutorResult, - TraceTrigger, + StorageReadReplayEvent, TransactionExecutionStatus, ValidationResult, }, @@ -342,7 +346,6 @@ where let options = ExecutionOptions { extra_tx_checks: utxo_validation, backtrace: self.config.backtrace, - execution_trace: None, }; let component = Components { @@ -376,15 +379,50 @@ where self.validate_inner(block, options) } - pub fn execution_traces( + pub fn storage_read_replay( &self, block: &Block, - trigger: TraceTrigger, - ) -> ExecutorResult> { - let mut options: ExecutionOptions = self.config.as_ref().into(); - options.execution_trace = Some(trigger); - self.validate_inner(block, options) - .map(|v| v.into_result().tx_status) + ) -> ExecutorResult>> { + // HERE! + + let block_version = block.header().state_transition_bytecode_version; + let native_executor_version = self.native_executor_version(); + if block_version != native_executor_version { + todo!("Handle this. Wasm?"); // TODO + } + + let previous_block_height = block.header().height().pred(); + let relayer = self.relayer_view_provider.latest_view()?; + let relayer = RelayerRecorder::new(relayer); + let relayer_rec = relayer.record.clone(); + + let mut accesses_per_tx = Vec::new(); + + if let Some(previous_block_height) = previous_block_height { + let database = self.storage_view_provider.view_at(&previous_block_height)?; + let database = StorageAccessRecorder::new(database); + let database_rec = database.record.clone(); + + let executor = + ExecutionInstance::new(relayer, database, self.config.as_ref().into()); + + let result = executor.record_storage_reads_for(block, &mut |index| { + println!("== tx {index} starts =="); + let changes = database_rec.take(); + if index > 0 { + // We don't care about block-level accesses, only the tx-level ones + accesses_per_tx.push(changes); + } + })?; + + dbg!(&accesses_per_tx, database_rec, relayer_rec); + + result + } else { + todo!(); + }; + + Ok(accesses_per_tx) } #[cfg(feature = "wasm-executor")] diff --git a/crates/services/upgradable-executor/src/lib.rs b/crates/services/upgradable-executor/src/lib.rs index baade6d4fab..4cfb9de5dc3 100644 --- a/crates/services/upgradable-executor/src/lib.rs +++ b/crates/services/upgradable-executor/src/lib.rs @@ -7,6 +7,9 @@ pub mod config; pub mod error; pub mod executor; +mod relayer_recorder; +mod storage_access_recorder; + #[cfg(feature = "wasm-executor")] pub mod instance; diff --git a/crates/services/upgradable-executor/src/relayer_recorder.rs b/crates/services/upgradable-executor/src/relayer_recorder.rs new file mode 100644 index 00000000000..f4ce2e7be3f --- /dev/null +++ b/crates/services/upgradable-executor/src/relayer_recorder.rs @@ -0,0 +1,50 @@ +use fuel_core_executor::ports::RelayerPort; +use fuel_core_types::{ + blockchain::primitives::DaBlockHeight, + services::relayer::Event, +}; +use std::{ + cell::RefCell, + collections::BTreeMap, + sync::Arc, +}; + +#[derive(Debug, Clone, Default)] +pub struct Relayer(RefCell>>); + +impl Relayer { + pub fn add_event(&self, da_block_height: DaBlockHeight, events: Vec) { + self.0.borrow_mut().insert(da_block_height, events); + } +} + +#[derive(Debug, Clone)] +pub struct RelayerRecorder { + storage: S, + pub record: Arc>, +} + +impl RelayerRecorder { + pub fn new(storage: S) -> Self { + Self { + storage, + record: Default::default(), + } + } +} + +impl RelayerPort for RelayerRecorder +where + S: RelayerPort, +{ + fn enabled(&self) -> bool { + self.storage.enabled() + } + + fn get_events(&self, da_height: &DaBlockHeight) -> anyhow::Result> { + println!("events for {:?}", da_height); // TODO: remove this + let events = self.storage.get_events(da_height)?; + self.record.borrow().add_event(*da_height, events.clone()); + Ok(events) + } +} diff --git a/crates/services/upgradable-executor/src/storage_access_recorder.rs b/crates/services/upgradable-executor/src/storage_access_recorder.rs new file mode 100644 index 00000000000..23704efd285 --- /dev/null +++ b/crates/services/upgradable-executor/src/storage_access_recorder.rs @@ -0,0 +1,51 @@ +use fuel_core_storage::{ + kv_store::{ + KeyValueInspect, + StorageColumn, + Value, + }, + Result as StorageResult, +}; +use fuel_core_types::services::executor::StorageReadReplayEvent; +use std::{ + cell::RefCell, + sync::Arc, +}; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct StorageAccessRecorder +where + S: KeyValueInspect, +{ + pub storage: S, + pub record: Arc>>, +} + +impl StorageAccessRecorder +where + S: KeyValueInspect, +{ + pub fn new(storage: S) -> Self { + Self { + storage, + record: Default::default(), + } + } +} + +impl KeyValueInspect for StorageAccessRecorder +where + S: KeyValueInspect, +{ + type Column = S::Column; + + fn get(&self, key: &[u8], column: Self::Column) -> StorageResult> { + let value = self.storage.get(key, column)?; + self.record.borrow_mut().push(StorageReadReplayEvent { + column: column.name(), + key: key.to_vec(), + value: value.as_ref().map(|v| v.to_vec()), + }); + Ok(value) + } +} diff --git a/crates/storage/src/kv_store.rs b/crates/storage/src/kv_store.rs index 214b46fe51b..3e7c1359114 100644 --- a/crates/storage/src/kv_store.rs +++ b/crates/storage/src/kv_store.rs @@ -67,23 +67,17 @@ pub trait KeyValueInspect { &self, key: &[u8], column: Self::Column, - offset: usize, buf: &mut [u8], ) -> StorageResult> { self.get(key, column)? .map(|value| { let read = value.len(); - if offset.saturating_add(read) != buf.len() { + if read != buf.len() { return Err(StorageError::Other(anyhow::anyhow!( - "Buffer size is not equal to the value size after offset" + "Buffer size is not equal to the value size" ))); } - if offset >= buf.len() { - return Err(StorageError::Other(anyhow::anyhow!( - "Offset too large for the buffer" - ))); - } - buf.copy_from_slice(&value.as_ref()[offset..]); + buf.copy_from_slice(value.as_ref()); Ok(read) }) .transpose() @@ -117,10 +111,9 @@ where &self, key: &[u8], column: Self::Column, - offset: usize, buf: &mut [u8], ) -> StorageResult> { - self.deref().read(key, column, offset, buf) + self.deref().read(key, column, buf) } } diff --git a/crates/storage/src/structured_storage.rs b/crates/storage/src/structured_storage.rs index 4e1239c5982..122792cbf8f 100644 --- a/crates/storage/src/structured_storage.rs +++ b/crates/storage/src/structured_storage.rs @@ -147,10 +147,9 @@ where &self, key: &[u8], column: Self::Column, - offset: usize, buf: &mut [u8], ) -> StorageResult> { - self.inner.read(key, column, offset, buf) + self.inner.read(key, column, buf) } } @@ -359,7 +358,6 @@ where fn read( &self, key: &::Key, - offset: usize, buf: &mut [u8], ) -> Result, Self::Error> { let key_encoder = @@ -367,12 +365,8 @@ where key, ); let key_bytes = key_encoder.as_bytes(); - self.inner.read( - key_bytes.as_ref(), - ::column(), - offset, - buf, - ) + self.inner + .read(key_bytes.as_ref(), ::column(), buf) } fn read_alloc( diff --git a/crates/storage/src/transactional.rs b/crates/storage/src/transactional.rs index 7740828f110..4494393ad33 100644 --- a/crates/storage/src/transactional.rs +++ b/crates/storage/src/transactional.rs @@ -392,30 +392,24 @@ where &self, key: &[u8], column: Self::Column, - offset: usize, buf: &mut [u8], ) -> StorageResult> { if let Some(operation) = self.get_from_changes(key, column) { match operation { WriteOperation::Insert(value) => { let read = value.len(); - if offset.saturating_add(read) != buf.len() { + if read != buf.len() { return Err(crate::Error::Other(anyhow::anyhow!( - "Buffer size is not equal to the value size after offset" + "Buffer size is not equal to the value size" ))); } - if offset >= buf.len() { - return Err(crate::Error::Other(anyhow::anyhow!( - "Offset too large for the buffer" - ))); - } - buf.copy_from_slice(&value.as_ref()[offset..]); + buf.copy_from_slice(value.as_ref()); Ok(Some(read)) } WriteOperation::Remove => Ok(None), } } else { - self.storage.read(key, column, offset, buf) + self.storage.read(key, column, buf) } } } diff --git a/crates/storage/src/vm_storage.rs b/crates/storage/src/vm_storage.rs index 99f6c360628..6188c3cf85c 100644 --- a/crates/storage/src/vm_storage.rs +++ b/crates/storage/src/vm_storage.rs @@ -193,10 +193,9 @@ where fn read( &self, key: &M::Key, - offset: usize, buf: &mut [u8], ) -> Result, Self::Error> { - StorageRead::::read(&self.database, key, offset, buf) + StorageRead::::read(&self.database, key, buf) } fn read_alloc( diff --git a/crates/types/Cargo.toml b/crates/types/Cargo.toml index f0587443416..10e5922ca3b 100644 --- a/crates/types/Cargo.toml +++ b/crates/types/Cargo.toml @@ -27,7 +27,6 @@ fuel-vm-private = { workspace = true, default-features = false, features = [ rand = { workspace = true, optional = true } secrecy = "0.8" serde = { workspace = true, features = ["derive"], optional = true } -serde-big-array = { version = "0.5", default-features = false} # 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" diff --git a/crates/types/src/services/executor.rs b/crates/types/src/services/executor.rs index a5168382f3a..30a4fbe7b74 100644 --- a/crates/types/src/services/executor.rs +++ b/crates/types/src/services/executor.rs @@ -26,11 +26,9 @@ use crate::{ Bytes32, ContractId, Nonce, - Word, }, fuel_vm::{ checked_transaction::CheckError, - consts::VM_REGISTER_COUNT, ProgramState, }, services::Uncommitted, @@ -199,8 +197,6 @@ pub enum TransactionExecutionResult { result: Option, /// The receipts generated by the executed transaction. receipts: Vec, - /// Full execution trace, if recorded. - execution_trace: Vec, /// The total gas used by the transaction. total_gas: u64, /// The total fee paid by the transaction. @@ -212,8 +208,6 @@ pub enum TransactionExecutionResult { result: Option, /// The receipts generated by the executed transaction. receipts: Vec, - /// Full execution trace, if recorded. - execution_trace: Vec, /// The total gas used by the transaction. total_gas: u64, /// The total fee paid by the transaction. @@ -262,6 +256,18 @@ impl TransactionExecutionResult { } } +/// When storage in column:key was read, it contained this value. +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct StorageReadReplayEvent { + /// Column in the storage, identified by name. + pub column: String, + /// Key in the column. + pub key: Vec, + /// Value at the column:key pair. None if the key was not found. + pub value: Option>, +} + #[allow(missing_docs)] #[derive(Debug, Clone, PartialEq, derive_more::Display, derive_more::From)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] @@ -405,25 +411,3 @@ impl From for TransactionValidityError { Self::Validation(CheckError::Validity(e)) } } - -/// Snapshot of the execution state, with some delta compression applied -#[derive(Debug, Clone)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct TraceFrame { - /// Registers at this point - #[cfg_attr(feature = "serde", serde(with = "serde_big_array::BigArray"))] - pub registers: [Word; VM_REGISTER_COUNT], - /// Memory delta from the previous snapshot - pub memory_diff: Vec<(usize, Vec)>, - /// How many of the receipts have been added by now - pub receipt_count: usize, -} - -/// When to record a new snapshot -#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)] -pub enum TraceTrigger { - /// Capture state after an instruction adds a new receipt - OnReceipt, - /// Capture state after each instruction - OnInstruction, -} diff --git a/crates/types/src/services/txpool.rs b/crates/types/src/services/txpool.rs index dd0ee5c835f..c684a78b092 100644 --- a/crates/types/src/services/txpool.rs +++ b/crates/types/src/services/txpool.rs @@ -377,7 +377,6 @@ pub fn from_executor_to_status( receipts, total_gas, total_fee, - execution_trace: _, // Discarded, txpool doesn't use these } => TransactionStatus::Success { block_height, time, @@ -391,7 +390,6 @@ pub fn from_executor_to_status( receipts, total_gas, total_fee, - execution_trace: _, // Discarded, txpool doesn't use these } => TransactionStatus::Failed { block_height, time, From 5abde1cd722dc73f7921b1c325d8cb12af08748d Mon Sep 17 00:00:00 2001 From: Hannes Karppila Date: Wed, 15 Jan 2025 17:13:49 +0200 Subject: [PATCH 09/25] Add wasm executor support --- Cargo.lock | 1 + crates/client/assets/schema.sdl | 2 +- crates/client/src/client.rs | 4 +- .../src/client/schema/storage_read_replay.rs | 2 +- crates/fuel-core/src/graphql_api/ports.rs | 2 +- crates/fuel-core/src/schema/tx.rs | 9 +- .../src/service/adapters/graphql_api.rs | 2 +- .../src/service/adapters/producer.rs | 2 +- crates/services/executor/src/executor.rs | 13 -- .../services/producer/src/block_producer.rs | 2 +- crates/services/producer/src/ports.rs | 2 +- .../services/upgradable-executor/Cargo.toml | 5 +- .../upgradable-executor/src/executor.rs | 121 ++++++++++++++---- .../services/upgradable-executor/src/lib.rs | 4 +- .../src/relayer_recorder.rs | 50 -------- .../src/storage_access_recorder.rs | 8 +- crates/types/Cargo.toml | 1 + crates/types/src/services/executor.rs | 13 +- 18 files changed, 130 insertions(+), 113 deletions(-) delete mode 100644 crates/services/upgradable-executor/src/relayer_recorder.rs diff --git a/Cargo.lock b/Cargo.lock index 6fa3ea715a0..b2d7c9bff86 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3809,6 +3809,7 @@ dependencies = [ "derivative", "derive_more 0.99.18", "fuel-vm 0.59.1", + "hex", "rand", "secrecy", "serde", diff --git a/crates/client/assets/schema.sdl b/crates/client/assets/schema.sdl index f581e01e2ac..44d9490b11d 100644 --- a/crates/client/assets/schema.sdl +++ b/crates/client/assets/schema.sdl @@ -738,7 +738,7 @@ type Mutation { """ Get execution trace for an already-executed transaction. """ - storageReadReplay(height: U32!): [[StorageReadReplayEvent!]!]! + storageReadReplay(height: U32!): [StorageReadReplayEvent!]! """ Submits transaction to the `TxPool`. diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index e14827e6a38..3f4df4372ea 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -513,7 +513,7 @@ impl FuelClient { pub async fn storage_read_replay( &self, height: &BlockHeight, - ) -> io::Result>> { + ) -> io::Result> { let query: Operation< schema::storage_read_replay::StorageReadReplay, schema::storage_read_replay::StorageReadReplayArgs, @@ -527,7 +527,7 @@ impl FuelClient { .await .map(|r| r.storage_read_replay)? .into_iter() - .map(|events| events.into_iter().map(Into::into).collect()) + .map(Into::into) .collect()) } diff --git a/crates/client/src/client/schema/storage_read_replay.rs b/crates/client/src/client/schema/storage_read_replay.rs index 075f4952bb9..eeb5e79b63c 100644 --- a/crates/client/src/client/schema/storage_read_replay.rs +++ b/crates/client/src/client/schema/storage_read_replay.rs @@ -39,7 +39,7 @@ pub struct StorageReadReplayArgs { )] pub struct StorageReadReplay { #[arguments(height: $height)] - pub storage_read_replay: Vec>, + pub storage_read_replay: Vec, } #[cfg(test)] diff --git a/crates/fuel-core/src/graphql_api/ports.rs b/crates/fuel-core/src/graphql_api/ports.rs index 57f1006f3a5..cbe6e702588 100644 --- a/crates/fuel-core/src/graphql_api/ports.rs +++ b/crates/fuel-core/src/graphql_api/ports.rs @@ -241,7 +241,7 @@ pub trait BlockProducerPort: Send + Sync { async fn storage_read_replay( &self, height: BlockHeight, - ) -> anyhow::Result>>; + ) -> anyhow::Result>; } #[async_trait::async_trait] diff --git a/crates/fuel-core/src/schema/tx.rs b/crates/fuel-core/src/schema/tx.rs index 9a14aabb3cf..1965ae76109 100644 --- a/crates/fuel-core/src/schema/tx.rs +++ b/crates/fuel-core/src/schema/tx.rs @@ -341,19 +341,14 @@ impl TxMutation { &self, ctx: &Context<'_>, height: U32, - ) -> async_graphql::Result>> { + ) -> async_graphql::Result> { let block_height = height.into(); let block_producer = ctx.data_unchecked::(); Ok(block_producer .storage_read_replay(block_height) .await? .into_iter() - .map(|items| { - items - .into_iter() - .map(StorageReadReplayEvent::from) - .collect() - }) + .map(StorageReadReplayEvent::from) .collect()) } diff --git a/crates/fuel-core/src/service/adapters/graphql_api.rs b/crates/fuel-core/src/service/adapters/graphql_api.rs index 667a12b5cba..a031549792c 100644 --- a/crates/fuel-core/src/service/adapters/graphql_api.rs +++ b/crates/fuel-core/src/service/adapters/graphql_api.rs @@ -130,7 +130,7 @@ impl BlockProducerPort for BlockProducerAdapter { async fn storage_read_replay( &self, height: BlockHeight, - ) -> anyhow::Result>> { + ) -> anyhow::Result> { self.block_producer.storage_read_replay(height).await } } diff --git a/crates/fuel-core/src/service/adapters/producer.rs b/crates/fuel-core/src/service/adapters/producer.rs index d5763192957..9cfd3bdf22c 100644 --- a/crates/fuel-core/src/service/adapters/producer.rs +++ b/crates/fuel-core/src/service/adapters/producer.rs @@ -133,7 +133,7 @@ impl fuel_core_producer::ports::StorageReadReplayRecorder for ExecutorAdapter { fn storage_read_replay( &self, block: &Block, - ) -> ExecutorResult>> { + ) -> ExecutorResult> { self.executor.storage_read_replay(block) } } diff --git a/crates/services/executor/src/executor.rs b/crates/services/executor/src/executor.rs index 756b5e59fcc..da985a713e7 100644 --- a/crates/services/executor/src/executor.rs +++ b/crates/services/executor/src/executor.rs @@ -330,19 +330,6 @@ where Ok(UncommittedResult::new(result, changes)) } - pub fn record_storage_reads_for( - self, - block: &Block, - tx_separator_callback: &mut dyn FnMut(usize) -> (), - ) -> ExecutorResult> { - let consensus_params_version = block.header().consensus_parameters_version; - let (block_executor, storage_tx) = - self.into_executor(consensus_params_version)?; - Ok(block_executor - .record_storage_reads_for(block, storage_tx, tx_separator_callback)? - .tx_status) - } - pub fn validate_without_commit( self, block: &Block, diff --git a/crates/services/producer/src/block_producer.rs b/crates/services/producer/src/block_producer.rs index d013dc4d405..1d59a5ffcf0 100644 --- a/crates/services/producer/src/block_producer.rs +++ b/crates/services/producer/src/block_producer.rs @@ -392,7 +392,7 @@ where pub async fn storage_read_replay( &self, height: BlockHeight, - ) -> anyhow::Result>> { + ) -> anyhow::Result> { let view = self.view_provider.latest_view()?; let block = view.get_block(&height)?; diff --git a/crates/services/producer/src/ports.rs b/crates/services/producer/src/ports.rs index c60cbf8435d..42a9c15c2c0 100644 --- a/crates/services/producer/src/ports.rs +++ b/crates/services/producer/src/ports.rs @@ -114,5 +114,5 @@ pub trait StorageReadReplayRecorder: Send + Sync { fn storage_read_replay( &self, block: &Block, - ) -> ExecutorResult>>; + ) -> ExecutorResult>; } diff --git a/crates/services/upgradable-executor/Cargo.toml b/crates/services/upgradable-executor/Cargo.toml index c93cf1e3d42..16eeb0fc391 100644 --- a/crates/services/upgradable-executor/Cargo.toml +++ b/crates/services/upgradable-executor/Cargo.toml @@ -11,8 +11,7 @@ description = "Fuel Block Upgradable Executor" build = "build.rs" [dependencies] -anyhow = { workspace = true } -# anyhow = { workspace = true, optional = true } +anyhow = { workspace = true, optional = true } derive_more = { workspace = true, optional = true } fuel-core-executor = { workspace = true } fuel-core-storage = { workspace = true, features = ["std"] } @@ -48,7 +47,7 @@ smt = [ "fuel-core-wasm-executor?/smt", ] wasm-executor = [ - # "dep:anyhow", + "dep:anyhow", "dep:derive_more", "dep:parking_lot", "dep:postcard", diff --git a/crates/services/upgradable-executor/src/executor.rs b/crates/services/upgradable-executor/src/executor.rs index 42eab027d48..01ca5af3d18 100644 --- a/crates/services/upgradable-executor/src/executor.rs +++ b/crates/services/upgradable-executor/src/executor.rs @@ -1,10 +1,9 @@ #[cfg(feature = "wasm-executor")] -use crate::error::UpgradableError; use crate::{ - config::Config, - relayer_recorder::RelayerRecorder, + error::UpgradableError, storage_access_recorder::StorageAccessRecorder, }; +use crate::config::Config; use fuel_core_executor::{ executor::{ @@ -381,50 +380,124 @@ where self.validate_inner(block, options) } + #[cfg(not(feature = "wasm-executor"))] pub fn storage_read_replay( &self, block: &Block, - ) -> ExecutorResult>> { - // HERE! + ) -> ExecutorResult> { + let block_version = block.header().state_transition_bytecode_version; + let native_executor_version = self.native_executor_version(); + if block_version == native_executor_version { + self.native_storage_read_replay(block) + } else { + Err(ExecutorError::Other(format!( + "Not supported version `{block_version}`. Expected version is `{}`", + Self::VERSION + ))) + } + } + #[cfg(feature = "wasm-executor")] + pub fn storage_read_replay( + &self, + block: &Block, + ) -> ExecutorResult> { let block_version = block.header().state_transition_bytecode_version; let native_executor_version = self.native_executor_version(); - if block_version != native_executor_version { - todo!("Handle this. Wasm?"); // TODO + if block_version == native_executor_version { + match &self.execution_strategy { + ExecutionStrategy::Native => { + self.native_storage_read_replay(block) + } + ExecutionStrategy::Wasm { module } => { + if let Some(module) = self.get_module(block_version).ok() { + self.wasm_storage_read_replay(&module, block) + } else { + self.wasm_storage_read_replay(module, block) + } + } + } + } else { + let module = self.get_module(block_version)?; + self.wasm_storage_read_replay(&module, block) } + } + pub fn native_storage_read_replay( + &self, + block: &Block, + ) -> ExecutorResult> { let previous_block_height = block.header().height().pred(); let relayer = self.relayer_view_provider.latest_view()?; - let relayer = RelayerRecorder::new(relayer); - let relayer_rec = relayer.record.clone(); - let mut accesses_per_tx = Vec::new(); - - if let Some(previous_block_height) = previous_block_height { + let storage_rec = if let Some(previous_block_height) = previous_block_height { let database = self.storage_view_provider.view_at(&previous_block_height)?; let database = StorageAccessRecorder::new(database); let database_rec = database.record.clone(); let executor = ExecutionInstance::new(relayer, database, self.config.as_ref().into()); + let _ = executor.validate_without_commit(block)?; + database_rec + } else { + let database = self.storage_view_provider.latest_view()?; + let database = StorageAccessRecorder::new(database); + let database_rec = database.record.clone(); + let executor = + ExecutionInstance::new(relayer, database, self.config.as_ref().into()); + let _ = executor.validate_without_commit(block)?; + database_rec + }; + + let mut g = storage_rec.lock(); + Ok(core::mem::take(&mut g)) + } - let result = executor.record_storage_reads_for(block, &mut |index| { - println!("== tx {index} starts =="); - let changes = database_rec.take(); - if index > 0 { - // We don't care about block-level accesses, only the tx-level ones - accesses_per_tx.push(changes); - } - })?; - dbg!(&accesses_per_tx, database_rec, relayer_rec); - result + #[cfg(feature = "wasm-executor")] + fn wasm_storage_read_replay( + &self, + module: &wasmtime::Module, + block: &Block, + ) -> ExecutorResult> { + let options = self.config.as_ref().into(); + let previous_block_height = block.header().height().pred(); + + let instance = crate::instance::Instance::new(&self.engine).no_source()?; + + let (instance, storage_rec) = if let Some(previous_block_height) = previous_block_height { + let storage = self.storage_view_provider.view_at(&previous_block_height)?; + let storage = StorageAccessRecorder::new(storage); + let storage_rec = storage.record.clone(); + (instance.add_storage(storage)?, storage_rec) } else { - todo!(); + let storage = self.storage_view_provider.latest_view()?; + let storage = StorageAccessRecorder::new(storage); + let storage_rec = storage.record.clone(); + (instance.add_storage(storage)?, storage_rec) }; - Ok(accesses_per_tx) + let relayer = self.relayer_view_provider.latest_view()?; + let instance = instance + .add_relayer(relayer)? + .add_validation_input_data(block, options)?; + + let output = instance.run(&module)?; + + match output { + ReturnType::ExecutionV0(result) => { + let _ = convert_from_v0_execution_result(result)?; + } + ReturnType::ExecutionV1(result) => { + let _ = convert_from_v1_execution_result(result)?; + } + ReturnType::Validation(result) => { + let _ = result?; + }, + } + let mut g = storage_rec.lock(); + Ok(core::mem::take(&mut g)) } #[cfg(feature = "wasm-executor")] diff --git a/crates/services/upgradable-executor/src/lib.rs b/crates/services/upgradable-executor/src/lib.rs index 4cfb9de5dc3..757c9469218 100644 --- a/crates/services/upgradable-executor/src/lib.rs +++ b/crates/services/upgradable-executor/src/lib.rs @@ -1,13 +1,13 @@ #![deny(clippy::arithmetic_side_effects)] #![deny(clippy::cast_possible_truncation)] #![deny(unused_crate_dependencies)] -#![deny(warnings)] +// #![deny(warnings)] pub mod config; pub mod error; pub mod executor; -mod relayer_recorder; +#[cfg(feature = "wasm-executor")] mod storage_access_recorder; #[cfg(feature = "wasm-executor")] diff --git a/crates/services/upgradable-executor/src/relayer_recorder.rs b/crates/services/upgradable-executor/src/relayer_recorder.rs deleted file mode 100644 index f4ce2e7be3f..00000000000 --- a/crates/services/upgradable-executor/src/relayer_recorder.rs +++ /dev/null @@ -1,50 +0,0 @@ -use fuel_core_executor::ports::RelayerPort; -use fuel_core_types::{ - blockchain::primitives::DaBlockHeight, - services::relayer::Event, -}; -use std::{ - cell::RefCell, - collections::BTreeMap, - sync::Arc, -}; - -#[derive(Debug, Clone, Default)] -pub struct Relayer(RefCell>>); - -impl Relayer { - pub fn add_event(&self, da_block_height: DaBlockHeight, events: Vec) { - self.0.borrow_mut().insert(da_block_height, events); - } -} - -#[derive(Debug, Clone)] -pub struct RelayerRecorder { - storage: S, - pub record: Arc>, -} - -impl RelayerRecorder { - pub fn new(storage: S) -> Self { - Self { - storage, - record: Default::default(), - } - } -} - -impl RelayerPort for RelayerRecorder -where - S: RelayerPort, -{ - fn enabled(&self) -> bool { - self.storage.enabled() - } - - fn get_events(&self, da_height: &DaBlockHeight) -> anyhow::Result> { - println!("events for {:?}", da_height); // TODO: remove this - let events = self.storage.get_events(da_height)?; - self.record.borrow().add_event(*da_height, events.clone()); - Ok(events) - } -} diff --git a/crates/services/upgradable-executor/src/storage_access_recorder.rs b/crates/services/upgradable-executor/src/storage_access_recorder.rs index 23704efd285..6ce208a3fd0 100644 --- a/crates/services/upgradable-executor/src/storage_access_recorder.rs +++ b/crates/services/upgradable-executor/src/storage_access_recorder.rs @@ -7,18 +7,18 @@ use fuel_core_storage::{ Result as StorageResult, }; use fuel_core_types::services::executor::StorageReadReplayEvent; +use parking_lot::Mutex; use std::{ - cell::RefCell, sync::Arc, }; -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Debug)] pub struct StorageAccessRecorder where S: KeyValueInspect, { pub storage: S, - pub record: Arc>>, + pub record: Arc>>, } impl StorageAccessRecorder @@ -41,7 +41,7 @@ where fn get(&self, key: &[u8], column: Self::Column) -> StorageResult> { let value = self.storage.get(key, column)?; - self.record.borrow_mut().push(StorageReadReplayEvent { + self.record.lock().push(StorageReadReplayEvent { column: column.name(), key: key.to_vec(), value: value.as_ref().map(|v| v.to_vec()), diff --git a/crates/types/Cargo.toml b/crates/types/Cargo.toml index 246952eef4f..116590b31f5 100644 --- a/crates/types/Cargo.toml +++ b/crates/types/Cargo.toml @@ -26,6 +26,7 @@ derive_more = { version = "0.99" } fuel-vm-private = { workspace = true, default-features = false, features = [ "alloc", ] } +hex = { version = "0.4", default-features = false } rand = { workspace = true, optional = true } secrecy = "0.8" serde = { workspace = true, features = ["derive"], optional = true } diff --git a/crates/types/src/services/executor.rs b/crates/types/src/services/executor.rs index 30a4fbe7b74..d87a4755c43 100644 --- a/crates/types/src/services/executor.rs +++ b/crates/types/src/services/executor.rs @@ -1,5 +1,7 @@ //! Types related to executor service. +use core::fmt; + use crate::{ blockchain::{ block::Block, @@ -257,7 +259,7 @@ impl TransactionExecutionResult { } /// When storage in column:key was read, it contained this value. -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct StorageReadReplayEvent { /// Column in the storage, identified by name. @@ -267,6 +269,15 @@ pub struct StorageReadReplayEvent { /// Value at the column:key pair. None if the key was not found. pub value: Option>, } +impl fmt::Debug for StorageReadReplayEvent { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("StorageReadReplayEvent") + .field("column", &self.column) + .field("key", &hex::encode(&self.key)) + .field("value", &self.value.as_ref().map(|v| hex::encode(v))) + .finish() + } +} #[allow(missing_docs)] #[derive(Debug, Clone, PartialEq, derive_more::Display, derive_more::From)] From 6827e0dd540c0ce8a4ee8861c8b2121f33309299 Mon Sep 17 00:00:00 2001 From: Hannes Karppila Date: Thu, 23 Jan 2025 10:38:50 +0200 Subject: [PATCH 10/25] Move behind --debug flag --- crates/fuel-core/src/schema/tx.rs | 10 +++++++++- crates/services/executor/src/executor.rs | 17 ++++++++--------- .../upgradable-executor/src/executor.rs | 14 ++++++-------- .../src/storage_access_recorder.rs | 4 +--- 4 files changed, 24 insertions(+), 21 deletions(-) diff --git a/crates/fuel-core/src/schema/tx.rs b/crates/fuel-core/src/schema/tx.rs index 1965ae76109..8a18d117b74 100644 --- a/crates/fuel-core/src/schema/tx.rs +++ b/crates/fuel-core/src/schema/tx.rs @@ -10,6 +10,7 @@ use crate::{ TxPool, }, query_costs, + Config as GraphQLConfig, IntoApiResult, }, graphql_api::{ @@ -336,12 +337,19 @@ impl TxMutation { } /// Get execution trace for an already-executed transaction. - #[graphql(complexity = "query_costs().dry_run + child_complexity")] + #[graphql(complexity = "query_costs().storage_read_replay + child_complexity")] async fn storage_read_replay( &self, ctx: &Context<'_>, height: U32, ) -> async_graphql::Result> { + let config = ctx.data_unchecked::().clone(); + if !config.debug { + return Err( + anyhow::anyhow!("`debug` must be enabled to use this endpoint").into(), + ); + } + let block_height = height.into(); let block_producer = ctx.data_unchecked::(); Ok(block_producer diff --git a/crates/services/executor/src/executor.rs b/crates/services/executor/src/executor.rs index da985a713e7..269277331a5 100644 --- a/crates/services/executor/src/executor.rs +++ b/crates/services/executor/src/executor.rs @@ -1355,15 +1355,14 @@ where checked_tx = self.extra_tx_checks(checked_tx, header, storage_tx, memory)?; } - let (reverted, state, tx, receipts) = self - .attempt_tx_execution_with_vm::<_, _>( - checked_tx, - header, - coinbase_contract_id, - gas_price, - storage_tx, - memory, - )?; + let (reverted, state, tx, receipts) = self.attempt_tx_execution_with_vm::<_, _>( + checked_tx, + header, + coinbase_contract_id, + gas_price, + storage_tx, + memory, + )?; self.spend_input_utxos(tx.inputs(), storage_tx, reverted, execution_data)?; diff --git a/crates/services/upgradable-executor/src/executor.rs b/crates/services/upgradable-executor/src/executor.rs index 01ca5af3d18..f301577ff4e 100644 --- a/crates/services/upgradable-executor/src/executor.rs +++ b/crates/services/upgradable-executor/src/executor.rs @@ -1,9 +1,9 @@ +use crate::config::Config; #[cfg(feature = "wasm-executor")] use crate::{ error::UpgradableError, storage_access_recorder::StorageAccessRecorder, }; -use crate::config::Config; use fuel_core_executor::{ executor::{ @@ -406,9 +406,7 @@ where let native_executor_version = self.native_executor_version(); if block_version == native_executor_version { match &self.execution_strategy { - ExecutionStrategy::Native => { - self.native_storage_read_replay(block) - } + ExecutionStrategy::Native => self.native_storage_read_replay(block), ExecutionStrategy::Wasm { module } => { if let Some(module) = self.get_module(block_version).ok() { self.wasm_storage_read_replay(&module, block) @@ -453,8 +451,6 @@ where Ok(core::mem::take(&mut g)) } - - #[cfg(feature = "wasm-executor")] fn wasm_storage_read_replay( &self, @@ -466,7 +462,9 @@ where let instance = crate::instance::Instance::new(&self.engine).no_source()?; - let (instance, storage_rec) = if let Some(previous_block_height) = previous_block_height { + let (instance, storage_rec) = if let Some(previous_block_height) = + previous_block_height + { let storage = self.storage_view_provider.view_at(&previous_block_height)?; let storage = StorageAccessRecorder::new(storage); let storage_rec = storage.record.clone(); @@ -494,7 +492,7 @@ where } ReturnType::Validation(result) => { let _ = result?; - }, + } } let mut g = storage_rec.lock(); Ok(core::mem::take(&mut g)) diff --git a/crates/services/upgradable-executor/src/storage_access_recorder.rs b/crates/services/upgradable-executor/src/storage_access_recorder.rs index 6ce208a3fd0..f495360d8ef 100644 --- a/crates/services/upgradable-executor/src/storage_access_recorder.rs +++ b/crates/services/upgradable-executor/src/storage_access_recorder.rs @@ -8,9 +8,7 @@ use fuel_core_storage::{ }; use fuel_core_types::services::executor::StorageReadReplayEvent; use parking_lot::Mutex; -use std::{ - sync::Arc, -}; +use std::sync::Arc; #[derive(Clone, Debug)] pub struct StorageAccessRecorder From acb9bab8c5b6632d2202d35f1db21f45dd18e009 Mon Sep 17 00:00:00 2001 From: Hannes Karppila Date: Thu, 23 Jan 2025 11:35:23 +0200 Subject: [PATCH 11/25] Adjust query cost --- crates/fuel-core/src/graphql_api.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/fuel-core/src/graphql_api.rs b/crates/fuel-core/src/graphql_api.rs index 313cfc7b43f..ddde37fbfb5 100644 --- a/crates/fuel-core/src/graphql_api.rs +++ b/crates/fuel-core/src/graphql_api.rs @@ -89,7 +89,7 @@ pub const DEFAULT_QUERY_COSTS: Costs = Costs { get_peers: 40001, estimate_predicates: 40001, dry_run: 12000, - storage_read_replay: 1_000_000, + storage_read_replay: 12000, submit: 40001, submit_and_await: 40001, status_change: 40001, From bb6c9ddc72a754653b291a82d1c6260b561033de Mon Sep 17 00:00:00 2001 From: Hannes Karppila Date: Thu, 23 Jan 2025 11:35:40 +0200 Subject: [PATCH 12/25] Add debug flag decription --- crates/fuel-core/src/service/config.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/fuel-core/src/service/config.rs b/crates/fuel-core/src/service/config.rs index 0e093c2b8b6..125dd95b966 100644 --- a/crates/fuel-core/src/service/config.rs +++ b/crates/fuel-core/src/service/config.rs @@ -47,6 +47,7 @@ pub struct Config { /// When `true`: /// - Enables manual block production. /// - Enables debugger endpoint. + /// - Enables storage read replay for historical blocks. /// - Allows setting `utxo_validation` to `false`. pub debug: bool, // default to false until downstream consumers stabilize From 75ff219f2a8821131b9997c9a1a0debeca3e7c84 Mon Sep 17 00:00:00 2001 From: Hannes Karppila Date: Thu, 23 Jan 2025 11:35:55 +0200 Subject: [PATCH 13/25] Approve snapshot changes --- ...y__tests__execution_trace_block_tx_gql_output.snap | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 crates/client/src/client/schema/snapshots/fuel_core_client__client__schema__storage_read_replay__tests__execution_trace_block_tx_gql_output.snap diff --git a/crates/client/src/client/schema/snapshots/fuel_core_client__client__schema__storage_read_replay__tests__execution_trace_block_tx_gql_output.snap b/crates/client/src/client/schema/snapshots/fuel_core_client__client__schema__storage_read_replay__tests__execution_trace_block_tx_gql_output.snap new file mode 100644 index 00000000000..4792798eea3 --- /dev/null +++ b/crates/client/src/client/schema/snapshots/fuel_core_client__client__schema__storage_read_replay__tests__execution_trace_block_tx_gql_output.snap @@ -0,0 +1,11 @@ +--- +source: crates/client/src/client/schema/storage_read_replay.rs +expression: query.query +--- +mutation StorageReadReplay($height: U32!) { + storageReadReplay(height: $height) { + column + key + value + } +} From 73cb7a6c5155c712a17227456b2f3e1c45f2bf6c Mon Sep 17 00:00:00 2001 From: Hannes Karppila Date: Thu, 23 Jan 2025 11:36:10 +0200 Subject: [PATCH 14/25] Add a test case --- tests/tests/lib.rs | 2 + tests/tests/storage_read_replay.rs | 168 +++++++++++++++++++++++++++++ 2 files changed, 170 insertions(+) create mode 100644 tests/tests/storage_read_replay.rs diff --git a/tests/tests/lib.rs b/tests/tests/lib.rs index c42218b045f..5198c0b5086 100644 --- a/tests/tests/lib.rs +++ b/tests/tests/lib.rs @@ -52,6 +52,8 @@ mod snapshot; #[cfg(not(feature = "only-p2p"))] mod state_rewind; #[cfg(not(feature = "only-p2p"))] +mod storage_read_replay; +#[cfg(not(feature = "only-p2p"))] mod trigger_integration; #[cfg(not(feature = "only-p2p"))] mod tx; diff --git a/tests/tests/storage_read_replay.rs b/tests/tests/storage_read_replay.rs new file mode 100644 index 00000000000..32159e526b6 --- /dev/null +++ b/tests/tests/storage_read_replay.rs @@ -0,0 +1,168 @@ +use fuel_core::service::{ + Config, + FuelService, +}; +use fuel_core_client::client::{ + types::TransactionStatus, + FuelClient, +}; +use fuel_core_types::{ + fuel_asm::{ + op, + GTFArgs, + RegId, + }, + fuel_tx::{ + Bytes32, + ContractId, + CreateMetadata, + Finalizable, + Input, + Output, + StorageSlot, + TransactionBuilder, + }, + fuel_types::BlockHeight, + fuel_vm::Salt, +}; +use futures::StreamExt; +use rand::{ + Rng, + SeedableRng, +}; + +async fn make_counter_contract( + client: &FuelClient, + rng: &mut rand::rngs::StdRng, +) -> (ContractId, BlockHeight) { + let maturity = Default::default(); + + let code: Vec<_> = [ + // Make zero key + op::movi(0x12, 32), + op::aloc(0x12), + // Read value + op::srw(0x10, 0x11, 0x12), + // Increment value + op::addi(0x10, 0x10, 1), + // Write value + op::sww(0x12, 0x11, 0x10), + // Return new counter value + op::ret(0x10), + ] + .into_iter() + .collect(); + + let salt: Salt = rng.gen(); + let tx = TransactionBuilder::create( + code.into(), + salt, + vec![StorageSlot::new(Bytes32::zeroed(), Bytes32::zeroed())], + ) + .maturity(maturity) + .add_fee_input() + .add_contract_created() + .finalize(); + + let contract_id = CreateMetadata::compute(&tx).unwrap().contract_id; + + let mut status_stream = client.submit_and_await_status(&tx.into()).await.unwrap(); + let intermediate_status = status_stream.next().await.unwrap().unwrap(); + assert!(matches!( + intermediate_status, + TransactionStatus::Submitted { .. } + )); + let final_status = status_stream.next().await.unwrap().unwrap(); + let TransactionStatus::Success { block_height, .. } = final_status else { + panic!("Tx wasn't included in a block: {:?}", final_status); + }; + (contract_id, block_height) +} + +async fn increment_counter(client: &FuelClient, + rng: &mut rand::rngs::StdRng, + + contract_id: ContractId) -> BlockHeight { + let gas_limit = 1_000_000; + let maturity = Default::default(); + + let script = [ + op::gtf_args(0x10, RegId::ZERO, GTFArgs::ScriptData), + op::call(0x10, RegId::ZERO, RegId::ZERO, RegId::CGAS), + op::log(0x10, RegId::ZERO, RegId::ZERO, RegId::ZERO), + op::ret(RegId::ONE), + ]; + + let mut script_data = contract_id.to_vec(); + script_data.extend(0u64.to_be_bytes()); + script_data.extend(0u64.to_be_bytes()); + + let tx = TransactionBuilder::script(script.into_iter().collect(), script_data) + .script_gas_limit(gas_limit) + .maturity(maturity) + .add_fee_input() + .add_input(Input::contract( + Default::default(), + rng.gen(), + rng.gen(), + Default::default(), + contract_id, + )) + .add_output(Output::contract(1, Default::default(), Default::default())) + .finalize_as_transaction(); + + let mut status_stream = client.submit_and_await_status(&tx).await.unwrap(); + let intermediate_status = status_stream.next().await.unwrap().unwrap(); + assert!(matches!( + intermediate_status, + TransactionStatus::Submitted { .. } + )); + let final_status = status_stream.next().await.unwrap().unwrap(); + let TransactionStatus::Success { block_height, .. } = final_status else { + panic!("Tx wasn't included in a block: {:?}", final_status); + }; + block_height +} + +/// Create a counter contract. +/// Increment it multiple times, and make sure the replay gives correct storage state every time. +#[tokio::test(flavor = "multi_thread")] +async fn test_storage_read_replay_returns_counter_state() { + let mut rng = rand::rngs::StdRng::seed_from_u64(0xBAADF00D); + + let mut node_config = Config::local_node(); + node_config.debug = true; + let srv = FuelService::new_node(node_config.clone()).await.unwrap(); + let client = FuelClient::from(srv.bound_address); + + let (contract_id, block_height) = make_counter_contract(&client, &mut rng).await; + + let _replay = client + .storage_read_replay(&block_height) + .await + .expect("Failed to replay storage read"); + + let mut storage_slot_key = contract_id.to_vec(); + storage_slot_key.extend(Bytes32::zeroed().to_vec()); + + for i in 0..10u64 { + let block_height = increment_counter(&client, &mut rng, contract_id).await; + + let replay = client + .storage_read_replay(&block_height) + .await + .expect("Failed to replay storage read"); + + let value = replay + .iter() + .find(|item| item.column == "ContractsState" && item.key == storage_slot_key) + .expect("No storage read found") + .value + .clone(); + + let mut expected_value = [0; 32]; + expected_value[..8].copy_from_slice(&i.to_be_bytes()); + + assert!(value == Some(expected_value.to_vec())); + } +} From 341544f7199cf6054816f894d067567be2eddc6a Mon Sep 17 00:00:00 2001 From: Hannes Karppila Date: Thu, 23 Jan 2025 11:41:36 +0200 Subject: [PATCH 15/25] Add changelog entry --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 623142a445b..ac741b10c4b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] +### Added +- [2491](https://github.com/FuelLabs/fuel-core/pull/2491): Storage read replays of historical blocks for execution tracing. Only available behind `--debug` flag. + ## [Version 0.41.2] ### Fixed From 84140f40a70c20b8319e978b1d6f8788e8e63a3f Mon Sep 17 00:00:00 2001 From: Hannes Karppila Date: Thu, 23 Jan 2025 11:41:55 +0200 Subject: [PATCH 16/25] fmt --- tests/tests/storage_read_replay.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/tests/storage_read_replay.rs b/tests/tests/storage_read_replay.rs index 32159e526b6..d5bcab86ec1 100644 --- a/tests/tests/storage_read_replay.rs +++ b/tests/tests/storage_read_replay.rs @@ -79,10 +79,12 @@ async fn make_counter_contract( (contract_id, block_height) } -async fn increment_counter(client: &FuelClient, +async fn increment_counter( + client: &FuelClient, rng: &mut rand::rngs::StdRng, - - contract_id: ContractId) -> BlockHeight { + + contract_id: ContractId, +) -> BlockHeight { let gas_limit = 1_000_000; let maturity = Default::default(); From 35876056f0f8f6c148ff78d8ca2be73162cce8f8 Mon Sep 17 00:00:00 2001 From: Hannes Karppila Date: Thu, 23 Jan 2025 22:20:50 +0200 Subject: [PATCH 17/25] Use rng correctly to avoid repeat txids --- tests/tests/storage_read_replay.rs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/tests/tests/storage_read_replay.rs b/tests/tests/storage_read_replay.rs index d5bcab86ec1..d14325ce2b7 100644 --- a/tests/tests/storage_read_replay.rs +++ b/tests/tests/storage_read_replay.rs @@ -23,7 +23,7 @@ use fuel_core_types::{ TransactionBuilder, }, fuel_types::BlockHeight, - fuel_vm::Salt, + fuel_vm::{Salt, SecretKey}, }; use futures::StreamExt; use rand::{ @@ -82,7 +82,6 @@ async fn make_counter_contract( async fn increment_counter( client: &FuelClient, rng: &mut rand::rngs::StdRng, - contract_id: ContractId, ) -> BlockHeight { let gas_limit = 1_000_000; @@ -102,9 +101,15 @@ async fn increment_counter( let tx = TransactionBuilder::script(script.into_iter().collect(), script_data) .script_gas_limit(gas_limit) .maturity(maturity) - .add_fee_input() - .add_input(Input::contract( + .add_unsigned_coin_input( + SecretKey::random(rng), + rng.gen(), + u32::MAX as u64, Default::default(), + Default::default(), + ) + .add_input(Input::contract( + rng.gen(), rng.gen(), rng.gen(), Default::default(), From 2a52ec285ecf4270600107e6a83494f8357821f2 Mon Sep 17 00:00:00 2001 From: Hannes Karppila Date: Thu, 23 Jan 2025 22:21:04 +0200 Subject: [PATCH 18/25] restore #[deny(warnings)] --- crates/services/upgradable-executor/src/executor.rs | 3 +-- crates/services/upgradable-executor/src/lib.rs | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/crates/services/upgradable-executor/src/executor.rs b/crates/services/upgradable-executor/src/executor.rs index 628a54f2833..22d6bacf0a9 100644 --- a/crates/services/upgradable-executor/src/executor.rs +++ b/crates/services/upgradable-executor/src/executor.rs @@ -1,6 +1,5 @@ -use crate::config::Config; -#[cfg(feature = "wasm-executor")] use crate::{ + config::Config, error::UpgradableError, storage_access_recorder::StorageAccessRecorder, }; diff --git a/crates/services/upgradable-executor/src/lib.rs b/crates/services/upgradable-executor/src/lib.rs index 757c9469218..7fdd71261a1 100644 --- a/crates/services/upgradable-executor/src/lib.rs +++ b/crates/services/upgradable-executor/src/lib.rs @@ -1,13 +1,12 @@ #![deny(clippy::arithmetic_side_effects)] #![deny(clippy::cast_possible_truncation)] #![deny(unused_crate_dependencies)] -// #![deny(warnings)] +#![deny(warnings)] pub mod config; pub mod error; pub mod executor; -#[cfg(feature = "wasm-executor")] mod storage_access_recorder; #[cfg(feature = "wasm-executor")] From 2c2c788f79188e1c0796ea0329508bf2f7becdbe Mon Sep 17 00:00:00 2001 From: Hannes Karppila Date: Thu, 23 Jan 2025 22:44:20 +0200 Subject: [PATCH 19/25] fmt --- tests/tests/storage_read_replay.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/tests/storage_read_replay.rs b/tests/tests/storage_read_replay.rs index d14325ce2b7..01d757273fd 100644 --- a/tests/tests/storage_read_replay.rs +++ b/tests/tests/storage_read_replay.rs @@ -23,7 +23,10 @@ use fuel_core_types::{ TransactionBuilder, }, fuel_types::BlockHeight, - fuel_vm::{Salt, SecretKey}, + fuel_vm::{ + Salt, + SecretKey, + }, }; use futures::StreamExt; use rand::{ From 83e2919a75c3602c3d09dbec4f0f1724d6c9adf9 Mon Sep 17 00:00:00 2001 From: Hannes Karppila Date: Sat, 25 Jan 2025 00:55:39 +0200 Subject: [PATCH 20/25] clippy --- crates/services/upgradable-executor/Cargo.toml | 3 +-- crates/services/upgradable-executor/src/executor.rs | 3 ++- crates/types/src/services/executor.rs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/services/upgradable-executor/Cargo.toml b/crates/services/upgradable-executor/Cargo.toml index 819253f449d..0d573ad4cd5 100644 --- a/crates/services/upgradable-executor/Cargo.toml +++ b/crates/services/upgradable-executor/Cargo.toml @@ -19,7 +19,7 @@ fuel-core-types = { workspace = true, features = ["std"] } fuel-core-wasm-executor = { workspace = true, features = [ "std", ], optional = true } -parking_lot = { workspace = true, optional = true } +parking_lot = { workspace = true } postcard = { workspace = true, optional = true } tracing = { workspace = true, optional = true } wasmtime = { version = "23.0.2", default-features = false, features = [ @@ -49,7 +49,6 @@ smt = [ wasm-executor = [ "dep:anyhow", "dep:derive_more", - "dep:parking_lot", "dep:postcard", "dep:tracing", "dep:fuel-core-wasm-executor", diff --git a/crates/services/upgradable-executor/src/executor.rs b/crates/services/upgradable-executor/src/executor.rs index 22d6bacf0a9..96f2eac7570 100644 --- a/crates/services/upgradable-executor/src/executor.rs +++ b/crates/services/upgradable-executor/src/executor.rs @@ -1,6 +1,7 @@ +#[cfg(feature = "wasm-executor")] +use crate::error::UpgradableError; use crate::{ config::Config, - error::UpgradableError, storage_access_recorder::StorageAccessRecorder, }; diff --git a/crates/types/src/services/executor.rs b/crates/types/src/services/executor.rs index ff2be2eb61e..80baa97adca 100644 --- a/crates/types/src/services/executor.rs +++ b/crates/types/src/services/executor.rs @@ -274,7 +274,7 @@ impl fmt::Debug for StorageReadReplayEvent { f.debug_struct("StorageReadReplayEvent") .field("column", &self.column) .field("key", &hex::encode(&self.key)) - .field("value", &self.value.as_ref().map(|v| hex::encode(v))) + .field("value", &self.value.as_ref().map(hex::encode)) .finish() } } From de92215da8b578eaa060b787ed9a485b00f2172f Mon Sep 17 00:00:00 2001 From: Hannes Karppila Date: Sat, 25 Jan 2025 01:16:35 +0200 Subject: [PATCH 21/25] more clippy --- crates/services/executor/src/executor.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/services/executor/src/executor.rs b/crates/services/executor/src/executor.rs index bfa632bc3f6..e5f3b63e859 100644 --- a/crates/services/executor/src/executor.rs +++ b/crates/services/executor/src/executor.rs @@ -707,7 +707,7 @@ where mut self, block: &Block, mut block_storage_tx: StorageTransaction, - tx_separator_callback: &mut dyn FnMut(usize) -> (), + tx_separator_callback: &mut dyn FnMut(usize), ) -> ExecutorResult where D: KeyValueInspect, From 31fd109ee4e92b997202b1d5504621de2fac1fa7 Mon Sep 17 00:00:00 2001 From: Hannes Karppila Date: Sat, 25 Jan 2025 01:24:34 +0200 Subject: [PATCH 22/25] fix typo --- crates/client/src/client/schema/tx.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/client/src/client/schema/tx.rs b/crates/client/src/client/schema/tx.rs index f67152f9db3..0503301223c 100644 --- a/crates/client/src/client/schema/tx.rs +++ b/crates/client/src/client/schema/tx.rs @@ -310,7 +310,7 @@ impl TryFrom for TransactionExecutionResult { } } DryRunTransactionStatus::Unknown => { - return Err(Self::Error::UnknownVariant("DryRuyTxStatus")) + return Err(Self::Error::UnknownVariant("DryRunTxStatus")) } }) } From a90b55944d70421a827a62430dd69357b9aa4845 Mon Sep 17 00:00:00 2001 From: Hannes Karppila Date: Sat, 25 Jan 2025 01:27:46 +0200 Subject: [PATCH 23/25] Add TODO comments for refactoring --- crates/services/executor/src/executor.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/crates/services/executor/src/executor.rs b/crates/services/executor/src/executor.rs index e5f3b63e859..60732cd3606 100644 --- a/crates/services/executor/src/executor.rs +++ b/crates/services/executor/src/executor.rs @@ -712,6 +712,9 @@ where where D: KeyValueInspect, { + // TODO: Refactor this function after https://github.com/FuelLabs/fuel-core/issues/2062 + // has been implemented + let mut data = ExecutionData::new(); let partial_header = PartialBlockHeader::from(block.header()); @@ -722,6 +725,7 @@ where let (gas_price, coinbase_contract_id) = Self::get_coinbase_info_from_mint_tx(transactions)?; + // Fhe following code has been inlined below: // self.process_l1_txs( // &mut partial_block, // coinbase_contract_id, @@ -740,6 +744,7 @@ where &mut memory, )?; + // Fhe following code has been inlined below: // self.process_relayed_txs( // relayed_txs, // block, From 017c03ee10d0651ac88869f5f5004c42c1045956 Mon Sep 17 00:00:00 2001 From: Hannes Karppila Date: Sat, 25 Jan 2025 01:38:33 +0200 Subject: [PATCH 24/25] more clippy --- crates/services/upgradable-executor/src/executor.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/services/upgradable-executor/src/executor.rs b/crates/services/upgradable-executor/src/executor.rs index ff78e906afc..36b1bdb08a2 100644 --- a/crates/services/upgradable-executor/src/executor.rs +++ b/crates/services/upgradable-executor/src/executor.rs @@ -409,7 +409,7 @@ where match &self.execution_strategy { ExecutionStrategy::Native => self.native_storage_read_replay(block), ExecutionStrategy::Wasm { module } => { - if let Some(module) = self.get_module(block_version).ok() { + if let Ok(module) = self.get_module(block_version) { self.wasm_storage_read_replay(&module, block) } else { self.wasm_storage_read_replay(module, block) @@ -482,7 +482,7 @@ where .add_relayer(relayer)? .add_validation_input_data(block, options)?; - let output = instance.run(&module)?; + let output = instance.run(module)?; match output { ReturnType::ExecutionV0(result) => { From fce82ea70f5de473f5f16edbd79111c3a97eafba Mon Sep 17 00:00:00 2001 From: Hannes Karppila Date: Wed, 29 Jan 2025 16:59:55 +0200 Subject: [PATCH 25/25] Address PR comments --- crates/fuel-core/src/schema/tx.rs | 2 +- crates/services/executor/src/executor.rs | 108 ----------------------- 2 files changed, 1 insertion(+), 109 deletions(-) diff --git a/crates/fuel-core/src/schema/tx.rs b/crates/fuel-core/src/schema/tx.rs index 8a18d117b74..a2e2664eedd 100644 --- a/crates/fuel-core/src/schema/tx.rs +++ b/crates/fuel-core/src/schema/tx.rs @@ -343,7 +343,7 @@ impl TxMutation { ctx: &Context<'_>, height: U32, ) -> async_graphql::Result> { - let config = ctx.data_unchecked::().clone(); + let config = ctx.data_unchecked::(); if !config.debug { return Err( anyhow::anyhow!("`debug` must be enabled to use this endpoint").into(), diff --git a/crates/services/executor/src/executor.rs b/crates/services/executor/src/executor.rs index 60732cd3606..96c0c9e227a 100644 --- a/crates/services/executor/src/executor.rs +++ b/crates/services/executor/src/executor.rs @@ -702,114 +702,6 @@ where Ok(()) } - #[tracing::instrument(skip_all)] - fn record_storage_reads_for( - mut self, - block: &Block, - mut block_storage_tx: StorageTransaction, - tx_separator_callback: &mut dyn FnMut(usize), - ) -> ExecutorResult - where - D: KeyValueInspect, - { - // TODO: Refactor this function after https://github.com/FuelLabs/fuel-core/issues/2062 - // has been implemented - - let mut data = ExecutionData::new(); - - let partial_header = PartialBlockHeader::from(block.header()); - let mut partial_block = PartialFuelBlock::new(partial_header, vec![]); - let transactions = block.transactions(); - let mut memory = MemoryInstance::new(); - - let (gas_price, coinbase_contract_id) = - Self::get_coinbase_info_from_mint_tx(transactions)?; - - // Fhe following code has been inlined below: - // self.process_l1_txs( - // &mut partial_block, - // coinbase_contract_id, - // &mut block_storage_tx, - // &mut data, - // &mut memory, - // )?; - - let block_height = *partial_block.header.height(); - let da_block_height = partial_block.header.da_height; - let relayed_txs = self.get_relayed_txs( - block_height, - da_block_height, - &mut data, - &mut block_storage_tx, - &mut memory, - )?; - - // Fhe following code has been inlined below: - // self.process_relayed_txs( - // relayed_txs, - // block, - // storage_tx, - // data, - // coinbase_contract_id, - // memory, - // )?; - - let consensus_parameters_version = - partial_block.header.consensus_parameters_version; - let relayed_tx_iter = relayed_txs.into_iter(); - for (i, checked) in relayed_tx_iter.enumerate() { - tx_separator_callback(i); - let maybe_checked_transaction = MaybeCheckedTransaction::CheckedTransaction( - checked, - consensus_parameters_version, - ); - let tx_id = maybe_checked_transaction.id(&self.consensus_params.chain_id()); - match self.execute_transaction_and_commit( - &mut partial_block, - &mut block_storage_tx, - &mut data, - maybe_checked_transaction, - Self::RELAYED_GAS_PRICE, - coinbase_contract_id, - &mut memory, - ) { - Ok(_) => {} - Err(err) => { - let event = ExecutorEvent::ForcedTransactionFailed { - id: tx_id.into(), - block_height, - failure: err.to_string(), - }; - data.events.push(event); - } - } - } - - let processed_l1_tx_count = partial_block.transactions.len(); - - for (i, transaction) in - transactions.iter().enumerate().skip(processed_l1_tx_count) - { - tx_separator_callback(i); - let maybe_checked_tx = - MaybeCheckedTransaction::Transaction(transaction.clone()); - self.execute_transaction_and_commit( - &mut partial_block, - &mut block_storage_tx, - &mut data, - maybe_checked_tx, - gas_price, - coinbase_contract_id, - &mut memory, - )?; - } - - self.check_block_matches(partial_block, block, &data)?; - - data.changes = block_storage_tx.into_changes(); - Ok(data) - } - #[tracing::instrument(skip_all)] fn validate_block( mut self,