From 045498b8f34fb555b157993b9572535556a06889 Mon Sep 17 00:00:00 2001 From: Jakob Meier Date: Thu, 24 Nov 2022 13:15:49 +0000 Subject: [PATCH] refactor: cleaning up RuntimeConfig (#8100) refactor: cleaning up RuntimeConfig Sorry. This is a humongous change. But it's really just a lot of the same boilerplate code changes. The key changes are: - Action fees are now accessed through one single function `RuntimeFeesConfig::fee(&self, cost: ActionCosts)` instead of selecting a specific field deep down in the structure - Action fees are stored in an EnumMap instead of by hard-coded fields - The JSON RPC now has `RuntimeConfigView` to keep compatibility - Creation of `RuntimeConfig` no longer goes thorough serde JSON serialization + deserialization The goal of this PR is to make gas profiling based on parameters possible, which in turn makes parameter weights possible to implement much more easily. The key here is that we no longer have to lookup the gas value up independently from the profile key. Today: ```rust self.gas_counter.pay_action_base( &self.fees_config.action_creation_config.deploy_contract_cost, sir, ActionCosts::deploy_contract_base, )?; ``` After PR: ```rust self.pay_action_base(ActionCosts::deploy_contract_base, sir)?; ``` Also it gives us simplified fee lookup on `RuntimeFeesConfig`. Before, we need things like: ```rust let exec_gas = self.cfg.action_receipt_creation_config.exec_fee() + self.cfg.action_creation_config.add_key_cost.function_call_cost.exec_fee() + num_bytes * self .cfg .action_creation_config .add_key_cost .function_call_cost_per_byte .exec_fee(); ``` which becomes ```rust let exec_gas = self.cfg.fee(ActionCosts::new_action_receipt).exec_fee() + self.cfg.fee(ActionCosts::add_function_call_key_base).exec_fee() + num_bytes * self.cfg.fee(ActionCosts::add_function_call_key_byte).exec_fee(); ``` The final state after all refactoring is done is described in #8033. This PR contains step 2 and 4 combined because I was not able to separate them due to unforeseen dependencies we have in the code. The hardest part should be done after the current PR and what follows should be small PRs that get rid of types one by one. # Test We have to make sure this is a pure refactoring change without side-effects. Luckily, we have `test_json_unchanged` that detects changes in any protocol version. Otherwise, we rely on tests that have gas profile snapshots to make sure the gas fee lookup translation did not have errors. --- Cargo.lock | 4 + chain/indexer/src/streamer/utils.rs | 4 +- chain/rosetta-rpc/src/adapters/mod.rs | 4 +- .../rosetta-rpc/src/adapters/transactions.rs | 6 +- chain/rosetta-rpc/src/utils.rs | 17 +- core/chain-configs/src/genesis_config.rs | 6 +- core/primitives-core/Cargo.toml | 1 + core/primitives-core/src/config.rs | 22 +- core/primitives-core/src/parameter.rs | 26 ++ core/primitives-core/src/runtime/fees.rs | 199 +++++++-------- core/primitives/Cargo.toml | 1 + core/primitives/src/runtime/config.rs | 41 ++- core/primitives/src/runtime/config_store.rs | 37 ++- core/primitives/src/runtime/mod.rs | 2 +- .../primitives/src/runtime/parameter_table.rs | 143 +++++++---- core/primitives/src/views.rs | 239 +++++++++++++++++- integration-tests/src/node/runtime_node.rs | 3 +- .../src/tests/client/process_blocks.rs | 19 +- .../src/tests/nearcore/rpc_nodes.rs | 13 +- .../src/tests/runtime/deployment.rs | 2 +- .../src/tests/standard_cases/mod.rs | 9 +- .../src/tests/standard_cases/runtime.rs | 2 +- integration-tests/src/tests/test_errors.rs | 2 +- nearcore/tests/economics.rs | 2 +- runtime/near-vm-logic/src/gas_counter.rs | 43 ---- runtime/near-vm-logic/src/logic.rs | 137 ++++------ runtime/runtime-params-estimator/Cargo.toml | 1 + .../src/costs_to_runtime_config.rs | 49 ++-- .../src/function_call.rs | 2 +- .../src/gas_metering.rs | 2 +- runtime/runtime-params-estimator/src/main.rs | 4 +- runtime/runtime/Cargo.toml | 1 + runtime/runtime/src/actions.rs | 4 +- runtime/runtime/src/balance_checker.rs | 11 +- runtime/runtime/src/config.rs | 67 ++--- runtime/runtime/src/genesis.rs | 2 +- runtime/runtime/src/lib.rs | 51 ++-- runtime/runtime/src/verifier.rs | 14 +- .../runtime/tests/runtime_group_tools/mod.rs | 5 +- .../runtime_group_tools/random_config.rs | 32 +-- test-utils/testlib/src/fees_utils.rs | 143 +++++------ 41 files changed, 785 insertions(+), 587 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 31bcee94268..b9aa23ad774 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3414,6 +3414,7 @@ dependencies = [ "chrono", "derive_more", "easy-ext", + "enum-map", "hex", "insta", "near-crypto", @@ -3443,6 +3444,7 @@ dependencies = [ "borsh", "bs58", "derive_more", + "enum-map", "near-account-id", "num-rational", "serde", @@ -3799,6 +3801,7 @@ dependencies = [ "assert_matches", "borsh", "byteorder", + "enum-map", "hex", "indicatif", "near-chain-configs", @@ -4920,6 +4923,7 @@ dependencies = [ "cfg-if 1.0.0", "chrono", "clap 3.1.18", + "enum-map", "genesis-populate", "hex", "indicatif", diff --git a/chain/indexer/src/streamer/utils.rs b/chain/indexer/src/streamer/utils.rs index 0cdf447cce2..85cdacd9a80 100644 --- a/chain/indexer/src/streamer/utils.rs +++ b/chain/indexer/src/streamer/utils.rs @@ -19,11 +19,13 @@ pub(crate) async fn convert_transactions_sir_into_local_receipts( let prev_block = fetch_block(&client, block.header.prev_hash).await?; let prev_block_gas_price = prev_block.header.gas_price; + let runtime_config = + node_runtime::config::RuntimeConfig::from(protocol_config.runtime_config.clone()); let local_receipts: Vec = txs.into_iter() .map(|tx| { let cost = tx_cost( - &protocol_config.runtime_config.transaction_costs, + &runtime_config.fees, &near_primitives::transaction::Transaction { signer_id: tx.transaction.signer_id.clone(), public_key: tx.transaction.public_key.clone(), diff --git a/chain/rosetta-rpc/src/adapters/mod.rs b/chain/rosetta-rpc/src/adapters/mod.rs index 098bb5893e0..e93dbec7fef 100644 --- a/chain/rosetta-rpc/src/adapters/mod.rs +++ b/chain/rosetta-rpc/src/adapters/mod.rs @@ -694,11 +694,13 @@ mod tests { use actix::System; use near_actix_test_utils::run_actix; use near_client::test_utils::setup_no_network; + use near_primitives::runtime::config::RuntimeConfig; + use near_primitives::views::RuntimeConfigView; #[test] fn test_convert_block_changes_to_transactions() { run_actix(async { - let runtime_config = near_primitives::runtime::config::RuntimeConfig::test(); + let runtime_config: RuntimeConfigView = RuntimeConfig::test().into(); let (_client, view_client) = setup_no_network( vec!["test".parse().unwrap()], "other".parse().unwrap(), diff --git a/chain/rosetta-rpc/src/adapters/transactions.rs b/chain/rosetta-rpc/src/adapters/transactions.rs index b219ad3a7cb..8281f1a897b 100644 --- a/chain/rosetta-rpc/src/adapters/transactions.rs +++ b/chain/rosetta-rpc/src/adapters/transactions.rs @@ -235,7 +235,7 @@ impl<'a> RosettaTransactions<'a> { /// Returns Rosetta transactions which map to given account changes. pub(crate) async fn convert_block_changes_to_transactions( view_client_addr: &Addr, - runtime_config: &near_primitives::runtime::config::RuntimeConfig, + runtime_config: &near_primitives::views::RuntimeConfigView, block_hash: &CryptoHash, accounts_changes: near_primitives::views::StateChangesView, mut accounts_previous_state: std::collections::HashMap< @@ -315,7 +315,7 @@ pub(crate) async fn convert_block_changes_to_transactions( } fn convert_account_update_to_operations( - runtime_config: &near_primitives::runtime::config::RuntimeConfig, + runtime_config: &near_primitives::views::RuntimeConfigView, operations: &mut Vec, account_id: &near_primitives::types::AccountId, previous_account_state: Option<&near_primitives::views::AccountView>, @@ -455,7 +455,7 @@ fn convert_account_update_to_operations( } fn convert_account_delete_to_operations( - runtime_config: &near_primitives::runtime::config::RuntimeConfig, + runtime_config: &near_primitives::views::RuntimeConfigView, operations: &mut Vec, account_id: &near_primitives::types::AccountId, previous_account_state: Option, diff --git a/chain/rosetta-rpc/src/utils.rs b/chain/rosetta-rpc/src/utils.rs index 4fc094b2af8..2ebffe84d2e 100644 --- a/chain/rosetta-rpc/src/utils.rs +++ b/chain/rosetta-rpc/src/utils.rs @@ -269,14 +269,14 @@ where } } +/// Tokens not locked due to staking (=liquid) but reserved for state. fn get_liquid_balance_for_storage( - mut account: near_primitives::account::Account, - runtime_config: &near_primitives::runtime::config::RuntimeConfig, + account: &near_primitives::account::Account, + storage_amount_per_byte: near_primitives::types::Balance, ) -> near_primitives::types::Balance { - account.set_amount(0); - near_primitives::runtime::get_insufficient_storage_stake(&account, runtime_config) - .expect("get_insufficient_storage_stake never fails when state is consistent") - .unwrap_or(0) + let required_amount = + near_primitives::types::Balance::from(account.storage_usage()) * storage_amount_per_byte; + required_amount.saturating_sub(account.locked()) } pub(crate) struct RosettaAccountBalances { @@ -292,12 +292,13 @@ impl RosettaAccountBalances { pub fn from_account>( account: T, - runtime_config: &near_primitives::runtime::config::RuntimeConfig, + runtime_config: &near_primitives::views::RuntimeConfigView, ) -> Self { let account = account.into(); let amount = account.amount(); let locked = account.locked(); - let liquid_for_storage = get_liquid_balance_for_storage(account, runtime_config); + let liquid_for_storage = + get_liquid_balance_for_storage(&account, runtime_config.storage_amount_per_byte); Self { liquid_for_storage, liquid: amount.saturating_sub(liquid_for_storage), locked } } diff --git a/core/chain-configs/src/genesis_config.rs b/core/chain-configs/src/genesis_config.rs index 3d1f9f8ad72..ad1d958f557 100644 --- a/core/chain-configs/src/genesis_config.rs +++ b/core/chain-configs/src/genesis_config.rs @@ -11,6 +11,7 @@ use std::{fmt, io}; use anyhow::Context; use chrono::{DateTime, Utc}; +use near_primitives::views::RuntimeConfigView; use num_rational::Rational32; use serde::de::{self, DeserializeSeed, IgnoredAny, MapAccess, SeqAccess, Visitor}; use serde::{Deserialize, Deserializer, Serialize}; @@ -594,6 +595,7 @@ impl GenesisChangeConfig { // Note: this type cannot be placed in primitives/src/view.rs because of `RuntimeConfig` dependency issues. // Ideally we should create `RuntimeConfigView`, but given the deeply nested nature and the number of fields inside // `RuntimeConfig`, it should be its own endeavor. +// TODO: This has changed, there is now `RuntimeConfigView`. Reconsider if moving this is possible now. #[derive(Serialize, Deserialize, Debug)] pub struct ProtocolConfigView { /// Current Protocol Version @@ -636,7 +638,7 @@ pub struct ProtocolConfigView { /// Gas price adjustment rate pub gas_price_adjustment_rate: Rational32, /// Runtime configuration (mostly economics constants). - pub runtime_config: RuntimeConfig, + pub runtime_config: RuntimeConfigView, /// Number of blocks for which a given transaction is valid pub transaction_validity_period: NumBlocks, /// Protocol treasury rate @@ -683,7 +685,7 @@ impl From for ProtocolConfigView { online_min_threshold: genesis_config.online_min_threshold, online_max_threshold: genesis_config.online_max_threshold, gas_price_adjustment_rate: genesis_config.gas_price_adjustment_rate, - runtime_config, + runtime_config: RuntimeConfigView::from(runtime_config), transaction_validity_period: genesis_config.transaction_validity_period, protocol_reward_rate: genesis_config.protocol_reward_rate, max_inflation_rate: genesis_config.max_inflation_rate, diff --git a/core/primitives-core/Cargo.toml b/core/primitives-core/Cargo.toml index db6e06e60f6..bc013f52dd1 100644 --- a/core/primitives-core/Cargo.toml +++ b/core/primitives-core/Cargo.toml @@ -18,6 +18,7 @@ base64.workspace = true borsh.workspace = true bs58.workspace = true derive_more.workspace = true +enum-map.workspace = true num-rational.workspace = true serde.workspace = true serde_repr.workspace = true diff --git a/core/primitives-core/src/config.rs b/core/primitives-core/src/config.rs index bf23b15c2c9..b8f41f858f7 100644 --- a/core/primitives-core/src/config.rs +++ b/core/primitives-core/src/config.rs @@ -5,6 +5,13 @@ use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; use strum::{Display, EnumCount}; +/// Dynamic configuration parameters required for the WASM runtime to +/// execute a smart contract. +/// +/// This (`VMConfig`) and `RuntimeFeesConfig` combined are sufficient to define +/// protocol specific behavior of the contract runtime. The former contains +/// configuration for the WASM runtime specifically, while the latter contains +/// configuration for the transaction runtime and WASM runtime. #[derive(Clone, Debug, Hash, Serialize, Deserialize, PartialEq, Eq)] pub struct VMConfig { /// Costs for runtime externals @@ -128,7 +135,7 @@ pub enum StackLimiterVersion { } impl StackLimiterVersion { - fn v0() -> StackLimiterVersion { + pub fn v0() -> StackLimiterVersion { StackLimiterVersion::V0 } } @@ -650,7 +657,18 @@ pub enum ExtCosts { // Type of an action, used in fees logic. #[derive( - Copy, Clone, Hash, PartialEq, Eq, Debug, PartialOrd, Ord, EnumCount, Display, strum::EnumIter, + Copy, + Clone, + Hash, + PartialEq, + Eq, + Debug, + PartialOrd, + Ord, + EnumCount, + Display, + strum::EnumIter, + enum_map::Enum, )] #[allow(non_camel_case_types)] pub enum ActionCosts { diff --git a/core/primitives-core/src/parameter.rs b/core/primitives-core/src/parameter.rs index 4c3096cab25..872f3575fec 100644 --- a/core/primitives-core/src/parameter.rs +++ b/core/primitives-core/src/parameter.rs @@ -1,5 +1,7 @@ use std::slice; +use crate::config::ActionCosts; + /// Protocol configuration parameter which may change between protocol versions. #[derive( Clone, @@ -313,3 +315,27 @@ impl Parameter { .iter() } } + +// TODO: consider renaming parameters to "action_{ActionCosts}" and deleting +// `FeeParameter` all together. +impl From for FeeParameter { + fn from(other: ActionCosts) -> Self { + match other { + ActionCosts::create_account => Self::ActionCreateAccount, + ActionCosts::delete_account => Self::ActionDeleteAccount, + ActionCosts::deploy_contract_base => Self::ActionDeployContract, + ActionCosts::deploy_contract_byte => Self::ActionDeployContractPerByte, + ActionCosts::function_call_base => Self::ActionFunctionCall, + ActionCosts::function_call_byte => Self::ActionFunctionCallPerByte, + ActionCosts::transfer => Self::ActionTransfer, + ActionCosts::stake => Self::ActionStake, + ActionCosts::add_full_access_key => Self::ActionAddFullAccessKey, + ActionCosts::add_function_call_key_base => Self::ActionAddFunctionCallKey, + ActionCosts::add_function_call_key_byte => Self::ActionAddFunctionCallKeyPerByte, + ActionCosts::delete_key => Self::ActionDeleteKey, + ActionCosts::new_action_receipt => Self::ActionReceiptCreation, + ActionCosts::new_data_receipt_base => Self::DataReceiptCreationBase, + ActionCosts::new_data_receipt_byte => Self::DataReceiptCreationPerByte, + } + } +} diff --git a/core/primitives-core/src/runtime/fees.rs b/core/primitives-core/src/runtime/fees.rs index f02b4fed20d..29acd6b3182 100644 --- a/core/primitives-core/src/runtime/fees.rs +++ b/core/primitives-core/src/runtime/fees.rs @@ -3,10 +3,12 @@ //! * sir -- sender is receiver. Receipts that are directed by an account to itself are guaranteed //! to not be cross-shard which is cheaper than cross-shard. Conversely, when sender is not a //! receiver it might or might not be a cross-shard communication. +use enum_map::EnumMap; use serde::{Deserialize, Serialize}; +use crate::config::ActionCosts; use crate::num_rational::Rational; -use crate::types::Gas; +use crate::types::{Balance, Gas}; /// Costs associated with an object that can only be sent over the network (and executed /// by the receiver). @@ -38,23 +40,16 @@ impl Fee { } /// The minimum fee to send and execute. - fn min_send_and_exec_fee(&self) -> Gas { + pub fn min_send_and_exec_fee(&self) -> Gas { std::cmp::min(self.send_sir, self.send_not_sir) + self.execution } } -#[derive(Debug, Serialize, Deserialize, Clone, Hash, PartialEq, Eq)] +#[derive(Debug, Clone, Hash, PartialEq, Eq)] pub struct RuntimeFeesConfig { - /// Describes the cost of creating an action receipt, `ActionReceipt`, excluding the actual cost - /// of actions. - /// - `send` cost is burned when a receipt is created using `promise_create` or - /// `promise_batch_create` - /// - `exec` cost is burned when the receipt is being executed. - pub action_receipt_creation_config: Fee, - /// Describes the cost of creating a data receipt, `DataReceipt`. - pub data_receipt_creation_config: DataReceiptCreationConfig, - /// Describes the cost of creating a certain action, `Action`. Includes all variants. - pub action_creation_config: ActionCreationConfig, + /// Gas fees for sending and executing actions. + pub action_fees: EnumMap, + /// Describes fees for storage. pub storage_usage_config: StorageUsageConfig, @@ -127,8 +122,11 @@ pub struct AccessKeyCreationConfig { } /// Describes cost of storage per block -#[derive(Debug, Serialize, Deserialize, Clone, Hash, PartialEq, Eq)] +#[derive(Debug, Clone, Hash, PartialEq, Eq)] pub struct StorageUsageConfig { + /// Amount of yN per byte required to have on the account. See + /// for details. + pub storage_amount_per_byte: Balance, /// Number of bytes for an account record, including rounding up for account id. pub num_bytes_account: u64, /// Additional number of bytes for a k/v record @@ -136,129 +134,102 @@ pub struct StorageUsageConfig { } impl RuntimeFeesConfig { + /// Access action fee by `ActionCosts`. + pub fn fee(&self, cost: ActionCosts) -> &Fee { + &self.action_fees[cost] + } + pub fn test() -> Self { - #[allow(clippy::unreadable_literal)] Self { - action_receipt_creation_config: Fee { - send_sir: 108059500000, - send_not_sir: 108059500000, - execution: 108059500000, - }, - data_receipt_creation_config: DataReceiptCreationConfig { - base_cost: Fee { - send_sir: 4697339419375, - send_not_sir: 4697339419375, - execution: 4697339419375, - }, - cost_per_byte: Fee { - send_sir: 59357464, - send_not_sir: 59357464, - execution: 59357464, - }, - }, - action_creation_config: ActionCreationConfig { - create_account_cost: Fee { + storage_usage_config: StorageUsageConfig::test(), + burnt_gas_reward: Rational::new(3, 10), + pessimistic_gas_price_inflation_ratio: Rational::new(103, 100), + action_fees: enum_map::enum_map! { + ActionCosts::create_account => Fee { send_sir: 99607375000, send_not_sir: 99607375000, execution: 99607375000, }, - deploy_contract_cost: Fee { + ActionCosts::delete_account => Fee { + send_sir: 147489000000, + send_not_sir: 147489000000, + execution: 147489000000, + }, + ActionCosts::deploy_contract_base => Fee { send_sir: 184765750000, send_not_sir: 184765750000, execution: 184765750000, }, - deploy_contract_cost_per_byte: Fee { + ActionCosts::deploy_contract_byte => Fee { send_sir: 6812999, send_not_sir: 6812999, execution: 6812999, }, - function_call_cost: Fee { + ActionCosts::function_call_base => Fee { send_sir: 2319861500000, send_not_sir: 2319861500000, execution: 2319861500000, }, - function_call_cost_per_byte: Fee { + ActionCosts::function_call_byte => Fee { send_sir: 2235934, send_not_sir: 2235934, execution: 2235934, }, - transfer_cost: Fee { + ActionCosts::transfer => Fee { send_sir: 115123062500, send_not_sir: 115123062500, execution: 115123062500, }, - stake_cost: Fee { + ActionCosts::stake => Fee { send_sir: 141715687500, send_not_sir: 141715687500, execution: 102217625000, }, - add_key_cost: AccessKeyCreationConfig { - full_access_cost: Fee { - send_sir: 101765125000, - send_not_sir: 101765125000, - execution: 101765125000, - }, - function_call_cost: Fee { - send_sir: 102217625000, - send_not_sir: 102217625000, - execution: 102217625000, - }, - function_call_cost_per_byte: Fee { - send_sir: 1925331, - send_not_sir: 1925331, - execution: 1925331, - }, + ActionCosts::add_full_access_key => Fee { + send_sir: 101765125000, + send_not_sir: 101765125000, + execution: 101765125000, + }, + ActionCosts::add_function_call_key_base => Fee { + send_sir: 102217625000, + send_not_sir: 102217625000, + execution: 102217625000, + }, + ActionCosts::add_function_call_key_byte => Fee { + send_sir: 1925331, + send_not_sir: 1925331, + execution: 1925331, }, - delete_key_cost: Fee { + ActionCosts::delete_key => Fee { send_sir: 94946625000, send_not_sir: 94946625000, execution: 94946625000, }, - delete_account_cost: Fee { - send_sir: 147489000000, - send_not_sir: 147489000000, - execution: 147489000000, + ActionCosts::new_action_receipt => Fee { + send_sir: 108059500000, + send_not_sir: 108059500000, + execution: 108059500000, + }, + ActionCosts::new_data_receipt_base => Fee { + send_sir: 4697339419375, + send_not_sir: 4697339419375, + execution: 4697339419375, + }, + ActionCosts::new_data_receipt_byte => Fee { + send_sir: 59357464, + send_not_sir: 59357464, + execution: 59357464, }, }, - storage_usage_config: StorageUsageConfig { - // See Account in core/primitives/src/account.rs for the data structure. - // TODO(2291): figure out value for the mainnet. - num_bytes_account: 100, - num_extra_bytes_record: 40, - }, - burnt_gas_reward: Rational::new(3, 10), - pessimistic_gas_price_inflation_ratio: Rational::new(103, 100), } } pub fn free() -> Self { - let free = Fee { send_sir: 0, send_not_sir: 0, execution: 0 }; - RuntimeFeesConfig { - action_receipt_creation_config: free.clone(), - data_receipt_creation_config: DataReceiptCreationConfig { - base_cost: free.clone(), - cost_per_byte: free.clone(), - }, - action_creation_config: ActionCreationConfig { - create_account_cost: free.clone(), - deploy_contract_cost: free.clone(), - deploy_contract_cost_per_byte: free.clone(), - function_call_cost: free.clone(), - function_call_cost_per_byte: free.clone(), - transfer_cost: free.clone(), - stake_cost: free.clone(), - add_key_cost: AccessKeyCreationConfig { - full_access_cost: free.clone(), - function_call_cost: free.clone(), - function_call_cost_per_byte: free.clone(), - }, - delete_key_cost: free.clone(), - delete_account_cost: free, - }, - storage_usage_config: StorageUsageConfig { - num_bytes_account: 0, - num_extra_bytes_record: 0, + Self { + action_fees: enum_map::enum_map! { + _ => Fee { send_sir: 0, send_not_sir: 0, execution: 0 } }, + storage_usage_config: StorageUsageConfig::free(), burnt_gas_reward: Rational::from_integer(0), pessimistic_gas_price_inflation_ratio: Rational::from_integer(0), } @@ -269,8 +240,22 @@ impl RuntimeFeesConfig { /// This amount is used to determine how many receipts can be created, send and executed for /// some amount of prepaid gas using function calls. pub fn min_receipt_with_function_call_gas(&self) -> Gas { - self.action_receipt_creation_config.min_send_and_exec_fee() - + self.action_creation_config.function_call_cost.min_send_and_exec_fee() + self.fee(ActionCosts::new_action_receipt).min_send_and_exec_fee() + + self.fee(ActionCosts::function_call_base).min_send_and_exec_fee() + } +} + +impl StorageUsageConfig { + pub fn test() -> Self { + Self { + num_bytes_account: 100, + num_extra_bytes_record: 40, + storage_amount_per_byte: 909 * 100_000_000_000_000_000, + } + } + + pub(crate) fn free() -> StorageUsageConfig { + Self { num_bytes_account: 0, num_extra_bytes_record: 0, storage_amount_per_byte: 0 } } } @@ -278,26 +263,26 @@ impl RuntimeFeesConfig { /// In case of implicit account creation they always include extra fees for the CreateAccount and /// AddFullAccessKey actions that are implicit. /// We can assume that no overflow will happen here. -pub fn transfer_exec_fee(cfg: &ActionCreationConfig, is_receiver_implicit: bool) -> Gas { +pub fn transfer_exec_fee(cfg: &RuntimeFeesConfig, is_receiver_implicit: bool) -> Gas { if is_receiver_implicit { - cfg.create_account_cost.exec_fee() - + cfg.add_key_cost.full_access_cost.exec_fee() - + cfg.transfer_cost.exec_fee() + cfg.fee(ActionCosts::create_account).exec_fee() + + cfg.fee(ActionCosts::add_full_access_key).exec_fee() + + cfg.fee(ActionCosts::transfer).exec_fee() } else { - cfg.transfer_cost.exec_fee() + cfg.fee(ActionCosts::transfer).exec_fee() } } pub fn transfer_send_fee( - cfg: &ActionCreationConfig, + cfg: &RuntimeFeesConfig, sender_is_receiver: bool, is_receiver_implicit: bool, ) -> Gas { if is_receiver_implicit { - cfg.create_account_cost.send_fee(sender_is_receiver) - + cfg.add_key_cost.full_access_cost.send_fee(sender_is_receiver) - + cfg.transfer_cost.send_fee(sender_is_receiver) + cfg.fee(ActionCosts::create_account).send_fee(sender_is_receiver) + + cfg.fee(ActionCosts::add_full_access_key).send_fee(sender_is_receiver) + + cfg.fee(ActionCosts::transfer).send_fee(sender_is_receiver) } else { - cfg.transfer_cost.send_fee(sender_is_receiver) + cfg.fee(ActionCosts::transfer).send_fee(sender_is_receiver) } } diff --git a/core/primitives/Cargo.toml b/core/primitives/Cargo.toml index c6663b07e4d..34d37428d87 100644 --- a/core/primitives/Cargo.toml +++ b/core/primitives/Cargo.toml @@ -21,6 +21,7 @@ cfg-if.workspace = true chrono.workspace = true derive_more.workspace = true easy-ext.workspace = true +enum-map.workspace = true hex.workspace = true num-rational.workspace = true once_cell.workspace = true diff --git a/core/primitives/src/runtime/config.rs b/core/primitives/src/runtime/config.rs index 90214d1cdea..6d75728ffef 100644 --- a/core/primitives/src/runtime/config.rs +++ b/core/primitives/src/runtime/config.rs @@ -1,26 +1,25 @@ //! Settings of the parameters of the runtime. -use serde::{Deserialize, Serialize}; - use crate::config::VMConfig; use crate::runtime::config_store::INITIAL_TESTNET_CONFIG; use crate::runtime::fees::RuntimeFeesConfig; use crate::runtime::parameter_table::ParameterTable; -use crate::serialize::dec_format; -use crate::types::{AccountId, Balance}; +use crate::types::AccountId; +use near_primitives_core::types::Balance; use super::parameter_table::InvalidConfigError; /// The structure that holds the parameters of the runtime, mostly economics. -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct RuntimeConfig { - /// Amount of yN per byte required to have on the account. See - /// for details. - #[serde(with = "dec_format")] - pub storage_amount_per_byte: Balance, - /// Costs of different actions that need to be performed when sending and processing transaction - /// and receipts. - pub transaction_costs: RuntimeFeesConfig, - /// Config of wasm operations. + /// Action gas costs, storage fees, and economic constants around them. + /// + /// This contains parameters that are required by the WASM runtime and the + /// transaction runtime. + pub fees: RuntimeFeesConfig, + /// Config of wasm operations, also includes wasm gas costs. + /// + /// This contains all the configuration parameters that are only required by + /// the WASM runtime. pub wasm_config: VMConfig, /// Config that defines rules for account creation. pub account_creation_config: AccountCreationConfig, @@ -28,8 +27,7 @@ pub struct RuntimeConfig { impl RuntimeConfig { pub(crate) fn new(params: &ParameterTable) -> Result { - serde_json::from_value(params.runtime_config_json()) - .map_err(InvalidConfigError::WrongStructure) + RuntimeConfig::try_from(params) } pub fn initial_testnet_config() -> RuntimeConfig { @@ -41,9 +39,7 @@ impl RuntimeConfig { pub fn test() -> Self { RuntimeConfig { - // See https://nomicon.io/Economics/README.html#general-variables for how it was calculated. - storage_amount_per_byte: 909 * 100_000_000_000_000_000, - transaction_costs: RuntimeFeesConfig::test(), + fees: RuntimeFeesConfig::test(), wasm_config: VMConfig::test(), account_creation_config: AccountCreationConfig::default(), } @@ -51,16 +47,19 @@ impl RuntimeConfig { pub fn free() -> Self { Self { - storage_amount_per_byte: 0, - transaction_costs: RuntimeFeesConfig::free(), + fees: RuntimeFeesConfig::free(), wasm_config: VMConfig::free(), account_creation_config: AccountCreationConfig::default(), } } + + pub fn storage_amount_per_byte(&self) -> Balance { + self.fees.storage_usage_config.storage_amount_per_byte + } } /// The structure describes configuration for creation of new accounts. -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct AccountCreationConfig { /// The minimum length of the top-level account ID that is allowed to be created by any account. pub min_allowed_top_level_account_length: u8, diff --git a/core/primitives/src/runtime/config_store.rs b/core/primitives/src/runtime/config_store.rs index ed79debdf3b..7b337c82eba 100644 --- a/core/primitives/src/runtime/config_store.rs +++ b/core/primitives/src/runtime/config_store.rs @@ -69,7 +69,7 @@ impl RuntimeConfigStore { let mut config = runtime_config.clone(); store.insert(0, Arc::new(config.clone())); - config.storage_amount_per_byte = 10u128.pow(19); + config.fees.storage_usage_config.storage_amount_per_byte = 10u128.pow(19); store.insert(42, Arc::new(config)); } @@ -109,6 +109,7 @@ mod tests { use crate::version::ProtocolFeature::{ LowerDataReceiptAndEcrecoverBaseCost, LowerStorageCost, LowerStorageKeyLimit, }; + use near_primitives_core::config::ActionCosts; const GENESIS_PROTOCOL_VERSION: ProtocolVersion = 29; const RECEIPTS_DEPTH: u64 = 63; @@ -119,7 +120,7 @@ mod tests { for (protocol_version, config) in store.store.iter() { assert!( config.wasm_config.limit_config.max_total_prepaid_gas - / config.transaction_costs.min_receipt_with_function_call_gas() + / config.fees.min_receipt_with_function_call_gas() <= 63, "The maximum desired depth of receipts for protocol version {} should be at most {}", protocol_version, @@ -133,7 +134,7 @@ mod tests { let store = RuntimeConfigStore::new(None); let base_cfg = store.get_config(GENESIS_PROTOCOL_VERSION); let new_cfg = store.get_config(LowerStorageCost.protocol_version()); - assert!(base_cfg.storage_amount_per_byte > new_cfg.storage_amount_per_byte); + assert!(base_cfg.storage_amount_per_byte() > new_cfg.storage_amount_per_byte()); } #[test] @@ -158,12 +159,12 @@ mod tests { let base_cfg = store.get_config(LowerStorageCost.protocol_version()); let new_cfg = store.get_config(LowerDataReceiptAndEcrecoverBaseCost.protocol_version()); assert!( - base_cfg.transaction_costs.data_receipt_creation_config.base_cost.send_sir - > new_cfg.transaction_costs.data_receipt_creation_config.base_cost.send_sir + base_cfg.fees.fee(ActionCosts::new_data_receipt_base).send_sir + > new_cfg.fees.fee(ActionCosts::new_data_receipt_base).send_sir ); assert!( - base_cfg.transaction_costs.data_receipt_creation_config.cost_per_byte.send_sir - > new_cfg.transaction_costs.data_receipt_creation_config.cost_per_byte.send_sir + base_cfg.fees.fee(ActionCosts::new_data_receipt_byte).send_sir + > new_cfg.fees.fee(ActionCosts::new_data_receipt_byte).send_sir ); } @@ -179,12 +180,9 @@ mod tests { assert_eq!(config.as_ref(), &base_config); let config = store.get_config(LowerStorageCost.protocol_version()); - assert_eq!(base_config.storage_amount_per_byte, 100_000_000_000_000_000_000u128); - assert_eq!(config.storage_amount_per_byte, 10_000_000_000_000_000_000u128); - assert_eq!( - config.transaction_costs.data_receipt_creation_config.base_cost.send_sir, - 4_697_339_419_375 - ); + assert_eq!(base_config.storage_amount_per_byte(), 100_000_000_000_000_000_000u128); + assert_eq!(config.storage_amount_per_byte(), 10_000_000_000_000_000_000u128); + assert_eq!(config.fees.fee(ActionCosts::new_data_receipt_base).send_sir, 4_697_339_419_375); assert_ne!(config.as_ref(), &base_config); assert_ne!( config.as_ref(), @@ -199,10 +197,7 @@ mod tests { assert_eq!(**config, expected_config); let config = store.get_config(LowerDataReceiptAndEcrecoverBaseCost.protocol_version()); - assert_eq!( - config.transaction_costs.data_receipt_creation_config.base_cost.send_sir, - 36_486_732_312 - ); + assert_eq!(config.fees.fee(ActionCosts::new_data_receipt_base).send_sir, 36_486_732_312); let expected_config = { let second_diff = CONFIG_DIFFS[1].1.parse().unwrap(); base_params.apply_diff(second_diff).unwrap(); @@ -240,11 +235,14 @@ mod tests { #[test] #[cfg(not(feature = "nightly"))] fn test_json_unchanged() { + use crate::views::RuntimeConfigView; + let store = RuntimeConfigStore::new(None); for version in store.store.keys() { let snapshot_name = format!("{version}.json"); - insta::assert_json_snapshot!(snapshot_name, store.get_config(*version)); + let config_view = RuntimeConfigView::from(store.get_config(*version).as_ref().clone()); + insta::assert_json_snapshot!(snapshot_name, config_view); } // Testnet initial config for old version was different, thus needs separate testing @@ -254,7 +252,8 @@ mod tests { for version in testnet_store.store.keys() { let snapshot_name = format!("testnet_{version}.json"); - insta::assert_json_snapshot!(snapshot_name, store.get_config(*version)); + let config_view = RuntimeConfigView::from(store.get_config(*version).as_ref().clone()); + insta::assert_json_snapshot!(snapshot_name, config_view); } } } diff --git a/core/primitives/src/runtime/mod.rs b/core/primitives/src/runtime/mod.rs index 3232eb5ee4f..7f7968e1c53 100644 --- a/core/primitives/src/runtime/mod.rs +++ b/core/primitives/src/runtime/mod.rs @@ -23,7 +23,7 @@ pub fn get_insufficient_storage_stake( runtime_config: &RuntimeConfig, ) -> Result, String> { let required_amount = Balance::from(account.storage_usage()) - .checked_mul(runtime_config.storage_amount_per_byte) + .checked_mul(runtime_config.storage_amount_per_byte()) .ok_or_else(|| { format!("Account's storage_usage {} overflows multiplication", account.storage_usage()) })?; diff --git a/core/primitives/src/runtime/parameter_table.rs b/core/primitives/src/runtime/parameter_table.rs index fb8c3ed4d18..50372ed6fff 100644 --- a/core/primitives/src/runtime/parameter_table.rs +++ b/core/primitives/src/runtime/parameter_table.rs @@ -1,5 +1,11 @@ +use super::config::{AccountCreationConfig, RuntimeConfig}; +use near_primitives_core::config::VMConfig; use near_primitives_core::parameter::{FeeParameter, Parameter}; +use near_primitives_core::runtime::fees::{RuntimeFeesConfig, StorageUsageConfig}; +use num_rational::Rational; +use serde::de::DeserializeOwned; use serde_json::json; +use std::any::Any; use std::collections::BTreeMap; pub(crate) struct ParameterTable { @@ -31,6 +37,10 @@ pub(crate) enum InvalidConfigError { NoOldValueExists(Parameter, String), #[error("expected old value `{1}` but found `{2}` for parameter `{0}` in config diff")] WrongOldValue(Parameter, String, String), + #[error("expected a value for `{0}` but found none")] + MissingParameter(Parameter), + #[error("expected a value of type `{2}` for `{1}` but could not parse it from `{3}`")] + WrongValueType(#[source] serde_json::Error, Parameter, &'static str, String), } impl std::str::FromStr for ParameterTable { @@ -46,27 +56,48 @@ impl std::str::FromStr for ParameterTable { } } -impl ParameterTable { - /// Transforms parameters stored in the table into a JSON representation of `RuntimeConfig`. - pub(crate) fn runtime_config_json(&self) -> serde_json::Value { - let storage_amount_per_byte = self.get(Parameter::StorageAmountPerByte); - let transaction_costs = self.transaction_costs_json(); - json!({ - "storage_amount_per_byte": storage_amount_per_byte, - "transaction_costs": transaction_costs, - "wasm_config": { - "ext_costs": self.json_map(Parameter::ext_costs(), "wasm_"), - "grow_mem_cost": self.get(Parameter::WasmGrowMemCost), - "regular_op_cost": self.get(Parameter::WasmRegularOpCost), - "limit_config": self.json_map(Parameter::vm_limits(), ""), +impl TryFrom<&ParameterTable> for RuntimeConfig { + type Error = InvalidConfigError; + + fn try_from(params: &ParameterTable) -> Result { + Ok(RuntimeConfig { + fees: RuntimeFeesConfig { + action_fees: enum_map::enum_map! { + action_cost => params.fee(action_cost) + }, + burnt_gas_reward: Rational::new( + params.get_parsed(Parameter::BurntGasRewardNumerator)?, + params.get_parsed(Parameter::BurntGasRewardDenominator)?, + ), + pessimistic_gas_price_inflation_ratio: Rational::new( + params.get_parsed(Parameter::PessimisticGasPriceInflationNumerator)?, + params.get_parsed(Parameter::PessimisticGasPriceInflationDenominator)?, + ), + storage_usage_config: StorageUsageConfig { + storage_amount_per_byte: params.get_u128(Parameter::StorageAmountPerByte)?, + num_bytes_account: params.get_parsed(Parameter::StorageNumBytesAccount)?, + num_extra_bytes_record: params + .get_parsed(Parameter::StorageNumExtraBytesRecord)?, + }, + }, + wasm_config: VMConfig { + ext_costs: serde_json::from_value(params.json_map(Parameter::ext_costs(), "wasm_")) + .map_err(InvalidConfigError::WrongStructure)?, + grow_mem_cost: params.get_parsed(Parameter::WasmGrowMemCost)?, + regular_op_cost: params.get_parsed(Parameter::WasmRegularOpCost)?, + limit_config: serde_json::from_value(params.json_map(Parameter::vm_limits(), "")) + .map_err(InvalidConfigError::WrongStructure)?, + }, + account_creation_config: AccountCreationConfig { + min_allowed_top_level_account_length: params + .get_parsed(Parameter::MinAllowedTopLevelAccountLength)?, + registrar_account_id: params.get_parsed(Parameter::RegistrarAccountId)?, }, - "account_creation_config": { - "min_allowed_top_level_account_length": self.get(Parameter::MinAllowedTopLevelAccountLength), - "registrar_account_id": self.get(Parameter::RegistrarAccountId), - } }) } +} +impl ParameterTable { pub(crate) fn apply_diff( &mut self, diff: ParameterTableDiff, @@ -103,44 +134,6 @@ impl ParameterTable { Ok(()) } - fn transaction_costs_json(&self) -> serde_json::Value { - json!( { - "action_receipt_creation_config": self.fee_json(FeeParameter::ActionReceiptCreation), - "data_receipt_creation_config": { - "base_cost": self.fee_json(FeeParameter::DataReceiptCreationBase), - "cost_per_byte": self.fee_json(FeeParameter::DataReceiptCreationPerByte), - }, - "action_creation_config": { - "create_account_cost": self.fee_json(FeeParameter::ActionCreateAccount), - "deploy_contract_cost": self.fee_json(FeeParameter::ActionDeployContract), - "deploy_contract_cost_per_byte": self.fee_json(FeeParameter::ActionDeployContractPerByte), - "function_call_cost": self.fee_json(FeeParameter::ActionFunctionCall), - "function_call_cost_per_byte": self.fee_json(FeeParameter::ActionFunctionCallPerByte), - "transfer_cost": self.fee_json(FeeParameter::ActionTransfer), - "stake_cost": self.fee_json(FeeParameter::ActionStake), - "add_key_cost": { - "full_access_cost": self.fee_json(FeeParameter::ActionAddFullAccessKey), - "function_call_cost": self.fee_json(FeeParameter::ActionAddFunctionCallKey), - "function_call_cost_per_byte": self.fee_json(FeeParameter::ActionAddFunctionCallKeyPerByte), - }, - "delete_key_cost": self.fee_json(FeeParameter::ActionDeleteKey), - "delete_account_cost": self.fee_json(FeeParameter::ActionDeleteAccount), - }, - "storage_usage_config": { - "num_bytes_account": self.get(Parameter::StorageNumBytesAccount), - "num_extra_bytes_record": self.get(Parameter::StorageNumExtraBytesRecord), - }, - "burnt_gas_reward": [ - self.get(Parameter::BurntGasRewardNumerator), - self.get(Parameter::BurntGasRewardDenominator) - ], - "pessimistic_gas_price_inflation_ratio": [ - self.get(Parameter::PessimisticGasPriceInflationNumerator), - self.get(Parameter::PessimisticGasPriceInflationDenominator) - ] - }) - } - fn json_map( &self, params: impl Iterator, @@ -161,6 +154,46 @@ impl ParameterTable { self.parameters.get(&key) } + /// Access action fee by `ActionCosts`. + fn fee( + &self, + cost: near_primitives_core::config::ActionCosts, + ) -> near_primitives_core::runtime::fees::Fee { + let json = self.fee_json(FeeParameter::from(cost)); + serde_json::from_value::(json) + .expect("just constructed a Fee JSON") + } + + /// Read and parse a parameter from the `ParameterTable`. + fn get_parsed( + &self, + key: Parameter, + ) -> Result { + let value = self.parameters.get(&key).ok_or(InvalidConfigError::MissingParameter(key))?; + serde_json::from_value(value.clone()).map_err(|parse_err| { + InvalidConfigError::WrongValueType( + parse_err, + key, + std::any::type_name::(), + value.to_string(), + ) + }) + } + + /// Read and parse a parameter from the `ParameterTable`. + fn get_u128(&self, key: Parameter) -> Result { + let value = self.parameters.get(&key).ok_or(InvalidConfigError::MissingParameter(key))?; + + near_primitives_core::serialize::dec_format::deserialize(value).map_err(|parse_err| { + InvalidConfigError::WrongValueType( + parse_err, + key, + std::any::type_name::(), + value.to_string(), + ) + }) + } + fn fee_json(&self, key: FeeParameter) -> serde_json::Value { json!( { "send_sir": self.get(format!("{key}_send_sir").parse().unwrap()), diff --git a/core/primitives/src/views.rs b/core/primitives/src/views.rs index dc13a627303..60376d0e8d6 100644 --- a/core/primitives/src/views.rs +++ b/core/primitives/src/views.rs @@ -10,7 +10,9 @@ use std::sync::Arc; use borsh::{BorshDeserialize, BorshSerialize}; use chrono::DateTime; -use near_primitives_core::config::ActionCosts; +use near_primitives_core::config::{ActionCosts, VMConfig}; +use near_primitives_core::runtime::fees::Fee; +use num_rational::Rational; use serde::{Deserialize, Serialize}; use near_crypto::{PublicKey, Signature}; @@ -30,6 +32,7 @@ use crate::merkle::{combine_hash, MerklePath}; use crate::network::PeerId; use crate::profile::Cost; use crate::receipt::{ActionReceipt, DataReceipt, DataReceiver, Receipt, ReceiptEnum}; +use crate::runtime::config::RuntimeConfig; use crate::serialize::{base64_format, dec_format, option_base64_format}; use crate::sharding::{ ChunkHash, ShardChunk, ShardChunkHeader, ShardChunkHeaderInner, ShardChunkHeaderInnerV2, @@ -1962,3 +1965,237 @@ pub type StateChangesView = Vec; /// Maintenance windows view are a vector of maintenance window. pub type MaintenanceWindowsView = Vec>; + +/// View that preserves JSON format of the runtime config. +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct RuntimeConfigView { + /// Amount of yN per byte required to have on the account. See + /// for details. + #[serde(with = "dec_format")] + pub storage_amount_per_byte: Balance, + /// Costs of different actions that need to be performed when sending and + /// processing transaction and receipts. + pub transaction_costs: RuntimeFeesConfigView, + /// Config of wasm operations. + /// + /// TODO: This should be refactored to `VMConfigView` to detach the config + /// format from RPC output. + pub wasm_config: VMConfig, + /// Config that defines rules for account creation. + pub account_creation_config: AccountCreationConfigView, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct RuntimeFeesConfigView { + /// Describes the cost of creating an action receipt, `ActionReceipt`, excluding the actual cost + /// of actions. + /// - `send` cost is burned when a receipt is created using `promise_create` or + /// `promise_batch_create` + /// - `exec` cost is burned when the receipt is being executed. + pub action_receipt_creation_config: Fee, + /// Describes the cost of creating a data receipt, `DataReceipt`. + pub data_receipt_creation_config: DataReceiptCreationConfigView, + /// Describes the cost of creating a certain action, `Action`. Includes all variants. + pub action_creation_config: ActionCreationConfigView, + /// Describes fees for storage. + pub storage_usage_config: StorageUsageConfigView, + + /// Fraction of the burnt gas to reward to the contract account for execution. + pub burnt_gas_reward: Rational, + + /// Pessimistic gas price inflation ratio. + pub pessimistic_gas_price_inflation_ratio: Rational, +} + +/// The structure describes configuration for creation of new accounts. +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct AccountCreationConfigView { + /// The minimum length of the top-level account ID that is allowed to be created by any account. + pub min_allowed_top_level_account_length: u8, + /// The account ID of the account registrar. This account ID allowed to create top-level + /// accounts of any valid length. + pub registrar_account_id: AccountId, +} + +#[derive(Debug, Serialize, Deserialize, Clone, Hash, PartialEq, Eq)] +pub struct DataReceiptCreationConfigView { + /// Base cost of creating a data receipt. + /// Both `send` and `exec` costs are burned when a new receipt has input dependencies. The gas + /// is charged for each input dependency. The dependencies are specified when a receipt is + /// created using `promise_then` and `promise_batch_then`. + /// NOTE: Any receipt with output dependencies will produce data receipts. Even if it fails. + /// Even if the last action is not a function call (in case of success it will return empty + /// value). + pub base_cost: Fee, + /// Additional cost per byte sent. + /// Both `send` and `exec` costs are burned when a function call finishes execution and returns + /// `N` bytes of data to every output dependency. For each output dependency the cost is + /// `(send(sir) + exec()) * N`. + pub cost_per_byte: Fee, +} + +/// Describes the cost of creating a specific action, `Action`. Includes all variants. +#[derive(Debug, Serialize, Deserialize, Clone, Hash, PartialEq, Eq)] +pub struct ActionCreationConfigView { + /// Base cost of creating an account. + pub create_account_cost: Fee, + + /// Base cost of deploying a contract. + pub deploy_contract_cost: Fee, + /// Cost per byte of deploying a contract. + pub deploy_contract_cost_per_byte: Fee, + + /// Base cost of calling a function. + pub function_call_cost: Fee, + /// Cost per byte of method name and arguments of calling a function. + pub function_call_cost_per_byte: Fee, + + /// Base cost of making a transfer. + pub transfer_cost: Fee, + + /// Base cost of staking. + pub stake_cost: Fee, + + /// Base cost of adding a key. + pub add_key_cost: AccessKeyCreationConfigView, + + /// Base cost of deleting a key. + pub delete_key_cost: Fee, + + /// Base cost of deleting an account. + pub delete_account_cost: Fee, +} + +/// Describes the cost of creating an access key. +#[derive(Debug, Serialize, Deserialize, Clone, Hash, PartialEq, Eq)] +pub struct AccessKeyCreationConfigView { + /// Base cost of creating a full access access-key. + pub full_access_cost: Fee, + /// Base cost of creating an access-key restricted to specific functions. + pub function_call_cost: Fee, + /// Cost per byte of method_names of creating a restricted access-key. + pub function_call_cost_per_byte: Fee, +} + +/// Describes cost of storage per block +#[derive(Debug, Serialize, Deserialize, Clone, Hash, PartialEq, Eq)] +pub struct StorageUsageConfigView { + /// Number of bytes for an account record, including rounding up for account id. + pub num_bytes_account: u64, + /// Additional number of bytes for a k/v record + pub num_extra_bytes_record: u64, +} + +impl From for RuntimeConfigView { + fn from(config: RuntimeConfig) -> Self { + Self { + storage_amount_per_byte: config.storage_amount_per_byte(), + transaction_costs: RuntimeFeesConfigView { + action_receipt_creation_config: config + .fees + .fee(ActionCosts::new_action_receipt) + .clone(), + data_receipt_creation_config: DataReceiptCreationConfigView { + base_cost: config.fees.fee(ActionCosts::new_data_receipt_base).clone(), + cost_per_byte: config.fees.fee(ActionCosts::new_data_receipt_byte).clone(), + }, + action_creation_config: ActionCreationConfigView { + create_account_cost: config.fees.fee(ActionCosts::create_account).clone(), + deploy_contract_cost: config + .fees + .fee(ActionCosts::deploy_contract_base) + .clone(), + deploy_contract_cost_per_byte: config + .fees + .fee(ActionCosts::deploy_contract_byte) + .clone(), + function_call_cost: config.fees.fee(ActionCosts::function_call_base).clone(), + function_call_cost_per_byte: config + .fees + .fee(ActionCosts::function_call_byte) + .clone(), + transfer_cost: config.fees.fee(ActionCosts::transfer).clone(), + stake_cost: config.fees.fee(ActionCosts::stake).clone(), + add_key_cost: AccessKeyCreationConfigView { + full_access_cost: config.fees.fee(ActionCosts::add_full_access_key).clone(), + function_call_cost: config + .fees + .fee(ActionCosts::add_function_call_key_base) + .clone(), + function_call_cost_per_byte: config + .fees + .fee(ActionCosts::add_function_call_key_byte) + .clone(), + }, + delete_key_cost: config.fees.fee(ActionCosts::delete_key).clone(), + delete_account_cost: config.fees.fee(ActionCosts::delete_account).clone(), + }, + storage_usage_config: StorageUsageConfigView { + num_bytes_account: config.fees.storage_usage_config.num_bytes_account, + num_extra_bytes_record: config.fees.storage_usage_config.num_extra_bytes_record, + }, + burnt_gas_reward: config.fees.burnt_gas_reward, + pessimistic_gas_price_inflation_ratio: config + .fees + .pessimistic_gas_price_inflation_ratio, + }, + wasm_config: config.wasm_config, + account_creation_config: AccountCreationConfigView { + min_allowed_top_level_account_length: config + .account_creation_config + .min_allowed_top_level_account_length, + registrar_account_id: config.account_creation_config.registrar_account_id, + }, + } + } +} + +// reverse direction: rosetta adapter uses this, also we use to test that all fields are present in view (TODO) +impl From for RuntimeConfig { + fn from(config: RuntimeConfigView) -> Self { + Self { + fees: near_primitives_core::runtime::fees::RuntimeFeesConfig { + storage_usage_config: near_primitives_core::runtime::fees::StorageUsageConfig { + storage_amount_per_byte: config.storage_amount_per_byte, + num_bytes_account: config + .transaction_costs + .storage_usage_config + .num_bytes_account, + num_extra_bytes_record: config + .transaction_costs + .storage_usage_config + .num_extra_bytes_record, + }, + burnt_gas_reward: config.transaction_costs.burnt_gas_reward, + pessimistic_gas_price_inflation_ratio: config + .transaction_costs + .pessimistic_gas_price_inflation_ratio, + action_fees: enum_map::enum_map! { + ActionCosts::create_account => config.transaction_costs.action_creation_config.create_account_cost.clone(), + ActionCosts::delete_account => config.transaction_costs.action_creation_config.delete_account_cost.clone(), + ActionCosts::deploy_contract_base => config.transaction_costs.action_creation_config.deploy_contract_cost.clone(), + ActionCosts::deploy_contract_byte => config.transaction_costs.action_creation_config.deploy_contract_cost_per_byte.clone(), + ActionCosts::function_call_base => config.transaction_costs.action_creation_config.function_call_cost.clone(), + ActionCosts::function_call_byte => config.transaction_costs.action_creation_config.function_call_cost_per_byte.clone(), + ActionCosts::transfer => config.transaction_costs.action_creation_config.transfer_cost.clone(), + ActionCosts::stake => config.transaction_costs.action_creation_config.stake_cost.clone(), + ActionCosts::add_full_access_key => config.transaction_costs.action_creation_config.add_key_cost.full_access_cost.clone(), + ActionCosts::add_function_call_key_base => config.transaction_costs.action_creation_config.add_key_cost.function_call_cost.clone(), + ActionCosts::add_function_call_key_byte => config.transaction_costs.action_creation_config.add_key_cost.function_call_cost_per_byte.clone(), + ActionCosts::delete_key => config.transaction_costs.action_creation_config.delete_key_cost.clone(), + ActionCosts::new_action_receipt => config.transaction_costs.action_receipt_creation_config.clone(), + ActionCosts::new_data_receipt_base => config.transaction_costs.data_receipt_creation_config.base_cost.clone(), + ActionCosts::new_data_receipt_byte => config.transaction_costs.data_receipt_creation_config.cost_per_byte.clone(), + + }, + }, + wasm_config: config.wasm_config, + account_creation_config: crate::runtime::config::AccountCreationConfig { + min_allowed_top_level_account_length: config + .account_creation_config + .min_allowed_top_level_account_length, + registrar_account_id: config.account_creation_config.registrar_account_id, + }, + } + } +} diff --git a/integration-tests/src/node/runtime_node.rs b/integration-tests/src/node/runtime_node.rs index 10abf2a0482..cce1b8db24c 100644 --- a/integration-tests/src/node/runtime_node.rs +++ b/integration-tests/src/node/runtime_node.rs @@ -109,8 +109,7 @@ mod tests { let (alice1, bob1) = (node.view_balance(&alice).unwrap(), node.view_balance(&bob).unwrap()); node_user.send_money(alice.clone(), bob.clone(), 1).unwrap(); let runtime_config = node.client.as_ref().read().unwrap().runtime_config.clone(); - let fee_helper = - FeeHelper::new(runtime_config.transaction_costs, node.genesis().config.min_gas_price); + let fee_helper = FeeHelper::new(runtime_config.fees, node.genesis().config.min_gas_price); let transfer_cost = fee_helper.transfer_cost(); let (alice2, bob2) = (node.view_balance(&alice).unwrap(), node.view_balance(&bob).unwrap()); assert_eq!(alice2, alice1 - 1 - transfer_cost); diff --git a/integration-tests/src/tests/client/process_blocks.rs b/integration-tests/src/tests/client/process_blocks.rs index 235ad273ded..b9e5508e155 100644 --- a/integration-tests/src/tests/client/process_blocks.rs +++ b/integration-tests/src/tests/client/process_blocks.rs @@ -9,6 +9,7 @@ use assert_matches::assert_matches; use futures::{future, FutureExt}; use near_chain::test_utils::ValidatorSchedule; use near_chunks::test_utils::MockClientAdapterForShardsManager; +use near_primitives::config::ActionCosts; use near_primitives::num_rational::{Ratio, Rational32}; use near_actix_test_utils::run_actix; @@ -1850,13 +1851,12 @@ fn test_gas_price_change() { init_test_logger(); let mut genesis = Genesis::test(vec!["test0".parse().unwrap(), "test1".parse().unwrap()], 1); let target_num_tokens_left = NEAR_BASE / 10 + 1; - let transaction_costs = RuntimeConfig::test().transaction_costs; + let transaction_costs = RuntimeConfig::test().fees; - let send_money_total_gas = - transaction_costs.action_creation_config.transfer_cost.send_fee(false) - + transaction_costs.action_receipt_creation_config.send_fee(false) - + transaction_costs.action_creation_config.transfer_cost.exec_fee() - + transaction_costs.action_receipt_creation_config.exec_fee(); + let send_money_total_gas = transaction_costs.fee(ActionCosts::transfer).send_fee(false) + + transaction_costs.fee(ActionCosts::new_action_receipt).send_fee(false) + + transaction_costs.fee(ActionCosts::transfer).exec_fee() + + transaction_costs.fee(ActionCosts::new_action_receipt).exec_fee(); let min_gas_price = target_num_tokens_left / send_money_total_gas as u128; let gas_limit = 1000000000000; let gas_price_adjustment_rate = Ratio::new(1, 10); @@ -2647,10 +2647,9 @@ fn test_execution_metadata() { let config = RuntimeConfigStore::test().get_config(PROTOCOL_VERSION).clone(); // Total costs for creating a function call receipt. - let expected_receipt_cost = config.transaction_costs.action_receipt_creation_config.execution - + config.transaction_costs.action_creation_config.function_call_cost.exec_fee() - + config.transaction_costs.action_creation_config.function_call_cost_per_byte.exec_fee() - * "main".len() as u64; + let expected_receipt_cost = config.fees.fee(ActionCosts::new_action_receipt).execution + + config.fees.fee(ActionCosts::function_call_base).exec_fee() + + config.fees.fee(ActionCosts::function_call_byte).exec_fee() * "main".len() as u64; // Profile for what's happening *inside* wasm vm during function call. let expected_profile = serde_json::json!([ diff --git a/integration-tests/src/tests/nearcore/rpc_nodes.rs b/integration-tests/src/tests/nearcore/rpc_nodes.rs index 7c212f3d27f..0bec8c9a655 100644 --- a/integration-tests/src/tests/nearcore/rpc_nodes.rs +++ b/integration-tests/src/tests/nearcore/rpc_nodes.rs @@ -22,7 +22,8 @@ use near_primitives::types::{ BlockId, BlockReference, EpochId, EpochReference, Finality, TransactionOrReceiptId, }; use near_primitives::version::ProtocolVersion; -use near_primitives::views::{ExecutionOutcomeView, ExecutionStatusView}; +use near_primitives::views::{ExecutionOutcomeView, ExecutionStatusView, RuntimeConfigView}; +use node_runtime::config::RuntimeConfig; use std::time::Duration; #[test] @@ -250,10 +251,16 @@ fn test_protocol_config_rpc() { let latest_runtime_config = runtime_config_store.get_config(ProtocolVersion::MAX); assert_ne!( config_response.config_view.runtime_config.storage_amount_per_byte, - intial_runtime_config.storage_amount_per_byte + intial_runtime_config.storage_amount_per_byte() ); + // compare JSON view assert_eq!( - config_response.config_view.runtime_config, + serde_json::json!(config_response.config_view.runtime_config), + serde_json::json!(RuntimeConfigView::from(latest_runtime_config.as_ref().clone())) + ); + // compare struct used by runtime + assert_eq!( + RuntimeConfig::from(config_response.config_view.runtime_config), latest_runtime_config.as_ref().clone() ); System::current().stop(); diff --git a/integration-tests/src/tests/runtime/deployment.rs b/integration-tests/src/tests/runtime/deployment.rs index f4fc1874c20..ccc0b6d5283 100644 --- a/integration-tests/src/tests/runtime/deployment.rs +++ b/integration-tests/src/tests/runtime/deployment.rs @@ -39,7 +39,7 @@ fn test_deploy_max_size_contract() { let max_transaction_size = config.wasm_config.limit_config.max_transaction_size; let contract_size = max_contract_size.min(max_transaction_size - tx_overhead); // Enough token to store contract + 1 NEAR for account - let token_balance = config.storage_amount_per_byte * contract_size as u128 + ONE_NEAR; + let token_balance = config.storage_amount_per_byte() * contract_size as u128 + ONE_NEAR; // Create test account let transaction_result = node_user diff --git a/integration-tests/src/tests/standard_cases/mod.rs b/integration-tests/src/tests/standard_cases/mod.rs index 6648baa6ae9..3d54381d98c 100644 --- a/integration-tests/src/tests/standard_cases/mod.rs +++ b/integration-tests/src/tests/standard_cases/mod.rs @@ -7,6 +7,7 @@ use assert_matches::assert_matches; use near_crypto::{InMemorySigner, KeyType}; use near_jsonrpc_primitives::errors::ServerError; use near_primitives::account::{AccessKey, AccessKeyPermission, FunctionCallPermission}; +use near_primitives::config::ActionCosts; use near_primitives::errors::{ ActionError, ActionErrorKind, InvalidAccessKeyError, InvalidTxError, TxExecutionError, }; @@ -34,7 +35,7 @@ use testlib::runtime_utils::{ const FUNCTION_CALL_AMOUNT: Balance = TESTING_INIT_BALANCE / 10; fn fee_helper(node: &impl Node) -> FeeHelper { - FeeHelper::new(RuntimeConfig::test().transaction_costs, node.genesis().config.min_gas_price) + FeeHelper::new(RuntimeConfig::test().fees, node.genesis().config.min_gas_price) } /// Adds given access key to the given account_id using signer2. @@ -403,12 +404,10 @@ pub fn trying_to_create_implicit_account(node: impl Node) { let cost = fee_helper.create_account_transfer_full_key_cost_fail_on_create_account() + fee_helper.gas_to_balance( - fee_helper.cfg.action_creation_config.create_account_cost.send_fee(false) + fee_helper.cfg.fee(ActionCosts::create_account).send_fee(false) + fee_helper .cfg - .action_creation_config - .add_key_cost - .full_access_cost + .fee(near_primitives::config::ActionCosts::add_full_access_key) .send_fee(false), ); diff --git a/integration-tests/src/tests/standard_cases/runtime.rs b/integration-tests/src/tests/standard_cases/runtime.rs index 1fdf3fed165..72a5b5a816f 100644 --- a/integration-tests/src/tests/standard_cases/runtime.rs +++ b/integration-tests/src/tests/standard_cases/runtime.rs @@ -19,7 +19,7 @@ fn create_runtime_with_expensive_storage() -> RuntimeNode { add_test_contract(&mut genesis, &bob_account()); // Set expensive state requirements and add alice more money. let mut runtime_config = RuntimeConfig::test(); - runtime_config.storage_amount_per_byte = TESTING_INIT_BALANCE / 1000; + runtime_config.fees.storage_usage_config.storage_amount_per_byte = TESTING_INIT_BALANCE / 1000; let records = genesis.force_read_records().as_mut(); match &mut records[0] { StateRecord::Account { account, .. } => account.set_amount(TESTING_INIT_BALANCE * 10000), diff --git a/integration-tests/src/tests/test_errors.rs b/integration-tests/src/tests/test_errors.rs index e17fea16d12..ab27eb833f0 100644 --- a/integration-tests/src/tests/test_errors.rs +++ b/integration-tests/src/tests/test_errors.rs @@ -67,7 +67,7 @@ fn test_deliver_tx_error_log() { let runtime_config_store = RuntimeConfigStore::new(None); let runtime_config = runtime_config_store.get_config(PROTOCOL_VERSION); let fee_helper = testlib::fees_utils::FeeHelper::new( - runtime_config.transaction_costs.clone(), + runtime_config.fees.clone(), node.genesis().config.min_gas_price, ); let signer = diff --git a/nearcore/tests/economics.rs b/nearcore/tests/economics.rs index 48841d218fa..79d5adad625 100644 --- a/nearcore/tests/economics.rs +++ b/nearcore/tests/economics.rs @@ -62,7 +62,7 @@ fn test_burn_mint() { .get_protocol_config(&EpochId::default()) .unwrap() .runtime_config - .transaction_costs; + .fees; let fee_helper = FeeHelper::new(transaction_costs, genesis.config.min_gas_price); let signer = InMemorySigner::from_seed("test0".parse().unwrap(), KeyType::ED25519, "test0"); let initial_total_supply = env.chain_genesis.total_supply; diff --git a/runtime/near-vm-logic/src/gas_counter.rs b/runtime/near-vm-logic/src/gas_counter.rs index ae0363f1aaa..1284c01e935 100644 --- a/runtime/near-vm-logic/src/gas_counter.rs +++ b/runtime/near-vm-logic/src/gas_counter.rs @@ -2,7 +2,6 @@ use crate::{HostError, VMLogicError}; use near_primitives::types::TrieNodesCount; use near_primitives_core::config::ExtCosts::read_cached_trie_node; use near_primitives_core::config::ExtCosts::touching_trie_node; -use near_primitives_core::runtime::fees::Fee; use near_primitives_core::{ config::{ActionCosts, ExtCosts, ExtCostsConfig}, profile::ProfileData, @@ -214,48 +213,6 @@ impl GasCounter { self.burn_gas(base_fee) } - /// A helper function to pay per byte gas fee for batching an action. - /// # Args: - /// * `per_byte_fee`: the fee per byte; - /// * `num_bytes`: the number of bytes; - /// * `sir`: whether the receiver_id is same as the current account ID; - /// * `action`: what kind of action is charged for; - pub fn pay_action_per_byte( - &mut self, - per_byte_fee: &Fee, - num_bytes: u64, - sir: bool, - action: ActionCosts, - ) -> Result<()> { - let burn_gas = - num_bytes.checked_mul(per_byte_fee.send_fee(sir)).ok_or(HostError::IntegerOverflow)?; - let use_gas = burn_gas - .checked_add( - num_bytes.checked_mul(per_byte_fee.exec_fee()).ok_or(HostError::IntegerOverflow)?, - ) - .ok_or(HostError::IntegerOverflow)?; - self.update_profile_action(action, burn_gas); - self.deduct_gas(burn_gas, use_gas) - } - - /// A helper function to pay base cost gas fee for batching an action. - /// # Args: - /// * `base_fee`: base fee for the action; - /// * `sir`: whether the receiver_id is same as the current account ID; - /// * `action`: what kind of action is charged for; - pub fn pay_action_base( - &mut self, - base_fee: &Fee, - sir: bool, - action: ActionCosts, - ) -> Result<()> { - let burn_gas = base_fee.send_fee(sir); - let use_gas = - burn_gas.checked_add(base_fee.exec_fee()).ok_or(HostError::IntegerOverflow)?; - self.update_profile_action(action, burn_gas); - self.deduct_gas(burn_gas, use_gas) - } - /// A helper function to pay base cost gas fee for batching an action. /// # Args: /// * `burn_gas`: amount of gas to burn; diff --git a/runtime/near-vm-logic/src/logic.rs b/runtime/near-vm-logic/src/logic.rs index c7427e2c4da..f545ebc7015 100644 --- a/runtime/near-vm-logic/src/logic.rs +++ b/runtime/near-vm-logic/src/logic.rs @@ -9,13 +9,12 @@ use byteorder::ByteOrder; use near_crypto::Secp256K1Signature; use near_primitives::checked_feature; use near_primitives::config::ViewConfig; +use near_primitives::runtime::fees::RuntimeFeesConfig; use near_primitives::version::is_implicit_account_creation_enabled; use near_primitives_core::config::ExtCosts::*; use near_primitives_core::config::{ActionCosts, ExtCosts, VMConfig}; use near_primitives_core::profile::ProfileData; -use near_primitives_core::runtime::fees::{ - transfer_exec_fee, transfer_send_fee, RuntimeFeesConfig, -}; +use near_primitives_core::runtime::fees::{transfer_exec_fee, transfer_send_fee}; use near_primitives_core::types::{ AccountId, Balance, EpochHeight, Gas, ProtocolVersion, StorageUsage, }; @@ -33,7 +32,7 @@ pub struct VMLogic<'a> { ext: &'a mut dyn External, /// Part of Context API and Economics API that was extracted from the receipt. context: VMContext, - /// Parameters of Wasm and economic parameters. + /// All gas and economic parameters required during contract execution. config: &'a VMConfig, /// Fees for creating (async) actions on runtime. fees_config: &'a RuntimeFeesConfig, @@ -1224,19 +1223,16 @@ impl<'a> VMLogic<'a> { /// pay for the content transmitted through the dependency upon the actual creation of the /// DataReceipt. fn pay_gas_for_new_receipt(&mut self, sir: bool, data_dependencies: &[bool]) -> Result<()> { - let fees_config_cfg = &self.fees_config; - self.gas_counter.pay_action_base( - &fees_config_cfg.action_receipt_creation_config, - sir, - ActionCosts::new_action_receipt, - )?; + self.pay_action_base(ActionCosts::new_action_receipt, sir)?; let mut burn_gas = 0u64; for dep in data_dependencies { // Both creation and execution for data receipts are considered burnt gas. burn_gas = burn_gas - .checked_add(fees_config_cfg.data_receipt_creation_config.base_cost.send_fee(*dep)) + .checked_add( + self.fees_config.fee(ActionCosts::new_data_receipt_base).send_fee(*dep), + ) .ok_or(HostError::IntegerOverflow)? - .checked_add(fees_config_cfg.data_receipt_creation_config.base_cost.exec_fee()) + .checked_add(self.fees_config.fee(ActionCosts::new_data_receipt_base).exec_fee()) .ok_or(HostError::IntegerOverflow)?; } self.gas_counter.pay_action_accumulated( @@ -1564,11 +1560,7 @@ impl<'a> VMLogic<'a> { } let (receipt_idx, sir) = self.promise_idx_to_receipt_idx_with_sir(promise_idx)?; - self.gas_counter.pay_action_base( - &self.fees_config.action_creation_config.create_account_cost, - sir, - ActionCosts::create_account, - )?; + self.pay_action_base(ActionCosts::create_account, sir)?; self.receipt_manager.append_action_create_account(receipt_idx)?; Ok(()) @@ -1616,17 +1608,8 @@ impl<'a> VMLogic<'a> { let (receipt_idx, sir) = self.promise_idx_to_receipt_idx_with_sir(promise_idx)?; let num_bytes = code.len() as u64; - self.gas_counter.pay_action_base( - &self.fees_config.action_creation_config.deploy_contract_cost, - sir, - ActionCosts::deploy_contract_base, - )?; - self.gas_counter.pay_action_per_byte( - &self.fees_config.action_creation_config.deploy_contract_cost_per_byte, - num_bytes, - sir, - ActionCosts::deploy_contract_byte, - )?; + self.pay_action_base(ActionCosts::deploy_contract_base, sir)?; + self.pay_action_per_byte(ActionCosts::deploy_contract_byte, num_bytes, sir)?; self.receipt_manager.append_action_deploy_contract(receipt_idx, code)?; Ok(()) @@ -1737,17 +1720,8 @@ impl<'a> VMLogic<'a> { // Input can't be large enough to overflow let num_bytes = method_name.len() as u64 + arguments.len() as u64; - self.gas_counter.pay_action_base( - &self.fees_config.action_creation_config.function_call_cost, - sir, - ActionCosts::function_call_base, - )?; - self.gas_counter.pay_action_per_byte( - &self.fees_config.action_creation_config.function_call_cost_per_byte, - num_bytes, - sir, - ActionCosts::function_call_byte, - )?; + self.pay_action_base(ActionCosts::function_call_base, sir)?; + self.pay_action_per_byte(ActionCosts::function_call_byte, num_bytes, sir)?; // Prepaid gas self.gas_counter.prepay_gas(gas)?; @@ -1799,10 +1773,8 @@ impl<'a> VMLogic<'a> { is_implicit_account_creation_enabled(self.current_protocol_version) && receiver_id.is_implicit(); - let send_fee = - transfer_send_fee(&self.fees_config.action_creation_config, sir, is_receiver_implicit); - let exec_fee = - transfer_exec_fee(&self.fees_config.action_creation_config, is_receiver_implicit); + let send_fee = transfer_send_fee(self.fees_config, sir, is_receiver_implicit); + let exec_fee = transfer_exec_fee(self.fees_config, is_receiver_implicit); let burn_gas = send_fee; let use_gas = burn_gas.checked_add(exec_fee).ok_or(HostError::IntegerOverflow)?; self.gas_counter.pay_action_accumulated(burn_gas, use_gas, ActionCosts::transfer)?; @@ -1848,13 +1820,7 @@ impl<'a> VMLogic<'a> { let public_key = self.get_vec_from_memory_or_register(public_key_ptr, public_key_len)?; let (receipt_idx, sir) = self.promise_idx_to_receipt_idx_with_sir(promise_idx)?; - - self.gas_counter.pay_action_base( - &self.fees_config.action_creation_config.stake_cost, - sir, - ActionCosts::stake, - )?; - + self.pay_action_base(ActionCosts::stake, sir)?; self.receipt_manager.append_action_stake(receipt_idx, amount, public_key)?; Ok(()) } @@ -1893,13 +1859,7 @@ impl<'a> VMLogic<'a> { let public_key = self.get_vec_from_memory_or_register(public_key_ptr, public_key_len)?; let (receipt_idx, sir) = self.promise_idx_to_receipt_idx_with_sir(promise_idx)?; - - self.gas_counter.pay_action_base( - &self.fees_config.action_creation_config.add_key_cost.full_access_cost, - sir, - ActionCosts::add_full_access_key, - )?; - + self.pay_action_base(ActionCosts::add_full_access_key, sir)?; self.receipt_manager.append_action_add_key_with_full_access( receipt_idx, public_key, @@ -1958,17 +1918,8 @@ impl<'a> VMLogic<'a> { // +1 is to account for null-terminating characters. let num_bytes = method_names.iter().map(|v| v.len() as u64 + 1).sum::(); - self.gas_counter.pay_action_base( - &self.fees_config.action_creation_config.add_key_cost.function_call_cost, - sir, - ActionCosts::add_function_call_key_base, - )?; - self.gas_counter.pay_action_per_byte( - &self.fees_config.action_creation_config.add_key_cost.function_call_cost_per_byte, - num_bytes, - sir, - ActionCosts::add_function_call_key_byte, - )?; + self.pay_action_base(ActionCosts::add_function_call_key_base, sir)?; + self.pay_action_per_byte(ActionCosts::add_function_call_key_byte, num_bytes, sir)?; self.receipt_manager.append_action_add_key_with_function_call( receipt_idx, @@ -2014,13 +1965,7 @@ impl<'a> VMLogic<'a> { let public_key = self.get_vec_from_memory_or_register(public_key_ptr, public_key_len)?; let (receipt_idx, sir) = self.promise_idx_to_receipt_idx_with_sir(promise_idx)?; - - self.gas_counter.pay_action_base( - &self.fees_config.action_creation_config.delete_key_cost, - sir, - ActionCosts::delete_key, - )?; - + self.pay_action_base(ActionCosts::delete_key, sir)?; self.receipt_manager.append_action_delete_key(receipt_idx, public_key)?; Ok(()) } @@ -2058,11 +2003,7 @@ impl<'a> VMLogic<'a> { self.read_and_parse_account_id(beneficiary_id_ptr, beneficiary_id_len)?; let (receipt_idx, sir) = self.promise_idx_to_receipt_idx_with_sir(promise_idx)?; - self.gas_counter.pay_action_base( - &self.fees_config.action_creation_config.delete_account_cost, - sir, - ActionCosts::delete_account, - )?; + self.pay_action_base(ActionCosts::delete_account, sir)?; self.receipt_manager.append_action_delete_account(receipt_idx, beneficiary_id)?; Ok(()) @@ -2195,7 +2136,6 @@ impl<'a> VMLogic<'a> { } .into()); } - let data_cfg = &self.fees_config.data_receipt_creation_config; for data_receiver in &self.context.output_data_receivers { let sir = data_receiver == &self.context.current_account_id; // We deduct for execution here too, because if we later have an OR combinator @@ -2207,10 +2147,12 @@ impl<'a> VMLogic<'a> { // The gas here is considered burnt, cause we'll prepay for it upfront. burn_gas = burn_gas .checked_add( - data_cfg - .cost_per_byte + self.fees_config + .fee(ActionCosts::new_data_receipt_byte) .send_fee(sir) - .checked_add(data_cfg.cost_per_byte.exec_fee()) + .checked_add( + self.fees_config.fee(ActionCosts::new_data_receipt_byte).exec_fee(), + ) .ok_or(HostError::IntegerOverflow)? .checked_mul(num_bytes) .ok_or(HostError::IntegerOverflow)?, @@ -2825,6 +2767,33 @@ impl<'a> VMLogic<'a> { self.gas_counter.process_gas_limit(new_burn_gas, new_used_gas) } + /// A helper function to pay base cost gas fee for batching an action. + pub fn pay_action_base(&mut self, action: ActionCosts, sir: bool) -> Result<()> { + let base_fee = self.fees_config.fee(action); + let burn_gas = base_fee.send_fee(sir); + let use_gas = + burn_gas.checked_add(base_fee.exec_fee()).ok_or(HostError::IntegerOverflow)?; + self.gas_counter.pay_action_accumulated(burn_gas, use_gas, action) + } + + /// A helper function to pay per byte gas fee for batching an action. + pub fn pay_action_per_byte( + &mut self, + action: ActionCosts, + num_bytes: u64, + sir: bool, + ) -> Result<()> { + let per_byte_fee = self.fees_config.fee(action); + let burn_gas = + num_bytes.checked_mul(per_byte_fee.send_fee(sir)).ok_or(HostError::IntegerOverflow)?; + let use_gas = burn_gas + .checked_add( + num_bytes.checked_mul(per_byte_fee.exec_fee()).ok_or(HostError::IntegerOverflow)?, + ) + .ok_or(HostError::IntegerOverflow)?; + self.gas_counter.pay_action_accumulated(burn_gas, use_gas, action) + } + /// VM independent setup before loading the executable. /// /// Does VM independent checks that happen after the instantiation of diff --git a/runtime/runtime-params-estimator/Cargo.toml b/runtime/runtime-params-estimator/Cargo.toml index 2a598fed4f0..368ec24d863 100644 --- a/runtime/runtime-params-estimator/Cargo.toml +++ b/runtime/runtime-params-estimator/Cargo.toml @@ -17,6 +17,7 @@ bytesize.workspace = true cfg-if.workspace = true chrono.workspace = true clap.workspace = true +enum-map.workspace = true hex.workspace = true indicatif.workspace = true libc.workspace = true diff --git a/runtime/runtime-params-estimator/src/costs_to_runtime_config.rs b/runtime/runtime-params-estimator/src/costs_to_runtime_config.rs index f6b65d0241d..3318afd4a98 100644 --- a/runtime/runtime-params-estimator/src/costs_to_runtime_config.rs +++ b/runtime/runtime-params-estimator/src/costs_to_runtime_config.rs @@ -1,12 +1,9 @@ use near_primitives::runtime::config::AccountCreationConfig; use near_primitives::runtime::config_store::RuntimeConfigStore; -use near_primitives::runtime::fees::{ - AccessKeyCreationConfig, ActionCreationConfig, DataReceiptCreationConfig, Fee, - RuntimeFeesConfig, -}; +use near_primitives::runtime::fees::{Fee, RuntimeFeesConfig}; use near_primitives::types::Gas; use near_primitives::version::PROTOCOL_VERSION; -use near_vm_logic::{ExtCostsConfig, VMConfig}; +use near_vm_logic::{ActionCosts, ExtCostsConfig, VMConfig}; use node_runtime::config::RuntimeConfig; use anyhow::Context; @@ -33,9 +30,7 @@ pub fn costs_to_runtime_config(cost_table: &CostTable) -> anyhow::Result anyhow::Result fee(Cost::ActionCreateAccount)?, + ActionCosts::delete_account => fee(Cost::ActionDeleteAccount)?, + ActionCosts::deploy_contract_base => fee(Cost::ActionDeployContractBase)?, + ActionCosts::deploy_contract_byte => fee(Cost::ActionDeployContractPerByte)?, + ActionCosts::function_call_base => fee(Cost::ActionFunctionCallBase)?, + ActionCosts::function_call_byte => fee(Cost::ActionFunctionCallPerByte)?, + ActionCosts::transfer => fee(Cost::ActionTransfer)?, + ActionCosts::stake => fee(Cost::ActionStake)?, + ActionCosts::add_full_access_key => fee(Cost::ActionAddFullAccessKey)?, + ActionCosts::add_function_call_key_base => fee(Cost::ActionAddFunctionAccessKeyBase)?, + ActionCosts::add_function_call_key_byte => fee(Cost::ActionAddFunctionAccessKeyPerByte)?, + ActionCosts::delete_key => fee(Cost::ActionDeleteKey)?, + ActionCosts::new_action_receipt => fee(Cost::ActionReceiptCreation)?, + ActionCosts::new_data_receipt_base => fee(Cost::DataReceiptCreationBase)?, + ActionCosts::new_data_receipt_byte => fee(Cost::DataReceiptCreationPerByte)?, }, ..actual_fees_config.clone() }; diff --git a/runtime/runtime-params-estimator/src/function_call.rs b/runtime/runtime-params-estimator/src/function_call.rs index 89a53a5b6ef..65d2ae1c981 100644 --- a/runtime/runtime-params-estimator/src/function_call.rs +++ b/runtime/runtime-params-estimator/src/function_call.rs @@ -70,7 +70,7 @@ fn compute_function_call_cost( let runtime_config = config_store.get_config(protocol_version).as_ref(); let vm_config = runtime_config.wasm_config.clone(); let runtime = vm_kind.runtime(vm_config).expect("runtime has not been enabled"); - let fees = runtime_config.transaction_costs.clone(); + let fees = runtime_config.fees.clone(); let mut fake_external = MockedExternal::new(); let fake_context = create_context(vec![]); let promise_results = vec![]; diff --git a/runtime/runtime-params-estimator/src/gas_metering.rs b/runtime/runtime-params-estimator/src/gas_metering.rs index f538e1c172a..a7fafac897f 100644 --- a/runtime/runtime-params-estimator/src/gas_metering.rs +++ b/runtime/runtime-params-estimator/src/gas_metering.rs @@ -135,7 +135,7 @@ pub(crate) fn compute_gas_metering_cost(config: &Config, contract: &ContractCode let vm_config_gas = runtime_config.wasm_config.clone(); let runtime = vm_kind.runtime(vm_config_gas).expect("runtime has not been enabled"); let runtime_free_gas = vm_kind.runtime(VMConfig::free()).expect("runtime has not been enabled"); - let fees = runtime_config.transaction_costs.clone(); + let fees = runtime_config.fees.clone(); let mut fake_external = MockedExternal::new(); let fake_context = create_context(vec![]); let promise_results = vec![]; diff --git a/runtime/runtime-params-estimator/src/main.rs b/runtime/runtime-params-estimator/src/main.rs index 67cda33f806..342e5b36a8f 100644 --- a/runtime/runtime-params-estimator/src/main.rs +++ b/runtime/runtime-params-estimator/src/main.rs @@ -5,6 +5,7 @@ use clap::Parser; use genesis_populate::GenesisBuilder; use near_chain_configs::GenesisValidationMode; use near_primitives::version::PROTOCOL_VERSION; +use near_primitives::views::RuntimeConfigView; use near_vm_runner::internal::VMKind; use replay::ReplayCmd; use runtime_params_estimator::config::{Config, GasMetric}; @@ -199,7 +200,8 @@ fn main() -> anyhow::Result<()> { println!("Generated RuntimeConfig:\n"); println!("{:#?}", runtime_config); - let str = serde_json::to_string_pretty(&runtime_config) + let config_view = RuntimeConfigView::from(runtime_config); + let str = serde_json::to_string_pretty(&config_view) .expect("Failed serializing the runtime config"); let output_path = state_dump_path.join("runtime_config.json"); diff --git a/runtime/runtime/Cargo.toml b/runtime/runtime/Cargo.toml index 326fd7cece9..08f6cd99f64 100644 --- a/runtime/runtime/Cargo.toml +++ b/runtime/runtime/Cargo.toml @@ -46,6 +46,7 @@ sandbox = ["near-vm-logic/sandbox", "near-vm-runner/sandbox"] [dev-dependencies] assert_matches.workspace = true +enum-map.workspace = true indicatif.workspace = true rayon.workspace = true serde_json.workspace = true diff --git a/runtime/runtime/src/actions.rs b/runtime/runtime/src/actions.rs index 25fd563ab57..59c93618da3 100644 --- a/runtime/runtime/src/actions.rs +++ b/runtime/runtime/src/actions.rs @@ -110,7 +110,7 @@ pub(crate) fn execute_function_call( runtime_ext, context, &config.wasm_config, - &config.transaction_costs, + &config.fees, promise_results, apply_state.current_protocol_version, apply_state.cache.as_deref(), @@ -605,7 +605,7 @@ pub(crate) fn action_add_key( &add_key.access_key, ); }; - let storage_config = &apply_state.config.transaction_costs.storage_usage_config; + let storage_config = &apply_state.config.fees.storage_usage_config; account.set_storage_usage( account .storage_usage() diff --git a/runtime/runtime/src/balance_checker.rs b/runtime/runtime/src/balance_checker.rs index 5c6cbfcf8e7..ca6feb59918 100644 --- a/runtime/runtime/src/balance_checker.rs +++ b/runtime/runtime/src/balance_checker.rs @@ -15,6 +15,7 @@ use near_primitives::trie_key::TrieKey; use near_primitives::types::{AccountId, Balance}; use near_primitives::version::ProtocolVersion; use near_store::{get, get_account, get_postponed_receipt, TrieAccess, TrieUpdate}; +use near_vm_logic::ActionCosts; use std::collections::HashSet; /// Returns delayed receipts with given range of indices. @@ -45,7 +46,7 @@ fn receipt_cost( let mut total_cost = total_deposit(&action_receipt.actions)?; if !AccountId::is_system(&receipt.predecessor_id) { let mut total_gas = safe_add_gas( - transaction_costs.action_receipt_creation_config.exec_fee(), + transaction_costs.fee(ActionCosts::new_action_receipt).exec_fee(), total_prepaid_exec_fees( transaction_costs, &action_receipt.actions, @@ -384,10 +385,10 @@ mod tests { let deposit = 500_000_000; let gas_price = 100; let cfg = RuntimeFeesConfig::test(); - let exec_gas = cfg.action_receipt_creation_config.exec_fee() - + cfg.action_creation_config.transfer_cost.exec_fee(); - let send_gas = cfg.action_receipt_creation_config.send_fee(false) - + cfg.action_creation_config.transfer_cost.send_fee(false); + let exec_gas = cfg.fee(ActionCosts::new_action_receipt).exec_fee() + + cfg.fee(ActionCosts::transfer).exec_fee(); + let send_gas = cfg.fee(ActionCosts::new_action_receipt).send_fee(false) + + cfg.fee(ActionCosts::transfer).send_fee(false); let contract_reward = send_gas as u128 * *cfg.burnt_gas_reward.numer() as u128 * gas_price / (*cfg.burnt_gas_reward.denom() as u128); let total_validator_reward = send_gas as Balance * gas_price - contract_reward; diff --git a/runtime/runtime/src/config.rs b/runtime/runtime/src/config.rs index ee133a8788b..a6e516e51c3 100644 --- a/runtime/runtime/src/config.rs +++ b/runtime/runtime/src/config.rs @@ -1,5 +1,6 @@ //! Settings of the parameters of the runtime. +use near_vm_logic::ActionCosts; use num_bigint::BigUint; use num_traits::cast::ToPrimitive; use num_traits::pow::Pow; @@ -74,31 +75,34 @@ pub fn total_send_fees( receiver_id: &AccountId, current_protocol_version: ProtocolVersion, ) -> Result { - let cfg = &config.action_creation_config; let mut result = 0; for action in actions { use Action::*; let delta = match action { - CreateAccount(_) => cfg.create_account_cost.send_fee(sender_is_receiver), + CreateAccount(_) => { + config.fee(ActionCosts::create_account).send_fee(sender_is_receiver) + } DeployContract(DeployContractAction { code }) => { let num_bytes = code.len() as u64; - cfg.deploy_contract_cost.send_fee(sender_is_receiver) - + cfg.deploy_contract_cost_per_byte.send_fee(sender_is_receiver) * num_bytes + config.fee(ActionCosts::deploy_contract_base).send_fee(sender_is_receiver) + + config.fee(ActionCosts::deploy_contract_byte).send_fee(sender_is_receiver) + * num_bytes } FunctionCall(FunctionCallAction { method_name, args, .. }) => { let num_bytes = method_name.as_bytes().len() as u64 + args.len() as u64; - cfg.function_call_cost.send_fee(sender_is_receiver) - + cfg.function_call_cost_per_byte.send_fee(sender_is_receiver) * num_bytes + config.fee(ActionCosts::function_call_base).send_fee(sender_is_receiver) + + config.fee(ActionCosts::function_call_byte).send_fee(sender_is_receiver) + * num_bytes } Transfer(_) => { // Account for implicit account creation let is_receiver_implicit = is_implicit_account_creation_enabled(current_protocol_version) && receiver_id.is_implicit(); - transfer_send_fee(cfg, sender_is_receiver, is_receiver_implicit) + transfer_send_fee(config, sender_is_receiver, is_receiver_implicit) } - Stake(_) => cfg.stake_cost.send_fee(sender_is_receiver), + Stake(_) => config.fee(ActionCosts::stake).send_fee(sender_is_receiver), AddKey(AddKeyAction { access_key, .. }) => match &access_key.permission { AccessKeyPermission::FunctionCall(call_perm) => { let num_bytes = call_perm @@ -107,19 +111,20 @@ pub fn total_send_fees( // Account for null-terminating characters. .map(|name| name.as_bytes().len() as u64 + 1) .sum::(); - cfg.add_key_cost.function_call_cost.send_fee(sender_is_receiver) + config.fee(ActionCosts::add_function_call_key_base).send_fee(sender_is_receiver) + num_bytes - * cfg - .add_key_cost - .function_call_cost_per_byte + * config + .fee(ActionCosts::add_function_call_key_byte) .send_fee(sender_is_receiver) } AccessKeyPermission::FullAccess => { - cfg.add_key_cost.full_access_cost.send_fee(sender_is_receiver) + config.fee(ActionCosts::add_full_access_key).send_fee(sender_is_receiver) } }, - DeleteKey(_) => cfg.delete_key_cost.send_fee(sender_is_receiver), - DeleteAccount(_) => cfg.delete_account_cost.send_fee(sender_is_receiver), + DeleteKey(_) => config.fee(ActionCosts::delete_key).send_fee(sender_is_receiver), + DeleteAccount(_) => { + config.fee(ActionCosts::delete_account).send_fee(sender_is_receiver) + } }; result = safe_add_gas(result, delta)?; } @@ -132,29 +137,28 @@ pub fn exec_fee( receiver_id: &AccountId, current_protocol_version: ProtocolVersion, ) -> Gas { - let cfg = &config.action_creation_config; use Action::*; match action { - CreateAccount(_) => cfg.create_account_cost.exec_fee(), + CreateAccount(_) => config.fee(ActionCosts::create_account).exec_fee(), DeployContract(DeployContractAction { code }) => { let num_bytes = code.len() as u64; - cfg.deploy_contract_cost.exec_fee() - + cfg.deploy_contract_cost_per_byte.exec_fee() * num_bytes + config.fee(ActionCosts::deploy_contract_base).exec_fee() + + config.fee(ActionCosts::deploy_contract_byte).exec_fee() * num_bytes } FunctionCall(FunctionCallAction { method_name, args, .. }) => { let num_bytes = method_name.as_bytes().len() as u64 + args.len() as u64; - cfg.function_call_cost.exec_fee() - + cfg.function_call_cost_per_byte.exec_fee() * num_bytes + config.fee(ActionCosts::function_call_base).exec_fee() + + config.fee(ActionCosts::function_call_byte).exec_fee() * num_bytes } Transfer(_) => { // Account for implicit account creation let is_receiver_implicit = is_implicit_account_creation_enabled(current_protocol_version) && receiver_id.is_implicit(); - transfer_exec_fee(cfg, is_receiver_implicit) + transfer_exec_fee(config, is_receiver_implicit) } - Stake(_) => cfg.stake_cost.exec_fee(), + Stake(_) => config.fee(ActionCosts::stake).exec_fee(), AddKey(AddKeyAction { access_key, .. }) => match &access_key.permission { AccessKeyPermission::FunctionCall(call_perm) => { let num_bytes = call_perm @@ -163,13 +167,15 @@ pub fn exec_fee( // Account for null-terminating characters. .map(|name| name.as_bytes().len() as u64 + 1) .sum::(); - cfg.add_key_cost.function_call_cost.exec_fee() - + num_bytes * cfg.add_key_cost.function_call_cost_per_byte.exec_fee() + config.fee(ActionCosts::add_function_call_key_base).exec_fee() + + num_bytes * config.fee(ActionCosts::add_function_call_key_byte).exec_fee() + } + AccessKeyPermission::FullAccess => { + config.fee(ActionCosts::add_full_access_key).exec_fee() } - AccessKeyPermission::FullAccess => cfg.add_key_cost.full_access_cost.exec_fee(), }, - DeleteKey(_) => cfg.delete_key_cost.exec_fee(), - DeleteAccount(_) => cfg.delete_account_cost.exec_fee(), + DeleteKey(_) => config.fee(ActionCosts::delete_key).exec_fee(), + DeleteAccount(_) => config.fee(ActionCosts::delete_account).exec_fee(), } } @@ -181,7 +187,8 @@ pub fn tx_cost( sender_is_receiver: bool, current_protocol_version: ProtocolVersion, ) -> Result { - let mut gas_burnt: Gas = config.action_receipt_creation_config.send_fee(sender_is_receiver); + let mut gas_burnt: Gas = + config.fee(ActionCosts::new_action_receipt).send_fee(sender_is_receiver); gas_burnt = safe_add_gas( gas_burnt, total_send_fees( @@ -213,7 +220,7 @@ pub fn tx_cost( }; let mut gas_remaining = - safe_add_gas(prepaid_gas, config.action_receipt_creation_config.exec_fee())?; + safe_add_gas(prepaid_gas, config.fee(ActionCosts::new_action_receipt).exec_fee())?; gas_remaining = safe_add_gas( gas_remaining, total_prepaid_exec_fees( diff --git a/runtime/runtime/src/genesis.rs b/runtime/runtime/src/genesis.rs index b1918d58bd1..7df1a6765c0 100644 --- a/runtime/runtime/src/genesis.rs +++ b/runtime/runtime/src/genesis.rs @@ -35,7 +35,7 @@ pub struct StorageComputer<'a> { impl<'a> StorageComputer<'a> { pub fn new(config: &'a RuntimeConfig) -> Self { - Self { result: HashMap::new(), config: &config.transaction_costs.storage_usage_config } + Self { result: HashMap::new(), config: &config.fees.storage_usage_config } } /// Updates user's storage info based on the StateRecord. diff --git a/runtime/runtime/src/lib.rs b/runtime/runtime/src/lib.rs index 2eec9cbe1a7..46411939900 100644 --- a/runtime/runtime/src/lib.rs +++ b/runtime/runtime/src/lib.rs @@ -49,7 +49,7 @@ use near_store::{ }; use near_store::{set_access_key, set_code}; use near_vm_logic::types::PromiseResult; -use near_vm_logic::ReturnData; +use near_vm_logic::{ActionCosts, ReturnData}; pub use near_vm_runner::with_ext_cost_counter; use crate::actions::*; @@ -303,7 +303,7 @@ impl Runtime { // println!("enter apply_action"); let mut result = ActionResult::default(); let exec_fees = exec_fee( - &apply_state.config.transaction_costs, + &apply_state.config.fees, action, &receipt.receiver_id, apply_state.current_protocol_version, @@ -334,7 +334,7 @@ impl Runtime { match action { Action::CreateAccount(_) => { action_create_account( - &apply_state.config.transaction_costs, + &apply_state.config.fees, &apply_state.config.account_creation_config, account, actor_id, @@ -390,7 +390,7 @@ impl Runtime { debug_assert!(!is_refund); action_implicit_account_creation_transfer( state_update, - &apply_state.config.transaction_costs, + &apply_state.config.fees, account, actor_id, &receipt.receiver_id, @@ -422,7 +422,7 @@ impl Runtime { } Action::DeleteKey(delete_key) => { action_delete_key( - &apply_state.config.transaction_costs, + &apply_state.config.fees, state_update, account.as_mut().expect(EXPECT_ACCOUNT_EXISTS), &mut result, @@ -494,8 +494,7 @@ 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.transaction_costs.action_receipt_creation_config.exec_fee(); + let exec_fee = apply_state.config.fees.fee(ActionCosts::new_action_receipt).exec_fee(); result.gas_used = exec_fee; result.gas_burnt = exec_fee; // Executing actions one by one @@ -587,7 +586,7 @@ impl Runtime { action_receipt, &mut result, apply_state.current_protocol_version, - &apply_state.config.transaction_costs, + &apply_state.config.fees, )? }; stats.gas_deficit_amount = safe_add_balance(stats.gas_deficit_amount, gas_deficit_amount)?; @@ -619,8 +618,8 @@ impl Runtime { // Adding burnt gas reward for function calls if the account exists. let receiver_gas_reward = result.gas_burnt_for_function_call - * *apply_state.config.transaction_costs.burnt_gas_reward.numer() as u64 - / *apply_state.config.transaction_costs.burnt_gas_reward.denom() as u64; + * *apply_state.config.fees.burnt_gas_reward.numer() as u64 + / *apply_state.config.fees.burnt_gas_reward.denom() as u64; // The balance that the current account should receive as a reward for function call // execution. let receiver_reward = safe_gas_to_balance(apply_state.gas_price, receiver_gas_reward)? @@ -756,7 +755,7 @@ impl Runtime { &receipt.receiver_id, current_protocol_version, )?, - transaction_costs.action_receipt_creation_config.exec_fee(), + transaction_costs.fee(ActionCosts::new_action_receipt).exec_fee(), )?; let deposit_refund = if result.result.is_err() { total_deposit } else { 0 }; let gas_refund = if result.result.is_err() { @@ -1361,7 +1360,7 @@ impl Runtime { } check_balance( - &apply_state.config.transaction_costs, + &apply_state.config.fees, &state_update, validator_accounts_update, incoming_receipts, @@ -1771,12 +1770,9 @@ mod tests { let (runtime, tries, mut root, mut apply_state, _, epoch_info_provider) = setup_runtime(initial_balance, initial_locked, 1); - let receipt_gas_cost = apply_state - .config - .transaction_costs - .action_receipt_creation_config - .exec_fee() - + apply_state.config.transaction_costs.action_creation_config.transfer_cost.exec_fee(); + let receipt_gas_cost = + apply_state.config.fees.fee(ActionCosts::new_action_receipt).exec_fee() + + apply_state.config.fees.fee(ActionCosts::transfer).exec_fee(); apply_state.gas_limit = Some(receipt_gas_cost * 3); let n = 40; @@ -1825,12 +1821,9 @@ mod tests { let (runtime, tries, mut root, mut apply_state, _, epoch_info_provider) = setup_runtime(initial_balance, initial_locked, 1); - let receipt_gas_cost = apply_state - .config - .transaction_costs - .action_receipt_creation_config - .exec_fee() - + apply_state.config.transaction_costs.action_creation_config.transfer_cost.exec_fee(); + let receipt_gas_cost = + apply_state.config.fees.fee(ActionCosts::new_action_receipt).exec_fee() + + apply_state.config.fees.fee(ActionCosts::transfer).exec_fee(); let n = 120; let receipts = generate_receipts(small_transfer, n); @@ -1927,7 +1920,7 @@ mod tests { let receipt_exec_gas_fee = 1000; let mut free_config = RuntimeConfig::free(); - free_config.transaction_costs.action_receipt_creation_config.execution = + free_config.fees.action_fees[ActionCosts::new_action_receipt].execution = receipt_exec_gas_fee; apply_state.config = Arc::new(free_config); // This allows us to execute 3 receipts per apply. @@ -2209,9 +2202,9 @@ mod tests { })]; let expected_gas_burnt = safe_add_gas( - apply_state.config.transaction_costs.action_receipt_creation_config.exec_fee(), + apply_state.config.fees.fee(ActionCosts::new_action_receipt).exec_fee(), total_prepaid_exec_fees( - &apply_state.config.transaction_costs, + &apply_state.config.fees, &actions, &alice_account(), PROTOCOL_VERSION, @@ -2278,9 +2271,9 @@ mod tests { })]; let expected_gas_burnt = safe_add_gas( - apply_state.config.transaction_costs.action_receipt_creation_config.exec_fee(), + apply_state.config.fees.fee(ActionCosts::new_action_receipt).exec_fee(), total_prepaid_exec_fees( - &apply_state.config.transaction_costs, + &apply_state.config.fees, &actions, &alice_account(), PROTOCOL_VERSION, diff --git a/runtime/runtime/src/verifier.rs b/runtime/runtime/src/verifier.rs index 9fa6369b21c..309bfb2fee6 100644 --- a/runtime/runtime/src/verifier.rs +++ b/runtime/runtime/src/verifier.rs @@ -60,14 +60,8 @@ pub fn validate_transaction( let sender_is_receiver = &transaction.receiver_id == signer_id; - tx_cost( - &config.transaction_costs, - transaction, - gas_price, - sender_is_receiver, - current_protocol_version, - ) - .map_err(|_| InvalidTxError::CostOverflow.into()) + tx_cost(&config.fees, transaction, gas_price, sender_is_receiver, current_protocol_version) + .map_err(|_| InvalidTxError::CostOverflow.into()) } /// Verifies the signed transaction on top of given state, charges transaction fees @@ -871,7 +865,7 @@ mod tests { #[test] fn test_validate_transaction_invalid_low_balance() { let mut config = RuntimeConfig::free(); - config.storage_amount_per_byte = 10_000_000; + config.fees.storage_usage_config.storage_amount_per_byte = 10_000_000; let initial_balance = 1_000_000_000; let transfer_amount = 950_000_000; let (signer, mut state_update, gas_price) = @@ -898,7 +892,7 @@ mod tests { RuntimeError::InvalidTxError(InvalidTxError::LackBalanceForState { signer_id: alice_account(), amount: Balance::from(std::mem::size_of::() as u64) - * config.storage_amount_per_byte + * config.storage_amount_per_byte() - (initial_balance - transfer_amount) }) ); diff --git a/runtime/runtime/tests/runtime_group_tools/mod.rs b/runtime/runtime/tests/runtime_group_tools/mod.rs index 4af1f170cff..5f97e03c304 100644 --- a/runtime/runtime/tests/runtime_group_tools/mod.rs +++ b/runtime/runtime/tests/runtime_group_tools/mod.rs @@ -12,6 +12,7 @@ use near_primitives::types::{AccountId, AccountInfo, Balance}; use near_primitives::version::PROTOCOL_VERSION; use near_store::test_utils::create_tries; use near_store::ShardTries; +use near_vm_logic::ActionCosts; use node_runtime::{ApplyState, Runtime}; use random_config::random_config; use std::collections::{HashMap, HashSet}; @@ -53,9 +54,9 @@ impl StandaloneRuntime { let mut runtime_config = random_config(); // Bumping costs to avoid inflation overflows. runtime_config.wasm_config.limit_config.max_total_prepaid_gas = 10u64.pow(15); - runtime_config.transaction_costs.action_receipt_creation_config.execution = + runtime_config.fees.action_fees[ActionCosts::new_action_receipt].execution = runtime_config.wasm_config.limit_config.max_total_prepaid_gas / 64; - runtime_config.transaction_costs.data_receipt_creation_config.base_cost.execution = + runtime_config.fees.action_fees[ActionCosts::new_data_receipt_base].execution = runtime_config.wasm_config.limit_config.max_total_prepaid_gas / 64; let runtime = Runtime::new(); diff --git a/runtime/runtime/tests/runtime_group_tools/random_config.rs b/runtime/runtime/tests/runtime_group_tools/random_config.rs index be0b3838e54..7b35087111c 100644 --- a/runtime/runtime/tests/runtime_group_tools/random_config.rs +++ b/runtime/runtime/tests/runtime_group_tools/random_config.rs @@ -1,9 +1,6 @@ use near_primitives::num_rational::Rational; use near_primitives::runtime::config::RuntimeConfig; -use near_primitives::runtime::fees::{ - AccessKeyCreationConfig, ActionCreationConfig, DataReceiptCreationConfig, Fee, - RuntimeFeesConfig, StorageUsageConfig, -}; +use near_primitives::runtime::fees::{Fee, RuntimeFeesConfig, StorageUsageConfig}; use rand::{thread_rng, RngCore}; pub fn random_config() -> RuntimeConfig { @@ -14,31 +11,14 @@ pub fn random_config() -> RuntimeConfig { execution: rng.next_u64() % 1000, }; RuntimeConfig { - transaction_costs: RuntimeFeesConfig { - action_receipt_creation_config: random_fee(), - data_receipt_creation_config: DataReceiptCreationConfig { - base_cost: random_fee(), - cost_per_byte: random_fee(), - }, - action_creation_config: ActionCreationConfig { - create_account_cost: random_fee(), - deploy_contract_cost: random_fee(), - deploy_contract_cost_per_byte: random_fee(), - function_call_cost: random_fee(), - function_call_cost_per_byte: random_fee(), - transfer_cost: random_fee(), - stake_cost: random_fee(), - add_key_cost: AccessKeyCreationConfig { - full_access_cost: random_fee(), - function_call_cost: random_fee(), - function_call_cost_per_byte: random_fee(), - }, - delete_key_cost: random_fee(), - delete_account_cost: random_fee(), + fees: RuntimeFeesConfig { + action_fees: enum_map::enum_map! { + _ => random_fee(), }, storage_usage_config: StorageUsageConfig { num_bytes_account: rng.next_u64() % 10000, num_extra_bytes_record: rng.next_u64() % 10000, + storage_amount_per_byte: rng.next_u64() as u128, }, burnt_gas_reward: Rational::new((rng.next_u32() % 100).try_into().unwrap(), 100), pessimistic_gas_price_inflation_ratio: Rational::new( @@ -52,5 +32,5 @@ pub fn random_config() -> RuntimeConfig { #[test] fn test_random_fees() { - assert_ne!(random_config().transaction_costs, random_config().transaction_costs); + assert_ne!(random_config().fees, random_config().fees); } diff --git a/test-utils/testlib/src/fees_utils.rs b/test-utils/testlib/src/fees_utils.rs index 17306662cc6..cb8bee0ad63 100644 --- a/test-utils/testlib/src/fees_utils.rs +++ b/test-utils/testlib/src/fees_utils.rs @@ -1,5 +1,6 @@ //! Helper functions to compute the costs of certain actions assuming they succeed and the only //! actions in the transaction batch. +use near_primitives::config::ActionCosts; use near_primitives::runtime::fees::RuntimeFeesConfig; use near_primitives::types::{Balance, Gas}; @@ -30,22 +31,22 @@ impl FeeHelper { } pub fn create_account_cost(&self) -> Balance { - let exec_gas = self.cfg.action_receipt_creation_config.exec_fee() - + self.cfg.action_creation_config.create_account_cost.exec_fee(); - let send_gas = self.cfg.action_receipt_creation_config.send_fee(false) - + self.cfg.action_creation_config.create_account_cost.send_fee(false); + let exec_gas = self.cfg.fee(ActionCosts::new_action_receipt).exec_fee() + + self.cfg.fee(ActionCosts::create_account).exec_fee(); + let send_gas = self.cfg.fee(ActionCosts::new_action_receipt).send_fee(false) + + self.cfg.fee(ActionCosts::create_account).send_fee(false); self.gas_to_balance(exec_gas + send_gas) } pub fn create_account_transfer_full_key_fee(&self) -> Gas { - let exec_gas = self.cfg.action_receipt_creation_config.exec_fee() - + self.cfg.action_creation_config.create_account_cost.exec_fee() - + self.cfg.action_creation_config.transfer_cost.exec_fee() - + self.cfg.action_creation_config.add_key_cost.full_access_cost.exec_fee(); - let send_gas = self.cfg.action_receipt_creation_config.send_fee(false) - + self.cfg.action_creation_config.create_account_cost.send_fee(false) - + self.cfg.action_creation_config.transfer_cost.send_fee(false) - + self.cfg.action_creation_config.add_key_cost.full_access_cost.send_fee(false); + let exec_gas = self.cfg.fee(ActionCosts::new_action_receipt).exec_fee() + + self.cfg.fee(ActionCosts::create_account).exec_fee() + + self.cfg.fee(ActionCosts::transfer).exec_fee() + + self.cfg.fee(ActionCosts::add_full_access_key).exec_fee(); + let send_gas = self.cfg.fee(ActionCosts::new_action_receipt).send_fee(false) + + self.cfg.fee(ActionCosts::create_account).send_fee(false) + + self.cfg.fee(ActionCosts::transfer).send_fee(false) + + self.cfg.fee(ActionCosts::add_full_access_key).send_fee(false); exec_gas + send_gas } @@ -54,58 +55,56 @@ impl FeeHelper { } pub fn create_account_transfer_full_key_cost_no_reward(&self) -> Balance { - let exec_gas = self.cfg.action_receipt_creation_config.exec_fee() - + self.cfg.action_creation_config.create_account_cost.exec_fee() - + self.cfg.action_creation_config.transfer_cost.exec_fee() - + self.cfg.action_creation_config.add_key_cost.full_access_cost.exec_fee(); - let send_gas = self.cfg.action_receipt_creation_config.send_fee(false) - + self.cfg.action_creation_config.create_account_cost.send_fee(false) - + self.cfg.action_creation_config.transfer_cost.send_fee(false) - + self.cfg.action_creation_config.add_key_cost.full_access_cost.send_fee(false); + let exec_gas = self.cfg.fee(ActionCosts::new_action_receipt).exec_fee() + + self.cfg.fee(ActionCosts::create_account).exec_fee() + + self.cfg.fee(ActionCosts::transfer).exec_fee() + + self.cfg.fee(ActionCosts::add_full_access_key).exec_fee(); + let send_gas = self.cfg.fee(ActionCosts::new_action_receipt).send_fee(false) + + self.cfg.fee(ActionCosts::create_account).send_fee(false) + + self.cfg.fee(ActionCosts::transfer).send_fee(false) + + self.cfg.fee(ActionCosts::add_full_access_key).send_fee(false); self.gas_to_balance(send_gas) + self.gas_to_balance_inflated(exec_gas) } pub fn create_account_transfer_full_key_cost_fail_on_create_account(&self) -> Balance { - let exec_gas = self.cfg.action_receipt_creation_config.exec_fee() - + self.cfg.action_creation_config.create_account_cost.exec_fee(); - let send_gas = self.cfg.action_receipt_creation_config.send_fee(false) - + self.cfg.action_creation_config.create_account_cost.send_fee(false) - + self.cfg.action_creation_config.transfer_cost.send_fee(false) - + self.cfg.action_creation_config.add_key_cost.full_access_cost.send_fee(false); + let exec_gas = self.cfg.fee(ActionCosts::new_action_receipt).exec_fee() + + self.cfg.fee(ActionCosts::create_account).exec_fee(); + let send_gas = self.cfg.fee(ActionCosts::new_action_receipt).send_fee(false) + + self.cfg.fee(ActionCosts::create_account).send_fee(false) + + self.cfg.fee(ActionCosts::transfer).send_fee(false) + + self.cfg.fee(ActionCosts::add_full_access_key).send_fee(false); self.gas_to_balance(exec_gas + send_gas) } pub fn deploy_contract_cost(&self, num_bytes: u64) -> Balance { - let exec_gas = self.cfg.action_receipt_creation_config.exec_fee() - + self.cfg.action_creation_config.deploy_contract_cost.exec_fee() - + num_bytes * self.cfg.action_creation_config.deploy_contract_cost_per_byte.exec_fee(); - let send_gas = self.cfg.action_receipt_creation_config.send_fee(true) - + self.cfg.action_creation_config.deploy_contract_cost.send_fee(true) - + num_bytes - * self.cfg.action_creation_config.deploy_contract_cost_per_byte.send_fee(true); + let exec_gas = self.cfg.fee(ActionCosts::new_action_receipt).exec_fee() + + self.cfg.fee(ActionCosts::deploy_contract_base).exec_fee() + + num_bytes * self.cfg.fee(ActionCosts::deploy_contract_byte).exec_fee(); + let send_gas = self.cfg.fee(ActionCosts::new_action_receipt).send_fee(true) + + self.cfg.fee(ActionCosts::deploy_contract_base).send_fee(true) + + num_bytes * self.cfg.fee(ActionCosts::deploy_contract_byte).send_fee(true); self.gas_to_balance(exec_gas + send_gas) } pub fn function_call_exec_gas(&self, num_bytes: u64) -> Gas { - self.cfg.action_receipt_creation_config.exec_fee() - + self.cfg.action_creation_config.function_call_cost.exec_fee() - + num_bytes * self.cfg.action_creation_config.function_call_cost_per_byte.exec_fee() + self.cfg.fee(ActionCosts::new_action_receipt).exec_fee() + + self.cfg.fee(ActionCosts::function_call_base).exec_fee() + + num_bytes * self.cfg.fee(ActionCosts::function_call_byte).exec_fee() } pub fn function_call_cost(&self, num_bytes: u64, prepaid_gas: u64) -> Balance { let exec_gas = self.function_call_exec_gas(num_bytes); - let send_gas = self.cfg.action_receipt_creation_config.send_fee(false) - + self.cfg.action_creation_config.function_call_cost.send_fee(false) - + num_bytes - * self.cfg.action_creation_config.function_call_cost_per_byte.send_fee(false); + let send_gas = self.cfg.fee(ActionCosts::new_action_receipt).send_fee(false) + + self.cfg.fee(ActionCosts::function_call_base).send_fee(false) + + num_bytes * self.cfg.fee(ActionCosts::function_call_byte).send_fee(false); self.gas_to_balance(exec_gas + send_gas + prepaid_gas) } pub fn transfer_fee(&self) -> Gas { - let exec_gas = self.cfg.action_receipt_creation_config.exec_fee() - + self.cfg.action_creation_config.transfer_cost.exec_fee(); - let send_gas = self.cfg.action_receipt_creation_config.send_fee(false) - + self.cfg.action_creation_config.transfer_cost.send_fee(false); + let exec_gas = self.cfg.fee(ActionCosts::new_action_receipt).exec_fee() + + self.cfg.fee(ActionCosts::transfer).exec_fee(); + let send_gas = self.cfg.fee(ActionCosts::new_action_receipt).send_fee(false) + + self.cfg.fee(ActionCosts::transfer).send_fee(false); exec_gas + send_gas } @@ -118,56 +117,44 @@ impl FeeHelper { } pub fn stake_cost(&self) -> Balance { - let exec_gas = self.cfg.action_receipt_creation_config.exec_fee() - + self.cfg.action_creation_config.stake_cost.exec_fee(); - let send_gas = self.cfg.action_receipt_creation_config.send_fee(true) - + self.cfg.action_creation_config.stake_cost.send_fee(true); + let exec_gas = self.cfg.fee(ActionCosts::new_action_receipt).exec_fee() + + self.cfg.fee(ActionCosts::stake).exec_fee(); + let send_gas = self.cfg.fee(ActionCosts::new_action_receipt).send_fee(true) + + self.cfg.fee(ActionCosts::stake).send_fee(true); self.gas_to_balance(exec_gas + send_gas) } pub fn add_key_cost(&self, num_bytes: u64) -> Balance { - let exec_gas = self.cfg.action_receipt_creation_config.exec_fee() - + self.cfg.action_creation_config.add_key_cost.function_call_cost.exec_fee() - + num_bytes - * self - .cfg - .action_creation_config - .add_key_cost - .function_call_cost_per_byte - .exec_fee(); - let send_gas = self.cfg.action_receipt_creation_config.send_fee(true) - + self.cfg.action_creation_config.add_key_cost.function_call_cost.send_fee(true) - + num_bytes - * self - .cfg - .action_creation_config - .add_key_cost - .function_call_cost_per_byte - .send_fee(true); + let exec_gas = self.cfg.fee(ActionCosts::new_action_receipt).exec_fee() + + self.cfg.fee(ActionCosts::add_function_call_key_base).exec_fee() + + num_bytes * self.cfg.fee(ActionCosts::add_function_call_key_byte).exec_fee(); + let send_gas = self.cfg.fee(ActionCosts::new_action_receipt).send_fee(true) + + self.cfg.fee(ActionCosts::add_function_call_key_base).send_fee(true) + + num_bytes * self.cfg.fee(ActionCosts::add_function_call_key_byte).send_fee(true); self.gas_to_balance(exec_gas + send_gas) } pub fn add_key_full_cost(&self) -> Balance { - let exec_gas = self.cfg.action_receipt_creation_config.exec_fee() - + self.cfg.action_creation_config.add_key_cost.full_access_cost.exec_fee(); - let send_gas = self.cfg.action_receipt_creation_config.send_fee(true) - + self.cfg.action_creation_config.add_key_cost.full_access_cost.send_fee(true); + let exec_gas = self.cfg.fee(ActionCosts::new_action_receipt).exec_fee() + + self.cfg.fee(ActionCosts::add_full_access_key).exec_fee(); + let send_gas = self.cfg.fee(ActionCosts::new_action_receipt).send_fee(true) + + self.cfg.fee(ActionCosts::add_full_access_key).send_fee(true); self.gas_to_balance(exec_gas + send_gas) } pub fn delete_key_cost(&self) -> Balance { - let exec_gas = self.cfg.action_receipt_creation_config.exec_fee() - + self.cfg.action_creation_config.delete_key_cost.exec_fee(); - let send_gas = self.cfg.action_receipt_creation_config.send_fee(true) - + self.cfg.action_creation_config.delete_key_cost.send_fee(true); + let exec_gas = self.cfg.fee(ActionCosts::new_action_receipt).exec_fee() + + self.cfg.fee(ActionCosts::delete_key).exec_fee(); + let send_gas = self.cfg.fee(ActionCosts::new_action_receipt).send_fee(true) + + self.cfg.fee(ActionCosts::delete_key).send_fee(true); self.gas_to_balance(exec_gas + send_gas) } pub fn prepaid_delete_account_cost(&self) -> Balance { - let exec_gas = self.cfg.action_receipt_creation_config.exec_fee() - + self.cfg.action_creation_config.delete_account_cost.exec_fee(); - let send_gas = self.cfg.action_receipt_creation_config.send_fee(false) - + self.cfg.action_creation_config.delete_account_cost.send_fee(false); + let exec_gas = self.cfg.fee(ActionCosts::new_action_receipt).exec_fee() + + self.cfg.fee(ActionCosts::delete_account).exec_fee(); + let send_gas = self.cfg.fee(ActionCosts::new_action_receipt).send_fee(false) + + self.cfg.fee(ActionCosts::delete_account).send_fee(false); let total_fee = exec_gas + send_gas;