diff --git a/aptos-move/aptos-gas-meter/src/meter.rs b/aptos-move/aptos-gas-meter/src/meter.rs index 2db1d96f87c50..725bfe8295ed9 100644 --- a/aptos-move/aptos-gas-meter/src/meter.rs +++ b/aptos-move/aptos-gas-meter/src/meter.rs @@ -554,4 +554,10 @@ where .charge_execution(MIN_TRANSACTION_GAS_UNITS + INTRINSIC_GAS_PER_BYTE * excess) .map_err(|e| e.finish(Location::Undefined)) } + + fn charge_keyless(&mut self) -> VMResult<()> { + self.algebra + .charge_execution(KEYLESS_BASE_COST) + .map_err(|e| e.finish(Location::Undefined)) + } } diff --git a/aptos-move/aptos-gas-meter/src/traits.rs b/aptos-move/aptos-gas-meter/src/traits.rs index 66b3b608f755b..13efaa69f2f43 100644 --- a/aptos-move/aptos-gas-meter/src/traits.rs +++ b/aptos-move/aptos-gas-meter/src/traits.rs @@ -110,9 +110,14 @@ pub trait AptosGasMeter: MoveGasMeter { /// Charges an intrinsic cost for executing the transaction. /// /// The cost stays constant for transactions below a certain size, but will grow proportionally - /// for bigger ones. + /// for bigger ones. THe multiplier can be used to increase the unit cost for exceptional + /// transactions like keyless. fn charge_intrinsic_gas_for_transaction(&mut self, txn_size: NumBytes) -> VMResult<()>; + /// Charges an additional cost for keyless transactions to compensate for the + /// expensive computation required. + fn charge_keyless(&mut self) -> VMResult<()>; + /// Charges IO gas for the transaction itself. fn charge_io_gas_for_transaction(&mut self, txn_size: NumBytes) -> VMResult<()>; diff --git a/aptos-move/aptos-gas-profiling/src/erased.rs b/aptos-move/aptos-gas-profiling/src/erased.rs index b59547af47bea..febdee5874026 100644 --- a/aptos-move/aptos-gas-profiling/src/erased.rs +++ b/aptos-move/aptos-gas-profiling/src/erased.rs @@ -217,6 +217,8 @@ impl ExecutionAndIOCosts { nodes.push(Node::new("intrinsic", self.intrinsic_cost)); + nodes.push(Node::new("keyless", self.keyless_cost)); + if !self.dependencies.is_empty() { let deps = Node::new_with_children( "dependencies", diff --git a/aptos-move/aptos-gas-profiling/src/flamegraph.rs b/aptos-move/aptos-gas-profiling/src/flamegraph.rs index c7e3fb6c7e99c..1c08054d73a2c 100644 --- a/aptos-move/aptos-gas-profiling/src/flamegraph.rs +++ b/aptos-move/aptos-gas-profiling/src/flamegraph.rs @@ -101,6 +101,8 @@ impl ExecutionAndIOCosts { lines.push("intrinsic", self.intrinsic_cost); + lines.push("keyless", self.keyless_cost); + let mut path = vec![]; struct Rec<'a> { diff --git a/aptos-move/aptos-gas-profiling/src/log.rs b/aptos-move/aptos-gas-profiling/src/log.rs index f3f4c585d64b7..ae7e36f37e82b 100644 --- a/aptos-move/aptos-gas-profiling/src/log.rs +++ b/aptos-move/aptos-gas-profiling/src/log.rs @@ -117,6 +117,7 @@ pub struct ExecutionAndIOCosts { pub total: InternalGas, pub intrinsic_cost: InternalGas, + pub keyless_cost: InternalGas, pub dependencies: Vec, pub call_graph: CallFrame, pub transaction_transient: Option, @@ -238,6 +239,7 @@ impl ExecutionAndIOCosts { let mut total = InternalGas::zero(); total += self.intrinsic_cost; + total += self.keyless_cost; for dep in &self.dependencies { total += dep.cost; diff --git a/aptos-move/aptos-gas-profiling/src/profiler.rs b/aptos-move/aptos-gas-profiling/src/profiler.rs index 1136f831d47bd..89ba8fa72e0b5 100644 --- a/aptos-move/aptos-gas-profiling/src/profiler.rs +++ b/aptos-move/aptos-gas-profiling/src/profiler.rs @@ -34,6 +34,7 @@ pub struct GasProfiler { base: G, intrinsic_cost: Option, + keyless_cost: Option, dependencies: Vec, frames: Vec, transaction_transient: Option, @@ -90,6 +91,7 @@ impl GasProfiler { base, intrinsic_cost: None, + keyless_cost: None, dependencies: vec![], frames: vec![CallFrame::new_script()], transaction_transient: None, @@ -109,6 +111,7 @@ impl GasProfiler { base, intrinsic_cost: None, + keyless_cost: None, dependencies: vec![], frames: vec![CallFrame::new_function(module_id, func_name, ty_args)], transaction_transient: None, @@ -650,6 +653,14 @@ where res } + + fn charge_keyless(&mut self) -> VMResult<()> { + let (_cost, res) = self.delegate_charge(|base| base.charge_keyless()); + + // TODO: add keyless + + res + } } impl GasProfiler @@ -667,6 +678,7 @@ where gas_scaling_factor: self.base.gas_unit_scaling_factor(), total: self.algebra().execution_gas_used() + self.algebra().io_gas_used(), intrinsic_cost: self.intrinsic_cost.unwrap_or_else(|| 0.into()), + keyless_cost: self.keyless_cost.unwrap_or_else(|| 0.into()), dependencies: self.dependencies, call_graph: self.frames.pop().expect("frame must exist"), transaction_transient: self.transaction_transient, diff --git a/aptos-move/aptos-gas-profiling/src/report.rs b/aptos-move/aptos-gas-profiling/src/report.rs index 60ccf1934c97b..fc2aa9c25c3b1 100644 --- a/aptos-move/aptos-gas-profiling/src/report.rs +++ b/aptos-move/aptos-gas-profiling/src/report.rs @@ -109,6 +109,20 @@ impl TransactionGasLog { data.insert("intrinsic-percentage".to_string(), json!(percentage)); } + // Keyless cost + if !self.exec_io.keyless_cost.is_zero() { + let cost_scaled = format!( + "{:.8}", + (u64::from(self.exec_io.keyless_cost) as f64 / scaling_factor) + ); + let percentage = format!( + "{:.2}%", + u64::from(self.exec_io.keyless_cost) as f64 / total_exec_io * 100.0 + ); + data.insert("keyless".to_string(), json!(cost_scaled)); + data.insert("keyless-percentage".to_string(), json!(percentage)); + } + let mut deps = self.exec_io.dependencies.clone(); deps.sort_by(|lhs, rhs| rhs.cost.cmp(&lhs.cost)); data.insert( diff --git a/aptos-move/aptos-gas-profiling/templates/index.html b/aptos-move/aptos-gas-profiling/templates/index.html index 7b98da7ec0831..683bde34d065b 100644 --- a/aptos-move/aptos-gas-profiling/templates/index.html +++ b/aptos-move/aptos-gas-profiling/templates/index.html @@ -82,6 +82,12 @@

Intrinsic Cost

{{#if intrinsic-percentage}} , {{intrinsic-percentage}} of the total cost for execution & IO. {{/if}} + + {{#if keyless}} +

Keyless Cost

+ {{keyless}} gas units, {{keyless-percentage}} of the total cost for execution & IO. + {{/if}} +

Dependencies

{{#if deps}} @@ -147,60 +153,60 @@

State Reads

{{/if}}

Ledger Writes

Transaction Itself
-
- - - - - {{#with transaction_write}} - - - - - {{/with}} -
Cost in Gas UnitsPercentage
{{cost}}{{percentage}}
+ + + + + + {{#with transaction_write}} + + + + + {{/with}} +
Cost in Gas UnitsPercentage
{{cost}}{{percentage}}
Events
- {{#if event_writes}} - - - - - - - - {{#each event_writes}} - - - - - - - {{/each}} -
Event TypeNumber of HitsCost in Gas UnitsPercentage
{{name}}{{hits}}{{cost}}{{percentage}}
- {{else}} - (No writes to show.) - {{/if}} + {{#if event_writes}} + + + + + + + + {{#each event_writes}} + + + + + + + {{/each}} +
Event TypeNumber of HitsCost in Gas UnitsPercentage
{{name}}{{hits}}{{cost}}{{percentage}}
+ {{else}} + (No writes to show.) + {{/if}}
State Write Ops
- {{#if writes}} - - - - - - - - {{#each writes}} - - - - - - - {{/each}} -
Resource NameNumber of HitsCost in Gas UnitsPercentage
{{name}}{{hits}}{{cost}}{{percentage}}
- {{else}} - (No writes to show.) - {{/if}} + {{#if writes}} + + + + + + + + {{#each writes}} + + + + + + + {{/each}} +
Resource NameNumber of HitsCost in Gas UnitsPercentage
{{name}}{{hits}}{{cost}}{{percentage}}
+ {{else}} + (No writes to show.) + {{/if}}

Storage

The storage fees cover the extended-term storage of states and events and are assessed at a fixed price in APT. diff --git a/aptos-move/aptos-gas-schedule/src/gas_schedule/transaction.rs b/aptos-move/aptos-gas-schedule/src/gas_schedule/transaction.rs index fe5d9c6b44594..ec04c0b8d6d7d 100644 --- a/aptos-move/aptos-gas-schedule/src/gas_schedule/transaction.rs +++ b/aptos-move/aptos-gas-schedule/src/gas_schedule/transaction.rs @@ -22,6 +22,7 @@ crate::gas_schedule::macros::define_gas_parameters!( [ // The flat minimum amount of gas required for any transaction. // Charged at the start of execution. + // It is variable to charge more for more expensive authenticators, e.g., keyless [ min_transaction_gas_units: InternalGas, "min_transaction_gas_units", @@ -230,6 +231,11 @@ crate::gas_schedule::macros::define_gas_parameters!( { 15.. => "max_total_dependency_size" }, 1024 * 1024 * 12 / 10, // 1.2 MB ], + [ + keyless_base_cost: InternalGas, + { 17.. => "keyless.base" }, + 414_000_000, + ] ] ); diff --git a/aptos-move/aptos-gas-schedule/src/ver.rs b/aptos-move/aptos-gas-schedule/src/ver.rs index 846ed6190b243..a7a36d3de470e 100644 --- a/aptos-move/aptos-gas-schedule/src/ver.rs +++ b/aptos-move/aptos-gas-schedule/src/ver.rs @@ -8,6 +8,8 @@ /// - Changing how gas is calculated in any way /// /// Change log: +/// - V17 +/// - Gas for keyless /// - V16 /// - IO Gas for the transaction itself and events in the transaction output /// - V15 @@ -55,4 +57,4 @@ /// global operations. /// - V1 /// - TBA -pub const LATEST_GAS_FEATURE_VERSION: u64 = 16; +pub const LATEST_GAS_FEATURE_VERSION: u64 = 17; diff --git a/aptos-move/aptos-memory-usage-tracker/src/lib.rs b/aptos-move/aptos-memory-usage-tracker/src/lib.rs index 702559e8ac23b..0db7ca683a008 100644 --- a/aptos-move/aptos-memory-usage-tracker/src/lib.rs +++ b/aptos-move/aptos-memory-usage-tracker/src/lib.rs @@ -489,5 +489,7 @@ where ) -> PartialVMResult<()>; fn charge_intrinsic_gas_for_transaction(&mut self, txn_size: NumBytes) -> VMResult<()>; + + fn charge_keyless(&mut self) -> VMResult<()>; } } diff --git a/aptos-move/aptos-vm/src/aptos_vm.rs b/aptos-move/aptos-vm/src/aptos_vm.rs index c7ced543850b4..0835d4f01440e 100644 --- a/aptos-move/aptos-vm/src/aptos_vm.rs +++ b/aptos-move/aptos-vm/src/aptos_vm.rs @@ -817,6 +817,9 @@ impl AptosVM { }); gas_meter.charge_intrinsic_gas_for_transaction(txn_data.transaction_size())?; + if txn_data.is_keyless() { + gas_meter.charge_keyless()?; + } match payload { TransactionPayload::Script(script) => { @@ -1001,6 +1004,9 @@ impl AptosVM { }); gas_meter.charge_intrinsic_gas_for_transaction(txn_data.transaction_size())?; + if txn_data.is_keyless() { + gas_meter.charge_keyless()?; + } // Step 1: Obtain the payload. If any errors happen here, the entire transaction should fail let invariant_violation_error = || { @@ -1568,6 +1574,7 @@ impl AptosVM { keyless_validation::validate_authenticators( &authenticators, self.features(), + self.gas_feature_version, resolver, )?; } diff --git a/aptos-move/aptos-vm/src/gas.rs b/aptos-move/aptos-vm/src/gas.rs index 0c0ef735816b9..e1496a14523e5 100644 --- a/aptos-move/aptos-vm/src/gas.rs +++ b/aptos-move/aptos-vm/src/gas.rs @@ -2,9 +2,10 @@ // SPDX-License-Identifier: Apache-2.0 use crate::{move_vm_ext::AptosMoveResolver, transaction_metadata::TransactionMetadata}; -use aptos_gas_algebra::GasExpression; +use aptos_gas_algebra::{Gas, GasExpression, InternalGas}; use aptos_gas_schedule::{ - AptosGasParameters, FromOnChainGasSchedule, MiscGasParameters, NativeGasParameters, + gas_params::txn::KEYLESS_BASE_COST, AptosGasParameters, FromOnChainGasSchedule, + MiscGasParameters, NativeGasParameters, }; use aptos_logger::{enabled, Level}; use aptos_types::on_chain_config::{ @@ -186,17 +187,22 @@ pub(crate) fn check_gas( // The submitted transactions max gas units needs to be at least enough to cover the // intrinsic cost of the transaction as calculated against the size of the // underlying `RawTransaction`. + let keyless = if txn_metadata.is_keyless() { + KEYLESS_BASE_COST.evaluate(gas_feature_version, &gas_params.vm) + } else { + InternalGas::zero() + }; let intrinsic_gas = txn_gas_params .calculate_intrinsic_gas(raw_bytes_len) - .evaluate(gas_feature_version, &gas_params.vm) - .to_unit_round_up_with_params(txn_gas_params); + .evaluate(gas_feature_version, &gas_params.vm); + let total_rounded: Gas = (intrinsic_gas + keyless).to_unit_round_up_with_params(txn_gas_params); - if txn_metadata.max_gas_amount() < intrinsic_gas { + if txn_metadata.max_gas_amount() < total_rounded { speculative_warn!( log_context, format!( "[VM] Gas unit error; min {}, submitted {}", - intrinsic_gas, + total_rounded, txn_metadata.max_gas_amount() ), ); diff --git a/aptos-move/aptos-vm/src/keyless_validation.rs b/aptos-move/aptos-vm/src/keyless_validation.rs index 51d5626f23738..ed67a42a664f1 100644 --- a/aptos-move/aptos-vm/src/keyless_validation.rs +++ b/aptos-move/aptos-vm/src/keyless_validation.rs @@ -121,23 +121,24 @@ fn get_jwk_for_authenticator( pub(crate) fn validate_authenticators( authenticators: &Vec<(KeylessPublicKey, KeylessSignature)>, features: &Features, + gas_feature_version: u64, resolver: &impl AptosMoveResolver, ) -> Result<(), VMStatus> { for (_, sig) in authenticators { // Feature-gating for keyless TXNs (whether ZK or ZKless, whether passkey-based or not) if matches!(sig.cert, EphemeralCertificate::ZeroKnowledgeSig { .. }) - && !features.is_zk_keyless_enabled() + && !(features.is_zk_keyless_enabled() && gas_feature_version >= 17) { return Err(VMStatus::error(StatusCode::FEATURE_UNDER_GATING, None)); } if matches!(sig.cert, EphemeralCertificate::OpenIdSig { .. }) - && !features.is_zkless_keyless_enabled() + && !(features.is_zkless_keyless_enabled() && gas_feature_version >= 17) { return Err(VMStatus::error(StatusCode::FEATURE_UNDER_GATING, None)); } if matches!(sig.ephemeral_signature, EphemeralSignature::WebAuthn { .. }) - && !features.is_keyless_with_passkeys_enabled() + && !(features.is_keyless_with_passkeys_enabled() && gas_feature_version >= 17) { return Err(VMStatus::error(StatusCode::FEATURE_UNDER_GATING, None)); } diff --git a/aptos-move/aptos-vm/src/transaction_metadata.rs b/aptos-move/aptos-vm/src/transaction_metadata.rs index e6533b74607a1..9b800eb3a9830 100644 --- a/aptos-move/aptos-vm/src/transaction_metadata.rs +++ b/aptos-move/aptos-vm/src/transaction_metadata.rs @@ -26,6 +26,7 @@ pub struct TransactionMetadata { pub script_hash: Vec, pub script_size: NumBytes, pub required_deposit: Option, + pub is_keyless: bool, } impl TransactionMetadata { @@ -65,6 +66,9 @@ impl TransactionMetadata { _ => NumBytes::zero(), }, required_deposit: None, + is_keyless: aptos_types::keyless::get_authenticators(txn) + .map(|res| !res.is_empty()) + .unwrap_or(false), } } @@ -125,4 +129,8 @@ impl TransactionMetadata { pub fn set_required_deposit(&mut self, required_deposit: Option) { self.required_deposit = required_deposit; } + + pub fn is_keyless(&self) -> bool { + self.is_keyless + } } diff --git a/aptos-move/e2e-testsuite/src/tests/verify_txn.rs b/aptos-move/e2e-testsuite/src/tests/verify_txn.rs index 9a7b2f54183a1..bbf2f2d59b4a3 100644 --- a/aptos-move/e2e-testsuite/src/tests/verify_txn.rs +++ b/aptos-move/e2e-testsuite/src/tests/verify_txn.rs @@ -304,9 +304,8 @@ fn verify_simple_payment() { // Test for a max_gas_amount that is insufficient to pay the minimum fee. // Find the minimum transaction gas units and subtract 1. - let mut gas_limit: Gas = txn_gas_params - .min_transaction_gas_units - .to_unit_round_up_with_params(&txn_gas_params); + let mut gas_limit: Gas = + (txn_gas_params.min_transaction_gas_units).to_unit_round_up_with_params(&txn_gas_params); if gas_limit > 0.into() { gas_limit = gas_limit.checked_sub(1.into()).unwrap();