Skip to content

Commit

Permalink
1749 debug trace call (#1771) (#1772)
Browse files Browse the repository at this point in the history
# 1748 debug_traceCall
  • Loading branch information
kladkogex authored Jan 3, 2024
1 parent 9661bd7 commit a6a060f
Show file tree
Hide file tree
Showing 25 changed files with 5,357 additions and 3,603 deletions.
3 changes: 3 additions & 0 deletions libethereum/Block.h
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,9 @@ class Block {

// Information concerning ongoing transactions

/// Get the gas limit in this block.
u256 gasLimit() const { return m_currentBlock.gasLimit(); }

/// Get the remaining gas limit in this block.
u256 gasLimitRemaining() const { return m_currentBlock.gasLimit() - gasUsed(); }

Expand Down
74 changes: 55 additions & 19 deletions libethereum/Client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1233,7 +1233,7 @@ h256 Client::importTransaction( Transaction const& _t ) {


ExecutionResult Client::call( Address const& _from, u256 _value, Address _dest, bytes const& _data,
u256 _gas, u256 _gasPrice,
u256 _gasLimit, u256 _gasPrice,
#ifdef HISTORIC_STATE
BlockNumber _blockNumber,
#endif
Expand All @@ -1246,17 +1246,20 @@ ExecutionResult Client::call( Address const& _from, u256 _value, Address _dest,
// historic state
try {
u256 nonce = historicBlock.mutableState().mutableHistoricState().getNonce( _from );
u256 gas = _gas == Invalid256 ? gasLimitRemaining() : _gas;
// if the user did not specify transaction gas limit, we give transaction block gas
// limit of gas
u256 gasLimit = _gasLimit == Invalid256 ? historicBlock.gasLimit() : _gasLimit;
u256 gasPrice = _gasPrice == Invalid256 ? gasBidPrice() : _gasPrice;
Transaction t( _value, gasPrice, gas, _dest, _data, nonce );
Transaction t( _value, gasPrice, gasLimit, _dest, _data, nonce );
t.forceSender( _from );
t.forceChainId( chainParams().chainID );
t.checkOutExternalGas( ~u256( 0 ) );
if ( _ff == FudgeFactor::Lenient ) {
historicBlock.mutableState().mutableHistoricState().addBalance(
_from, ( u256 )( t.gas() * t.gasPrice() + t.value() ) );
}

// if we are in a call, we add to the balance of the account
// value needed for the call to guaranteed pass
// geth does a similar thing, we need to check whether it is fully compatible with
// geth
historicBlock.mutableState().mutableHistoricState().addBalance(
_from, ( u256 )( t.gas() * t.gasPrice() + t.value() ) );
ret = historicBlock.executeHistoricCall( bc().lastBlockHashes(), t, nullptr, 0 );
} catch ( ... ) {
cwarn << boost::current_exception_diagnostic_information();
Expand All @@ -1271,9 +1274,11 @@ ExecutionResult Client::call( Address const& _from, u256 _value, Address _dest,
// TODO there can be race conditions between prev and next line!
State readStateForLock = temp.mutableState().createStateReadOnlyCopy();
u256 nonce = max< u256 >( temp.transactionsFrom( _from ), m_tq.maxNonce( _from ) );
u256 gas = _gas == Invalid256 ? gasLimitRemaining() : _gas;
// if the user did not specify transaction gas limit, we give transaction block gas
// limit of gas
u256 gasLimit = _gasLimit == Invalid256 ? temp.gasLimit() : _gasLimit;
u256 gasPrice = _gasPrice == Invalid256 ? gasBidPrice() : _gasPrice;
Transaction t( _value, gasPrice, gas, _dest, _data, nonce );
Transaction t( _value, gasPrice, gasLimit, _dest, _data, nonce );
t.forceSender( _from );
t.forceChainId( chainParams().chainID );
t.checkOutExternalGas( ~u256( 0 ) );
Expand All @@ -1294,22 +1299,52 @@ ExecutionResult Client::call( Address const& _from, u256 _value, Address _dest,


#ifdef HISTORIC_STATE
Json::Value Client::traceCall(
Transaction& _t, BlockNumber _blockNumber, std::shared_ptr< AlethStandardTrace > _tracer ) {
Block historicBlock = blockByNumber( _blockNumber );

Json::Value Client::traceCall( Address const& _from, u256 _value, Address _to, bytes const& _data,
u256 _gasLimit, u256 _gasPrice, BlockNumber _blockNumber,
Json::Value const& _jsonTraceConfig ) {
try {
_t.setNonce( historicBlock.mutableState().mutableHistoricState().getNonce( _t.from() ) );
_t.checkOutExternalGas( ~u256( 0 ) );
Block historicBlock = blockByNumber( _blockNumber );
auto nonce = historicBlock.mutableState().mutableHistoricState().getNonce( _from );
// if the user did not specify transaction gas limit, we give transaction block gas
// limit of gas
auto gasLimit = _gasLimit == Invalid256 ? historicBlock.gasLimit() : _gasLimit;

Transaction t = createTransactionForCallOrTraceCall(
_from, _value, _to, _data, gasLimit, _gasPrice, nonce );
// record original t.from balance for trace and then give
// lots of gas to it
auto originalFromBalance = historicBlock.mutableState().balance( _from );
historicBlock.mutableState().mutableHistoricState().addBalance(
_t.from(), ( u256 )( _t.gas() * _t.gasPrice() + _t.value() ) );
auto er = historicBlock.executeHistoricCall( bc().lastBlockHashes(), _t, _tracer, 0 );
return _tracer->getJSONResult();
_from, ( u256 )( t.gas() * t.gasPrice() + t.value() ) );
auto traceOptions = TraceOptions::make( _jsonTraceConfig );
auto tracer =
make_shared< AlethStandardTrace >( t, historicBlock.author(), traceOptions, true );
tracer->setOriginalFromBalance( originalFromBalance );
auto er = historicBlock.executeHistoricCall( bc().lastBlockHashes(), t, tracer, 0 );
return tracer->getJSONResult();
} catch ( ... ) {
cwarn << boost::current_exception_diagnostic_information();
throw;
}
}


Transaction Client::createTransactionForCallOrTraceCall( const Address& _from, const u256& _value,
const Address& _to, const bytes& _data, const u256& _gasLimit, const u256& _gasPrice,
const u256& _nonce ) const {
auto gasPrice = _gasPrice == Invalid256 ? gasBidPrice() : _gasPrice;
Transaction t( _value, gasPrice, _gasLimit, _to, _data, _nonce );
// if call or trace call request did not specify from address, zero address is used
auto from = _from ? _from : ZeroAddress;
t.forceSender( from );
t.forceChainId( chainParams().chainID );
// call and traceCall do not use PoW
t.checkOutExternalGas( ~u256( 0 ) );
return t;
}


Json::Value Client::traceBlock( BlockNumber _blockNumber, Json::Value const& _jsonTraceConfig ) {
Block previousBlock = blockByNumber( _blockNumber - 1 );
Block historicBlock = blockByNumber( _blockNumber );
Expand All @@ -1335,7 +1370,8 @@ Json::Value Client::traceBlock( BlockNumber _blockNumber, Json::Value const& _js
auto hashString = toHexPrefixed( tx.sha3() );
transactionLog["txHash"] = hashString;
tx.checkOutExternalGas( chainParams().externalGasDifficulty );
auto tracer = std::make_shared< AlethStandardTrace >( tx, traceOptions );
auto tracer =
std::make_shared< AlethStandardTrace >( tx, historicBlock.author(), traceOptions );
auto executionResult =
previousBlock.executeHistoricCall( bc().lastBlockHashes(), tx, tracer, k );
auto result = tracer->getJSONResult();
Expand Down
7 changes: 5 additions & 2 deletions libethereum/Client.h
Original file line number Diff line number Diff line change
Expand Up @@ -130,9 +130,12 @@ class Client : public ClientBase, protected Worker {
FudgeFactor _ff = FudgeFactor::Strict ) override;

#ifdef HISTORIC_STATE
Json::Value traceCall(
Transaction& _t, BlockNumber _blockNumber, std::shared_ptr< AlethStandardTrace > _tracer );
Json::Value traceCall( Address const& _from, u256 _value, Address _to, bytes const& _data,
u256 _gas, u256 _gasPrice, BlockNumber _blockNumber, Json::Value const& _jsonTraceConfig );
Json::Value traceBlock( BlockNumber _blockNumber, Json::Value const& _jsonTraceConfig );
Transaction createTransactionForCallOrTraceCall( const Address& _from, const u256& _value,
const Address& _to, const bytes& _data, const u256& _gasLimit, const u256& _gasPrice,
const u256& nonce ) const;
#endif


Expand Down
59 changes: 47 additions & 12 deletions libhistoric/AlethStandardTrace.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,9 @@ void AlethStandardTrace::processFunctionCallOrReturnIfHappened(
STATE_CHECK( currentDepth == m_lastOpRecord.m_depth )
}
}
const Address& AlethStandardTrace::getFrom() const {
return m_from;
}

vector< uint8_t > AlethStandardTrace::extractSmartContractMemoryByteArrayFromStackPointer(
const LegacyVM* _vm ) {
Expand Down Expand Up @@ -212,14 +215,14 @@ Json::Value AlethStandardTrace::getJSONResult() const {
}

AlethStandardTrace::AlethStandardTrace(
Transaction& _t, const TraceOptions& _options, bool _isCall )
Transaction& _t, const Address& _blockAuthor, const TraceOptions& _options, bool _isCall )
: m_defaultOpTrace{ std::make_shared< Json::Value >() },
m_from{ _t.from() },
m_to( _t.to() ),
m_options( _options ),
// if it is a call trace, the transaction does not have signature
// therefore, its hash should not include signature
m_txHash( _t.sha3(_isCall? dev::eth::WithoutSignature: dev::eth::WithSignature) ),
m_txHash( _t.sha3( _isCall ? dev::eth::WithoutSignature : dev::eth::WithSignature ) ),
m_lastOpRecord(
// the top function is executed at depth 0
// therefore it is called from depth -1
Expand All @@ -238,18 +241,27 @@ AlethStandardTrace::AlethStandardTrace(
{ TraceType::CALL_TRACER, m_callTracePrinter },
{ TraceType::REPLAY_TRACER, m_replayTracePrinter },
{ TraceType::FOUR_BYTE_TRACER, m_fourByteTracePrinter },
{ TraceType::NOOP_TRACER, m_noopTracePrinter } } {
{ TraceType::NOOP_TRACER, m_noopTracePrinter } },
m_blockAuthor( _blockAuthor ),
m_isCall( _isCall ) {
// mark from and to accounts as accessed
m_accessedAccounts.insert( m_from );
m_accessedAccounts.insert( m_to );
}
void AlethStandardTrace::setOriginalFromBalance( const u256& _originalFromBalance ) {
m_originalFromBalance = _originalFromBalance;
}

/*
* This function is called by EVM on each instruction
*/
void AlethStandardTrace::operator()( uint64_t, uint64_t _pc, Instruction _inst, bigint,
void AlethStandardTrace::operator()( uint64_t _counter, uint64_t _pc, Instruction _inst, bigint,
bigint _gasOpGas, bigint _gasRemaining, VMFace const* _vm, ExtVMFace const* _ext ) {
STATE_CHECK( !m_isFinalized )
if ( _counter ) {
recordMinerPayment( u256( _gasOpGas ) );
}

recordInstructionIsExecuted( _pc, _inst, _gasOpGas, _gasRemaining, _vm, _ext );
}

Expand Down Expand Up @@ -289,14 +301,8 @@ void AlethStandardTrace::appendOpToStandardOpTrace( uint64_t _pc, Instruction& _
Json::Value stack( Json::arrayValue );
// Try extracting information about the stack from the VM is supported.
for ( auto const& i : _vm->stack() ) {
auto stackStr = toCompactHex( i );
// now make it compatible with the way geth prints string
if ( stackStr.empty() ) {
stackStr = "0";
} else if ( stackStr.front() == '0' ) {
stackStr = stackStr.substr( 1 );
}
stack.append( "0x" + stackStr );
string stackStr = toGethCompatibleCompactHexPrefixed( i );
stack.append( stackStr );
}
r["stack"] = stack;
}
Expand Down Expand Up @@ -344,6 +350,17 @@ void AlethStandardTrace::appendOpToStandardOpTrace( uint64_t _pc, Instruction& _
m_defaultOpTrace->append( r );
}

string AlethStandardTrace::toGethCompatibleCompactHexPrefixed( const u256& _value ) {
auto hexStr = toCompactHex( _value );
// now make it compatible with the way geth prints string
if ( hexStr.empty() ) {
hexStr = "0";
} else if ( hexStr.front() == '0' ) {
hexStr = hexStr.substr( 1 );
}
return "0x" + hexStr;
}

// execution completed. Now use the tracer that the user requested
// to print the resulting trace
void eth::AlethStandardTrace::finalizeTrace(
Expand Down Expand Up @@ -419,6 +436,24 @@ void AlethStandardTrace::setCurrentlyExecutingFunctionCall(
STATE_CHECK( _currentlyExecutingFunctionCall )
m_currentlyExecutingFunctionCall = _currentlyExecutingFunctionCall;
}
const Address& AlethStandardTrace::getBlockAuthor() const {
return m_blockAuthor;
}
const u256& AlethStandardTrace::getMinerPayment() const {
return m_minerPayment;
}
void AlethStandardTrace::recordMinerPayment( u256 _minerGasPayment ) {
this->m_minerPayment = _minerGasPayment;
// add miner to the list of accessed accounts, since the miner is paid
// transaction fee
this->m_accessedAccounts.insert( m_blockAuthor );
}
bool AlethStandardTrace::isCall() const {
return m_isCall;
}
const u256& AlethStandardTrace::getOriginalFromBalance() const {
return m_originalFromBalance;
}
} // namespace dev::eth

#endif
22 changes: 20 additions & 2 deletions libhistoric/AlethStandardTrace.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ class FunctionCallRecord;
class AlethStandardTrace {
public:
// Append json trace to given (array) value
explicit AlethStandardTrace(
Transaction& _t, const TraceOptions& _options, bool _isCall = false );
explicit AlethStandardTrace( Transaction& _t, const Address& _blockAuthor,
const TraceOptions& _options, bool _isCall = false );

// this function is executed on each operation
[[nodiscard]] OnOpFunc functionToExecuteOnEachOperation() {
Expand Down Expand Up @@ -83,6 +83,17 @@ class AlethStandardTrace {
const;
void setTopFunctionCall( const std::shared_ptr< FunctionCallRecord >& _topFunctionCall );

[[nodiscard]] const Address& getBlockAuthor() const;
[[nodiscard]] const u256& getMinerPayment() const;
void setOriginalFromBalance( const u256& _originalFromBalance );

[[nodiscard]] const u256& getOriginalFromBalance() const;

[[nodiscard]] bool isCall() const;


static string toGethCompatibleCompactHexPrefixed( const u256& _value );
const Address& getFrom() const;

private:
// this operator will be executed by skaled on each EVM instruction
Expand Down Expand Up @@ -124,6 +135,8 @@ class AlethStandardTrace {
void printAllTraces( Json::Value& _jsonTrace, ExecutionResult& _er,
const HistoricState& _statePre, const HistoricState& _statePost );

void recordMinerPayment( u256 _minerGasPayment );

std::shared_ptr< FunctionCallRecord > m_topFunctionCall;
std::shared_ptr< FunctionCallRecord > m_currentlyExecutingFunctionCall;
std::vector< Instruction > m_lastInst;
Expand All @@ -149,5 +162,10 @@ class AlethStandardTrace {
DefaultTracePrinter m_defaultTracePrinter;

const std::map< TraceType, TracePrinter& > m_tracePrinters;

const Address m_blockAuthor;
u256 m_minerPayment;
u256 m_originalFromBalance;
bool m_isCall;
};
} // namespace dev::eth
2 changes: 1 addition & 1 deletion libhistoric/CallTracePrinter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ namespace dev::eth {
void CallTracePrinter::print(
Json::Value& _jsonTrace, const ExecutionResult&, const HistoricState&, const HistoricState& ) {
STATE_CHECK( _jsonTrace.isObject() )
m_standardTrace.getTopFunctionCall()->printTrace( _jsonTrace, 0, m_standardTrace.getOptions() );
m_trace.getTopFunctionCall()->printTrace( _jsonTrace, 0, m_trace.getOptions() );
}

CallTracePrinter::CallTracePrinter( AlethStandardTrace& _standardTrace )
Expand Down
4 changes: 2 additions & 2 deletions libhistoric/DefaultTracePrinter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,13 @@ void DefaultTracePrinter::print( Json::Value& _jsonTrace, const ExecutionResult&
const HistoricState&, const HistoricState& ) {
STATE_CHECK( _jsonTrace.isObject() )
_jsonTrace["gas"] = ( uint64_t ) _er.gasUsed;
auto defaultOpTrace = m_standardTrace.getDefaultOpTrace();
auto defaultOpTrace = m_trace.getDefaultOpTrace();
STATE_CHECK( defaultOpTrace );
_jsonTrace["structLogs"] = *defaultOpTrace;
auto failed = _er.excepted != TransactionException::None;
_jsonTrace["failed"] = failed;
if ( !failed ) {
if ( m_standardTrace.getOptions().enableReturnData ) {
if ( m_trace.getOptions().enableReturnData ) {
_jsonTrace["returnValue"] = toHex( _er.output );
}
} else {
Expand Down
4 changes: 2 additions & 2 deletions libhistoric/FourByteTracePrinter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ void FourByteTracePrinter::print(
STATE_CHECK( _jsonTrace.isObject() )
std::map< string, uint64_t > callMap;

m_standardTrace.getTopFunctionCall()->collectFourByteTrace( callMap );
m_trace.getTopFunctionCall()->collectFourByteTrace( callMap );
for ( auto&& key : callMap ) {
_jsonTrace[key.first] = to_string( key.second );
_jsonTrace[key.first] = key.second;
}
}

Expand Down
7 changes: 4 additions & 3 deletions libhistoric/FunctionCallRecord.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -73,16 +73,17 @@ void FunctionCallRecord::printFunctionExecutionDetail(
if ( m_type != Instruction::CREATE && m_type != Instruction::CREATE2 ) {
_jsonTrace["to"] = toHexPrefixed( m_to );
}
_jsonTrace["gas"] = toCompactHexPrefixed( m_functionGasLimit );
_jsonTrace["gasUsed"] = toCompactHexPrefixed( m_gasUsed );
_jsonTrace["gas"] =
AlethStandardTrace::toGethCompatibleCompactHexPrefixed( m_functionGasLimit );
_jsonTrace["gasUsed"] = AlethStandardTrace::toGethCompatibleCompactHexPrefixed( m_gasUsed );
if ( !m_error.empty() ) {
_jsonTrace["error"] = m_error;
}
if ( !m_revertReason.empty() ) {
_jsonTrace["revertReason"] = m_revertReason;
}

_jsonTrace["value"] = toCompactHexPrefixed( m_value );
_jsonTrace["value"] = AlethStandardTrace::toGethCompatibleCompactHexPrefixed( m_value );

if ( !m_outputData.empty() ) {
_jsonTrace["output"] = toHexPrefixed( m_outputData );
Expand Down
Loading

0 comments on commit a6a060f

Please sign in to comment.