From 773f390762a42f8f2a1a25935d0cc309f96d6bac Mon Sep 17 00:00:00 2001 From: Andrei Kashin Date: Tue, 4 Apr 2023 13:05:51 +0200 Subject: [PATCH] feature: Implement compute usage aggregation and limiting (#8805) feature: Limit compute usage of a chunk The commit addresses https://github.com/near/nearcore/issues/8265 by introducing aggregation of compute usage across all operations performed during chunk application and limiting this compute usage to 1s. This should not change the behavior of nodes in the short run because compute costs match gas costs which is validated by the `assert`, so any discrepancy should be caught on canary nodes. There are two follow-ups to this work: - https://github.com/near/nearcore/issues/8859 - https://github.com/near/nearcore/issues/8860 --- chain/chain/src/test_utils/kv_runtime.rs | 1 + chain/chain/src/types.rs | 2 + .../primitives/src/runtime/parameter_table.rs | 4 + core/primitives/src/transaction.rs | 12 +- runtime/near-vm-logic/src/logic.rs | 8 +- runtime/runtime/Cargo.toml | 1 + runtime/runtime/src/actions.rs | 7 +- runtime/runtime/src/config.rs | 6 +- runtime/runtime/src/lib.rs | 211 +++++++++++++++--- tools/state-viewer/src/contract_accounts.rs | 1 + 10 files changed, 222 insertions(+), 31 deletions(-) diff --git a/chain/chain/src/test_utils/kv_runtime.rs b/chain/chain/src/test_utils/kv_runtime.rs index 20919c43eca..c7b136591e1 100644 --- a/chain/chain/src/test_utils/kv_runtime.rs +++ b/chain/chain/src/test_utils/kv_runtime.rs @@ -1183,6 +1183,7 @@ impl RuntimeAdapter for KeyValueRuntime { logs: vec![], receipt_ids: new_receipt_hashes, gas_burnt: 0, + compute_usage: Some(0), tokens_burnt: 0, executor_id: to.clone(), metadata: ExecutionMetadata::V1, diff --git a/chain/chain/src/types.rs b/chain/chain/src/types.rs index ad182530e29..bc4a5309c70 100644 --- a/chain/chain/src/types.rs +++ b/chain/chain/src/types.rs @@ -637,6 +637,7 @@ mod tests { logs: vec!["outcome1".to_string()], receipt_ids: vec![hash(&[1])], gas_burnt: 100, + compute_usage: Some(200), tokens_burnt: 10000, executor_id: "alice".parse().unwrap(), metadata: ExecutionMetadata::V1, @@ -649,6 +650,7 @@ mod tests { logs: vec!["outcome2".to_string()], receipt_ids: vec![], gas_burnt: 0, + compute_usage: Some(0), tokens_burnt: 0, executor_id: "bob".parse().unwrap(), metadata: ExecutionMetadata::V1, diff --git a/core/primitives/src/runtime/parameter_table.rs b/core/primitives/src/runtime/parameter_table.rs index 3cb95aeba44..5f65910ff1c 100644 --- a/core/primitives/src/runtime/parameter_table.rs +++ b/core/primitives/src/runtime/parameter_table.rs @@ -96,6 +96,10 @@ impl TryFrom<&ParameterValue> for ParameterCost { fn try_from(value: &ParameterValue) -> Result { match value { ParameterValue::ParameterCost { gas, compute } => { + if !cfg!(feature = "protocol_feature_compute_costs") { + assert_eq!(compute, gas, "Compute cost must match gas cost"); + } + Ok(ParameterCost { gas: *gas, compute: *compute }) } // If not specified, compute costs default to gas costs. diff --git a/core/primitives/src/transaction.rs b/core/primitives/src/transaction.rs index d32e6e40c68..a0b760cb719 100644 --- a/core/primitives/src/transaction.rs +++ b/core/primitives/src/transaction.rs @@ -9,6 +9,7 @@ use borsh::{BorshDeserialize, BorshSerialize}; use near_crypto::{PublicKey, Signature}; use near_o11y::pretty; use near_primitives_core::profile::{ProfileDataV2, ProfileDataV3}; +use near_primitives_core::types::Compute; use std::borrow::Borrow; use std::fmt; use std::hash::{Hash, Hasher}; @@ -415,6 +416,13 @@ pub struct ExecutionOutcome { pub receipt_ids: Vec, /// The amount of the gas burnt by the given transaction or receipt. pub gas_burnt: Gas, + /// The amount of compute time spent by the given transaction or receipt. + // TODO(#8859): Treat this field in the same way as `gas_burnt`. + // At the moment this field is only set at runtime and is not persisted in the database. + // This means that when execution outcomes are read from the database, this value will not be + // set and any code that attempts to use it will crash. + #[borsh_skip] + pub compute_usage: Option, /// The amount of tokens burnt corresponding to the burnt gas amount. /// This value doesn't always equal to the `gas_burnt` multiplied by the gas price, because /// the prepaid gas price might be lower than the actual gas price and it creates a deficit. @@ -437,7 +445,7 @@ pub enum ExecutionMetadata { V1, /// V2: With ProfileData by legacy `Cost` enum V2(ProfileDataV2), - // V3: With ProfileData by gas parameters + /// V3: With ProfileData by gas parameters V3(ProfileDataV3), } @@ -453,6 +461,7 @@ impl fmt::Debug for ExecutionOutcome { .field("logs", &pretty::Slice(&self.logs)) .field("receipt_ids", &pretty::Slice(&self.receipt_ids)) .field("burnt_gas", &self.gas_burnt) + .field("compute_usage", &self.compute_usage.unwrap_or_default()) .field("tokens_burnt", &self.tokens_burnt) .field("status", &self.status) .field("metadata", &self.metadata) @@ -598,6 +607,7 @@ mod tests { logs: vec!["123".to_string(), "321".to_string()], receipt_ids: vec![], gas_burnt: 123, + compute_usage: Some(456), tokens_burnt: 1234000, executor_id: "alice".parse().unwrap(), metadata: ExecutionMetadata::V1, diff --git a/runtime/near-vm-logic/src/logic.rs b/runtime/near-vm-logic/src/logic.rs index 94a20d0fbe7..d26a16f4235 100644 --- a/runtime/near-vm-logic/src/logic.rs +++ b/runtime/near-vm-logic/src/logic.rs @@ -15,9 +15,9 @@ use near_primitives_core::config::ExtCosts::*; use near_primitives_core::config::{ActionCosts, ExtCosts, VMConfig}; use near_primitives_core::runtime::fees::{transfer_exec_fee, transfer_send_fee}; use near_primitives_core::types::{ - AccountId, Balance, EpochHeight, Gas, ProtocolVersion, StorageUsage, + AccountId, Balance, Compute, EpochHeight, Gas, GasDistribution, GasWeight, ProtocolVersion, + StorageUsage, }; -use near_primitives_core::types::{GasDistribution, GasWeight}; use near_vm_errors::{FunctionCallError, InconsistentStateError}; use near_vm_errors::{HostError, VMLogicError}; use std::mem::size_of; @@ -2794,6 +2794,7 @@ impl<'a> VMLogic<'a> { let mut profile = self.gas_counter.profile_data(); profile.compute_wasm_instruction_cost(burnt_gas); + let compute_usage = profile.total_compute_usage(&self.config.ext_costs); VMOutcome { balance: self.current_account_balance, @@ -2801,6 +2802,7 @@ impl<'a> VMLogic<'a> { return_data: self.return_data, burnt_gas, used_gas, + compute_usage, logs: self.logs, profile, action_receipts: self.receipt_manager.action_receipts, @@ -2919,6 +2921,7 @@ pub struct VMOutcome { pub return_data: ReturnData, pub burnt_gas: Gas, pub used_gas: Gas, + pub compute_usage: Compute, pub logs: Vec, /// Data collected from making a contract call pub profile: ProfileDataV3, @@ -2952,6 +2955,7 @@ impl VMOutcome { return_data: ReturnData::None, burnt_gas: 0, used_gas: 0, + compute_usage: 0, logs: Vec::new(), profile: ProfileDataV3::default(), action_receipts: Vec::new(), diff --git a/runtime/runtime/Cargo.toml b/runtime/runtime/Cargo.toml index 6306e2933e2..e29994b2212 100644 --- a/runtime/runtime/Cargo.toml +++ b/runtime/runtime/Cargo.toml @@ -34,6 +34,7 @@ near-vm-runner.workspace = true default = [] dump_errors_schema = ["near-vm-errors/dump_errors_schema"] protocol_feature_flat_state = ["near-store/protocol_feature_flat_state", "near-vm-logic/protocol_feature_flat_state"] +protocol_feature_compute_costs = ["near-primitives/protocol_feature_compute_costs"] nightly_protocol = ["near-primitives/nightly_protocol"] no_cpu_compatibility_checks = ["near-vm-runner/no_cpu_compatibility_checks"] diff --git a/runtime/runtime/src/actions.rs b/runtime/runtime/src/actions.rs index 61a20c40020..fa0e2191337 100644 --- a/runtime/runtime/src/actions.rs +++ b/runtime/runtime/src/actions.rs @@ -1,6 +1,6 @@ use crate::config::{ - safe_add_gas, total_prepaid_exec_fees, total_prepaid_gas, total_prepaid_send_fees, - RuntimeConfig, + safe_add_compute, safe_add_gas, total_prepaid_exec_fees, total_prepaid_gas, + total_prepaid_send_fees, RuntimeConfig, }; use crate::ext::{ExternalError, RuntimeExt}; use crate::{metrics, ActionResult, ApplyState}; @@ -254,6 +254,7 @@ pub(crate) fn action_function_call( // return a real `gas_used` instead of the `gas_burnt` into `ActionResult` even for // `FunctionCall`s error. result.gas_used = safe_add_gas(result.gas_used, outcome.used_gas)?; + result.compute_usage = safe_add_compute(result.compute_usage, outcome.compute_usage)?; result.logs.extend(outcome.logs); result.profile.merge(&outcome.profile); if execution_succeeded { @@ -687,6 +688,8 @@ pub(crate) fn apply_delegate_action( // gas_used is incremented because otherwise the gas will be refunded. Refund function checks only gas_used. result.gas_used = safe_add_gas(result.gas_used, prepaid_send_fees)?; result.gas_burnt = safe_add_gas(result.gas_burnt, prepaid_send_fees)?; + // TODO(#8806): Support compute costs for actions. For now they match burnt gas. + result.compute_usage = safe_add_compute(result.compute_usage, prepaid_send_fees)?; result.new_receipts.push(new_receipt); Ok(()) diff --git a/runtime/runtime/src/config.rs b/runtime/runtime/src/config.rs index 2d4228d2a32..a95d7a10508 100644 --- a/runtime/runtime/src/config.rs +++ b/runtime/runtime/src/config.rs @@ -14,7 +14,7 @@ use near_primitives::runtime::fees::{transfer_exec_fee, transfer_send_fee, Runti use near_primitives::transaction::{ Action, AddKeyAction, DeployContractAction, FunctionCallAction, Transaction, }; -use near_primitives::types::{AccountId, Balance, Gas}; +use near_primitives::types::{AccountId, Balance, Compute, Gas}; use near_primitives::version::{is_implicit_account_creation_enabled, ProtocolVersion}; /// Describes the cost of converting this transaction into a receipt. @@ -59,6 +59,10 @@ pub fn safe_add_balance(a: Balance, b: Balance) -> Result Result { + a.checked_add(b).ok_or_else(|| IntegerOverflowError {}) +} + #[macro_export] macro_rules! safe_add_balance_apply { ($x: expr) => {$x}; diff --git a/runtime/runtime/src/lib.rs b/runtime/runtime/src/lib.rs index 3a9147600f8..e4bc959a1e3 100644 --- a/runtime/runtime/src/lib.rs +++ b/runtime/runtime/src/lib.rs @@ -1,7 +1,7 @@ use crate::actions::*; use crate::balance_checker::check_balance; use crate::config::{ - exec_fee, safe_add_balance, safe_add_gas, safe_gas_to_balance, total_deposit, + exec_fee, safe_add_balance, safe_add_compute, safe_add_gas, safe_gas_to_balance, total_deposit, total_prepaid_exec_fees, total_prepaid_gas, RuntimeConfig, }; use crate::genesis::{GenesisStateApplier, StorageComputer}; @@ -35,7 +35,7 @@ use near_primitives::transaction::{ }; use near_primitives::trie_key::TrieKey; use near_primitives::types::{ - validator_stake::ValidatorStake, AccountId, Balance, EpochInfoProvider, Gas, + validator_stake::ValidatorStake, AccountId, Balance, Compute, EpochInfoProvider, Gas, RawStateChangesWithTrieKey, ShardId, StateChangeCause, StateRoot, }; use near_primitives::utils::{ @@ -125,6 +125,7 @@ pub struct ActionResult { pub gas_burnt: Gas, pub gas_burnt_for_function_call: Gas, pub gas_used: Gas, + pub compute_usage: Compute, pub result: Result, pub logs: Vec, pub new_receipts: Vec, @@ -147,6 +148,7 @@ impl ActionResult { next_result.gas_burnt_for_function_call, )?; self.gas_used = safe_add_gas(self.gas_used, next_result.gas_used)?; + self.compute_usage = safe_add_compute(self.compute_usage, next_result.compute_usage)?; self.profile.merge(&next_result.profile); self.result = next_result.result; self.logs.append(&mut next_result.logs); @@ -171,6 +173,7 @@ impl Default for ActionResult { gas_burnt: 0, gas_burnt_for_function_call: 0, gas_used: 0, + compute_usage: 0, result: Ok(ReturnData::None), logs: vec![], new_receipts: vec![], @@ -263,6 +266,8 @@ impl Runtime { logs: vec![], receipt_ids: vec![receipt.receipt_id], gas_burnt: verification_result.gas_burnt, + // TODO(#8806): Support compute costs for actions. For now they match burnt gas. + compute_usage: Some(verification_result.gas_burnt), tokens_burnt: verification_result.burnt_amount, executor_id: transaction.signer_id.clone(), // TODO: profile data is only counted in apply_action, which only happened at process_receipt @@ -295,16 +300,17 @@ impl Runtime { actions: &[Action], epoch_info_provider: &dyn EpochInfoProvider, ) -> Result { - // println!("enter apply_action"); - let mut result = ActionResult::default(); let exec_fees = exec_fee( &apply_state.config.fees, action, &receipt.receiver_id, apply_state.current_protocol_version, ); - result.gas_burnt += exec_fees; - result.gas_used += exec_fees; + let mut result = ActionResult::default(); + result.gas_used = exec_fees; + result.gas_burnt = exec_fees; + // TODO(#8806): Support compute costs for actions. For now they match burnt gas. + result.compute_usage = exec_fees; let account_id = &receipt.receiver_id; let is_the_only_action = actions.len() == 1; let is_refund = AccountId::is_system(&receipt.predecessor_id); @@ -499,9 +505,11 @@ impl Runtime { let mut account = get_account(state_update, account_id)?; let mut actor_id = receipt.predecessor_id.clone(); let mut result = ActionResult::default(); - let exec_fee = apply_state.config.fees.fee(ActionCosts::new_action_receipt).exec_fee(); - result.gas_used = exec_fee; - result.gas_burnt = exec_fee; + let exec_fees = apply_state.config.fees.fee(ActionCosts::new_action_receipt).exec_fee(); + result.gas_used = exec_fees; + result.gas_burnt = exec_fees; + // TODO(#8806): Support compute costs for actions. For now they match burnt gas. + result.compute_usage = exec_fees; // Executing actions one by one for (action_index, action) in action_receipt.actions.iter().enumerate() { let action_hash = create_action_hash( @@ -586,6 +594,7 @@ impl Runtime { apply_state.current_protocol_version ) { result.gas_burnt = 0; + result.compute_usage = 0; result.gas_used = 0; } @@ -749,6 +758,7 @@ impl Runtime { logs: result.logs, receipt_ids, gas_burnt: result.gas_burnt, + compute_usage: Some(result.compute_usage), tokens_burnt, executor_id: account_id.clone(), metadata: ExecutionMetadata::V3(result.profile), @@ -1253,6 +1263,7 @@ impl Runtime { // charge any gas for refund receipts, we still count the gas use towards the block gas // limit let mut total_gas_burnt = gas_used_for_migrations; + let mut total_compute_usage = total_gas_burnt; for signed_transaction in transactions { let (receipt, outcome_with_id) = self.process_transaction( @@ -1268,6 +1279,22 @@ impl Runtime { } total_gas_burnt = safe_add_gas(total_gas_burnt, outcome_with_id.outcome.gas_burnt)?; + total_compute_usage = safe_add_compute( + total_compute_usage, + outcome_with_id + .outcome + .compute_usage + .expect("`process_transaction` must populate compute usage"), + )?; + + // TODO(#8032): Remove when compute costs are stabilized. + if !cfg!(feature = "protocol_feature_compute_costs") { + assert_eq!( + total_compute_usage, total_gas_burnt, + "Compute usage must match burnt gas" + ); + } + outcomes.push(outcome_with_id); } @@ -1277,7 +1304,8 @@ impl Runtime { let mut process_receipt = |receipt: &Receipt, state_update: &mut TrieUpdate, - total_gas_burnt: &mut Gas| + total_gas_burnt: &mut Gas, + total_compute_usage: &mut Compute| -> Result<_, RuntimeError> { let _span = tracing::debug_span!( target: "runtime", @@ -1302,12 +1330,28 @@ impl Runtime { if let Some(outcome_with_id) = result? { *total_gas_burnt = safe_add_gas(*total_gas_burnt, outcome_with_id.outcome.gas_burnt)?; + *total_compute_usage = safe_add_compute( + *total_compute_usage, + outcome_with_id + .outcome + .compute_usage + .expect("`process_receipt` must populate compute usage"), + )?; + // TODO(#8032): Remove when compute costs are stabilized. + if !cfg!(feature = "protocol_feature_compute_costs") { + assert_eq!( + total_compute_usage, total_gas_burnt, + "Compute usage must match burnt gas" + ); + } outcomes.push(outcome_with_id); } Ok(()) }; - let gas_limit = apply_state.gas_limit.unwrap_or(Gas::max_value()); + // TODO(#8859): Introduce a dedicated `compute_limit` for the chunk. + // For now compute limit always matches the gas limit. + let compute_limit = apply_state.gas_limit.unwrap_or(Gas::max_value()); // We first process local receipts. They contain staking, local contract calls, etc. if let Some(prefetcher) = &mut prefetcher { @@ -1316,10 +1360,15 @@ impl Runtime { _ = prefetcher.prefetch_receipts_data(&local_receipts); } for receipt in local_receipts.iter() { - if total_gas_burnt < gas_limit { + if total_compute_usage < compute_limit { // NOTE: We don't need to validate the local receipt, because it's just validated in // the `verify_and_charge_transaction`. - process_receipt(receipt, &mut state_update, &mut total_gas_burnt)?; + process_receipt( + receipt, + &mut state_update, + &mut total_gas_burnt, + &mut total_compute_usage, + )?; } else { Self::delay_receipt(&mut state_update, &mut delayed_receipts_indices, receipt)?; } @@ -1327,7 +1376,7 @@ impl Runtime { // Then we process the delayed receipts. It's a backlog of receipts from the past blocks. while delayed_receipts_indices.first_index < delayed_receipts_indices.next_available_index { - if total_gas_burnt >= gas_limit { + if total_compute_usage >= compute_limit { break; } let key = TrieKey::DelayedReceipt { index: delayed_receipts_indices.first_index }; @@ -1360,7 +1409,12 @@ impl Runtime { state_update.remove(key); // Math checked above: first_index is less than next_available_index delayed_receipts_indices.first_index += 1; - process_receipt(&receipt, &mut state_update, &mut total_gas_burnt)?; + process_receipt( + &receipt, + &mut state_update, + &mut total_gas_burnt, + &mut total_compute_usage, + )?; processed_delayed_receipts.push(receipt); } @@ -1379,8 +1433,13 @@ impl Runtime { apply_state.current_protocol_version, ) .map_err(RuntimeError::ReceiptValidationError)?; - if total_gas_burnt < gas_limit { - process_receipt(receipt, &mut state_update, &mut total_gas_burnt)?; + if total_compute_usage < compute_limit { + process_receipt( + receipt, + &mut state_update, + &mut total_gas_burnt, + &mut total_compute_usage, + )?; } else { Self::delay_receipt(&mut state_update, &mut delayed_receipts_indices, receipt)?; } @@ -1535,6 +1594,8 @@ impl Runtime { #[cfg(test)] mod tests { + #[cfg(feature = "protocol_feature_compute_costs")] + use assert_matches::assert_matches; use near_crypto::{InMemorySigner, KeyType, Signer}; use near_primitives::account::AccessKey; use near_primitives::contract::ContractCode; @@ -1550,6 +1611,8 @@ mod tests { use near_store::set_access_key; use near_store::test_utils::create_tries; use near_store::StoreCompiledContractCache; + #[cfg(feature = "protocol_feature_compute_costs")] + use near_vm_logic::{ExtCosts, ParameterCost}; use near_vm_runner::get_contract_cache_key; use near_vm_runner::internal::VMKind; use testlib::runtime_utils::{alice_account, bob_account}; @@ -1562,15 +1625,15 @@ mod tests { near * 10u128.pow(24) } - fn create_receipts_with_actions( + fn create_receipt_with_actions( account_id: AccountId, signer: Arc, actions: Vec, - ) -> Vec { - vec![Receipt { + ) -> Receipt { + Receipt { predecessor_id: account_id.clone(), receiver_id: account_id.clone(), - receipt_id: CryptoHash::default(), + receipt_id: CryptoHash::hash_borsh(actions.clone()), receipt: ReceiptEnum::Action(ActionReceipt { signer_id: account_id, signer_public_key: signer.public_key(), @@ -1579,7 +1642,7 @@ mod tests { input_data_ids: vec![], actions, }), - }] + } } #[test] @@ -2377,7 +2440,7 @@ mod tests { }), ]; - let receipts = create_receipts_with_actions(alice_account(), signer, actions); + let receipts = vec![create_receipt_with_actions(alice_account(), signer, actions)]; let apply_result = runtime .apply( @@ -2423,7 +2486,7 @@ mod tests { let actions = vec![Action::DeleteKey(DeleteKeyAction { public_key: signer.public_key() })]; - let receipts = create_receipts_with_actions(alice_account(), signer, actions); + let receipts = vec![create_receipt_with_actions(alice_account(), signer, actions)]; let apply_result = runtime .apply( @@ -2462,7 +2525,7 @@ mod tests { let actions = vec![Action::DeployContract(DeployContractAction { code: wasm_code.clone() })]; - let receipts = create_receipts_with_actions(alice_account(), signer, actions); + let receipts = vec![create_receipt_with_actions(alice_account(), signer, actions)]; let apply_result = runtime .apply( @@ -2489,6 +2552,104 @@ mod tests { .expect("Compiled contract should be cached") .expect("Compilation result should be non-empty"); } + + #[cfg(feature = "protocol_feature_compute_costs")] + #[test] + fn test_compute_usage_limit() { + let initial_balance = to_yocto(1_000_000); + let initial_locked = to_yocto(500_000); + let (runtime, tries, root, mut apply_state, signer, epoch_info_provider) = + setup_runtime(initial_balance, initial_locked, 1); + + let mut free_config = RuntimeConfig::free(); + let sha256_cost = + ParameterCost { gas: Gas::from(1u64), compute: Compute::from(10_000_000_000_000u64) }; + free_config.wasm_config.ext_costs.costs[ExtCosts::sha256_base] = sha256_cost.clone(); + apply_state.config = Arc::new(free_config); + // This allows us to execute 1 receipt with a function call per apply. + apply_state.gas_limit = Some(sha256_cost.compute); + + let deploy_contract_receipt = create_receipt_with_actions( + alice_account(), + signer.clone(), + vec![Action::DeployContract(DeployContractAction { + code: near_test_contracts::rs_contract().to_vec(), + })], + ); + + let first_call_receipt = create_receipt_with_actions( + alice_account(), + signer.clone(), + vec![Action::FunctionCall(FunctionCallAction { + method_name: "ext_sha256".to_string(), + args: b"first".to_vec(), + gas: 1, + deposit: 0, + })], + ); + + let second_call_receipt = create_receipt_with_actions( + alice_account(), + signer.clone(), + vec![Action::FunctionCall(FunctionCallAction { + method_name: "ext_sha256".to_string(), + args: b"second".to_vec(), + gas: 1, + deposit: 0, + })], + ); + + let apply_result = runtime + .apply( + tries.get_trie_for_shard(ShardUId::single_shard(), root), + &None, + &apply_state, + &vec![ + deploy_contract_receipt.clone(), + first_call_receipt.clone(), + second_call_receipt.clone(), + ], + &[], + &epoch_info_provider, + Default::default(), + ) + .unwrap(); + let mut store_update = tries.store_update(); + let root = tries.apply_all( + &apply_result.trie_changes, + ShardUId::single_shard(), + &mut store_update, + ); + store_update.commit().unwrap(); + + // Only first two receipts should fit into the chunk due to the compute usage limit. + assert_matches!(&apply_result.outcomes[..], [first, second] => { + assert_eq!(first.id, deploy_contract_receipt.receipt_id); + assert_matches!(first.outcome.status, ExecutionStatus::SuccessValue(_)); + + assert_eq!(second.id, first_call_receipt.receipt_id); + assert_eq!(second.outcome.compute_usage.unwrap(), sha256_cost.compute); + assert_matches!(second.outcome.status, ExecutionStatus::SuccessValue(_)); + }); + + let apply_result = runtime + .apply( + tries.get_trie_for_shard(ShardUId::single_shard(), root), + &None, + &apply_state, + &[], + &[], + &epoch_info_provider, + Default::default(), + ) + .unwrap(); + + assert_matches!(&apply_result.outcomes[..], [ExecutionOutcomeWithId { id, outcome }] => { + assert_eq!(*id, second_call_receipt.receipt_id); + assert_eq!(outcome.compute_usage.unwrap(), sha256_cost.compute); + assert_matches!(outcome.status, ExecutionStatus::SuccessValue(_)); + }); + } } /// Interface provided for gas cost estimations. diff --git a/tools/state-viewer/src/contract_accounts.rs b/tools/state-viewer/src/contract_accounts.rs index 540227fc6a3..910d64aeda4 100644 --- a/tools/state-viewer/src/contract_accounts.rs +++ b/tools/state-viewer/src/contract_accounts.rs @@ -677,6 +677,7 @@ mod tests { logs: vec![], receipt_ids, gas_burnt: 100, + compute_usage: Some(200), tokens_burnt: 2000, executor_id: "someone.near".parse().unwrap(), status: ExecutionStatus::SuccessValue(vec![]),