diff --git a/core/blockchain.go b/core/blockchain.go index d56996dadb..8157b8cd57 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -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 } diff --git a/core/blockchain_verkle_test.go b/core/blockchain_verkle_test.go new file mode 100644 index 0000000000..5763eab88f --- /dev/null +++ b/core/blockchain_verkle_test.go @@ -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: ¶ms.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) + } +} \ No newline at end of file diff --git a/core/state/statedb.go b/core/state/statedb.go index e3f5b9e1a0..bc9970102c 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -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) +} \ No newline at end of file diff --git a/core/state/verkle_transition.go b/core/state/verkle_transition.go new file mode 100644 index 0000000000..46526691c5 --- /dev/null +++ b/core/state/verkle_transition.go @@ -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 +} \ No newline at end of file diff --git a/core/state/verkle_transition_test.go b/core/state/verkle_transition_test.go new file mode 100644 index 0000000000..33db55079d --- /dev/null +++ b/core/state/verkle_transition_test.go @@ -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") + } +} \ No newline at end of file