Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(trie): implement new trie architecture with separated node types #2355

Open
wants to merge 54 commits into
base: weiihann/improve-state-trie
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
d3b74fb
add bytes benchmark
weiihann Dec 13, 2024
4ec4030
add Rsh test
weiihann Dec 14, 2024
b545a38
add Truncate
weiihann Dec 14, 2024
f113492
all tests passed
weiihann Dec 17, 2024
5644207
improve comments
weiihann Dec 18, 2024
b7f5f49
minor chore
weiihann Dec 18, 2024
fbace00
improvements
weiihann Dec 19, 2024
894c540
Implement trie nodes
weiihann Dec 26, 2024
2245bb4
Update() works on TrieD
weiihann Dec 30, 2024
f50a7cf
add docs
weiihann Dec 31, 2024
dc7c63e
Add hasher
weiihann Jan 2, 2025
d358c9e
add tracer
weiihann Jan 7, 2025
71b26f4
Add package `trienode`
weiihann Jan 9, 2025
bf97a43
Add collector
weiihann Jan 9, 2025
7575430
Add Trie.Commit
weiihann Jan 9, 2025
0049380
add Clear() to OrderedSet
weiihann Jan 15, 2025
cbcb01a
implement triedb
weiihann Jan 15, 2025
f0cf8ac
rename trie/utils to trie/trieutils
weiihann Jan 15, 2025
3dbfb0b
minor changes on trienode
weiihann Jan 15, 2025
b6b99fd
add trie id
weiihann Jan 15, 2025
dcce3ad
...a bunch of changes
weiihann Jan 15, 2025
409ef00
bitarray changes
weiihann Jan 17, 2025
782068d
bitarray changes
weiihann Jan 20, 2025
4cbdaec
at this point range proof tests pass 50%
weiihann Jan 20, 2025
bbee633
still failing
weiihann Jan 21, 2025
696fc9f
pass base test but got nil dereference bug
weiihann Jan 21, 2025
efd5d4c
fix invalid non existent proof
weiihann Jan 21, 2025
4139b53
fix hasRightElement
weiihann Jan 21, 2025
ed96278
create TrieDB interface
weiihann Jan 21, 2025
d1b22cc
range proof test cases all pass
weiihann Jan 21, 2025
9640f21
hasRightElement fix edgeNode case
weiihann Jan 21, 2025
ab47579
remove test
weiihann Jan 22, 2025
dc6bee0
fix node encoding and add tests
weiihann Jan 22, 2025
161c41a
everything pass!!!!!
weiihann Jan 22, 2025
2eddacb
linter
weiihann Jan 23, 2025
69b4056
add comments
weiihann Jan 23, 2025
910bf32
fix rebase
weiihann Jan 23, 2025
c7247dd
remove proof_test changes
weiihann Jan 23, 2025
a542135
add bitarray_test.go
weiihann Jan 23, 2025
b15073f
linter
weiihann Jan 23, 2025
c708732
add comments
weiihann Jan 23, 2025
e6353f9
Remove leading zero bytes from bit array
weiihann Feb 6, 2025
f6959e9
comments
weiihann Feb 12, 2025
a9bbc83
Fix encoding bug
weiihann Feb 17, 2025
76f64df
make fields and interface public
weiihann Feb 18, 2025
9b03c30
add node iterator, delete leaf nodes
weiihann Feb 18, 2025
26a135f
feat(trie): use interface for trie identifiers
weiihann Feb 25, 2025
cbd9f7e
feat: length is encoded last in bitarray and prepend node type in key
weiihann Feb 26, 2025
dc3f89e
fix: parallelization in node collection
weiihann Feb 26, 2025
be972d8
docs
weiihann Feb 26, 2025
9b5d267
refactor: revert encoding without leading zeros
weiihann Feb 27, 2025
90230ff
refactor: create pathdb package
weiihann Mar 3, 2025
c4e0ecb
refactor: use type alias path instead of bitarray
weiihann Mar 3, 2025
3dca1e3
refactor: prepare for hashdb separation
weiihann Mar 3, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
133 changes: 133 additions & 0 deletions core/trie2/collector.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
package trie2

