-
Notifications
You must be signed in to change notification settings - Fork 13
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fixes for performing the overlay transition #203
Changes from all commits
4c4205b
426ae7c
95ccc05
b10b176
249cd18
0d2bd5d
f75311a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,6 +19,7 @@ package state | |
import ( | ||
"errors" | ||
"fmt" | ||
"sync" | ||
|
||
"github.com/VictoriaMetrics/fastcache" | ||
"github.com/ethereum/go-ethereum/common" | ||
|
@@ -162,11 +163,18 @@ type ForkingDB struct { | |
*VerkleDB | ||
|
||
// TODO ensure that this info is in the DB | ||
started, ended bool | ||
translatedRoots map[common.Hash]common.Hash // hash of the translated root, for opening | ||
baseRoot common.Hash // hash of the read-only base tree | ||
LastAccHash common.Hash // hash of the last translated account address | ||
LastSlotHash common.Hash // hash of the last translated storage slot address | ||
started, ended bool | ||
translatedRoots map[common.Hash]common.Hash // hash of the translated root, for opening | ||
translatedRootsLock sync.RWMutex | ||
|
||
baseRoot common.Hash // hash of the read-only base tree | ||
CurrentAccountHash common.Hash // hash of the last translated account | ||
CurrentSlotHash common.Hash // hash of the last translated storage slot | ||
|
||
// 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 | ||
} | ||
|
||
// ContractCode implements Database | ||
|
@@ -193,7 +201,7 @@ func (fdb *ForkingDB) CopyTrie(t Trie) Trie { | |
|
||
if fdb.started { | ||
overlay := fdb.VerkleDB.CopyTrie(t) | ||
return trie.NewTransitionTree(mpt.(*trie.SecureTrie), overlay.(*trie.VerkleTrie)) | ||
return trie.NewTransitionTree(mpt.(*trie.SecureTrie), overlay.(*trie.VerkleTrie), false) | ||
} | ||
|
||
return mpt | ||
|
@@ -205,11 +213,13 @@ func (fdb *ForkingDB) OpenStorageTrie(stateRoot, addrHash, root common.Hash, sel | |
if fdb.started && err == nil { | ||
// Return a "storage trie" that is an adapter between the storge MPT | ||
// and the unique verkle tree. | ||
vkt, err := fdb.VerkleDB.OpenStorageTrie(stateRoot, addrHash, fdb.translatedRoots[root], self) | ||
fdb.translatedRootsLock.RLock() | ||
vkt, err := fdb.VerkleDB.OpenStorageTrie(stateRoot, addrHash, fdb.translatedRoots[root], self.(*trie.TransitionTrie).Overlay()) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I had to add an extra parameter to |
||
fdb.translatedRootsLock.RUnlock() | ||
if err != nil { | ||
return nil, err | ||
} | ||
return trie.NewTransitionTree(mpt.(*trie.SecureTrie), vkt.(*trie.VerkleTrie)), nil | ||
return trie.NewTransitionTree(mpt.(*trie.SecureTrie), vkt.(*trie.VerkleTrie), true), nil | ||
} | ||
|
||
return mpt, err | ||
|
@@ -221,21 +231,24 @@ func (fdb *ForkingDB) OpenTrie(root common.Hash) (Trie, error) { | |
mpt Trie | ||
err error | ||
) | ||
|
||
if fdb.started { | ||
mpt, err = fdb.cachingDB.OpenTrie(fdb.baseRoot) | ||
if err != nil { | ||
return nil, err | ||
} | ||
fdb.translatedRootsLock.RLock() | ||
vkt, err := fdb.VerkleDB.OpenTrie(fdb.translatedRoots[root]) | ||
fdb.translatedRootsLock.RUnlock() | ||
if err != nil { | ||
return nil, err | ||
} | ||
return trie.NewTransitionTree(mpt.(*trie.SecureTrie), vkt.(*trie.VerkleTrie), false), nil | ||
} else { | ||
mpt, err = fdb.cachingDB.OpenTrie(root) | ||
if err != nil { | ||
return nil, err | ||
} else { | ||
mpt, err = fdb.cachingDB.OpenTrie(root) | ||
if err != nil { | ||
return nil, err | ||
} | ||
} | ||
return trie.NewTransitionTree(mpt.(*trie.SecureTrie), vkt.(*trie.VerkleTrie)), nil | ||
} | ||
|
||
return mpt, nil | ||
|
@@ -263,6 +276,10 @@ func (fdg *ForkingDB) InTransition() bool { | |
return fdg.started && !fdg.ended | ||
} | ||
|
||
func (fdg *ForkingDB) Transitionned() bool { | ||
return fdg.ended | ||
} | ||
|
||
// Fork implements the fork | ||
func (fdb *ForkingDB) StartTransition(originalRoot, translatedRoot common.Hash) { | ||
fmt.Println(` | ||
|
@@ -275,6 +292,8 @@ func (fdb *ForkingDB) StartTransition(originalRoot, translatedRoot common.Hash) | |
fdb.started = true | ||
fdb.translatedRoots = map[common.Hash]common.Hash{originalRoot: translatedRoot} | ||
fdb.baseRoot = originalRoot | ||
// initialize so that the first storage-less accounts are processed | ||
fdb.StorageProcessed = true | ||
} | ||
|
||
func (fdb *ForkingDB) EndTransition() { | ||
|
@@ -290,7 +309,9 @@ func (fdb *ForkingDB) EndTransition() { | |
|
||
func (fdb *ForkingDB) AddTranslation(orig, trans common.Hash) { | ||
// TODO make this persistent | ||
fdb.translatedRootsLock.Lock() | ||
fdb.translatedRoots[orig] = trans | ||
fdb.translatedRootsLock.Unlock() | ||
} | ||
|
||
type cachingDB struct { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -200,8 +200,9 @@ func (s *stateObject) GetCommittedState(db Database, key common.Hash) common.Has | |
} | ||
// If no live objects are available, attempt to use snapshots | ||
var ( | ||
enc []byte | ||
err error | ||
enc []byte | ||
err error | ||
value common.Hash | ||
) | ||
if s.db.snap != nil { | ||
// If the object was destructed in *this* block (and potentially resurrected), | ||
|
@@ -221,11 +222,15 @@ func (s *stateObject) GetCommittedState(db Database, key common.Hash) common.Has | |
} | ||
// If the snapshot is unavailable or reading from it fails, load from the database. | ||
if s.db.snap == nil || err != nil { | ||
var tr = s.getTrie(db) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we move this line inside the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yup |
||
start := time.Now() | ||
if s.db.GetTrie().IsVerkle() { | ||
panic("verkle trees use the snapshot") | ||
var v []byte | ||
v, err = tr.TryGet(s.address[:], key.Bytes()) | ||
copy(value[:], v) | ||
} else { | ||
enc, err = s.getTrie(db).TryGet(s.address[:], key.Bytes()) | ||
Comment on lines
+229
to
+232
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OK, despite this looks correct it's a bit weird-ish at first sight, because:
To be clear, the logic is correct and it's trying to be a bit smart into "merging" the potential RLP decoding in a single place, considering you can come from different places. And eagerly doing that L230 copy to "accommodate" into that. Probably we can come up with a better way to describe this logic that doesn't look that weird. Feels like this code is waiting to be a bug the next time someone adds some extra line of code around here. To be honest, I don't have a good suggestion now. We can think about it when cleaning up all the work maybe. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this temporary code, I have a geth PR awaiting a merge that should make all this simpler |
||
} | ||
start := time.Now() | ||
enc, err = s.getTrie(db).TryGet(s.address[:], key.Bytes()) | ||
if metrics.EnabledExpensive { | ||
s.db.StorageReads += time.Since(start) | ||
} | ||
|
@@ -234,7 +239,6 @@ func (s *stateObject) GetCommittedState(db Database, key common.Hash) common.Has | |
return common.Hash{} | ||
} | ||
} | ||
var value common.Hash | ||
if len(enc) > 0 { | ||
_, content, _, err := rlp.Split(enc) | ||
if err != nil { | ||
|
@@ -324,11 +328,7 @@ func (s *stateObject) updateTrie(db Database) Trie { | |
var storage map[common.Hash][]byte | ||
// Insert all the pending updates into the trie | ||
var tr Trie | ||
if s.db.trie.IsVerkle() { | ||
tr = s.db.trie | ||
} else { | ||
tr = s.getTrie(db) | ||
} | ||
tr = s.getTrie(db) | ||
Comment on lines
330
to
+331
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: merge both L330 and L331 in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes it should be simplified like this |
||
hasher := s.db.hasher | ||
|
||
usedStorage := make([][]byte, 0, len(s.pendingStorage)) | ||
|
@@ -373,7 +373,7 @@ func (s *stateObject) updateTrie(db Database) Trie { | |
if len(s.pendingStorage) > 0 { | ||
s.pendingStorage = make(Storage) | ||
} | ||
return tr | ||
return s.trie | ||
} | ||
|
||
// UpdateRoot sets the trie root to the current root hash of | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -34,6 +34,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/rlp" | ||
"github.com/ethereum/go-ethereum/trie" | ||
tutils "github.com/ethereum/go-ethereum/trie/utils" | ||
"github.com/gballet/go-verkle" | ||
|
@@ -102,42 +103,89 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg | |
if fdb, ok := statedb.Database().(*state.ForkingDB); ok { | ||
if fdb.InTransition() { | ||
now := time.Now() | ||
// XXX overkill, just save the parent root in the forking db | ||
tt := statedb.GetTrie().(*trie.TransitionTrie) | ||
mpt := tt.Base() | ||
|
||
accIt, err := statedb.Snaps().AccountIterator(mpt.Hash(), fdb.LastAccHash) | ||
if err != nil { | ||
return nil, nil, 0, err | ||
} | ||
stIt, err := statedb.Snaps().StorageIterator(mpt.Hash(), fdb.LastAccHash, fdb.LastSlotHash) | ||
if err != nil { | ||
return nil, nil, 0, err | ||
} | ||
defer accIt.Release() | ||
accIt.Next() | ||
|
||
const maxMovedCount = 500 | ||
const maxMovedCount = 1000 | ||
Comment on lines
-118
to
+116
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Q: Not sure if makes sense to switch to 10k. |
||
// 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 | ||
addr := rawdb.ReadPreimage(statedb.Database().DiskDB(), accIt.Hash()) | ||
for ; stIt.Next() && count < maxMovedCount; count++ { | ||
slotnr := rawdb.ReadPreimage(statedb.Database().DiskDB(), stIt.Hash()) | ||
mkv.addStorageSlot(addr, slotnr, stIt.Slot()) | ||
} | ||
|
||
// if less than maxCount slots were moved, move to the next account | ||
for count < maxMovedCount { | ||
if accIt.Next() { | ||
acc, err := snapshot.FullAccount(accIt.Account()) | ||
fdb.CurrentAccountHash = accIt.Hash() | ||
|
||
acc, err := snapshot.FullAccount(accIt.Account()) | ||
if err != nil { | ||
log.Error("Invalid account encountered during traversal", "error", err) | ||
return nil, nil, 0, err | ||
} | ||
addr := rawdb.ReadPreimage(statedb.Database().DiskDB(), accIt.Hash()) | ||
if len(addr) == 0 { | ||
panic(fmt.Sprintf("%x %x %v", addr, accIt.Hash(), acc)) | ||
} | ||
|
||
// 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(), fdb.CurrentSlotHash) | ||
if err != nil { | ||
log.Error("Invalid account encountered during traversal", "error", err) | ||
return nil, nil, 0, err | ||
} | ||
addr := rawdb.ReadPreimage(statedb.Database().DiskDB(), accIt.Hash()) | ||
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 ; !fdb.StorageProcessed && 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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. in the MPT, the data is serialized using RLP. It must be deserialized before it is inserted. |
||
return nil, nil, 0, fmt.Errorf("error decoding bytes %x: %w", stIt.Slot(), err) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. called |
||
} | ||
copy(safeValue[32-len(value):], value) | ||
slotnr := rawdb.ReadPreimage(statedb.Database().DiskDB(), stIt.Hash()) | ||
|
||
mkv.addStorageSlot(addr, slotnr, safeValue[:]) | ||
|
||
// advance the storage iterator | ||
fdb.StorageProcessed = !stIt.Next() | ||
if !fdb.StorageProcessed { | ||
fdb.CurrentSlotHash = 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(addr, acc) | ||
|
||
|
@@ -149,26 +197,24 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg | |
mkv.addAccountCode(addr, uint64(len(code)), chunks) | ||
} | ||
|
||
if acc.HasStorage() { | ||
for ; stIt.Next() && count < maxMovedCount; count++ { | ||
slotnr := rawdb.ReadPreimage(statedb.Database().DiskDB(), stIt.Hash()) | ||
|
||
mkv.addStorageSlot(addr, slotnr, stIt.Slot()) | ||
} | ||
// reset storage iterator marker for next account | ||
fdb.StorageProcessed = false | ||
fdb.CurrentSlotHash = common.Hash{} | ||
|
||
// Move to the next account, if available - or end | ||
// the transition otherwise. | ||
if accIt.Next() { | ||
fdb.CurrentAccountHash = accIt.Hash() | ||
} else { | ||
// case when the account iterator has | ||
// reached the end but count < maxCount | ||
fdb.EndTransition() | ||
break | ||
} | ||
} | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think there's a corner case that isn't correctly handled here; let me explain to double-check with you.
In this situation, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. right, initially I was doing like you said: not checking for |
||
|
||
// If the iterators have reached the end, mark the | ||
// transition as complete. | ||
if !accIt.Next() && !stIt.Next() { | ||
fdb.EndTransition() | ||
} else { | ||
// Update the pointers in the forking db | ||
fdb.LastAccHash = accIt.Hash() | ||
fdb.LastSlotHash = stIt.Hash() | ||
} | ||
log.Info("Collected and prepared key values from base tree", "count", count, "duration", time.Since(now)) | ||
log.Info("Collected and prepared key values from base tree", "count", count, "duration", time.Since(now), "last account", fdb.CurrentAccountHash) | ||
|
||
now = time.Now() | ||
if err := mkv.migrateCollectedKeyValues(tt.Overlay()); err != nil { | ||
|
@@ -337,7 +383,7 @@ func (kvm *keyValueMigrator) getOrInitLeafNodeData(stem []byte) *verkle.BatchNew | |
stemStr := string(stem) | ||
if _, ok := kvm.vktLeafData[stemStr]; !ok { | ||
kvm.vktLeafData[stemStr] = &verkle.BatchNewLeafNodeData{ | ||
Stem: stem, | ||
Stem: stem[:verkle.StemSize], | ||
Values: make(map[byte][]byte), | ||
} | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need a mutex?
Is this related to the prefetcher? (but that shouldn't be "on" in VKT mode?)
Maybe it will become apparent later when I continue with the review.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we need a mutex because the map is accessed from the processor and the state prefetcher, that is on (not the trie prefetecher, that is off).