Skip to content

Commit 39fbd31

Browse files
authored
feat(provider): storage proofs for pre fork point blocks (#365)
1 parent e8014f1 commit 39fbd31

File tree

6 files changed

+487
-28
lines changed

6 files changed

+487
-28
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/rpc/rpc-types/src/trie.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use katana_trie::bitvec::view::BitView;
77
use katana_trie::{BitVec, MultiProof, Path, ProofNode};
88
use serde::{Deserialize, Serialize};
99

10-
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
10+
#[derive(Debug, Clone, Default, Serialize, Deserialize, Eq, PartialEq, Hash)]
1111
pub struct ContractStorageKeys {
1212
#[serde(rename = "contract_address")]
1313
pub address: ContractAddress,
@@ -25,7 +25,7 @@ pub struct GlobalRoots {
2525
}
2626

2727
/// Node in the Merkle-Patricia trie.
28-
#[derive(Debug, Clone, Serialize, Deserialize)]
28+
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2929
#[serde(untagged)]
3030
pub enum MerkleNode {
3131
/// Represents a path to the highest non-zero descendant node.
@@ -107,13 +107,13 @@ pub struct ContractStorageProofs {
107107
pub nodes: Vec<Nodes>,
108108
}
109109

110-
#[derive(Debug, Clone, Serialize, Deserialize)]
110+
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
111111
pub struct NodeWithHash {
112112
pub node_hash: Felt,
113113
pub node: MerkleNode,
114114
}
115115

116-
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
116+
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
117117
#[serde(transparent)]
118118
pub struct Nodes(pub Vec<NodeWithHash>);
119119

crates/storage/fork/src/lib.rs

Lines changed: 243 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,15 @@ use katana_primitives::class::{
2525
};
2626
use katana_primitives::contract::{ContractAddress, Nonce, StorageKey, StorageValue};
2727
use katana_primitives::transaction::TxHash;
28+
use katana_primitives::Felt;
2829
use katana_rpc_client::starknet::{
2930
Client as StarknetClient, Error as StarknetClientError, StarknetApiError,
3031
};
3132
use katana_rpc_types::class::Class;
32-
use katana_rpc_types::{GetBlockWithReceiptsResponse, StateUpdate, TxReceiptWithBlockInfo};
33+
use katana_rpc_types::{
34+
ContractStorageKeys, GetBlockWithReceiptsResponse, GetStorageProofResponse, StateUpdate,
35+
TxReceiptWithBlockInfo,
36+
};
3337
use parking_lot::Mutex;
3438
use tracing::{error, trace};
3539

@@ -246,6 +250,105 @@ impl Backend {
246250
}
247251
}
248252

253+
pub fn get_classes_proofs(
254+
&self,
255+
classes: Vec<ClassHash>,
256+
block_id: BlockNumber,
257+
) -> Result<Option<GetStorageProofResponse>, BackendClientError> {
258+
trace!(classes = %classes.len(), block = %block_id, "Requesting classes proofs.");
259+
let (req, rx) = BackendRequest::classes_proof(classes, block_id);
260+
self.request(req)?;
261+
match rx.recv()? {
262+
BackendResponse::Proofs(res) => {
263+
if let Some(proofs) = handle_not_found_err(res)? {
264+
Ok(Some(proofs))
265+
} else {
266+
Ok(None)
267+
}
268+
}
269+
response => Err(BackendClientError::UnexpectedResponse(anyhow!("{response:?}"))),
270+
}
271+
}
272+
273+
pub fn get_contracts_proofs(
274+
&self,
275+
contracts: Vec<ContractAddress>,
276+
block_id: BlockNumber,
277+
) -> Result<Option<GetStorageProofResponse>, BackendClientError> {
278+
trace!(contracts = %contracts.len(), block = %block_id, "Requesting contracts proofs.");
279+
let (req, rx) = BackendRequest::contracts_proof(contracts, block_id);
280+
self.request(req)?;
281+
match rx.recv()? {
282+
BackendResponse::Proofs(res) => {
283+
if let Some(proofs) = handle_not_found_err(res)? {
284+
Ok(Some(proofs))
285+
} else {
286+
Ok(None)
287+
}
288+
}
289+
response => Err(BackendClientError::UnexpectedResponse(anyhow!("{response:?}"))),
290+
}
291+
}
292+
293+
pub fn get_storages_proofs(
294+
&self,
295+
storages: Vec<ContractStorageKeys>,
296+
block_id: BlockNumber,
297+
) -> Result<Option<GetStorageProofResponse>, BackendClientError> {
298+
trace!(contracts = %storages.len(), block = %block_id, "Requesting storages proofs.");
299+
let (req, rx) = BackendRequest::storages_proof(storages, block_id);
300+
self.request(req)?;
301+
match rx.recv()? {
302+
BackendResponse::Proofs(res) => {
303+
if let Some(proofs) = handle_not_found_err(res)? {
304+
Ok(Some(proofs))
305+
} else {
306+
Ok(None)
307+
}
308+
}
309+
response => Err(BackendClientError::UnexpectedResponse(anyhow!("{response:?}"))),
310+
}
311+
}
312+
313+
pub fn get_global_roots(
314+
&self,
315+
block_id: BlockNumber,
316+
) -> Result<Option<GetStorageProofResponse>, BackendClientError> {
317+
trace!(block = %block_id, "Requesting state roots.");
318+
let (req, rx) = BackendRequest::global_roots(block_id);
319+
self.request(req)?;
320+
match rx.recv()? {
321+
BackendResponse::GlobalRoots(res) => {
322+
if let Some(roots) = handle_not_found_err(res)? {
323+
Ok(Some(roots))
324+
} else {
325+
Ok(None)
326+
}
327+
}
328+
response => Err(BackendClientError::UnexpectedResponse(anyhow!("{response:?}"))),
329+
}
330+
}
331+
332+
pub fn get_storage_root(
333+
&self,
334+
contract: ContractAddress,
335+
block_id: BlockNumber,
336+
) -> Result<Option<Felt>, BackendClientError> {
337+
trace!(%contract, block = %block_id, "Requesting storage root.");
338+
let (req, rx) = BackendRequest::storage_root(contract, block_id);
339+
self.request(req)?;
340+
match rx.recv()? {
341+
BackendResponse::StorageRoot(res) => {
342+
if let Some(root) = handle_not_found_err(res)? {
343+
Ok(Some(root))
344+
} else {
345+
Ok(None)
346+
}
347+
}
348+
response => Err(BackendClientError::UnexpectedResponse(anyhow!("{response:?}"))),
349+
}
350+
}
351+
249352
/// Send a request to the backend thread.
250353
fn request(&self, req: BackendRequest) -> Result<(), BackendClientError> {
251354
self.sender.lock().try_send(req).map_err(|e| e.into_send_error())?;
@@ -277,6 +380,9 @@ enum BackendResponse {
277380
Storage(BackendResult<StorageValue>),
278381
ClassHashAt(BackendResult<ClassHash>),
279382
ClassAt(BackendResult<Class>),
383+
StorageRoot(BackendResult<Felt>),
384+
GlobalRoots(BackendResult<GetStorageProofResponse>),
385+
Proofs(BackendResult<GetStorageProofResponse>),
280386
}
281387

282388
/// Errors that can occur when interacting with the backend.
@@ -295,16 +401,26 @@ struct Request<P> {
295401
sender: OneshotSender<BackendResponse>,
296402
}
297403

404+
#[derive(Eq, Hash, PartialEq, Clone, Debug)]
405+
enum ProofsType {
406+
Classes(Vec<ClassHash>),
407+
Contracts(Vec<ContractAddress>),
408+
Storages(Vec<ContractStorageKeys>),
409+
}
410+
298411
/// The types of request that can be sent to [`Backend`].
299412
///
300413
/// Each request consists of a payload and the sender half of a oneshot channel that will be used
301414
/// to send the result back to the backend handle.
302415
enum BackendRequest {
303416
Receipt(Request<TxHash>),
417+
GlobalRoots(Request<BlockNumber>),
418+
StorageRoot(Request<(ContractAddress, BlockNumber)>),
304419
Block(Request<BlockHashOrNumber>),
305420
StateUpdate(Request<BlockHashOrNumber>),
306-
Nonce(Request<(ContractAddress, BlockNumber)>),
307421
Class(Request<(ClassHash, BlockNumber)>),
422+
Proofs(Request<(ProofsType, BlockNumber)>),
423+
Nonce(Request<(ContractAddress, BlockNumber)>),
308424
ClassHash(Request<(ContractAddress, BlockNumber)>),
309425
Storage(Request<((ContractAddress, StorageKey), BlockNumber)>),
310426
}
@@ -364,16 +480,59 @@ impl BackendRequest {
364480
let (sender, receiver) = oneshot();
365481
(BackendRequest::Storage(Request { payload: ((address, key), block_id), sender }), receiver)
366482
}
483+
484+
fn classes_proof(
485+
classes: Vec<ClassHash>,
486+
block_id: BlockNumber,
487+
) -> (BackendRequest, OneshotReceiver<BackendResponse>) {
488+
let (sender, receiver) = oneshot();
489+
let payload = (ProofsType::Classes(classes), block_id);
490+
(BackendRequest::Proofs(Request { payload, sender }), receiver)
491+
}
492+
493+
fn contracts_proof(
494+
contracts: Vec<ContractAddress>,
495+
block_id: BlockNumber,
496+
) -> (BackendRequest, OneshotReceiver<BackendResponse>) {
497+
let (sender, receiver) = oneshot();
498+
let payload = (ProofsType::Contracts(contracts), block_id);
499+
(BackendRequest::Proofs(Request { payload, sender }), receiver)
500+
}
501+
502+
fn storages_proof(
503+
storages: Vec<ContractStorageKeys>,
504+
block_id: BlockNumber,
505+
) -> (BackendRequest, OneshotReceiver<BackendResponse>) {
506+
let (sender, receiver) = oneshot();
507+
let payload = (ProofsType::Storages(storages), block_id);
508+
(BackendRequest::Proofs(Request { payload, sender }), receiver)
509+
}
510+
511+
fn global_roots(block_id: BlockNumber) -> (BackendRequest, OneshotReceiver<BackendResponse>) {
512+
let (sender, receiver) = oneshot();
513+
(BackendRequest::GlobalRoots(Request { payload: block_id, sender }), receiver)
514+
}
515+
516+
fn storage_root(
517+
contract: ContractAddress,
518+
block_id: BlockNumber,
519+
) -> (BackendRequest, OneshotReceiver<BackendResponse>) {
520+
let (sender, receiver) = oneshot();
521+
(BackendRequest::StorageRoot(Request { payload: (contract, block_id), sender }), receiver)
522+
}
367523
}
368524

369525
type BackendRequestFuture = BoxFuture<'static, BackendResponse>;
370526

371527
// Identifier for pending requests.
372528
// This is used for request deduplication.
373-
#[derive(Eq, Hash, PartialEq, Clone, Copy, Debug)]
529+
#[derive(Eq, Hash, PartialEq, Clone, Debug)]
374530
enum BackendRequestIdentifier {
375531
Receipt(TxHash),
532+
GlobalRoots(BlockNumber),
533+
StorageRoot(ContractAddress, BlockNumber),
376534
Block(BlockHashOrNumber),
535+
Proofs(ProofsType, BlockNumber),
377536
StateUpdate(BlockHashOrNumber),
378537
Nonce(ContractAddress, BlockNumber),
379538
Class(ClassHash, BlockNumber),
@@ -554,6 +713,86 @@ impl BackendWorker {
554713
}),
555714
);
556715
}
716+
717+
BackendRequest::Proofs(Request { payload: (r#type, block_id), sender }) => {
718+
let req_key = BackendRequestIdentifier::Proofs(r#type.clone(), block_id);
719+
let block_id = BlockIdOrTag::from(block_id);
720+
721+
let request: BoxFuture<'static, BackendResponse> = match r#type {
722+
ProofsType::Classes(classes) => Box::pin(async move {
723+
let res = provider
724+
.get_storage_proof(block_id, Some(classes), None, None)
725+
.await
726+
.map_err(|e| BackendError::StarknetProvider(Arc::new(e)));
727+
728+
BackendResponse::Proofs(res)
729+
}),
730+
731+
ProofsType::Contracts(contracts) => Box::pin(async move {
732+
let res = provider
733+
.get_storage_proof(block_id, None, Some(contracts), None)
734+
.await
735+
.map_err(|e| BackendError::StarknetProvider(Arc::new(e)));
736+
737+
BackendResponse::Proofs(res)
738+
}),
739+
740+
ProofsType::Storages(storage_keys) => Box::pin(async move {
741+
let res = provider
742+
.get_storage_proof(block_id, None, None, Some(storage_keys))
743+
.await
744+
.map_err(|e| BackendError::StarknetProvider(Arc::new(e)));
745+
746+
BackendResponse::Proofs(res)
747+
}),
748+
};
749+
750+
self.dedup_request(req_key, sender, request);
751+
}
752+
753+
BackendRequest::GlobalRoots(Request { payload: block_id, sender }) => {
754+
let req_key = BackendRequestIdentifier::GlobalRoots(block_id);
755+
let block_id = BlockIdOrTag::from(block_id);
756+
757+
self.dedup_request(
758+
req_key,
759+
sender,
760+
Box::pin(async move {
761+
let res = provider
762+
.get_storage_proof(block_id, None, None, None)
763+
.await
764+
.map_err(|e| BackendError::StarknetProvider(Arc::new(e)));
765+
766+
BackendResponse::GlobalRoots(res)
767+
}),
768+
);
769+
}
770+
771+
BackendRequest::StorageRoot(Request { payload: (contract, block_id), sender }) => {
772+
let req_key = BackendRequestIdentifier::StorageRoot(contract, block_id);
773+
let block_id = BlockIdOrTag::from(block_id);
774+
775+
self.dedup_request(
776+
req_key,
777+
sender,
778+
Box::pin(async move {
779+
let res = provider
780+
.get_storage_proof(block_id, None, Some(vec![contract]), None)
781+
.await
782+
.map(|mut proof| {
783+
proof
784+
.contracts_proof
785+
.contract_leaves_data
786+
.pop()
787+
.expect("must exist")
788+
.storage_root
789+
})
790+
.map_err(|e| BackendError::StarknetProvider(Arc::new(e)));
791+
792+
BackendResponse::StorageRoot(res)
793+
}),
794+
);
795+
}
557796
}
558797
}
559798

@@ -563,7 +802,7 @@ impl BackendWorker {
563802
sender: OneshotSender<BackendResponse>,
564803
rpc_call_future: BoxFuture<'static, BackendResponse>,
565804
) {
566-
if let Entry::Vacant(e) = self.request_dedup_map.entry(req_key) {
805+
if let Entry::Vacant(e) = self.request_dedup_map.entry(req_key.clone()) {
567806
self.pending_requests.push((req_key, rpc_call_future));
568807
e.insert(vec![sender]);
569808
} else {

crates/storage/provider/provider/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ katana-rpc-client.workspace = true
1717
katana-trie.workspace = true
1818

1919
anyhow.workspace = true
20+
assert_matches.workspace = true
2021
auto_impl.workspace = true
2122
bitvec.workspace = true
2223
lazy_static.workspace = true

0 commit comments

Comments
 (0)