mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-06-08 07:58:40 +00:00
Fix several interacting issues that prevented partial state nodes from syncing and following the chain on bal-devnet-2: 1. Stale pivot deadlock: Replace unconditional pivot suppression with rate-limited advances (2-minute cooldown). This prevents the restart loop bug while allowing recovery when the initial pivot is too stale for peers to serve. 2. Storage root resolution: Add snap-based resolver that queries peers for untracked contracts' storage roots during BAL processing. This lets the computed state root converge toward the header root. 3. SetCanonical for partial state: When the computed root differs from the header root (expected when untracked contracts have unresolved storage roots), check HasState(partialState.Root()) instead of only HasState(block.Root()). Guard against zero root during snap sync. 4. Canonical hash backfill: AdvancePartialHead now writes canonical hashes for all blocks between the pivot and snap head, fixing the "final block not in canonical chain" error caused by InsertReceiptChain skipping blocks whose bodies already exist. 5. Gap block processing: After snap sync completes, process accumulated blocks between the sync head and chain tip using their persisted BALs before entering steady-state chain following. 6. Computed root chaining: Use partialState.Root() (actual computed root) as parentRoot for subsequent blocks, not the header root. This ensures correct trie chaining when computed != header root. Tested end-to-end on bal-devnet-2: snap sync completes, gap blocks processed, canonical head advances at chain tip (~1 block/12s). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1126 lines
38 KiB
Go
1126 lines
38 KiB
Go
// Copyright 2025 The go-ethereum Authors
|
|
// This file is part of the go-ethereum library.
|
|
//
|
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Lesser General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU Lesser General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Lesser General Public License
|
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
package partial
|
|
|
|
import (
|
|
"bytes"
|
|
"testing"
|
|
|
|
"github.com/ethereum/go-ethereum/common"
|
|
"github.com/ethereum/go-ethereum/core/rawdb"
|
|
"github.com/ethereum/go-ethereum/core/types"
|
|
"github.com/ethereum/go-ethereum/core/types/bal"
|
|
"github.com/ethereum/go-ethereum/crypto"
|
|
"github.com/ethereum/go-ethereum/rlp"
|
|
"github.com/ethereum/go-ethereum/trie"
|
|
"github.com/ethereum/go-ethereum/trie/trienode"
|
|
"github.com/ethereum/go-ethereum/triedb"
|
|
"github.com/holiman/uint256"
|
|
)
|
|
|
|
// testBALBuilder is a test helper for constructing BlockAccessLists.
|
|
// It wraps ConstructionBlockAccessList and provides convenience methods
|
|
// matching the test patterns (BalanceChange, NonceChange, StorageWrite, CodeChange).
|
|
type testBALBuilder struct {
|
|
accesses bal.ConstructionBlockAccessList
|
|
}
|
|
|
|
func newTestBALBuilder() *testBALBuilder {
|
|
return &testBALBuilder{
|
|
accesses: make(bal.ConstructionBlockAccessList),
|
|
}
|
|
}
|
|
|
|
func (b *testBALBuilder) ensureAccount(addr common.Address) *bal.ConstructionAccountAccesses {
|
|
if _, ok := b.accesses[addr]; !ok {
|
|
b.accesses[addr] = &bal.ConstructionAccountAccesses{}
|
|
}
|
|
return b.accesses[addr]
|
|
}
|
|
|
|
func (b *testBALBuilder) BalanceChange(txIdx uint16, addr common.Address, balance *uint256.Int) {
|
|
acc := b.ensureAccount(addr)
|
|
if acc.BalanceChanges == nil {
|
|
acc.BalanceChanges = make(map[uint16]*uint256.Int)
|
|
}
|
|
acc.BalanceChanges[txIdx] = balance
|
|
}
|
|
|
|
func (b *testBALBuilder) NonceChange(addr common.Address, txIdx uint16, nonce uint64) {
|
|
acc := b.ensureAccount(addr)
|
|
if acc.NonceChanges == nil {
|
|
acc.NonceChanges = make(map[uint16]uint64)
|
|
}
|
|
acc.NonceChanges[txIdx] = nonce
|
|
}
|
|
|
|
func (b *testBALBuilder) StorageWrite(txIdx uint16, addr common.Address, slot, value common.Hash) {
|
|
acc := b.ensureAccount(addr)
|
|
if acc.StorageWrites == nil {
|
|
acc.StorageWrites = make(map[common.Hash]map[uint16]common.Hash)
|
|
}
|
|
if _, ok := acc.StorageWrites[slot]; !ok {
|
|
acc.StorageWrites[slot] = make(map[uint16]common.Hash)
|
|
}
|
|
acc.StorageWrites[slot][txIdx] = value
|
|
}
|
|
|
|
func (b *testBALBuilder) CodeChange(addr common.Address, txIdx uint16, code []byte) {
|
|
acc := b.ensureAccount(addr)
|
|
if acc.CodeChanges == nil {
|
|
acc.CodeChanges = make(map[uint16]bal.CodeChange)
|
|
}
|
|
acc.CodeChanges[txIdx] = bal.CodeChange{TxIdx: txIdx, Code: code}
|
|
}
|
|
|
|
// Build converts the construction BAL to the encoding format via RLP round-trip.
|
|
func (b *testBALBuilder) Build(t *testing.T) *bal.BlockAccessList {
|
|
t.Helper()
|
|
|
|
var buf bytes.Buffer
|
|
if err := b.accesses.EncodeRLP(&buf); err != nil {
|
|
t.Fatalf("failed to encode BAL: %v", err)
|
|
}
|
|
|
|
var result bal.BlockAccessList
|
|
if err := result.DecodeRLP(rlp.NewStream(bytes.NewReader(buf.Bytes()), 0)); err != nil {
|
|
t.Fatalf("failed to decode BAL: %v", err)
|
|
}
|
|
return &result
|
|
}
|
|
|
|
// setupTestPartialState creates a test partial state with the given tracked contracts.
|
|
func setupTestPartialState(t *testing.T, trackedContracts []common.Address) (*PartialState, *triedb.Database, common.Hash) {
|
|
t.Helper()
|
|
|
|
db := rawdb.NewMemoryDatabase()
|
|
trieDB := triedb.NewDatabase(db, triedb.HashDefaults)
|
|
|
|
filter := NewConfiguredFilter(trackedContracts)
|
|
ps := NewPartialState(db, trieDB, filter, 256)
|
|
|
|
// Create empty state trie
|
|
stateTrie, err := trie.NewStateTrie(trie.StateTrieID(types.EmptyRootHash), trieDB)
|
|
if err != nil {
|
|
t.Fatalf("failed to create state trie: %v", err)
|
|
}
|
|
emptyRoot, _ := stateTrie.Commit(false)
|
|
|
|
return ps, trieDB, emptyRoot
|
|
}
|
|
|
|
// setupTestStateWithAccount creates a state trie with a single account.
|
|
func setupTestStateWithAccount(t *testing.T, trieDB *triedb.Database, addr common.Address, account *types.StateAccount) common.Hash {
|
|
t.Helper()
|
|
|
|
stateTrie, err := trie.NewStateTrie(trie.StateTrieID(types.EmptyRootHash), trieDB)
|
|
if err != nil {
|
|
t.Fatalf("failed to create state trie: %v", err)
|
|
}
|
|
|
|
if err := stateTrie.UpdateAccount(addr, account, 0); err != nil {
|
|
t.Fatalf("failed to update account: %v", err)
|
|
}
|
|
|
|
root, nodeSet := stateTrie.Commit(false)
|
|
if nodeSet != nil {
|
|
merged := trienode.NewWithNodeSet(nodeSet)
|
|
if err := trieDB.Update(root, types.EmptyRootHash, 0, merged, nil); err != nil {
|
|
t.Fatalf("failed to update trieDB: %v", err)
|
|
}
|
|
if err := trieDB.Commit(root, false); err != nil {
|
|
t.Fatalf("failed to commit trieDB: %v", err)
|
|
}
|
|
}
|
|
|
|
return root
|
|
}
|
|
|
|
func TestApplyBALAndComputeRoot_EmptyBAL(t *testing.T) {
|
|
ps, _, emptyRoot := setupTestPartialState(t, nil)
|
|
|
|
// Apply empty BAL
|
|
emptyBAL := bal.BlockAccessList{}
|
|
accessList := &emptyBAL
|
|
|
|
newRoot, err := ps.ApplyBALAndComputeRoot(emptyRoot, common.Hash{}, accessList)
|
|
if err != nil {
|
|
t.Fatalf("failed to apply empty BAL: %v", err)
|
|
}
|
|
|
|
// Empty BAL should result in same root
|
|
if newRoot != emptyRoot {
|
|
t.Errorf("expected empty root %x, got %x", emptyRoot, newRoot)
|
|
}
|
|
}
|
|
|
|
func TestApplyBALAndComputeRoot_BalanceChange(t *testing.T) {
|
|
addr := common.HexToAddress("0x1234567890123456789012345678901234567890")
|
|
ps, trieDB, _ := setupTestPartialState(t, []common.Address{addr})
|
|
|
|
// Create initial account
|
|
initialBalance := uint256.NewInt(1000)
|
|
initialAccount := &types.StateAccount{
|
|
Nonce: 0,
|
|
Balance: initialBalance,
|
|
Root: types.EmptyRootHash,
|
|
CodeHash: types.EmptyCodeHash.Bytes(),
|
|
}
|
|
parentRoot := setupTestStateWithAccount(t, trieDB, addr, initialAccount)
|
|
|
|
// Create BAL with balance change using ConstructionBlockAccessList
|
|
newBalance := uint256.NewInt(2000)
|
|
cbal := newTestBALBuilder()
|
|
cbal.BalanceChange(0, addr, newBalance)
|
|
|
|
accessList := cbal.Build(t)
|
|
|
|
newRoot, err := ps.ApplyBALAndComputeRoot(parentRoot, common.Hash{}, accessList)
|
|
if err != nil {
|
|
t.Fatalf("failed to apply BAL: %v", err)
|
|
}
|
|
|
|
// Verify new root is different
|
|
if newRoot == parentRoot {
|
|
t.Error("expected different root after balance change")
|
|
}
|
|
|
|
// Verify the account balance was updated
|
|
newTrie, err := trie.NewStateTrie(trie.StateTrieID(newRoot), trieDB)
|
|
if err != nil {
|
|
t.Fatalf("failed to open new trie: %v", err)
|
|
}
|
|
updatedAccount, err := newTrie.GetAccount(addr)
|
|
if err != nil {
|
|
t.Fatalf("failed to get updated account: %v", err)
|
|
}
|
|
if updatedAccount.Balance.Cmp(newBalance) != 0 {
|
|
t.Errorf("expected balance %v, got %v", newBalance, updatedAccount.Balance)
|
|
}
|
|
}
|
|
|
|
func TestApplyBALAndComputeRoot_NonceChange(t *testing.T) {
|
|
addr := common.HexToAddress("0x1234567890123456789012345678901234567890")
|
|
ps, trieDB, _ := setupTestPartialState(t, []common.Address{addr})
|
|
|
|
// Create initial account
|
|
initialAccount := &types.StateAccount{
|
|
Nonce: 5,
|
|
Balance: uint256.NewInt(1000),
|
|
Root: types.EmptyRootHash,
|
|
CodeHash: types.EmptyCodeHash.Bytes(),
|
|
}
|
|
parentRoot := setupTestStateWithAccount(t, trieDB, addr, initialAccount)
|
|
|
|
// Create BAL with nonce change
|
|
cbal := newTestBALBuilder()
|
|
cbal.NonceChange(addr, 0, 6)
|
|
|
|
accessList := cbal.Build(t)
|
|
|
|
newRoot, err := ps.ApplyBALAndComputeRoot(parentRoot, common.Hash{}, accessList)
|
|
if err != nil {
|
|
t.Fatalf("failed to apply BAL: %v", err)
|
|
}
|
|
|
|
// Verify the account nonce was updated
|
|
newTrie, err := trie.NewStateTrie(trie.StateTrieID(newRoot), trieDB)
|
|
if err != nil {
|
|
t.Fatalf("failed to open new trie: %v", err)
|
|
}
|
|
updatedAccount, err := newTrie.GetAccount(addr)
|
|
if err != nil {
|
|
t.Fatalf("failed to get updated account: %v", err)
|
|
}
|
|
if updatedAccount.Nonce != 6 {
|
|
t.Errorf("expected nonce 6, got %d", updatedAccount.Nonce)
|
|
}
|
|
}
|
|
|
|
func TestApplyBALAndComputeRoot_StorageChange(t *testing.T) {
|
|
addr := common.HexToAddress("0x1234567890123456789012345678901234567890")
|
|
ps, trieDB, _ := setupTestPartialState(t, []common.Address{addr})
|
|
|
|
// Create initial account (tracked contract)
|
|
initialAccount := &types.StateAccount{
|
|
Nonce: 0,
|
|
Balance: uint256.NewInt(0),
|
|
Root: types.EmptyRootHash,
|
|
CodeHash: types.EmptyCodeHash.Bytes(),
|
|
}
|
|
parentRoot := setupTestStateWithAccount(t, trieDB, addr, initialAccount)
|
|
|
|
// Create BAL with storage change
|
|
slot := common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000001")
|
|
value := common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000042")
|
|
|
|
cbal := newTestBALBuilder()
|
|
cbal.StorageWrite(0, addr, slot, value)
|
|
|
|
accessList := cbal.Build(t)
|
|
|
|
newRoot, err := ps.ApplyBALAndComputeRoot(parentRoot, common.Hash{}, accessList)
|
|
if err != nil {
|
|
t.Fatalf("failed to apply BAL: %v", err)
|
|
}
|
|
|
|
// Verify new root is different (storage changed)
|
|
if newRoot == parentRoot {
|
|
t.Error("expected different root after storage change")
|
|
}
|
|
|
|
// Verify the account storage root changed
|
|
newTrie, err := trie.NewStateTrie(trie.StateTrieID(newRoot), trieDB)
|
|
if err != nil {
|
|
t.Fatalf("failed to open new trie: %v", err)
|
|
}
|
|
updatedAccount, err := newTrie.GetAccount(addr)
|
|
if err != nil {
|
|
t.Fatalf("failed to get updated account: %v", err)
|
|
}
|
|
if updatedAccount.Root == types.EmptyRootHash {
|
|
t.Error("expected non-empty storage root after storage change")
|
|
}
|
|
}
|
|
|
|
func TestApplyBALAndComputeRoot_UntrackedContractStorageIgnored(t *testing.T) {
|
|
trackedAddr := common.HexToAddress("0x1111111111111111111111111111111111111111")
|
|
untrackedAddr := common.HexToAddress("0x2222222222222222222222222222222222222222")
|
|
|
|
// Only track one contract
|
|
ps, trieDB, _ := setupTestPartialState(t, []common.Address{trackedAddr})
|
|
|
|
// Create initial accounts
|
|
initialAccount := &types.StateAccount{
|
|
Nonce: 0,
|
|
Balance: uint256.NewInt(1000),
|
|
Root: types.EmptyRootHash,
|
|
CodeHash: types.EmptyCodeHash.Bytes(),
|
|
}
|
|
|
|
// Add both accounts to state
|
|
stateTrie, _ := trie.NewStateTrie(trie.StateTrieID(types.EmptyRootHash), trieDB)
|
|
stateTrie.UpdateAccount(trackedAddr, initialAccount, 0)
|
|
stateTrie.UpdateAccount(untrackedAddr, initialAccount, 0)
|
|
parentRoot, nodeSet := stateTrie.Commit(false)
|
|
if nodeSet != nil {
|
|
merged := trienode.NewWithNodeSet(nodeSet)
|
|
trieDB.Update(parentRoot, types.EmptyRootHash, 0, merged, nil)
|
|
trieDB.Commit(parentRoot, false)
|
|
}
|
|
|
|
// Create BAL with storage changes for both contracts
|
|
slot := common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000001")
|
|
value := common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000042")
|
|
|
|
cbal := newTestBALBuilder()
|
|
cbal.StorageWrite(0, trackedAddr, slot, value)
|
|
cbal.StorageWrite(0, untrackedAddr, slot, value)
|
|
|
|
accessList := cbal.Build(t)
|
|
|
|
newRoot, err := ps.ApplyBALAndComputeRoot(parentRoot, common.Hash{}, accessList)
|
|
if err != nil {
|
|
t.Fatalf("failed to apply BAL: %v", err)
|
|
}
|
|
|
|
// Verify tracked contract has storage
|
|
newTrie, _ := trie.NewStateTrie(trie.StateTrieID(newRoot), trieDB)
|
|
trackedAccount, _ := newTrie.GetAccount(trackedAddr)
|
|
if trackedAccount.Root == types.EmptyRootHash {
|
|
t.Error("tracked contract should have storage root")
|
|
}
|
|
|
|
// Verify untracked contract has NO storage (storage was ignored)
|
|
untrackedAccount, _ := newTrie.GetAccount(untrackedAddr)
|
|
if untrackedAccount.Root != types.EmptyRootHash {
|
|
t.Error("untracked contract should have empty storage root")
|
|
}
|
|
}
|
|
|
|
func TestApplyBALAndComputeRoot_NewAccount(t *testing.T) {
|
|
addr := common.HexToAddress("0x1234567890123456789012345678901234567890")
|
|
ps, trieDB, emptyRoot := setupTestPartialState(t, []common.Address{addr})
|
|
|
|
// Create BAL that creates a new account
|
|
balance := uint256.NewInt(1000)
|
|
|
|
cbal := newTestBALBuilder()
|
|
cbal.BalanceChange(0, addr, balance)
|
|
cbal.NonceChange(addr, 0, 1)
|
|
|
|
accessList := cbal.Build(t)
|
|
|
|
newRoot, err := ps.ApplyBALAndComputeRoot(emptyRoot, common.Hash{}, accessList)
|
|
if err != nil {
|
|
t.Fatalf("failed to apply BAL: %v", err)
|
|
}
|
|
|
|
// Verify new account was created
|
|
newTrie, _ := trie.NewStateTrie(trie.StateTrieID(newRoot), trieDB)
|
|
account, err := newTrie.GetAccount(addr)
|
|
if err != nil {
|
|
t.Fatalf("failed to get new account: %v", err)
|
|
}
|
|
if account == nil {
|
|
t.Fatal("expected account to exist")
|
|
}
|
|
if account.Balance.Cmp(balance) != 0 {
|
|
t.Errorf("expected balance %v, got %v", balance, account.Balance)
|
|
}
|
|
if account.Nonce != 1 {
|
|
t.Errorf("expected nonce 1, got %d", account.Nonce)
|
|
}
|
|
}
|
|
|
|
func TestApplyBALAndComputeRoot_CodeChange(t *testing.T) {
|
|
addr := common.HexToAddress("0x1234567890123456789012345678901234567890")
|
|
db := rawdb.NewMemoryDatabase()
|
|
trieDB := triedb.NewDatabase(db, triedb.HashDefaults)
|
|
filter := NewConfiguredFilter([]common.Address{addr})
|
|
ps := NewPartialState(db, trieDB, filter, 256)
|
|
|
|
// Create initial account
|
|
initialAccount := &types.StateAccount{
|
|
Nonce: 0,
|
|
Balance: uint256.NewInt(0),
|
|
Root: types.EmptyRootHash,
|
|
CodeHash: types.EmptyCodeHash.Bytes(),
|
|
}
|
|
parentRoot := setupTestStateWithAccount(t, trieDB, addr, initialAccount)
|
|
|
|
// Create BAL with code deployment
|
|
code := []byte{0x60, 0x60, 0x60, 0x40, 0x52} // Some bytecode
|
|
codeHash := crypto.Keccak256Hash(code)
|
|
|
|
cbal := newTestBALBuilder()
|
|
cbal.CodeChange(addr, 0, code)
|
|
|
|
accessList := cbal.Build(t)
|
|
|
|
newRoot, err := ps.ApplyBALAndComputeRoot(parentRoot, common.Hash{}, accessList)
|
|
if err != nil {
|
|
t.Fatalf("failed to apply BAL: %v", err)
|
|
}
|
|
|
|
// Verify code hash was updated
|
|
newTrie, _ := trie.NewStateTrie(trie.StateTrieID(newRoot), trieDB)
|
|
account, _ := newTrie.GetAccount(addr)
|
|
if common.BytesToHash(account.CodeHash) != codeHash {
|
|
t.Errorf("expected code hash %x, got %x", codeHash, account.CodeHash)
|
|
}
|
|
|
|
// Verify code was stored (tracked contract)
|
|
storedCode := rawdb.ReadCode(db, codeHash)
|
|
if storedCode == nil {
|
|
t.Error("expected code to be stored for tracked contract")
|
|
}
|
|
}
|
|
|
|
func TestApplyBALAndComputeRoot_MultipleTransactions(t *testing.T) {
|
|
addr := common.HexToAddress("0x1234567890123456789012345678901234567890")
|
|
ps, trieDB, _ := setupTestPartialState(t, []common.Address{addr})
|
|
|
|
// Create initial account
|
|
initialAccount := &types.StateAccount{
|
|
Nonce: 0,
|
|
Balance: uint256.NewInt(1000),
|
|
Root: types.EmptyRootHash,
|
|
CodeHash: types.EmptyCodeHash.Bytes(),
|
|
}
|
|
parentRoot := setupTestStateWithAccount(t, trieDB, addr, initialAccount)
|
|
|
|
// Create BAL with multiple balance/nonce changes (only last should apply)
|
|
balance1 := uint256.NewInt(500)
|
|
balance2 := uint256.NewInt(2000)
|
|
balance3 := uint256.NewInt(1500) // Final balance
|
|
|
|
cbal := newTestBALBuilder()
|
|
cbal.BalanceChange(0, addr, balance1)
|
|
cbal.BalanceChange(1, addr, balance2)
|
|
cbal.BalanceChange(2, addr, balance3) // Final
|
|
cbal.NonceChange(addr, 0, 1)
|
|
cbal.NonceChange(addr, 1, 2)
|
|
cbal.NonceChange(addr, 2, 3) // Final
|
|
|
|
accessList := cbal.Build(t)
|
|
|
|
newRoot, err := ps.ApplyBALAndComputeRoot(parentRoot, common.Hash{}, accessList)
|
|
if err != nil {
|
|
t.Fatalf("failed to apply BAL: %v", err)
|
|
}
|
|
|
|
// Verify only final values are applied
|
|
newTrie, _ := trie.NewStateTrie(trie.StateTrieID(newRoot), trieDB)
|
|
account, _ := newTrie.GetAccount(addr)
|
|
if account.Balance.Cmp(balance3) != 0 {
|
|
t.Errorf("expected final balance %v, got %v", balance3, account.Balance)
|
|
}
|
|
if account.Nonce != 3 {
|
|
t.Errorf("expected final nonce 3, got %d", account.Nonce)
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// Task 1: Edge Case Tests for ApplyBALAndComputeRoot
|
|
// ============================================================================
|
|
|
|
// TestApplyBALAndComputeRoot_StorageDeletion tests deleting a storage slot by writing zero value.
|
|
func TestApplyBALAndComputeRoot_StorageDeletion(t *testing.T) {
|
|
addr := common.HexToAddress("0x1234567890123456789012345678901234567890")
|
|
db := rawdb.NewMemoryDatabase()
|
|
trieDB := triedb.NewDatabase(db, triedb.HashDefaults)
|
|
filter := NewConfiguredFilter([]common.Address{addr})
|
|
ps := NewPartialState(db, trieDB, filter, 256)
|
|
|
|
// Create initial account with storage
|
|
slot := common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000001")
|
|
initialValue := common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000042")
|
|
|
|
// First, create account and add storage
|
|
stateTrie, _ := trie.NewStateTrie(trie.StateTrieID(types.EmptyRootHash), trieDB)
|
|
initialAccount := &types.StateAccount{
|
|
Nonce: 0,
|
|
Balance: uint256.NewInt(1000),
|
|
Root: types.EmptyRootHash,
|
|
CodeHash: types.EmptyCodeHash.Bytes(),
|
|
}
|
|
|
|
// Create storage trie with initial value
|
|
addrHash := crypto.Keccak256Hash(addr.Bytes())
|
|
storageTrie, _ := trie.NewStateTrie(trie.StorageTrieID(types.EmptyRootHash, addrHash, types.EmptyRootHash), trieDB)
|
|
storageTrie.UpdateStorage(addr, slot.Bytes(), initialValue.Bytes())
|
|
storageRoot, storageNodes := storageTrie.Commit(false)
|
|
|
|
initialAccount.Root = storageRoot
|
|
stateTrie.UpdateAccount(addr, initialAccount, 0)
|
|
parentRoot, accountNodes := stateTrie.Commit(false)
|
|
|
|
// Commit storage and account nodes
|
|
allNodes := trienode.NewMergedNodeSet()
|
|
if storageNodes != nil {
|
|
allNodes.Merge(storageNodes)
|
|
}
|
|
if accountNodes != nil {
|
|
allNodes.Merge(accountNodes)
|
|
}
|
|
trieDB.Update(parentRoot, types.EmptyRootHash, 0, allNodes, nil)
|
|
trieDB.Commit(parentRoot, false)
|
|
|
|
// Create BAL that deletes the storage slot (write zero value)
|
|
cbal := newTestBALBuilder()
|
|
cbal.StorageWrite(0, addr, slot, common.Hash{}) // Zero value = delete
|
|
|
|
accessList := cbal.Build(t)
|
|
|
|
newRoot, err := ps.ApplyBALAndComputeRoot(parentRoot, common.Hash{}, accessList)
|
|
if err != nil {
|
|
t.Fatalf("failed to apply BAL: %v", err)
|
|
}
|
|
|
|
// Verify storage was deleted (root should be empty)
|
|
newTrie, _ := trie.NewStateTrie(trie.StateTrieID(newRoot), trieDB)
|
|
account, _ := newTrie.GetAccount(addr)
|
|
if account.Root != types.EmptyRootHash {
|
|
t.Errorf("expected empty storage root after deletion, got %x", account.Root)
|
|
}
|
|
}
|
|
|
|
// TestApplyBALAndComputeRoot_MultipleStorageWritesSameSlot tests last-write-wins for storage.
|
|
func TestApplyBALAndComputeRoot_MultipleStorageWritesSameSlot(t *testing.T) {
|
|
addr := common.HexToAddress("0x1234567890123456789012345678901234567890")
|
|
ps, trieDB, _ := setupTestPartialState(t, []common.Address{addr})
|
|
|
|
// Create initial account
|
|
initialAccount := &types.StateAccount{
|
|
Nonce: 0,
|
|
Balance: uint256.NewInt(1000),
|
|
Root: types.EmptyRootHash,
|
|
CodeHash: types.EmptyCodeHash.Bytes(),
|
|
}
|
|
parentRoot := setupTestStateWithAccount(t, trieDB, addr, initialAccount)
|
|
|
|
// Create BAL with multiple writes to same slot
|
|
slot := common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000001")
|
|
value1 := common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000001")
|
|
value2 := common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000002")
|
|
value3 := common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000003") // Final
|
|
|
|
cbal := newTestBALBuilder()
|
|
cbal.StorageWrite(0, addr, slot, value1)
|
|
cbal.StorageWrite(1, addr, slot, value2)
|
|
cbal.StorageWrite(2, addr, slot, value3) // Final value
|
|
|
|
accessList := cbal.Build(t)
|
|
|
|
newRoot, err := ps.ApplyBALAndComputeRoot(parentRoot, common.Hash{}, accessList)
|
|
if err != nil {
|
|
t.Fatalf("failed to apply BAL: %v", err)
|
|
}
|
|
|
|
// Verify only final value is stored
|
|
newTrie, _ := trie.NewStateTrie(trie.StateTrieID(newRoot), trieDB)
|
|
account, _ := newTrie.GetAccount(addr)
|
|
|
|
// Open storage trie and check value
|
|
addrHash := crypto.Keccak256Hash(addr.Bytes())
|
|
storageTrie, err := trie.NewStateTrie(trie.StorageTrieID(newRoot, addrHash, account.Root), trieDB)
|
|
if err != nil {
|
|
t.Fatalf("failed to open storage trie: %v", err)
|
|
}
|
|
|
|
storedValue, err := storageTrie.GetStorage(addr, slot.Bytes())
|
|
if err != nil {
|
|
t.Fatalf("failed to get storage: %v", err)
|
|
}
|
|
if common.BytesToHash(storedValue) != value3 {
|
|
t.Errorf("expected final value %x, got %x", value3, storedValue)
|
|
}
|
|
}
|
|
|
|
// TestApplyBALAndComputeRoot_AccountDeletion_EIP161 tests EIP-161 account deletion.
|
|
// An account should be deleted if: existed before, modified, and now empty.
|
|
func TestApplyBALAndComputeRoot_AccountDeletion_EIP161(t *testing.T) {
|
|
addr := common.HexToAddress("0x1234567890123456789012345678901234567890")
|
|
ps, trieDB, _ := setupTestPartialState(t, []common.Address{addr})
|
|
|
|
// Create initial account with balance
|
|
initialAccount := &types.StateAccount{
|
|
Nonce: 0,
|
|
Balance: uint256.NewInt(1000),
|
|
Root: types.EmptyRootHash,
|
|
CodeHash: types.EmptyCodeHash.Bytes(),
|
|
}
|
|
parentRoot := setupTestStateWithAccount(t, trieDB, addr, initialAccount)
|
|
|
|
// Create BAL that empties the account
|
|
cbal := newTestBALBuilder()
|
|
cbal.BalanceChange(0, addr, uint256.NewInt(0)) // Zero balance
|
|
|
|
accessList := cbal.Build(t)
|
|
|
|
newRoot, err := ps.ApplyBALAndComputeRoot(parentRoot, common.Hash{}, accessList)
|
|
if err != nil {
|
|
t.Fatalf("failed to apply BAL: %v", err)
|
|
}
|
|
|
|
// Verify account was deleted (EIP-161: empty account should be removed)
|
|
newTrie, _ := trie.NewStateTrie(trie.StateTrieID(newRoot), trieDB)
|
|
account, err := newTrie.GetAccount(addr)
|
|
if err != nil {
|
|
t.Fatalf("failed to get account: %v", err)
|
|
}
|
|
if account != nil {
|
|
t.Errorf("expected account to be deleted (EIP-161), but it still exists with balance %v", account.Balance)
|
|
}
|
|
}
|
|
|
|
// TestApplyBALAndComputeRoot_NeverExistedEmptyAccount tests that empty accounts that never existed
|
|
// are not added to the trie.
|
|
func TestApplyBALAndComputeRoot_NeverExistedEmptyAccount(t *testing.T) {
|
|
addr := common.HexToAddress("0x1234567890123456789012345678901234567890")
|
|
ps, trieDB, emptyRoot := setupTestPartialState(t, []common.Address{addr})
|
|
|
|
// Create BAL that "touches" an account but leaves it empty
|
|
// This simulates an account that receives 0 balance and sends 0 balance
|
|
cbal := newTestBALBuilder()
|
|
cbal.BalanceChange(0, addr, uint256.NewInt(0)) // Zero balance on never-existed account
|
|
|
|
accessList := cbal.Build(t)
|
|
|
|
newRoot, err := ps.ApplyBALAndComputeRoot(emptyRoot, common.Hash{}, accessList)
|
|
if err != nil {
|
|
t.Fatalf("failed to apply BAL: %v", err)
|
|
}
|
|
|
|
// Root should be the same as empty root (no account added)
|
|
if newRoot != emptyRoot {
|
|
t.Errorf("expected empty root (no account added), got different root")
|
|
}
|
|
|
|
// Verify account does not exist
|
|
newTrie, _ := trie.NewStateTrie(trie.StateTrieID(newRoot), trieDB)
|
|
account, _ := newTrie.GetAccount(addr)
|
|
if account != nil {
|
|
t.Errorf("expected no account (never existed + empty), but found one")
|
|
}
|
|
}
|
|
|
|
// TestApplyBALAndComputeRoot_CodeChangeUntracked tests that code hash is updated for untracked
|
|
// contracts but the code bytes are NOT stored.
|
|
func TestApplyBALAndComputeRoot_CodeChangeUntracked(t *testing.T) {
|
|
trackedAddr := common.HexToAddress("0x1111111111111111111111111111111111111111")
|
|
untrackedAddr := common.HexToAddress("0x2222222222222222222222222222222222222222")
|
|
|
|
db := rawdb.NewMemoryDatabase()
|
|
trieDB := triedb.NewDatabase(db, triedb.HashDefaults)
|
|
// Only track one contract
|
|
filter := NewConfiguredFilter([]common.Address{trackedAddr})
|
|
ps := NewPartialState(db, trieDB, filter, 256)
|
|
|
|
// Create initial accounts
|
|
initialAccount := &types.StateAccount{
|
|
Nonce: 0,
|
|
Balance: uint256.NewInt(1000),
|
|
Root: types.EmptyRootHash,
|
|
CodeHash: types.EmptyCodeHash.Bytes(),
|
|
}
|
|
|
|
stateTrie, _ := trie.NewStateTrie(trie.StateTrieID(types.EmptyRootHash), trieDB)
|
|
stateTrie.UpdateAccount(trackedAddr, initialAccount, 0)
|
|
stateTrie.UpdateAccount(untrackedAddr, initialAccount, 0)
|
|
parentRoot, nodeSet := stateTrie.Commit(false)
|
|
if nodeSet != nil {
|
|
merged := trienode.NewWithNodeSet(nodeSet)
|
|
trieDB.Update(parentRoot, types.EmptyRootHash, 0, merged, nil)
|
|
trieDB.Commit(parentRoot, false)
|
|
}
|
|
|
|
// Create BAL with code changes for both
|
|
code := []byte{0x60, 0x60, 0x60, 0x40, 0x52}
|
|
codeHash := crypto.Keccak256Hash(code)
|
|
|
|
cbal := newTestBALBuilder()
|
|
cbal.CodeChange(trackedAddr, 0, code)
|
|
cbal.CodeChange(untrackedAddr, 0, code)
|
|
|
|
accessList := cbal.Build(t)
|
|
|
|
newRoot, err := ps.ApplyBALAndComputeRoot(parentRoot, common.Hash{}, accessList)
|
|
if err != nil {
|
|
t.Fatalf("failed to apply BAL: %v", err)
|
|
}
|
|
|
|
// Verify both accounts have updated code hash
|
|
newTrie, _ := trie.NewStateTrie(trie.StateTrieID(newRoot), trieDB)
|
|
|
|
trackedAccount, _ := newTrie.GetAccount(trackedAddr)
|
|
if common.BytesToHash(trackedAccount.CodeHash) != codeHash {
|
|
t.Errorf("tracked contract should have updated code hash")
|
|
}
|
|
|
|
untrackedAccount, _ := newTrie.GetAccount(untrackedAddr)
|
|
if common.BytesToHash(untrackedAccount.CodeHash) != codeHash {
|
|
t.Errorf("untracked contract should have updated code hash")
|
|
}
|
|
|
|
// Verify code is stored for tracked contract
|
|
storedCode := rawdb.ReadCode(db, codeHash)
|
|
if storedCode == nil {
|
|
t.Error("code should be stored for tracked contract")
|
|
}
|
|
|
|
// Note: We can't directly test that code is NOT stored for untracked because
|
|
// both contracts use the same code hash, and it's stored once for the tracked one.
|
|
// The key invariant is that the code hash is correct for both.
|
|
}
|
|
|
|
// TestApplyBALAndComputeRoot_MixedChanges tests applying multiple types of changes to one account.
|
|
func TestApplyBALAndComputeRoot_MixedChanges(t *testing.T) {
|
|
addr := common.HexToAddress("0x1234567890123456789012345678901234567890")
|
|
db := rawdb.NewMemoryDatabase()
|
|
trieDB := triedb.NewDatabase(db, triedb.HashDefaults)
|
|
filter := NewConfiguredFilter([]common.Address{addr})
|
|
ps := NewPartialState(db, trieDB, filter, 256)
|
|
|
|
// Create initial account
|
|
initialAccount := &types.StateAccount{
|
|
Nonce: 5,
|
|
Balance: uint256.NewInt(1000),
|
|
Root: types.EmptyRootHash,
|
|
CodeHash: types.EmptyCodeHash.Bytes(),
|
|
}
|
|
parentRoot := setupTestStateWithAccount(t, trieDB, addr, initialAccount)
|
|
|
|
// Create BAL with balance, nonce, code, and storage changes
|
|
newBalance := uint256.NewInt(2000)
|
|
newNonce := uint64(10)
|
|
code := []byte{0x60, 0x60, 0x60, 0x40, 0x52}
|
|
codeHash := crypto.Keccak256Hash(code)
|
|
slot := common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000001")
|
|
value := common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000042")
|
|
|
|
cbal := newTestBALBuilder()
|
|
cbal.BalanceChange(0, addr, newBalance)
|
|
cbal.NonceChange(addr, 0, newNonce)
|
|
cbal.CodeChange(addr, 0, code)
|
|
cbal.StorageWrite(0, addr, slot, value)
|
|
|
|
accessList := cbal.Build(t)
|
|
|
|
newRoot, err := ps.ApplyBALAndComputeRoot(parentRoot, common.Hash{}, accessList)
|
|
if err != nil {
|
|
t.Fatalf("failed to apply BAL: %v", err)
|
|
}
|
|
|
|
// Verify all changes applied
|
|
newTrie, _ := trie.NewStateTrie(trie.StateTrieID(newRoot), trieDB)
|
|
account, _ := newTrie.GetAccount(addr)
|
|
|
|
if account.Balance.Cmp(newBalance) != 0 {
|
|
t.Errorf("expected balance %v, got %v", newBalance, account.Balance)
|
|
}
|
|
if account.Nonce != newNonce {
|
|
t.Errorf("expected nonce %d, got %d", newNonce, account.Nonce)
|
|
}
|
|
if common.BytesToHash(account.CodeHash) != codeHash {
|
|
t.Errorf("expected code hash %x, got %x", codeHash, account.CodeHash)
|
|
}
|
|
if account.Root == types.EmptyRootHash {
|
|
t.Error("expected non-empty storage root")
|
|
}
|
|
|
|
// Verify storage value
|
|
addrHash := crypto.Keccak256Hash(addr.Bytes())
|
|
storageTrie, _ := trie.NewStateTrie(trie.StorageTrieID(newRoot, addrHash, account.Root), trieDB)
|
|
storedValue, _ := storageTrie.GetStorage(addr, slot.Bytes())
|
|
if common.BytesToHash(storedValue) != value {
|
|
t.Errorf("expected storage value %x, got %x", value, storedValue)
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// Task 3: Error Path Tests
|
|
// ============================================================================
|
|
|
|
// TestApplyBALAndComputeRoot_ErrorInvalidParentRoot tests error handling for invalid parent root.
|
|
func TestApplyBALAndComputeRoot_ErrorInvalidParentRoot(t *testing.T) {
|
|
addr := common.HexToAddress("0x1234567890123456789012345678901234567890")
|
|
ps, _, _ := setupTestPartialState(t, []common.Address{addr})
|
|
|
|
// Use a non-existent root
|
|
invalidRoot := common.HexToHash("0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef")
|
|
|
|
cbal := newTestBALBuilder()
|
|
cbal.BalanceChange(0, addr, uint256.NewInt(1000))
|
|
accessList := cbal.Build(t)
|
|
|
|
_, err := ps.ApplyBALAndComputeRoot(invalidRoot, common.Hash{}, accessList)
|
|
if err == nil {
|
|
t.Fatal("expected error for invalid parent root, got nil")
|
|
}
|
|
// Error should mention trie opening failure
|
|
if !bytes.Contains([]byte(err.Error()), []byte("failed to open state trie")) {
|
|
t.Errorf("expected 'failed to open state trie' error, got: %v", err)
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// Task 4: isEmptyAccount Tests
|
|
// ============================================================================
|
|
|
|
// TestIsEmptyAccount tests the EIP-161 empty account detection logic.
|
|
func TestIsEmptyAccount(t *testing.T) {
|
|
ps, _, _ := setupTestPartialState(t, nil)
|
|
|
|
tests := []struct {
|
|
name string
|
|
account *types.StateAccount
|
|
expected bool
|
|
}{
|
|
{
|
|
name: "completely empty account",
|
|
account: &types.StateAccount{
|
|
Nonce: 0,
|
|
Balance: uint256.NewInt(0),
|
|
Root: types.EmptyRootHash,
|
|
CodeHash: types.EmptyCodeHash.Bytes(),
|
|
},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "non-zero balance",
|
|
account: &types.StateAccount{
|
|
Nonce: 0,
|
|
Balance: uint256.NewInt(1),
|
|
Root: types.EmptyRootHash,
|
|
CodeHash: types.EmptyCodeHash.Bytes(),
|
|
},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "non-zero nonce",
|
|
account: &types.StateAccount{
|
|
Nonce: 1,
|
|
Balance: uint256.NewInt(0),
|
|
Root: types.EmptyRootHash,
|
|
CodeHash: types.EmptyCodeHash.Bytes(),
|
|
},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "non-empty storage root",
|
|
account: &types.StateAccount{
|
|
Nonce: 0,
|
|
Balance: uint256.NewInt(0),
|
|
Root: common.HexToHash("0x1234567890123456789012345678901234567890123456789012345678901234"),
|
|
CodeHash: types.EmptyCodeHash.Bytes(),
|
|
},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "non-empty code hash",
|
|
account: &types.StateAccount{
|
|
Nonce: 0,
|
|
Balance: uint256.NewInt(0),
|
|
Root: types.EmptyRootHash,
|
|
CodeHash: common.HexToHash("0x1234567890123456789012345678901234567890123456789012345678901234").Bytes(),
|
|
},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "large balance (uint256)",
|
|
account: &types.StateAccount{
|
|
Nonce: 0,
|
|
Balance: uint256.MustFromHex("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"),
|
|
Root: types.EmptyRootHash,
|
|
CodeHash: types.EmptyCodeHash.Bytes(),
|
|
},
|
|
expected: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := ps.isEmptyAccount(tt.account)
|
|
if result != tt.expected {
|
|
t.Errorf("isEmptyAccount() = %v, expected %v", result, tt.expected)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// Task 2: buildStateSet Tests (indirect verification)
|
|
// ============================================================================
|
|
|
|
// TestBuildStateSet_AccountModification verifies that modified accounts are correctly
|
|
// tracked in the StateSet by checking the resulting state.
|
|
func TestBuildStateSet_AccountModification(t *testing.T) {
|
|
addr := common.HexToAddress("0x1234567890123456789012345678901234567890")
|
|
ps, trieDB, _ := setupTestPartialState(t, []common.Address{addr})
|
|
|
|
// Create initial account
|
|
initialAccount := &types.StateAccount{
|
|
Nonce: 5,
|
|
Balance: uint256.NewInt(1000),
|
|
Root: types.EmptyRootHash,
|
|
CodeHash: types.EmptyCodeHash.Bytes(),
|
|
}
|
|
parentRoot := setupTestStateWithAccount(t, trieDB, addr, initialAccount)
|
|
|
|
// Apply balance change
|
|
cbal := newTestBALBuilder()
|
|
cbal.BalanceChange(0, addr, uint256.NewInt(2000))
|
|
accessList := cbal.Build(t)
|
|
|
|
newRoot, err := ps.ApplyBALAndComputeRoot(parentRoot, common.Hash{}, accessList)
|
|
if err != nil {
|
|
t.Fatalf("failed to apply BAL: %v", err)
|
|
}
|
|
|
|
// Verify the state was correctly updated (indirectly tests buildStateSet)
|
|
newTrie, _ := trie.NewStateTrie(trie.StateTrieID(newRoot), trieDB)
|
|
account, _ := newTrie.GetAccount(addr)
|
|
|
|
// The nonce should be preserved (not modified)
|
|
if account.Nonce != 5 {
|
|
t.Errorf("nonce should be preserved: expected 5, got %d", account.Nonce)
|
|
}
|
|
// Balance should be updated
|
|
if account.Balance.Cmp(uint256.NewInt(2000)) != 0 {
|
|
t.Errorf("balance should be updated: expected 2000, got %v", account.Balance)
|
|
}
|
|
}
|
|
|
|
// TestBuildStateSet_StorageRLPEncoding verifies that storage values are correctly
|
|
// RLP-encoded in the StateSet.
|
|
func TestBuildStateSet_StorageRLPEncoding(t *testing.T) {
|
|
addr := common.HexToAddress("0x1234567890123456789012345678901234567890")
|
|
db := rawdb.NewMemoryDatabase()
|
|
trieDB := triedb.NewDatabase(db, triedb.HashDefaults)
|
|
filter := NewConfiguredFilter([]common.Address{addr})
|
|
ps := NewPartialState(db, trieDB, filter, 256)
|
|
|
|
// Create initial account
|
|
initialAccount := &types.StateAccount{
|
|
Nonce: 0,
|
|
Balance: uint256.NewInt(1000),
|
|
Root: types.EmptyRootHash,
|
|
CodeHash: types.EmptyCodeHash.Bytes(),
|
|
}
|
|
parentRoot := setupTestStateWithAccount(t, trieDB, addr, initialAccount)
|
|
|
|
// Write storage value
|
|
slot := common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000001")
|
|
value := common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000042")
|
|
|
|
cbal := newTestBALBuilder()
|
|
cbal.StorageWrite(0, addr, slot, value)
|
|
accessList := cbal.Build(t)
|
|
|
|
newRoot, err := ps.ApplyBALAndComputeRoot(parentRoot, common.Hash{}, accessList)
|
|
if err != nil {
|
|
t.Fatalf("failed to apply BAL: %v", err)
|
|
}
|
|
|
|
// Verify storage is readable
|
|
newTrie, _ := trie.NewStateTrie(trie.StateTrieID(newRoot), trieDB)
|
|
account, _ := newTrie.GetAccount(addr)
|
|
|
|
addrHash := crypto.Keccak256Hash(addr.Bytes())
|
|
storageTrie, err := trie.NewStateTrie(trie.StorageTrieID(newRoot, addrHash, account.Root), trieDB)
|
|
if err != nil {
|
|
t.Fatalf("failed to open storage trie: %v", err)
|
|
}
|
|
|
|
storedValue, err := storageTrie.GetStorage(addr, slot.Bytes())
|
|
if err != nil {
|
|
t.Fatalf("failed to get storage: %v", err)
|
|
}
|
|
|
|
if common.BytesToHash(storedValue) != value {
|
|
t.Errorf("storage value mismatch: expected %x, got %x", value, storedValue)
|
|
}
|
|
}
|
|
|
|
// TestBuildStateSet_OriginTracking verifies that account origins are tracked correctly
|
|
// for PathDB compatibility.
|
|
func TestBuildStateSet_OriginTracking(t *testing.T) {
|
|
addr := common.HexToAddress("0x1234567890123456789012345678901234567890")
|
|
ps, trieDB, _ := setupTestPartialState(t, []common.Address{addr})
|
|
|
|
// Create initial account with specific values
|
|
initialAccount := &types.StateAccount{
|
|
Nonce: 10,
|
|
Balance: uint256.NewInt(5000),
|
|
Root: types.EmptyRootHash,
|
|
CodeHash: types.EmptyCodeHash.Bytes(),
|
|
}
|
|
parentRoot := setupTestStateWithAccount(t, trieDB, addr, initialAccount)
|
|
|
|
// Modify the account
|
|
cbal := newTestBALBuilder()
|
|
cbal.BalanceChange(0, addr, uint256.NewInt(6000))
|
|
cbal.NonceChange(addr, 0, 11)
|
|
accessList := cbal.Build(t)
|
|
|
|
newRoot, err := ps.ApplyBALAndComputeRoot(parentRoot, common.Hash{}, accessList)
|
|
if err != nil {
|
|
t.Fatalf("failed to apply BAL: %v", err)
|
|
}
|
|
|
|
// Verify the new state is correct (origin tracking happens internally)
|
|
newTrie, _ := trie.NewStateTrie(trie.StateTrieID(newRoot), trieDB)
|
|
account, _ := newTrie.GetAccount(addr)
|
|
|
|
if account.Nonce != 11 {
|
|
t.Errorf("expected nonce 11, got %d", account.Nonce)
|
|
}
|
|
if account.Balance.Cmp(uint256.NewInt(6000)) != 0 {
|
|
t.Errorf("expected balance 6000, got %v", account.Balance)
|
|
}
|
|
|
|
// The fact that this works with PathDB verifies origin tracking is correct
|
|
// (PathDB requires origins for diff computation)
|
|
}
|
|
|
|
// TestApplyBALAndComputeRoot_MultipleAccountTypes tests processing multiple accounts with
|
|
// different modification patterns in one block.
|
|
func TestApplyBALAndComputeRoot_MultipleAccountTypes(t *testing.T) {
|
|
addr1 := common.HexToAddress("0x1111111111111111111111111111111111111111") // Balance only
|
|
addr2 := common.HexToAddress("0x2222222222222222222222222222222222222222") // Storage only
|
|
addr3 := common.HexToAddress("0x3333333333333333333333333333333333333333") // New account
|
|
|
|
db := rawdb.NewMemoryDatabase()
|
|
trieDB := triedb.NewDatabase(db, triedb.HashDefaults)
|
|
filter := NewConfiguredFilter([]common.Address{addr1, addr2, addr3})
|
|
ps := NewPartialState(db, trieDB, filter, 256)
|
|
|
|
// Create initial accounts for addr1 and addr2
|
|
initialAccount1 := &types.StateAccount{
|
|
Nonce: 0,
|
|
Balance: uint256.NewInt(1000),
|
|
Root: types.EmptyRootHash,
|
|
CodeHash: types.EmptyCodeHash.Bytes(),
|
|
}
|
|
initialAccount2 := &types.StateAccount{
|
|
Nonce: 5,
|
|
Balance: uint256.NewInt(500),
|
|
Root: types.EmptyRootHash,
|
|
CodeHash: types.EmptyCodeHash.Bytes(),
|
|
}
|
|
|
|
stateTrie, _ := trie.NewStateTrie(trie.StateTrieID(types.EmptyRootHash), trieDB)
|
|
stateTrie.UpdateAccount(addr1, initialAccount1, 0)
|
|
stateTrie.UpdateAccount(addr2, initialAccount2, 0)
|
|
parentRoot, nodeSet := stateTrie.Commit(false)
|
|
if nodeSet != nil {
|
|
merged := trienode.NewWithNodeSet(nodeSet)
|
|
trieDB.Update(parentRoot, types.EmptyRootHash, 0, merged, nil)
|
|
trieDB.Commit(parentRoot, false)
|
|
}
|
|
|
|
// Create BAL with different changes for each account
|
|
cbal := newTestBALBuilder()
|
|
|
|
// addr1: balance change
|
|
cbal.BalanceChange(0, addr1, uint256.NewInt(2000))
|
|
|
|
// addr2: storage write
|
|
slot := common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000001")
|
|
value := common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000042")
|
|
cbal.StorageWrite(0, addr2, slot, value)
|
|
|
|
// addr3: new account
|
|
cbal.BalanceChange(0, addr3, uint256.NewInt(3000))
|
|
cbal.NonceChange(addr3, 0, 1)
|
|
|
|
accessList := cbal.Build(t)
|
|
|
|
newRoot, err := ps.ApplyBALAndComputeRoot(parentRoot, common.Hash{}, accessList)
|
|
if err != nil {
|
|
t.Fatalf("failed to apply BAL: %v", err)
|
|
}
|
|
|
|
// Verify all accounts
|
|
newTrie, _ := trie.NewStateTrie(trie.StateTrieID(newRoot), trieDB)
|
|
|
|
// addr1: balance changed
|
|
acc1, _ := newTrie.GetAccount(addr1)
|
|
if acc1.Balance.Cmp(uint256.NewInt(2000)) != 0 {
|
|
t.Errorf("addr1: expected balance 2000, got %v", acc1.Balance)
|
|
}
|
|
|
|
// addr2: storage changed
|
|
acc2, _ := newTrie.GetAccount(addr2)
|
|
if acc2.Root == types.EmptyRootHash {
|
|
t.Error("addr2: expected non-empty storage root")
|
|
}
|
|
|
|
// addr3: new account created
|
|
acc3, _ := newTrie.GetAccount(addr3)
|
|
if acc3 == nil {
|
|
t.Fatal("addr3: expected account to exist")
|
|
}
|
|
if acc3.Balance.Cmp(uint256.NewInt(3000)) != 0 {
|
|
t.Errorf("addr3: expected balance 3000, got %v", acc3.Balance)
|
|
}
|
|
if acc3.Nonce != 1 {
|
|
t.Errorf("addr3: expected nonce 1, got %d", acc3.Nonce)
|
|
}
|
|
}
|