core/state: Track block-hash to base-state-root mapping for Verkle transition

This commit is contained in:
samuel 2025-04-09 16:15:49 +01:00
parent a7f24c26c0
commit a8240e46ea
5 changed files with 432 additions and 0 deletions

View file

@ -274,6 +274,10 @@ type BlockChain struct {
processor Processor // Block transaction processor interface
vmConfig vm.Config
logger *tracing.Hooks
// Verkle transition fields
blockToBaseStateRoot *state.BlockToBaseStateRoot
verkleTransitionBlock uint64 // Block number when Verkle transition begins
}
// NewBlockChain returns a fully initialised block chain using information
@ -486,6 +490,21 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis
if txLookupLimit != nil {
bc.txIndexer = newTxIndexer(*txLookupLimit, bc)
}
// Initialize verkle transition mapping if needed
if chainConfig.IsVerkleActive(0) || (chainConfig.VerkleBlock != nil && chainConfig.VerkleBlock.Uint64() > 0) {
bc.verkleTransitionBlock = chainConfig.VerkleBlock.Uint64()
mapping, err := state.LoadBlockToBaseStateRoot(db)
if err != nil {
return nil, err
}
bc.blockToBaseStateRoot = mapping
log.Info("Initialized Verkle transition mapping", "verkleBlock", bc.verkleTransitionBlock)
} else {
bc.blockToBaseStateRoot = state.NewBlockToBaseStateRootMapping()
}
return bc, nil
}

View file

