diff --git a/.github/workflows/scripts/run_rpc_tests_ethereum.sh b/.github/workflows/scripts/run_rpc_tests_ethereum.sh index 648205d9442..206353ed1a9 100755 --- a/.github/workflows/scripts/run_rpc_tests_ethereum.sh +++ b/.github/workflows/scripts/run_rpc_tests_ethereum.sh @@ -11,8 +11,6 @@ DISABLED_TEST_LIST=( # Failing after the PR https://github.com/erigontech/erigon/pull/13617 that fixed this incompatibility # issues https://hive.pectra-devnet-5.ethpandaops.io/suite.html?suiteid=1738266984-51ae1a2f376e5de5e9ba68f034f80e32.json&suitename=rpc-compat net_listening/test_1.json - # Erigon2 and Erigon3 never supported this api methods - trace_rawTransaction # Temporary disable required block 24298763 debug_traceBlockByNumber/test_51.json # to investigate @@ -43,4 +41,4 @@ DISABLED_TEST_LIST=( DISABLED_TESTS=$(IFS=,; echo "${DISABLED_TEST_LIST[*]}") # Call the main test runner script with the required and optional parameters -"$(dirname "$0")/run_rpc_tests.sh" mainnet v1.119.0 "$DISABLED_TESTS" "$WORKSPACE" "$RESULT_DIR" +"$(dirname "$0")/run_rpc_tests.sh" mainnet v1.120.0 "$DISABLED_TESTS" "$WORKSPACE" "$RESULT_DIR" diff --git a/.github/workflows/scripts/run_rpc_tests_ethereum_latest.sh b/.github/workflows/scripts/run_rpc_tests_ethereum_latest.sh index 14f5d2b3153..16a9365c170 100755 --- a/.github/workflows/scripts/run_rpc_tests_ethereum_latest.sh +++ b/.github/workflows/scripts/run_rpc_tests_ethereum_latest.sh @@ -34,4 +34,4 @@ DISABLED_TEST_LIST=( DISABLED_TESTS=$(IFS=,; echo "${DISABLED_TEST_LIST[*]}") # Call the main test runner script with the required and optional parameters -"$(dirname "$0")/run_rpc_tests.sh" mainnet v1.119.0 "$DISABLED_TESTS" "$WORKSPACE" "$RESULT_DIR" "latest" "$REFERENCE_HOST" "do-not-compare-error-message" "$DUMP_RESPONSE" +"$(dirname "$0")/run_rpc_tests.sh" mainnet v1.120.0 "$DISABLED_TESTS" "$WORKSPACE" "$RESULT_DIR" "latest" "$REFERENCE_HOST" "do-not-compare-error-message" "$DUMP_RESPONSE" diff --git a/.github/workflows/scripts/run_rpc_tests_gnosis.sh b/.github/workflows/scripts/run_rpc_tests_gnosis.sh index 4192b4241be..54ff5750695 100755 --- a/.github/workflows/scripts/run_rpc_tests_gnosis.sh +++ b/.github/workflows/scripts/run_rpc_tests_gnosis.sh @@ -22,5 +22,5 @@ DISABLED_TEST_LIST=( DISABLED_TESTS=$(IFS=,; echo "${DISABLED_TEST_LIST[*]}") # Call the main test runner script with the required and optional parameters -"$(dirname "$0")/run_rpc_tests.sh" gnosis v1.119.0 "$DISABLED_TESTS" "$WORKSPACE" "$RESULT_DIR" +"$(dirname "$0")/run_rpc_tests.sh" gnosis v1.120.0 "$DISABLED_TESTS" "$WORKSPACE" "$RESULT_DIR" diff --git a/.github/workflows/scripts/run_rpc_tests_remote_ethereum.sh b/.github/workflows/scripts/run_rpc_tests_remote_ethereum.sh index 0a61da2f91c..c9b68edef7f 100755 --- a/.github/workflows/scripts/run_rpc_tests_remote_ethereum.sh +++ b/.github/workflows/scripts/run_rpc_tests_remote_ethereum.sh @@ -28,4 +28,4 @@ DISABLED_TEST_LIST=( DISABLED_TESTS=$(IFS=,; echo "${DISABLED_TEST_LIST[*]}") # Call the main test runner script with the required and optional parameters -"$(dirname "$0")/run_rpc_tests.sh" mainnet v1.119.0 "$DISABLED_TESTS" "$WORKSPACE" "$RESULT_DIR" +"$(dirname "$0")/run_rpc_tests.sh" mainnet v1.120.0 "$DISABLED_TESTS" "$WORKSPACE" "$RESULT_DIR" diff --git a/cmd/rpcdaemon/README.md b/cmd/rpcdaemon/README.md index baee6e9f621..a730ed6cd6a 100644 --- a/cmd/rpcdaemon/README.md +++ b/cmd/rpcdaemon/README.md @@ -355,7 +355,7 @@ The following table shows the current implementation status of Erigon's RPC daem | | | | | trace_call | Yes | | | trace_callMany | Yes | | -| trace_rawTransaction | - | not yet implemented (come help!) | +| trace_rawTransaction | Yes | | | trace_replayBlockTransactions | yes | stateDiff only (come help!) | | trace_replayTransaction | yes | stateDiff only (come help!) | | trace_block | Yes | | diff --git a/rpc/jsonrpc/trace_adhoc.go b/rpc/jsonrpc/trace_adhoc.go index 8d0ec418c6e..94b963de0de 100644 --- a/rpc/jsonrpc/trace_adhoc.go +++ b/rpc/jsonrpc/trace_adhoc.go @@ -1734,7 +1734,156 @@ func (api *TraceAPIImpl) doCall(ctx context.Context, dbtx kv.Tx, stateReader sta } // RawTransaction implements trace_rawTransaction. -func (api *TraceAPIImpl) RawTransaction(ctx context.Context, txHash common.Hash, traceTypes []string) ([]any, error) { - var stub []any - return stub, fmt.Errorf(NotImplemented, "trace_rawTransaction") +func (api *TraceAPIImpl) RawTransaction(ctx context.Context, encodedTx hexutil.Bytes, traceTypes []string) (*TraceCallResult, error) { + txn, err := types.DecodeWrappedTransaction(encodedTx) + if err != nil { + return nil, err + } + + dbtx, err := api.kv.BeginTemporalRo(ctx) + if err != nil { + return nil, err + } + defer dbtx.Rollback() + + chainConfig, err := api.chainConfig(ctx, dbtx) + if err != nil { + return nil, err + } + engine := api.engine() + + var num = rpc.LatestBlockNumber + blockNrOrHash := rpc.BlockNumberOrHash{BlockNumber: &num} + + blockNumber, hash, latest, err := rpchelper.GetBlockNumber(ctx, blockNrOrHash, dbtx, api._blockReader, api.filters) + if err != nil { + return nil, err + } + + err = api.BaseAPI.checkPruneHistory(ctx, dbtx, blockNumber) + if err != nil { + return nil, err + } + + header, err := api.headerByNumber(ctx, rpc.BlockNumber(blockNumber), dbtx) + if err != nil { + return nil, err + } + if header == nil { + return nil, fmt.Errorf("block %d(%x) not found", blockNumber, hash) + } + + err = rpchelper.CheckBlockExecuted(dbtx, blockNumber) + if err != nil { + return nil, err + } + + stateReader, err := rpchelper.CreateStateReaderFromBlockNumber(ctx, dbtx, blockNumber, latest, 0, api.stateCache, api._txNumReader) + if err != nil { + return nil, err + } + + ibs := state.New(stateReader) + + var cancel context.CancelFunc + if api.evmCallTimeout > 0 { + ctx, cancel = context.WithTimeout(ctx, api.evmCallTimeout) + } else { + ctx, cancel = context.WithCancel(ctx) + } + defer cancel() + + traceResult := &TraceCallResult{Trace: []*ParityTrace{}} + var traceTypeTrace, traceTypeStateDiff, traceTypeVmTrace bool + for _, traceType := range traceTypes { + switch traceType { + case TraceTypeTrace: + traceTypeTrace = true + case TraceTypeStateDiff: + traceTypeStateDiff = true + case TraceTypeVmTrace: + traceTypeVmTrace = true + default: + return nil, fmt.Errorf("unrecognized trace type: %s", traceType) + } + } + if traceTypeVmTrace { + traceResult.VmTrace = &VmTrace{Ops: []*VmTraceOp{}} + } + + var ot OeTracer + ot.config, err = parseOeTracerConfig(nil) + if err != nil { + return nil, err + } + ot.compat = api.compatibility + if traceTypeTrace || traceTypeVmTrace || traceTypeStateDiff { + ot.r = traceResult + ot.traceAddr = []int{} + } + + signer := types.MakeSigner(chainConfig, header.Number.Uint64(), header.Time) + blockCtx := transactions.NewEVMBlockContext(engine, header, blockNrOrHash.RequireCanonical, dbtx, api._blockReader, chainConfig) + rules := blockCtx.Rules(chainConfig) + + msg, err := txn.AsMessage(*signer, header.BaseFee, rules) + if err != nil { + return nil, err + } + msg.SetCheckNonce(false) + msg.SetCheckTransaction(false) + msg.SetCheckGas(false) + + txCtx := protocol.NewEVMTxContext(msg) + + blockCtx.GasLimit = math.MaxUint64 + blockCtx.MaxGasLimit = true + + evm := vm.NewEVM(blockCtx, txCtx, ibs, chainConfig, vm.Config{Tracer: ot.Tracer().Hooks}) + + go func() { + <-ctx.Done() + evm.Cancel() + }() + + gp := new(protocol.GasPool).AddGas(msg.Gas()).AddBlobGas(msg.BlobGas()) + var execResult *evmtypes.ExecutionResult + ibs.SetTxContext(blockCtx.BlockNumber, 0) + ibs.SetHooks(ot.Tracer().Hooks) + + if ot.Tracer() != nil && ot.Tracer().Hooks.OnTxStart != nil { + ot.Tracer().OnTxStart(evm.GetVMContext(), txn, msg.From()) + } + execResult, err = protocol.ApplyMessage(evm, msg, gp, true /* refunds */, true /* gasBailout */, engine) + if err != nil { + if ot.Tracer() != nil && ot.Tracer().Hooks.OnTxEnd != nil { + ot.Tracer().OnTxEnd(nil, err) + } + return nil, err + } + if ot.Tracer() != nil && ot.Tracer().Hooks.OnTxEnd != nil { + ot.Tracer().OnTxEnd(&types.Receipt{GasUsed: execResult.ReceiptGasUsed}, nil) + } + + traceResult.Output = common.Copy(execResult.ReturnData) + if traceTypeStateDiff { + sdMap := make(map[accounts.Address]*StateDiffAccount) + traceResult.StateDiff = sdMap + sd := &StateDiff{sdMap: sdMap} + if err = ibs.FinalizeTx(evm.ChainRules(), sd); err != nil { + return nil, err + } + initialIbs := state.New(stateReader) + sd.CompareStates(initialIbs, ibs) + } + + if evm.Cancelled() { + return nil, fmt.Errorf("execution aborted (timeout = %v)", api.evmCallTimeout) + } + + if !traceTypeTrace { + traceResult.Trace = []*ParityTrace{} + } + + return traceResult, nil } diff --git a/rpc/jsonrpc/trace_adhoc_test.go b/rpc/jsonrpc/trace_adhoc_test.go index f434315847c..e894be9e3b1 100644 --- a/rpc/jsonrpc/trace_adhoc_test.go +++ b/rpc/jsonrpc/trace_adhoc_test.go @@ -17,6 +17,7 @@ package jsonrpc import ( + "bytes" "context" "encoding/json" "os" @@ -459,3 +460,31 @@ func TestOeTracer(t *testing.T) { }) } } + +func TestRawTransaction(t *testing.T) { + m, _, _ := rpcdaemontest.CreateTestExecModule(t) + api := NewTraceAPI(newBaseApiForTest(m), m.DB, &httpcfg.HttpCfg{}) + + // Read a transaction from block 6 and re-encode it as raw bytes. + var encodedTx []byte + if err := m.DB.View(context.Background(), func(tx kv.Tx) error { + b, err := m.BlockReader.BlockByNumber(m.Ctx, tx, 6) + if err != nil { + return err + } + txn := b.Transactions()[0] + var buf bytes.Buffer + if err = txn.MarshalBinary(&buf); err != nil { + return err + } + encodedTx = buf.Bytes() + return nil + }); err != nil { + t.Fatal(err) + } + + result, err := api.RawTransaction(context.Background(), encodedTx, []string{"trace"}) + require.NoError(t, err) + require.NotNil(t, result) + require.NotEmpty(t, result.Trace) +} diff --git a/rpc/jsonrpc/trace_api.go b/rpc/jsonrpc/trace_api.go index 6badfe44cc3..9b424cb35ed 100644 --- a/rpc/jsonrpc/trace_api.go +++ b/rpc/jsonrpc/trace_api.go @@ -37,7 +37,7 @@ type TraceAPI interface { ReplayTransaction(ctx context.Context, txHash common.Hash, traceTypes []string, gasBailOut *bool, traceConfig *config.TraceConfig) (*TraceCallResult, error) Call(ctx context.Context, call TraceCallParam, types []string, blockNr *rpc.BlockNumberOrHash, traceConfig *config.TraceConfig) (*TraceCallResult, error) CallMany(ctx context.Context, calls json.RawMessage, blockNr *rpc.BlockNumberOrHash, traceConfig *config.TraceConfig) ([]*TraceCallResult, error) - RawTransaction(ctx context.Context, txHash common.Hash, traceTypes []string) ([]any, error) + RawTransaction(ctx context.Context, encodedTx hexutil.Bytes, traceTypes []string) (*TraceCallResult, error) // Filtering (see ./trace_filtering.go)