Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 10 additions & 42 deletions core/overlay/state_transition.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,21 @@
package overlay

import (
"bytes"
"encoding/gob"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/log"
)

// TransitionState is a structure that holds the progress markers of the
// translation process.
// TODO gballet:
// * see if I can get rid of the pointer now that this piece
// has been rewritten.
// * the conversion pointers should no longer be necessary,
// remove them when it's been confirmed.
// * we can't keep the preimage offset in the file, since
// some clients might decide to record their preimages and
// skip the use of the file altogether. Therefore, they can't
// know what the offset it, unless they keep track of how many
// bytes have been read since the start, which is a possibility.
type TransitionState struct {
CurrentAccountAddress *common.Address // addresss of the last translated account
CurrentSlotHash common.Hash // hash of the last translated storage slot
Expand Down Expand Up @@ -68,39 +72,3 @@ func (ts *TransitionState) Copy() *TransitionState {
}
return ret
}

// LoadTransitionState retrieves the Verkle transition state associated with
// the given state root hash from the database.
func LoadTransitionState(db ethdb.KeyValueReader, root common.Hash, isVerkle bool) *TransitionState {
var ts *TransitionState

data, _ := rawdb.ReadVerkleTransitionState(db, root)

// 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 nil
}
ts = &newts
}

// 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", "verkle", isVerkle)

// Start with a fresh state
ts = &TransitionState{Ended: isVerkle}
}
return ts
}
107 changes: 96 additions & 11 deletions core/state/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,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/params"
"github.com/ethereum/go-ethereum/trie"
"github.com/ethereum/go-ethereum/trie/bintrie"
"github.com/ethereum/go-ethereum/trie/transitiontrie"
Expand Down Expand Up @@ -177,6 +178,70 @@ func NewDatabaseForTesting() *CachingDB {
return NewDatabase(triedb.NewDatabase(rawdb.NewMemoryDatabase(), nil), nil)
}

var (
transitionStartedKey = common.Hash{} // slot 0: non-zero if transition started
conversionProgressAddressKey = common.Hash{1} // slot 1: current account pointer
conversionProgressSlotKey = common.Hash{2} // slot 2: current slot pointer
conversionProgressStorageProcessed = common.Hash{3} // slot 3: storage processed flag
transitionEndedKey = common.Hash{4} // slot 4: non-zero if transition ended
)

// isTransitionActive checks if the binary tree transition has been activated
func isTransitionActive(reader StateReader) bool {
val, err := reader.Storage(params.BinaryTransitionRegistryAddress, transitionStartedKey)
if err != nil {
return false
}
return val != (common.Hash{})
}

// LoadTransitionState retrieves the Verkle transition state associated with
// the given state root hash from the database.
func LoadTransitionState(reader StateReader, root common.Hash) *overlay.TransitionState {
startedBytes, err := reader.Storage(params.BinaryTransitionRegistryAddress, transitionStartedKey)
if err != nil {
return nil
}
started := startedBytes != (common.Hash{})

// If not started, return nil to indicate no active transition
if !started {
return nil
}

endedBytes, err := reader.Storage(params.BinaryTransitionRegistryAddress, transitionEndedKey)
if err != nil {
// Registry exists but can't read ended flag - treat as still in transition
endedBytes = common.Hash{}
}
ended := endedBytes != (common.Hash{})

currentAccountBytes, err := reader.Storage(params.BinaryTransitionRegistryAddress, conversionProgressAddressKey)
if err != nil {
panic(fmt.Errorf("error reading conversion account pointer: %w", err))
}
currentAccount := common.BytesToAddress(currentAccountBytes[12:])

currentSlotHash, err := reader.Storage(params.BinaryTransitionRegistryAddress, conversionProgressSlotKey)
if err != nil {
panic(fmt.Errorf("error reading conversion slot pointer: %w", err))
}

storageProcessedBytes, err := reader.Storage(params.BinaryTransitionRegistryAddress, conversionProgressStorageProcessed)
if err != nil {
panic(fmt.Errorf("error reading conversion storage processing completion status: %w", err))
}
storageProcessed := storageProcessedBytes[0] == 1

return &overlay.TransitionState{
Started: started,
Ended: ended,
CurrentAccountAddress: &currentAccount,
CurrentSlotHash: currentSlotHash,
StorageProcessed: storageProcessed,
}
}

