Skip to content

Commit ab2cef3

Browse files
committed
persist conversion state to db and use an LRU cache for active transition states (#375)
* 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
1 parent a58d087 commit ab2cef3

File tree

8 files changed

+212
-41
lines changed

8 files changed

+212
-41
lines changed

.github/workflows/conversion.yml

+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
name: Overlay conversion
2+
3+
on:
4+
push:
5+
branches: [ master, transition-post-genesis, store-transition-state-in-db ]
6+
pull_request:
7+
branches: [ master, kaustinen-with-shapella, transition-post-genesis, store-transition-state-in-db, lock-overlay-transition ]
8+
workflow_dispatch:
9+
10+
jobs:
11+
build:
12+
runs-on: self-hosted
13+
steps:
14+
- uses: actions/checkout@v2
15+
- name: Set up Go
16+
uses: actions/setup-go@v2
17+
with:
18+
go-version: 1.21.1
19+
20+
- name: Cleanup from previous runs
21+
run: |
22+
rm -f log.txt
23+
rm -rf .shadowfork
24+
rm -f genesis.json
25+
26+
- name: Download genesis file
27+
run: wget https://gist.githubusercontent.com/gballet/0b02a025428aa0e7b67941864d54716c/raw/bfb4e158bca5217b356a19b2ec55c4a45a7b2bad/genesis.json
28+
29+
- name: Init data
30+
run: go run ./cmd/geth --dev --cache.preimages init genesis.json
31+
32+
- name: Run geth in devmode
33+
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 &
34+
35+
- name: Wait for the transition to start
36+
run: |
37+
start_time=$(date +%s)
38+
while true; do
39+
sleep 5
40+
current_time=$(date +%s)
41+
elapsed_time=$((current_time - start_time))
42+
43+
# 2 minute timeout
44+
if [ $elapsed_time -ge 120 ]; then
45+
kill -9 $(pgrep -f geth)
46+
exit 1
47+
fi
48+
49+
# Check for signs that the conversion has started
50+
if grep -q "Processing verkle conversion starting at" log.txt; then
51+
break
52+
fi
53+
done
54+
55+
- name: Wait for the transition to end
56+
run: |
57+
start_time=$(date +%s)
58+
while true; do
59+
sleep 5
60+
current_time=$(date +%s)
61+
elapsed_time=$((current_time - start_time))
62+
63+
# 10 minute timeout
64+
if [ $elapsed_time -ge 300 ]; then
65+
cat log.txt
66+
kill -9 $(pgrep -f geth)
67+
exit 1
68+
fi
69+
70+
# Check for signs that the conversion has started
71+
if egrep -q "at block.*performing transition\? false" log.txt; then
72+
kill -9 $(pgrep -f geth)
73+
break
74+
fi
75+
done

consensus/beacon/consensus.go

+2
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,8 @@ func (beacon *Beacon) FinalizeAndAssemble(chain consensus.ChainHeaderReader, hea
406406
return nil, fmt.Errorf("nil parent header for block %d", header.Number)
407407
}
408408

409+
// Load transition state at beginning of block, because
410+
// OpenTrie needs to know what the conversion status is.
409411
state.Database().LoadTransitionState(parent.Root)
410412

411413
if chain.Config().ProofInBlocks {

core/overlay/conversion.go

+4-3
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,8 @@ func (kvm *keyValueMigrator) migrateCollectedKeyValues(tree *trie.VerkleTrie) er
221221
// OverlayVerkleTransition contains the overlay conversion logic
222222
func OverlayVerkleTransition(statedb *state.StateDB, root common.Hash, maxMovedCount uint64) error {
223223
migrdb := statedb.Database()
224+
migrdb.LockCurrentTransitionState()
225+
defer migrdb.UnLockCurrentTransitionState()
224226

225227
// verkle transition: if the conversion process is in progress, move
226228
// N values from the MPT into the verkle tree.
@@ -289,7 +291,7 @@ func OverlayVerkleTransition(statedb *state.StateDB, root common.Hash, maxMovedC
289291
for count < maxMovedCount {
290292
acc, err := types.FullAccount(accIt.Account())
291293
if err != nil {
292-
log.Error("Invalid account encountered during traversal", "error", err)
294+
fmt.Println("Invalid account encountered during traversal", "error", err)
293295
return err
294296
}
295297
vkt.SetStorageRootConversion(*migrdb.GetCurrentAccountAddress(), acc.Root)
@@ -399,7 +401,6 @@ func OverlayVerkleTransition(statedb *state.StateDB, root common.Hash, maxMovedC
399401
return fmt.Errorf("account address len is zero is not 20: %d", len(addr))
400402
}
401403
}
402-
// fmt.Printf("account switch: %s != %s\n", crypto.Keccak256Hash(addr[:]), accIt.Hash())
403404
if crypto.Keccak256Hash(addr[:]) != accIt.Hash() {
404405
return fmt.Errorf("preimage file does not match account hash: %s != %s", crypto.Keccak256Hash(addr[:]), accIt.Hash())
405406
}
@@ -416,7 +417,7 @@ func OverlayVerkleTransition(statedb *state.StateDB, root common.Hash, maxMovedC
416417
}
417418
migrdb.SetCurrentPreimageOffset(preimageSeek)
418419

419-
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())
420+
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())
420421

421422
// Take all the collected key-values and prepare the new leaf values.
422423
// This fires a background routine that will start doing the work that

core/rawdb/accessors_overlay.go

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Copyright 2024 The go-ethereum Authors
2+
// This file is part of the go-ethereum library.
3+
//
4+
// The go-ethereum library is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU Lesser General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// The go-ethereum library is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU Lesser General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU Lesser General Public License
15+
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
16+
17+
package rawdb
18+
19+
import (
20+
"github.com/ethereum/go-ethereum/common"
21+
"github.com/ethereum/go-ethereum/ethdb"
22+
)
23+
24+
func ReadVerkleTransitionState(db ethdb.KeyValueReader, hash common.Hash) ([]byte, error) {
25+
return db.Get(transitionStateKey(hash))
26+
}
27+
28+
func WriteVerkleTransitionState(db ethdb.KeyValueWriter, hash common.Hash, state []byte) error {
29+
return db.Put(transitionStateKey(hash), state)
30+
}

core/rawdb/schema.go

+7
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,8 @@ var (
122122

123123
CliqueSnapshotPrefix = []byte("clique-")
124124

125+
VerkleTransitionStatePrefix = []byte("verkle-transition-state-")
126+
125127
preimageCounter = metrics.NewRegisteredCounter("db/preimage/total", nil)
126128
preimageHitCounter = metrics.NewRegisteredCounter("db/preimage/hits", nil)
127129
)
@@ -250,6 +252,11 @@ func storageTrieNodeKey(accountHash common.Hash, path []byte) []byte {
250252
return append(append(trieNodeStoragePrefix, accountHash.Bytes()...), path...)
251253
}
252254

255+
// transitionStateKey = transitionStatusKey + hash
256+
func transitionStateKey(hash common.Hash) []byte {
257+
return append(VerkleTransitionStatePrefix, hash.Bytes()...)
258+
}
259+
253260
// IsLegacyTrieNode reports whether a provided database entry is a legacy trie
254261
// node. The characteristics of legacy trie node are:
255262
// - the key length is 32 bytes

core/state/database.go

+87-32
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,12 @@
1717
package state
1818

1919
import (
20+
"bytes"
21+
"encoding/gob"
2022
"errors"
2123
"fmt"
24+
"runtime/debug"
25+
"sync"
2226

2327
"github.com/ethereum/go-ethereum/common"
2428
"github.com/ethereum/go-ethereum/common/lru"
@@ -102,6 +106,10 @@ type Database interface {
102106
SaveTransitionState(common.Hash)
103107

104108
LoadTransitionState(common.Hash)
109+
110+
LockCurrentTransitionState()
111+
112+
UnLockCurrentTransitionState()
105113
}
106114

107115
// Trie is a Ethereum Merkle Patricia trie.
@@ -189,22 +197,24 @@ func NewDatabase(db ethdb.Database) Database {
189197
// large memory cache.
190198
func NewDatabaseWithConfig(db ethdb.Database, config *trie.Config) Database {
191199
return &cachingDB{
192-
disk: db,
193-
codeSizeCache: lru.NewCache[common.Hash, int](codeSizeCacheSize),
194-
codeCache: lru.NewSizeConstrainedCache[common.Hash, []byte](codeCacheSize),
195-
triedb: trie.NewDatabaseWithConfig(db, config),
196-
addrToPoint: utils.NewPointCache(),
200+
disk: db,
201+
codeSizeCache: lru.NewCache[common.Hash, int](codeSizeCacheSize),
202+
codeCache: lru.NewSizeConstrainedCache[common.Hash, []byte](codeCacheSize),
203+
triedb: trie.NewDatabaseWithConfig(db, config),
204+
addrToPoint: utils.NewPointCache(),
205+
TransitionStatePerRoot: lru.NewBasicLRU[common.Hash, *TransitionState](100),
197206
}
198207
}
199208

200209
// NewDatabaseWithNodeDB creates a state database with an already initialized node database.
201210
func NewDatabaseWithNodeDB(db ethdb.Database, triedb *trie.Database) Database {
202211
return &cachingDB{
203-
disk: db,
204-
codeSizeCache: lru.NewCache[common.Hash, int](codeSizeCacheSize),
205-
codeCache: lru.NewSizeConstrainedCache[common.Hash, []byte](codeCacheSize),
206-
triedb: triedb,
207-
addrToPoint: utils.NewPointCache(),
212+
disk: db,
213+
codeSizeCache: lru.NewCache[common.Hash, int](codeSizeCacheSize),
214+
codeCache: lru.NewSizeConstrainedCache[common.Hash, []byte](codeCacheSize),
215+
triedb: triedb,
216+
addrToPoint: utils.NewPointCache(),
217+
TransitionStatePerRoot: lru.NewBasicLRU[common.Hash, *TransitionState](100),
208218
}
209219
}
210220

@@ -305,12 +315,12 @@ type cachingDB struct {
305315
// TODO ensure that this info is in the DB
306316
LastMerkleRoot common.Hash // root hash of the read-only base tree
307317
CurrentTransitionState *TransitionState
308-
TransitionStatePerRoot map[common.Hash]*TransitionState
318+
TransitionStatePerRoot lru.BasicLRU[common.Hash, *TransitionState]
319+
transitionStateLock sync.Mutex
309320

310321
addrToPoint *utils.PointCache
311322

312323
baseRoot common.Hash // hash of the read-only base tree
313-
314324
}
315325

316326
func (db *cachingDB) openMPTTrie(root common.Hash) (Trie, error) {
@@ -543,37 +553,82 @@ func (db *cachingDB) SetLastMerkleRoot(merkleRoot common.Hash) {
543553
}
544554

545555
func (db *cachingDB) SaveTransitionState(root common.Hash) {
546-
if db.TransitionStatePerRoot == nil {
547-
db.TransitionStatePerRoot = make(map[common.Hash]*TransitionState)
548-
}
549-
556+
db.transitionStateLock.Lock()
557+
defer db.transitionStateLock.Unlock()
550558
if db.CurrentTransitionState != nil {
551-
// Copy so that the address pointer isn't updated after
552-
// it has been saved.
553-
db.TransitionStatePerRoot[root] = db.CurrentTransitionState.Copy()
559+
var buf bytes.Buffer
560+
enc := gob.NewEncoder(&buf)
561+
err := enc.Encode(db.CurrentTransitionState)
562+
if err != nil {
563+
log.Error("failed to encode transition state", "err", err)
564+
return
565+
}
566+
567+
if !db.TransitionStatePerRoot.Contains(root) {
568+
// Copy so that the address pointer isn't updated after
569+
// it has been saved.
570+
db.TransitionStatePerRoot.Add(root, db.CurrentTransitionState.Copy())
571+
572+
rawdb.WriteVerkleTransitionState(db.DiskDB(), root, buf.Bytes())
573+
}
554574

555-
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)
575+
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)
556576
}
557577
}
558578

559579
func (db *cachingDB) LoadTransitionState(root common.Hash) {
560-
if db.TransitionStatePerRoot == nil {
561-
db.TransitionStatePerRoot = make(map[common.Hash]*TransitionState)
562-
}
580+
db.transitionStateLock.Lock()
581+
defer db.transitionStateLock.Unlock()
582+
// Try to get the transition state from the cache and
583+
// the DB if it's not there.
584+
ts, ok := db.TransitionStatePerRoot.Get(root)
585+
if !ok {
586+
// Not in the cache, try getting it from the DB
587+
data, err := rawdb.ReadVerkleTransitionState(db.DiskDB(), root)
588+
if err != nil {
589+
log.Error("failed to read transition state", "err", err)
590+
return
591+
}
592+
593+
// if a state could be read from the db, attempt to decode it
594+
if len(data) > 0 {
595+
var (
596+
newts TransitionState
597+
buf = bytes.NewBuffer(data[:])
598+
dec = gob.NewDecoder(buf)
599+
)
600+
// Decode transition state
601+
err = dec.Decode(&newts)
602+
if err != nil {
603+
log.Error("failed to decode transition state", "err", err)
604+
return
605+
}
606+
ts = &newts
607+
}
563608

564-
// Initialize the first transition state, with the "ended"
565-
// field set to true if the database was created
566-
// as a verkle database.
567-
ts, ok := db.TransitionStatePerRoot[root]
568-
if !ok || ts == nil {
569-
fmt.Println("could not find any transition state, starting with a fresh state", "is verkle", db.triedb.IsVerkle())
570-
// Start with a fresh state
571-
ts = &TransitionState{ended: false}
609+
// Fallback that should only happen before the transition
610+
if ts == nil {
611+
// Initialize the first transition state, with the "ended"
612+
// field set to true if the database was created
613+
// as a verkle database.
614+
log.Debug("no transition state found, starting fresh", "is verkle", db.triedb.IsVerkle())
615+
// Start with a fresh state
616+
ts = &TransitionState{ended: db.triedb.IsVerkle()}
617+
}
572618
}
573619

574620
// Copy so that the CurrentAddress pointer in the map
575621
// doesn't get overwritten.
576622
db.CurrentTransitionState = ts.Copy()
577623

578-
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)
624+
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)
625+
debug.PrintStack()
626+
}
627+
628+
func (db *cachingDB) LockCurrentTransitionState() {
629+
db.transitionStateLock.Lock()
630+
}
631+
632+
func (db *cachingDB) UnLockCurrentTransitionState() {
633+
db.transitionStateLock.Unlock()
579634
}

core/state_processor.go

-6
Original file line numberDiff line numberDiff line change
@@ -107,12 +107,6 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
107107
return nil, nil, 0, errors.New("withdrawals before shanghai")
108108
}
109109

110-
// Perform the overlay transition, if relevant
111-
//parent := p.bc.GetHeaderByHash(header.ParentHash)
112-
//if err := OverlayVerkleTransition(statedb, parent.Root); err != nil {
113-
// return nil, nil, 0, fmt.Errorf("error performing verkle overlay transition: %w", err)
114-
//}
115-
116110
// Finalize the block, applying any consensus engine specific extras (e.g. block rewards)
117111
p.engine.Finalize(p.bc, header, statedb, block.Transactions(), block.Uncles(), withdrawals)
118112

light/trie.go

+7
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,13 @@ func (db *odrDatabase) LoadTransitionState(common.Hash) {
177177
panic("not implemented") // TODO: Implement
178178
}
179179

180+
func (db *odrDatabase) LockCurrentTransitionState() {
181+
panic("not implemented") // TODO: Implement
182+
}
183+
func (db *odrDatabase) UnLockCurrentTransitionState() {
184+
panic("not implemented") // TODO: Implement
185+
}
186+
180187
type odrTrie struct {
181188
db *odrDatabase
182189
id *TrieID

0 commit comments

Comments
 (0)