From 6f9acc3561d73e67788f52a35b190416e5bb7e2f Mon Sep 17 00:00:00 2001 From: Sydhds Date: Wed, 15 May 2024 14:03:19 +0200 Subject: [PATCH] Initial code for execution info (#4685) * Initial code for execution info * Cargo clippy fixes * Update protoc actions for CI * Merge use * Update ExecutionInfo with ExecutionInfoForSlot * Fix bad rebase * Cargo fmt pass * Fix schnellru deps * Update ExecuteInfoForSlot * Cargo fmt pass --- .github/workflows/ci.yml | 13 +- Cargo.lock | 1 + massa-api/src/tests/public.rs | 6 + massa-execution-exports/Cargo.toml | 1 + massa-execution-exports/src/channels.rs | 2 +- .../src/controller_traits.rs | 2 +- massa-execution-exports/src/lib.rs | 5 +- massa-execution-exports/src/types.rs | 148 +----------------- .../src/types_trace_info.rs | 145 +++++++++++++++++ massa-execution-worker/Cargo.toml | 9 +- massa-execution-worker/src/context.rs | 63 ++++++-- massa-execution-worker/src/controller.rs | 6 +- massa-execution-worker/src/execution.rs | 117 ++++++++++++-- massa-execution-worker/src/execution_info.rs | 96 ++++++++++++ massa-execution-worker/src/lib.rs | 2 + .../src/speculative_roll_state.rs | 9 +- .../src/tests/scenarios_mandatories.rs | 4 +- .../src/tests/tests_active_history.rs | 3 + massa-execution-worker/src/tests/universe.rs | 2 +- massa-execution-worker/src/trace_history.rs | 3 +- massa-grpc/src/public.rs | 2 +- massa-grpc/src/tests/public.rs | 3 + massa-grpc/src/tests/stream.rs | 3 + massa-node/Cargo.toml | 3 + 24 files changed, 461 insertions(+), 187 deletions(-) create mode 100644 massa-execution-exports/src/types_trace_info.rs create mode 100644 massa-execution-worker/src/execution_info.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7b1a625cff0..91c4f6a531f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -62,9 +62,9 @@ jobs: with: shared-key: "check" save-if: ${{ github.ref_name == 'main' }} - - uses: arduino/setup-protoc@v1 + - uses: arduino/setup-protoc@v3 with: - version: '3.x' + version: '23.x' repo-token: ${{ secrets.GITHUB_TOKEN }} - uses: actions-rs/cargo@v1 with: @@ -89,9 +89,9 @@ jobs: with: shared-key: "clippy" save-if: ${{ github.ref_name == 'main' }} - - uses: arduino/setup-protoc@v1 + - uses: arduino/setup-protoc@v3 with: - version: '3.x' + version: '23.x' repo-token: ${{ secrets.GITHUB_TOKEN }} - uses: actions-rs/clippy-check@v1 with: @@ -199,7 +199,6 @@ jobs: save-if: ${{ github.ref_name == 'main' }} - uses: arduino/setup-protoc@v3 with: - # version: '3.x' version: "23.x" include-pre-releases: false repo-token: ${{ secrets.GITHUB_TOKEN }} @@ -282,9 +281,9 @@ jobs: with: shared-key: "doc" save-if: ${{ github.ref_name == 'main' }} - - uses: arduino/setup-protoc@v1 + - uses: arduino/setup-protoc@v3 with: - version: '3.x' + version: '23.x' repo-token: ${{ secrets.GITHUB_TOKEN }} - uses: actions-rs/cargo@v1 with: diff --git a/Cargo.lock b/Cargo.lock index 41fd5537ac8..11c6944ed6a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2884,6 +2884,7 @@ dependencies = [ "anyhow", "blake3", "bs58", + "cfg-if", "criterion", "hex-literal", "libsecp256k1", diff --git a/massa-api/src/tests/public.rs b/massa-api/src/tests/public.rs index 035bcd22822..e711e04fed7 100644 --- a/massa-api/src/tests/public.rs +++ b/massa-api/src/tests/public.rs @@ -698,6 +698,9 @@ async fn execute_read_only_bytecode() { slot_trace: None, #[cfg(feature = "dump-block")] storage: None, + deferred_credits_execution: vec![], + cancel_async_message_execution: vec![], + auto_sell_execution: vec![], }, gas_cost: 100, call_result: "toto".as_bytes().to_vec(), @@ -782,6 +785,9 @@ async fn execute_read_only_call() { slot_trace: None, #[cfg(feature = "dump-block")] storage: None, + deferred_credits_execution: vec![], + cancel_async_message_execution: vec![], + auto_sell_execution: vec![], }, gas_cost: 100, call_result: "toto".as_bytes().to_vec(), diff --git a/massa-execution-exports/Cargo.toml b/massa-execution-exports/Cargo.toml index 760745c5b12..fc84b6e6c0d 100644 --- a/massa-execution-exports/Cargo.toml +++ b/massa-execution-exports/Cargo.toml @@ -9,6 +9,7 @@ gas_calibration = ["tempfile"] test-exports = ["massa_models/test-exports", "tempfile", "mockall"] execution-trace = ["massa-sc-runtime/execution-trace"] dump-block = [] +execution-info = ["execution-trace"] [dependencies] displaydoc = {workspace = true} diff --git a/massa-execution-exports/src/channels.rs b/massa-execution-exports/src/channels.rs index 8dd11748dc2..dba845a66cf 100644 --- a/massa-execution-exports/src/channels.rs +++ b/massa-execution-exports/src/channels.rs @@ -3,7 +3,7 @@ use crate::types::SlotExecutionOutput; #[cfg(feature = "execution-trace")] -use crate::types::SlotAbiCallStack; +use crate::types_trace_info::SlotAbiCallStack; /// channels used by the execution worker #[derive(Clone)] diff --git a/massa-execution-exports/src/controller_traits.rs b/massa-execution-exports/src/controller_traits.rs index 85f6f7d925b..cc6a8e570ae 100644 --- a/massa-execution-exports/src/controller_traits.rs +++ b/massa-execution-exports/src/controller_traits.rs @@ -22,7 +22,7 @@ use std::collections::BTreeMap; use std::collections::HashMap; #[cfg(feature = "execution-trace")] -use crate::types::{AbiTrace, SlotAbiCallStack, Transfer}; +use crate::types_trace_info::{AbiTrace, SlotAbiCallStack, Transfer}; #[cfg_attr(feature = "test-exports", mockall::automock)] /// interface that communicates with the execution worker thread diff --git a/massa-execution-exports/src/lib.rs b/massa-execution-exports/src/lib.rs index b1cee610585..8d76e76f470 100644 --- a/massa-execution-exports/src/lib.rs +++ b/massa-execution-exports/src/lib.rs @@ -72,7 +72,10 @@ pub use types::{ #[cfg(any(feature = "test-exports", feature = "gas_calibration"))] pub mod test_exports; +/// types for execution-trace / execution-info +pub mod types_trace_info; + #[cfg(feature = "execution-trace")] -pub use types::{ +pub use types_trace_info::{ AbiTrace, SCRuntimeAbiTraceType, SCRuntimeAbiTraceValue, SlotAbiCallStack, Transfer, }; diff --git a/massa-execution-exports/src/types.rs b/massa-execution-exports/src/types.rs index 270ec83e021..4c8afbb1a99 100644 --- a/massa-execution-exports/src/types.rs +++ b/massa-execution-exports/src/types.rs @@ -22,15 +22,7 @@ use massa_storage::Storage; use std::collections::{BTreeMap, BTreeSet}; #[cfg(feature = "execution-trace")] -use massa_models::prehash::PreHashMap; -#[cfg(feature = "execution-trace")] -pub use massa_sc_runtime::AbiTrace as SCRuntimeAbiTrace; -#[cfg(feature = "execution-trace")] -pub use massa_sc_runtime::AbiTraceType as SCRuntimeAbiTraceType; -#[cfg(feature = "execution-trace")] -pub use massa_sc_runtime::AbiTraceValue as SCRuntimeAbiTraceValue; -#[cfg(feature = "execution-trace")] -use std::collections::VecDeque; +use crate::types_trace_info::{SlotAbiCallStack, Transfer}; /// Metadata needed to execute the block #[derive(Clone, Debug)] @@ -214,118 +206,6 @@ pub struct ExecutionAddressInfo { pub cycle_infos: Vec, } -#[cfg(feature = "execution-trace")] -/// A trace of an abi call + its parameters + the result -#[derive(Debug, Clone)] -pub struct AbiTrace { - /// Abi name - pub name: String, - /// Abi parameters - pub parameters: Vec, - /// Abi return value - pub return_value: SCRuntimeAbiTraceType, - /// Abi sub calls - pub sub_calls: Option>, -} - -#[cfg(feature = "execution-trace")] -impl From for AbiTrace { - fn from(trace: SCRuntimeAbiTrace) -> Self { - Self { - name: trace.name, - parameters: trace.params, - return_value: trace.return_value, - sub_calls: trace.sub_calls.map(|sub_calls| { - sub_calls - .into_iter() - .map(|sub_call| sub_call.into()) - .collect() - }), - } - } -} - -#[cfg(feature = "execution-trace")] -impl AbiTrace { - /// Flatten and filter for abi names in an AbiTrace - pub fn flatten_filter(&self, abi_names: &Vec) -> Vec<&Self> { - let mut filtered: Vec<&Self> = Default::default(); - let mut to_process: VecDeque<&Self> = vec![self].into(); - - while !to_process.is_empty() { - let t = to_process.pop_front(); - if let Some(trace) = t { - if abi_names.iter().find(|t| *(*t) == trace.name).is_some() { - // filtered.extend(&trace) - filtered.push(trace); - } - - if let Some(sub_call) = &trace.sub_calls { - for sc in sub_call.iter().rev() { - to_process.push_front(sc); - } - } - } - } - - filtered - } - - /// This function assumes that the abi trace is a transfer. - /// Calling this function on a non-transfer abi trace will have undefined behavior. - pub fn parse_transfer(&self) -> (String, String, u64) { - let t_from = self - .parameters - .iter() - .find_map(|p| { - if p.name == "from_address" { - if let SCRuntimeAbiTraceType::String(v) = &p.value { - return Some(v.clone()); - } - } - None - }) - .unwrap_or_default(); - let t_to = self - .parameters - .iter() - .find_map(|p| { - if p.name == "to_address" { - if let SCRuntimeAbiTraceType::String(v) = &p.value { - return Some(v.clone()); - } - } - None - }) - .unwrap_or_default(); - let t_amount = self - .parameters - .iter() - .find_map(|p| { - if p.name == "raw_amount" { - if let SCRuntimeAbiTraceType::U64(v) = &p.value { - return Some(v.clone()); - } - } - None - }) - .unwrap_or_default(); - (t_from, t_to, t_amount) - } -} - -#[cfg(feature = "execution-trace")] -#[derive(Debug, Clone)] -/// Structure for all abi calls in a slot -pub struct SlotAbiCallStack { - /// Slot - pub slot: Slot, - /// asc call stacks - pub asc_call_stacks: Vec>, - /// operation call stacks - pub operation_call_stacks: PreHashMap>, -} - /// structure describing the output of the execution of a slot #[derive(Debug, Clone)] pub enum SlotExecutionOutput { @@ -364,26 +244,12 @@ pub struct ExecutionOutput { /// storage #[cfg(feature = "dump-block")] pub storage: Option, -} - -#[cfg(feature = "execution-trace")] -#[derive(Debug, Clone)] -/// structure describing a transfer -pub struct Transfer { - /// From - pub from: Address, - /// To - pub to: Address, - /// Amount - pub amount: Amount, - /// Effective received amount - pub effective_received_amount: Amount, - /// operation id - pub op_id: OperationId, - /// success or not - pub succeed: bool, - /// Fee - pub fee: Amount, + /// Deferred credits execution (empty if execution-info feature is NOT enabled) + pub deferred_credits_execution: Vec<(Address, Result)>, + /// Cancel async message execution (empty if execution-info feature is NOT enabled) + pub cancel_async_message_execution: Vec<(Address, Result)>, + /// Auto sell roll execution (empty if execution-info feature is NOT enabled) + pub auto_sell_execution: Vec<(Address, Amount)>, } /// structure describing the output of a read only execution diff --git a/massa-execution-exports/src/types_trace_info.rs b/massa-execution-exports/src/types_trace_info.rs new file mode 100644 index 00000000000..4f93c4ca63f --- /dev/null +++ b/massa-execution-exports/src/types_trace_info.rs @@ -0,0 +1,145 @@ +#[cfg(feature = "execution-trace")] +use std::collections::VecDeque; + +#[cfg(feature = "execution-trace")] +use massa_models::{ + address::Address, amount::Amount, operation::OperationId, prehash::PreHashMap, slot::Slot, +}; + +#[cfg(feature = "execution-trace")] +pub use massa_sc_runtime::{ + AbiTrace as SCRuntimeAbiTrace, AbiTraceType as SCRuntimeAbiTraceType, + AbiTraceValue as SCRuntimeAbiTraceValue, +}; + +#[cfg(feature = "execution-trace")] +#[derive(Debug, Clone)] +/// Structure for all abi calls in a slot +pub struct SlotAbiCallStack { + /// Slot + pub slot: Slot, + /// asc call stacks + pub asc_call_stacks: Vec>, + /// operation call stacks + pub operation_call_stacks: PreHashMap>, +} + +#[cfg(feature = "execution-trace")] +#[derive(Debug, Clone)] +/// structure describing a transfer +pub struct Transfer { + /// From + pub from: Address, + /// To + pub to: Address, + /// Amount + pub amount: Amount, + /// Effective received amount + pub effective_received_amount: Amount, + /// operation id + pub op_id: OperationId, + /// success or not + pub succeed: bool, + /// Fee + pub fee: Amount, +} + +#[cfg(feature = "execution-trace")] +/// A trace of an abi call + its parameters + the result +#[derive(Debug, Clone)] +pub struct AbiTrace { + /// Abi name + pub name: String, + /// Abi parameters + pub parameters: Vec, + /// Abi return value + pub return_value: SCRuntimeAbiTraceType, + /// Abi sub calls + pub sub_calls: Option>, +} + +#[cfg(feature = "execution-trace")] +impl From for AbiTrace { + fn from(trace: SCRuntimeAbiTrace) -> Self { + Self { + name: trace.name, + parameters: trace.params, + return_value: trace.return_value, + sub_calls: trace.sub_calls.map(|sub_calls| { + sub_calls + .into_iter() + .map(|sub_call| sub_call.into()) + .collect() + }), + } + } +} + +#[cfg(feature = "execution-trace")] +impl AbiTrace { + /// Flatten and filter for abi names in an AbiTrace + pub fn flatten_filter(&self, abi_names: &[String]) -> Vec<&Self> { + let mut filtered: Vec<&Self> = Default::default(); + let mut to_process: VecDeque<&Self> = vec![self].into(); + + while !to_process.is_empty() { + let t = to_process.pop_front(); + if let Some(trace) = t { + if abi_names.iter().find(|t| *(*t) == trace.name).is_some() { + // filtered.extend(&trace) + filtered.push(trace); + } + + if let Some(sub_call) = &trace.sub_calls { + for sc in sub_call.iter().rev() { + to_process.push_front(sc); + } + } + } + } + + filtered + } + + /// This function assumes that the abi trace is a transfer. + /// Calling this function on a non-transfer abi trace will have undefined behavior. + pub fn parse_transfer(&self) -> (String, String, u64) { + let t_from = self + .parameters + .iter() + .find_map(|p| { + if p.name == "from_address" { + if let SCRuntimeAbiTraceType::String(v) = &p.value { + return Some(v.clone()); + } + } + None + }) + .unwrap_or_default(); + let t_to = self + .parameters + .iter() + .find_map(|p| { + if p.name == "to_address" { + if let SCRuntimeAbiTraceType::String(v) = &p.value { + return Some(v.clone()); + } + } + None + }) + .unwrap_or_default(); + let t_amount = self + .parameters + .iter() + .find_map(|p| { + if p.name == "raw_amount" { + if let SCRuntimeAbiTraceType::U64(v) = &p.value { + return Some(*v); + } + } + None + }) + .unwrap_or_default(); + (t_from, t_to, t_amount) + } +} diff --git a/massa-execution-worker/Cargo.toml b/massa-execution-worker/Cargo.toml index 2d4f0b6f972..24e11a871fb 100644 --- a/massa-execution-worker/Cargo.toml +++ b/massa-execution-worker/Cargo.toml @@ -40,12 +40,14 @@ benchmarking = [ ] metrics = [] execution-trace = [ - "schnellru", "massa_execution_exports/execution-trace", ] dump-block = [ "prost", - "massa_execution_exports/dump-block", + "massa_execution_exports/dump-block" +] +execution-info = [ + "execution-trace" ] [dependencies] @@ -86,8 +88,9 @@ massa_db_worker = { workspace = true, optional = true } tempfile = { workspace = true, optional = true } massa_wallet = { workspace = true } massa-proto-rs = { workspace = true } -schnellru = { workspace = true, optional = true } +schnellru = { workspace = true } prost = { version = "=0.12", optional = true } +cfg-if = "1.0.0" [dev-dependencies] massa_storage = { workspace = true } diff --git a/massa-execution-worker/src/context.rs b/massa-execution-worker/src/context.rs index ebca7761ddc..3153adadc9d 100644 --- a/massa-execution-worker/src/context.rs +++ b/massa-execution-worker/src/context.rs @@ -708,13 +708,28 @@ impl ExecutionContext { /// /// # Arguments /// * `msg`: the asynchronous message to cancel - pub fn cancel_async_message(&mut self, msg: &AsyncMessage) { - if let Err(e) = self.transfer_coins(None, Some(msg.sender), msg.coins, false) { + pub fn cancel_async_message( + &mut self, + msg: &AsyncMessage, + ) -> Option<(Address, Result)> { + #[allow(unused_assignments, unused_mut)] + let mut result = None; + let transfer_result = self.transfer_coins(None, Some(msg.sender), msg.coins, false); + if let Err(e) = transfer_result.as_ref() { debug!( "async message cancel: reimbursement of {} failed: {}", msg.sender, e ); } + + #[cfg(feature = "execution-info")] + if let Err(e) = transfer_result { + result = Some((msg.sender, Err(e.to_string()))) + } else { + result = Some((msg.sender, Ok(msg.coins))); + } + + result } /// Add `roll_count` rolls to the buyer address. @@ -831,21 +846,38 @@ impl ExecutionContext { /// /// # Arguments /// * `slot`: associated slot of the deferred credits to be executed - pub fn execute_deferred_credits(&mut self, slot: &Slot) { + pub fn execute_deferred_credits( + &mut self, + slot: &Slot, + ) -> Vec<(Address, Result)> { + #[allow(unused_mut)] + let mut result = vec![]; + for (_slot, map) in self .speculative_roll_state .take_unexecuted_deferred_credits(slot) .credits { for (address, amount) in map { - if let Err(e) = self.transfer_coins(None, Some(address), amount, false) { + let transfer_result = self.transfer_coins(None, Some(address), amount, false); + + if let Err(e) = transfer_result.as_ref() { debug!( "could not credit {} deferred coins to {} at slot {}: {}", amount, address, slot, e ); } + + #[cfg(feature = "execution-info")] + if let Err(e) = transfer_result { + result.push((address, Err(e.to_string()))); + } else { + result.push((address, Ok(amount))); + } } } + + result } /// Finishes a slot and generates the execution output. @@ -859,7 +891,7 @@ impl ExecutionContext { let slot = self.slot; // execute the deferred credits coming from roll sells - self.execute_deferred_credits(&slot); + let deferred_credits_transfers = self.execute_deferred_credits(&slot); // take the ledger changes first as they are needed for async messages and cache let ledger_changes = self.speculative_ledger.take(); @@ -868,8 +900,12 @@ impl ExecutionContext { let deleted_messages = self .speculative_async_pool .settle_slot(&slot, &ledger_changes); + + let mut cancel_async_message_transfers = vec![]; for (_msg_id, msg) in deleted_messages { - self.cancel_async_message(&msg); + if let Some(t) = self.cancel_async_message(&msg) { + cancel_async_message_transfers.push(t) + } } // update module cache @@ -882,7 +918,7 @@ impl ExecutionContext { } // if the current slot is last in cycle check the production stats and act accordingly - if self + let auto_sell_rolls = if self .slot .is_last_of_cycle(self.config.periods_per_cycle, self.config.thread_count) { @@ -892,8 +928,10 @@ impl ExecutionContext { self.config.thread_count, self.config.roll_price, self.config.max_miss_ratio, - ); - } + ) + } else { + vec![] + }; // generate the execution output let state_changes = StateChanges { @@ -915,6 +953,9 @@ impl ExecutionContext { slot_trace: None, #[cfg(feature = "dump-block")] storage: None, + deferred_credits_execution: deferred_credits_transfers, + cancel_async_message_execution: cancel_async_message_transfers, + auto_sell_execution: auto_sell_rolls, } } @@ -1049,7 +1090,7 @@ impl ExecutionContext { /// /// async_msg, call OP, call SC to SC, read only call /// - /// check if the given address is a smart contract address and if it exist + /// check if the given address is a smart contract address and if it exists /// returns an error instead pub fn check_target_sc_address( &self, @@ -1115,6 +1156,6 @@ fn init_prng(execution_trail_hash: &massa_hash::Hash) -> Xoshiro256PlusPlus { // We use Xoshiro256PlusPlus because it is very fast, // has a period long enough to ensure no repetitions will ever happen, // of decent quality (given the unsafe constraints) - // but not cryptographically secure (and that's ok because the internal state is exposed anyways) + // but not cryptographically secure (and that's ok because the internal state is exposed anyway) Xoshiro256PlusPlus::from_seed(seed) } diff --git a/massa-execution-worker/src/controller.rs b/massa-execution-worker/src/controller.rs index 6cdd627bbb4..33a7258feb5 100644 --- a/massa-execution-worker/src/controller.rs +++ b/massa-execution-worker/src/controller.rs @@ -26,7 +26,11 @@ use std::sync::Arc; use tracing::info; #[cfg(feature = "execution-trace")] -use massa_execution_exports::{AbiTrace, SlotAbiCallStack, Transfer}; +use massa_execution_exports::types_trace_info::AbiTrace; +#[cfg(feature = "execution-trace")] +use massa_execution_exports::types_trace_info::SlotAbiCallStack; +#[cfg(feature = "execution-trace")] +use massa_execution_exports::types_trace_info::Transfer; /// structure used to communicate with execution thread pub(crate) struct ExecutionInputData { diff --git a/massa-execution-worker/src/execution.rs b/massa-execution-worker/src/execution.rs index db04668f1c7..c79f6801179 100644 --- a/massa-execution-worker/src/execution.rs +++ b/massa-execution-worker/src/execution.rs @@ -49,6 +49,9 @@ use std::collections::{BTreeMap, BTreeSet}; use std::sync::Arc; use tracing::{debug, info, trace, warn}; +use crate::execution_info::{AsyncMessageExecutionResult, DenunciationResult}; +#[cfg(feature = "execution-info")] +use crate::execution_info::{ExecutionInfo, ExecutionInfoForSlot, OperationInfo}; #[cfg(feature = "execution-trace")] use crate::trace_history::TraceHistory; #[cfg(feature = "execution-trace")] @@ -129,6 +132,8 @@ pub(crate) struct ExecutionState { massa_metrics: MassaMetrics, #[cfg(feature = "execution-trace")] pub(crate) trace_history: Arc>, + #[cfg(feature = "execution-info")] + pub(crate) execution_info: Arc>, } impl ExecutionState { @@ -215,6 +220,10 @@ impl ExecutionState { (MAX_GAS_PER_BLOCK / BASE_OPERATION_GAS_COST) as u32, ), ))), + #[cfg(feature = "execution-info")] + execution_info: Arc::new(RwLock::new(ExecutionInfo::new( + config.max_execution_traces_slot_limit as u32, + ))), config, } } @@ -628,7 +637,7 @@ impl ExecutionState { denunciation: &Denunciation, block_slot: &Slot, block_credits: &mut Amount, - ) -> Result<(), ExecutionError> { + ) -> Result { let addr_denounced = Address::from_public_key(denunciation.get_public_key()); // acquire write access to the context @@ -732,7 +741,7 @@ impl ExecutionState { self.config.roll_count_to_slash_on_denunciation, ); - match slashed { + match slashed.as_ref() { Ok(slashed_amount) => { // Add slashed amount / 2 to block reward let amount = slashed_amount.checked_div_u64(2).ok_or_else(|| { @@ -760,7 +769,11 @@ impl ExecutionState { } } - Ok(()) + Ok(DenunciationResult { + address_denounced: addr_denounced, + slot: *de_slot, + slashed: slashed.unwrap_or_default(), + }) } /// Execute an operation of type `RollSell` @@ -1086,7 +1099,15 @@ impl ExecutionState { &self, message: AsyncMessage, bytecode: Option, - ) -> Result { + ) -> Result { + let mut result = AsyncMessageExecutionResult::new(); + #[cfg(feature = "execution-info")] + { + // TODO: From impl + no ::new -> no cfg feature + result.sender = Some(message.sender); + result.destination = Some(message.destination); + } + // prepare execution context let context_snapshot; let bytecode = { @@ -1136,9 +1157,12 @@ impl ExecutionState { "could not credit coins to target of async execution: {}", err )); + context.reset_to_snapshot(context_snapshot, err.clone()); context.cancel_async_message(&message); return Err(err); + } else { + result.coins = Some(message.coins); } bytecode.0 @@ -1165,12 +1189,13 @@ impl ExecutionState { .set_init_cost(&bytecode, res.init_gas_cost); #[cfg(feature = "execution-trace")] { - Ok((res.trace.into_iter().map(|t| t.into()).collect(), true)) + result.traces = Some((res.trace.into_iter().map(|t| t.into()).collect(), true)); } - #[cfg(not(feature = "execution-trace"))] + #[cfg(feature = "execution-info")] { - Ok(()) + result.success = true; } + Ok(result) } Err(error) => { if let VMError::ExecutionError { init_gas_cost, .. } = error { @@ -1215,6 +1240,10 @@ impl ExecutionState { }; #[cfg(feature = "execution-trace")] let mut transfers = vec![]; + + #[cfg(feature = "execution-info")] + let mut exec_info = ExecutionInfoForSlot::new(); + // Create a new execution context for the whole active slot let mut execution_context = ExecutionContext::active_slot( self.config.clone(), @@ -1240,13 +1269,21 @@ impl ExecutionState { for (opt_bytecode, message) in messages { match self.execute_async_message(message, opt_bytecode) { Ok(_message_return) => { - #[cfg(feature = "execution-trace")] - { - slot_trace.asc_call_stacks.push(_message_return.0); + cfg_if::cfg_if! { + if #[cfg(feature = "execution-trace")] { + // Safe to unwrap + slot_trace.asc_call_stacks.push(_message_return.traces.unwrap().0); + } else if #[cfg(feature = "execution-info")] { + slot_trace.asc_call_stacks.push(_message_return.traces.clone().unwrap().0); + exec_info.async_messages.push(Ok(_message_return)); + } } } Err(err) => { - debug!("failed executing async message: {}", err); + let msg = format!("failed executing async message: {}", err); + #[cfg(feature = "execution-info")] + exec_info.async_messages.push(Err(msg.clone())); + debug!(msg); } } } @@ -1377,6 +1414,19 @@ impl ExecutionState { _ => {} } } + + #[cfg(feature = "execution-info")] + { + match &operation.content.op { + OperationType::RollBuy { roll_count } => exec_info + .operations + .push(OperationInfo::RollBuy(*roll_count)), + OperationType::RollSell { roll_count } => exec_info + .operations + .push(OperationInfo::RollSell(*roll_count)), + _ => {} + } + } } Err(err) => { debug!( @@ -1389,15 +1439,24 @@ impl ExecutionState { // Try executing the denunciations of this block for denunciation in &stored_block.content.header.content.denunciations { - if let Err(e) = self.execute_denunciation( + match self.execute_denunciation( denunciation, &stored_block.content.header.content.slot, &mut block_credits, ) { - debug!( - "Failed processing denunciation: {:?}, in block: {}: {}", - denunciation, block_id, e - ); + Ok(_de_res) => { + #[cfg(feature = "execution-info")] + exec_info.denunciations.push(Ok(_de_res)); + } + Err(e) => { + let msg = format!( + "Failed processing denunciation: {:?}, in block: {}: {}", + denunciation, block_id, e + ); + #[cfg(feature = "execution-info")] + exec_info.denunciations.push(Err(msg.clone())); + debug!(msg); + } } } @@ -1425,6 +1484,11 @@ impl ExecutionState { ) { Ok(_) => { remaining_credit = remaining_credit.saturating_sub(block_credit_part); + + #[cfg(feature = "execution-info")] + exec_info + .endorsement_creator_rewards + .insert(endorsement_creator, block_credit_part); } Err(err) => { debug!( @@ -1443,6 +1507,11 @@ impl ExecutionState { ) { Ok(_) => { remaining_credit = remaining_credit.saturating_sub(block_credit_part); + #[cfg(feature = "execution-info")] + { + exec_info.endorsement_target_reward = + Some((endorsement_target_creator, block_credit_part)); + } } Err(err) => { debug!( @@ -1461,6 +1530,11 @@ impl ExecutionState { "failed to credit {} coins to block creator {} on block execution: {}", remaining_credit, block_creator_addr, err ) + } else { + #[cfg(feature = "execution-info")] + { + exec_info.block_producer_reward = Some((block_creator_addr, remaining_credit)); + } } } else { // the slot is a miss, check who was supposed to be the creator and update production stats @@ -1494,6 +1568,17 @@ impl ExecutionState { } } + #[cfg(feature = "execution-info")] + { + exec_info.deferred_credits_execution = + std::mem::replace(&mut exec_out.deferred_credits_execution, vec![]); + exec_info.cancel_async_message_execution = + std::mem::replace(&mut exec_out.cancel_async_message_execution, vec![]); + exec_info.auto_sell_execution = + std::mem::replace(&mut exec_out.auto_sell_execution, vec![]); + self.execution_info.write().save_for_slot(*slot, exec_info); + } + // Broadcast a slot execution output to active channel subscribers. if self.config.broadcast_enabled { let slot_exec_out = SlotExecutionOutput::ExecutedSlot(exec_out.clone()); diff --git a/massa-execution-worker/src/execution_info.rs b/massa-execution-worker/src/execution_info.rs new file mode 100644 index 00000000000..aaac0da631d --- /dev/null +++ b/massa-execution-worker/src/execution_info.rs @@ -0,0 +1,96 @@ +#![allow(dead_code)] + +use std::collections::HashMap; + +use schnellru::{ByLength, LruMap}; +// use massa_execution_exports::Transfer; + +use massa_models::address::Address; +use massa_models::amount::Amount; +use massa_models::slot::Slot; + +use crate::execution::ExecutionResult; + +pub struct ExecutionInfo { + info_per_slot: LruMap, +} + +impl ExecutionInfo { + pub(crate) fn new(max_slot_size_cache: u32) -> Self { + Self { + info_per_slot: LruMap::new(ByLength::new(max_slot_size_cache)), + } + } + + /// Save transfer for a given slot + pub(crate) fn save_for_slot(&mut self, slot: Slot, info: ExecutionInfoForSlot) { + self.info_per_slot.insert(slot, info); + } +} + +pub enum OperationInfo { + RollBuy(u64), + RollSell(u64), +} + +pub struct ExecutionInfoForSlot { + pub(crate) block_producer_reward: Option<(Address, Amount)>, + pub(crate) endorsement_creator_rewards: HashMap, + pub(crate) endorsement_target_reward: Option<(Address, Amount)>, + pub(crate) denunciations: Vec>, + pub(crate) operations: Vec, + pub(crate) async_messages: Vec>, + /// Deferred credits execution (empty if execution-info feature is NOT enabled) + pub deferred_credits_execution: Vec<(Address, Result)>, + /// Cancel async message execution (empty if execution-info feature is NOT enabled) + pub cancel_async_message_execution: Vec<(Address, Result)>, + /// Auto sell roll execution (empty if execution-info feature is NOT enabled) + pub auto_sell_execution: Vec<(Address, Amount)>, +} + +impl ExecutionInfoForSlot { + pub fn new() -> Self { + Self { + block_producer_reward: None, + endorsement_creator_rewards: Default::default(), + endorsement_target_reward: None, + denunciations: Default::default(), + operations: Default::default(), + async_messages: Default::default(), + deferred_credits_execution: vec![], + cancel_async_message_execution: vec![], + auto_sell_execution: vec![], + } + } +} + +/// structure describing the output of a denunciation execution +#[derive(Debug)] +pub struct DenunciationResult { + /// Target address of the denunciation + pub address_denounced: Address, + /// Denunciation slot + pub slot: Slot, + /// Amount slashed if successfully executed + pub slashed: Amount, +} + +pub struct AsyncMessageExecutionResult { + pub(crate) success: bool, + pub(crate) sender: Option
, + pub(crate) destination: Option
, + pub(crate) coins: Option, + pub(crate) traces: Option, +} + +impl AsyncMessageExecutionResult { + pub fn new() -> Self { + Self { + success: false, + sender: None, + destination: None, + coins: None, + traces: None, + } + } +} diff --git a/massa-execution-worker/src/lib.rs b/massa-execution-worker/src/lib.rs index 030bb929e5e..2c39b79f6ca 100644 --- a/massa-execution-worker/src/lib.rs +++ b/massa-execution-worker/src/lib.rs @@ -97,6 +97,8 @@ mod worker; #[cfg(feature = "execution-trace")] mod trace_history; +mod execution_info; + use massa_db_exports as _; pub use worker::start_execution_worker; diff --git a/massa-execution-worker/src/speculative_roll_state.rs b/massa-execution-worker/src/speculative_roll_state.rs index 0b44436f23b..5509c702aea 100644 --- a/massa-execution-worker/src/speculative_roll_state.rs +++ b/massa-execution-worker/src/speculative_roll_state.rs @@ -254,7 +254,9 @@ impl SpeculativeRollState { thread_count: u8, roll_price: Amount, max_miss_ratio: Ratio, - ) { + ) -> Vec<(Address, Amount)> { + #[allow(unused_mut)] + let mut result = vec![]; let cycle = slot.get_cycle(periods_per_cycle); let (production_stats, full) = @@ -286,6 +288,9 @@ impl SpeculativeRollState { .saturating_add(amount); target_credits.insert(addr, new_deferred_credits); self.added_changes.roll_changes.insert(addr, 0); + + #[cfg(feature = "execution-info")] + result.push((addr, amount)); } } } @@ -295,6 +300,8 @@ impl SpeculativeRollState { credits.credits.insert(target_slot, target_credits); self.added_changes.deferred_credits.extend(credits); } + + result } /// Get deferred credits of an address starting from a given slot diff --git a/massa-execution-worker/src/tests/scenarios_mandatories.rs b/massa-execution-worker/src/tests/scenarios_mandatories.rs index ccac3173a4b..b011554a019 100644 --- a/massa-execution-worker/src/tests/scenarios_mandatories.rs +++ b/massa-execution-worker/src/tests/scenarios_mandatories.rs @@ -40,7 +40,9 @@ use std::{cmp::Reverse, collections::BTreeMap, str::FromStr, time::Duration}; use super::universe::{ExecutionForeignControllers, ExecutionTestUniverse}; #[cfg(feature = "execution-trace")] -use massa_execution_exports::{AbiTrace, SCRuntimeAbiTraceType, SCRuntimeAbiTraceValue}; +use massa_execution_exports::types_trace_info::AbiTrace; +#[cfg(feature = "execution-trace")] +use massa_execution_exports::{SCRuntimeAbiTraceType, SCRuntimeAbiTraceValue}; #[cfg(feature = "execution-trace")] use massa_models::operation::OperationId; #[cfg(feature = "execution-trace")] diff --git a/massa-execution-worker/src/tests/tests_active_history.rs b/massa-execution-worker/src/tests/tests_active_history.rs index 5088584d687..9d384b422fb 100644 --- a/massa-execution-worker/src/tests/tests_active_history.rs +++ b/massa-execution-worker/src/tests/tests_active_history.rs @@ -58,6 +58,9 @@ fn test_active_history_deferred_credits() { slot_trace: Default::default(), #[cfg(feature = "dump-block")] storage: None, + deferred_credits_execution: Default::default(), + cancel_async_message_execution: Default::default(), + auto_sell_execution: Default::default(), }; let active_history = ActiveHistory(VecDeque::from([exec_output_1])); diff --git a/massa-execution-worker/src/tests/universe.rs b/massa-execution-worker/src/tests/universe.rs index 56a66de5fa0..029661eb576 100644 --- a/massa-execution-worker/src/tests/universe.rs +++ b/massa-execution-worker/src/tests/universe.rs @@ -41,7 +41,7 @@ use tokio::sync::broadcast; use crate::start_execution_worker; #[cfg(feature = "execution-trace")] -use massa_execution_exports::SlotAbiCallStack; +use massa_execution_exports::types_trace_info::SlotAbiCallStack; pub struct ExecutionForeignControllers { pub selector_controller: Box, diff --git a/massa-execution-worker/src/trace_history.rs b/massa-execution-worker/src/trace_history.rs index 7f8261a2f6b..872c286dd87 100644 --- a/massa-execution-worker/src/trace_history.rs +++ b/massa-execution-worker/src/trace_history.rs @@ -1,4 +1,5 @@ -use massa_execution_exports::{AbiTrace, SlotAbiCallStack, Transfer}; +use massa_execution_exports::types_trace_info::AbiTrace; +use massa_execution_exports::types_trace_info::{SlotAbiCallStack, Transfer}; use massa_models::{operation::OperationId, slot::Slot}; use schnellru::{ByLength, LruMap}; diff --git a/massa-grpc/src/public.rs b/massa-grpc/src/public.rs index 28de4328143..40a330525d7 100644 --- a/massa-grpc/src/public.rs +++ b/massa-grpc/src/public.rs @@ -31,7 +31,7 @@ use std::collections::HashSet; use std::str::FromStr; #[cfg(feature = "execution-trace")] -use massa_execution_exports::AbiTrace; +use massa_execution_exports::types_trace_info::AbiTrace; #[cfg(feature = "execution-trace")] use massa_proto_rs::massa::api::v1::abi_call_stack_element_parent::CallStackElement; #[cfg(feature = "execution-trace")] diff --git a/massa-grpc/src/tests/public.rs b/massa-grpc/src/tests/public.rs index abbf5b37765..9d322c90227 100644 --- a/massa-grpc/src/tests/public.rs +++ b/massa-grpc/src/tests/public.rs @@ -359,6 +359,9 @@ async fn execute_read_only_call() { slot_trace: None, #[cfg(feature = "dump-block")] storage: None, + deferred_credits_execution: vec![], + cancel_async_message_execution: vec![], + auto_sell_execution: vec![], }, gas_cost: 100, call_result: "toto".as_bytes().to_vec(), diff --git a/massa-grpc/src/tests/stream.rs b/massa-grpc/src/tests/stream.rs index ae535a61637..97935bbf2c5 100644 --- a/massa-grpc/src/tests/stream.rs +++ b/massa-grpc/src/tests/stream.rs @@ -1072,6 +1072,9 @@ async fn new_slot_execution_outputs() { slot_trace: None, #[cfg(feature = "dump-block")] storage: None, + deferred_credits_execution: vec![], + cancel_async_message_execution: vec![], + auto_sell_execution: vec![], }; let (tx_request, rx) = tokio::sync::mpsc::channel(10); diff --git a/massa-node/Cargo.toml b/massa-node/Cargo.toml index c9fabd525f6..dfecc58bcf7 100644 --- a/massa-node/Cargo.toml +++ b/massa-node/Cargo.toml @@ -24,6 +24,9 @@ sandbox = [ ] execution-trace = ["massa_grpc/execution-trace", "massa_api/execution-trace", "massa_execution_worker/execution-trace", "massa_execution_exports/execution-trace"] dump-block = ["massa_execution_worker/dump-block", "massa_execution_exports/dump-block"] +execution-info = [ + "execution-trace" +] [dependencies] crossbeam-channel = { workspace = true } # BOM UPGRADE Revert to "0.5.6" if problem