import (
"fmt"
"sync"

"github.com/NethermindEth/juno/core/trie2/trienode"
)

const (
parallelThreshold = 8 // TODO: arbitrary number, configure this based on monitoring
)

// Used as a tool to collect all dirty nodes in a NodeSet
type collector struct {
nodes *trienode.NodeSet
}

func newCollector(nodes *trienode.NodeSet) *collector {
return &collector{nodes: nodes}
}

// Collects the nodes in the node set and collapses a node into a hash node
func (c *collector) Collect(n Node, parallel bool) *HashNode {
return c.collect(new(Path), n, parallel, 0).(*HashNode)
}

// Collects all dirty nodes in the trie and converts them to hash nodes.
// Traverses the trie recursively, processing each node type:
// - EdgeNode: Collects its child before processing the edge node itself
// - BinaryNode: Collects both children (potentially in parallel) before processing the node
// - HashNode: Already processed, returns as is
// - ValueNode: Stores as a leaf in the node set
// Returns a HashNode representing the processed node.
func (c *collector) collect(path *Path, n Node, parallel bool, depth int) Node {
// This path has not been modified, just return the cache
hash, dirty := n.cache()
if hash != nil && !dirty {
return hash
}

// Collect children and then parent
switch cn := n.(type) {
case *EdgeNode:
collapsed := cn.copy()

// If the child is a binary node, recurse into it.
// Otherwise, it can only be a HashNode or ValueNode.
// Combination of edge (parent) + edge (child) is not possible.
collapsed.Child = c.collect(new(Path).Append(path, cn.Path), cn.Child, parallel, depth+1)
return c.store(path, collapsed)
case *BinaryNode:
collapsed := cn.copy()
collapsed.Children = c.collectChildren(path, cn, parallel, depth+1)
return c.store(path, collapsed)
case *HashNode:
return cn
case *ValueNode: // each leaf node is stored as a single entry in the node set
return c.store(path, cn)
default:
panic(fmt.Sprintf("unknown node type: %T", cn))

Check warning on line 61 in core/trie2/collector.go

View check run for this annotation

Codecov / codecov/patch

core/trie2/collector.go#L60-L61

Added lines #L60 - L61 were not covered by tests
}
}

// Collects the children of a binary node, may apply parallel processing if configured
func (c *collector) collectChildren(path *Path, n *BinaryNode, parallel bool, depth int) [2]Node {
children := [2]Node{}

var mu sync.Mutex

// Helper function to process a single child
processChild := func(i int) {
child := n.Children[i]
// Return early if it's already a hash node
if hn, ok := child.(*HashNode); ok {
children[i] = hn
return
}

// Create child path
childPath := new(Path).AppendBit(path, uint8(i))

if !parallel {
children[i] = c.collect(childPath, child, parallel, depth)
return
}

// Parallel processing
childSet := trienode.NewNodeSet(c.nodes.Owner)
childCollector := newCollector(childSet)
children[i] = childCollector.collect(childPath, child, depth < parallelThreshold, depth)

// Merge the child set into the parent set
// Must be done under the mutex because node set is not thread safe
mu.Lock()
c.nodes.MergeSet(childSet) //nolint:errcheck // guaranteed to succeed because same owner
mu.Unlock()
}

if !parallel {
// Sequential processing
processChild(0)
processChild(1)
return children
}

// Parallel processing
var wg sync.WaitGroup
for i := range 2 {
wg.Add(1)
go func(idx int) {
defer wg.Done()
processChild(idx)
}(i)
}
wg.Wait()

return children
}

// Stores the node in the node set and returns the hash node
func (c *collector) store(path *Path, n Node) Node {
hash, _ := n.cache()

blob := nodeToBytes(n)
if hash == nil { // this is a value node
c.nodes.Add(*path, trienode.NewLeaf(blob))
return n
}

c.nodes.Add(*path, trienode.NewNonLeaf(hash.Felt, blob))
return hash
}
8 changes: 8 additions & 0 deletions core/trie2/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package trie2

