From f76c96fd5ed2e745949d1ab156d665af05565830 Mon Sep 17 00:00:00 2001 From: Hannes Karppila Date: Wed, 18 Dec 2024 11:21:02 +0200 Subject: [PATCH] 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, +}