GetRequiredBlockState to return traceBlock result

This commit is contained in:
jstr1121 2023-10-05 23:51:04 +08:00
parent 530cea02f8
commit e96eea8ccd
3 changed files with 184 additions and 4 deletions

View file

@ -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

View file

@ -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) {

View file

@ -0,0 +1,11 @@
[
{
"txHash": "0xb51ee3d2a89ba5d5623c73133c8d7a6ba9fb41194c17f4302c21b30994a1180f",
"result": {
"gas": 21001,
"failed": false,
"returnValue": "",
"structLogs": []
}
}
]