use crate::{
code_cache::{
module_cache::{ModuleCache, TransactionModuleCache},
script_cache::ScriptCache,
},
loaded_data::function::{FunctionRef, FunctionReference},
process_txn::{execute::ExecutedTransaction, validate::ValidatedTransaction},
txn_executor::TransactionExecutor,
};
use solana_libra_bytecode_verifier::{VerifiedModule, VerifiedScript};
use solana_libra_logger::prelude::*;
use solana_libra_types::{
account_address::AccountAddress,
transaction::{
Module, Program, Script, SignatureCheckedTransaction, TransactionArgument,
TransactionPayload,
},
vm_error::{StatusCode, VMStatus},
};
use solana_libra_vm::{
access::ModuleAccess,
errors::{verification_error, VMResult},
file_format::{CompiledModule, CompiledScript, FunctionSignature, SignatureToken},
IndexKind,
};
pub struct VerifiedTransaction<'alloc, 'txn, P>
where
'alloc: 'txn,
P: ModuleCache<'alloc>,
{
txn: SignatureCheckedTransaction,
#[allow(dead_code)]
txn_state: Option<VerifiedTransactionState<'alloc, 'txn, P>>,
}
impl<'alloc, 'txn, P> VerifiedTransaction<'alloc, 'txn, P>
where
'alloc: 'txn,
P: ModuleCache<'alloc>,
{
pub(super) fn new(
mut validated_txn: ValidatedTransaction<'alloc, 'txn, P>,
script_cache: &'txn ScriptCache<'alloc>,
) -> Result<Self, VMStatus> {
let txn_state = validated_txn.take_state();
let txn = validated_txn.as_inner();
let txn_state = match txn.payload() {
TransactionPayload::Program(program) => {
let txn_state = txn_state
.expect("program-based transactions should always have associated state");
let (main, modules) = Self::verify_program(&txn.sender(), program, script_cache)?;
Some(VerifiedTransactionState {
txn_executor: txn_state.txn_executor,
verified_txn: VerTxn::Program(VerProgram { main, modules }),
})
}
TransactionPayload::WriteSet(_write_set) => {
None
}
TransactionPayload::Module(module) => {
let txn_state = txn_state
.expect("module-based transactions should always have associated state");
let verified_module = Self::verify_module(&txn.sender(), module)?;
Some(VerifiedTransactionState {
txn_executor: txn_state.txn_executor,
verified_txn: VerTxn::Module(Box::new(verified_module)),
})
}
TransactionPayload::Script(script) => {
let txn_state = txn_state
.expect("script-based transactions should always have associated state");
let main = Self::verify_script(script, script_cache)?;
Some(VerifiedTransactionState {
txn_executor: txn_state.txn_executor,
verified_txn: VerTxn::Script(main),
})
}
};
Ok(Self {
txn: validated_txn.into_inner(),
txn_state,
})
}
fn verify_program(
sender_address: &AccountAddress,
program: &Program,
script_cache: &'txn ScriptCache<'alloc>,
) -> VMResult<(FunctionRef<'alloc>, Vec<VerifiedModule>)> {
let main = match script_cache.cache_script(&program.code()) {
Ok(main) => main,
Err(err) => return Err(err),
};
if !verify_actuals(main.signature(), program.args()) {
return Err(VMStatus::new(StatusCode::TYPE_MISMATCH)
.with_message("Actual Type Mismatch".to_string()));
}
let modules: Vec<CompiledModule> = match program
.modules()
.iter()
.map(|module_blob| CompiledModule::deserialize(&module_blob))
.collect()
{
Ok(modules) => modules,
Err(err) => {
warn!("[VM] module deserialization failed {:?}", err);
return Err(err);
}
};
let modules = match static_verify_modules(sender_address, modules) {
Ok(modules) => modules,
Err(mut statuses) => {
warn!("[VM] bytecode verifier returned errors");
let err = if statuses.is_empty() {
VMStatus::new(StatusCode::VERIFIER_INVARIANT_VIOLATION)
} else {
statuses.remove(0)
};
return Err(err);
}
};
Ok((main, modules))
}
fn verify_module(
sender_address: &AccountAddress,
module: &Module,
) -> Result<VerifiedModule, VMStatus> {
let compiled_module = match CompiledModule::deserialize(module.code()) {
Ok(module) => module,
Err(err) => {
warn!("[VM] module deserialization failed {:?}", err);
return Err(err);
}
};
if compiled_module.address() != sender_address {
return Err(verification_error(
IndexKind::AddressPool,
CompiledModule::IMPLEMENTED_MODULE_INDEX as usize,
StatusCode::MODULE_ADDRESS_DOES_NOT_MATCH_SENDER,
));
}
match VerifiedModule::new(compiled_module) {
Ok(ver_module) => Ok(ver_module),
Err((_, mut errors)) => {
let err = if errors.is_empty() {
VMStatus::new(StatusCode::VERIFIER_INVARIANT_VIOLATION)
} else {
errors.remove(0)
};
Err(err)
}
}
}
fn verify_script(
script: &Script,
script_cache: &'txn ScriptCache<'alloc>,
) -> Result<FunctionRef<'alloc>, VMStatus> {
let main = script_cache.cache_script(&script.code())?;
if !verify_actuals(main.signature(), script.args()) {
return Err(VMStatus::new(StatusCode::TYPE_MISMATCH)
.with_message("Actual Type Mismatch".to_string()));
}
Ok(main)
}
pub fn execute(self) -> ExecutedTransaction {
ExecutedTransaction::new(self)
}
pub(super) fn take_state(&mut self) -> Option<VerifiedTransactionState<'alloc, 'txn, P>> {
self.txn_state.take()
}
#[allow(dead_code)]
pub fn as_inner(&self) -> &SignatureCheckedTransaction {
&self.txn
}
pub fn into_inner(self) -> SignatureCheckedTransaction {
self.txn
}
}
#[allow(dead_code)]
pub(super) struct VerifiedTransactionState<'alloc, 'txn, P>
where
'alloc: 'txn,
P: ModuleCache<'alloc>,
{
pub(super) txn_executor:
TransactionExecutor<'txn, 'txn, TransactionModuleCache<'alloc, 'txn, P>>,
pub(super) verified_txn: VerTxn<'alloc>,
}
pub struct VerProgram<'alloc> {
pub(super) main: FunctionRef<'alloc>,
pub(super) modules: Vec<VerifiedModule>,
}
pub enum VerTxn<'alloc> {
Program(VerProgram<'alloc>),
Script(FunctionRef<'alloc>),
Module(Box<VerifiedModule>),
}
fn static_verify_modules(
sender_address: &AccountAddress,
modules: Vec<CompiledModule>,
) -> Result<Vec<VerifiedModule>, Vec<VMStatus>> {
let mut statuses: Vec<Box<dyn Iterator<Item = VMStatus>>> = vec![];
let modules_len = modules.len();
let mut modules_out = vec![];
for module in modules.into_iter() {
let self_error = if module.address() != sender_address {
Some(verification_error(
IndexKind::AddressPool,
CompiledModule::IMPLEMENTED_MODULE_INDEX as usize,
StatusCode::MODULE_ADDRESS_DOES_NOT_MATCH_SENDER,
))
} else {
None
};
let (module, mut errors) = match VerifiedModule::new(module) {
Ok(module) => (Some(module), vec![]),
Err((_, errors)) => (None, errors),
};
if let Some(error) = self_error {
assume!(errors.len() < usize::max_value());
errors.push(error);
}
if errors.is_empty() {
assume!(modules_out.len() < usize::max_value());
modules_out.push(module.expect("empty errors => module should verify"));
} else {
statuses.push(Box::new(errors.into_iter()));
}
}
let statuses: Vec<_> = statuses.into_iter().flatten().collect();
if statuses.is_empty() {
assert_eq!(modules_out.len(), modules_len);
Ok(modules_out)
} else {
Err(statuses)
}
}
pub fn static_verify_program(
sender_address: &AccountAddress,
script: CompiledScript,
modules: Vec<CompiledModule>,
) -> Result<(VerifiedScript, Vec<VerifiedModule>), Vec<VMStatus>> {
let mut statuses: Vec<VMStatus> = vec![];
let script = match VerifiedScript::new(script) {
Ok(script) => Some(script),
Err((_, errors)) => {
statuses.extend(errors.into_iter());
None
}
};
let modules = match static_verify_modules(sender_address, modules) {
Ok(modules) => Some(modules),
Err(module_statuses) => {
statuses.extend(module_statuses);
None
}
};
if statuses.is_empty() {
Ok((
script.expect("Ok case => script should verify"),
modules.expect("Ok case => modules should verify"),
))
} else {
Err(statuses)
}
}
fn verify_actuals(signature: &FunctionSignature, args: &[TransactionArgument]) -> bool {
if signature.arg_types.len() != args.len() {
warn!(
"[VM] different argument length: actuals {}, formals {}",
args.len(),
signature.arg_types.len()
);
return false;
}
for (ty, arg) in signature.arg_types.iter().zip(args.iter()) {
match (ty, arg) {
(SignatureToken::U64, TransactionArgument::U64(_)) => (),
(SignatureToken::Address, TransactionArgument::Address(_)) => (),
(SignatureToken::ByteArray, TransactionArgument::ByteArray(_)) => (),
(SignatureToken::String, TransactionArgument::String(_)) => (),
_ => {
warn!(
"[VM] different argument type: formal {:?}, actual {:?}",
ty, arg
);
return false;
}
}
}
true
}