From dcbe34c6770ac25ce3629faeae567144a180af88 Mon Sep 17 00:00:00 2001 From: Ed Hastings Date: Fri, 15 Mar 2024 03:13:35 -0700 Subject: [PATCH] reworked execute finalized block flow...all node tests are passing except should_store_finalized_approvals; will investigate further prior to opening PR --- .../test_support/src/wasm_test_builder.rs | 9 +- .../system_contracts/auction/distribute.rs | 3 + node/src/components/contract_runtime.rs | 4 +- .../components/contract_runtime/operations.rs | 530 +++++++++--------- node/src/components/contract_runtime/types.rs | 226 +++++++- node/src/components/contract_runtime/utils.rs | 10 +- node/src/reactor/main_reactor/tests.rs | 6 +- storage/src/data_access_layer/balance.rs | 10 + storage/src/data_access_layer/balance_hold.rs | 1 + storage/src/data_access_layer/bidding.rs | 10 +- storage/src/global_state/state/mod.rs | 222 ++++---- types/src/transaction.rs | 15 + 12 files changed, 626 insertions(+), 420 deletions(-) diff --git a/execution_engine_testing/test_support/src/wasm_test_builder.rs b/execution_engine_testing/test_support/src/wasm_test_builder.rs index 5ba6d08eea..e6402b1eaa 100644 --- a/execution_engine_testing/test_support/src/wasm_test_builder.rs +++ b/execution_engine_testing/test_support/src/wasm_test_builder.rs @@ -792,14 +792,7 @@ where authorization_keys, auction_method, ); - let ret = self.data_access_layer().bidding(bidding_req); - if let BiddingResult::Success { - post_state_hash, .. - } = ret - { - self.post_state_hash = Some(post_state_hash); - } - ret + self.data_access_layer().bidding(bidding_req) } /// Runs an [`ExecuteRequest`]. diff --git a/execution_engine_testing/tests/src/test/system_contracts/auction/distribute.rs b/execution_engine_testing/tests/src/test/system_contracts/auction/distribute.rs index 5dddddac78..5328759da2 100644 --- a/execution_engine_testing/tests/src/test/system_contracts/auction/distribute.rs +++ b/execution_engine_testing/tests/src/test/system_contracts/auction/distribute.rs @@ -998,6 +998,7 @@ fn should_distribute_rewards_after_restaking_delegated_funds() { }, ); assert!(undelegate_result.is_success(), "{:?}", undelegate_result); + builder.commit_transforms(builder.get_post_state_hash(), undelegate_result.effects()); delegator_1_stake = get_delegator_staked_amount(&mut builder, VALIDATOR_1.clone(), DELEGATOR_1.clone()); @@ -1017,6 +1018,7 @@ fn should_distribute_rewards_after_restaking_delegated_funds() { }, ); assert!(updelegate_result.is_success(), "{:?}", updelegate_result); + builder.commit_transforms(builder.get_post_state_hash(), undelegate_result.effects()); delegator_2_stake = get_delegator_staked_amount(&mut builder, VALIDATOR_1.clone(), DELEGATOR_2.clone()); @@ -1044,6 +1046,7 @@ fn should_distribute_rewards_after_restaking_delegated_funds() { auction_method, ); assert!(bid_flip_result.is_success(), "{:?}", bid_flip_result); + builder.commit_transforms(builder.get_post_state_hash(), undelegate_result.effects()); validator_stake = get_validator_bid(&mut builder, VALIDATOR_1.clone()) .expect("should have validator bid") .staked_amount(); diff --git a/node/src/components/contract_runtime.rs b/node/src/components/contract_runtime.rs index c91fd19297..4c7bcb9e40 100644 --- a/node/src/components/contract_runtime.rs +++ b/node/src/components/contract_runtime.rs @@ -72,8 +72,8 @@ pub(crate) use operations::compute_execution_results_checksum; pub use operations::execute_finalized_block; use operations::speculatively_execute; pub(crate) use types::{ - BlockAndExecutionResults, ExecutionArtifact, ExecutionPreState, SpeculativeExecutionState, - StepEffectsAndUpcomingEraValidators, + BlockAndExecutionArtifacts, ExecutionArtifact, ExecutionArtifacts, ExecutionPreState, + SpeculativeExecutionState, StepOutcome, }; use utils::{exec_or_requeue, run_intensive_task}; diff --git a/node/src/components/contract_runtime/operations.rs b/node/src/components/contract_runtime/operations.rs index 3a664a48da..90594b695b 100644 --- a/node/src/components/contract_runtime/operations.rs +++ b/node/src/components/contract_runtime/operations.rs @@ -11,10 +11,11 @@ use casper_execution_engine::engine_state::{ use casper_storage::{ block_store::types::ApprovalsHashes, data_access_layer::{ - AuctionMethod, BalanceHoldRequest, BiddingRequest, BiddingResult, BlockRewardsRequest, - BlockRewardsResult, DataAccessLayer, EraValidatorsRequest, EraValidatorsResult, EvictItem, - FeeRequest, FeeResult, FlushRequest, InsufficientBalanceHandling, PruneRequest, - PruneResult, StepRequest, StepResult, TransferRequest, TransferResult, + balance::BalanceHandling, AuctionMethod, BalanceHoldRequest, BalanceIdentifier, + BalanceRequest, BiddingRequest, BlockRewardsRequest, BlockRewardsResult, DataAccessLayer, + EraValidatorsRequest, EraValidatorsResult, EvictItem, FeeRequest, FeeResult, FlushRequest, + InsufficientBalanceHandling, PruneRequest, PruneResult, StepRequest, StepResult, + TransferRequest, }, global_state::{ error::Error as GlobalStateError, @@ -29,26 +30,27 @@ use casper_types::{ binary_port::SpeculativeExecutionResult, bytesrepr::{self, ToBytes, U32_SERIALIZED_LENGTH}, contract_messages::Messages, - execution::{Effects, ExecutionResult, ExecutionResultV2, TransformKindV2, TransformV2}, + execution::{Effects, ExecutionResult, TransformKindV2, TransformV2}, system::mint::BalanceHoldAddrTag, - ApprovalsHash, BlockTime, BlockV2, CLValue, Chainspec, ChecksumRegistry, DeployHash, Digest, - EraEndV2, EraId, FeeHandling, GasLimited, Key, ProtocolVersion, PublicKey, Transaction, U512, + BlockTime, BlockV2, CLValue, CategorizedTransaction, Chainspec, ChecksumRegistry, DeployHash, + Digest, EraEndV2, EraId, FeeHandling, GasLimited, Key, ProtocolVersion, PublicKey, Transaction, + TransactionCategory, U512, }; use crate::{ components::{ contract_runtime::{ - error::BlockExecutionError, types::StepEffectsAndUpcomingEraValidators, - BlockAndExecutionResults, ExecutionPreState, Metrics, SpeculativeExecutionState, - APPROVALS_CHECKSUM_NAME, EXECUTION_RESULTS_CHECKSUM_NAME, + error::BlockExecutionError, types::StepOutcome, BlockAndExecutionArtifacts, + ExecutionPreState, Metrics, SpeculativeExecutionState, APPROVALS_CHECKSUM_NAME, + EXECUTION_RESULTS_CHECKSUM_NAME, }, fetcher::FetchItem, }, - contract_runtime::utils::calculate_prune_eras, + contract_runtime::{types::ExecutionArtifactOutcome, utils::calculate_prune_eras}, types::{self, Chunkable, ExecutableBlock, InternalEraReport}, }; -use super::ExecutionArtifact; +use super::{ExecutionArtifact, ExecutionArtifacts}; /// Executes a finalized block. #[allow(clippy::too_many_arguments)] @@ -60,7 +62,7 @@ pub fn execute_finalized_block( execution_pre_state: ExecutionPreState, executable_block: ExecutableBlock, key_block_height_for_activation_point: u64, -) -> Result { +) -> Result { if executable_block.height != execution_pre_state.next_block_height() { return Err(BlockExecutionError::WrongBlockHeight { executable_block: Box::new(executable_block), @@ -78,21 +80,15 @@ pub fn execute_finalized_block( let parent_seed = execution_pre_state.parent_seed(); let mut state_root_hash = pre_state_root_hash; - let mut execution_artifacts: Vec = - Vec::with_capacity(executable_block.transactions.len()); + let mut artifacts = ExecutionArtifacts::with_capacity(executable_block.transactions.len()); let block_time = executable_block.timestamp.millis(); + let balance_handling = BalanceHandling::Available { + block_time, + hold_interval: chainspec.core_config.balance_hold_interval.millis(), + }; let holds_epoch = Some(chainspec.balance_holds_epoch(executable_block.timestamp)); let start = Instant::now(); - let txn_ids = executable_block - .transactions - .iter() - .map(Transaction::fetch_id) - .collect_vec(); - let approvals_checksum = types::compute_approvals_checksum(txn_ids.clone()) - .map_err(BlockExecutionError::FailedToComputeApprovalsChecksum)?; - let approvals_hashes: Vec = - txn_ids.into_iter().map(|id| id.approvals_hash()).collect(); let scratch_state = data_access_layer.get_scratch_global_state(); let mut effects = Effects::new(); @@ -101,79 +97,195 @@ pub fn execute_finalized_block( let insufficient_balance_handling = InsufficientBalanceHandling::HoldRemaining; let gas_price = Some(1); // < --TODO: this is where Karan's calculated gas price needs to be used - for transaction in executable_block.transactions { + for transaction in &executable_block.transactions { let transaction_hash = transaction.hash(); + let transaction_header = transaction.header(); let runtime_args = transaction.session_args().clone(); let entry_point = transaction.entry_point(); + let balance_identifier: BalanceIdentifier = transaction.initiator_addr().into(); // NOTE: this is the actual adjusted cost (gas limit * gas price) // NOT the allowed computation limit (gas limit) let cost = match transaction.gas_limit(&system_costs, gas_price) { Ok(gas) => gas.value(), Err(ite) => { - execution_artifacts.push(ExecutionArtifact::new( + artifacts.push_invalid_transaction( transaction_hash, - transaction.header(), - ExecutionResult::V2(ExecutionResultV2::Failure { - effects: Effects::new(), - cost: U512::zero(), - transfers: vec![], - error_message: format!("{:?}", ite), - }), - Messages::default(), - )); + transaction_header.clone(), + ite.clone(), + ); debug!(%ite, "invalid transaction"); continue; } }; + let initial_balance_result = scratch_state.balance(BalanceRequest::new( + state_root_hash, + protocol_version, + balance_identifier.clone(), + balance_handling, + )); + + let is_sufficient_balance = initial_balance_result.is_sufficient(cost); + let authorization_keys = transaction.authorization_keys(); + + match (transaction.category(), is_sufficient_balance) { + (_, false) => { + debug!( + "skipping execution of {} due to insufficient balance", + transaction_hash + ); + } + (TransactionCategory::Mint, _) => { + let transfer_result = scratch_state.transfer(TransferRequest::with_runtime_args( + native_runtime_config.clone(), + state_root_hash, + holds_epoch, + protocol_version, + transaction_hash, + transaction.initiator_addr().account_hash(), + authorization_keys.clone(), + runtime_args.clone(), + cost, + )); + match artifacts.push_transfer_result( + transaction_hash, + transaction_header.clone(), + transfer_result, + cost, + ) { + ExecutionArtifactOutcome::RootNotFound => { + return Err(BlockExecutionError::RootNotFound(state_root_hash)) + } + ExecutionArtifactOutcome::Failure => { + continue; + } + ExecutionArtifactOutcome::Success(transfer_effects) => { + effects.append(transfer_effects.clone()); + } + } + } + (TransactionCategory::Auction, _) => { + let auction_method = match AuctionMethod::from_parts( + entry_point, + &runtime_args, + holds_epoch, + chainspec, + ) { + Ok(auction_method) => auction_method, + Err(_) => { + artifacts.push_auction_method_failure( + transaction_hash, + transaction_header.clone(), + cost, + ); + continue; + } + }; + let bidding_result = scratch_state.bidding(BiddingRequest::new( + native_runtime_config.clone(), + state_root_hash, + block_time, + protocol_version, + transaction_hash, + transaction.initiator_addr().account_hash(), + authorization_keys.clone(), + auction_method, + )); + match artifacts.push_bidding_result( + transaction_hash, + transaction_header.clone(), + bidding_result, + cost, + ) { + ExecutionArtifactOutcome::RootNotFound => { + return Err(BlockExecutionError::RootNotFound(state_root_hash)) + } + ExecutionArtifactOutcome::Failure => { + continue; + } + ExecutionArtifactOutcome::Success(bidding_effects) => { + effects.append(bidding_effects.clone()); + } + } + } + (TransactionCategory::Standard, _) | (TransactionCategory::InstallUpgrade, _) => { + // TODO: i think fraser is doing the Transaction::V1(_) fixing here, but either way + // it needs to get done soonish to complete this work. All of this section needs + // a bit of a re-work to allow the rest of this logic to move further away from + // the legacy notions. + let (deploy_hash, deploy) = match transaction { + Transaction::Deploy(deploy) => { + let deploy_hash = *deploy.hash(); + (deploy_hash, deploy) + } + Transaction::V1(_) => continue, + }; + + let deploy_header = deploy.header().clone(); + let execute_request = ExecuteRequest::new( + state_root_hash, + block_time, + vec![DeployItem::from(deploy.clone())], + protocol_version, + PublicKey::clone(&executable_block.proposer), + ); + + let exec_result = execute( + &scratch_state, + execution_engine_v1, + metrics.clone(), + execute_request, + )?; + + trace!(?deploy_hash, ?exec_result, "transaction execution result"); + // As for now a given state is expected to exist. + let (state_hash, execution_result, messages) = commit_execution_results( + &scratch_state, + metrics.clone(), + state_root_hash, + deploy_hash, + exec_result, + )?; + artifacts.push(ExecutionArtifact::deploy( + deploy_hash, + deploy_header, + execution_result, + messages, + )); + state_root_hash = state_hash; + } + } + // handle payment per the chainspec determined fee setting match chainspec.core_config.fee_handling { FeeHandling::NoFee => { // this is the "fee elimination" model...a BalanceHold for the full cost is placed // on the initiator's purse. - let hold_amount = cost; let hold_result = scratch_state.balance_hold(BalanceHoldRequest::new( state_root_hash, protocol_version, - transaction.initiator_addr().into(), + balance_identifier.clone(), BalanceHoldAddrTag::Gas, - hold_amount, + cost, BlockTime::new(block_time), chainspec.core_config.balance_hold_interval, insufficient_balance_handling, )); - if hold_result.is_root_not_found() { - return Err(BlockExecutionError::RootNotFound(state_root_hash)); - } - let execution_result = { - let hold_cost = U512::zero(); // we don't charge for the hold itself. - let hold_effects = hold_result.effects(); - if hold_result.is_fully_covered() { - ExecutionResultV2::Success { - effects: hold_effects, - transfers: vec![], - cost: hold_cost, - } - } else { - let error_message = hold_result.error_message(); - debug!(%error_message); - ExecutionResultV2::Failure { - effects: hold_effects, - transfers: vec![], - error_message, - cost: hold_cost, - } - } - }; - execution_artifacts.push(ExecutionArtifact::new( + match artifacts.push_hold_result( transaction_hash, - transaction.header(), - ExecutionResult::V2(execution_result), - Messages::default(), - )); - if !hold_result.is_fully_covered() { - continue; + transaction_header.clone(), + hold_result, + ) { + ExecutionArtifactOutcome::RootNotFound => { + return Err(BlockExecutionError::RootNotFound(state_root_hash)) + } + ExecutionArtifactOutcome::Failure => { + continue; + } + ExecutionArtifactOutcome::Success(hold_effects) => { + effects.append(hold_effects.clone()) + } } } FeeHandling::PayToProposer => { @@ -194,158 +306,47 @@ pub fn execute_finalized_block( // fees are simply burned, lowering total supply. } } + } - if transaction.is_native_mint() { - // native transfers are routed to the data provider - let authorization_keys = transaction.authorization_keys(); - let transfer_req = TransferRequest::with_runtime_args( - native_runtime_config.clone(), - state_root_hash, - holds_epoch, - protocol_version, - transaction_hash, - transaction.initiator_addr().account_hash(), - authorization_keys, - runtime_args, - U512::zero(), /* <-- this should be from chainspec cost table */ - ); - //NOTE: native mint interactions auto-commit - let transfer_result = scratch_state.transfer(transfer_req); - trace!( - ?transaction_hash, - ?transfer_result, - "native transfer result" - ); - match transfer_result { - TransferResult::RootNotFound => { - return Err(BlockExecutionError::RootNotFound(state_root_hash)); - } - TransferResult::Failure(transfer_error) => { - let artifact = ExecutionArtifact::new( - transaction_hash, - transaction.header(), - ExecutionResult::V2(ExecutionResultV2::Failure { - effects: Effects::new(), - cost: U512::zero(), - transfers: vec![], - error_message: format!("{:?}", transfer_error), - }), - Messages::default(), - ); - execution_artifacts.push(artifact); - debug!(%transfer_error); - // a failure does not auto commit - continue; - } - TransferResult::Success { - effects: transfer_effects, - transfers, - .. - } => { - effects.append(transfer_effects.clone()); - let artifact = ExecutionArtifact::new( - transaction_hash, - transaction.header(), - ExecutionResult::V2(ExecutionResultV2::Success { - effects: transfer_effects, - cost: U512::zero(), - transfers, - }), - Messages::default(), - ); - execution_artifacts.push(artifact); - } - } - continue; - } - if transaction.is_native_auction() { - let runtime_args = transaction.session_args(); - let auction_method = match AuctionMethod::from_parts( - entry_point, - runtime_args, - holds_epoch, - chainspec, - ) { - Ok(auction_method) => auction_method, - Err(_) => { - error!(%transaction_hash, "failed to resolve auction method"); - continue; // skip to next record - } - }; - let authorization_keys = transaction.authorization_keys(); - let bidding_req = BiddingRequest::new( - native_runtime_config.clone(), - state_root_hash, - block_time, - protocol_version, - transaction_hash, - transaction.initiator_addr().account_hash(), - authorization_keys, - auction_method, - ); + // calculate and store checksums for approvals and execution effects across the transactions in + // the block we do this so that the full set of approvals and the full set of effect meta + // data can be verified if necessary for a given block. the block synchronizer in particular + // depends on the existence of such checksums. + let transactions_approvals_hashes = { + let mut checksum_registry = ChecksumRegistry::new(); - let bidding_result = scratch_state.bidding(bidding_req); - trace!(?transaction_hash, ?bidding_result, "native auction result"); - match bidding_result { - BiddingResult::RootNotFound => { - return Err(BlockExecutionError::RootNotFound(state_root_hash)) - } - BiddingResult::Success { - post_state_hash, .. - } => { - // we need a way to capture the effects from this without double committing - state_root_hash = post_state_hash; - } - BiddingResult::Failure(tce) => { - debug!(%tce); - continue; - } - } - } + let txn_ids = executable_block + .transactions + .iter() + .map(Transaction::fetch_id) + .collect_vec(); - let (deploy_hash, deploy) = match transaction { - Transaction::Deploy(deploy) => { - let deploy_hash = *deploy.hash(); - (deploy_hash, deploy) - } - Transaction::V1(_) => continue, - }; + let approvals_checksum = types::compute_approvals_checksum(txn_ids.clone()) + .map_err(BlockExecutionError::FailedToComputeApprovalsChecksum)?; - let deploy_header = deploy.header().clone(); - let execute_request = ExecuteRequest::new( - state_root_hash, - block_time, - vec![DeployItem::from(deploy)], - protocol_version, - PublicKey::clone(&executable_block.proposer), - ); + checksum_registry.insert(APPROVALS_CHECKSUM_NAME, approvals_checksum); - let exec_result = execute( - &scratch_state, - execution_engine_v1, - metrics.clone(), - execute_request, - )?; - - trace!(?deploy_hash, ?exec_result, "transaction execution result"); - // As for now a given state is expected to exist. - let (state_hash, execution_result, messages) = commit_execution_results( - &scratch_state, - metrics.clone(), - state_root_hash, - deploy_hash, - exec_result, - )?; - execution_artifacts.push(ExecutionArtifact::deploy( - deploy_hash, - deploy_header, - execution_result, - messages, + // Write the deploy approvals' and execution results' checksums to global state. + let execution_results_checksum = + compute_execution_results_checksum(artifacts.execution_results().into_iter())?; + checksum_registry.insert(EXECUTION_RESULTS_CHECKSUM_NAME, execution_results_checksum); + + effects.push(TransformV2::new( + Key::ChecksumRegistry, + TransformKindV2::Write( + CLValue::from_t(checksum_registry) + .map_err(BlockExecutionError::ChecksumRegistryToCLValue)? + .into(), + ), )); - state_root_hash = state_hash; - } - // Pay out fees, if relevant. + txn_ids.into_iter().map(|id| id.approvals_hash()).collect() + }; + + // After all transaction processing has been completed, commit all of the effects. + scratch_state.commit(state_root_hash, effects)?; + + // Pay out block fees, if relevant. { let fee_req = FeeRequest::new( native_runtime_config.clone(), @@ -373,6 +374,12 @@ pub fn execute_finalized_block( } } + // Update exec_block metric BEFORE determining per era things such as era rewards and step. + // the commit_step function handles the metrics for step + if let Some(metrics) = metrics.as_ref() { + metrics.exec_block.observe(start.elapsed().as_secs_f64()); + } + // Pay out ̶b̶l̶o̶c̶k̶ e͇r͇a͇ rewards // NOTE: despite the name, these rewards are currently paid out per ERA not per BLOCK // at one point, they were going to be paid out per block (and might be in the future) @@ -401,43 +408,9 @@ pub fn execute_finalized_block( } } - // handle checksum registry - let approvals_hashes = { - let mut checksum_registry = ChecksumRegistry::new(); - - checksum_registry.insert(APPROVALS_CHECKSUM_NAME, approvals_checksum); - - // Write the deploy approvals' and execution results' checksums to global state. - let execution_results_checksum = compute_execution_results_checksum( - execution_artifacts - .iter() - .map(|artifact| &artifact.execution_result), - )?; - checksum_registry.insert(EXECUTION_RESULTS_CHECKSUM_NAME, execution_results_checksum); - - effects.push(TransformV2::new( - Key::ChecksumRegistry, - TransformKindV2::Write( - CLValue::from_t(checksum_registry) - .map_err(BlockExecutionError::ChecksumRegistryToCLValue)? - .into(), - ), - )); - - approvals_hashes - }; - - scratch_state.commit(state_root_hash, effects)?; - - if let Some(metrics) = metrics.as_ref() { - metrics.exec_block.observe(start.elapsed().as_secs_f64()); - } - - // If the finalized block has an era report, run the auction contract and get the upcoming era - // validators. - let maybe_step_effects_and_upcoming_era_validators = if let Some(era_report) = - &executable_block.era_report - { + // if era report is some, this is a switch block. a series of end-of-era extra processing must + // transpire before this block is entirely finished. + let step_outcome = if let Some(era_report) = &executable_block.era_report { let step_effects = match commit_step( native_runtime_config, &scratch_state, @@ -452,7 +425,14 @@ pub fn execute_finalized_block( return Err(BlockExecutionError::RootNotFound(state_root_hash)) } StepResult::Failure(err) => return Err(BlockExecutionError::Step(err)), - StepResult::Success { effects, .. } => effects, + StepResult::Success { + effects, + post_state_hash, + .. + } => { + state_root_hash = post_state_hash; + effects + } }; state_root_hash = data_access_layer.write_scratch_to_db(state_root_hash, scratch_state)?; @@ -461,12 +441,12 @@ pub fn execute_finalized_block( let era_validators_result = data_access_layer.era_validators(era_validators_req); let upcoming_era_validators = match era_validators_result { - EraValidatorsResult::AuctionNotFound => { - panic!("auction not found"); - } EraValidatorsResult::RootNotFound => { panic!("root not found"); } + EraValidatorsResult::AuctionNotFound => { + panic!("auction not found"); + } EraValidatorsResult::ValueNotFound(msg) => { panic!("validator snapshot not found: {}", msg); } @@ -476,7 +456,7 @@ pub fn execute_finalized_block( EraValidatorsResult::Success { era_validators } => era_validators, }; - Some(StepEffectsAndUpcomingEraValidators { + Some(StepOutcome { step_effects, upcoming_era_validators, }) @@ -487,14 +467,6 @@ pub fn execute_finalized_block( None }; - // Flush once, after all deploys have been executed. - let flush_req = FlushRequest::new(); - let flush_result = data_access_layer.flush(flush_req); - if let Err(gse) = flush_result.as_error() { - error!("failed to flush lmdb"); - return Err(BlockExecutionError::Lmdb(gse)); - } - // Pruning if let Some(previous_block_height) = executable_block.height.checked_sub(1) { if let Some(keys_to_prune) = calculate_prune_eras( @@ -555,16 +527,22 @@ pub fn execute_finalized_block( } } + // Flush once, after all data mutation. + let flush_req = FlushRequest::new(); + let flush_result = data_access_layer.flush(flush_req); + if let Err(gse) = flush_result.as_error() { + error!("failed to flush lmdb"); + return Err(BlockExecutionError::Lmdb(gse)); + } + let maybe_next_era_validator_weights: Option> = { let next_era_id = executable_block.era_id.successor(); - maybe_step_effects_and_upcoming_era_validators - .as_ref() - .and_then( - |StepEffectsAndUpcomingEraValidators { - upcoming_era_validators, - .. - }| upcoming_era_validators.get(&next_era_id).cloned(), - ) + step_outcome.as_ref().and_then( + |StepOutcome { + upcoming_era_validators, + .. + }| upcoming_era_validators.get(&next_era_id).cloned(), + ) }; let era_end = match ( @@ -637,16 +615,18 @@ pub fn execute_finalized_block( Box::new(ApprovalsHashes::new( *block.hash(), - approvals_hashes, + transactions_approvals_hashes, proof_of_checksum_registry, )) }; - Ok(BlockAndExecutionResults { + let execution_artifacts = artifacts.take(); + + Ok(BlockAndExecutionArtifacts { block, approvals_hashes, - execution_results: execution_artifacts, - maybe_step_effects_and_upcoming_era_validators, + execution_artifacts, + step_outcome, }) } diff --git a/node/src/components/contract_runtime/types.rs b/node/src/components/contract_runtime/types.rs index d15226e368..ffa6ccf01a 100644 --- a/node/src/components/contract_runtime/types.rs +++ b/node/src/components/contract_runtime/types.rs @@ -2,15 +2,17 @@ use std::{collections::BTreeMap, sync::Arc}; use datasize::DataSize; use serde::Serialize; +use tracing::{debug, trace}; use casper_storage::{ - block_store::types::ApprovalsHashes, data_access_layer::EraValidatorsRequest, + block_store::types::ApprovalsHashes, + data_access_layer::{BalanceHoldResult, BiddingResult, EraValidatorsRequest, TransferResult}, }; use casper_types::{ contract_messages::Messages, - execution::{Effects, ExecutionResult}, - BlockHash, BlockHeaderV2, BlockV2, DeployHash, DeployHeader, Digest, EraId, ProtocolVersion, - PublicKey, Timestamp, TransactionHash, TransactionHeader, TransactionV1Hash, + execution::{Effects, ExecutionResult, ExecutionResultV2}, + BlockHash, BlockHeaderV2, BlockV2, DeployHash, DeployHeader, Digest, EraId, InvalidTransaction, + ProtocolVersion, PublicKey, Timestamp, TransactionHash, TransactionHeader, TransactionV1Hash, TransactionV1Header, U512, }; @@ -56,13 +58,218 @@ impl From for EraValidatorsRequest { /// Effects from running step and the next era validators that are gathered when an era ends. #[derive(Clone, Debug, DataSize)] -pub(crate) struct StepEffectsAndUpcomingEraValidators { +pub(crate) struct StepOutcome { /// Validator sets for all upcoming eras that have already been determined. pub(crate) upcoming_era_validators: BTreeMap>, /// An [`Effects`] created by an era ending. pub(crate) step_effects: Effects, } +pub(crate) struct ExecutionArtifacts { + artifacts: Vec, +} + +pub(crate) enum ExecutionArtifactOutcome { + RootNotFound, + Failure, + Success(Effects), +} + +impl ExecutionArtifacts { + pub fn with_capacity(capacity: usize) -> Self { + let artifacts = Vec::with_capacity(capacity); + ExecutionArtifacts { artifacts } + } + + pub fn push(&mut self, execution_artifact: ExecutionArtifact) { + self.artifacts.push(execution_artifact); + } + + pub fn push_invalid_transaction( + &mut self, + transaction_hash: TransactionHash, + transaction_header: TransactionHeader, + invalid_transaction: InvalidTransaction, + ) { + self.push_failure( + transaction_hash, + transaction_header, + format!("{:?}", invalid_transaction), + ); + } + + pub fn push_auction_method_failure( + &mut self, + transaction_hash: TransactionHash, + transaction_header: TransactionHeader, + cost: U512, + ) { + let msg = "failed to resolve auction method".to_string(); + let artifact = ExecutionArtifact::new( + transaction_hash, + transaction_header, + ExecutionResult::V2(ExecutionResultV2::Failure { + effects: Effects::new(), + transfers: vec![], + error_message: msg.clone(), + cost, + }), + Messages::default(), + ); + debug!(%transaction_hash, "{:?}", msg); + self.artifacts.push(artifact); + } + + pub fn push_transfer_result( + &mut self, + transaction_hash: TransactionHash, + transaction_header: TransactionHeader, + transfer_result: TransferResult, + cost: U512, + ) -> ExecutionArtifactOutcome { + trace!( + ?transaction_hash, + ?transfer_result, + "native transfer result" + ); + match transfer_result { + TransferResult::RootNotFound => ExecutionArtifactOutcome::RootNotFound, + TransferResult::Failure(transfer_error) => { + self.push_failure( + transaction_hash, + transaction_header, + format!("{:?}", transfer_error), + ); + debug!(%transfer_error); + ExecutionArtifactOutcome::Failure + } + TransferResult::Success { + effects: transfer_effects, + transfers, + } => { + self.artifacts.push(ExecutionArtifact::new( + transaction_hash, + transaction_header, + ExecutionResult::V2(ExecutionResultV2::Success { + effects: transfer_effects.clone(), + cost, + transfers, + }), + Messages::default(), + )); + ExecutionArtifactOutcome::Success(transfer_effects) + } + } + } + + pub fn push_bidding_result( + &mut self, + transaction_hash: TransactionHash, + transaction_header: TransactionHeader, + bidding_result: BiddingResult, + cost: U512, + ) -> ExecutionArtifactOutcome { + trace!(?transaction_hash, ?bidding_result, "bidding result"); + match bidding_result { + BiddingResult::RootNotFound => ExecutionArtifactOutcome::RootNotFound, + BiddingResult::Failure(tce) => { + self.artifacts.push(ExecutionArtifact::new( + transaction_hash, + transaction_header, + ExecutionResult::V2(ExecutionResultV2::Failure { + effects: Effects::new(), + cost, + transfers: vec![], + error_message: format!("{:?}", tce), + }), + Messages::default(), + )); + debug!(%tce); + ExecutionArtifactOutcome::Failure + } + BiddingResult::Success { + effects: bidding_effects, + .. + } => { + self.artifacts.push(ExecutionArtifact::new( + transaction_hash, + transaction_header, + ExecutionResult::V2(ExecutionResultV2::Success { + effects: bidding_effects.clone(), + cost, + transfers: vec![], + }), + Messages::default(), + )); + ExecutionArtifactOutcome::Success(bidding_effects) + } + } + } + + pub fn push_hold_result( + &mut self, + transaction_hash: TransactionHash, + transaction_header: TransactionHeader, + hold_result: BalanceHoldResult, + ) -> ExecutionArtifactOutcome { + trace!(?transaction_hash, ?hold_result, "balance hold result"); + if hold_result.is_root_not_found() { + return ExecutionArtifactOutcome::RootNotFound; + } + if !hold_result.is_fully_covered() { + let error_message = hold_result.error_message(); + self.push_failure(transaction_hash, transaction_header, error_message.clone()); + debug!(%error_message); + ExecutionArtifactOutcome::Failure + } else { + let hold_cost = U512::zero(); // we don't charge for the hold itself. + let hold_effects = hold_result.effects(); + self.artifacts.push(ExecutionArtifact::new( + transaction_hash, + transaction_header, + ExecutionResult::V2(ExecutionResultV2::Success { + effects: hold_effects.clone(), + transfers: vec![], + cost: hold_cost, + }), + Messages::default(), + )); + ExecutionArtifactOutcome::Success(hold_effects) + } + } + + fn push_failure( + &mut self, + transaction_hash: TransactionHash, + transaction_header: TransactionHeader, + error_message: String, + ) { + let execution_artifact = ExecutionArtifact::new( + transaction_hash, + transaction_header, + ExecutionResult::V2(ExecutionResultV2::Failure { + effects: Effects::new(), + cost: U512::zero(), + transfers: vec![], + error_message, + }), + Messages::default(), + ); + self.artifacts.push(execution_artifact); + } + + pub fn execution_results(&self) -> Vec<&ExecutionResult> { + self.artifacts + .iter() + .map(|artifact| &artifact.execution_result) + .collect::>() + } + + pub fn take(self) -> Vec { + self.artifacts + } +} + #[derive(Clone, Debug, DataSize, PartialEq, Eq, Serialize)] pub(crate) struct ExecutionArtifact { pub(crate) transaction_hash: TransactionHash, @@ -120,16 +327,15 @@ impl ExecutionArtifact { /// A [`Block`] that was the result of execution in the `ContractRuntime` along with any execution /// effects it may have. #[derive(Clone, Debug, DataSize)] -pub struct BlockAndExecutionResults { +pub struct BlockAndExecutionArtifacts { /// The [`Block`] the contract runtime executed. pub(crate) block: Arc, /// The [`ApprovalsHashes`] for the deploys in this block. pub(crate) approvals_hashes: Box, - /// The results from executing the deploys in the block. - pub(crate) execution_results: Vec, + /// The results from executing the transactions in the block. + pub(crate) execution_artifacts: Vec, /// The [`Effects`] and the upcoming validator sets determined by the `step` - pub(crate) maybe_step_effects_and_upcoming_era_validators: - Option, + pub(crate) step_outcome: Option, } #[derive(DataSize, Debug, Clone, Serialize)] diff --git a/node/src/components/contract_runtime/utils.rs b/node/src/components/contract_runtime/utils.rs index 61e73f5adc..5d1856c4dd 100644 --- a/node/src/components/contract_runtime/utils.rs +++ b/node/src/components/contract_runtime/utils.rs @@ -3,7 +3,7 @@ use crate::{ exec_queue::{ExecQueue, QueueItem}, execute_finalized_block, metrics::Metrics, - rewards, BlockAndExecutionResults, ExecutionPreState, StepEffectsAndUpcomingEraValidators, + rewards, BlockAndExecutionArtifacts, ExecutionPreState, StepOutcome, }, effect::{ announcements::{ContractRuntimeAnnouncement, FatalAnnouncement, MetaBlockAnnouncement}, @@ -99,11 +99,11 @@ pub(super) async fn exec_or_requeue( }); } - let BlockAndExecutionResults { + let BlockAndExecutionArtifacts { block, approvals_hashes, - execution_results, - maybe_step_effects_and_upcoming_era_validators, + execution_artifacts: execution_results, + step_outcome: maybe_step_effects_and_upcoming_era_validators, } = match run_intensive_task(move || { debug!("ContractRuntime: execute_finalized_block"); execute_finalized_block( @@ -149,7 +149,7 @@ pub(super) async fn exec_or_requeue( let current_era_id = block.era_id(); - if let Some(StepEffectsAndUpcomingEraValidators { + if let Some(StepOutcome { step_effects, mut upcoming_era_validators, }) = maybe_step_effects_and_upcoming_era_validators diff --git a/node/src/reactor/main_reactor/tests.rs b/node/src/reactor/main_reactor/tests.rs index 3f9aa3a2af..e59ef4b7e4 100644 --- a/node/src/reactor/main_reactor/tests.rs +++ b/node/src/reactor/main_reactor/tests.rs @@ -1311,11 +1311,11 @@ async fn should_store_finalized_approvals() { // Run until the transaction gets executed. let has_stored_exec_results = |nodes: &Nodes| { nodes.values().all(|runner| { - runner + let read = runner .main_reactor() .storage() - .read_execution_result(&transaction_hash) - .is_some() + .read_execution_result(&transaction_hash); + read.is_some() }) }; fixture.run_until(has_stored_exec_results, ONE_MIN).await; diff --git a/storage/src/data_access_layer/balance.rs b/storage/src/data_access_layer/balance.rs index 8311a8fed9..2263c259e1 100644 --- a/storage/src/data_access_layer/balance.rs +++ b/storage/src/data_access_layer/balance.rs @@ -290,4 +290,14 @@ impl BalanceResult { _ => None, } } + + /// Is the available balance sufficient to cover the cost? + pub fn is_sufficient(&self, cost: U512) -> bool { + match self { + BalanceResult::RootNotFound | BalanceResult::Failure(_) => false, + BalanceResult::Success { + available_balance, .. + } => available_balance >= &cost, + } + } } diff --git a/storage/src/data_access_layer/balance_hold.rs b/storage/src/data_access_layer/balance_hold.rs index 794015eb2e..d56250e240 100644 --- a/storage/src/data_access_layer/balance_hold.rs +++ b/storage/src/data_access_layer/balance_hold.rs @@ -236,6 +236,7 @@ impl Display for BalanceHoldError { } /// Result enum that represents all possible outcomes of a balance hold request. +#[derive(Debug)] pub enum BalanceHoldResult { /// Returned if a passed state root hash is not found. RootNotFound, diff --git a/storage/src/data_access_layer/bidding.rs b/storage/src/data_access_layer/bidding.rs index 85b6e604c3..7982376bb3 100644 --- a/storage/src/data_access_layer/bidding.rs +++ b/storage/src/data_access_layer/bidding.rs @@ -279,8 +279,6 @@ pub enum BiddingResult { Success { // The ret value, if any. ret: AuctionMethodRet, - /// State hash after bidding interaction is committed to the global state. - post_state_hash: Digest, /// Effects of bidding interaction. effects: Effects, }, @@ -293,4 +291,12 @@ impl BiddingResult { pub fn is_success(&self) -> bool { matches!(self, BiddingResult::Success { .. }) } + + /// Effects. + pub fn effects(&self) -> Effects { + match self { + BiddingResult::RootNotFound | BiddingResult::Failure(_) => Effects::new(), + BiddingResult::Success { effects, .. } => effects.clone(), + } + } } diff --git a/storage/src/global_state/state/mod.rs b/storage/src/global_state/state/mod.rs index 342faf6e82..df258f4263 100644 --- a/storage/src/global_state/state/mod.rs +++ b/storage/src/global_state/state/mod.rs @@ -619,121 +619,6 @@ pub trait CommitProvider: StateProvider { } FeeResult::Failure(FeeError::NoFeesDistributed) } - - /// Direct auction interaction for all variations of bid management. - fn bidding(&self, request: BiddingRequest) -> BiddingResult { - let state_hash = request.state_hash(); - let tc = match self.tracking_copy(state_hash) { - Ok(Some(tc)) => Rc::new(RefCell::new(tc)), - Ok(None) => return BiddingResult::RootNotFound, - Err(err) => return BiddingResult::Failure(TrackingCopyError::Storage(err)), - }; - - let protocol_version = request.protocol_version(); - let config = request.config(); - - let mut runtime = match RuntimeNative::new_system_runtime( - config.clone(), - protocol_version, - Id::Transaction(request.transaction_hash()), - Rc::clone(&tc), - Phase::Session, - ) { - Ok(rt) => rt, - Err(tce) => { - return BiddingResult::Failure(tce); - } - }; - - let auction_method = request.auction_method(); - - let result = match auction_method { - AuctionMethod::ActivateBid { - validator_public_key, - } => runtime - .activate_bid(validator_public_key) - .map(|_| AuctionMethodRet::Unit) - .map_err(|auc_err| { - TrackingCopyError::SystemContract(system::Error::Auction(auc_err)) - }), - AuctionMethod::AddBid { - public_key, - delegation_rate, - amount, - holds_epoch, - } => runtime - .add_bid(public_key, delegation_rate, amount, holds_epoch) - .map(AuctionMethodRet::UpdatedAmount) - .map_err(TrackingCopyError::Api), - AuctionMethod::WithdrawBid { public_key, amount } => runtime - .withdraw_bid(public_key, amount) - .map(AuctionMethodRet::UpdatedAmount) - .map_err(|auc_err| { - TrackingCopyError::SystemContract(system::Error::Auction(auc_err)) - }), - AuctionMethod::Delegate { - delegator_public_key, - validator_public_key, - amount, - max_delegators_per_validator, - minimum_delegation_amount, - holds_epoch, - } => runtime - .delegate( - delegator_public_key, - validator_public_key, - amount, - max_delegators_per_validator, - minimum_delegation_amount, - holds_epoch, - ) - .map(AuctionMethodRet::UpdatedAmount) - .map_err(TrackingCopyError::Api), - AuctionMethod::Undelegate { - delegator_public_key, - validator_public_key, - amount, - } => runtime - .undelegate(delegator_public_key, validator_public_key, amount) - .map(AuctionMethodRet::UpdatedAmount) - .map_err(|auc_err| { - TrackingCopyError::SystemContract(system::Error::Auction(auc_err)) - }), - AuctionMethod::Redelegate { - delegator_public_key, - validator_public_key, - amount, - new_validator, - minimum_delegation_amount, - } => runtime - .redelegate( - delegator_public_key, - validator_public_key, - amount, - new_validator, - minimum_delegation_amount, - ) - .map(AuctionMethodRet::UpdatedAmount) - .map_err(|auc_err| { - TrackingCopyError::SystemContract(system::Error::Auction(auc_err)) - }), - }; - - let effects = tc.borrow_mut().effects(); - - // commit - match result { - Ok(ret) => match self.commit(state_hash, effects.clone()) { - Ok(post_state_hash) => BiddingResult::Success { - ret, - post_state_hash, - effects, - }, - Err(tce) => BiddingResult::Failure(tce.into()), - }, - Err(tce) => BiddingResult::Failure(tce), - } - } } /// A trait expressing operations over the trie. @@ -1010,6 +895,113 @@ pub trait StateProvider { BidsResult::Success { bids } } + /// Direct auction interaction for all variations of bid management. + fn bidding(&self, request: BiddingRequest) -> BiddingResult { + let state_hash = request.state_hash(); + let tc = match self.tracking_copy(state_hash) { + Ok(Some(tc)) => Rc::new(RefCell::new(tc)), + Ok(None) => return BiddingResult::RootNotFound, + Err(err) => return BiddingResult::Failure(TrackingCopyError::Storage(err)), + }; + + let protocol_version = request.protocol_version(); + let config = request.config(); + + let mut runtime = match RuntimeNative::new_system_runtime( + config.clone(), + protocol_version, + Id::Transaction(request.transaction_hash()), + Rc::clone(&tc), + Phase::Session, + ) { + Ok(rt) => rt, + Err(tce) => { + return BiddingResult::Failure(tce); + } + }; + + let auction_method = request.auction_method(); + + let result = match auction_method { + AuctionMethod::ActivateBid { + validator_public_key, + } => runtime + .activate_bid(validator_public_key) + .map(|_| AuctionMethodRet::Unit) + .map_err(|auc_err| { + TrackingCopyError::SystemContract(system::Error::Auction(auc_err)) + }), + AuctionMethod::AddBid { + public_key, + delegation_rate, + amount, + holds_epoch, + } => runtime + .add_bid(public_key, delegation_rate, amount, holds_epoch) + .map(AuctionMethodRet::UpdatedAmount) + .map_err(TrackingCopyError::Api), + AuctionMethod::WithdrawBid { public_key, amount } => runtime + .withdraw_bid(public_key, amount) + .map(AuctionMethodRet::UpdatedAmount) + .map_err(|auc_err| { + TrackingCopyError::SystemContract(system::Error::Auction(auc_err)) + }), + AuctionMethod::Delegate { + delegator_public_key, + validator_public_key, + amount, + max_delegators_per_validator, + minimum_delegation_amount, + holds_epoch, + } => runtime + .delegate( + delegator_public_key, + validator_public_key, + amount, + max_delegators_per_validator, + minimum_delegation_amount, + holds_epoch, + ) + .map(AuctionMethodRet::UpdatedAmount) + .map_err(TrackingCopyError::Api), + AuctionMethod::Undelegate { + delegator_public_key, + validator_public_key, + amount, + } => runtime + .undelegate(delegator_public_key, validator_public_key, amount) + .map(AuctionMethodRet::UpdatedAmount) + .map_err(|auc_err| { + TrackingCopyError::SystemContract(system::Error::Auction(auc_err)) + }), + AuctionMethod::Redelegate { + delegator_public_key, + validator_public_key, + amount, + new_validator, + minimum_delegation_amount, + } => runtime + .redelegate( + delegator_public_key, + validator_public_key, + amount, + new_validator, + minimum_delegation_amount, + ) + .map(AuctionMethodRet::UpdatedAmount) + .map_err(|auc_err| { + TrackingCopyError::SystemContract(system::Error::Auction(auc_err)) + }), + }; + + let effects = tc.borrow_mut().effects(); + + match result { + Ok(ret) => BiddingResult::Success { ret, effects }, + Err(tce) => BiddingResult::Failure(tce), + } + } + /// Gets the execution result checksum. fn execution_result_checksum( &self, diff --git a/types/src/transaction.rs b/types/src/transaction.rs index 40a7c200d6..76da0a449b 100644 --- a/types/src/transaction.rs +++ b/types/src/transaction.rs @@ -335,6 +335,21 @@ impl Transaction { } } + /// Is this a transaction that should be sent to the v1 execution engine? + pub fn is_v1_wasm(&self) -> bool { + match self { + Transaction::Deploy(deploy) => !deploy.is_transfer(), + Transaction::V1(v1) => match v1.target() { + TransactionTarget::Native => false, + TransactionTarget::Stored { runtime, .. } + | TransactionTarget::Session { runtime, .. } => { + matches!(runtime, TransactionRuntime::VmCasperV1) + && (v1.is_standard() || v1.is_install_or_upgrade()) + } + }, + } + } + /// Authorization keys. pub fn authorization_keys(&self) -> BTreeSet { match self {