@ -0,0 +1,150 @@
package core
import (
"math/big"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus/ethash"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/params"
)
// TestVerkleTransitionWithReorg tests the mapping during a reorg at the verkle transition
func TestVerkleTransitionWithReorg(t *testing.T) {
// Configure Verkle transition at block 10
var (
db = rawdb.NewMemoryDatabase()
gspec = &Genesis{
Config: &params.ChainConfig{
ChainID: big.NewInt(1337),
VerkleBlock: big.NewInt(10),
HomesteadBlock: big.NewInt(0),
EIP150Block: big.NewInt(0),
EIP155Block: big.NewInt(0),
EIP158Block: big.NewInt(0),
},
Alloc: GenesisAlloc{
common.HexToAddress("0x71562b71999873DB5b286dF957af199Ec94617F7"): {Balance: big.NewInt(1000000000000000000)},
},
}
genesis = gspec.MustCommit(db)
)
// Create our blockchain instance
blockchain, err := NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil)
if err != nil {
t.Fatalf("Failed to create blockchain: %v", err)
}
defer blockchain.Stop()
// Create a chain up to the fork block (block 9)
blocks, _ := GenerateChain(gspec.Config, genesis, ethash.NewFaker(), db, 9, func(i int, gen *BlockGen) {
gen.SetCoinbase(common.HexToAddress("0x" + string(i+'0')))
})
// Insert blocks up to block 9
if _, err := blockchain.InsertChain(blocks); err != nil {
t.Fatalf("Failed to insert initial chain: %v", err)
}
// Create two competing forks from block 9 (both are at the Verkle transition height)
// Fork 1 with account A1
fork1Block10, _ := GenerateChain(gspec.Config, blocks[len(blocks)-1], ethash.NewFaker(), db, 1, func(i int, gen *BlockGen) {
gen.SetCoinbase(common.HexToAddress("0xA1"))
})
// Fork 2 with account A2
fork2Block10, _ := GenerateChain(gspec.Config, blocks[len(blocks)-1], ethash.NewFaker(), db, 1, func(i int, gen *BlockGen) {
gen.SetCoinbase(common.HexToAddress("0xA2"))
})
// Insert fork 1 (blocks 10-A1)
if _, err := blockchain.InsertChain(fork1Block10); err != nil {
t.Fatalf("Failed to insert fork 1: %v", err)
}
// Both forks add a new account A3
addr3 := common.HexToAddress("0xA3")
// Create block 11 on fork 1 (adding account A3)
fork1Block11, _ := GenerateChain(gspec.Config, fork1Block10[0], ethash.NewFaker(), db, 1, func(i int, gen *BlockGen) {
// Add account A3
tx, _ := types.SignTx(
types.NewTransaction(0, addr3, big.NewInt(100), 21000, big.NewInt(1), nil),
types.HomesteadSigner{},
testKey,
)
gen.AddTx(tx)
})
// Insert block 11 on fork 1
if _, err := blockchain.InsertChain(fork1Block11); err != nil {
t.Fatalf("Failed to insert fork 1 block 11: %v", err)
}
// Verify the mapping for fork 1's block 10
baseRoot1, exists := blockchain.blockToBaseStateRoot.Get(fork1Block10[0].Hash())
if !exists {
t.Fatalf("Expected mapping to exist for fork 1 block 10")
}
// Now simulate a reorg by inserting fork 2 (with higher total difficulty)
for i := range fork2Block10 {
fork2Block10[i].Header().Difficulty = new(big.Int).Add(fork2Block10[i].Header().Difficulty, big.NewInt(1000))
}
// Insert fork 2 (blocks 10-A2)
if _, err := blockchain.InsertChain(fork2Block10); err != nil {
t.Fatalf("Failed to insert fork 2: %v", err)
}
// Create block 11 on fork 2 (also adding account A3)
fork2Block11, _ := GenerateChain(gspec.Config, fork2Block10[0], ethash.NewFaker(), db, 1, func(i int, gen *BlockGen) {
// Add same account A3
tx, _ := types.SignTx(
types.NewTransaction(0, addr3, big.NewInt(100), 21000, big.NewInt(1), nil),
types.HomesteadSigner{},
testKey,
)
gen.AddTx(tx)
})
// Ensure fork 2 has higher total difficulty
for i := range fork2Block11 {
fork2Block11[i].Header().Difficulty = new(big.Int).Add(fork2Block11[i].Header().Difficulty, big.NewInt(1000))
}
// Insert block 11 on fork 2
if _, err := blockchain.InsertChain(fork2Block11); err != nil {
t.Fatalf("Failed to insert fork 2 block 11: %v", err)
}
// Verify the mapping for fork 2's block 10
baseRoot2, exists := blockchain.blockToBaseStateRoot.Get(fork2Block10[0].Hash())
if !exists {
t.Fatalf("Expected mapping to exist for fork 2 block 10")
}
// The base roots should be different
if baseRoot1 == baseRoot2 {
t.Fatalf("Expected different base roots for different forks")
}
// Current chain head should be fork 2 block 11
if blockchain.CurrentBlock().Hash() != fork2Block11[0].Hash() {
t.Fatalf("Expected chain head to be fork 2 block 11")
}
// Verify we can correctly get state for fork 2 block 11
state, err := blockchain.StateAt(fork2Block11[0].Root())
if err != nil {
t.Fatalf("Failed to get state: %v", err)
}
// Check A3 is present in the state
if balance := state.GetBalance(addr3); balance.Cmp(big.NewInt(100)) != 0 {
t.Fatalf("Expected A3 balance of 100, got %v", balance)
}
}

View file