// StateReader returns a state reader associated with the specified state root.
func (db *CachingDB) StateReader(stateRoot common.Hash) (StateReader, error) {
var readers []StateReader
Expand All @@ -190,19 +255,24 @@ func (db *CachingDB) StateReader(stateRoot common.Hash) (StateReader, error) {
readers = append(readers, newFlatReader(snap))
}
}
var ts *overlay.TransitionState
// Configure the state reader using the path database in path mode.
// This reader offers improved performance but is optional and only
// partially useful if the snapshot data in path database is not
// fully generated.
if db.TrieDB().Scheme() == rawdb.PathScheme {
reader, err := db.triedb.StateReader(stateRoot)
if err == nil {
readers = append(readers, newFlatReader(reader))
flatReader := newFlatReader(reader)
readers = append(readers, flatReader)
if isTransitionActive(flatReader) || db.triedb.IsVerkle() {
ts = LoadTransitionState(flatReader, stateRoot)
}
}
}
// Configure the trie reader, which is expected to be available as the
// gatekeeper unless the state is corrupted.
tr, err := newTrieReader(stateRoot, db.triedb)
tr, err := newTrieReader(stateRoot, db.triedb, ts)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -238,16 +308,31 @@ func (db *CachingDB) ReadersWithCacheStats(stateRoot common.Hash) (ReaderWithSta

// OpenTrie opens the main account trie at a specific root hash.
func (db *CachingDB) OpenTrie(root common.Hash) (Trie, error) {
if db.triedb.IsVerkle() {
ts := overlay.LoadTransitionState(db.TrieDB().Disk(), root, db.triedb.IsVerkle())
if ts.InTransition() {
panic("state tree transition isn't supported yet")
reader, err := db.StateReader(root)
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

get flat state reader only

if err != nil {
tr, err := trie.NewStateTrie(trie.StateTrieID(root), db.triedb)
if err != nil {
return nil, err
}
if ts.Transitioned() {
// Use BinaryTrie instead of VerkleTrie when IsVerkle is set
// (IsVerkle actually means Binary Trie mode in this codebase)
return bintrie.NewBinaryTrie(root, db.triedb)
return tr, nil
}

if isTransitionActive(reader) || db.triedb.IsVerkle() {
bt, err := bintrie.NewBinaryTrie(root, db.triedb)
if err != nil {
return nil, fmt.Errorf("could not open the overlay tree: %w", err)
}
ts := LoadTransitionState(reader, root)
if !ts.InTransition() {
// Transition complete, use BinaryTrie only
return bt, nil
}

base, err := trie.NewStateTrie(trie.StateTrieID(root), db.triedb)
if err != nil {
return nil, fmt.Errorf("could not create base trie in OpenTrie: %w", err)
}
return transitiontrie.NewTransitionTrie(base, bt, false), nil
}
tr, err := trie.NewStateTrie(trie.StateTrieID(root), db.triedb)
if err != nil {
Expand All @@ -258,7 +343,7 @@ func (db *CachingDB) OpenTrie(root common.Hash) (Trie, error) {

// 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) {
if db.triedb.IsVerkle() {
if self != nil && self.IsVerkle() {
return self, nil
}
tr, err := trie.NewStateTrie(trie.StorageTrieID(stateRoot, crypto.Keccak256Hash(address.Bytes()), root), db.triedb)
Expand Down
3 changes: 1 addition & 2 deletions core/state/reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,7 @@ type trieReader struct {

// newTrieReader constructs a trie reader of the specific state. An error will be
// returned if the associated trie specified by root is not existent.
func newTrieReader(root common.Hash, db *triedb.Database) (*trieReader, error) {
func newTrieReader(root common.Hash, db *triedb.Database, ts *overlay.TransitionState) (*trieReader, error) {
var (
tr Trie
err error
Expand All @@ -330,7 +330,6 @@ func newTrieReader(root common.Hash, db *triedb.Database) (*trieReader, error) {
// Based on the transition status, determine if the overlay
// tree needs to be created, or if a single, target tree is
// to be picked.
ts := overlay.LoadTransitionState(db.Disk(), root, true)
if ts.InTransition() {
mpt, err := trie.NewStateTrie(trie.StateTrieID(ts.BaseRoot), db)
if err != nil {
Expand Down
7 changes: 7 additions & 0 deletions core/state/statedb.go
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,13 @@ func (s *StateDB) GetStorageRoot(addr common.Address) common.Hash {
return common.Hash{}
}

// TransitionComplete checks if the EIP-7612 transition is complete.
func (s *StateDB) InTransition() bool {
completeKey := common.Hash{} // slot 0 for completion flag
completeValue := s.GetState(params.BinaryTransitionRegistryAddress, completeKey)
return completeValue != (common.Hash{})
}

// TxIndex returns the current transaction index set by SetTxContext.
func (s *StateDB) TxIndex() int {
return s.txIndex
Expand Down
5 changes: 5 additions & 0 deletions core/state_processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,11 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
if config.IsPrague(block.Number(), block.Time()) || config.IsVerkle(block.Number(), block.Time()) {
ProcessParentBlockHash(block.ParentHash(), evm)
}
if config.IsVerkle(header.Number, header.Time) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can we treat the TransitionRegistry contract like the deposit contract? We can pre-deploy it across all the network with the same address and update it according to the transition status?

It feels wrong to me to manually update it here.

statedb.SetCode(params.BinaryTransitionRegistryAddress, []byte{1, 2, 3}, tracing.CodeChangeUnspecified)
statedb.SetNonce(params.BinaryTransitionRegistryAddress, 1, tracing.NonceChangeUnspecified)
statedb.SetState(params.BinaryTransitionRegistryAddress, common.Hash{}, common.Hash{1})
}

// Iterate over and process the individual transactions
for i, tx := range block.Transactions() {
Expand Down
7 changes: 7 additions & 0 deletions eth/state_accessor.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,12 @@ import (
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/eth/tracers"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/trie"
"github.com/ethereum/go-ethereum/triedb"
)
Expand Down Expand Up @@ -251,6 +253,11 @@ func (eth *Ethereum) stateAtTransaction(ctx context.Context, block *types.Block,
if eth.blockchain.Config().IsPrague(block.Number(), block.Time()) {
core.ProcessParentBlockHash(block.ParentHash(), evm)
}
if eth.blockchain.Config().IsVerkle(block.Number(), block.Time()) {
statedb.SetCode(params.BinaryTransitionRegistryAddress, []byte{1, 2, 3}, tracing.CodeChangeUnspecified)
statedb.SetNonce(params.BinaryTransitionRegistryAddress, 1, tracing.NonceChangeUnspecified)
statedb.SetState(params.BinaryTransitionRegistryAddress, common.Hash{}, common.Hash{1})
}
if txIndex == 0 && len(block.Transactions()) == 0 {
return nil, context, statedb, release, nil
}
Expand Down
6 changes: 6 additions & 0 deletions miner/worker.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/stateless"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/txpool"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
Expand Down Expand Up @@ -266,6 +267,11 @@ func (miner *Miner) prepareWork(genParams *generateParams, witness bool) (*envir
if miner.chainConfig.IsPrague(header.Number, header.Time) {
core.ProcessParentBlockHash(header.ParentHash, env.evm)
}
if miner.chainConfig.IsVerkle(header.Number, header.Time) {
env.state.SetCode(params.BinaryTransitionRegistryAddress, []byte{1, 2, 3}, tracing.CodeChangeUnspecified)
env.state.SetNonce(params.BinaryTransitionRegistryAddress, 1, tracing.NonceChangeUnspecified)
env.state.SetState(params.BinaryTransitionRegistryAddress, common.Hash{}, common.Hash{1})
}
return env, nil
}

Expand Down
3 changes: 3 additions & 0 deletions params/protocol_params.go
Original file line number Diff line number Diff line change
Expand Up @@ -219,4 +219,7 @@ var (
// EIP-7251 - Increase the MAX_EFFECTIVE_BALANCE
ConsolidationQueueAddress = common.HexToAddress("0x0000BBdDc7CE488642fb579F8B00f3a590007251")
ConsolidationQueueCode = common.FromHex("3373fffffffffffffffffffffffffffffffffffffffe1460d35760115f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1461019a57600182026001905f5b5f82111560685781019083028483029004916001019190604d565b9093900492505050366060146088573661019a573461019a575f5260205ff35b341061019a57600154600101600155600354806004026004013381556001015f358155600101602035815560010160403590553360601b5f5260605f60143760745fa0600101600355005b6003546002548082038060021160e7575060025b5f5b8181146101295782810160040260040181607402815460601b815260140181600101548152602001816002015481526020019060030154905260010160e9565b910180921461013b5790600255610146565b90505f6002555f6003555b5f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff141561017357505f5b6001546001828201116101885750505f61018e565b01600190035b5f555f6001556074025ff35b5f5ffd")

// EIP-7612 - Tree transition registry contract address
BinaryTransitionRegistryAddress = common.HexToAddress("0x1622162216221622162216221622162216221622162216221622162216221622")
)
Loading