diff --git a/chain/chain/src/test_utils/kv_runtime.rs b/chain/chain/src/test_utils/kv_runtime.rs index 7af4586d8b2..8f6e5b8ad21 100644 --- a/chain/chain/src/test_utils/kv_runtime.rs +++ b/chain/chain/src/test_utils/kv_runtime.rs @@ -1170,6 +1170,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/tests/simple_chain.rs b/chain/chain/src/tests/simple_chain.rs index 82d89b3594b..9458de249e3 100644 --- a/chain/chain/src/tests/simple_chain.rs +++ b/chain/chain/src/tests/simple_chain.rs @@ -45,7 +45,7 @@ fn build_chain() { if cfg!(feature = "nightly") { insta::assert_display_snapshot!(hash, @"3Dkg6hjpnYvMuoyEdSLnEXza6Ct2ZV9xoridA37AJzSz"); } else { - insta::assert_display_snapshot!(hash, @"3fK7Uu3HC9y9DPMsDNaAP8sv56UCK2ZV1txRhTriF9qb"); + insta::assert_display_snapshot!(hash, @"DuT1f8dmu3xYvFfsEFiAycgeLpJWQM74PZ8JtjT7SGyK"); } for i in 1..5 { @@ -75,7 +75,7 @@ fn build_chain() { if cfg!(feature = "nightly") { insta::assert_display_snapshot!(hash, @"6uCZwfkpE8qV54n5MvZXqTt8RMHYDduX4eE7quNzgLNk"); } else { - insta::assert_display_snapshot!(hash, @"7hQ4bvvu2TmgFVhELFThZPHFoBPwa74VT6D8uk79xPzB"); + insta::assert_display_snapshot!(hash, @"Ce8Ehs6S2RmXUdp2WnuyQFBGs4S3Pvs5sBwuSMZW7pqS"); } } diff --git a/chain/chain/src/types.rs b/chain/chain/src/types.rs index a88be70c394..02674fe8d83 100644 --- a/chain/chain/src/types.rs +++ b/chain/chain/src/types.rs @@ -631,6 +631,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, @@ -643,6 +644,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/chain/jsonrpc/jsonrpc-tests/res/genesis_config.json b/chain/jsonrpc/jsonrpc-tests/res/genesis_config.json index eea75629d9e..f682359642b 100644 --- a/chain/jsonrpc/jsonrpc-tests/res/genesis_config.json +++ b/chain/jsonrpc/jsonrpc-tests/res/genesis_config.json @@ -1,5 +1,5 @@ { - "protocol_version": 60, + "protocol_version": 61, "genesis_time": "1970-01-01T00:00:00.000000000Z", "chain_id": "sample", "genesis_height": 0, @@ -69,4 +69,4 @@ ], "use_production_config": false, "records": [] -} +} \ No newline at end of file diff --git a/core/primitives/Cargo.toml b/core/primitives/Cargo.toml index e4347735301..c10d36f92fb 100644 --- a/core/primitives/Cargo.toml +++ b/core/primitives/Cargo.toml @@ -50,14 +50,12 @@ protocol_feature_fix_staking_threshold = [] protocol_feature_fix_contract_loading_cost = [] protocol_feature_reject_blocks_with_outdated_protocol_version = [] protocol_feature_flat_state = [] -protocol_feature_compute_costs = [] nightly = [ "nightly_protocol", "protocol_feature_fix_staking_threshold", "protocol_feature_fix_contract_loading_cost", "protocol_feature_reject_blocks_with_outdated_protocol_version", "protocol_feature_flat_state", - "protocol_feature_compute_costs", ] nightly_protocol = [] diff --git a/core/primitives/src/transaction.rs b/core/primitives/src/transaction.rs index 6ff6388b1ec..23a6ddd6600 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_fmt::{AbbrBytes, Slice}; 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", &Slice(&self.logs)) .field("receipt_ids", &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/core/primitives/src/version.rs b/core/primitives/src/version.rs index 807152d3f0c..033fb1b83b0 100644 --- a/core/primitives/src/version.rs +++ b/core/primitives/src/version.rs @@ -139,6 +139,12 @@ pub enum ProtocolFeature { /// Meta Transaction NEP-366: https://github.com/near/NEPs/blob/master/neps/nep-0366.md DelegateAction, + /// Decouple compute and gas costs of operations to safely limit the compute time it takes to + /// process the chunk. + /// + /// Compute Costs NEP-455: https://github.com/near/NEPs/blob/master/neps/nep-0455.md + ComputeCosts, + /// In case not all validator seats are occupied our algorithm provide incorrect minimal seat /// price - it reports as alpha * sum_stake instead of alpha * sum_stake / (1 - alpha), where /// alpha is min stake ratio @@ -152,8 +158,6 @@ pub enum ProtocolFeature { RejectBlocksWithOutdatedProtocolVersions, #[cfg(feature = "protocol_feature_flat_state")] FlatStorageReads, - #[cfg(feature = "protocol_feature_compute_costs")] - ComputeCosts, } /// Both, outgoing and incoming tcp connections to peers, will be rejected if `peer's` @@ -163,7 +167,7 @@ pub const PEER_MIN_ALLOWED_PROTOCOL_VERSION: ProtocolVersion = STABLE_PROTOCOL_V /// Current protocol version used on the mainnet. /// Some features (e. g. FixStorageUsage) require that there is at least one epoch with exactly /// the corresponding version -const STABLE_PROTOCOL_VERSION: ProtocolVersion = 60; +const STABLE_PROTOCOL_VERSION: ProtocolVersion = 61; /// Largest protocol version supported by the current binary. pub const PROTOCOL_VERSION: ProtocolVersion = if cfg!(feature = "nightly_protocol") { @@ -238,6 +242,7 @@ impl ProtocolFeature { ProtocolFeature::Ed25519Verify | ProtocolFeature::ZeroBalanceAccount | ProtocolFeature::DelegateAction => 59, + ProtocolFeature::ComputeCosts => 61, // Nightly features #[cfg(feature = "protocol_feature_fix_staking_threshold")] @@ -248,8 +253,6 @@ impl ProtocolFeature { ProtocolFeature::RejectBlocksWithOutdatedProtocolVersions => 132, #[cfg(feature = "protocol_feature_flat_state")] ProtocolFeature::FlatStorageReads => 135, - #[cfg(feature = "protocol_feature_compute_costs")] - ProtocolFeature::ComputeCosts => 136, } } } diff --git a/runtime/near-vm-logic/src/logic.rs b/runtime/near-vm-logic/src/logic.rs index 32c72e9b4e0..98c7d439cdd 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/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 bfe078e44ca..3883d2075d8 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,21 @@ 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"), + )?; + + if !checked_feature!("stable", ComputeCosts, apply_state.current_protocol_version) { + assert_eq!( + total_compute_usage, total_gas_burnt, + "Compute usage must match burnt gas" + ); + } + outcomes.push(outcome_with_id); } @@ -1277,7 +1303,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 +1329,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"), + )?; + + if !checked_feature!("stable", ComputeCosts, apply_state.current_protocol_version) { + 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 +1359,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 +1375,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 +1408,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 +1432,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,18 +1593,20 @@ impl Runtime { #[cfg(test)] mod tests { + use assert_matches::assert_matches; use near_crypto::{InMemorySigner, KeyType, Signer}; use near_primitives::account::AccessKey; use near_primitives::hash::hash; use near_primitives::shard_layout::ShardUId; use near_primitives::test_utils::{account_new, MockEpochInfoProvider}; use near_primitives::transaction::{ - AddKeyAction, DeleteKeyAction, FunctionCallAction, TransferAction, + AddKeyAction, DeleteKeyAction, DeployContractAction, FunctionCallAction, TransferAction, }; use near_primitives::types::MerkleHash; use near_primitives::version::PROTOCOL_VERSION; use near_store::test_utils::create_tries; use near_store::{set_access_key, StoreCompiledContractCache}; + use near_vm_logic::{ExtCosts, ParameterCost}; use testlib::runtime_utils::{alice_account, bob_account}; use super::*; @@ -1557,15 +1617,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(), @@ -1574,7 +1634,7 @@ mod tests { input_data_ids: vec![], actions, }), - }] + } } #[test] @@ -2372,7 +2432,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( @@ -2418,7 +2478,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( @@ -2457,11 +2517,9 @@ mod tests { let wasm_code = near_test_contracts::rs_contract().to_vec(); let actions = - vec![Action::DeployContract(near_primitives::transaction::DeployContractAction { - code: wasm_code.clone(), - })]; + 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( @@ -2494,6 +2552,148 @@ mod tests { .expect("Compiled contract should be cached") .expect("Compilation result should be non-empty"); } + + #[test] + fn test_compute_usage_limit() { + let (runtime, tries, root, mut apply_state, signer, epoch_info_provider) = + setup_runtime(to_yocto(1_000_000), to_yocto(500_000), 1); + + let mut free_config = RuntimeConfig::free(); + let sha256_cost = ParameterCost { + gas: Gas::from(1_000_000u64), + 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: sha256_cost.gas, + 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: sha256_cost.gas, + 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(_)); + }); + } + + #[test] + fn test_compute_usage_limit_with_failed_receipt() { + let (runtime, tries, root, apply_state, signer, epoch_info_provider) = + setup_runtime(to_yocto(1_000_000), to_yocto(500_000), 10u64.pow(15)); + + 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 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()], + &[], + &epoch_info_provider, + Default::default(), + ) + .unwrap(); + + 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_matches!(second.outcome.status, ExecutionStatus::Failure(_)); + }); + } } /// 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![]),