Skip to content

Stateless transition #31532

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

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft

Conversation

gballet
Copy link
Member

@gballet gballet commented Mar 31, 2025

This is an implementation of the state conversion process described in EIP-7612.

What this PR does:

  • It creates a special type of "facade pattern" tree, called TransitionTree, that is in charge of abstracting which one of the underlying trees (the "base" MPT tree or the "overlay" verkle tree) values should be read from/written to.
  • At the end of the block execution, a tree-sweeping process goes over the snapshot and converts N "leaves" (either a single slot or a full account header + code). In order to resume where the previous block stopped, a TransitionState structure is maintained in the db, keyed by the pre-state root of the block. This helps recovering these pointers in case the process is ran multiple times (e.g. block production, followed by insertion).

@gballet gballet added the verkle label Mar 31, 2025
@gballet gballet force-pushed the stateless-transition branch from 5f4b22f to 16cb4fa Compare March 31, 2025 15:50
@@ -22,7 +22,7 @@ require (
github.com/donovanhide/eventsource v0.0.0-20210830082556-c59027999da0
github.com/dop251/goja v0.0.0-20230605162241-28ee0ee714f3
github.com/ethereum/c-kzg-4844 v1.0.0
github.com/ethereum/go-verkle v0.2.2
github.com/ethereum/go-verkle v0.2.1
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is due to a bug being found in 0.2.2 that breaks the Holesky shadowfork. I will revert this in a further PR, when I have time to perform a deeper investigation of the bug. v0.2.2 is a db-size optimization and has no functional impact.

Comment on lines 72 to 116
// StartVerkleTransition marks the start of the verkle transition
StartVerkleTransition(originalRoot, translatedRoot common.Hash, chainConfig *params.ChainConfig, verkleTime *uint64, root common.Hash)

// EndVerkleTransition marks the end of the verkle transition
EndVerkleTransition()

// InTransition returns true if the verkle transition is currently ongoing
InTransition() bool

// Transitioned returns true if the verkle transition has ended
Transitioned() bool

InitTransitionStatus(bool, bool, common.Hash)

SetCurrentSlotHash(common.Hash)

GetCurrentAccountAddress() *common.Address

SetCurrentAccountAddress(common.Address)

GetCurrentAccountHash() common.Hash

GetCurrentSlotHash() common.Hash

SetStorageProcessed(bool)

GetStorageProcessed() bool

GetCurrentPreimageOffset() int64

SetCurrentPreimageOffset(int64)

AddRootTranslation(originalRoot, translatedRoot common.Hash)

SetLastMerkleRoot(common.Hash)

SaveTransitionState(common.Hash)

LoadTransitionState(common.Hash)

LockCurrentTransitionState()

UnLockCurrentTransitionState()
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rjl493456442 these are helper functions to handle the transition. In my own branch, this is the most convenient way to access them. But maybe they could be moved to a verkle-specific TrieDB if it's cleaner ... what do you think?

if len(val) != 0 {
return val, nil
}
// TODO also insert value into overlay
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's contrary to the spec

Suggested change
// TODO also insert value into overlay

}
return data, nil
}
// TODO also insert value into overlay
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same thing as above

Suggested change
// TODO also insert value into overlay

return t.base
}

// TODO(gballet/jsign): consider removing this API.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note to self: remove that if it's no longer needed

@gballet gballet force-pushed the stateless-transition branch 2 times, most recently from 8e8d10a to b9884bf Compare April 2, 2025 13:08
Copy link
Member Author

@gballet gballet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I pushed a second commit in order to explore the idea I mentioned earlier, which is to move as much of the transition management code to the TransitionTrie itself. The code is still incomplete and doesn't compile, but reading my messages will help explain some aspects of it - and hopefully we can converge towards a working and reliable design.

Comment on lines 74 to 117
StartVerkleTransition(originalRoot, translatedRoot common.Hash, chainConfig *params.ChainConfig, verkleTime *uint64, root common.Hash)

// EndVerkleTransition marks the end of the verkle transition
EndVerkleTransition()

// InTransition returns true if the verkle transition is currently ongoing
InTransition() bool

// Transitioned returns true if the verkle transition has ended
Transitioned() bool

