From ad3281f4e7ad08b5d40acec2b7c7bc44758aa2fe Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Wed, 8 Nov 2023 10:51:02 +0100 Subject: [PATCH 01/22] post-genesis transition --- core/blockchain.go | 6 +-- core/overlay_transition.go | 42 +++++++++--------- core/state/database.go | 89 ++++++++++++++++++++++---------------- core/state/statedb.go | 9 ++-- core/state_processor.go | 5 ++- eth/catalyst/api.go | 2 +- light/trie.go | 24 +++++----- miner/worker.go | 4 +- 8 files changed, 98 insertions(+), 83 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index a97bfb20c1aa..ea521835a740 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -1747,7 +1747,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) } if parent.Number.Uint64() == conversionBlock { - bc.StartVerkleTransition(parent.Root, emptyVerkleRoot, bc.Config(), &parent.Time) + bc.StartVerkleTransition(parent.Root, emptyVerkleRoot, bc.Config(), &parent.Time, parent.Root) bc.stateCache.SetLastMerkleRoot(parent.Root) } statedb, err := state.New(parent.Root, bc.stateCache, bc.snaps) @@ -2533,8 +2533,8 @@ func (bc *BlockChain) GetTrieFlushInterval() time.Duration { return time.Duration(bc.flushInterval.Load()) } -func (bc *BlockChain) StartVerkleTransition(originalRoot, translatedRoot common.Hash, chainConfig *params.ChainConfig, pragueTime *uint64) { - bc.stateCache.StartVerkleTransition(originalRoot, translatedRoot, chainConfig, pragueTime) +func (bc *BlockChain) StartVerkleTransition(originalRoot, translatedRoot common.Hash, chainConfig *params.ChainConfig, pragueTime *uint64, root common.Hash) { + bc.stateCache.StartVerkleTransition(originalRoot, translatedRoot, chainConfig, pragueTime, root) } func (bc *BlockChain) ReorgThroughVerkleTransition() { bc.stateCache.ReorgThroughVerkleTransition() diff --git a/core/overlay_transition.go b/core/overlay_transition.go index 35c09d22d938..24bb7d5e6c02 100644 --- a/core/overlay_transition.go +++ b/core/overlay_transition.go @@ -35,7 +35,7 @@ import ( ) // OverlayVerkleTransition contains the overlay conversion logic -func OverlayVerkleTransition(statedb *state.StateDB) error { +func OverlayVerkleTransition(statedb *state.StateDB, root common.Hash) error { migrdb := statedb.Database() // verkle transition: if the conversion process is in progress, move @@ -47,7 +47,7 @@ func OverlayVerkleTransition(statedb *state.StateDB) error { mpt = tt.Base() vkt = tt.Overlay() hasPreimagesBin = false - preimageSeek = migrdb.GetCurrentPreimageOffset() + preimageSeek = migrdb.GetCurrentPreimageOffset(root) fpreimages *bufio.Reader ) @@ -65,7 +65,7 @@ func OverlayVerkleTransition(statedb *state.StateDB) error { hasPreimagesBin = true } - accIt, err := statedb.Snaps().AccountIterator(mpt.Hash(), migrdb.GetCurrentAccountHash()) + accIt, err := statedb.Snaps().AccountIterator(mpt.Hash(), migrdb.GetCurrentAccountHash(root)) if err != nil { return err } @@ -73,7 +73,7 @@ func OverlayVerkleTransition(statedb *state.StateDB) error { accIt.Next() // If we're about to start with the migration process, we have to read the first account hash preimage. - if migrdb.GetCurrentAccountAddress() == nil { + if migrdb.GetCurrentAccountAddress(root) == nil { var addr common.Address if hasPreimagesBin { if _, err := io.ReadFull(fpreimages, addr[:]); err != nil { @@ -85,8 +85,8 @@ func OverlayVerkleTransition(statedb *state.StateDB) error { return fmt.Errorf("addr len is zero is not 32: %d", len(addr)) } } - migrdb.SetCurrentAccountAddress(addr) - if migrdb.GetCurrentAccountHash() != accIt.Hash() { + migrdb.SetCurrentAccountAddress(addr, root) + if migrdb.GetCurrentAccountHash(root) != accIt.Hash() { return fmt.Errorf("preimage file does not match account hash: %s != %s", crypto.Keccak256Hash(addr[:]), accIt.Hash()) } preimageSeek += int64(len(addr)) @@ -108,7 +108,7 @@ func OverlayVerkleTransition(statedb *state.StateDB) error { log.Error("Invalid account encountered during traversal", "error", err) return err } - vkt.SetStorageRootConversion(*migrdb.GetCurrentAccountAddress(), acc.Root) + vkt.SetStorageRootConversion(*migrdb.GetCurrentAccountAddress(root), acc.Root) // Start with processing the storage, because once the account is // converted, the `stateRoot` field loses its meaning. Which means @@ -120,7 +120,7 @@ func OverlayVerkleTransition(statedb *state.StateDB) error { // to during normal block execution. A mitigation strategy has been // introduced with the `*StorageRootConversion` fields in VerkleDB. if acc.HasStorage() { - stIt, err := statedb.Snaps().StorageIterator(mpt.Hash(), accIt.Hash(), migrdb.GetCurrentSlotHash()) + stIt, err := statedb.Snaps().StorageIterator(mpt.Hash(), accIt.Hash(), migrdb.GetCurrentSlotHash(root)) if err != nil { return err } @@ -132,7 +132,7 @@ func OverlayVerkleTransition(statedb *state.StateDB) error { // processing the storage for that account where we left off. // If the entire storage was processed, then the iterator was // created in vain, but it's ok as this will not happen often. - for ; !migrdb.GetStorageProcessed() && count < maxMovedCount; count++ { + for ; !migrdb.GetStorageProcessed(root) && count < maxMovedCount; count++ { var ( value []byte // slot value after RLP decoding safeValue [32]byte // 32-byte aligned value @@ -160,12 +160,12 @@ func OverlayVerkleTransition(statedb *state.StateDB) error { } preimageSeek += int64(len(slotnr)) - mkv.addStorageSlot(migrdb.GetCurrentAccountAddress().Bytes(), slotnr, safeValue[:]) + mkv.addStorageSlot(migrdb.GetCurrentAccountAddress(root).Bytes(), slotnr, safeValue[:]) // advance the storage iterator - migrdb.SetStorageProcessed(!stIt.Next()) - if !migrdb.GetStorageProcessed() { - migrdb.SetCurrentSlotHash(stIt.Hash()) + migrdb.SetStorageProcessed(!stIt.Next(), root) + if !migrdb.GetStorageProcessed(root) { + migrdb.SetCurrentSlotHash(stIt.Hash(), root) } } stIt.Release() @@ -178,20 +178,20 @@ func OverlayVerkleTransition(statedb *state.StateDB) error { if count < maxMovedCount { count++ // count increase for the account itself - mkv.addAccount(migrdb.GetCurrentAccountAddress().Bytes(), acc) - vkt.ClearStrorageRootConversion(*migrdb.GetCurrentAccountAddress()) + mkv.addAccount(migrdb.GetCurrentAccountAddress(root).Bytes(), acc) + vkt.ClearStrorageRootConversion(*migrdb.GetCurrentAccountAddress(root)) // Store the account code if present if !bytes.Equal(acc.CodeHash, types.EmptyCodeHash[:]) { code := rawdb.ReadCode(statedb.Database().DiskDB(), common.BytesToHash(acc.CodeHash)) chunks := trie.ChunkifyCode(code) - mkv.addAccountCode(migrdb.GetCurrentAccountAddress().Bytes(), uint64(len(code)), chunks) + mkv.addAccountCode(migrdb.GetCurrentAccountAddress(root).Bytes(), uint64(len(code)), chunks) } // reset storage iterator marker for next account - migrdb.SetStorageProcessed(false) - migrdb.SetCurrentSlotHash(common.Hash{}) + migrdb.SetStorageProcessed(false, root) + migrdb.SetCurrentSlotHash(common.Hash{}, root) // Move to the next account, if available - or end // the transition otherwise. @@ -212,7 +212,7 @@ func OverlayVerkleTransition(statedb *state.StateDB) error { return fmt.Errorf("preimage file does not match account hash: %s != %s", crypto.Keccak256Hash(addr[:]), accIt.Hash()) } preimageSeek += int64(len(addr)) - migrdb.SetCurrentAccountAddress(addr) + migrdb.SetCurrentAccountAddress(addr, root) } else { // case when the account iterator has // reached the end but count < maxCount @@ -221,9 +221,9 @@ func OverlayVerkleTransition(statedb *state.StateDB) error { } } } - migrdb.SetCurrentPreimageOffset(preimageSeek) + migrdb.SetCurrentPreimageOffset(preimageSeek, root) - log.Info("Collected key values from base tree", "count", count, "duration", time.Since(now), "last account", statedb.Database().GetCurrentAccountHash()) + log.Info("Collected key values from base tree", "count", count, "duration", time.Since(now), "last account", statedb.Database().GetCurrentAccountHash(root)) // Take all the collected key-values and prepare the new leaf values. // This fires a background routine that will start doing the work that diff --git a/core/state/database.go b/core/state/database.go index 5707e2c88b60..7c42cb9cde5b 100644 --- a/core/state/database.go +++ b/core/state/database.go @@ -64,7 +64,7 @@ type Database interface { // TrieDB retrieves the low level trie database used for data storage. TrieDB() *trie.Database - StartVerkleTransition(originalRoot, translatedRoot common.Hash, chainConfig *params.ChainConfig, cancunTime *uint64) + StartVerkleTransition(originalRoot, translatedRoot common.Hash, chainConfig *params.ChainConfig, cancunTime *uint64, root common.Hash) ReorgThroughVerkleTransition() @@ -74,27 +74,27 @@ type Database interface { Transitioned() bool - SetCurrentSlotHash(hash common.Hash) + SetCurrentSlotHash(common.Hash, common.Hash) - GetCurrentAccountAddress() *common.Address + GetCurrentAccountAddress(common.Hash) *common.Address - SetCurrentAccountAddress(common.Address) + SetCurrentAccountAddress(common.Address, common.Hash) - GetCurrentAccountHash() common.Hash + GetCurrentAccountHash(common.Hash) common.Hash - GetCurrentSlotHash() common.Hash + GetCurrentSlotHash(common.Hash) common.Hash - SetStorageProcessed(bool) + SetStorageProcessed(bool, common.Hash) - GetStorageProcessed() bool + GetStorageProcessed(common.Hash) bool - GetCurrentPreimageOffset() int64 + GetCurrentPreimageOffset(common.Hash) int64 - SetCurrentPreimageOffset(int64) + SetCurrentPreimageOffset(int64, common.Hash) AddRootTranslation(originalRoot, translatedRoot common.Hash) - SetLastMerkleRoot(root common.Hash) + SetLastMerkleRoot(common.Hash) } // Trie is a Ethereum Merkle Patricia trie. @@ -187,6 +187,10 @@ func NewDatabaseWithConfig(db ethdb.Database, config *trie.Config) Database { codeCache: lru.NewSizeConstrainedCache[common.Hash, []byte](codeCacheSize), triedb: trie.NewDatabaseWithConfig(db, config), addrToPoint: utils.NewPointCache(), + StorageProcessed: map[common.Hash]bool{}, + CurrentAccountAddress: map[common.Hash]*common.Address{}, + CurrentSlotHash: map[common.Hash]common.Hash{}, + CurrentPreimageOffset: map[common.Hash]int64{}, } } @@ -199,6 +203,10 @@ func NewDatabaseWithNodeDB(db ethdb.Database, triedb *trie.Database) Database { triedb: triedb, addrToPoint: utils.NewPointCache(), ended: triedb.IsVerkle(), + StorageProcessed: map[common.Hash]bool{}, + CurrentAccountAddress: map[common.Hash]*common.Address{}, + CurrentSlotHash: map[common.Hash]common.Hash{}, + CurrentPreimageOffset: map[common.Hash]int64{}, } } @@ -211,7 +219,7 @@ func (db *cachingDB) Transitioned() bool { } // Fork implements the fork -func (db *cachingDB) StartVerkleTransition(originalRoot, translatedRoot common.Hash, chainConfig *params.ChainConfig, pragueTime *uint64) { +func (db *cachingDB) StartVerkleTransition(originalRoot, translatedRoot common.Hash, chainConfig *params.ChainConfig, pragueTime *uint64, root common.Hash) { fmt.Println(` __________.__ .__ .__ __ .__ .__ ____ \__ ___| |__ ____ ____ | | ____ ______ | |__ _____ _____/ |_ | |__ _____ ______ __ _ _|__| ____ / ___\ ______ @@ -224,7 +232,12 @@ func (db *cachingDB) StartVerkleTransition(originalRoot, translatedRoot common.H // db.AddTranslation(originalRoot, translatedRoot) db.baseRoot = originalRoot // initialize so that the first storage-less accounts are processed - db.StorageProcessed = true + db.StorageProcessed[root] = true + + // Reinitialize values in case of a reorg + db.CurrentAccountAddress[root] = &(common.Address{}) + db.CurrentSlotHash[root] = common.Hash{} + db.CurrentPreimageOffset[root] = 0 if pragueTime != nil { chainConfig.PragueTime = pragueTime } @@ -263,14 +276,14 @@ type cachingDB struct { addrToPoint *utils.PointCache baseRoot common.Hash // hash of the read-only base tree - CurrentAccountAddress *common.Address // addresss of the last translated account - CurrentSlotHash common.Hash // hash of the last translated storage slot - CurrentPreimageOffset int64 // next byte to read from the preimage file + CurrentAccountAddress map[common.Hash]*common.Address // addresss of the last translated account + CurrentSlotHash map[common.Hash]common.Hash // hash of the last translated storage slot + CurrentPreimageOffset map[common.Hash]int64 // next byte to read from the preimage file // Mark whether the storage for an account has been processed. This is useful if the // maximum number of leaves of the conversion is reached before the whole storage is // processed. - StorageProcessed bool + StorageProcessed map[common.Hash]bool } func (db *cachingDB) openMPTTrie(root common.Hash) (Trie, error) { @@ -450,49 +463,49 @@ func (db *cachingDB) GetTreeKeyHeader(addr []byte) *verkle.Point { return db.addrToPoint.GetTreeKeyHeader(addr) } -func (db *cachingDB) SetCurrentAccountAddress(addr common.Address) { - db.CurrentAccountAddress = &addr +func (db *cachingDB) SetCurrentAccountAddress(addr common.Address, root common.Hash) { + db.CurrentAccountAddress[root] = &addr } -func (db *cachingDB) GetCurrentAccountHash() common.Hash { +func (db *cachingDB) GetCurrentAccountHash(root common.Hash) common.Hash { var addrHash common.Hash - if db.CurrentAccountAddress != nil { - addrHash = crypto.Keccak256Hash(db.CurrentAccountAddress[:]) + if db.CurrentAccountAddress[root] != nil { + addrHash = crypto.Keccak256Hash(db.CurrentAccountAddress[root][:]) } return addrHash } -func (db *cachingDB) GetCurrentAccountAddress() *common.Address { - return db.CurrentAccountAddress +func (db *cachingDB) GetCurrentAccountAddress(root common.Hash) *common.Address { + return db.CurrentAccountAddress[root] } -func (db *cachingDB) GetCurrentPreimageOffset() int64 { - return db.CurrentPreimageOffset +func (db *cachingDB) GetCurrentPreimageOffset(root common.Hash) int64 { + return db.CurrentPreimageOffset[root] } -func (db *cachingDB) SetCurrentPreimageOffset(offset int64) { - db.CurrentPreimageOffset = offset +func (db *cachingDB) SetCurrentPreimageOffset(offset int64, root common.Hash) { + db.CurrentPreimageOffset[root] = offset } -func (db *cachingDB) SetCurrentSlotHash(hash common.Hash) { - db.CurrentSlotHash = hash +func (db *cachingDB) SetCurrentSlotHash(hash common.Hash, root common.Hash) { + db.CurrentSlotHash[root] = hash } -func (db *cachingDB) GetCurrentSlotHash() common.Hash { - return db.CurrentSlotHash +func (db *cachingDB) GetCurrentSlotHash(root common.Hash) common.Hash { + return db.CurrentSlotHash[root] } -func (db *cachingDB) SetStorageProcessed(processed bool) { - db.StorageProcessed = processed +func (db *cachingDB) SetStorageProcessed(processed bool, root common.Hash) { + db.StorageProcessed[root] = processed } -func (db *cachingDB) GetStorageProcessed() bool { - return db.StorageProcessed +func (db *cachingDB) GetStorageProcessed(root common.Hash) bool { + return db.StorageProcessed[root] } func (db *cachingDB) AddRootTranslation(originalRoot, translatedRoot common.Hash) { } -func (db *cachingDB) SetLastMerkleRoot(root common.Hash) { - db.LastMerkleRoot = root +func (db *cachingDB) SetLastMerkleRoot(merkleRoot common.Hash) { + db.LastMerkleRoot = merkleRoot } diff --git a/core/state/statedb.go b/core/state/statedb.go index 1b47746458b5..a10c5252f113 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -176,10 +176,11 @@ func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error) if tr.IsVerkle() { sdb.witness = sdb.NewAccessWitness() } - // if sdb.snaps != nil { - // if sdb.snap = sdb.snaps.Snapshot(root); sdb.snap == nil { - // } - // } + if sdb.snaps != nil { + // if sdb.snap = sdb.snaps.Snapshot(root); sdb.snap == nil { + // } + sdb.snap = sdb.snaps.Snapshot(root) + } return sdb, nil } diff --git a/core/state_processor.go b/core/state_processor.go index d28df7aac1d0..8cc8ac50ca3d 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -115,7 +115,8 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg } // Perform the overlay transition, if relevant - if err := OverlayVerkleTransition(statedb); err != nil { + parent := p.bc.GetHeaderByHash(header.ParentHash) + if err := OverlayVerkleTransition(statedb, parent.Root); err != nil { return nil, nil, 0, fmt.Errorf("error performing verkle overlay transition: %w", err) } @@ -329,7 +330,7 @@ func (kvm *keyValueMigrator) prepare() { var currAddr common.Address var currPoint *verkle.Point for i := range batch { - if batch[i].branchKey.addr != currAddr { + if batch[i].branchKey.addr != currAddr || currAddr == (common.Address{}) { currAddr = batch[i].branchKey.addr currPoint = tutils.EvaluateAddressPoint(currAddr[:]) } diff --git a/eth/catalyst/api.go b/eth/catalyst/api.go index 63079415fc14..864b3efe84f5 100644 --- a/eth/catalyst/api.go +++ b/eth/catalyst/api.go @@ -532,7 +532,7 @@ func (api *ConsensusAPI) newPayload(params engine.ExecutableData, versionedHashe if api.eth.BlockChain().Config().IsPrague(block.Number(), block.Time()) && !api.eth.BlockChain().Config().IsPrague(parent.Number(), parent.Time()) { parent := api.eth.BlockChain().GetHeaderByNumber(block.NumberU64() - 1) if !api.eth.BlockChain().Config().IsPrague(parent.Number, parent.Time) { - api.eth.BlockChain().StartVerkleTransition(parent.Root, common.Hash{}, api.eth.BlockChain().Config(), nil) + api.eth.BlockChain().StartVerkleTransition(parent.Root, common.Hash{}, api.eth.BlockChain().Config(), nil, parent.Root) } } // Reset db merge state in case of a reorg diff --git a/light/trie.go b/light/trie.go index 53d54615d909..6d0c654ff111 100644 --- a/light/trie.go +++ b/light/trie.go @@ -101,7 +101,7 @@ func (db *odrDatabase) DiskDB() ethdb.KeyValueStore { panic("not implemented") } -func (db *odrDatabase) StartVerkleTransition(originalRoot common.Hash, translatedRoot common.Hash, chainConfig *params.ChainConfig, _ *uint64) { +func (db *odrDatabase) StartVerkleTransition(originalRoot common.Hash, translatedRoot common.Hash, chainConfig *params.ChainConfig, _ *uint64, _ common.Hash) { panic("not implemented") // TODO: Implement } @@ -121,47 +121,47 @@ func (db *odrDatabase) Transitioned() bool { panic("not implemented") // TODO: Implement } -func (db *odrDatabase) SetCurrentSlotHash(hash common.Hash) { +func (db *odrDatabase) SetCurrentSlotHash(common.Hash, common.Hash) { panic("not implemented") // TODO: Implement } -func (db *odrDatabase) GetCurrentAccountAddress() *common.Address { +func (db *odrDatabase) GetCurrentAccountAddress(common.Hash) *common.Address { panic("not implemented") // TODO: Implement } -func (db *odrDatabase) SetCurrentAccountAddress(_ common.Address) { +func (db *odrDatabase) SetCurrentAccountAddress(common.Address, common.Hash) { panic("not implemented") // TODO: Implement } -func (db *odrDatabase) GetCurrentAccountHash() common.Hash { +func (db *odrDatabase) GetCurrentAccountHash(common.Hash) common.Hash { panic("not implemented") // TODO: Implement } -func (db *odrDatabase) GetCurrentSlotHash() common.Hash { +func (db *odrDatabase) GetCurrentSlotHash(common.Hash) common.Hash { panic("not implemented") // TODO: Implement } -func (db *odrDatabase) SetStorageProcessed(_ bool) { +func (db *odrDatabase) SetStorageProcessed(bool, common.Hash) { panic("not implemented") // TODO: Implement } -func (db *odrDatabase) GetStorageProcessed() bool { +func (db *odrDatabase) GetStorageProcessed(common.Hash) bool { panic("not implemented") // TODO: Implement } -func (db *odrDatabase) GetCurrentPreimageOffset() int64 { +func (db *odrDatabase) GetCurrentPreimageOffset(common.Hash) int64 { panic("not implemented") // TODO: Implement } -func (db *odrDatabase) SetCurrentPreimageOffset(_ int64) { +func (db *odrDatabase) SetCurrentPreimageOffset(int64, common.Hash) { panic("not implemented") // TODO: Implement } -func (db *odrDatabase) AddRootTranslation(originalRoot common.Hash, translatedRoot common.Hash) { +func (db *odrDatabase) AddRootTranslation(common.Hash, common.Hash) { panic("not implemented") // TODO: Implement } -func (db *odrDatabase) SetLastMerkleRoot(root common.Hash) { +func (db *odrDatabase) SetLastMerkleRoot(common.Hash) { panic("not implemented") // TODO: Implement } diff --git a/miner/worker.go b/miner/worker.go index aae4fe8b6454..1d1b2fda07a2 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -894,7 +894,7 @@ func (w *worker) prepareWork(genParams *generateParams) (*environment, error) { if w.chain.Config().IsPrague(header.Number, header.Time) { parent := w.chain.GetHeaderByNumber(header.Number.Uint64() - 1) if !w.chain.Config().IsPrague(parent.Number, parent.Time) { - w.chain.StartVerkleTransition(parent.Root, common.Hash{}, w.chain.Config(), nil) + w.chain.StartVerkleTransition(parent.Root, common.Hash{}, w.chain.Config(), w.chain.Config().PragueTime, parent.Root) } } @@ -905,7 +905,7 @@ func (w *worker) prepareWork(genParams *generateParams) (*environment, error) { return nil, err } if w.chain.Config().IsPrague(header.Number, header.Time) { - core.OverlayVerkleTransition(state) + core.OverlayVerkleTransition(state, parent.Root) } // Run the consensus preparation with the default or customized consensus engine. if err := w.engine.Prepare(w.chain, header); err != nil { From b7381c0240c46156060c7f5704af5ac24af4df9d Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Wed, 8 Nov 2023 11:05:08 +0100 Subject: [PATCH 02/22] quell linter issue --- core/state/database.go | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/core/state/database.go b/core/state/database.go index 7c42cb9cde5b..cd9f8dcf43f1 100644 --- a/core/state/database.go +++ b/core/state/database.go @@ -182,11 +182,11 @@ func NewDatabase(db ethdb.Database) Database { // large memory cache. func NewDatabaseWithConfig(db ethdb.Database, config *trie.Config) Database { return &cachingDB{ - disk: db, - codeSizeCache: lru.NewCache[common.Hash, int](codeSizeCacheSize), - codeCache: lru.NewSizeConstrainedCache[common.Hash, []byte](codeCacheSize), - triedb: trie.NewDatabaseWithConfig(db, config), - addrToPoint: utils.NewPointCache(), + disk: db, + codeSizeCache: lru.NewCache[common.Hash, int](codeSizeCacheSize), + codeCache: lru.NewSizeConstrainedCache[common.Hash, []byte](codeCacheSize), + triedb: trie.NewDatabaseWithConfig(db, config), + addrToPoint: utils.NewPointCache(), StorageProcessed: map[common.Hash]bool{}, CurrentAccountAddress: map[common.Hash]*common.Address{}, CurrentSlotHash: map[common.Hash]common.Hash{}, @@ -197,12 +197,12 @@ func NewDatabaseWithConfig(db ethdb.Database, config *trie.Config) Database { // NewDatabaseWithNodeDB creates a state database with an already initialized node database. func NewDatabaseWithNodeDB(db ethdb.Database, triedb *trie.Database) Database { return &cachingDB{ - disk: db, - codeSizeCache: lru.NewCache[common.Hash, int](codeSizeCacheSize), - codeCache: lru.NewSizeConstrainedCache[common.Hash, []byte](codeCacheSize), - triedb: triedb, - addrToPoint: utils.NewPointCache(), - ended: triedb.IsVerkle(), + disk: db, + codeSizeCache: lru.NewCache[common.Hash, int](codeSizeCacheSize), + codeCache: lru.NewSizeConstrainedCache[common.Hash, []byte](codeCacheSize), + triedb: triedb, + addrToPoint: utils.NewPointCache(), + ended: triedb.IsVerkle(), StorageProcessed: map[common.Hash]bool{}, CurrentAccountAddress: map[common.Hash]*common.Address{}, CurrentSlotHash: map[common.Hash]common.Hash{}, @@ -275,7 +275,7 @@ type cachingDB struct { addrToPoint *utils.PointCache - baseRoot common.Hash // hash of the read-only base tree + baseRoot common.Hash // hash of the read-only base tree CurrentAccountAddress map[common.Hash]*common.Address // addresss of the last translated account CurrentSlotHash map[common.Hash]common.Hash // hash of the last translated storage slot CurrentPreimageOffset map[common.Hash]int64 // next byte to read from the preimage file From d49cbd6331ffbac89fc55a7a63ff181d859ed3a0 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Wed, 8 Nov 2023 17:33:28 +0100 Subject: [PATCH 03/22] support Transition post tree in conversion --- consensus/beacon/consensus.go | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/consensus/beacon/consensus.go b/consensus/beacon/consensus.go index 35a7ed2b56d0..5eeddc47f07e 100644 --- a/consensus/beacon/consensus.go +++ b/consensus/beacon/consensus.go @@ -410,7 +410,19 @@ func (beacon *Beacon) FinalizeAndAssemble(chain consensus.ChainHeaderReader, hea switch pre := preTrie.(type) { case *trie.VerkleTrie: vtrpre, okpre = preTrie.(*trie.VerkleTrie) - vtrpost, okpost = state.GetTrie().(*trie.VerkleTrie) + switch tr := state.GetTrie().(type) { + case *trie.VerkleTrie: + vtrpost = tr + okpost = true + // This is to handle a situation right at the start of the conversion: + // the post trie is a transition tree when the pre tree is an empty + // verkle tree. + case *trie.TransitionTrie: + vtrpost = tr.Overlay() + okpost = true + default: + okpost = false + } case *trie.TransitionTrie: vtrpre = pre.Overlay() okpre = true From a7bc86f6dcca1ee9e2e18f04985225fdb8aa5a75 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Thu, 30 Nov 2023 14:45:58 +0100 Subject: [PATCH 04/22] Refactor transition post genesis (#311) * rewrite per-block conversion pointer management * remove unused method * fix: a branch that can verge at genesis or post genesis (#314) * fix: import cycle in conversion refactor (#315) * fix shadowfork panic in OpenStorageTrie --- consensus/beacon/consensus.go | 7 + core/blockchain.go | 6 + core/chain_makers.go | 7 + core/genesis.go | 21 +- .../conversion.go} | 224 ++++++++++++++++-- core/state/database.go | 206 ++++++++++------ core/state_processor.go | 200 +--------------- light/trie.go | 30 ++- miner/worker.go | 3 - 9 files changed, 389 insertions(+), 315 deletions(-) rename core/{overlay_transition.go => overlay/conversion.go} (53%) diff --git a/consensus/beacon/consensus.go b/consensus/beacon/consensus.go index 5eeddc47f07e..dd94b8da7790 100644 --- a/consensus/beacon/consensus.go +++ b/consensus/beacon/consensus.go @@ -25,6 +25,7 @@ import ( "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus/misc/eip1559" "github.com/ethereum/go-ethereum/consensus/misc/eip4844" + "github.com/ethereum/go-ethereum/core/overlay" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/params" @@ -363,6 +364,12 @@ func (beacon *Beacon) Finalize(chain consensus.ChainHeaderReader, header *types. state.Witness().TouchAddressOnWriteAndComputeGas(w.Address[:], uint256.Int{}, utils.CodeKeccakLeafKey) state.Witness().TouchAddressOnWriteAndComputeGas(w.Address[:], uint256.Int{}, utils.CodeSizeLeafKey) } + + if chain.Config().IsPrague(header.Number, header.Time) { + fmt.Println("at block", header.Number, "performing transition?", state.Database().InTransition()) + parent := chain.GetHeaderByHash(header.ParentHash) + overlay.OverlayVerkleTransition(state, parent.Root) + } } // FinalizeAndAssemble implements consensus.Engine, setting the final state and diff --git a/core/blockchain.go b/core/blockchain.go index ea521835a740..6dcd0e81fab3 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -313,6 +313,12 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis // Declare the end of the verkle transition if need be if bc.chainConfig.Rules(head.Number, false /* XXX */, head.Time).IsPrague { + // TODO this only works when resuming a chain that has already gone + // through the conversion. All pointers should be saved to the DB + // for it to be able to recover if interrupted during the transition + // but that's left out to a later PR since there's not really a need + // right now. + bc.stateCache.InitTransitionStatus(true, true) bc.stateCache.EndVerkleTransition() } diff --git a/core/chain_makers.go b/core/chain_makers.go index 5b9dc0c6ff08..3909500d91fa 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -435,8 +435,15 @@ func GenerateVerkleChain(config *params.ChainConfig, parent *types.Block, engine } var snaps *snapshot.Tree triedb := state.NewDatabaseWithConfig(db, nil) + triedb.StartVerkleTransition(common.Hash{}, common.Hash{}, config, config.PragueTime, common.Hash{}) triedb.EndVerkleTransition() + //statedb, err := state.New(parent.Root(), triedb, snaps) + //if err != nil { + // panic(fmt.Sprintf("could not find state for block %d: err=%v, parent root=%x", parent.NumberU64(), err, parent.Root())) + //} + statedb.Database().SaveTransitionState(parent.Root()) for i := 0; i < n; i++ { + // XXX merge uncommment statedb, err := state.New(parent.Root(), triedb, snaps) if err != nil { panic(fmt.Sprintf("could not find state for block %d: err=%v, parent root=%x", i, err, parent.Root())) diff --git a/core/genesis.go b/core/genesis.go index c8a4bc5952d9..0aad87a10d70 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -126,6 +126,7 @@ func (ga *GenesisAlloc) deriveHash(cfg *params.ChainConfig, timestamp uint64) (c // all the derived states will be discarded to not pollute disk. db := state.NewDatabase(rawdb.NewMemoryDatabase()) if cfg.IsPrague(big.NewInt(int64(0)), timestamp) { + db.StartVerkleTransition(common.Hash{}, common.Hash{}, cfg, ×tamp, common.Hash{}) db.EndVerkleTransition() } statedb, err := state.New(types.EmptyRootHash, db, nil) @@ -146,15 +147,17 @@ func (ga *GenesisAlloc) deriveHash(cfg *params.ChainConfig, timestamp uint64) (c // flush is very similar with deriveHash, but the main difference is // all the generated states will be persisted into the given database. // Also, the genesis state specification will be flushed as well. -func (ga *GenesisAlloc) flush(db ethdb.Database, triedb *trie.Database, blockhash common.Hash, cfg *params.ChainConfig) error { - statedb, err := state.New(types.EmptyRootHash, state.NewDatabaseWithNodeDB(db, triedb), nil) - if err != nil { - return err +func (ga *GenesisAlloc) flush(db ethdb.Database, triedb *trie.Database, blockhash common.Hash, cfg *params.ChainConfig, timestamp *uint64) error { + database := state.NewDatabaseWithNodeDB(db, triedb) + // End the verkle conversion at genesis if the fork block is 0 + if timestamp != nil && cfg.IsPrague(big.NewInt(int64(0)), *timestamp) { + database.StartVerkleTransition(common.Hash{}, common.Hash{}, cfg, timestamp, common.Hash{}) + database.EndVerkleTransition() } - // End the verkle conversion at genesis if the fork block is 0 - if triedb.IsVerkle() { - statedb.Database().EndVerkleTransition() + statedb, err := state.New(types.EmptyRootHash, database, nil) + if err != nil { + return err } for addr, account := range *ga { @@ -221,7 +224,7 @@ func CommitGenesisState(db ethdb.Database, triedb *trie.Database, blockhash comm return errors.New("not found") } } - return alloc.flush(db, triedb, blockhash, config) + return alloc.flush(db, triedb, blockhash, config, nil) } // GenesisAccount is an account in the state of the genesis block. @@ -536,7 +539,7 @@ func (g *Genesis) Commit(db ethdb.Database, triedb *trie.Database) (*types.Block // All the checks has passed, flush the states derived from the genesis // specification as well as the specification itself into the provided // database. - if err := g.Alloc.flush(db, triedb, block.Hash(), g.Config); err != nil { + if err := g.Alloc.flush(db, triedb, block.Hash(), g.Config, &g.Timestamp); err != nil { return nil, err } rawdb.WriteTd(db, block.Hash(), block.NumberU64(), block.Difficulty()) diff --git a/core/overlay_transition.go b/core/overlay/conversion.go similarity index 53% rename from core/overlay_transition.go rename to core/overlay/conversion.go index 24bb7d5e6c02..e76aa5900173 100644 --- a/core/overlay_transition.go +++ b/core/overlay/conversion.go @@ -14,14 +14,17 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -package core +package overlay import ( "bufio" "bytes" + "encoding/binary" "fmt" "io" "os" + "runtime" + "sync" "time" "github.com/ethereum/go-ethereum/common" @@ -32,8 +35,187 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/trie" + "github.com/ethereum/go-ethereum/trie/utils" + "github.com/gballet/go-verkle" + "github.com/holiman/uint256" ) +var zeroTreeIndex uint256.Int + +// keyValueMigrator is a helper module that collects key-values from the overlay-tree migration for Verkle Trees. +// It assumes that the walk of the base tree is done in address-order, so it exploit that fact to +// collect the key-values in a way that is efficient. +type keyValueMigrator struct { + // leafData contains the values for the future leaf for a particular VKT branch. + leafData []migratedKeyValue + + // When prepare() is called, it will start a background routine that will process the leafData + // saving the result in newLeaves to be used by migrateCollectedKeyValues(). The background + // routine signals that it is done by closing processingReady. + processingReady chan struct{} + newLeaves []verkle.LeafNode + prepareErr error +} + +func newKeyValueMigrator() *keyValueMigrator { + // We do initialize the VKT config since prepare() might indirectly make multiple GetConfig() calls + // in different goroutines when we never called GetConfig() before, causing a race considering the way + // that `config` is designed in go-verkle. + // TODO: jsign as a fix for this in the PR where we move to a file-less precomp, since it allows safe + // concurrent calls to GetConfig(). When that gets merged, we can remove this line. + _ = verkle.GetConfig() + return &keyValueMigrator{ + processingReady: make(chan struct{}), + leafData: make([]migratedKeyValue, 0, 10_000), + } +} + +type migratedKeyValue struct { + branchKey branchKey + leafNodeData verkle.BatchNewLeafNodeData +} +type branchKey struct { + addr common.Address + treeIndex uint256.Int +} + +func newBranchKey(addr []byte, treeIndex *uint256.Int) branchKey { + var sk branchKey + copy(sk.addr[:], addr) + sk.treeIndex = *treeIndex + return sk +} + +func (kvm *keyValueMigrator) addStorageSlot(addr []byte, slotNumber []byte, slotValue []byte) { + treeIndex, subIndex := utils.GetTreeKeyStorageSlotTreeIndexes(slotNumber) + leafNodeData := kvm.getOrInitLeafNodeData(newBranchKey(addr, treeIndex)) + leafNodeData.Values[subIndex] = slotValue +} + +func (kvm *keyValueMigrator) addAccount(addr []byte, acc *types.StateAccount) { + leafNodeData := kvm.getOrInitLeafNodeData(newBranchKey(addr, &zeroTreeIndex)) + + var version [verkle.LeafValueSize]byte + leafNodeData.Values[utils.VersionLeafKey] = version[:] + + var balance [verkle.LeafValueSize]byte + for i, b := range acc.Balance.Bytes() { + balance[len(acc.Balance.Bytes())-1-i] = b + } + leafNodeData.Values[utils.BalanceLeafKey] = balance[:] + + var nonce [verkle.LeafValueSize]byte + binary.LittleEndian.PutUint64(nonce[:8], acc.Nonce) + leafNodeData.Values[utils.NonceLeafKey] = nonce[:] + + leafNodeData.Values[utils.CodeKeccakLeafKey] = acc.CodeHash[:] +} + +func (kvm *keyValueMigrator) addAccountCode(addr []byte, codeSize uint64, chunks []byte) { + leafNodeData := kvm.getOrInitLeafNodeData(newBranchKey(addr, &zeroTreeIndex)) + + // Save the code size. + var codeSizeBytes [verkle.LeafValueSize]byte + binary.LittleEndian.PutUint64(codeSizeBytes[:8], codeSize) + leafNodeData.Values[utils.CodeSizeLeafKey] = codeSizeBytes[:] + + // The first 128 chunks are stored in the account header leaf. + for i := 0; i < 128 && i < len(chunks)/32; i++ { + leafNodeData.Values[byte(128+i)] = chunks[32*i : 32*(i+1)] + } + + // Potential further chunks, have their own leaf nodes. + for i := 128; i < len(chunks)/32; { + treeIndex, _ := utils.GetTreeKeyCodeChunkIndices(uint256.NewInt(uint64(i))) + leafNodeData := kvm.getOrInitLeafNodeData(newBranchKey(addr, treeIndex)) + + j := i + for ; (j-i) < 256 && j < len(chunks)/32; j++ { + leafNodeData.Values[byte((j-128)%256)] = chunks[32*j : 32*(j+1)] + } + i = j + } +} + +func (kvm *keyValueMigrator) getOrInitLeafNodeData(bk branchKey) *verkle.BatchNewLeafNodeData { + // Remember that keyValueMigration receives actions ordered by (address, subtreeIndex). + // This means that we can assume that the last element of leafData is the one that we + // are looking for, or that we need to create a new one. + if len(kvm.leafData) == 0 || kvm.leafData[len(kvm.leafData)-1].branchKey != bk { + kvm.leafData = append(kvm.leafData, migratedKeyValue{ + branchKey: bk, + leafNodeData: verkle.BatchNewLeafNodeData{ + Stem: nil, // It will be calculated in the prepare() phase, since it's CPU heavy. + Values: make(map[byte][]byte), + }, + }) + } + return &kvm.leafData[len(kvm.leafData)-1].leafNodeData +} + +func (kvm *keyValueMigrator) prepare() { + // We fire a background routine to process the leafData and save the result in newLeaves. + // The background routine signals that it is done by closing processingReady. + go func() { + // Step 1: We split kvm.leafData in numBatches batches, and we process each batch in a separate goroutine. + // This fills each leafNodeData.Stem with the correct value. + var wg sync.WaitGroup + batchNum := runtime.NumCPU() + batchSize := (len(kvm.leafData) + batchNum - 1) / batchNum + for i := 0; i < len(kvm.leafData); i += batchSize { + start := i + end := i + batchSize + if end > len(kvm.leafData) { + end = len(kvm.leafData) + } + wg.Add(1) + + batch := kvm.leafData[start:end] + go func() { + defer wg.Done() + var currAddr common.Address + var currPoint *verkle.Point + for i := range batch { + if batch[i].branchKey.addr != currAddr || currAddr == (common.Address{}) { + currAddr = batch[i].branchKey.addr + currPoint = utils.EvaluateAddressPoint(currAddr[:]) + } + stem := utils.GetTreeKeyWithEvaluatedAddess(currPoint, &batch[i].branchKey.treeIndex, 0) + stem = stem[:verkle.StemSize] + batch[i].leafNodeData.Stem = stem + } + }() + } + wg.Wait() + + // Step 2: Now that we have all stems (i.e: tree keys) calculated, we can create the new leaves. + nodeValues := make([]verkle.BatchNewLeafNodeData, len(kvm.leafData)) + for i := range kvm.leafData { + nodeValues[i] = kvm.leafData[i].leafNodeData + } + + // Create all leaves in batch mode so we can optimize cryptography operations. + kvm.newLeaves, kvm.prepareErr = verkle.BatchNewLeafNode(nodeValues) + close(kvm.processingReady) + }() +} + +func (kvm *keyValueMigrator) migrateCollectedKeyValues(tree *trie.VerkleTrie) error { + now := time.Now() + <-kvm.processingReady + if kvm.prepareErr != nil { + return fmt.Errorf("failed to prepare key values: %w", kvm.prepareErr) + } + log.Info("Prepared key values from base tree", "duration", time.Since(now)) + + // Insert into the tree. + if err := tree.InsertMigratedLeaves(kvm.newLeaves); err != nil { + return fmt.Errorf("failed to insert migrated leaves: %w", err) + } + + return nil +} + // OverlayVerkleTransition contains the overlay conversion logic func OverlayVerkleTransition(statedb *state.StateDB, root common.Hash) error { migrdb := statedb.Database() @@ -47,7 +229,7 @@ func OverlayVerkleTransition(statedb *state.StateDB, root common.Hash) error { mpt = tt.Base() vkt = tt.Overlay() hasPreimagesBin = false - preimageSeek = migrdb.GetCurrentPreimageOffset(root) + preimageSeek = migrdb.GetCurrentPreimageOffset() fpreimages *bufio.Reader ) @@ -65,7 +247,7 @@ func OverlayVerkleTransition(statedb *state.StateDB, root common.Hash) error { hasPreimagesBin = true } - accIt, err := statedb.Snaps().AccountIterator(mpt.Hash(), migrdb.GetCurrentAccountHash(root)) + accIt, err := statedb.Snaps().AccountIterator(mpt.Hash(), migrdb.GetCurrentAccountHash()) if err != nil { return err } @@ -73,7 +255,7 @@ func OverlayVerkleTransition(statedb *state.StateDB, root common.Hash) error { accIt.Next() // If we're about to start with the migration process, we have to read the first account hash preimage. - if migrdb.GetCurrentAccountAddress(root) == nil { + if migrdb.GetCurrentAccountAddress() == nil { var addr common.Address if hasPreimagesBin { if _, err := io.ReadFull(fpreimages, addr[:]); err != nil { @@ -85,8 +267,8 @@ func OverlayVerkleTransition(statedb *state.StateDB, root common.Hash) error { return fmt.Errorf("addr len is zero is not 32: %d", len(addr)) } } - migrdb.SetCurrentAccountAddress(addr, root) - if migrdb.GetCurrentAccountHash(root) != accIt.Hash() { + migrdb.SetCurrentAccountAddress(addr) + if migrdb.GetCurrentAccountHash() != accIt.Hash() { return fmt.Errorf("preimage file does not match account hash: %s != %s", crypto.Keccak256Hash(addr[:]), accIt.Hash()) } preimageSeek += int64(len(addr)) @@ -108,7 +290,7 @@ func OverlayVerkleTransition(statedb *state.StateDB, root common.Hash) error { log.Error("Invalid account encountered during traversal", "error", err) return err } - vkt.SetStorageRootConversion(*migrdb.GetCurrentAccountAddress(root), acc.Root) + vkt.SetStorageRootConversion(*migrdb.GetCurrentAccountAddress(), acc.Root) // Start with processing the storage, because once the account is // converted, the `stateRoot` field loses its meaning. Which means @@ -120,7 +302,7 @@ func OverlayVerkleTransition(statedb *state.StateDB, root common.Hash) error { // to during normal block execution. A mitigation strategy has been // introduced with the `*StorageRootConversion` fields in VerkleDB. if acc.HasStorage() { - stIt, err := statedb.Snaps().StorageIterator(mpt.Hash(), accIt.Hash(), migrdb.GetCurrentSlotHash(root)) + stIt, err := statedb.Snaps().StorageIterator(mpt.Hash(), accIt.Hash(), migrdb.GetCurrentSlotHash()) if err != nil { return err } @@ -132,7 +314,7 @@ func OverlayVerkleTransition(statedb *state.StateDB, root common.Hash) error { // processing the storage for that account where we left off. // If the entire storage was processed, then the iterator was // created in vain, but it's ok as this will not happen often. - for ; !migrdb.GetStorageProcessed(root) && count < maxMovedCount; count++ { + for ; !migrdb.GetStorageProcessed() && count < maxMovedCount; count++ { var ( value []byte // slot value after RLP decoding safeValue [32]byte // 32-byte aligned value @@ -160,12 +342,12 @@ func OverlayVerkleTransition(statedb *state.StateDB, root common.Hash) error { } preimageSeek += int64(len(slotnr)) - mkv.addStorageSlot(migrdb.GetCurrentAccountAddress(root).Bytes(), slotnr, safeValue[:]) + mkv.addStorageSlot(migrdb.GetCurrentAccountAddress().Bytes(), slotnr, safeValue[:]) // advance the storage iterator - migrdb.SetStorageProcessed(!stIt.Next(), root) - if !migrdb.GetStorageProcessed(root) { - migrdb.SetCurrentSlotHash(stIt.Hash(), root) + migrdb.SetStorageProcessed(!stIt.Next()) + if !migrdb.GetStorageProcessed() { + migrdb.SetCurrentSlotHash(stIt.Hash()) } } stIt.Release() @@ -178,20 +360,20 @@ func OverlayVerkleTransition(statedb *state.StateDB, root common.Hash) error { if count < maxMovedCount { count++ // count increase for the account itself - mkv.addAccount(migrdb.GetCurrentAccountAddress(root).Bytes(), acc) - vkt.ClearStrorageRootConversion(*migrdb.GetCurrentAccountAddress(root)) + mkv.addAccount(migrdb.GetCurrentAccountAddress().Bytes(), acc) + vkt.ClearStrorageRootConversion(*migrdb.GetCurrentAccountAddress()) // Store the account code if present if !bytes.Equal(acc.CodeHash, types.EmptyCodeHash[:]) { code := rawdb.ReadCode(statedb.Database().DiskDB(), common.BytesToHash(acc.CodeHash)) chunks := trie.ChunkifyCode(code) - mkv.addAccountCode(migrdb.GetCurrentAccountAddress(root).Bytes(), uint64(len(code)), chunks) + mkv.addAccountCode(migrdb.GetCurrentAccountAddress().Bytes(), uint64(len(code)), chunks) } // reset storage iterator marker for next account - migrdb.SetStorageProcessed(false, root) - migrdb.SetCurrentSlotHash(common.Hash{}, root) + migrdb.SetStorageProcessed(false) + migrdb.SetCurrentSlotHash(common.Hash{}) // Move to the next account, if available - or end // the transition otherwise. @@ -212,7 +394,7 @@ func OverlayVerkleTransition(statedb *state.StateDB, root common.Hash) error { return fmt.Errorf("preimage file does not match account hash: %s != %s", crypto.Keccak256Hash(addr[:]), accIt.Hash()) } preimageSeek += int64(len(addr)) - migrdb.SetCurrentAccountAddress(addr, root) + migrdb.SetCurrentAccountAddress(addr) } else { // case when the account iterator has // reached the end but count < maxCount @@ -221,9 +403,9 @@ func OverlayVerkleTransition(statedb *state.StateDB, root common.Hash) error { } } } - migrdb.SetCurrentPreimageOffset(preimageSeek, root) + migrdb.SetCurrentPreimageOffset(preimageSeek) - log.Info("Collected key values from base tree", "count", count, "duration", time.Since(now), "last account", statedb.Database().GetCurrentAccountHash(root)) + log.Info("Collected key values from base tree", "count", count, "duration", time.Since(now), "last account", statedb.Database().GetCurrentAccountHash()) // Take all the collected key-values and prepare the new leaf values. // This fires a background routine that will start doing the work that diff --git a/core/state/database.go b/core/state/database.go index cd9f8dcf43f1..fcc41ba95927 100644 --- a/core/state/database.go +++ b/core/state/database.go @@ -26,6 +26,7 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie/trienode" @@ -74,27 +75,33 @@ type Database interface { Transitioned() bool - SetCurrentSlotHash(common.Hash, common.Hash) + InitTransitionStatus(bool, bool) - GetCurrentAccountAddress(common.Hash) *common.Address + SetCurrentSlotHash(common.Hash) - SetCurrentAccountAddress(common.Address, common.Hash) + GetCurrentAccountAddress() *common.Address - GetCurrentAccountHash(common.Hash) common.Hash + SetCurrentAccountAddress(common.Address) - GetCurrentSlotHash(common.Hash) common.Hash + GetCurrentAccountHash() common.Hash - SetStorageProcessed(bool, common.Hash) + GetCurrentSlotHash() common.Hash - GetStorageProcessed(common.Hash) bool + SetStorageProcessed(bool) - GetCurrentPreimageOffset(common.Hash) int64 + GetStorageProcessed() bool - SetCurrentPreimageOffset(int64, common.Hash) + GetCurrentPreimageOffset() int64 + + SetCurrentPreimageOffset(int64) AddRootTranslation(originalRoot, translatedRoot common.Hash) SetLastMerkleRoot(common.Hash) + + SaveTransitionState(common.Hash) + + LoadTransitionState(common.Hash) } // Trie is a Ethereum Merkle Patricia trie. @@ -182,40 +189,31 @@ func NewDatabase(db ethdb.Database) Database { // large memory cache. func NewDatabaseWithConfig(db ethdb.Database, config *trie.Config) Database { return &cachingDB{ - disk: db, - codeSizeCache: lru.NewCache[common.Hash, int](codeSizeCacheSize), - codeCache: lru.NewSizeConstrainedCache[common.Hash, []byte](codeCacheSize), - triedb: trie.NewDatabaseWithConfig(db, config), - addrToPoint: utils.NewPointCache(), - StorageProcessed: map[common.Hash]bool{}, - CurrentAccountAddress: map[common.Hash]*common.Address{}, - CurrentSlotHash: map[common.Hash]common.Hash{}, - CurrentPreimageOffset: map[common.Hash]int64{}, + disk: db, + codeSizeCache: lru.NewCache[common.Hash, int](codeSizeCacheSize), + codeCache: lru.NewSizeConstrainedCache[common.Hash, []byte](codeCacheSize), + triedb: trie.NewDatabaseWithConfig(db, config), + addrToPoint: utils.NewPointCache(), } } // NewDatabaseWithNodeDB creates a state database with an already initialized node database. func NewDatabaseWithNodeDB(db ethdb.Database, triedb *trie.Database) Database { return &cachingDB{ - disk: db, - codeSizeCache: lru.NewCache[common.Hash, int](codeSizeCacheSize), - codeCache: lru.NewSizeConstrainedCache[common.Hash, []byte](codeCacheSize), - triedb: triedb, - addrToPoint: utils.NewPointCache(), - ended: triedb.IsVerkle(), - StorageProcessed: map[common.Hash]bool{}, - CurrentAccountAddress: map[common.Hash]*common.Address{}, - CurrentSlotHash: map[common.Hash]common.Hash{}, - CurrentPreimageOffset: map[common.Hash]int64{}, + disk: db, + codeSizeCache: lru.NewCache[common.Hash, int](codeSizeCacheSize), + codeCache: lru.NewSizeConstrainedCache[common.Hash, []byte](codeCacheSize), + triedb: triedb, + addrToPoint: utils.NewPointCache(), } } func (db *cachingDB) InTransition() bool { - return db.started && !db.ended + return db.CurrentTransitionState != nil && db.CurrentTransitionState.started && !db.CurrentTransitionState.ended } func (db *cachingDB) Transitioned() bool { - return db.ended + return db.CurrentTransitionState != nil && db.CurrentTransitionState.ended } // Fork implements the fork @@ -227,29 +225,35 @@ func (db *cachingDB) StartVerkleTransition(originalRoot, translatedRoot common.H | | | Y \ ___/ \ ___/| |_\ ___/| |_> | Y \/ __ \| | | | | Y \/ __ \_\___ \ \ /| | | \\___ /\___ \ |____| |___| /\___ \___ |____/\___ | __/|___| (____ |___| |__| |___| (____ /_____/ \/\_/ |__|___| /_____//_____/ |__|`) - db.started = true - db.ended = false + db.CurrentTransitionState = &TransitionState{ + started: true, + // initialize so that the first storage-less accounts are processed + StorageProcessed: true, + } // db.AddTranslation(originalRoot, translatedRoot) db.baseRoot = originalRoot - // initialize so that the first storage-less accounts are processed - db.StorageProcessed[root] = true // Reinitialize values in case of a reorg - db.CurrentAccountAddress[root] = &(common.Address{}) - db.CurrentSlotHash[root] = common.Hash{} - db.CurrentPreimageOffset[root] = 0 if pragueTime != nil { chainConfig.PragueTime = pragueTime } } func (db *cachingDB) ReorgThroughVerkleTransition() { - db.ended, db.started = false, false + log.Warn("trying to reorg through the transition, which makes no sense at this point") +} + +func (db *cachingDB) InitTransitionStatus(started, ended bool) { + db.CurrentTransitionState = &TransitionState{ + ended: ended, + started: started, + // TODO add other fields when we handle mid-transition interrupts + } } func (db *cachingDB) EndVerkleTransition() { - if !db.started { - db.started = true + if !db.CurrentTransitionState.started { + db.CurrentTransitionState.started = true } fmt.Println(` @@ -259,7 +263,35 @@ func (db *cachingDB) EndVerkleTransition() { | | | Y \ ___/ \ ___/| |_\ ___/| |_> | Y \/ __ \| | | | | Y \/ __ \_\___ \ | |__/ __ \| | / /_/ \ ___// /_/ | |____| |___| /\___ \___ |____/\___ | __/|___| (____ |___| |__| |___| (____ /_____/ |____(____ |___| \____ |\___ \____ | |__|`) - db.ended = true + db.CurrentTransitionState.ended = true +} + +type TransitionState struct { + CurrentAccountAddress *common.Address // addresss of the last translated account + CurrentSlotHash common.Hash // hash of the last translated storage slot + CurrentPreimageOffset int64 // next byte to read from the preimage file + started, ended bool + + // Mark whether the storage for an account has been processed. This is useful if the + // maximum number of leaves of the conversion is reached before the whole storage is + // processed. + StorageProcessed bool +} + +func (ts *TransitionState) Copy() *TransitionState { + ret := &TransitionState{ + started: ts.started, + ended: ts.ended, + CurrentSlotHash: ts.CurrentSlotHash, + CurrentPreimageOffset: ts.CurrentPreimageOffset, + } + + if ts.CurrentAccountAddress != nil { + ret.CurrentAccountAddress = &common.Address{} + copy(ret.CurrentAccountAddress[:], ts.CurrentAccountAddress[:]) + } + + return ret } type cachingDB struct { @@ -268,22 +300,16 @@ type cachingDB struct { codeCache *lru.SizeConstrainedCache[common.Hash, []byte] triedb *trie.Database - // Verkle specific fields + // Transition-specific fields // TODO ensure that this info is in the DB - started, ended bool - LastMerkleRoot common.Hash // root hash of the read-only base tree + LastMerkleRoot common.Hash // root hash of the read-only base tree + CurrentTransitionState *TransitionState + TransitionStatePerRoot map[common.Hash]*TransitionState addrToPoint *utils.PointCache - baseRoot common.Hash // hash of the read-only base tree - CurrentAccountAddress map[common.Hash]*common.Address // addresss of the last translated account - CurrentSlotHash map[common.Hash]common.Hash // hash of the last translated storage slot - CurrentPreimageOffset map[common.Hash]int64 // next byte to read from the preimage file + baseRoot common.Hash // hash of the read-only base tree - // Mark whether the storage for an account has been processed. This is useful if the - // maximum number of leaves of the conversion is reached before the whole storage is - // processed. - StorageProcessed map[common.Hash]bool } func (db *cachingDB) openMPTTrie(root common.Hash) (Trie, error) { @@ -297,14 +323,14 @@ func (db *cachingDB) openMPTTrie(root common.Hash) (Trie, error) { func (db *cachingDB) openVKTrie(root common.Hash) (Trie, error) { payload, err := db.DiskDB().Get(trie.FlatDBVerkleNodeKeyPrefix) if err != nil { - return trie.NewVerkleTrie(verkle.New(), db.triedb, db.addrToPoint, db.ended), nil + return trie.NewVerkleTrie(verkle.New(), db.triedb, db.addrToPoint, db.CurrentTransitionState.ended), nil } r, err := verkle.ParseNode(payload, 0) if err != nil { panic(err) } - return trie.NewVerkleTrie(r, db.triedb, db.addrToPoint, db.ended), err + return trie.NewVerkleTrie(r, db.triedb, db.addrToPoint, db.CurrentTransitionState.ended), err } // OpenTrie opens the main account trie at a specific root hash. @@ -316,7 +342,7 @@ func (db *cachingDB) OpenTrie(root common.Hash) (Trie, error) { // TODO separate both cases when I can be certain that it won't // find a Verkle trie where is expects a Transitoion trie. - if db.started || db.ended { + if db.CurrentTransitionState != nil && (db.CurrentTransitionState.started || db.CurrentTransitionState.ended) { // NOTE this is a kaustinen-only change, it will break replay vkt, err := db.openVKTrie(root) if err != nil { @@ -325,7 +351,7 @@ func (db *cachingDB) OpenTrie(root common.Hash) (Trie, error) { // If the verkle conversion has ended, return a single // verkle trie. - if db.ended { + if db.CurrentTransitionState.ended { return vkt, nil } @@ -358,7 +384,7 @@ func (db *cachingDB) openStorageMPTrie(stateRoot common.Hash, address common.Add // OpenStorageTrie opens the storage trie of an account func (db *cachingDB) OpenStorageTrie(stateRoot common.Hash, address common.Address, root common.Hash, self Trie) (Trie, error) { // TODO this should only return a verkle tree - if db.ended { + if db.Transitioned() { mpt, err := db.openStorageMPTrie(types.EmptyRootHash, address, common.Hash{}, self) if err != nil { return nil, err @@ -374,7 +400,7 @@ func (db *cachingDB) OpenStorageTrie(stateRoot common.Hash, address common.Addre panic("unexpected trie type") } } - if db.started { + if db.InTransition() { mpt, err := db.openStorageMPTrie(db.LastMerkleRoot, address, root, nil) if err != nil { return nil, err @@ -463,44 +489,44 @@ func (db *cachingDB) GetTreeKeyHeader(addr []byte) *verkle.Point { return db.addrToPoint.GetTreeKeyHeader(addr) } -func (db *cachingDB) SetCurrentAccountAddress(addr common.Address, root common.Hash) { - db.CurrentAccountAddress[root] = &addr +func (db *cachingDB) SetCurrentAccountAddress(addr common.Address) { + db.CurrentTransitionState.CurrentAccountAddress = &addr } -func (db *cachingDB) GetCurrentAccountHash(root common.Hash) common.Hash { +func (db *cachingDB) GetCurrentAccountHash() common.Hash { var addrHash common.Hash - if db.CurrentAccountAddress[root] != nil { - addrHash = crypto.Keccak256Hash(db.CurrentAccountAddress[root][:]) + if db.CurrentTransitionState.CurrentAccountAddress != nil { + addrHash = crypto.Keccak256Hash(db.CurrentTransitionState.CurrentAccountAddress[:]) } return addrHash } -func (db *cachingDB) GetCurrentAccountAddress(root common.Hash) *common.Address { - return db.CurrentAccountAddress[root] +func (db *cachingDB) GetCurrentAccountAddress() *common.Address { + return db.CurrentTransitionState.CurrentAccountAddress } -func (db *cachingDB) GetCurrentPreimageOffset(root common.Hash) int64 { - return db.CurrentPreimageOffset[root] +func (db *cachingDB) GetCurrentPreimageOffset() int64 { + return db.CurrentTransitionState.CurrentPreimageOffset } -func (db *cachingDB) SetCurrentPreimageOffset(offset int64, root common.Hash) { - db.CurrentPreimageOffset[root] = offset +func (db *cachingDB) SetCurrentPreimageOffset(offset int64) { + db.CurrentTransitionState.CurrentPreimageOffset = offset } -func (db *cachingDB) SetCurrentSlotHash(hash common.Hash, root common.Hash) { - db.CurrentSlotHash[root] = hash +func (db *cachingDB) SetCurrentSlotHash(hash common.Hash) { + db.CurrentTransitionState.CurrentSlotHash = hash } -func (db *cachingDB) GetCurrentSlotHash(root common.Hash) common.Hash { - return db.CurrentSlotHash[root] +func (db *cachingDB) GetCurrentSlotHash() common.Hash { + return db.CurrentTransitionState.CurrentSlotHash } -func (db *cachingDB) SetStorageProcessed(processed bool, root common.Hash) { - db.StorageProcessed[root] = processed +func (db *cachingDB) SetStorageProcessed(processed bool) { + db.CurrentTransitionState.StorageProcessed = processed } -func (db *cachingDB) GetStorageProcessed(root common.Hash) bool { - return db.StorageProcessed[root] +func (db *cachingDB) GetStorageProcessed() bool { + return db.CurrentTransitionState.StorageProcessed } func (db *cachingDB) AddRootTranslation(originalRoot, translatedRoot common.Hash) { @@ -509,3 +535,29 @@ func (db *cachingDB) AddRootTranslation(originalRoot, translatedRoot common.Hash func (db *cachingDB) SetLastMerkleRoot(merkleRoot common.Hash) { db.LastMerkleRoot = merkleRoot } + +func (db *cachingDB) SaveTransitionState(root common.Hash) { + if db.TransitionStatePerRoot == nil { + db.TransitionStatePerRoot = make(map[common.Hash]*TransitionState) + } + + db.TransitionStatePerRoot[root] = db.CurrentTransitionState +} + +func (db *cachingDB) LoadTransitionState(root common.Hash) { + if db.TransitionStatePerRoot == nil { + db.TransitionStatePerRoot = make(map[common.Hash]*TransitionState) + } + + ts, ok := db.TransitionStatePerRoot[root] + if !ok || ts == nil { + // Start with a fresh state + ts = &TransitionState{ended: db.triedb.IsVerkle()} + } + + db.CurrentTransitionState = ts.Copy() + + if db.CurrentTransitionState != nil { + fmt.Println("address", db.CurrentTransitionState.CurrentAccountAddress) + } +} diff --git a/core/state_processor.go b/core/state_processor.go index 8cc8ac50ca3d..cb4d622d6d90 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -21,9 +21,6 @@ import ( "errors" "fmt" "math/big" - "runtime" - "sync" - "time" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus" @@ -34,11 +31,6 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" - "github.com/ethereum/go-ethereum/trie" - "github.com/ethereum/go-ethereum/trie/utils" - tutils "github.com/ethereum/go-ethereum/trie/utils" - "github.com/ethereum/go-verkle" - "github.com/holiman/uint256" ) // StateProcessor is a basic Processor, which takes care of transitioning @@ -115,10 +107,10 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg } // Perform the overlay transition, if relevant - parent := p.bc.GetHeaderByHash(header.ParentHash) - if err := OverlayVerkleTransition(statedb, parent.Root); err != nil { - return nil, nil, 0, fmt.Errorf("error performing verkle overlay transition: %w", err) - } + //parent := p.bc.GetHeaderByHash(header.ParentHash) + //if err := OverlayVerkleTransition(statedb, parent.Root); err != nil { + // return nil, nil, 0, fmt.Errorf("error performing verkle overlay transition: %w", err) + //} // Finalize the block, applying any consensus engine specific extras (e.g. block rewards) p.engine.Finalize(p.bc, header, statedb, block.Transactions(), block.Uncles(), withdrawals) @@ -194,190 +186,6 @@ func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *commo return applyTransaction(msg, config, gp, statedb, header.Number, header.Hash(), tx, usedGas, vmenv) } -var zeroTreeIndex uint256.Int - -// keyValueMigrator is a helper module that collects key-values from the overlay-tree migration for Verkle Trees. -// It assumes that the walk of the base tree is done in address-order, so it exploit that fact to -// collect the key-values in a way that is efficient. -type keyValueMigrator struct { - // leafData contains the values for the future leaf for a particular VKT branch. - leafData []migratedKeyValue - - // When prepare() is called, it will start a background routine that will process the leafData - // saving the result in newLeaves to be used by migrateCollectedKeyValues(). The background - // routine signals that it is done by closing processingReady. - processingReady chan struct{} - newLeaves []verkle.LeafNode - prepareErr error -} - -func newKeyValueMigrator() *keyValueMigrator { - // We do initialize the VKT config since prepare() might indirectly make multiple GetConfig() calls - // in different goroutines when we never called GetConfig() before, causing a race considering the way - // that `config` is designed in go-verkle. - // TODO: jsign as a fix for this in the PR where we move to a file-less precomp, since it allows safe - // concurrent calls to GetConfig(). When that gets merged, we can remove this line. - _ = verkle.GetConfig() - return &keyValueMigrator{ - processingReady: make(chan struct{}), - leafData: make([]migratedKeyValue, 0, 10_000), - } -} - -type migratedKeyValue struct { - branchKey branchKey - leafNodeData verkle.BatchNewLeafNodeData -} -type branchKey struct { - addr common.Address - treeIndex uint256.Int -} - -func newBranchKey(addr []byte, treeIndex *uint256.Int) branchKey { - var sk branchKey - copy(sk.addr[:], addr) - sk.treeIndex = *treeIndex - return sk -} - -func (kvm *keyValueMigrator) addStorageSlot(addr []byte, slotNumber []byte, slotValue []byte) { - treeIndex, subIndex := tutils.GetTreeKeyStorageSlotTreeIndexes(slotNumber) - leafNodeData := kvm.getOrInitLeafNodeData(newBranchKey(addr, treeIndex)) - leafNodeData.Values[subIndex] = slotValue -} - -func (kvm *keyValueMigrator) addAccount(addr []byte, acc *types.StateAccount) { - leafNodeData := kvm.getOrInitLeafNodeData(newBranchKey(addr, &zeroTreeIndex)) - - var version [verkle.LeafValueSize]byte - leafNodeData.Values[tutils.VersionLeafKey] = version[:] - - var balance [verkle.LeafValueSize]byte - for i, b := range acc.Balance.Bytes() { - balance[len(acc.Balance.Bytes())-1-i] = b - } - leafNodeData.Values[tutils.BalanceLeafKey] = balance[:] - - var nonce [verkle.LeafValueSize]byte - binary.LittleEndian.PutUint64(nonce[:8], acc.Nonce) - leafNodeData.Values[tutils.NonceLeafKey] = nonce[:] - - leafNodeData.Values[tutils.CodeKeccakLeafKey] = acc.CodeHash[:] -} - -func (kvm *keyValueMigrator) addAccountCode(addr []byte, codeSize uint64, chunks []byte) { - leafNodeData := kvm.getOrInitLeafNodeData(newBranchKey(addr, &zeroTreeIndex)) - - // Save the code size. - var codeSizeBytes [verkle.LeafValueSize]byte - binary.LittleEndian.PutUint64(codeSizeBytes[:8], codeSize) - leafNodeData.Values[tutils.CodeSizeLeafKey] = codeSizeBytes[:] - - // The first 128 chunks are stored in the account header leaf. - for i := 0; i < 128 && i < len(chunks)/32; i++ { - leafNodeData.Values[byte(128+i)] = chunks[32*i : 32*(i+1)] - } - - // Potential further chunks, have their own leaf nodes. - for i := 128; i < len(chunks)/32; { - treeIndex, _ := tutils.GetTreeKeyCodeChunkIndices(uint256.NewInt(uint64(i))) - leafNodeData := kvm.getOrInitLeafNodeData(newBranchKey(addr, treeIndex)) - - j := i - for ; (j-i) < 256 && j < len(chunks)/32; j++ { - leafNodeData.Values[byte((j-128)%256)] = chunks[32*j : 32*(j+1)] - } - i = j - } -} - -func (kvm *keyValueMigrator) getOrInitLeafNodeData(bk branchKey) *verkle.BatchNewLeafNodeData { - // Remember that keyValueMigration receives actions ordered by (address, subtreeIndex). - // This means that we can assume that the last element of leafData is the one that we - // are looking for, or that we need to create a new one. - if len(kvm.leafData) == 0 || kvm.leafData[len(kvm.leafData)-1].branchKey != bk { - kvm.leafData = append(kvm.leafData, migratedKeyValue{ - branchKey: bk, - leafNodeData: verkle.BatchNewLeafNodeData{ - Stem: nil, // It will be calculated in the prepare() phase, since it's CPU heavy. - Values: make(map[byte][]byte), - }, - }) - } - return &kvm.leafData[len(kvm.leafData)-1].leafNodeData -} - -func (kvm *keyValueMigrator) prepare() { - // We fire a background routine to process the leafData and save the result in newLeaves. - // The background routine signals that it is done by closing processingReady. - go func() { - // Step 1: We split kvm.leafData in numBatches batches, and we process each batch in a separate goroutine. - // This fills each leafNodeData.Stem with the correct value. - var wg sync.WaitGroup - batchNum := runtime.NumCPU() - batchSize := (len(kvm.leafData) + batchNum - 1) / batchNum - for i := 0; i < len(kvm.leafData); i += batchSize { - start := i - end := i + batchSize - if end > len(kvm.leafData) { - end = len(kvm.leafData) - } - wg.Add(1) - - batch := kvm.leafData[start:end] - go func() { - defer wg.Done() - var currAddr common.Address - var currPoint *verkle.Point - for i := range batch { - if batch[i].branchKey.addr != currAddr || currAddr == (common.Address{}) { - currAddr = batch[i].branchKey.addr - currPoint = tutils.EvaluateAddressPoint(currAddr[:]) - } - stem := tutils.GetTreeKeyWithEvaluatedAddess(currPoint, &batch[i].branchKey.treeIndex, 0) - stem = stem[:verkle.StemSize] - batch[i].leafNodeData.Stem = stem - } - }() - } - wg.Wait() - - // Step 2: Now that we have all stems (i.e: tree keys) calculated, we can create the new leaves. - nodeValues := make([]verkle.BatchNewLeafNodeData, len(kvm.leafData)) - for i := range kvm.leafData { - nodeValues[i] = kvm.leafData[i].leafNodeData - } - - // Create all leaves in batch mode so we can optimize cryptography operations. - kvm.newLeaves, kvm.prepareErr = verkle.BatchNewLeafNode(nodeValues) - close(kvm.processingReady) - }() -} - -func (kvm *keyValueMigrator) migrateCollectedKeyValues(tree *trie.VerkleTrie) error { - now := time.Now() - <-kvm.processingReady - if kvm.prepareErr != nil { - return fmt.Errorf("failed to prepare key values: %w", kvm.prepareErr) - } - log.Info("Prepared key values from base tree", "duration", time.Since(now)) - - // Insert into the tree. - if err := tree.InsertMigratedLeaves(kvm.newLeaves); err != nil { - return fmt.Errorf("failed to insert migrated leaves: %w", err) - } - - return nil -} - -func InsertBlockHashHistoryAtEip2935Fork(statedb *state.StateDB, prevNumber uint64, prevHash common.Hash, chain consensus.ChainHeaderReader) { - ancestor := chain.GetHeader(prevHash, prevNumber) - for i := prevNumber; i > 0 && i >= prevNumber-256; i-- { - ProcessParentBlockHash(statedb, i, ancestor.Hash()) - ancestor = chain.GetHeader(ancestor.ParentHash, ancestor.Number.Uint64()-1) - } -} - func ProcessParentBlockHash(statedb *state.StateDB, prevNumber uint64, prevHash common.Hash) { ringIndex := prevNumber % 256 var key common.Hash diff --git a/light/trie.go b/light/trie.go index 6d0c654ff111..df300c8c6ed2 100644 --- a/light/trie.go +++ b/light/trie.go @@ -121,39 +121,43 @@ func (db *odrDatabase) Transitioned() bool { panic("not implemented") // TODO: Implement } -func (db *odrDatabase) SetCurrentSlotHash(common.Hash, common.Hash) { +func (db *odrDatabase) InitTransitionStatus(bool, bool) { panic("not implemented") // TODO: Implement } -func (db *odrDatabase) GetCurrentAccountAddress(common.Hash) *common.Address { +func (db *odrDatabase) SetCurrentSlotHash(common.Hash) { panic("not implemented") // TODO: Implement } -func (db *odrDatabase) SetCurrentAccountAddress(common.Address, common.Hash) { +func (db *odrDatabase) GetCurrentAccountAddress() *common.Address { panic("not implemented") // TODO: Implement } -func (db *odrDatabase) GetCurrentAccountHash(common.Hash) common.Hash { +func (db *odrDatabase) SetCurrentAccountAddress(common.Address) { panic("not implemented") // TODO: Implement } -func (db *odrDatabase) GetCurrentSlotHash(common.Hash) common.Hash { +func (db *odrDatabase) GetCurrentAccountHash() common.Hash { panic("not implemented") // TODO: Implement } -func (db *odrDatabase) SetStorageProcessed(bool, common.Hash) { +func (db *odrDatabase) GetCurrentSlotHash() common.Hash { panic("not implemented") // TODO: Implement } -func (db *odrDatabase) GetStorageProcessed(common.Hash) bool { +func (db *odrDatabase) SetStorageProcessed(bool) { panic("not implemented") // TODO: Implement } -func (db *odrDatabase) GetCurrentPreimageOffset(common.Hash) int64 { +func (db *odrDatabase) GetStorageProcessed() bool { panic("not implemented") // TODO: Implement } -func (db *odrDatabase) SetCurrentPreimageOffset(int64, common.Hash) { +func (db *odrDatabase) GetCurrentPreimageOffset() int64 { + panic("not implemented") // TODO: Implement +} + +func (db *odrDatabase) SetCurrentPreimageOffset(int64) { panic("not implemented") // TODO: Implement } @@ -165,6 +169,14 @@ func (db *odrDatabase) SetLastMerkleRoot(common.Hash) { panic("not implemented") // TODO: Implement } +func (db *odrDatabase) SaveTransitionState(common.Hash) { + panic("not implemented") // TODO: Implement +} + +func (db *odrDatabase) LoadTransitionState(common.Hash) { + panic("not implemented") // TODO: Implement +} + type odrTrie struct { db *odrDatabase id *TrieID diff --git a/miner/worker.go b/miner/worker.go index 1d1b2fda07a2..9ac258c77591 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -904,9 +904,6 @@ func (w *worker) prepareWork(genParams *generateParams) (*environment, error) { if err != nil { return nil, err } - if w.chain.Config().IsPrague(header.Number, header.Time) { - core.OverlayVerkleTransition(state, parent.Root) - } // Run the consensus preparation with the default or customized consensus engine. if err := w.engine.Prepare(w.chain, header); err != nil { log.Error("Failed to prepare header for sealing", "err", err) From 0582870e68f67e5e36c475f2d7420fca4409a99a Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Tue, 5 Dec 2023 16:53:46 +0100 Subject: [PATCH 05/22] fix OpenStorageTrie: return an error instead of panicking if the account tree not a verkle tree (#321) --- core/state/database.go | 2 +- core/state/trie_prefetcher.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/state/database.go b/core/state/database.go index fcc41ba95927..175759c76d01 100644 --- a/core/state/database.go +++ b/core/state/database.go @@ -413,7 +413,7 @@ func (db *cachingDB) OpenStorageTrie(stateRoot common.Hash, address common.Addre case *trie.TransitionTrie: return trie.NewTransitionTree(mpt.(*trie.SecureTrie), self.Overlay(), true), nil default: - panic("unexpected trie type") + return nil, errors.New("expected a verkle account tree, and found another type") } } mpt, err := db.openStorageMPTrie(stateRoot, address, root, nil) diff --git a/core/state/trie_prefetcher.go b/core/state/trie_prefetcher.go index 6c5c158cc239..4521736c7beb 100644 --- a/core/state/trie_prefetcher.go +++ b/core/state/trie_prefetcher.go @@ -302,7 +302,7 @@ func (sf *subfetcher) loop() { } sf.trie = trie } else { - trie, err := sf.db.OpenStorageTrie(sf.state, sf.addr, sf.root, nil /* safe to set to nil for now, as there is no prefetcher for verkle */) + trie, err := sf.db.OpenStorageTrie(sf.state, sf.addr, sf.root, sf.trie) if err != nil { log.Warn("Trie prefetcher failed opening trie", "root", sf.root, "err", err) return From 3e5b4b74527948bbcbee7cfe2341b8491c827f18 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Tue, 5 Dec 2023 17:19:11 +0100 Subject: [PATCH 06/22] add a switch to force proof in blocks (#322) * add a switch to force proof in blocks * activate switch * fix switch type --- cmd/geth/config.go | 4 ++++ cmd/geth/main.go | 1 + cmd/utils/flags.go | 5 +++++ core/blockchain.go | 7 +++++-- core/genesis.go | 5 +++-- eth/backend.go | 3 +++ eth/ethconfig/config.go | 3 +++ 7 files changed, 24 insertions(+), 4 deletions(-) diff --git a/cmd/geth/config.go b/cmd/geth/config.go index bf01c6f91857..4e861d75350b 100644 --- a/cmd/geth/config.go +++ b/cmd/geth/config.go @@ -175,6 +175,10 @@ func makeFullNode(ctx *cli.Context) (*node.Node, ethapi.Backend) { v := ctx.Uint64(utils.OverridePrague.Name) cfg.Eth.OverridePrague = &v } + if ctx.IsSet(utils.OverrideProofInBlock.Name) { + v := ctx.Bool(utils.OverrideProofInBlock.Name) + cfg.Eth.OverrideProofInBlock = &v + } backend, eth := utils.RegisterEthService(stack, &cfg.Eth) // Configure log filter RPC API. diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 38fb755b4b5a..2945a3c4b1f9 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -69,6 +69,7 @@ var ( utils.SmartCardDaemonPathFlag, utils.OverrideCancun, utils.OverridePrague, + utils.OverrideProofInBlock, utils.EnablePersonal, utils.TxPoolLocalsFlag, utils.TxPoolNoLocalsFlag, diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index b927d0f94f83..5f071d0a7479 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -273,6 +273,11 @@ var ( Usage: "Manually specify the Verkle fork timestamp, overriding the bundled setting", Category: flags.EthCategory, } + OverrideProofInBlock = &cli.BoolFlag{ + Name: "override.blockproof", + Usage: "Manually specify the proof-in-block setting", + Category: flags.EthCategory, + } // Light server and client settings LightServeFlag = &cli.IntFlag{ Name: "light.serve", diff --git a/core/blockchain.go b/core/blockchain.go index 6dcd0e81fab3..0cc4115a486a 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -251,6 +251,9 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis if _, ok := genesisErr.(*params.ConfigCompatError); genesisErr != nil && !ok { return nil, genesisErr } + if overrides.OverrideProofInBlock != nil { + chainConfig.ProofInBlocks = *overrides.OverrideProofInBlock + } log.Info("") log.Info(strings.Repeat("-", 153)) for _, line := range strings.Split(chainConfig.Description(), "\n") { @@ -316,8 +319,8 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis // TODO this only works when resuming a chain that has already gone // through the conversion. All pointers should be saved to the DB // for it to be able to recover if interrupted during the transition - // but that's left out to a later PR since there's not really a need - // right now. + // but that's left out to a later PR since there's not really a need + // right now. bc.stateCache.InitTransitionStatus(true, true) bc.stateCache.EndVerkleTransition() } diff --git a/core/genesis.go b/core/genesis.go index 0aad87a10d70..9fd19f03671d 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -291,8 +291,9 @@ func (e *GenesisMismatchError) Error() string { // ChainOverrides contains the changes to chain config. type ChainOverrides struct { - OverrideCancun *uint64 - OverridePrague *uint64 + OverrideCancun *uint64 + OverridePrague *uint64 + OverrideProofInBlock *bool } // SetupGenesisBlock writes or updates the genesis block in db. diff --git a/eth/backend.go b/eth/backend.go index a6c80159077d..c5be460c0d91 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -201,6 +201,9 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { if config.OverridePrague != nil { overrides.OverridePrague = config.OverridePrague } + if config.OverrideProofInBlock != nil { + overrides.OverrideProofInBlock = config.OverrideProofInBlock + } eth.blockchain, err = core.NewBlockChain(chainDb, cacheConfig, config.Genesis, &overrides, eth.engine, vmConfig, eth.shouldPreserve, &config.TxLookupLimit) if err != nil { return nil, err diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go index 4606b60408dd..b9660f5c60b3 100644 --- a/eth/ethconfig/config.go +++ b/eth/ethconfig/config.go @@ -158,6 +158,9 @@ type Config struct { // OverrideVerkle (TODO: remove after the fork) OverridePrague *uint64 `toml:",omitempty"` + + // OverrideProofInBlock + OverrideProofInBlock *bool `toml:",omitempty"` } // CreateConsensusEngine creates a consensus engine for the given chain config. From 8bfaaa01e76589fd53c77e45beda10b977840f69 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Tue, 5 Dec 2023 17:44:28 +0100 Subject: [PATCH 07/22] add switch to override the stride of the overlay conversion (#323) * add switch to override the stride of the overlay conversion * set a default stride of 10k --- cmd/geth/config.go | 4 ++++ cmd/geth/main.go | 1 + cmd/utils/flags.go | 6 ++++++ consensus/beacon/consensus.go | 2 +- core/blockchain.go | 3 +++ core/genesis.go | 7 ++++--- core/overlay/conversion.go | 5 ++--- eth/ethconfig/config.go | 3 +++ params/config.go | 3 ++- 9 files changed, 26 insertions(+), 8 deletions(-) diff --git a/cmd/geth/config.go b/cmd/geth/config.go index 4e861d75350b..cdd893d2a8ec 100644 --- a/cmd/geth/config.go +++ b/cmd/geth/config.go @@ -179,6 +179,10 @@ func makeFullNode(ctx *cli.Context) (*node.Node, ethapi.Backend) { v := ctx.Bool(utils.OverrideProofInBlock.Name) cfg.Eth.OverrideProofInBlock = &v } + if ctx.IsSet(utils.OverrideOverlayStride.Name) { + v := ctx.Uint64(utils.OverrideOverlayStride.Name) + cfg.Eth.OverrideOverlayStride = &v + } backend, eth := utils.RegisterEthService(stack, &cfg.Eth) // Configure log filter RPC API. diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 2945a3c4b1f9..e2e4ba25fe06 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -67,6 +67,7 @@ var ( utils.NoUSBFlag, utils.USBFlag, utils.SmartCardDaemonPathFlag, + utils.OverrideOverlayStride, utils.OverrideCancun, utils.OverridePrague, utils.OverrideProofInBlock, diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 5f071d0a7479..0dd311706abb 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -263,6 +263,12 @@ var ( Value: 2048, Category: flags.EthCategory, } + OverrideOverlayStride = &cli.Uint64Flag{ + Name: "override.overlay-stride", + Usage: "Manually specify the stride of the overlay transition, overriding the bundled setting", + Value: 10000, + Category: flags.EthCategory, + } OverrideCancun = &cli.Uint64Flag{ Name: "override.cancun", Usage: "Manually specify the Cancun fork timestamp, overriding the bundled setting", diff --git a/consensus/beacon/consensus.go b/consensus/beacon/consensus.go index dd94b8da7790..70008b270259 100644 --- a/consensus/beacon/consensus.go +++ b/consensus/beacon/consensus.go @@ -368,7 +368,7 @@ func (beacon *Beacon) Finalize(chain consensus.ChainHeaderReader, header *types. if chain.Config().IsPrague(header.Number, header.Time) { fmt.Println("at block", header.Number, "performing transition?", state.Database().InTransition()) parent := chain.GetHeaderByHash(header.ParentHash) - overlay.OverlayVerkleTransition(state, parent.Root) + overlay.OverlayVerkleTransition(state, parent.Root, chain.Config().OverlayStride) } } diff --git a/core/blockchain.go b/core/blockchain.go index 0cc4115a486a..1008ca692896 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -254,6 +254,9 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis if overrides.OverrideProofInBlock != nil { chainConfig.ProofInBlocks = *overrides.OverrideProofInBlock } + if overrides.OverrideOverlayStride != nil { + chainConfig.OverlayStride = *overrides.OverrideOverlayStride + } log.Info("") log.Info(strings.Repeat("-", 153)) for _, line := range strings.Split(chainConfig.Description(), "\n") { diff --git a/core/genesis.go b/core/genesis.go index 9fd19f03671d..a2a331d1fe33 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -291,9 +291,10 @@ func (e *GenesisMismatchError) Error() string { // ChainOverrides contains the changes to chain config. type ChainOverrides struct { - OverrideCancun *uint64 - OverridePrague *uint64 - OverrideProofInBlock *bool + OverrideCancun *uint64 + OverridePrague *uint64 + OverrideProofInBlock *bool + OverrideOverlayStride *uint64 } // SetupGenesisBlock writes or updates the genesis block in db. diff --git a/core/overlay/conversion.go b/core/overlay/conversion.go index e76aa5900173..0e9047920a0b 100644 --- a/core/overlay/conversion.go +++ b/core/overlay/conversion.go @@ -217,7 +217,7 @@ func (kvm *keyValueMigrator) migrateCollectedKeyValues(tree *trie.VerkleTrie) er } // OverlayVerkleTransition contains the overlay conversion logic -func OverlayVerkleTransition(statedb *state.StateDB, root common.Hash) error { +func OverlayVerkleTransition(statedb *state.StateDB, root common.Hash, maxMovedCount uint64) error { migrdb := statedb.Database() // verkle transition: if the conversion process is in progress, move @@ -274,14 +274,13 @@ func OverlayVerkleTransition(statedb *state.StateDB, root common.Hash) error { preimageSeek += int64(len(addr)) } - const maxMovedCount = 10000 // mkv will be assiting in the collection of up to maxMovedCount key values to be migrated to the VKT. // It has internal caches to do efficient MPT->VKT key calculations, which will be discarded after // this function. mkv := newKeyValueMigrator() // move maxCount accounts into the verkle tree, starting with the // slots from the previous account. - count := 0 + count := uint64(0) // if less than maxCount slots were moved, move to the next account for count < maxMovedCount { diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go index b9660f5c60b3..fc9550147bcc 100644 --- a/eth/ethconfig/config.go +++ b/eth/ethconfig/config.go @@ -161,6 +161,9 @@ type Config struct { // OverrideProofInBlock OverrideProofInBlock *bool `toml:",omitempty"` + + // OverrideOverlayStride + OverrideOverlayStride *uint64 `toml:",omitempty"` } // CreateConsensusEngine creates a consensus engine for the given chain config. diff --git a/params/config.go b/params/config.go index 19e633a71def..a2df06893a22 100644 --- a/params/config.go +++ b/params/config.go @@ -301,7 +301,8 @@ type ChainConfig struct { IsDevMode bool `json:"isDev,omitempty"` // Proof in block - ProofInBlocks bool `json:"proofInBlocks,omitempty"` + ProofInBlocks bool `json:"proofInBlocks,omitempty"` + OverlayStride uint64 `json:"overlayStride,omitempty"` } // EthashConfig is the consensus engine configs for proof-of-work based sealing. From 677e66ea65223c0fc7796d8ce4000b4d1b909261 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Tue, 5 Dec 2023 17:55:39 +0100 Subject: [PATCH 08/22] add a few traces for relaunch --- core/overlay/conversion.go | 1 + core/state/database.go | 1 + 2 files changed, 2 insertions(+) diff --git a/core/overlay/conversion.go b/core/overlay/conversion.go index 0e9047920a0b..1a987ab69250 100644 --- a/core/overlay/conversion.go +++ b/core/overlay/conversion.go @@ -223,6 +223,7 @@ func OverlayVerkleTransition(statedb *state.StateDB, root common.Hash, maxMovedC // verkle transition: if the conversion process is in progress, move // N values from the MPT into the verkle tree. if migrdb.InTransition() { + fmt.Printf("Processing verkle conversion starting at %x %x, building on top of %x\n", migrdb.GetCurrentAccountHash(), migrdb.GetCurrentSlotHash(), root) var ( now = time.Now() tt = statedb.GetTrie().(*trie.TransitionTrie) diff --git a/core/state/database.go b/core/state/database.go index 175759c76d01..5baaedb9d786 100644 --- a/core/state/database.go +++ b/core/state/database.go @@ -401,6 +401,7 @@ func (db *cachingDB) OpenStorageTrie(stateRoot common.Hash, address common.Addre } } if db.InTransition() { + fmt.Printf("OpenStorageTrie during transition, state root=%x root=%x\n", stateRoot, root) mpt, err := db.openStorageMPTrie(db.LastMerkleRoot, address, root, nil) if err != nil { return nil, err From 28eee603d117610eb07ef6368c2777739d6618d5 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Wed, 6 Dec 2023 11:42:53 +0100 Subject: [PATCH 09/22] add missing step in the chain of setting the override for overlay stride --- eth/backend.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/eth/backend.go b/eth/backend.go index c5be460c0d91..c47bc6b5bb35 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -204,6 +204,9 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { if config.OverrideProofInBlock != nil { overrides.OverrideProofInBlock = config.OverrideProofInBlock } + if config.OverrideOverlayStride != nil { + overrides.OverrideOverlayStride = config.OverrideOverlayStride + } eth.blockchain, err = core.NewBlockChain(chainDb, cacheConfig, config.Genesis, &overrides, eth.engine, vmConfig, eth.shouldPreserve, &config.TxLookupLimit) if err != nil { return nil, err From 3cbf6015ceaa9b1721feafa82e7a514ee3f19d54 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Fri, 8 Dec 2023 15:02:38 +0100 Subject: [PATCH 10/22] fix: save and load transition state for block processing (#324) * fix: save and load transition state for block processing * log whether the tree is verkle in LoadTransitionState * fix: ensure the transition is marked as started in insertChain * dump saved address * fix nil pointer panic * remove stacktrace that is no longer useful * fix a panic * fix build * check: copy current account address BEFORE it's saved * mandatory panic fix * Remove debug fmt.Println * more cleanup + comments --- core/block_validator.go | 1 + core/blockchain.go | 9 +++++++++ core/state/database.go | 14 ++++++++------ 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/core/block_validator.go b/core/block_validator.go index b1ceab9d5c6c..d977c1d63d96 100644 --- a/core/block_validator.go +++ b/core/block_validator.go @@ -131,6 +131,7 @@ func (v *BlockValidator) ValidateState(block *types.Block, statedb *state.StateD if root := statedb.IntermediateRoot(v.config.IsEIP158(header.Number)); header.Root != root { return fmt.Errorf("invalid merkle root (remote: %x local: %x) dberr: %w", header.Root, root, statedb.Error()) } + statedb.Database().SaveTransitionState(header.Root) return nil } diff --git a/core/blockchain.go b/core/blockchain.go index 1008ca692896..fdde955b87bd 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -1758,6 +1758,15 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) parent = bc.GetHeader(block.ParentHash(), block.NumberU64()-1) } + if bc.Config().IsPrague(block.Number(), block.Time()) { + bc.stateCache.LoadTransitionState(parent.Root) + + // pragueTime has been reached. If the transition isn't active, it means this + // is the fork block and that the conversion needs to be marked at started. + if !bc.stateCache.InTransition() && !bc.stateCache.Transitioned() { + bc.stateCache.StartVerkleTransition(parent.Root, emptyVerkleRoot, bc.Config(), bc.Config().PragueTime, parent.Root) + } + } if parent.Number.Uint64() == conversionBlock { bc.StartVerkleTransition(parent.Root, emptyVerkleRoot, bc.Config(), &parent.Time, parent.Root) bc.stateCache.SetLastMerkleRoot(parent.Root) diff --git a/core/state/database.go b/core/state/database.go index 5baaedb9d786..c9d06b443b18 100644 --- a/core/state/database.go +++ b/core/state/database.go @@ -342,7 +342,7 @@ func (db *cachingDB) OpenTrie(root common.Hash) (Trie, error) { // TODO separate both cases when I can be certain that it won't // find a Verkle trie where is expects a Transitoion trie. - if db.CurrentTransitionState != nil && (db.CurrentTransitionState.started || db.CurrentTransitionState.ended) { + if db.InTransition() || db.Transitioned() { // NOTE this is a kaustinen-only change, it will break replay vkt, err := db.openVKTrie(root) if err != nil { @@ -542,7 +542,11 @@ func (db *cachingDB) SaveTransitionState(root common.Hash) { db.TransitionStatePerRoot = make(map[common.Hash]*TransitionState) } - db.TransitionStatePerRoot[root] = db.CurrentTransitionState + if db.CurrentTransitionState != nil { + // Copy so that the address pointer isn't updated after + // it has been saved. + db.TransitionStatePerRoot[root] = db.CurrentTransitionState.Copy() + } } func (db *cachingDB) LoadTransitionState(root common.Hash) { @@ -556,9 +560,7 @@ func (db *cachingDB) LoadTransitionState(root common.Hash) { ts = &TransitionState{ended: db.triedb.IsVerkle()} } + // Copy so that the CurrentAddress pointer in the map + // doesn't get overwritten. db.CurrentTransitionState = ts.Copy() - - if db.CurrentTransitionState != nil { - fmt.Println("address", db.CurrentTransitionState.CurrentAccountAddress) - } } From 7e86b8058a9230c27cca600ceb7716412c2c193c Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Sun, 10 Dec 2023 20:34:08 +0100 Subject: [PATCH 11/22] fix: ensure StorageProcessed is properly recovered --- core/overlay/conversion.go | 2 +- core/state/database.go | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/core/overlay/conversion.go b/core/overlay/conversion.go index 1a987ab69250..929357203380 100644 --- a/core/overlay/conversion.go +++ b/core/overlay/conversion.go @@ -405,7 +405,7 @@ func OverlayVerkleTransition(statedb *state.StateDB, root common.Hash, maxMovedC } migrdb.SetCurrentPreimageOffset(preimageSeek) - log.Info("Collected key values from base tree", "count", count, "duration", time.Since(now), "last account", statedb.Database().GetCurrentAccountHash()) + log.Info("Collected key values from base tree", "count", count, "duration", time.Since(now), "last account", statedb.Database().GetCurrentAccountHash(), "storage processed", statedb.Database().GetStorageProcessed(), "last storage", statedb.Database().GetCurrentSlotHash()) // Take all the collected key-values and prepare the new leaf values. // This fires a background routine that will start doing the work that diff --git a/core/state/database.go b/core/state/database.go index c9d06b443b18..55d5dac739ff 100644 --- a/core/state/database.go +++ b/core/state/database.go @@ -284,6 +284,7 @@ func (ts *TransitionState) Copy() *TransitionState { ended: ts.ended, CurrentSlotHash: ts.CurrentSlotHash, CurrentPreimageOffset: ts.CurrentPreimageOffset, + StorageProcessed: ts.StorageProcessed, } if ts.CurrentAccountAddress != nil { @@ -563,4 +564,6 @@ func (db *cachingDB) LoadTransitionState(root common.Hash) { // Copy so that the CurrentAddress pointer in the map // doesn't get overwritten. db.CurrentTransitionState = ts.Copy() + + fmt.Println("loaded transition state", db.CurrentTransitionState.StorageProcessed) } From 6a255d0de6ba55647701a414fb7d975c7eea2090 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Mon, 11 Dec 2023 16:26:20 +0100 Subject: [PATCH 12/22] add traces for gas pool overflow --- core/state_transition.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/state_transition.go b/core/state_transition.go index 969e7a75fb9b..5b89b89c4f29 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -508,6 +508,10 @@ func (st *StateTransition) refundGas(refundQuotient uint64) { remaining := new(big.Int).Mul(new(big.Int).SetUint64(st.gasRemaining), st.msg.GasPrice) st.state.AddBalance(st.msg.From, remaining) + if st.gp.Gas() > math.MaxUint64-st.gasRemaining { + fmt.Println(st.gp.Gas(), refund, refundQuotient, st.gasUsed(), st.initialGas, st.gasUsed(), st.evm.Accesses, st.msg) + } + // Also return remaining gas to the block gas counter so it is // available for the next transaction. st.gp.AddGas(st.gasRemaining) From 9b54f2925a089355b0d562722504f29fed0f4fde Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Fri, 26 Jan 2024 17:57:55 +0100 Subject: [PATCH 13/22] cmd/{geth, utils}: add a command to clear verkle costs (#326) * cmd/{geth, utils}: add a command to clear verkle costs fix: boolean issue fix: load finalization state in FinalizeAndAssemble (#340) * Conversion and TransitionTrie fixes (#346) * fixes Signed-off-by: Ignacio Hagopian * remove old comment Signed-off-by: Ignacio Hagopian --------- Signed-off-by: Ignacio Hagopian * trace cleanup --------- Signed-off-by: Ignacio Hagopian Co-authored-by: Ignacio Hagopian --- cmd/geth/config.go | 3 ++ cmd/geth/main.go | 1 + cmd/utils/flags.go | 5 ++ consensus/beacon/consensus.go | 88 +++++++++++++++++++---------------- core/blockchain.go | 7 +-- core/overlay/conversion.go | 47 ++++++++++++------- core/state/database.go | 11 ++++- core/state/statedb.go | 2 +- miner/worker.go | 2 +- trie/transition.go | 8 +++- 10 files changed, 109 insertions(+), 65 deletions(-) diff --git a/cmd/geth/config.go b/cmd/geth/config.go index cdd893d2a8ec..4184290e86c6 100644 --- a/cmd/geth/config.go +++ b/cmd/geth/config.go @@ -183,6 +183,9 @@ func makeFullNode(ctx *cli.Context) (*node.Node, ethapi.Backend) { v := ctx.Uint64(utils.OverrideOverlayStride.Name) cfg.Eth.OverrideOverlayStride = &v } + if ctx.IsSet(utils.ClearVerkleCosts.Name) { + params.ClearVerkleWitnessCosts() + } backend, eth := utils.RegisterEthService(stack, &cfg.Eth) // Configure log filter RPC API. diff --git a/cmd/geth/main.go b/cmd/geth/main.go index e2e4ba25fe06..5cb1580df3d1 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -71,6 +71,7 @@ var ( utils.OverrideCancun, utils.OverridePrague, utils.OverrideProofInBlock, + utils.ClearVerkleCosts, utils.EnablePersonal, utils.TxPoolLocalsFlag, utils.TxPoolNoLocalsFlag, diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 0dd311706abb..3395eebca866 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -284,6 +284,11 @@ var ( Usage: "Manually specify the proof-in-block setting", Category: flags.EthCategory, } + ClearVerkleCosts = &cli.BoolFlag{ + Name: "clear.verkle.costs", + Usage: "Clear verkle costs (for shadow forks)", + Category: flags.EthCategory, + } // Light server and client settings LightServeFlag = &cli.IntFlag{ Name: "light.serve", diff --git a/consensus/beacon/consensus.go b/consensus/beacon/consensus.go index 70008b270259..7c93844f39c9 100644 --- a/consensus/beacon/consensus.go +++ b/consensus/beacon/consensus.go @@ -28,6 +28,7 @@ import ( "github.com/ethereum/go-ethereum/core/overlay" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/trie" @@ -368,7 +369,9 @@ func (beacon *Beacon) Finalize(chain consensus.ChainHeaderReader, header *types. if chain.Config().IsPrague(header.Number, header.Time) { fmt.Println("at block", header.Number, "performing transition?", state.Database().InTransition()) parent := chain.GetHeaderByHash(header.ParentHash) - overlay.OverlayVerkleTransition(state, parent.Root, chain.Config().OverlayStride) + if err := overlay.OverlayVerkleTransition(state, parent.Root, chain.Config().OverlayStride); err != nil { + log.Error("error performing the transition", "err", err) + } } } @@ -394,63 +397,68 @@ func (beacon *Beacon) FinalizeAndAssemble(chain consensus.ChainHeaderReader, hea // Assign the final state root to header. header.Root = state.IntermediateRoot(true) + state.Database().SaveTransitionState(header.Root) var ( p *verkle.VerkleProof k verkle.StateDiff keys = state.Witness().Keys() ) - if chain.Config().IsPrague(header.Number, header.Time) && chain.Config().ProofInBlocks { + if chain.Config().IsPrague(header.Number, header.Time) { // Open the pre-tree to prove the pre-state against parent := chain.GetHeaderByNumber(header.Number.Uint64() - 1) if parent == nil { return nil, fmt.Errorf("nil parent header for block %d", header.Number) } - preTrie, err := state.Database().OpenTrie(parent.Root) - if err != nil { - return nil, fmt.Errorf("error opening pre-state tree root: %w", err) - } + state.Database().LoadTransitionState(parent.Root) + + if chain.Config().ProofInBlocks { + preTrie, err := state.Database().OpenTrie(parent.Root) + if err != nil { + return nil, fmt.Errorf("error opening pre-state tree root: %w", err) + } - var okpre, okpost bool - var vtrpre, vtrpost *trie.VerkleTrie - switch pre := preTrie.(type) { - case *trie.VerkleTrie: - vtrpre, okpre = preTrie.(*trie.VerkleTrie) - switch tr := state.GetTrie().(type) { + var okpre, okpost bool + var vtrpre, vtrpost *trie.VerkleTrie + switch pre := preTrie.(type) { case *trie.VerkleTrie: - vtrpost = tr - okpost = true - // This is to handle a situation right at the start of the conversion: - // the post trie is a transition tree when the pre tree is an empty - // verkle tree. + vtrpre, okpre = preTrie.(*trie.VerkleTrie) + switch tr := state.GetTrie().(type) { + case *trie.VerkleTrie: + vtrpost = tr + okpost = true + // This is to handle a situation right at the start of the conversion: + // the post trie is a transition tree when the pre tree is an empty + // verkle tree. + case *trie.TransitionTrie: + vtrpost = tr.Overlay() + okpost = true + default: + okpost = false + } case *trie.TransitionTrie: - vtrpost = tr.Overlay() + vtrpre = pre.Overlay() + okpre = true + post, _ := state.GetTrie().(*trie.TransitionTrie) + vtrpost = post.Overlay() okpost = true default: - okpost = false + // This should only happen for the first block of the + // conversion, when the previous tree is a merkle tree. + // Logically, the "previous" verkle tree is an empty tree. + okpre = true + vtrpre = trie.NewVerkleTrie(verkle.New(), state.Database().TrieDB(), utils.NewPointCache(), false) + post := state.GetTrie().(*trie.TransitionTrie) + vtrpost = post.Overlay() + okpost = true } - case *trie.TransitionTrie: - vtrpre = pre.Overlay() - okpre = true - post, _ := state.GetTrie().(*trie.TransitionTrie) - vtrpost = post.Overlay() - okpost = true - default: - // This should only happen for the first block of the - // conversion, when the previous tree is a merkle tree. - // Logically, the "previous" verkle tree is an empty tree. - okpre = true - vtrpre = trie.NewVerkleTrie(verkle.New(), state.Database().TrieDB(), utils.NewPointCache(), false) - post := state.GetTrie().(*trie.TransitionTrie) - vtrpost = post.Overlay() - okpost = true - } - if okpre && okpost { - if len(keys) > 0 { - p, k, err = trie.ProveAndSerialize(vtrpre, vtrpost, keys, vtrpre.FlatdbNodeResolver) - if err != nil { - return nil, fmt.Errorf("error generating verkle proof for block %d: %w", header.Number, err) + if okpre && okpost { + if len(keys) > 0 { + p, k, err = trie.ProveAndSerialize(vtrpre, vtrpost, keys, vtrpre.FlatdbNodeResolver) + if err != nil { + return nil, fmt.Errorf("error generating verkle proof for block %d: %w", header.Number, err) + } } } } diff --git a/core/blockchain.go b/core/blockchain.go index fdde955b87bd..c92e0e36f631 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -103,7 +103,7 @@ const ( txLookupCacheLimit = 1024 maxFutureBlocks = 256 maxTimeFutureBlocks = 30 - TriesInMemory = 128 + TriesInMemory = 8192 // BlockChainVersion ensures that an incompatible database forces a resync from scratch. // @@ -2010,7 +2010,7 @@ func (bc *BlockChain) insertSideChain(block *types.Block, it *insertIterator) (i parent = bc.GetHeader(parent.ParentHash, parent.Number.Uint64()-1) } if parent == nil { - return it.index, errors.New("missing parent") + return it.index, fmt.Errorf("missing parent: hash=%x, number=%d", current.Hash(), current.Number) } // Import all the pruned blocks to make the state available var ( @@ -2071,7 +2071,7 @@ func (bc *BlockChain) recoverAncestors(block *types.Block) (common.Hash, error) } } if parent == nil { - return common.Hash{}, errors.New("missing parent") + return common.Hash{}, fmt.Errorf("missing parent during ancestor recovery: hash=%x, number=%d", block.ParentHash(), block.Number()) } // Import all the pruned blocks to make the state available for i := len(hashes) - 1; i >= 0; i-- { @@ -2309,6 +2309,7 @@ func (bc *BlockChain) SetCanonical(head *types.Block) (common.Hash, error) { defer bc.chainmu.Unlock() // Re-execute the reorged chain in case the head state is missing. + log.Trace("looking for state", "root", head.Root(), "has state", bc.HasState(head.Root())) if !bc.HasState(head.Root()) { if latestValidHash, err := bc.recoverAncestors(head); err != nil { return latestValidHash, err diff --git a/core/overlay/conversion.go b/core/overlay/conversion.go index 929357203380..e538c58bcce0 100644 --- a/core/overlay/conversion.go +++ b/core/overlay/conversion.go @@ -47,7 +47,7 @@ var zeroTreeIndex uint256.Int // collect the key-values in a way that is efficient. type keyValueMigrator struct { // leafData contains the values for the future leaf for a particular VKT branch. - leafData []migratedKeyValue + leafData map[branchKey]*migratedKeyValue // When prepare() is called, it will start a background routine that will process the leafData // saving the result in newLeaves to be used by migrateCollectedKeyValues(). The background @@ -66,7 +66,7 @@ func newKeyValueMigrator() *keyValueMigrator { _ = verkle.GetConfig() return &keyValueMigrator{ processingReady: make(chan struct{}), - leafData: make([]migratedKeyValue, 0, 10_000), + leafData: make(map[branchKey]*migratedKeyValue, 10_000), } } @@ -138,19 +138,17 @@ func (kvm *keyValueMigrator) addAccountCode(addr []byte, codeSize uint64, chunks } func (kvm *keyValueMigrator) getOrInitLeafNodeData(bk branchKey) *verkle.BatchNewLeafNodeData { - // Remember that keyValueMigration receives actions ordered by (address, subtreeIndex). - // This means that we can assume that the last element of leafData is the one that we - // are looking for, or that we need to create a new one. - if len(kvm.leafData) == 0 || kvm.leafData[len(kvm.leafData)-1].branchKey != bk { - kvm.leafData = append(kvm.leafData, migratedKeyValue{ - branchKey: bk, - leafNodeData: verkle.BatchNewLeafNodeData{ - Stem: nil, // It will be calculated in the prepare() phase, since it's CPU heavy. - Values: make(map[byte][]byte), - }, - }) + if ld, ok := kvm.leafData[bk]; ok { + return &ld.leafNodeData } - return &kvm.leafData[len(kvm.leafData)-1].leafNodeData + kvm.leafData[bk] = &migratedKeyValue{ + branchKey: bk, + leafNodeData: verkle.BatchNewLeafNodeData{ + Stem: nil, // It will be calculated in the prepare() phase, since it's CPU heavy. + Values: make(map[byte][]byte, 256), + }, + } + return &kvm.leafData[bk].leafNodeData } func (kvm *keyValueMigrator) prepare() { @@ -159,6 +157,10 @@ func (kvm *keyValueMigrator) prepare() { go func() { // Step 1: We split kvm.leafData in numBatches batches, and we process each batch in a separate goroutine. // This fills each leafNodeData.Stem with the correct value. + leafData := make([]migratedKeyValue, 0, len(kvm.leafData)) + for _, v := range kvm.leafData { + leafData = append(leafData, *v) + } var wg sync.WaitGroup batchNum := runtime.NumCPU() batchSize := (len(kvm.leafData) + batchNum - 1) / batchNum @@ -170,7 +172,7 @@ func (kvm *keyValueMigrator) prepare() { } wg.Add(1) - batch := kvm.leafData[start:end] + batch := leafData[start:end] go func() { defer wg.Done() var currAddr common.Address @@ -190,8 +192,8 @@ func (kvm *keyValueMigrator) prepare() { // Step 2: Now that we have all stems (i.e: tree keys) calculated, we can create the new leaves. nodeValues := make([]verkle.BatchNewLeafNodeData, len(kvm.leafData)) - for i := range kvm.leafData { - nodeValues[i] = kvm.leafData[i].leafNodeData + for i := range leafData { + nodeValues[i] = leafData[i].leafNodeData } // Create all leaves in batch mode so we can optimize cryptography operations. @@ -306,7 +308,12 @@ func OverlayVerkleTransition(statedb *state.StateDB, root common.Hash, maxMovedC if err != nil { return err } - stIt.Next() + processed := stIt.Next() + if processed { + log.Debug("account has storage and a next item") + } else { + log.Debug("account has storage and NO next item") + } // fdb.StorageProcessed will be initialized to `true` if the // entire storage for an account was not entirely processed @@ -315,6 +322,7 @@ func OverlayVerkleTransition(statedb *state.StateDB, root common.Hash, maxMovedC // If the entire storage was processed, then the iterator was // created in vain, but it's ok as this will not happen often. for ; !migrdb.GetStorageProcessed() && count < maxMovedCount; count++ { + log.Trace("Processing storage", "count", count, "slot", stIt.Slot(), "storage processed", migrdb.GetStorageProcessed(), "current account", migrdb.GetCurrentAccountAddress(), "current account hash", migrdb.GetCurrentAccountHash()) var ( value []byte // slot value after RLP decoding safeValue [32]byte // 32-byte aligned value @@ -337,6 +345,7 @@ func OverlayVerkleTransition(statedb *state.StateDB, root common.Hash, maxMovedC return fmt.Errorf("slotnr len is zero is not 32: %d", len(slotnr)) } } + log.Trace("found slot number", "number", slotnr) if crypto.Keccak256Hash(slotnr[:]) != stIt.Hash() { return fmt.Errorf("preimage file does not match storage hash: %s!=%s", crypto.Keccak256Hash(slotnr), stIt.Hash()) } @@ -378,6 +387,7 @@ func OverlayVerkleTransition(statedb *state.StateDB, root common.Hash, maxMovedC // Move to the next account, if available - or end // the transition otherwise. if accIt.Next() { + log.Trace("Found another account to convert", "hash", accIt.Hash()) var addr common.Address if hasPreimagesBin { if _, err := io.ReadFull(fpreimages, addr[:]); err != nil { @@ -393,6 +403,7 @@ func OverlayVerkleTransition(statedb *state.StateDB, root common.Hash, maxMovedC if crypto.Keccak256Hash(addr[:]) != accIt.Hash() { return fmt.Errorf("preimage file does not match account hash: %s != %s", crypto.Keccak256Hash(addr[:]), accIt.Hash()) } + log.Trace("Converting account address", "hash", accIt.Hash(), "addr", addr) preimageSeek += int64(len(addr)) migrdb.SetCurrentAccountAddress(addr) } else { diff --git a/core/state/database.go b/core/state/database.go index 55d5dac739ff..ab714837d411 100644 --- a/core/state/database.go +++ b/core/state/database.go @@ -340,6 +340,7 @@ func (db *cachingDB) OpenTrie(root common.Hash) (Trie, error) { mpt Trie err error ) + fmt.Printf("opening trie with root %x, %v %v\n", root, db.InTransition(), db.Transitioned()) // TODO separate both cases when I can be certain that it won't // find a Verkle trie where is expects a Transitoion trie. @@ -347,12 +348,14 @@ func (db *cachingDB) OpenTrie(root common.Hash) (Trie, error) { // NOTE this is a kaustinen-only change, it will break replay vkt, err := db.openVKTrie(root) if err != nil { + log.Error("failed to open the vkt", "err", err) return nil, err } // If the verkle conversion has ended, return a single // verkle trie. if db.CurrentTransitionState.ended { + log.Debug("transition ended, returning a simple verkle tree") return vkt, nil } @@ -360,6 +363,7 @@ func (db *cachingDB) OpenTrie(root common.Hash) (Trie, error) { // trie and an overlay, verkle trie. mpt, err = db.openMPTTrie(db.baseRoot) if err != nil { + log.Error("failed to open the mpt", "err", err, "root", db.baseRoot) return nil, err } @@ -547,6 +551,8 @@ func (db *cachingDB) SaveTransitionState(root common.Hash) { // Copy so that the address pointer isn't updated after // it has been saved. db.TransitionStatePerRoot[root] = db.CurrentTransitionState.Copy() + + log.Debug("saving transition state", "storage processed", db.CurrentTransitionState.StorageProcessed, "addr", db.CurrentTransitionState.CurrentAccountAddress, "slot hash", db.CurrentTransitionState.CurrentSlotHash, "root", root, "ended", db.CurrentTransitionState.ended, "started", db.CurrentTransitionState.started) } } @@ -555,6 +561,9 @@ func (db *cachingDB) LoadTransitionState(root common.Hash) { db.TransitionStatePerRoot = make(map[common.Hash]*TransitionState) } + // Initialize the first transition state, with the "ended" + // field set to true if the database was created + // as a verkle database. ts, ok := db.TransitionStatePerRoot[root] if !ok || ts == nil { // Start with a fresh state @@ -565,5 +574,5 @@ func (db *cachingDB) LoadTransitionState(root common.Hash) { // doesn't get overwritten. db.CurrentTransitionState = ts.Copy() - fmt.Println("loaded transition state", db.CurrentTransitionState.StorageProcessed) + log.Debug("loaded transition state", "storage processed", db.CurrentTransitionState.StorageProcessed, "addr", db.CurrentTransitionState.CurrentAccountAddress, "slot hash", db.CurrentTransitionState.CurrentSlotHash, "root", root, "ended", db.CurrentTransitionState.ended, "started", db.CurrentTransitionState.started) } diff --git a/core/state/statedb.go b/core/state/statedb.go index a10c5252f113..6ce3b9b2b9b2 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -1318,7 +1318,7 @@ func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool) (common.Hash, er // - head layer is paired with HEAD state // - head-1 layer is paired with HEAD-1 state // - head-127 layer(bottom-most diff layer) is paired with HEAD-127 state - if err := s.snaps.Cap(root, 128); err != nil { + if err := s.snaps.Cap(root, 8192); err != nil { log.Warn("Failed to cap snapshot tree", "root", root, "layers", 128, "err", err) } } diff --git a/miner/worker.go b/miner/worker.go index 9ac258c77591..3fb4a3fa43e5 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -852,7 +852,7 @@ func (w *worker) prepareWork(genParams *generateParams) (*environment, error) { if genParams.parentHash != (common.Hash{}) { block := w.chain.GetBlockByHash(genParams.parentHash) if block == nil { - return nil, fmt.Errorf("missing parent") + return nil, fmt.Errorf("missing parent: %x", genParams.parentHash) } parent = block.Header() } diff --git a/trie/transition.go b/trie/transition.go index 24daf436ed8a..0fe197336524 100644 --- a/trie/transition.go +++ b/trie/transition.go @@ -17,6 +17,8 @@ package trie import ( + "fmt" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethdb" @@ -62,7 +64,11 @@ func (t *TransitionTrie) GetKey(key []byte) []byte { // not be modified by the caller. If a node was not found in the database, a // trie.MissingNodeError is returned. func (t *TransitionTrie) GetStorage(addr common.Address, key []byte) ([]byte, error) { - if val, err := t.overlay.GetStorage(addr, key); len(val) != 0 || err != nil { + val, err := t.overlay.GetStorage(addr, key) + if err != nil { + return nil, fmt.Errorf("get storage from overlay: %s", err) + } + if len(val) != 0 { return val, nil } // TODO also insert value into overlay From 68410635fb7f5b446887c052bd3a5835a187295b Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Sat, 3 Feb 2024 09:26:47 +0100 Subject: [PATCH 14/22] fix: check for nil override in NewBlockChain (#357) --- core/blockchain.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index c92e0e36f631..36e58ca8df57 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -251,11 +251,13 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis if _, ok := genesisErr.(*params.ConfigCompatError); genesisErr != nil && !ok { return nil, genesisErr } - if overrides.OverrideProofInBlock != nil { - chainConfig.ProofInBlocks = *overrides.OverrideProofInBlock - } - if overrides.OverrideOverlayStride != nil { - chainConfig.OverlayStride = *overrides.OverrideOverlayStride + if overrides != nil { + if overrides.OverrideProofInBlock != nil { + chainConfig.ProofInBlocks = *overrides.OverrideProofInBlock + } + if overrides.OverrideOverlayStride != nil { + chainConfig.OverlayStride = *overrides.OverrideOverlayStride + } } log.Info("") log.Info(strings.Repeat("-", 153)) From a4c556dbf0364c011a623d7f5236b9198c644b1c Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Thu, 8 Feb 2024 13:10:43 +0100 Subject: [PATCH 15/22] fix rebase issue --- core/overlay/conversion.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/overlay/conversion.go b/core/overlay/conversion.go index e538c58bcce0..89d8dc8079ca 100644 --- a/core/overlay/conversion.go +++ b/core/overlay/conversion.go @@ -36,7 +36,7 @@ import ( "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie/utils" - "github.com/gballet/go-verkle" + "github.com/ethereum/go-verkle" "github.com/holiman/uint256" ) From a774d16436307c03ecb9af0b056342441b4f5adf Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Thu, 8 Feb 2024 17:55:15 +0100 Subject: [PATCH 16/22] fix a few rebase issues --- core/chain_makers.go | 18 +++++++----------- core/state_processor.go | 9 +++++++++ 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/core/chain_makers.go b/core/chain_makers.go index 3909500d91fa..1c232e6b6d92 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -365,7 +365,7 @@ func GenerateChainWithGenesis(genesis *Genesis, engine consensus.Engine, n int, return db, blocks, receipts } -func GenerateVerkleChain(config *params.ChainConfig, parent *types.Block, engine consensus.Engine, db ethdb.Database, n int, gen func(int, *BlockGen)) ([]*types.Block, []types.Receipts, []*verkle.VerkleProof, []verkle.StateDiff) { +func GenerateVerkleChain(config *params.ChainConfig, parent *types.Block, engine consensus.Engine, diskdb ethdb.Database, n int, gen func(int, *BlockGen)) ([]*types.Block, []types.Receipts, []*verkle.VerkleProof, []verkle.StateDiff) { if config == nil { config = params.TestChainConfig } @@ -434,20 +434,16 @@ func GenerateVerkleChain(config *params.ChainConfig, parent *types.Block, engine return nil, nil } var snaps *snapshot.Tree - triedb := state.NewDatabaseWithConfig(db, nil) - triedb.StartVerkleTransition(common.Hash{}, common.Hash{}, config, config.PragueTime, common.Hash{}) - triedb.EndVerkleTransition() - //statedb, err := state.New(parent.Root(), triedb, snaps) - //if err != nil { - // panic(fmt.Sprintf("could not find state for block %d: err=%v, parent root=%x", parent.NumberU64(), err, parent.Root())) - //} - statedb.Database().SaveTransitionState(parent.Root()) + db := state.NewDatabaseWithConfig(diskdb, nil) + db.StartVerkleTransition(common.Hash{}, common.Hash{}, config, config.PragueTime, common.Hash{}) + db.EndVerkleTransition() + db.SaveTransitionState(parent.Root()) for i := 0; i < n; i++ { - // XXX merge uncommment - statedb, err := state.New(parent.Root(), triedb, snaps) + statedb, err := state.New(parent.Root(), db, snaps) if err != nil { panic(fmt.Sprintf("could not find state for block %d: err=%v, parent root=%x", i, err, parent.Root())) } + statedb.NewAccessWitness() block, receipt := genblock(i, parent, statedb) blocks[i] = block receipts[i] = receipt diff --git a/core/state_processor.go b/core/state_processor.go index cb4d622d6d90..227608fb21b4 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -31,6 +31,7 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/trie/utils" ) // StateProcessor is a basic Processor, which takes care of transitioning @@ -186,6 +187,14 @@ func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *commo return applyTransaction(msg, config, gp, statedb, header.Number, header.Hash(), tx, usedGas, vmenv) } +func InsertBlockHashHistoryAtEip2935Fork(statedb *state.StateDB, prevNumber uint64, prevHash common.Hash, chain consensus.ChainHeaderReader) { + ancestor := chain.GetHeader(prevHash, prevNumber) + for i := prevNumber; i > 0 && i >= prevNumber-256; i-- { + ProcessParentBlockHash(statedb, i, ancestor.Hash()) + ancestor = chain.GetHeader(ancestor.ParentHash, ancestor.Number.Uint64()-1) + } +} + func ProcessParentBlockHash(statedb *state.StateDB, prevNumber uint64, prevHash common.Hash) { ringIndex := prevNumber % 256 var key common.Hash From 17843378f5de6e2da1d5adc978db9eac46c15eb0 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Fri, 9 Feb 2024 11:08:48 +0100 Subject: [PATCH 17/22] add debug traces --- core/blockchain.go | 2 ++ core/state/database.go | 5 +++++ eth/catalyst/api.go | 8 ++++---- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index 36e58ca8df57..8958b15754dc 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -1767,6 +1767,8 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) // is the fork block and that the conversion needs to be marked at started. if !bc.stateCache.InTransition() && !bc.stateCache.Transitioned() { bc.stateCache.StartVerkleTransition(parent.Root, emptyVerkleRoot, bc.Config(), bc.Config().PragueTime, parent.Root) + } else { + log.Debug("skipped initialization") } } if parent.Number.Uint64() == conversionBlock { diff --git a/core/state/database.go b/core/state/database.go index ab714837d411..fa1af2a869e1 100644 --- a/core/state/database.go +++ b/core/state/database.go @@ -19,6 +19,7 @@ package state import ( "errors" "fmt" + "runtime/debug" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/lru" @@ -237,6 +238,7 @@ func (db *cachingDB) StartVerkleTransition(originalRoot, translatedRoot common.H if pragueTime != nil { chainConfig.PragueTime = pragueTime } + debug.PrintStack() } func (db *cachingDB) ReorgThroughVerkleTransition() { @@ -355,6 +357,7 @@ func (db *cachingDB) OpenTrie(root common.Hash) (Trie, error) { // If the verkle conversion has ended, return a single // verkle trie. if db.CurrentTransitionState.ended { + debug.PrintStack() log.Debug("transition ended, returning a simple verkle tree") return vkt, nil } @@ -566,6 +569,8 @@ func (db *cachingDB) LoadTransitionState(root common.Hash) { // as a verkle database. ts, ok := db.TransitionStatePerRoot[root] if !ok || ts == nil { + log.Debug("could not find any transition state, starting with a fresh state", "is verkle", db.triedb.IsVerkle()) + debug.PrintStack() // Start with a fresh state ts = &TransitionState{ended: db.triedb.IsVerkle()} } diff --git a/eth/catalyst/api.go b/eth/catalyst/api.go index 864b3efe84f5..087a77015437 100644 --- a/eth/catalyst/api.go +++ b/eth/catalyst/api.go @@ -535,10 +535,10 @@ func (api *ConsensusAPI) newPayload(params engine.ExecutableData, versionedHashe api.eth.BlockChain().StartVerkleTransition(parent.Root, common.Hash{}, api.eth.BlockChain().Config(), nil, parent.Root) } } - // Reset db merge state in case of a reorg - if !api.eth.BlockChain().Config().IsPrague(block.Number(), block.Time()) { - api.eth.BlockChain().ReorgThroughVerkleTransition() - } + // // Reset db merge state in case of a reorg + // if !api.eth.BlockChain().Config().IsPrague(block.Number(), block.Time()) { + // api.eth.BlockChain().ReorgThroughVerkleTransition() + // } // Another cornercase: if the node is in snap sync mode, but the CL client // tries to make it import a block. That should be denied as pushing something // into the database directly will conflict with the assumptions of snap sync From ff3616838b4d315b5b47529f35e983d08c2345fa Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Fri, 9 Feb 2024 12:15:27 +0100 Subject: [PATCH 18/22] fix: assume that having to create a new transaction state mean it hasn't happened. This is a workaround, because it will always be false when restarting a node that has started the transition but not necessarily completed it. --- core/blockchain.go | 2 -- core/state/database.go | 6 +----- eth/catalyst/api.go | 4 ---- 3 files changed, 1 insertion(+), 11 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index 8958b15754dc..36e58ca8df57 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -1767,8 +1767,6 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) // is the fork block and that the conversion needs to be marked at started. if !bc.stateCache.InTransition() && !bc.stateCache.Transitioned() { bc.stateCache.StartVerkleTransition(parent.Root, emptyVerkleRoot, bc.Config(), bc.Config().PragueTime, parent.Root) - } else { - log.Debug("skipped initialization") } } if parent.Number.Uint64() == conversionBlock { diff --git a/core/state/database.go b/core/state/database.go index fa1af2a869e1..97facc29907f 100644 --- a/core/state/database.go +++ b/core/state/database.go @@ -19,7 +19,6 @@ package state import ( "errors" "fmt" - "runtime/debug" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/lru" @@ -238,7 +237,6 @@ func (db *cachingDB) StartVerkleTransition(originalRoot, translatedRoot common.H if pragueTime != nil { chainConfig.PragueTime = pragueTime } - debug.PrintStack() } func (db *cachingDB) ReorgThroughVerkleTransition() { @@ -357,7 +355,6 @@ func (db *cachingDB) OpenTrie(root common.Hash) (Trie, error) { // If the verkle conversion has ended, return a single // verkle trie. if db.CurrentTransitionState.ended { - debug.PrintStack() log.Debug("transition ended, returning a simple verkle tree") return vkt, nil } @@ -570,9 +567,8 @@ func (db *cachingDB) LoadTransitionState(root common.Hash) { ts, ok := db.TransitionStatePerRoot[root] if !ok || ts == nil { log.Debug("could not find any transition state, starting with a fresh state", "is verkle", db.triedb.IsVerkle()) - debug.PrintStack() // Start with a fresh state - ts = &TransitionState{ended: db.triedb.IsVerkle()} + ts = &TransitionState{ended: false} } // Copy so that the CurrentAddress pointer in the map diff --git a/eth/catalyst/api.go b/eth/catalyst/api.go index 087a77015437..925494a74d5f 100644 --- a/eth/catalyst/api.go +++ b/eth/catalyst/api.go @@ -535,10 +535,6 @@ func (api *ConsensusAPI) newPayload(params engine.ExecutableData, versionedHashe api.eth.BlockChain().StartVerkleTransition(parent.Root, common.Hash{}, api.eth.BlockChain().Config(), nil, parent.Root) } } - // // Reset db merge state in case of a reorg - // if !api.eth.BlockChain().Config().IsPrague(block.Number(), block.Time()) { - // api.eth.BlockChain().ReorgThroughVerkleTransition() - // } // Another cornercase: if the node is in snap sync mode, but the CL client // tries to make it import a block. That should be denied as pushing something // into the database directly will conflict with the assumptions of snap sync From a641947179c0f9ecabae2d499b6c9e988bea2fac Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Tue, 13 Feb 2024 07:58:37 +0100 Subject: [PATCH 19/22] add logs to debug shadowfork --- core/state/database.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/state/database.go b/core/state/database.go index 97facc29907f..075b948cf32e 100644 --- a/core/state/database.go +++ b/core/state/database.go @@ -552,7 +552,7 @@ func (db *cachingDB) SaveTransitionState(root common.Hash) { // it has been saved. db.TransitionStatePerRoot[root] = db.CurrentTransitionState.Copy() - log.Debug("saving transition state", "storage processed", db.CurrentTransitionState.StorageProcessed, "addr", db.CurrentTransitionState.CurrentAccountAddress, "slot hash", db.CurrentTransitionState.CurrentSlotHash, "root", root, "ended", db.CurrentTransitionState.ended, "started", db.CurrentTransitionState.started) + fmt.Println("saving transition state", "storage processed", db.CurrentTransitionState.StorageProcessed, "addr", db.CurrentTransitionState.CurrentAccountAddress, "slot hash", db.CurrentTransitionState.CurrentSlotHash, "root", root, "ended", db.CurrentTransitionState.ended, "started", db.CurrentTransitionState.started) } } @@ -566,7 +566,7 @@ func (db *cachingDB) LoadTransitionState(root common.Hash) { // as a verkle database. ts, ok := db.TransitionStatePerRoot[root] if !ok || ts == nil { - log.Debug("could not find any transition state, starting with a fresh state", "is verkle", db.triedb.IsVerkle()) + fmt.Println("could not find any transition state, starting with a fresh state", "is verkle", db.triedb.IsVerkle()) // Start with a fresh state ts = &TransitionState{ended: false} } @@ -575,5 +575,5 @@ func (db *cachingDB) LoadTransitionState(root common.Hash) { // doesn't get overwritten. db.CurrentTransitionState = ts.Copy() - log.Debug("loaded transition state", "storage processed", db.CurrentTransitionState.StorageProcessed, "addr", db.CurrentTransitionState.CurrentAccountAddress, "slot hash", db.CurrentTransitionState.CurrentSlotHash, "root", root, "ended", db.CurrentTransitionState.ended, "started", db.CurrentTransitionState.started) + fmt.Println("loaded transition state", "storage processed", db.CurrentTransitionState.StorageProcessed, "addr", db.CurrentTransitionState.CurrentAccountAddress, "slot hash", db.CurrentTransitionState.CurrentSlotHash, "root", root, "ended", db.CurrentTransitionState.ended, "started", db.CurrentTransitionState.started) } From 7a3bb67881f307cae45437425910b3ca12ef0d3f Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Thu, 22 Feb 2024 16:03:51 +0100 Subject: [PATCH 20/22] persist conversion state to db and use an LRU cache for active transition states (#375) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * persist conversion state to db * fix: don't recreate LRU when writing state * opt: only write state to db if not already present in LRU * fix: rlp can't encode TransitionState * fix: use gob because binary.Write does not support structs 🤦‍♂️ * fix: nil pointer panic * add logs to debug shadowfork * no such thing as not enough traces * ditto * fix stupid bug * add a comment for readability * add more traces * Lock the state transition during conversion (#384) * heavy handed approach: lock the state transition during conversion * also lock transition state loading/unloading * reduce logs verbosity * add conversion test to workflow (#386) * add conversion test to workflow * mandatory -f switch fix in rm * forgot & at the end of the geth command * remove incorrect kill * add debug traces * add an overlay stride * fix typo * Apply suggestions from code review --- .github/workflows/conversion.yml | 75 +++++++++++++++++++ consensus/beacon/consensus.go | 2 + core/overlay/conversion.go | 7 +- core/rawdb/accessors_overlay.go | 30 ++++++++ core/rawdb/schema.go | 7 ++ core/state/database.go | 119 ++++++++++++++++++++++--------- core/state_processor.go | 6 -- light/trie.go | 7 ++ 8 files changed, 212 insertions(+), 41 deletions(-) create mode 100644 .github/workflows/conversion.yml create mode 100644 core/rawdb/accessors_overlay.go diff --git a/.github/workflows/conversion.yml b/.github/workflows/conversion.yml new file mode 100644 index 000000000000..40d4fed6925d --- /dev/null +++ b/.github/workflows/conversion.yml @@ -0,0 +1,75 @@ +name: Overlay conversion + +on: + push: + branches: [ master, transition-post-genesis, store-transition-state-in-db ] + pull_request: + branches: [ master, kaustinen-with-shapella, transition-post-genesis, store-transition-state-in-db, lock-overlay-transition ] + workflow_dispatch: + +jobs: + build: + runs-on: self-hosted + steps: + - uses: actions/checkout@v2 + - name: Set up Go + uses: actions/setup-go@v2 + with: + go-version: 1.21.1 + + - name: Cleanup from previous runs + run: | + rm -f log.txt + rm -rf .shadowfork + rm -f genesis.json + + - name: Download genesis file + run: wget https://gist.githubusercontent.com/gballet/0b02a025428aa0e7b67941864d54716c/raw/bfb4e158bca5217b356a19b2ec55c4a45a7b2bad/genesis.json + + - name: Init data + run: go run ./cmd/geth --dev --cache.preimages init genesis.json + + - name: Run geth in devmode + run: go run ./cmd/geth --dev --dev.period=5 --cache.preimages --http --datadir=.shadowfork --override.overlay-stride=10 --override.prague=$(($(date +%s) + 45)) > log.txt & + + - name: Wait for the transition to start + run: | + start_time=$(date +%s) + while true; do + sleep 5 + current_time=$(date +%s) + elapsed_time=$((current_time - start_time)) + + # 2 minute timeout + if [ $elapsed_time -ge 120 ]; then + kill -9 $(pgrep -f geth) + exit 1 + fi + + # Check for signs that the conversion has started + if grep -q "Processing verkle conversion starting at" log.txt; then + break + fi + done + + - name: Wait for the transition to end + run: | + start_time=$(date +%s) + while true; do + sleep 5 + current_time=$(date +%s) + elapsed_time=$((current_time - start_time)) + + # 10 minute timeout + if [ $elapsed_time -ge 300 ]; then + cat log.txt + kill -9 $(pgrep -f geth) + exit 1 + fi + + # Check for signs that the conversion has started + if egrep -q "at block.*performing transition\? false" log.txt; then + kill -9 $(pgrep -f geth) + break + fi + done diff --git a/consensus/beacon/consensus.go b/consensus/beacon/consensus.go index 7c93844f39c9..62d9f48feb2a 100644 --- a/consensus/beacon/consensus.go +++ b/consensus/beacon/consensus.go @@ -411,6 +411,8 @@ func (beacon *Beacon) FinalizeAndAssemble(chain consensus.ChainHeaderReader, hea return nil, fmt.Errorf("nil parent header for block %d", header.Number) } + // Load transition state at beginning of block, because + // OpenTrie needs to know what the conversion status is. state.Database().LoadTransitionState(parent.Root) if chain.Config().ProofInBlocks { diff --git a/core/overlay/conversion.go b/core/overlay/conversion.go index 89d8dc8079ca..bf77ff0033f1 100644 --- a/core/overlay/conversion.go +++ b/core/overlay/conversion.go @@ -221,6 +221,8 @@ func (kvm *keyValueMigrator) migrateCollectedKeyValues(tree *trie.VerkleTrie) er // OverlayVerkleTransition contains the overlay conversion logic func OverlayVerkleTransition(statedb *state.StateDB, root common.Hash, maxMovedCount uint64) error { migrdb := statedb.Database() + migrdb.LockCurrentTransitionState() + defer migrdb.UnLockCurrentTransitionState() // verkle transition: if the conversion process is in progress, move // N values from the MPT into the verkle tree. @@ -289,7 +291,7 @@ func OverlayVerkleTransition(statedb *state.StateDB, root common.Hash, maxMovedC for count < maxMovedCount { acc, err := types.FullAccount(accIt.Account()) if err != nil { - log.Error("Invalid account encountered during traversal", "error", err) + fmt.Println("Invalid account encountered during traversal", "error", err) return err } vkt.SetStorageRootConversion(*migrdb.GetCurrentAccountAddress(), acc.Root) @@ -399,7 +401,6 @@ func OverlayVerkleTransition(statedb *state.StateDB, root common.Hash, maxMovedC return fmt.Errorf("account address len is zero is not 20: %d", len(addr)) } } - // fmt.Printf("account switch: %s != %s\n", crypto.Keccak256Hash(addr[:]), accIt.Hash()) if crypto.Keccak256Hash(addr[:]) != accIt.Hash() { return fmt.Errorf("preimage file does not match account hash: %s != %s", crypto.Keccak256Hash(addr[:]), accIt.Hash()) } @@ -416,7 +417,7 @@ func OverlayVerkleTransition(statedb *state.StateDB, root common.Hash, maxMovedC } migrdb.SetCurrentPreimageOffset(preimageSeek) - log.Info("Collected key values from base tree", "count", count, "duration", time.Since(now), "last account", statedb.Database().GetCurrentAccountHash(), "storage processed", statedb.Database().GetStorageProcessed(), "last storage", statedb.Database().GetCurrentSlotHash()) + log.Info("Collected key values from base tree", "count", count, "duration", time.Since(now), "last account hash", statedb.Database().GetCurrentAccountHash(), "last account address", statedb.Database().GetCurrentAccountAddress(), "storage processed", statedb.Database().GetStorageProcessed(), "last storage", statedb.Database().GetCurrentSlotHash()) // Take all the collected key-values and prepare the new leaf values. // This fires a background routine that will start doing the work that diff --git a/core/rawdb/accessors_overlay.go b/core/rawdb/accessors_overlay.go new file mode 100644 index 000000000000..5a371b9d307f --- /dev/null +++ b/core/rawdb/accessors_overlay.go @@ -0,0 +1,30 @@ +// Copyright 2024 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package rawdb + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethdb" +) + +func ReadVerkleTransitionState(db ethdb.KeyValueReader, hash common.Hash) ([]byte, error) { + return db.Get(transitionStateKey(hash)) +} + +func WriteVerkleTransitionState(db ethdb.KeyValueWriter, hash common.Hash, state []byte) error { + return db.Put(transitionStateKey(hash), state) +} diff --git a/core/rawdb/schema.go b/core/rawdb/schema.go index 18722ed5d4cb..f7a3515715ae 100644 --- a/core/rawdb/schema.go +++ b/core/rawdb/schema.go @@ -122,6 +122,8 @@ var ( CliqueSnapshotPrefix = []byte("clique-") + VerkleTransitionStatePrefix = []byte("verkle-transition-state-") + preimageCounter = metrics.NewRegisteredCounter("db/preimage/total", nil) preimageHitCounter = metrics.NewRegisteredCounter("db/preimage/hits", nil) ) @@ -250,6 +252,11 @@ func storageTrieNodeKey(accountHash common.Hash, path []byte) []byte { return append(append(trieNodeStoragePrefix, accountHash.Bytes()...), path...) } +// transitionStateKey = transitionStatusKey + hash +func transitionStateKey(hash common.Hash) []byte { + return append(VerkleTransitionStatePrefix, hash.Bytes()...) +} + // IsLegacyTrieNode reports whether a provided database entry is a legacy trie // node. The characteristics of legacy trie node are: // - the key length is 32 bytes diff --git a/core/state/database.go b/core/state/database.go index 075b948cf32e..cca65bb6f993 100644 --- a/core/state/database.go +++ b/core/state/database.go @@ -17,8 +17,12 @@ package state import ( + "bytes" + "encoding/gob" "errors" "fmt" + "runtime/debug" + "sync" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/lru" @@ -102,6 +106,10 @@ type Database interface { SaveTransitionState(common.Hash) LoadTransitionState(common.Hash) + + LockCurrentTransitionState() + + UnLockCurrentTransitionState() } // Trie is a Ethereum Merkle Patricia trie. @@ -189,22 +197,24 @@ func NewDatabase(db ethdb.Database) Database { // large memory cache. func NewDatabaseWithConfig(db ethdb.Database, config *trie.Config) Database { return &cachingDB{ - disk: db, - codeSizeCache: lru.NewCache[common.Hash, int](codeSizeCacheSize), - codeCache: lru.NewSizeConstrainedCache[common.Hash, []byte](codeCacheSize), - triedb: trie.NewDatabaseWithConfig(db, config), - addrToPoint: utils.NewPointCache(), + disk: db, + codeSizeCache: lru.NewCache[common.Hash, int](codeSizeCacheSize), + codeCache: lru.NewSizeConstrainedCache[common.Hash, []byte](codeCacheSize), + triedb: trie.NewDatabaseWithConfig(db, config), + addrToPoint: utils.NewPointCache(), + TransitionStatePerRoot: lru.NewBasicLRU[common.Hash, *TransitionState](100), } } // NewDatabaseWithNodeDB creates a state database with an already initialized node database. func NewDatabaseWithNodeDB(db ethdb.Database, triedb *trie.Database) Database { return &cachingDB{ - disk: db, - codeSizeCache: lru.NewCache[common.Hash, int](codeSizeCacheSize), - codeCache: lru.NewSizeConstrainedCache[common.Hash, []byte](codeCacheSize), - triedb: triedb, - addrToPoint: utils.NewPointCache(), + disk: db, + codeSizeCache: lru.NewCache[common.Hash, int](codeSizeCacheSize), + codeCache: lru.NewSizeConstrainedCache[common.Hash, []byte](codeCacheSize), + triedb: triedb, + addrToPoint: utils.NewPointCache(), + TransitionStatePerRoot: lru.NewBasicLRU[common.Hash, *TransitionState](100), } } @@ -305,12 +315,12 @@ type cachingDB struct { // TODO ensure that this info is in the DB LastMerkleRoot common.Hash // root hash of the read-only base tree CurrentTransitionState *TransitionState - TransitionStatePerRoot map[common.Hash]*TransitionState + TransitionStatePerRoot lru.BasicLRU[common.Hash, *TransitionState] + transitionStateLock sync.Mutex addrToPoint *utils.PointCache baseRoot common.Hash // hash of the read-only base tree - } func (db *cachingDB) openMPTTrie(root common.Hash) (Trie, error) { @@ -543,37 +553,82 @@ func (db *cachingDB) SetLastMerkleRoot(merkleRoot common.Hash) { } func (db *cachingDB) SaveTransitionState(root common.Hash) { - if db.TransitionStatePerRoot == nil { - db.TransitionStatePerRoot = make(map[common.Hash]*TransitionState) - } - + db.transitionStateLock.Lock() + defer db.transitionStateLock.Unlock() if db.CurrentTransitionState != nil { - // Copy so that the address pointer isn't updated after - // it has been saved. - db.TransitionStatePerRoot[root] = db.CurrentTransitionState.Copy() + var buf bytes.Buffer + enc := gob.NewEncoder(&buf) + err := enc.Encode(db.CurrentTransitionState) + if err != nil { + log.Error("failed to encode transition state", "err", err) + return + } + + if !db.TransitionStatePerRoot.Contains(root) { + // Copy so that the address pointer isn't updated after + // it has been saved. + db.TransitionStatePerRoot.Add(root, db.CurrentTransitionState.Copy()) + + rawdb.WriteVerkleTransitionState(db.DiskDB(), root, buf.Bytes()) + } - fmt.Println("saving transition state", "storage processed", db.CurrentTransitionState.StorageProcessed, "addr", db.CurrentTransitionState.CurrentAccountAddress, "slot hash", db.CurrentTransitionState.CurrentSlotHash, "root", root, "ended", db.CurrentTransitionState.ended, "started", db.CurrentTransitionState.started) + log.Debug("saving transition state", "storage processed", db.CurrentTransitionState.StorageProcessed, "addr", db.CurrentTransitionState.CurrentAccountAddress, "slot hash", db.CurrentTransitionState.CurrentSlotHash, "root", root, "ended", db.CurrentTransitionState.ended, "started", db.CurrentTransitionState.started) } } func (db *cachingDB) LoadTransitionState(root common.Hash) { - if db.TransitionStatePerRoot == nil { - db.TransitionStatePerRoot = make(map[common.Hash]*TransitionState) - } + db.transitionStateLock.Lock() + defer db.transitionStateLock.Unlock() + // Try to get the transition state from the cache and + // the DB if it's not there. + ts, ok := db.TransitionStatePerRoot.Get(root) + if !ok { + // Not in the cache, try getting it from the DB + data, err := rawdb.ReadVerkleTransitionState(db.DiskDB(), root) + if err != nil { + log.Error("failed to read transition state", "err", err) + return + } + + // if a state could be read from the db, attempt to decode it + if len(data) > 0 { + var ( + newts TransitionState + buf = bytes.NewBuffer(data[:]) + dec = gob.NewDecoder(buf) + ) + // Decode transition state + err = dec.Decode(&newts) + if err != nil { + log.Error("failed to decode transition state", "err", err) + return + } + ts = &newts + } - // Initialize the first transition state, with the "ended" - // field set to true if the database was created - // as a verkle database. - ts, ok := db.TransitionStatePerRoot[root] - if !ok || ts == nil { - fmt.Println("could not find any transition state, starting with a fresh state", "is verkle", db.triedb.IsVerkle()) - // Start with a fresh state - ts = &TransitionState{ended: false} + // Fallback that should only happen before the transition + if ts == nil { + // Initialize the first transition state, with the "ended" + // field set to true if the database was created + // as a verkle database. + log.Debug("no transition state found, starting fresh", "is verkle", db.triedb.IsVerkle()) + // Start with a fresh state + ts = &TransitionState{ended: db.triedb.IsVerkle()} + } } // Copy so that the CurrentAddress pointer in the map // doesn't get overwritten. db.CurrentTransitionState = ts.Copy() - fmt.Println("loaded transition state", "storage processed", db.CurrentTransitionState.StorageProcessed, "addr", db.CurrentTransitionState.CurrentAccountAddress, "slot hash", db.CurrentTransitionState.CurrentSlotHash, "root", root, "ended", db.CurrentTransitionState.ended, "started", db.CurrentTransitionState.started) + log.Debug("loaded transition state", "storage processed", db.CurrentTransitionState.StorageProcessed, "addr", db.CurrentTransitionState.CurrentAccountAddress, "slot hash", db.CurrentTransitionState.CurrentSlotHash, "root", root, "ended", db.CurrentTransitionState.ended, "started", db.CurrentTransitionState.started) + debug.PrintStack() +} + +func (db *cachingDB) LockCurrentTransitionState() { + db.transitionStateLock.Lock() +} + +func (db *cachingDB) UnLockCurrentTransitionState() { + db.transitionStateLock.Unlock() } diff --git a/core/state_processor.go b/core/state_processor.go index 227608fb21b4..2574120ce45f 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -107,12 +107,6 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg return nil, nil, 0, errors.New("withdrawals before shanghai") } - // Perform the overlay transition, if relevant - //parent := p.bc.GetHeaderByHash(header.ParentHash) - //if err := OverlayVerkleTransition(statedb, parent.Root); err != nil { - // return nil, nil, 0, fmt.Errorf("error performing verkle overlay transition: %w", err) - //} - // Finalize the block, applying any consensus engine specific extras (e.g. block rewards) p.engine.Finalize(p.bc, header, statedb, block.Transactions(), block.Uncles(), withdrawals) diff --git a/light/trie.go b/light/trie.go index df300c8c6ed2..7e7c03bc16c1 100644 --- a/light/trie.go +++ b/light/trie.go @@ -177,6 +177,13 @@ func (db *odrDatabase) LoadTransitionState(common.Hash) { panic("not implemented") // TODO: Implement } +func (db *odrDatabase) LockCurrentTransitionState() { + panic("not implemented") // TODO: Implement +} +func (db *odrDatabase) UnLockCurrentTransitionState() { + panic("not implemented") // TODO: Implement +} + type odrTrie struct { db *odrDatabase id *TrieID From afc364be32732350ee8be7d75eb6e0cb7065ad9a Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Sat, 2 Mar 2024 08:00:37 +0100 Subject: [PATCH 21/22] eth: add debug_conversionStatus RPC call (#392) * eth: add debug_conversionStatus RPC call * add debug trace4s * Apply suggestions from code review * export started/ended fields --- core/state/database.go | 36 ++++++++++++++++++------------------ eth/api_debug.go | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 18 deletions(-) diff --git a/core/state/database.go b/core/state/database.go index cca65bb6f993..5fa7738fb525 100644 --- a/core/state/database.go +++ b/core/state/database.go @@ -219,11 +219,11 @@ func NewDatabaseWithNodeDB(db ethdb.Database, triedb *trie.Database) Database { } func (db *cachingDB) InTransition() bool { - return db.CurrentTransitionState != nil && db.CurrentTransitionState.started && !db.CurrentTransitionState.ended + return db.CurrentTransitionState != nil && db.CurrentTransitionState.Started && !db.CurrentTransitionState.Ended } func (db *cachingDB) Transitioned() bool { - return db.CurrentTransitionState != nil && db.CurrentTransitionState.ended + return db.CurrentTransitionState != nil && db.CurrentTransitionState.Ended } // Fork implements the fork @@ -236,7 +236,7 @@ func (db *cachingDB) StartVerkleTransition(originalRoot, translatedRoot common.H |____| |___| /\___ \___ |____/\___ | __/|___| (____ |___| |__| |___| (____ /_____/ \/\_/ |__|___| /_____//_____/ |__|`) db.CurrentTransitionState = &TransitionState{ - started: true, + Started: true, // initialize so that the first storage-less accounts are processed StorageProcessed: true, } @@ -255,15 +255,15 @@ func (db *cachingDB) ReorgThroughVerkleTransition() { func (db *cachingDB) InitTransitionStatus(started, ended bool) { db.CurrentTransitionState = &TransitionState{ - ended: ended, - started: started, + Ended: ended, + Started: started, // TODO add other fields when we handle mid-transition interrupts } } func (db *cachingDB) EndVerkleTransition() { - if !db.CurrentTransitionState.started { - db.CurrentTransitionState.started = true + if !db.CurrentTransitionState.Started { + db.CurrentTransitionState.Started = true } fmt.Println(` @@ -273,14 +273,14 @@ func (db *cachingDB) EndVerkleTransition() { | | | Y \ ___/ \ ___/| |_\ ___/| |_> | Y \/ __ \| | | | | Y \/ __ \_\___ \ | |__/ __ \| | / /_/ \ ___// /_/ | |____| |___| /\___ \___ |____/\___ | __/|___| (____ |___| |__| |___| (____ /_____/ |____(____ |___| \____ |\___ \____ | |__|`) - db.CurrentTransitionState.ended = true + db.CurrentTransitionState.Ended = true } type TransitionState struct { CurrentAccountAddress *common.Address // addresss of the last translated account CurrentSlotHash common.Hash // hash of the last translated storage slot CurrentPreimageOffset int64 // next byte to read from the preimage file - started, ended bool + Started, Ended bool // Mark whether the storage for an account has been processed. This is useful if the // maximum number of leaves of the conversion is reached before the whole storage is @@ -290,8 +290,8 @@ type TransitionState struct { func (ts *TransitionState) Copy() *TransitionState { ret := &TransitionState{ - started: ts.started, - ended: ts.ended, + Started: ts.Started, + Ended: ts.Ended, CurrentSlotHash: ts.CurrentSlotHash, CurrentPreimageOffset: ts.CurrentPreimageOffset, StorageProcessed: ts.StorageProcessed, @@ -334,14 +334,14 @@ func (db *cachingDB) openMPTTrie(root common.Hash) (Trie, error) { func (db *cachingDB) openVKTrie(root common.Hash) (Trie, error) { payload, err := db.DiskDB().Get(trie.FlatDBVerkleNodeKeyPrefix) if err != nil { - return trie.NewVerkleTrie(verkle.New(), db.triedb, db.addrToPoint, db.CurrentTransitionState.ended), nil + return trie.NewVerkleTrie(verkle.New(), db.triedb, db.addrToPoint, db.CurrentTransitionState.Ended), nil } r, err := verkle.ParseNode(payload, 0) if err != nil { panic(err) } - return trie.NewVerkleTrie(r, db.triedb, db.addrToPoint, db.CurrentTransitionState.ended), err + return trie.NewVerkleTrie(r, db.triedb, db.addrToPoint, db.CurrentTransitionState.Ended), err } // OpenTrie opens the main account trie at a specific root hash. @@ -364,7 +364,7 @@ func (db *cachingDB) OpenTrie(root common.Hash) (Trie, error) { // If the verkle conversion has ended, return a single // verkle trie. - if db.CurrentTransitionState.ended { + if db.CurrentTransitionState.Ended { log.Debug("transition ended, returning a simple verkle tree") return vkt, nil } @@ -572,7 +572,7 @@ func (db *cachingDB) SaveTransitionState(root common.Hash) { rawdb.WriteVerkleTransitionState(db.DiskDB(), root, buf.Bytes()) } - log.Debug("saving transition state", "storage processed", db.CurrentTransitionState.StorageProcessed, "addr", db.CurrentTransitionState.CurrentAccountAddress, "slot hash", db.CurrentTransitionState.CurrentSlotHash, "root", root, "ended", db.CurrentTransitionState.ended, "started", db.CurrentTransitionState.started) + log.Debug("saving transition state", "storage processed", db.CurrentTransitionState.StorageProcessed, "addr", db.CurrentTransitionState.CurrentAccountAddress, "slot hash", db.CurrentTransitionState.CurrentSlotHash, "root", root, "ended", db.CurrentTransitionState.Ended, "started", db.CurrentTransitionState.Started) } } @@ -590,7 +590,7 @@ func (db *cachingDB) LoadTransitionState(root common.Hash) { return } - // if a state could be read from the db, attempt to decode it + // if a state could be read from the db, attempt to decode it if len(data) > 0 { var ( newts TransitionState @@ -613,7 +613,7 @@ func (db *cachingDB) LoadTransitionState(root common.Hash) { // as a verkle database. log.Debug("no transition state found, starting fresh", "is verkle", db.triedb.IsVerkle()) // Start with a fresh state - ts = &TransitionState{ended: db.triedb.IsVerkle()} + ts = &TransitionState{Ended: db.triedb.IsVerkle()} } } @@ -621,7 +621,7 @@ func (db *cachingDB) LoadTransitionState(root common.Hash) { // doesn't get overwritten. db.CurrentTransitionState = ts.Copy() - log.Debug("loaded transition state", "storage processed", db.CurrentTransitionState.StorageProcessed, "addr", db.CurrentTransitionState.CurrentAccountAddress, "slot hash", db.CurrentTransitionState.CurrentSlotHash, "root", root, "ended", db.CurrentTransitionState.ended, "started", db.CurrentTransitionState.started) + log.Debug("loaded transition state", "storage processed", db.CurrentTransitionState.StorageProcessed, "addr", db.CurrentTransitionState.CurrentAccountAddress, "slot hash", db.CurrentTransitionState.CurrentSlotHash, "root", root, "ended", db.CurrentTransitionState.Ended, "started", db.CurrentTransitionState.Started) debug.PrintStack() } diff --git a/eth/api_debug.go b/eth/api_debug.go index 9cfa9103fb58..3e0daac1b5b0 100644 --- a/eth/api_debug.go +++ b/eth/api_debug.go @@ -17,7 +17,9 @@ package eth import ( + "bytes" "context" + "encoding/gob" "errors" "fmt" "time" @@ -432,3 +434,41 @@ func (api *DebugAPI) SetTrieFlushInterval(interval string) error { func (api *DebugAPI) GetTrieFlushInterval() string { return api.eth.blockchain.GetTrieFlushInterval().String() } + +type ConversionStatusResult struct { + Started bool `json:"started"` + Ended bool `json:"ended"` +} + +func (api *DebugAPI) ConversionStatus(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*ConversionStatusResult, error) { + block, err := api.eth.APIBackend.BlockByNumberOrHash(ctx, blockNrOrHash) + if err != nil { + return nil, err + } + data, err := rawdb.ReadVerkleTransitionState(api.eth.ChainDb(), block.Root()) + if err != nil { + if err.Error() == "pebble: not found" { + return &ConversionStatusResult{}, nil + } + return nil, err + } + log.Info("found entry", "data", data) + if len(data) == 0 { + log.Info("found no data") + // started and ended will be false as no conversion has started + return &ConversionStatusResult{}, nil + } + + var ( + ts state.TransitionState + buf = bytes.NewBuffer(data[:]) + dec = gob.NewDecoder(buf) + ) + // Decode transition state + err = dec.Decode(&ts) + if err != nil { + return nil, fmt.Errorf("failed to decode transition state, err=%v", err) + } + + return &ConversionStatusResult{Started: ts.Started, Ended: ts.Ended}, nil +} From bb3a39deb11fcdcb1d4023e3f82946c22f8d5399 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Wed, 13 Mar 2024 17:46:43 +0100 Subject: [PATCH 22/22] fix post-verge sync (#404) * fix post-verge sync * review: fix truncated comment --- core/blockchain.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/core/blockchain.go b/core/blockchain.go index 36e58ca8df57..9143fd6c699d 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -1768,6 +1768,11 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) if !bc.stateCache.InTransition() && !bc.stateCache.Transitioned() { bc.stateCache.StartVerkleTransition(parent.Root, emptyVerkleRoot, bc.Config(), bc.Config().PragueTime, parent.Root) } + } else { + // If the verkle activation time hasn't started, declare it as "not started". + // This is so that if the miner activates the conversion, the insertion happens + // in the correct mode. + bc.stateCache.InitTransitionStatus(false, false) } if parent.Number.Uint64() == conversionBlock { bc.StartVerkleTransition(parent.Root, emptyVerkleRoot, bc.Config(), &parent.Time, parent.Root)