Skip to content

Commit

Permalink
[2/x][dev-inspect] Add authority end-point (MystenLabs#6651)
Browse files Browse the repository at this point in the history
* [2/x][dev-inspect] Add authority end-point

- Add dev_inspect_transction entry point on the authority
- Added new result type for dev inspect
  • Loading branch information
tnowacki authored Dec 15, 2022
1 parent f34e579 commit cb0dbe4
Show file tree
Hide file tree
Showing 9 changed files with 816 additions and 37 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/sui-adapter/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ move-binary-format.workspace = true
move-bytecode-verifier.workspace = true
move-core-types.workspace = true
move-vm-runtime.workspace = true
move-vm-types.workspace = true

sui-framework = { path = "../sui-framework" }
sui-json = { path = "../sui-json" }
Expand Down
50 changes: 28 additions & 22 deletions crates/sui-adapter/src/adapter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -211,10 +211,16 @@ fn execute_internal<
.map_err(|e| convert_type_argument_error(idx, e))?;
}
// script visibility checked manually for entry points
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()?)))?;
let mode_result = Mode::make_result(&result)?;
let result = session.execute_function_bypass_visibility(
module_id,
function,
type_args.clone(),
args,
gas_status,
)?;
let mode_result = Mode::make_result(&session, module_id, function, &type_args, &result)?;

let (change_set, events, mut native_context_extensions) = session.finish_with_extensions()?;
let SerializedReturnValues {
mut mutable_reference_outputs,
..
Expand All @@ -226,16 +232,6 @@ fn execute_internal<
assert_invariant!(change_set.accounts().is_empty(), "Change set must be empty");
// Sui Move no longer uses Move's internal event system
assert_invariant!(events.is_empty(), "Events must be empty");
// Input ref parameters we put in should be the same number we get out, plus one for the &mut TxContext
let num_mut_objects = if has_ctx_arg {
mutable_ref_objects.len() + 1
} else {
mutable_ref_objects.len()
};
assert_invariant!(
num_mut_objects == mutable_reference_outputs.len(),
"Number of mutable references returned does not match input"
);

// When this function is used during publishing, it
// may be executed several times, with objects being
Expand All @@ -250,14 +246,24 @@ fn execute_internal<
ctx.update_state(updated_ctx)?;
}

