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: Precharge fn loading gas cost #6772

Merged
merged 18 commits into from
Jun 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions chain/chain/src/tests/simple_chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ fn build_chain() {
// cargo insta test --accept -p near-chain --features nightly -- tests::simple_chain::build_chain
let hash = chain.head().unwrap().last_block_hash;
if cfg!(feature = "nightly") {
insta::assert_display_snapshot!(hash, @"8F4vXPPNevoQXVGdwKAZQfzzxhSyqWp3xJiik4RMUKSk");
insta::assert_display_snapshot!(hash, @"7Db9S56zVuTKvYGnHXvsJYH6CvQBBEm2TC7Y3S85SZps");
} else {
insta::assert_display_snapshot!(hash, @"6sAno2uEwwQ5yiDscePWY8HWmRJLpGNv39uoff3BCpxT");
}
Expand Down Expand Up @@ -68,7 +68,7 @@ fn build_chain() {

let hash = chain.head().unwrap().last_block_hash;
if cfg!(feature = "nightly") {
insta::assert_display_snapshot!(hash, @"DrW7MsRaFhEdjQcxjqrTXvNmQ1eptgURQ7RUTeZnrBXC");
insta::assert_display_snapshot!(hash, @"HbPMs5o1Twqb7ZqrrhaCGawwZbYJVaPHZ7tfoihcnYxc");
} else {
insta::assert_display_snapshot!(hash, @"Fn9MgjUx6VXhPYNqqDtf2C9kBVveY2vuSLXNLZUNJCqK");
}
Expand Down
2 changes: 2 additions & 0 deletions core/primitives/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,14 @@ 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_fix_contract_loading_cost = []
nightly = [
"nightly_protocol",
"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_fix_contract_loading_cost",
]
nightly_protocol = []
deepsize_feature = [
Expand Down
7 changes: 6 additions & 1 deletion core/primitives/src/version.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,9 @@ pub enum ProtocolFeature {
/// alpha is min stake ratio
#[cfg(feature = "protocol_feature_fix_staking_threshold")]
FixStakingThreshold,
/// Charge for contract loading before it happens.
#[cfg(feature = "protocol_feature_fix_contract_loading_cost")]
FixContractLoadingCost,
}

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

/// The points in time after which the voting for the protocol version should start.
#[allow(dead_code)]
Expand Down Expand Up @@ -241,6 +244,8 @@ impl ProtocolFeature {
ProtocolFeature::RoutingExchangeAlgorithm => 117,
#[cfg(feature = "protocol_feature_fix_staking_threshold")]
ProtocolFeature::FixStakingThreshold => 126,
#[cfg(feature = "protocol_feature_fix_contract_loading_cost")]
ProtocolFeature::FixContractLoadingCost => 129,
}
}
}
Expand Down
4 changes: 4 additions & 0 deletions nearcore/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,9 @@ protocol_feature_fix_staking_threshold = [
"near-primitives/protocol_feature_fix_staking_threshold",
"near-epoch-manager/protocol_feature_fix_staking_threshold",
]
protocol_feature_fix_contract_loading_cost = [
"near-vm-runner/protocol_feature_fix_contract_loading_cost",
]
nightly = [
"nightly_protocol",
"near-primitives/nightly",
Expand All @@ -138,6 +141,7 @@ nightly = [
"protocol_feature_routing_exchange_algorithm",
"protocol_feature_access_key_nonce_for_implicit_accounts",
"protocol_feature_fix_staking_threshold",
"protocol_feature_fix_contract_loading_cost",
]
nightly_protocol = [
"near-primitives/nightly_protocol",
Expand Down
3 changes: 3 additions & 0 deletions runtime/near-vm-logic/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ serde_json = { version = "1", features = ["preserve_order"] }

[features]
default = []
protocol_feature_fix_contract_loading_cost = [
"near-primitives/protocol_feature_fix_contract_loading_cost",
]

# Use this feature to enable counting of fees and costs applied.
costs_counting = []
Expand Down
69 changes: 67 additions & 2 deletions runtime/near-vm-logic/src/logic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ use crate::utils::split_method_names;
use crate::{ReceiptMetadata, ValuePtr};
use byteorder::ByteOrder;
use near_crypto::Secp256K1Signature;
use near_primitives::checked_feature;
use near_primitives::config::ViewConfig;
use near_primitives::version::is_implicit_account_creation_enabled;
use near_primitives_core::config::ExtCosts::*;
use near_primitives_core::config::{ActionCosts, ExtCosts, VMConfig, ViewConfig};
use near_primitives_core::config::{ActionCosts, ExtCosts, VMConfig};
use near_primitives_core::profile::ProfileData;
use near_primitives_core::runtime::fees::{
transfer_exec_fee, transfer_send_fee, RuntimeFeesConfig,
Expand All @@ -18,8 +20,8 @@ use near_primitives_core::types::{
AccountId, Balance, EpochHeight, Gas, ProtocolVersion, StorageUsage,
};
use near_primitives_core::types::{GasDistribution, GasWeight};
use near_vm_errors::InconsistentStateError;
use near_vm_errors::{HostError, VMLogicError};
use near_vm_errors::{InconsistentStateError, VMError};
use std::collections::HashMap;
use std::mem::size_of;

Expand Down Expand Up @@ -2670,6 +2672,13 @@ impl<'a> VMLogic<'a> {
}
}

/// Add a cost for loading the contract code in the VM.
///
/// This cost does not consider the structure of the contract code, only the
/// size. This is currently the only loading fee. A fee that takes the code
/// structure into consideration could be added. But since that would have
/// to happen after loading, we cannot pre-charge it. This is the main
/// motivation to (only) have this simple fee.
pub fn add_contract_loading_fee(&mut self, code_len: u64) -> Result<()> {
self.gas_counter.pay_per(contract_loading_bytes, code_len)?;
self.gas_counter.pay_base(contract_loading_base)
Expand All @@ -2686,6 +2695,62 @@ impl<'a> VMLogic<'a> {
let new_used_gas = self.gas_counter.used_gas();
self.gas_counter.process_gas_limit(new_burn_gas, new_used_gas)
}

/// VM independent setup before loading the executable.
///
/// Does VM independent checks that happen after the instantiation of
/// VMLogic but before loading the executable. This includes pre-charging gas
/// costs for loading the executable, which depends on the size of the WASM code.
pub fn before_loading_executable(
&mut self,
method_name: &str,
current_protocol_version: u32,
wasm_code_bytes: usize,
) -> std::result::Result<(), VMError> {
if method_name.is_empty() {
let error =
VMError::FunctionCallError(near_vm_errors::FunctionCallError::MethodResolveError(
near_vm_errors::MethodResolveError::MethodEmptyName,
));
return Err(error);
}
if checked_feature!(
"protocol_feature_fix_contract_loading_cost",
FixContractLoadingCost,
current_protocol_version
) {
if self.add_contract_loading_fee(wasm_code_bytes as u64).is_err() {
let error =
VMError::FunctionCallError(near_vm_errors::FunctionCallError::HostError(
near_vm_errors::HostError::GasExceeded,
));
return Err(error);
}
}
Ok(())
}

/// Legacy code to preserve old gas charging behaviour in old protocol versions.
pub fn after_loading_executable(
&mut self,
current_protocol_version: u32,
wasm_code_bytes: usize,
) -> std::result::Result<(), VMError> {
if !checked_feature!(
"protocol_feature_fix_contract_loading_cost",
FixContractLoadingCost,
current_protocol_version
) {
if self.add_contract_loading_fee(wasm_code_bytes as u64).is_err() {
return Err(VMError::FunctionCallError(
near_vm_errors::FunctionCallError::HostError(
near_vm_errors::HostError::GasExceeded,
),
));
}
}
Ok(())
}
}

#[derive(Clone, PartialEq)]
Expand Down
6 changes: 6 additions & 0 deletions runtime/near-vm-runner/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,14 @@ no_cpu_compatibility_checks = []

no_cache = []

protocol_feature_fix_contract_loading_cost = [
"near-primitives/protocol_feature_fix_contract_loading_cost",
"near-vm-logic/protocol_feature_fix_contract_loading_cost",
]

nightly = [
"near-primitives/nightly",
"protocol_feature_fix_contract_loading_cost",
]
sandbox = ["near-vm-logic/sandbox"]

Expand Down
19 changes: 19 additions & 0 deletions runtime/near-vm-runner/src/runner.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use near_primitives::checked_feature;
use near_primitives::config::VMConfig;
use near_primitives::contract::ContractCode;
use near_primitives::hash::CryptoHash;
Expand Down Expand Up @@ -153,6 +154,24 @@ impl VMResult {
VMResult::Aborted(outcome, error)
}

/// Like `VMResult::abort()` but without feature `FixContractLoadingCost` it
/// will return a NOP outcome. This is used for backwards-compatibility only.
pub fn abort_but_nop_outcome_in_old_protocol(
logic: VMLogic,
error: VMError,
current_protocol_version: u32,
) -> VMResult {
if checked_feature!(
"protocol_feature_fix_contract_loading_cost",
FixContractLoadingCost,
current_protocol_version
) {
VMResult::abort(logic, error)
} else {
VMResult::nop_outcome(error)
}
}

/// Borrow the internal outcome, if there is one.
pub fn outcome(&self) -> &VMOutcome {
match self {
Expand Down
65 changes: 37 additions & 28 deletions runtime/near-vm-runner/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,21 +55,29 @@ fn create_context(input: Vec<u8>) -> VMContext {
}
}

fn make_simple_contract_call_with_gas_vm(
/// Exhaustive parameters for making a simple contract call.
/// Calling with no protocol version specified will use a test configuration.
fn make_simple_contract_call_ex(
code: &[u8],
method_name: &str,
prepaid_gas: u64,
vm_kind: VMKind,
protocol_version: Option<ProtocolVersion>,
prepaid_gas: u64,
) -> (VMOutcome, Option<VMError>) {
let mut fake_external = MockedExternal::new();
let mut context = create_context(vec![]);
context.prepaid_gas = prepaid_gas;
let config = VMConfig::test();
let fees = RuntimeFeesConfig::test();
let (config, fees) = if let Some(protocol_version) = protocol_version {
let runtime_config_store = RuntimeConfigStore::new(None);
let runtime_config = runtime_config_store.get_config(protocol_version);
(runtime_config.wasm_config.clone(), runtime_config.transaction_costs.clone())
} else {
(VMConfig::test(), RuntimeFeesConfig::test())
};

let promise_results = vec![];

let code = ContractCode::new(code.to_vec(), None);

let runtime = vm_kind.runtime(config).expect("runtime has not been compiled");
runtime
.run(
Expand All @@ -79,7 +87,7 @@ fn make_simple_contract_call_with_gas_vm(
context,
&fees,
&promise_results,
LATEST_PROTOCOL_VERSION,
protocol_version.unwrap_or(LATEST_PROTOCOL_VERSION),
None,
)
.outcome_error()
Expand All @@ -91,36 +99,24 @@ fn make_simple_contract_call_with_protocol_version_vm(
protocol_version: ProtocolVersion,
vm_kind: VMKind,
) -> (VMOutcome, Option<VMError>) {
let mut fake_external = MockedExternal::new();
let context = create_context(vec![]);
let runtime_config_store = RuntimeConfigStore::new(None);
let runtime_config = runtime_config_store.get_config(protocol_version);
let fees = &runtime_config.transaction_costs;
let runtime =
vm_kind.runtime(runtime_config.wasm_config.clone()).expect("runtime has not been compiled");
make_simple_contract_call_ex(code, method_name, vm_kind, Some(protocol_version), 10u64.pow(14))
}

let promise_results = vec![];
let code = ContractCode::new(code.to_vec(), None);
runtime
.run(
&code,
method_name,
&mut fake_external,
context,
fees,
&promise_results,
protocol_version,
None,
)
.outcome_error()
fn make_simple_contract_call_with_gas_vm(
code: &[u8],
method_name: &str,
prepaid_gas: u64,
vm_kind: VMKind,
) -> (VMOutcome, Option<VMError>) {
make_simple_contract_call_ex(code, method_name, vm_kind, None, prepaid_gas)
}

fn make_simple_contract_call_vm(
code: &[u8],
method_name: &str,
vm_kind: VMKind,
) -> (VMOutcome, Option<VMError>) {
make_simple_contract_call_with_gas_vm(code, method_name, 10u64.pow(14), vm_kind)
make_simple_contract_call_ex(code, method_name, vm_kind, None, 10u64.pow(14))
}

#[track_caller]
Expand All @@ -134,3 +130,16 @@ fn gas_and_error_match(
assert_eq!(outcome.burnt_gas, expected_gas, "burnt gas differs");
assert_eq!(outcome_and_error.1, expected_error);
}

/// Small helper to compute expected loading gas cost charged before loading.
///
/// Includes hard-coded value for runtime parameter values
/// `wasm_contract_loading_base` and `wasm_contract_loading_bytes` which would
/// have to be updated if they change in the future.
fn prepaid_loading_gas(bytes: usize) -> u64 {
if cfg!(feature = "protocol_feature_fix_contract_loading_cost") {
35_445_963 + bytes as u64 * 21_6750
} else {
0
}
}
5 changes: 3 additions & 2 deletions runtime/near-vm-runner/src/tests/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,15 +53,16 @@ fn test_does_not_cache_io_error() {

cache.set_read_fault(true);
let result = make_cached_contract_call_vm(&cache, &code, "main", prepaid_gas, vm_kind);
assert_eq!(result.outcome().used_gas, 0);
let expected_gas = crate::tests::prepaid_loading_gas(code.len());
assert_eq!(result.outcome().used_gas, expected_gas);
assert_matches!(
result.error(),
Some(&VMError::CacheError(near_vm_errors::CacheError::ReadError))
);

cache.set_write_fault(true);
let result = make_cached_contract_call_vm(&cache, &code, "main", prepaid_gas, vm_kind);
assert_eq!(result.outcome().used_gas, 0);
assert_eq!(result.outcome().used_gas, expected_gas);
assert_matches!(
result.error(),
Some(&VMError::CacheError(near_vm_errors::CacheError::WriteError))
Expand Down
3 changes: 2 additions & 1 deletion runtime/near-vm-runner/src/tests/compile_errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -245,9 +245,10 @@ fn test_limit_locals() {
}
.make();
let res = make_simple_contract_call_vm(&wasm_err, "main", vm_kind);
let expected_gas = super::prepaid_loading_gas(wasm_err.len());
gas_and_error_match(
res,
0,
expected_gas,
Some(VMError::FunctionCallError(FunctionCallError::CompilationError(
CompilationError::PrepareError(PrepareError::Deserialization),
))),
Expand Down
3 changes: 2 additions & 1 deletion runtime/near-vm-runner/src/tests/contract_preload.rs
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,8 @@ fn test_vm_runner(preloaded: bool, vm_kind: VMKind, repeat: i32) {
ProtocolVersion::MAX,
cache.as_deref(),
);
test_result(result2, 0).expect_err("Call expected to fail.");
let expected_gas = crate::tests::prepaid_loading_gas(code2.code().len());
test_result(result2, expected_gas).expect_err("Call expected to fail.");
}
}
}
Expand Down
Loading