diff --git a/fvm/src/call_manager/mod.rs b/fvm/src/call_manager/mod.rs index 141a205eb..e72975f73 100644 --- a/fvm/src/call_manager/mod.rs +++ b/fvm/src/call_manager/mod.rs @@ -116,7 +116,7 @@ pub trait CallManager: 'static { /// Charge gas. fn charge_gas(&mut self, charge: GasCharge) -> Result<()> { - self.gas_tracker_mut().charge_gas(charge)?; + self.gas_tracker_mut().apply_charge(charge)?; Ok(()) } } diff --git a/fvm/src/executor/default.rs b/fvm/src/executor/default.rs index 97eb845d5..ea0a605ac 100644 --- a/fvm/src/executor/default.rs +++ b/fvm/src/executor/default.rs @@ -15,7 +15,7 @@ use num_traits::Zero; use super::{ApplyFailure, ApplyKind, ApplyRet, Executor}; use crate::call_manager::{backtrace, CallManager, InvocationResult}; -use crate::gas::{GasCharge, GasOutputs}; +use crate::gas::{milligas_to_gas, GasCharge, GasOutputs}; use crate::kernel::{ClassifyResult, Context as _, ExecutionError, Kernel}; use crate::machine::{Machine, BURNT_FUNDS_ACTOR_ADDR, REWARD_ACTOR_ADDR}; @@ -220,7 +220,7 @@ where ApplyKind::Implicit => (GasCharge::new("none", 0, 0), Default::default()), ApplyKind::Explicit => { let inclusion_cost = pl.on_chain_message(raw_length); - let inclusion_total = inclusion_cost.total(); + let inclusion_total = milligas_to_gas(inclusion_cost.total(), true); // Verify the cost of the message is not over the message gas limit. if inclusion_total > msg.gas_limit { diff --git a/fvm/src/gas/charge.rs b/fvm/src/gas/charge.rs index cc52bd03f..f713b10cc 100644 --- a/fvm/src/gas/charge.rs +++ b/fvm/src/gas/charge.rs @@ -1,16 +1,20 @@ // Copyright 2019-2022 ChainSafe Systems // SPDX-License-Identifier: Apache-2.0, MIT +use crate::gas::Milligas; + /// Single gas charge in the VM. Contains information about what gas was for, as well /// as the amount of gas needed for computation and storage respectively. pub struct GasCharge<'a> { pub name: &'a str, - pub compute_gas: i64, - pub storage_gas: i64, + /// Compute costs in milligas. + pub compute_gas: Milligas, + /// Storage costs in milligas. + pub storage_gas: Milligas, } impl<'a> GasCharge<'a> { - pub fn new(name: &'a str, compute_gas: i64, storage_gas: i64) -> Self { + pub fn new(name: &'a str, compute_gas: Milligas, storage_gas: Milligas) -> Self { Self { name, compute_gas, @@ -18,8 +22,9 @@ impl<'a> GasCharge<'a> { } } - /// Calculates total gas charge based on compute and storage multipliers. - pub fn total(&self) -> i64 { + /// Calculates total gas charge (in milligas) by summing compute and + /// storage gas associated with this charge. + pub fn total(&self) -> Milligas { self.compute_gas + self.storage_gas } } diff --git a/fvm/src/gas/mod.rs b/fvm/src/gas/mod.rs index ec250ce51..d296add39 100644 --- a/fvm/src/gas/mod.rs +++ b/fvm/src/gas/mod.rs @@ -12,13 +12,19 @@ mod price_list; pub const MILLIGAS_PRECISION: i64 = 1000; +// Type aliases to disambiguate units in interfaces. +pub type Gas = i64; +pub type Milligas = i64; + pub struct GasTracker { milligas_limit: i64, milligas_used: i64, } impl GasTracker { - pub fn new(gas_limit: i64, gas_used: i64) -> Self { + /// Gas limit and gas used are provided in protocol units (i.e. full units). + /// They are converted to milligas for internal canonical accounting. + pub fn new(gas_limit: Gas, gas_used: Gas) -> Self { Self { milligas_limit: gas_to_milligas(gas_limit), milligas_used: gas_to_milligas(gas_used), @@ -27,7 +33,7 @@ impl GasTracker { /// Safely consumes gas and returns an out of gas error if there is not sufficient /// enough gas remaining for charge. - pub fn charge_milligas(&mut self, name: &str, to_use: i64) -> Result<()> { + pub fn charge_milligas(&mut self, name: &str, to_use: Milligas) -> Result<()> { match self.milligas_used.checked_add(to_use) { None => { log::trace!("gas overflow: {}", name); @@ -48,51 +54,49 @@ impl GasTracker { } } - pub fn charge_gas(&mut self, charge: GasCharge) -> Result<()> { - self.charge_milligas( - charge.name, - charge.total().saturating_mul(MILLIGAS_PRECISION), - ) + /// Applies the specified gas charge, where quantities are supplied in milligas. + pub fn apply_charge(&mut self, charge: GasCharge) -> Result<()> { + self.charge_milligas(charge.name, charge.total()) } /// Getter for gas available. - pub fn gas_limit(&self) -> i64 { + pub fn gas_limit(&self) -> Gas { milligas_to_gas(self.milligas_limit, false) } /// Getter for milligas available. - pub fn milligas_limit(&self) -> i64 { + pub fn milligas_limit(&self) -> Milligas { self.milligas_limit } /// Getter for gas used. - pub fn gas_used(&self) -> i64 { + pub fn gas_used(&self) -> Gas { milligas_to_gas(self.milligas_used, true) } /// Getter for milligas used. - pub fn milligas_used(&self) -> i64 { + pub fn milligas_used(&self) -> Milligas { self.milligas_used } - pub fn gas_available(&self) -> i64 { + pub fn gas_available(&self) -> Gas { milligas_to_gas(self.milligas_available(), false) } - pub fn milligas_available(&self) -> i64 { + pub fn milligas_available(&self) -> Milligas { self.milligas_limit.saturating_sub(self.milligas_used) } } /// Converts the specified gas into equivalent fractional gas units #[inline] -fn gas_to_milligas(gas: i64) -> i64 { +pub(crate) fn gas_to_milligas(gas: i64) -> i64 { gas.saturating_mul(MILLIGAS_PRECISION) } /// Converts the specified fractional gas units into gas units #[inline] -fn milligas_to_gas(milligas: i64, round_up: bool) -> i64 { +pub(crate) fn milligas_to_gas(milligas: i64, round_up: bool) -> i64 { let mut div_result = milligas / MILLIGAS_PRECISION; if milligas > 0 && round_up && milligas % MILLIGAS_PRECISION != 0 { div_result = div_result.saturating_add(1); @@ -107,13 +111,17 @@ mod tests { use super::*; #[test] - fn basic_gas_tracker() { + #[allow(clippy::identity_op)] + fn basic_gas_tracker() -> Result<()> { let mut t = GasTracker::new(20, 10); - t.charge_gas(GasCharge::new("", 5, 0)).unwrap(); + t.apply_charge(GasCharge::new("", 5 * MILLIGAS_PRECISION, 0))?; assert_eq!(t.gas_used(), 15); - t.charge_gas(GasCharge::new("", 5, 0)).unwrap(); + t.apply_charge(GasCharge::new("", 5 * MILLIGAS_PRECISION, 0))?; assert_eq!(t.gas_used(), 20); - assert!(t.charge_gas(GasCharge::new("", 1, 0)).is_err()) + assert!(t + .apply_charge(GasCharge::new("", 1 * MILLIGAS_PRECISION, 0)) + .is_err()); + Ok(()) } #[test] diff --git a/fvm/src/gas/outputs.rs b/fvm/src/gas/outputs.rs index d52cec83a..ccb1b819f 100644 --- a/fvm/src/gas/outputs.rs +++ b/fvm/src/gas/outputs.rs @@ -3,6 +3,8 @@ use std::convert::TryFrom; use fvm_shared::bigint::BigInt; use fvm_shared::econ::TokenAmount; +use crate::gas::Gas; + #[derive(Clone, Default)] pub(crate) struct GasOutputs { pub base_fee_burn: TokenAmount, @@ -11,14 +13,14 @@ pub(crate) struct GasOutputs { pub miner_tip: TokenAmount, pub refund: TokenAmount, - pub gas_refund: i64, - pub gas_burned: i64, + pub gas_refund: Gas, + pub gas_burned: Gas, } impl GasOutputs { pub fn compute( - gas_used: i64, - gas_limit: i64, + gas_used: Gas, + gas_limit: Gas, base_fee: &TokenAmount, fee_cap: &TokenAmount, gas_premium: &TokenAmount, @@ -57,7 +59,7 @@ impl GasOutputs { } } -fn compute_gas_overestimation_burn(gas_used: i64, gas_limit: i64) -> (i64, i64) { +fn compute_gas_overestimation_burn(gas_used: Gas, gas_limit: Gas) -> (Gas, Gas) { const GAS_OVERUSE_NUM: i64 = 11; const GAS_OVERUSE_DENOM: i64 = 10; diff --git a/fvm/src/gas/price_list.rs b/fvm/src/gas/price_list.rs index a91c0ceac..3744c6a94 100644 --- a/fvm/src/gas/price_list.rs +++ b/fvm/src/gas/price_list.rs @@ -17,43 +17,51 @@ use lazy_static::lazy_static; use num_traits::Zero; use super::GasCharge; +use crate::gas::Milligas; + +/// Converts a static value to milligas. This operation does not saturate, +/// and should only be used with constant values in pricelists. +macro_rules! static_milligas { + ($ex:expr) => { + $ex * $crate::gas::MILLIGAS_PRECISION + }; +} lazy_static! { static ref OH_SNAP_PRICES: PriceList = PriceList { - compute_gas_multiplier: 1, storage_gas_multiplier: 1300, - on_chain_message_compute_base: 38863, - on_chain_message_storage_base: 36, - on_chain_message_storage_per_byte: 1, + on_chain_message_compute_base: static_milligas!(38863), + on_chain_message_storage_base: static_milligas!(36), + on_chain_message_storage_per_byte: static_milligas!(1), - on_chain_return_value_per_byte: 1, + on_chain_return_value_per_byte: static_milligas!(1), - send_base: 29233, - send_transfer_funds: 27500, - send_transfer_only_premium: 159672, - send_invoke_method: -5377, + send_base: static_milligas!(29233), + send_transfer_funds: static_milligas!(27500), + send_transfer_only_premium: static_milligas!(159672), + send_invoke_method: static_milligas!(-5377), - create_actor_compute: 1108454, - create_actor_storage: 36 + 40, - delete_actor: -(36 + 40), + create_actor_compute: static_milligas!(1108454), + create_actor_storage: static_milligas!(36 + 40), + delete_actor: static_milligas!(-(36 + 40)), - bls_sig_cost: 16598605, - secp256k1_sig_cost: 1637292, + bls_sig_cost: static_milligas!(16598605), + secp256k1_sig_cost: static_milligas!(1637292), - hashing_base: 31355, - compute_unsealed_sector_cid_base: 98647, - verify_seal_base: 2000, // TODO revisit potential removal of this + hashing_base: static_milligas!(31355), + compute_unsealed_sector_cid_base: static_milligas!(98647), + verify_seal_base: static_milligas!(2000), // TODO revisit potential removal of this verify_aggregate_seal_base: 0, verify_aggregate_seal_per: [ ( RegisteredSealProof::StackedDRG32GiBV1P1, - 449900 + static_milligas!(449900) ), ( RegisteredSealProof::StackedDRG64GiBV1P1, - 359272 + static_milligas!(359272) ) ].iter().copied().collect(), verify_aggregate_seal_steps: [ @@ -61,14 +69,14 @@ lazy_static! { RegisteredSealProof::StackedDRG32GiBV1P1, StepCost ( vec![ - Step{start: 4, cost: 103994170}, - Step{start: 7, cost: 112356810}, - Step{start: 13, cost: 122912610}, - Step{start: 26, cost: 137559930}, - Step{start: 52, cost: 162039100}, - Step{start: 103, cost: 210960780}, - Step{start: 205, cost: 318351180}, - Step{start: 410, cost: 528274980}, + Step{start: 4, cost: static_milligas!(103994170)}, + Step{start: 7, cost: static_milligas!(112356810)}, + Step{start: 13, cost: static_milligas!(122912610)}, + Step{start: 26, cost: static_milligas!(137559930)}, + Step{start: 52, cost: static_milligas!(162039100)}, + Step{start: 103, cost: static_milligas!(210960780)}, + Step{start: 205, cost: static_milligas!(318351180)}, + Step{start: 410, cost: static_milligas!(528274980)}, ] ) ), @@ -76,14 +84,14 @@ lazy_static! { RegisteredSealProof::StackedDRG64GiBV1P1, StepCost ( vec![ - Step{start: 4, cost: 102581240}, - Step{start: 7, cost: 110803030}, - Step{start: 13, cost: 120803700}, - Step{start: 26, cost: 134642130}, - Step{start: 52, cost: 157357890}, - Step{start: 103, cost: 203017690}, - Step{start: 205, cost: 304253590}, - Step{start: 410, cost: 509880640}, + Step{start: 4, cost: static_milligas!(102581240)}, + Step{start: 7, cost: static_milligas!(110803030)}, + Step{start: 13, cost: static_milligas!(120803700)}, + Step{start: 26, cost: static_milligas!(134642130)}, + Step{start: 52, cost: static_milligas!(157357890)}, + Step{start: 103, cost: static_milligas!(203017690)}, + Step{start: 205, cost: static_milligas!(304253590)}, + Step{start: 410, cost: static_milligas!(509880640)}, ] ) ) @@ -91,27 +99,27 @@ lazy_static! { .cloned() .collect(), - verify_consensus_fault: 495422, - verify_replica_update: 36316136, + verify_consensus_fault: static_milligas!(495422), + verify_replica_update: static_milligas!(36316136), verify_post_lookup: [ ( RegisteredPoStProof::StackedDRGWindow512MiBV1, ScalingCost { - flat: 117680921, + flat: static_milligas!(117680921), scale: 43780, }, ), ( RegisteredPoStProof::StackedDRGWindow32GiBV1, ScalingCost { - flat: 117680921, + flat: static_milligas!(117680921), scale: 43780, }, ), ( RegisteredPoStProof::StackedDRGWindow64GiBV1, ScalingCost { - flat: 117680921, + flat: static_milligas!(117680921), scale: 43780, }, ), @@ -124,14 +132,21 @@ lazy_static! { get_randomness_per_byte: 0, block_memcpy_per_byte_cost: 0, - block_io_per_byte_cost: 0, - block_link_per_byte_cost: 1, - block_open_base: 114617, - block_read_base: 0, + block_open_base: static_milligas!(114617), + block_open_memret_per_byte_cost: 0, + + block_link_base: static_milligas!(353640), + block_link_storage_per_byte_cost: static_milligas!(1), + block_create_base: 0, - block_link_base: 353640, - block_stat: 0, + block_create_memret_per_byte_cost: 0, + + block_read_base: 0, + block_stat_base: 0, + + syscall_cost: 0, + extern_cost: 0, wasm_rules: WasmGasPrices{ exec_instruction_cost_milli: 0, @@ -139,40 +154,39 @@ lazy_static! { }; static ref SKYR_PRICES: PriceList = PriceList { - compute_gas_multiplier: 1, storage_gas_multiplier: 1300, - on_chain_message_compute_base: 38863, - on_chain_message_storage_base: 36, - on_chain_message_storage_per_byte: 1, + on_chain_message_compute_base: static_milligas!(38863), + on_chain_message_storage_base: static_milligas!(36), + on_chain_message_storage_per_byte: static_milligas!(1), - on_chain_return_value_per_byte: 1, + on_chain_return_value_per_byte: static_milligas!(1), - send_base: 29233, - send_transfer_funds: 27500, - send_transfer_only_premium: 159672, - send_invoke_method: -5377, + send_base: static_milligas!(29233), + send_transfer_funds: static_milligas!(27500), + send_transfer_only_premium: static_milligas!(159672), + send_invoke_method: static_milligas!(-5377), - create_actor_compute: 1108454, - create_actor_storage: 36 + 40, - delete_actor: -(36 + 40), + create_actor_compute: static_milligas!(1108454), + create_actor_storage: static_milligas!(36 + 40), + delete_actor: static_milligas!(-(36 + 40)), - bls_sig_cost: 16598605, - secp256k1_sig_cost: 1637292, + bls_sig_cost: static_milligas!(16598605), + secp256k1_sig_cost: static_milligas!(1637292), - hashing_base: 31355, - compute_unsealed_sector_cid_base: 98647, - verify_seal_base: 2000, // TODO revisit potential removal of this + hashing_base: static_milligas!(31355), + compute_unsealed_sector_cid_base: static_milligas!(98647), + verify_seal_base: static_milligas!(2000), // TODO revisit potential removal of this verify_aggregate_seal_base: 0, verify_aggregate_seal_per: [ ( RegisteredSealProof::StackedDRG32GiBV1P1, - 449900 + static_milligas!(449900) ), ( RegisteredSealProof::StackedDRG64GiBV1P1, - 359272 + static_milligas!(359272) ) ].iter().copied().collect(), verify_aggregate_seal_steps: [ @@ -180,14 +194,14 @@ lazy_static! { RegisteredSealProof::StackedDRG32GiBV1P1, StepCost ( vec![ - Step{start: 4, cost: 103994170}, - Step{start: 7, cost: 112356810}, - Step{start: 13, cost: 122912610}, - Step{start: 26, cost: 137559930}, - Step{start: 52, cost: 162039100}, - Step{start: 103, cost: 210960780}, - Step{start: 205, cost: 318351180}, - Step{start: 410, cost: 528274980}, + Step{start: 4, cost: static_milligas!(103994170)}, + Step{start: 7, cost: static_milligas!(112356810)}, + Step{start: 13, cost: static_milligas!(122912610)}, + Step{start: 26, cost: static_milligas!(137559930)}, + Step{start: 52, cost: static_milligas!(162039100)}, + Step{start: 103, cost: static_milligas!(210960780)}, + Step{start: 205, cost: static_milligas!(318351180)}, + Step{start: 410, cost: static_milligas!(528274980)}, ] ) ), @@ -195,14 +209,14 @@ lazy_static! { RegisteredSealProof::StackedDRG64GiBV1P1, StepCost ( vec![ - Step{start: 4, cost: 102581240}, - Step{start: 7, cost: 110803030}, - Step{start: 13, cost: 120803700}, - Step{start: 26, cost: 134642130}, - Step{start: 52, cost: 157357890}, - Step{start: 103, cost: 203017690}, - Step{start: 205, cost: 304253590}, - Step{start: 410, cost: 509880640}, + Step{start: 4, cost: static_milligas!(102581240)}, + Step{start: 7, cost: static_milligas!(110803030)}, + Step{start: 13, cost: static_milligas!(120803700)}, + Step{start: 26, cost: static_milligas!(134642130)}, + Step{start: 52, cost: static_milligas!(157357890)}, + Step{start: 103, cost: static_milligas!(203017690)}, + Step{start: 205, cost: static_milligas!(304253590)}, + Step{start: 410, cost: static_milligas!(509880640)}, ] ) ) @@ -211,27 +225,27 @@ lazy_static! { .collect(), // TODO: PARAM_FINISH: this may need to be increased to account for the cost of an extern - verify_consensus_fault: 495422, - verify_replica_update: 36316136, + verify_consensus_fault: static_milligas!(495422), + verify_replica_update: static_milligas!(36316136), verify_post_lookup: [ ( RegisteredPoStProof::StackedDRGWindow512MiBV1, ScalingCost { - flat: 117680921, + flat: static_milligas!(117680921), scale: 43780, }, ), ( RegisteredPoStProof::StackedDRGWindow32GiBV1, ScalingCost { - flat: 117680921, + flat: static_milligas!(117680921), scale: 43780, }, ), ( RegisteredPoStProof::StackedDRGWindow64GiBV1, ScalingCost { - flat: 117680921, + flat: static_milligas!(117680921), scale: 43780, }, ), @@ -240,31 +254,29 @@ lazy_static! { .copied() .collect(), - block_memcpy_per_byte_cost: 4, - block_io_per_byte_cost: 2, - block_link_per_byte_cost: 1, - // TODO: PARAM_FINISH - - // TODO: PARAM_FINISH - get_randomness_base: 1, - // TODO: PARAM_FINISH - get_randomness_per_byte: 1, - - // TODO: PARAM_FINIuiSH - block_open_base: 1, - // TODO: PARAM_FINISH - block_read_base: 1, - // TODO: PARAM_FINISH - block_create_base: 1, - // TODO: PARAM_FINISH - block_link_base: 1, - // TODO: PARAM_FINISH - block_stat: 1, + get_randomness_base: 0, + get_randomness_per_byte: 0, + + block_memcpy_per_byte_cost: 500, + + block_open_base: static_milligas!(114617), + block_open_memret_per_byte_cost: static_milligas!(10), + + block_link_base: static_milligas!(353640), + block_link_storage_per_byte_cost: static_milligas!(1), + + block_create_base: 0, + block_create_memret_per_byte_cost: static_milligas!(10), + + block_read_base: 0, + block_stat_base: 0, + + syscall_cost: static_milligas!(14000), + extern_cost: static_milligas!(21000), wasm_rules: WasmGasPrices{ - exec_instruction_cost_milli: 5000, + exec_instruction_cost_milli: static_milligas!(4) as u64, }, - // TODO: PARAM_FINISH }; } @@ -300,14 +312,10 @@ impl StepCost { } } -/// Provides prices for operations in the VM +/// Provides prices for operations in the VM. +/// All costs are in milligas. #[derive(Clone, Debug)] pub struct PriceList { - /// Compute gas charge multiplier - // * This multiplier is not currently applied to anything, but is matching lotus. - // * If the possible values are non 1 or if Lotus adds, we should change also. - #[allow(unused)] - pub(crate) compute_gas_multiplier: i64, /// Storage gas charge multiplier pub(crate) storage_gas_multiplier: i64, @@ -317,77 +325,98 @@ pub struct PriceList { /// Together, these account for the cost of message propagation and validation, /// up to but excluding any actual processing by the VM. /// This is the cost a block producer burns when including an invalid message. - pub(crate) on_chain_message_compute_base: i64, - pub(crate) on_chain_message_storage_base: i64, - pub(crate) on_chain_message_storage_per_byte: i64, + pub(crate) on_chain_message_compute_base: Milligas, + pub(crate) on_chain_message_storage_base: Milligas, + pub(crate) on_chain_message_storage_per_byte: Milligas, /// Gas cost charged to the originator of a non-nil return value produced /// by an on-chain message is given by: /// len(return value)*OnChainReturnValuePerByte - pub(crate) on_chain_return_value_per_byte: i64, + pub(crate) on_chain_return_value_per_byte: Milligas, /// Gas cost for any message send execution(including the top-level one /// initiated by an on-chain message). /// This accounts for the cost of loading sender and receiver actors and /// (for top-level messages) incrementing the sender's sequence number. /// Load and store of actor sub-state is charged separately. - pub(crate) send_base: i64, + pub(crate) send_base: Milligas, /// Gas cost charged, in addition to SendBase, if a message send /// is accompanied by any nonzero currency amount. /// Accounts for writing receiver's new balance (the sender's state is /// already accounted for). - pub(crate) send_transfer_funds: i64, + pub(crate) send_transfer_funds: Milligas, /// Gas cost charged, in addition to SendBase, if message only transfers funds. - pub(crate) send_transfer_only_premium: i64, + pub(crate) send_transfer_only_premium: Milligas, /// Gas cost charged, in addition to SendBase, if a message invokes /// a method on the receiver. /// Accounts for the cost of loading receiver code and method dispatch. - pub(crate) send_invoke_method: i64, + pub(crate) send_invoke_method: Milligas, /// Gas cost for creating a new actor (via InitActor's Exec method). /// Note: this costs assume that the extra will be partially or totally refunded while /// the base is covering for the put. - pub(crate) create_actor_compute: i64, - pub(crate) create_actor_storage: i64, + pub(crate) create_actor_compute: Milligas, + pub(crate) create_actor_storage: Milligas, /// Gas cost for deleting an actor. /// Note: this partially refunds the create cost to incentivise the deletion of the actors. - pub(crate) delete_actor: i64, + pub(crate) delete_actor: Milligas, /// Gas cost for verifying bls signature - pub(crate) bls_sig_cost: i64, + pub(crate) bls_sig_cost: Milligas, /// Gas cost for verifying secp256k1 signature - pub(crate) secp256k1_sig_cost: i64, + pub(crate) secp256k1_sig_cost: Milligas, - pub(crate) hashing_base: i64, + pub(crate) hashing_base: Milligas, - pub(crate) compute_unsealed_sector_cid_base: i64, - pub(crate) verify_seal_base: i64, + pub(crate) compute_unsealed_sector_cid_base: Milligas, + pub(crate) verify_seal_base: Milligas, #[allow(unused)] - pub(crate) verify_aggregate_seal_base: i64, + pub(crate) verify_aggregate_seal_base: Milligas, pub(crate) verify_aggregate_seal_per: AHashMap, pub(crate) verify_aggregate_seal_steps: AHashMap, pub(crate) verify_post_lookup: AHashMap, - pub(crate) verify_consensus_fault: i64, - pub(crate) verify_replica_update: i64, - - pub(crate) get_randomness_base: i64, - pub(crate) get_randomness_per_byte: i64, - - pub(crate) block_memcpy_per_byte_cost: i64, - pub(crate) block_io_per_byte_cost: i64, - pub(crate) block_link_per_byte_cost: i64, - - pub(crate) block_open_base: i64, - pub(crate) block_read_base: i64, - pub(crate) block_create_base: i64, - pub(crate) block_link_base: i64, - pub(crate) block_stat: i64, - + pub(crate) verify_consensus_fault: Milligas, + pub(crate) verify_replica_update: Milligas, + + /// Gas cost for fetching randomness. + pub(crate) get_randomness_base: Milligas, + /// Gas cost per every byte of randomness fetched. + pub(crate) get_randomness_per_byte: Milligas, + + /// Gas cost per every block byte memcopied across boundaries. + pub(crate) block_memcpy_per_byte_cost: Milligas, + + /// Gas cost for opening a block. + pub(crate) block_open_base: Milligas, + /// Gas cost for every byte retained in FVM space when opening a block. + pub(crate) block_open_memret_per_byte_cost: Milligas, + + /// Gas cost for linking a block. + pub(crate) block_link_base: Milligas, + /// Multiplier for storage gas per byte. + pub(crate) block_link_storage_per_byte_cost: Milligas, + + /// Gas cost for creating a block. + pub(crate) block_create_base: Milligas, + /// Gas cost for every byte retained in FVM space when writing a block. + pub(crate) block_create_memret_per_byte_cost: Milligas, + + /// Gas cost for reading a block into actor space. + pub(crate) block_read_base: Milligas, + /// Gas cost for statting a block. + pub(crate) block_stat_base: Milligas, + + /// General gas cost for performing a syscall, accounting for the overhead thereof. + pub(crate) syscall_cost: Milligas, + /// General gas cost for calling an extern, accounting for the overhead thereof. + pub(crate) extern_cost: Milligas, + + /// Rules for execution gas. pub(crate) wasm_rules: WasmGasPrices, } @@ -408,6 +437,7 @@ impl PriceList { * self.storage_gas_multiplier, ) } + /// Returns the gas required for storing the response of a message in the chain. #[inline] pub fn on_chain_return_value(&self, data_size: usize) -> GasCharge<'static> { @@ -417,6 +447,7 @@ impl PriceList { data_size as i64 * self.on_chain_return_value_per_byte * self.storage_gas_multiplier, ) } + /// Returns the gas required when invoking a method. #[inline] pub fn on_method_invocation( @@ -436,6 +467,12 @@ impl PriceList { } GasCharge::new("OnMethodInvocation", ret, 0) } + + /// Returns the gas cost to be applied on a syscall. + pub fn on_syscall(&self) -> GasCharge<'static> { + GasCharge::new("OnSyscall", self.syscall_cost, 0) + } + /// Returns the gas required for creating an actor. #[inline] pub fn on_create_actor(&self) -> GasCharge<'static> { @@ -445,6 +482,7 @@ impl PriceList { self.create_actor_storage * self.storage_gas_multiplier, ) } + /// Returns the gas required for deleting an actor. #[inline] pub fn on_delete_actor(&self) -> GasCharge<'static> { @@ -454,6 +492,7 @@ impl PriceList { self.delete_actor * self.storage_gas_multiplier, ) } + /// Returns gas required for signature verification. #[inline] pub fn on_verify_signature(&self, sig_type: SignatureType) -> GasCharge<'static> { @@ -463,11 +502,13 @@ impl PriceList { }; GasCharge::new("OnVerifySignature", val, 0) } + /// Returns gas required for hashing data. #[inline] pub fn on_hashing(&self, _: usize) -> GasCharge<'static> { GasCharge::new("OnHashing", self.hashing_base, 0) } + /// Returns gas required for computing unsealed sector Cid. #[inline] pub fn on_compute_unsealed_sector_cid( @@ -481,6 +522,7 @@ impl PriceList { 0, ) } + /// Returns gas required for seal verification. #[inline] pub fn on_verify_seal(&self, _info: &SealVerifyInfo) -> GasCharge<'static> { @@ -521,11 +563,13 @@ impl PriceList { 0, ) } + /// Returns gas required for replica verification. #[inline] pub fn on_verify_replica_update(&self, _replica: &ReplicaUpdateInfo) -> GasCharge<'static> { GasCharge::new("OnVerifyReplicaUpdate", self.verify_replica_update, 0) } + /// Returns gas required for PoSt verification. #[inline] pub fn on_verify_post(&self, info: &WindowPoStVerifyInfo) -> GasCharge<'static> { @@ -544,10 +588,15 @@ impl PriceList { GasCharge::new("OnVerifyPost", gas_used, 0) } + /// Returns gas required for verifying consensus fault. #[inline] pub fn on_verify_consensus_fault(&self) -> GasCharge<'static> { - GasCharge::new("OnVerifyConsensusFault", self.verify_consensus_fault, 0) + GasCharge::new( + "OnVerifyConsensusFault", + self.extern_cost.saturating_add(self.verify_consensus_fault), + 0, + ) } /// Returns the cost of the gas required for getting randomness from the client, based on the @@ -556,10 +605,12 @@ impl PriceList { pub fn on_get_randomness(&self, entropy_size: usize) -> GasCharge<'static> { GasCharge::new( "OnGetRandomness", - self.get_randomness_base.saturating_add( - self.get_randomness_per_byte - .saturating_mul(entropy_size as i64), - ), + self.extern_cost + .saturating_add(self.get_randomness_base) + .saturating_add( + self.get_randomness_per_byte + .saturating_mul(entropy_size as i64), + ), 0, ) } @@ -567,19 +618,25 @@ impl PriceList { /// Returns the base gas required for loading an object, independent of the object's size. #[inline] pub fn on_block_open_base(&self) -> GasCharge<'static> { - GasCharge::new("OnBlockOpenBase", self.block_open_base, 0) + GasCharge::new( + "OnBlockOpenBase", + self.extern_cost.saturating_add(self.block_open_base), + 0, + ) } /// Returns the gas required for loading an object based on the size of the object. #[inline] pub fn on_block_open_per_byte(&self, data_size: usize) -> GasCharge<'static> { - // TODO: Should we also throw on a memcpy cost here (see https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0032.md#ipld-state-management-fees) + let size = data_size as i64; GasCharge::new( "OnBlockOpenPerByte", - self.block_io_per_byte_cost.saturating_mul(data_size as i64), + self.block_open_memret_per_byte_cost.saturating_mul(size) + + self.block_memcpy_per_byte_cost.saturating_mul(size), 0, ) } + /// Returns the gas required for reading a loaded object. #[inline] pub fn on_block_read(&self, data_size: usize) -> GasCharge<'static> { @@ -596,12 +653,14 @@ impl PriceList { /// Returns the gas required for adding an object to the FVM cache. #[inline] pub fn on_block_create(&self, data_size: usize) -> GasCharge<'static> { + let size = data_size as i64; + let mem_costs = self + .block_create_memret_per_byte_cost + .saturating_mul(size) + .saturating_add(self.block_memcpy_per_byte_cost.saturating_mul(size)); GasCharge::new( "OnBlockCreate", - self.block_create_base.saturating_add( - self.block_memcpy_per_byte_cost - .saturating_mul(data_size as i64), - ), + self.block_create_base.saturating_add(mem_costs), 0, ) } @@ -609,21 +668,26 @@ impl PriceList { /// Returns the gas required for committing an object to the state blockstore. #[inline] pub fn on_block_link(&self, data_size: usize) -> GasCharge<'static> { - // TODO: The FIP makes it sound like this would need 2 memcpys, is that what's desired? + let size = data_size as i64; + let memcpy = self.block_memcpy_per_byte_cost.saturating_mul(size); GasCharge::new( "OnBlockLink", - self.block_link_base, - // data_size as i64 * self.block_link_per_byte_cost * self.storage_gas_multiplier, - self.block_link_per_byte_cost + // twice the memcpy cost: + // - one from the block registry to the FVM BufferedBlockstore + // - one from the FVM BufferedBlockstore to the Node's Blockstore + // when the machine finishes. + self.block_link_base + .saturating_add((2_i64).saturating_mul(memcpy)), + self.block_link_storage_per_byte_cost .saturating_mul(self.storage_gas_multiplier) - .saturating_mul(data_size as i64), + .saturating_mul(size), ) } /// Returns the gas required for storing an object. #[inline] pub fn on_block_stat(&self) -> GasCharge<'static> { - GasCharge::new("OnBlockStat", self.block_stat, 0) + GasCharge::new("OnBlockStat", self.block_stat_base, 0) } } @@ -636,12 +700,30 @@ pub fn price_list_by_network_version(network_version: NetworkVersion) -> &'stati } impl Rules for WasmGasPrices { - fn instruction_cost(&self, _instruction: &Instruction) -> Option { - Some(self.exec_instruction_cost_milli) + fn instruction_cost(&self, instruction: &Instruction) -> Option { + if self.exec_instruction_cost_milli == 0 { + return Some(0); + } + + // Rules valid for nv16. We will need to be generic over Rules (massive + // generics tax), use &dyn Rules (which breaks other things), or pass + // in the network version, or rules version, to vary these prices going + // forward. + match instruction { + // FIP-0032: nop, drop, block, loop, unreachable, return, else, end are priced 0. + Instruction::Nop + | Instruction::Drop + | Instruction::Block(_) + | Instruction::Loop(_) + | Instruction::Unreachable + | Instruction::Return + | Instruction::Else + | Instruction::End => Some(0), + _ => Some(self.exec_instruction_cost_milli), + } } fn memory_grow_cost(&self) -> MemoryGrowCost { - // todo use pricelist MemoryGrowCost::Free } } diff --git a/fvm/src/kernel/default.rs b/fvm/src/kernel/default.rs index 3a043f00b..c59fcc6bb 100644 --- a/fvm/src/kernel/default.rs +++ b/fvm/src/kernel/default.rs @@ -599,6 +599,7 @@ where .externs() .verify_consensus_fault(h1, h2, extra) .or_illegal_argument()?; + if self.network_version() <= NetworkVersion::V15 { self.call_manager.charge_gas(GasCharge::new( "verify_consensus_fault_accesses", @@ -762,20 +763,14 @@ where fn gas_available(&self) -> i64 { self.call_manager.gas_tracker().gas_available() } + fn milligas_available(&self) -> i64 { self.call_manager.gas_tracker().milligas_available() } - fn charge_gas(&mut self, name: &str, compute: i64) -> Result<()> { + fn charge_milligas(&mut self, name: &str, compute: Milligas) -> Result<()> { let charge = GasCharge::new(name, compute, 0); - self.call_manager.gas_tracker_mut().charge_gas(charge) - } - - fn charge_milligas(&mut self, name: &str, compute: i64) -> Result<()> { - self.call_manager - .gas_tracker_mut() - .charge_milligas(name, compute)?; - Ok(()) + self.call_manager.gas_tracker_mut().apply_charge(charge) } fn price_list(&self) -> &PriceList { @@ -816,6 +811,7 @@ where .price_list() .on_get_randomness(entropy.len()), )?; + // TODO: Check error code self.call_manager .externs() @@ -835,6 +831,7 @@ where .price_list() .on_get_randomness(entropy.len()), )?; + // TODO: Check error code self.call_manager .externs() diff --git a/fvm/src/kernel/mod.rs b/fvm/src/kernel/mod.rs index 5eee6911a..ff65b3098 100644 --- a/fvm/src/kernel/mod.rs +++ b/fvm/src/kernel/mod.rs @@ -24,10 +24,9 @@ mod error; pub use error::{ClassifyResult, Context, ExecutionError, Result, SyscallError}; use crate::call_manager::{CallManager, InvocationResult}; -use crate::gas::PriceList; +use crate::gas::{Gas, Milligas, PriceList}; use crate::machine::Machine; -/// The "kernel" implements pub trait Kernel: ActorOps + BlockOps @@ -214,19 +213,19 @@ pub trait CircSupplyOps { /// Operations for explicit gas charging. pub trait GasOps { - /// GasUsed return the gas used by the transaction so far. - fn gas_used(&self) -> i64; - fn milligas_used(&self) -> i64; + /// Returns the gas used by the transaction so far. + fn gas_used(&self) -> Gas; + fn milligas_used(&self) -> Milligas; - fn gas_available(&self) -> i64; - fn milligas_available(&self) -> i64; + /// Returns the remaining gas for the transaction. + fn gas_available(&self) -> Gas; + fn milligas_available(&self) -> Milligas; /// ChargeGas charges specified amount of `gas` for execution. - /// `name` provides information about gas charging point - fn charge_gas(&mut self, name: &str, compute: i64) -> Result<()>; - - fn charge_milligas(&mut self, name: &str, compute: i64) -> Result<()>; + /// `name` provides information about gas charging point. + fn charge_milligas(&mut self, name: &str, compute: Milligas) -> Result<()>; + /// Returns the currently active gas price list. fn price_list(&self) -> &PriceList; } diff --git a/fvm/src/syscalls/bind.rs b/fvm/src/syscalls/bind.rs index 2afcb48dd..62889b91a 100644 --- a/fvm/src/syscalls/bind.rs +++ b/fvm/src/syscalls/bind.rs @@ -95,6 +95,15 @@ fn memory_and_data<'a, K: Kernel>( (Memory::new(mem), data) } +macro_rules! charge_syscall_gas { + ($kernel:expr) => { + let charge = $kernel.price_list().on_syscall(); + $kernel + .charge_milligas(charge.name, charge.compute_gas) + .map_err(Abort::from_error_as_fatal)?; + }; +} + // Unfortunately, we can't implement this for _all_ functions. So we implement it for functions of up to 6 arguments. macro_rules! impl_bind_syscalls { ($($t:ident)*) => { @@ -118,6 +127,8 @@ macro_rules! impl_bind_syscalls { charge_for_exec(&mut caller)?; let (mut memory, mut data) = memory_and_data(&mut caller); + charge_syscall_gas!(data.kernel); + let ctx = Context{kernel: &mut data.kernel, memory: &mut memory}; let out = syscall(ctx $(, $t)*).into(); @@ -146,6 +157,7 @@ macro_rules! impl_bind_syscalls { charge_for_exec(&mut caller)?; let (mut memory, mut data) = memory_and_data(&mut caller); + charge_syscall_gas!(data.kernel); // We need to check to make sure we can store the return value _before_ we do anything. if (ret as u64) > (memory.len() as u64) diff --git a/fvm/src/syscalls/error.rs b/fvm/src/syscalls/error.rs index 5872da9cd..d8a7b0257 100644 --- a/fvm/src/syscalls/error.rs +++ b/fvm/src/syscalls/error.rs @@ -1,6 +1,7 @@ //! This module contains code used to convert errors to and from wasmtime traps. use std::sync::Mutex; +use anyhow::anyhow; use derive_more::Display; use fvm_shared::error::ExitCode; use wasmtime::Trap; @@ -34,6 +35,15 @@ impl Abort { ExecutionError::Fatal(err) => Abort::Fatal(err), } } + + /// Just like from_error, but escalating syscall errors as fatal. + pub fn from_error_as_fatal(e: ExecutionError) -> Self { + match e { + ExecutionError::OutOfGas => Abort::OutOfGas, + ExecutionError::Fatal(e) => Abort::Fatal(e), + ExecutionError::Syscall(e) => Abort::Fatal(anyhow!("unexpected syscall error: {}", e)), + } + } } /// Wraps an execution error in a Trap. diff --git a/fvm/src/syscalls/gas.rs b/fvm/src/syscalls/gas.rs index 75bfd14c6..9009506d4 100644 --- a/fvm/src/syscalls/gas.rs +++ b/fvm/src/syscalls/gas.rs @@ -1,6 +1,7 @@ use std::str; use super::Context; +use crate::gas::gas_to_milligas; use crate::kernel::{ClassifyResult, Result}; use crate::Kernel; @@ -12,5 +13,8 @@ pub fn charge_gas( ) -> Result<()> { let name = str::from_utf8(context.memory.try_slice(name_off, name_len)?).or_illegal_argument()?; - context.kernel.charge_gas(name, compute) + // Gas charges from actors are always in full gas units. We use milligas internally, so convert here. + context + .kernel + .charge_milligas(name, gas_to_milligas(compute)) } diff --git a/fvm/src/syscalls/mod.rs b/fvm/src/syscalls/mod.rs index a368abe9b..440ea6f0f 100644 --- a/fvm/src/syscalls/mod.rs +++ b/fvm/src/syscalls/mod.rs @@ -5,7 +5,6 @@ use cid::Cid; use wasmtime::{AsContextMut, Global, Linker, Memory, Val}; use crate::call_manager::backtrace; -use crate::kernel::ExecutionError; use crate::Kernel; pub(crate) mod error; @@ -62,6 +61,7 @@ pub fn update_gas_available( Ok(()) } +/// Updates the FVM-side gas tracker with newly accrued execution gas charges. pub fn charge_for_exec( ctx: &mut impl AsContextMut>, ) -> Result<(), Abort> { @@ -74,7 +74,7 @@ pub fn charge_for_exec( .context("failed to get wasm gas") .map_err(Abort::Fatal)?; - // Determine milligas used, and update the "o + // Determine milligas used, and update the gas tracker. let milligas_used = { let data = ctx.data_mut(); let last_milligas = mem::replace(&mut data.last_milligas_available, milligas_available); @@ -85,11 +85,8 @@ pub fn charge_for_exec( ctx.data_mut() .kernel .charge_milligas("wasm_exec", milligas_used) - .map_err(|e| match e { - ExecutionError::OutOfGas => Abort::OutOfGas, - ExecutionError::Fatal(e) => Abort::Fatal(e), - ExecutionError::Syscall(e) => Abort::Fatal(anyhow!("unexpected syscall error: {}", e)), - })?; + .map_err(Abort::from_error_as_fatal)?; + Ok(()) } diff --git a/testing/conformance/src/vm.rs b/testing/conformance/src/vm.rs index 9d6471f06..ccf9fec70 100644 --- a/testing/conformance/src/vm.rs +++ b/testing/conformance/src/vm.rs @@ -426,14 +426,14 @@ where // NOT forwarded fn verify_seal(&mut self, vi: &SealVerifyInfo) -> Result { let charge = self.1.price_list.on_verify_seal(vi); - self.0.charge_gas(charge.name, charge.total())?; + self.0.charge_milligas(charge.name, charge.total())?; Ok(true) } // NOT forwarded fn verify_post(&mut self, vi: &WindowPoStVerifyInfo) -> Result { let charge = self.1.price_list.on_verify_post(vi); - self.0.charge_gas(charge.name, charge.total())?; + self.0.charge_milligas(charge.name, charge.total())?; Ok(true) } @@ -445,7 +445,7 @@ where _extra: &[u8], ) -> Result> { let charge = self.1.price_list.on_verify_consensus_fault(); - self.0.charge_gas(charge.name, charge.total())?; + self.0.charge_milligas(charge.name, charge.total())?; // TODO this seems wrong, should probably be parameterized. Ok(None) } @@ -453,14 +453,14 @@ where // NOT forwarded fn verify_aggregate_seals(&mut self, agg: &AggregateSealVerifyProofAndInfos) -> Result { let charge = self.1.price_list.on_verify_aggregate_seals(agg); - self.0.charge_gas(charge.name, charge.total())?; + self.0.charge_milligas(charge.name, charge.total())?; Ok(true) } // NOT forwarded fn verify_replica_update(&mut self, rep: &ReplicaUpdateInfo) -> Result { let charge = self.1.price_list.on_verify_replica_update(rep); - self.0.charge_gas(charge.name, charge.total())?; + self.0.charge_milligas(charge.name, charge.total())?; Ok(true) } } @@ -490,8 +490,8 @@ where self.0.gas_used() } - fn charge_gas(&mut self, name: &str, compute: i64) -> Result<()> { - self.0.charge_gas(name, compute) + fn charge_milligas(&mut self, name: &str, compute: i64) -> Result<()> { + self.0.charge_milligas(name, compute) } fn price_list(&self) -> &PriceList { @@ -509,10 +509,6 @@ where fn milligas_available(&self) -> i64 { self.0.milligas_available() } - - fn charge_milligas(&mut self, name: &str, compute: i64) -> Result<()> { - self.0.charge_milligas(name, compute) - } } impl MessageOps for TestKernel