let mutable_refs = mutable_reference_outputs
.into_iter()
.map(|(local_idx, bytes, _layout)| {
let object_id = mutable_ref_objects.remove(&local_idx).unwrap();
debug_assert!(!by_value_objects.contains(&object_id));
(object_id, bytes)
})
.collect();
let mut mutable_refs = vec![];
for (local_idx, bytes, _layout) in mutable_reference_outputs {
let object_id = match mutable_ref_objects.remove(&local_idx) {
Some(id) => id,
None => {
assert_invariant!(
Mode::allow_arbitrary_function_calls(),
"Mutable references should be populated only by objects in normal execution"
);
continue;
}
};
assert_invariant!(
!by_value_objects.contains(&object_id),
"object used by-ref and by-value"
);
mutable_refs.push((object_id, bytes));
}
assert_invariant!(
mutable_ref_objects.is_empty(),
"All mutable references should have been marked as updated"
Expand Down
116 changes: 104 additions & 12 deletions crates/sui-adapter/src/execution_mode.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,19 @@
// Copyright (c) Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

use move_vm_runtime::session::SerializedReturnValues;
use sui_types::error::ExecutionError;
use move_binary_format::file_format::LocalIndex;
use move_core_types::{
identifier::Identifier,
language_storage::{ModuleId, TypeTag},
resolver::MoveResolver,
};
use move_vm_runtime::session::{SerializedReturnValues, Session};
use move_vm_types::loaded_data::runtime_types::Type;
use sui_types::{
base_types::{TX_CONTEXT_MODULE_NAME, TX_CONTEXT_STRUCT_NAME},
error::ExecutionError,
SUI_FRAMEWORK_ADDRESS,
};

pub type TransactionIndex = usize;

Expand All @@ -19,7 +30,11 @@ pub trait ExecutionMode {
/// 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(
fn make_result<S: MoveResolver>(
session: &Session<S>,
module_id: &ModuleId,
function: &Identifier,
type_arguments: &[TypeTag],
return_values: &SerializedReturnValues,
) -> Result<Self::ExecutionResult, ExecutionError>;

Expand All @@ -42,7 +57,13 @@ impl ExecutionMode for Normal {
false
}

fn make_result(srv: &SerializedReturnValues) -> Result<Self::ExecutionResult, ExecutionError> {
fn make_result<S: MoveResolver>(
_session: &Session<S>,
_module_id: &ModuleId,
_function: &Identifier,
_type_arguments: &[TypeTag],
srv: &SerializedReturnValues,
) -> Result<Self::ExecutionResult, ExecutionError> {
assert_invariant!(srv.return_values.is_empty(), "Return values must be empty");
Ok(())
}
Expand All @@ -57,27 +78,73 @@ impl ExecutionMode for Normal {
/// BCS bytes!
pub struct DevInspect;

pub type ExecutionResult = (
/* mutable_reference_outputs */ Vec<(LocalIndex, Vec<u8>, TypeTag)>,
/* return_values */ Vec<(Vec<u8>, TypeTag)>,
);

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

fn allow_arbitrary_function_calls() -> bool {
true
}

fn make_result(srv: &SerializedReturnValues) -> Result<Self::ExecutionResult, ExecutionError> {
fn make_result<S: MoveResolver>(
session: &Session<S>,
module_id: &ModuleId,
function: &Identifier,
type_arguments: &[TypeTag],
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(),
})
let loaded_function = match session.load_function(module_id, function, type_arguments) {
Ok(loaded) => loaded,
Err(_) => {
return Err(ExecutionError::new_with_source(
sui_types::error::ExecutionErrorKind::InvariantViolation,
"The function should have been able to load, as it was already executed",
));
}
};
let ty_args = &loaded_function.type_arguments;
let mut mutable_reference_outputs = mutable_reference_outputs
.iter()
.map(|(i, bytes, _)| {
let ty =
remove_ref_and_subst_ty(&loaded_function.parameters[(*i as usize)], ty_args)?;
let tag = type_to_type_tag(session, &ty)?;
Ok((*i, bytes.clone(), tag))
})
.collect::<Result<Vec<_>, ExecutionError>>()?;
// ignore the TxContext if it is there
let last = mutable_reference_outputs.last();
if let Some((_, _, TypeTag::Struct(st))) = last {
let is_txn_ctx = st.address == SUI_FRAMEWORK_ADDRESS
&& st.module.as_ident_str() == TX_CONTEXT_MODULE_NAME
&& st.name.as_ident_str() == TX_CONTEXT_STRUCT_NAME;
if is_txn_ctx {
mutable_reference_outputs.pop();
}
}
let return_values = return_values
.iter()
.enumerate()
.map(|(i, (bytes, _))| {
let ty = remove_ref_and_subst_ty(&loaded_function.return_[i], ty_args)?;
let tag = type_to_type_tag(session, &ty)?;
Ok((bytes.clone(), tag))
})
.collect::<Result<Vec<_>, ExecutionError>>()?;
Ok((mutable_reference_outputs, return_values))
}

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

fn add_result(
Expand All @@ -88,3 +155,28 @@ impl ExecutionMode for DevInspect {
results.push((idx, result))
}
}

fn type_to_type_tag<S: MoveResolver>(
session: &Session<S>,
ty: &Type,
) -> Result<TypeTag, ExecutionError> {
session.get_type_tag(ty).map_err(|_| {
ExecutionError::new_with_source(
sui_types::error::ExecutionErrorKind::InvariantViolation,
"The type should make a type tag, as the function was already executed",
)
})
}

fn remove_ref_and_subst_ty(ty: &Type, ty_args: &[Type]) -> Result<Type, ExecutionError> {
let ty = match ty {
Type::Reference(inner) | Type::MutableReference(inner) => inner,
_ => ty,
};
ty.subst(ty_args).map_err(|_| {
ExecutionError::new_with_source(
sui_types::error::ExecutionErrorKind::InvariantViolation,
"The type should subst, as the function was already executed",
)
})
}
31 changes: 30 additions & 1 deletion crates/sui-core/src/authority.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ use narwhal_types::CommittedSubDag;
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,
type_and_fields_from_move_struct, DevInspectResults, SuiEvent, SuiEventEnvelope,
SuiTransactionEffects,
};
use sui_simulator::nondeterministic;
use sui_storage::write_ahead_log::WriteAheadLog;
Expand Down Expand Up @@ -1199,6 +1200,34 @@ impl AuthorityState {
SuiTransactionEffects::try_from(effects, self.module_cache.as_ref())
}

pub async fn dev_inspect_transaction(
&self,
transaction: TransactionData,
transaction_digest: TransactionDigest,
) -> Result<DevInspectResults, anyhow::Error> {
let (gas_status, input_objects) =
transaction_input_checker::check_dev_inspect_input(&self.database, &transaction)
.await?;
let shared_object_refs = input_objects.filter_shared_objects();

let transaction_dependencies = input_objects.transaction_dependencies();
let temporary_store =
TemporaryStore::new(self.database.clone(), input_objects, transaction_digest);
let (_inner_temp_store, effects, execution_result) =
execution_engine::execute_transaction_to_effects::<execution_mode::DevInspect, _>(
shared_object_refs,
temporary_store,
transaction,
transaction_digest,
transaction_dependencies,
&self.move_vm,
&self._native_functions,
gas_status,
self.epoch(),
);
DevInspectResults::new(effects, execution_result, self.module_cache.as_ref())
}

pub fn is_tx_already_executed(&self, digest: &TransactionDigest) -> SuiResult<bool> {
self.database.effects_exists(digest)
}
Expand Down
27 changes: 27 additions & 0 deletions crates/sui-core/src/transaction_input_checker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,33 @@ pub async fn check_transaction_input(
Ok((gas_status, input_objects))
}

#[instrument(level = "trace", skip_all)]
/// WARNING! This should only be used for the dev-inspect transaction. This transaction type
/// bypasses many of the normal object checks
pub(crate) async fn check_dev_inspect_input(
store: &AuthorityStore,
transaction: &TransactionData,
) -> SuiResult<(SuiGasStatus<'static>, InputObjects)> {
transaction.validity_check()?;
transaction.kind.validity_check()?;
let gas_status = get_gas_status(store, transaction).await?;
let input_objects = transaction.input_objects()?;
let objects = store.check_input_objects(&input_objects)?;
let mut used_objects: HashSet<SuiAddress> = HashSet::new();
for object in &objects {
if !object.is_immutable() {
fp_ensure!(
used_objects.insert(object.id().into()),
SuiError::InvalidBatchTransaction {
error: format!("Mutable object {} cannot appear in more than one single transactions in a batch", object.id()),
}
);
}
}
let input_objects = InputObjects::new(input_objects.into_iter().zip(objects).collect());
Ok((gas_status, input_objects))
}

pub async fn check_certificate_input(
store: &AuthorityStore,
cert: &VerifiedCertificate,
Expand Down
Loading

0 comments on commit cb0dbe4

Please sign in to comment.