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. // The traced section of block import.
start := time.Now() 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 { if err != nil {
return nil, it.index, err return nil, it.index, err
} }
@ -1973,9 +1973,13 @@ type blockProcessingResult struct {
witness *stateless.Witness 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. // 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 ( var (
err error err error
startTime = time.Now() startTime = time.Now()

View file

@ -19,20 +19,21 @@ package stateless
import ( import (
"io" "io"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
) )
// toExtWitness converts our internal witness representation to the consensus one. // ToExtWitness converts our internal witness representation to the consensus one.
func (w *Witness) toExtWitness() *extWitness { func (w *Witness) ToExtWitness() *ExtWitness {
ext := &extWitness{ ext := &ExtWitness{
Headers: w.Headers, 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 { for code := range w.Codes {
ext.Codes = append(ext.Codes, []byte(code)) 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 { for node := range w.State {
ext.State = append(ext.State, []byte(node)) 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. // 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.Headers = ext.Headers
w.Codes = make(map[string]struct{}, len(ext.Codes)) 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. // EncodeRLP serializes a witness as RLP.
func (w *Witness) EncodeRLP(wr io.Writer) error { 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. // DecodeRLP decodes a witness from RLP.
func (w *Witness) DecodeRLP(s *rlp.Stream) error { func (w *Witness) DecodeRLP(s *rlp.Stream) error {
var ext extWitness var ext ExtWitness
if err := s.Decode(&ext); err != nil { if err := s.Decode(&ext); err != nil {
return err return err
} }
return w.fromExtWitness(&ext) return w.fromExtWitness(&ext)
} }
// extWitness is a witness RLP encoding for transferring across clients. // ExtWitness is a witness RLP encoding for transferring across clients.
type extWitness struct { type ExtWitness struct {
Headers []*types.Header Headers []*types.Header `json:"headers"`
Codes [][]byte Codes []hexutil.Bytes `json:"codes"`
State [][]byte 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 // Copy deep-copies the witness object. Witness.Block isn't deep-copied as it
// is never mutated by Witness // is never mutated by Witness
func (w *Witness) Copy() *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/common/hexutil"
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state" "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/core/types"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/internal/ethapi" "github.com/ethereum/go-ethereum/internal/ethapi"
@ -491,3 +492,43 @@ func (api *DebugAPI) StateSize(blockHashOrNumber *rpc.BlockNumberOrHash) (interf
"contractCodeBytes": hexutil.Uint64(stats.ContractCodeBytes), "contractCodeBytes": hexutil.Uint64(stats.ContractCodeBytes),
}, nil }, 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
}