InitTransitionStatus(bool, bool, common.Hash)

// SetCurrentSlotHash provides the next slot to be translated
SetCurrentSlotHash(common.Hash)

// GetCurrentAccountAddress returns the address of the account that is currently being translated
GetCurrentAccountAddress() *common.Address

SetCurrentAccountAddress(common.Address)

GetCurrentAccountHash() common.Hash

GetCurrentSlotHash() common.Hash

SetStorageProcessed(bool)

GetStorageProcessed() bool

GetCurrentPreimageOffset() int64

SetCurrentPreimageOffset(int64)

AddRootTranslation(originalRoot, translatedRoot common.Hash)

SetLastMerkleRoot(common.Hash)

SaveTransitionState(common.Hash)

LoadTransitionState(common.Hash)

LockCurrentTransitionState()

UnLockCurrentTransitionState()
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are helper functions managing the conversion pointers (current slot being considered, current account, as well as methods to save and restore the state from the db).

In the source branch, these are stored in the unique disk database. Having to partially rewrite the code means that we can consider other methods. For instance, I am exploring the possibility to store the pointers in the MPT or verkle DB (if it's not present there, it means that the transition hasn't started) - and I am thinking it'd be better if the transition tree were the one deciding where the data is - I have a hunch this might simplify the design.

Activation of the forks would still be explicit (no way to work around that anyway, we need a header for chainConfig.IsVerkle()) but the interface would be that of the tree.

Comment on lines 209 to 168
// Transition-specific fields
CurrentTransitionState *TransitionState
TransitionStatePerRoot lru.BasicLRU[common.Hash, *TransitionState]
transitionStateLock sync.Mutex
addrToPoint *utils.PointCache
baseRoot common.Hash // hash of last read-only MPT base tree
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These could be moved to the TransitionTree, if it makes the interface/diff simpler (see previous comment)

}

// NewDatabase creates a state database with the provided data sources.
func NewDatabase(triedb *triedb.Database, snap *snapshot.Tree) *CachingDB {
return &CachingDB{
disk: triedb.Disk(),
triedb: triedb,
verkledb: nil, // XXX change the interface, but it's a big change so wait for the PR to be reviewed by Gary
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The question of how this gets initialized is still open. I have a hunch we could simply create it no matter what, and keep it empty as long as the transition hasn't happened.

I'm not a 100% clear on how this would work in a fully-verkle tesnet, but I guess that in this case, we could have an empty MPT db with just the info that the transition has completed and therefore, that there is no need to go through it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gary: pass verkle db as parameter

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and initialize both DBs at the beginning so that we support verkle at genesis networks

if !db.IsVerkle() {
tr, err = trie.NewStateTrie(trie.StateTrieID(root), db)
// get the transition status from the MPT triedb
ts, err := db.LoadTransitionState(root)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note that this method doesn't currently exist, I'm proposing that we move it from (*state.Database).LoadTransitionState) and keep it inside the tree somehow.

}
tr = trie.NewTransitionTree(mptr, vktr, false)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so as a follow-up to the message above, this would be NewTransitionTree(mptr, vktr, false, ts)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not present in this PR yet, but the call to StartVerkleTransition could be called at the start ofProcess, since the state should not be needed beforehand.

log.Error("error performing the transition", "err", err)
panic(fmt.Sprintf("error performing the transition: %s", err))
}
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note to self: this is also where the pointers would be saved at the db. And the equivalent "start / save" would have to be added to the miner for block production.

tr, err := trie.NewStateTrie(trie.StateTrieID(root), db.triedb)
if err != nil {
return nil, err
}
return tr, nil
}

// OpenTrie opens the main account trie at a specific root hash.
func (db *CachingDB) OpenTrie(root common.Hash) (Trie, error) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gary: read the pointers directly from ethdb.KeyValueStore

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and store it in the cachingdb, no lock should be needed

// Transition-specific fields
CurrentTransitionState *TransitionState
TransitionStatePerRoot lru.BasicLRU[common.Hash, *TransitionState]
transitionStateLock sync.Mutex
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gary: the cache itself has an internal lock, this should not be necessary

@gballet gballet force-pushed the stateless-transition branch from 500abed to 7942e52 Compare April 3, 2025 18:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant