use crate::{
access_path_cache::AccessPathCache,
counters::*,
data_cache::RemoteStorage,
delta_ext::TransactionOutputExt,
errors::{convert_epilogue_error, convert_prologue_error, expect_only_successful_execution},
logging::AdapterLogSchema,
move_vm_ext::{MoveResolverExt, MoveVmExt, SessionExt, SessionId},
transaction_metadata::TransactionMetadata,
};
use aptos_crypto::HashValue;
use aptos_gas::{AptosGasParameters, FromOnChainGasSchedule, NativeGasParameters};
use aptos_logger::prelude::*;
use aptos_state_view::StateView;
use aptos_types::{
account_config::{ChainSpecificAccountInfo, APTOS_CHAIN_INFO, CORE_CODE_ADDRESS},
on_chain_config::{GasSchedule, OnChainConfig, Version, APTOS_VERSION_3},
transaction::{ExecutionStatus, TransactionOutput, TransactionStatus},
vm_status::{StatusCode, VMStatus},
};
use fail::fail_point;
use move_deps::{
move_binary_format::{errors::VMResult, CompiledModule},
move_core_types::{
gas_schedule::GasAlgebra,
language_storage::ModuleId,
move_resource::MoveStructType,
resolver::ResourceResolver,
value::{serialize_values, MoveValue},
},
move_vm_runtime::logging::expect_no_verification_errors,
move_vm_types::gas::UnmeteredGasMeter,
};
use std::sync::Arc;
#[derive(Clone)]
pub struct AptosVMImpl {
move_vm: Arc<MoveVmExt>,
gas_params: Option<AptosGasParameters>,
version: Option<Version>,
chain_account_info: Option<ChainSpecificAccountInfo>,
}
impl AptosVMImpl {
#[allow(clippy::new_without_default)]
pub fn new<S: StateView>(state: &S) -> Self {
let storage = RemoteStorage::new(state);
let gas_params = GasSchedule::fetch_config(&storage).and_then(|gas_schedule| {
let gas_schedule = gas_schedule.to_btree_map();
AptosGasParameters::from_on_chain_gas_schedule(&gas_schedule)
});
let native_gas_params = match &gas_params {
Some(gas_params) => gas_params.natives.clone(),
None => NativeGasParameters::zeros(),
};
let inner = MoveVmExt::new(native_gas_params)
.expect("should be able to create Move VM; check if there are duplicated natives");
let mut vm = Self {
move_vm: Arc::new(inner),
gas_params,
version: None,
chain_account_info: None,
};
vm.version = Version::fetch_config(&storage);
vm.chain_account_info = Self::get_chain_specific_account_info(&RemoteStorage::new(state));
vm
}
pub fn init_with_config(version: Version, gas_schedule: GasSchedule) -> Self {
let gas_params =
AptosGasParameters::from_on_chain_gas_schedule(&gas_schedule.to_btree_map())
.expect("failed to get gas parameters");
let inner = MoveVmExt::new(gas_params.natives.clone())
.expect("should be able to create Move VM; check if there are duplicated natives");
Self {
move_vm: Arc::new(inner),
gas_params: Some(gas_params),
version: Some(version),
chain_account_info: None,
}
}
pub fn internals(&self) -> AptosVMInternals {
AptosVMInternals(self)
}
pub(crate) fn chain_info(&self) -> &ChainSpecificAccountInfo {
self.chain_account_info
.as_ref()
.unwrap_or(&APTOS_CHAIN_INFO)
}
fn get_chain_specific_account_info<S: ResourceResolver>(
remote_cache: &S,
) -> Option<ChainSpecificAccountInfo> {
match remote_cache
.get_resource(&CORE_CODE_ADDRESS, &ChainSpecificAccountInfo::struct_tag())
.ok()?
{
Some(blob) => bcs::from_bytes::<ChainSpecificAccountInfo>(&blob).ok(),
_ => None,
}
}
pub fn get_gas_parameters(
&self,
log_context: &AdapterLogSchema,
) -> Result<&AptosGasParameters, VMStatus> {
self.gas_params.as_ref().ok_or_else(|| {
log_context.alert();
error!(*log_context, "VM Startup Failed. Gas Parameters Not Found");
VMStatus::Error(StatusCode::VM_STARTUP_FAILURE)
})
}
pub fn get_version(&self) -> Result<Version, VMStatus> {
self.version.clone().ok_or_else(|| {
CRITICAL_ERRORS.inc();
error!("VM Startup Failed. Version Not Found");
VMStatus::Error(StatusCode::VM_STARTUP_FAILURE)
})
}
pub fn check_gas(
&self,
txn_data: &TransactionMetadata,
log_context: &AdapterLogSchema,
) -> Result<(), VMStatus> {
let gas_constants = &self.get_gas_parameters(log_context)?.txn;
let raw_bytes_len = txn_data.transaction_size;
if txn_data.transaction_size > gas_constants.max_transaction_size_in_bytes {
warn!(
*log_context,
"[VM] Transaction size too big {} (max {})",
raw_bytes_len,
gas_constants.max_transaction_size_in_bytes,
);
return Err(VMStatus::Error(StatusCode::EXCEEDED_MAX_TRANSACTION_SIZE));
}
assume!(raw_bytes_len <= gas_constants.max_transaction_size_in_bytes);
if txn_data.max_gas_amount() > gas_constants.maximum_number_of_gas_units {
warn!(
*log_context,
"[VM] Gas unit error; max {}, submitted {}",
gas_constants.maximum_number_of_gas_units,
txn_data.max_gas_amount(),
);
return Err(VMStatus::Error(
StatusCode::MAX_GAS_UNITS_EXCEEDS_MAX_GAS_UNITS_BOUND,
));
}
let min_txn_fee = {
let min_transaction_fee = gas_constants.min_transaction_gas_units;
if raw_bytes_len > gas_constants.large_transaction_cutoff {
let excess = raw_bytes_len - gas_constants.large_transaction_cutoff;
min_transaction_fee + gas_constants.intrinsic_gas_per_byte * excess
} else {
min_transaction_fee
}
};
let min_txn_fee = gas_constants.to_external_units(min_txn_fee);
if txn_data.max_gas_amount() < min_txn_fee {
warn!(
*log_context,
"[VM] Gas unit error; min {}, submitted {}",
min_txn_fee,
txn_data.max_gas_amount(),
);
return Err(VMStatus::Error(
StatusCode::MAX_GAS_UNITS_BELOW_MIN_TRANSACTION_GAS_UNITS,
));
}
#[allow(clippy::absurd_extreme_comparisons)]
let below_min_bound =
txn_data.gas_unit_price().get() < gas_constants.min_price_per_gas_unit;
if below_min_bound {
warn!(
*log_context,
"[VM] Gas unit error; min {}, submitted {}",
gas_constants.min_price_per_gas_unit,
txn_data.gas_unit_price().get(),
);
return Err(VMStatus::Error(StatusCode::GAS_UNIT_PRICE_BELOW_MIN_BOUND));
}
if txn_data.gas_unit_price().get() > gas_constants.max_price_per_gas_unit {
warn!(
*log_context,
"[VM] Gas unit error; min {}, submitted {}",
gas_constants.max_price_per_gas_unit,
txn_data.gas_unit_price().get(),
);
return Err(VMStatus::Error(StatusCode::GAS_UNIT_PRICE_ABOVE_MAX_BOUND));
}
Ok(())
}
pub(crate) fn run_script_prologue<S: MoveResolverExt>(
&self,
session: &mut SessionExt<S>,
txn_data: &TransactionMetadata,
log_context: &AdapterLogSchema,
) -> Result<(), VMStatus> {
let chain_specific_info = self.chain_info();
let gas_currency = vec![];
let txn_sequence_number = txn_data.sequence_number();
let txn_public_key = txn_data.authentication_key_preimage().to_vec();
let txn_gas_price = txn_data.gas_unit_price().get();
let txn_max_gas_units = txn_data.max_gas_amount();
let txn_expiration_timestamp_secs = txn_data.expiration_timestamp_secs();
let chain_id = txn_data.chain_id();
let mut gas_meter = UnmeteredGasMeter;
let secondary_public_key_hashes: Vec<MoveValue> = txn_data
.secondary_authentication_key_preimages
.iter()
.map(|preimage| MoveValue::vector_u8(HashValue::sha3_256_of(preimage).to_vec()))
.collect();
let args = if self.get_version()? >= APTOS_VERSION_3 && txn_data.is_multi_agent() {
vec![
MoveValue::Signer(txn_data.sender),
MoveValue::U64(txn_sequence_number),
MoveValue::vector_u8(txn_public_key),
MoveValue::vector_address(txn_data.secondary_signers()),
MoveValue::Vector(secondary_public_key_hashes),
MoveValue::U64(txn_gas_price),
MoveValue::U64(txn_max_gas_units),
MoveValue::U64(txn_expiration_timestamp_secs),
MoveValue::U8(chain_id.id()),
]
} else {
vec![
MoveValue::Signer(txn_data.sender),
MoveValue::U64(txn_sequence_number),
MoveValue::vector_u8(txn_public_key),
MoveValue::U64(txn_gas_price),
MoveValue::U64(txn_max_gas_units),
MoveValue::U64(txn_expiration_timestamp_secs),
MoveValue::U8(chain_id.id()),
MoveValue::vector_u8(txn_data.script_hash.clone()),
]
};
let prologue_function_name =
if self.get_version()? >= APTOS_VERSION_3 && txn_data.is_multi_agent() {
&chain_specific_info.multi_agent_prologue_name
} else {
&chain_specific_info.script_prologue_name
};
session
.execute_function_bypass_visibility(
&chain_specific_info.module_id(),
prologue_function_name,
gas_currency,
serialize_values(&args),
&mut gas_meter,
)
.map(|_return_vals| ())
.map_err(expect_no_verification_errors)
.or_else(|err| convert_prologue_error(chain_specific_info, err, log_context))
}
pub(crate) fn run_module_prologue<S: MoveResolverExt>(
&self,
session: &mut SessionExt<S>,
txn_data: &TransactionMetadata,
log_context: &AdapterLogSchema,
) -> Result<(), VMStatus> {
let chain_specific_info = self.chain_info();
let gas_currency = vec![];
let txn_sequence_number = txn_data.sequence_number();
let txn_public_key = txn_data.authentication_key_preimage().to_vec();
let txn_gas_price = txn_data.gas_unit_price().get();
let txn_max_gas_units = txn_data.max_gas_amount();
let txn_expiration_timestamp_secs = txn_data.expiration_timestamp_secs();
let chain_id = txn_data.chain_id();
let mut gas_meter = UnmeteredGasMeter;
session
.execute_function_bypass_visibility(
&chain_specific_info.module_id(),
&chain_specific_info.module_prologue_name,
gas_currency,
serialize_values(&vec![
MoveValue::Signer(txn_data.sender),
MoveValue::U64(txn_sequence_number),
MoveValue::vector_u8(txn_public_key),
MoveValue::U64(txn_gas_price),
MoveValue::U64(txn_max_gas_units),
MoveValue::U64(txn_expiration_timestamp_secs),
MoveValue::U8(chain_id.id()),
]),
&mut gas_meter,
)
.map(|_return_vals| ())
.map_err(expect_no_verification_errors)
.or_else(|err| convert_prologue_error(chain_specific_info, err, log_context))
}
pub(crate) fn run_success_epilogue<S: MoveResolverExt>(
&self,
session: &mut SessionExt<S>,
gas_remaining: u64,
txn_data: &TransactionMetadata,
log_context: &AdapterLogSchema,
) -> Result<(), VMStatus> {
fail_point!("move_adapter::run_success_epilogue", |_| {
Err(VMStatus::Error(
StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR,
))
});
let gas_currency = vec![];
let chain_specific_info = self.chain_info();
let txn_sequence_number = txn_data.sequence_number();
let txn_gas_price = txn_data.gas_unit_price().get();
let txn_max_gas_units = txn_data.max_gas_amount();
session
.execute_function_bypass_visibility(
&chain_specific_info.module_id(),
&chain_specific_info.user_epilogue_name,
gas_currency,
serialize_values(&vec![
MoveValue::Signer(txn_data.sender),
MoveValue::U64(txn_sequence_number),
MoveValue::U64(txn_gas_price),
MoveValue::U64(txn_max_gas_units),
MoveValue::U64(gas_remaining),
]),
&mut UnmeteredGasMeter,
)
.map(|_return_vals| ())
.map_err(expect_no_verification_errors)
.or_else(|err| convert_epilogue_error(chain_specific_info, err, log_context))
}
pub(crate) fn run_failure_epilogue<S: MoveResolverExt>(
&self,
session: &mut SessionExt<S>,
gas_remaining: u64,
txn_data: &TransactionMetadata,
log_context: &AdapterLogSchema,
) -> Result<(), VMStatus> {
let gas_currency = vec![];
let chain_specific_info = self.chain_info();
let txn_sequence_number = txn_data.sequence_number();
let txn_gas_price = txn_data.gas_unit_price().get();
let txn_max_gas_units = txn_data.max_gas_amount();
session
.execute_function_bypass_visibility(
&chain_specific_info.module_id(),
&chain_specific_info.user_epilogue_name,
gas_currency,
serialize_values(&vec![
MoveValue::Signer(txn_data.sender),
MoveValue::U64(txn_sequence_number),
MoveValue::U64(txn_gas_price),
MoveValue::U64(txn_max_gas_units),
MoveValue::U64(gas_remaining),
]),
&mut UnmeteredGasMeter,
)
.map(|_return_vals| ())
.map_err(expect_no_verification_errors)
.or_else(|e| {
expect_only_successful_execution(
e,
chain_specific_info.user_epilogue_name.as_str(),
log_context,
)
})
}
pub(crate) fn run_writeset_prologue<S: MoveResolverExt>(
&self,
session: &mut SessionExt<S>,
txn_data: &TransactionMetadata,
log_context: &AdapterLogSchema,
) -> Result<(), VMStatus> {
let txn_sequence_number = txn_data.sequence_number();
let txn_public_key = txn_data.authentication_key_preimage().to_vec();
let txn_expiration_timestamp_secs = txn_data.expiration_timestamp_secs();
let chain_id = txn_data.chain_id();
let chain_specific_info = self.chain_info();
let mut gas_meter = UnmeteredGasMeter;
session
.execute_function_bypass_visibility(
&chain_specific_info.module_id(),
&chain_specific_info.writeset_prologue_name,
vec![],
serialize_values(&vec![
MoveValue::Signer(txn_data.sender),
MoveValue::U64(txn_sequence_number),
MoveValue::vector_u8(txn_public_key),
MoveValue::U64(txn_expiration_timestamp_secs),
MoveValue::U8(chain_id.id()),
]),
&mut gas_meter,
)
.map(|_return_vals| ())
.map_err(expect_no_verification_errors)
.or_else(|err| convert_prologue_error(chain_specific_info, err, log_context))
}
pub(crate) fn run_writeset_epilogue<S: MoveResolverExt>(
&self,
session: &mut SessionExt<S>,
txn_data: &TransactionMetadata,
should_trigger_reconfiguration: bool,
log_context: &AdapterLogSchema,
) -> Result<(), VMStatus> {
let mut gas_meter = UnmeteredGasMeter;
let chain_specific_info = self.chain_info();
session
.execute_function_bypass_visibility(
&chain_specific_info.module_id(),
&chain_specific_info.writeset_epilogue_name,
vec![],
serialize_values(&vec![
MoveValue::Signer(txn_data.sender),
MoveValue::U64(txn_data.sequence_number),
MoveValue::Bool(should_trigger_reconfiguration),
]),
&mut gas_meter,
)
.map(|_return_vals| ())
.map_err(expect_no_verification_errors)
.or_else(|e| {
expect_only_successful_execution(
e,
chain_specific_info.writeset_epilogue_name.as_str(),
log_context,
)
})
}
pub fn new_session<'r, R: MoveResolverExt>(
&self,
r: &'r R,
session_id: SessionId,
) -> SessionExt<'r, '_, R> {
self.move_vm.new_session(r, session_id)
}
pub fn load_module<'r, R: MoveResolverExt>(
&self,
module_id: &ModuleId,
remote: &'r R,
) -> VMResult<Arc<CompiledModule>> {
self.move_vm.load_module(module_id, remote)
}
}
#[derive(Clone, Copy)]
pub struct AptosVMInternals<'a>(&'a AptosVMImpl);
impl<'a> AptosVMInternals<'a> {
pub fn new(internal: &'a AptosVMImpl) -> Self {
Self(internal)
}
pub fn move_vm(self) -> &'a MoveVmExt {
&self.0.move_vm
}
pub fn gas_params(
self,
log_context: &AdapterLogSchema,
) -> Result<&'a AptosGasParameters, VMStatus> {
self.0.get_gas_parameters(log_context)
}
pub fn version(self) -> Result<Version, VMStatus> {
self.0.get_version()
}
pub fn with_txn_data_cache<T, S: StateView>(
self,
state_view: &S,
f: impl for<'txn, 'r> FnOnce(SessionExt<'txn, 'r, RemoteStorage<S>>) -> T,
session_id: SessionId,
) -> T {
let remote_storage = RemoteStorage::new(state_view);
let session = self.move_vm().new_session(&remote_storage, session_id);
f(session)
}
}
pub(crate) fn get_transaction_output<A: AccessPathCache, S: MoveResolverExt>(
ap_cache: &mut A,
session: SessionExt<S>,
gas_left: u64,
txn_data: &TransactionMetadata,
status: ExecutionStatus,
) -> Result<TransactionOutputExt, VMStatus> {
let gas_used: u64 = txn_data.max_gas_amount() - gas_left;
let session_out = session.finish().map_err(|e| e.into_vm_status())?;
let (delta_change_set, change_set) = session_out.into_change_set_ext(ap_cache)?.into_inner();
let (write_set, events) = change_set.into_inner();
let txn_output =
TransactionOutput::new(write_set, events, gas_used, TransactionStatus::Keep(status));
Ok(TransactionOutputExt::new(delta_change_set, txn_output))
}
#[test]
fn vm_thread_safe() {
fn assert_send<T: Send>() {}
fn assert_sync<T: Sync>() {}
use crate::AptosVM;
assert_send::<AptosVM>();
assert_sync::<AptosVM>();
assert_send::<MoveVmExt>();
assert_sync::<MoveVmExt>();
}