import "errors"

var (
ErrCommitted = errors.New("trie is committed")
ErrEmptyRange = errors.New("empty range")
)
117 changes: 117 additions & 0 deletions core/trie2/hasher.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package trie2

import (
"fmt"
"sync"

"github.com/NethermindEth/juno/core/crypto"
)

// A tool for hashing nodes in the trie. It supports both sequential and parallel
// hashing modes.
type hasher struct {
hashFn crypto.HashFn // The hash function to use
parallel bool // Whether to hash binary node children in parallel
}

func newHasher(hash crypto.HashFn, parallel bool) hasher {
return hasher{
hashFn: hash,
parallel: parallel,
}
}

// Computes the hash of a node and returns both the hash node and a cached
// version of the original node. If the node already has a cached hash, returns
// that instead of recomputing.
func (h *hasher) hash(n Node) (Node, Node) {
if hash, _ := n.cache(); hash != nil {
return hash, n
}

switch n := n.(type) {
case *EdgeNode:
collapsed, cached := h.hashEdgeChild(n)
hn := &HashNode{Felt: *collapsed.Hash(h.hashFn)}
cached.flags.hash = hn
return hn, cached
case *BinaryNode:
collapsed, cached := h.hashBinaryChildren(n)
hn := &HashNode{Felt: *collapsed.Hash(h.hashFn)}
cached.flags.hash = hn
return hn, cached
case *ValueNode, *HashNode:
return n, n
default:
panic(fmt.Sprintf("unknown node type: %T", n))

Check warning on line 46 in core/trie2/hasher.go

View check run for this annotation

Codecov / codecov/patch

core/trie2/hasher.go#L45-L46

Added lines #L45 - L46 were not covered by tests
}
}

func (h *hasher) hashEdgeChild(n *EdgeNode) (collapsed, cached *EdgeNode) {
collapsed, cached = n.copy(), n.copy()

switch n.Child.(type) {
case *EdgeNode, *BinaryNode:
collapsed.Child, cached.Child = h.hash(n.Child)
}

return collapsed, cached
}

func (h *hasher) hashBinaryChildren(n *BinaryNode) (collapsed, cached *BinaryNode) {
collapsed, cached = n.copy(), n.copy()

if h.parallel { // TODO(weiihann): double check this parallel strategy
var wg sync.WaitGroup
wg.Add(2)

go func() {
defer wg.Done()
if n.Children[0] != nil {
collapsed.Children[0], cached.Children[0] = h.hash(n.Children[0])
} else {
collapsed.Children[0], cached.Children[0] = nilValueNode, nilValueNode
}

Check warning on line 74 in core/trie2/hasher.go

View check run for this annotation

Codecov / codecov/patch

core/trie2/hasher.go#L73-L74

Added lines #L73 - L74 were not covered by tests
}()

go func() {
defer wg.Done()
if n.Children[1] != nil {
collapsed.Children[1], cached.Children[1] = h.hash(n.Children[1])
} else {
collapsed.Children[1], cached.Children[1] = nilValueNode, nilValueNode
}

Check warning on line 83 in core/trie2/hasher.go

View check run for this annotation

Codecov / codecov/patch

core/trie2/hasher.go#L82-L83

Added lines #L82 - L83 were not covered by tests
}()

wg.Wait()
} else {
if n.Children[0] != nil {
collapsed.Children[0], cached.Children[0] = h.hash(n.Children[0])
} else {
collapsed.Children[0], cached.Children[0] = nilValueNode, nilValueNode
}

if n.Children[1] != nil {
collapsed.Children[1], cached.Children[1] = h.hash(n.Children[1])
} else {
collapsed.Children[1], cached.Children[1] = nilValueNode, nilValueNode
}
}

return collapsed, cached
}

