diff --git a/api/src/tests/call_trace_tests.rs b/api/src/tests/call_trace_tests.rs new file mode 100644 index 0000000000000..17cad102a11b1 --- /dev/null +++ b/api/src/tests/call_trace_tests.rs @@ -0,0 +1,30 @@ +// Copyright © Aptos Foundation +// SPDX-License-Identifier: Apache-2.0 + +use super::new_test_context; +use aptos_api_test_context::current_function_name; +use serde_json::json; + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn test_simple_call_trace() { + let mut context = new_test_context(current_function_name!()); + let creator = &mut context.gen_account(); + let owner = &mut context.gen_account(); + let txn1 = context.mint_user_account(creator).await; + let txn2 = context.account_transfer(creator, owner, 100_000); + + context.commit_block(&vec![txn1, txn2]).await; + + let resp = context + .post( + "/view", + json!({ + "function":"0x1::coin::balance", + "arguments": vec![owner.address().to_string()], + "type_arguments": vec!["0x1::aptos_coin::AptosCoin"], + }), + ) + .await; + + context.check_golden_output_no_prune(resp); +} diff --git a/api/src/tests/mod.rs b/api/src/tests/mod.rs index ef743c6b20ddd..16345b7041eec 100644 --- a/api/src/tests/mod.rs +++ b/api/src/tests/mod.rs @@ -17,6 +17,7 @@ mod string_resource_test; mod transaction_vector_test; mod transactions_test; mod view_function; +mod call_trace_tests; use aptos_api_test_context::{new_test_context as super_new_test_context, TestContext}; use aptos_config::config::NodeConfig; diff --git a/api/src/transactions.rs b/api/src/transactions.rs index 7039fb3476e2b..7d02ef4e1461d 100644 --- a/api/src/transactions.rs +++ b/api/src/transactions.rs @@ -19,14 +19,7 @@ use crate::{ ApiTags, }; use anyhow::{anyhow, Context as AnyhowContext}; -use aptos_api_types::{ - verify_function_identifier, verify_module_identifier, Address, AptosError, AptosErrorCode, - AsConverter, EncodeSubmissionRequest, GasEstimation, GasEstimationBcs, HashValue, - HexEncodedBytes, LedgerInfo, MoveType, PendingTransaction, SubmitTransactionRequest, - Transaction, TransactionData, TransactionOnChainData, TransactionsBatchSingleSubmissionFailure, - TransactionsBatchSubmissionResult, UserTransaction, VerifyInput, VerifyInputWithRecursion, - MAX_RECURSIVE_TYPES_ALLOWED, U64, -}; +use aptos_api_types::{verify_function_identifier, verify_module_identifier, Address, AptosError, AptosErrorCode, AsConverter, EncodeSubmissionRequest, GasEstimation, GasEstimationBcs, HashValue, HexEncodedBytes, LedgerInfo, MoveType, PendingTransaction, SubmitTransactionRequest, Transaction, TransactionData, TransactionOnChainData, TransactionsBatchSingleSubmissionFailure, TransactionsBatchSubmissionResult, UserTransaction, VerifyInput, VerifyInputWithRecursion, MAX_RECURSIVE_TYPES_ALLOWED, U64, ViewRequest}; use aptos_crypto::{hash::CryptoHash, signing_message}; use aptos_types::{ account_config::CoinStoreResource, @@ -45,6 +38,9 @@ use poem_openapi::{ ApiRequest, OpenApi, }; use std::sync::Arc; +use aptos_api_types::call_trace::{CallTrace}; +use aptos_types::transaction::Version; +use move_core_types::call_trace::CallTraces; generate_success_response!(SubmitTransactionResponse, (202, Accepted)); @@ -72,6 +68,8 @@ type SubmitTransactionsBatchResult = type SimulateTransactionResult = poem::Result, SubmitTransactionError>; +type DebugCallTraceResult = poem::Result, SubmitTransactionError>; + // TODO: Consider making both content types accept either // SubmitTransactionRequest or SignedTransaction, the way // it is now is quite confusing. @@ -205,6 +203,133 @@ impl TransactionsApi { .await } + #[oai( + path = "/call_trace/by_hash/:txn_hash", + method = "get", + operation_id = "get_transaction_call_trace_by_hash", + tag = "ApiTags::Transactions" + )] + async fn get_transaction_call_trace_by_hash( + &self, + accept_type: AcceptType, + /// Hash of transaction to retrieve + txn_hash: Path, + ) -> BasicResultWith404 { + fail_point_poem("endpoint_transaction_call_trace_by_hash")?; + self.context + .check_api_output_enabled("Get transaction call trace by hash", &accept_type)?; + let ledger_info = self.context.get_latest_ledger_info()?; + let hash = txn_hash.0; + let txn_data = self + .get_by_hash(hash.into(), &ledger_info) + .await + .context(format!("Failed to get transaction by hash {}", hash)) + .map_err(|err| { + BasicErrorWith404::internal_with_code( + err, + AptosErrorCode::InternalError, + &ledger_info, + ) + })? + .context(format!("Failed to find transaction with hash: {}", hash)) + .map_err(|_| transaction_not_found_by_hash(hash, &ledger_info))?; + + let state_view = self.context.latest_state_view_poem(&ledger_info)?; + let resolver = state_view.as_move_resolver(); + let transaction = match txn_data { + TransactionData::OnChain(txn) => { + let timestamp = + self.context.get_block_timestamp(&ledger_info, txn.version)?; + resolver + .as_converter(self.context.db.clone()) + .try_into_onchain_transaction(timestamp, txn) + .context("Failed to convert on chain transaction to Transaction") + .map_err(|err| { + BasicErrorWith404::internal_with_code( + err, + AptosErrorCode::InternalError, + &ledger_info, + ) + })? + }, + TransactionData::Pending(txn) => resolver + .as_converter(self.context.db.clone()) + .try_into_pending_transaction(*txn) + .context("Failed to convert on pending transaction to Transaction") + .map_err(|err| { + BasicErrorWith404::internal_with_code( + err, + AptosErrorCode::InternalError, + &ledger_info, + ) + })?, + }; + + let call_trace = match transaction { + Transaction::PendingTransaction(_) => { + Ok(CallTraces::new()) + } + Transaction::UserTransaction(user_transaction) => { + let state_view = self + .context + .state_view_at_version(Version::from(user_transaction.info.version)) + .map_err(|err| { + BasicErrorWith404::bad_request_with_code( + err, + AptosErrorCode::InternalError, + &ledger_info, + ) + })?; + let payload = user_transaction.request.payload; + match payload { + aptos_api_types::TransactionPayload::EntryFunctionPayload(entry_func_payload) => { + let entry_func = resolver + .as_converter(self.context.db.clone()) + .convert_view_function(ViewRequest { + function: entry_func_payload.function, + type_arguments: entry_func_payload.type_arguments, + arguments: entry_func_payload.arguments + }) + .map_err(|err| { + BasicErrorWith404::bad_request_with_code( + err, + AptosErrorCode::InvalidInput, + &ledger_info, + ) + })?; + AptosVM::get_call_trace( + &state_view, + entry_func.module().clone(), + entry_func.function().to_owned(), + entry_func.ty_args().to_owned(), + entry_func.args().to_owned(), + self.context.node_config.api.max_gas_view_function, + ) + } + aptos_api_types::TransactionPayload::ScriptPayload(_) => {Ok(CallTraces::new())} + aptos_api_types::TransactionPayload::ModuleBundlePayload(_) => {Ok(CallTraces::new())} + aptos_api_types::TransactionPayload::MultisigPayload(_) => {Ok(CallTraces::new())} + } + } + Transaction::GenesisTransaction(_) => {Ok(CallTraces::new())} + Transaction::BlockMetadataTransaction(_) => {Ok(CallTraces::new())} + Transaction::StateCheckpointTransaction(_) => {Ok(CallTraces::new())} + }; + + match call_trace { + Ok(mut _call_traces) => { + BasicResponse::try_from_json((CallTrace::from(_call_traces.root().unwrap()), &ledger_info, BasicResponseStatus::Ok)) + } + Err(_) => { + Err(BasicErrorWith404::bad_request_with_code( + "Cannot get ing status", + AptosErrorCode::InvalidInput, + &ledger_info, + )) + } + } + } + /// Get transaction by version /// /// Retrieves a transaction by a given version. If the version has been diff --git a/api/types/src/call_trace.rs b/api/types/src/call_trace.rs new file mode 100644 index 0000000000000..44926b3abcb4b --- /dev/null +++ b/api/types/src/call_trace.rs @@ -0,0 +1,33 @@ +use poem_openapi_derive::Object; +use serde::{Deserialize, Serialize}; +use move_core_types::call_trace::InternalCallTrace; + +/// A call trace +/// +/// This is a representation of the debug call trace +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, Object)] +pub struct CallTrace { + pub pc: u16, + pub module_id: String, + pub func_name: String, + pub inputs: Vec, + pub outputs: Vec, + pub type_args: Vec, + pub sub_traces: Vec, +} + +impl From for CallTrace { + fn from(value: InternalCallTrace) -> Self { + CallTrace { + pc: value.pc, + module_id: value.module_id, + func_name: value.func_name, + inputs: value.inputs, + outputs: value.outputs, + type_args: value.type_args, + sub_traces: value.sub_traces.into_iter().enumerate().map(|(_, trace)| { + CallTrace::from(trace) + }).collect(), + } + } +} diff --git a/api/types/src/lib.rs b/api/types/src/lib.rs index 0ff0f7fb0f8bc..a4846acbcdde0 100644 --- a/api/types/src/lib.rs +++ b/api/types/src/lib.rs @@ -22,6 +22,7 @@ mod table; pub mod transaction; mod view; mod wrappers; +pub mod call_trace; pub use account::AccountData; pub use address::Address; diff --git a/aptos-move/aptos-vm/src/aptos_vm.rs b/aptos-move/aptos-vm/src/aptos_vm.rs index 1f9c5209aa706..ec1d8bb032643 100644 --- a/aptos-move/aptos-vm/src/aptos_vm.rs +++ b/aptos-move/aptos-vm/src/aptos_vm.rs @@ -78,6 +78,8 @@ use std::{ Arc, }, }; +use move_core_types::call_trace::CallTraces; +// use move_vm_types::call_trace::CallTraces; static EXECUTION_CONCURRENCY_LEVEL: OnceCell = OnceCell::new(); static NUM_EXECUTION_SHARD: OnceCell = OnceCell::new(); @@ -1406,6 +1408,28 @@ impl AptosVM { ) } + pub fn get_call_trace( + state_view: &impl StateView, + module_id: ModuleId, + func_name: Identifier, + type_args: Vec, + arguments: Vec>, + gas_budget: u64, + ) -> Result { + let vm = AptosVM::new_from_state_view(state_view); + let log_context = AdapterLogSchema::new(state_view.id(), 0); + let mut gas_meter = + MemoryTrackedGasMeter::new(StandardGasMeter::new(StandardGasAlgebra::new( + vm.0.get_gas_feature_version(), + vm.0.get_gas_parameters(&log_context)?.vm.clone(), + vm.0.get_storage_gas_parameters(&log_context)?.clone(), + gas_budget, + ))); + let resolver = vm.as_move_resolver(state_view); + let mut session = vm.new_session(&resolver, SessionId::Void); + Ok(session.call_trace(&module_id, &func_name, type_args, arguments, &mut gas_meter).unwrap()) + } + pub fn execute_view_function( state_view: &impl StateView, module_id: ModuleId, diff --git a/third_party/move/move-core/types/src/call_trace.rs b/third_party/move/move-core/types/src/call_trace.rs new file mode 100644 index 0000000000000..8ed2609be09b9 --- /dev/null +++ b/third_party/move/move-core/types/src/call_trace.rs @@ -0,0 +1,57 @@ +use serde::{Deserialize, Serialize}; + +const CALL_STACK_SIZE_LIMIT: usize = 1024; + +/// A call trace +/// +/// This is a representation of the debug call trace +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +pub struct InternalCallTrace { + pub pc: u16, + pub module_id: String, + pub func_name: String, + pub inputs: Vec, + pub outputs: Vec, + pub type_args: Vec, + pub sub_traces: Vec, +} + +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +pub struct CallTraces(Vec); + +impl CallTraces { + pub fn new() -> Self { + CallTraces(vec![]) + } + + pub fn push(&mut self, trace: InternalCallTrace) -> Result<(), InternalCallTrace> { + if self.0.len() < CALL_STACK_SIZE_LIMIT { + self.0.push(trace); + Ok(()) + } else { + Err(trace) + } + } + + pub fn pop(&mut self) -> Option { + self.0.pop() + } + + pub fn set_outputs(&mut self, outputs: Vec) { + let length = self.0.len(); + self.0[length - 1].outputs = outputs + } + + pub fn push_call_trace(&mut self, call_trace: InternalCallTrace) { + let length = self.0.len(); + self.0[length - 1].sub_traces.push(call_trace); + } + + pub fn len(&self) -> usize { + self.0.len() + } + + pub fn root(&mut self) -> Option { + self.0.pop() + } +} diff --git a/third_party/move/move-core/types/src/lib.rs b/third_party/move/move-core/types/src/lib.rs index 7e6b2637ef9e1..6bdfa9c0ded99 100644 --- a/third_party/move/move-core/types/src/lib.rs +++ b/third_party/move/move-core/types/src/lib.rs @@ -25,3 +25,4 @@ pub mod u256; mod unit_tests; pub mod value; pub mod vm_status; +pub mod call_trace; diff --git a/third_party/move/move-vm/integration-tests/src/tests/multi_func_tests.rs b/third_party/move/move-vm/integration-tests/src/tests/multi_func_tests.rs index e4227196a77fc..6f92006829464 100644 --- a/third_party/move/move-vm/integration-tests/src/tests/multi_func_tests.rs +++ b/third_party/move/move-vm/integration-tests/src/tests/multi_func_tests.rs @@ -10,9 +10,9 @@ use move_core_types::{ language_storage::{ModuleId, TypeTag}, value::{MoveTypeLayout, MoveValue}, }; +use move_core_types::call_trace::CallTraces; use move_vm_runtime::{move_vm::MoveVM, session::SerializedReturnValues}; use move_vm_test_utils::InMemoryStorage; -use move_vm_types::call_trace::CallTraces; use move_vm_types::gas::UnmeteredGasMeter; const TEST_ADDR: AccountAddress = AccountAddress::new([42; AccountAddress::LENGTH]); diff --git a/third_party/move/move-vm/runtime/src/interpreter.rs b/third_party/move/move-vm/runtime/src/interpreter.rs index b7c4ac010c8da..2ef9f2e128ea7 100644 --- a/third_party/move/move-vm/runtime/src/interpreter.rs +++ b/third_party/move/move-vm/runtime/src/interpreter.rs @@ -2,7 +2,7 @@ // Copyright (c) The Move Contributors // SPDX-License-Identifier: Apache-2.0 -use crate::{data_cache::TransactionDataCache, interpreter, loader::{Function, Loader, Resolver}, native_extensions::NativeContextExtensions, native_functions::NativeContext, trace}; +use crate::{data_cache::TransactionDataCache, loader::{Function, Loader, Resolver}, native_extensions::NativeContextExtensions, native_functions::NativeContext, trace}; use fail::fail_point; use move_binary_format::{ errors::*, @@ -23,9 +23,9 @@ use move_vm_types::{ Vector, VectorRef, }, views::TypeView, - call_trace::{CallTraces, CallTrace}, }; use std::{cmp::min, collections::VecDeque, fmt::Write, sync::Arc}; +use move_core_types::call_trace::{InternalCallTrace, CallTraces}; macro_rules! debug_write { ($($toks: tt)*) => { @@ -311,13 +311,14 @@ impl Interpreter { let mut current_frame = self .make_new_frame(loader, function, ty_args, locals) .map_err(|err| self.set_location(err))?; - call_traces.push(CallTrace { + call_traces.push(InternalCallTrace { pc: current_frame.pc, module_id: "".to_string(), func_name: current_frame.function.name().to_string(), - inputs: args_1, + inputs: args_1.into_iter().enumerate().map(|(_, i)| i.to_string()).collect(), outputs: vec![], - type_args: current_frame.ty_args.clone(), + // TODO(pcxu): add type args + type_args: vec![], sub_traces: vec![], }).map_err(|_e| { let err = PartialVMError::new(StatusCode::ABORTED); @@ -347,7 +348,7 @@ impl Interpreter { for val in self.operand_stack.last_n(current_frame.function.return_type_count()).unwrap() { outputs.push((*val).copy_value().unwrap()); } - call_traces.set_outputs(outputs); + call_traces.set_outputs(outputs.into_iter().enumerate().map(|(_, o)| {o.to_string()}).collect()); if let Some(frame) = self.call_stack.pop() { // Note: the caller will find the callee's return values at the top of the shared operand stack @@ -402,11 +403,11 @@ impl Interpreter { for val in self.operand_stack.last_n(func.arg_count()).unwrap() { inputs.push((*val).copy_value().unwrap()); } - call_traces.push(CallTrace { + call_traces.push(InternalCallTrace { pc: 0, module_id: "".to_string(), func_name: func.name().to_string(), - inputs, + inputs: inputs.into_iter().enumerate().map(|(_, i)| i.to_string()).collect(), outputs: vec![], type_args: vec![], sub_traces: vec![], @@ -469,11 +470,11 @@ impl Interpreter { for val in self.operand_stack.last_n(func.arg_count()).unwrap() { inputs.push((*val).copy_value().unwrap()); } - call_traces.push(CallTrace { + call_traces.push(InternalCallTrace { pc: 0, module_id: "".to_string(), func_name: func.name().to_string(), - inputs, + inputs: inputs.into_iter().enumerate().map(|(_, i)| i.to_string()).collect(), outputs: vec![], type_args: vec![], sub_traces: vec![], diff --git a/third_party/move/move-vm/runtime/src/runtime.rs b/third_party/move/move-vm/runtime/src/runtime.rs index 0c8f31fc8b8c9..138f55870e997 100644 --- a/third_party/move/move-vm/runtime/src/runtime.rs +++ b/third_party/move/move-vm/runtime/src/runtime.rs @@ -30,9 +30,9 @@ use move_vm_types::{ gas::GasMeter, loaded_data::runtime_types::Type, values::{Locals, Reference, VMValueCast, Value}, - call_trace::CallTraces, }; use std::{borrow::Borrow, collections::BTreeSet, sync::Arc}; +use move_core_types::call_trace::{CallTraces}; /// An instantiation of the MoveVM. pub(crate) struct VMRuntime { diff --git a/third_party/move/move-vm/runtime/src/session.rs b/third_party/move/move-vm/runtime/src/session.rs index 05c566edd0f43..59d04f2fafaa0 100644 --- a/third_party/move/move-vm/runtime/src/session.rs +++ b/third_party/move/move-vm/runtime/src/session.rs @@ -23,9 +23,9 @@ use move_vm_types::{ gas::GasMeter, loaded_data::runtime_types::{CachedStructIndex, StructType, Type}, values::{GlobalValue, Value}, - call_trace::CallTraces, }; use std::{borrow::Borrow, sync::Arc}; +use move_core_types::call_trace::{CallTraces}; pub struct Session<'r, 'l> { pub(crate) move_vm: &'l MoveVM, diff --git a/third_party/move/move-vm/types/src/call_trace.rs b/third_party/move/move-vm/types/src/call_trace.rs deleted file mode 100644 index 4abdd2f913ad6..0000000000000 --- a/third_party/move/move-vm/types/src/call_trace.rs +++ /dev/null @@ -1,48 +0,0 @@ -use crate::loaded_data::runtime_types::Type; -use crate::values::Value; - -const CALL_STACK_SIZE_LIMIT: usize = 1024; -pub struct CallTrace { - pub pc: u16, - pub module_id: String, - pub func_name: String, - pub inputs: Vec, - pub outputs: Vec, - pub type_args: Vec, - pub sub_traces: Vec, -} - -pub struct CallTraces(Vec); - -impl CallTraces { - pub fn new() -> Self { - CallTraces(vec![]) - } - - pub fn push(&mut self, trace: CallTrace) -> Result<(), CallTrace> { - if self.0.len() < CALL_STACK_SIZE_LIMIT { - self.0.push(trace); - Ok(()) - } else { - Err(trace) - } - } - - pub fn pop(&mut self) -> Option { - self.0.pop() - } - - pub fn set_outputs(&mut self, outputs: Vec) { - let length = self.0.len(); - self.0[length - 1].outputs = outputs - } - - pub fn push_call_trace(&mut self, call_trace: CallTrace) { - let length = self.0.len(); - self.0[length - 1].sub_traces.push(call_trace); - } - - pub fn len(&self) -> usize { - self.0.len() - } -} diff --git a/third_party/move/move-vm/types/src/lib.rs b/third_party/move/move-vm/types/src/lib.rs index 8c0f3028c86a2..4204e4a15b915 100644 --- a/third_party/move/move-vm/types/src/lib.rs +++ b/third_party/move/move-vm/types/src/lib.rs @@ -30,4 +30,3 @@ pub mod views; #[cfg(test)] mod unit_tests; -pub mod call_trace;