diff --git a/core/blockchain_reader.go b/core/blockchain_reader.go index ee15c152c4..9938df0595 100644 --- a/core/blockchain_reader.go +++ b/core/blockchain_reader.go @@ -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 } diff --git a/core/state/database.go b/core/state/database.go index fadaae1b5b..0fc6a6e3bd 100644 --- a/core/state/database.go +++ b/core/state/database.go @@ -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: ¤tAccount, 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) } diff --git a/core/state_processor.go b/core/state_processor.go index 558181ca5b..eeffc5bb16 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -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()) diff --git a/core/transition_registry.go b/core/transition_registry.go index 83cb62e30a..20f4a282ad 100644 --- a/core/transition_registry.go +++ b/core/transition_registry.go @@ -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 } diff --git a/miner/worker.go b/miner/worker.go index cb2e24d0cd..a3325c10cb 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -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 }