Skip to content

Commit

Permalink
WIP: add get call trace api (#3)
Browse files Browse the repository at this point in the history
* WIP: add get call trace api

* Update

* make the build pass
  • Loading branch information
Poytr1 authored Sep 20, 2023
1 parent dae86e5 commit acb8138
Show file tree
Hide file tree
Showing 14 changed files with 294 additions and 70 deletions.
30 changes: 30 additions & 0 deletions api/src/tests/call_trace_tests.rs
Original file line number Diff line number Diff line change
@@ -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);
}
1 change: 1 addition & 0 deletions api/src/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
141 changes: 133 additions & 8 deletions api/src/transactions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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));

Expand Down Expand Up @@ -72,6 +68,8 @@ type SubmitTransactionsBatchResult<T> =

type SimulateTransactionResult<T> = poem::Result<BasicResponse<T>, SubmitTransactionError>;

type DebugCallTraceResult<T> = poem::Result<BasicResponse<T>, SubmitTransactionError>;

// TODO: Consider making both content types accept either
// SubmitTransactionRequest or SignedTransaction, the way
// it is now is quite confusing.
Expand Down Expand Up @@ -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<HashValue>,
) -> BasicResultWith404<CallTrace> {
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
Expand Down
33 changes: 33 additions & 0 deletions api/types/src/call_trace.rs
Original file line number Diff line number Diff line change
@@ -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<String>,
pub outputs: Vec<String>,
pub type_args: Vec<String>,
pub sub_traces: Vec<CallTrace>,
}

impl From<InternalCallTrace> 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(),
}
}
}
1 change: 1 addition & 0 deletions api/types/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
24 changes: 24 additions & 0 deletions aptos-move/aptos-vm/src/aptos_vm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<usize> = OnceCell::new();
static NUM_EXECUTION_SHARD: OnceCell<usize> = OnceCell::new();
Expand Down Expand Up @@ -1410,6 +1412,28 @@ impl AptosVM {
)
}

pub fn get_call_trace(
state_view: &impl StateView,
module_id: ModuleId,
func_name: Identifier,
type_args: Vec<TypeTag>,
arguments: Vec<Vec<u8>>,
gas_budget: u64,
) -> Result<CallTraces> {
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,
Expand Down
57 changes: 57 additions & 0 deletions third_party/move/move-core/types/src/call_trace.rs
Original file line number Diff line number Diff line change
@@ -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<String>,
pub outputs: Vec<String>,
pub type_args: Vec<String>,
pub sub_traces: Vec<InternalCallTrace>,
}

#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct CallTraces(Vec<InternalCallTrace>);

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<InternalCallTrace> {
self.0.pop()
}

pub fn set_outputs(&mut self, outputs: Vec<String>) {
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<InternalCallTrace> {
self.0.pop()
}
}
1 change: 1 addition & 0 deletions third_party/move/move-core/types/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,4 @@ pub mod u256;
mod unit_tests;
pub mod value;
pub mod vm_status;
pub mod call_trace;
Original file line number Diff line number Diff line change
Expand Up @@ -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]);
Expand Down
Loading

0 comments on commit acb8138

Please sign in to comment.