@@ -5,13 +5,21 @@ import (
55 "errors"
66 "fmt"
77
8+ "github.com/NethermindEth/juno/core"
89 "github.com/NethermindEth/juno/core/felt"
10+ "github.com/NethermindEth/juno/core/trie"
911 "github.com/NethermindEth/juno/db"
1012 "github.com/NethermindEth/juno/jsonrpc"
1113 "github.com/NethermindEth/juno/rpc/rpccore"
14+ "github.com/NethermindEth/juno/utils"
1215 "go.uber.org/zap"
1316)
1417
18+ const (
19+ MissingContractAddress = "missing field: contract_address"
20+ MissingStorageKeys = "missing field: storage_keys"
21+ )
22+
1523// StorageAtResponseFlags represents the flags for the `starknet_getStorageAt` operation.
1624type StorageAtResponseFlags struct {
1725 IncludeLastUpdateBlock bool
@@ -71,6 +79,10 @@ func (st *StorageAtResponse) UnmarshalJSON(data []byte) error {
7179 return st .Value .UnmarshalJSON (data )
7280}
7381
82+ /****************************************************
83+ Contract Handlers
84+ *****************************************************/
85+
7486// StorageAt gets the value of the storage at the given address and key.
7587//
7688// It follows the specification defined here:
@@ -125,3 +137,357 @@ func (h *Handler) StorageAt(
125137
126138 return & result , nil
127139}
140+
141+ type StorageProofResult struct {
142+ ClassesProof []* HashToNode `json:"classes_proof"`
143+ ContractsProof * ContractProof `json:"contracts_proof"`
144+ ContractsStorageProofs [][]* HashToNode `json:"contracts_storage_proofs"`
145+ GlobalRoots * GlobalRoots `json:"global_roots"`
146+ }
147+
148+ // StorageAt get merkle paths in one of the state tries: global state, classes,
149+ // individual contract. A single request can query for any mix of the three types
150+ // of storage proofs (classes, contracts, and storage).
151+ //
152+ // It follows the specification defined here:
153+ // https://github.com/starkware-libs/starknet-specs/blob/release/v0.10.2/api/starknet_api_openrpc.json#L980
154+ func (h * Handler ) StorageProof (
155+ id * BlockID , classes , contracts []felt.Felt , storageKeys []StorageKeys ,
156+ ) (* StorageProofResult , * jsonrpc.Error ) {
157+ state , closer , err := h .bcReader .HeadState ()
158+ if err != nil {
159+ return nil , rpccore .ErrInternal .CloneWithData (err )
160+ }
161+
162+ chainHeight , err := h .bcReader .Height ()
163+ if err != nil {
164+ return nil , rpccore .ErrInternal .CloneWithData (err )
165+ }
166+
167+ // TODO(infrmtcs): This is still a half baked solution because we're using another
168+ // transaction to get the block number. We don't use the head query directly to avoid
169+ // race condition where there is a new incoming block. Currently it's still working
170+ // because we don't have revert yet. We should figure out a way to merge the two transactions.
171+ header , err := h .bcReader .BlockHeaderByNumber (chainHeight )
172+ if err != nil {
173+ return nil , rpccore .ErrInternal .CloneWithData (err )
174+ }
175+
176+ // We do not support historical storage proofs for now
177+ // Ensure that the block requested is the head block
178+ if rpcErr := h .isBlockSupported (id , chainHeight ); rpcErr != nil {
179+ return nil , rpcErr
180+ }
181+
182+ defer h .callAndLogErr (closer , "Error closing state reader in getStorageProof" )
183+
184+ classTrie , err := state .ClassTrie ()
185+ if err != nil {
186+ return nil , rpccore .ErrInternal .CloneWithData (err )
187+ }
188+
189+ contractTrie , err := state .ContractTrie ()
190+ if err != nil {
191+ return nil , rpccore .ErrInternal .CloneWithData (err )
192+ }
193+
194+ // Do a sanity check and remove duplicates from the inputs
195+ classes = utils .Set (classes )
196+ contracts = utils .Set (contracts )
197+ uniqueStorageKeys , rpcErr := processStorageKeys (storageKeys )
198+ if rpcErr != nil {
199+ return nil , rpcErr
200+ }
201+
202+ classProof , err := getClassProof (classTrie , classes )
203+ if err != nil {
204+ return nil , rpccore .ErrInternal .CloneWithData (err )
205+ }
206+
207+ contractProof , err := getContractProof (contractTrie , state , contracts )
208+ if err != nil {
209+ return nil , rpccore .ErrInternal .CloneWithData (err )
210+ }
211+
212+ contractStorageProof , err := getContractStorageProof (state , uniqueStorageKeys )
213+ if err != nil {
214+ return nil , rpccore .ErrInternal .CloneWithData (err )
215+ }
216+
217+ contractTreeRoot , err := contractTrie .Hash ()
218+ if err != nil {
219+ return nil , rpccore .ErrInternal .CloneWithData (err )
220+ }
221+
222+ classTreeRoot , err := classTrie .Hash ()
223+ if err != nil {
224+ return nil , rpccore .ErrInternal .CloneWithData (err )
225+ }
226+
227+ return & StorageProofResult {
228+ ClassesProof : classProof ,
229+ ContractsProof : contractProof ,
230+ ContractsStorageProofs : contractStorageProof ,
231+ GlobalRoots : & GlobalRoots {
232+ ContractsTreeRoot : & contractTreeRoot ,
233+ ClassesTreeRoot : & classTreeRoot ,
234+ BlockHash : header .Hash ,
235+ },
236+ }, nil
237+ }
238+
239+ // Ensures each contract is unique and each storage key in each contract is unique
240+ func processStorageKeys (storageKeys []StorageKeys ) ([]StorageKeys , * jsonrpc.Error ) {
241+ if len (storageKeys ) == 0 {
242+ return nil , nil
243+ }
244+
245+ merged := make (map [felt.Felt ][]felt.Felt , len (storageKeys ))
246+ for _ , sk := range storageKeys {
247+ // Ensure that both contract and keys are provided
248+ if sk .Contract == nil {
249+ return nil , jsonrpc .Err (jsonrpc .InvalidParams , MissingContractAddress )
250+ }
251+ if len (sk .Keys ) == 0 {
252+ return nil , jsonrpc .Err (jsonrpc .InvalidParams , MissingStorageKeys )
253+ }
254+
255+ contract := * sk .Contract
256+ merged [contract ] = append (merged [contract ], sk .Keys ... )
257+ }
258+
259+ uniqueStorageKeys := make ([]StorageKeys , 0 , len (merged ))
260+ for contract , keys := range merged {
261+ uniqueStorageKeys = append (
262+ uniqueStorageKeys ,
263+ StorageKeys {Contract : & contract , Keys : utils .Set (keys )},
264+ )
265+ }
266+
267+ return uniqueStorageKeys , nil
268+ }
269+
270+ // isBlockSupported checks if the block ID requested is supported for storage proofs
271+ // Currently returns true only if the block ID requested matches the head block
272+ func (h * Handler ) isBlockSupported (blockID * BlockID , chainHeight uint64 ) * jsonrpc.Error {
273+ var blockNumber uint64
274+ switch {
275+ case blockID .IsLatest ():
276+ return nil
277+ case blockID .IsPreConfirmed ():
278+ return rpccore .ErrCallOnPreConfirmed
279+ case blockID .IsHash ():
280+ header , err := h .bcReader .BlockHeaderByHash (blockID .Hash ())
281+ if err != nil {
282+ if errors .Is (err , db .ErrKeyNotFound ) {
283+ return rpccore .ErrBlockNotFound
284+ }
285+ return rpccore .ErrInternal .CloneWithData (err )
286+ }
287+ blockNumber = header .Number
288+ case blockID .IsNumber ():
289+ blockNumber = blockID .Number ()
290+ case blockID .IsL1Accepted ():
291+ return rpccore .ErrStorageProofNotSupported
292+ default :
293+ panic (fmt .Sprintf ("invalid block id type %d" , blockID .Type ()))
294+ }
295+
296+ switch {
297+ case blockNumber < chainHeight :
298+ return rpccore .ErrStorageProofNotSupported
299+ case blockNumber > chainHeight :
300+ return rpccore .ErrBlockNotFound
301+ }
302+ return nil
303+ }
304+
305+ func getClassProof (tr core.Trie , classes []felt.Felt ) ([]* HashToNode , error ) {
306+ // TODO(maksym): remove after trie2 integration. RPC packages shouldn't
307+ // care about which trie implementation is being used and the output format should be the same
308+ t , ok := tr .(* trie.Trie )
309+ if ! ok {
310+ return nil , fmt .Errorf ("unknown trie type: %T" , tr )
311+ }
312+
313+ classProof := trie .NewProofNodeSet ()
314+ for _ , class := range classes {
315+ if err := t .Prove (& class , classProof ); err != nil {
316+ return nil , err
317+ }
318+ }
319+
320+ return adaptProofNodes (classProof ), nil
321+ }
322+
323+ func getContractProof (
324+ tr core.Trie ,
325+ state core.StateReader ,
326+ contracts []felt.Felt ,
327+ ) (* ContractProof , error ) {
328+ // TODO(maksym): remove after trie2 integration. RPC packages shouldn't
329+ // care about which trie implementation is being used and the output format should be the same
330+ t , ok := tr .(* trie.Trie )
331+ if ! ok {
332+ return nil , fmt .Errorf ("unknown trie type: %T" , tr )
333+ }
334+
335+ contractProof := trie .NewProofNodeSet ()
336+ contractLeavesData := make ([]* LeafData , len (contracts ))
337+ for i , contract := range contracts {
338+ if err := t .Prove (& contract , contractProof ); err != nil {
339+ return nil , err
340+ }
341+
342+ classHash , err := state .ContractClassHash (& contract )
343+ if err != nil {
344+ if errors .Is (err , db .ErrKeyNotFound ) { // contract does not exist, skip getting leaf data
345+ continue
346+ }
347+ return nil , err
348+ }
349+
350+ nonce , err := state .ContractNonce (& contract )
351+ if err != nil {
352+ return nil , err
353+ }
354+
355+ contractStorageTrie , err := state .ContractStorageTrie (& contract )
356+ if err != nil {
357+ return nil , err
358+ }
359+
360+ storageRoot , err := contractStorageTrie .Hash ()
361+ if err != nil {
362+ return nil , err
363+ }
364+
365+ contractLeavesData [i ] = & LeafData {
366+ Nonce : & nonce ,
367+ ClassHash : & classHash ,
368+ StorageRoot : & storageRoot ,
369+ }
370+ }
371+
372+ return & ContractProof {
373+ Nodes : adaptProofNodes (contractProof ),
374+ LeavesData : contractLeavesData ,
375+ }, nil
376+ }
377+
378+ func getContractStorageProof (
379+ state core.StateReader ,
380+ storageKeys []StorageKeys ,
381+ ) ([][]* HashToNode , error ) {
382+ contractStorageRes := make ([][]* HashToNode , len (storageKeys ))
383+ for i , storageKey := range storageKeys {
384+ contractStorageTrie , err := state .ContractStorageTrie (storageKey .Contract )
385+ if err != nil {
386+ return nil , err
387+ }
388+
389+ t , ok := contractStorageTrie .(* trie.Trie )
390+ if ! ok {
391+ return nil , fmt .Errorf ("unknown trie type: %T" , t )
392+ }
393+ contractStorageProof := trie .NewProofNodeSet ()
394+ for _ , key := range storageKey .Keys {
395+ if err := t .Prove (& key , contractStorageProof ); err != nil {
396+ return nil , err
397+ }
398+ }
399+
400+ contractStorageRes [i ] = adaptProofNodes (contractStorageProof )
401+ }
402+
403+ return contractStorageRes , nil
404+ }
405+
406+ func adaptProofNodes (proof * trie.ProofNodeSet ) []* HashToNode {
407+ nodes := make ([]* HashToNode , proof .Size ())
408+ nodeList := proof .List ()
409+ for i , hash := range proof .Keys () {
410+ var node Node
411+
412+ switch n := nodeList [i ].(type ) {
413+ case * trie.Binary :
414+ node = & BinaryNode {
415+ Left : n .LeftHash ,
416+ Right : n .RightHash ,
417+ }
418+ case * trie.Edge :
419+ path := n .Path .Felt ()
420+ node = & EdgeNode {
421+ Path : path .String (),
422+ Length : int (n .Path .Len ()),
423+ Child : n .Child ,
424+ }
425+ }
426+
427+ nodes [i ] = & HashToNode {
428+ Hash : & hash ,
429+ Node : node ,
430+ }
431+ }
432+
433+ return nodes
434+ }
435+
436+ type StorageKeys struct {
437+ Contract * felt.Felt `json:"contract_address"`
438+ Keys []felt.Felt `json:"storage_keys"`
439+ }
440+
441+ type Node interface {
442+ AsProofNode () trie.ProofNode
443+ }
444+
445+ type BinaryNode struct {
446+ Left * felt.Felt `json:"left"`
447+ Right * felt.Felt `json:"right"`
448+ }
449+
450+ type EdgeNode struct {
451+ Path string `json:"path"`
452+ Length int `json:"length"`
453+ Child * felt.Felt `json:"child"`
454+ }
455+
456+ func (e * EdgeNode ) AsProofNode () trie.ProofNode {
457+ f , _ := felt.NewFromString [felt.Felt ](e .Path )
458+ pbs := f .Bytes ()
459+
460+ return & trie.Edge {
461+ Path : new (trie.BitArray ).SetBytes (uint8 (e .Length ), pbs [:]),
462+ Child : e .Child ,
463+ }
464+ }
465+
466+ func (b * BinaryNode ) AsProofNode () trie.ProofNode {
467+ return & trie.Binary {
468+ LeftHash : b .Left ,
469+ RightHash : b .Right ,
470+ }
471+ }
472+
473+ type HashToNode struct {
474+ Hash * felt.Felt `json:"node_hash"`
475+ Node Node `json:"node"`
476+ }
477+
478+ type LeafData struct {
479+ Nonce * felt.Felt `json:"nonce"`
480+ ClassHash * felt.Felt `json:"class_hash"`
481+ StorageRoot * felt.Felt `json:"storage_root"`
482+ }
483+
484+ type ContractProof struct {
485+ Nodes []* HashToNode `json:"nodes"`
486+ LeavesData []* LeafData `json:"contract_leaves_data"`
487+ }
488+
489+ type GlobalRoots struct {
490+ ContractsTreeRoot * felt.Felt `json:"contracts_tree_root"`
491+ ClassesTreeRoot * felt.Felt `json:"classes_tree_root"`
492+ BlockHash * felt.Felt `json:"block_hash"`
493+ }
0 commit comments