Skip to content

Commit 4b4b538

Browse files
weiihannpnowosie
authored andcommitted
getStorageProof rpc method
1 parent 71c7ae9 commit 4b4b538

18 files changed

+1595
-136
lines changed

blockchain/blockchain.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ type Reader interface {
3636
L1HandlerTxnHash(msgHash *common.Hash) (l1HandlerTxnHash *felt.Felt, err error)
3737

3838
HeadState() (core.StateReader, StateCloser, error)
39+
HeadTrie() (core.TrieReader, StateCloser, error)
3940
StateAtBlockHash(blockHash *felt.Felt) (core.StateReader, StateCloser, error)
4041
StateAtBlockNumber(blockNumber uint64) (core.StateReader, StateCloser, error)
4142

@@ -768,6 +769,17 @@ func (b *Blockchain) HeadState() (core.StateReader, StateCloser, error) {
768769
return core.NewState(txn), txn.Discard, nil
769770
}
770771

772+
func (b *Blockchain) HeadTrie() (core.TrieReader, StateCloser, error) {
773+
// Note: I'm not sure I should open a new db txn since the TrieReader is a State
774+
// so the same instance of the state we create in HeadState will do job.
775+
txn, err := b.database.NewTransaction(false)
776+
if err != nil {
777+
return nil, nil, err
778+
}
779+
780+
return core.NewState(txn), txn.Discard, nil
781+
}
782+
771783
// StateAtBlockNumber returns a StateReader that provides a stable view to the state at the given block number
772784
func (b *Blockchain) StateAtBlockNumber(blockNumber uint64) (core.StateReader, StateCloser, error) {
773785
b.listener.OnRead("StateAtBlockNumber")

core/state.go

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,17 @@ type StateReader interface {
4444
Class(classHash *felt.Felt) (*DeclaredClass, error)
4545
}
4646

47+
// TrieReader used for storage proofs, can only be supported by current state implementation (for now, we plan to add db snapshots)
48+
var _ TrieReader = (*State)(nil)
49+
50+
//go:generate mockgen -destination=../mocks/mock_trie.go -package=mocks github.com/NethermindEth/juno/core TrieReader
51+
type TrieReader interface {
52+
ClassTrie() (*trie.Trie, func() error, error)
53+
StorageTrie() (*trie.Trie, func() error, error)
54+
StorageTrieForAddr(addr *felt.Felt) (*trie.Trie, error)
55+
StateAndClassRoot() (*felt.Felt, *felt.Felt, error)
56+
}
57+
4758
type State struct {
4859
*history
4960
txn db.Transaction
@@ -129,6 +140,18 @@ func (s *State) storage() (*trie.Trie, func() error, error) {
129140
return s.globalTrie(db.StateTrie, trie.NewTriePedersen)
130141
}
131142

143+
func (s *State) StorageTrie() (*trie.Trie, func() error, error) {
144+
return s.storage()
145+
}
146+
147+
func (s *State) ClassTrie() (*trie.Trie, func() error, error) {
148+
return s.classesTrie()
149+
}
150+
151+
func (s *State) StorageTrieForAddr(addr *felt.Felt) (*trie.Trie, error) {
152+
return storage(addr, s.txn)
153+
}
154+
132155
func (s *State) classesTrie() (*trie.Trie, func() error, error) {
133156
return s.globalTrie(db.ClassesTrie, trie.NewTriePoseidon)
134157
}
@@ -547,7 +570,7 @@ func (s *State) Revert(blockNumber uint64, update *StateUpdate) error {
547570

548571
err = s.performStateDeletions(blockNumber, update.StateDiff)
549572
if err != nil {
550-
return fmt.Errorf("error performing state deletions: %v", err)
573+
return fmt.Errorf("build reverse diff: %v", err)
551574
}
552575

553576
stateTrie, storageCloser, err := s.storage()
@@ -581,6 +604,7 @@ func (s *State) purgeNoClassContracts() error {
581604
// As noClassContracts are not in StateDiff.DeployedContracts we can only purge them if their storage no longer exists.
582605
// Updating contracts with reverse diff will eventually lead to the deletion of noClassContract's storage key from db. Thus,
583606
// we can use the lack of key's existence as reason for purging noClassContracts.
607+
584608
for addr := range noClassContracts {
585609
noClassC, err := NewContractUpdater(&addr, s.txn)
586610
if err != nil {
@@ -743,3 +767,35 @@ func (s *State) performStateDeletions(blockNumber uint64, diff *StateDiff) error
743767

744768
return nil
745769
}
770+
771+
func (s *State) StateAndClassRoot() (*felt.Felt, *felt.Felt, error) {
772+
var storageRoot, classesRoot *felt.Felt
773+
774+
sStorage, closer, err := s.storage()
775+
if err != nil {
776+
return nil, nil, err
777+
}
778+
779+
if storageRoot, err = sStorage.Root(); err != nil {
780+
return nil, nil, err
781+
}
782+
783+
if err = closer(); err != nil {
784+
return nil, nil, err
785+
}
786+
787+
classes, closer, err := s.classesTrie()
788+
if err != nil {
789+
return nil, nil, err
790+
}
791+
792+
if classesRoot, err = classes.Root(); err != nil {
793+
return nil, nil, err
794+
}
795+
796+
if err = closer(); err != nil {
797+
return nil, nil, err
798+
}
799+
800+
return storageRoot, classesRoot, nil
801+
}

core/trie/key.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
package trie
22

33
import (
4-
"bytes"
54
"encoding/hex"
65
"errors"
76
"fmt"
7+
"io"
88
"math/big"
99

1010
"github.com/NethermindEth/juno/core/felt"
@@ -39,8 +39,8 @@ func (k *Key) unusedBytes() []byte {
3939
return k.bitset[:len(k.bitset)-int(k.bytesNeeded())]
4040
}
4141

42-
func (k *Key) WriteTo(buf *bytes.Buffer) (int64, error) {
43-
if err := buf.WriteByte(k.len); err != nil {
42+
func (k *Key) WriteTo(buf io.Writer) (int64, error) {
43+
if _, err := buf.Write([]byte{k.len}); err != nil {
4444
return 0, err
4545
}
4646

core/trie/key_test.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package trie_test
22

33
import (
44
"bytes"
5+
"errors"
56
"testing"
67

78
"github.com/NethermindEth/juno/core/felt"
@@ -227,3 +228,39 @@ func TestMostSignificantBits(t *testing.T) {
227228
})
228229
}
229230
}
231+
232+
func TestKeyErrorHandling(t *testing.T) {
233+
t.Run("passed too long key bytes panics", func(t *testing.T) {
234+
defer func() {
235+
r := recover()
236+
require.NotNil(t, r)
237+
require.Contains(t, r.(string), "bytes does not fit in bitset")
238+
}()
239+
tooLongKeyB := make([]byte, 33)
240+
trie.NewKey(8, tooLongKeyB)
241+
})
242+
t.Run("MostSignificantBits n greater than key length", func(t *testing.T) {
243+
key := trie.NewKey(8, []byte{0x01})
244+
_, err := key.MostSignificantBits(9)
245+
require.Error(t, err)
246+
require.Contains(t, err.Error(), "cannot get more bits than the key length")
247+
})
248+
t.Run("MostSignificantBits equals key length return copy of key", func(t *testing.T) {
249+
key := trie.NewKey(8, []byte{0x01})
250+
kCopy, err := key.MostSignificantBits(8)
251+
require.NoError(t, err)
252+
require.Equal(t, key, *kCopy)
253+
})
254+
t.Run("WriteTo returns error", func(t *testing.T) {
255+
key := trie.NewKey(8, []byte{0x01})
256+
wrote, err := key.WriteTo(&errorBuffer{})
257+
require.Error(t, err)
258+
require.Equal(t, int64(0), wrote)
259+
})
260+
}
261+
262+
type errorBuffer struct{}
263+
264+
func (*errorBuffer) Write([]byte) (int, error) {
265+
return 0, errors.New("expected to fail")
266+
}

core/trie/node.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
package trie
22

33
import (
4-
"bytes"
54
"errors"
65
"fmt"
6+
"io"
77

88
"github.com/NethermindEth/juno/core/felt"
99
)
@@ -19,7 +19,7 @@ type Node struct {
1919
}
2020

2121
// Hash calculates the hash of a [Node]
22-
func (n *Node) Hash(path *Key, hashFunc hashFunc) *felt.Felt {
22+
func (n *Node) Hash(path *Key, hashFunc HashFunc) *felt.Felt {
2323
if path.Len() == 0 {
2424
// we have to deference the Value, since the Node can released back
2525
// to the NodePool and be reused anytime
@@ -34,12 +34,12 @@ func (n *Node) Hash(path *Key, hashFunc hashFunc) *felt.Felt {
3434
}
3535

3636
// Hash calculates the hash of a [Node]
37-
func (n *Node) HashFromParent(parentKey, nodeKey *Key, hashFunc hashFunc) *felt.Felt {
37+
func (n *Node) HashFromParent(parentKey, nodeKey *Key, hashFunc HashFunc) *felt.Felt {
3838
path := path(nodeKey, parentKey)
3939
return n.Hash(&path, hashFunc)
4040
}
4141

42-
func (n *Node) WriteTo(buf *bytes.Buffer) (int64, error) {
42+
func (n *Node) WriteTo(buf io.Writer) (int64, error) {
4343
if n.Value == nil {
4444
return 0, errors.New("cannot marshal node with nil value")
4545
}

core/trie/node_test.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package trie_test
22

33
import (
4+
"bytes"
45
"encoding/hex"
6+
"errors"
57
"testing"
68

79
"github.com/NethermindEth/juno/core/crypto"
@@ -26,3 +28,33 @@ func TestNodeHash(t *testing.T) {
2628

2729
assert.Equal(t, expected, node.Hash(&path, crypto.Pedersen), "TestTrieNode_Hash failed")
2830
}
31+
32+
func TestNodeErrorHandling(t *testing.T) {
33+
t.Run("WriteTo node value is nil", func(t *testing.T) {
34+
node := trie.Node{}
35+
var buffer bytes.Buffer
36+
_, err := node.WriteTo(&buffer)
37+
require.Error(t, err)
38+
})
39+
t.Run("WriteTo returns error", func(t *testing.T) {
40+
node := trie.Node{
41+
Value: new(felt.Felt).SetUint64(42),
42+
Left: &trie.Key{},
43+
Right: &trie.Key{},
44+
}
45+
46+
wrote, err := node.WriteTo(&errorBuffer{})
47+
require.Error(t, err)
48+
require.Equal(t, int64(0), wrote)
49+
})
50+
t.Run("UnmarshalBinary returns error", func(t *testing.T) {
51+
node := trie.Node{}
52+
53+
err := node.UnmarshalBinary([]byte{42})
54+
require.Equal(t, errors.New("size of input data is less than felt size"), err)
55+
56+
bs := new(felt.Felt).Bytes()
57+
err = node.UnmarshalBinary(append(bs[:], 0, 0, 42))
58+
require.Equal(t, errors.New("the node does not contain both left and right hash"), err)
59+
})
60+
}

core/trie/proof.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ func NewProofNodeSet() *ProofNodeSet {
1616
}
1717

1818
type ProofNode interface {
19-
Hash(hash hashFunc) *felt.Felt
19+
Hash(hash HashFunc) *felt.Felt
2020
Len() uint8
2121
String() string
2222
}
@@ -26,7 +26,7 @@ type Binary struct {
2626
RightHash *felt.Felt
2727
}
2828

29-
func (b *Binary) Hash(hash hashFunc) *felt.Felt {
29+
func (b *Binary) Hash(hash HashFunc) *felt.Felt {
3030
return hash(b.LeftHash, b.RightHash)
3131
}
3232

@@ -43,7 +43,7 @@ type Edge struct {
4343
Path *Key // path from parent to child
4444
}
4545

46-
func (e *Edge) Hash(hash hashFunc) *felt.Felt {
46+
func (e *Edge) Hash(hash HashFunc) *felt.Felt {
4747
length := make([]byte, len(e.Path.bitset))
4848
length[len(e.Path.bitset)-1] = e.Path.len
4949
pathFelt := e.Path.Felt()
@@ -137,7 +137,7 @@ func (t *Trie) GetRangeProof(leftKey, rightKey *felt.Felt, proofSet *ProofNodeSe
137137
// - Any node's computed hash doesn't match its expected hash
138138
// - The path bits don't match the key bits
139139
// - The proof ends before processing all key bits
140-
func VerifyProof(root, keyFelt *felt.Felt, proof *ProofNodeSet, hash hashFunc) (*felt.Felt, error) {
140+
func VerifyProof(root, keyFelt *felt.Felt, proof *ProofNodeSet, hash HashFunc) (*felt.Felt, error) {
141141
key := FeltToKey(globalTrieHeight, keyFelt)
142142
expectedHash := root
143143
keyLen := key.Len()

core/trie/proofset.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package trie
2+
3+
import (
4+
"sync"
5+
6+
"github.com/NethermindEth/juno/core/felt"
7+
)
8+
9+
// ProofSet represents a set of trie nodes used in a Merkle proof verification process.
10+
// Rather than relying on only either map of list, ProofSet provides both for the following reasons:
11+
// - map allows for unique node insertion
12+
// - list allows for ordered iteration over the proof nodes
13+
// It also supports concurrent read and write operations.
14+
type ProofSet struct {
15+
nodeSet map[felt.Felt]ProofNode
16+
nodeList []ProofNode
17+
size int
18+
lock sync.RWMutex
19+
}
20+
21+
func NewProofSet() *ProofSet {
22+
return &ProofSet{
23+
nodeSet: make(map[felt.Felt]ProofNode),
24+
}
25+
}
26+
27+
func (ps *ProofSet) Put(key felt.Felt, node ProofNode) {
28+
ps.lock.Lock()
29+
defer ps.lock.Unlock()
30+
31+
ps.nodeSet[key] = node
32+
ps.nodeList = append(ps.nodeList, node)
33+
ps.size++
34+
}
35+
36+
func (ps *ProofSet) Get(key felt.Felt) (ProofNode, bool) {
37+
ps.lock.RLock()
38+
defer ps.lock.RUnlock()
39+
40+
node, ok := ps.nodeSet[key]
41+
return node, ok
42+
}
43+
44+
func (ps *ProofSet) Size() int {
45+
ps.lock.RLock()
46+
defer ps.lock.RUnlock()
47+
48+
return ps.size
49+
}

core/trie/trie.go

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import (
1616

1717
const globalTrieHeight = 251 // TODO(weiihann): this is declared in core also, should be moved to a common place
1818

19-
type hashFunc func(*felt.Felt, *felt.Felt) *felt.Felt
19+
type HashFunc func(*felt.Felt, *felt.Felt) *felt.Felt
2020

2121
// Trie is a dense Merkle Patricia Trie (i.e., all internal nodes have two children).
2222
//
@@ -40,7 +40,7 @@ type Trie struct {
4040
rootKey *Key
4141
maxKey *felt.Felt
4242
storage *Storage
43-
hash hashFunc
43+
hash HashFunc
4444

4545
dirtyNodes []*Key
4646
rootKeyIsDirty bool
@@ -56,7 +56,7 @@ func NewTriePoseidon(storage *Storage, height uint8) (*Trie, error) {
5656
return newTrie(storage, height, crypto.Poseidon)
5757
}
5858

59-
func newTrie(storage *Storage, height uint8, hash hashFunc) (*Trie, error) {
59+
func newTrie(storage *Storage, height uint8, hash HashFunc) (*Trie, error) {
6060
if height > felt.Bits {
6161
return nil, fmt.Errorf("max trie height is %d, got: %d", felt.Bits, height)
6262
}
@@ -96,12 +96,17 @@ func RunOnTempTriePoseidon(height uint8, do func(*Trie) error) error {
9696
return do(trie)
9797
}
9898

99-
// feltToKey Converts a key, given in felt, to a trie.Key which when followed on a [Trie],
99+
// FeltToKey Converts a key, given in felt, to a trie.Key which when followed on a [Trie],
100100
// leads to the corresponding [Node]
101101
func (t *Trie) FeltToKey(k *felt.Felt) Key {
102102
return FeltToKey(t.height, k)
103103
}
104104

105+
// HashFunc returns the hash function used by the trie
106+
func (t *Trie) HashFunc() HashFunc {
107+
return t.hash
108+
}
109+
105110
// path returns the path as mentioned in the [specification] for commitment calculations.
106111
// path is suffix of key that diverges from parentKey. For example,
107112
// for a key 0b1011 and parentKey 0b10, this function would return the path object of 0b0.

0 commit comments

Comments
 (0)