mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-05 04:31:16 +00:00
350 lines
9.2 KiB
Go
350 lines
9.2 KiB
Go
package nomttrie
|
|
|
|
import (
|
|
"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/nomt/core"
|
|
"github.com/ethereum/go-ethereum/trie/bintrie"
|
|
"github.com/ethereum/go-ethereum/triedb/nomtdb"
|
|
"github.com/holiman/uint256"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// newTestTrie creates a NomtTrie backed by an in-memory ethdb.
|
|
func newTestTrie(t *testing.T) *NomtTrie {
|
|
t.Helper()
|
|
diskdb := rawdb.NewMemoryDatabase()
|
|
backend := nomtdb.New(diskdb, nil)
|
|
t.Cleanup(func() { backend.Close() })
|
|
|
|
tr, err := New(common.Hash{}, backend)
|
|
require.NoError(t, err)
|
|
return tr
|
|
}
|
|
|
|
func TestUpdateAndGetAccount(t *testing.T) {
|
|
tr := newTestTrie(t)
|
|
|
|
addr := common.HexToAddress("0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef")
|
|
acc := &types.StateAccount{
|
|
Nonce: 42,
|
|
Balance: uint256.NewInt(1_000_000),
|
|
CodeHash: common.FromHex("c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"),
|
|
}
|
|
|
|
require.NoError(t, tr.UpdateAccount(addr, acc, 0))
|
|
|
|
// Flush to flat state + page tree.
|
|
root := tr.Hash()
|
|
assert.NotEqual(t, common.Hash{}, root, "root should be non-zero after update")
|
|
|
|
// Read back from flat state.
|
|
got, err := tr.GetAccount(addr)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, got)
|
|
|
|
assert.Equal(t, acc.Nonce, got.Nonce)
|
|
assert.Equal(t, acc.Balance.Uint64(), got.Balance.Uint64())
|
|
assert.Equal(t, acc.CodeHash, got.CodeHash)
|
|
}
|
|
|
|
func TestGetAccountNonExistent(t *testing.T) {
|
|
tr := newTestTrie(t)
|
|
addr := common.HexToAddress("0x1111111111111111111111111111111111111111")
|
|
|
|
got, err := tr.GetAccount(addr)
|
|
require.NoError(t, err)
|
|
assert.Nil(t, got, "nonexistent account should return nil")
|
|
}
|
|
|
|
func TestUpdateAndGetStorage(t *testing.T) {
|
|
tr := newTestTrie(t)
|
|
|
|
addr := common.HexToAddress("0xaaaa")
|
|
slot := common.Hex2Bytes(
|
|
"0000000000000000000000000000000000000000000000000000000000000001",
|
|
)
|
|
value := common.Hex2Bytes("00000000000000000000000000000000000000000000000000000000000000ff")
|
|
|
|
require.NoError(t, tr.UpdateStorage(addr, slot, value))
|
|
tr.Hash()
|
|
|
|
got, err := tr.GetStorage(addr, slot)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, value, got)
|
|
}
|
|
|
|
func TestGetStorageNonExistent(t *testing.T) {
|
|
tr := newTestTrie(t)
|
|
addr := common.HexToAddress("0xbbbb")
|
|
slot := make([]byte, 32)
|
|
|
|
got, err := tr.GetStorage(addr, slot)
|
|
require.NoError(t, err)
|
|
assert.Nil(t, got)
|
|
}
|
|
|
|
func TestDeleteStorage(t *testing.T) {
|
|
tr := newTestTrie(t)
|
|
|
|
addr := common.HexToAddress("0xcccc")
|
|
slot := make([]byte, 32)
|
|
slot[31] = 1
|
|
value := make([]byte, 32)
|
|
value[31] = 0x42
|
|
|
|
// Write then flush.
|
|
require.NoError(t, tr.UpdateStorage(addr, slot, value))
|
|
tr.Hash()
|
|
|
|
// Delete then flush.
|
|
require.NoError(t, tr.DeleteStorage(addr, slot))
|
|
tr.Hash()
|
|
|
|
// Value should now be 32 zero bytes (not nil).
|
|
got, err := tr.GetStorage(addr, slot)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, make([]byte, bintrie.HashSize), got)
|
|
}
|
|
|
|
func TestDeleteAccountIsNoOp(t *testing.T) {
|
|
tr := newTestTrie(t)
|
|
addr := common.HexToAddress("0xdddd")
|
|
|
|
// DeleteAccount should never error.
|
|
require.NoError(t, tr.DeleteAccount(addr))
|
|
}
|
|
|
|
func TestUpdateContractCode(t *testing.T) {
|
|
tr := newTestTrie(t)
|
|
|
|
addr := common.HexToAddress("0xeeee")
|
|
code := make([]byte, 100) // 100 bytes of code
|
|
for i := range code {
|
|
code[i] = byte(i)
|
|
}
|
|
|
|
require.NoError(t, tr.UpdateContractCode(addr, common.Hash{}, code))
|
|
|
|
// Should have queued pending updates: ceil(100/31) = 4 chunks.
|
|
expectedChunks := (len(code) + bintrie.StemSize - 1) / bintrie.StemSize
|
|
codeUpdates := 0
|
|
for _, u := range tr.pending {
|
|
// Code chunks start at suffix derived from offset 128+.
|
|
codeUpdates++
|
|
_ = u
|
|
}
|
|
assert.Equal(t, expectedChunks, codeUpdates)
|
|
|
|
// Flush and verify root changes.
|
|
root := tr.Hash()
|
|
assert.NotEqual(t, common.Hash{}, root)
|
|
}
|
|
|
|
func TestHashIdempotent(t *testing.T) {
|
|
tr := newTestTrie(t)
|
|
|
|
addr := common.HexToAddress("0x1234")
|
|
acc := &types.StateAccount{
|
|
Nonce: 1,
|
|
Balance: uint256.NewInt(100),
|
|
CodeHash: make([]byte, 32),
|
|
}
|
|
|
|
require.NoError(t, tr.UpdateAccount(addr, acc, 0))
|
|
|
|
root1 := tr.Hash()
|
|
root2 := tr.Hash()
|
|
|
|
assert.Equal(t, root1, root2, "Hash() should be idempotent")
|
|
assert.False(t, tr.dirty, "dirty flag should be cleared after Hash()")
|
|
assert.Empty(t, tr.pending, "pending should be empty after Hash()")
|
|
}
|
|
|
|
func TestHashEmptyTrieIsZero(t *testing.T) {
|
|
tr := newTestTrie(t)
|
|
root := tr.Hash()
|
|
assert.Equal(t, common.Hash{}, root, "empty trie root should be zero")
|
|
}
|
|
|
|
func TestCommitReturnsRootAndNodeSet(t *testing.T) {
|
|
tr := newTestTrie(t)
|
|
|
|
addr := common.HexToAddress("0x5678")
|
|
acc := &types.StateAccount{
|
|
Nonce: 5,
|
|
Balance: uint256.NewInt(999),
|
|
CodeHash: make([]byte, 32),
|
|
}
|
|
require.NoError(t, tr.UpdateAccount(addr, acc, 0))
|
|
|
|
root, nodeset := tr.Commit(false)
|
|
assert.NotEqual(t, common.Hash{}, root)
|
|
assert.NotNil(t, nodeset)
|
|
}
|
|
|
|
func TestMultipleAccountsSameBlock(t *testing.T) {
|
|
tr := newTestTrie(t)
|
|
|
|
addrs := []common.Address{
|
|
common.HexToAddress("0x1111111111111111111111111111111111111111"),
|
|
common.HexToAddress("0x2222222222222222222222222222222222222222"),
|
|
common.HexToAddress("0x3333333333333333333333333333333333333333"),
|
|
}
|
|
|
|
for i, addr := range addrs {
|
|
acc := &types.StateAccount{
|
|
Nonce: uint64(i + 1),
|
|
Balance: uint256.NewInt(uint64((i + 1) * 1000)),
|
|
CodeHash: make([]byte, 32),
|
|
}
|
|
require.NoError(t, tr.UpdateAccount(addr, acc, 0))
|
|
}
|
|
|
|
root := tr.Hash()
|
|
assert.NotEqual(t, common.Hash{}, root)
|
|
|
|
// Verify all accounts can be read back.
|
|
for i, addr := range addrs {
|
|
got, err := tr.GetAccount(addr)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, got)
|
|
assert.Equal(t, uint64(i+1), got.Nonce)
|
|
}
|
|
}
|
|
|
|
func TestSequentialBlocks(t *testing.T) {
|
|
tr := newTestTrie(t)
|
|
|
|
addr := common.HexToAddress("0xabcdef0000000000000000000000000000000000")
|
|
|
|
// Block 1: create account.
|
|
acc := &types.StateAccount{
|
|
Nonce: 1,
|
|
Balance: uint256.NewInt(100),
|
|
CodeHash: make([]byte, 32),
|
|
}
|
|
require.NoError(t, tr.UpdateAccount(addr, acc, 0))
|
|
root1 := tr.Hash()
|
|
assert.NotEqual(t, common.Hash{}, root1)
|
|
|
|
// Block 2: update balance.
|
|
acc.Nonce = 2
|
|
acc.Balance = uint256.NewInt(200)
|
|
require.NoError(t, tr.UpdateAccount(addr, acc, 0))
|
|
root2 := tr.Hash()
|
|
assert.NotEqual(t, root1, root2, "root should change after update")
|
|
|
|
// Verify updated account.
|
|
got, err := tr.GetAccount(addr)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, got)
|
|
assert.Equal(t, uint64(2), got.Nonce)
|
|
assert.Equal(t, uint64(200), got.Balance.Uint64())
|
|
}
|
|
|
|
func TestAccountWithStorageAndCode(t *testing.T) {
|
|
tr := newTestTrie(t)
|
|
addr := common.HexToAddress("0xffff")
|
|
|
|
// Update account.
|
|
acc := &types.StateAccount{
|
|
Nonce: 10,
|
|
Balance: uint256.NewInt(5000),
|
|
CodeHash: common.FromHex("c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"),
|
|
}
|
|
require.NoError(t, tr.UpdateAccount(addr, acc, 64))
|
|
|
|
// Update storage.
|
|
slot := make([]byte, 32)
|
|
slot[31] = 1
|
|
val := make([]byte, 32)
|
|
val[31] = 0x42
|
|
require.NoError(t, tr.UpdateStorage(addr, slot, val))
|
|
|
|
// Update code (small contract).
|
|
code := []byte{0x60, 0x00, 0x60, 0x00, 0xFD} // PUSH0 PUSH0 REVERT
|
|
require.NoError(t, tr.UpdateContractCode(addr, common.Hash{}, code))
|
|
|
|
root := tr.Hash()
|
|
assert.NotEqual(t, common.Hash{}, root)
|
|
|
|
// Verify account.
|
|
got, err := tr.GetAccount(addr)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, got)
|
|
assert.Equal(t, uint64(10), got.Nonce)
|
|
|
|
// Verify storage.
|
|
gotVal, err := tr.GetStorage(addr, slot)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, val, gotVal)
|
|
}
|
|
|
|
func TestCopyTrieIsIndependent(t *testing.T) {
|
|
tr := newTestTrie(t)
|
|
|
|
addr := common.HexToAddress("0x9999")
|
|
acc := &types.StateAccount{
|
|
Nonce: 1,
|
|
Balance: uint256.NewInt(100),
|
|
CodeHash: make([]byte, 32),
|
|
}
|
|
require.NoError(t, tr.UpdateAccount(addr, acc, 0))
|
|
|
|
// Copy before flushing.
|
|
tr2 := tr.Copy()
|
|
assert.Equal(t, len(tr.pending), len(tr2.pending))
|
|
|
|
// Flush original.
|
|
root1 := tr.Hash()
|
|
assert.NotEqual(t, common.Hash{}, root1)
|
|
assert.Empty(t, tr.pending)
|
|
|
|
// Copy should still have pending updates.
|
|
assert.NotEmpty(t, tr2.pending)
|
|
assert.True(t, tr2.dirty)
|
|
}
|
|
|
|
func TestIsVerkle(t *testing.T) {
|
|
tr := newTestTrie(t)
|
|
assert.True(t, tr.IsVerkle())
|
|
}
|
|
|
|
func TestHashProducesCorrectStemHash(t *testing.T) {
|
|
// Verify that a single-account trie produces a root that matches
|
|
// manual stem hash computation.
|
|
tr := newTestTrie(t)
|
|
|
|
addr := common.HexToAddress("0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")
|
|
acc := &types.StateAccount{
|
|
Nonce: 7,
|
|
Balance: uint256.NewInt(42),
|
|
CodeHash: make([]byte, 32),
|
|
}
|
|
codeLen := 0
|
|
|
|
require.NoError(t, tr.UpdateAccount(addr, acc, codeLen))
|
|
root := tr.Hash()
|
|
|
|
// Reproduce the expected root manually.
|
|
stem := accountStem(addr)
|
|
basicData := packBasicData(acc, codeLen)
|
|
codeHashVal := make([]byte, bintrie.HashSize)
|
|
copy(codeHashVal, acc.CodeHash)
|
|
|
|
var values [core.StemNodeWidth][]byte
|
|
values[bintrie.BasicDataLeafKey] = basicData[:]
|
|
values[bintrie.CodeHashLeafKey] = codeHashVal
|
|
stemHash := core.HashStem(stem, values)
|
|
|
|
// A single-stem trie's canonical root equals the stem hash directly,
|
|
// matching bintrie's behavior (StemNode hash IS the root).
|
|
assert.NotEqual(t, common.Hash{}, root)
|
|
assert.Equal(t, common.Hash(stemHash), root,
|
|
"single-stem trie root should equal the stem hash")
|
|
}
|