Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement NEP264, function call gas weight #6285

Merged
merged 29 commits into from
Mar 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
1b5f49e
feat: implement NEP264, function call gas ratio
austinabell Feb 12, 2022
fd91668
adjust wording to weight instead of ratio, cleanup
austinabell Feb 18, 2022
e012271
cleanup remaining ratio references
austinabell Feb 18, 2022
44044ff
change logic to assign all unused gas
austinabell Feb 18, 2022
ac4c2f0
wrap gas weight and fix clearing weights
austinabell Feb 18, 2022
60d7293
add test for weight over u64 bounds
austinabell Feb 19, 2022
886ebf6
Merge branch 'master' of github.com:near/nearcore into austin/nep264
austinabell Feb 19, 2022
78d4a95
expose GasWeight newtype to trait interface
austinabell Feb 28, 2022
43b2f92
Merge branch 'master' into austin/nep264
austinabell Feb 28, 2022
ab3e7ac
addr comments
austinabell Mar 2, 2022
0350337
Merge branch 'master' into austin/nep264
austinabell Mar 2, 2022
9bf9846
address nagisa's comments
austinabell Mar 2, 2022
6d0ec3a
fix lint ignore
austinabell Mar 2, 2022
0436ab6
switch vm logic function to have generic callback and gate by feature
austinabell Mar 3, 2022
1e88e76
Merge branch 'master' of github.com:near/nearcore into austin/nep264
austinabell Mar 3, 2022
a979f6a
fit lint and nightly hash for Protocol version bump
austinabell Mar 3, 2022
56caf81
update gas distribution function return
austinabell Mar 3, 2022
343741b
gate new types under feature flag
austinabell Mar 3, 2022
5aca615
Make GasDistribution non_exhaustive
austinabell Mar 3, 2022
50d3a13
Merge branch 'master' into austin/nep264
austinabell Mar 4, 2022
0f36da2
Merge branch 'master' into austin/nep264
austinabell Mar 4, 2022
bfb3c99
Merge branch 'master' into austin/nep264
austinabell Mar 4, 2022
e46f4a0
Merge branch 'master' into austin/nep264
austinabell Mar 7, 2022
b713308
addr docs comments
austinabell Mar 8, 2022
429c4d6
fmt
austinabell Mar 8, 2022
db092fb
Merge branch 'master' of github.com:near/nearcore into austin/nep264
austinabell Mar 8, 2022
662a7f7
update assert syntax
austinabell Mar 8, 2022
bf069c1
address comments
austinabell Mar 10, 2022
0f6a8a2
Merge branch 'master' of github.com:near/nearcore into austin/nep264
austinabell Mar 10, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions chain/chain/src/tests/simple_chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,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);
Expand Down Expand Up @@ -54,7 +54,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!(
Expand All @@ -77,7 +77,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!(
Expand Down
1 change: 1 addition & 0 deletions core/primitives-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
18 changes: 18 additions & 0 deletions core/primitives-core/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,24 @@ 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.
#[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]
#[non_exhaustive]
#[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.
Expand Down
2 changes: 2 additions & 0 deletions core/primitives/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,15 @@ 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 = ["near-primitives-core/protocol_feature_function_call_weight"]
nightly_protocol_features = [
"nightly_protocol",
"protocol_feature_alt_bn128",
"protocol_feature_chunk_only_producers",
"protocol_feature_routing_exchange_algorithm",
"protocol_feature_access_key_nonce_for_implicit_accounts",
"protocol_feature_fix_staking_threshold",
"protocol_feature_function_call_weight",
]
nightly_protocol = []
deepsize_feature = [
Expand Down
6 changes: 5 additions & 1 deletion core/primitives/src/version.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,8 @@ pub enum ProtocolFeature {
/// alpha is min stake ratio
#[cfg(feature = "protocol_feature_fix_staking_threshold")]
FixStakingThreshold,
#[cfg(feature = "protocol_feature_function_call_weight")]
FunctionCallWeight,
}

/// Both, outgoing and incoming tcp connections to peers, will be rejected if `peer's`
Expand All @@ -166,7 +168,7 @@ const STABLE_PROTOCOL_VERSION: ProtocolVersion = 52;
pub const PROTOCOL_VERSION: ProtocolVersion = STABLE_PROTOCOL_VERSION;
/// Current latest nightly version of the protocol.
#[cfg(feature = "nightly_protocol")]
pub const PROTOCOL_VERSION: ProtocolVersion = 126;
pub const PROTOCOL_VERSION: ProtocolVersion = 127;

/// The points in time after which the voting for the protocol version should start.
#[allow(dead_code)]
Expand Down Expand Up @@ -226,6 +228,8 @@ impl ProtocolFeature {
ProtocolFeature::RoutingExchangeAlgorithm => 117,
#[cfg(feature = "protocol_feature_fix_staking_threshold")]
ProtocolFeature::FixStakingThreshold => 126,
#[cfg(feature = "protocol_feature_function_call_weight")]
ProtocolFeature::FunctionCallWeight => 127,
}
}
}
Expand Down
4 changes: 4 additions & 0 deletions runtime/near-vm-logic/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +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",
"near-primitives-core/protocol_feature_function_call_weight",
]

