diff --git a/rpc/contract.go b/rpc/contract.go index f1959046..0cf00ba7 100644 --- a/rpc/contract.go +++ b/rpc/contract.go @@ -161,7 +161,8 @@ func (provider *Provider) EstimateMessageFee(ctx context.Context, msg MsgFromL1, return &raw, nil } -// Get merkle paths in one of the state tries: global state, classes, individual contract +// Get merkle paths in one of the state tries: global state, classes, individual contract. +// A single request can query for any mix of the three types of storage proofs (classes, contracts, and storage) // // Parameters: // - ctx: The context of the function call @@ -173,7 +174,7 @@ func (provider *Provider) GetStorageProof(ctx context.Context, storageProofInput var raw StorageProofResult if err := do(ctx, provider.c, "starknet_getStorageProof", &raw, storageProofInput); err != nil { - return nil, tryUnwrapToRPCErr(err) + return nil, tryUnwrapToRPCErr(err, ErrBlockNotFound, ErrStorageProofNotSupported) } return &raw, nil } diff --git a/rpc/contract_test.go b/rpc/contract_test.go index 5b621e5c..63a8c23b 100644 --- a/rpc/contract_test.go +++ b/rpc/contract_test.go @@ -683,3 +683,7 @@ func TestEstimateFee(t *testing.T) { } } } + +func TestGetStorageProof(t *testing.T) { + t.Skip("TODO: create a test before merge") +} diff --git a/rpc/errors.go b/rpc/errors.go index 337f4d42..d0effbf6 100644 --- a/rpc/errors.go +++ b/rpc/errors.go @@ -138,6 +138,10 @@ var ( Code: 41, Message: "Transaction execution error", } + ErrStorageProofNotSupported = &RPCError{ + Code: 42, + Message: "the node doesn't support storage proofs for blocks that are too far in the past", + } ErrInvalidContractClass = &RPCError{ Code: 50, Message: "Invalid contract class", diff --git a/rpc/types_contract.go b/rpc/types_contract.go index 6977b002..d7ea97b7 100644 --- a/rpc/types_contract.go +++ b/rpc/types_contract.go @@ -74,8 +74,9 @@ type ContractClass struct { ABI string `json:"abi,omitempty"` } -// You must provide one of these fields type StorageProofInput struct { + // The hash of the requested block, or number (height) of the requested block, or a block tag + BlockID BlockID `json:"block_id"` // A list of the class hashes for which we want to prove membership in the classes trie ClassHashes []*felt.Felt `json:"class_hashes,omitempty"` // A list of contracts for which we want to prove membership in the global state trie @@ -84,33 +85,65 @@ type StorageProofInput struct { ContractsStorageKeys []ContractStorageKeys `json:"contracts_storage_keys,omitempty"` } -type StorageProofResult struct { - ClassesProof NodeHashToNode `json:"classes_proof,omitempty"` - ContractsProof NodeHashToNode `json:"contracts_proof,omitempty"` - ContractsStorageProofs []NodeHashToNode `json:"contracts_storage_proofs,omitempty"` -} - type ContractStorageKeys struct { ContractAddress *felt.Felt `json:"contract_address"` StorageKeys []*felt.Felt `json:"storage_keys"` } -// A node_hash -> node mapping of all the nodes in the union of the paths between the requested leaves and the root (for each node present, its sibling is also present) +// The requested storage proofs. Note that if a requested leaf has the default value, +// the path to it may end in an edge node whose path is not a prefix of the requested leaf, +// thus effecitvely proving non-membership +type StorageProofResult struct { + ClassesProof NodeHashToNode `json:"classes_proof"` + ContractsProof ContractsProof `json:"contracts_proof"` + ContractsStorageProofs []NodeHashToNode `json:"contracts_storage_proofs"` + GlobalRoots []NodeHashToNode `json:"global_roots"` +} + +type ContractsProof struct { + // The nodes in the union of the paths from the contracts tree root to the requested leaves + Nodes NodeHashToNode `json:"nodes"` + ContractLeavesData []ContractLeavesData `json:"contract_leaves_data"` +} + +// The nonce and class hash for each requested contract address, in the order in which +// they appear in the request. These values are needed to construct the associated leaf node +type ContractLeavesData struct { + Nonce *felt.Felt `json:"nonce"` + ClassHash *felt.Felt `json:"class_hash"` +} + +type GlobalRoots struct { + ContractsTreeRoot *felt.Felt `json:"contracts_tree_root"` + ClassesTreeRoot *felt.Felt `json:"classes_tree_root"` + // the associated block hash (needed in case the caller used a block tag for the block_id parameter) + BlockHash *felt.Felt `json:"block_hash"` +} + +// A node_hash -> node mapping of all the nodes in the union of the paths between the requested leaves and the root type NodeHashToNode struct { NodeHash *felt.Felt `json:"node_hash"` Node MerkleNode `json:"node"` } -type MerkleNode struct { - Path uint `json:"path"` - Length uint `json:"length"` - Value *felt.Felt `json:"value"` - // the hash of the child nodes, if not present then the node is a leaf - ChildrenHashes ChildrenHashes `json:"children_hashes,omitempty"` +// A node in the Merkle-Patricia tree, can be a leaf, binary node, or an edge node +type MerkleNode interface{} // it should be an EdgeNode or BinaryNode + +// Represents a path to the highest non-zero descendant node +type EdgeNode struct { + // an integer whose binary representation represents the path from the current node to its highest non-zero descendant (bounded by 2^251) + Path NumAsHex `json:"path"` + // the length of the path (bounded by 251) + Length uint `json:"length"` + // the hash of the unique non-zero maximal-height descendant node + Child *felt.Felt `json:"child"` } -type ChildrenHashes struct { - Left *felt.Felt `json:"left"` +// An internal node whose both children are non-zero +type BinaryNode struct { + // the hash of the left child + Left *felt.Felt `json:"left"` + // the hash of the right child Right *felt.Felt `json:"right"` } @@ -208,6 +241,43 @@ func (c *DeprecatedContractClass) UnmarshalJSON(content []byte) error { return nil } +func (nodeHashToNode *NodeHashToNode) UnmarshalJSON(bytes []byte) error { + valueMap := make(map[string]any) + if err := json.Unmarshal(bytes, &valueMap); err != nil { + return err + } + + nodeHash, ok := valueMap["node_hash"] + if !ok { + return fmt.Errorf("missing 'node_hash' in json object") + } + nodeHashFelt, ok := nodeHash.(felt.Felt) + if !ok { + return fmt.Errorf("error casting 'node_hash' to felt.Felt") + } + + node, ok := valueMap["node"] + if !ok { + return fmt.Errorf("missing 'node' in json object") + } + var merkleNode MerkleNode + switch nodeT := node.(type) { + case BinaryNode: + merkleNode = nodeT + case EdgeNode: + merkleNode = nodeT + default: + return fmt.Errorf("'node' should be an EdgeNode or BinaryNode") + } + + *nodeHashToNode = NodeHashToNode{ + NodeHash: &nodeHashFelt, + Node: merkleNode, + } + + return nil +} + type SierraEntryPoint struct { // The index of the function in the program FunctionIdx int `json:"function_idx"`