mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-04 12:11:17 +00:00
Wire the NOMT binary merkle trie engine into geth's triedb/state framework. This adds two new packages: - triedb/nomtdb: backend implementing triedb.backend interface, manages flat state persistence in ethdb and delegates trie ops to nomt/db - trie/nomttrie: NomtTrie implementing state.Trie, accumulates LeafOps during block execution and flushes to NOMT engine on Hash()/Commit() Key design choices: - Single flat keyspace: accounts use keccak256(addr), storage uses keccak256(keccak256(addr) || keccak256(slot)) as 256-bit trie paths - OpenStorageTrie returns the account trie itself (no separate tries) - Flat state (account/storage values) stored in ethdb with prefixed keys - NOMT trie stores only hashes; reads delegate to ethdb flat state Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
249 lines
6.8 KiB
Go
249 lines
6.8 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/crypto"
|
|
"github.com/ethereum/go-ethereum/triedb"
|
|
"github.com/ethereum/go-ethereum/triedb/nomtdb"
|
|
"github.com/holiman/uint256"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func newTestBackend(t *testing.T) *nomtdb.Database {
|
|
t.Helper()
|
|
dir := t.TempDir()
|
|
diskdb := rawdb.NewMemoryDatabase()
|
|
config := &triedb.Config{
|
|
NomtDB: &nomtdb.Config{
|
|
DataDir: dir,
|
|
HTCapacity: 1024,
|
|
},
|
|
}
|
|
tdb := triedb.NewDatabase(diskdb, config)
|
|
t.Cleanup(func() { tdb.Close() })
|
|
return tdb.NomtBackend()
|
|
}
|
|
|
|
func TestNewEmptyTrie(t *testing.T) {
|
|
backend := newTestBackend(t)
|
|
tr, err := New(common.Hash{}, backend)
|
|
require.NoError(t, err)
|
|
|
|
// Empty trie should hash to zero (NOMT Terminator).
|
|
root := tr.Hash()
|
|
assert.Equal(t, common.Hash{}, root)
|
|
}
|
|
|
|
func TestUpdateSingleAccount(t *testing.T) {
|
|
backend := newTestBackend(t)
|
|
tr, err := New(common.Hash{}, backend)
|
|
require.NoError(t, err)
|
|
|
|
addr := common.HexToAddress("0x1234567890abcdef1234567890abcdef12345678")
|
|
acc := &types.StateAccount{
|
|
Nonce: 42,
|
|
Balance: uint256.NewInt(1000),
|
|
Root: types.EmptyRootHash,
|
|
CodeHash: types.EmptyCodeHash.Bytes(),
|
|
}
|
|
|
|
require.NoError(t, tr.UpdateAccount(addr, acc, 0))
|
|
root := tr.Hash()
|
|
assert.NotEqual(t, common.Hash{}, root, "root should be non-zero after insert")
|
|
}
|
|
|
|
func TestUpdateTwoAccounts(t *testing.T) {
|
|
backend := newTestBackend(t)
|
|
tr, err := New(common.Hash{}, backend)
|
|
require.NoError(t, err)
|
|
|
|
addr1 := common.HexToAddress("0x1111111111111111111111111111111111111111")
|
|
addr2 := common.HexToAddress("0x2222222222222222222222222222222222222222")
|
|
acc := &types.StateAccount{
|
|
Nonce: 1,
|
|
Balance: uint256.NewInt(100),
|
|
Root: types.EmptyRootHash,
|
|
CodeHash: types.EmptyCodeHash.Bytes(),
|
|
}
|
|
|
|
require.NoError(t, tr.UpdateAccount(addr1, acc, 0))
|
|
require.NoError(t, tr.UpdateAccount(addr2, acc, 0))
|
|
|
|
root := tr.Hash()
|
|
assert.NotEqual(t, common.Hash{}, root)
|
|
}
|
|
|
|
func TestDeterministicRoot(t *testing.T) {
|
|
addr := common.HexToAddress("0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef")
|
|
acc := &types.StateAccount{
|
|
Nonce: 7,
|
|
Balance: uint256.NewInt(999),
|
|
Root: types.EmptyRootHash,
|
|
CodeHash: types.EmptyCodeHash.Bytes(),
|
|
}
|
|
|
|
run := func() common.Hash {
|
|
backend := newTestBackend(t)
|
|
tr, err := New(common.Hash{}, backend)
|
|
require.NoError(t, err)
|
|
require.NoError(t, tr.UpdateAccount(addr, acc, 0))
|
|
return tr.Hash()
|
|
}
|
|
|
|
r1 := run()
|
|
r2 := run()
|
|
assert.Equal(t, r1, r2, "same ops should produce same root")
|
|
}
|
|
|
|
func TestCommitReturnsRoot(t *testing.T) {
|
|
backend := newTestBackend(t)
|
|
tr, err := New(common.Hash{}, backend)
|
|
require.NoError(t, err)
|
|
|
|
addr := common.HexToAddress("0xaaaa")
|
|
acc := &types.StateAccount{
|
|
Nonce: 1,
|
|
Balance: uint256.NewInt(1),
|
|
Root: types.EmptyRootHash,
|
|
CodeHash: types.EmptyCodeHash.Bytes(),
|
|
}
|
|
require.NoError(t, tr.UpdateAccount(addr, acc, 0))
|
|
|
|
root, nodeSet := tr.Commit(false)
|
|
assert.NotEqual(t, common.Hash{}, root)
|
|
assert.NotNil(t, nodeSet)
|
|
}
|
|
|
|
func TestDeleteAccount(t *testing.T) {
|
|
backend := newTestBackend(t)
|
|
tr, err := New(common.Hash{}, backend)
|
|
require.NoError(t, err)
|
|
|
|
addr := common.HexToAddress("0xbbbb")
|
|
acc := &types.StateAccount{
|
|
Nonce: 1,
|
|
Balance: uint256.NewInt(100),
|
|
Root: types.EmptyRootHash,
|
|
CodeHash: types.EmptyCodeHash.Bytes(),
|
|
}
|
|
|
|
require.NoError(t, tr.UpdateAccount(addr, acc, 0))
|
|
root1 := tr.Hash()
|
|
assert.NotEqual(t, common.Hash{}, root1)
|
|
|
|
// DeleteAccount accumulates a nil-value LeafOp. Verify it's queued.
|
|
require.NoError(t, tr.DeleteAccount(addr))
|
|
assert.True(t, tr.dirty)
|
|
assert.Len(t, tr.pending, 1)
|
|
}
|
|
|
|
func TestUpdateStorage(t *testing.T) {
|
|
backend := newTestBackend(t)
|
|
tr, err := New(common.Hash{}, backend)
|
|
require.NoError(t, err)
|
|
|
|
addr := common.HexToAddress("0xcccc")
|
|
slot := common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001")
|
|
value := common.Hex2Bytes("00000000000000000000000000000000000000000000000000000000000000ff")
|
|
|
|
require.NoError(t, tr.UpdateStorage(addr, slot, value))
|
|
root := tr.Hash()
|
|
assert.NotEqual(t, common.Hash{}, root)
|
|
}
|
|
|
|
func TestStorageKeyPathDeterminism(t *testing.T) {
|
|
addr := common.HexToAddress("0xdddd")
|
|
slot := common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001")
|
|
|
|
kp1 := storageKeyPath(addr, slot)
|
|
kp2 := storageKeyPath(addr, slot)
|
|
assert.Equal(t, kp1, kp2, "same inputs should produce same keypath")
|
|
|
|
// Different slot should produce different keypath.
|
|
slot2 := common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002")
|
|
kp3 := storageKeyPath(addr, slot2)
|
|
assert.NotEqual(t, kp1, kp3)
|
|
}
|
|
|
|
func TestAccountKeyPathMatchesCryptoKeccak(t *testing.T) {
|
|
addr := common.HexToAddress("0xeeee")
|
|
expected := crypto.Keccak256Hash(addr.Bytes())
|
|
|
|
backend := newTestBackend(t)
|
|
tr, err := New(common.Hash{}, backend)
|
|
require.NoError(t, err)
|
|
|
|
acc := &types.StateAccount{
|
|
Nonce: 1,
|
|
Balance: uint256.NewInt(1),
|
|
Root: types.EmptyRootHash,
|
|
CodeHash: types.EmptyCodeHash.Bytes(),
|
|
}
|
|
require.NoError(t, tr.UpdateAccount(addr, acc, 0))
|
|
|
|
// Pending op should have keypath = keccak256(addr).
|
|
require.Len(t, tr.pending, 1)
|
|
assert.Equal(t, [32]byte(expected), tr.pending[0].Key)
|
|
|
|
// After Hash(), pending should be flushed.
|
|
tr.Hash()
|
|
assert.Len(t, tr.pending, 0)
|
|
}
|
|
|
|
func TestIsVerkle(t *testing.T) {
|
|
backend := newTestBackend(t)
|
|
tr, err := New(common.Hash{}, backend)
|
|
require.NoError(t, err)
|
|
assert.False(t, tr.IsVerkle())
|
|
}
|
|
|
|
func TestCopy(t *testing.T) {
|
|
backend := newTestBackend(t)
|
|
tr, err := New(common.Hash{}, backend)
|
|
require.NoError(t, err)
|
|
|
|
addr := common.HexToAddress("0xffff")
|
|
acc := &types.StateAccount{
|
|
Nonce: 5,
|
|
Balance: uint256.NewInt(500),
|
|
Root: types.EmptyRootHash,
|
|
CodeHash: types.EmptyCodeHash.Bytes(),
|
|
}
|
|
require.NoError(t, tr.UpdateAccount(addr, acc, 0))
|
|
|
|
cp := tr.Copy()
|
|
assert.Equal(t, len(tr.pending), len(cp.pending))
|
|
assert.Equal(t, tr.root, cp.root)
|
|
assert.Equal(t, tr.dirty, cp.dirty)
|
|
}
|
|
|
|
func TestMultipleHashCalls(t *testing.T) {
|
|
backend := newTestBackend(t)
|
|
tr, err := New(common.Hash{}, backend)
|
|
require.NoError(t, err)
|
|
|
|
addr1 := common.HexToAddress("0x1111")
|
|
addr2 := common.HexToAddress("0x2222")
|
|
acc := &types.StateAccount{
|
|
Nonce: 1,
|
|
Balance: uint256.NewInt(1),
|
|
Root: types.EmptyRootHash,
|
|
CodeHash: types.EmptyCodeHash.Bytes(),
|
|
}
|
|
|
|
// First update + hash.
|
|
require.NoError(t, tr.UpdateAccount(addr1, acc, 0))
|
|
root1 := tr.Hash()
|
|
assert.NotEqual(t, common.Hash{}, root1)
|
|
|
|
// Second update + hash (simulating per-transaction root computation).
|
|
require.NoError(t, tr.UpdateAccount(addr2, acc, 0))
|
|
root2 := tr.Hash()
|
|
assert.NotEqual(t, common.Hash{}, root2)
|
|
assert.NotEqual(t, root1, root2, "root should change after second update")
|
|
}
|