Skip to content

Commit

Permalink
Merge pull request #1783 from skalenetwork/1751_trace_transaction_fro…
Browse files Browse the repository at this point in the history
…m_a_single_user

1751 trace multiple transactions in a single block
  • Loading branch information
kladkogex authored Jan 22, 2024
2 parents a6a060f + cd21227 commit da9bf68
Show file tree
Hide file tree
Showing 23 changed files with 682 additions and 186 deletions.
66 changes: 43 additions & 23 deletions libethereum/Block.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -795,36 +795,56 @@ u256 Block::enact( VerifiedBlockRef const& _block, BlockChain const& _bc ) {
#ifdef HISTORIC_STATE
ExecutionResult Block::executeHistoricCall( LastBlockHashesFace const& _lh, Transaction const& _t,
std::shared_ptr< AlethStandardTrace > _tracer, uint64_t _transactionIndex ) {
auto onOp = OnOpFunc();
try {
auto onOp = OnOpFunc();

if ( _tracer ) {
onOp = _tracer->functionToExecuteOnEachOperation();
}
if ( _tracer ) {
onOp = _tracer->functionToExecuteOnEachOperation();
}


if ( isSealed() )
BOOST_THROW_EXCEPTION( InvalidOperationOnSealedBlock() );
if ( isSealed() )
BOOST_THROW_EXCEPTION( InvalidOperationOnSealedBlock() );

// Uncommitting is a non-trivial operation - only do it once we've verified as much of the
// transaction as possible.
uncommitToSeal();
uncommitToSeal();

u256 const gasUsed =
_transactionIndex ? receipt( _transactionIndex - 1 ).cumulativeGasUsed() : 0;
STATE_CHECK( _transactionIndex <= m_receipts.size() )

EnvInfo const envInfo{ info(), _lh, gasUsed, m_sealEngine->chainParams().chainID };
u256 const gasUsed =
_transactionIndex ? receipt( _transactionIndex - 1 ).cumulativeGasUsed() : 0;

if ( _tracer ) {
HistoricState stateBefore( m_state.mutableHistoricState() );
auto resultReceipt = m_state.mutableHistoricState().execute(
envInfo, *m_sealEngine, _t, skale::Permanence::Uncommitted, onOp );
HistoricState stateAfter( m_state.mutableHistoricState() );
_tracer->finalizeTrace( resultReceipt.first, stateBefore, stateAfter );
return resultReceipt.first;
} else {
auto resultReceipt = m_state.mutableHistoricState().execute(
envInfo, *m_sealEngine, _t, skale::Permanence::Reverted, onOp );
return resultReceipt.first;
EnvInfo const envInfo{ info(), _lh, gasUsed, m_sealEngine->chainParams().chainID };

if ( _tracer ) {
try {
HistoricState stateBefore( m_state.mutableHistoricState() );

auto resultReceipt = m_state.mutableHistoricState().execute(
envInfo, *m_sealEngine, _t, skale::Permanence::Uncommitted, onOp );
HistoricState stateAfter( m_state.mutableHistoricState() );
_tracer->finalizeAndPrintTrace( resultReceipt.first, stateBefore, stateAfter );
// for tracing the entire block is traced therefore, we save transaction receipt
// as it is used for execution of the next transaction
m_receipts.push_back( resultReceipt.second );
return resultReceipt.first;
} catch ( std::exception& e ) {
throw dev::eth::VMTracingError( "Exception doing trace for transaction index:" +
std::to_string( _transactionIndex ) + ":" +
e.what() );
}
} else {
auto resultReceipt = m_state.mutableHistoricState().execute(
envInfo, *m_sealEngine, _t, skale::Permanence::Reverted, onOp );
return resultReceipt.first;
}
} catch ( std::exception& e ) {
BOOST_THROW_EXCEPTION(
std::runtime_error( "Could not execute historic call for transactionIndex:" +
to_string( _transactionIndex ) + ":" + e.what() ) );
} catch ( ... ) {
BOOST_THROW_EXCEPTION(
std::runtime_error( "Could not execute historic call for transactionIndex:" +
to_string( _transactionIndex ) + ": unknown error" ) );
}
}
#endif
Expand Down
63 changes: 34 additions & 29 deletions libethereum/Client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1346,43 +1346,48 @@ Transaction Client::createTransactionForCallOrTraceCall( const Address& _from, c


Json::Value Client::traceBlock( BlockNumber _blockNumber, Json::Value const& _jsonTraceConfig ) {
Block previousBlock = blockByNumber( _blockNumber - 1 );
Block historicBlock = blockByNumber( _blockNumber );
try {
Block previousBlock = blockByNumber( _blockNumber - 1 );
Block historicBlock = blockByNumber( _blockNumber );

Json::Value traces( Json::arrayValue );
Json::Value traces( Json::arrayValue );

auto hash = ClientBase::hashFromNumber( _blockNumber );
Transactions transactions = this->transactions( hash );
auto hash = ClientBase::hashFromNumber( _blockNumber );
Transactions transactions = this->transactions( hash );

auto traceOptions = TraceOptions::make( _jsonTraceConfig );
auto traceOptions = TraceOptions::make( _jsonTraceConfig );

// cache results for better peformance
string key = to_string( _blockNumber ) + traceOptions.toString();
// cache results for better peformance
string key = to_string( _blockNumber ) + traceOptions.toString();

auto cachedResult = m_blockTraceCache.getIfExists( key );
if ( cachedResult.has_value() ) {
return std::any_cast< Json::Value >( cachedResult );
}
auto cachedResult = m_blockTraceCache.getIfExists( key );
if ( cachedResult.has_value() ) {
return std::any_cast< Json::Value >( cachedResult );
}

for ( unsigned k = 0; k < transactions.size(); k++ ) {
Json::Value transactionLog( Json::objectValue );
Transaction tx = transactions.at( k );
auto hashString = toHexPrefixed( tx.sha3() );
transactionLog["txHash"] = hashString;
tx.checkOutExternalGas( chainParams().externalGasDifficulty );
auto tracer =
std::make_shared< AlethStandardTrace >( tx, historicBlock.author(), traceOptions );
auto executionResult =
previousBlock.executeHistoricCall( bc().lastBlockHashes(), tx, tracer, k );
auto result = tracer->getJSONResult();
transactionLog["result"] = result;
traces.append( transactionLog );
}
for ( unsigned k = 0; k < transactions.size(); k++ ) {
Json::Value transactionLog( Json::objectValue );
Transaction tx = transactions.at( k );
auto hashString = toHexPrefixed( tx.sha3() );
transactionLog["txHash"] = hashString;
tx.checkOutExternalGas( chainParams().externalGasDifficulty );
auto tracer =
std::make_shared< AlethStandardTrace >( tx, historicBlock.author(), traceOptions );
auto executionResult =
previousBlock.executeHistoricCall( bc().lastBlockHashes(), tx, tracer, k );
auto result = tracer->getJSONResult();
transactionLog["result"] = result;
traces.append( transactionLog );
}

auto tracesSize = traces.toStyledString().size();
m_blockTraceCache.put( key, traces, tracesSize );
auto tracesSize = traces.toStyledString().size();
m_blockTraceCache.put( key, traces, tracesSize );

return traces;
return traces;
} catch ( std::exception& e ) {
BOOST_THROW_EXCEPTION( std::runtime_error(
"Could not trace block:" + to_string( _blockNumber ) + ":" + e.what() ) );
}
}

#endif
Expand Down
85 changes: 70 additions & 15 deletions libhistoric/AlethStandardTrace.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ along with skaled. If not, see <http://www.gnu.org/licenses/>.
namespace dev::eth {

TraceOptions eth::AlethStandardTrace::getOptions() const {
STATE_CHECK( m_isFinalized )
return m_options;
}

Expand All @@ -35,6 +36,7 @@ void AlethStandardTrace::analyzeInstructionAndRecordNeededInformation( uint64_t,
const LegacyVM* _vm ) {
STATE_CHECK( _face )
STATE_CHECK( _vm )
STATE_CHECK( !m_isFinalized )

// check if instruction depth changed. This means a function has been called or has returned
processFunctionCallOrReturnIfHappened( _ext, _vm, ( uint64_t ) _gasRemaining );
Expand Down Expand Up @@ -105,6 +107,7 @@ void AlethStandardTrace::analyzeInstructionAndRecordNeededInformation( uint64_t,
// record the instruction
m_lastOpRecord = OpExecutionRecord( _ext.depth, _inst, _gasRemaining, _lastOpGas );
}

void AlethStandardTrace::processFunctionCallOrReturnIfHappened(
const AlethExtVM& _ext, const LegacyVM* _vm, uint64_t _gasRemaining ) {
STATE_CHECK( !m_isFinalized )
Expand All @@ -128,6 +131,7 @@ void AlethStandardTrace::processFunctionCallOrReturnIfHappened(
}
}
const Address& AlethStandardTrace::getFrom() const {
STATE_CHECK( m_isFinalized )
return m_from;
}

Expand All @@ -147,7 +151,6 @@ vector< uint8_t > AlethStandardTrace::extractSmartContractMemoryByteArrayFromSta
return result;
}


void AlethStandardTrace::recordFunctionIsCalled( const Address& _from, const Address& _to,
uint64_t _gasLimit, const vector< uint8_t >& _inputData, const u256& _value ) {
STATE_CHECK( !m_isFinalized )
Expand Down Expand Up @@ -178,13 +181,15 @@ void AlethStandardTrace::recordFunctionIsCalled( const Address& _from, const Add
void AlethStandardTrace::setTopFunctionCall(
const shared_ptr< FunctionCallRecord >& _topFunctionCall ) {
STATE_CHECK( _topFunctionCall )
STATE_CHECK( !m_isFinalized )
m_topFunctionCall = _topFunctionCall;
}

void AlethStandardTrace::recordFunctionReturned(
evmc_status_code _status, const vector< uint8_t >& _returnData, uint64_t _gasUsed ) {
STATE_CHECK( m_lastOpRecord.m_gasRemaining >= m_lastOpRecord.m_opGas )
STATE_CHECK( m_currentlyExecutingFunctionCall )
STATE_CHECK( !m_isFinalized )

// record return values
getCurrentlyExecutingFunctionCall()->setReturnValues( _status, _returnData, _gasUsed );
Expand All @@ -203,7 +208,6 @@ void AlethStandardTrace::recordFunctionReturned(
// the getter functions are called by printer classes after the trace has been generated
const shared_ptr< FunctionCallRecord >& AlethStandardTrace::getTopFunctionCall() const {
STATE_CHECK( m_isFinalized )
STATE_CHECK( m_topFunctionCall );
return m_topFunctionCall;
}

Expand All @@ -214,6 +218,11 @@ Json::Value AlethStandardTrace::getJSONResult() const {
return m_jsonTrace;
}

uint64_t AlethStandardTrace::getTotalGasUsed() const {
STATE_CHECK( m_isFinalized )
return m_totalGasUsed;
}

AlethStandardTrace::AlethStandardTrace(
Transaction& _t, const Address& _blockAuthor, const TraceOptions& _options, bool _isCall )
: m_defaultOpTrace{ std::make_shared< Json::Value >() },
Expand Down Expand Up @@ -243,12 +252,22 @@ AlethStandardTrace::AlethStandardTrace(
{ TraceType::FOUR_BYTE_TRACER, m_fourByteTracePrinter },
{ TraceType::NOOP_TRACER, m_noopTracePrinter } },
m_blockAuthor( _blockAuthor ),
m_isCall( _isCall ) {
m_isCall( _isCall ),
m_value( _t.value() ),
m_gasLimit( _t.gas() ),
m_inputData( _t.data() ),
m_gasPrice( _t.gasPrice() ) {
// mark from and to accounts as accessed
m_accessedAccounts.insert( m_from );
m_accessedAccounts.insert( m_to );
}

const u256& AlethStandardTrace::getGasLimit() const {
STATE_CHECK( m_isFinalized )
return m_gasLimit;
}
void AlethStandardTrace::setOriginalFromBalance( const u256& _originalFromBalance ) {
STATE_CHECK( !m_isFinalized )
m_originalFromBalance = _originalFromBalance;
}

Expand Down Expand Up @@ -361,24 +380,30 @@ string AlethStandardTrace::toGethCompatibleCompactHexPrefixed( const u256& _valu
return "0x" + hexStr;
}

// execution completed. Now use the tracer that the user requested
// to print the resulting trace
void eth::AlethStandardTrace::finalizeTrace(
// execution completed. Now finalize the trace and use the tracer that the user requested
// to print the resulting trace to json
void eth::AlethStandardTrace::finalizeAndPrintTrace(
ExecutionResult& _er, HistoricState& _statePre, HistoricState& _statePost ) {
auto totalGasUsed = ( uint64_t ) _er.gasUsed;
auto statusCode = AlethExtVM::transactionExceptionToEvmcStatusCode( _er.excepted );
m_totalGasUsed = ( uint64_t ) _er.gasUsed;

// we are done. Set the trace to finalized.
STATE_CHECK( !m_isFinalized.exchange( true ) )
auto statusCode = AlethExtVM::transactionExceptionToEvmcStatusCode( _er.excepted );

STATE_CHECK( m_topFunctionCall )
STATE_CHECK( m_topFunctionCall == m_currentlyExecutingFunctionCall )

// if transaction is not just ETH transfer
// record return of the top function.
recordFunctionReturned( statusCode, _er.output, totalGasUsed );
if ( m_topFunctionCall ) {
recordFunctionReturned( statusCode, _er.output, m_totalGasUsed );
}
// we are done. Set the trace to finalized
STATE_CHECK( !m_isFinalized.exchange( true ) )
// now print trace
printTrace( _er, _statePre, _statePost );
}

void eth::AlethStandardTrace::printTrace( ExecutionResult& _er, const HistoricState& _statePre,
const HistoricState& _statePost ) { // now print the trace
m_jsonTrace = Json::Value( Json::objectValue );

// now run the trace that the user wants based on options provided
if ( m_tracePrinters.count( m_options.tracerType ) > 0 ) {
m_tracePrinters.at( m_options.tracerType ).print( m_jsonTrace, _er, _statePre, _statePost );
Expand Down Expand Up @@ -427,33 +452,63 @@ const shared_ptr< Json::Value >& AlethStandardTrace::getDefaultOpTrace() const {

const shared_ptr< FunctionCallRecord >& AlethStandardTrace::getCurrentlyExecutingFunctionCall()
const {
STATE_CHECK( !m_isFinalized )
STATE_CHECK( m_currentlyExecutingFunctionCall )
return m_currentlyExecutingFunctionCall;
}

void AlethStandardTrace::setCurrentlyExecutingFunctionCall(
const shared_ptr< FunctionCallRecord >& _currentlyExecutingFunctionCall ) {
STATE_CHECK( !m_isFinalized )
STATE_CHECK( _currentlyExecutingFunctionCall )
m_currentlyExecutingFunctionCall = _currentlyExecutingFunctionCall;
}

const Address& AlethStandardTrace::getBlockAuthor() const {
STATE_CHECK( m_isFinalized )
return m_blockAuthor;
}

const u256& AlethStandardTrace::getMinerPayment() const {
STATE_CHECK( m_isFinalized )
return m_minerPayment;
}

void AlethStandardTrace::recordMinerPayment( u256 _minerGasPayment ) {
this->m_minerPayment = _minerGasPayment;
STATE_CHECK( !m_isFinalized )
m_minerPayment = _minerGasPayment;
// add miner to the list of accessed accounts, since the miner is paid
// transaction fee
this->m_accessedAccounts.insert( m_blockAuthor );
m_accessedAccounts.insert( m_blockAuthor );
}

bool AlethStandardTrace::isCall() const {
STATE_CHECK( m_isFinalized )
return m_isCall;
}

const u256& AlethStandardTrace::getOriginalFromBalance() const {
STATE_CHECK( m_isFinalized )
return m_originalFromBalance;
}

const bytes& AlethStandardTrace::getInputData() const {
STATE_CHECK( m_isFinalized )
return m_inputData;
}
const u256& AlethStandardTrace::getValue() const {
STATE_CHECK( m_isFinalized )
return m_value;
}
const Address& AlethStandardTrace::getTo() const {
STATE_CHECK( m_isFinalized )
return m_to;
}

const u256& AlethStandardTrace::getGasPrice() const {
STATE_CHECK( m_isFinalized )
return m_gasPrice;
}
} // namespace dev::eth

#endif
Loading

0 comments on commit da9bf68

Please sign in to comment.