# Use this feature to enable counting of fees and costs applied.
costs_counting = []
Expand Down
65 changes: 65 additions & 0 deletions runtime/near-vm-logic/src/dependencies.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

use crate::types::{PublicKey, ReceiptIndex};
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.
Expand Down Expand Up @@ -276,6 +278,57 @@ pub trait External {
prepaid_gas: Gas,
) -> Result<()>;

/// 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).
///
/// For more information, see [crate::VMLogic::promise_batch_action_function_call_weight].
///
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd mention that this is the same function as above (append_action_function_call) with the added argument.

And maybe in this documentation focus a little bit more on what 'gas_weight' means.

And what happens if I set both 'prepaid_gas' and gas_weight?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and what if gas_weight is 0 ? does it still include gas then ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assumed the docs for this would be lighter since it's documented in promise_batch_action_function_call_weight, but I see the benefit in giving some info here also.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it is fine to have lighter docs - but then let's make sure that we have a 'pointer' to the place where people can read more details.

(I actually don't know which file is read by users - this one or logic.rs?)

/// # 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_weight` - relative weight 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_weight(
/// 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_weight")]
fn append_action_function_call_weight(
&mut self,
receipt_index: ReceiptIndex,
method_name: Vec<u8>,
arguments: Vec<u8>,
attached_deposit: Balance,
prepaid_gas: Gas,
gas_weight: GasWeight,
) -> Result<()>;

/// Attach the [`TransferAction`] action to an existing receipt.
///
/// # Arguments
Expand Down Expand Up @@ -486,4 +539,16 @@ pub trait External {

/// Returns total stake of validators in the current epoch.
fn validator_total_stake(&self) -> Result<Balance>;

/// Distribute the gas among the scheduled function calls that specify a gas weight.
///
/// # Arguments
///
/// * `gas` - amount of unused gas to distribute
austinabell marked this conversation as resolved.
Show resolved Hide resolved
///
/// # Returns
///
/// 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) -> GasDistribution;
}
121 changes: 117 additions & 4 deletions runtime/near-vm-logic/src/logic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ use near_primitives_core::runtime::fees::{
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;
Expand Down Expand Up @@ -1518,6 +1520,102 @@ impl<'a> VMLogic<'a> {
arguments_ptr: u64,
amount_ptr: u64,
gas: Gas,
) -> Result<()> {
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,
arguments_len,
arguments_ptr,
amount_ptr,
gas,
append_action_fn,
)
}

/// 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 weight.
///
/// # Gas
///
/// 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 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 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
austinabell marked this conversation as resolved.
Show resolved Hide resolved
/// 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
///
/// * 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`.
#[cfg(feature = "protocol_feature_function_call_weight")]
pub fn promise_batch_action_function_call_weight(
&mut self,
promise_idx: u64,
method_name_len: u64,
method_name_ptr: u64,
arguments_len: u64,
arguments_ptr: u64,
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<u8>, Vec<u8>, u128, u64) -> Result<()>,
) -> Result<()> {
self.gas_counter.pay_base(base)?;
if self.context.is_view() {
Expand Down Expand Up @@ -1553,8 +1651,7 @@ impl<'a> VMLogic<'a> {

self.deduct_balance(amount)?;

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
Expand Down Expand Up @@ -2509,8 +2606,24 @@ impl<'a> VMLogic<'a> {
}))
}

/// Computes the outcome of execution.
pub fn outcome(self) -> VMOutcome {
/// 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.
#[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() {
// Distribute unused gas to scheduled function calls
let unused_gas = self.context.prepaid_gas - self.gas_counter.used_gas();

// Distribute the unused gas and prepay for the gas.
if matches!(self.ext.distribute_unused_gas(unused_gas), GasDistribution::All) {
self.gas_counter.prepay_gas(unused_gas).unwrap();
}
}

let burnt_gas = self.gas_counter.burnt_gas();
let used_gas = self.gas_counter.used_gas();

Expand Down
Loading