casper_node/components/contract_runtime/
operations.rs

1pub(crate) mod wasm_v2_request;
2
3use casper_executor_wasm::ExecutorV2;
4use itertools::Itertools;
5use std::{collections::BTreeMap, convert::TryInto, sync::Arc, time::Instant};
6use tracing::{debug, error, info, trace, warn};
7use wasm_v2_request::{WasmV2Request, WasmV2Result};
8
9use casper_execution_engine::engine_state::{
10    BlockInfo, ExecutionEngineV1, WasmV1Request, WasmV1Result,
11};
12use casper_storage::{
13    block_store::types::ApprovalsHashes,
14    data_access_layer::{
15        balance::BalanceHandling,
16        mint::{BalanceIdentifierTransferArgs, BurnRequest},
17        AuctionMethod, BalanceHoldKind, BalanceHoldRequest, BalanceIdentifier,
18        BalanceIdentifierPurseRequest, BalanceIdentifierPurseResult, BalanceRequest,
19        BiddingRequest, BlockGlobalRequest, BlockGlobalResult, BlockRewardsRequest,
20        BlockRewardsResult, DataAccessLayer, EntryPointRequest, EntryPointResult,
21        EraValidatorsRequest, EraValidatorsResult, EvictItem, FeeRequest, FeeResult, FlushRequest,
22        HandleFeeMode, HandleFeeRequest, HandleRefundMode, HandleRefundRequest,
23        InsufficientBalanceHandling, ProofHandling, PruneRequest, PruneResult, StepRequest,
24        StepResult, TransferRequest,
25    },
26    global_state::state::{
27        lmdb::LmdbGlobalState, scratch::ScratchGlobalState, CommitProvider, ScratchProvider,
28        StateProvider, StateReader,
29    },
30    system::runtime_native::Config as NativeRuntimeConfig,
31};
32use casper_types::{
33    bytesrepr::{self, ToBytes, U32_SERIALIZED_LENGTH},
34    execution::{Effects, ExecutionResult, TransformKindV2, TransformV2},
35    system::handle_payment::ARG_AMOUNT,
36    BlockHash, BlockHeader, BlockTime, BlockV2, CLValue, Chainspec, ChecksumRegistry, Digest,
37    EntityAddr, EraEndV2, EraId, FeeHandling, Gas, InvalidTransaction, InvalidTransactionV1, Key,
38    ProtocolVersion, PublicKey, RefundHandling, Transaction, TransactionEntryPoint,
39    AUCTION_LANE_ID, MINT_LANE_ID, U512,
40};
41
42use super::{
43    types::{SpeculativeExecutionResult, StepOutcome},
44    utils::{self, calculate_prune_eras},
45    BlockAndExecutionArtifacts, BlockExecutionError, ExecutionPreState, Metrics, StateResultError,
46    APPROVALS_CHECKSUM_NAME, EXECUTION_RESULTS_CHECKSUM_NAME,
47};
48use crate::{
49    components::fetcher::FetchItem,
50    contract_runtime::types::ExecutionArtifactBuilder,
51    types::{self, Chunkable, ExecutableBlock, InternalEraReport, MetaTransaction},
52};
53
54/// Executes a finalized block.
55#[allow(clippy::too_many_arguments)]
56pub fn execute_finalized_block(
57    data_access_layer: &DataAccessLayer<LmdbGlobalState>,
58    execution_engine_v1: &ExecutionEngineV1,
59    execution_engine_v2: ExecutorV2,
60    chainspec: &Chainspec,
61    metrics: Option<Arc<Metrics>>,
62    execution_pre_state: ExecutionPreState,
63    executable_block: ExecutableBlock,
64    key_block_height_for_activation_point: u64,
65    current_gas_price: u8,
66    next_era_gas_price: Option<u8>,
67    last_switch_block_hash: Option<BlockHash>,
68) -> Result<BlockAndExecutionArtifacts, BlockExecutionError> {
69    let block_height = executable_block.height;
70    if block_height != execution_pre_state.next_block_height() {
71        return Err(BlockExecutionError::WrongBlockHeight {
72            executable_block: Box::new(executable_block),
73            execution_pre_state: Box::new(execution_pre_state),
74        });
75    }
76    if executable_block.era_report.is_some() && next_era_gas_price.is_none() {
77        return Err(BlockExecutionError::FailedToGetNewEraGasPrice {
78            era_id: executable_block.era_id.successor(),
79        });
80    }
81    let start = Instant::now();
82    let protocol_version = chainspec.protocol_version();
83    let activation_point_era_id = chainspec.protocol_config.activation_point.era_id();
84    let prune_batch_size = chainspec.core_config.prune_batch_size;
85    let native_runtime_config = NativeRuntimeConfig::from_chainspec(chainspec);
86    let addressable_entity_enabled = chainspec.core_config.enable_addressable_entity();
87
88    if addressable_entity_enabled != data_access_layer.enable_addressable_entity {
89        return Err(BlockExecutionError::InvalidAESetting(
90            data_access_layer.enable_addressable_entity,
91        ));
92    }
93
94    // scrape variables from execution pre state
95    let parent_hash = execution_pre_state.parent_hash();
96    let parent_seed = execution_pre_state.parent_seed();
97    let parent_block_hash = execution_pre_state.parent_hash();
98    let pre_state_root_hash = execution_pre_state.pre_state_root_hash();
99    let mut state_root_hash = pre_state_root_hash; // initial state root is parent's state root
100
101    let payment_balance_addr =
102        match data_access_layer.balance_purse(BalanceIdentifierPurseRequest::new(
103            state_root_hash,
104            protocol_version,
105            BalanceIdentifier::Payment,
106        )) {
107            BalanceIdentifierPurseResult::RootNotFound => {
108                return Err(BlockExecutionError::RootNotFound(state_root_hash))
109            }
110            BalanceIdentifierPurseResult::Failure(tce) => {
111                return Err(BlockExecutionError::BlockGlobal(format!("{:?}", tce)));
112            }
113            BalanceIdentifierPurseResult::Success { purse_addr } => purse_addr,
114        };
115
116    // scrape variables from executable block
117    let block_time = BlockTime::new(executable_block.timestamp.millis());
118
119    let proposer = executable_block.proposer.clone();
120    let era_id = executable_block.era_id;
121    let mut artifacts = Vec::with_capacity(executable_block.transactions.len());
122
123    // set up accounting variables / settings
124    let insufficient_balance_handling = InsufficientBalanceHandling::HoldRemaining;
125    let refund_handling = chainspec.core_config.refund_handling;
126    let fee_handling = chainspec.core_config.fee_handling;
127    let baseline_motes_amount = chainspec.core_config.baseline_motes_amount_u512();
128    let balance_handling = BalanceHandling::Available;
129
130    // get scratch state, which must be used for all processing and post-processing data
131    // requirements.
132    let scratch_state = data_access_layer.get_scratch_global_state();
133
134    // pre-processing is finished
135    if let Some(metrics) = metrics.as_ref() {
136        metrics
137            .exec_block_pre_processing
138            .observe(start.elapsed().as_secs_f64());
139    }
140
141    // grabbing transaction id's now to avoid cloning transactions
142    let transaction_ids = executable_block
143        .transactions
144        .iter()
145        .map(Transaction::fetch_id)
146        .collect_vec();
147
148    // transaction processing starts now
149    let txn_processing_start = Instant::now();
150
151    // put block_time to global state
152    // NOTE this must occur prior to any block processing as subsequent logic
153    // will refer to the block time value being written to GS now.
154    match scratch_state.block_global(BlockGlobalRequest::block_time(
155        state_root_hash,
156        protocol_version,
157        block_time,
158    )) {
159        BlockGlobalResult::RootNotFound => {
160            return Err(BlockExecutionError::RootNotFound(state_root_hash));
161        }
162        BlockGlobalResult::Failure(err) => {
163            return Err(BlockExecutionError::BlockGlobal(format!("{:?}", err)));
164        }
165        BlockGlobalResult::Success {
166            post_state_hash, ..
167        } => {
168            state_root_hash = post_state_hash;
169        }
170    }
171
172    // put protocol version to global state
173    match scratch_state.block_global(BlockGlobalRequest::set_protocol_version(
174        state_root_hash,
175        protocol_version,
176    )) {
177        BlockGlobalResult::RootNotFound => {
178            return Err(BlockExecutionError::RootNotFound(state_root_hash));
179        }
180        BlockGlobalResult::Failure(err) => {
181            return Err(BlockExecutionError::BlockGlobal(format!("{:?}", err)));
182        }
183        BlockGlobalResult::Success {
184            post_state_hash, ..
185        } => {
186            state_root_hash = post_state_hash;
187        }
188    }
189
190    // put enable addressable entity flag to global state
191    match scratch_state.block_global(BlockGlobalRequest::set_addressable_entity(
192        state_root_hash,
193        protocol_version,
194        addressable_entity_enabled,
195    )) {
196        BlockGlobalResult::RootNotFound => {
197            return Err(BlockExecutionError::RootNotFound(state_root_hash));
198        }
199        BlockGlobalResult::Failure(err) => {
200            return Err(BlockExecutionError::BlockGlobal(format!("{:?}", err)));
201        }
202        BlockGlobalResult::Success {
203            post_state_hash, ..
204        } => {
205            state_root_hash = post_state_hash;
206        }
207    }
208
209    let transaction_config = &chainspec.transaction_config;
210
211    for stored_transaction in executable_block.transactions {
212        let mut artifact_builder = ExecutionArtifactBuilder::new(
213            &stored_transaction,
214            baseline_motes_amount, // <-- default minimum cost, may be overridden later in logic
215            current_gas_price,
216        );
217        let transaction = MetaTransaction::from_transaction(
218            &stored_transaction,
219            chainspec.core_config.pricing_handling,
220            transaction_config,
221        )
222        .map_err(|err| BlockExecutionError::TransactionConversion(err.to_string()))?;
223        let initiator_addr = transaction.initiator_addr();
224        let transaction_hash = transaction.hash();
225        let transaction_args = transaction.session_args().clone();
226        let entry_point = transaction.entry_point();
227        let authorization_keys = transaction.signers();
228
229        /*
230        we solve for halting state using a `gas limit` which is the maximum amount of
231        computation we will allow a given transaction to consume. the transaction itself
232        provides a function to determine this if provided with the current cost tables
233        gas_limit is ALWAYS calculated with price == 1.
234
235        next there is the actual cost, i.e. how much we charge for that computation
236        this is calculated by multiplying the gas limit by the current `gas_price`
237        gas price has a floor of 1, and the ceiling is configured in the chainspec
238        NOTE: when the gas price is 1, the gas limit and the cost are coincidentally
239        equal because x == x * 1; thus it is recommended to run tests with
240        price >1 to avoid being confused by this.
241
242        the third important value is the amount of computation consumed by executing a
243        transaction  for native transactions there is no wasm and the consumed always
244        equals the limit  for bytecode / wasm based transactions the consumed is based on
245        what opcodes were executed and can range from >=0 to <=gas_limit.
246        consumed is determined after execution and is used for refund & fee post-processing.
247
248        we check these top level concerns early so that we can skip if there is an error
249        */
250
251        // NOTE: this is the allowed computation limit (gas limit)
252        let gas_limit =
253            match stored_transaction.gas_limit(chainspec, transaction.transaction_lane()) {
254                Ok(gas) => gas,
255                Err(ite) => {
256                    debug!(%transaction_hash, %ite, "invalid transaction (gas limit)");
257                    artifact_builder.with_invalid_transaction(&ite);
258                    artifacts.push(artifact_builder.build());
259                    continue;
260                }
261            };
262        artifact_builder.with_gas_limit(gas_limit);
263
264        // NOTE: this is the actual adjusted cost that we charge for (gas limit * gas price)
265        let cost = match stored_transaction.gas_cost(
266            chainspec,
267            transaction.transaction_lane(),
268            current_gas_price,
269        ) {
270            Ok(motes) => motes.value(),
271            Err(ite) => {
272                debug!(%transaction_hash, "invalid transaction (motes conversion)");
273                artifact_builder.with_invalid_transaction(&ite);
274                artifacts.push(artifact_builder.build());
275                continue;
276            }
277        };
278        artifact_builder.with_added_cost(cost);
279
280        let is_standard_payment = transaction.is_standard_payment();
281        let is_custom_payment = !is_standard_payment && transaction.is_custom_payment();
282        let is_v1_wasm = transaction.is_v1_wasm();
283        let is_v2_wasm = transaction.is_v2_wasm();
284        let refund_purse_active = is_custom_payment;
285        if refund_purse_active {
286            // if custom payment before doing any processing, initialize the initiator's main purse
287            //  to be the refund purse for this transaction.
288            // NOTE: when executed, custom payment logic has the option to call set_refund_purse
289            //  on the handle payment contract to set up a different refund purse, if desired.
290            let handle_refund_request = HandleRefundRequest::new(
291                native_runtime_config.clone(),
292                state_root_hash,
293                protocol_version,
294                transaction_hash,
295                HandleRefundMode::SetRefundPurse {
296                    target: Box::new(initiator_addr.clone().into()),
297                },
298            );
299            let handle_refund_result = scratch_state.handle_refund(handle_refund_request);
300            if let Err(root_not_found) =
301                artifact_builder.with_set_refund_purse_result(&handle_refund_result)
302            {
303                if root_not_found {
304                    return Err(BlockExecutionError::RootNotFound(state_root_hash));
305                }
306                artifacts.push(artifact_builder.build());
307                continue; // don't commit effects, move on
308            }
309            state_root_hash = scratch_state
310                .commit_effects(state_root_hash, handle_refund_result.effects().clone())?;
311        }
312
313        {
314            // Ensure the initiator's main purse can cover the penalty payment before proceeding.
315            let initial_balance_result = scratch_state.balance(BalanceRequest::new(
316                state_root_hash,
317                protocol_version,
318                initiator_addr.clone().into(),
319                balance_handling,
320                ProofHandling::NoProofs,
321            ));
322
323            if let Err(root_not_found) = artifact_builder
324                .with_initial_balance_result(initial_balance_result.clone(), baseline_motes_amount)
325            {
326                if root_not_found {
327                    return Err(BlockExecutionError::RootNotFound(state_root_hash));
328                }
329                trace!(%transaction_hash, "insufficient initial balance");
330                debug!(%transaction_hash, ?initial_balance_result, %baseline_motes_amount, "insufficient initial balance");
331                artifacts.push(artifact_builder.build());
332                // only reads have happened so far, and we can't charge due
333                // to insufficient balance, so move on with no effects committed
334                continue;
335            }
336        }
337
338        let mut balance_identifier = {
339            if is_standard_payment {
340                let contract_might_pay =
341                    addressable_entity_enabled && transaction.is_contract_by_hash_invocation();
342
343                if contract_might_pay {
344                    match invoked_contract_will_pay(&scratch_state, state_root_hash, &transaction) {
345                        Ok(Some(entity_addr)) => BalanceIdentifier::Entity(entity_addr),
346                        Ok(None) => {
347                            // the initiating account pays using its main purse
348                            trace!(%transaction_hash, "direct invocation with account payment");
349                            initiator_addr.clone().into()
350                        }
351                        Err(err) => {
352                            trace!(%transaction_hash, "failed to resolve contract self payment");
353                            artifact_builder
354                                .with_state_result_error(err)
355                                .map_err(|_| BlockExecutionError::RootNotFound(state_root_hash))?;
356                            BalanceIdentifier::PenalizedAccount(
357                                initiator_addr.clone().account_hash(),
358                            )
359                        }
360                    }
361                } else {
362                    // the initiating account pays using its main purse
363                    trace!(%transaction_hash, "account session with standard payment");
364                    initiator_addr.clone().into()
365                }
366            } else if is_v2_wasm {
367                // vm2 does not support custom payment, so it MUST be standard payment
368                // if transaction runtime is v2 then the initiating account will pay using
369                // the refund purse
370                initiator_addr.clone().into()
371            } else if is_custom_payment {
372                // this is the custom payment flow
373                // the initiating account will pay, but wants to do so with a different purse or
374                // in a custom way. If anything goes wrong, penalize the sender, do not execute
375                let custom_payment_gas_limit =
376                    Gas::new(chainspec.transaction_config.native_transfer_minimum_motes * 5);
377                let pay_result = match WasmV1Request::new_custom_payment(
378                    BlockInfo::new(
379                        state_root_hash,
380                        block_time,
381                        parent_block_hash,
382                        block_height,
383                        protocol_version,
384                    ),
385                    custom_payment_gas_limit,
386                    &transaction.to_payment_input_data(),
387                ) {
388                    Ok(mut pay_request) => {
389                        pay_request
390                            .args
391                            .insert(ARG_AMOUNT, cost)
392                            .map_err(|e| BlockExecutionError::PaymentError(e.to_string()))?;
393                        execution_engine_v1.execute(&scratch_state, pay_request)
394                    }
395                    Err(error) => {
396                        WasmV1Result::invalid_executable_item(custom_payment_gas_limit, error)
397                    }
398                };
399
400                let insufficient_payment_deposited =
401                    !pay_result.balance_increased_by_amount(payment_balance_addr, cost);
402
403                if insufficient_payment_deposited || pay_result.error().is_some() {
404                    // Charge initiator for the penalty payment amount
405                    // the most expedient way to do this that aligns with later code
406                    // is to transfer from the initiator's main purse to the payment purse
407                    let transfer_result = scratch_state.transfer(TransferRequest::new_indirect(
408                        native_runtime_config.clone(),
409                        state_root_hash,
410                        protocol_version,
411                        transaction_hash,
412                        initiator_addr.clone(),
413                        authorization_keys.clone(),
414                        BalanceIdentifierTransferArgs::new(
415                            None,
416                            initiator_addr.clone().into(),
417                            BalanceIdentifier::Payment,
418                            baseline_motes_amount,
419                            None,
420                        ),
421                    ));
422
423                    let msg = match pay_result.error() {
424                        Some(err) => format!("{}", err),
425                        None => {
426                            if insufficient_payment_deposited {
427                                "Insufficient custom payment".to_string()
428                            } else {
429                                // this should be unreachable due to guard condition above
430                                let unk = "Unknown custom payment issue";
431                                warn!(%transaction_hash, unk);
432                                debug_assert!(false, "{}", unk);
433                                unk.to_string()
434                            }
435                        }
436                    };
437                    // commit penalty payment effects
438                    state_root_hash = scratch_state
439                        .commit_effects(state_root_hash, transfer_result.effects().clone())?;
440                    artifact_builder
441                        .with_error_message(msg)
442                        .with_transfer_result(transfer_result)
443                        .map_err(|_| BlockExecutionError::RootNotFound(state_root_hash))?;
444                    trace!(%transaction_hash, balance_identifier=?BalanceIdentifier::PenalizedPayment, "account session with custom payment failed");
445                    BalanceIdentifier::PenalizedPayment
446                } else {
447                    // commit successful effects
448                    state_root_hash = scratch_state
449                        .commit_effects(state_root_hash, pay_result.effects().clone())?;
450                    artifact_builder
451                        .with_wasm_v1_result(pay_result)
452                        .map_err(|_| BlockExecutionError::RootNotFound(state_root_hash))?;
453                    trace!(%transaction_hash, balance_identifier=?BalanceIdentifier::Payment, "account session with custom payment success");
454                    BalanceIdentifier::Payment
455                }
456            } else {
457                BalanceIdentifier::PenalizedAccount(initiator_addr.clone().account_hash())
458            }
459        };
460
461        let post_payment_balance_result = scratch_state.balance(BalanceRequest::new(
462            state_root_hash,
463            protocol_version,
464            balance_identifier.clone(),
465            balance_handling,
466            ProofHandling::NoProofs,
467        ));
468
469        let lane_id = transaction.transaction_lane();
470
471        let allow_execution = {
472            let is_not_penalized = !balance_identifier.is_penalty();
473            // in the case of custom payment, we do all payment processing up front after checking
474            // if the initiator can cover the penalty payment, and then either charge the full
475            // amount in the happy path or the penalty amount in the sad path...in whichever case
476            // the sad path is handled by is_penalty and the balance in the payment purse is
477            // the penalty payment or the full amount but is 'sufficient' either way
478            let is_sufficient_balance =
479                is_custom_payment || post_payment_balance_result.is_sufficient(cost);
480            let is_allowed_by_chainspec = chainspec.is_supported(lane_id);
481            let allow = is_not_penalized && is_sufficient_balance && is_allowed_by_chainspec;
482            if !allow {
483                if artifact_builder.error_message().is_none() {
484                    artifact_builder.with_error_message(format!(
485                        "penalized: {}, sufficient balance: {}, allowed by chainspec: {}",
486                        !is_not_penalized, is_sufficient_balance, is_allowed_by_chainspec
487                    ));
488                }
489                info!(%transaction_hash, ?balance_identifier, ?is_sufficient_balance, ?is_not_penalized, ?is_allowed_by_chainspec, "payment preprocessing unsuccessful");
490            } else {
491                debug!(%transaction_hash, ?balance_identifier, ?is_sufficient_balance, ?is_not_penalized, ?is_allowed_by_chainspec, "payment preprocessing successful");
492            }
493            allow
494        };
495
496        if allow_execution {
497            debug!(%transaction_hash, ?allow_execution, "execution allowed");
498            if is_standard_payment {
499                // place a processing hold on the paying account to prevent double spend.
500                let hold_amount = cost;
501                let hold_request = BalanceHoldRequest::new_processing_hold(
502                    state_root_hash,
503                    protocol_version,
504                    balance_identifier.clone(),
505                    hold_amount,
506                    insufficient_balance_handling,
507                );
508                let hold_result = scratch_state.balance_hold(hold_request);
509                state_root_hash =
510                    scratch_state.commit_effects(state_root_hash, hold_result.effects().clone())?;
511                artifact_builder
512                    .with_balance_hold_result(&hold_result)
513                    .map_err(|_| BlockExecutionError::RootNotFound(state_root_hash))?;
514            }
515
516            trace!(%transaction_hash, ?lane_id, "eligible for execution");
517            match lane_id {
518                lane_id if lane_id == MINT_LANE_ID => {
519                    let runtime_args = transaction_args
520                        .as_named()
521                        .ok_or(BlockExecutionError::InvalidTransactionArgs)?;
522                    let entry_point = transaction.entry_point();
523                    if let TransactionEntryPoint::Transfer = entry_point {
524                        let transfer_result =
525                            scratch_state.transfer(TransferRequest::with_runtime_args(
526                                native_runtime_config.clone(),
527                                state_root_hash,
528                                protocol_version,
529                                transaction_hash,
530                                initiator_addr.clone(),
531                                authorization_keys,
532                                runtime_args.clone(),
533                            ));
534                        state_root_hash = scratch_state
535                            .commit_effects(state_root_hash, transfer_result.effects().clone())?;
536                        artifact_builder
537                            .with_min_cost(gas_limit.value())
538                            .with_added_consumed(gas_limit)
539                            .with_transfer_result(transfer_result)
540                            .map_err(|_| BlockExecutionError::RootNotFound(state_root_hash))?;
541                    } else if let TransactionEntryPoint::Burn = entry_point {
542                        let burn_result = scratch_state.burn(BurnRequest::with_runtime_args(
543                            native_runtime_config.clone(),
544                            state_root_hash,
545                            protocol_version,
546                            transaction_hash,
547                            initiator_addr.clone(),
548                            authorization_keys,
549                            runtime_args.clone(),
550                        ));
551                        state_root_hash = scratch_state
552                            .commit_effects(state_root_hash, burn_result.effects().clone())?;
553                        artifact_builder
554                            .with_min_cost(gas_limit.value())
555                            .with_added_consumed(gas_limit)
556                            .with_burn_result(burn_result)
557                            .map_err(|_| BlockExecutionError::RootNotFound(state_root_hash))?;
558                    } else {
559                        artifact_builder.with_error_message(format!(
560                            "Attempt to call unsupported native mint entrypoint: {}",
561                            entry_point
562                        ));
563                    }
564                }
565                lane_id if lane_id == AUCTION_LANE_ID => {
566                    let runtime_args = transaction_args
567                        .as_named()
568                        .ok_or(BlockExecutionError::InvalidTransactionArgs)?;
569                    match AuctionMethod::from_parts(entry_point, runtime_args, chainspec) {
570                        Ok(auction_method) => {
571                            let bidding_result = scratch_state.bidding(BiddingRequest::new(
572                                native_runtime_config.clone(),
573                                state_root_hash,
574                                protocol_version,
575                                transaction_hash,
576                                initiator_addr.clone(),
577                                authorization_keys,
578                                auction_method,
579                            ));
580                            state_root_hash = scratch_state.commit_effects(
581                                state_root_hash,
582                                bidding_result.effects().clone(),
583                            )?;
584                            artifact_builder
585                                .with_min_cost(gas_limit.value())
586                                .with_added_consumed(gas_limit)
587                                .with_bidding_result(bidding_result)
588                                .map_err(|_| BlockExecutionError::RootNotFound(state_root_hash))?;
589                        }
590                        Err(ame) => {
591                            error!(
592                                %transaction_hash,
593                                ?ame,
594                                "failed to determine auction method"
595                            );
596                            artifact_builder.with_auction_method_error(&ame);
597                        }
598                    };
599                }
600                _ if is_v1_wasm => {
601                    let wasm_v1_start = Instant::now();
602                    let session_input_data = transaction.to_session_input_data();
603                    match WasmV1Request::new_session(
604                        BlockInfo::new(
605                            state_root_hash,
606                            block_time,
607                            parent_block_hash,
608                            block_height,
609                            protocol_version,
610                        ),
611                        gas_limit,
612                        &session_input_data,
613                    ) {
614                        Ok(wasm_v1_request) => {
615                            trace!(%transaction_hash, ?lane_id, ?wasm_v1_request, "able to get wasm v1 request");
616                            let wasm_v1_result =
617                                execution_engine_v1.execute(&scratch_state, wasm_v1_request);
618                            trace!(%transaction_hash, ?lane_id, ?wasm_v1_result, "able to get wasm v1 result");
619                            state_root_hash = scratch_state.commit_effects(
620                                state_root_hash,
621                                wasm_v1_result.effects().clone(),
622                            )?;
623                            // note: consumed is scraped from wasm_v1_result along w/ other fields
624                            artifact_builder
625                                .with_wasm_v1_result(wasm_v1_result)
626                                .map_err(|_| BlockExecutionError::RootNotFound(state_root_hash))?;
627                        }
628                        Err(ire) => {
629                            debug!(%transaction_hash, ?lane_id, ?ire, "unable to get wasm v1 request");
630                            artifact_builder.with_invalid_wasm_v1_request(&ire);
631                        }
632                    };
633                    if let Some(metrics) = metrics.as_ref() {
634                        metrics
635                            .exec_wasm_v1
636                            .observe(wasm_v1_start.elapsed().as_secs_f64());
637                    }
638                }
639                _ if is_v2_wasm => match WasmV2Request::new(
640                    gas_limit,
641                    chainspec.network_config.name.clone(),
642                    state_root_hash,
643                    parent_block_hash,
644                    block_height,
645                    &transaction,
646                ) {
647                    Ok(wasm_v2_request) => {
648                        match wasm_v2_request.execute(
649                            &execution_engine_v2,
650                            state_root_hash,
651                            &scratch_state,
652                        ) {
653                            Ok(wasm_v2_result) => {
654                                match &wasm_v2_result {
655                                    WasmV2Result::Install(install_result) => {
656                                        info!(
657                                            contract_hash=base16::encode_lower(&install_result.smart_contract_addr()),
658                                            pre_state_root_hash=%state_root_hash,
659                                            post_state_root_hash=%install_result.post_state_hash(),
660                                            "install contract result");
661                                    }
662
663                                    WasmV2Result::Execute(execute_result) => {
664                                        info!(
665                                            pre_state_root_hash=%state_root_hash,
666                                            post_state_root_hash=%execute_result.post_state_hash(),
667                                            host_error=?execute_result.host_error.as_ref(),
668                                            "execute contract result");
669                                    }
670                                }
671
672                                state_root_hash = wasm_v2_result.post_state_hash();
673                                artifact_builder.with_wasm_v2_result(wasm_v2_result);
674                            }
675                            Err(wasm_v2_error) => {
676                                artifact_builder.with_wasm_v2_error(wasm_v2_error);
677                            }
678                        }
679                    }
680                    Err(ire) => {
681                        debug!(%transaction_hash, ?lane_id, ?ire, "unable to get wasm v2 request");
682                        artifact_builder.with_invalid_wasm_v2_request(ire);
683                    }
684                },
685                _ => {
686                    // it is currently not possible to specify a vm other than v1 or v2 on the
687                    // transaction itself, so this should be unreachable
688                    unreachable!("Unknown VM target")
689                }
690            }
691        }
692
693        // clear all holds on the balance_identifier purse before payment processing
694        {
695            let hold_request = BalanceHoldRequest::new_clear(
696                state_root_hash,
697                protocol_version,
698                BalanceHoldKind::All,
699                balance_identifier.clone(),
700            );
701            let hold_result = scratch_state.balance_hold(hold_request);
702            state_root_hash =
703                scratch_state.commit_effects(state_root_hash, hold_result.effects().clone())?;
704            artifact_builder
705                .with_balance_hold_result(&hold_result)
706                .map_err(|_| BlockExecutionError::RootNotFound(state_root_hash))?;
707        }
708
709        // handle refunds per the chainspec determined setting.
710        let refund_amount = {
711            let consumed =
712                if balance_identifier.is_penalty() || artifact_builder.error_message().is_some() {
713                    artifact_builder.cost_to_use() // no refund for penalty
714                } else {
715                    artifact_builder.consumed()
716                };
717
718            let refund_mode = match refund_handling {
719                RefundHandling::NoRefund => {
720                    if fee_handling.is_no_fee() && is_custom_payment {
721                        // in no fee mode, we need to return the motes to the refund purse,
722                        //  and then point the balance_identifier to the refund purse
723                        // this will result in the downstream no fee handling logic
724                        //  placing a hold on the correct purse.
725                        balance_identifier = BalanceIdentifier::Refund;
726                        Some(HandleRefundMode::RefundNoFeeCustomPayment {
727                            initiator_addr: Box::new(initiator_addr.clone()),
728                            limit: gas_limit.value(),
729                            gas_price: current_gas_price,
730                            cost,
731                        })
732                    } else {
733                        None
734                    }
735                }
736                RefundHandling::Burn { refund_ratio } => Some(HandleRefundMode::Burn {
737                    limit: gas_limit.value(),
738                    gas_price: current_gas_price,
739                    cost,
740                    consumed,
741                    source: Box::new(balance_identifier.clone()),
742                    ratio: refund_ratio,
743                }),
744                RefundHandling::Refund { refund_ratio } => {
745                    let source = Box::new(balance_identifier.clone());
746                    if is_custom_payment {
747                        // in custom payment we have to do all payment handling up front.
748                        // therefore, if refunds are turned on we have to transfer the refunded
749                        // amount back to the specified refund purse.
750
751                        // the refund purse for a given transaction is set to the initiator's main
752                        // purse by default, but the custom payment provided by the initiator can
753                        // set a different purse when executed. thus, the handle payment system
754                        // contract tracks a refund purse and is handled internally at processing
755                        // time. Outer logic should never assume or refer to a specific purse for
756                        // purposes of refund. instead, `BalanceIdentifier::Refund` is used by outer
757                        // logic, which is interpreted by inner logic to use the currently set
758                        // refund purse.
759                        let target = Box::new(BalanceIdentifier::Refund);
760                        Some(HandleRefundMode::Refund {
761                            initiator_addr: Box::new(initiator_addr.clone()),
762                            limit: gas_limit.value(),
763                            gas_price: current_gas_price,
764                            consumed,
765                            cost,
766                            ratio: refund_ratio,
767                            source,
768                            target,
769                        })
770                    } else {
771                        // in normal payment handling we put a temporary processing hold
772                        // on the paying purse rather than take the token up front.
773                        // thus, here we only want to determine the refund amount rather than
774                        // attempt to process a refund on something we haven't actually taken yet.
775                        // later in the flow when the processing hold is released and payment is
776                        // finalized we reduce the amount taken by the refunded amount. This avoids
777                        // the churn of taking the token up front via transfer (which writes
778                        // multiple permanent records) and then transfer some of it back (which
779                        // writes more permanent records).
780                        Some(HandleRefundMode::CalculateAmount {
781                            limit: gas_limit.value(),
782                            gas_price: current_gas_price,
783                            consumed,
784                            cost,
785                            ratio: refund_ratio,
786                            source,
787                        })
788                    }
789                }
790            };
791            match refund_mode {
792                Some(refund_mode) => {
793                    let handle_refund_request = HandleRefundRequest::new(
794                        native_runtime_config.clone(),
795                        state_root_hash,
796                        protocol_version,
797                        transaction_hash,
798                        refund_mode,
799                    );
800                    let handle_refund_result = scratch_state.handle_refund(handle_refund_request);
801                    let refunded_amount = handle_refund_result.refund_amount();
802                    state_root_hash = scratch_state
803                        .commit_effects(state_root_hash, handle_refund_result.effects().clone())?;
804                    artifact_builder
805                        .with_handle_refund_result(&handle_refund_result)
806                        .map_err(|_| BlockExecutionError::RootNotFound(state_root_hash))?;
807
808                    refunded_amount
809                }
810                None => U512::zero(),
811            }
812        };
813        artifact_builder.with_refund_amount(refund_amount);
814        // handle fees per the chainspec determined setting.
815        let handle_fee_result = match fee_handling {
816            FeeHandling::NoFee => {
817                // in this mode, a gas hold is placed on the payer's purse.
818                let amount = cost.saturating_sub(refund_amount);
819                let hold_request = BalanceHoldRequest::new_gas_hold(
820                    state_root_hash,
821                    protocol_version,
822                    balance_identifier,
823                    amount,
824                    insufficient_balance_handling,
825                );
826                let hold_result = scratch_state.balance_hold(hold_request);
827                state_root_hash =
828                    scratch_state.commit_effects(state_root_hash, hold_result.effects().clone())?;
829                artifact_builder
830                    .with_balance_hold_result(&hold_result)
831                    .map_err(|_| BlockExecutionError::RootNotFound(state_root_hash))?;
832                let handle_fee_request = HandleFeeRequest::new(
833                    native_runtime_config.clone(),
834                    state_root_hash,
835                    protocol_version,
836                    transaction_hash,
837                    HandleFeeMode::credit(proposer.clone(), amount, era_id),
838                );
839                scratch_state.handle_fee(handle_fee_request)
840            }
841            FeeHandling::Burn => {
842                // in this mode, the fee portion is burned.
843                let amount = cost.saturating_sub(refund_amount);
844                let handle_fee_request = HandleFeeRequest::new(
845                    native_runtime_config.clone(),
846                    state_root_hash,
847                    protocol_version,
848                    transaction_hash,
849                    HandleFeeMode::burn(balance_identifier, Some(amount)),
850                );
851                scratch_state.handle_fee(handle_fee_request)
852            }
853            FeeHandling::PayToProposer => {
854                // in this mode, the consumed gas is paid as a fee to the block proposer
855                let amount = cost.saturating_sub(refund_amount);
856                let handle_fee_request = HandleFeeRequest::new(
857                    native_runtime_config.clone(),
858                    state_root_hash,
859                    protocol_version,
860                    transaction_hash,
861                    HandleFeeMode::pay(
862                        Box::new(initiator_addr.clone()),
863                        balance_identifier,
864                        BalanceIdentifier::Public(*(proposer.clone())),
865                        amount,
866                    ),
867                );
868                scratch_state.handle_fee(handle_fee_request)
869            }
870            FeeHandling::Accumulate => {
871                // in this mode, consumed gas is accumulated into a single purse
872                // for later distribution
873                let amount = cost.saturating_sub(refund_amount);
874                let handle_fee_request = HandleFeeRequest::new(
875                    native_runtime_config.clone(),
876                    state_root_hash,
877                    protocol_version,
878                    transaction_hash,
879                    HandleFeeMode::pay(
880                        Box::new(initiator_addr.clone()),
881                        balance_identifier,
882                        BalanceIdentifier::Accumulate,
883                        amount,
884                    ),
885                );
886                scratch_state.handle_fee(handle_fee_request)
887            }
888        };
889
890        state_root_hash =
891            scratch_state.commit_effects(state_root_hash, handle_fee_result.effects().clone())?;
892
893        artifact_builder
894            .with_handle_fee_result(&handle_fee_result)
895            .map_err(|_| BlockExecutionError::RootNotFound(state_root_hash))?;
896
897        // clear refund purse if it was set
898        if refund_purse_active {
899            // if refunds are turned on we initialize the refund purse to the initiator's main
900            // purse before doing any processing. NOTE: when executed, custom payment logic
901            // has the option to call set_refund_purse on the handle payment contract to set
902            // up a different refund purse, if desired.
903            let handle_refund_request = HandleRefundRequest::new(
904                native_runtime_config.clone(),
905                state_root_hash,
906                protocol_version,
907                transaction_hash,
908                HandleRefundMode::ClearRefundPurse,
909            );
910            let handle_refund_result = scratch_state.handle_refund(handle_refund_request);
911            if let Err(root_not_found) =
912                artifact_builder.with_clear_refund_purse_result(&handle_refund_result)
913            {
914                if root_not_found {
915                    return Err(BlockExecutionError::RootNotFound(state_root_hash));
916                }
917                warn!(
918                    "{}",
919                    artifact_builder.error_message().unwrap_or(
920                        "unknown error encountered when attempting to clear refund purse"
921                            .to_string()
922                    )
923                );
924            }
925            state_root_hash = scratch_state
926                .commit_effects(state_root_hash, handle_refund_result.effects().clone())?;
927        }
928
929        artifacts.push(artifact_builder.build());
930    }
931
932    // transaction processing is finished
933    if let Some(metrics) = metrics.as_ref() {
934        metrics
935            .exec_block_tnx_processing
936            .observe(txn_processing_start.elapsed().as_secs_f64());
937    }
938
939    // post-processing starts now
940    let post_processing_start = Instant::now();
941
942    // calculate and store checksums for approvals and execution effects across the transactions in
943    // the block we do this so that the full set of approvals and the full set of effect metadata
944    // can be verified if necessary for a given block. the block synchronizer in particular
945    // depends on the existence of such checksums.
946    let transaction_approvals_hashes = {
947        let approvals_checksum = types::compute_approvals_checksum(transaction_ids.clone())
948            .map_err(BlockExecutionError::FailedToComputeApprovalsChecksum)?;
949        let execution_results_checksum = compute_execution_results_checksum(
950            artifacts.iter().map(|artifact| &artifact.execution_result),
951        )?;
952        let mut checksum_registry = ChecksumRegistry::new();
953        checksum_registry.insert(APPROVALS_CHECKSUM_NAME, approvals_checksum);
954        checksum_registry.insert(EXECUTION_RESULTS_CHECKSUM_NAME, execution_results_checksum);
955
956        let mut effects = Effects::new();
957        effects.push(TransformV2::new(
958            Key::ChecksumRegistry,
959            TransformKindV2::Write(
960                CLValue::from_t(checksum_registry)
961                    .map_err(BlockExecutionError::ChecksumRegistryToCLValue)?
962                    .into(),
963            ),
964        ));
965        scratch_state.commit_effects(state_root_hash, effects)?;
966        transaction_ids
967            .into_iter()
968            .map(|id| id.approvals_hash())
969            .collect()
970    };
971
972    if let Some(metrics) = metrics.as_ref() {
973        metrics
974            .txn_approvals_hashes_calculation
975            .observe(post_processing_start.elapsed().as_secs_f64());
976    }
977
978    // Pay out  ̶b̶l̶o̶c̶k̶ e͇r͇a͇ rewards
979    // NOTE: despite the name, these rewards are currently paid out per ERA not per BLOCK
980    // at one point, they were going to be paid out per block (and might be in the future)
981    // but it ended up settling on per era. the behavior is driven by Some / None
982    // thus if in future the calling logic passes rewards per block it should just work as is.
983    // This auto-commits.
984    if let Some(rewards) = &executable_block.rewards {
985        let block_rewards_payout_start = Instant::now();
986        // Pay out block fees, if relevant. This auto-commits
987        {
988            let fee_req = FeeRequest::new(
989                native_runtime_config.clone(),
990                state_root_hash,
991                protocol_version,
992                block_time,
993            );
994            debug!(?fee_req, "distributing fees");
995            match scratch_state.distribute_fees(fee_req) {
996                FeeResult::RootNotFound => {
997                    return Err(BlockExecutionError::RootNotFound(state_root_hash));
998                }
999                FeeResult::Failure(fer) => return Err(BlockExecutionError::DistributeFees(fer)),
1000                FeeResult::Success {
1001                    post_state_hash, ..
1002                } => {
1003                    debug!("fee distribution success");
1004                    state_root_hash = post_state_hash;
1005                }
1006            }
1007        }
1008
1009        let rewards_req = BlockRewardsRequest::new(
1010            native_runtime_config.clone(),
1011            state_root_hash,
1012            protocol_version,
1013            block_time,
1014            rewards.clone(),
1015        );
1016        debug!(?rewards_req, "distributing rewards");
1017        match scratch_state.distribute_block_rewards(rewards_req) {
1018            BlockRewardsResult::RootNotFound => {
1019                return Err(BlockExecutionError::RootNotFound(state_root_hash));
1020            }
1021            BlockRewardsResult::Failure(bre) => {
1022                return Err(BlockExecutionError::DistributeBlockRewards(bre));
1023            }
1024            BlockRewardsResult::Success {
1025                post_state_hash, ..
1026            } => {
1027                debug!("rewards distribution success");
1028                state_root_hash = post_state_hash;
1029            }
1030        }
1031        if let Some(metrics) = metrics.as_ref() {
1032            metrics
1033                .block_rewards_payout
1034                .observe(block_rewards_payout_start.elapsed().as_secs_f64());
1035        }
1036    }
1037
1038    // if era report is some, this is a switch block. a series of end-of-era extra processing must
1039    // transpire before this block is entirely finished.
1040    let step_outcome = if let Some(era_report) = &executable_block.era_report {
1041        // step processing starts now
1042        let step_processing_start = Instant::now();
1043
1044        debug!("committing step");
1045        let step_effects = match commit_step(
1046            native_runtime_config,
1047            &scratch_state,
1048            metrics.clone(),
1049            protocol_version,
1050            state_root_hash,
1051            era_report.clone(),
1052            block_time.value(),
1053            executable_block.era_id.successor(),
1054        ) {
1055            StepResult::RootNotFound => {
1056                return Err(BlockExecutionError::RootNotFound(state_root_hash));
1057            }
1058            StepResult::Failure(err) => return Err(BlockExecutionError::Step(err)),
1059            StepResult::Success {
1060                effects,
1061                post_state_hash,
1062                ..
1063            } => {
1064                state_root_hash = post_state_hash;
1065                effects
1066            }
1067        };
1068        debug!("step committed");
1069
1070        let era_validators_req = EraValidatorsRequest::new(state_root_hash);
1071        let era_validators_result = data_access_layer.era_validators(era_validators_req);
1072
1073        let upcoming_era_validators = match era_validators_result {
1074            EraValidatorsResult::RootNotFound => {
1075                panic!("root not found");
1076            }
1077            EraValidatorsResult::AuctionNotFound => {
1078                panic!("auction not found");
1079            }
1080            EraValidatorsResult::ValueNotFound(msg) => {
1081                panic!("validator snapshot not found: {}", msg);
1082            }
1083            EraValidatorsResult::Failure(tce) => {
1084                return Err(BlockExecutionError::GetEraValidators(tce));
1085            }
1086            EraValidatorsResult::Success { era_validators } => era_validators,
1087        };
1088
1089        // step processing is finished
1090        if let Some(metrics) = metrics.as_ref() {
1091            metrics
1092                .exec_block_step_processing
1093                .observe(step_processing_start.elapsed().as_secs_f64());
1094        }
1095        Some(StepOutcome {
1096            step_effects,
1097            upcoming_era_validators,
1098        })
1099    } else {
1100        None
1101    };
1102
1103    // Pruning -- this is orthogonal to the contents of the block, but we deliberately do it
1104    // at the end to avoid a read ordering issue during block execution.
1105    if let Some(previous_block_height) = block_height.checked_sub(1) {
1106        if let Some(keys_to_prune) = calculate_prune_eras(
1107            activation_point_era_id,
1108            key_block_height_for_activation_point,
1109            previous_block_height,
1110            prune_batch_size,
1111        ) {
1112            let pruning_start = Instant::now();
1113
1114            let first_key = keys_to_prune.first().copied();
1115            let last_key = keys_to_prune.last().copied();
1116            info!(
1117                previous_block_height,
1118                %key_block_height_for_activation_point,
1119                %state_root_hash,
1120                first_key=?first_key,
1121                last_key=?last_key,
1122                "commit prune: preparing prune config"
1123            );
1124            let request = PruneRequest::new(state_root_hash, keys_to_prune);
1125            match scratch_state.prune(request) {
1126                PruneResult::RootNotFound => {
1127                    error!(
1128                        previous_block_height,
1129                        %state_root_hash,
1130                        "commit prune: root not found"
1131                    );
1132                    panic!(
1133                        "Root {} not found while performing a prune.",
1134                        state_root_hash
1135                    );
1136                }
1137                PruneResult::MissingKey => {
1138                    warn!(
1139                        previous_block_height,
1140                        %state_root_hash,
1141                        "commit prune: key does not exist"
1142                    );
1143                }
1144                PruneResult::Success {
1145                    post_state_hash, ..
1146                } => {
1147                    info!(
1148                        previous_block_height,
1149                        %key_block_height_for_activation_point,
1150                        %state_root_hash,
1151                        %post_state_hash,
1152                        first_key=?first_key,
1153                        last_key=?last_key,
1154                        "commit prune: success"
1155                    );
1156                    state_root_hash = post_state_hash;
1157                }
1158                PruneResult::Failure(tce) => {
1159                    error!(?tce, "commit prune: failure");
1160                    return Err(tce.into());
1161                }
1162            }
1163            if let Some(metrics) = metrics.as_ref() {
1164                metrics
1165                    .pruning_time
1166                    .observe(pruning_start.elapsed().as_secs_f64());
1167            }
1168        }
1169    }
1170
1171    {
1172        let database_write_start = Instant::now();
1173        // Finally, the new state-root-hash from the cumulative changes to global state is
1174        // returned when they are written to LMDB.
1175        state_root_hash = data_access_layer.write_scratch_to_db(state_root_hash, scratch_state)?;
1176        if let Some(metrics) = metrics.as_ref() {
1177            metrics
1178                .scratch_lmdb_write_time
1179                .observe(database_write_start.elapsed().as_secs_f64());
1180        }
1181
1182        // Flush once, after all data mutation.
1183        let database_flush_start = Instant::now();
1184        let flush_req = FlushRequest::new();
1185        let flush_result = data_access_layer.flush(flush_req);
1186        if let Err(gse) = flush_result.as_error() {
1187            error!("failed to flush lmdb");
1188            return Err(BlockExecutionError::Lmdb(gse));
1189        }
1190        if let Some(metrics) = metrics.as_ref() {
1191            metrics
1192                .database_flush_time
1193                .observe(database_flush_start.elapsed().as_secs_f64());
1194        }
1195    }
1196
1197    // the rest of this is post process, picking out data bits to return to caller
1198    let next_era_id = executable_block.era_id.successor();
1199    let maybe_next_era_validator_weights: Option<(BTreeMap<PublicKey, U512>, u8)> =
1200        match step_outcome.as_ref() {
1201            None => None,
1202            Some(effects_and_validators) => {
1203                match effects_and_validators
1204                    .upcoming_era_validators
1205                    .get(&next_era_id)
1206                    .cloned()
1207                {
1208                    Some(validators) => next_era_gas_price.map(|gas_price| (validators, gas_price)),
1209                    None => None,
1210                }
1211            }
1212        };
1213
1214    let era_end = match (
1215        executable_block.era_report,
1216        maybe_next_era_validator_weights,
1217    ) {
1218        (None, None) => None,
1219        (
1220            Some(InternalEraReport {
1221                equivocators,
1222                inactive_validators,
1223            }),
1224            Some((next_era_validator_weights, next_era_gas_price)),
1225        ) => Some(EraEndV2::new(
1226            equivocators,
1227            inactive_validators,
1228            next_era_validator_weights,
1229            executable_block.rewards.unwrap_or_default(),
1230            next_era_gas_price,
1231        )),
1232        (maybe_era_report, maybe_next_era_validator_weights) => {
1233            if maybe_era_report.is_none() {
1234                error!(
1235                    "era_end {}: maybe_era_report is none",
1236                    executable_block.era_id
1237                );
1238            }
1239            if maybe_next_era_validator_weights.is_none() {
1240                error!(
1241                    "era_end {}: maybe_next_era_validator_weights is none",
1242                    executable_block.era_id
1243                );
1244            }
1245            return Err(BlockExecutionError::FailedToCreateEraEnd {
1246                maybe_era_report,
1247                maybe_next_era_validator_weights,
1248            });
1249        }
1250    };
1251
1252    let block = Arc::new(BlockV2::new(
1253        parent_hash,
1254        parent_seed,
1255        state_root_hash,
1256        executable_block.random_bit,
1257        era_end,
1258        executable_block.timestamp,
1259        executable_block.era_id,
1260        block_height,
1261        protocol_version,
1262        (*proposer).clone(),
1263        executable_block.transaction_map,
1264        executable_block.rewarded_signatures,
1265        current_gas_price,
1266        last_switch_block_hash,
1267    ));
1268
1269    let proof_of_checksum_registry = match data_access_layer.tracking_copy(state_root_hash)? {
1270        Some(tc) => match tc.reader().read_with_proof(&Key::ChecksumRegistry)? {
1271            Some(proof) => proof,
1272            None => return Err(BlockExecutionError::MissingChecksumRegistry),
1273        },
1274        None => return Err(BlockExecutionError::RootNotFound(state_root_hash)),
1275    };
1276
1277    let approvals_hashes = Box::new(ApprovalsHashes::new(
1278        *block.hash(),
1279        transaction_approvals_hashes,
1280        proof_of_checksum_registry,
1281    ));
1282
1283    // processing is finished now
1284    if let Some(metrics) = metrics.as_ref() {
1285        metrics
1286            .exec_block_post_processing
1287            .observe(post_processing_start.elapsed().as_secs_f64());
1288        metrics
1289            .exec_block_total
1290            .observe(start.elapsed().as_secs_f64());
1291    }
1292
1293    Ok(BlockAndExecutionArtifacts {
1294        block,
1295        approvals_hashes,
1296        execution_artifacts: artifacts,
1297        step_outcome,
1298    })
1299}
1300
1301/// Execute the transaction without committing the effects.
1302/// Intended to be used for discovery operations on read-only nodes.
1303///
1304/// Returns effects of the execution.
1305pub(super) fn speculatively_execute<S>(
1306    state_provider: &S,
1307    chainspec: &Chainspec,
1308    execution_engine_v1: &ExecutionEngineV1,
1309    block_header: BlockHeader,
1310    input_transaction: Transaction,
1311) -> SpeculativeExecutionResult
1312where
1313    S: StateProvider,
1314{
1315    let transaction_config = &chainspec.transaction_config;
1316    let maybe_transaction = MetaTransaction::from_transaction(
1317        &input_transaction,
1318        chainspec.core_config.pricing_handling,
1319        transaction_config,
1320    );
1321    if let Err(error) = maybe_transaction {
1322        return SpeculativeExecutionResult::invalid_transaction(error);
1323    }
1324    let transaction = maybe_transaction.unwrap();
1325    let state_root_hash = block_header.state_root_hash();
1326    let parent_block_hash = block_header.block_hash();
1327    let block_height = block_header.height();
1328    let block_time = block_header
1329        .timestamp()
1330        .saturating_add(chainspec.core_config.minimum_block_time);
1331    let gas_limit = match input_transaction.gas_limit(chainspec, transaction.transaction_lane()) {
1332        Ok(gas_limit) => gas_limit,
1333        Err(_) => {
1334            return SpeculativeExecutionResult::invalid_gas_limit(input_transaction);
1335        }
1336    };
1337
1338    if transaction.is_deploy_transaction() {
1339        if transaction.is_native() {
1340            let limit = Gas::from(chainspec.system_costs_config.mint_costs().transfer);
1341            let protocol_version = chainspec.protocol_version();
1342            let native_runtime_config = NativeRuntimeConfig::from_chainspec(chainspec);
1343            let transaction_hash = transaction.hash();
1344            let initiator_addr = transaction.initiator_addr();
1345            let authorization_keys = transaction.authorization_keys();
1346            let runtime_args = match transaction.session_args().as_named() {
1347                Some(runtime_args) => runtime_args.clone(),
1348                None => {
1349                    return SpeculativeExecutionResult::InvalidTransaction(InvalidTransaction::V1(
1350                        InvalidTransactionV1::ExpectedNamedArguments,
1351                    ));
1352                }
1353            };
1354
1355            let result = state_provider.transfer(TransferRequest::with_runtime_args(
1356                native_runtime_config.clone(),
1357                *state_root_hash,
1358                protocol_version,
1359                transaction_hash,
1360                initiator_addr.clone(),
1361                authorization_keys,
1362                runtime_args,
1363            ));
1364            SpeculativeExecutionResult::WasmV1(Box::new(utils::spec_exec_from_transfer_result(
1365                limit,
1366                result,
1367                block_header.block_hash(),
1368            )))
1369        } else {
1370            let block_info = BlockInfo::new(
1371                *state_root_hash,
1372                block_time.into(),
1373                parent_block_hash,
1374                block_height,
1375                execution_engine_v1.config().protocol_version(),
1376            );
1377            let session_input_data = transaction.to_session_input_data();
1378            let wasm_v1_result =
1379                match WasmV1Request::new_session(block_info, gas_limit, &session_input_data) {
1380                    Ok(wasm_v1_request) => {
1381                        execution_engine_v1.execute(state_provider, wasm_v1_request)
1382                    }
1383                    Err(error) => WasmV1Result::invalid_executable_item(gas_limit, error),
1384                };
1385            SpeculativeExecutionResult::WasmV1(Box::new(utils::spec_exec_from_wasm_v1_result(
1386                wasm_v1_result,
1387                block_header.block_hash(),
1388            )))
1389        }
1390    } else {
1391        SpeculativeExecutionResult::ReceivedV1Transaction
1392    }
1393}
1394
1395fn invoked_contract_will_pay(
1396    state_provider: &ScratchGlobalState,
1397    state_root_hash: Digest,
1398    transaction: &MetaTransaction,
1399) -> Result<Option<EntityAddr>, StateResultError> {
1400    let (hash_addr, entry_point_name) = match transaction.contract_direct_address() {
1401        None => {
1402            return Err(StateResultError::ValueNotFound(
1403                "contract direct address not found".to_string(),
1404            ))
1405        }
1406        Some((hash_addr, entry_point_name)) => (hash_addr, entry_point_name),
1407    };
1408    let entity_addr = EntityAddr::new_smart_contract(hash_addr);
1409    let entry_point_request = EntryPointRequest::new(state_root_hash, entry_point_name, hash_addr);
1410    let entry_point_response = state_provider.entry_point(entry_point_request);
1411    match entry_point_response {
1412        EntryPointResult::RootNotFound => Err(StateResultError::RootNotFound),
1413        EntryPointResult::ValueNotFound(msg) => Err(StateResultError::ValueNotFound(msg)),
1414        EntryPointResult::Failure(tce) => Err(StateResultError::Failure(tce)),
1415        EntryPointResult::Success { entry_point } => {
1416            if entry_point.will_pay_direct_invocation() {
1417                Ok(Some(entity_addr))
1418            } else {
1419                Ok(None)
1420            }
1421        }
1422    }
1423}
1424
1425#[allow(clippy::too_many_arguments)]
1426fn commit_step(
1427    native_runtime_config: NativeRuntimeConfig,
1428    scratch_state: &ScratchGlobalState,
1429    maybe_metrics: Option<Arc<Metrics>>,
1430    protocol_version: ProtocolVersion,
1431    state_hash: Digest,
1432    InternalEraReport {
1433        equivocators,
1434        inactive_validators,
1435    }: InternalEraReport,
1436    era_end_timestamp_millis: u64,
1437    next_era_id: EraId,
1438) -> StepResult {
1439    // Both inactive validators and equivocators are evicted
1440    let evict_items = inactive_validators
1441        .into_iter()
1442        .chain(equivocators)
1443        .map(EvictItem::new)
1444        .collect();
1445
1446    let step_request = StepRequest::new(
1447        native_runtime_config,
1448        state_hash,
1449        protocol_version,
1450        vec![], // <-- casper mainnet currently does not slash
1451        evict_items,
1452        next_era_id,
1453        era_end_timestamp_millis,
1454    );
1455
1456    // Commit the step.
1457    let start = Instant::now();
1458    let result = scratch_state.step(step_request);
1459    debug_assert!(result.is_success(), "{:?}", result);
1460    if let Some(metrics) = maybe_metrics {
1461        let elapsed = start.elapsed().as_secs_f64();
1462        metrics.commit_step.observe(elapsed);
1463        metrics.latest_commit_step.set(elapsed);
1464    }
1465    trace!(?result, "step response");
1466    result
1467}
1468
1469/// Computes the checksum of the given set of execution results.
1470///
1471/// This will either be a simple hash of the bytesrepr-encoded results (in the case that the
1472/// serialized results are not greater than `ChunkWithProof::CHUNK_SIZE_BYTES`), or otherwise will
1473/// be a Merkle root hash of the chunks derived from the serialized results.
1474pub(crate) fn compute_execution_results_checksum<'a>(
1475    execution_results_iter: impl Iterator<Item = &'a ExecutionResult> + Clone,
1476) -> Result<Digest, BlockExecutionError> {
1477    // Serialize the execution results as if they were `Vec<ExecutionResult>`.
1478    let serialized_length = U32_SERIALIZED_LENGTH
1479        + execution_results_iter
1480            .clone()
1481            .map(|exec_result| exec_result.serialized_length())
1482            .sum::<usize>();
1483    let mut serialized = vec![];
1484    serialized
1485        .try_reserve_exact(serialized_length)
1486        .map_err(|_| {
1487            BlockExecutionError::FailedToComputeApprovalsChecksum(bytesrepr::Error::OutOfMemory)
1488        })?;
1489    let item_count: u32 = execution_results_iter
1490        .clone()
1491        .count()
1492        .try_into()
1493        .map_err(|_| {
1494            BlockExecutionError::FailedToComputeApprovalsChecksum(
1495                bytesrepr::Error::NotRepresentable,
1496            )
1497        })?;
1498    item_count
1499        .write_bytes(&mut serialized)
1500        .map_err(BlockExecutionError::FailedToComputeExecutionResultsChecksum)?;
1501    for execution_result in execution_results_iter {
1502        execution_result
1503            .write_bytes(&mut serialized)
1504            .map_err(BlockExecutionError::FailedToComputeExecutionResultsChecksum)?;
1505    }
1506
1507    // Now hash the serialized execution results, using the `Chunkable` trait's `hash` method to
1508    // chunk if required.
1509    serialized.hash().map_err(|_| {
1510        BlockExecutionError::FailedToComputeExecutionResultsChecksum(bytesrepr::Error::OutOfMemory)
1511    })
1512}