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

Refactor storage proofs #232

Merged
merged 5 commits into from
Oct 15, 2024
Merged
Changes from all commits
Commits
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
224 changes: 148 additions & 76 deletions api/starknet_api_openrpc.json
Original file line number Diff line number Diff line change
Expand Up @@ -908,81 +908,138 @@
},
{
"name": "starknet_getStorageProof",
"summary": "get merkle paths in one of the state tries: global state, classes, individual contract",
"summary": "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).",
"params": [
{
"name": "class_hashes",
"description": "a list of the class hashes for which we want to prove membership in the classes trie",
"required": false,
"schema": {
"title": "classes",
"type": "array",
"items": {
"$ref": "#/components/schemas/FELT"
}
}
},
{
"name": "contract_addresses",
"description": "a list of contracts for which we want to prove membership in the global state trie",
"required": false,
"schema": {
"title": "contracts",
"type": "array",
"items": {
"$ref": "#/components/schemas/ADDRESS"
}
}
},
{
"name": "contracts_storage_keys",
"description": "a list of (contract_address, storage_keys) pairs",
"required": false,
"schema": {
"type": "array",
"items": {
"type": "object",
"properties": {
"contract_address": {
"$ref": "#/components/schemas/ADDRESS"
},
"storage_keys": {
{
"name": "block_id",
"description": "The hash of the requested block, or number (height) of the requested block, or a block tag",
"required": true,
"schema": {
"title": "Block id",
"$ref": "#/components/schemas/BLOCK_ID"
}
},
{
"name": "class_hashes",
"description": "a list of the class hashes for which we want to prove membership in the classes trie",
"required": false,
"schema": {
"title": "classes",
"type": "array",
"items": {
"$ref": "#/components/schemas/FELT"
}
}
},
{
"name": "contract_addresses",
"description": "a list of contracts for which we want to prove membership in the global state trie",
"required": false,
"schema": {
"title": "contracts",
"type": "array",
"items": {
"$ref": "#/components/schemas/ADDRESS"
}
}
},
{
"name": "contracts_storage_keys",
"description": "a list of (contract_address, storage_keys) pairs",
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was curious about a very dumb edge case: When a client says that it wants a contract storage proof for a key in a specific contract, and that same contract does not appear in contracts_proof, just in contracts_storage_proofs - does that mean that we have to return a proof for that contract in the contract trie anyway?
if we don't that would mean that storage proof is not verifiable by itself, but maybe that's the client's fault for not putting it in the contracts_proof too

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right regards verification. IMO best is to return whatever requested and no guessing how it's used on the client side

"required": false,
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/FELT"
"type": "object",
"properties": {
"contract_address": {
"$ref": "#/components/schemas/ADDRESS"
},
"storage_keys": {
"type": "array",
"items": {
"$ref": "#/components/schemas/FELT"
}
}
},
"required": ["contract_address", "storage_keys"]
}
}
},
"required": ["contract_address", "storage_keys"]
}
}
}
}
],
"result": {
"name": "result",
"description": "The contract's nonce at the requested state",
"description": "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",
"schema": {
"type": "object",
"properties": {
"classes_proof": {
"$ref": "#/components/schemas/NODE_HASH_TO_NODE_MAPPING"
},
"contracts_proof": {
"$ref": "#/components/schemas/NODE_HASH_TO_NODE_MAPPING"
"type": "object",
"properties": {
"nodes": {
"description": "The nodes in the union of the paths from the contracts tree root to the requested leaves",
"$ref": "#/components/schemas/NODE_HASH_TO_NODE_MAPPING"
},
"contract_leaves_data": {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how do we know which contract corresponds to which contract leaf data here? just using the index of the contract address in the request?

Copy link

@pnowosie pnowosie Oct 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 for the index, but more convenient would be add an address here as well.

Copy link

@cchudant cchudant Oct 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for context, I am sorting the request keys before traversing the trie in db, so that I can do the whole trie proof using the optimal least amount of backtracking/revisiting nodes possible - I would have a preference toward letting full nodes return the data in whatever order they want
I can re-sort the resulting array in the requested order if the spec really needs that, I just find it weird that it needs it

EDIT: actually nevermind it doesnt matter at all for this field, we can do either

"type": "array",
"items": {
"description": "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": "object",
"properties": {
"nonce": {
"$ref": "#/components/schemas/FELT"
},
"class_hash": {
"$ref": "#/components/schemas/FELT"
}
},
"required": ["nonce", "class_hash"]
}
}
},
"required": ["nodes", "contract_leaves_data"]
},
"contracts_storage_proofs": {
"type": "array",
"items": {
"$ref": "#/components/schemas/NODE_HASH_TO_NODE_MAPPING"
}
},
"global_roots": {
"type": "object",
"properties": {
"contracts_tree_root": {
"$ref": "#/components/schemas/FELT"
},
"classes_tree_root": {
"$ref": "#/components/schemas/FELT"
},
"block_hash": {
"description": "the associated block hash (needed in case the caller used a block tag for the block_id parameter)",
"$ref": "#/components/schemas/FELT"
}
},
"required": ["contracts_tree_root", "classes_tree_root", "block_hash"]
}
},
"required": [
"classes_proof",
"contracts_proof",
"contracts_storage_proofs"
"contracts_storage_proofs",
"global_roots"
]
}
},
"errors": [
{
"$ref": "#/components/errors/BLOCK_NOT_FOUND"
},
{
"$ref": "#/components/errors/STORAGE_PROOF_NOT_SUPPORTED"
}
]
}
}
],
Expand Down Expand Up @@ -3634,42 +3691,53 @@
"required": ["l1_gas", "l1_data_gas", "l2_gas"]
},
"MERKLE_NODE": {
"title": "MP node",
"description": "a node in the Merkle-Patricia tree, can be a leaf, binary node, or an edge node",
"oneOf": [
{
"$ref": "#/components/schemas/BINARY_NODE"
},
{
"$ref": "#/components/schemas/EDGE_NODE"
}
]
},
"BINARY_NODE": {
"type": "object",
"description": "an internal node whose both children are non-zero",
"properties": {
"path": {
"type": "integer"
},
"length": {
"type": "integer"
},
"value": {
"$ref": "#/components/schemas/FELT"
},
"children_hashes": {
"type": "object",
"description": "the hash of the child nodes, if not present then the node is a leaf",
"properties": {
"left": {
"left": {
"description": "the hash of the left child",
"$ref": "#/components/schemas/FELT"
},
"right": {
},
"right": {
"description": "the hash of the right child",
"$ref": "#/components/schemas/FELT"
}
}
},
"required": ["left", "right"]
},
"EDGE_NODE": {
"type": "object",
"description": "represents a path to the highest non-zero descendant node",
"properties": {
"path": {
Copy link

@cchudant cchudant Oct 8, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for edge nodes: i believe the path could be 251 bit long in the worst case, which won't fit in the type "integer" i think. I think a Felt here makes sense?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right now I'm using: Path -> Felt -> Int so the result is be garbage if overflowed 🤓

"description": "an integer whose binary representation represents the path from the current node to its highest non-zero descendant (bounded by 2^251)",
"$ref": "#/components/schemas/NUM_AS_HEX"
},
"required": [
"left",
"right"
]
}
"length": {
"description": "the length of the path (bounded by 251)",
"type": "integer"
},
"child": {
"description": "the hash of the unique non-zero maximal-height descendant node",
"$ref": "#/components/schemas/FELT"
}
},
"required": [
"path",
"length",
"value"
]
"required": ["path", "length", "child"]
},
"NODE_HASH_TO_NODE_MAPPING": {
"description": "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)",
"description": "a node_hash -> node mapping of all the nodes in the union of the paths between the requested leaves and the root",
"type": "array",
"items": {
"type": "object",
Expand Down Expand Up @@ -3796,6 +3864,10 @@
},
"required": ["transaction_index", "execution_error"]
}
},
"STORAGE_PROOF_NOT_SUPPORTED": {
"code": 42,
"message": "the node doesn't support storage proofs for blocks that are too far in the past"
}
}
}
Expand Down