diff --git a/consensus/beacon/consensus.go b/consensus/beacon/consensus.go index 2651ac55788f..de30f4e9e5e3 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" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/params" @@ -341,10 +342,7 @@ func (beacon *Beacon) Prepare(chain consensus.ChainHeaderReader, header *types.H // Trigger the start of the verkle conversion if we're at the right block if chain.Config().IsPrague(header.Number, header.Time) { - parent := chain.GetHeaderByNumber(header.Number.Uint64() - 1) - if !chain.Config().IsPrague(parent.Number, parent.Time) { - statedb.Database().StartVerkleTransition(common.Hash{}, common.Hash{}, chain.Config(), nil) - } + core.VerkleTransition(statedb) } return nil @@ -409,8 +407,21 @@ func (beacon *Beacon) FinalizeAndAssemble(chain consensus.ChainHeaderReader, hea return nil, fmt.Errorf("error opening pre-state tree root: %w", err) } - vtrpre, okpre := preTrie.(*trie.VerkleTrie) - vtrpost, okpost := state.GetTrie().(*trie.VerkleTrie) + var okpre, okpost bool + var vtrpre, vtrpost *trie.VerkleTrie + switch pre := preTrie.(type) { + case *trie.VerkleTrie: + vtrpre, okpre = preTrie.(*trie.VerkleTrie) + vtrpost, okpost = state.GetTrie().(*trie.VerkleTrie) + case *trie.TransitionTrie: + vtrpre = pre.Overlay() + okpre = true + post, _ := state.GetTrie().(*trie.TransitionTrie) + vtrpost = post.Overlay() + okpost = true + default: + panic("invalid tree type") + } if okpre && okpost { // Resolve values from the pre state, the post // state should already have the values in memory. diff --git a/core/state/database.go b/core/state/database.go index 766a1e8b929f..78e49ff9a55d 100644 --- a/core/state/database.go +++ b/core/state/database.go @@ -219,7 +219,7 @@ func (db *cachingDB) StartVerkleTransition(originalRoot, translatedRoot common.H |____| |___| /\___ \___ |____/\___ | __/|___| (____ |___| |__| |___| (____ /_____/ \/\_/ |__|___| /_____//_____/ |__|`) db.started = true - db.AddTranslation(originalRoot, translatedRoot) + // db.AddTranslation(originalRoot, translatedRoot) db.baseRoot = originalRoot // initialize so that the first storage-less accounts are processed db.StorageProcessed = true @@ -243,25 +243,6 @@ func (db *cachingDB) EndVerkleTransition() { db.ended = true } -func (db *cachingDB) AddTranslation(orig, trans common.Hash) { - // TODO make this persistent - db.translatedRootsLock.Lock() - defer db.translatedRootsLock.Unlock() - db.translatedRoots[db.translationIndex] = trans - db.origRoots[db.translationIndex] = orig - db.translationIndex = (db.translationIndex + 1) % len(db.translatedRoots) -} - -func (db *cachingDB) getTranslation(orig common.Hash) common.Hash { - db.translatedRootsLock.RLock() - defer db.translatedRootsLock.RUnlock() - for i, o := range db.origRoots { - if o == orig { - return db.translatedRoots[i] - } - } - return common.Hash{} -} type cachingDB struct { disk ethdb.KeyValueStore @@ -322,14 +303,8 @@ 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 { - var r common.Hash // NOTE this is a kaustinen-only change, it will break replay - // if db.ended { - r = root - // } else { - // r = db.getTranslation(root) - // } - vkt, err := db.openVKTrie(r) + vkt, err := db.openVKTrie(root) if err != nil { return nil, err } diff --git a/core/state/statedb.go b/core/state/statedb.go index 3b0135f9e9d5..5bb0c5e14d0b 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -191,12 +191,6 @@ func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error) } if sdb.snaps != nil { if sdb.snap = sdb.snaps.Snapshot(root); sdb.snap == nil { - if db, ok := db.(*cachingDB); ok { - trans := db.getTranslation(root) - if trans != (common.Hash{}) { - sdb.snap = sdb.snaps.Snapshot(trans) - } - } } } return sdb, nil diff --git a/core/state_processor.go b/core/state_processor.go index 0e177b8d038a..8b07511803aa 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -22,22 +22,20 @@ import ( "encoding/binary" "errors" "fmt" - // "io" "math/big" - // "os" - // "time" + "time" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus/misc" - // "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" - // "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/trie" tutils "github.com/ethereum/go-ethereum/trie/utils" "github.com/gballet/go-verkle" @@ -63,6 +61,207 @@ func NewStateProcessor(config *params.ChainConfig, bc *BlockChain, engine consen } } +func VerkleTransition(statedb *state.StateDB) error { + // Overlay tree migration logic + migrdb := statedb.Database() + + // verkle transition: if the conversion process is in progress, move + // N values from the MPT into the verkle tree. + if migrdb.InTransition() { + var ( + now = time.Now() + tt = statedb.GetTrie().(*trie.TransitionTrie) + mpt = tt.Base() + vkt = tt.Overlay() + // hasPreimagesBin = false + // preimageSeek = migrdb.GetCurrentPreimageOffset() + // fpreimages *bufio.Reader + ) + + // // TODO: avoid opening the preimages file here and make it part of, potentially, statedb.Database(). + // filePreimages, err := os.Open("preimages.bin") + // if err != nil { + // // fallback on reading the db + // log.Warn("opening preimage file", "error", err) + // } else { + // defer filePreimages.Close() + // if _, err := filePreimages.Seek(preimageSeek, io.SeekStart); err != nil { + // return nil, nil, 0, fmt.Errorf("seeking preimage file: %s", err) + // } + // fpreimages = bufio.NewReader(filePreimages) + // hasPreimagesBin = true + // } + + accIt, err := statedb.Snaps().AccountIterator(mpt.Hash(), migrdb.GetCurrentAccountHash()) + if err != nil { + return fmt.Errorf("error getting account iterator: %w", err) + } + defer accIt.Release() + 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 { + var addr common.Address + // if hasPreimagesBin { + // if _, err := io.ReadFull(fpreimages, addr[:]); err != nil { + // return nil, nil, 0, fmt.Errorf("reading preimage file: %s", err) + // } + // } else { + addr = common.BytesToAddress(rawdb.ReadPreimage(migrdb.DiskDB(), accIt.Hash())) + if len(addr) != 20 { + return fmt.Errorf("addr len is zero is not 32: %d", len(addr)) + } + // } + 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)) + } + + 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 := &keyValueMigrator{vktLeafData: make(map[string]*verkle.BatchNewLeafNodeData)} + // move maxCount accounts into the verkle tree, starting with the + // slots from the previous account. + count := 0 + + // if less than maxCount slots were moved, move to the next account + for count < maxMovedCount { + acc, err := types.FullAccount(accIt.Account()) + if err != nil { + log.Error("Invalid account encountered during traversal", "error", err) + return fmt.Errorf("error deserializing account: %w", err) + } + 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 + // that it opens the door to a situation in which the storage isn't + // converted, but it can not be found since the account was and so + // there is no way to find the MPT storage from the information found + // in the verkle account. + // Note that this issue can still occur if the account gets written + // 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()) + if err != nil { + return fmt.Errorf("error getting storage iterator: %w", err) + } + stIt.Next() + + // fdb.StorageProcessed will be initialized to `true` if the + // entire storage for an account was not entirely processed + // by the previous block. This is used as a signal to resume + // 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++ { + var ( + value []byte // slot value after RLP decoding + safeValue [32]byte // 32-byte aligned value + ) + if err := rlp.DecodeBytes(stIt.Slot(), &value); err != nil { + return fmt.Errorf("error decoding snapshot slot bytes %x: %w", stIt.Slot(), err) + } + copy(safeValue[32-len(value):], value) + + var slotnr []byte + // if hasPreimagesBin { + // var s [32]byte + // slotnr = s[:] + // if _, err := io.ReadFull(fpreimages, slotnr); err != nil { + // return nil, nil, 0, fmt.Errorf("reading preimage file: %s", err) + // } + // } else { + slotnr = rawdb.ReadPreimage(migrdb.DiskDB(), stIt.Hash()) + if len(slotnr) != 32 { + return fmt.Errorf("slotnr len is zero is not 32: %d", len(slotnr)) + } + // } + if crypto.Keccak256Hash(slotnr[:]) != stIt.Hash() { + return fmt.Errorf("preimage file does not match storage hash: %s!=%s", crypto.Keccak256Hash(slotnr), stIt.Hash()) + } + // preimageSeek += int64(len(slotnr)) + + mkv.addStorageSlot(migrdb.GetCurrentAccountAddress().Bytes(), slotnr, safeValue[:]) + + // advance the storage iterator + migrdb.SetStorageProcessed(!stIt.Next()) + if !migrdb.GetStorageProcessed() { + migrdb.SetCurrentSlotHash(stIt.Hash()) + } + } + stIt.Release() + } + + // If the maximum number of leaves hasn't been reached, then + // it means that the storage has finished processing (or none + // was available for this account) and that the account itself + // can be processed. + if count < maxMovedCount { + count++ // count increase for the account itself + + 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().Bytes(), uint64(len(code)), chunks) + } + + // reset storage iterator marker for next account + migrdb.SetStorageProcessed(false) + migrdb.SetCurrentSlotHash(common.Hash{}) + + // Move to the next account, if available - or end + // the transition otherwise. + if accIt.Next() { + var addr common.Address + // if hasPreimagesBin { + // if _, err := io.ReadFull(fpreimages, addr[:]); err != nil { + // return nil, nil, 0, fmt.Errorf("reading preimage file: %s", err) + // } + // } else { + addr = common.BytesToAddress(rawdb.ReadPreimage(migrdb.DiskDB(), accIt.Hash())) + if len(addr) != 20 { + 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()) + } + // preimageSeek += int64(len(addr)) + migrdb.SetCurrentAccountAddress(addr) + } else { + // case when the account iterator has + // reached the end but count < maxCount + migrdb.EndVerkleTransition() + break + } + } + } + // migrdb.SetCurrentPreimageOffset(preimageSeek) + + log.Info("Collected and prepared key values from base tree", "count", count, "duration", time.Since(now), "last account", statedb.Database().GetCurrentAccountHash()) + + now = time.Now() + if err := mkv.migrateCollectedKeyValues(tt.Overlay()); err != nil { + return fmt.Errorf("could not migrate key values: %w", err) + } + log.Info("Inserted key values in overlay tree", "count", count, "duration", time.Since(now)) + } + + return nil +} + // Process processes the state changes according to the Ethereum rules by running // the transaction messages using the statedb and applying any rewards to both // the processor (coinbase) and any included uncles. @@ -109,202 +308,10 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg return nil, nil, 0, errors.New("withdrawals before shanghai") } - // Overlay tree migration logic - // migrdb := statedb.Database() - - // verkle transition: if the conversion process is in progress, move - // N values from the MPT into the verkle tree. - // if migrdb.InTransition() { - // var ( - // now = time.Now() - // tt = statedb.GetTrie().(*trie.TransitionTrie) - // mpt = tt.Base() - // vkt = tt.Overlay() - // hasPreimagesBin = false - // preimageSeek = migrdb.GetCurrentPreimageOffset() - // fpreimages *bufio.Reader - // ) - - // // TODO: avoid opening the preimages file here and make it part of, potentially, statedb.Database(). - // filePreimages, err := os.Open("preimages.bin") - // if err != nil { - // // fallback on reading the db - // log.Warn("opening preimage file", "error", err) - // } else { - // defer filePreimages.Close() - // if _, err := filePreimages.Seek(preimageSeek, io.SeekStart); err != nil { - // return nil, nil, 0, fmt.Errorf("seeking preimage file: %s", err) - // } - // fpreimages = bufio.NewReader(filePreimages) - // hasPreimagesBin = true - // } - - // accIt, err := statedb.Snaps().AccountIterator(mpt.Hash(), migrdb.GetCurrentAccountHash()) - // if err != nil { - // return nil, nil, 0, err - // } - // defer accIt.Release() - // 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 { - // var addr common.Address - // if hasPreimagesBin { - // if _, err := io.ReadFull(fpreimages, addr[:]); err != nil { - // return nil, nil, 0, fmt.Errorf("reading preimage file: %s", err) - // } - // } else { - // addr = common.BytesToAddress(rawdb.ReadPreimage(migrdb.DiskDB(), accIt.Hash())) - // if len(addr) != 20 { - // return nil, nil, 0, fmt.Errorf("addr len is zero is not 32: %d", len(addr)) - // } - // } - // migrdb.SetCurrentAccountAddress(addr) - // if migrdb.GetCurrentAccountHash() != accIt.Hash() { - // return nil, nil, 0, fmt.Errorf("preimage file does not match account hash: %s != %s", crypto.Keccak256Hash(addr[:]), accIt.Hash()) - // } - // 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 := &keyValueMigrator{vktLeafData: make(map[string]*verkle.BatchNewLeafNodeData)} - // // move maxCount accounts into the verkle tree, starting with the - // // slots from the previous account. - // count := 0 - - // // if less than maxCount slots were moved, move to the next account - // for count < maxMovedCount { - // acc, err := types.FullAccount(accIt.Account()) - // if err != nil { - // log.Error("Invalid account encountered during traversal", "error", err) - // return nil, nil, 0, err - // } - // 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 - // // that it opens the door to a situation in which the storage isn't - // // converted, but it can not be found since the account was and so - // // there is no way to find the MPT storage from the information found - // // in the verkle account. - // // Note that this issue can still occur if the account gets written - // // 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()) - // if err != nil { - // return nil, nil, 0, err - // } - // stIt.Next() - - // // fdb.StorageProcessed will be initialized to `true` if the - // // entire storage for an account was not entirely processed - // // by the previous block. This is used as a signal to resume - // // 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++ { - // var ( - // value []byte // slot value after RLP decoding - // safeValue [32]byte // 32-byte aligned value - // ) - // if err := rlp.DecodeBytes(stIt.Slot(), &value); err != nil { - // return nil, nil, 0, fmt.Errorf("error decoding bytes %x: %w", stIt.Slot(), err) - // } - // copy(safeValue[32-len(value):], value) - - // var slotnr []byte - // if hasPreimagesBin { - // var s [32]byte - // slotnr = s[:] - // if _, err := io.ReadFull(fpreimages, slotnr); err != nil { - // return nil, nil, 0, fmt.Errorf("reading preimage file: %s", err) - // } - // } else { - // slotnr = rawdb.ReadPreimage(migrdb.DiskDB(), stIt.Hash()) - // if len(slotnr) != 32 { - // return nil, nil, 0, fmt.Errorf("slotnr len is zero is not 32: %d", len(slotnr)) - // } - // } - // if crypto.Keccak256Hash(slotnr[:]) != stIt.Hash() { - // return nil, nil, 0, fmt.Errorf("preimage file does not match storage hash: %s!=%s", crypto.Keccak256Hash(slotnr), stIt.Hash()) - // } - // preimageSeek += int64(len(slotnr)) - - // mkv.addStorageSlot(migrdb.GetCurrentAccountAddress().Bytes(), slotnr, safeValue[:]) - - // // advance the storage iterator - // migrdb.SetStorageProcessed(!stIt.Next()) - // if !migrdb.GetStorageProcessed() { - // migrdb.SetCurrentSlotHash(stIt.Hash()) - // } - // } - // stIt.Release() - // } - - // // If the maximum number of leaves hasn't been reached, then - // // it means that the storage has finished processing (or none - // // was available for this account) and that the account itself - // // can be processed. - // if count < maxMovedCount { - // count++ // count increase for the account itself - - // 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().Bytes(), uint64(len(code)), chunks) - // } - - // // reset storage iterator marker for next account - // migrdb.SetStorageProcessed(false) - // migrdb.SetCurrentSlotHash(common.Hash{}) - - // // Move to the next account, if available - or end - // // the transition otherwise. - // if accIt.Next() { - // var addr common.Address - // if hasPreimagesBin { - // if _, err := io.ReadFull(fpreimages, addr[:]); err != nil { - // return nil, nil, 0, fmt.Errorf("reading preimage file: %s", err) - // } - // } else { - // addr = common.BytesToAddress(rawdb.ReadPreimage(migrdb.DiskDB(), accIt.Hash())) - // if len(addr) != 20 { - // return nil, nil, 0, 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 nil, nil, 0, fmt.Errorf("preimage file does not match account hash: %s != %s", crypto.Keccak256Hash(addr[:]), accIt.Hash()) - // } - // preimageSeek += int64(len(addr)) - // migrdb.SetCurrentAccountAddress(addr) - // } else { - // // case when the account iterator has - // // reached the end but count < maxCount - // migrdb.EndVerkleTransition() - // break - // } - // } - // } - // migrdb.SetCurrentPreimageOffset(preimageSeek) - - // log.Info("Collected and prepared key values from base tree", "count", count, "duration", time.Since(now), "last account", statedb.Database().GetCurrentAccountHash()) - - // now = time.Now() - // if err := mkv.migrateCollectedKeyValues(tt.Overlay()); err != nil { - // return nil, nil, 0, fmt.Errorf("could not migrate key values: %w", err) - // } - // log.Info("Inserted key values in overlay tree", "count", count, "duration", time.Since(now)) - // } + err := VerkleTransition(statedb) + if err != nil { + return nil, nil, 0, fmt.Errorf("error performing 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/miner/worker.go b/miner/worker.go index 341e9892862d..f1759e9ad7ee 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -889,6 +889,15 @@ func (w *worker) prepareWork(genParams *generateParams) (*environment, error) { header.GasLimit = core.CalcGasLimit(parentGasLimit, w.config.GasCeil) } } + + // Trigger the start of the verkle conversion if we're at the right block + 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) + } + } + // Retrieve the parent state to execute on top and start a prefetcher for // the miner to speed block sealing up a bit. state, err := w.chain.StateAt(parent.Root)