mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-20 10:52:25 +00:00
GetRequiredBlockState to return traceBlock result
This commit is contained in:
parent
530cea02f8
commit
e96eea8ccd
3 changed files with 184 additions and 4 deletions
|
|
@ -19,6 +19,7 @@ package ethapi
|
|||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
|
@ -934,15 +935,104 @@ func (s *BlockChainAPI) GetBlockReceipts(ctx context.Context, blockNrOrHash rpc.
|
|||
}
|
||||
|
||||
// GetRequiredBlockState returns all state required to execute a single historical block.
|
||||
func (s *BlockChainAPI) GetRequiredBlockState(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) ([]map[string]interface{}, error) {
|
||||
func (s *BlockChainAPI) GetRequiredBlockState(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) ([]*txTraceResult, error) {
|
||||
block, err := s.b.BlockByNumberOrHash(ctx, blockNrOrHash)
|
||||
if block == nil || err != nil {
|
||||
// When the block doesn't exist, the RPC method should return JSON null
|
||||
return nil, nil
|
||||
}
|
||||
return s.traceBlock(ctx, block)
|
||||
}
|
||||
|
||||
result := make([]map[string]interface{}, 1)
|
||||
return result, nil
|
||||
// txTraceResult is the result of a single transaction trace.
|
||||
type txTraceResult struct {
|
||||
TxHash common.Hash `json:"txHash"` // transaction hash
|
||||
Result interface{} `json:"result,omitempty"` // Trace results produced by the tracer
|
||||
Error string `json:"error,omitempty"` // Trace failure produced by the tracer
|
||||
}
|
||||
|
||||
const (
|
||||
// defaultTraceTimeout is the amount of time a single transaction can execute
|
||||
// by default before being forcefully aborted.
|
||||
defaultTraceTimeout = 5 * time.Second
|
||||
)
|
||||
|
||||
// traceBlock configures a new tracer according to the provided configuration, and
|
||||
// executes all the transactions contained within. The return value will be one item
|
||||
// per transaction, dependent on the requested tracer.
|
||||
func (s *BlockChainAPI) traceBlock(ctx context.Context, block *types.Block) ([]*txTraceResult, error) {
|
||||
if block.NumberU64() == 0 {
|
||||
return nil, errors.New("genesis is not traceable")
|
||||
}
|
||||
// Prepare base state
|
||||
statedb, _, err := s.b.StateAndHeaderByNumber(ctx, rpc.BlockNumber(block.NumberU64()-1))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Native tracers have low overhead
|
||||
var (
|
||||
txs = block.Transactions()
|
||||
is158 = s.b.ChainConfig().IsEIP158(block.Number())
|
||||
blockCtx = core.NewEVMBlockContext(block.Header(), NewChainContext(ctx, s.b), nil)
|
||||
signer = types.MakeSigner(s.b.ChainConfig(), block.Number(), block.Time())
|
||||
results = make([]*txTraceResult, len(txs))
|
||||
)
|
||||
for i, tx := range txs {
|
||||
// Generate the next state snapshot fast without tracing
|
||||
msg, _ := core.TransactionToMessage(tx, signer, block.BaseFee())
|
||||
res, err := s.traceTx(ctx, msg, blockCtx, statedb)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
results[i] = &txTraceResult{TxHash: tx.Hash(), Result: res}
|
||||
// Finalize the state so any modifications are written to the trie
|
||||
// Only delete empty objects if EIP158/161 (a.k.a Spurious Dragon) is in effect
|
||||
statedb.Finalise(is158)
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// Tracer interface extends vm.EVMLogger and additionally
|
||||
// allows collecting the tracing result.
|
||||
type Tracer interface {
|
||||
vm.EVMLogger
|
||||
GetResult() (json.RawMessage, error)
|
||||
// Stop terminates execution of the tracer at the first opportune moment.
|
||||
Stop(err error)
|
||||
}
|
||||
|
||||
// traceTx configures a new tracer according to the provided configuration, and
|
||||
// executes the given message in the provided environment. The return value will
|
||||
// be tracer dependent.
|
||||
func (s *BlockChainAPI) traceTx(ctx context.Context, message *core.Message, vmctx vm.BlockContext, statedb *state.StateDB) (interface{}, error) {
|
||||
var (
|
||||
tracer Tracer
|
||||
err error
|
||||
timeout = defaultTraceTimeout
|
||||
txContext = core.NewEVMTxContext(message)
|
||||
)
|
||||
// Default tracer is the struct logger
|
||||
tracer = logger.NewStructLogger(&logger.Config{})
|
||||
vmenv := vm.NewEVM(vmctx, txContext, statedb, s.b.ChainConfig(), vm.Config{Tracer: tracer, NoBaseFee: true})
|
||||
|
||||
// Define a meaningful timeout of a single transaction trace
|
||||
deadlineCtx, cancel := context.WithTimeout(ctx, timeout)
|
||||
go func() {
|
||||
<-deadlineCtx.Done()
|
||||
if errors.Is(deadlineCtx.Err(), context.DeadlineExceeded) {
|
||||
tracer.Stop(errors.New("execution timeout"))
|
||||
// Stop evm execution. Note cancellation is not necessarily immediate.
|
||||
vmenv.Cancel()
|
||||
}
|
||||
}()
|
||||
defer cancel()
|
||||
|
||||
// Call Prepare to clear out the statedb access list
|
||||
if _, err = core.ApplyMessage(vmenv, message, new(core.GasPool).AddGas(message.GasLimit)); err != nil {
|
||||
return nil, fmt.Errorf("tracing failed: %w", err)
|
||||
}
|
||||
return tracer.GetResult()
|
||||
}
|
||||
|
||||
// OverrideAccount indicates the overriding fields of account during the execution
|
||||
|
|
|
|||
|
|
@ -1620,7 +1620,86 @@ func TestRPCGetBlockReceipts(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestRPCGetRequiredBlockState(t *testing.T) {
|
||||
//
|
||||
t.Parallel()
|
||||
|
||||
var (
|
||||
genBlocks = 6
|
||||
backend, _ = setupReceiptBackend(t, genBlocks)
|
||||
api = NewBlockChainAPI(backend)
|
||||
)
|
||||
blockHashes := make([]common.Hash, genBlocks+1)
|
||||
ctx := context.Background()
|
||||
for i := 0; i <= genBlocks; i++ {
|
||||
header, err := backend.HeaderByNumber(ctx, rpc.BlockNumber(i))
|
||||
if err != nil {
|
||||
t.Errorf("failed to get block: %d err: %v", i, err)
|
||||
}
|
||||
blockHashes[i] = header.Hash()
|
||||
}
|
||||
|
||||
var testSuite = []struct {
|
||||
test rpc.BlockNumberOrHash
|
||||
file string
|
||||
}{
|
||||
// 3. latest tag
|
||||
{
|
||||
test: rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber),
|
||||
file: "tag-latest",
|
||||
},
|
||||
// // 4. block with legacy transfer tx(hash)
|
||||
// {
|
||||
// test: rpc.BlockNumberOrHashWithHash(blockHashes[1], false),
|
||||
// file: "block-with-legacy-transfer-tx",
|
||||
// },
|
||||
// // 5. block with contract create tx(number)
|
||||
// {
|
||||
// test: rpc.BlockNumberOrHashWithNumber(rpc.BlockNumber(2)),
|
||||
// file: "block-with-contract-create-tx",
|
||||
// },
|
||||
// // 6. block with legacy contract call tx(hash)
|
||||
// {
|
||||
// test: rpc.BlockNumberOrHashWithHash(blockHashes[3], false),
|
||||
// file: "block-with-legacy-contract-call-tx",
|
||||
// },
|
||||
// // 7. block with dynamic fee tx(number)
|
||||
// {
|
||||
// test: rpc.BlockNumberOrHashWithNumber(rpc.BlockNumber(4)),
|
||||
// file: "block-with-dynamic-fee-tx",
|
||||
// },
|
||||
// // 8. block is empty
|
||||
// {
|
||||
// test: rpc.BlockNumberOrHashWithHash(common.Hash{}, false),
|
||||
// file: "hash-empty",
|
||||
// },
|
||||
// // 9. block is not found
|
||||
// {
|
||||
// test: rpc.BlockNumberOrHashWithHash(common.HexToHash("deadbeef"), false),
|
||||
// file: "hash-notfound",
|
||||
// },
|
||||
// // 10. block is not found
|
||||
// {
|
||||
// test: rpc.BlockNumberOrHashWithNumber(rpc.BlockNumber(genBlocks + 1)),
|
||||
// file: "block-notfound",
|
||||
// },
|
||||
// // 11. block with blob tx
|
||||
// {
|
||||
// test: rpc.BlockNumberOrHashWithNumber(rpc.BlockNumber(6)),
|
||||
// file: "block-with-blob-tx",
|
||||
// },
|
||||
}
|
||||
|
||||
for i, tt := range testSuite {
|
||||
var (
|
||||
result interface{}
|
||||
err error
|
||||
)
|
||||
result, err = api.GetRequiredBlockState(context.Background(), tt.test)
|
||||
if err != nil {
|
||||
t.Errorf("test %d: want no error, have %v", i, err)
|
||||
continue
|
||||
}
|
||||
testRPCResponseWithFile(t, i, result, "eth_getRequiredBlockState", tt.file)
|
||||
}
|
||||
}
|
||||
|
||||
func testRPCResponseWithFile(t *testing.T, testid int, result interface{}, rpc string, file string) {
|
||||
|
|
|
|||
11
internal/ethapi/testdata/eth_getRequiredBlockState-tag-latest.json
vendored
Normal file
11
internal/ethapi/testdata/eth_getRequiredBlockState-tag-latest.json
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
[
|
||||
{
|
||||
"txHash": "0xb51ee3d2a89ba5d5623c73133c8d7a6ba9fb41194c17f4302c21b30994a1180f",
|
||||
"result": {
|
||||
"gas": 21001,
|
||||
"failed": false,
|
||||
"returnValue": "",
|
||||
"structLogs": []
|
||||
}
|
||||
}
|
||||
]
|
||||
Loading…
Reference in a new issue