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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"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.
|
// 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)
|
block, err := s.b.BlockByNumberOrHash(ctx, blockNrOrHash)
|
||||||
if block == nil || err != nil {
|
if block == nil || err != nil {
|
||||||
// When the block doesn't exist, the RPC method should return JSON null
|
// When the block doesn't exist, the RPC method should return JSON null
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
return s.traceBlock(ctx, block)
|
||||||
|
}
|
||||||
|
|
||||||
result := make([]map[string]interface{}, 1)
|
// txTraceResult is the result of a single transaction trace.
|
||||||
return result, nil
|
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
|
// OverrideAccount indicates the overriding fields of account during the execution
|
||||||
|
|
|
||||||
|
|
@ -1620,7 +1620,86 @@ func TestRPCGetBlockReceipts(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRPCGetRequiredBlockState(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) {
|
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