// Copyright (c) The Libra Core Contributors
// SPDX-License-Identifier: Apache-2.0
use crate::{
block_processor::execute_block,
code_cache::{
module_adapter::ModuleFetcherImpl,
module_cache::{BlockModuleCache, VMModuleCache},
script_cache::ScriptCache,
},
counters::report_verification_status,
data_cache::BlockDataCache,
loaded_data::loaded_module::LoadedModule,
process_txn::{validate::ValidationMode, ProcessTransaction},
};
use solana_libra_config::config::{VMConfig, VMPublishingOption};
use solana_libra_logger::prelude::*;
use solana_libra_state_view::StateView;
use solana_libra_types::{
transaction::{SignedTransaction, TransactionOutput},
vm_error::{StatusCode, VMStatus},
};
use solana_libra_vm_cache_map::Arena;
/// An instantiation of the MoveVM.
/// `code_cache` is the top level module cache that holds loaded published modules.
/// `script_cache` is the cache that stores all the scripts that have previously been invoked.
/// `publishing_option` is the publishing option that is set. This can be one of either:
/// * Locked, with a whitelist of scripts that the VM is allowed to execute. For scripts that aren't
/// in the whitelist, the VM will just reject it in `verify_transaction`.
/// * Custom scripts, which will allow arbitrary valid scripts, but no module publishing
/// * Open script and module publishing
pub struct VMRuntime<'alloc> {
code_cache: VMModuleCache<'alloc>,
script_cache: ScriptCache<'alloc>,
publishing_option: VMPublishingOption,
}
impl<'alloc> VMRuntime<'alloc> {
/// Create a new VM instance with an Arena allocator to store the modules and a `config` that
/// contains the whitelist that this VM is allowed to execute.
pub fn new(allocator: &'alloc Arena<LoadedModule>, config: &VMConfig) -> Self {
VMRuntime {
code_cache: VMModuleCache::new(allocator),
script_cache: ScriptCache::new(allocator),
publishing_option: config.publishing_options.clone(),
}
}
/// Determine if a transaction is valid. Will return `None` if the transaction is accepted,
/// `Some(Err)` if the VM rejects it, with `Err` as an error code. We verify the following
/// items:
/// 1. The signature on the `SignedTransaction` matches the public key included in the
/// transaction
/// 2. The script to be executed is in the whitelist.
/// 3. Invokes `LibraAccount.prologue`, which checks properties such as the transaction has the
/// right sequence number and the sender has enough balance to pay for the gas. 4.
/// Transaction arguments matches the main function's type signature. 5. Script and modules
/// in the transaction pass the bytecode static verifier.
///
/// Note: In the future. we may defer these checks to a later pass, as all the scripts we will
/// execute are pre-verified scripts. And bytecode verification is expensive. Thus whether we
/// want to perform this check here remains unknown.
pub fn verify_transaction(
&self,
txn: SignedTransaction,
data_view: &dyn StateView,
) -> Option<VMStatus> {
trace!("[VM] Verify transaction: {:?}", txn);
// Treat a transaction as a single block.
let module_cache =
BlockModuleCache::new(&self.code_cache, ModuleFetcherImpl::new(data_view));
let data_cache = BlockDataCache::new(data_view);
let arena = Arena::new();
let signature_verified_txn = match txn.check_signature() {
Ok(t) => t,
Err(_) => return Some(VMStatus::new(StatusCode::INVALID_SIGNATURE)),
};
let process_txn =
ProcessTransaction::new(signature_verified_txn, module_cache, &data_cache, &arena);
let mode = if data_view.is_genesis() {
ValidationMode::Genesis
} else {
ValidationMode::Validating
};
let validated_txn = match process_txn.validate(mode, &self.publishing_option) {
Ok(validated_txn) => validated_txn,
Err(vm_status) => {
let res = Some(vm_status);
report_verification_status(&res);
return res;
}
};
let res = match validated_txn.verify(&self.script_cache) {
Ok(_) => None,
Err(vm_status) => Some(vm_status),
};
report_verification_status(&res);
res
}
/// Execute a block of transactions. The output vector will have the exact same length as the
/// input vector. The discarded transactions will be marked as `TransactionStatus::Discard` and
/// have an empty writeset. Also the data view is immutable, and also does not have interior
/// mutability. writes to be applied to the data view are encoded in the write set part of a
/// transaction output.
pub fn execute_block_transactions(
&self,
txn_block: Vec<SignedTransaction>,
data_view: &dyn StateView,
) -> Vec<TransactionOutput> {
execute_block(
txn_block,
&self.code_cache,
&self.script_cache,
data_view,
&self.publishing_option,
)
}
}