// Construct trie proofs and returns the collapsed node (i.e. nodes with hash children)
// and the hashed node.
func (h *hasher) proofHash(original Node) (collapsed, hashed Node) {
switch n := original.(type) {
case *EdgeNode:
en, _ := h.hashEdgeChild(n)
return en, &HashNode{Felt: *en.Hash(h.hashFn)}
case *BinaryNode:
bn, _ := h.hashBinaryChildren(n)
return bn, &HashNode{Felt: *bn.Hash(h.hashFn)}
default:
return n, n

Check warning on line 115 in core/trie2/hasher.go

View check run for this annotation

Codecov / codecov/patch

core/trie2/hasher.go#L114-L115

Added lines #L114 - L115 were not covered by tests
}
}
64 changes: 64 additions & 0 deletions core/trie2/id.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package trie2

import (
"github.com/NethermindEth/juno/core/felt"
"github.com/NethermindEth/juno/db"
)

var (
_ TrieID = (*ClassTrieID)(nil)
_ TrieID = (*ContractTrieID)(nil)
_ TrieID = (*ContractStorageTrieID)(nil)
_ TrieID = (*EmptyTrieID)(nil)
)

type TrieType uint8

const (
Empty TrieType = iota
ClassTrie
ContractTrie
)

// Represents the identifier for uniquely identifying a trie.
type ID struct {
TrieType TrieType
Owner felt.Felt // The contract address which the trie belongs to
}

// Identifier for a class trie
type ClassTrieID struct{}

func NewClassTrieID() *ClassTrieID { return &ClassTrieID{} }
func (ClassTrieID) Bucket() db.Bucket { return db.ClassTrie }
func (ClassTrieID) Owner() felt.Felt { return felt.Zero }

Check warning on line 34 in core/trie2/id.go

View check run for this annotation

Codecov / codecov/patch

core/trie2/id.go#L32-L34

Added lines #L32 - L34 were not covered by tests

// Identifier for a contract trie
type ContractTrieID struct{}

func NewContractTrieID() *ContractTrieID { return &ContractTrieID{} }
func (id ContractTrieID) Bucket() db.Bucket { return db.ContractTrieContract }
func (id ContractTrieID) Owner() felt.Felt { return felt.Zero }

Check warning on line 41 in core/trie2/id.go

View check run for this annotation

Codecov / codecov/patch

core/trie2/id.go#L39-L41

Added lines #L39 - L41 were not covered by tests

// Identifier for a contract storage trie
type ContractStorageTrieID struct {
owner felt.Felt
}

func NewContractStorageTrieID(owner felt.Felt) *ContractStorageTrieID {
return &ContractStorageTrieID{owner: owner}

Check warning on line 49 in core/trie2/id.go

View check run for this annotation

Codecov / codecov/patch

core/trie2/id.go#L48-L49

Added lines #L48 - L49 were not covered by tests
}

func NewContractStorageTrieIDFromFelt(owner felt.Felt) *ContractStorageTrieID {
return &ContractStorageTrieID{owner: owner}

Check warning on line 53 in core/trie2/id.go

View check run for this annotation

Codecov / codecov/patch

core/trie2/id.go#L52-L53

Added lines #L52 - L53 were not covered by tests
}

func (id ContractStorageTrieID) Bucket() db.Bucket { return db.ContractTrieStorage }
func (id ContractStorageTrieID) Owner() felt.Felt { return id.owner }

Check warning on line 57 in core/trie2/id.go

View check run for this annotation

Codecov / codecov/patch

core/trie2/id.go#L56-L57

Added lines #L56 - L57 were not covered by tests

// Identifier for an empty trie, only used for temporary purposes
type EmptyTrieID struct{}

func NewEmptyTrieID() *EmptyTrieID { return &EmptyTrieID{} }
func (EmptyTrieID) Bucket() db.Bucket { return db.Bucket(0) }
func (EmptyTrieID) Owner() felt.Felt { return felt.Zero }
Loading