diff --git a/libethereum/Block.cpp b/libethereum/Block.cpp index 0ce672045..61f198810 100644 --- a/libethereum/Block.cpp +++ b/libethereum/Block.cpp @@ -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 diff --git a/libethereum/Client.cpp b/libethereum/Client.cpp index f82f3e194..bd013a914 100644 --- a/libethereum/Client.cpp +++ b/libethereum/Client.cpp @@ -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 diff --git a/libhistoric/AlethStandardTrace.cpp b/libhistoric/AlethStandardTrace.cpp index 01ca179db..ea04315c5 100644 --- a/libhistoric/AlethStandardTrace.cpp +++ b/libhistoric/AlethStandardTrace.cpp @@ -27,6 +27,7 @@ along with skaled. If not, see . namespace dev::eth { TraceOptions eth::AlethStandardTrace::getOptions() const { + STATE_CHECK( m_isFinalized ) return m_options; } @@ -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 ); @@ -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 ) @@ -128,6 +131,7 @@ void AlethStandardTrace::processFunctionCallOrReturnIfHappened( } } const Address& AlethStandardTrace::getFrom() const { + STATE_CHECK( m_isFinalized ) return m_from; } @@ -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 ) @@ -178,6 +181,7 @@ 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; } @@ -185,6 +189,7 @@ 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 ); @@ -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; } @@ -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 >() }, @@ -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; } @@ -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 ); @@ -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 diff --git a/libhistoric/AlethStandardTrace.h b/libhistoric/AlethStandardTrace.h index 1d9eea6bc..ddd448fdb 100644 --- a/libhistoric/AlethStandardTrace.h +++ b/libhistoric/AlethStandardTrace.h @@ -64,7 +64,15 @@ class AlethStandardTrace { } // this function will be called at the end of executions - void finalizeTrace( ExecutionResult& _er, HistoricState& _statePre, HistoricState& _statePost ); + void finalizeAndPrintTrace( + ExecutionResult& _er, HistoricState& _statePre, HistoricState& _statePost ); + + + // this is to set original from balance for calls + // in a geth call, the from account balance is always incremented to + // make sure account has enough funds for block gas limit of gas + // we need to save original from account balance since it is printed in trace + void setOriginalFromBalance( const u256& _originalFromBalance ); [[nodiscard]] Json::Value getJSONResult() const; [[nodiscard]] const std::shared_ptr< FunctionCallRecord >& getTopFunctionCall() const; @@ -75,27 +83,27 @@ class AlethStandardTrace { [[nodiscard]] const h256& getTxHash() const; [[nodiscard]] const std::shared_ptr< Json::Value >& getDefaultOpTrace() const; - - void setCurrentlyExecutingFunctionCall( - const std::shared_ptr< FunctionCallRecord >& _currentlyExecutingFunctionCall ); - [[nodiscard]] const std::shared_ptr< FunctionCallRecord >& getCurrentlyExecutingFunctionCall() 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; - + [[nodiscard]] const Address& getFrom() const; + [[nodiscard]] uint64_t getTotalGasUsed() const; + [[nodiscard]] const u256& getGasLimit() const; + [[nodiscard]] const u256& getValue() const; + [[nodiscard]] const bytes& getInputData() const; + [[nodiscard]] const Address& getTo() const; static string toGethCompatibleCompactHexPrefixed( const u256& _value ); - const Address& getFrom() const; + private: + void setCurrentlyExecutingFunctionCall( + const std::shared_ptr< FunctionCallRecord >& _currentlyExecutingFunctionCall ); + void setTopFunctionCall( const std::shared_ptr< FunctionCallRecord >& _topFunctionCall ); + // this operator will be executed by skaled on each EVM instruction void operator()( uint64_t _steps, uint64_t _pc, Instruction _inst, bigint _newMemSize, bigint _gasOpGas, bigint _gasRemaining, VMFace const* _vm, ExtVMFace const* _voidExt ); @@ -152,6 +160,7 @@ class AlethStandardTrace { // std::map of all storage addresses accessed (read or write) during execution // for each storage address the current value if recorded std::map< Address, std::map< dev::u256, dev::u256 > > m_accessedStorageValues; + OpExecutionRecord m_lastOpRecord; std::atomic< bool > m_isFinalized = false; NoopTracePrinter m_noopTracePrinter; @@ -167,5 +176,18 @@ class AlethStandardTrace { u256 m_minerPayment; u256 m_originalFromBalance; bool m_isCall; + +public: + const u256& getGasPrice() const; + +private: + uint64_t m_totalGasUsed; + u256 m_value; + u256 m_gasLimit; + bytes m_inputData; + u256 m_gasPrice; + + void printTrace( + ExecutionResult& _er, const HistoricState& _statePre, const HistoricState& _statePost ); }; } // namespace dev::eth diff --git a/libhistoric/CallTracePrinter.cpp b/libhistoric/CallTracePrinter.cpp index 75f436a9a..7486b9f38 100644 --- a/libhistoric/CallTracePrinter.cpp +++ b/libhistoric/CallTracePrinter.cpp @@ -31,11 +31,41 @@ namespace dev::eth { void CallTracePrinter::print( Json::Value& _jsonTrace, const ExecutionResult&, const HistoricState&, const HistoricState& ) { STATE_CHECK( _jsonTrace.isObject() ) - m_trace.getTopFunctionCall()->printTrace( _jsonTrace, 0, m_trace.getOptions() ); + + auto topFunctionCallRecord = m_trace.getTopFunctionCall(); + if ( !topFunctionCallRecord ) { + // no bytecodes were executed + printTransferTrace( _jsonTrace ); + } else { + topFunctionCallRecord->printTrace( _jsonTrace, 0, m_trace.getOptions() ); + } } CallTracePrinter::CallTracePrinter( AlethStandardTrace& _standardTrace ) : TracePrinter( _standardTrace, "callTrace" ) {} + + +void CallTracePrinter::printTransferTrace( Json::Value& _jsonTrace ) { + STATE_CHECK( _jsonTrace.isObject() ) + + _jsonTrace["type"] = "CALL"; + _jsonTrace["from"] = toHexPrefixed( m_trace.getFrom() ); + _jsonTrace["to"] = toHexPrefixed( m_trace.getTo() ); + + _jsonTrace["gas"] = + AlethStandardTrace::toGethCompatibleCompactHexPrefixed( m_trace.getGasLimit() ); + _jsonTrace["gasUsed"] = + AlethStandardTrace::toGethCompatibleCompactHexPrefixed( m_trace.getTotalGasUsed() ); + + + _jsonTrace["value"] = + AlethStandardTrace::toGethCompatibleCompactHexPrefixed( m_trace.getValue() ); + + _jsonTrace["input"] = toHexPrefixed( m_trace.getInputData() ); +} + + } // namespace dev::eth + #endif \ No newline at end of file diff --git a/libhistoric/CallTracePrinter.h b/libhistoric/CallTracePrinter.h index 83239c2bb..a12d29de9 100644 --- a/libhistoric/CallTracePrinter.h +++ b/libhistoric/CallTracePrinter.h @@ -36,5 +36,8 @@ class CallTracePrinter : public TracePrinter { void print( Json::Value& _jsonTrace, const ExecutionResult&, const HistoricState&, const HistoricState& ) override; + +private: + void printTransferTrace( Json::Value& _jsonTrace ); }; } // namespace dev::eth diff --git a/libhistoric/DefaultTracePrinter.cpp b/libhistoric/DefaultTracePrinter.cpp index 745e926bc..9cda7342b 100644 --- a/libhistoric/DefaultTracePrinter.cpp +++ b/libhistoric/DefaultTracePrinter.cpp @@ -34,7 +34,13 @@ void DefaultTracePrinter::print( Json::Value& _jsonTrace, const ExecutionResult& _jsonTrace["gas"] = ( uint64_t ) _er.gasUsed; auto defaultOpTrace = m_trace.getDefaultOpTrace(); STATE_CHECK( defaultOpTrace ); - _jsonTrace["structLogs"] = *defaultOpTrace; + if ( defaultOpTrace->empty() ) { + // make it compatible with geth in cases where + // no contract was called so there is no trace + _jsonTrace["structLogs"] = Json::Value( Json::arrayValue ); + } else { + _jsonTrace["structLogs"] = *defaultOpTrace; + } auto failed = _er.excepted != TransactionException::None; _jsonTrace["failed"] = failed; if ( !failed ) { diff --git a/libhistoric/FourByteTracePrinter.cpp b/libhistoric/FourByteTracePrinter.cpp index 165163e0e..b57dc8c03 100644 --- a/libhistoric/FourByteTracePrinter.cpp +++ b/libhistoric/FourByteTracePrinter.cpp @@ -33,7 +33,14 @@ void FourByteTracePrinter::print( STATE_CHECK( _jsonTrace.isObject() ) std::map< string, uint64_t > callMap; - m_trace.getTopFunctionCall()->collectFourByteTrace( callMap ); + auto topFunctionCallRecord = m_trace.getTopFunctionCall(); + if ( !topFunctionCallRecord ) { + // no bytecodes were executed, this was purely ETH transfer + // print nothing + return; + } + + topFunctionCallRecord->collectFourByteTrace( callMap ); for ( auto&& key : callMap ) { _jsonTrace[key.first] = key.second; } diff --git a/libhistoric/PrestateTracePrinter.cpp b/libhistoric/PrestateTracePrinter.cpp index 92b6b057c..cf85a993d 100644 --- a/libhistoric/PrestateTracePrinter.cpp +++ b/libhistoric/PrestateTracePrinter.cpp @@ -32,28 +32,19 @@ void PrestateTracePrinter::print( Json::Value& _jsonTrace, const ExecutionResult if ( m_trace.getOptions().prestateDiffMode ) { printDiff( _jsonTrace, _er, _statePre, _statePost ); } else { - printPre( _jsonTrace, _statePre ); + printPre( _jsonTrace, _statePre, _statePost ); } } -void PrestateTracePrinter::printPre( Json::Value& _jsonTrace, const HistoricState& _statePre ) { +void PrestateTracePrinter::printPre( + Json::Value& _jsonTrace, const HistoricState& _statePre, const HistoricState& _statePost ) { for ( auto&& item : m_trace.getAccessedAccounts() ) { - printAllAccessedAccountPreValues( _jsonTrace, _statePre, item ); + printAllAccessedAccountPreValues( _jsonTrace, _statePre, _statePost, item ); }; + // geth always prints the balance of block miner balance - if ( !m_trace.isCall() ) - return; - - // when in call trace geth always prints the balance of block miner balance - - auto minerAddress = m_trace.getBlockAuthor(); - auto minerBalance = _statePre.balance( minerAddress ); - - if ( minerAddress == m_trace.getFrom() ) { - // take into account that for calls balance is modified in the state before execution - minerBalance = m_trace.getOriginalFromBalance(); - } - + Address minerAddress = m_trace.getBlockAuthor(); + u256 minerBalance = getMinerBalancePre( _statePre ); _jsonTrace[toHexPrefixed( minerAddress )]["balance"] = AlethStandardTrace::toGethCompatibleCompactHexPrefixed( minerBalance ); } @@ -71,14 +62,34 @@ void PrestateTracePrinter::printDiff( Json::Value& _jsonTrace, const ExecutionRe printAccountPostDiff( postDiff, _statePre, _statePost, item ); }; + + // now deal with miner balance change as a result of transaction + // geth always prints miner balance change when NOT in call + if ( !m_trace.isCall() ) { + printMinerBalanceChange( _statePre, preDiff, postDiff ); + } + + // we are done, complete the trace JSON + _jsonTrace["pre"] = preDiff; _jsonTrace["post"] = postDiff; } +void PrestateTracePrinter::printMinerBalanceChange( + const HistoricState& _statePre, Json::Value& preDiff, Json::Value& postDiff ) const { + Address minerAddress = m_trace.getBlockAuthor(); + u256 minerBalancePre = getMinerBalancePre( _statePre ); + u256 minerBalancePost = getMinerBalancePost( _statePre ); + + preDiff[toHexPrefixed( minerAddress )]["balance"] = + AlethStandardTrace::toGethCompatibleCompactHexPrefixed( minerBalancePre ); + postDiff[toHexPrefixed( minerAddress )]["balance"] = + AlethStandardTrace::toGethCompatibleCompactHexPrefixed( minerBalancePost ); +} // this function returns original values (pre) to result -void PrestateTracePrinter::printAllAccessedAccountPreValues( - Json::Value& _jsonTrace, const HistoricState& _statePre, const Address& _address ) { +void PrestateTracePrinter::printAllAccessedAccountPreValues( Json::Value& _jsonTrace, + const HistoricState& _statePre, const HistoricState& _statePost, const Address& _address ) { STATE_CHECK( _jsonTrace.isObject() ) @@ -89,12 +100,22 @@ void PrestateTracePrinter::printAllAccessedAccountPreValues( auto balance = _statePre.balance( _address ); + // take into account that for calls balance is modified in the state before execution if ( m_trace.isCall() && _address == m_trace.getFrom() ) { - // take into account that for calls balance is modified in the state before execution balance = m_trace.getOriginalFromBalance(); - } else { - // geth does not print nonce for from address in debug_traceCall; - accountPreValues["nonce"] = ( uint64_t ) _statePre.getNonce( _address ); + } + + + // geth does not print nonce for from address in debug_traceCall; + bool dontPrintNonce = m_trace.isCall() && _address == m_trace.getFrom(); + + if ( !dontPrintNonce ) { + auto preNonce = ( uint64_t ) _statePre.getNonce( _address ); + auto postNonce = ( uint64_t ) _statePost.getNonce( _address ); + // in calls nonce is always printed by geth + if ( postNonce != preNonce || m_trace.isCall() ) { + accountPreValues["nonce"] = preNonce; + } } accountPreValues["balance"] = AlethStandardTrace::toGethCompatibleCompactHexPrefixed( balance ); @@ -276,6 +297,24 @@ PrestateTracePrinter::PrestateTracePrinter( AlethStandardTrace& standardTrace ) : TracePrinter( standardTrace, "prestateTrace" ) {} +u256 PrestateTracePrinter::getMinerBalancePre( const HistoricState& _statePre ) const { + auto minerAddress = m_trace.getBlockAuthor(); + auto minerBalance = _statePre.balance( minerAddress ); + + if ( m_trace.isCall() && minerAddress == m_trace.getFrom() ) { + // take into account that for calls balance is modified in the state before execution + minerBalance = m_trace.getOriginalFromBalance(); + } + + return minerBalance; +} + +u256 PrestateTracePrinter::getMinerBalancePost( const HistoricState& _statePre ) const { + auto minerBalance = + getMinerBalancePre( _statePre ) + m_trace.getTotalGasUsed() * m_trace.getGasPrice(); + return minerBalance; +} + } // namespace dev::eth #endif \ No newline at end of file diff --git a/libhistoric/PrestateTracePrinter.h b/libhistoric/PrestateTracePrinter.h index e859b50ee..f9e8d9fed 100644 --- a/libhistoric/PrestateTracePrinter.h +++ b/libhistoric/PrestateTracePrinter.h @@ -38,12 +38,15 @@ class PrestateTracePrinter : public TracePrinter { explicit PrestateTracePrinter( AlethStandardTrace& standardTrace ); + [[nodiscard]] u256 getMinerBalancePre( const HistoricState& _statePre ) const; + [[nodiscard]] u256 getMinerBalancePost( const HistoricState& _statePre ) const; + private: void printDiff( Json::Value& _jsonTrace, const ExecutionResult&, const HistoricState& _statePre, const HistoricState& _statePost ); - void printAllAccessedAccountPreValues( - Json::Value& _jsonTrace, const HistoricState& _statePre, const Address& _address ); + void printAllAccessedAccountPreValues( Json::Value& _jsonTrace, const HistoricState& _statePre, + const HistoricState& _statePost, const Address& _address ); void printAccountPreDiff( Json::Value& _preDiffTrace, const HistoricState& _statePre, const HistoricState& _statePost, const Address& _address ); @@ -54,6 +57,10 @@ class PrestateTracePrinter : public TracePrinter { uint64_t m_storageValuesReturnedPre = 0; uint64_t m_storageValuesReturnedPost = 0; uint64_t m_storageValuesReturnedAll = 0; - void printPre( Json::Value& _jsonTrace, const HistoricState& _statePre ); + void printPre( + Json::Value& _jsonTrace, const HistoricState& _statePre, const HistoricState& _statePost ); + + void printMinerBalanceChange( + const HistoricState& _statePre, Json::Value& preDiff, Json::Value& postDiff ) const; }; } // namespace dev::eth diff --git a/libhistoric/ReplayTracePrinter.cpp b/libhistoric/ReplayTracePrinter.cpp index 5d465ef56..f6d08f78d 100644 --- a/libhistoric/ReplayTracePrinter.cpp +++ b/libhistoric/ReplayTracePrinter.cpp @@ -45,7 +45,15 @@ void ReplayTracePrinter::print( Json::Value& _jsonTrace, const ExecutionResult& Json::Value functionTraceArray( Json::arrayValue ); Json::Value emptyAddress( Json::arrayValue ); - m_trace.getTopFunctionCall()->printParityFunctionTrace( functionTraceArray, emptyAddress ); + auto topFunctionCallRecord = m_trace.getTopFunctionCall(); + + + // if topFunctionCallRecord is null + // it means that no bytecodes were executed, this was purely ETH transfer + // print nothing + if ( topFunctionCallRecord ) { + topFunctionCallRecord->printParityFunctionTrace( functionTraceArray, emptyAddress ); + } _jsonTrace["trace"] = functionTraceArray; } diff --git a/libhistoric/TraceOptions.h b/libhistoric/TraceOptions.h index 51fa8923a..92e8c4d66 100644 --- a/libhistoric/TraceOptions.h +++ b/libhistoric/TraceOptions.h @@ -49,6 +49,7 @@ class TraceOptions { [[nodiscard]] std::string toString() { std::stringstream s; + s << ( uint64_t ) tracerType; s << disableStorage; s << enableMemory; s << disableStack; diff --git a/libhistoric/TracePrinter.h b/libhistoric/TracePrinter.h index eb29ddd00..61047a183 100644 --- a/libhistoric/TracePrinter.h +++ b/libhistoric/TracePrinter.h @@ -35,7 +35,7 @@ class AlethStandardTrace; class TracePrinter { public: - TracePrinter( AlethStandardTrace& mStandardTrace, const std::string jsonName ); + TracePrinter( AlethStandardTrace& _standardTrace, const std::string jsonName ); virtual void print( Json::Value& _jsonTrace, const ExecutionResult&, const HistoricState&, const HistoricState& ) = 0; diff --git a/libweb3jsonrpc/Debug.cpp b/libweb3jsonrpc/Debug.cpp index 4f7e97839..47128b129 100644 --- a/libweb3jsonrpc/Debug.cpp +++ b/libweb3jsonrpc/Debug.cpp @@ -23,6 +23,12 @@ using namespace dev::rpc; using namespace dev::eth; using namespace skale; + +#define THROW_TRACE_JSON_EXCEPTION( __MSG__ ) \ + throw jsonrpc::JsonRpcException( std::string( __FUNCTION__ ) + ":" + \ + std::to_string( __LINE__ ) + ":" + std::string( __MSG__ ) ) + + void Debug::checkPrivilegedAccess() const { if ( !m_enablePrivilegedApis ) { BOOST_THROW_EXCEPTION( jsonrpc::JsonRpcException( "This API call is not enabled" ) ); @@ -52,7 +58,7 @@ h256 Debug::blockHash( string const& _blockNumberOrHash ) const { try { return m_eth.blockChain().numberHash( stoul( _blockNumberOrHash ) ); } catch ( ... ) { - BOOST_THROW_EXCEPTION( jsonrpc::JsonRpcException( "Invalid argument" ) ); + THROW_TRACE_JSON_EXCEPTION( "Invalid argument" ); } } @@ -76,22 +82,22 @@ Json::Value Debug::debug_traceBlockByNumber( const string& } if ( !m_eth.isKnown( bN ) ) { - BOOST_THROW_EXCEPTION( - jsonrpc::JsonRpcException( "Unknown block number:" + _blockNumber ) ); + THROW_TRACE_JSON_EXCEPTION( "Unknown block number:" + _blockNumber ); } if ( bN == 0 ) { - BOOST_THROW_EXCEPTION( jsonrpc::JsonRpcException( "Block number must be more than zero" ) ); + THROW_TRACE_JSON_EXCEPTION( "Block number must be more than zero" ); } try { return m_eth.traceBlock( bN, _jsonTraceConfig ); - } catch ( Exception const& _e ) { - BOOST_THROW_EXCEPTION( jsonrpc::JsonRpcException( _e.what() ) ); + } catch ( std::exception const& _e ) { + THROW_TRACE_JSON_EXCEPTION( _e.what() ); + } catch ( ... ) { + THROW_TRACE_JSON_EXCEPTION( "Unknown server error" ); } #else - BOOST_THROW_EXCEPTION( - jsonrpc::JsonRpcException( "This API call is only supported on archive nodes" ) ); + THROW_TRACE_JSON_EXCEPTION( "This API call is only supported on archive nodes" ); #endif } @@ -111,23 +117,24 @@ Json::Value Debug::debug_traceBlockByHash( string const& h256 h = jsToFixed< 32 >( _blockHash ); if ( !m_eth.isKnown( h ) ) { - BOOST_THROW_EXCEPTION( jsonrpc::JsonRpcException( "Unknown block hash" ) ); + THROW_TRACE_JSON_EXCEPTION( "Unknown block hash" + _blockHash ); } BlockNumber bN = m_eth.numberFromHash( h ); if ( bN == 0 ) { - BOOST_THROW_EXCEPTION( jsonrpc::JsonRpcException( "Block number must be more than zero" ) ); + THROW_TRACE_JSON_EXCEPTION( "Block number must be more than zero" ); } try { return m_eth.traceBlock( bN, _jsonTraceConfig ); - } catch ( Exception const& _e ) { - BOOST_THROW_EXCEPTION( jsonrpc::JsonRpcException( _e.what() ) ); + } catch ( std::exception const& _e ) { + THROW_TRACE_JSON_EXCEPTION( _e.what() ); + } catch ( ... ) { + THROW_TRACE_JSON_EXCEPTION( "Unknown server error" ); } #else - BOOST_THROW_EXCEPTION( - jsonrpc::JsonRpcException( "This API call is only supported on archive nodes" ) ); + THROW_TRACE_JSON_EXCEPTION( "This API call is only supported on archive nodes" ); #endif } @@ -150,23 +157,24 @@ Json::Value Debug::debug_traceTransaction( string const& LocalisedTransaction localisedTransaction = m_eth.localisedTransaction( txHash ); if ( localisedTransaction.blockHash() == h256( 0 ) ) { - BOOST_THROW_EXCEPTION( - jsonrpc::JsonRpcException( "no committed transaction with this hash" ) ); + THROW_TRACE_JSON_EXCEPTION( + "Can't find committed transaction with this hash:" + _txHashStr ); } auto blockNumber = localisedTransaction.blockNumber(); + if ( !m_eth.isKnown( blockNumber ) ) { - BOOST_THROW_EXCEPTION( jsonrpc::JsonRpcException( "Unknown block number" ) ); + THROW_TRACE_JSON_EXCEPTION( "Unknown block number:" + to_string( blockNumber ) ); } if ( blockNumber == 0 ) { - BOOST_THROW_EXCEPTION( jsonrpc::JsonRpcException( "Block number must be more than zero" ) ); + THROW_TRACE_JSON_EXCEPTION( "Block number must be more than zero" ); } - try { Json::Value tracedBlock; + tracedBlock = m_eth.traceBlock( blockNumber, _jsonTraceConfig ); STATE_CHECK( tracedBlock.isArray() ) STATE_CHECK( !tracedBlock.empty() ) @@ -188,10 +196,14 @@ Json::Value Debug::debug_traceTransaction( string const& } } - BOOST_THROW_EXCEPTION( jsonrpc::JsonRpcException( "No transaction in block" ) ); + THROW_TRACE_JSON_EXCEPTION( "Transaction not found in block" ); - } catch ( Exception const& _e ) { - BOOST_THROW_EXCEPTION( jsonrpc::JsonRpcException( _e.what() ) ); + } catch ( jsonrpc::JsonRpcException& ) { + throw; + } catch ( std::exception const& _e ) { + THROW_TRACE_JSON_EXCEPTION( _e.what() ); + } catch ( ... ) { + THROW_TRACE_JSON_EXCEPTION( "Unknown server error" ); } #else BOOST_THROW_EXCEPTION( @@ -228,21 +240,23 @@ Json::Value Debug::debug_traceCall( Json::Value const& } if ( !m_eth.isKnown( bN ) ) { - BOOST_THROW_EXCEPTION( - jsonrpc::JsonRpcException( "Unknown block number:" + _blockNumber ) ); + THROW_TRACE_JSON_EXCEPTION( "Unknown block number:" + _blockNumber ); } if ( bN == 0 ) { - BOOST_THROW_EXCEPTION( - jsonrpc::JsonRpcException( "Block number must be more than zero" ) ); + THROW_TRACE_JSON_EXCEPTION( "Block number must be more than zero" ); } TransactionSkeleton ts = toTransactionSkeleton( _call ); return m_eth.traceCall( ts.from, ts.value, ts.to, ts.data, ts.gas, ts.gasPrice, bN, _jsonTraceConfig ); - } catch ( Exception const& _e ) { - BOOST_THROW_EXCEPTION( jsonrpc::JsonRpcException( _e.what() ) ); + } catch ( jsonrpc::JsonRpcException& ) { + throw; + } catch ( std::exception const& _e ) { + THROW_TRACE_JSON_EXCEPTION( _e.what() ); + } catch ( ... ) { + THROW_TRACE_JSON_EXCEPTION( "Unknown server error" ); } #else diff --git a/test/historicstate/configs/basic_config.json b/test/historicstate/configs/basic_config.json index aa5915277..d34997231 100644 --- a/test/historicstate/configs/basic_config.json +++ b/test/historicstate/configs/basic_config.json @@ -326,6 +326,7 @@ "schainOwner": "0x907cd0881E50d359bb9Fd120B1A5A143b1C97De6", "contractStorageLimit": 10000000000, "emptyBlockIntervalMs": 10000, + "multiTransactionMode": true, "nodes": [ { "nodeID": 1112, "ip": "127.0.0.1", "basePort": 1231, "schainIndex" : 1, "publicKey":""} ] diff --git a/test/historicstate/hardhat/scripts/geth_traces/Tracer.getBalance.prestateTracer.json b/test/historicstate/hardhat/scripts/geth_traces/Tracer.getBalance.prestateTracer.json index 42700d1ff..d154d2087 100644 --- a/test/historicstate/hardhat/scripts/geth_traces/Tracer.getBalance.prestateTracer.json +++ b/test/historicstate/hardhat/scripts/geth_traces/Tracer.getBalance.prestateTracer.json @@ -1,6 +1,6 @@ { "0x0000000000000000000000000000000000000000": { - "balance": "0x1085583" + "balance": "0x439f07a" }, "Tracer.address": { "balance": "0x0", diff --git a/test/historicstate/hardhat/scripts/geth_traces/Tracer.getBalance.replayTracer.json b/test/historicstate/hardhat/scripts/geth_traces/Tracer.getBalance.replayTracer.json new file mode 100644 index 000000000..0c33b42dd --- /dev/null +++ b/test/historicstate/hardhat/scripts/geth_traces/Tracer.getBalance.replayTracer.json @@ -0,0 +1,25 @@ +{ + "output": "0x0000000000000000000000000000000000000000000000000000000000000000", + "stateDiff": null, + "trace": [ + { + "action": { + "callType": "call", + "from": "CALL.address", + "gas": "0x0000000000000000000000000000000000000000000000000000000ffffface7", + "input": "0x12065fe0", + "to": "Tracer.address", + "value": "0x0000000000000000000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x000000000000000000000000000000000000000000000000000000000000551b", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000" + }, + "subtraces": 0, + "traceAddress": [], + "type": "call" + } + ], + "transactionHash": "0x58aa03e29bf344b31f0e65d3f28ce2d5cab1159795083ad942f1d57f42a5e6f2", + "vmTrace": null +} \ No newline at end of file diff --git a/test/historicstate/hardhat/scripts/geth_traces/Tracer.transfer.4byteTracer.json b/test/historicstate/hardhat/scripts/geth_traces/Tracer.transfer.4byteTracer.json new file mode 100644 index 000000000..9e26dfeeb --- /dev/null +++ b/test/historicstate/hardhat/scripts/geth_traces/Tracer.transfer.4byteTracer.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/test/historicstate/hardhat/scripts/geth_traces/Tracer.transfer.callTracer.json b/test/historicstate/hardhat/scripts/geth_traces/Tracer.transfer.callTracer.json new file mode 100644 index 000000000..bd28f6087 --- /dev/null +++ b/test/historicstate/hardhat/scripts/geth_traces/Tracer.transfer.callTracer.json @@ -0,0 +1,9 @@ +{ + "from": "0x907cd0881e50d359bb9fd120b1a5a143b1c97de6", + "gas": "0x5208", + "gasUsed": "0x5208", + "to": "0x388c818ca8b9251b393131c08a736a67ccb19297", + "input": "0x", + "value": "0x16345785d8a0000", + "type": "CALL" +} \ No newline at end of file diff --git a/test/historicstate/hardhat/scripts/geth_traces/Tracer.transfer.defaultTracer.json b/test/historicstate/hardhat/scripts/geth_traces/Tracer.transfer.defaultTracer.json new file mode 100644 index 000000000..9b0ab9d23 --- /dev/null +++ b/test/historicstate/hardhat/scripts/geth_traces/Tracer.transfer.defaultTracer.json @@ -0,0 +1,6 @@ +{ + "gas": 21000, + "failed": false, + "returnValue": "", + "structLogs": [] +} \ No newline at end of file diff --git a/test/historicstate/hardhat/scripts/geth_traces/Tracer.transfer.prestateDiffTracer.json b/test/historicstate/hardhat/scripts/geth_traces/Tracer.transfer.prestateDiffTracer.json new file mode 100644 index 000000000..006f60d67 --- /dev/null +++ b/test/historicstate/hardhat/scripts/geth_traces/Tracer.transfer.prestateDiffTracer.json @@ -0,0 +1,26 @@ +{ + "post": { + "0x0000000000000000000000000000000000000000": { + "balance": "0x439f07a" + }, + "0x388c818ca8b9251b393131c08a736a67ccb19297": { + "balance": "0x14d1120d7b160000" + }, + "0x907cd0881e50d359bb9fd120b1a5a143b1c97de6": { + "balance": "0x35a999048b57805303", + "nonce": 359 + } + }, + "pre": { + "0x0000000000000000000000000000000000000000": { + "balance": "0x4399e72" + }, + "0x388c818ca8b9251b393131c08a736a67ccb19297": { + "balance": "0x136dcc951d8c0000" + }, + "0x907cd0881e50d359bb9fd120b1a5a143b1c97de6": { + "balance": "0x35aafc4a03b50d354b", + "nonce": 358 + } + } +} \ No newline at end of file diff --git a/test/historicstate/hardhat/scripts/geth_traces/Tracer.transfer.prestateTracer.json b/test/historicstate/hardhat/scripts/geth_traces/Tracer.transfer.prestateTracer.json new file mode 100644 index 000000000..0749f6dab --- /dev/null +++ b/test/historicstate/hardhat/scripts/geth_traces/Tracer.transfer.prestateTracer.json @@ -0,0 +1,12 @@ +{ + "0x0000000000000000000000000000000000000000": { + "balance": "0x4399e72" + }, + "0x388c818ca8b9251b393131c08a736a67ccb19297": { + "balance": "0x136dcc951d8c0000" + }, + "0x907cd0881e50d359bb9fd120b1a5a143b1c97de6": { + "balance": "0x35aafc4a03b50d354b", + "nonce": 358 + } +} \ No newline at end of file diff --git a/test/historicstate/hardhat/scripts/trace.ts b/test/historicstate/hardhat/scripts/trace.ts index a443bef56..b4a8cc4a6 100644 --- a/test/historicstate/hardhat/scripts/trace.ts +++ b/test/historicstate/hardhat/scripts/trace.ts @@ -6,6 +6,7 @@ import deepDiff, {diff} from 'deep-diff'; import {expect} from "chai"; import * as path from 'path'; import {int, string} from "hardhat/internal/core/params/argumentTypes"; +import internal from "node:stream"; const OWNER_ADDRESS: string = "0x907cd0881E50d359bb9Fd120B1A5A143b1C97De6"; const CALL_ADDRESS: string = "0xCe5c7ca85F8cB94FA284a303348ef42ADD23f5e7"; @@ -23,7 +24,7 @@ let DEFAULT_TRACER = "defaultTracer"; let CALL_TRACER = "callTracer"; let PRESTATE_TRACER = "prestateTracer"; let PRESTATEDIFF_TRACER = "prestateDiffTracer"; -let FOUR_BYTE_TRACER = "4byteTracer"; +let FOURBYTE_TRACER = "4byteTracer"; let REPLAY_TRACER = "replayTracer" @@ -41,6 +42,13 @@ async function getTraceJsonOptions(_tracer: string): Promise { const TEST_CONTRACT_DEPLOY_FILE_NAME = TEST_CONTRACT_NAME + ".deploy.defaultTracer.json"; const TEST_CONTRACT_RUN_FILE_NAME = TEST_CONTRACT_NAME + "." + RUN_FUNCTION_NAME + ".defaultTracer.json"; +const TEST_TRANSFER_DEFAULTTRACER_FILE_NAME = TEST_CONTRACT_NAME + ".transfer.defaultTracer.json"; +const TEST_TRANSFER_CALLTRACER_FILE_NAME = TEST_CONTRACT_NAME + ".transfer.callTracer.json"; +const TEST_TRANSFER_PRESTATETRACER_FILE_NAME = TEST_CONTRACT_NAME + ".transfer.prestateTracer.json"; +const TEST_TRANSFER_PRESTATEDIFFTRACER_FILE_NAME = TEST_CONTRACT_NAME + ".transfer.prestateDiffTracer.json"; +const TEST_TRANSFER_FOURBYTETRACER_FILE_NAME = TEST_CONTRACT_NAME + ".transfer.4byteTracer.json"; + + const TEST_CONTRACT_CALL_DEFAULTTRACER_FILE_NAME = TEST_CONTRACT_NAME + "." + CALL_FUNCTION_NAME + ".defaultTracer.json"; const TEST_CONTRACT_CALL_CALLTRACER_FILE_NAME = TEST_CONTRACT_NAME + "." + CALL_FUNCTION_NAME + ".callTracer.json"; const TEST_CONTRACT_CALL_PRESTATETRACER_FILE_NAME = TEST_CONTRACT_NAME + "." + CALL_FUNCTION_NAME + ".prestateTracer.json"; @@ -137,31 +145,15 @@ function CHECK(result: any): void { } -async function getAndPrintBlockTrace(blockNumber: number): Promise { +async function getBlockTrace(blockNumber: number): Promise { const blockStr = "0x" + blockNumber.toString(16); const trace = await ethers.provider.send('debug_traceBlockByNumber', [blockStr, {}]); - console.log(JSON.stringify(trace, null, 4)); + 0// console.log(JSON.stringify(trace, null, 4)); return trace; } -async function getAndPrintTrace(hash: string, _skaleFileName: string): Promise { -// const trace = await ethers.provider.send('debug_traceTransaction', [hash, {"tracer":"prestateTracer", -// "tracerConfig": {"diffMode":true}}]); - -// const trace = await ethers.provider.send('debug_traceTransaction', [hash, {"tracer": "callTracer", -// "tracerConfig": {"withLog":true}}]); - - console.log("Calling debug_traceTransaction to generate " + _skaleFileName); - - const trace = await ethers.provider.send('debug_traceTransaction', [hash, {}]); - - const result = JSON.stringify(trace, null, 4); - writeFileSync(SKALE_TRACES_DIR + _skaleFileName, result); - - return trace; -} async function deployTestContract(): Promise { @@ -178,28 +170,101 @@ async function deployTestContract(): Promise { const hash = deployedTestContract.deployTransaction.hash; console.log(`Contract deployed to ${deployedTestContract.address} at block ${deployBlockNumber.toString(16)} tx hash ${hash}`); - // await waitUntilNextBlock() - - //await getAndPrintBlockTrace(deployBlockNumber); - //await getAndPrintBlockTrace(deployBlockNumber); - await getAndPrintTrace(hash, TEST_CONTRACT_DEPLOY_FILE_NAME); + await getAndPrintCommittedTransactionTrace(hash, DEFAULT_TRACER, TEST_CONTRACT_DEPLOY_FILE_NAME); return deployedTestContract; } -async function callTestContractRun(deployedContract: any): Promise { +function generateNewWallet() { + const wallet = hre.ethers.Wallet.createRandom(); + console.log("Address:", wallet.address); + console.log("Private Key:", wallet.privateKey); + return wallet; +} + +function sleep(ms: number) { + return new Promise(resolve => setTimeout(resolve, ms)); +} + + +async function sendMoneyWithoutConfirmation(): Promise { + // Generate a new wallet + const newWallet = generateNewWallet(); + + await sleep(3000); // Sleep for 1000 milliseconds (1 second) + + // Get the first signer from Hardhat's local network + const [signer] = await hre.ethers.getSigners(); + + const currentNonce = await signer.getTransactionCount(); + + // Define the transaction + const tx = { + to: newWallet.address, + value: hre.ethers.utils.parseEther("0.1"), + nonce: currentNonce + }; + + // Send the transaction and wait until it is submitted ot the queue + const txResponse = signer.sendTransaction(tx); + + if (hre.network.name == "geth") { + await txResponse; + } + + console.log(`Submitted a tx to send 0.1 ETH to ${newWallet.address}`); + + return currentNonce; +} + +async function sendMoneyWithConfirmation(): Promise { + // Generate a new wallet + const newWallet = generateNewWallet(); + + await sleep(3000); // Sleep for 1000 milliseconds (1 second) + + // Get the first signer from Hardhat's local network + const [signer] = await hre.ethers.getSigners(); + + const currentNonce = await signer.getTransactionCount(); + + // Define the transaction + const tx = { + to: "0x388C818CA8B9251b393131C08a736A67ccB19297", + value: hre.ethers.utils.parseEther("0.1"), + }; + + // Send the transaction and wait until it is submitted ot the queue + const txResponse = await signer.sendTransaction(tx); + const txReceipt = await txResponse.wait(); + + console.log(`Submitted a tx to send 0.1 ETH to ${newWallet.address}`); + + return txReceipt.transactionHash!; +} + + +async function callTestContractRun(deployedContract: any): Promise { + + let currentNonce: int = await sendMoneyWithoutConfirmation(); const transferReceipt = await deployedContract[RUN_FUNCTION_NAME](1000, { gasLimit: 2100000, // this is just an example value; you'll need to set an appropriate gas limit for your specific function call + nonce: currentNonce + 1, }); - //await getAndPrintBlockTrace(transferReceipt.blockNumber); - //await getAndPrintBlockTrace(transferReceipt.blockNumber); - // + expect(transferReceipt.blockNumber).not.to.be.null; + + + await getAndPrintCommittedTransactionTrace(transferReceipt.hash, DEFAULT_TRACER, TEST_CONTRACT_RUN_FILE_NAME); + await getBlockTrace(transferReceipt.blockNumber); + + const transferHash: string = await sendMoneyWithConfirmation(); + + return transferHash; - await getAndPrintTrace(transferReceipt.hash, TEST_CONTRACT_RUN_FILE_NAME); } @@ -249,6 +314,20 @@ async function callDebugTraceCall(_deployedContract: any, _tracer: string, _trac } +async function getAndPrintCommittedTransactionTrace(hash: string, _tracer: string, _skaleFileName: string): Promise { + + let traceOptions = await getTraceJsonOptions(_tracer); + + console.log("Calling debug_traceTransaction to generate " + _skaleFileName); + + const trace = await ethers.provider.send('debug_traceTransaction', [hash, traceOptions]); + + const result = JSON.stringify(trace, null, 4); + writeFileSync(SKALE_TRACES_DIR + _skaleFileName, result); + + return trace; +} + async function readJSONFile(fileName: string): Promise { return new Promise((resolve, reject) => { readFile(fileName, 'utf8', (err, data) => { @@ -269,7 +348,7 @@ async function readJSONFile(fileName: string): Promise { async function verifyDefaultTraceAgainstGethTrace(_fileName: string) { - console.log("Verifying " + _fileName); + console.log("Verifying " + _fileName); const _expectedResultFileName = GETH_TRACES_DIR + _fileName; const _actualResultFileName = SKALE_TRACES_DIR + _fileName; @@ -317,10 +396,119 @@ async function verifyDefaultTraceAgainstGethTrace(_fileName: string) { await expect(foundDiffs).to.be.eq(false) } +async function verifyTransferTraceAgainstGethTrace(_fileName: string) { + + console.log("Verifying " + _fileName); + + const _expectedResultFileName = GETH_TRACES_DIR + _fileName; + const _actualResultFileName = SKALE_TRACES_DIR + _fileName; + + let expectedResult = await readJSONFile(_expectedResultFileName) + let actualResult = await readJSONFile(_actualResultFileName) + + const differences = deepDiff(expectedResult, actualResult)!; + + let foundDiffs = false; + + + if (differences) { + differences.forEach((difference, index) => { + foundDiffs = true; + }); + } + ; + + await expect(foundDiffs).to.be.eq(false) +} + +async function verifyPrestateTransferTraceAgainstGethTrace(_fileName: string) { + + console.log("Verifying " + _fileName); + + const _expectedResultFileName = GETH_TRACES_DIR + _fileName; + const _actualResultFileName = SKALE_TRACES_DIR + _fileName; + + let expectedResult = await readJSONFile(_expectedResultFileName) + let actualResult = await readJSONFile(_actualResultFileName) + + const differences = deepDiff(expectedResult, actualResult)!; + + let foundDiffs = false; + + + if (differences) { + differences.forEach((difference, index) => { + + if (difference.kind == "E" && difference.path!.length == 2) { + if (difference.path![1] == "balance" || difference.path![1] == "nonce") { + return; + } + } + + if (difference.kind == "E" && difference.path!.length == 2) { + let address = difference.path![0]; + if (address == ZERO_ADDRESS && difference.path![1] == "balance") { + return; + } + } + + console.log(`Found difference (lhs is expected value) ${index + 1} at path:`, difference.path); + console.log(`Difference ${index + 1}:`, difference); + + foundDiffs = true; + }); + } + ; + + await expect(foundDiffs).to.be.eq(false) +} + +async function verifyPrestateDiffTransferTraceAgainstGethTrace(_fileName: string) { + + console.log("Verifying " + _fileName); + + const _expectedResultFileName = GETH_TRACES_DIR + _fileName; + const _actualResultFileName = SKALE_TRACES_DIR + _fileName; + + let expectedResult = await readJSONFile(_expectedResultFileName) + let actualResult = await readJSONFile(_actualResultFileName) + + const differences = deepDiff(expectedResult, actualResult)!; + + let foundDiffs = false; + + + if (differences) { + differences.forEach((difference, index) => { + + if (difference.kind == "E" && difference.path!.length == 3) { + if (difference.path![2] == "balance" || difference.path![2] == "nonce") { + return; + } + } + + if (difference.kind == "E" && difference.path!.length == 3) { + let address = difference.path![1]; + if (address == ZERO_ADDRESS && difference.path![2] == "balance") { + return; + } + } + + console.log(`Found difference (lhs is expected value) ${index + 1} at path:`, difference.path); + console.log(`Difference ${index + 1}:`, difference); + + foundDiffs = true; + }); + } + ; + + await expect(foundDiffs).to.be.eq(false) +} + async function verifyCallTraceAgainstGethTrace(_fileName: string) { - console.log("Verifying " + _fileName); + console.log("Verifying " + _fileName); const _expectedResultFileName = GETH_TRACES_DIR + _fileName; const _actualResultFileName = SKALE_TRACES_DIR + _fileName; @@ -356,7 +544,7 @@ async function verifyCallTraceAgainstGethTrace(_fileName: string) { async function verifyFourByteTraceAgainstGethTrace(_fileName: string) { - console.log("Verifying " + _fileName); + console.log("Verifying " + _fileName); const _expectedResultFileName = GETH_TRACES_DIR + _fileName; const _actualResultFileName = SKALE_TRACES_DIR + _fileName; @@ -387,7 +575,7 @@ async function verifyFourByteTraceAgainstGethTrace(_fileName: string) { async function verifyPrestateTraceAgainstGethTrace(_fileName: string) { - console.log("Verifying " + _fileName); + console.log("Verifying " + _fileName); const _expectedResultFileName = GETH_TRACES_DIR + _fileName; const _actualResultFileName = SKALE_TRACES_DIR + _fileName; @@ -421,7 +609,7 @@ async function verifyPrestateTraceAgainstGethTrace(_fileName: string) { async function verifyPrestateDiffTraceAgainstGethTrace(_fileName: string) { - console.log("Verifying " + _fileName); + console.log("Verifying " + _fileName); const _expectedResultFileName = GETH_TRACES_DIR + _fileName; const _actualResultFileName = SKALE_TRACES_DIR + _fileName; @@ -449,9 +637,6 @@ async function verifyPrestateDiffTraceAgainstGethTrace(_fileName: string) { } - - - async function verifyGasCalculations(_actualResult: any): Promise { let structLogs: object[] = _actualResult.structLogs; expect(structLogs.length > 0) @@ -479,25 +664,39 @@ async function main(): Promise { let deployedContract = await deployTestContract(); + const transferHash: string = await callTestContractRun(deployedContract); + + await getAndPrintCommittedTransactionTrace(transferHash, DEFAULT_TRACER, TEST_TRANSFER_DEFAULTTRACER_FILE_NAME); + await getAndPrintCommittedTransactionTrace(transferHash, CALL_TRACER, TEST_TRANSFER_CALLTRACER_FILE_NAME); + await getAndPrintCommittedTransactionTrace(transferHash, PRESTATE_TRACER, TEST_TRANSFER_PRESTATETRACER_FILE_NAME); + await getAndPrintCommittedTransactionTrace(transferHash, PRESTATEDIFF_TRACER, TEST_TRANSFER_PRESTATEDIFFTRACER_FILE_NAME); + await getAndPrintCommittedTransactionTrace(transferHash, FOURBYTE_TRACER, TEST_TRANSFER_FOURBYTETRACER_FILE_NAME); - await callTestContractRun(deployedContract); await callDebugTraceCall(deployedContract, DEFAULT_TRACER, TEST_CONTRACT_CALL_DEFAULTTRACER_FILE_NAME); await callDebugTraceCall(deployedContract, CALL_TRACER, TEST_CONTRACT_CALL_CALLTRACER_FILE_NAME); - await callDebugTraceCall(deployedContract, FOUR_BYTE_TRACER, TEST_CONTRACT_CALL_FOURBYTETRACER_FILE_NAME); + await callDebugTraceCall(deployedContract, FOURBYTE_TRACER, TEST_CONTRACT_CALL_FOURBYTETRACER_FILE_NAME); await callDebugTraceCall(deployedContract, PRESTATE_TRACER, TEST_CONTRACT_CALL_PRESTATETRACER_FILE_NAME); await callDebugTraceCall(deployedContract, PRESTATEDIFF_TRACER, TEST_CONTRACT_CALL_PRESTATEDIFFTRACER_FILE_NAME); - await callDebugTraceCall(deployedContract, REPLAY_TRACER, TEST_CONTRACT_CALL_REPLAYTRACER_FILE_NAME); + // geth does not have replay trace + if (hre.network.name != "geth") { + await callDebugTraceCall(deployedContract, REPLAY_TRACER, TEST_CONTRACT_CALL_REPLAYTRACER_FILE_NAME); + } - await verifyDefaultTraceAgainstGethTrace(TEST_CONTRACT_DEPLOY_FILE_NAME) - await verifyDefaultTraceAgainstGethTrace(TEST_CONTRACT_RUN_FILE_NAME) - await verifyDefaultTraceAgainstGethTrace(TEST_CONTRACT_CALL_DEFAULTTRACER_FILE_NAME) - await verifyCallTraceAgainstGethTrace(TEST_CONTRACT_CALL_CALLTRACER_FILE_NAME) - await verifyFourByteTraceAgainstGethTrace(TEST_CONTRACT_CALL_FOURBYTETRACER_FILE_NAME) - await verifyPrestateTraceAgainstGethTrace(TEST_CONTRACT_CALL_PRESTATETRACER_FILE_NAME) - await verifyPrestateDiffTraceAgainstGethTrace(TEST_CONTRACT_CALL_PRESTATEDIFFTRACER_FILE_NAME) - + await verifyTransferTraceAgainstGethTrace(TEST_TRANSFER_DEFAULTTRACER_FILE_NAME); + await verifyTransferTraceAgainstGethTrace(TEST_TRANSFER_CALLTRACER_FILE_NAME); + await verifyPrestateDiffTransferTraceAgainstGethTrace(TEST_TRANSFER_PRESTATEDIFFTRACER_FILE_NAME); + await verifyPrestateTransferTraceAgainstGethTrace(TEST_TRANSFER_PRESTATETRACER_FILE_NAME); + await verifyTransferTraceAgainstGethTrace(TEST_TRANSFER_FOURBYTETRACER_FILE_NAME); + + await verifyDefaultTraceAgainstGethTrace(TEST_CONTRACT_DEPLOY_FILE_NAME); + await verifyDefaultTraceAgainstGethTrace(TEST_CONTRACT_RUN_FILE_NAME); + await verifyDefaultTraceAgainstGethTrace(TEST_CONTRACT_CALL_DEFAULTTRACER_FILE_NAME); + await verifyCallTraceAgainstGethTrace(TEST_CONTRACT_CALL_CALLTRACER_FILE_NAME); + await verifyFourByteTraceAgainstGethTrace(TEST_CONTRACT_CALL_FOURBYTETRACER_FILE_NAME); + await verifyPrestateTraceAgainstGethTrace(TEST_CONTRACT_CALL_PRESTATETRACER_FILE_NAME); + await verifyPrestateDiffTraceAgainstGethTrace(TEST_CONTRACT_CALL_PRESTATEDIFFTRACER_FILE_NAME); }