From 1b5f49eb41a7b10c72e04501cf2d7cd9804995ce Mon Sep 17 00:00:00 2001 From: austinabell Date: Fri, 11 Feb 2022 19:48:22 -0500 Subject: [PATCH 01/19] feat: implement NEP264, function call gas ratio --- core/primitives/Cargo.toml | 2 + core/primitives/src/version.rs | 8 +- runtime/near-vm-logic/Cargo.toml | 1 + runtime/near-vm-logic/src/dependencies.rs | 52 +++++++++++ runtime/near-vm-logic/src/logic.rs | 92 ++++++++++++++++++- .../near-vm-logic/src/mocks/mock_external.rs | 70 ++++++++++++++ .../near-vm-logic/src/tests/gas_counter.rs | 47 ++++++++++ runtime/near-vm-logic/src/tests/helpers.rs | 29 ++++++ runtime/near-vm-runner/src/imports.rs | 10 ++ runtime/runtime/Cargo.toml | 4 + runtime/runtime/src/ext.rs | 91 +++++++++++++++++- 11 files changed, 398 insertions(+), 8 deletions(-) diff --git a/core/primitives/Cargo.toml b/core/primitives/Cargo.toml index a09a6e7221a..451ff5a0d57 100644 --- a/core/primitives/Cargo.toml +++ b/core/primitives/Cargo.toml @@ -45,6 +45,7 @@ protocol_feature_chunk_only_producers = [] protocol_feature_routing_exchange_algorithm = ["near-primitives-core/protocol_feature_routing_exchange_algorithm"] protocol_feature_access_key_nonce_for_implicit_accounts = [] protocol_feature_fix_staking_threshold = [] +protocol_feature_function_call_ratio = [] nightly_protocol_features = [ "nightly_protocol", "protocol_feature_alt_bn128", @@ -52,6 +53,7 @@ nightly_protocol_features = [ "protocol_feature_routing_exchange_algorithm", "protocol_feature_access_key_nonce_for_implicit_accounts", "protocol_feature_fix_staking_threshold", + "protocol_feature_function_call_ratio", ] nightly_protocol = [] deepsize_feature = [ diff --git a/core/primitives/src/version.rs b/core/primitives/src/version.rs index d69ba07070c..0dc6ba885ce 100644 --- a/core/primitives/src/version.rs +++ b/core/primitives/src/version.rs @@ -146,6 +146,8 @@ pub enum ProtocolFeature { /// alpha is min stake ratio #[cfg(feature = "protocol_feature_fix_staking_threshold")] FixStakingThreshold, + #[cfg(feature = "protocol_feature_function_call_ratio")] + FunctionCallRatio, } /// Both, outgoing and incoming tcp connections to peers, will be rejected if `peer's` @@ -162,7 +164,8 @@ const MAIN_NET_PROTOCOL_VERSION: ProtocolVersion = 51; pub const PROTOCOL_VERSION: ProtocolVersion = MAIN_NET_PROTOCOL_VERSION; /// Current latest nightly version of the protocol. #[cfg(feature = "nightly_protocol")] -pub const PROTOCOL_VERSION: ProtocolVersion = 126; +// TODO remember to update this also +pub const PROTOCOL_VERSION: ProtocolVersion = 127; impl ProtocolFeature { pub const fn protocol_version(self) -> ProtocolVersion { @@ -202,6 +205,9 @@ impl ProtocolFeature { ProtocolFeature::RoutingExchangeAlgorithm => 117, #[cfg(feature = "protocol_feature_fix_staking_threshold")] ProtocolFeature::FixStakingThreshold => 126, + #[cfg(feature = "protocol_feature_function_call_ratio")] + // TODO idk where this number comes from + ProtocolFeature::FunctionCallRatio => 127, } } } diff --git a/runtime/near-vm-logic/Cargo.toml b/runtime/near-vm-logic/Cargo.toml index f1fb6e255c6..b949dfe846b 100644 --- a/runtime/near-vm-logic/Cargo.toml +++ b/runtime/near-vm-logic/Cargo.toml @@ -43,6 +43,7 @@ protocol_feature_alt_bn128 = [ "near-primitives-core/protocol_feature_alt_bn128", "near-vm-errors/protocol_feature_alt_bn128", ] +protocol_feature_function_call_ratio = ["near-primitives/protocol_feature_function_call_ratio"] # Use this feature to enable counting of fees and costs applied. costs_counting = [] diff --git a/runtime/near-vm-logic/src/dependencies.rs b/runtime/near-vm-logic/src/dependencies.rs index a8132a9c27c..5e3e28de389 100644 --- a/runtime/near-vm-logic/src/dependencies.rs +++ b/runtime/near-vm-logic/src/dependencies.rs @@ -276,6 +276,51 @@ pub trait External { prepaid_gas: Gas, ) -> Result<()>; + /// Attach the [`FunctionCallAction`] action to an existing receipt. + /// + /// # Arguments + /// + /// * `receipt_index` - an index of Receipt to append an action + /// * `method_name` - a name of the contract method to call + /// * `arguments` - a Wasm code to attach + /// * `attached_deposit` - amount of tokens to transfer with the call + /// * `prepaid_gas` - amount of prepaid gas to attach to the call + /// * `gas_ratio` - ratio of unused gas to distribute to the function call action + /// + /// # Example + /// + /// ``` + /// # use near_vm_logic::mocks::mock_external::MockedExternal; + /// # use near_vm_logic::External; + /// + /// # let mut external = MockedExternal::new(); + /// let receipt_index = external.create_receipt(vec![], "charli.near".parse().unwrap()).unwrap(); + /// external.append_action_function_call_ratio( + /// receipt_index, + /// b"method_name".to_vec(), + /// b"{serialised: arguments}".to_vec(), + /// 100000u128, + /// 100u64, + /// 2, + /// ).unwrap(); + /// ``` + /// + /// # Panics + /// + /// Panics if the `receipt_index` does not refer to a known receipt. + #[cfg(feature = "protocol_feature_function_call_ratio")] + // TODO: unclear if this should be a supertrait to avoid semver breaking changes + // TODO: or if we can just modify the existing function + fn append_action_function_call_ratio( + &mut self, + receipt_index: ReceiptIndex, + method_name: Vec, + arguments: Vec, + attached_deposit: Balance, + prepaid_gas: Gas, + gas_ratio: u64, + ) -> Result<()>; + /// Attach the [`TransferAction`] action to an existing receipt. /// /// # Arguments @@ -486,4 +531,11 @@ pub trait External { /// Returns total stake of validators in the current epoch. fn validator_total_stake(&self) -> Result; + + /// Distribute the gas among the scheduled function calls that specify a gas ratio. + /// Returns the amount of distributed gas. + /// + /// * `gas` - amount of unused gas to distribute + #[cfg(feature = "protocol_feature_function_call_ratio")] + fn distribute_unused_gas(&mut self, gas: Gas) -> Gas; } diff --git a/runtime/near-vm-logic/src/logic.rs b/runtime/near-vm-logic/src/logic.rs index ef64d75b2b2..1018f42a61e 100644 --- a/runtime/near-vm-logic/src/logic.rs +++ b/runtime/near-vm-logic/src/logic.rs @@ -1506,6 +1506,62 @@ impl<'a> VMLogic<'a> { arguments_ptr: u64, amount_ptr: u64, gas: Gas, + ) -> Result<()> { + self.promise_batch_action_function_call_ratio( + promise_idx, + method_name_len, + method_name_ptr, + arguments_len, + arguments_ptr, + amount_ptr, + gas, + 0, + ) + } + + /// Appends `FunctionCall` action to the batch of actions for the given promise pointed by + /// `promise_idx`. This function allows not specifying a specific gas value and allowing the + /// runtime to assign remaining gas based on a ratio. + /// + /// # Gas + /// + /// Gas can be specified using a static amount, a ratio of remaining prepaid gas, or a mixture + /// of both. To omit a static gas amount, [`u64::MAX`] can be passed for the `gas` parameter. + /// To omit assigning remaining gas, [`u64::MAX`] can be passed as the `gas_ratio` parameter. + /// + /// The gas ratio parameter works as the following: + /// + /// All unused prepaid gas from the current function call is split among all function calls + /// which supply this gas ratio. The amount attached to each respective call depends on the + /// value of the ratio. + /// + /// For example, if 40 gas is leftover from the current method call and three functions specify + /// the ratios 1, 5, 2 then 5, 25, 10 gas will be added to each function call respectively, + /// using up all remaining available gas. + /// + /// # Errors + /// + /// * If `promise_idx` does not correspond to an existing promise returns `InvalidPromiseIndex`. + /// * If the promise pointed by the `promise_idx` is an ephemeral promise created by + /// `promise_and` returns `CannotAppendActionToJointPromise`. + /// * If `method_name_len + method_name_ptr` or `arguments_len + arguments_ptr` or + /// `amount_ptr + 16` points outside the memory of the guest or host returns + /// `MemoryAccessViolation`. + /// * If called as view function returns `ProhibitedInView`. + /// - If the [`u64::MAX`] special value is passed for `gas` and `gas_ratio` parameters + /// + /// + /// [`u64::MAX`]: std::u64::MAX + pub fn promise_batch_action_function_call_ratio( + &mut self, + promise_index: u64, + method_name_len: u64, + method_name_ptr: u64, + arguments_len: u64, + arguments_ptr: u64, + amount_ptr: u64, + gas: u64, + gas_ratio: u64, ) -> Result<()> { self.gas_counter.pay_base(base)?; if self.context.is_view() { @@ -1521,7 +1577,7 @@ impl<'a> VMLogic<'a> { } let arguments = self.get_vec_from_memory_or_register(arguments_ptr, arguments_len)?; - let (receipt_idx, sir) = self.promise_idx_to_receipt_idx_with_sir(promise_idx)?; + let (receipt_idx, sir) = self.promise_idx_to_receipt_idx_with_sir(promise_index)?; // Input can't be large enough to overflow let num_bytes = method_name.len() as u64 + arguments.len() as u64; @@ -1541,7 +1597,27 @@ impl<'a> VMLogic<'a> { self.deduct_balance(amount)?; - self.ext.append_action_function_call(receipt_idx, method_name, arguments, amount, gas)?; + #[cfg(feature = "protocol_feature_function_call_ratio")] + self.ext.append_action_function_call_ratio( + receipt_idx, + method_name, + arguments, + amount, + gas, + gas_ratio, + )?; + + #[cfg(not(feature = "protocol_feature_function_call_ratio"))] + { + let _ = gas_ratio; + self.ext.append_action_function_call( + receipt_idx, + method_name, + arguments, + amount, + gas, + )?; + } Ok(()) } @@ -2481,7 +2557,17 @@ impl<'a> VMLogic<'a> { } /// Computes the outcome of execution. - pub fn outcome(self) -> VMOutcome { + #[allow(unused_mut)] + pub fn outcome(mut self) -> VMOutcome { + #[cfg(feature = "protocol_feature_function_call_ratio")] + if !self.context.is_view() { + // Distribute unused gas to scheduled function calls + let unused_gas = self.context.prepaid_gas - self.gas_counter.used_gas(); + let distributed_gas = self.ext.distribute_unused_gas(unused_gas); + // Distributed gas must be below gas available if `distribute_unused_gas` is correct. + self.gas_counter.prepay_gas(distributed_gas).unwrap(); + } + let burnt_gas = self.gas_counter.burnt_gas(); let used_gas = self.gas_counter.used_gas(); diff --git a/runtime/near-vm-logic/src/mocks/mock_external.rs b/runtime/near-vm-logic/src/mocks/mock_external.rs index b627f71dadb..1d8b62fa40e 100644 --- a/runtime/near-vm-logic/src/mocks/mock_external.rs +++ b/runtime/near-vm-logic/src/mocks/mock_external.rs @@ -10,6 +10,18 @@ pub struct MockedExternal { pub fake_trie: HashMap, Vec>, receipts: Vec, pub validators: HashMap, + #[cfg(feature = "protocol_feature_function_call_ratio")] + distribute_leftover_gas_to: Vec, + #[cfg(feature = "protocol_feature_function_call_ratio")] + gas_ratio_sum: u64, +} + +#[derive(Clone)] +#[cfg(feature = "protocol_feature_function_call_ratio")] +struct GasRatioMetadata { + receipt_index: usize, + action_index: usize, + gas_ratio: u64, } pub struct MockedValuePtr { @@ -118,6 +130,37 @@ impl External for MockedExternal { Ok(()) } + #[cfg(feature = "protocol_feature_function_call_ratio")] + fn append_action_function_call_ratio( + &mut self, + receipt_index: u64, + method_name: Vec, + arguments: Vec, + attached_deposit: u128, + prepaid_gas: u64, + gas_ratio: u64, + ) -> Result<()> { + let receipt_index = receipt_index as usize; + let receipt = self.receipts.get_mut(receipt_index).unwrap(); + if gas_ratio > 0 { + self.distribute_leftover_gas_to.push(GasRatioMetadata { + receipt_index, + action_index: receipt.actions.len(), + gas_ratio, + }); + self.gas_ratio_sum = + self.gas_ratio_sum.checked_add(gas_ratio).ok_or(HostError::IntegerOverflow)?; + } + + receipt.actions.push(Action::FunctionCall(FunctionCallAction { + method_name, + args: arguments, + deposit: attached_deposit, + gas: prepaid_gas, + })); + Ok(()) + } + fn append_action_transfer(&mut self, receipt_index: u64, amount: u128) -> Result<()> { self.receipts .get_mut(receipt_index as usize) @@ -209,6 +252,33 @@ impl External for MockedExternal { fn validator_total_stake(&self) -> Result { Ok(self.validators.values().sum()) } + + #[cfg(feature = "protocol_feature_function_call_ratio")] + fn distribute_unused_gas(&mut self, gas: Gas) -> Gas { + if self.gas_ratio_sum != 0 { + let gas_per_ratio = gas / self.gas_ratio_sum; + + self.distribute_leftover_gas_to + .drain(..) + .map(|GasRatioMetadata { receipt_index, action_index, gas_ratio }| { + let assign_gas = gas_per_ratio * gas_ratio; + if let Some(Action::FunctionCall(FunctionCallAction { ref mut gas, .. })) = self + .receipts + .get_mut(receipt_index) + .and_then(|receipt| receipt.actions.get_mut(action_index)) + { + *gas += assign_gas; + } else { + panic!("Invalid index for assigning unused gas ratio"); + } + + assign_gas + }) + .sum() + } else { + 0 + } + } } #[derive(Serialize, Deserialize, Clone, Debug)] diff --git a/runtime/near-vm-logic/src/tests/gas_counter.rs b/runtime/near-vm-logic/src/tests/gas_counter.rs index 322bb7129fe..22bad1f0527 100644 --- a/runtime/near-vm-logic/src/tests/gas_counter.rs +++ b/runtime/near-vm-logic/src/tests/gas_counter.rs @@ -102,6 +102,53 @@ fn test_hit_prepaid_gas_limit() { assert_eq!(outcome.used_gas, gas_limit); } +#[cfg(feature = "protocol_feature_function_call_ratio")] +fn function_call_ratio_check(function_calls: impl IntoIterator) { + let gas_limit = 10u64.pow(14); + + let mut logic_builder = VMLogicBuilder::default().max_gas_burnt(gas_limit); + let mut logic = logic_builder.build_with_prepaid_gas(gas_limit); + + let mut ratio_sum = 0; + for (static_gas, gas_ratio) in function_calls { + let index = promise_batch_create(&mut logic, "rick.test").expect("should create a promise"); + promise_batch_action_function_call_ratio(&mut logic, index, 0, static_gas, gas_ratio) + .expect("batch action function call should succeed"); + + ratio_sum += gas_ratio; + } + let outcome = logic.outcome(); + + // Verify that all gas is used when only one ratio is specified + println!("{} {} {}", outcome.used_gas, gas_limit, ratio_sum); + assert!(outcome.used_gas + ratio_sum - 1 >= gas_limit); +} + +#[cfg(feature = "protocol_feature_function_call_ratio")] +#[test] +fn function_call_ratio_single_smoke_test() { + // Single function call + function_call_ratio_check([(0, 1)]); + + // Single function with static gas + function_call_ratio_check([(888, 1)]); + + // Large ratio + function_call_ratio_check([(0, 88888)]); + + // Ratio larger than gas limit + function_call_ratio_check([(0, 11u64.pow(14))]); + + // Split two + function_call_ratio_check([(0, 3), (0, 2)]); + + // Split two with static gas + function_call_ratio_check([(1_000_000, 3), (3_000_000, 2)]); + + // Many different gas ratios + function_call_ratio_check([(1_000_000, 3), (3_000_000, 2), (0, 1), (1_000_000_000, 0), (0, 4)]); +} + impl VMLogicBuilder { fn max_gas_burnt(mut self, max_gas_burnt: Gas) -> Self { self.config.limit_config.max_gas_burnt = max_gas_burnt; diff --git a/runtime/near-vm-logic/src/tests/helpers.rs b/runtime/near-vm-logic/src/tests/helpers.rs index 06b9cedae67..6948ea577cd 100644 --- a/runtime/near-vm-logic/src/tests/helpers.rs +++ b/runtime/near-vm-logic/src/tests/helpers.rs @@ -27,6 +27,11 @@ pub fn promise_create( ) } +#[allow(dead_code)] +pub fn promise_batch_create(logic: &mut VMLogic, account_id: &str) -> Result { + logic.promise_batch_create(account_id.len() as _, account_id.as_ptr() as _) +} + #[allow(dead_code)] pub fn promise_batch_action_function_call( logic: &mut VMLogic<'_>, @@ -48,6 +53,30 @@ pub fn promise_batch_action_function_call( ) } +#[cfg(feature = "protocol_feature_function_call_ratio")] +#[allow(dead_code)] +pub fn promise_batch_action_function_call_ratio( + logic: &mut VMLogic<'_>, + promise_index: u64, + amount: u128, + gas: Gas, + ratio: u64, +) -> Result<()> { + let method_id = b"promise_batch_action"; + let args = b"promise_batch_action_args"; + + logic.promise_batch_action_function_call_ratio( + promise_index, + method_id.len() as _, + method_id.as_ptr() as _, + args.len() as _, + args.as_ptr() as _, + amount.to_le_bytes().as_ptr() as _, + gas, + ratio, + ) +} + #[allow(dead_code)] pub fn promise_batch_action_add_key_with_function_call( logic: &mut VMLogic<'_>, diff --git a/runtime/near-vm-runner/src/imports.rs b/runtime/near-vm-runner/src/imports.rs index 113cd53e992..83bad873004 100644 --- a/runtime/near-vm-runner/src/imports.rs +++ b/runtime/near-vm-runner/src/imports.rs @@ -154,6 +154,16 @@ imports! { amount_ptr: u64, gas: u64 ] -> []>, + promise_batch_action_function_call_ratio<[ + promise_index: u64, + method_name_len: u64, + method_name_ptr: u64, + arguments_len: u64, + arguments_ptr: u64, + amount_ptr: u64, + gas: u64, + gas_ratio: u64 + ] -> []>, promise_batch_action_transfer<[promise_index: u64, amount_ptr: u64] -> []>, promise_batch_action_stake<[ promise_index: u64, diff --git a/runtime/runtime/Cargo.toml b/runtime/runtime/Cargo.toml index 2b9abea7cf5..5f0a4e353a5 100644 --- a/runtime/runtime/Cargo.toml +++ b/runtime/runtime/Cargo.toml @@ -56,6 +56,10 @@ protocol_feature_alt_bn128 = [ "near-vm-runner/protocol_feature_alt_bn128", "near-vm-errors/protocol_feature_alt_bn128", ] +protocol_feature_function_call_ratio = [ + "near-primitives/protocol_feature_function_call_ratio", + "near-vm-logic/protocol_feature_function_call_ratio", +] sandbox = [] [dev-dependencies] diff --git a/runtime/runtime/src/ext.rs b/runtime/runtime/src/ext.rs index 17fefa28d3a..ccea39c02a9 100644 --- a/runtime/runtime/src/ext.rs +++ b/runtime/runtime/src/ext.rs @@ -35,6 +35,18 @@ pub struct RuntimeExt<'a> { last_block_hash: &'a CryptoHash, epoch_info_provider: &'a dyn EpochInfoProvider, current_protocol_version: ProtocolVersion, + + #[cfg(feature = "protocol_feature_function_call_ratio")] + distribute_leftover_gas_to: Vec, + #[cfg(feature = "protocol_feature_function_call_ratio")] + gas_ratio_sum: u64, +} + +#[cfg(feature = "protocol_feature_function_call_ratio")] +struct GasRatioMetadata { + receipt_index: usize, + action_index: usize, + gas_ratio: u64, } /// Error used by `RuntimeExt`. @@ -93,6 +105,11 @@ impl<'a> RuntimeExt<'a> { last_block_hash, epoch_info_provider, current_protocol_version, + + #[cfg(feature = "protocol_feature_function_call_ratio")] + distribute_leftover_gas_to: vec![], + #[cfg(feature = "protocol_feature_function_call_ratio")] + gas_ratio_sum: 0, } } @@ -140,13 +157,18 @@ impl<'a> RuntimeExt<'a> { .collect() } - fn append_action(&mut self, receipt_index: u64, action: Action) { - self.action_receipts + fn append_action(&mut self, receipt_index: u64, action: Action) -> usize { + let actions = &mut self + .action_receipts .get_mut(receipt_index as usize) .expect("receipt index should be present") .1 - .actions - .push(action); + .actions; + + actions.push(action); + + // Return index that action was inserted at + actions.len() - 1 } #[inline] @@ -254,6 +276,40 @@ impl<'a> External for RuntimeExt<'a> { Ok(()) } + #[cfg(feature = "protocol_feature_function_call_ratio")] + fn append_action_function_call_ratio( + &mut self, + receipt_index: u64, + method_name: Vec, + args: Vec, + attached_deposit: u128, + prepaid_gas: u64, + gas_ratio: u64, + ) -> ExtResult<()> { + let action_index = self.append_action( + receipt_index, + Action::FunctionCall(FunctionCallAction { + method_name: String::from_utf8(method_name) + .map_err(|_| HostError::InvalidMethodName)?, + args, + gas: prepaid_gas, + deposit: attached_deposit, + }), + ); + + if gas_ratio > 0 { + self.distribute_leftover_gas_to.push(GasRatioMetadata { + receipt_index: receipt_index as usize, + action_index, + gas_ratio, + }); + self.gas_ratio_sum = + self.gas_ratio_sum.checked_add(gas_ratio).ok_or(HostError::IntegerOverflow)?; + } + + Ok(()) + } + fn append_action_function_call( &mut self, receipt_index: u64, @@ -389,4 +445,31 @@ impl<'a> External for RuntimeExt<'a> { .validator_total_stake(self.epoch_id, self.prev_block_hash) .map_err(|e| ExternalError::ValidatorError(e).into()) } + + #[cfg(feature = "protocol_feature_function_call_ratio")] + fn distribute_unused_gas(&mut self, gas: u64) -> u64 { + if self.gas_ratio_sum != 0 { + let gas_per_ratio = gas / self.gas_ratio_sum; + + self.distribute_leftover_gas_to + .drain(..) + .map(|GasRatioMetadata { receipt_index, action_index, gas_ratio }| { + let assign_gas = gas_per_ratio * gas_ratio; + if let Some(Action::FunctionCall(FunctionCallAction { ref mut gas, .. })) = self + .action_receipts + .get_mut(receipt_index) + .and_then(|(_, receipt)| receipt.actions.get_mut(action_index)) + { + *gas += assign_gas; + } else { + panic!("Invalid index for assigning unused gas ratio"); + } + + assign_gas + }) + .sum() + } else { + 0 + } + } } From fd91668b31701da136090f0deb5f1e30a9d3c08f Mon Sep 17 00:00:00 2001 From: austinabell Date: Fri, 18 Feb 2022 12:38:18 -0700 Subject: [PATCH 02/19] adjust wording to weight instead of ratio, cleanup --- core/primitives/Cargo.toml | 4 +- core/primitives/src/version.rs | 8 ++-- runtime/near-vm-logic/Cargo.toml | 2 +- runtime/near-vm-logic/src/dependencies.rs | 12 +++--- runtime/near-vm-logic/src/logic.rs | 39 +++++++++---------- .../near-vm-logic/src/mocks/mock_external.rs | 12 +++--- .../near-vm-logic/src/tests/gas_counter.rs | 24 ++++++------ runtime/near-vm-logic/src/tests/helpers.rs | 6 +-- runtime/near-vm-runner/src/imports.rs | 2 +- runtime/runtime/Cargo.toml | 6 +-- runtime/runtime/src/ext.rs | 16 ++++---- 11 files changed, 65 insertions(+), 66 deletions(-) diff --git a/core/primitives/Cargo.toml b/core/primitives/Cargo.toml index 451ff5a0d57..0d7ae32ab72 100644 --- a/core/primitives/Cargo.toml +++ b/core/primitives/Cargo.toml @@ -45,7 +45,7 @@ protocol_feature_chunk_only_producers = [] protocol_feature_routing_exchange_algorithm = ["near-primitives-core/protocol_feature_routing_exchange_algorithm"] protocol_feature_access_key_nonce_for_implicit_accounts = [] protocol_feature_fix_staking_threshold = [] -protocol_feature_function_call_ratio = [] +protocol_feature_function_call_weight = [] nightly_protocol_features = [ "nightly_protocol", "protocol_feature_alt_bn128", @@ -53,7 +53,7 @@ nightly_protocol_features = [ "protocol_feature_routing_exchange_algorithm", "protocol_feature_access_key_nonce_for_implicit_accounts", "protocol_feature_fix_staking_threshold", - "protocol_feature_function_call_ratio", + "protocol_feature_function_call_weight", ] nightly_protocol = [] deepsize_feature = [ diff --git a/core/primitives/src/version.rs b/core/primitives/src/version.rs index 0dc6ba885ce..bb55d02e43f 100644 --- a/core/primitives/src/version.rs +++ b/core/primitives/src/version.rs @@ -146,8 +146,8 @@ pub enum ProtocolFeature { /// alpha is min stake ratio #[cfg(feature = "protocol_feature_fix_staking_threshold")] FixStakingThreshold, - #[cfg(feature = "protocol_feature_function_call_ratio")] - FunctionCallRatio, + #[cfg(feature = "protocol_feature_function_call_weight")] + FunctionCallWeight, } /// Both, outgoing and incoming tcp connections to peers, will be rejected if `peer's` @@ -205,9 +205,9 @@ impl ProtocolFeature { ProtocolFeature::RoutingExchangeAlgorithm => 117, #[cfg(feature = "protocol_feature_fix_staking_threshold")] ProtocolFeature::FixStakingThreshold => 126, - #[cfg(feature = "protocol_feature_function_call_ratio")] + #[cfg(feature = "protocol_feature_function_call_weight")] // TODO idk where this number comes from - ProtocolFeature::FunctionCallRatio => 127, + ProtocolFeature::FunctionCallWeight => 127, } } } diff --git a/runtime/near-vm-logic/Cargo.toml b/runtime/near-vm-logic/Cargo.toml index b949dfe846b..6373343ca1f 100644 --- a/runtime/near-vm-logic/Cargo.toml +++ b/runtime/near-vm-logic/Cargo.toml @@ -43,7 +43,7 @@ protocol_feature_alt_bn128 = [ "near-primitives-core/protocol_feature_alt_bn128", "near-vm-errors/protocol_feature_alt_bn128", ] -protocol_feature_function_call_ratio = ["near-primitives/protocol_feature_function_call_ratio"] +protocol_feature_function_call_weight = ["near-primitives/protocol_feature_function_call_weight"] # Use this feature to enable counting of fees and costs applied. costs_counting = [] diff --git a/runtime/near-vm-logic/src/dependencies.rs b/runtime/near-vm-logic/src/dependencies.rs index 5e3e28de389..3fabd545a35 100644 --- a/runtime/near-vm-logic/src/dependencies.rs +++ b/runtime/near-vm-logic/src/dependencies.rs @@ -285,7 +285,7 @@ pub trait External { /// * `arguments` - a Wasm code to attach /// * `attached_deposit` - amount of tokens to transfer with the call /// * `prepaid_gas` - amount of prepaid gas to attach to the call - /// * `gas_ratio` - ratio of unused gas to distribute to the function call action + /// * `gas_weight` - relative weight of unused gas to distribute to the function call action /// /// # Example /// @@ -295,7 +295,7 @@ pub trait External { /// /// # let mut external = MockedExternal::new(); /// let receipt_index = external.create_receipt(vec![], "charli.near".parse().unwrap()).unwrap(); - /// external.append_action_function_call_ratio( + /// external.append_action_function_call_weight( /// receipt_index, /// b"method_name".to_vec(), /// b"{serialised: arguments}".to_vec(), @@ -308,10 +308,10 @@ pub trait External { /// # Panics /// /// Panics if the `receipt_index` does not refer to a known receipt. - #[cfg(feature = "protocol_feature_function_call_ratio")] + #[cfg(feature = "protocol_feature_function_call_weight")] // TODO: unclear if this should be a supertrait to avoid semver breaking changes // TODO: or if we can just modify the existing function - fn append_action_function_call_ratio( + fn append_action_function_call_weight( &mut self, receipt_index: ReceiptIndex, method_name: Vec, @@ -535,7 +535,9 @@ pub trait External { /// Distribute the gas among the scheduled function calls that specify a gas ratio. /// Returns the amount of distributed gas. /// + /// # Arguments + /// /// * `gas` - amount of unused gas to distribute - #[cfg(feature = "protocol_feature_function_call_ratio")] + #[cfg(feature = "protocol_feature_function_call_weight")] fn distribute_unused_gas(&mut self, gas: Gas) -> Gas; } diff --git a/runtime/near-vm-logic/src/logic.rs b/runtime/near-vm-logic/src/logic.rs index 1018f42a61e..824682cdaa5 100644 --- a/runtime/near-vm-logic/src/logic.rs +++ b/runtime/near-vm-logic/src/logic.rs @@ -1507,7 +1507,7 @@ impl<'a> VMLogic<'a> { amount_ptr: u64, gas: Gas, ) -> Result<()> { - self.promise_batch_action_function_call_ratio( + self.promise_batch_action_function_call_weight( promise_idx, method_name_len, method_name_ptr, @@ -1521,22 +1521,22 @@ impl<'a> VMLogic<'a> { /// Appends `FunctionCall` action to the batch of actions for the given promise pointed by /// `promise_idx`. This function allows not specifying a specific gas value and allowing the - /// runtime to assign remaining gas based on a ratio. + /// runtime to assign remaining gas based on a weight. /// /// # Gas /// - /// Gas can be specified using a static amount, a ratio of remaining prepaid gas, or a mixture - /// of both. To omit a static gas amount, [`u64::MAX`] can be passed for the `gas` parameter. - /// To omit assigning remaining gas, [`u64::MAX`] can be passed as the `gas_ratio` parameter. + /// Gas can be specified using a static amount, a weight of remaining prepaid gas, or a mixture + /// of both. To omit a static gas amount, `0` can be passed for the `gas` parameter. + /// To omit assigning remaining gas, `0` can be passed as the `gas_weight` parameter. /// - /// The gas ratio parameter works as the following: + /// The gas weight parameter works as the following: /// /// All unused prepaid gas from the current function call is split among all function calls - /// which supply this gas ratio. The amount attached to each respective call depends on the - /// value of the ratio. + /// which supply this gas weight. The amount attached to each respective call depends on the + /// value of the weight. /// /// For example, if 40 gas is leftover from the current method call and three functions specify - /// the ratios 1, 5, 2 then 5, 25, 10 gas will be added to each function call respectively, + /// the weights 1, 5, 2 then 5, 25, 10 gas will be added to each function call respectively, /// using up all remaining available gas. /// /// # Errors @@ -1548,11 +1548,8 @@ impl<'a> VMLogic<'a> { /// `amount_ptr + 16` points outside the memory of the guest or host returns /// `MemoryAccessViolation`. /// * If called as view function returns `ProhibitedInView`. - /// - If the [`u64::MAX`] special value is passed for `gas` and `gas_ratio` parameters - /// - /// - /// [`u64::MAX`]: std::u64::MAX - pub fn promise_batch_action_function_call_ratio( + /// - If `0` is passed for both `gas` and `gas_weight` parameters + pub fn promise_batch_action_function_call_weight( &mut self, promise_index: u64, method_name_len: u64, @@ -1561,7 +1558,7 @@ impl<'a> VMLogic<'a> { arguments_ptr: u64, amount_ptr: u64, gas: u64, - gas_ratio: u64, + gas_weight: u64, ) -> Result<()> { self.gas_counter.pay_base(base)?; if self.context.is_view() { @@ -1597,19 +1594,19 @@ impl<'a> VMLogic<'a> { self.deduct_balance(amount)?; - #[cfg(feature = "protocol_feature_function_call_ratio")] - self.ext.append_action_function_call_ratio( + #[cfg(feature = "protocol_feature_function_call_weight")] + self.ext.append_action_function_call_weight( receipt_idx, method_name, arguments, amount, gas, - gas_ratio, + gas_weight, )?; - #[cfg(not(feature = "protocol_feature_function_call_ratio"))] + #[cfg(not(feature = "protocol_feature_function_call_weight"))] { - let _ = gas_ratio; + let _ = gas_weight; self.ext.append_action_function_call( receipt_idx, method_name, @@ -2559,7 +2556,7 @@ impl<'a> VMLogic<'a> { /// Computes the outcome of execution. #[allow(unused_mut)] pub fn outcome(mut self) -> VMOutcome { - #[cfg(feature = "protocol_feature_function_call_ratio")] + #[cfg(feature = "protocol_feature_function_call_weight")] if !self.context.is_view() { // Distribute unused gas to scheduled function calls let unused_gas = self.context.prepaid_gas - self.gas_counter.used_gas(); diff --git a/runtime/near-vm-logic/src/mocks/mock_external.rs b/runtime/near-vm-logic/src/mocks/mock_external.rs index 1d8b62fa40e..b26453faca9 100644 --- a/runtime/near-vm-logic/src/mocks/mock_external.rs +++ b/runtime/near-vm-logic/src/mocks/mock_external.rs @@ -10,14 +10,14 @@ pub struct MockedExternal { pub fake_trie: HashMap, Vec>, receipts: Vec, pub validators: HashMap, - #[cfg(feature = "protocol_feature_function_call_ratio")] + #[cfg(feature = "protocol_feature_function_call_weight")] distribute_leftover_gas_to: Vec, - #[cfg(feature = "protocol_feature_function_call_ratio")] + #[cfg(feature = "protocol_feature_function_call_weight")] gas_ratio_sum: u64, } #[derive(Clone)] -#[cfg(feature = "protocol_feature_function_call_ratio")] +#[cfg(feature = "protocol_feature_function_call_weight")] struct GasRatioMetadata { receipt_index: usize, action_index: usize, @@ -130,8 +130,8 @@ impl External for MockedExternal { Ok(()) } - #[cfg(feature = "protocol_feature_function_call_ratio")] - fn append_action_function_call_ratio( + #[cfg(feature = "protocol_feature_function_call_weight")] + fn append_action_function_call_weight( &mut self, receipt_index: u64, method_name: Vec, @@ -253,7 +253,7 @@ impl External for MockedExternal { Ok(self.validators.values().sum()) } - #[cfg(feature = "protocol_feature_function_call_ratio")] + #[cfg(feature = "protocol_feature_function_call_weight")] fn distribute_unused_gas(&mut self, gas: Gas) -> Gas { if self.gas_ratio_sum != 0 { let gas_per_ratio = gas / self.gas_ratio_sum; diff --git a/runtime/near-vm-logic/src/tests/gas_counter.rs b/runtime/near-vm-logic/src/tests/gas_counter.rs index 22bad1f0527..e479f979bbe 100644 --- a/runtime/near-vm-logic/src/tests/gas_counter.rs +++ b/runtime/near-vm-logic/src/tests/gas_counter.rs @@ -102,8 +102,8 @@ fn test_hit_prepaid_gas_limit() { assert_eq!(outcome.used_gas, gas_limit); } -#[cfg(feature = "protocol_feature_function_call_ratio")] -fn function_call_ratio_check(function_calls: impl IntoIterator) { +#[cfg(feature = "protocol_feature_function_call_weight")] +fn function_call_weight_check(function_calls: impl IntoIterator) { let gas_limit = 10u64.pow(14); let mut logic_builder = VMLogicBuilder::default().max_gas_burnt(gas_limit); @@ -112,7 +112,7 @@ fn function_call_ratio_check(function_calls: impl IntoIterator= gas_limit); } -#[cfg(feature = "protocol_feature_function_call_ratio")] +#[cfg(feature = "protocol_feature_function_call_weight")] #[test] -fn function_call_ratio_single_smoke_test() { +fn function_call_weight_single_smoke_test() { // Single function call - function_call_ratio_check([(0, 1)]); + function_call_weight_check([(0, 1)]); // Single function with static gas - function_call_ratio_check([(888, 1)]); + function_call_weight_check([(888, 1)]); // Large ratio - function_call_ratio_check([(0, 88888)]); + function_call_weight_check([(0, 88888)]); // Ratio larger than gas limit - function_call_ratio_check([(0, 11u64.pow(14))]); + function_call_weight_check([(0, 11u64.pow(14))]); // Split two - function_call_ratio_check([(0, 3), (0, 2)]); + function_call_weight_check([(0, 3), (0, 2)]); // Split two with static gas - function_call_ratio_check([(1_000_000, 3), (3_000_000, 2)]); + function_call_weight_check([(1_000_000, 3), (3_000_000, 2)]); // Many different gas ratios - function_call_ratio_check([(1_000_000, 3), (3_000_000, 2), (0, 1), (1_000_000_000, 0), (0, 4)]); + function_call_weight_check([(1_000_000, 3), (3_000_000, 2), (0, 1), (1_000_000_000, 0), (0, 4)]); } impl VMLogicBuilder { diff --git a/runtime/near-vm-logic/src/tests/helpers.rs b/runtime/near-vm-logic/src/tests/helpers.rs index 6948ea577cd..0c1943b2dc6 100644 --- a/runtime/near-vm-logic/src/tests/helpers.rs +++ b/runtime/near-vm-logic/src/tests/helpers.rs @@ -53,9 +53,9 @@ pub fn promise_batch_action_function_call( ) } -#[cfg(feature = "protocol_feature_function_call_ratio")] +#[cfg(feature = "protocol_feature_function_call_weight")] #[allow(dead_code)] -pub fn promise_batch_action_function_call_ratio( +pub fn promise_batch_action_function_call_weight( logic: &mut VMLogic<'_>, promise_index: u64, amount: u128, @@ -65,7 +65,7 @@ pub fn promise_batch_action_function_call_ratio( let method_id = b"promise_batch_action"; let args = b"promise_batch_action_args"; - logic.promise_batch_action_function_call_ratio( + logic.promise_batch_action_function_call_weight( promise_index, method_id.len() as _, method_id.as_ptr() as _, diff --git a/runtime/near-vm-runner/src/imports.rs b/runtime/near-vm-runner/src/imports.rs index 83bad873004..1a803e5b9f1 100644 --- a/runtime/near-vm-runner/src/imports.rs +++ b/runtime/near-vm-runner/src/imports.rs @@ -154,7 +154,7 @@ imports! { amount_ptr: u64, gas: u64 ] -> []>, - promise_batch_action_function_call_ratio<[ + #["protocol_feature_function_call_weight", FunctionCallRatio] promise_batch_action_function_call_weight<[ promise_index: u64, method_name_len: u64, method_name_ptr: u64, diff --git a/runtime/runtime/Cargo.toml b/runtime/runtime/Cargo.toml index 5f0a4e353a5..b18f6f9ec21 100644 --- a/runtime/runtime/Cargo.toml +++ b/runtime/runtime/Cargo.toml @@ -56,9 +56,9 @@ protocol_feature_alt_bn128 = [ "near-vm-runner/protocol_feature_alt_bn128", "near-vm-errors/protocol_feature_alt_bn128", ] -protocol_feature_function_call_ratio = [ - "near-primitives/protocol_feature_function_call_ratio", - "near-vm-logic/protocol_feature_function_call_ratio", +protocol_feature_function_call_weight = [ + "near-primitives/protocol_feature_function_call_weight", + "near-vm-logic/protocol_feature_function_call_weight", ] sandbox = [] diff --git a/runtime/runtime/src/ext.rs b/runtime/runtime/src/ext.rs index ccea39c02a9..80c2dddd167 100644 --- a/runtime/runtime/src/ext.rs +++ b/runtime/runtime/src/ext.rs @@ -36,13 +36,13 @@ pub struct RuntimeExt<'a> { epoch_info_provider: &'a dyn EpochInfoProvider, current_protocol_version: ProtocolVersion, - #[cfg(feature = "protocol_feature_function_call_ratio")] + #[cfg(feature = "protocol_feature_function_call_weight")] distribute_leftover_gas_to: Vec, - #[cfg(feature = "protocol_feature_function_call_ratio")] + #[cfg(feature = "protocol_feature_function_call_weight")] gas_ratio_sum: u64, } -#[cfg(feature = "protocol_feature_function_call_ratio")] +#[cfg(feature = "protocol_feature_function_call_weight")] struct GasRatioMetadata { receipt_index: usize, action_index: usize, @@ -106,9 +106,9 @@ impl<'a> RuntimeExt<'a> { epoch_info_provider, current_protocol_version, - #[cfg(feature = "protocol_feature_function_call_ratio")] + #[cfg(feature = "protocol_feature_function_call_weight")] distribute_leftover_gas_to: vec![], - #[cfg(feature = "protocol_feature_function_call_ratio")] + #[cfg(feature = "protocol_feature_function_call_weight")] gas_ratio_sum: 0, } } @@ -276,8 +276,8 @@ impl<'a> External for RuntimeExt<'a> { Ok(()) } - #[cfg(feature = "protocol_feature_function_call_ratio")] - fn append_action_function_call_ratio( + #[cfg(feature = "protocol_feature_function_call_weight")] + fn append_action_function_call_weight( &mut self, receipt_index: u64, method_name: Vec, @@ -446,7 +446,7 @@ impl<'a> External for RuntimeExt<'a> { .map_err(|e| ExternalError::ValidatorError(e).into()) } - #[cfg(feature = "protocol_feature_function_call_ratio")] + #[cfg(feature = "protocol_feature_function_call_weight")] fn distribute_unused_gas(&mut self, gas: u64) -> u64 { if self.gas_ratio_sum != 0 { let gas_per_ratio = gas / self.gas_ratio_sum; From e012271ae7e693e44e0977c437c2b94cadf79b9c Mon Sep 17 00:00:00 2001 From: austinabell Date: Fri, 18 Feb 2022 12:45:25 -0700 Subject: [PATCH 03/19] cleanup remaining ratio references --- runtime/near-vm-logic/src/dependencies.rs | 6 ++-- .../near-vm-logic/src/mocks/mock_external.rs | 30 ++++++++--------- .../near-vm-logic/src/tests/gas_counter.rs | 26 +++++++++------ runtime/near-vm-runner/src/imports.rs | 2 +- runtime/runtime/src/ext.rs | 32 +++++++++---------- 5 files changed, 51 insertions(+), 45 deletions(-) diff --git a/runtime/near-vm-logic/src/dependencies.rs b/runtime/near-vm-logic/src/dependencies.rs index 3fabd545a35..56c9638e8fc 100644 --- a/runtime/near-vm-logic/src/dependencies.rs +++ b/runtime/near-vm-logic/src/dependencies.rs @@ -318,7 +318,7 @@ pub trait External { arguments: Vec, attached_deposit: Balance, prepaid_gas: Gas, - gas_ratio: u64, + gas_weight: u64, ) -> Result<()>; /// Attach the [`TransferAction`] action to an existing receipt. @@ -532,11 +532,11 @@ pub trait External { /// Returns total stake of validators in the current epoch. fn validator_total_stake(&self) -> Result; - /// Distribute the gas among the scheduled function calls that specify a gas ratio. + /// Distribute the gas among the scheduled function calls that specify a gas weight. /// Returns the amount of distributed gas. /// /// # Arguments - /// + /// /// * `gas` - amount of unused gas to distribute #[cfg(feature = "protocol_feature_function_call_weight")] fn distribute_unused_gas(&mut self, gas: Gas) -> Gas; diff --git a/runtime/near-vm-logic/src/mocks/mock_external.rs b/runtime/near-vm-logic/src/mocks/mock_external.rs index b26453faca9..79f8221b583 100644 --- a/runtime/near-vm-logic/src/mocks/mock_external.rs +++ b/runtime/near-vm-logic/src/mocks/mock_external.rs @@ -11,17 +11,17 @@ pub struct MockedExternal { receipts: Vec, pub validators: HashMap, #[cfg(feature = "protocol_feature_function_call_weight")] - distribute_leftover_gas_to: Vec, + distribute_leftover_gas_to: Vec, #[cfg(feature = "protocol_feature_function_call_weight")] - gas_ratio_sum: u64, + gas_weight_sum: u64, } #[derive(Clone)] #[cfg(feature = "protocol_feature_function_call_weight")] -struct GasRatioMetadata { +struct GasWeightMetadata { receipt_index: usize, action_index: usize, - gas_ratio: u64, + gas_weight: u64, } pub struct MockedValuePtr { @@ -138,18 +138,18 @@ impl External for MockedExternal { arguments: Vec, attached_deposit: u128, prepaid_gas: u64, - gas_ratio: u64, + gas_weight: u64, ) -> Result<()> { let receipt_index = receipt_index as usize; let receipt = self.receipts.get_mut(receipt_index).unwrap(); - if gas_ratio > 0 { - self.distribute_leftover_gas_to.push(GasRatioMetadata { + if gas_weight > 0 { + self.distribute_leftover_gas_to.push(GasWeightMetadata { receipt_index, action_index: receipt.actions.len(), - gas_ratio, + gas_weight, }); - self.gas_ratio_sum = - self.gas_ratio_sum.checked_add(gas_ratio).ok_or(HostError::IntegerOverflow)?; + self.gas_weight_sum = + self.gas_weight_sum.checked_add(gas_weight).ok_or(HostError::IntegerOverflow)?; } receipt.actions.push(Action::FunctionCall(FunctionCallAction { @@ -255,13 +255,13 @@ impl External for MockedExternal { #[cfg(feature = "protocol_feature_function_call_weight")] fn distribute_unused_gas(&mut self, gas: Gas) -> Gas { - if self.gas_ratio_sum != 0 { - let gas_per_ratio = gas / self.gas_ratio_sum; + if self.gas_weight_sum != 0 { + let gas_per_weight = gas / self.gas_weight_sum; self.distribute_leftover_gas_to .drain(..) - .map(|GasRatioMetadata { receipt_index, action_index, gas_ratio }| { - let assign_gas = gas_per_ratio * gas_ratio; + .map(|GasWeightMetadata { receipt_index, action_index, gas_weight }| { + let assign_gas = gas_per_weight * gas_weight; if let Some(Action::FunctionCall(FunctionCallAction { ref mut gas, .. })) = self .receipts .get_mut(receipt_index) @@ -269,7 +269,7 @@ impl External for MockedExternal { { *gas += assign_gas; } else { - panic!("Invalid index for assigning unused gas ratio"); + panic!("Invalid index for assigning unused gas weight"); } assign_gas diff --git a/runtime/near-vm-logic/src/tests/gas_counter.rs b/runtime/near-vm-logic/src/tests/gas_counter.rs index e479f979bbe..ae722973aba 100644 --- a/runtime/near-vm-logic/src/tests/gas_counter.rs +++ b/runtime/near-vm-logic/src/tests/gas_counter.rs @@ -109,19 +109,19 @@ fn function_call_weight_check(function_calls: impl IntoIterator= gas_limit); + println!("{} {} {}", outcome.used_gas, gas_limit, weight_sum); + assert!(outcome.used_gas + weight_sum - 1 >= gas_limit); } #[cfg(feature = "protocol_feature_function_call_weight")] @@ -133,10 +133,10 @@ fn function_call_weight_single_smoke_test() { // Single function with static gas function_call_weight_check([(888, 1)]); - // Large ratio + // Large weight function_call_weight_check([(0, 88888)]); - // Ratio larger than gas limit + // Weight larger than gas limit function_call_weight_check([(0, 11u64.pow(14))]); // Split two @@ -145,8 +145,14 @@ fn function_call_weight_single_smoke_test() { // Split two with static gas function_call_weight_check([(1_000_000, 3), (3_000_000, 2)]); - // Many different gas ratios - function_call_weight_check([(1_000_000, 3), (3_000_000, 2), (0, 1), (1_000_000_000, 0), (0, 4)]); + // Many different gas weights + function_call_weight_check([ + (1_000_000, 3), + (3_000_000, 2), + (0, 1), + (1_000_000_000, 0), + (0, 4), + ]); } impl VMLogicBuilder { diff --git a/runtime/near-vm-runner/src/imports.rs b/runtime/near-vm-runner/src/imports.rs index 1a803e5b9f1..9b1789af19e 100644 --- a/runtime/near-vm-runner/src/imports.rs +++ b/runtime/near-vm-runner/src/imports.rs @@ -162,7 +162,7 @@ imports! { arguments_ptr: u64, amount_ptr: u64, gas: u64, - gas_ratio: u64 + gas_weight: u64 ] -> []>, promise_batch_action_transfer<[promise_index: u64, amount_ptr: u64] -> []>, promise_batch_action_stake<[ diff --git a/runtime/runtime/src/ext.rs b/runtime/runtime/src/ext.rs index 80c2dddd167..1eac4a7a51d 100644 --- a/runtime/runtime/src/ext.rs +++ b/runtime/runtime/src/ext.rs @@ -37,16 +37,16 @@ pub struct RuntimeExt<'a> { current_protocol_version: ProtocolVersion, #[cfg(feature = "protocol_feature_function_call_weight")] - distribute_leftover_gas_to: Vec, + distribute_leftover_gas_to: Vec, #[cfg(feature = "protocol_feature_function_call_weight")] - gas_ratio_sum: u64, + gas_weight_sum: u64, } #[cfg(feature = "protocol_feature_function_call_weight")] -struct GasRatioMetadata { +struct GasWeightMetadata { receipt_index: usize, action_index: usize, - gas_ratio: u64, + gas_weight: u64, } /// Error used by `RuntimeExt`. @@ -109,7 +109,7 @@ impl<'a> RuntimeExt<'a> { #[cfg(feature = "protocol_feature_function_call_weight")] distribute_leftover_gas_to: vec![], #[cfg(feature = "protocol_feature_function_call_weight")] - gas_ratio_sum: 0, + gas_weight_sum: 0, } } @@ -284,7 +284,7 @@ impl<'a> External for RuntimeExt<'a> { args: Vec, attached_deposit: u128, prepaid_gas: u64, - gas_ratio: u64, + gas_weight: u64, ) -> ExtResult<()> { let action_index = self.append_action( receipt_index, @@ -297,14 +297,14 @@ impl<'a> External for RuntimeExt<'a> { }), ); - if gas_ratio > 0 { - self.distribute_leftover_gas_to.push(GasRatioMetadata { + if gas_weight > 0 { + self.distribute_leftover_gas_to.push(GasWeightMetadata { receipt_index: receipt_index as usize, action_index, - gas_ratio, + gas_weight, }); - self.gas_ratio_sum = - self.gas_ratio_sum.checked_add(gas_ratio).ok_or(HostError::IntegerOverflow)?; + self.gas_weight_sum = + self.gas_weight_sum.checked_add(gas_weight).ok_or(HostError::IntegerOverflow)?; } Ok(()) @@ -448,13 +448,13 @@ impl<'a> External for RuntimeExt<'a> { #[cfg(feature = "protocol_feature_function_call_weight")] fn distribute_unused_gas(&mut self, gas: u64) -> u64 { - if self.gas_ratio_sum != 0 { - let gas_per_ratio = gas / self.gas_ratio_sum; + if self.gas_weight_sum != 0 { + let gas_per_ratio = gas / self.gas_weight_sum; self.distribute_leftover_gas_to .drain(..) - .map(|GasRatioMetadata { receipt_index, action_index, gas_ratio }| { - let assign_gas = gas_per_ratio * gas_ratio; + .map(|GasWeightMetadata { receipt_index, action_index, gas_weight }| { + let assign_gas = gas_per_ratio * gas_weight; if let Some(Action::FunctionCall(FunctionCallAction { ref mut gas, .. })) = self .action_receipts .get_mut(receipt_index) @@ -462,7 +462,7 @@ impl<'a> External for RuntimeExt<'a> { { *gas += assign_gas; } else { - panic!("Invalid index for assigning unused gas ratio"); + panic!("Invalid index for assigning unused gas weight"); } assign_gas From 44044ff395656197a99a1ca0f3b770f6d1d91f97 Mon Sep 17 00:00:00 2001 From: austinabell Date: Fri, 18 Feb 2022 15:29:25 -0700 Subject: [PATCH 04/19] change logic to assign all unused gas --- runtime/near-vm-logic/src/dependencies.rs | 6 +- runtime/near-vm-logic/src/logic.rs | 12 +-- .../near-vm-logic/src/mocks/mock_external.rs | 73 ++++++++++-------- runtime/runtime/src/ext.rs | 77 ++++++++++--------- 4 files changed, 95 insertions(+), 73 deletions(-) diff --git a/runtime/near-vm-logic/src/dependencies.rs b/runtime/near-vm-logic/src/dependencies.rs index 56c9638e8fc..a204b6a19ec 100644 --- a/runtime/near-vm-logic/src/dependencies.rs +++ b/runtime/near-vm-logic/src/dependencies.rs @@ -538,6 +538,10 @@ pub trait External { /// # Arguments /// /// * `gas` - amount of unused gas to distribute + /// + /// # Returns + /// + /// Function returns true if gas was distributed, false if no ratios existed to distribute to. #[cfg(feature = "protocol_feature_function_call_weight")] - fn distribute_unused_gas(&mut self, gas: Gas) -> Gas; + fn distribute_unused_gas(&mut self, gas: Gas) -> bool; } diff --git a/runtime/near-vm-logic/src/logic.rs b/runtime/near-vm-logic/src/logic.rs index 824682cdaa5..675fb998c2f 100644 --- a/runtime/near-vm-logic/src/logic.rs +++ b/runtime/near-vm-logic/src/logic.rs @@ -1551,7 +1551,7 @@ impl<'a> VMLogic<'a> { /// - If `0` is passed for both `gas` and `gas_weight` parameters pub fn promise_batch_action_function_call_weight( &mut self, - promise_index: u64, + promise_idx: u64, method_name_len: u64, method_name_ptr: u64, arguments_len: u64, @@ -1574,7 +1574,7 @@ impl<'a> VMLogic<'a> { } let arguments = self.get_vec_from_memory_or_register(arguments_ptr, arguments_len)?; - let (receipt_idx, sir) = self.promise_idx_to_receipt_idx_with_sir(promise_index)?; + let (receipt_idx, sir) = self.promise_idx_to_receipt_idx_with_sir(promise_idx)?; // Input can't be large enough to overflow let num_bytes = method_name.len() as u64 + arguments.len() as u64; @@ -2560,9 +2560,11 @@ impl<'a> VMLogic<'a> { if !self.context.is_view() { // Distribute unused gas to scheduled function calls let unused_gas = self.context.prepaid_gas - self.gas_counter.used_gas(); - let distributed_gas = self.ext.distribute_unused_gas(unused_gas); - // Distributed gas must be below gas available if `distribute_unused_gas` is correct. - self.gas_counter.prepay_gas(distributed_gas).unwrap(); + + // Distribute the unused gas and prepay for the gas. + if self.ext.distribute_unused_gas(unused_gas) { + self.gas_counter.prepay_gas(unused_gas).unwrap(); + } } let burnt_gas = self.gas_counter.burnt_gas(); diff --git a/runtime/near-vm-logic/src/mocks/mock_external.rs b/runtime/near-vm-logic/src/mocks/mock_external.rs index 79f8221b583..f1d79a3f7ce 100644 --- a/runtime/near-vm-logic/src/mocks/mock_external.rs +++ b/runtime/near-vm-logic/src/mocks/mock_external.rs @@ -11,17 +11,14 @@ pub struct MockedExternal { receipts: Vec, pub validators: HashMap, #[cfg(feature = "protocol_feature_function_call_weight")] - distribute_leftover_gas_to: Vec, - #[cfg(feature = "protocol_feature_function_call_weight")] - gas_weight_sum: u64, + gas_weights: Vec<(FunctionCallActionIndex, u64)>, } #[derive(Clone)] #[cfg(feature = "protocol_feature_function_call_weight")] -struct GasWeightMetadata { +struct FunctionCallActionIndex { receipt_index: usize, action_index: usize, - gas_weight: u64, } pub struct MockedValuePtr { @@ -143,13 +140,10 @@ impl External for MockedExternal { let receipt_index = receipt_index as usize; let receipt = self.receipts.get_mut(receipt_index).unwrap(); if gas_weight > 0 { - self.distribute_leftover_gas_to.push(GasWeightMetadata { - receipt_index, - action_index: receipt.actions.len(), + self.gas_weights.push(( + FunctionCallActionIndex { receipt_index, action_index: receipt.actions.len() }, gas_weight, - }); - self.gas_weight_sum = - self.gas_weight_sum.checked_add(gas_weight).ok_or(HostError::IntegerOverflow)?; + )); } receipt.actions.push(Action::FunctionCall(FunctionCallAction { @@ -254,29 +248,44 @@ impl External for MockedExternal { } #[cfg(feature = "protocol_feature_function_call_weight")] - fn distribute_unused_gas(&mut self, gas: Gas) -> Gas { - if self.gas_weight_sum != 0 { - let gas_per_weight = gas / self.gas_weight_sum; - - self.distribute_leftover_gas_to - .drain(..) - .map(|GasWeightMetadata { receipt_index, action_index, gas_weight }| { - let assign_gas = gas_per_weight * gas_weight; - if let Some(Action::FunctionCall(FunctionCallAction { ref mut gas, .. })) = self - .receipts - .get_mut(receipt_index) - .and_then(|receipt| receipt.actions.get_mut(action_index)) - { - *gas += assign_gas; - } else { - panic!("Invalid index for assigning unused gas weight"); - } - - assign_gas + fn distribute_unused_gas(&mut self, gas: Gas) -> bool { + let gas_weight_sum: u128 = + self.gas_weights.iter().map(|(_, gas_weight)| *gas_weight as u128).sum(); + if gas_weight_sum != 0 { + let gas_per_weight = (gas as u128 / gas_weight_sum) as u64; + + let mut distribute_gas = |metadata: &FunctionCallActionIndex, assigned_gas: u64| { + let FunctionCallActionIndex { receipt_index, action_index } = metadata; + if let Some(Action::FunctionCall(FunctionCallAction { ref mut gas, .. })) = self + .receipts + .get_mut(*receipt_index) + .and_then(|receipt| receipt.actions.get_mut(*action_index)) + { + *gas += assigned_gas; + } else { + panic!("Invalid index for assigning unused gas weight"); + } + }; + + let distributed: u64 = self + .gas_weights + .iter() + .map(|(action_index, gas_weight)| { + let assigned_gas = gas_per_weight * gas_weight; + + distribute_gas(action_index, assigned_gas); + + assigned_gas }) - .sum() + .sum(); + + // Distribute remaining gas to final action. + if let Some((last_idx, _)) = self.gas_weights.last() { + distribute_gas(last_idx, gas - distributed); + } + true } else { - 0 + false } } } diff --git a/runtime/runtime/src/ext.rs b/runtime/runtime/src/ext.rs index 1eac4a7a51d..f962819e003 100644 --- a/runtime/runtime/src/ext.rs +++ b/runtime/runtime/src/ext.rs @@ -37,16 +37,13 @@ pub struct RuntimeExt<'a> { current_protocol_version: ProtocolVersion, #[cfg(feature = "protocol_feature_function_call_weight")] - distribute_leftover_gas_to: Vec, - #[cfg(feature = "protocol_feature_function_call_weight")] - gas_weight_sum: u64, + gas_weights: Vec<(FunctionCallActionIndex, u64)>, } #[cfg(feature = "protocol_feature_function_call_weight")] -struct GasWeightMetadata { +struct FunctionCallActionIndex { receipt_index: usize, action_index: usize, - gas_weight: u64, } /// Error used by `RuntimeExt`. @@ -107,9 +104,7 @@ impl<'a> RuntimeExt<'a> { current_protocol_version, #[cfg(feature = "protocol_feature_function_call_weight")] - distribute_leftover_gas_to: vec![], - #[cfg(feature = "protocol_feature_function_call_weight")] - gas_weight_sum: 0, + gas_weights: vec![], } } @@ -298,13 +293,10 @@ impl<'a> External for RuntimeExt<'a> { ); if gas_weight > 0 { - self.distribute_leftover_gas_to.push(GasWeightMetadata { - receipt_index: receipt_index as usize, - action_index, + self.gas_weights.push(( + FunctionCallActionIndex { receipt_index: receipt_index as usize, action_index }, gas_weight, - }); - self.gas_weight_sum = - self.gas_weight_sum.checked_add(gas_weight).ok_or(HostError::IntegerOverflow)?; + )); } Ok(()) @@ -447,29 +439,44 @@ impl<'a> External for RuntimeExt<'a> { } #[cfg(feature = "protocol_feature_function_call_weight")] - fn distribute_unused_gas(&mut self, gas: u64) -> u64 { - if self.gas_weight_sum != 0 { - let gas_per_ratio = gas / self.gas_weight_sum; - - self.distribute_leftover_gas_to - .drain(..) - .map(|GasWeightMetadata { receipt_index, action_index, gas_weight }| { - let assign_gas = gas_per_ratio * gas_weight; - if let Some(Action::FunctionCall(FunctionCallAction { ref mut gas, .. })) = self - .action_receipts - .get_mut(receipt_index) - .and_then(|(_, receipt)| receipt.actions.get_mut(action_index)) - { - *gas += assign_gas; - } else { - panic!("Invalid index for assigning unused gas weight"); - } - - assign_gas + fn distribute_unused_gas(&mut self, gas: u64) -> bool { + let gas_weight_sum: u128 = + self.gas_weights.iter().map(|(_, gas_weight)| *gas_weight as u128).sum(); + if gas_weight_sum != 0 { + let gas_per_weight = (gas as u128 / gas_weight_sum) as u64; + + let mut distribute_gas = |metadata: &FunctionCallActionIndex, assigned_gas: u64| { + let FunctionCallActionIndex { receipt_index, action_index } = metadata; + if let Some(Action::FunctionCall(FunctionCallAction { ref mut gas, .. })) = self + .action_receipts + .get_mut(*receipt_index) + .and_then(|(_, receipt)| receipt.actions.get_mut(*action_index)) + { + *gas += assigned_gas; + } else { + panic!("Invalid index for assigning unused gas weight"); + } + }; + + let distributed: u64 = self + .gas_weights + .iter() + .map(|(action_index, gas_weight)| { + let assigned_gas = gas_per_weight * gas_weight; + + distribute_gas(action_index, assigned_gas); + + assigned_gas }) - .sum() + .sum(); + + // Distribute remaining gas to final action. + if let Some((last_idx, _)) = self.gas_weights.last() { + distribute_gas(last_idx, gas - distributed); + } + true } else { - 0 + false } } } From ac4c2f04dcfc2c6ee7bb0164c55c1f18c4ed9d66 Mon Sep 17 00:00:00 2001 From: austinabell Date: Fri, 18 Feb 2022 15:33:42 -0700 Subject: [PATCH 05/19] wrap gas weight and fix clearing weights --- runtime/near-vm-logic/src/mocks/mock_external.rs | 14 +++++++++----- runtime/runtime/src/ext.rs | 14 +++++++++----- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/runtime/near-vm-logic/src/mocks/mock_external.rs b/runtime/near-vm-logic/src/mocks/mock_external.rs index f1d79a3f7ce..dd4a11608de 100644 --- a/runtime/near-vm-logic/src/mocks/mock_external.rs +++ b/runtime/near-vm-logic/src/mocks/mock_external.rs @@ -11,7 +11,7 @@ pub struct MockedExternal { receipts: Vec, pub validators: HashMap, #[cfg(feature = "protocol_feature_function_call_weight")] - gas_weights: Vec<(FunctionCallActionIndex, u64)>, + gas_weights: Vec<(FunctionCallActionIndex, GasWeight)>, } #[derive(Clone)] @@ -21,6 +21,9 @@ struct FunctionCallActionIndex { action_index: usize, } +#[derive(Clone)] +struct GasWeight(u64); + pub struct MockedValuePtr { value: Vec, } @@ -142,7 +145,7 @@ impl External for MockedExternal { if gas_weight > 0 { self.gas_weights.push(( FunctionCallActionIndex { receipt_index, action_index: receipt.actions.len() }, - gas_weight, + GasWeight(gas_weight), )); } @@ -250,7 +253,7 @@ impl External for MockedExternal { #[cfg(feature = "protocol_feature_function_call_weight")] fn distribute_unused_gas(&mut self, gas: Gas) -> bool { let gas_weight_sum: u128 = - self.gas_weights.iter().map(|(_, gas_weight)| *gas_weight as u128).sum(); + self.gas_weights.iter().map(|(_, GasWeight(weight))| *weight as u128).sum(); if gas_weight_sum != 0 { let gas_per_weight = (gas as u128 / gas_weight_sum) as u64; @@ -270,8 +273,8 @@ impl External for MockedExternal { let distributed: u64 = self .gas_weights .iter() - .map(|(action_index, gas_weight)| { - let assigned_gas = gas_per_weight * gas_weight; + .map(|(action_index, GasWeight(weight))| { + let assigned_gas = gas_per_weight * weight; distribute_gas(action_index, assigned_gas); @@ -283,6 +286,7 @@ impl External for MockedExternal { if let Some((last_idx, _)) = self.gas_weights.last() { distribute_gas(last_idx, gas - distributed); } + self.gas_weights.clear(); true } else { false diff --git a/runtime/runtime/src/ext.rs b/runtime/runtime/src/ext.rs index f962819e003..c578c05df6c 100644 --- a/runtime/runtime/src/ext.rs +++ b/runtime/runtime/src/ext.rs @@ -37,7 +37,7 @@ pub struct RuntimeExt<'a> { current_protocol_version: ProtocolVersion, #[cfg(feature = "protocol_feature_function_call_weight")] - gas_weights: Vec<(FunctionCallActionIndex, u64)>, + gas_weights: Vec<(FunctionCallActionIndex, GasWeight)>, } #[cfg(feature = "protocol_feature_function_call_weight")] @@ -46,6 +46,9 @@ struct FunctionCallActionIndex { action_index: usize, } +#[derive(Clone)] +struct GasWeight(u64); + /// Error used by `RuntimeExt`. #[derive(Debug, Clone, PartialEq, Eq)] pub(crate) enum ExternalError { @@ -295,7 +298,7 @@ impl<'a> External for RuntimeExt<'a> { if gas_weight > 0 { self.gas_weights.push(( FunctionCallActionIndex { receipt_index: receipt_index as usize, action_index }, - gas_weight, + GasWeight(gas_weight), )); } @@ -441,7 +444,7 @@ impl<'a> External for RuntimeExt<'a> { #[cfg(feature = "protocol_feature_function_call_weight")] fn distribute_unused_gas(&mut self, gas: u64) -> bool { let gas_weight_sum: u128 = - self.gas_weights.iter().map(|(_, gas_weight)| *gas_weight as u128).sum(); + self.gas_weights.iter().map(|(_, GasWeight(weight))| *weight as u128).sum(); if gas_weight_sum != 0 { let gas_per_weight = (gas as u128 / gas_weight_sum) as u64; @@ -461,8 +464,8 @@ impl<'a> External for RuntimeExt<'a> { let distributed: u64 = self .gas_weights .iter() - .map(|(action_index, gas_weight)| { - let assigned_gas = gas_per_weight * gas_weight; + .map(|(action_index, GasWeight(weight))| { + let assigned_gas = gas_per_weight * weight; distribute_gas(action_index, assigned_gas); @@ -474,6 +477,7 @@ impl<'a> External for RuntimeExt<'a> { if let Some((last_idx, _)) = self.gas_weights.last() { distribute_gas(last_idx, gas - distributed); } + self.gas_weights.clear(); true } else { false From 60d7293bcbfb22dff083a453845a135fdbba9421 Mon Sep 17 00:00:00 2001 From: austinabell Date: Fri, 18 Feb 2022 23:40:12 -0700 Subject: [PATCH 06/19] add test for weight over u64 bounds --- runtime/near-vm-logic/src/tests/gas_counter.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/runtime/near-vm-logic/src/tests/gas_counter.rs b/runtime/near-vm-logic/src/tests/gas_counter.rs index ae722973aba..d799b964336 100644 --- a/runtime/near-vm-logic/src/tests/gas_counter.rs +++ b/runtime/near-vm-logic/src/tests/gas_counter.rs @@ -109,19 +109,19 @@ fn function_call_weight_check(function_calls: impl IntoIterator= gas_limit); + assert!(outcome.used_gas as u128 + weight_sum - 1 >= gas_limit as u128); } #[cfg(feature = "protocol_feature_function_call_weight")] @@ -153,6 +153,9 @@ fn function_call_weight_single_smoke_test() { (1_000_000_000, 0), (0, 4), ]); + + // Weight over u64 bounds + function_call_weight_check([(0, u64::MAX), (0, 1000)]); } impl VMLogicBuilder { From 78d4a95212d4caec39e020fadfddf4bb5096369b Mon Sep 17 00:00:00 2001 From: austinabell Date: Mon, 28 Feb 2022 16:20:03 -0500 Subject: [PATCH 07/19] expose GasWeight newtype to trait interface --- core/primitives-core/src/types.rs | 4 ++++ runtime/near-vm-logic/src/dependencies.rs | 4 +++- runtime/near-vm-logic/src/logic.rs | 8 ++++---- .../near-vm-logic/src/mocks/mock_external.rs | 13 ++++++------- runtime/near-vm-logic/src/tests/gas_counter.rs | 4 +++- runtime/near-vm-logic/src/tests/helpers.rs | 4 +++- runtime/runtime/src/ext.rs | 17 ++++++++--------- 7 files changed, 31 insertions(+), 23 deletions(-) diff --git a/core/primitives-core/src/types.rs b/core/primitives-core/src/types.rs index a44a530d3f7..8f356911db1 100644 --- a/core/primitives-core/src/types.rs +++ b/core/primitives-core/src/types.rs @@ -24,6 +24,10 @@ pub type ShardId = u64; pub type Balance = u128; /// Gas is a type for storing amount of gas. pub type Gas = u64; +/// Weight of unused gas to distribute to scheduled function call actions. +/// Used in `promise_batch_action_function_call_weight` host function. +#[derive(Clone, Debug)] +pub struct GasWeight(pub u64); /// Number of blocks in current group. pub type NumBlocks = u64; diff --git a/runtime/near-vm-logic/src/dependencies.rs b/runtime/near-vm-logic/src/dependencies.rs index a204b6a19ec..d82d70e21e2 100644 --- a/runtime/near-vm-logic/src/dependencies.rs +++ b/runtime/near-vm-logic/src/dependencies.rs @@ -1,6 +1,8 @@ //! External dependencies of the near-vm-logic. use crate::types::{PublicKey, ReceiptIndex}; +#[cfg(feature = "protocol_feature_function_call_weight")] +use near_primitives_core::types::GasWeight; use near_primitives_core::types::{AccountId, Balance, Gas}; use near_vm_errors::VMLogicError; @@ -318,7 +320,7 @@ pub trait External { arguments: Vec, attached_deposit: Balance, prepaid_gas: Gas, - gas_weight: u64, + gas_weight: GasWeight, ) -> Result<()>; /// Attach the [`TransferAction`] action to an existing receipt. diff --git a/runtime/near-vm-logic/src/logic.rs b/runtime/near-vm-logic/src/logic.rs index 675fb998c2f..b2c97ac3e49 100644 --- a/runtime/near-vm-logic/src/logic.rs +++ b/runtime/near-vm-logic/src/logic.rs @@ -14,7 +14,7 @@ use near_primitives_core::runtime::fees::{ transfer_exec_fee, transfer_send_fee, RuntimeFeesConfig, }; use near_primitives_core::types::{ - AccountId, Balance, EpochHeight, Gas, ProtocolVersion, StorageUsage, + AccountId, Balance, EpochHeight, Gas, GasWeight, ProtocolVersion, StorageUsage, }; use near_vm_errors::InconsistentStateError; use near_vm_errors::{HostError, VMLogicError}; @@ -1515,7 +1515,7 @@ impl<'a> VMLogic<'a> { arguments_ptr, amount_ptr, gas, - 0, + GasWeight(0), ) } @@ -1557,8 +1557,8 @@ impl<'a> VMLogic<'a> { arguments_len: u64, arguments_ptr: u64, amount_ptr: u64, - gas: u64, - gas_weight: u64, + gas: Gas, + gas_weight: GasWeight, ) -> Result<()> { self.gas_counter.pay_base(base)?; if self.context.is_view() { diff --git a/runtime/near-vm-logic/src/mocks/mock_external.rs b/runtime/near-vm-logic/src/mocks/mock_external.rs index dd4a11608de..fc1450f486a 100644 --- a/runtime/near-vm-logic/src/mocks/mock_external.rs +++ b/runtime/near-vm-logic/src/mocks/mock_external.rs @@ -1,4 +1,6 @@ use crate::{External, ValuePtr}; +#[cfg(feature = "protocol_feature_function_call_weight")] +use near_primitives::types::GasWeight; use near_primitives_core::types::{AccountId, Balance, Gas}; use near_vm_errors::HostError; use serde::{Deserialize, Serialize}; @@ -21,9 +23,6 @@ struct FunctionCallActionIndex { action_index: usize, } -#[derive(Clone)] -struct GasWeight(u64); - pub struct MockedValuePtr { value: Vec, } @@ -137,15 +136,15 @@ impl External for MockedExternal { method_name: Vec, arguments: Vec, attached_deposit: u128, - prepaid_gas: u64, - gas_weight: u64, + prepaid_gas: Gas, + gas_weight: GasWeight, ) -> Result<()> { let receipt_index = receipt_index as usize; let receipt = self.receipts.get_mut(receipt_index).unwrap(); - if gas_weight > 0 { + if gas_weight.0 > 0 { self.gas_weights.push(( FunctionCallActionIndex { receipt_index, action_index: receipt.actions.len() }, - GasWeight(gas_weight), + gas_weight, )); } diff --git a/runtime/near-vm-logic/src/tests/gas_counter.rs b/runtime/near-vm-logic/src/tests/gas_counter.rs index d799b964336..f724f09aa23 100644 --- a/runtime/near-vm-logic/src/tests/gas_counter.rs +++ b/runtime/near-vm-logic/src/tests/gas_counter.rs @@ -104,6 +104,8 @@ fn test_hit_prepaid_gas_limit() { #[cfg(feature = "protocol_feature_function_call_weight")] fn function_call_weight_check(function_calls: impl IntoIterator) { + use near_primitives::types::GasWeight; + let gas_limit = 10u64.pow(14); let mut logic_builder = VMLogicBuilder::default().max_gas_burnt(gas_limit); @@ -112,7 +114,7 @@ fn function_call_weight_check(function_calls: impl IntoIterator Result<()> { let method_id = b"promise_batch_action"; let args = b"promise_batch_action_args"; diff --git a/runtime/runtime/src/ext.rs b/runtime/runtime/src/ext.rs index c578c05df6c..b30ca518166 100644 --- a/runtime/runtime/src/ext.rs +++ b/runtime/runtime/src/ext.rs @@ -14,7 +14,9 @@ use near_primitives::transaction::{ DeployContractAction, FunctionCallAction, StakeAction, TransferAction, }; use near_primitives::trie_key::{trie_key_parsers, TrieKey}; -use near_primitives::types::{AccountId, Balance, EpochId, EpochInfoProvider}; +#[cfg(feature = "protocol_feature_function_call_weight")] +use near_primitives::types::GasWeight; +use near_primitives::types::{AccountId, Balance, EpochId, EpochInfoProvider, Gas}; use near_primitives::utils::create_data_id; use near_primitives::version::ProtocolVersion; use near_store::{get_code, TrieUpdate, TrieUpdateValuePtr}; @@ -46,9 +48,6 @@ struct FunctionCallActionIndex { action_index: usize, } -#[derive(Clone)] -struct GasWeight(u64); - /// Error used by `RuntimeExt`. #[derive(Debug, Clone, PartialEq, Eq)] pub(crate) enum ExternalError { @@ -281,8 +280,8 @@ impl<'a> External for RuntimeExt<'a> { method_name: Vec, args: Vec, attached_deposit: u128, - prepaid_gas: u64, - gas_weight: u64, + prepaid_gas: Gas, + gas_weight: GasWeight, ) -> ExtResult<()> { let action_index = self.append_action( receipt_index, @@ -295,10 +294,10 @@ impl<'a> External for RuntimeExt<'a> { }), ); - if gas_weight > 0 { + if gas_weight.0 > 0 { self.gas_weights.push(( FunctionCallActionIndex { receipt_index: receipt_index as usize, action_index }, - GasWeight(gas_weight), + gas_weight, )); } @@ -311,7 +310,7 @@ impl<'a> External for RuntimeExt<'a> { method_name: Vec, args: Vec, attached_deposit: u128, - prepaid_gas: u64, + prepaid_gas: Gas, ) -> ExtResult<()> { self.append_action( receipt_index, From ab3e7acbf3f7e7ed5c860f579796713158fed1ab Mon Sep 17 00:00:00 2001 From: austinabell Date: Tue, 1 Mar 2022 19:19:24 -0500 Subject: [PATCH 08/19] addr comments --- core/primitives/src/version.rs | 2 -- runtime/near-vm-logic/src/dependencies.rs | 2 -- runtime/near-vm-logic/src/logic.rs | 8 +++++-- .../near-vm-logic/src/mocks/mock_external.rs | 15 +++++------- .../near-vm-logic/src/tests/gas_counter.rs | 24 ++++++++++++------- runtime/near-vm-logic/src/tests/miscs.rs | 22 ++++++++--------- runtime/near-vm-runner/src/wasmer2_runner.rs | 6 ++--- runtime/near-vm-runner/src/wasmer_runner.rs | 6 ++--- runtime/near-vm-runner/src/wasmtime_runner.rs | 18 ++++++++++---- runtime/runtime/src/ext.rs | 15 +++++------- 10 files changed, 63 insertions(+), 55 deletions(-) diff --git a/core/primitives/src/version.rs b/core/primitives/src/version.rs index 81ded4f03ff..51f1535ec8d 100644 --- a/core/primitives/src/version.rs +++ b/core/primitives/src/version.rs @@ -166,7 +166,6 @@ const MAIN_NET_PROTOCOL_VERSION: ProtocolVersion = 52; pub const PROTOCOL_VERSION: ProtocolVersion = MAIN_NET_PROTOCOL_VERSION; /// Current latest nightly version of the protocol. #[cfg(feature = "nightly_protocol")] -// TODO remember to update this also pub const PROTOCOL_VERSION: ProtocolVersion = 127; impl ProtocolFeature { @@ -208,7 +207,6 @@ impl ProtocolFeature { #[cfg(feature = "protocol_feature_fix_staking_threshold")] ProtocolFeature::FixStakingThreshold => 126, #[cfg(feature = "protocol_feature_function_call_weight")] - // TODO idk where this number comes from ProtocolFeature::FunctionCallWeight => 127, } } diff --git a/runtime/near-vm-logic/src/dependencies.rs b/runtime/near-vm-logic/src/dependencies.rs index d82d70e21e2..9a0d4aca455 100644 --- a/runtime/near-vm-logic/src/dependencies.rs +++ b/runtime/near-vm-logic/src/dependencies.rs @@ -311,8 +311,6 @@ pub trait External { /// /// Panics if the `receipt_index` does not refer to a known receipt. #[cfg(feature = "protocol_feature_function_call_weight")] - // TODO: unclear if this should be a supertrait to avoid semver breaking changes - // TODO: or if we can just modify the existing function fn append_action_function_call_weight( &mut self, receipt_index: ReceiptIndex, diff --git a/runtime/near-vm-logic/src/logic.rs b/runtime/near-vm-logic/src/logic.rs index b2c97ac3e49..0b3a286fb11 100644 --- a/runtime/near-vm-logic/src/logic.rs +++ b/runtime/near-vm-logic/src/logic.rs @@ -2553,9 +2553,13 @@ impl<'a> VMLogic<'a> { })) } - /// Computes the outcome of execution. + /// Computes the outcome of the execution. + /// + /// If `FunctionCallWeight` protocol feature (127) is enabled, unused gas will be + /// distributed to functions that specify a gas weight. If there are no functions with + /// a gas weight, the outcome will contain unused gas as usual. #[allow(unused_mut)] - pub fn outcome(mut self) -> VMOutcome { + pub fn compute_outcome_and_distribute_gas(mut self) -> VMOutcome { #[cfg(feature = "protocol_feature_function_call_weight")] if !self.context.is_view() { // Distribute unused gas to scheduled function calls diff --git a/runtime/near-vm-logic/src/mocks/mock_external.rs b/runtime/near-vm-logic/src/mocks/mock_external.rs index fc1450f486a..6417be2b5cd 100644 --- a/runtime/near-vm-logic/src/mocks/mock_external.rs +++ b/runtime/near-vm-logic/src/mocks/mock_external.rs @@ -269,17 +269,14 @@ impl External for MockedExternal { } }; - let distributed: u64 = self - .gas_weights - .iter() - .map(|(action_index, GasWeight(weight))| { - let assigned_gas = gas_per_weight * weight; + let mut distributed = 0; + for (action_index, GasWeight(weight)) in &self.gas_weights { + let assigned_gas = gas_per_weight * weight; - distribute_gas(action_index, assigned_gas); + distribute_gas(action_index, assigned_gas); - assigned_gas - }) - .sum(); + distributed += assigned_gas + } // Distribute remaining gas to final action. if let Some((last_idx, _)) = self.gas_weights.last() { diff --git a/runtime/near-vm-logic/src/tests/gas_counter.rs b/runtime/near-vm-logic/src/tests/gas_counter.rs index f724f09aa23..49a3c080086 100644 --- a/runtime/near-vm-logic/src/tests/gas_counter.rs +++ b/runtime/near-vm-logic/src/tests/gas_counter.rs @@ -14,7 +14,7 @@ fn test_dont_burn_gas_when_exceeding_attached_gas_limit() { let index = promise_create(&mut logic, b"rick.test", 0, 0).expect("should create a promise"); promise_batch_action_function_call(&mut logic, index, 0, gas_limit * 2) .expect_err("should fail with gas limit"); - let outcome = logic.outcome(); + let outcome = logic.compute_outcome_and_distribute_gas(); // Just avoid hard-coding super-precise amount of gas burnt. assert!(outcome.burnt_gas < gas_limit / 2); @@ -33,7 +33,7 @@ fn test_limit_wasm_gas_after_attaching_gas() { promise_batch_action_function_call(&mut logic, index, 0, gas_limit / 2) .expect("should add action to receipt"); logic.gas((op_limit / 2) as u32).expect_err("should fail with gas limit"); - let outcome = logic.outcome(); + let outcome = logic.compute_outcome_and_distribute_gas(); assert_eq!(outcome.used_gas, gas_limit); assert!(gas_limit / 2 < outcome.burnt_gas); @@ -49,7 +49,7 @@ fn test_cant_burn_more_than_max_gas_burnt_gas() { let mut logic = logic_builder.build_with_prepaid_gas(gas_limit * 2); logic.gas(op_limit * 3).expect_err("should fail with gas limit"); - let outcome = logic.outcome(); + let outcome = logic.compute_outcome_and_distribute_gas(); assert_eq!(outcome.burnt_gas, gas_limit); assert_eq!(outcome.used_gas, gas_limit * 2); @@ -64,7 +64,7 @@ fn test_cant_burn_more_than_prepaid_gas() { let mut logic = logic_builder.build_with_prepaid_gas(gas_limit); logic.gas(op_limit * 3).expect_err("should fail with gas limit"); - let outcome = logic.outcome(); + let outcome = logic.compute_outcome_and_distribute_gas(); assert_eq!(outcome.burnt_gas, gas_limit); assert_eq!(outcome.used_gas, gas_limit); @@ -80,7 +80,7 @@ fn test_hit_max_gas_burnt_limit() { promise_create(&mut logic, b"rick.test", 0, gas_limit / 2).expect("should create a promise"); logic.gas(op_limit * 2).expect_err("should fail with gas limit"); - let outcome = logic.outcome(); + let outcome = logic.compute_outcome_and_distribute_gas(); assert_eq!(outcome.burnt_gas, gas_limit); assert!(outcome.used_gas > gas_limit * 2); @@ -96,7 +96,7 @@ fn test_hit_prepaid_gas_limit() { promise_create(&mut logic, b"rick.test", 0, gas_limit / 2).expect("should create a promise"); logic.gas(op_limit * 2).expect_err("should fail with gas limit"); - let outcome = logic.outcome(); + let outcome = logic.compute_outcome_and_distribute_gas(); assert_eq!(outcome.burnt_gas, gas_limit); assert_eq!(outcome.used_gas, gas_limit); @@ -114,12 +114,18 @@ fn function_call_weight_check(function_calls: impl IntoIterator( } let err = run_method(&module, &import_object, method_name).err(); - (Some(logic.outcome()), err) + (Some(logic.compute_outcome_and_distribute_gas()), err) } pub(crate) fn wasmer0_vm_hash() -> u64 { @@ -346,7 +346,7 @@ impl crate::runner::VM for Wasmer0VM { // TODO: remove, as those costs are incorrectly computed, and we shall account it on deployment. if logic.add_contract_compile_fee(code.code().len() as u64).is_err() { return ( - Some(logic.outcome()), + Some(logic.compute_outcome_and_distribute_gas()), Some(VMError::FunctionCallError(FunctionCallError::HostError( near_vm_errors::HostError::GasExceeded, ))), @@ -361,7 +361,7 @@ impl crate::runner::VM for Wasmer0VM { } let err = run_method(&module, &import_object, method_name).err(); - (Some(logic.outcome()), err) + (Some(logic.compute_outcome_and_distribute_gas()), err) } fn precompile( diff --git a/runtime/near-vm-runner/src/wasmtime_runner.rs b/runtime/near-vm-runner/src/wasmtime_runner.rs index 073a3e1fb0d..1d5347b6a94 100644 --- a/runtime/near-vm-runner/src/wasmtime_runner.rs +++ b/runtime/near-vm-runner/src/wasmtime_runner.rs @@ -227,7 +227,7 @@ impl crate::runner::VM for WasmtimeVM { // TODO: remove, as those costs are incorrectly computed, and we shall account it on deployment. if logic.add_contract_compile_fee(code.code().len() as u64).is_err() { return ( - Some(logic.outcome()), + Some(logic.compute_outcome_and_distribute_gas()), Some(VMError::FunctionCallError(FunctionCallError::HostError( near_vm_errors::HostError::GasExceeded, ))), @@ -282,10 +282,16 @@ impl crate::runner::VM for WasmtimeVM { Ok(instance) => match instance.get_func(&mut store, method_name) { Some(func) => match func.typed::<(), (), _>(&mut store) { Ok(run) => match run.call(&mut store, ()) { - Ok(_) => (Some(logic.outcome()), None), - Err(err) => (Some(logic.outcome()), Some(err.into_vm_error())), + Ok(_) => (Some(logic.compute_outcome_and_distribute_gas()), None), + Err(err) => ( + Some(logic.compute_outcome_and_distribute_gas()), + Some(err.into_vm_error()), + ), }, - Err(err) => (Some(logic.outcome()), Some(err.into_vm_error())), + Err(err) => ( + Some(logic.compute_outcome_and_distribute_gas()), + Some(err.into_vm_error()), + ), }, None => ( None, @@ -294,7 +300,9 @@ impl crate::runner::VM for WasmtimeVM { ))), ), }, - Err(err) => (Some(logic.outcome()), Some(err.into_vm_error())), + Err(err) => { + (Some(logic.compute_outcome_and_distribute_gas()), Some(err.into_vm_error())) + } } } diff --git a/runtime/runtime/src/ext.rs b/runtime/runtime/src/ext.rs index 1e15845120c..29425811d61 100644 --- a/runtime/runtime/src/ext.rs +++ b/runtime/runtime/src/ext.rs @@ -460,17 +460,14 @@ impl<'a> External for RuntimeExt<'a> { } }; - let distributed: u64 = self - .gas_weights - .iter() - .map(|(action_index, GasWeight(weight))| { - let assigned_gas = gas_per_weight * weight; + let mut distributed = 0; + for (action_index, GasWeight(weight)) in &self.gas_weights { + let assigned_gas = gas_per_weight * weight; - distribute_gas(action_index, assigned_gas); + distribute_gas(action_index, assigned_gas); - assigned_gas - }) - .sum(); + distributed += assigned_gas + } // Distribute remaining gas to final action. if let Some((last_idx, _)) = self.gas_weights.last() { From 9bf98462e86ecc6ef9e8e901ef16e813984c9c30 Mon Sep 17 00:00:00 2001 From: austinabell Date: Wed, 2 Mar 2022 12:16:35 -0500 Subject: [PATCH 09/19] address nagisa's comments --- runtime/near-vm-logic/src/dependencies.rs | 1 - runtime/runtime/src/ext.rs | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/near-vm-logic/src/dependencies.rs b/runtime/near-vm-logic/src/dependencies.rs index 9a0d4aca455..e85d6e05a08 100644 --- a/runtime/near-vm-logic/src/dependencies.rs +++ b/runtime/near-vm-logic/src/dependencies.rs @@ -533,7 +533,6 @@ pub trait External { fn validator_total_stake(&self) -> Result; /// Distribute the gas among the scheduled function calls that specify a gas weight. - /// Returns the amount of distributed gas. /// /// # Arguments /// diff --git a/runtime/runtime/src/ext.rs b/runtime/runtime/src/ext.rs index 29425811d61..26230c1365f 100644 --- a/runtime/runtime/src/ext.rs +++ b/runtime/runtime/src/ext.rs @@ -154,6 +154,7 @@ impl<'a> RuntimeExt<'a> { .collect() } + /// Appends an action and returns the index the action was inserted in the receipt fn append_action(&mut self, receipt_index: u64, action: Action) -> usize { let actions = &mut self .action_receipts From 6d0ec3aac556e2e3dc9a9cad9862c27e419e3037 Mon Sep 17 00:00:00 2001 From: austinabell Date: Wed, 2 Mar 2022 13:17:31 -0500 Subject: [PATCH 10/19] fix lint ignore --- runtime/near-vm-logic/src/logic.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/near-vm-logic/src/logic.rs b/runtime/near-vm-logic/src/logic.rs index 0b3a286fb11..dd54b8d4702 100644 --- a/runtime/near-vm-logic/src/logic.rs +++ b/runtime/near-vm-logic/src/logic.rs @@ -2558,7 +2558,7 @@ impl<'a> VMLogic<'a> { /// If `FunctionCallWeight` protocol feature (127) is enabled, unused gas will be /// distributed to functions that specify a gas weight. If there are no functions with /// a gas weight, the outcome will contain unused gas as usual. - #[allow(unused_mut)] + #[cfg_attr(not(feature = "protocol_feature_function_call_weight"), allow(unused_mut))] pub fn compute_outcome_and_distribute_gas(mut self) -> VMOutcome { #[cfg(feature = "protocol_feature_function_call_weight")] if !self.context.is_view() { From 0436ab68781d316bf3e29ff0f1c093d55f9fb2c4 Mon Sep 17 00:00:00 2001 From: austinabell Date: Thu, 3 Mar 2022 10:30:12 -0500 Subject: [PATCH 11/19] switch vm logic function to have generic callback and gate by feature --- runtime/near-vm-logic/src/logic.rs | 64 +++++++++++++++++++----------- 1 file changed, 40 insertions(+), 24 deletions(-) diff --git a/runtime/near-vm-logic/src/logic.rs b/runtime/near-vm-logic/src/logic.rs index dd54b8d4702..874ac6698ad 100644 --- a/runtime/near-vm-logic/src/logic.rs +++ b/runtime/near-vm-logic/src/logic.rs @@ -1507,7 +1507,10 @@ impl<'a> VMLogic<'a> { amount_ptr: u64, gas: Gas, ) -> Result<()> { - self.promise_batch_action_function_call_weight( + let append_action_fn = |vm: &mut Self, receipt_idx, method_name, arguments, amount, gas| { + vm.ext.append_action_function_call(receipt_idx, method_name, arguments, amount, gas) + }; + self.internal_promise_batch_action_function_call( promise_idx, method_name_len, method_name_ptr, @@ -1515,7 +1518,7 @@ impl<'a> VMLogic<'a> { arguments_ptr, amount_ptr, gas, - GasWeight(0), + append_action_fn, ) } @@ -1549,6 +1552,7 @@ impl<'a> VMLogic<'a> { /// `MemoryAccessViolation`. /// * If called as view function returns `ProhibitedInView`. /// - If `0` is passed for both `gas` and `gas_weight` parameters + #[cfg(feature = "protocol_feature_function_call_weight")] pub fn promise_batch_action_function_call_weight( &mut self, promise_idx: u64, @@ -1559,6 +1563,39 @@ impl<'a> VMLogic<'a> { amount_ptr: u64, gas: Gas, gas_weight: GasWeight, + ) -> Result<()> { + let append_action_fn = |vm: &mut Self, receipt_idx, method_name, arguments, amount, gas| { + vm.ext.append_action_function_call_weight( + receipt_idx, + method_name, + arguments, + amount, + gas, + gas_weight, + ) + }; + self.internal_promise_batch_action_function_call( + promise_idx, + method_name_len, + method_name_ptr, + arguments_len, + arguments_ptr, + amount_ptr, + gas, + append_action_fn, + ) + } + + fn internal_promise_batch_action_function_call( + &mut self, + promise_idx: u64, + method_name_len: u64, + method_name_ptr: u64, + arguments_len: u64, + arguments_ptr: u64, + amount_ptr: u64, + gas: Gas, + append_action_fn: impl FnOnce(&mut Self, u64, Vec, Vec, u128, u64) -> Result<()>, ) -> Result<()> { self.gas_counter.pay_base(base)?; if self.context.is_view() { @@ -1594,28 +1631,7 @@ impl<'a> VMLogic<'a> { self.deduct_balance(amount)?; - #[cfg(feature = "protocol_feature_function_call_weight")] - self.ext.append_action_function_call_weight( - receipt_idx, - method_name, - arguments, - amount, - gas, - gas_weight, - )?; - - #[cfg(not(feature = "protocol_feature_function_call_weight"))] - { - let _ = gas_weight; - self.ext.append_action_function_call( - receipt_idx, - method_name, - arguments, - amount, - gas, - )?; - } - Ok(()) + append_action_fn(self, receipt_idx, method_name, arguments, amount, gas) } /// Appends `Transfer` action to the batch of actions for the given promise pointed by From a979f6a20167a963ef9ac14f7d6d83999ead31ba Mon Sep 17 00:00:00 2001 From: austinabell Date: Thu, 3 Mar 2022 11:27:59 -0500 Subject: [PATCH 12/19] fit lint and nightly hash for Protocol version bump --- chain/chain/src/tests/simple_chain.rs | 6 +++--- runtime/near-vm-logic/src/logic.rs | 4 +++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/chain/chain/src/tests/simple_chain.rs b/chain/chain/src/tests/simple_chain.rs index 2025b656de4..a124bf5de43 100644 --- a/chain/chain/src/tests/simple_chain.rs +++ b/chain/chain/src/tests/simple_chain.rs @@ -24,7 +24,7 @@ fn empty_chain() { let hash = chain.head().unwrap().last_block_hash; // The hashes here will have to be modified after each change to genesis file. #[cfg(feature = "nightly_protocol")] - assert_eq!(hash, CryptoHash::from_str("4iPPuWZ2BZj6i6zGCa96xFTQhp3FHkY2CzUCJFUUryt8").unwrap()); + assert_eq!(hash, CryptoHash::from_str("2VFkBfWwcTqyVJ83zy78n5WUNadwGuJbLc2KEp9SJ8dV").unwrap()); #[cfg(not(feature = "nightly_protocol"))] assert_eq!(hash, CryptoHash::from_str("8UF2TCELQ2sSqorskN5myyC7h1XfgxYm68JHJMKo5n8X").unwrap()); assert_eq!(count_utc, 1); @@ -50,7 +50,7 @@ fn build_chain() { #[cfg(feature = "nightly_protocol")] assert_eq!( prev_hash, - CryptoHash::from_str("zcVm8wC8eBt2b5C2uTNch2UyfXCwjs3qgYGZwyXcUAA").unwrap() + CryptoHash::from_str("299HrY4hpubeFXa3V9DNtR36dGEtiz4AVfMbfL6hT2sq").unwrap() ); #[cfg(not(feature = "nightly_protocol"))] assert_eq!( @@ -73,7 +73,7 @@ fn build_chain() { #[cfg(feature = "nightly_protocol")] assert_eq!( chain.head().unwrap().last_block_hash, - CryptoHash::from_str("CDfAT886U5up6bQZ3QNVcvxtuVM6sNyJnF6Nk6RMHnEZ").unwrap() + CryptoHash::from_str("A1ZqLuyanSg6YeD3HxGco2tJYEAsmHvAva5n4dsPTgij").unwrap() ); #[cfg(not(feature = "nightly_protocol"))] assert_eq!( diff --git a/runtime/near-vm-logic/src/logic.rs b/runtime/near-vm-logic/src/logic.rs index 4e1a4eeba57..66542b8616b 100644 --- a/runtime/near-vm-logic/src/logic.rs +++ b/runtime/near-vm-logic/src/logic.rs @@ -13,8 +13,10 @@ use near_primitives_core::profile::ProfileData; use near_primitives_core::runtime::fees::{ transfer_exec_fee, transfer_send_fee, RuntimeFeesConfig, }; +#[cfg(feature = "protocol_feature_function_call_weight")] +use near_primitives_core::types::GasWeight; use near_primitives_core::types::{ - AccountId, Balance, EpochHeight, Gas, GasWeight, ProtocolVersion, StorageUsage, + AccountId, Balance, EpochHeight, Gas, ProtocolVersion, StorageUsage, }; use near_vm_errors::InconsistentStateError; use near_vm_errors::{HostError, VMLogicError}; From 56caf81239afd4cff74874e065284b06bba1a611 Mon Sep 17 00:00:00 2001 From: austinabell Date: Thu, 3 Mar 2022 11:41:41 -0500 Subject: [PATCH 13/19] update gas distribution function return --- core/primitives-core/src/types.rs | 13 ++++++++++++- runtime/near-vm-logic/src/dependencies.rs | 6 +++--- runtime/near-vm-logic/src/logic.rs | 6 +++--- runtime/near-vm-logic/src/mocks/mock_external.rs | 8 ++++---- runtime/runtime/src/ext.rs | 10 +++++----- 5 files changed, 27 insertions(+), 16 deletions(-) diff --git a/core/primitives-core/src/types.rs b/core/primitives-core/src/types.rs index 8f356911db1..3bd8a909d43 100644 --- a/core/primitives-core/src/types.rs +++ b/core/primitives-core/src/types.rs @@ -24,11 +24,22 @@ pub type ShardId = u64; pub type Balance = u128; /// Gas is a type for storing amount of gas. pub type Gas = u64; + /// Weight of unused gas to distribute to scheduled function call actions. /// Used in `promise_batch_action_function_call_weight` host function. -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub struct GasWeight(pub u64); +/// Result from a gas distribution among function calls with ratios. +#[must_use] +#[derive(Debug, PartialEq)] +pub enum GasDistribution { + /// All remaining gas was distributed to functions. + All, + /// There were no function call actions with a ratio specified. + NoRatios, +} + /// Number of blocks in current group. pub type NumBlocks = u64; /// Number of shards in current group. diff --git a/runtime/near-vm-logic/src/dependencies.rs b/runtime/near-vm-logic/src/dependencies.rs index e85d6e05a08..45c8ee142d0 100644 --- a/runtime/near-vm-logic/src/dependencies.rs +++ b/runtime/near-vm-logic/src/dependencies.rs @@ -2,7 +2,7 @@ use crate::types::{PublicKey, ReceiptIndex}; #[cfg(feature = "protocol_feature_function_call_weight")] -use near_primitives_core::types::GasWeight; +use near_primitives_core::types::{GasWeight, GasDistribution}; use near_primitives_core::types::{AccountId, Balance, Gas}; use near_vm_errors::VMLogicError; @@ -540,7 +540,7 @@ pub trait External { /// /// # Returns /// - /// Function returns true if gas was distributed, false if no ratios existed to distribute to. + /// Function returns a [GasDistribution] that indicates how the gas was distributed. #[cfg(feature = "protocol_feature_function_call_weight")] - fn distribute_unused_gas(&mut self, gas: Gas) -> bool; + fn distribute_unused_gas(&mut self, gas: Gas) -> GasDistribution; } diff --git a/runtime/near-vm-logic/src/logic.rs b/runtime/near-vm-logic/src/logic.rs index 66542b8616b..88ed59d1a8b 100644 --- a/runtime/near-vm-logic/src/logic.rs +++ b/runtime/near-vm-logic/src/logic.rs @@ -13,11 +13,11 @@ use near_primitives_core::profile::ProfileData; use near_primitives_core::runtime::fees::{ transfer_exec_fee, transfer_send_fee, RuntimeFeesConfig, }; -#[cfg(feature = "protocol_feature_function_call_weight")] -use near_primitives_core::types::GasWeight; use near_primitives_core::types::{ AccountId, Balance, EpochHeight, Gas, ProtocolVersion, StorageUsage, }; +#[cfg(feature = "protocol_feature_function_call_weight")] +use near_primitives_core::types::{GasDistribution, GasWeight}; use near_vm_errors::InconsistentStateError; use near_vm_errors::{HostError, VMLogicError}; use std::collections::HashMap; @@ -2613,7 +2613,7 @@ impl<'a> VMLogic<'a> { let unused_gas = self.context.prepaid_gas - self.gas_counter.used_gas(); // Distribute the unused gas and prepay for the gas. - if self.ext.distribute_unused_gas(unused_gas) { + if matches!(self.ext.distribute_unused_gas(unused_gas), GasDistribution::All) { self.gas_counter.prepay_gas(unused_gas).unwrap(); } } diff --git a/runtime/near-vm-logic/src/mocks/mock_external.rs b/runtime/near-vm-logic/src/mocks/mock_external.rs index 6417be2b5cd..bff4b4e0da7 100644 --- a/runtime/near-vm-logic/src/mocks/mock_external.rs +++ b/runtime/near-vm-logic/src/mocks/mock_external.rs @@ -1,6 +1,6 @@ use crate::{External, ValuePtr}; #[cfg(feature = "protocol_feature_function_call_weight")] -use near_primitives::types::GasWeight; +use near_primitives::types::{GasDistribution, GasWeight}; use near_primitives_core::types::{AccountId, Balance, Gas}; use near_vm_errors::HostError; use serde::{Deserialize, Serialize}; @@ -250,7 +250,7 @@ impl External for MockedExternal { } #[cfg(feature = "protocol_feature_function_call_weight")] - fn distribute_unused_gas(&mut self, gas: Gas) -> bool { + fn distribute_unused_gas(&mut self, gas: Gas) -> GasDistribution { let gas_weight_sum: u128 = self.gas_weights.iter().map(|(_, GasWeight(weight))| *weight as u128).sum(); if gas_weight_sum != 0 { @@ -283,9 +283,9 @@ impl External for MockedExternal { distribute_gas(last_idx, gas - distributed); } self.gas_weights.clear(); - true + GasDistribution::All } else { - false + GasDistribution::NoRatios } } } diff --git a/runtime/runtime/src/ext.rs b/runtime/runtime/src/ext.rs index 26230c1365f..a067fcb6e53 100644 --- a/runtime/runtime/src/ext.rs +++ b/runtime/runtime/src/ext.rs @@ -14,9 +14,9 @@ use near_primitives::transaction::{ DeployContractAction, FunctionCallAction, StakeAction, TransferAction, }; use near_primitives::trie_key::{trie_key_parsers, TrieKey}; -#[cfg(feature = "protocol_feature_function_call_weight")] -use near_primitives::types::GasWeight; use near_primitives::types::{AccountId, Balance, EpochId, EpochInfoProvider, Gas}; +#[cfg(feature = "protocol_feature_function_call_weight")] +use near_primitives::types::{GasDistribution, GasWeight}; use near_primitives::utils::create_data_id; use near_primitives::version::ProtocolVersion; use near_store::{get_code, TrieUpdate, TrieUpdateValuePtr}; @@ -442,7 +442,7 @@ impl<'a> External for RuntimeExt<'a> { } #[cfg(feature = "protocol_feature_function_call_weight")] - fn distribute_unused_gas(&mut self, gas: u64) -> bool { + fn distribute_unused_gas(&mut self, gas: u64) -> GasDistribution { let gas_weight_sum: u128 = self.gas_weights.iter().map(|(_, GasWeight(weight))| *weight as u128).sum(); if gas_weight_sum != 0 { @@ -475,9 +475,9 @@ impl<'a> External for RuntimeExt<'a> { distribute_gas(last_idx, gas - distributed); } self.gas_weights.clear(); - true + GasDistribution::All } else { - false + GasDistribution::NoRatios } } } From 343741b885574b2e0b158824f0d64d5922964ea4 Mon Sep 17 00:00:00 2001 From: austinabell Date: Thu, 3 Mar 2022 11:47:26 -0500 Subject: [PATCH 14/19] gate new types under feature flag --- core/primitives-core/Cargo.toml | 1 + core/primitives-core/src/types.rs | 2 ++ core/primitives/Cargo.toml | 2 +- runtime/near-vm-logic/Cargo.toml | 5 ++++- runtime/near-vm-logic/src/dependencies.rs | 4 ++-- 5 files changed, 10 insertions(+), 4 deletions(-) diff --git a/core/primitives-core/Cargo.toml b/core/primitives-core/Cargo.toml index 7156f6f27f3..20f6ea5b63d 100644 --- a/core/primitives-core/Cargo.toml +++ b/core/primitives-core/Cargo.toml @@ -31,6 +31,7 @@ serde_json = "1" default = [] protocol_feature_alt_bn128 = [] protocol_feature_routing_exchange_algorithm = [] +protocol_feature_function_call_weight = [] deepsize_feature = [ "deepsize", "near-account-id/deepsize_feature", diff --git a/core/primitives-core/src/types.rs b/core/primitives-core/src/types.rs index 3bd8a909d43..47f52199d69 100644 --- a/core/primitives-core/src/types.rs +++ b/core/primitives-core/src/types.rs @@ -27,10 +27,12 @@ pub type Gas = u64; /// Weight of unused gas to distribute to scheduled function call actions. /// Used in `promise_batch_action_function_call_weight` host function. +#[cfg(feature = "protocol_feature_function_call_weight")] #[derive(Clone, Debug, PartialEq)] pub struct GasWeight(pub u64); /// Result from a gas distribution among function calls with ratios. +#[cfg(feature = "protocol_feature_function_call_weight")] #[must_use] #[derive(Debug, PartialEq)] pub enum GasDistribution { diff --git a/core/primitives/Cargo.toml b/core/primitives/Cargo.toml index 0d7ae32ab72..2d2fa585e83 100644 --- a/core/primitives/Cargo.toml +++ b/core/primitives/Cargo.toml @@ -45,7 +45,7 @@ protocol_feature_chunk_only_producers = [] protocol_feature_routing_exchange_algorithm = ["near-primitives-core/protocol_feature_routing_exchange_algorithm"] protocol_feature_access_key_nonce_for_implicit_accounts = [] protocol_feature_fix_staking_threshold = [] -protocol_feature_function_call_weight = [] +protocol_feature_function_call_weight = ["near-primitives-core/protocol_feature_function_call_weight"] nightly_protocol_features = [ "nightly_protocol", "protocol_feature_alt_bn128", diff --git a/runtime/near-vm-logic/Cargo.toml b/runtime/near-vm-logic/Cargo.toml index 81fd15ed461..aa2e42f0ccf 100644 --- a/runtime/near-vm-logic/Cargo.toml +++ b/runtime/near-vm-logic/Cargo.toml @@ -44,7 +44,10 @@ protocol_feature_alt_bn128 = [ "near-primitives-core/protocol_feature_alt_bn128", "near-vm-errors/protocol_feature_alt_bn128", ] -protocol_feature_function_call_weight = ["near-primitives/protocol_feature_function_call_weight"] +protocol_feature_function_call_weight = [ + "near-primitives/protocol_feature_function_call_weight", + "near-primitives-core/protocol_feature_function_call_weight", +] # Use this feature to enable counting of fees and costs applied. costs_counting = [] diff --git a/runtime/near-vm-logic/src/dependencies.rs b/runtime/near-vm-logic/src/dependencies.rs index 45c8ee142d0..ef3c8475a34 100644 --- a/runtime/near-vm-logic/src/dependencies.rs +++ b/runtime/near-vm-logic/src/dependencies.rs @@ -1,9 +1,9 @@ //! External dependencies of the near-vm-logic. use crate::types::{PublicKey, ReceiptIndex}; -#[cfg(feature = "protocol_feature_function_call_weight")] -use near_primitives_core::types::{GasWeight, GasDistribution}; use near_primitives_core::types::{AccountId, Balance, Gas}; +#[cfg(feature = "protocol_feature_function_call_weight")] +use near_primitives_core::types::{GasDistribution, GasWeight}; use near_vm_errors::VMLogicError; /// An abstraction over the memory of the smart contract. From 5aca6155d868547aaa95b228e835e57a9e80ca7a Mon Sep 17 00:00:00 2001 From: austinabell Date: Thu, 3 Mar 2022 11:49:54 -0500 Subject: [PATCH 15/19] Make GasDistribution non_exhaustive --- core/primitives-core/src/types.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/core/primitives-core/src/types.rs b/core/primitives-core/src/types.rs index 47f52199d69..4332cd71a1d 100644 --- a/core/primitives-core/src/types.rs +++ b/core/primitives-core/src/types.rs @@ -34,6 +34,7 @@ pub struct GasWeight(pub u64); /// Result from a gas distribution among function calls with ratios. #[cfg(feature = "protocol_feature_function_call_weight")] #[must_use] +#[non_exhaustive] #[derive(Debug, PartialEq)] pub enum GasDistribution { /// All remaining gas was distributed to functions. From b7133086f0f90d9c0589239776ade5180294fb25 Mon Sep 17 00:00:00 2001 From: austinabell Date: Tue, 8 Mar 2022 11:49:47 -0500 Subject: [PATCH 16/19] addr docs comments --- runtime/near-vm-logic/src/dependencies.rs | 8 +++++++- runtime/near-vm-logic/src/logic.rs | 8 +++++++- runtime/near-vm-logic/src/mocks/mock_external.rs | 2 ++ runtime/near-vm-logic/src/tests/gas_counter.rs | 8 ++------ runtime/runtime/src/ext.rs | 2 ++ 5 files changed, 20 insertions(+), 8 deletions(-) diff --git a/runtime/near-vm-logic/src/dependencies.rs b/runtime/near-vm-logic/src/dependencies.rs index ef3c8475a34..28fa35724d8 100644 --- a/runtime/near-vm-logic/src/dependencies.rs +++ b/runtime/near-vm-logic/src/dependencies.rs @@ -278,7 +278,13 @@ pub trait External { prepaid_gas: Gas, ) -> Result<()>; - /// Attach the [`FunctionCallAction`] action to an existing receipt. + /// Attach the [`FunctionCallAction`] action to an existing receipt. This method has similar + /// functionality to [`append_action_function_call`](Self::append_action_function_call) except + /// that it allows specifying a weight to use leftover gas from the current execution. + /// + /// `prepaid_gas` and `gas_weight` can either be specified or both. If a `gas_weight` is + /// specified, the action should be allocated gas in + /// [`distribute_unused_gas`](Self::distribute_unused_gas). /// /// # Arguments /// diff --git a/runtime/near-vm-logic/src/logic.rs b/runtime/near-vm-logic/src/logic.rs index 88ed59d1a8b..4f18d2603ad 100644 --- a/runtime/near-vm-logic/src/logic.rs +++ b/runtime/near-vm-logic/src/logic.rs @@ -1555,6 +1555,13 @@ impl<'a> VMLogic<'a> { /// For example, if 40 gas is leftover from the current method call and three functions specify /// the weights 1, 5, 2 then 5, 25, 10 gas will be added to each function call respectively, /// using up all remaining available gas. + /// + /// If the `gas_weight` parameter is set as a large value, the amount of distributed gas + /// to each action can be 0 or a very low value because the amount of gas per weight is + /// based on the floor division of the amount of gas by the sum of weights. + /// + /// Any remaining gas will be distributed to the last scheduled function call with a weight + /// specified. /// /// # Errors /// @@ -1565,7 +1572,6 @@ impl<'a> VMLogic<'a> { /// `amount_ptr + 16` points outside the memory of the guest or host returns /// `MemoryAccessViolation`. /// * If called as view function returns `ProhibitedInView`. - /// - If `0` is passed for both `gas` and `gas_weight` parameters #[cfg(feature = "protocol_feature_function_call_weight")] pub fn promise_batch_action_function_call_weight( &mut self, diff --git a/runtime/near-vm-logic/src/mocks/mock_external.rs b/runtime/near-vm-logic/src/mocks/mock_external.rs index bff4b4e0da7..a8640bedee6 100644 --- a/runtime/near-vm-logic/src/mocks/mock_external.rs +++ b/runtime/near-vm-logic/src/mocks/mock_external.rs @@ -271,6 +271,8 @@ impl External for MockedExternal { let mut distributed = 0; for (action_index, GasWeight(weight)) in &self.gas_weights { + // This can't overflow because the gas_per_weight is floor division + // of the weight sum. let assigned_gas = gas_per_weight * weight; distribute_gas(action_index, assigned_gas); diff --git a/runtime/near-vm-logic/src/tests/gas_counter.rs b/runtime/near-vm-logic/src/tests/gas_counter.rs index 49a3c080086..1e9fb5eeac8 100644 --- a/runtime/near-vm-logic/src/tests/gas_counter.rs +++ b/runtime/near-vm-logic/src/tests/gas_counter.rs @@ -111,7 +111,6 @@ fn function_call_weight_check(function_calls: impl IntoIterator= gas_limit as u128); + // Verify that all gas was consumed (assumes at least one ratio is provided) + assert!(outcome.used_gas == gas_limit); } #[cfg(feature = "protocol_feature_function_call_weight")] diff --git a/runtime/runtime/src/ext.rs b/runtime/runtime/src/ext.rs index a067fcb6e53..17683c8a1fd 100644 --- a/runtime/runtime/src/ext.rs +++ b/runtime/runtime/src/ext.rs @@ -463,6 +463,8 @@ impl<'a> External for RuntimeExt<'a> { let mut distributed = 0; for (action_index, GasWeight(weight)) in &self.gas_weights { + // This can't overflow because the gas_per_weight is floor division + // of the weight sum. let assigned_gas = gas_per_weight * weight; distribute_gas(action_index, assigned_gas); From 429c4d60c365e918c1e61b07b9e96b9c41349569 Mon Sep 17 00:00:00 2001 From: austinabell Date: Tue, 8 Mar 2022 11:51:04 -0500 Subject: [PATCH 17/19] fmt --- runtime/near-vm-logic/src/logic.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/runtime/near-vm-logic/src/logic.rs b/runtime/near-vm-logic/src/logic.rs index 4f18d2603ad..bf12124e598 100644 --- a/runtime/near-vm-logic/src/logic.rs +++ b/runtime/near-vm-logic/src/logic.rs @@ -1555,11 +1555,11 @@ impl<'a> VMLogic<'a> { /// For example, if 40 gas is leftover from the current method call and three functions specify /// the weights 1, 5, 2 then 5, 25, 10 gas will be added to each function call respectively, /// using up all remaining available gas. - /// + /// /// If the `gas_weight` parameter is set as a large value, the amount of distributed gas /// to each action can be 0 or a very low value because the amount of gas per weight is /// based on the floor division of the amount of gas by the sum of weights. - /// + /// /// Any remaining gas will be distributed to the last scheduled function call with a weight /// specified. /// From 662a7f7f8b518dc85f2d7ff94fca5e525d16a756 Mon Sep 17 00:00:00 2001 From: austinabell Date: Tue, 8 Mar 2022 12:09:31 -0500 Subject: [PATCH 18/19] update assert syntax --- runtime/near-vm-logic/src/tests/gas_counter.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/near-vm-logic/src/tests/gas_counter.rs b/runtime/near-vm-logic/src/tests/gas_counter.rs index 1e9fb5eeac8..3fc21f2ee37 100644 --- a/runtime/near-vm-logic/src/tests/gas_counter.rs +++ b/runtime/near-vm-logic/src/tests/gas_counter.rs @@ -125,7 +125,7 @@ fn function_call_weight_check(function_calls: impl IntoIterator Date: Thu, 10 Mar 2022 11:58:13 -0500 Subject: [PATCH 19/19] address comments --- runtime/near-vm-logic/src/dependencies.rs | 2 ++ .../near-vm-logic/src/mocks/mock_external.rs | 11 ++++++++- .../near-vm-logic/src/tests/gas_counter.rs | 23 +++++++++++++++++++ runtime/runtime/src/ext.rs | 11 ++++++++- 4 files changed, 45 insertions(+), 2 deletions(-) diff --git a/runtime/near-vm-logic/src/dependencies.rs b/runtime/near-vm-logic/src/dependencies.rs index 28fa35724d8..3b1311828b6 100644 --- a/runtime/near-vm-logic/src/dependencies.rs +++ b/runtime/near-vm-logic/src/dependencies.rs @@ -286,6 +286,8 @@ pub trait External { /// specified, the action should be allocated gas in /// [`distribute_unused_gas`](Self::distribute_unused_gas). /// + /// For more information, see [crate::VMLogic::promise_batch_action_function_call_weight]. + /// /// # Arguments /// /// * `receipt_index` - an index of Receipt to append an action diff --git a/runtime/near-vm-logic/src/mocks/mock_external.rs b/runtime/near-vm-logic/src/mocks/mock_external.rs index a8640bedee6..c348af4829f 100644 --- a/runtime/near-vm-logic/src/mocks/mock_external.rs +++ b/runtime/near-vm-logic/src/mocks/mock_external.rs @@ -249,11 +249,16 @@ impl External for MockedExternal { Ok(self.validators.values().sum()) } + /// Distributes the gas passed in by splitting it among weights defined in `gas_weights`. + /// This will sum all weights, retrieve the gas per weight, then update each function + /// to add the respective amount of gas. Once all gas is distributed, the remainder of + /// the gas not assigned due to precision loss is added to the last function with a weight. #[cfg(feature = "protocol_feature_function_call_weight")] fn distribute_unused_gas(&mut self, gas: Gas) -> GasDistribution { let gas_weight_sum: u128 = self.gas_weights.iter().map(|(_, GasWeight(weight))| *weight as u128).sum(); if gas_weight_sum != 0 { + // Floor division that will ensure gas allocated is <= gas to distribute let gas_per_weight = (gas as u128 / gas_weight_sum) as u64; let mut distribute_gas = |metadata: &FunctionCallActionIndex, assigned_gas: u64| { @@ -265,7 +270,11 @@ impl External for MockedExternal { { *gas += assigned_gas; } else { - panic!("Invalid index for assigning unused gas weight"); + panic!( + "Invalid index for assigning unused gas weight \ + (promise_index={}, action_index={})", + receipt_index, action_index + ); } }; diff --git a/runtime/near-vm-logic/src/tests/gas_counter.rs b/runtime/near-vm-logic/src/tests/gas_counter.rs index 3fc21f2ee37..3e2c9137373 100644 --- a/runtime/near-vm-logic/src/tests/gas_counter.rs +++ b/runtime/near-vm-logic/src/tests/gas_counter.rs @@ -160,6 +160,29 @@ fn function_call_weight_single_smoke_test() { // Weight over u64 bounds function_call_weight_check([(0, u64::MAX), (0, 1000)]); + + // Weights with one zero and one non-zero + function_call_weight_check([(0, 0), (0, 1)]) +} + +#[cfg(feature = "protocol_feature_function_call_weight")] +#[test] +fn function_call_no_weight_refund() { + use near_primitives::types::GasWeight; + + let gas_limit = 10u64.pow(14); + + let mut logic_builder = VMLogicBuilder::default().max_gas_burnt(gas_limit); + let mut logic = logic_builder.build_with_prepaid_gas(gas_limit); + + let index = promise_batch_create(&mut logic, "rick.test").expect("should create a promise"); + promise_batch_action_function_call_weight(&mut logic, index, 0, 1000, GasWeight(0)) + .expect("batch action function call should succeed"); + + let outcome = logic.compute_outcome_and_distribute_gas(); + + // Verify that unused gas was not allocated to function call + assert!(outcome.used_gas < gas_limit); } impl VMLogicBuilder { diff --git a/runtime/runtime/src/ext.rs b/runtime/runtime/src/ext.rs index 17683c8a1fd..3d9548d40b8 100644 --- a/runtime/runtime/src/ext.rs +++ b/runtime/runtime/src/ext.rs @@ -441,11 +441,16 @@ impl<'a> External for RuntimeExt<'a> { .map_err(|e| ExternalError::ValidatorError(e).into()) } + /// Distributes the gas passed in by splitting it among weights defined in `gas_weights`. + /// This will sum all weights, retrieve the gas per weight, then update each function + /// to add the respective amount of gas. Once all gas is distributed, the remainder of + /// the gas not assigned due to precision loss is added to the last function with a weight. #[cfg(feature = "protocol_feature_function_call_weight")] fn distribute_unused_gas(&mut self, gas: u64) -> GasDistribution { let gas_weight_sum: u128 = self.gas_weights.iter().map(|(_, GasWeight(weight))| *weight as u128).sum(); if gas_weight_sum != 0 { + // Floor division that will ensure gas allocated is <= gas to distribute let gas_per_weight = (gas as u128 / gas_weight_sum) as u64; let mut distribute_gas = |metadata: &FunctionCallActionIndex, assigned_gas: u64| { @@ -457,7 +462,11 @@ impl<'a> External for RuntimeExt<'a> { { *gas += assigned_gas; } else { - panic!("Invalid index for assigning unused gas weight"); + panic!( + "Invalid index for assigning unused gas weight \ + (promise_index={}, action_index={})", + receipt_index, action_index + ); } };