core/stateless: API methods to get execution witness of block

This PR adds a new RPC call, which re-executes a block with stateless
mode activated, so that the witness data are collected and returned.

They are `debug_executionWitnessByHash` which takes in a block hash
and `debug_executionWitness` which takes in a block number.

---------

Signed-off-by: Guillaume Ballet <3272758+gballet@users.noreply.github.com>
This commit is contained in:
Guillaume Ballet 2025-09-16 14:46:28 +02:00 committed by GitHub
parent 110b4e13c5
commit 03b77d1a49
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 67 additions and 16 deletions

View file

@ -1907,7 +1907,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool, makeWitness
}
// The traced section of block import.
start := time.Now()
res, err := bc.processBlock(parent.Root, block, setHead, makeWitness && len(chain) == 1)
res, err := bc.ProcessBlock(parent.Root, block, setHead, makeWitness && len(chain) == 1)
if err != nil {
return nil, it.index, err
}
@ -1973,9 +1973,13 @@ type blockProcessingResult struct {
witness *stateless.Witness
}
// processBlock executes and validates the given block. If there was no error
func (bpr *blockProcessingResult) Witness() *stateless.Witness {
return bpr.witness
}
// ProcessBlock executes and validates the given block. If there was no error
// it writes the block and associated state to database.
func (bc *BlockChain) processBlock(parentRoot common.Hash, block *types.Block, setHead bool, makeWitness bool) (_ *blockProcessingResult, blockEndErr error) {
func (bc *BlockChain) ProcessBlock(parentRoot common.Hash, block *types.Block, setHead bool, makeWitness bool) (_ *blockProcessingResult, blockEndErr error) {
var (
err error
startTime = time.Now()

View file

@ -19,20 +19,21 @@ package stateless
import (
"io"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/rlp"
)
// toExtWitness converts our internal witness representation to the consensus one.
func (w *Witness) toExtWitness() *extWitness {
ext := &extWitness{
// ToExtWitness converts our internal witness representation to the consensus one.
func (w *Witness) ToExtWitness() *ExtWitness {
ext := &ExtWitness{
Headers: w.Headers,
}
ext.Codes = make([][]byte, 0, len(w.Codes))
ext.Codes = make([]hexutil.Bytes, 0, len(w.Codes))
for code := range w.Codes {
ext.Codes = append(ext.Codes, []byte(code))
}
ext.State = make([][]byte, 0, len(w.State))
ext.State = make([]hexutil.Bytes, 0, len(w.State))
for node := range w.State {
ext.State = append(ext.State, []byte(node))
}
@ -40,7 +41,7 @@ func (w *Witness) toExtWitness() *extWitness {
}
// fromExtWitness converts the consensus witness format into our internal one.
func (w *Witness) fromExtWitness(ext *extWitness) error {
func (w *Witness) fromExtWitness(ext *ExtWitness) error {
w.Headers = ext.Headers
w.Codes = make(map[string]struct{}, len(ext.Codes))
@ -56,21 +57,22 @@ func (w *Witness) fromExtWitness(ext *extWitness) error {
// EncodeRLP serializes a witness as RLP.
func (w *Witness) EncodeRLP(wr io.Writer) error {
return rlp.Encode(wr, w.toExtWitness())
return rlp.Encode(wr, w.ToExtWitness())
}
// DecodeRLP decodes a witness from RLP.
func (w *Witness) DecodeRLP(s *rlp.Stream) error {
var ext extWitness
var ext ExtWitness
if err := s.Decode(&ext); err != nil {
return err
}
return w.fromExtWitness(&ext)
}
// extWitness is a witness RLP encoding for transferring across clients.
type extWitness struct {
Headers []*types.Header
Codes [][]byte
State [][]byte
// ExtWitness is a witness RLP encoding for transferring across clients.
type ExtWitness struct {
Headers []*types.Header `json:"headers"`
Codes []hexutil.Bytes `json:"codes"`
State []hexutil.Bytes `json:"state"`
Keys []hexutil.Bytes `json:"keys"`
}

View file

@ -100,6 +100,10 @@ func (w *Witness) AddState(nodes map[string][]byte) {
}
}
func (w *Witness) AddKey() {
panic("not yet implemented")
}
// Copy deep-copies the witness object. Witness.Block isn't deep-copied as it
// is never mutated by Witness
func (w *Witness) Copy() *Witness {

View file

@ -26,6 +26,7 @@ import (
"github.com/ethereum/go-ethereum/common/hexutil"
"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"
@ -491,3 +492,43 @@ func (api *DebugAPI) StateSize(blockHashOrNumber *rpc.BlockNumberOrHash) (interf
"contractCodeBytes": hexutil.Uint64(stats.ContractCodeBytes),
}, nil
}
func (api *DebugAPI) ExecutionWitness(bn rpc.BlockNumber) (*stateless.ExtWitness, error) {
bc := api.eth.blockchain
block, err := api.eth.APIBackend.BlockByNumber(context.Background(), bn)
if err != nil {
return &stateless.ExtWitness{}, fmt.Errorf("block number %v not found", bn)
}
parent := bc.GetHeader(block.ParentHash(), block.NumberU64()-1)
if parent == nil {
return &stateless.ExtWitness{}, fmt.Errorf("block number %v found, but parent missing", bn)
}
result, err := bc.ProcessBlock(parent.Root, block, false, true)
if err != nil {
return nil, err
}
return result.Witness().ToExtWitness(), nil
}
func (api *DebugAPI) ExecutionWitnessByHash(hash common.Hash) (*stateless.ExtWitness, error) {
bc := api.eth.blockchain
block := bc.GetBlockByHash(hash)
if block == nil {
return &stateless.ExtWitness{}, fmt.Errorf("block hash %x not found", hash)
}
parent := bc.GetHeader(block.ParentHash(), block.NumberU64()-1)
if parent == nil {
return &stateless.ExtWitness{}, fmt.Errorf("block number %x found, but parent missing", hash)
}
result, err := bc.ProcessBlock(parent.Root, block, false, true)
if err != nil {
return nil, err
}
return result.Witness().ToExtWitness(), nil
}