Skip to content

Commit

Permalink
Execution mode for dev-inspect (MystenLabs#6538)
Browse files Browse the repository at this point in the history
* [1/x][dev-inspect] Adds execution mode for a more expressive dry-run

- Introduces dev-inspect transaction type that can call any Move function
- Allows return values from that Move function
- Also provides much more detailed error message information
  • Loading branch information
tnowacki authored Dec 8, 2022
1 parent 434b825 commit bbf1684
Show file tree
Hide file tree
Showing 10 changed files with 346 additions and 231 deletions.
91 changes: 41 additions & 50 deletions crates/sui-adapter/src/adapter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,16 +55,7 @@ use sui_verifier::{
};
use tracing::instrument;

macro_rules! assert_invariant {
($cond:expr, $msg:expr) => {
if !$cond {
return Err(ExecutionError::new_with_source(
ExecutionErrorKind::InvariantViolation,
$msg,
));
}
};
}
use crate::execution_mode::{self, ExecutionMode};

pub fn new_move_vm(natives: NativeFunctionTable) -> Result<MoveVM, SuiError> {
MoveVM::new_with_config(
Expand Down Expand Up @@ -114,6 +105,7 @@ pub fn new_session<
#[allow(clippy::too_many_arguments)]
#[instrument(name = "adapter_execute", level = "trace", skip_all)]
pub fn execute<
Mode: ExecutionMode,
E: Debug,
S: ResourceResolver<Error = E>
+ ModuleResolver<Error = E>
Expand All @@ -129,33 +121,29 @@ pub fn execute<
args: Vec<CallArg>,
gas_status: &mut GasStatus,
ctx: &mut TxContext,
) -> Result<(), ExecutionError> {
let objects = args
.iter()
.filter_map(|arg| match arg {
CallArg::Pure(_) => None,
) -> Result<Mode::ExecutionResult, ExecutionError> {
let mut objects: BTreeMap<ObjectID, &Object> = BTreeMap::new();
for arg in &args {
match arg {
CallArg::Pure(_) => continue,
CallArg::Object(ObjectArg::ImmOrOwnedObject((id, _, _)))
| CallArg::Object(ObjectArg::SharedObject { id, .. }) => {
Some(vec![(*id, state_view.read_object(id)?)])
let obj = state_view.read_object(id);
assert_invariant!(obj.is_some(), format!("Object {} does not exist yet", id));
objects.insert(*id, obj.unwrap());
}
CallArg::ObjVec(vec) => {
if vec.is_empty() {
return None;
CallArg::ObjVec(obj_args) => {
for obj_arg in obj_args {
let (ObjectArg::ImmOrOwnedObject((id, _, _))
| ObjectArg::SharedObject { id, .. }) = obj_arg;
let obj = state_view.read_object(id);
assert_invariant!(obj.is_some(), format!("Object {} does not exist yet", id));
objects.insert(*id, obj.unwrap());
}
Some(
vec.iter()
.filter_map(|obj_arg| match obj_arg {
ObjectArg::ImmOrOwnedObject((id, _, _))
| ObjectArg::SharedObject { id, .. } => {
Some((*id, state_view.read_object(id)?))
}
})
.collect(),
)
}
})
.flatten()
.collect();
}
}

let module = vm.load_module(&module_id, state_view)?;
let is_genesis = ctx.digest() == TransactionDigest::genesis();
let TypeCheckSuccess {
Expand All @@ -165,12 +153,12 @@ pub fn execute<
by_value_objects,
mutable_ref_objects,
has_ctx_arg,
} = resolve_and_type_check(&objects, &module, function, &type_args, args, is_genesis)?;
} = resolve_and_type_check::<Mode>(&objects, &module, function, &type_args, args, is_genesis)?;

if has_ctx_arg {
args.push(ctx.to_vec());
}
execute_internal(
execute_internal::<Mode, _, _>(
vm,
state_view,
&module_id,
Expand All @@ -190,6 +178,7 @@ pub fn execute<
/// call.
#[allow(clippy::too_many_arguments)]
fn execute_internal<
Mode: ExecutionMode,
E: Debug,
S: ResourceResolver<Error = E>
+ ModuleResolver<Error = E>
Expand All @@ -209,7 +198,7 @@ fn execute_internal<
mut mutable_ref_objects: BTreeMap<LocalIndex, ObjectID>,
gas_status: &mut GasStatus, // gas status for the current call operation
ctx: &mut TxContext,
) -> Result<(), ExecutionError> {
) -> Result<Mode::ExecutionResult, ExecutionError> {
let input_objects = object_data
.iter()
.map(|(id, (owner, _))| (*id, (by_value_objects.contains(id), *owner)))
Expand All @@ -222,16 +211,14 @@ fn execute_internal<
.map_err(|e| convert_type_argument_error(idx, e))?;
}
// script visibility checked manually for entry points
let (
SerializedReturnValues {
mut mutable_reference_outputs,
return_values,
},
(change_set, events, mut native_context_extensions),
) = session
let (result, (change_set, events, mut native_context_extensions)) = session
.execute_function_bypass_visibility(module_id, function, type_args, args, gas_status)
.and_then(|ret| Ok((ret, session.finish_with_extensions()?)))?;
assert_invariant!(return_values.is_empty(), "Return values must be empty");
let mode_result = Mode::make_result(&result)?;
let SerializedReturnValues {
mut mutable_reference_outputs,
..
} = result;
let object_runtime: ObjectRuntime = native_context_extensions.remove();
std::mem::drop(native_context_extensions);

Expand Down Expand Up @@ -317,8 +304,7 @@ fn execute_internal<
user_events,
ctx,
)?;

Ok(())
Ok(mode_result)
}

#[instrument(name = "adapter_publish", level = "trace", skip_all)]
Expand Down Expand Up @@ -431,8 +417,7 @@ fn init_modules<
}
args.push(ctx.to_vec());
let has_ctx_arg = true;

execute_internal(
execute_internal::<execution_mode::Normal, _, _>(
vm,
state_view,
&module_id,
Expand Down Expand Up @@ -691,7 +676,7 @@ pub struct TypeCheckSuccess {
/// - Check that the the signature of `function` is well-typed w.r.t `type_args`, `object_args`, and `pure_args`
/// - Return the ID of the resolved module, a vector of BCS encoded arguments to pass to the VM, and a partitioning
/// of the input objects into objects passed by value vs by mutable reference
pub fn resolve_and_type_check(
pub fn resolve_and_type_check<Mode: ExecutionMode>(
objects: &BTreeMap<ObjectID, impl Borrow<Object>>,
module: &CompiledModule,
function: &Identifier,
Expand All @@ -718,12 +703,15 @@ pub fn resolve_and_type_check(
));
}
};
// Check for entry modifier, but ignore for genesis.
// Check for entry modifier, but ignore for genesis or dev-inspect.
// Genesis calls non-entry, private functions, and bypasses this rule. This is helpful for
// ensuring the functions are not called again later.
// In other words, this is an implementation detail that we are using `execute` for genesis
// functions, and as such need to bypass this check.
if !fdef.is_entry && !is_genesis {
// Similarly, we will bypass this check for dev-inspect, as the mode does not make state changes
// and is just for developers to check the result of Move functions. This mode is flagged by
// allow_arbitrary_function_calls
if !fdef.is_entry && !is_genesis && !Mode::allow_arbitrary_function_calls() {
return Err(ExecutionError::new_with_source(
ExecutionErrorKind::NonEntryFunctionInvoked,
"Can only call `entry` functions",
Expand Down Expand Up @@ -782,6 +770,9 @@ pub fn resolve_and_type_check(
let param_type = &parameters[idx];
let idx = idx as LocalIndex;
let object_arg = match arg {
// dev-inspect does not make state changes and just a developer aid, so let through
// any BCS bytes (they will be checked later by the VM)
CallArg::Pure(arg) if Mode::allow_arbitrary_function_calls() => return Ok(arg),
CallArg::Pure(arg) => {
let (is_primitive, type_layout_opt) =
primitive_type(view, type_args, param_type);
Expand Down
90 changes: 90 additions & 0 deletions crates/sui-adapter/src/execution_mode.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// Copyright (c) Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

use move_vm_runtime::session::SerializedReturnValues;
use sui_types::error::ExecutionError;

pub type TransactionIndex = usize;

pub trait ExecutionMode {
/// the type of a single Move call execution
type ExecutionResult;

/// the gathered results from batched executions
type ExecutionResults;

/// Controls two things:
/// - the calling of arbitrary Move functions
/// - the ability to instantiate any Move function parameter with a Pure call arg.
/// In other words, you can instantiate any struct or object or other value with its BCS bytes.
fn allow_arbitrary_function_calls() -> bool;

fn make_result(
return_values: &SerializedReturnValues,
) -> Result<Self::ExecutionResult, ExecutionError>;

fn empty_results() -> Self::ExecutionResults;

fn add_result(
results: &mut Self::ExecutionResults,
idx: TransactionIndex,
result: Self::ExecutionResult,
);
}

pub struct Normal;

impl ExecutionMode for Normal {
type ExecutionResult = ();
type ExecutionResults = ();

fn allow_arbitrary_function_calls() -> bool {
false
}

fn make_result(srv: &SerializedReturnValues) -> Result<Self::ExecutionResult, ExecutionError> {
assert_invariant!(srv.return_values.is_empty(), "Return values must be empty");
Ok(())
}

fn empty_results() -> Self::ExecutionResults {}

fn add_result(_: &mut Self::ExecutionResults, _: TransactionIndex, _: Self::ExecutionResult) {}
}

/// WARNING! Using this mode will bypass all normal checks around Move entry functions! This
/// includes the various rules for function arguments, meaning any object can be created just from
/// BCS bytes!
pub struct DevInspect;

impl ExecutionMode for DevInspect {
type ExecutionResult = SerializedReturnValues;
type ExecutionResults = Vec<(TransactionIndex, SerializedReturnValues)>;

fn allow_arbitrary_function_calls() -> bool {
true
}

fn make_result(srv: &SerializedReturnValues) -> Result<Self::ExecutionResult, ExecutionError> {
let SerializedReturnValues {
mutable_reference_outputs,
return_values,
} = srv;
Ok(SerializedReturnValues {
mutable_reference_outputs: mutable_reference_outputs.clone(),
return_values: return_values.clone(),
})
}

fn empty_results() -> Self::ExecutionResults {
todo!()
}

fn add_result(
results: &mut Self::ExecutionResults,
idx: TransactionIndex,
result: Self::ExecutionResult,
) {
results.push((idx, result))
}
}
12 changes: 12 additions & 0 deletions crates/sui-adapter/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
// Copyright (c) Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

macro_rules! assert_invariant {
($cond:expr, $msg:expr) => {
if !$cond {
return Err(sui_types::error::ExecutionError::new_with_source(
sui_types::error::ExecutionErrorKind::InvariantViolation,
$msg,
));
}
};
}

pub mod adapter;
pub mod execution_mode;
pub mod genesis;
4 changes: 2 additions & 2 deletions crates/sui-config/src/genesis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ use serde_with::serde_as;
use std::collections::BTreeMap;
use std::convert::TryInto;
use std::{fs, path::Path};
use sui_adapter::adapter;
use sui_adapter::adapter::MoveVM;
use sui_adapter::{adapter, execution_mode};
use sui_types::base_types::ObjectID;
use sui_types::base_types::TransactionDigest;
use sui_types::crypto::{AuthorityPublicKey, ToFromBytes};
Expand Down Expand Up @@ -565,7 +565,7 @@ pub fn generate_genesis_system_object(
commission_rates.push(validator.commission_rate());
}

adapter::execute(
adapter::execute::<execution_mode::Normal, _, _>(
move_vm,
&mut temporary_store,
ModuleId::new(SUI_FRAMEWORK_ADDRESS, ident_str!("genesis").to_owned()),
Expand Down
6 changes: 3 additions & 3 deletions crates/sui-core/src/authority.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ use narwhal_config::{
WorkerId as ConsensusWorkerId,
};
use narwhal_types::CommittedSubDag;
use sui_adapter::adapter;
use sui_adapter::{adapter, execution_mode};
use sui_config::genesis::Genesis;
use sui_json_rpc_types::{
type_and_fields_from_move_struct, SuiEvent, SuiEventEnvelope, SuiTransactionEffects,
Expand Down Expand Up @@ -1058,7 +1058,7 @@ impl AuthorityState {
let temporary_store =
TemporaryStore::new(self.database.clone(), input_objects, *certificate.digest());
let (inner_temp_store, effects, _execution_error) =
execution_engine::execute_transaction_to_effects(
execution_engine::execute_transaction_to_effects::<execution_mode::Normal, _>(
shared_object_refs,
temporary_store,
certificate.data().data.clone(),
Expand Down Expand Up @@ -1090,7 +1090,7 @@ impl AuthorityState {
let temporary_store =
TemporaryStore::new(self.database.clone(), input_objects, transaction_digest);
let (_inner_temp_store, effects, _execution_error) =
execution_engine::execute_transaction_to_effects(
execution_engine::execute_transaction_to_effects::<execution_mode::Normal, _>(
shared_object_refs,
temporary_store,
transaction,
Expand Down
Loading

0 comments on commit bbf1684

Please sign in to comment.