mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-03 03:31:14 +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>
226 lines
6.9 KiB
Go
226 lines
6.9 KiB
Go
// Package nomttrie implements a state.Trie backed by the NOMT binary merkle
|
|
// trie engine. It accumulates leaf operations during block execution and
|
|
// commits them to the NOMT page-based storage on Hash()/Commit().
|
|
//
|
|
// Read operations (GetAccount, GetStorage) delegate to geth's ethdb flat state
|
|
// since NOMT's trie stores only hashes, not actual values.
|
|
package nomttrie
|
|
|
|
import (
|
|
"github.com/ethereum/go-ethereum/common"
|
|
"github.com/ethereum/go-ethereum/core/types"
|
|
"github.com/ethereum/go-ethereum/crypto"
|
|
"github.com/ethereum/go-ethereum/ethdb"
|
|
"github.com/ethereum/go-ethereum/nomt/core"
|
|
"github.com/ethereum/go-ethereum/nomt/db"
|
|
"github.com/ethereum/go-ethereum/trie"
|
|
"github.com/ethereum/go-ethereum/trie/trienode"
|
|
"github.com/ethereum/go-ethereum/triedb/nomtdb"
|
|
)
|
|
|
|
// NomtTrie implements the state.Trie interface using NOMT's page-based binary
|
|
// merkle trie. It accumulates changes as LeafOps during block execution and
|
|
// flushes them to the NOMT engine on Hash()/Commit().
|
|
type NomtTrie struct {
|
|
nomtDB *db.DB // NOMT trie engine (page storage + walker)
|
|
backend *nomtdb.Database // NOMT triedb backend (for flat state access)
|
|
root common.Hash // current trie root as common.Hash
|
|
pending []core.LeafOp // accumulated leaf operations
|
|
dirty bool // whether pending ops exist
|
|
}
|
|
|
|
// New creates a new NomtTrie. The root parameter is the current state root.
|
|
// If root is empty or the zero hash, the trie starts empty.
|
|
func New(root common.Hash, backend *nomtdb.Database) (*NomtTrie, error) {
|
|
return &NomtTrie{
|
|
nomtDB: backend.NomtDB(),
|
|
backend: backend,
|
|
root: root,
|
|
pending: make([]core.LeafOp, 0, 64),
|
|
}, nil
|
|
}
|
|
|
|
// GetKey returns the sha3 preimage of a hashed key. NOMT doesn't maintain
|
|
// preimage mappings; returns the key as-is.
|
|
func (t *NomtTrie) GetKey(key []byte) []byte {
|
|
return key
|
|
}
|
|
|
|
// GetAccount reads an account from flat state storage (ethdb).
|
|
// NOMT's trie stores only hashes, so actual data comes from flat KV.
|
|
func (t *NomtTrie) GetAccount(address common.Address) (*types.StateAccount, error) {
|
|
data, err := t.backend.DiskDB().Get(nomtdb.NomtAccountKey(crypto.Keccak256Hash(address.Bytes())))
|
|
if err != nil {
|
|
return nil, nil // not found
|
|
}
|
|
if len(data) == 0 {
|
|
return nil, nil
|
|
}
|
|
return types.FullAccount(data)
|
|
}
|
|
|
|
// PrefetchAccount is a no-op for NOMT — flat state reads are fast by design.
|
|
func (t *NomtTrie) PrefetchAccount(addresses []common.Address) error {
|
|
return nil
|
|
}
|
|
|
|
// GetStorage reads a storage slot from flat state storage (ethdb).
|
|
func (t *NomtTrie) GetStorage(addr common.Address, key []byte) ([]byte, error) {
|
|
addrHash := crypto.Keccak256Hash(addr.Bytes())
|
|
slotHash := crypto.Keccak256Hash(key)
|
|
data, err := t.backend.DiskDB().Get(nomtdb.NomtStorageKey(addrHash, slotHash))
|
|
if err != nil {
|
|
return nil, nil // not found
|
|
}
|
|
return data, nil
|
|
}
|
|
|
|
// PrefetchStorage is a no-op for NOMT.
|
|
func (t *NomtTrie) PrefetchStorage(addr common.Address, keys [][]byte) error {
|
|
return nil
|
|
}
|
|
|
|
// UpdateAccount encodes the account and adds a leaf op to the pending batch.
|
|
// The trie keypath is keccak256(address), value hash is keccak256(slimRLP).
|
|
func (t *NomtTrie) UpdateAccount(address common.Address, account *types.StateAccount, codeLen int) error {
|
|
slimData := types.SlimAccountRLP(*account)
|
|
keyPath := crypto.Keccak256Hash(address.Bytes())
|
|
valueHash := crypto.Keccak256Hash(slimData)
|
|
|
|
var kp core.KeyPath
|
|
copy(kp[:], keyPath[:])
|
|
var vh core.ValueHash
|
|
copy(vh[:], valueHash[:])
|
|
|
|
t.pending = append(t.pending, core.LeafOp{
|
|
Key: kp,
|
|
Value: &vh,
|
|
})
|
|
t.dirty = true
|
|
return nil
|
|
}
|
|
|
|
// UpdateStorage adds a storage update leaf op to the pending batch.
|
|
// The trie keypath is keccak256(keccak256(address) || keccak256(slot)),
|
|
// value hash is keccak256(value).
|
|
func (t *NomtTrie) UpdateStorage(addr common.Address, key, value []byte) error {
|
|
kp := storageKeyPath(addr, key)
|
|
|
|
if len(value) == 0 {
|
|
// Empty value = delete.
|
|
t.pending = append(t.pending, core.LeafOp{
|
|
Key: kp,
|
|
Value: nil,
|
|
})
|
|
} else {
|
|
valueHash := crypto.Keccak256Hash(value)
|
|
var vh core.ValueHash
|
|
copy(vh[:], valueHash[:])
|
|
t.pending = append(t.pending, core.LeafOp{
|
|
Key: kp,
|
|
Value: &vh,
|
|
})
|
|
}
|
|
t.dirty = true
|
|
return nil
|
|
}
|
|
|
|
// DeleteAccount adds a delete leaf op for the account.
|
|
func (t *NomtTrie) DeleteAccount(address common.Address) error {
|
|
keyPath := crypto.Keccak256Hash(address.Bytes())
|
|
var kp core.KeyPath
|
|
copy(kp[:], keyPath[:])
|
|
|
|
t.pending = append(t.pending, core.LeafOp{
|
|
Key: kp,
|
|
Value: nil,
|
|
})
|
|
t.dirty = true
|
|
return nil
|
|
}
|
|
|
|
// DeleteStorage adds a delete leaf op for a storage slot.
|
|
func (t *NomtTrie) DeleteStorage(addr common.Address, key []byte) error {
|
|
kp := storageKeyPath(addr, key)
|
|
t.pending = append(t.pending, core.LeafOp{
|
|
Key: kp,
|
|
Value: nil,
|
|
})
|
|
t.dirty = true
|
|
return nil
|
|
}
|
|
|
|
// UpdateContractCode is a no-op for NOMT — code is stored in ethdb by the
|
|
// state layer, not in the trie.
|
|
func (t *NomtTrie) UpdateContractCode(address common.Address, codeHash common.Hash, code []byte) error {
|
|
return nil
|
|
}
|
|
|
|
// Hash returns the current root hash. If there are pending operations, they
|
|
// are flushed to the NOMT engine first.
|
|
func (t *NomtTrie) Hash() common.Hash {
|
|
if !t.dirty || len(t.pending) == 0 {
|
|
return t.root
|
|
}
|
|
newRoot, err := t.nomtDB.Update(t.pending)
|
|
if err != nil {
|
|
// On error, return current root without updating.
|
|
return t.root
|
|
}
|
|
t.root = common.Hash(newRoot)
|
|
t.pending = t.pending[:0]
|
|
t.dirty = false
|
|
return t.root
|
|
}
|
|
|
|
// Commit flushes pending operations and returns the root hash.
|
|
// The returned NodeSet is empty since NOMT uses page-based storage, not
|
|
// individual node persistence.
|
|
func (t *NomtTrie) Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet) {
|
|
root := t.Hash()
|
|
return root, trienode.NewNodeSet(common.Hash{})
|
|
}
|
|
|
|
// Witness returns accessed trie nodes. NOMT doesn't track witnesses yet.
|
|
func (t *NomtTrie) Witness() map[string][]byte {
|
|
return nil
|
|
}
|
|
|
|
// NodeIterator returns an iterator over trie nodes. Not yet implemented.
|
|
func (t *NomtTrie) NodeIterator(startKey []byte) (trie.NodeIterator, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
// Prove constructs a merkle proof. Not yet implemented.
|
|
func (t *NomtTrie) Prove(key []byte, proofDb ethdb.KeyValueWriter) error {
|
|
return nil
|
|
}
|
|
|
|
// IsVerkle returns false — NOMT is a binary merkle trie, not verkle.
|
|
func (t *NomtTrie) IsVerkle() bool {
|
|
return false
|
|
}
|
|
|
|
// Copy creates a deep copy of the trie.
|
|
func (t *NomtTrie) Copy() *NomtTrie {
|
|
pending := make([]core.LeafOp, len(t.pending))
|
|
copy(pending, t.pending)
|
|
return &NomtTrie{
|
|
nomtDB: t.nomtDB,
|
|
backend: t.backend,
|
|
root: t.root,
|
|
pending: pending,
|
|
dirty: t.dirty,
|
|
}
|
|
}
|
|
|
|
// storageKeyPath computes the NOMT keypath for a storage slot.
|
|
// Format: keccak256(keccak256(address) || keccak256(slot))
|
|
func storageKeyPath(addr common.Address, slot []byte) core.KeyPath {
|
|
addrHash := crypto.Keccak256Hash(addr.Bytes())
|
|
slotHash := crypto.Keccak256Hash(slot)
|
|
combined := crypto.Keccak256Hash(addrHash.Bytes(), slotHash.Bytes())
|
|
var kp core.KeyPath
|
|
copy(kp[:], combined[:])
|
|
return kp
|
|
}
|