From 9d378442b76b49e5657830a9fc8125ac6f42715c Mon Sep 17 00:00:00 2001 From: thiagodeev Date: Thu, 3 Oct 2024 17:36:44 -0300 Subject: [PATCH 1/2] Adds GetStorageProof method --- mocks/mock_rpc_provider.go | 15 ++++++++++++++ rpc/contract.go | 17 ++++++++++++++++ rpc/provider.go | 1 + rpc/types_contract.go | 40 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 73 insertions(+) diff --git a/mocks/mock_rpc_provider.go b/mocks/mock_rpc_provider.go index 6e86bf39..a8edd76f 100644 --- a/mocks/mock_rpc_provider.go +++ b/mocks/mock_rpc_provider.go @@ -296,6 +296,21 @@ func (mr *MockRpcProviderMockRecorder) Events(ctx, input any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Events", reflect.TypeOf((*MockRpcProvider)(nil).Events), ctx, input) } +// GetStorageProof mocks base method. +func (m *MockRpcProvider) GetStorageProof(ctx context.Context, storageProofInput rpc.StorageProofInput) (*rpc.StorageProofResult, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetStorageProof", ctx, storageProofInput) + ret0, _ := ret[0].(*rpc.StorageProofResult) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetStorageProof indicates an expected call of GetStorageProof. +func (mr *MockRpcProviderMockRecorder) GetStorageProof(ctx, storageProofInput any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetStorageProof", reflect.TypeOf((*MockRpcProvider)(nil).GetStorageProof), ctx, storageProofInput) +} + // GetTransactionStatus mocks base method. func (m *MockRpcProvider) GetTransactionStatus(ctx context.Context, transactionHash *felt.Felt) (*rpc.TxnStatusResp, error) { m.ctrl.T.Helper() diff --git a/rpc/contract.go b/rpc/contract.go index 8dcbe64e..f1959046 100644 --- a/rpc/contract.go +++ b/rpc/contract.go @@ -160,3 +160,20 @@ 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 +// +// Parameters: +// - ctx: The context of the function call +// - storageProofInput: an input containing at least one of the fields filled +// Returns: +// - *StorageProofResult: the proofs of the field passed in the input +// - error: an error if any occurred during the execution +func (provider *Provider) GetStorageProof(ctx context.Context, storageProofInput StorageProofInput) (*StorageProofResult, error) { + var raw StorageProofResult + if err := do(ctx, provider.c, "starknet_getStorageProof", &raw, storageProofInput); err != nil { + + return nil, tryUnwrapToRPCErr(err) + } + return &raw, nil +} diff --git a/rpc/provider.go b/rpc/provider.go index f7a5e53a..9159e7cc 100644 --- a/rpc/provider.go +++ b/rpc/provider.go @@ -59,6 +59,7 @@ type RpcProvider interface { EstimateMessageFee(ctx context.Context, msg MsgFromL1, blockID BlockID) (*FeeEstimation, error) Events(ctx context.Context, input EventsInput) (*EventChunk, error) BlockWithReceipts(ctx context.Context, blockID BlockID) (interface{}, error) + GetStorageProof(ctx context.Context, storageProofInput StorageProofInput) (*StorageProofResult, error) GetTransactionStatus(ctx context.Context, transactionHash *felt.Felt) (*TxnStatusResp, error) Nonce(ctx context.Context, blockID BlockID, contractAddress *felt.Felt) (*felt.Felt, error) SimulateTransactions(ctx context.Context, blockID BlockID, txns []BroadcastTxn, simulationFlags []SimulationFlag) ([]SimulatedTransaction, error) diff --git a/rpc/types_contract.go b/rpc/types_contract.go index 7dc88b25..6977b002 100644 --- a/rpc/types_contract.go +++ b/rpc/types_contract.go @@ -74,6 +74,46 @@ type ContractClass struct { ABI string `json:"abi,omitempty"` } +// You must provide one of these fields +type StorageProofInput struct { + // 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 + ContractAddresses []*felt.Felt `json:"contract_addresses,omitempty"` + // A list of (contract_address, storage_keys) pairs + 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) +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"` +} + +type ChildrenHashes struct { + Left *felt.Felt `json:"left"` + Right *felt.Felt `json:"right"` +} + // UnmarshalJSON unmarshals the JSON content into the DeprecatedContractClass struct. // // It takes a byte array `content` as a parameter and returns an error if there is any. From 785676b1bdf6fdb78d30c551df605d512f18130a Mon Sep 17 00:00:00 2001 From: thiagodeev Date: Thu, 24 Oct 2024 14:17:13 -0300 Subject: [PATCH 2/2] New spec updates --- rpc/contract.go | 5 ++- rpc/contract_test.go | 4 ++ rpc/errors.go | 4 ++ rpc/types_contract.go | 102 +++++++++++++++++++++++++++++++++++------- 4 files changed, 97 insertions(+), 18 deletions(-) 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"`