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/event"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp" "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" "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. // 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 { 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 return err == nil
} }

View file

@ -184,6 +184,7 @@ var (
conversionProgressSlotKey = common.Hash{2} // slot 2: current slot pointer conversionProgressSlotKey = common.Hash{2} // slot 2: current slot pointer
conversionProgressStorageProcessed = common.Hash{3} // slot 3: storage processed flag conversionProgressStorageProcessed = common.Hash{3} // slot 3: storage processed flag
transitionEndedKey = common.Hash{4} // slot 4: non-zero if transition ended 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 // 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 storageProcessed := storageProcessedBytes[0] == 1
baseRoot, err := reader.Storage(params.BinaryTransitionRegistryAddress, baseRootKey)
if err != nil {
return nil
}
return &overlay.TransitionState{ return &overlay.TransitionState{
Started: started, Started: started,
Ended: ended, Ended: ended,
CurrentAccountAddress: &currentAccount, CurrentAccountAddress: &currentAccount,
CurrentSlotHash: currentSlotHash, CurrentSlotHash: currentSlotHash,
StorageProcessed: storageProcessed, 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. // OpenTrie opens the main account trie at a specific root hash.
func (db *CachingDB) OpenTrie(root common.Hash) (Trie, error) { func (db *CachingDB) OpenTrie(root common.Hash) (Trie, error) {
reader, err := db.triedb.StateReader(root) // Only attempt transition-aware trie opening in path scheme, since
if err != nil { // hashdb does not implement StateReader.
return nil, err if db.TrieDB().Scheme() == rawdb.PathScheme {
} reader, err := db.triedb.StateReader(root)
flatReader := newFlatReader(reader)
ts := LoadTransitionState(flatReader, root)
if isTransitionActive(flatReader) || db.triedb.IsVerkle() {
bt, err := bintrie.NewBinaryTrie(root, db.triedb)
if err != nil { if err != nil {
return nil, fmt.Errorf("could not open the overlay tree: %w", err) return nil, err
}
if !ts.InTransition() {
// Transition complete, use BinaryTrie only
return bt, nil
} }
flatReader := newFlatReader(reader)
base, err := trie.NewStateTrie(trie.StateTrieID(ts.BaseRoot), db.triedb) ts := LoadTransitionState(flatReader, root)
if err != nil { if isTransitionActive(flatReader) || db.triedb.IsVerkle() {
return nil, fmt.Errorf("could not create base trie in OpenTrie: %w", err) 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) 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) context = NewEVMBlockContext(header, p.chain, nil)
evm := vm.NewEVM(context, tracingStateDB, config, cfg) 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 { if beaconRoot := block.BeaconRoot(); beaconRoot != nil {
ProcessBeaconBlockRoot(*beaconRoot, evm) ProcessBeaconBlockRoot(*beaconRoot, evm)
} }
if config.IsPrague(block.Number(), block.Time()) || config.IsVerkle(block.Number(), block.Time()) { if config.IsPrague(block.Number(), block.Time()) || config.IsVerkle(block.Number(), block.Time()) {
ProcessParentBlockHash(block.ParentHash(), evm) ProcessParentBlockHash(block.ParentHash(), evm)
} }
if config.IsVerkle(header.Number, header.Time) {
InitializeBinaryTransitionRegistry(statedb)
}
// Iterate over and process the individual transactions // Iterate over and process the individual transactions
for i, tx := range block.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) 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) // Finalize the block, applying any consensus engine specific extras (e.g. block rewards)
p.chain.Engine().Finalize(p.chain, header, tracingStateDB, block.Body()) 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.SetCode(params.BinaryTransitionRegistryAddress, []byte{1, 2, 3}, tracing.CodeChangeUnspecified)
statedb.SetNonce(params.BinaryTransitionRegistryAddress, 1, tracing.NonceChangeUnspecified) 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} 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 { if requests != nil {
reqHash := types.CalcRequestsHash(requests) reqHash := types.CalcRequestsHash(requests)
work.header.RequestsHash = &reqHash 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) log.Error("Failed to create sealing context", "err", err)
return nil, 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 { if header.ParentBeaconRoot != nil {
core.ProcessBeaconBlockRoot(*header.ParentBeaconRoot, env.evm) core.ProcessBeaconBlockRoot(*header.ParentBeaconRoot, env.evm)
} }
if miner.chainConfig.IsPrague(header.Number, header.Time) { if miner.chainConfig.IsPrague(header.Number, header.Time) {
core.ProcessParentBlockHash(header.ParentHash, env.evm) core.ProcessParentBlockHash(header.ParentHash, env.evm)
} }
if miner.chainConfig.IsVerkle(header.Number, header.Time) {
core.InitializeBinaryTransitionRegistry(env.state)
}
return env, nil return env, nil
} }