@ -86,6 +86,11 @@ type StateDB struct {
// It will be updated when the Commit is called.
originalRoot common.Hash
// Verkle transition fields for identifying state
verkleTransitionActive bool
baseStateRoot common.Hash
blockHash common.Hash
// This map holds 'live' objects, which will get modified while
// processing a state transition.
stateObjects map[common.Address]*stateObject
@ -1434,3 +1439,52 @@ func (s *StateDB) Witness() *stateless.Witness {
func (s *StateDB) AccessEvents() *AccessEvents {
return s.accessEvents
}
// Add new methods for Verkle transition
func (s *StateDB) SetVerkleTransitionData(blockHash, baseStateRoot common.Hash) {
s.verkleTransitionActive = true
s.baseStateRoot = baseStateRoot
s.blockHash = blockHash
}
// IsVerkleTransitionActive returns whether the state is in Verkle transition
func (s *StateDB) IsVerkleTransitionActive() bool {
return s.verkleTransitionActive
}
// BaseStateRoot returns the Merkle state root used as base during transition
func (s *StateDB) BaseStateRoot() common.Hash {
return s.baseStateRoot
}
func (s *StateDB) GetState(addr common.Address, hash common.Hash) common.Hash {
// Try current state first
value := s.GetState(addr, hash) // This is wrong - would cause recursion
// Instead, check how GetState is currently implemented
// Likely something like:
stateObject := s.getStateObject(addr)
if stateObject != nil {
value = stateObject.GetState(hash)
}
// If in verkle transition and value not found, fallback to base state
if s.verkleTransitionActive && value == (common.Hash{}) {
return s.getStateFromBase(addr, hash)
}
return value
}
// Add method to access base state
func (s *StateDB) getStateFromBase(addr common.Address, hash common.Hash) common.Hash {
// Implementation depends on how Merkle states are accessed
// This is a simplified implementation for the PR
baseState, err := New(s.baseStateRoot, s.db)
if err != nil {
log.Error("Failed to access base state", "error", err)
return common.Hash{}
}
return baseState.GetState(addr, hash)
}

View file

@ -0,0 +1,99 @@
package state
import (
"sync"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/log"
)
// BlockToBaseStateRoot maps block hashes to their corresponding base Merkle state roots
// during the Verkle transition period to uniquely identify states
type BlockToBaseStateRoot struct {
blockHashToStateRoot map[common.Hash]common.Hash
mutex sync.RWMutex
}
// NewBlockToBaseStateRootMapping creates a new mapping instance
func NewBlockToBaseStateRootMapping() *BlockToBaseStateRoot {
return &BlockToBaseStateRoot{
blockHashToStateRoot: make(map[common.Hash]common.Hash),
}
}
// Add associates a block hash with its base state root
func (m *BlockToBaseStateRoot) Add(blockHash common.Hash, stateRoot common.Hash) {
m.mutex.Lock()
defer m.mutex.Unlock()
m.blockHashToStateRoot[blockHash] = stateRoot
log.Debug("Added verkle transition mapping", "blockHash", blockHash, "baseStateRoot", stateRoot)
}
// Get retrieves the base state root for a given block hash
func (m *BlockToBaseStateRoot) Get(blockHash common.Hash) (common.Hash, bool) {
m.mutex.RLock()
defer m.mutex.RUnlock()
root, exists := m.blockHashToStateRoot[blockHash]
return root, exists
}
// Has checks if mapping exists for the given block hash
func (m *BlockToBaseStateRoot) Has(blockHash common.Hash) bool {
m.mutex.RLock()
defer m.mutex.RUnlock()
_, exists := m.blockHashToStateRoot[blockHash]
return exists
}
// Store persists the mapping to the database
func (m *BlockToBaseStateRoot) Store(db ethdb.Database) error {
m.mutex.RLock()
defer m.mutex.RUnlock()
batch := db.NewBatch()
count := 0
for blockHash, stateRoot := range m.blockHashToStateRoot {
key := append([]byte("verkle-base-state:"), blockHash.Bytes()...)
if err := batch.Put(key, stateRoot.Bytes()); err != nil {
return err
}
count++
}
if err := batch.Write(); err != nil {
return err
}
log.Debug("Stored verkle transition mappings", "count", count)
return nil
}
// Load loads the mapping from the database
func LoadBlockToBaseStateRoot(db ethdb.Database) (*BlockToBaseStateRoot, error) {
mapping := NewBlockToBaseStateRootMapping()
it := db.NewIterator([]byte("verkle-base-state:"), nil)
defer it.Release()
count := 0
for it.Next() {
key := it.Key()
blockHash := common.BytesToHash(key[len("verkle-base-state:"):])
stateRoot := common.BytesToHash(it.Value())
mapping.Add(blockHash, stateRoot)
count++
}
if err := it.Error(); err != nil {
return nil, err
}
log.Debug("Loaded verkle transition mappings", "count", count)
return mapping, nil
}

View file

@ -0,0 +1,110 @@
package state
import (
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethdb/memorydb"
)
func TestBlockToBaseStateRootMapping(t *testing.T) {
// Create a new mapping
mapping := NewBlockToBaseStateRootMapping()
// Test data
blockHash1 := common.HexToHash("0x1111111111111111111111111111111111111111111111111111111111111111")
stateRoot1 := common.HexToHash("0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
blockHash2 := common.HexToHash("0x2222222222222222222222222222222222222222222222222222222222222222")
stateRoot2 := common.HexToHash("0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb")
// Test Add and Get
mapping.Add(blockHash1, stateRoot1)
mapping.Add(blockHash2, stateRoot2)
root, exists := mapping.Get(blockHash1)
if !exists {
t.Fatalf("Expected mapping to exist for blockHash1")
}
if root != stateRoot1 {
t.Fatalf("Expected stateRoot1, got %x", root)
}
root, exists = mapping.Get(blockHash2)
if !exists {
t.Fatalf("Expected mapping to exist for blockHash2")
}
if root != stateRoot2 {
t.Fatalf("Expected stateRoot2, got %x", root)
}
// Test Has
if !mapping.Has(blockHash1) {
t.Fatalf("Expected Has to return true for blockHash1")
}
if mapping.Has(common.HexToHash("0x3333")) {
t.Fatalf("Expected Has to return false for unknown hash")
}
// Test Store and Load
db := memorydb.New()
err := mapping.Store(db)
if err != nil {
t.Fatalf("Failed to store mapping: %v", err)
}
loadedMapping, err := LoadBlockToBaseStateRoot(db)
if err != nil {
t.Fatalf("Failed to load mapping: %v", err)
}
root, exists = loadedMapping.Get(blockHash1)
if !exists {
t.Fatalf("Expected loaded mapping to contain blockHash1")
}
if root != stateRoot1 {
t.Fatalf("Expected stateRoot1 after loading, got %x", root)
}
}
func TestStateDBVerkleTransition(t *testing.T) {
// Create a test state
db := NewDatabase(memorydb.New())
baseState, _ := New(common.Hash{}, db, nil)
// Create base state with account1
addr1 := common.HexToAddress("0x1111111111111111111111111111111111111111")
key1 := common.HexToHash("0xaaaa")
value1 := common.HexToHash("0xbbbb")
baseState.SetState(addr1, key1, value1)
baseRoot, _ := baseState.Commit(false)
// Create new verkle state with account2
statedb, _ := New(common.Hash{}, db, nil)
statedb.SetVerkleTransitionData(common.HexToHash("0xblock"), baseRoot)
addr2 := common.HexToAddress("0x2222222222222222222222222222222222222222")
key2 := common.HexToHash("0xcccc")
value2 := common.HexToHash("0xdddd")
statedb.SetState(addr2, key2, value2)
// Test fallback to base state
retrievedValue1 := statedb.GetState(addr1, key1)
if retrievedValue1 != value1 {
t.Fatalf("Expected fallback to base state to return correct value, got %x", retrievedValue1)
}
// Test current state access
retrievedValue2 := statedb.GetState(addr2, key2)
if retrievedValue2 != value2 {
t.Fatalf("Expected current state access to return correct value, got %x", retrievedValue2)
}
// Check state properties
if !statedb.IsVerkleTransitionActive() {
t.Fatalf("Expected IsVerkleTransitionActive to return true")
}
if statedb.BaseStateRoot() != baseRoot {
t.Fatalf("Expected BaseStateRoot to return correct root")
}
}