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 47 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
120 changes: 120 additions & 0 deletions core/trie2/collector.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package trie2

import (
"fmt"
"sync"

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

// 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).(*HashNode)
}

func (c *collector) collect(path *Path, n Node, parallel bool) 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)
return c.store(path, collapsed)
case *BinaryNode:
collapsed := cn.copy()
collapsed.Children = c.collectChildren(path, cn, parallel)
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 51 in core/trie2/collector.go

View check run for this annotation

Codecov / codecov/patch

core/trie2/collector.go#L50-L51

Added lines #L50 - L51 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) [2]Node {
children := [2]Node{}

// 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)
return
}

// Parallel processing
childSet := trienode.NewNodeSet(c.nodes.Owner)
childCollector := newCollector(childSet)
children[i] = childCollector.collect(childPath, child, parallel)
c.nodes.MergeSet(childSet) //nolint:errcheck // guaranteed to succeed because same owner
}

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

// Parallel processing
var wg sync.WaitGroup
var mu sync.Mutex

for i := range 2 {
wg.Add(1)
go func(idx int) {
defer wg.Done()
mu.Lock()
processChild(idx)
mu.Unlock()
}(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.NewNode(felt.Felt{}, blob))
return n
}

c.nodes.Add(*path, trienode.NewNode(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 shashing 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
}
}
68 changes: 68 additions & 0 deletions core/trie2/id.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
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
}

// Represents 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 }
func (ClassTrieID) IsContractStorage() bool { return false }

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

View check run for this annotation

Codecov / codecov/patch

core/trie2/id.go#L32-L35

Added lines #L32 - L35 were not covered by tests

// Represents 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 }
func (id ContractTrieID) IsContractStorage() bool { return false }

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

View check run for this annotation

Codecov / codecov/patch

core/trie2/id.go#L40-L43

Added lines #L40 - L43 were not covered by tests

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

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

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

View check run for this annotation

Codecov / codecov/patch

core/trie2/id.go#L50-L51

Added lines #L50 - L51 were not covered by tests
}

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

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

View check run for this annotation

Codecov / codecov/patch

core/trie2/id.go#L54-L55

Added lines #L54 - L55 were not covered by tests
}

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

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

View check run for this annotation

Codecov / codecov/patch

core/trie2/id.go#L58-L60

Added lines #L58 - L60 were not covered by tests

// Represents 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 }
func (EmptyTrieID) IsContractStorage() bool { return false }

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

View check run for this annotation

Codecov / codecov/patch

core/trie2/id.go#L68

Added line #L68 was not covered by tests
Loading