two-steps bootstrapping

This commit is contained in:
Guillaume Ballet 2026-02-07 09:02:43 +01:00
parent 9f89625a0d
commit 8cc4463bbd
5 changed files with 99 additions and 27 deletions

View file

@ -32,6 +32,8 @@ import (
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie"
"github.com/ethereum/go-ethereum/trie/bintrie"
"github.com/ethereum/go-ethereum/triedb"
)
@ -370,8 +372,17 @@ func (bc *BlockChain) TxIndexDone() bool {
}
// HasState checks if state trie is fully present in the database or not.
// It avoids using OpenTrie which has transition-aware logic that may try
// to open a binary tree before it exists. Instead, it directly attempts
// to decode the root node as an MPT first, and if that fails, as a binary
// trie.
func (bc *BlockChain) HasState(hash common.Hash) bool {
_, err := bc.statedb.OpenTrie(hash)
// Try to open as a Merkle Patricia Trie first.
if _, err := trie.NewStateTrie(trie.StateTrieID(hash), bc.triedb); err == nil {
return true
}
// Fall back to trying as a binary trie.
_, err := bintrie.NewBinaryTrie(hash, bc.triedb)
return err == nil
}

View file

@ -184,6 +184,7 @@ var (
conversionProgressSlotKey = common.Hash{2} // slot 2: current slot pointer
conversionProgressStorageProcessed = common.Hash{3} // slot 3: storage processed flag
transitionEndedKey = common.Hash{4} // slot 4: non-zero if transition ended
baseRootKey = common.Hash{5} // slot 5: MPT base root at transition start
)
// isTransitionActive checks if the binary tree transition has been activated
@ -232,12 +233,18 @@ func LoadTransitionState(reader StateReader, root common.Hash) *overlay.Transiti
}
storageProcessed := storageProcessedBytes[0] == 1
baseRoot, err := reader.Storage(params.BinaryTransitionRegistryAddress, baseRootKey)
if err != nil {
return nil
}
return &overlay.TransitionState{
Started: started,
Ended: ended,
CurrentAccountAddress: &currentAccount,
CurrentSlotHash: currentSlotHash,
StorageProcessed: storageProcessed,
BaseRoot: baseRoot,
}
}
@ -307,30 +314,45 @@ func (db *CachingDB) ReadersWithCacheStats(stateRoot common.Hash) (ReaderWithSta
// OpenTrie opens the main account trie at a specific root hash.
func (db *CachingDB) OpenTrie(root common.Hash) (Trie, error) {
reader, err := db.triedb.StateReader(root)
if err != nil {
return nil, err
}
flatReader := newFlatReader(reader)
ts := LoadTransitionState(flatReader, root)
if isTransitionActive(flatReader) || db.triedb.IsVerkle() {
bt, err := bintrie.NewBinaryTrie(root, db.triedb)
// Only attempt transition-aware trie opening in path scheme, since
// hashdb does not implement StateReader.
if db.TrieDB().Scheme() == rawdb.PathScheme {
reader, err := db.triedb.StateReader(root)
if err != nil {
return nil, fmt.Errorf("could not open the overlay tree: %w", err)
}
if !ts.InTransition() {
// Transition complete, use BinaryTrie only
return bt, nil
return nil, err
}
flatReader := newFlatReader(reader)
base, err := trie.NewStateTrie(trie.StateTrieID(ts.BaseRoot), db.triedb)
if err != nil {
return nil, fmt.Errorf("could not create base trie in OpenTrie: %w", err)
ts := LoadTransitionState(flatReader, root)
if isTransitionActive(flatReader) || db.triedb.IsVerkle() {
fmt.Printf("Opening transition-aware trie for root %s with transition state: %+v\n", root, ts)
// special case of the tree bootsrap: the root will be that of the MPT, so in that
// case, open an empty binary tree.
var bt *bintrie.BinaryTrie
if ts.BaseRoot == (common.Hash{}) {
bt, err = bintrie.NewBinaryTrie(common.Hash{}, db.triedb)
if err != nil {
return nil, fmt.Errorf("could not bootstrap the overlay tree: %w", err)
}
} else {
bt, err = bintrie.NewBinaryTrie(root, db.triedb)
if err != nil {
return nil, fmt.Errorf("could not open the overlay tree: %w", err)
}
}
if !ts.InTransition() {
// Transition complete, use BinaryTrie only
return bt, nil
}
base, err := trie.NewStateTrie(trie.StateTrieID(ts.BaseRoot), db.triedb)
if err != nil {
return nil, fmt.Errorf("could not create base trie in OpenTrie: %w", err)
}
return transitiontrie.NewTransitionTrie(base, bt, false), nil
}
return transitiontrie.NewTransitionTrie(base, bt, false), nil
}
return trie.NewStateTrie(trie.StateTrieID(root), db.triedb)
}

View file

@ -87,15 +87,28 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
context = NewEVMBlockContext(header, p.chain, nil)
evm := vm.NewEVM(context, tracingStateDB, config, cfg)
if config.IsVerkle(header.Number, header.Time) {
// Bootstrap part deux: initialize the base root in the registry,
// as this is the first UBT block (which is the _second_ block of
// the transition, after the bootstrapping block that initializes
// the registry).
parentHeader := p.chain.GetHeaderByHash(block.ParentHash())
// Confusingly, the first IsVerkle block isn't "verkle"
if config.IsVerkle(parentHeader.Number, parentHeader.Time) {
// Store the parent's state root as the MPT base root for the
// binary trie transition. Only written once (first verkle block),
// before InitializeBinaryTransitionRegistry sets slot 0.
if statedb.GetState(params.BinaryTransitionRegistryAddress, common.Hash{5}) == (common.Hash{}) {
statedb.SetState(params.BinaryTransitionRegistryAddress, common.Hash{5}, parentHeader.Root)
}
}
}
if beaconRoot := block.BeaconRoot(); beaconRoot != nil {
ProcessBeaconBlockRoot(*beaconRoot, evm)
}
if config.IsPrague(block.Number(), block.Time()) || config.IsVerkle(block.Number(), block.Time()) {
ProcessParentBlockHash(block.ParentHash(), evm)
}
if config.IsVerkle(header.Number, header.Time) {
InitializeBinaryTransitionRegistry(statedb)
}
// Iterate over and process the individual transactions
for i, tx := range block.Transactions() {
@ -129,6 +142,15 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
return nil, fmt.Errorf("failed to process consolidation queue: %w", err)
}
}
if config.IsVerkle(header.Number, header.Time) {
// Bootstrap part one: initialize the registry to mark the transition as started,
// which has to be done at the end of the _previous_ block, so that the information
// can bee made available inside the tree.
parentHeader := p.chain.GetHeaderByHash(block.ParentHash())
if !config.IsVerkle(parentHeader.Number, parentHeader.Time) {
InitializeBinaryTransitionRegistry(statedb)
}
}
// Finalize the block, applying any consensus engine specific extras (e.g. block rewards)
p.chain.Engine().Finalize(p.chain, header, tracingStateDB, block.Body())

View file

@ -31,5 +31,5 @@ func InitializeBinaryTransitionRegistry(statedb *state.StateDB) {
}
statedb.SetCode(params.BinaryTransitionRegistryAddress, []byte{1, 2, 3}, tracing.CodeChangeUnspecified)
statedb.SetNonce(params.BinaryTransitionRegistryAddress, 1, tracing.NonceChangeUnspecified)
statedb.SetState(params.BinaryTransitionRegistryAddress, common.Hash{}, common.Hash{1})
statedb.SetState(params.BinaryTransitionRegistryAddress, common.Hash{}, common.Hash{1}) // slot 0: started
}

View file

@ -167,6 +167,15 @@ func (miner *Miner) generateWork(genParam *generateParams, witness bool) *newPay
return &newPayloadResult{err: err}
}
}
if miner.chainConfig.IsVerkle(work.header.Number, work.header.Time) {
// Bootstrap part one: initialize the registry to mark the transition as started,
// which has to be done at the end of the _previous_ block, so that the information
// can bee made available inside the tree.
parentHeader := miner.chain.GetHeaderByHash(work.header.ParentHash) // XXX parent could be added to the environment in prepareWork to avoid this lookup
if !miner.chainConfig.IsVerkle(parentHeader.Number, parentHeader.Time) {
core.InitializeBinaryTransitionRegistry(work.state)
}
}
if requests != nil {
reqHash := types.CalcRequestsHash(requests)
work.header.RequestsHash = &reqHash
@ -260,15 +269,23 @@ func (miner *Miner) prepareWork(genParams *generateParams, witness bool) (*envir
log.Error("Failed to create sealing context", "err", err)
return nil, err
}
if miner.chainConfig.IsVerkle(header.Number, header.Time) {
// Bootstrap part deux: initialize the base root in the registry,
// as this is the first UBT block (which is the _second_ block of
// the transition, after the bootstrapping block that initializes
// the registry).
if miner.chainConfig.IsVerkle(parent.Number, parent.Time) {
if env.state.GetState(params.BinaryTransitionRegistryAddress, common.Hash{5}) == (common.Hash{}) {
env.state.SetState(params.BinaryTransitionRegistryAddress, common.Hash{5}, parent.Root)
}
}
}
if header.ParentBeaconRoot != nil {
core.ProcessBeaconBlockRoot(*header.ParentBeaconRoot, env.evm)
}
if miner.chainConfig.IsPrague(header.Number, header.Time) {
core.ProcessParentBlockHash(header.ParentHash, env.evm)
}
if miner.chainConfig.IsVerkle(header.Number, header.Time) {
core.InitializeBinaryTransitionRegistry(env.state)
}
return env, nil
}