feat(rpc): implement debug_executionWitness API (#397)

pull/30613/head
Francis Li 3 weeks ago
parent 368e16f39d
commit 9b1fd811f8
No known key found for this signature in database
GPG Key ID: 39F1A72C987F3F88
  1. 32
      core/stateless/encoding.go
  2. 42
      eth/api_debug.go
  3. 33
      eth/api_debug_test.go

@ -19,7 +19,9 @@ package stateless
import (
"io"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/rlp"
)
@ -74,3 +76,33 @@ type extWitness struct {
Codes [][]byte
State [][]byte
}
// ExecutionWitness is a witness json encoding for transferring across clients
// in the future, we'll probably consider using the extWitness format instead for less overhead.
// currently we're using this format for compatibility with reth and also for simplicity in terms of parsing.
type ExecutionWitness struct {
Headers []*types.Header `json:"headers"`
Codes map[string]string `json:"codes"`
State map[string]string `json:"state"`
}
func transformMap(in map[string]struct{}) map[string]string {
out := make(map[string]string, len(in))
for item := range in {
bytes := []byte(item)
key := crypto.Keccak256Hash(bytes).Hex()
out[key] = hexutil.Encode(bytes)
}
return out
}
// ToExecutionWitness converts a witness to an execution witness format that is compatible with reth.
// keccak(node) => node
// keccak(bytecodes) => bytecodes
func (w *Witness) ToExecutionWitness() *ExecutionWitness {
return &ExecutionWitness{
Headers: w.Headers,
Codes: transformMap(w.Codes),
State: transformMap(w.State),
}
}

@ -24,8 +24,10 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/stateless"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/internal/ethapi"
@ -443,3 +445,43 @@ func (api *DebugAPI) GetTrieFlushInterval() (string, error) {
}
return api.eth.blockchain.GetTrieFlushInterval().String(), nil
}
func (api *DebugAPI) ExecutionWitness(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*stateless.ExecutionWitness, error) {
block, err := api.eth.APIBackend.BlockByNumberOrHash(ctx, blockNrOrHash)
if err != nil {
return nil, fmt.Errorf("failed to retrieve block: %w", err)
}
if block == nil {
return nil, fmt.Errorf("block not found: %s", blockNrOrHash.String())
}
witness, err := generateWitness(api.eth.blockchain, block)
return witness.ToExecutionWitness(), err
}
func generateWitness(blockchain *core.BlockChain, block *types.Block) (*stateless.Witness, error) {
witness, err := stateless.NewWitness(block.Header(), blockchain)
if err != nil {
return nil, fmt.Errorf("failed to create witness: %w", err)
}
parentHeader := witness.Headers[0]
statedb, err := blockchain.StateAt(parentHeader.Root)
if err != nil {
return nil, fmt.Errorf("failed to retrieve parent state: %w", err)
}
statedb.StartPrefetcher("debug_execution_witness", witness)
defer statedb.StopPrefetcher()
res, err := blockchain.Processor().Process(block, statedb, *blockchain.GetVMConfig())
if err != nil {
return nil, fmt.Errorf("failed to process block %d: %w", block.Number(), err)
}
if err := blockchain.Validator().ValidateState(block, statedb, res, false); err != nil {
return nil, fmt.Errorf("failed to validate block %d: %w", block.Number(), err)
}
return witness, nil
}

@ -19,6 +19,7 @@ package eth
import (
"bytes"
"fmt"
"math/big"
"reflect"
"slices"
"strings"
@ -26,13 +27,18 @@ import (
"github.com/davecgh/go-spew/spew"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus/ethash"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/triedb"
"github.com/holiman/uint256"
"github.com/stretchr/testify/require"
)
var dumper = spew.ConfigState{Indent: " "}
@ -224,3 +230,30 @@ func TestStorageRangeAt(t *testing.T) {
}
}
}
func TestExecutionWitness(t *testing.T) {
t.Parallel()
// Create a database pre-initialize with a genesis block
db := rawdb.NewMemoryDatabase()
gspec := &core.Genesis{
Config: params.TestChainConfig,
Alloc: types.GenesisAlloc{testAddr: {Balance: big.NewInt(1000000)}},
}
chain, _ := core.NewBlockChain(db, nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil)
blockNum := 10
_, bs, _ := core.GenerateChainWithGenesis(gspec, ethash.NewFaker(), blockNum, nil)
if _, err := chain.InsertChain(bs); err != nil {
panic(err)
}
block := chain.GetBlockByNumber(uint64(blockNum - 1))
require.NotNil(t, block)
witness, err := generateWitness(chain, block)
require.NoError(t, err)
_, _, err = core.ExecuteStateless(params.TestChainConfig, block, witness)
require.NoError(t, err)
}

Loading…
Cancel
Save