Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Execution mode for dev-inspect #6538

Merged
merged 3 commits into from
Dec 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -111,6 +102,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 @@ -126,33 +118,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 @@ -162,12 +150,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 @@ -187,6 +175,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 @@ -206,7 +195,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 @@ -219,16 +208,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 @@ -314,8 +301,7 @@ fn execute_internal<
user_events,
ctx,
)?;

Ok(())
Ok(mode_result)
}

#[instrument(name = "adapter_publish", level = "trace", skip_all)]
Expand Down Expand Up @@ -428,8 +414,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 @@ -688,7 +673,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 @@ -715,12 +700,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 @@ -779,6 +767,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;
tnowacki marked this conversation as resolved.
Show resolved Hide resolved

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;
tnowacki marked this conversation as resolved.
Show resolved Hide resolved

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 @@ -1042,7 +1042,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 @@ -1074,7 +1074,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