From e8f34ba3867fd6d62790122317224d8c65cdf887 Mon Sep 17 00:00:00 2001 From: Arun Jangra Date: Sat, 13 Jul 2024 13:20:33 +0530 Subject: [PATCH 001/282] feat : added pathfinder_getClassProof method --- .gitignore | 5 +- crates/merkle-tree/src/class.rs | 173 +++++++++++++++++- crates/merkle-tree/src/lib.rs | 2 +- crates/rpc/src/pathfinder.rs | 1 + crates/rpc/src/pathfinder/methods.rs | 1 + .../rpc/src/pathfinder/methods/get_proof.rs | 111 ++++++++++- 6 files changed, 283 insertions(+), 10 deletions(-) diff --git a/.gitignore b/.gitignore index c44283c5da..050cdb9ab0 100644 --- a/.gitignore +++ b/.gitignore @@ -24,4 +24,7 @@ pathfinder-var.env .vscode/ # mdbook compilation -**/book/ \ No newline at end of file +**/book/ + +# Intellij IDE generated files +.idea \ No newline at end of file diff --git a/crates/merkle-tree/src/class.rs b/crates/merkle-tree/src/class.rs index 273060c9f3..1131a2f770 100644 --- a/crates/merkle-tree/src/class.rs +++ b/crates/merkle-tree/src/class.rs @@ -1,16 +1,19 @@ +use crate::merkle_node::InternalNode; use anyhow::Context; -use pathfinder_common::hash::PoseidonHash; +use bitvec::order::Msb0; +use bitvec::prelude::BitSlice; +use bitvec::view::BitView; +use pathfinder_common::hash::{PedersenHash, PoseidonHash}; +use pathfinder_common::trie::TrieNode; use pathfinder_common::{ - BlockNumber, - ClassCommitment, - ClassCommitmentLeafHash, - ClassHash, - SierraHash, + BlockNumber, CasmHash, ClassCommitment, ClassCommitmentLeafHash, ClassHash, ContractAddress, + ContractRoot, SierraHash, StorageAddress, StorageValue, }; use pathfinder_crypto::Felt; use pathfinder_storage::{Transaction, TrieUpdate}; +use std::ops::ControlFlow; -use crate::tree::MerkleTree; +use crate::tree::{MerkleTree, Visit}; /// A [Patricia Merkle tree](MerkleTree) used to calculate commitments to /// Starknet's Sierra classes. @@ -71,6 +74,162 @@ impl<'tx> ClassCommitmentTree<'tx> { let commitment = ClassCommitment(update.root_commitment); Ok((commitment, update)) } + + /// Generates a proof for a given `key` + pub fn get_proof( + tx: &'tx Transaction<'tx>, + block: BlockNumber, + class_hash: ClassHash, + ) -> anyhow::Result>> { + let root = tx + .class_root_index(block) + .context("Querying class root index")?; + + let Some(root) = root else { + return Ok(None); + }; + + let storage = ClassTrieStorage { + tx, + block: Some(block), + }; + + let casm = tx + .casm_hash_at(block.into(), class_hash) + .context("Querying CASM hash")?; + + let Some(casm) = casm else { + return Ok(None); + }; + + MerkleTree::::get_proof(root, &storage, casm.view_bits()) + } +} + +/// A [Patricia Merkle tree](MerkleTree) used to calculate commitments to +/// Starknet's Sierra classes. +/// +/// It maps a class's [SierraHash] to its [ClassCommitmentLeafHash] +/// +/// Tree data is persisted by a sqlite table 'tree_class'. + +pub struct ClassStorageTree<'tx> { + tree: MerkleTree, + storage: ClassStorage<'tx>, +} + +impl<'tx> ClassStorageTree<'tx> { + pub fn empty(tx: &'tx Transaction<'tx>) -> Self { + let storage = ClassStorage { tx, block: None }; + let tree = MerkleTree::empty(); + + Self { tree, storage } + } + + pub fn load(tx: &'tx Transaction<'tx>, block: BlockNumber) -> anyhow::Result { + let root = tx + .class_root_index(block) + .context("Querying class root index")?; + + let Some(root) = root else { + return Ok(Self::empty(tx)); + }; + + let storage = ClassStorage { + tx, + block: Some(block), + }; + + let tree = MerkleTree::new(root); + + Ok(Self { tree, storage }) + } + + pub fn with_verify_hashes(mut self, verify_hashes: bool) -> Self { + self.tree = self.tree.with_verify_hashes(verify_hashes); + self + } + + /// Generates a proof for `key`. See [`MerkleTree::get_proof`]. + pub fn get_proof( + tx: &'tx Transaction<'tx>, + block: BlockNumber, + key: &BitSlice, + ) -> anyhow::Result>> { + let root = tx + .class_root_index(block) + .context("Querying class root index")?; + + let Some(root) = root else { + return Ok(None); + }; + + let storage = ClassStorage { + tx, + block: Some(block), + }; + + MerkleTree::::get_proof(root, &storage, key) + } + + pub fn set(&mut self, address: StorageAddress, value: StorageValue) -> anyhow::Result<()> { + let key = address.view_bits().to_owned(); + self.tree.set(&self.storage, key, value.0) + } + + /// Commits the changes and calculates the new node hashes. Returns the new + /// commitment and any potentially newly created nodes. + pub fn commit(self) -> anyhow::Result<(CasmHash, TrieUpdate)> { + let update = self.tree.commit(&self.storage)?; + let commitment = CasmHash(update.root_commitment); + Ok((commitment, update)) + } + + /// See [`MerkleTree::dfs`] + pub fn dfs) -> ControlFlow>( + &mut self, + f: &mut F, + ) -> anyhow::Result> { + self.tree.dfs(&self.storage, f) + } +} + +struct ClassTrieStorage<'tx> { + tx: &'tx Transaction<'tx>, + block: Option, +} + +impl crate::storage::Storage for ClassTrieStorage<'_> { + fn get(&self, index: u64) -> anyhow::Result> { + self.tx.storage_trie_node(index) + } + + fn hash(&self, index: u64) -> anyhow::Result> { + self.tx.storage_trie_node_hash(index) + } + + fn leaf(&self, path: &BitSlice) -> anyhow::Result> { + assert!(path.len() == 251); + + let Some(block) = self.block else { + return Ok(None); + }; + + let sierra = + ClassHash(Felt::from_bits(path).context("Mapping leaf path to contract address")?); + + let casm = self + .tx + .casm_hash_at(block.into(), sierra) + .context("Querying CASM hash")?; + let Some(casm) = casm else { + return Ok(None); + }; + + let value = self.tx.class_commitment_leaf(block, &casm)?.map(|x| x.0); + + Ok(value) + } } struct ClassStorage<'tx> { diff --git a/crates/merkle-tree/src/lib.rs b/crates/merkle-tree/src/lib.rs index 07ef9424f1..0bf2bbcdce 100644 --- a/crates/merkle-tree/src/lib.rs +++ b/crates/merkle-tree/src/lib.rs @@ -3,7 +3,7 @@ pub mod merkle_node; pub mod storage; pub mod tree; -mod class; +pub mod class; mod contract; mod transaction; diff --git a/crates/rpc/src/pathfinder.rs b/crates/rpc/src/pathfinder.rs index 4b0ca8ec4a..f491080f6e 100644 --- a/crates/rpc/src/pathfinder.rs +++ b/crates/rpc/src/pathfinder.rs @@ -8,4 +8,5 @@ pub fn register_routes() -> RpcRouterBuilder { .register("pathfinder_version", || { pathfinder_common::consts::VERGEN_GIT_DESCRIBE }) .register("pathfinder_getProof", methods::get_proof) .register("pathfinder_getTransactionStatus", methods::get_transaction_status) + .register("pathfinder_getClassProof", methods::get_proof_class) } diff --git a/crates/rpc/src/pathfinder/methods.rs b/crates/rpc/src/pathfinder/methods.rs index eab78fb2bd..a0e8bd5a7f 100644 --- a/crates/rpc/src/pathfinder/methods.rs +++ b/crates/rpc/src/pathfinder/methods.rs @@ -2,4 +2,5 @@ mod get_proof; mod get_transaction_status; pub(crate) use get_proof::get_proof; +pub(crate) use get_proof::get_proof_class; pub(crate) use get_transaction_status::get_transaction_status; diff --git a/crates/rpc/src/pathfinder/methods/get_proof.rs b/crates/rpc/src/pathfinder/methods/get_proof.rs index 3b5118f09d..99cf00d3ce 100644 --- a/crates/rpc/src/pathfinder/methods/get_proof.rs +++ b/crates/rpc/src/pathfinder/methods/get_proof.rs @@ -3,7 +3,8 @@ use pathfinder_common::prelude::*; use pathfinder_common::trie::TrieNode; use pathfinder_common::BlockId; use pathfinder_crypto::Felt; -use pathfinder_merkle_tree::{ContractsStorageTree, StorageCommitmentTree}; +use pathfinder_merkle_tree::class::ClassStorageTree; +use pathfinder_merkle_tree::{ClassCommitmentTree, ContractsStorageTree, StorageCommitmentTree}; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; @@ -16,6 +17,13 @@ pub struct GetProofInput { pub keys: Vec, } +#[derive(Deserialize, Debug, PartialEq, Eq)] +pub struct GetProofInputClass { + pub block_id: BlockId, + pub class_hash: ClassHash, + pub keys: Vec, +} + // FIXME: allow `generate_rpc_error_subset!` to work with enum struct variants. // This may not actually be possible though. #[derive(Debug)] @@ -151,6 +159,18 @@ pub struct GetProofOutput { contract_data: Option, } +#[derive(Debug, Serialize)] +#[skip_serializing_none] +pub struct GetProofOutputClass { + /// Required to verify that the hash of the class commitment and the root of + /// the [contract_proof](GetProofOutput::contract_proof) matches the + /// [state_commitment](Self#state_commitment). Present only for Starknet + /// blocks 0.11.0 onwards. + class_commitment: Option, + /// Membership / Non-membership proof for the queried contract classes + class_proof: ProofNodes, +} + /// Returns all the necessary data to trustlessly verify storage slots for a /// particular contract. pub async fn get_proof( @@ -278,6 +298,95 @@ pub async fn get_proof( jh.await.context("Database read panic or shutting down")? } +/// Returns all the necessary data to trustlessly verify class changes for a +/// particular contract. +pub async fn get_proof_class( + context: RpcContext, + input: GetProofInputClass, +) -> Result { + const MAX_KEYS: usize = 100; + if input.keys.len() > MAX_KEYS { + return Err(GetProofError::ProofLimitExceeded { + limit: MAX_KEYS as u32, + requested: input.keys.len() as u32, + }); + } + + let block_id = match input.block_id { + BlockId::Pending => { + return Err(GetProofError::Internal(anyhow!( + "'pending' is not currently supported by this method!" + ))) + } + other => other.try_into().expect("Only pending cast should fail"), + }; + + let storage = context.storage.clone(); + let span = tracing::Span::current(); + + let jh = tokio::task::spawn_blocking(move || { + let _g = span.enter(); + let mut db = storage + .connection() + .context("Opening database connection")?; + + let tx = db.transaction().context("Creating database transaction")?; + + // Use internal error to indicate that the process of querying for a particular + // block failed, which is not the same as being sure that the block is + // not in the db. + let header = tx + .block_header(block_id) + .context("Fetching block header")? + .ok_or(GetProofError::BlockNotFound)?; + + let class_commitment = match header.class_commitment { + ClassCommitment::ZERO => None, + other => Some(other), + }; + + // Generate a proof for this class. If the class does not exist, this will + // be a "non membership" proof. + let class_proof = ClassCommitmentTree::get_proof(&tx, header.number, input.class_hash) + .context("Creating contract proof")? + .ok_or(GetProofError::ProofMissing)?; + let class_proof = ProofNodes(class_proof); + + let class_root_exists = tx + .class_root_exists(header.number) + .context("Fetching class root existence")?; + + if !class_root_exists { + return Ok(GetProofOutputClass { + class_commitment, + class_proof, + }); + }; + + let mut class_proofs = Vec::new(); + for k in &input.keys { + let proof = ClassStorageTree::get_proof(&tx, header.number, k.view_bits()) + .context("Get proof from class tree")? + .ok_or_else(|| { + let e = anyhow!( + "Storage proof missing for key {:?}, but should be present", + k + ); + tracing::warn!("{e}"); + e + })?; + class_proofs.push(ProofNodes(proof)); + } + + Ok(GetProofOutputClass { + class_commitment, + class_proof, + }) + }); + + jh.await.context("Database read panic or shutting down")? +} + #[cfg(test)] mod tests { use pathfinder_common::macro_prelude::*; From 64daf4cf579efd692abf2042f5e82693e9c7df01 Mon Sep 17 00:00:00 2001 From: Arun Jangra Date: Mon, 15 Jul 2024 17:02:25 +0530 Subject: [PATCH 002/282] feat : added poseidon hash for class tree function and fixed lint tests --- .gitignore | 3 --- .idea/.gitignore | 5 ++++ .idea/modules.xml | 8 ++++++ .idea/pathfinder.iml | 37 ++++++++++++++++++++++++++++ .idea/vcs.xml | 6 +++++ crates/merkle-tree/src/class.rs | 20 +++++++++------ crates/rpc/src/pathfinder/methods.rs | 3 +-- 7 files changed, 70 insertions(+), 12 deletions(-) create mode 100644 .idea/.gitignore create mode 100644 .idea/modules.xml create mode 100644 .idea/pathfinder.iml create mode 100644 .idea/vcs.xml diff --git a/.gitignore b/.gitignore index 050cdb9ab0..ed158cfecf 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,3 @@ pathfinder-var.env # mdbook compilation **/book/ - -# Intellij IDE generated files -.idea \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000000..b58b603fea --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,5 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000000..921c18fcf8 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/pathfinder.iml b/.idea/pathfinder.iml new file mode 100644 index 0000000000..e4c15ed1a5 --- /dev/null +++ b/.idea/pathfinder.iml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000000..35eb1ddfbb --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/crates/merkle-tree/src/class.rs b/crates/merkle-tree/src/class.rs index 1131a2f770..e211ccc469 100644 --- a/crates/merkle-tree/src/class.rs +++ b/crates/merkle-tree/src/class.rs @@ -1,18 +1,24 @@ -use crate::merkle_node::InternalNode; +use std::ops::ControlFlow; + use anyhow::Context; use bitvec::order::Msb0; use bitvec::prelude::BitSlice; -use bitvec::view::BitView; use pathfinder_common::hash::{PedersenHash, PoseidonHash}; use pathfinder_common::trie::TrieNode; use pathfinder_common::{ - BlockNumber, CasmHash, ClassCommitment, ClassCommitmentLeafHash, ClassHash, ContractAddress, - ContractRoot, SierraHash, StorageAddress, StorageValue, + BlockNumber, + CasmHash, + ClassCommitment, + ClassCommitmentLeafHash, + ClassHash, + SierraHash, + StorageAddress, + StorageValue, }; use pathfinder_crypto::Felt; use pathfinder_storage::{Transaction, TrieUpdate}; -use std::ops::ControlFlow; +use crate::merkle_node::InternalNode; use crate::tree::{MerkleTree, Visit}; /// A [Patricia Merkle tree](MerkleTree) used to calculate commitments to @@ -102,7 +108,7 @@ impl<'tx> ClassCommitmentTree<'tx> { return Ok(None); }; - MerkleTree::::get_proof(root, &storage, casm.view_bits()) + MerkleTree::::get_proof(root, &storage, casm.view_bits()) } } @@ -114,7 +120,7 @@ impl<'tx> ClassCommitmentTree<'tx> { /// Tree data is persisted by a sqlite table 'tree_class'. pub struct ClassStorageTree<'tx> { - tree: MerkleTree, + tree: MerkleTree, storage: ClassStorage<'tx>, } diff --git a/crates/rpc/src/pathfinder/methods.rs b/crates/rpc/src/pathfinder/methods.rs index a0e8bd5a7f..d2c6f85074 100644 --- a/crates/rpc/src/pathfinder/methods.rs +++ b/crates/rpc/src/pathfinder/methods.rs @@ -1,6 +1,5 @@ mod get_proof; mod get_transaction_status; -pub(crate) use get_proof::get_proof; -pub(crate) use get_proof::get_proof_class; +pub(crate) use get_proof::{get_proof, get_proof_class}; pub(crate) use get_transaction_status::get_transaction_status; From 8f91005ff2e7be0eaf1f05d09e991b87eaac6095 Mon Sep 17 00:00:00 2001 From: Arun Jangra Date: Mon, 15 Jul 2024 17:02:41 +0530 Subject: [PATCH 003/282] feat : added poseidon hash for class tree function and fixed lint tests --- .idea/.gitignore | 5 ----- .idea/modules.xml | 8 -------- .idea/pathfinder.iml | 37 ------------------------------------- .idea/vcs.xml | 6 ------ 4 files changed, 56 deletions(-) delete mode 100644 .idea/.gitignore delete mode 100644 .idea/modules.xml delete mode 100644 .idea/pathfinder.iml delete mode 100644 .idea/vcs.xml diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index b58b603fea..0000000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml -# Editor-based HTTP Client requests -/httpRequests/ diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index 921c18fcf8..0000000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/pathfinder.iml b/.idea/pathfinder.iml deleted file mode 100644 index e4c15ed1a5..0000000000 --- a/.idea/pathfinder.iml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 35eb1ddfbb..0000000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file From 4d90d6f2359afcbffb0143a9665eefcba6eb97bc Mon Sep 17 00:00:00 2001 From: Olivier Desenfans Date: Mon, 12 Aug 2024 16:58:48 +0200 Subject: [PATCH 004/282] Fix: remove keys and use correct Merkle tree table --- crates/merkle-tree/src/class.rs | 112 ++---------------- .../rpc/src/pathfinder/methods/get_proof.rs | 37 +----- 2 files changed, 17 insertions(+), 132 deletions(-) diff --git a/crates/merkle-tree/src/class.rs b/crates/merkle-tree/src/class.rs index e211ccc469..54c5cf5ffd 100644 --- a/crates/merkle-tree/src/class.rs +++ b/crates/merkle-tree/src/class.rs @@ -1,25 +1,23 @@ -use std::ops::ControlFlow; - use anyhow::Context; use bitvec::order::Msb0; use bitvec::prelude::BitSlice; -use pathfinder_common::hash::{PedersenHash, PoseidonHash}; -use pathfinder_common::trie::TrieNode; + use pathfinder_common::{ - BlockNumber, - CasmHash, + BlockNumber + , ClassCommitment, ClassCommitmentLeafHash, ClassHash, - SierraHash, - StorageAddress, - StorageValue, + SierraHash + + , }; +use pathfinder_common::hash::PoseidonHash; +use pathfinder_common::trie::TrieNode; use pathfinder_crypto::Felt; use pathfinder_storage::{Transaction, TrieUpdate}; -use crate::merkle_node::InternalNode; -use crate::tree::{MerkleTree, Visit}; +use crate::tree::MerkleTree; /// A [Patricia Merkle tree](MerkleTree) used to calculate commitments to /// Starknet's Sierra classes. @@ -112,94 +110,6 @@ impl<'tx> ClassCommitmentTree<'tx> { } } -/// A [Patricia Merkle tree](MerkleTree) used to calculate commitments to -/// Starknet's Sierra classes. -/// -/// It maps a class's [SierraHash] to its [ClassCommitmentLeafHash] -/// -/// Tree data is persisted by a sqlite table 'tree_class'. - -pub struct ClassStorageTree<'tx> { - tree: MerkleTree, - storage: ClassStorage<'tx>, -} - -impl<'tx> ClassStorageTree<'tx> { - pub fn empty(tx: &'tx Transaction<'tx>) -> Self { - let storage = ClassStorage { tx, block: None }; - let tree = MerkleTree::empty(); - - Self { tree, storage } - } - - pub fn load(tx: &'tx Transaction<'tx>, block: BlockNumber) -> anyhow::Result { - let root = tx - .class_root_index(block) - .context("Querying class root index")?; - - let Some(root) = root else { - return Ok(Self::empty(tx)); - }; - - let storage = ClassStorage { - tx, - block: Some(block), - }; - - let tree = MerkleTree::new(root); - - Ok(Self { tree, storage }) - } - - pub fn with_verify_hashes(mut self, verify_hashes: bool) -> Self { - self.tree = self.tree.with_verify_hashes(verify_hashes); - self - } - - /// Generates a proof for `key`. See [`MerkleTree::get_proof`]. - pub fn get_proof( - tx: &'tx Transaction<'tx>, - block: BlockNumber, - key: &BitSlice, - ) -> anyhow::Result>> { - let root = tx - .class_root_index(block) - .context("Querying class root index")?; - - let Some(root) = root else { - return Ok(None); - }; - - let storage = ClassStorage { - tx, - block: Some(block), - }; - - MerkleTree::::get_proof(root, &storage, key) - } - - pub fn set(&mut self, address: StorageAddress, value: StorageValue) -> anyhow::Result<()> { - let key = address.view_bits().to_owned(); - self.tree.set(&self.storage, key, value.0) - } - - /// Commits the changes and calculates the new node hashes. Returns the new - /// commitment and any potentially newly created nodes. - pub fn commit(self) -> anyhow::Result<(CasmHash, TrieUpdate)> { - let update = self.tree.commit(&self.storage)?; - let commitment = CasmHash(update.root_commitment); - Ok((commitment, update)) - } - - /// See [`MerkleTree::dfs`] - pub fn dfs) -> ControlFlow>( - &mut self, - f: &mut F, - ) -> anyhow::Result> { - self.tree.dfs(&self.storage, f) - } -} - struct ClassTrieStorage<'tx> { tx: &'tx Transaction<'tx>, block: Option, @@ -207,11 +117,11 @@ struct ClassTrieStorage<'tx> { impl crate::storage::Storage for ClassTrieStorage<'_> { fn get(&self, index: u64) -> anyhow::Result> { - self.tx.storage_trie_node(index) + self.tx.class_trie_node(index) } fn hash(&self, index: u64) -> anyhow::Result> { - self.tx.storage_trie_node_hash(index) + self.tx.class_trie_node_hash(index) } fn leaf(&self, path: &BitSlice) -> anyhow::Result> { diff --git a/crates/rpc/src/pathfinder/methods/get_proof.rs b/crates/rpc/src/pathfinder/methods/get_proof.rs index 99cf00d3ce..a37508d267 100644 --- a/crates/rpc/src/pathfinder/methods/get_proof.rs +++ b/crates/rpc/src/pathfinder/methods/get_proof.rs @@ -3,7 +3,6 @@ use pathfinder_common::prelude::*; use pathfinder_common::trie::TrieNode; use pathfinder_common::BlockId; use pathfinder_crypto::Felt; -use pathfinder_merkle_tree::class::ClassStorageTree; use pathfinder_merkle_tree::{ClassCommitmentTree, ContractsStorageTree, StorageCommitmentTree}; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; @@ -18,10 +17,9 @@ pub struct GetProofInput { } #[derive(Deserialize, Debug, PartialEq, Eq)] -pub struct GetProofInputClass { +pub struct GetClassProofInput { pub block_id: BlockId, pub class_hash: ClassHash, - pub keys: Vec, } // FIXME: allow `generate_rpc_error_subset!` to work with enum struct variants. @@ -161,7 +159,7 @@ pub struct GetProofOutput { #[derive(Debug, Serialize)] #[skip_serializing_none] -pub struct GetProofOutputClass { +pub struct GetClassProofOutput { /// Required to verify that the hash of the class commitment and the root of /// the [contract_proof](GetProofOutput::contract_proof) matches the /// [state_commitment](Self#state_commitment). Present only for Starknet @@ -302,16 +300,8 @@ pub async fn get_proof( /// particular contract. pub async fn get_proof_class( context: RpcContext, - input: GetProofInputClass, -) -> Result { - const MAX_KEYS: usize = 100; - if input.keys.len() > MAX_KEYS { - return Err(GetProofError::ProofLimitExceeded { - limit: MAX_KEYS as u32, - requested: input.keys.len() as u32, - }); - } - + input: GetClassProofInput, +) -> Result { let block_id = match input.block_id { BlockId::Pending => { return Err(GetProofError::Internal(anyhow!( @@ -357,28 +347,13 @@ pub async fn get_proof_class( .context("Fetching class root existence")?; if !class_root_exists { - return Ok(GetProofOutputClass { + return Ok(GetClassProofOutput { class_commitment, class_proof, }); }; - let mut class_proofs = Vec::new(); - for k in &input.keys { - let proof = ClassStorageTree::get_proof(&tx, header.number, k.view_bits()) - .context("Get proof from class tree")? - .ok_or_else(|| { - let e = anyhow!( - "Storage proof missing for key {:?}, but should be present", - k - ); - tracing::warn!("{e}"); - e - })?; - class_proofs.push(ProofNodes(proof)); - } - - Ok(GetProofOutputClass { + Ok(GetClassProofOutput { class_commitment, class_proof, }) From a894a11a504e93847bb75584a7c438e746f72937 Mon Sep 17 00:00:00 2001 From: Olivier Desenfans Date: Wed, 21 Aug 2024 00:55:40 +0200 Subject: [PATCH 005/282] fix: use class hash instead of casm class hash when generating class proof --- crates/merkle-tree/src/class.rs | 10 +--------- crates/rpc/src/pathfinder/methods/get_proof.rs | 2 +- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/crates/merkle-tree/src/class.rs b/crates/merkle-tree/src/class.rs index 54c5cf5ffd..42abe82ce8 100644 --- a/crates/merkle-tree/src/class.rs +++ b/crates/merkle-tree/src/class.rs @@ -98,15 +98,7 @@ impl<'tx> ClassCommitmentTree<'tx> { block: Some(block), }; - let casm = tx - .casm_hash_at(block.into(), class_hash) - .context("Querying CASM hash")?; - - let Some(casm) = casm else { - return Ok(None); - }; - - MerkleTree::::get_proof(root, &storage, casm.view_bits()) + MerkleTree::::get_proof(root, &storage, class_hash.0.view_bits()) } } diff --git a/crates/rpc/src/pathfinder/methods/get_proof.rs b/crates/rpc/src/pathfinder/methods/get_proof.rs index a37508d267..ccf417b7d0 100644 --- a/crates/rpc/src/pathfinder/methods/get_proof.rs +++ b/crates/rpc/src/pathfinder/methods/get_proof.rs @@ -338,7 +338,7 @@ pub async fn get_proof_class( // Generate a proof for this class. If the class does not exist, this will // be a "non membership" proof. let class_proof = ClassCommitmentTree::get_proof(&tx, header.number, input.class_hash) - .context("Creating contract proof")? + .context("Creating class proof")? .ok_or(GetProofError::ProofMissing)?; let class_proof = ProofNodes(class_proof); From 1bd43fa7ab1078e303714c98c761218d55305ace Mon Sep 17 00:00:00 2001 From: Olivier Desenfans Date: Wed, 21 Aug 2024 11:53:16 +0200 Subject: [PATCH 006/282] changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f3a60d5d80..bb5a2d502f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Pathfinder JSON-RPC extension methods are now also exposed on the `/rpc/pathfinder/v0_1` endpoint. - `--sync.l1-poll-interval` CLI option has been added to set the poll interval for L1 state. Defaults to 30s. +- Added the `pathfinder_getClassProof` endpoint to retrieve the Merkle proof of any class hash in the class trie. ## [0.14.1] - 2024-07-29 From f57246ec2a7f19fa438512dbfff3813ac0b68010 Mon Sep 17 00:00:00 2001 From: Olivier Desenfans Date: Wed, 21 Aug 2024 13:38:34 +0200 Subject: [PATCH 007/282] revert whitespace change in .gitignore --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index ed158cfecf..c44283c5da 100644 --- a/.gitignore +++ b/.gitignore @@ -24,4 +24,4 @@ pathfinder-var.env .vscode/ # mdbook compilation -**/book/ +**/book/ \ No newline at end of file From 2c659a23ea593f4a25d803a9448db9e3636562f7 Mon Sep 17 00:00:00 2001 From: Olivier Desenfans Date: Wed, 21 Aug 2024 13:39:36 +0200 Subject: [PATCH 008/282] rustfmt --- crates/merkle-tree/src/class.rs | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/crates/merkle-tree/src/class.rs b/crates/merkle-tree/src/class.rs index 42abe82ce8..d657bec59f 100644 --- a/crates/merkle-tree/src/class.rs +++ b/crates/merkle-tree/src/class.rs @@ -2,18 +2,11 @@ use anyhow::Context; use bitvec::order::Msb0; use bitvec::prelude::BitSlice; -use pathfinder_common::{ - BlockNumber - , - ClassCommitment, - ClassCommitmentLeafHash, - ClassHash, - SierraHash - - , -}; use pathfinder_common::hash::PoseidonHash; use pathfinder_common::trie::TrieNode; +use pathfinder_common::{ + BlockNumber, ClassCommitment, ClassCommitmentLeafHash, ClassHash, SierraHash, +}; use pathfinder_crypto::Felt; use pathfinder_storage::{Transaction, TrieUpdate}; From 8f8667f81621387d1ad050a42c0801782b1ab678 Mon Sep 17 00:00:00 2001 From: Olivier Desenfans Date: Wed, 21 Aug 2024 13:42:05 +0200 Subject: [PATCH 009/282] remove useless condition --- crates/rpc/src/pathfinder.rs | 2 +- crates/rpc/src/pathfinder/methods/get_proof.rs | 11 ----------- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/crates/rpc/src/pathfinder.rs b/crates/rpc/src/pathfinder.rs index f491080f6e..256e9a0f99 100644 --- a/crates/rpc/src/pathfinder.rs +++ b/crates/rpc/src/pathfinder.rs @@ -7,6 +7,6 @@ pub fn register_routes() -> RpcRouterBuilder { RpcRouter::builder(crate::RpcVersion::PathfinderV01) .register("pathfinder_version", || { pathfinder_common::consts::VERGEN_GIT_DESCRIBE }) .register("pathfinder_getProof", methods::get_proof) - .register("pathfinder_getTransactionStatus", methods::get_transaction_status) .register("pathfinder_getClassProof", methods::get_proof_class) + .register("pathfinder_getTransactionStatus", methods::get_transaction_status) } diff --git a/crates/rpc/src/pathfinder/methods/get_proof.rs b/crates/rpc/src/pathfinder/methods/get_proof.rs index ccf417b7d0..e4b9fad237 100644 --- a/crates/rpc/src/pathfinder/methods/get_proof.rs +++ b/crates/rpc/src/pathfinder/methods/get_proof.rs @@ -342,17 +342,6 @@ pub async fn get_proof_class( .ok_or(GetProofError::ProofMissing)?; let class_proof = ProofNodes(class_proof); - let class_root_exists = tx - .class_root_exists(header.number) - .context("Fetching class root existence")?; - - if !class_root_exists { - return Ok(GetClassProofOutput { - class_commitment, - class_proof, - }); - }; - Ok(GetClassProofOutput { class_commitment, class_proof, From 5753824414a5264e2c24f5e548c242803bc9d584 Mon Sep 17 00:00:00 2001 From: Olivier Desenfans Date: Wed, 21 Aug 2024 13:48:50 +0200 Subject: [PATCH 010/282] rustfmt --- crates/merkle-tree/src/class.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/crates/merkle-tree/src/class.rs b/crates/merkle-tree/src/class.rs index d657bec59f..b3cd3b4783 100644 --- a/crates/merkle-tree/src/class.rs +++ b/crates/merkle-tree/src/class.rs @@ -1,11 +1,14 @@ use anyhow::Context; use bitvec::order::Msb0; use bitvec::prelude::BitSlice; - use pathfinder_common::hash::PoseidonHash; use pathfinder_common::trie::TrieNode; use pathfinder_common::{ - BlockNumber, ClassCommitment, ClassCommitmentLeafHash, ClassHash, SierraHash, + BlockNumber, + ClassCommitment, + ClassCommitmentLeafHash, + ClassHash, + SierraHash, }; use pathfinder_crypto::Felt; use pathfinder_storage::{Transaction, TrieUpdate}; From 738222e2ddd27a87a301156d18ae9a2afa0c8879 Mon Sep 17 00:00:00 2001 From: t00ts Date: Mon, 2 Sep 2024 10:23:16 +0200 Subject: [PATCH 011/282] chore: re-compute `transaction_commitment`, `event_commitment`, `state_diff_commitment` using 0.13.2 calculation --- .../examples/compute_pre0132_hashes.rs | 50 +++++++++++-------- 1 file changed, 30 insertions(+), 20 deletions(-) diff --git a/crates/pathfinder/examples/compute_pre0132_hashes.rs b/crates/pathfinder/examples/compute_pre0132_hashes.rs index 58ff622fec..a7b0c11789 100644 --- a/crates/pathfinder/examples/compute_pre0132_hashes.rs +++ b/crates/pathfinder/examples/compute_pre0132_hashes.rs @@ -8,12 +8,12 @@ use pathfinder_common::{ ReceiptCommitment, StarknetVersion, StateCommitment, - StateDiffCommitment, StorageCommitment, - TransactionCommitment, }; use pathfinder_lib::state::block_hash::{ + calculate_event_commitment, calculate_receipt_commitment, + calculate_transaction_commitment, compute_final_hash, BlockHeaderData, }; @@ -68,12 +68,12 @@ fn main() -> anyhow::Result<()> { let txn_data_for_block = tx .transaction_data_for_block(block_id)? .context("Transaction data missing")?; - drop(tx); // Compute receipt commitment if it's not there if header.receipt_commitment == ReceiptCommitment::ZERO { header.receipt_commitment = calculate_receipt_commitment( txn_data_for_block + .clone() .into_iter() .flat_map(|(_, r, _)| Some(r)) .collect::>() @@ -81,11 +81,33 @@ fn main() -> anyhow::Result<()> { )?; } - // Ensure for non-zero values for all other commitments - // Note: Zero values are allowed for: - // - `class_commitment` - Will be zero until the first Sierra class has been - // declared on chain - // - `event_commitment` - Will be zero when no events are sent in the block + // Recalculate transaction commitment + header.transaction_commitment = calculate_transaction_commitment( + &txn_data_for_block + .iter() + .map(|(tx, _, _)| tx.clone()) + .collect::>(), + VERSION_CUTOFF, + )?; + + // Recalculate event commitment + header.event_commitment = calculate_event_commitment( + &txn_data_for_block + .iter() + .map(|(tx, _, events)| (tx.hash, events.as_slice())) + .collect::>(), + VERSION_CUTOFF, + )?; + + // Recalculate state diff commitment + let state_update = tx + .state_update(block_id)? + .context("Fetching state update")?; + header.state_diff_commitment = state_update.compute_state_diff_commitment(VERSION_CUTOFF); + + drop(tx); + + // Ensure non-zero values for other commitments ensure!( header.state_commitment != StateCommitment::ZERO, "state_commitment missing" @@ -94,18 +116,6 @@ fn main() -> anyhow::Result<()> { header.storage_commitment != StorageCommitment::ZERO, "storage_commitment missing" ); - ensure!( - header.transaction_commitment != TransactionCommitment::ZERO, - "transaction_commitment missing" - ); - ensure!( - header.receipt_commitment != ReceiptCommitment::ZERO, - "receipt_commitment missing" - ); - ensure!( - header.state_diff_commitment != StateDiffCommitment::ZERO, - "state_diff_commitment missing" - ); // Compute the block hash in the 0.13.2 style let header_data = get_header_data(&header); From 6bf31309dcf1723c4aad7488083b7696517b2789 Mon Sep 17 00:00:00 2001 From: sistemd Date: Mon, 2 Sep 2024 12:35:48 +0200 Subject: [PATCH 012/282] refactor v07 rpc to use DeserializeForVersion --- crates/rpc/src/dto.rs | 377 +++++++++++++++--- crates/rpc/src/dto/block.rs | 41 +- crates/rpc/src/dto/class.rs | 5 +- crates/rpc/src/dto/fee.rs | 15 +- crates/rpc/src/dto/primitives.rs | 196 ++++++++- crates/rpc/src/dto/receipt.rs | 4 +- crates/rpc/src/dto/transaction.rs | 22 +- crates/rpc/src/jsonrpc/request.rs | 13 + crates/rpc/src/jsonrpc/router.rs | 46 ++- .../rpc/src/method/add_declare_transaction.rs | 51 ++- .../method/add_deploy_account_transaction.rs | 44 +- .../rpc/src/method/add_invoke_transaction.rs | 38 +- crates/rpc/src/method/estimate_fee.rs | 40 +- .../src/method/get_block_transaction_count.rs | 13 +- .../rpc/src/method/get_block_with_receipts.rs | 10 + .../src/method/get_block_with_tx_hashes.rs | 10 + crates/rpc/src/method/get_block_with_txs.rs | 10 + crates/rpc/src/method/get_class.rs | 21 +- crates/rpc/src/method/get_class_at.rs | 21 +- crates/rpc/src/method/get_class_hash_at.rs | 21 +- crates/rpc/src/method/get_events.rs | 51 ++- crates/rpc/src/method/get_nonce.rs | 14 +- crates/rpc/src/method/get_state_update.rs | 17 +- crates/rpc/src/method/get_storage_at.rs | 20 +- .../get_transaction_by_block_id_and_index.rs | 11 + .../rpc/src/method/get_transaction_by_hash.rs | 10 + .../rpc/src/method/get_transaction_receipt.rs | 12 +- .../rpc/src/method/get_transaction_status.rs | 12 +- .../rpc/src/pathfinder/methods/get_proof.rs | 17 +- .../methods/get_transaction_status.rs | 10 + .../v02/method/get_block_transaction_count.rs | 6 + crates/rpc/src/v02/method/get_class.rs | 6 + crates/rpc/src/v02/method/get_class_at.rs | 6 + .../rpc/src/v02/method/get_class_hash_at.rs | 6 + crates/rpc/src/v02/method/get_nonce.rs | 6 + crates/rpc/src/v02/method/get_storage_at.rs | 6 + .../get_transaction_by_block_id_and_index.rs | 6 + .../src/v02/method/get_transaction_by_hash.rs | 6 + crates/rpc/src/v02/types.rs | 273 +++++++++++-- crates/rpc/src/v02/types/class.rs | 45 ++- crates/rpc/src/v03/method/get_events.rs | 6 + crates/rpc/src/v03/method/get_state_update.rs | 6 + .../src/v06/method/add_declare_transaction.rs | 6 + .../method/add_deploy_account_transaction.rs | 6 + .../src/v06/method/add_invoke_transaction.rs | 6 + crates/rpc/src/v06/method/call.rs | 14 +- crates/rpc/src/v06/method/estimate_fee.rs | 6 + .../src/v06/method/estimate_message_fee.rs | 6 + .../v06/method/get_block_with_tx_hashes.rs | 6 + .../rpc/src/v06/method/get_block_with_txs.rs | 6 + .../src/v06/method/get_transaction_receipt.rs | 6 + .../src/v06/method/get_transaction_status.rs | 6 + .../src/v06/method/simulate_transactions.rs | 6 + .../v06/method/trace_block_transactions.rs | 6 + .../rpc/src/v06/method/trace_transaction.rs | 6 + crates/serde/src/lib.rs | 1 + 56 files changed, 1400 insertions(+), 237 deletions(-) diff --git a/crates/rpc/src/dto.rs b/crates/rpc/src/dto.rs index ce0f96590b..5ae371c0ac 100644 --- a/crates/rpc/src/dto.rs +++ b/crates/rpc/src/dto.rs @@ -33,13 +33,25 @@ pub trait DeserializeForVersion: Sized { #[derive(Debug)] pub struct Value { data: serde_json::Value, - version: RpcVersion, + pub version: RpcVersion, /// The name of the field that this value was deserialized from. None if /// this is a root value. name: Option<&'static str>, } impl Value { + pub fn new(data: serde_json::Value, version: RpcVersion) -> Self { + Self { + data, + version, + name: None, + } + } + + pub fn is_string(&self) -> bool { + self.data.is_string() + } + pub fn deserialize(self) -> Result { T::deserialize(self) } @@ -55,32 +67,49 @@ impl Value { self, cb: impl FnOnce(&mut Map) -> Result, ) -> Result { - let serde_json::Value::Object(map) = self.data else { - return Err(serde_json::Error::custom(match self.name { - Some(name) => format!("expected object for \"{name}\""), - None => "expected object".to_string(), - })); + let data = match self.data { + serde_json::Value::Object(map) => MapOrArray::Map(map), + serde_json::Value::Array(values) => MapOrArray::Array { values, offset: 0 }, + _ => { + return Err(serde_json::Error::custom(match self.name { + Some(name) => format!("expected object or array for \"{name}\""), + None => "expected object or array".to_string(), + })) + } }; let mut map = Map { - data: map, + data, version: self.version, }; let result = cb(&mut map)?; - if !map.data.is_empty() { - let fields = map - .data - .keys() - .map(|key| format!("\"{key}\"")) - .collect::>() - .join(", "); - return Err(serde_json::Error::custom(format!( - "unexpected field{}: {fields}{}", - if map.data.len() == 1 { "" } else { "s" }, - match self.name { - Some(name) => format!(" for \"{name}\""), - None => Default::default(), - }, - ))); + match map.data { + MapOrArray::Map(map) => { + if !map.is_empty() { + let fields = map + .keys() + .map(|key| format!("\"{key}\"")) + .collect::>() + .join(", "); + return Err(serde_json::Error::custom(format!( + "unexpected field{}: {fields}{}", + if map.len() == 1 { "" } else { "s" }, + match self.name { + Some(name) => format!(" for \"{name}\""), + None => Default::default(), + }, + ))); + } + } + MapOrArray::Array { values, offset } => { + if offset < values.len() { + return Err(serde_json::Error::custom(format!( + "expected {} field{}, got {}", + values.len(), + if values.len() == 1 { "" } else { "s" }, + offset, + ))); + } + } } Ok(result) } @@ -109,25 +138,93 @@ impl Value { } pub struct Map { - data: serde_json::value::Map, + data: MapOrArray, version: RpcVersion, } +enum MapOrArray { + Map(serde_json::value::Map), + Array { + values: Vec, + offset: usize, + }, +} + impl Map { + pub fn contains_key(&self, key: &'static str) -> bool { + match &self.data { + MapOrArray::Map(data) => data.contains_key(key), + MapOrArray::Array { values, offset } => false, + } + } + pub fn deserialize( &mut self, key: &'static str, ) -> Result { - let value = self - .data - .remove(key) - .ok_or_else(|| serde_json::Error::custom(format!("missing field: \"{key}\"")))?; - Value { - data: value, - name: Some(key), - version: self.version, + match &mut self.data { + MapOrArray::Map(data) => { + let value = data.remove(key).ok_or_else(|| { + serde_json::Error::custom(format!("missing field: \"{key}\"")) + })?; + Value { + data: value, + name: Some(key), + version: self.version, + } + .deserialize() + } + MapOrArray::Array { values, offset } => { + let value = values + .get_mut(*offset) + .ok_or_else(|| serde_json::Error::custom(format!("missing field: \"{key}\"")))? + .take(); + *offset += 1; + Value { + data: value, + name: Some(key), + version: self.version, + } + .deserialize() + } + } + } + + pub fn deserialize_optional( + &mut self, + key: &'static str, + ) -> Result, serde_json::Error> { + match &mut self.data { + MapOrArray::Map(data) => { + let value = data.remove(key); + match value { + Some(value) => { + let value = Value { + data: value, + name: Some(key), + version: self.version, + }; + Ok(Some(value.deserialize()?)) + } + None => Ok(None), + } + } + MapOrArray::Array { values, offset } => { + let value = values.get_mut(*offset).map(|value| value.take()); + match value { + Some(value) => { + let value = Value { + data: value, + name: Some(key), + version: self.version, + }; + *offset += 1; + Ok(Some(value.deserialize()?)) + } + None => Ok(None), + } + } } - .deserialize() } // TODO This should be removed once all existing DTOs have been migrated. @@ -135,16 +232,70 @@ impl Map { &mut self, key: &'static str, ) -> Result { - let value = self - .data - .remove(key) - .ok_or_else(|| serde_json::Error::custom(format!("missing field: \"{key}\"")))?; - Value { - data: value, - name: Some(key), - version: self.version, + match &mut self.data { + MapOrArray::Map(data) => { + let value = data.remove(key).ok_or_else(|| { + serde_json::Error::custom(format!("missing field: \"{key}\"")) + })?; + Value { + data: value, + name: Some(key), + version: self.version, + } + .deserialize_serde() + } + MapOrArray::Array { values, offset } => { + let value = values + .get_mut(*offset) + .ok_or_else(|| serde_json::Error::custom(format!("missing field: \"{key}\"")))? + .take(); + *offset += 1; + Value { + data: value, + name: Some(key), + version: self.version, + } + .deserialize_serde() + } + } + } + + // TODO This should be removed once all existing DTOs have been migrated. + pub fn deserialize_optional_serde serde::Deserialize<'a>>( + &mut self, + key: &'static str, + ) -> Result, serde_json::Error> { + match &mut self.data { + MapOrArray::Map(data) => { + let value = data.remove(key); + match value { + Some(value) => { + let value = Value { + data: value, + name: Some(key), + version: self.version, + }; + Ok(Some(value.deserialize_serde()?)) + } + None => Ok(None), + } + } + MapOrArray::Array { values, offset } => { + let value = values.get_mut(*offset).map(|value| value.take()); + match value { + Some(value) => { + let value = Value { + data: value, + name: Some(key), + version: self.version, + }; + *offset += 1; + Ok(Some(value.deserialize_serde()?)) + } + None => Ok(None), + } + } } - .deserialize_serde() } pub fn deserialize_map( @@ -152,16 +303,70 @@ impl Map { key: &'static str, cb: impl Fn(&mut Map) -> Result, ) -> Result { - let value = self - .data - .remove(key) - .ok_or_else(|| serde_json::Error::custom(format!("missing field: \"{key}\"")))?; - Value { - data: value, - name: Some(key), - version: self.version, + match &mut self.data { + MapOrArray::Map(data) => { + let value = data.remove(key).ok_or_else(|| { + serde_json::Error::custom(format!("missing field: \"{key}\"")) + })?; + Value { + data: value, + name: Some(key), + version: self.version, + } + .deserialize_map(cb) + } + MapOrArray::Array { values, offset } => { + let value = values + .get_mut(*offset) + .ok_or_else(|| serde_json::Error::custom(format!("missing field: \"{key}\"")))? + .take(); + *offset += 1; + Value { + data: value, + name: Some(key), + version: self.version, + } + .deserialize_map(cb) + } + } + } + + pub fn deserialize_optional_map( + &mut self, + key: &'static str, + cb: impl Fn(&mut Map) -> Result, + ) -> Result, serde_json::Error> { + match &mut self.data { + MapOrArray::Map(data) => { + let value = data.remove(key); + match value { + Some(value) => { + let value = Value { + data: value, + name: Some(key), + version: self.version, + }; + Ok(Some(value.deserialize_map(cb)?)) + } + None => Ok(None), + } + } + MapOrArray::Array { values, offset } => { + let value = values.get_mut(*offset).map(|value| value.take()); + match value { + Some(value) => { + let value = Value { + data: value, + name: Some(key), + version: self.version, + }; + *offset += 1; + Ok(Some(value.deserialize_map(cb)?)) + } + None => Ok(None), + } + } } - .deserialize_map(cb) } pub fn deserialize_array( @@ -169,15 +374,69 @@ impl Map { key: &'static str, cb: impl Fn(Value) -> Result, ) -> Result, serde_json::Error> { - let value = self - .data - .remove(key) - .ok_or_else(|| serde_json::Error::custom(format!("missing field: \"{key}\"")))?; - Value { - data: value, - name: Some(key), - version: self.version, + match &mut self.data { + MapOrArray::Map(data) => { + let value = data.remove(key).ok_or_else(|| { + serde_json::Error::custom(format!("missing field: \"{key}\"")) + })?; + Value { + data: value, + name: Some(key), + version: self.version, + } + .deserialize_array(cb) + } + MapOrArray::Array { values, offset } => { + let value = values + .get_mut(*offset) + .ok_or_else(|| serde_json::Error::custom(format!("missing field: \"{key}\"")))? + .take(); + *offset += 1; + Value { + data: value, + name: Some(key), + version: self.version, + } + .deserialize_array(cb) + } + } + } + + pub fn deserialize_optional_array( + &mut self, + key: &'static str, + cb: impl Fn(Value) -> Result, + ) -> Result>, serde_json::Error> { + match &mut self.data { + MapOrArray::Map(data) => { + let value = data.remove(key); + match value { + Some(value) => { + let value = Value { + data: value, + name: Some(key), + version: self.version, + }; + Ok(Some(value.deserialize_array(cb)?)) + } + None => Ok(None), + } + } + MapOrArray::Array { values, offset } => { + let value = values.get_mut(*offset).map(|value| value.take()); + match value { + Some(value) => { + let value = Value { + data: value, + name: Some(key), + version: self.version, + }; + *offset += 1; + Ok(Some(value.deserialize_array(cb)?)) + } + None => Ok(None), + } + } } - .deserialize_array(cb) } } diff --git a/crates/rpc/src/dto/block.rs b/crates/rpc/src/dto/block.rs index 0f09b4f4eb..635e297186 100644 --- a/crates/rpc/src/dto/block.rs +++ b/crates/rpc/src/dto/block.rs @@ -1,4 +1,5 @@ use pathfinder_common::{GasPrice, L1DataAvailabilityMode}; +use serde::de::Error; use super::serialize::SerializeStruct; @@ -8,6 +9,36 @@ pub struct BlockHeader<'a>(pub &'a pathfinder_common::BlockHeader); #[derive(Debug)] pub struct PendingBlockHeader<'a>(pub &'a starknet_gateway_types::reply::PendingBlock); +impl crate::dto::DeserializeForVersion for pathfinder_common::BlockId { + fn deserialize(value: super::Value) -> Result { + if value.is_string() { + let value: String = value.deserialize_serde()?; + match value.as_str() { + "latest" => Ok(Self::Latest), + "pending" => Ok(Self::Pending), + _ => Err(serde_json::Error::custom("Invalid block id")), + } + } else { + value.deserialize_map(|value| { + if value.contains_key("block_number") { + Ok(Self::Number( + pathfinder_common::BlockNumber::new( + value.deserialize_serde("block_number")?, + ) + .ok_or_else(|| serde_json::Error::custom("Invalid block number"))?, + )) + } else if value.contains_key("block_hash") { + Ok(Self::Hash(pathfinder_common::BlockHash( + value.deserialize("block_hash")?, + ))) + } else { + Err(serde_json::Error::custom("Invalid block id")) + } + }) + } + } +} + impl crate::dto::serialize::SerializeForVersion for BlockHeader<'_> { fn serialize( &self, @@ -99,14 +130,8 @@ impl crate::dto::serialize::SerializeForVersion for ResourcePrice { serializer: super::serialize::Serializer, ) -> Result { let mut serializer = serializer.serialize_struct()?; - serializer.serialize_field( - "price_in_wei", - &crate::dto::NumAsHex::U128(self.price_in_wei.0), - )?; - serializer.serialize_field( - "price_in_fri", - &crate::dto::NumAsHex::U128(self.price_in_fri.0), - )?; + serializer.serialize_field("price_in_wei", &crate::dto::U128Hex(self.price_in_wei.0))?; + serializer.serialize_field("price_in_fri", &crate::dto::U128Hex(self.price_in_fri.0))?; serializer.end() } } diff --git a/crates/rpc/src/dto/class.rs b/crates/rpc/src/dto/class.rs index 94a626c1d1..048a2ac73c 100644 --- a/crates/rpc/src/dto/class.rs +++ b/crates/rpc/src/dto/class.rs @@ -1,7 +1,8 @@ use serde_with::ser::SerializeAsWrap; +use super::U64Hex; use crate::dto::serialize::SerializeForVersion; -use crate::dto::{serialize, Felt, NumAsHex}; +use crate::dto::{serialize, Felt}; use crate::v02::types; pub struct DeprecatedContractClass<'a>(pub &'a types::CairoContractClass); @@ -138,7 +139,7 @@ impl SerializeForVersion for DeprecatedCairoEntryPoint<'_> { ) -> Result { let mut serializer = serializer.serialize_struct()?; - serializer.serialize_field("offset", &NumAsHex::U64(self.0.offset))?; + serializer.serialize_field("offset", &self.0.offset)?; serializer.serialize_field("selector", &Felt(&self.0.selector))?; serializer.end() diff --git a/crates/rpc/src/dto/fee.rs b/crates/rpc/src/dto/fee.rs index 66ff844db8..de21db6bfa 100644 --- a/crates/rpc/src/dto/fee.rs +++ b/crates/rpc/src/dto/fee.rs @@ -1,4 +1,4 @@ -use super::NumAsHex; +use super::U256Hex; #[derive(Debug, PartialEq, Eq)] pub struct FeeEstimate<'a>(pub &'a pathfinder_executor::types::FeeEstimate); @@ -9,14 +9,11 @@ impl crate::dto::serialize::SerializeForVersion for FeeEstimate<'_> { serializer: crate::dto::serialize::Serializer, ) -> Result { let mut serializer = serializer.serialize_struct()?; - serializer.serialize_field("gas_consumed", &NumAsHex::U256(&self.0.gas_consumed))?; - serializer.serialize_field("gas_price", &NumAsHex::U256(&self.0.gas_price))?; - serializer.serialize_field( - "data_gas_consumed", - &NumAsHex::U256(&self.0.data_gas_consumed), - )?; - serializer.serialize_field("data_gas_price", &NumAsHex::U256(&self.0.data_gas_price))?; - serializer.serialize_field("overall_fee", &NumAsHex::U256(&self.0.overall_fee))?; + serializer.serialize_field("gas_consumed", &U256Hex(self.0.gas_consumed))?; + serializer.serialize_field("gas_price", &U256Hex(self.0.gas_price))?; + serializer.serialize_field("data_gas_consumed", &U256Hex(self.0.data_gas_consumed))?; + serializer.serialize_field("data_gas_price", &U256Hex(self.0.data_gas_price))?; + serializer.serialize_field("overall_fee", &U256Hex(self.0.overall_fee))?; serializer.serialize_field("unit", &PriceUnit(&self.0.unit))?; serializer.end() } diff --git a/crates/rpc/src/dto/primitives.rs b/crates/rpc/src/dto/primitives.rs index 4a59e7030c..4a47a4bd6d 100644 --- a/crates/rpc/src/dto/primitives.rs +++ b/crates/rpc/src/dto/primitives.rs @@ -1,6 +1,8 @@ use pathfinder_common::ContractAddress; +use serde::de::Error; use super::serialize::SerializeForVersion; +use super::{DeserializeForVersion, Value}; use crate::dto::serialize::{self, Serializer}; pub struct SyncStatus<'a>(pub &'a crate::v02::types::syncing::Status); @@ -9,18 +11,27 @@ pub struct Felt<'a>(pub &'a pathfinder_crypto::Felt); pub struct BlockHash<'a>(pub &'a pathfinder_common::BlockHash); pub struct ChainId<'a>(pub &'a pathfinder_common::ChainId); pub struct BlockNumber(pub pathfinder_common::BlockNumber); -pub enum NumAsHex<'a> { - U64(u64), - U128(u128), - H256(&'a primitive_types::H256), - U256(&'a primitive_types::U256), -} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct U64Hex(pub u64); + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct U128Hex(pub u128); + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct H256Hex(pub primitive_types::H256); + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct U256Hex(pub primitive_types::U256); + pub struct Address<'a>(pub &'a ContractAddress); pub struct EthAddress<'a>(pub &'a pathfinder_common::EthereumAddress); -mod hex_str { +pub mod hex_str { use std::borrow::Cow; + use anyhow::anyhow; + const LUT: [u8; 16] = *b"0123456789abcdef"; pub fn bytes_to_hex_str_stripped(data: &[u8]) -> Cow<'static, str> { @@ -72,6 +83,83 @@ mod hex_str { // SAFETY: we only insert hex digits. unsafe { String::from_utf8_unchecked(buf) } } + + pub fn bytes_from_hex_str_stripped(hex_str: &str) -> anyhow::Result<[u8; N]> { + from_hex_str(hex_str, true) + } + + #[inline] + fn from_hex_str(hex_str: &str, stripped: bool) -> anyhow::Result<[u8; N]> { + fn parse_hex_digit(digit: u8) -> anyhow::Result { + match digit { + b'0'..=b'9' => Ok(digit - b'0'), + b'A'..=b'F' => Ok(digit - b'A' + 10), + b'a'..=b'f' => Ok(digit - b'a' + 10), + other => Err(anyhow!("invalid hex digit: {}", other)), + } + } + + let bytes = hex_str.as_bytes(); + let start = if bytes.len() >= 2 && bytes[0] == b'0' && bytes[1] == b'x' { + 2 + } else { + 0 + }; + let len = bytes.len() - start; + + if len > 2 * N { + return Err(anyhow!( + "hex string too long: expected at most 64 characters, got {}", + len + )); + } + + let mut buf = [0u8; N]; + + if stripped { + // Handle a possible odd nibble remaining nibble. + if len % 2 == 1 { + let idx = len / 2; + buf[N - 1 - idx] = match parse_hex_digit(bytes[start]) { + Ok(b) => b, + Err(e) => return Err(e), + }; + } + } else if len != 2 * N { + return Err(anyhow!( + "hex string too short: expected 64 characters, got {}", + len + )); + } + + let chunks = len / 2; + let mut chunk = 0; + + while chunk < chunks { + let lower = match parse_hex_digit(bytes[bytes.len() - chunk * 2 - 1]) { + Ok(b) => b, + Err(e) => return Err(e), + }; + let upper = match parse_hex_digit(bytes[bytes.len() - chunk * 2 - 2]) { + Ok(b) => b, + Err(e) => return Err(e), + }; + buf[N - 1 - chunk] = upper << 4 | lower; + chunk += 1; + } + + Ok(buf) + } +} + +impl DeserializeForVersion for U64Hex { + fn deserialize(value: Value) -> Result { + let hex_str: String = value.deserialize_serde()?; + let bytes = hex_str::bytes_from_hex_str_stripped::<8>(&hex_str).map_err(|e| { + serde_json::Error::custom(format!("failed to parse hex string as u64: {}", e)) + })?; + Ok(Self(u64::from_be_bytes(bytes))) + } } impl SerializeForVersion for SyncStatus<'_> { @@ -94,6 +182,15 @@ impl SerializeForVersion for Felt<'_> { } } +impl DeserializeForVersion for pathfinder_crypto::Felt { + fn deserialize(value: Value) -> Result { + let hex_str: String = value.deserialize_serde()?; + let bytes = hex_str::bytes_from_hex_str_stripped::<32>(&hex_str) + .map_err(|e| serde_json::Error::custom(format!("failed to parse hex string: {}", e)))?; + Self::from_be_bytes(bytes).map_err(|e| serde_json::Error::custom("felt overflow")) + } +} + impl SerializeForVersion for BlockHash<'_> { fn serialize(&self, serializer: Serializer) -> Result { serializer.serialize(&Felt(&self.0 .0)) @@ -113,15 +210,39 @@ impl SerializeForVersion for BlockNumber { } } -impl SerializeForVersion for NumAsHex<'_> { +impl SerializeForVersion for U64Hex { fn serialize(&self, serializer: Serializer) -> Result { - let hex_str = match self { - NumAsHex::U64(x) => hex_str::bytes_to_hex_str_stripped(&x.to_be_bytes()), - NumAsHex::U128(x) => hex_str::bytes_to_hex_str_stripped(&x.to_be_bytes()), - NumAsHex::H256(x) => hex_str::bytes_to_hex_str_stripped(x.as_bytes()), - NumAsHex::U256(&x) => hex_str::bytes_to_hex_str_stripped(&<[u8; 32]>::from(x)), - }; - serializer.serialize_str(&hex_str) + serializer.serialize_str(&hex_str::bytes_to_hex_str_stripped(&self.0.to_be_bytes())) + } +} + +impl SerializeForVersion for U128Hex { + fn serialize(&self, serializer: Serializer) -> Result { + serializer.serialize_str(&hex_str::bytes_to_hex_str_stripped(&self.0.to_be_bytes())) + } +} + +impl DeserializeForVersion for U128Hex { + fn deserialize(value: Value) -> Result { + let hex_str: String = value.deserialize_serde()?; + let bytes = hex_str::bytes_from_hex_str_stripped::<16>(&hex_str).map_err(|e| { + serde_json::Error::custom(format!("failed to parse hex string as u128: {}", e)) + })?; + Ok(Self(u128::from_be_bytes(bytes))) + } +} + +impl SerializeForVersion for H256Hex { + fn serialize(&self, serializer: Serializer) -> Result { + serializer.serialize_str(&hex_str::bytes_to_hex_str_stripped(self.0.as_bytes())) + } +} + +impl SerializeForVersion for U256Hex { + fn serialize(&self, serializer: Serializer) -> Result { + serializer.serialize_str(&hex_str::bytes_to_hex_str_stripped(&<[u8; 32]>::from( + self.0, + ))) } } @@ -224,7 +345,7 @@ mod tests { bytes[30] = 0x12; bytes[31] = 0x34; let uut = primitive_types::H256(bytes); - let uut = NumAsHex::H256(&uut); + let uut = H256Hex(uut); let expected = json!("0x1234"); let encoded = uut.serialize(Default::default()).unwrap(); @@ -233,7 +354,7 @@ mod tests { #[test] fn num_as_hex_u64() { - let uut = NumAsHex::U64(0x1234); + let uut = U64Hex(0x1234); let expected = json!("0x1234"); let encoded = uut.serialize(Default::default()).unwrap(); @@ -254,7 +375,7 @@ mod tests { assert_eq!(encoded, expected); } - mod hex_str { + mod to_hex { use super::super::hex_str::*; #[test] @@ -292,4 +413,43 @@ mod tests { assert_eq!(&bytes_to_hex_str_stripped(&data), "0x123456"); } } + + mod from_hex { + use super::super::hex_str::*; + + #[test] + fn zero() { + let bytes = bytes_from_hex_str_stripped::<2>("0x0").unwrap(); + assert_eq!(bytes, [0; 2]); + + let bytes = bytes_from_hex_str_stripped::<2>("0x").unwrap(); + assert_eq!(bytes, [0; 2]); + } + + #[test] + fn leading_zeros_even() { + let bytes = bytes_from_hex_str_stripped::<2>("0x12").unwrap(); + assert_eq!(bytes, [0, 0x12]); + let bytes = bytes_from_hex_str_stripped::<2>("0x0012").unwrap(); + assert_eq!(bytes, [0, 0x12]); + } + + #[test] + fn leading_zeros_odd() { + let bytes = bytes_from_hex_str_stripped::<2>("0x1").unwrap(); + assert_eq!(bytes, [0, 0x1]); + } + + #[test] + fn multibyte_odd() { + let bytes = bytes_from_hex_str_stripped::<4>("0x12345").unwrap(); + assert_eq!(bytes, [0, 0x01, 0x23, 0x45]); + } + + #[test] + fn multibyte_even() { + let bytes = bytes_from_hex_str_stripped::<4>("0x123456").unwrap(); + assert_eq!(bytes, [0, 0x12, 0x34, 0x56]); + } + } } diff --git a/crates/rpc/src/dto/receipt.rs b/crates/rpc/src/dto/receipt.rs index 99da1cc6b4..0a79fd4db4 100644 --- a/crates/rpc/src/dto/receipt.rs +++ b/crates/rpc/src/dto/receipt.rs @@ -4,7 +4,7 @@ use pathfinder_common::transaction::{Transaction, TransactionKind, TransactionVa use pathfinder_common::{BlockHash, BlockNumber, TransactionHash, TransactionVersion}; use serde::ser::Error; -use super::serialize; +use super::{serialize, H256Hex}; use crate::dto::serialize::{SerializeForVersion, Serializer}; use crate::{dto, RpcVersion}; @@ -277,7 +277,7 @@ impl SerializeForVersion for L1HandlerTxnReceipt<'_> { serializer.flatten(&CommonReceiptProperties(self.0))?; serializer.serialize_field("type", &"L1_HANDLER")?; - serializer.serialize_field("message_hash", &dto::NumAsHex::H256(&message_hash))?; + serializer.serialize_field("message_hash", &H256Hex(message_hash))?; serializer.end() } diff --git a/crates/rpc/src/dto/transaction.rs b/crates/rpc/src/dto/transaction.rs index 8745692053..988c80b77a 100644 --- a/crates/rpc/src/dto/transaction.rs +++ b/crates/rpc/src/dto/transaction.rs @@ -1,6 +1,8 @@ use pathfinder_common::transaction::TransactionVariant; use pathfinder_common::TransactionHash; +use serde::de::Error; +use super::{DeserializeForVersion, U128Hex, U64Hex}; use crate::dto; use crate::dto::serialize; use crate::dto::serialize::{SerializeForVersion, Serializer}; @@ -78,7 +80,7 @@ impl SerializeForVersion for Transaction<'_> { s.serialize_field("nonce", &dto::Felt(&tx.nonce.0))?; s.serialize_field("class_hash", &dto::Felt(&tx.class_hash.0))?; s.serialize_field("resource_bounds", &ResourceBounds(&tx.resource_bounds))?; - s.serialize_field("tip", &dto::NumAsHex::U64(tx.tip.0))?; + s.serialize_field("tip", &U64Hex(tx.tip.0))?; s.serialize_iter( "paymaster_data", tx.paymaster_data.len(), @@ -167,7 +169,7 @@ impl SerializeForVersion for Transaction<'_> { )?; s.serialize_field("class_hash", &dto::Felt(&tx.class_hash.0))?; s.serialize_field("resource_bounds", &ResourceBounds(&tx.resource_bounds))?; - s.serialize_field("tip", &dto::NumAsHex::U64(tx.tip.0))?; + s.serialize_field("tip", &U64Hex(tx.tip.0))?; s.serialize_iter( "paymaster_data", tx.paymaster_data.len(), @@ -235,7 +237,7 @@ impl SerializeForVersion for Transaction<'_> { )?; s.serialize_field("nonce", &dto::Felt(&tx.nonce.0))?; s.serialize_field("resource_bounds", &ResourceBounds(&tx.resource_bounds))?; - s.serialize_field("tip", &dto::NumAsHex::U64(tx.tip.0))?; + s.serialize_field("tip", &U64Hex(tx.tip.0))?; s.serialize_iter( "paymaster_data", tx.paymaster_data.len(), @@ -287,11 +289,8 @@ impl SerializeForVersion for ResourceBounds<'_> { impl SerializeForVersion for ResourceBound<'_> { fn serialize(&self, serializer: Serializer) -> Result { let mut s = serializer.serialize_struct()?; - s.serialize_field("max_amount", &dto::NumAsHex::U64(self.0.max_amount.0))?; - s.serialize_field( - "max_price_per_unit", - &dto::NumAsHex::U128(self.0.max_price_per_unit.0), - )?; + s.serialize_field("max_amount", &U64Hex(self.0.max_amount.0))?; + s.serialize_field("max_price_per_unit", &U128Hex(self.0.max_price_per_unit.0))?; s.end() } } @@ -308,3 +307,10 @@ impl SerializeForVersion for DataAvailabilityMode<'_> { } } } + +impl DeserializeForVersion for pathfinder_common::TransactionIndex { + fn deserialize(value: dto::Value) -> Result { + let idx = value.deserialize_serde()?; + Self::new(idx).ok_or_else(|| serde_json::Error::custom("Invalid transaction index")) + } +} diff --git a/crates/rpc/src/jsonrpc/request.rs b/crates/rpc/src/jsonrpc/request.rs index ac4f781fec..149e07ad42 100644 --- a/crates/rpc/src/jsonrpc/request.rs +++ b/crates/rpc/src/jsonrpc/request.rs @@ -3,7 +3,9 @@ use std::borrow::Cow; use serde::{Deserialize, Serialize}; use serde_json::value::RawValue; +use crate::dto::{DeserializeForVersion, Value}; use crate::jsonrpc::{RequestId, RpcError}; +use crate::RpcVersion; #[derive(Debug)] pub struct RpcRequest<'a> { @@ -50,6 +52,17 @@ impl<'a> RawParams<'a> { serde_json::from_str::(s).map_err(|e| RpcError::InvalidParams(e.to_string())) } + + pub fn deserialize_for_version( + &self, + version: RpcVersion, + ) -> Result { + let s = self.0.map(|x| x.get()).unwrap_or_default(); + let value: serde_json::Value = + serde_json::from_str(s).map_err(|e| RpcError::InvalidParams(e.to_string()))?; + T::deserialize(Value::new(value, version)) + .map_err(|e| RpcError::InvalidParams(e.to_string())) + } } impl<'de> Deserialize<'de> for RpcRequest<'de> { diff --git a/crates/rpc/src/jsonrpc/router.rs b/crates/rpc/src/jsonrpc/router.rs index b633e46af4..ec7b9739ad 100644 --- a/crates/rpc/src/jsonrpc/router.rs +++ b/crates/rpc/src/jsonrpc/router.rs @@ -7,7 +7,6 @@ use axum::http::StatusCode; use axum::response::IntoResponse; use futures::{Future, FutureExt, StreamExt}; use http::HeaderValue; -use serde::de::DeserializeOwned; use serde_json::value::RawValue; use tracing::Instrument; @@ -315,6 +314,7 @@ mod sealed { use super::*; use crate::dto::serialize::{SerializeForVersion, Serializer}; + use crate::dto::DeserializeForVersion; use crate::jsonrpc::error::RpcError; use crate::RpcVersion; @@ -343,7 +343,7 @@ mod sealed { Sealed<((), (), Input), ((), (), Output), ((), (), RpcContext)> for F where F: Fn(RpcContext, Input, RpcVersion) -> Fut + Sync + Send + 'static, - Input: DeserializeOwned + Send + Sync + 'static, + Input: DeserializeForVersion + Send + Sync + 'static, Output: SerializeForVersion + Send + Sync + 'static, Error: Into + Send + Sync + 'static, Fut: Future> + Send, @@ -358,7 +358,7 @@ mod sealed { impl RpcMethod for Helper where F: Fn(RpcContext, Input, RpcVersion) -> Fut + Sync + Send, - Input: DeserializeOwned + Send + Sync, + Input: DeserializeForVersion + Send + Sync, Output: SerializeForVersion + Send + Sync, Error: Into + Send + Sync, Fut: Future> + Send, @@ -369,7 +369,7 @@ mod sealed { input: RawParams<'a>, version: RpcVersion, ) -> RpcResult { - let input = input.deserialize()?; + let input = input.deserialize_for_version(version)?; (self.f)(state, input, version) .await .map_err(Into::into)? @@ -391,7 +391,7 @@ mod sealed { impl<'a, F, Input, Output, Error, Fut> Sealed<((), Input), ((), Output), ((), RpcContext)> for F where F: Fn(RpcContext, Input) -> Fut + Sync + Send + 'static, - Input: DeserializeOwned + Send + Sync + 'static, + Input: DeserializeForVersion + Send + Sync + 'static, Output: SerializeForVersion + Send + Sync + 'static, Error: Into + Send + Sync + 'static, Fut: Future> + Send, @@ -406,7 +406,7 @@ mod sealed { impl RpcMethod for Helper where F: Fn(RpcContext, Input) -> Fut + Sync + Send, - Input: DeserializeOwned + Send + Sync, + Input: DeserializeForVersion + Send + Sync, Output: SerializeForVersion + Send + Sync, Error: Into + Send + Sync, Fut: Future> + Send, @@ -417,7 +417,7 @@ mod sealed { input: RawParams<'a>, version: RpcVersion, ) -> RpcResult { - let input = input.deserialize()?; + let input = input.deserialize_for_version(version)?; (self.f)(state, input) .await .map_err(Into::into)? @@ -440,7 +440,7 @@ mod sealed { impl<'a, F, Input, Output, Error, Fut> Sealed<((), Input), ((), Output), ()> for F where F: Fn(Input) -> Fut + Sync + Send + 'static, - Input: DeserializeOwned + Sync + Send + 'static, + Input: DeserializeForVersion + Sync + Send + 'static, Output: SerializeForVersion + Sync + Send + 'static, Error: Into + Sync + Send + 'static, Fut: Future> + Send, @@ -455,7 +455,7 @@ mod sealed { impl RpcMethod for Helper where F: Fn(Input) -> Fut + Sync + Send, - Input: DeserializeOwned + Send + Sync, + Input: DeserializeForVersion + Send + Sync, Output: SerializeForVersion + Send + Sync, Error: Into + Send + Sync, Fut: Future> + Send, @@ -466,7 +466,7 @@ mod sealed { input: RawParams<'a>, version: RpcVersion, ) -> RpcResult { - let input = input.deserialize()?; + let input = input.deserialize_for_version(version)?; (self.f)(input) .await .map_err(Into::into)? @@ -736,21 +736,43 @@ mod tests { use serde_json::json; use super::*; + use crate::dto::DeserializeForVersion; fn spec_router() -> RpcRouter { crate::error::generate_rpc_error_subset!(ExampleError:); - #[derive(Debug, Deserialize, Serialize)] + #[derive(Debug, Serialize)] struct SubtractInput { minuend: i32, subtrahend: i32, } + + impl DeserializeForVersion for SubtractInput { + fn deserialize(value: crate::dto::Value) -> Result { + value.deserialize_map(|value| { + Ok(Self { + minuend: value.deserialize_serde("minuend")?, + subtrahend: value.deserialize_serde("subtrahend")?, + }) + }) + } + } + async fn subtract(input: SubtractInput) -> Result { Ok(Value::Number((input.minuend - input.subtrahend).into())) } - #[derive(Debug, Deserialize, Serialize)] + #[derive(Debug, Serialize)] struct SumInput(Vec); + + impl DeserializeForVersion for SumInput { + fn deserialize(value: crate::dto::Value) -> Result { + Ok(Self( + value.deserialize_array(|value| value.deserialize_serde())?, + )) + } + } + async fn sum(input: SumInput) -> Result { Ok(Value::Number((input.0.iter().sum::()).into())) } diff --git a/crates/rpc/src/method/add_declare_transaction.rs b/crates/rpc/src/method/add_declare_transaction.rs index be3c46052a..44ecf820cb 100644 --- a/crates/rpc/src/method/add_declare_transaction.rs +++ b/crates/rpc/src/method/add_declare_transaction.rs @@ -125,23 +125,46 @@ impl From for AddDeclareTransactionError { } } -#[derive(serde::Deserialize, Debug, PartialEq, Eq)] -#[serde(tag = "type")] +#[derive(Debug, PartialEq, Eq)] pub enum Transaction { - #[serde(rename = "DECLARE")] Declare(BroadcastedDeclareTransaction), } -#[derive(serde::Deserialize, Debug, PartialEq, Eq)] -#[serde(deny_unknown_fields)] +impl crate::dto::DeserializeForVersion for Transaction { + fn deserialize(value: crate::dto::Value) -> Result { + value.deserialize_map(|value| { + let tag: String = value.deserialize_serde("type")?; + if tag != "DECLARE" { + return Err(serde::de::Error::custom("Invalid transaction type")); + } + Ok(Self::Declare(BroadcastedDeclareTransaction::deserialize( + value, + )?)) + }) + } +} + +#[derive(Debug, PartialEq, Eq)] pub struct Input { declare_transaction: Transaction, // An undocumented parameter that we forward to the sequencer API // A deploy token is required to deploy contracts on Starknet mainnet only. - #[serde(default)] token: Option, } +impl crate::dto::DeserializeForVersion for Input { + fn deserialize(value: crate::dto::Value) -> Result { + value.deserialize_map(|value| { + let declare_transaction = value.deserialize("declare_transaction")?; + let token = value.deserialize_optional_serde("token")?; + Ok(Self { + declare_transaction, + token, + }) + }) + } +} + #[derive(Debug, PartialEq, Eq)] pub struct Output { transaction_hash: TransactionHash, @@ -356,7 +379,9 @@ mod tests { use serde_json::json; use super::super::*; + use crate::dto::DeserializeForVersion; use crate::v02::types::request::BroadcastedDeclareTransactionV1; + use crate::RpcVersion; fn test_declare_txn() -> Transaction { Transaction::Declare(BroadcastedDeclareTransaction::V1( @@ -382,7 +407,8 @@ mod tests { "contract_class": CONTRACT_CLASS.clone(), "sender_address": "0x1" }]); - let input = serde_json::from_value::(positional).unwrap(); + let input = Input::deserialize(crate::dto::Value::new(positional, RpcVersion::V07)) + .unwrap(); let expected = Input { declare_transaction: test_declare_txn(), token: None, @@ -404,7 +430,8 @@ mod tests { }, "token": "token" }); - let input = serde_json::from_value::(named).unwrap(); + let input = + Input::deserialize(crate::dto::Value::new(named, RpcVersion::V07)).unwrap(); let expected = Input { declare_transaction: test_declare_txn(), token: Some("token".to_owned()), @@ -447,7 +474,9 @@ mod tests { use serde_json::json; use super::super::*; + use crate::dto::DeserializeForVersion; use crate::v02::types::request::BroadcastedDeclareTransactionV2; + use crate::RpcVersion; fn test_declare_txn() -> Transaction { Transaction::Declare(BroadcastedDeclareTransaction::V2( @@ -476,7 +505,8 @@ mod tests { "compiled_class_hash": "0x1" }]); - let input = serde_json::from_value::(positional).unwrap(); + let input = Input::deserialize(crate::dto::Value::new(positional, RpcVersion::V07)) + .unwrap(); let expected = Input { declare_transaction: test_declare_txn(), token: None, @@ -500,7 +530,8 @@ mod tests { "token": "token" }); - let input = serde_json::from_value::(named).unwrap(); + let input = + Input::deserialize(crate::dto::Value::new(named, RpcVersion::V07)).unwrap(); let expected = Input { declare_transaction: test_declare_txn(), token: Some("token".to_owned()), diff --git a/crates/rpc/src/method/add_deploy_account_transaction.rs b/crates/rpc/src/method/add_deploy_account_transaction.rs index caa80d9c58..92a58fc499 100644 --- a/crates/rpc/src/method/add_deploy_account_transaction.rs +++ b/crates/rpc/src/method/add_deploy_account_transaction.rs @@ -1,4 +1,5 @@ use pathfinder_common::{ContractAddress, TransactionHash}; +use serde::de::Error; use starknet_gateway_client::GatewayApi; use starknet_gateway_types::error::{KnownStarknetErrorCode, SequencerError}; @@ -8,19 +9,38 @@ use crate::v02::types::request::{ BroadcastedDeployAccountTransactionV1, }; -#[derive(serde::Deserialize, Debug, PartialEq, Eq)] -#[serde(tag = "type")] +#[derive(Debug, PartialEq, Eq)] pub enum Transaction { - #[serde(rename = "DEPLOY_ACCOUNT")] DeployAccount(BroadcastedDeployAccountTransaction), } -#[derive(Debug, serde::Deserialize, PartialEq, Eq)] -#[serde(deny_unknown_fields)] +impl crate::dto::DeserializeForVersion for Transaction { + fn deserialize(value: crate::dto::Value) -> Result { + value.deserialize_map(|value| { + let tag: String = value.deserialize_serde("type")?; + if tag != "DEPLOY_ACCOUNT" { + return Err(serde_json::Error::custom("Invalid transaction type")); + } + BroadcastedDeployAccountTransaction::deserialize(value).map(Self::DeployAccount) + }) + } +} + +#[derive(Debug, PartialEq, Eq)] pub struct Input { deploy_account_transaction: Transaction, } +impl crate::dto::DeserializeForVersion for Input { + fn deserialize(value: crate::dto::Value) -> Result { + value.deserialize_map(|value| { + Ok(Self { + deploy_account_transaction: value.deserialize("deploy_account_transaction")?, + }) + }) + } +} + #[derive(Debug, PartialEq, Eq)] pub struct Output { transaction_hash: TransactionHash, @@ -246,16 +266,22 @@ mod tests { #[tokio::test] async fn test_parse_input_named() { - let json = format!("{{\"deploy_account_transaction\":{INPUT_JSON}}}"); - let input: Input = serde_json::from_str(&json).expect("parse named input"); + let json: serde_json::Value = + serde_json::from_str(&format!("{{\"deploy_account_transaction\":{INPUT_JSON}}}")) + .unwrap(); + let input: Input = crate::dto::Value::new(json, crate::RpcVersion::V07) + .deserialize() + .unwrap(); assert_eq!(input, get_input()); } #[tokio::test] async fn test_parse_input_positional() { - let json = format!("[{INPUT_JSON}]"); - let input: Input = serde_json::from_str(&json).expect("parse positional input"); + let json: serde_json::Value = serde_json::from_str(&format!("[{INPUT_JSON}]")).unwrap(); + let input: Input = crate::dto::Value::new(json, crate::RpcVersion::V07) + .deserialize() + .unwrap(); assert_eq!(input, get_input()); } diff --git a/crates/rpc/src/method/add_invoke_transaction.rs b/crates/rpc/src/method/add_invoke_transaction.rs index f3fa6b797f..4fe53b88d7 100644 --- a/crates/rpc/src/method/add_invoke_transaction.rs +++ b/crates/rpc/src/method/add_invoke_transaction.rs @@ -1,23 +1,43 @@ use pathfinder_common::TransactionHash; +use serde::de::Error; use starknet_gateway_client::GatewayApi; use starknet_gateway_types::error::SequencerError; use crate::context::RpcContext; use crate::v02::types::request::BroadcastedInvokeTransaction; -#[derive(serde::Deserialize, Debug, PartialEq, Eq)] -#[serde(tag = "type")] +#[derive(Debug, PartialEq, Eq)] pub enum Transaction { - #[serde(rename = "INVOKE")] Invoke(BroadcastedInvokeTransaction), } -#[derive(serde::Deserialize, Debug, PartialEq, Eq)] -#[serde(deny_unknown_fields)] +impl crate::dto::DeserializeForVersion for Transaction { + fn deserialize(value: crate::dto::Value) -> Result { + value.deserialize_map(|value| { + let tag: String = value.deserialize_serde("type")?; + if tag != "INVOKE" { + return Err(serde_json::Error::custom("Invalid transaction type")); + } + BroadcastedInvokeTransaction::deserialize(value).map(Self::Invoke) + }) + } +} + +#[derive(Debug, PartialEq, Eq)] pub struct Input { invoke_transaction: Transaction, } +impl crate::dto::DeserializeForVersion for Input { + fn deserialize(value: crate::dto::Value) -> Result { + value.deserialize_map(|value| { + Ok(Self { + invoke_transaction: value.deserialize("invoke_transaction")?, + }) + }) + } +} + #[derive(Debug, PartialEq, Eq)] pub struct Output { transaction_hash: TransactionHash, @@ -232,6 +252,7 @@ mod tests { use serde_json::json; use super::*; + use crate::dto::DeserializeForVersion; #[test] fn positional_args() { @@ -259,7 +280,9 @@ mod tests { } ]); - let input = serde_json::from_value::(positional).unwrap(); + let input = + Input::deserialize(crate::dto::Value::new(positional, crate::RpcVersion::V07)) + .unwrap(); let expected = Input { invoke_transaction: test_invoke_txn(), }; @@ -292,7 +315,8 @@ mod tests { } }); - let input = serde_json::from_value::(named).unwrap(); + let input = + Input::deserialize(crate::dto::Value::new(named, crate::RpcVersion::V07)).unwrap(); let expected = Input { invoke_transaction: test_invoke_txn(), }; diff --git a/crates/rpc/src/method/estimate_fee.rs b/crates/rpc/src/method/estimate_fee.rs index c3e3131893..c232d32778 100644 --- a/crates/rpc/src/method/estimate_fee.rs +++ b/crates/rpc/src/method/estimate_fee.rs @@ -1,20 +1,47 @@ use anyhow::Context; use pathfinder_common::BlockId; use pathfinder_executor::{ExecutionState, L1BlobDataAvailability}; +use serde::de::Error; use crate::context::RpcContext; use crate::error::ApplicationError; use crate::v02::types::request::BroadcastedTransaction; -use crate::v06::method::estimate_fee::{SimulationFlag, SimulationFlags}; -#[derive(serde::Deserialize, Debug, PartialEq, Eq)] -#[serde(deny_unknown_fields)] +#[derive(Debug, PartialEq, Eq)] pub struct Input { pub request: Vec, - pub simulation_flags: SimulationFlags, + pub simulation_flags: Vec, pub block_id: BlockId, } +impl crate::dto::DeserializeForVersion for Input { + fn deserialize(value: crate::dto::Value) -> Result { + value.deserialize_map(|value| { + Ok(Self { + request: value.deserialize_array("request", BroadcastedTransaction::deserialize)?, + simulation_flags: value + .deserialize_array("simulation_flags", SimulationFlag::deserialize)?, + block_id: value.deserialize_serde("block_id")?, + }) + }) + } +} + +#[derive(Debug, Eq, PartialEq)] +pub enum SimulationFlag { + SkipValidate, +} + +impl crate::dto::DeserializeForVersion for SimulationFlag { + fn deserialize(value: crate::dto::Value) -> Result { + let value: String = value.deserialize_serde()?; + match value.as_str() { + "SKIP_VALIDATE" => Ok(Self::SkipValidate), + _ => Err(serde_json::Error::custom("Invalid flag")), + } + } +} + #[derive(Debug, PartialEq, Eq)] pub struct Output(Vec); @@ -60,7 +87,6 @@ pub async fn estimate_fee(context: RpcContext, input: Input) -> Result Result { + value.deserialize_map(|value| { + Ok(Self { + block_id: value.deserialize("block_id")?, + }) + }) + } +} + crate::error::generate_rpc_error_subset!(Error: BlockNotFound); #[derive(Debug)] diff --git a/crates/rpc/src/method/get_block_with_receipts.rs b/crates/rpc/src/method/get_block_with_receipts.rs index 1f6df1466d..29c4df5d3e 100644 --- a/crates/rpc/src/method/get_block_with_receipts.rs +++ b/crates/rpc/src/method/get_block_with_receipts.rs @@ -25,6 +25,16 @@ pub struct Input { pub block_id: BlockId, } +impl crate::dto::DeserializeForVersion for Input { + fn deserialize(value: crate::dto::Value) -> Result { + value.deserialize_map(|value| { + Ok(Self { + block_id: value.deserialize_serde("block_id")?, + }) + }) + } +} + crate::error::generate_rpc_error_subset!(Error: BlockNotFound); pub async fn get_block_with_receipts(context: RpcContext, input: Input) -> Result { diff --git a/crates/rpc/src/method/get_block_with_tx_hashes.rs b/crates/rpc/src/method/get_block_with_tx_hashes.rs index 863701c05d..0b9a106be6 100644 --- a/crates/rpc/src/method/get_block_with_tx_hashes.rs +++ b/crates/rpc/src/method/get_block_with_tx_hashes.rs @@ -13,6 +13,16 @@ pub struct Input { pub block_id: BlockId, } +impl crate::dto::DeserializeForVersion for Input { + fn deserialize(value: crate::dto::Value) -> Result { + value.deserialize_map(|value| { + Ok(Self { + block_id: value.deserialize("block_id")?, + }) + }) + } +} + #[derive(Debug)] pub enum Output { Pending { diff --git a/crates/rpc/src/method/get_block_with_txs.rs b/crates/rpc/src/method/get_block_with_txs.rs index 2bee1d77b6..8f0dc52ea1 100644 --- a/crates/rpc/src/method/get_block_with_txs.rs +++ b/crates/rpc/src/method/get_block_with_txs.rs @@ -14,6 +14,16 @@ pub struct Input { pub block_id: BlockId, } +impl crate::dto::DeserializeForVersion for Input { + fn deserialize(value: crate::dto::Value) -> Result { + value.deserialize_map(|value| { + Ok(Self { + block_id: value.deserialize("block_id")?, + }) + }) + } +} + #[derive(Debug)] pub enum Output { Pending { diff --git a/crates/rpc/src/method/get_class.rs b/crates/rpc/src/method/get_class.rs index a08f8ff9fa..b944936251 100644 --- a/crates/rpc/src/method/get_class.rs +++ b/crates/rpc/src/method/get_class.rs @@ -8,13 +8,23 @@ use crate::v02::types::{CairoContractClass, ContractClass, SierraContractClass}; crate::error::generate_rpc_error_subset!(Error: BlockNotFound, ClassHashNotFound); -#[derive(serde::Deserialize, Debug, PartialEq, Eq)] -#[serde(deny_unknown_fields)] +#[derive(Debug, PartialEq, Eq)] pub struct Input { block_id: BlockId, class_hash: ClassHash, } +impl crate::dto::DeserializeForVersion for Input { + fn deserialize(value: crate::dto::Value) -> Result { + value.deserialize_map(|value| { + Ok(Self { + block_id: value.deserialize("block_id")?, + class_hash: ClassHash(value.deserialize("class_hash")?), + }) + }) + } +} + #[derive(Debug)] pub enum Output { DeprecatedClass(CairoContractClass), @@ -108,9 +118,11 @@ mod tests { use super::*; mod parsing { + use dto::DeserializeForVersion; use serde_json::json; use super::*; + use crate::RpcVersion; #[test] fn positional_args() { @@ -119,7 +131,8 @@ mod tests { "0x12345" ]); - let input = serde_json::from_value::(positional).unwrap(); + let input = + Input::deserialize(crate::dto::Value::new(positional, RpcVersion::V07)).unwrap(); let expected = Input { block_id: block_hash!("0xabcde").into(), class_hash: class_hash!("0x12345"), @@ -134,7 +147,7 @@ mod tests { "class_hash": "0x12345" }); - let input = serde_json::from_value::(named).unwrap(); + let input = Input::deserialize(crate::dto::Value::new(named, RpcVersion::V07)).unwrap(); let expected = Input { block_id: block_hash!("0xabcde").into(), class_hash: class_hash!("0x12345"), diff --git a/crates/rpc/src/method/get_class_at.rs b/crates/rpc/src/method/get_class_at.rs index 4f951873b2..b082806933 100644 --- a/crates/rpc/src/method/get_class_at.rs +++ b/crates/rpc/src/method/get_class_at.rs @@ -8,13 +8,23 @@ use crate::v02::types::{CairoContractClass, ContractClass, SierraContractClass}; crate::error::generate_rpc_error_subset!(Error: BlockNotFound, ContractNotFound); -#[derive(serde::Deserialize, Debug, PartialEq, Eq)] -#[serde(deny_unknown_fields)] +#[derive(Debug, PartialEq, Eq)] pub struct Input { block_id: BlockId, contract_address: ContractAddress, } +impl crate::dto::DeserializeForVersion for Input { + fn deserialize(value: crate::dto::Value) -> Result { + value.deserialize_map(|value| { + Ok(Self { + block_id: value.deserialize("block_id")?, + contract_address: ContractAddress(value.deserialize("contract_address")?), + }) + }) + } +} + #[derive(Debug)] pub enum Output { DeprecatedClass(CairoContractClass), @@ -107,9 +117,11 @@ mod tests { use super::*; mod parsing { + use dto::DeserializeForVersion; use serde_json::json; use super::*; + use crate::RpcVersion; #[test] fn positional_args() { @@ -118,7 +130,8 @@ mod tests { "0x12345" ]); - let input = serde_json::from_value::(positional).unwrap(); + let input = + Input::deserialize(crate::dto::Value::new(positional, RpcVersion::V07)).unwrap(); let expected = Input { block_id: block_hash!("0xabcde").into(), contract_address: contract_address!("0x12345"), @@ -133,7 +146,7 @@ mod tests { "contract_address": "0x12345" }); - let input = serde_json::from_value::(named).unwrap(); + let input = Input::deserialize(crate::dto::Value::new(named, RpcVersion::V07)).unwrap(); let expected = Input { block_id: block_hash!("0xabcde").into(), contract_address: contract_address!("0x12345"), diff --git a/crates/rpc/src/method/get_class_hash_at.rs b/crates/rpc/src/method/get_class_hash_at.rs index 9f08356daf..484267b489 100644 --- a/crates/rpc/src/method/get_class_hash_at.rs +++ b/crates/rpc/src/method/get_class_hash_at.rs @@ -5,13 +5,23 @@ use crate::context::RpcContext; crate::error::generate_rpc_error_subset!(Error: BlockNotFound, ContractNotFound); -#[derive(serde::Deserialize, Debug, PartialEq, Eq)] -#[serde(deny_unknown_fields)] +#[derive(Debug, PartialEq, Eq)] pub struct Input { block_id: BlockId, contract_address: ContractAddress, } +impl crate::dto::DeserializeForVersion for Input { + fn deserialize(value: crate::dto::Value) -> Result { + value.deserialize_map(|value| { + Ok(Self { + block_id: value.deserialize("block_id")?, + contract_address: ContractAddress(value.deserialize("contract_address")?), + }) + }) + } +} + #[derive(Debug)] pub struct Output(ClassHash); @@ -79,6 +89,8 @@ mod tests { use serde_json::json; use super::*; + use crate::dto::DeserializeForVersion; + use crate::RpcVersion; #[test] fn positional_args() { @@ -87,7 +99,8 @@ mod tests { "0x12345" ]); - let input = serde_json::from_value::(positional).unwrap(); + let input = + Input::deserialize(crate::dto::Value::new(positional, RpcVersion::V07)).unwrap(); let expected = Input { block_id: block_hash!("0xabcde").into(), contract_address: contract_address!("0x12345"), @@ -102,7 +115,7 @@ mod tests { "contract_address": "0x12345" }); - let input = serde_json::from_value::(named).unwrap(); + let input = Input::deserialize(crate::dto::Value::new(named, RpcVersion::V07)).unwrap(); let expected = Input { block_id: block_hash!("0xabcde").into(), contract_address: contract_address!("0x12345"), diff --git a/crates/rpc/src/method/get_events.rs b/crates/rpc/src/method/get_events.rs index 285eaa8013..6a1cc06b34 100644 --- a/crates/rpc/src/method/get_events.rs +++ b/crates/rpc/src/method/get_events.rs @@ -3,7 +3,6 @@ use std::str::FromStr; use anyhow::Context; use pathfinder_common::{BlockId, BlockNumber, ContractAddress, EventKey}; use pathfinder_storage::EventFilterError; -use serde::Deserialize; use starknet_gateway_types::reply::PendingBlock; use tokio::task::JoinHandle; @@ -41,35 +40,52 @@ impl From for crate::error::ApplicationError { } } -#[derive(serde::Deserialize, Debug, PartialEq, Eq)] -#[cfg_attr(test, derive(Clone))] -#[serde(deny_unknown_fields)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct GetEventsInput { filter: EventFilter, } +impl crate::dto::DeserializeForVersion for GetEventsInput { + fn deserialize(value: crate::dto::Value) -> Result { + value.deserialize_map(|value| { + Ok(Self { + filter: value.deserialize("filter")?, + }) + }) + } +} + /// Contains event filter parameters passed to `starknet_getEvents`. -#[serde_with::skip_serializing_none] -#[derive(Default, Clone, Debug, Deserialize, PartialEq, Eq)] -#[serde(deny_unknown_fields)] +#[derive(Default, Clone, Debug, PartialEq, Eq)] pub struct EventFilter { - #[serde(default)] pub from_block: Option, - #[serde(default)] pub to_block: Option, - #[serde(default)] pub address: Option, - #[serde(default)] pub keys: Vec>, - - // These are inlined here because serde flatten and deny_unknown_fields - // don't work together. pub chunk_size: usize, /// Offset, measured in events, which points to the requested chunk - #[serde(default)] pub continuation_token: Option, } +impl crate::dto::DeserializeForVersion for EventFilter { + fn deserialize(value: crate::dto::Value) -> Result { + value.deserialize_map(|value| { + Ok(Self { + from_block: value.deserialize_optional("from_block")?, + to_block: value.deserialize_optional("to_block")?, + address: value.deserialize_optional("address")?.map(ContractAddress), + keys: value + .deserialize_optional_array("keys", |value| { + value.deserialize_array(|value| value.deserialize().map(EventKey)) + })? + .unwrap_or_default(), + chunk_size: value.deserialize_serde("chunk_size")?, + continuation_token: value.deserialize_optional_serde("continuation_token")?, + }) + }) + } +} + /// Returns events matching the specified filter pub async fn get_events( context: RpcContext, @@ -567,6 +583,8 @@ mod tests { use super::types::{EmittedEvent, GetEventsResult}; use super::*; + use crate::dto::DeserializeForVersion; + use crate::RpcVersion; #[rstest::rstest] #[case::positional_with_optionals(json!([{ @@ -604,7 +622,8 @@ mod tests { }; let expected = GetEventsInput { filter }; - let input = serde_json::from_value::(input).unwrap(); + let input = + GetEventsInput::deserialize(crate::dto::Value::new(input, RpcVersion::V07)).unwrap(); assert_eq!(input, expected); } diff --git a/crates/rpc/src/method/get_nonce.rs b/crates/rpc/src/method/get_nonce.rs index b2141d6943..44cf168a36 100644 --- a/crates/rpc/src/method/get_nonce.rs +++ b/crates/rpc/src/method/get_nonce.rs @@ -3,13 +3,23 @@ use pathfinder_common::{BlockId, ContractAddress, ContractNonce}; use crate::context::RpcContext; -#[derive(serde::Deserialize, Debug, PartialEq, Eq)] -#[serde(deny_unknown_fields)] +#[derive(Debug, PartialEq, Eq)] pub struct Input { block_id: BlockId, contract_address: ContractAddress, } +impl crate::dto::DeserializeForVersion for Input { + fn deserialize(value: crate::dto::Value) -> Result { + value.deserialize_map(|value| { + Ok(Self { + block_id: value.deserialize("block_id")?, + contract_address: value.deserialize("contract_address").map(ContractAddress)?, + }) + }) + } +} + #[derive(Debug)] pub struct Output(ContractNonce); diff --git a/crates/rpc/src/method/get_state_update.rs b/crates/rpc/src/method/get_state_update.rs index d11af4cd98..be5e9d307d 100644 --- a/crates/rpc/src/method/get_state_update.rs +++ b/crates/rpc/src/method/get_state_update.rs @@ -5,12 +5,21 @@ use pathfinder_common::{BlockId, StateUpdate}; use crate::{dto, RpcContext}; -#[derive(serde::Deserialize, Debug, PartialEq, Eq)] -#[serde(deny_unknown_fields)] +#[derive(Debug, PartialEq, Eq)] pub struct Input { block_id: BlockId, } +impl crate::dto::DeserializeForVersion for Input { + fn deserialize(value: dto::Value) -> Result { + value.deserialize_map(|value| { + Ok(Self { + block_id: value.deserialize("block_id")?, + }) + }) + } +} + crate::error::generate_rpc_error_subset!(Error: BlockNotFound); #[derive(PartialEq, Debug)] @@ -71,12 +80,14 @@ pub async fn get_state_update(context: RpcContext, input: Input) -> Result Box { @@ -104,7 +115,7 @@ mod tests { #[case::hash_by_position(json!([{"block_hash": "0xbeef"}]), block_hash!("0xbeef").into())] #[case::hash_by_name(json!({"block_id": {"block_hash": "0xbeef"}}), block_hash!("0xbeef").into())] fn input_parsing(#[case] input: serde_json::Value, #[case] block_id: BlockId) { - let input = serde_json::from_value::(input).unwrap(); + let input = Input::deserialize(crate::dto::Value::new(input, RpcVersion::V07)).unwrap(); let expected = Input { block_id }; diff --git a/crates/rpc/src/method/get_storage_at.rs b/crates/rpc/src/method/get_storage_at.rs index eec281c5ad..3e6d364516 100644 --- a/crates/rpc/src/method/get_storage_at.rs +++ b/crates/rpc/src/method/get_storage_at.rs @@ -1,17 +1,27 @@ use anyhow::Context; use pathfinder_common::{BlockId, ContractAddress, StorageAddress, StorageValue}; -use serde::Deserialize; use crate::context::RpcContext; -#[derive(Deserialize, Debug, PartialEq, Eq)] -#[serde(deny_unknown_fields)] +#[derive(Debug, PartialEq, Eq)] pub struct Input { pub contract_address: ContractAddress, pub key: StorageAddress, pub block_id: BlockId, } +impl crate::dto::DeserializeForVersion for Input { + fn deserialize(value: crate::dto::Value) -> Result { + value.deserialize_map(|value| { + Ok(Self { + contract_address: value.deserialize("contract_address").map(ContractAddress)?, + key: value.deserialize("key").map(StorageAddress)?, + block_id: value.deserialize("block_id")?, + }) + }) + } +} + #[derive(Debug)] pub struct Output(StorageValue); @@ -88,6 +98,8 @@ mod tests { use serde_json::json; use super::*; + use crate::dto::DeserializeForVersion; + use crate::RpcVersion; /// # Important /// @@ -104,7 +116,7 @@ mod tests { block_id: BlockId::Latest, }; - let input = serde_json::from_value::(input).unwrap(); + let input = Input::deserialize(crate::dto::Value::new(input, RpcVersion::V07)).unwrap(); assert_eq!(input, expected); } diff --git a/crates/rpc/src/method/get_transaction_by_block_id_and_index.rs b/crates/rpc/src/method/get_transaction_by_block_id_and_index.rs index c4db5fbc4b..2a4bdb981e 100644 --- a/crates/rpc/src/method/get_transaction_by_block_id_and_index.rs +++ b/crates/rpc/src/method/get_transaction_by_block_id_and_index.rs @@ -11,6 +11,17 @@ pub struct Input { index: TransactionIndex, } +impl crate::dto::DeserializeForVersion for Input { + fn deserialize(value: crate::dto::Value) -> Result { + value.deserialize_map(|value| { + Ok(Self { + block_id: value.deserialize("block_id")?, + index: value.deserialize("index")?, + }) + }) + } +} + crate::error::generate_rpc_error_subset!( GetTransactionByBlockIdAndIndexError: BlockNotFound, InvalidTxnIndex diff --git a/crates/rpc/src/method/get_transaction_by_hash.rs b/crates/rpc/src/method/get_transaction_by_hash.rs index 0a27a246c8..6db1a11a00 100644 --- a/crates/rpc/src/method/get_transaction_by_hash.rs +++ b/crates/rpc/src/method/get_transaction_by_hash.rs @@ -12,6 +12,16 @@ pub struct Input { transaction_hash: TransactionHash, } +impl crate::dto::DeserializeForVersion for Input { + fn deserialize(value: crate::dto::Value) -> Result { + value.deserialize_map(|value| { + Ok(Self { + transaction_hash: value.deserialize("transaction_hash").map(TransactionHash)?, + }) + }) + } +} + #[derive(Debug, PartialEq, Eq)] pub struct Output(Transaction); diff --git a/crates/rpc/src/method/get_transaction_receipt.rs b/crates/rpc/src/method/get_transaction_receipt.rs index 10c508aaf5..b345c8666b 100644 --- a/crates/rpc/src/method/get_transaction_receipt.rs +++ b/crates/rpc/src/method/get_transaction_receipt.rs @@ -7,12 +7,20 @@ use pathfinder_common::{BlockHash, BlockNumber, TransactionHash}; use crate::context::RpcContext; use crate::dto::{self, serialize}; -#[derive(serde::Deserialize)] -#[serde(deny_unknown_fields)] pub struct Input { pub transaction_hash: TransactionHash, } +impl crate::dto::DeserializeForVersion for Input { + fn deserialize(value: crate::dto::Value) -> Result { + value.deserialize_map(|value| { + Ok(Self { + transaction_hash: value.deserialize("transaction_hash").map(TransactionHash)?, + }) + }) + } +} + pub enum Output { Full { block_hash: BlockHash, diff --git a/crates/rpc/src/method/get_transaction_status.rs b/crates/rpc/src/method/get_transaction_status.rs index dcc27f4bc6..6ee23ee410 100644 --- a/crates/rpc/src/method/get_transaction_status.rs +++ b/crates/rpc/src/method/get_transaction_status.rs @@ -4,11 +4,21 @@ use pathfinder_common::TransactionHash; use crate::context::RpcContext; use crate::dto::TxnExecutionStatus; -#[derive(serde::Deserialize, Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq)] pub struct Input { transaction_hash: TransactionHash, } +impl crate::dto::DeserializeForVersion for Input { + fn deserialize(value: crate::dto::Value) -> Result { + value.deserialize_map(|value| { + Ok(Self { + transaction_hash: value.deserialize("transaction_hash").map(TransactionHash)?, + }) + }) + } +} + #[derive(Debug, PartialEq)] pub enum Output { Received, diff --git a/crates/rpc/src/pathfinder/methods/get_proof.rs b/crates/rpc/src/pathfinder/methods/get_proof.rs index 3b5118f09d..3e342a5afd 100644 --- a/crates/rpc/src/pathfinder/methods/get_proof.rs +++ b/crates/rpc/src/pathfinder/methods/get_proof.rs @@ -4,18 +4,31 @@ use pathfinder_common::trie::TrieNode; use pathfinder_common::BlockId; use pathfinder_crypto::Felt; use pathfinder_merkle_tree::{ContractsStorageTree, StorageCommitmentTree}; -use serde::{Deserialize, Serialize}; +use serde::Serialize; use serde_with::skip_serializing_none; use crate::context::RpcContext; -#[derive(Deserialize, Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq)] pub struct GetProofInput { pub block_id: BlockId, pub contract_address: ContractAddress, pub keys: Vec, } +impl crate::dto::DeserializeForVersion for GetProofInput { + fn deserialize(value: crate::dto::Value) -> Result { + value.deserialize_map(|value| { + Ok(Self { + block_id: value.deserialize("block_id")?, + contract_address: ContractAddress(value.deserialize("contract_address")?), + keys: value + .deserialize_array("keys", |value| Ok(StorageAddress(value.deserialize()?)))?, + }) + }) + } +} + // FIXME: allow `generate_rpc_error_subset!` to work with enum struct variants. // This may not actually be possible though. #[derive(Debug)] diff --git a/crates/rpc/src/pathfinder/methods/get_transaction_status.rs b/crates/rpc/src/pathfinder/methods/get_transaction_status.rs index c01d391b03..70c974e1d9 100644 --- a/crates/rpc/src/pathfinder/methods/get_transaction_status.rs +++ b/crates/rpc/src/pathfinder/methods/get_transaction_status.rs @@ -11,6 +11,16 @@ pub struct GetGatewayTransactionInput { crate::error::generate_rpc_error_subset!(GetGatewayTransactionError:); +impl crate::dto::DeserializeForVersion for GetGatewayTransactionInput { + fn deserialize(value: crate::dto::Value) -> Result { + value.deserialize_map(|value| { + Ok(Self { + transaction_hash: TransactionHash(value.deserialize("transaction_hash")?), + }) + }) + } +} + pub async fn get_transaction_status( context: RpcContext, input: GetGatewayTransactionInput, diff --git a/crates/rpc/src/v02/method/get_block_transaction_count.rs b/crates/rpc/src/v02/method/get_block_transaction_count.rs index 5d5e7044e2..febc644c86 100644 --- a/crates/rpc/src/v02/method/get_block_transaction_count.rs +++ b/crates/rpc/src/v02/method/get_block_transaction_count.rs @@ -9,6 +9,12 @@ pub struct GetBlockTransactionCountInput { block_id: BlockId, } +impl crate::dto::DeserializeForVersion for GetBlockTransactionCountInput { + fn deserialize(value: crate::dto::Value) -> Result { + value.deserialize_serde() + } +} + type BlockTransactionCount = u64; crate::error::generate_rpc_error_subset!(GetBlockTransactionCountError: BlockNotFound); diff --git a/crates/rpc/src/v02/method/get_class.rs b/crates/rpc/src/v02/method/get_class.rs index e08b9fabff..5c30e28484 100644 --- a/crates/rpc/src/v02/method/get_class.rs +++ b/crates/rpc/src/v02/method/get_class.rs @@ -13,6 +13,12 @@ pub struct GetClassInput { class_hash: ClassHash, } +impl crate::dto::DeserializeForVersion for GetClassInput { + fn deserialize(value: crate::dto::Value) -> Result { + value.deserialize_serde() + } +} + pub async fn get_class( context: RpcContext, input: GetClassInput, diff --git a/crates/rpc/src/v02/method/get_class_at.rs b/crates/rpc/src/v02/method/get_class_at.rs index 224dffc985..e008dbbbe7 100644 --- a/crates/rpc/src/v02/method/get_class_at.rs +++ b/crates/rpc/src/v02/method/get_class_at.rs @@ -13,6 +13,12 @@ pub struct GetClassAtInput { contract_address: ContractAddress, } +impl crate::dto::DeserializeForVersion for GetClassAtInput { + fn deserialize(value: crate::dto::Value) -> Result { + value.deserialize_serde() + } +} + pub async fn get_class_at( context: RpcContext, input: GetClassAtInput, diff --git a/crates/rpc/src/v02/method/get_class_hash_at.rs b/crates/rpc/src/v02/method/get_class_hash_at.rs index a4c7f5776d..602c6ab4c9 100644 --- a/crates/rpc/src/v02/method/get_class_hash_at.rs +++ b/crates/rpc/src/v02/method/get_class_hash_at.rs @@ -13,6 +13,12 @@ pub struct GetClassHashAtInput { contract_address: ContractAddress, } +impl crate::dto::DeserializeForVersion for GetClassHashAtInput { + fn deserialize(value: crate::dto::Value) -> Result { + value.deserialize_serde() + } +} + #[serde_with::serde_as] #[derive(serde::Serialize, Debug)] pub struct GetClassHashOutput(#[serde_as(as = "RpcFelt")] ClassHash); diff --git a/crates/rpc/src/v02/method/get_nonce.rs b/crates/rpc/src/v02/method/get_nonce.rs index cf1d61c598..779488ca6c 100644 --- a/crates/rpc/src/v02/method/get_nonce.rs +++ b/crates/rpc/src/v02/method/get_nonce.rs @@ -11,6 +11,12 @@ pub struct GetNonceInput { contract_address: ContractAddress, } +impl crate::dto::DeserializeForVersion for GetNonceInput { + fn deserialize(value: crate::dto::Value) -> Result { + value.deserialize_serde() + } +} + #[serde_with::serde_as] #[derive(serde::Serialize, Debug, PartialEq)] pub struct GetNonceOutput(#[serde_as(as = "RpcFelt")] ContractNonce); diff --git a/crates/rpc/src/v02/method/get_storage_at.rs b/crates/rpc/src/v02/method/get_storage_at.rs index 9853b9ce73..7a9535b9bc 100644 --- a/crates/rpc/src/v02/method/get_storage_at.rs +++ b/crates/rpc/src/v02/method/get_storage_at.rs @@ -13,6 +13,12 @@ pub struct GetStorageAtInput { pub block_id: BlockId, } +impl crate::dto::DeserializeForVersion for GetStorageAtInput { + fn deserialize(value: crate::dto::Value) -> Result { + value.deserialize_serde() + } +} + #[serde_with::serde_as] #[derive(serde::Serialize, Debug)] pub struct GetStorageOutput(#[serde_as(as = "RpcFelt")] StorageValue); diff --git a/crates/rpc/src/v02/method/get_transaction_by_block_id_and_index.rs b/crates/rpc/src/v02/method/get_transaction_by_block_id_and_index.rs index 2aa21e8d14..0f65843548 100644 --- a/crates/rpc/src/v02/method/get_transaction_by_block_id_and_index.rs +++ b/crates/rpc/src/v02/method/get_transaction_by_block_id_and_index.rs @@ -11,6 +11,12 @@ pub struct GetTransactionByBlockIdAndIndexInput { index: TransactionIndex, } +impl crate::dto::DeserializeForVersion for GetTransactionByBlockIdAndIndexInput { + fn deserialize(value: crate::dto::Value) -> Result { + value.deserialize_serde() + } +} + crate::error::generate_rpc_error_subset!( GetTransactionByBlockIdAndIndexError: BlockNotFound, InvalidTxnIndex diff --git a/crates/rpc/src/v02/method/get_transaction_by_hash.rs b/crates/rpc/src/v02/method/get_transaction_by_hash.rs index 902e24f770..8d14bd096c 100644 --- a/crates/rpc/src/v02/method/get_transaction_by_hash.rs +++ b/crates/rpc/src/v02/method/get_transaction_by_hash.rs @@ -10,6 +10,12 @@ pub struct GetTransactionByHashInput { transaction_hash: TransactionHash, } +impl crate::dto::DeserializeForVersion for GetTransactionByHashInput { + fn deserialize(value: crate::dto::Value) -> Result { + value.deserialize_serde() + } +} + pub async fn get_transaction_by_hash_impl( context: RpcContext, input: GetTransactionByHashInput, diff --git a/crates/rpc/src/v02/types.rs b/crates/rpc/src/v02/types.rs index abd7807e89..1d75e3b521 100644 --- a/crates/rpc/src/v02/types.rs +++ b/crates/rpc/src/v02/types.rs @@ -3,7 +3,10 @@ pub(crate) mod class; pub use class::*; use pathfinder_common::{ResourceAmount, ResourcePricePerUnit}; +use serde::de::Error; use serde_with::serde_as; + +use crate::dto::{U128Hex, U64Hex}; pub mod syncing; #[derive(Copy, Clone, Debug, Default, serde::Deserialize, serde::Serialize, PartialEq, Eq)] @@ -12,6 +15,17 @@ pub struct ResourceBounds { pub l2_gas: ResourceBound, } +impl crate::dto::DeserializeForVersion for ResourceBounds { + fn deserialize(value: crate::dto::Value) -> Result { + value.deserialize_map(|value| { + Ok(Self { + l1_gas: value.deserialize("l1_gas")?, + l2_gas: value.deserialize("l2_gas")?, + }) + }) + } +} + impl From for pathfinder_common::transaction::ResourceBounds { fn from(resource_bounds: ResourceBounds) -> Self { Self { @@ -30,6 +44,19 @@ pub struct ResourceBound { pub max_price_per_unit: ResourcePricePerUnit, } +impl crate::dto::DeserializeForVersion for ResourceBound { + fn deserialize(value: crate::dto::Value) -> Result { + value.deserialize_map(|value| { + Ok(Self { + max_amount: ResourceAmount(value.deserialize::("max_amount")?.0), + max_price_per_unit: ResourcePricePerUnit( + value.deserialize::("max_price_per_unit")?.0, + ), + }) + }) + } +} + impl From for pathfinder_common::transaction::ResourceBound { fn from(resource_bound: ResourceBound) -> Self { Self { @@ -46,6 +73,17 @@ pub enum DataAvailabilityMode { L2, } +impl crate::dto::DeserializeForVersion for DataAvailabilityMode { + fn deserialize(value: crate::dto::Value) -> Result { + let value: String = value.deserialize_serde()?; + match value.as_str() { + "L1" => Ok(Self::L1), + "L2" => Ok(Self::L2), + _ => Err(serde_json::Error::custom("invalid data availability mode")), + } + } +} + impl From for pathfinder_common::transaction::DataAvailabilityMode { fn from(data_availability_mode: DataAvailabilityMode) -> Self { match data_availability_mode { @@ -82,9 +120,12 @@ pub mod request { TransactionSignatureElem, TransactionVersion, }; + use serde::de::Error; use serde::Deserialize; use serde_with::serde_as; + use crate::dto::U64Hex; + /// "Broadcasted" L2 transaction in requests the RPC API. /// /// "Broadcasted" transactions represent the data required to submit a new @@ -102,6 +143,26 @@ pub mod request { DeployAccount(BroadcastedDeployAccountTransaction), } + impl crate::dto::DeserializeForVersion for BroadcastedTransaction { + fn deserialize(value: crate::dto::Value) -> Result { + value.deserialize_map(|value| { + let tag: String = value.deserialize_serde("type")?; + match tag.as_str() { + "DECLARE" => Ok(Self::Declare(BroadcastedDeclareTransaction::deserialize( + value, + )?)), + "INVOKE" => Ok(Self::Invoke(BroadcastedInvokeTransaction::deserialize( + value, + )?)), + "DEPLOY_ACCOUNT" => Ok(Self::DeployAccount( + BroadcastedDeployAccountTransaction::deserialize(value)?, + )), + _ => Err(serde_json::Error::custom("unknown transaction type")), + } + }) + } + } + impl BroadcastedTransaction { pub fn into_invoke(self) -> Option { match self { @@ -154,6 +215,63 @@ pub mod request { V3(BroadcastedDeclareTransactionV3), } + impl BroadcastedDeclareTransaction { + pub fn deserialize(value: &mut crate::dto::Map) -> Result { + let version = value.deserialize("version").map(TransactionVersion)?; + let signature = value.deserialize_array("signature", |value| { + value.deserialize().map(TransactionSignatureElem) + })?; + let sender_address = value.deserialize("sender_address").map(ContractAddress)?; + match version.without_query_version() { + 0 => Ok(Self::V0(BroadcastedDeclareTransactionV0 { + max_fee: value.deserialize("max_fee").map(Fee)?, + version, + signature, + contract_class: value.deserialize("contract_class")?, + sender_address, + })), + 1 => Ok(Self::V1(BroadcastedDeclareTransactionV1 { + max_fee: value.deserialize("max_fee").map(Fee)?, + version, + signature, + nonce: value.deserialize("nonce").map(TransactionNonce)?, + contract_class: value.deserialize("contract_class")?, + sender_address, + })), + 2 => Ok(Self::V2(BroadcastedDeclareTransactionV2 { + max_fee: value.deserialize("max_fee").map(Fee)?, + version, + signature, + nonce: value.deserialize("nonce").map(TransactionNonce)?, + compiled_class_hash: value.deserialize("compiled_class_hash").map(CasmHash)?, + contract_class: value.deserialize("contract_class")?, + sender_address, + })), + 3 => Ok(Self::V3(BroadcastedDeclareTransactionV3 { + version, + signature, + nonce: value.deserialize("nonce").map(TransactionNonce)?, + resource_bounds: value.deserialize("resource_bounds")?, + tip: value.deserialize::("tip").map(|tip| Tip(tip.0))?, + paymaster_data: value.deserialize_array("paymaster_data", |value| { + value.deserialize().map(PaymasterDataElem) + })?, + account_deployment_data: value + .deserialize_array("account_deployment_data", |value| { + value.deserialize().map(AccountDeploymentDataElem) + })?, + nonce_data_availability_mode: value + .deserialize("nonce_data_availability_mode")?, + fee_data_availability_mode: value.deserialize("fee_data_availability_mode")?, + compiled_class_hash: value.deserialize("compiled_class_hash").map(CasmHash)?, + contract_class: value.deserialize("contract_class")?, + sender_address, + })), + _ => Err(serde_json::Error::custom("unknown transaction version")), + } + } + } + impl<'de> serde::Deserialize<'de> for BroadcastedDeclareTransaction { fn deserialize(deserializer: D) -> Result where @@ -266,15 +384,6 @@ pub mod request { V3(BroadcastedDeployAccountTransactionV3), } - impl BroadcastedDeployAccountTransaction { - pub fn deployed_contract_address(&self) -> ContractAddress { - match self { - Self::V1(tx) => tx.deployed_contract_address(), - Self::V3(tx) => tx.deployed_contract_address(), - } - } - } - impl<'de> serde::Deserialize<'de> for BroadcastedDeployAccountTransaction { fn deserialize(deserializer: D) -> Result where @@ -304,6 +413,59 @@ pub mod request { } } + impl BroadcastedDeployAccountTransaction { + pub fn deserialize(value: &mut crate::dto::Map) -> Result { + let version = value.deserialize("version").map(TransactionVersion)?; + let signature = value.deserialize_array("signature", |value| { + value.deserialize().map(TransactionSignatureElem) + })?; + let nonce = value.deserialize("nonce").map(TransactionNonce)?; + let contract_address_salt = value + .deserialize("contract_address_salt") + .map(ContractAddressSalt)?; + let constructor_calldata = value + .deserialize_array("constructor_calldata", |value| { + value.deserialize().map(CallParam) + })?; + let class_hash = value.deserialize("class_hash").map(ClassHash)?; + match version.without_query_version() { + 1 => Ok(Self::V1(BroadcastedDeployAccountTransactionV1 { + version, + max_fee: value.deserialize("max_fee").map(Fee)?, + signature, + nonce, + contract_address_salt, + constructor_calldata, + class_hash, + })), + 3 => Ok(Self::V3(BroadcastedDeployAccountTransactionV3 { + version, + signature, + nonce, + resource_bounds: value.deserialize("resource_bounds")?, + tip: value.deserialize::("tip").map(|tip| Tip(tip.0))?, + paymaster_data: value.deserialize_array("paymaster_data", |value| { + value.deserialize().map(PaymasterDataElem) + })?, + nonce_data_availability_mode: value + .deserialize("nonce_data_availability_mode")?, + fee_data_availability_mode: value.deserialize("fee_data_availability_mode")?, + contract_address_salt, + constructor_calldata, + class_hash, + })), + _ => Err(serde_json::Error::custom("unknown transaction version")), + } + } + + pub fn deployed_contract_address(&self) -> ContractAddress { + match self { + Self::V1(tx) => tx.deployed_contract_address(), + Self::V3(tx) => tx.deployed_contract_address(), + } + } + } + #[serde_as] #[derive(Clone, Debug, Deserialize, PartialEq, Eq)] #[cfg_attr(test, derive(serde::Serialize))] @@ -369,22 +531,6 @@ pub mod request { V3(BroadcastedInvokeTransactionV3), } - impl BroadcastedInvokeTransaction { - pub fn into_v1(self) -> Option { - match self { - Self::V1(x) => Some(x), - _ => None, - } - } - - pub fn into_v0(self) -> Option { - match self { - Self::V0(x) => Some(x), - _ => None, - } - } - } - impl<'de> Deserialize<'de> for BroadcastedInvokeTransaction { fn deserialize(deserializer: D) -> Result where @@ -415,6 +561,71 @@ pub mod request { } } + impl BroadcastedInvokeTransaction { + pub fn deserialize(value: &mut crate::dto::Map) -> Result { + let version = value.deserialize("version").map(TransactionVersion)?; + let signature = value.deserialize_array("signature", |value| { + value.deserialize().map(TransactionSignatureElem) + })?; + let calldata = + value.deserialize_array("calldata", |value| value.deserialize().map(CallParam))?; + match version.without_query_version() { + 0 => Ok(Self::V0(BroadcastedInvokeTransactionV0 { + version, + max_fee: value.deserialize("max_fee").map(Fee)?, + signature, + contract_address: value.deserialize("contract_address").map(ContractAddress)?, + entry_point_selector: value + .deserialize("entry_point_selector") + .map(EntryPoint)?, + calldata, + })), + 1 => Ok(Self::V1(BroadcastedInvokeTransactionV1 { + version, + max_fee: value.deserialize("max_fee").map(Fee)?, + signature, + nonce: value.deserialize("nonce").map(TransactionNonce)?, + sender_address: value.deserialize("sender_address").map(ContractAddress)?, + calldata, + })), + 3 => Ok(Self::V3(BroadcastedInvokeTransactionV3 { + version, + signature, + nonce: value.deserialize("nonce").map(TransactionNonce)?, + resource_bounds: value.deserialize("resource_bounds")?, + tip: value.deserialize::("tip").map(|tip| Tip(tip.0))?, + paymaster_data: value.deserialize_array("paymaster_data", |value| { + value.deserialize().map(PaymasterDataElem) + })?, + account_deployment_data: value + .deserialize_array("account_deployment_data", |value| { + value.deserialize().map(AccountDeploymentDataElem) + })?, + nonce_data_availability_mode: value + .deserialize("nonce_data_availability_mode")?, + fee_data_availability_mode: value.deserialize("fee_data_availability_mode")?, + sender_address: value.deserialize("sender_address").map(ContractAddress)?, + calldata, + })), + _ => Err(serde_json::Error::custom("unknown transaction version")), + } + } + + pub fn into_v1(self) -> Option { + match self { + Self::V1(x) => Some(x), + _ => None, + } + } + + pub fn into_v0(self) -> Option { + match self { + Self::V0(x) => Some(x), + _ => None, + } + } + } + #[serde_as] #[derive(Clone, Debug, Deserialize, PartialEq, Eq)] #[cfg_attr(test, derive(serde::Serialize))] @@ -619,6 +830,7 @@ pub mod request { use pretty_assertions_sorted::assert_eq; use super::super::*; + use crate::dto::DeserializeForVersion; use crate::v02::types::{ CairoContractClass, ContractEntryPoints, @@ -809,11 +1021,16 @@ pub mod request { )), ]; - let json_fixture = fixture!("broadcasted_transactions.json"); + let json_fixture: serde_json::Value = + serde_json::from_str(&fixture!("broadcasted_transactions.json")).unwrap(); - assert_eq!(serde_json::to_string(&txs).unwrap(), json_fixture); + assert_eq!(serde_json::to_value(&txs).unwrap(), json_fixture); assert_eq!( - serde_json::from_str::>(&json_fixture).unwrap(), + crate::dto::Value::new(dbg!(json_fixture), crate::RpcVersion::V07) + .deserialize_array( + ::deserialize + ) + .unwrap(), txs ); } diff --git a/crates/rpc/src/v02/types/class.rs b/crates/rpc/src/v02/types/class.rs index 92d95c3c05..82d74e655e 100644 --- a/crates/rpc/src/v02/types/class.rs +++ b/crates/rpc/src/v02/types/class.rs @@ -198,7 +198,7 @@ impl TryFrom } /// A Cairo 0.x class. -#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] #[serde(deny_unknown_fields)] pub struct CairoContractClass { pub program: String, @@ -206,6 +206,14 @@ pub struct CairoContractClass { pub abi: Option>, } +/// [`CairoContractClass`] is sometimes deserialized as JSON from raw bytes, so +/// the serde derives are necessary. +impl crate::dto::DeserializeForVersion for CairoContractClass { + fn deserialize(value: crate::dto::Value) -> Result { + value.deserialize_serde() + } +} + impl CairoContractClass { pub fn class_hash(&self) -> anyhow::Result { let serialized = self.serialize_to_json()?; @@ -429,6 +437,14 @@ pub struct SierraContractClass { pub abi: String, } +/// [`SierraContractClass`] is sometimes deserialized as JSON from raw bytes, so +/// the serde derives are necessary. +impl crate::dto::DeserializeForVersion for SierraContractClass { + fn deserialize(value: crate::dto::Value) -> Result { + value.deserialize_serde() + } +} + impl SierraContractClass { pub fn serialize_to_json(&self) -> anyhow::Result> { let json = serde_json::to_vec(self)?; @@ -542,21 +558,26 @@ mod tests { use pathfinder_executor::parse_deprecated_class_definition; + use crate::dto::DeserializeForVersion; use crate::v02::types::CairoContractClass; #[test] fn convert_deprecated_class_definition_without_debug_info_into_starknet_api_type() { - let definition = br#"{ - "program": "H4sIAAAAAAAC/5WPzQqDMBCE32XPIklPxVcpJURd2wWzCZu1FCTv3qiF9urcZphvf1bwqkL9opihu90b6BealfjrhhgSzSjuhZIpMnRgWntpLTQwevVH60msFVhLAzQiK01U60cQPLHLQ0xYWed26yqdhMIWmffV/Mlac07bJYITCvKAdTz7B0pd/Qv3V0r5AMLJpd3rAAAA", - "entry_points_by_type": { - "CONSTRUCTOR":[], - "EXTERNAL":[], - "L1_HANDLER":[] - }, - "abi": [] - }"#; - - let contract_class: CairoContractClass = serde_json::from_slice(definition).unwrap(); + let definition = serde_json::json!({ + "program": "H4sIAAAAAAAC/5WPzQqDMBCE32XPIklPxVcpJURd2wWzCZu1FCTv3qiF9urcZphvf1bwqkL9opihu90b6BealfjrhhgSzSjuhZIpMnRgWntpLTQwevVH60msFVhLAzQiK01U60cQPLHLQ0xYWed26yqdhMIWmffV/Mlac07bJYITCvKAdTz7B0pd/Qv3V0r5AMLJpd3rAAAA", + "entry_points_by_type": { + "CONSTRUCTOR":[], + "EXTERNAL":[], + "L1_HANDLER":[] + }, + "abi": [] + }); + + let contract_class = CairoContractClass::deserialize(crate::dto::Value::new( + definition, + crate::RpcVersion::V07, + )) + .unwrap(); let serialized_definition = contract_class.serialize_to_json().unwrap(); diff --git a/crates/rpc/src/v03/method/get_events.rs b/crates/rpc/src/v03/method/get_events.rs index 7eaadddd89..f90deb6345 100644 --- a/crates/rpc/src/v03/method/get_events.rs +++ b/crates/rpc/src/v03/method/get_events.rs @@ -48,6 +48,12 @@ pub struct GetEventsInput { filter: EventFilter, } +impl crate::dto::DeserializeForVersion for GetEventsInput { + fn deserialize(value: crate::dto::Value) -> Result { + value.deserialize_serde() + } +} + /// Contains event filter parameters passed to `starknet_getEvents`. #[serde_with::skip_serializing_none] #[derive(Default, Clone, Debug, Deserialize, PartialEq, Eq)] diff --git a/crates/rpc/src/v03/method/get_state_update.rs b/crates/rpc/src/v03/method/get_state_update.rs index 7534e662f3..d0df8eb8f0 100644 --- a/crates/rpc/src/v03/method/get_state_update.rs +++ b/crates/rpc/src/v03/method/get_state_update.rs @@ -9,6 +9,12 @@ pub struct GetStateUpdateInput { block_id: BlockId, } +impl crate::dto::DeserializeForVersion for GetStateUpdateInput { + fn deserialize(value: crate::dto::Value) -> Result { + value.deserialize_serde() + } +} + crate::error::generate_rpc_error_subset!(GetStateUpdateError: BlockNotFound); pub async fn get_state_update( diff --git a/crates/rpc/src/v06/method/add_declare_transaction.rs b/crates/rpc/src/v06/method/add_declare_transaction.rs index 32d831ca94..c89a6de8d6 100644 --- a/crates/rpc/src/v06/method/add_declare_transaction.rs +++ b/crates/rpc/src/v06/method/add_declare_transaction.rs @@ -143,6 +143,12 @@ pub struct AddDeclareTransactionInput { token: Option, } +impl crate::dto::DeserializeForVersion for AddDeclareTransactionInput { + fn deserialize(value: crate::dto::Value) -> Result { + value.deserialize_serde() + } +} + #[serde_with::serde_as] #[derive(serde::Serialize, Debug, PartialEq, Eq)] pub struct AddDeclareTransactionOutput { diff --git a/crates/rpc/src/v06/method/add_deploy_account_transaction.rs b/crates/rpc/src/v06/method/add_deploy_account_transaction.rs index 5ccdb6873b..498988233c 100644 --- a/crates/rpc/src/v06/method/add_deploy_account_transaction.rs +++ b/crates/rpc/src/v06/method/add_deploy_account_transaction.rs @@ -22,6 +22,12 @@ pub struct AddDeployAccountTransactionInput { deploy_account_transaction: Transaction, } +impl crate::dto::DeserializeForVersion for AddDeployAccountTransactionInput { + fn deserialize(value: crate::dto::Value) -> Result { + value.deserialize_serde() + } +} + #[serde_with::serde_as] #[derive(serde::Serialize, Debug, PartialEq, Eq)] pub struct AddDeployAccountTransactionOutput { diff --git a/crates/rpc/src/v06/method/add_invoke_transaction.rs b/crates/rpc/src/v06/method/add_invoke_transaction.rs index 464310a8ce..fd76960077 100644 --- a/crates/rpc/src/v06/method/add_invoke_transaction.rs +++ b/crates/rpc/src/v06/method/add_invoke_transaction.rs @@ -19,6 +19,12 @@ pub struct AddInvokeTransactionInput { invoke_transaction: Transaction, } +impl crate::dto::DeserializeForVersion for AddInvokeTransactionInput { + fn deserialize(value: crate::dto::Value) -> Result { + value.deserialize_serde() + } +} + #[serde_with::serde_as] #[derive(serde::Serialize, Debug, PartialEq, Eq)] pub struct AddInvokeTransactionOutput { diff --git a/crates/rpc/src/v06/method/call.rs b/crates/rpc/src/v06/method/call.rs index 731a749530..5c4c0e1cc0 100644 --- a/crates/rpc/src/v06/method/call.rs +++ b/crates/rpc/src/v06/method/call.rs @@ -67,6 +67,12 @@ pub struct CallInput { pub block_id: BlockId, } +impl crate::dto::DeserializeForVersion for CallInput { + fn deserialize(value: crate::dto::Value) -> Result { + value.deserialize_serde() + } +} + #[derive(Clone, serde::Deserialize, serde::Serialize, Debug, PartialEq, Eq)] #[serde(deny_unknown_fields)] pub struct FunctionCall { @@ -144,6 +150,8 @@ mod tests { use serde_json::json; use super::*; + use crate::dto::DeserializeForVersion; + use crate::RpcVersion; #[test] fn positional_args() { @@ -152,7 +160,8 @@ mod tests { { "block_hash": "0xbbbbbbbb" } ]); - let input = serde_json::from_value::(positional).unwrap(); + let input = CallInput::deserialize(crate::dto::Value::new(positional, RpcVersion::V07)) + .unwrap(); let expected = CallInput { request: FunctionCall { contract_address: contract_address!("0xabcde"), @@ -171,7 +180,8 @@ mod tests { "block_id": { "block_hash": "0xbbbbbbbb" } }); - let input = serde_json::from_value::(named).unwrap(); + let input = + CallInput::deserialize(crate::dto::Value::new(named, RpcVersion::V07)).unwrap(); let expected = CallInput { request: FunctionCall { contract_address: contract_address!("0xabcde"), diff --git a/crates/rpc/src/v06/method/estimate_fee.rs b/crates/rpc/src/v06/method/estimate_fee.rs index d0b5f33da0..fe694a9a94 100644 --- a/crates/rpc/src/v06/method/estimate_fee.rs +++ b/crates/rpc/src/v06/method/estimate_fee.rs @@ -16,6 +16,12 @@ pub struct EstimateFeeInput { pub block_id: BlockId, } +impl crate::dto::DeserializeForVersion for EstimateFeeInput { + fn deserialize(value: crate::dto::Value) -> Result { + value.deserialize_serde() + } +} + #[derive(Debug, serde::Deserialize, Eq, PartialEq)] pub struct SimulationFlags(pub Vec); diff --git a/crates/rpc/src/v06/method/estimate_message_fee.rs b/crates/rpc/src/v06/method/estimate_message_fee.rs index f21d84201f..5fd2011727 100644 --- a/crates/rpc/src/v06/method/estimate_message_fee.rs +++ b/crates/rpc/src/v06/method/estimate_message_fee.rs @@ -79,6 +79,12 @@ pub struct EstimateMessageFeeInput { pub block_id: BlockId, } +impl crate::dto::DeserializeForVersion for EstimateMessageFeeInput { + fn deserialize(value: crate::dto::Value) -> Result { + value.deserialize_serde() + } +} + #[derive(serde::Deserialize, Debug, PartialEq, Eq)] pub struct MsgFromL1 { pub from_address: EthereumAddress, diff --git a/crates/rpc/src/v06/method/get_block_with_tx_hashes.rs b/crates/rpc/src/v06/method/get_block_with_tx_hashes.rs index 1b62390b84..22f393c9a1 100644 --- a/crates/rpc/src/v06/method/get_block_with_tx_hashes.rs +++ b/crates/rpc/src/v06/method/get_block_with_tx_hashes.rs @@ -12,6 +12,12 @@ pub struct GetBlockInput { block_id: BlockId, } +impl crate::dto::DeserializeForVersion for GetBlockInput { + fn deserialize(value: crate::dto::Value) -> Result { + value.deserialize_serde() + } +} + crate::error::generate_rpc_error_subset!(GetBlockError: BlockNotFound); /// Get block information with transaction hashes given the block id diff --git a/crates/rpc/src/v06/method/get_block_with_txs.rs b/crates/rpc/src/v06/method/get_block_with_txs.rs index f2ec062cc7..714c61988a 100644 --- a/crates/rpc/src/v06/method/get_block_with_txs.rs +++ b/crates/rpc/src/v06/method/get_block_with_txs.rs @@ -13,6 +13,12 @@ pub struct GetBlockInput { block_id: BlockId, } +impl crate::dto::DeserializeForVersion for GetBlockInput { + fn deserialize(value: crate::dto::Value) -> Result { + value.deserialize_serde() + } +} + crate::error::generate_rpc_error_subset!(GetBlockError: BlockNotFound); /// Get block information with full transactions given the block id diff --git a/crates/rpc/src/v06/method/get_transaction_receipt.rs b/crates/rpc/src/v06/method/get_transaction_receipt.rs index b15aeb70d8..926f7733ae 100644 --- a/crates/rpc/src/v06/method/get_transaction_receipt.rs +++ b/crates/rpc/src/v06/method/get_transaction_receipt.rs @@ -9,6 +9,12 @@ pub struct GetTransactionReceiptInput { pub transaction_hash: TransactionHash, } +impl crate::dto::DeserializeForVersion for GetTransactionReceiptInput { + fn deserialize(value: crate::dto::Value) -> Result { + value.deserialize_serde() + } +} + crate::error::generate_rpc_error_subset!(GetTransactionReceiptError: TxnHashNotFound); pub async fn get_transaction_receipt( diff --git a/crates/rpc/src/v06/method/get_transaction_status.rs b/crates/rpc/src/v06/method/get_transaction_status.rs index 774c1b4865..a055c59f71 100644 --- a/crates/rpc/src/v06/method/get_transaction_status.rs +++ b/crates/rpc/src/v06/method/get_transaction_status.rs @@ -9,6 +9,12 @@ pub struct GetTransactionStatusInput { transaction_hash: TransactionHash, } +impl crate::dto::DeserializeForVersion for GetTransactionStatusInput { + fn deserialize(value: crate::dto::Value) -> Result { + value.deserialize_serde() + } +} + #[derive(Debug, PartialEq, Eq)] #[skip_serializing_none] pub enum GetTransactionStatusOutput { diff --git a/crates/rpc/src/v06/method/simulate_transactions.rs b/crates/rpc/src/v06/method/simulate_transactions.rs index 188eea508e..9a85495d46 100644 --- a/crates/rpc/src/v06/method/simulate_transactions.rs +++ b/crates/rpc/src/v06/method/simulate_transactions.rs @@ -18,6 +18,12 @@ pub struct SimulateTransactionInput { pub simulation_flags: dto::SimulationFlags, } +impl crate::dto::DeserializeForVersion for SimulateTransactionInput { + fn deserialize(value: crate::dto::Value) -> Result { + value.deserialize_serde() + } +} + #[derive(Debug, Serialize, Eq, PartialEq)] pub struct SimulateTransactionOutput(pub Vec); diff --git a/crates/rpc/src/v06/method/trace_block_transactions.rs b/crates/rpc/src/v06/method/trace_block_transactions.rs index a7870e6454..afa04d9c94 100644 --- a/crates/rpc/src/v06/method/trace_block_transactions.rs +++ b/crates/rpc/src/v06/method/trace_block_transactions.rs @@ -29,6 +29,12 @@ pub struct TraceBlockTransactionsInput { pub block_id: BlockId, } +impl crate::dto::DeserializeForVersion for TraceBlockTransactionsInput { + fn deserialize(value: crate::dto::Value) -> Result { + value.deserialize_serde() + } +} + #[derive(Debug, Serialize, Eq, PartialEq, Clone)] pub struct Trace { pub transaction_hash: TransactionHash, diff --git a/crates/rpc/src/v06/method/trace_transaction.rs b/crates/rpc/src/v06/method/trace_transaction.rs index bd226fe228..9c7b810ef9 100644 --- a/crates/rpc/src/v06/method/trace_transaction.rs +++ b/crates/rpc/src/v06/method/trace_transaction.rs @@ -21,6 +21,12 @@ pub struct TraceTransactionInput { pub transaction_hash: TransactionHash, } +impl crate::dto::DeserializeForVersion for TraceTransactionInput { + fn deserialize(value: crate::dto::Value) -> Result { + value.deserialize_serde() + } +} + #[derive(Debug, Serialize, Eq, PartialEq)] pub struct TraceTransactionOutput(pub TransactionTrace); diff --git a/crates/serde/src/lib.rs b/crates/serde/src/lib.rs index 2e336684e2..9a6d402865 100644 --- a/crates/serde/src/lib.rs +++ b/crates/serde/src/lib.rs @@ -256,6 +256,7 @@ serde_with::serde_conv!( |s: &str| bytes_from_hex_str::<8>(s).map(|b| Tip(u64::from_be_bytes(b))) ); +#[derive(Clone, Debug, PartialEq, Eq)] pub struct U64AsHexStr(pub u64); impl serde::Serialize for U64AsHexStr { From 6d59f13acb2e836bfb222e10fb36556658b2f238 Mon Sep 17 00:00:00 2001 From: Krzysztof Lis Date: Wed, 4 Sep 2024 17:08:23 +0200 Subject: [PATCH 013/282] refactor(BlockHeaderBuilder): remove the with_ prefix --- crates/common/src/header.rs | 45 +++++++++---------- crates/rpc/src/lib.rs | 32 ++++++------- crates/rpc/src/method/call.rs | 12 ++--- crates/rpc/src/method/estimate_message_fee.rs | 22 ++++----- .../src/method/trace_block_transactions.rs | 18 ++++---- crates/rpc/src/pending.rs | 22 ++++----- crates/rpc/src/test_setup.rs | 24 +++++----- crates/rpc/src/v06/method/call.rs | 12 ++--- .../src/v06/method/estimate_message_fee.rs | 10 ++--- .../v06/method/trace_block_transactions.rs | 14 +++--- crates/storage/src/connection/block.rs | 44 +++++++++--------- crates/storage/src/connection/event.rs | 6 +-- crates/storage/src/connection/signature.rs | 2 +- crates/storage/src/test_utils.rs | 18 ++++---- 14 files changed, 139 insertions(+), 142 deletions(-) diff --git a/crates/common/src/header.rs b/crates/common/src/header.rs index de7bf7248b..6c02559aa1 100644 --- a/crates/common/src/header.rs +++ b/crates/common/src/header.rs @@ -57,8 +57,8 @@ impl BlockHeader { /// and parent hash set to this block's hash. pub fn child_builder(&self) -> BlockHeaderBuilder { BlockHeaderBuilder(BlockHeader::default()) - .with_number(self.number + 1) - .with_parent_hash(self.hash) + .number(self.number + 1) + .parent_hash(self.hash) } /// Creates a [StateUpdate] with the block hash and state commitment fields @@ -71,103 +71,100 @@ impl BlockHeader { } impl BlockHeaderBuilder { - pub fn with_number(mut self, number: BlockNumber) -> Self { + pub fn number(mut self, number: BlockNumber) -> Self { self.0.number = number; self } - pub fn with_parent_hash(mut self, parent_hash: BlockHash) -> Self { + pub fn parent_hash(mut self, parent_hash: BlockHash) -> Self { self.0.parent_hash = parent_hash; self } - pub fn with_state_commitment(mut self, state_commmitment: StateCommitment) -> Self { + pub fn state_commitment(mut self, state_commmitment: StateCommitment) -> Self { self.0.state_commitment = state_commmitment; self } /// Sets the [StateCommitment] by calculating its value from the current /// [StorageCommitment] and [ClassCommitment]. - pub fn with_calculated_state_commitment(mut self) -> Self { + pub fn calculated_state_commitment(mut self) -> Self { self.0.state_commitment = StateCommitment::calculate(self.0.storage_commitment, self.0.class_commitment); self } - pub fn with_timestamp(mut self, timestamp: BlockTimestamp) -> Self { + pub fn timestamp(mut self, timestamp: BlockTimestamp) -> Self { self.0.timestamp = timestamp; self } - pub fn with_eth_l1_gas_price(mut self, eth_l1_gas_price: GasPrice) -> Self { + pub fn eth_l1_gas_price(mut self, eth_l1_gas_price: GasPrice) -> Self { self.0.eth_l1_gas_price = eth_l1_gas_price; self } - pub fn with_strk_l1_gas_price(mut self, strk_l1_gas_price: GasPrice) -> Self { + pub fn strk_l1_gas_price(mut self, strk_l1_gas_price: GasPrice) -> Self { self.0.strk_l1_gas_price = strk_l1_gas_price; self } - pub fn with_eth_l1_data_gas_price(mut self, eth_l1_data_gas_price: GasPrice) -> Self { + pub fn eth_l1_data_gas_price(mut self, eth_l1_data_gas_price: GasPrice) -> Self { self.0.eth_l1_data_gas_price = eth_l1_data_gas_price; self } - pub fn with_strk_l1_data_gas_price(mut self, strk_l1_data_gas_price: GasPrice) -> Self { + pub fn strk_l1_data_gas_price(mut self, strk_l1_data_gas_price: GasPrice) -> Self { self.0.strk_l1_data_gas_price = strk_l1_data_gas_price; self } - pub fn with_sequencer_address(mut self, sequencer_address: SequencerAddress) -> Self { + pub fn sequencer_address(mut self, sequencer_address: SequencerAddress) -> Self { self.0.sequencer_address = sequencer_address; self } - pub fn with_transaction_commitment( - mut self, - transaction_commitment: TransactionCommitment, - ) -> Self { + pub fn transaction_commitment(mut self, transaction_commitment: TransactionCommitment) -> Self { self.0.transaction_commitment = transaction_commitment; self } - pub fn with_event_commitment(mut self, event_commitment: EventCommitment) -> Self { + pub fn event_commitment(mut self, event_commitment: EventCommitment) -> Self { self.0.event_commitment = event_commitment; self } - pub fn with_storage_commitment(mut self, storage_commitment: StorageCommitment) -> Self { + pub fn storage_commitment(mut self, storage_commitment: StorageCommitment) -> Self { self.0.storage_commitment = storage_commitment; self } - pub fn with_class_commitment(mut self, class_commitment: ClassCommitment) -> Self { + pub fn class_commitment(mut self, class_commitment: ClassCommitment) -> Self { self.0.class_commitment = class_commitment; self } - pub fn with_starknet_version(mut self, starknet_version: StarknetVersion) -> Self { + pub fn starknet_version(mut self, starknet_version: StarknetVersion) -> Self { self.0.starknet_version = starknet_version; self } - pub fn with_transaction_count(mut self, transaction_count: usize) -> Self { + pub fn transaction_count(mut self, transaction_count: usize) -> Self { self.0.transaction_count = transaction_count; self } - pub fn with_event_count(mut self, event_count: usize) -> Self { + pub fn event_count(mut self, event_count: usize) -> Self { self.0.event_count = event_count; self } - pub fn with_l1_da_mode(mut self, l1_da_mode: L1DataAvailabilityMode) -> Self { + pub fn l1_da_mode(mut self, l1_da_mode: L1DataAvailabilityMode) -> Self { self.0.l1_da_mode = l1_da_mode; self } - pub fn with_receipt_commitment(mut self, receipt_commitment: ReceiptCommitment) -> Self { + pub fn receipt_commitment(mut self, receipt_commitment: ReceiptCommitment) -> Self { self.0.receipt_commitment = receipt_commitment; self } diff --git a/crates/rpc/src/lib.rs b/crates/rpc/src/lib.rs index b7863950da..4e13bdf6c2 100644 --- a/crates/rpc/src/lib.rs +++ b/crates/rpc/src/lib.rs @@ -356,10 +356,10 @@ pub mod test_utils { .insert_storage_root(BlockNumber::GENESIS, storage_root_idx) .unwrap(); let header0 = BlockHeader::builder() - .with_number(BlockNumber::GENESIS) - .with_storage_commitment(storage_commitment0) - .with_class_commitment(class_commitment0) - .with_calculated_state_commitment() + .number(BlockNumber::GENESIS) + .storage_commitment(storage_commitment0) + .class_commitment(class_commitment0) + .calculated_state_commitment() .finalize_with_hash(block_hash_bytes!(b"genesis")); db_txn.insert_block_header(&header0).unwrap(); db_txn @@ -398,12 +398,12 @@ pub mod test_utils { .unwrap(); let header1 = header0 .child_builder() - .with_timestamp(BlockTimestamp::new_or_panic(1)) - .with_storage_commitment(storage_commitment1) - .with_class_commitment(class_commitment1) - .with_calculated_state_commitment() - .with_eth_l1_gas_price(GasPrice::from(1)) - .with_sequencer_address(sequencer_address_bytes!(&[1u8])) + .timestamp(BlockTimestamp::new_or_panic(1)) + .storage_commitment(storage_commitment1) + .class_commitment(class_commitment1) + .calculated_state_commitment() + .eth_l1_gas_price(GasPrice::from(1)) + .sequencer_address(sequencer_address_bytes!(&[1u8])) .finalize_with_hash(block_hash_bytes!(b"block 1")); db_txn.insert_block_header(&header1).unwrap(); db_txn @@ -457,12 +457,12 @@ pub mod test_utils { .unwrap(); let header2 = header1 .child_builder() - .with_timestamp(BlockTimestamp::new_or_panic(2)) - .with_storage_commitment(storage_commitment2) - .with_class_commitment(class_commitment2) - .with_calculated_state_commitment() - .with_eth_l1_gas_price(GasPrice::from(2)) - .with_sequencer_address(sequencer_address_bytes!(&[2u8])) + .timestamp(BlockTimestamp::new_or_panic(2)) + .storage_commitment(storage_commitment2) + .class_commitment(class_commitment2) + .calculated_state_commitment() + .eth_l1_gas_price(GasPrice::from(2)) + .sequencer_address(sequencer_address_bytes!(&[2u8])) .finalize_with_hash(block_hash_bytes!(b"latest")); db_txn.insert_block_header(&header2).unwrap(); diff --git a/crates/rpc/src/method/call.rs b/crates/rpc/src/method/call.rs index d4c500c112..4ac2eb9803 100644 --- a/crates/rpc/src/method/call.rs +++ b/crates/rpc/src/method/call.rs @@ -250,8 +250,8 @@ mod tests { // Empty genesis block let header = BlockHeader::builder() - .with_number(BlockNumber::GENESIS) - .with_timestamp(BlockTimestamp::new_or_panic(0)) + .number(BlockNumber::GENESIS) + .timestamp(BlockTimestamp::new_or_panic(0)) .finalize_with_hash(BlockHash(felt!("0xb00"))); tx.insert_block_header(&header).unwrap(); @@ -263,9 +263,9 @@ mod tests { .unwrap(); let header = BlockHeader::builder() - .with_number(block1_number) - .with_timestamp(BlockTimestamp::new_or_panic(1)) - .with_eth_l1_gas_price(GasPrice(1)) + .number(block1_number) + .timestamp(BlockTimestamp::new_or_panic(1)) + .eth_l1_gas_price(GasPrice(1)) .finalize_with_hash(block1_hash); tx.insert_block_header(&header).unwrap(); @@ -478,7 +478,7 @@ mod tests { .unwrap(); let header = BlockHeader::builder() - .with_number(block_number) + .number(block_number) .finalize_with_hash(block_hash!("0xb02")); tx.insert_block_header(&header).unwrap(); diff --git a/crates/rpc/src/method/estimate_message_fee.rs b/crates/rpc/src/method/estimate_message_fee.rs index 72ecb1f1c3..6156fb6274 100644 --- a/crates/rpc/src/method/estimate_message_fee.rs +++ b/crates/rpc/src/method/estimate_message_fee.rs @@ -249,21 +249,21 @@ mod tests { if !matches!(mode, Setup::_SkipBlock) { let header = BlockHeader::builder() - .with_number(BlockNumber::GENESIS) - .with_timestamp(BlockTimestamp::new_or_panic(0)) - .with_l1_da_mode(pathfinder_common::L1DataAvailabilityMode::Blob) - .with_strk_l1_data_gas_price(GasPrice(0x10)) - .with_eth_l1_data_gas_price(GasPrice(0x12)) + .number(BlockNumber::GENESIS) + .timestamp(BlockTimestamp::new_or_panic(0)) + .l1_da_mode(pathfinder_common::L1DataAvailabilityMode::Blob) + .strk_l1_data_gas_price(GasPrice(0x10)) + .eth_l1_data_gas_price(GasPrice(0x12)) .finalize_with_hash(BlockHash(felt!("0xb00"))); tx.insert_block_header(&header).unwrap(); let header = BlockHeader::builder() - .with_number(block1_number) - .with_timestamp(BlockTimestamp::new_or_panic(1)) - .with_eth_l1_gas_price(GasPrice(2)) - .with_eth_l1_data_gas_price(GasPrice(1)) - .with_starknet_version(StarknetVersion::new(0, 13, 1, 0)) - .with_l1_da_mode(L1DataAvailabilityMode::Blob) + .number(block1_number) + .timestamp(BlockTimestamp::new_or_panic(1)) + .eth_l1_gas_price(GasPrice(2)) + .eth_l1_data_gas_price(GasPrice(1)) + .starknet_version(StarknetVersion::new(0, 13, 1, 0)) + .l1_da_mode(L1DataAvailabilityMode::Blob) .finalize_with_hash(block1_hash); tx.insert_block_header(&header).unwrap(); } diff --git a/crates/rpc/src/method/trace_block_transactions.rs b/crates/rpc/src/method/trace_block_transactions.rs index 059e667458..1faee27955 100644 --- a/crates/rpc/src/method/trace_block_transactions.rs +++ b/crates/rpc/src/method/trace_block_transactions.rs @@ -649,15 +649,15 @@ pub(crate) mod tests { )?; let next_block_header = BlockHeader::builder() - .with_number(last_block_header.number + 1) - .with_eth_l1_gas_price(GasPrice(1)) - .with_eth_l1_data_gas_price(GasPrice(2)) - .with_parent_hash(last_block_header.hash) - .with_starknet_version(last_block_header.starknet_version) - .with_sequencer_address(last_block_header.sequencer_address) - .with_timestamp(last_block_header.timestamp) - .with_starknet_version(StarknetVersion::new(0, 13, 1, 1)) - .with_l1_da_mode(L1DataAvailabilityMode::Blob) + .number(last_block_header.number + 1) + .eth_l1_gas_price(GasPrice(1)) + .eth_l1_data_gas_price(GasPrice(2)) + .parent_hash(last_block_header.hash) + .starknet_version(last_block_header.starknet_version) + .sequencer_address(last_block_header.sequencer_address) + .timestamp(last_block_header.timestamp) + .starknet_version(StarknetVersion::new(0, 13, 1, 1)) + .l1_da_mode(L1DataAvailabilityMode::Blob) .finalize_with_hash(block_hash!("0x1")); tx.insert_block_header(&next_block_header)?; diff --git a/crates/rpc/src/pending.rs b/crates/rpc/src/pending.rs index 0ec8a953f0..2c5ac75636 100644 --- a/crates/rpc/src/pending.rs +++ b/crates/rpc/src/pending.rs @@ -125,9 +125,9 @@ mod tests { .unwrap(); let latest = BlockHeader::builder() - .with_eth_l1_gas_price(GasPrice(1234)) - .with_strk_l1_gas_price(GasPrice(3377)) - .with_timestamp(BlockTimestamp::new_or_panic(6777)) + .eth_l1_gas_price(GasPrice(1234)) + .strk_l1_gas_price(GasPrice(3377)) + .timestamp(BlockTimestamp::new_or_panic(6777)) .finalize_with_hash(block_hash_bytes!(b"latest hash")); let tx = storage.transaction().unwrap(); @@ -174,18 +174,18 @@ mod tests { // Required otherwise latest doesn't have a valid parent hash in storage. let parent = BlockHeader::builder() - .with_number(BlockNumber::GENESIS + 12) + .number(BlockNumber::GENESIS + 12) .finalize_with_hash(block_hash_bytes!(b"parent hash")); let latest = parent .child_builder() - .with_eth_l1_gas_price(GasPrice(1234)) - .with_strk_l1_gas_price(GasPrice(3377)) - .with_eth_l1_data_gas_price(GasPrice(9999)) - .with_strk_l1_data_gas_price(GasPrice(8888)) - .with_l1_da_mode(L1DataAvailabilityMode::Blob) - .with_timestamp(BlockTimestamp::new_or_panic(6777)) - .with_sequencer_address(sequencer_address!("0xffff")) + .eth_l1_gas_price(GasPrice(1234)) + .strk_l1_gas_price(GasPrice(3377)) + .eth_l1_data_gas_price(GasPrice(9999)) + .strk_l1_data_gas_price(GasPrice(8888)) + .l1_da_mode(L1DataAvailabilityMode::Blob) + .timestamp(BlockTimestamp::new_or_panic(6777)) + .sequencer_address(sequencer_address!("0xffff")) .finalize_with_hash(block_hash_bytes!(b"latest hash")); let tx = storage.transaction().unwrap(); diff --git a/crates/rpc/src/test_setup.rs b/crates/rpc/src/test_setup.rs index 98ec08231b..3278e0a527 100644 --- a/crates/rpc/src/test_setup.rs +++ b/crates/rpc/src/test_setup.rs @@ -26,9 +26,9 @@ pub async fn test_storage StateUpdate>( // Empty genesis block let header = BlockHeader::builder() - .with_number(BlockNumber::GENESIS) - .with_timestamp(BlockTimestamp::new_or_panic(0)) - .with_starknet_version(version) + .number(BlockNumber::GENESIS) + .timestamp(BlockTimestamp::new_or_panic(0)) + .starknet_version(version) .finalize_with_hash(BlockHash(felt!("0xb00"))); tx.insert_block_header(&header).unwrap(); @@ -57,17 +57,17 @@ pub async fn test_storage StateUpdate>( .unwrap(); let header = BlockHeader::builder() - .with_number(block1_number) - .with_timestamp(BlockTimestamp::new_or_panic(1)) - .with_eth_l1_gas_price(GasPrice(1)) - .with_strk_l1_gas_price(GasPrice(2)) - .with_eth_l1_data_gas_price(GasPrice(2)) - .with_strk_l1_data_gas_price(GasPrice(2)) - .with_l1_da_mode(pathfinder_common::L1DataAvailabilityMode::Blob) - .with_sequencer_address(sequencer_address!( + .number(block1_number) + .timestamp(BlockTimestamp::new_or_panic(1)) + .eth_l1_gas_price(GasPrice(1)) + .strk_l1_gas_price(GasPrice(2)) + .eth_l1_data_gas_price(GasPrice(2)) + .strk_l1_data_gas_price(GasPrice(2)) + .l1_da_mode(pathfinder_common::L1DataAvailabilityMode::Blob) + .sequencer_address(sequencer_address!( "0x1176a1bd84444c89232ec27754698e5d2e7e1a7f1539f12027f28b23ec9f3d8" )) - .with_starknet_version(version) + .starknet_version(version) .finalize_with_hash(block1_hash); tx.insert_block_header(&header).unwrap(); diff --git a/crates/rpc/src/v06/method/call.rs b/crates/rpc/src/v06/method/call.rs index 731a749530..f0030b61b8 100644 --- a/crates/rpc/src/v06/method/call.rs +++ b/crates/rpc/src/v06/method/call.rs @@ -221,8 +221,8 @@ mod tests { // Empty genesis block let header = BlockHeader::builder() - .with_number(BlockNumber::GENESIS) - .with_timestamp(BlockTimestamp::new_or_panic(0)) + .number(BlockNumber::GENESIS) + .timestamp(BlockTimestamp::new_or_panic(0)) .finalize_with_hash(BlockHash(felt!("0xb00"))); tx.insert_block_header(&header).unwrap(); @@ -234,9 +234,9 @@ mod tests { .unwrap(); let header = BlockHeader::builder() - .with_number(block1_number) - .with_timestamp(BlockTimestamp::new_or_panic(1)) - .with_eth_l1_gas_price(GasPrice(1)) + .number(block1_number) + .timestamp(BlockTimestamp::new_or_panic(1)) + .eth_l1_gas_price(GasPrice(1)) .finalize_with_hash(block1_hash); tx.insert_block_header(&header).unwrap(); @@ -451,7 +451,7 @@ mod tests { .unwrap(); let header = BlockHeader::builder() - .with_number(block_number) + .number(block_number) .finalize_with_hash(block_hash!("0xb02")); tx.insert_block_header(&header).unwrap(); diff --git a/crates/rpc/src/v06/method/estimate_message_fee.rs b/crates/rpc/src/v06/method/estimate_message_fee.rs index f21d84201f..eab9dd352c 100644 --- a/crates/rpc/src/v06/method/estimate_message_fee.rs +++ b/crates/rpc/src/v06/method/estimate_message_fee.rs @@ -341,15 +341,15 @@ mod tests { if !matches!(mode, Setup::SkipBlock) { let header = BlockHeader::builder() - .with_number(BlockNumber::GENESIS) - .with_timestamp(BlockTimestamp::new_or_panic(0)) + .number(BlockNumber::GENESIS) + .timestamp(BlockTimestamp::new_or_panic(0)) .finalize_with_hash(BlockHash(felt!("0xb00"))); tx.insert_block_header(&header).unwrap(); let header = BlockHeader::builder() - .with_number(block1_number) - .with_timestamp(BlockTimestamp::new_or_panic(1)) - .with_eth_l1_gas_price(GasPrice(1)) + .number(block1_number) + .timestamp(BlockTimestamp::new_or_panic(1)) + .eth_l1_gas_price(GasPrice(1)) .finalize_with_hash(block1_hash); tx.insert_block_header(&header).unwrap(); } diff --git a/crates/rpc/src/v06/method/trace_block_transactions.rs b/crates/rpc/src/v06/method/trace_block_transactions.rs index a7870e6454..9df725e43c 100644 --- a/crates/rpc/src/v06/method/trace_block_transactions.rs +++ b/crates/rpc/src/v06/method/trace_block_transactions.rs @@ -400,13 +400,13 @@ pub(crate) mod tests { )?; let next_block_header = BlockHeader::builder() - .with_number(last_block_header.number + 1) - .with_eth_l1_gas_price(GasPrice(1)) - .with_eth_l1_data_gas_price(GasPrice(2)) - .with_parent_hash(last_block_header.hash) - .with_starknet_version(last_block_header.starknet_version) - .with_sequencer_address(last_block_header.sequencer_address) - .with_timestamp(last_block_header.timestamp) + .number(last_block_header.number + 1) + .eth_l1_gas_price(GasPrice(1)) + .eth_l1_data_gas_price(GasPrice(2)) + .parent_hash(last_block_header.hash) + .starknet_version(last_block_header.starknet_version) + .sequencer_address(last_block_header.sequencer_address) + .timestamp(last_block_header.timestamp) .finalize_with_hash(block_hash!("0x1")); tx.insert_block_header(&next_block_header)?; diff --git a/crates/storage/src/connection/block.rs b/crates/storage/src/connection/block.rs index 7723826c14..d3c15b60f4 100644 --- a/crates/storage/src/connection/block.rs +++ b/crates/storage/src/connection/block.rs @@ -683,32 +683,32 @@ mod tests { }; let header1 = genesis .child_builder() - .with_timestamp(BlockTimestamp::new_or_panic(12)) - .with_eth_l1_gas_price(GasPrice(34)) - .with_strk_l1_gas_price(GasPrice(35)) - .with_sequencer_address(sequencer_address_bytes!(b"sequencer address 1")) - .with_event_commitment(event_commitment_bytes!(b"event commitment 1")) - .with_class_commitment(class_commitment_bytes!(b"class commitment 1")) - .with_storage_commitment(storage_commitment_bytes!(b"storage commitment 1")) - .with_calculated_state_commitment() - .with_transaction_commitment(transaction_commitment_bytes!(b"tx commitment 1")) - .with_l1_da_mode(L1DataAvailabilityMode::Calldata) - .with_receipt_commitment(receipt_commitment_bytes!(b"block 1 receipt commitment")) + .timestamp(BlockTimestamp::new_or_panic(12)) + .eth_l1_gas_price(GasPrice(34)) + .strk_l1_gas_price(GasPrice(35)) + .sequencer_address(sequencer_address_bytes!(b"sequencer address 1")) + .event_commitment(event_commitment_bytes!(b"event commitment 1")) + .class_commitment(class_commitment_bytes!(b"class commitment 1")) + .storage_commitment(storage_commitment_bytes!(b"storage commitment 1")) + .calculated_state_commitment() + .transaction_commitment(transaction_commitment_bytes!(b"tx commitment 1")) + .l1_da_mode(L1DataAvailabilityMode::Calldata) + .receipt_commitment(receipt_commitment_bytes!(b"block 1 receipt commitment")) .finalize_with_hash(block_hash_bytes!(b"block 1 hash")); let header2 = header1 .child_builder() - .with_eth_l1_gas_price(GasPrice(38)) - .with_strk_l1_gas_price(GasPrice(39)) - .with_timestamp(BlockTimestamp::new_or_panic(15)) - .with_sequencer_address(sequencer_address_bytes!(b"sequencer address 2")) - .with_event_commitment(event_commitment_bytes!(b"event commitment 2")) - .with_class_commitment(class_commitment_bytes!(b"class commitment 2")) - .with_storage_commitment(storage_commitment_bytes!(b"storage commitment 2")) - .with_calculated_state_commitment() - .with_transaction_commitment(transaction_commitment_bytes!(b"tx commitment 2")) - .with_l1_da_mode(L1DataAvailabilityMode::Blob) - .with_receipt_commitment(receipt_commitment_bytes!(b"block 2 receipt commitment")) + .eth_l1_gas_price(GasPrice(38)) + .strk_l1_gas_price(GasPrice(39)) + .timestamp(BlockTimestamp::new_or_panic(15)) + .sequencer_address(sequencer_address_bytes!(b"sequencer address 2")) + .event_commitment(event_commitment_bytes!(b"event commitment 2")) + .class_commitment(class_commitment_bytes!(b"class commitment 2")) + .storage_commitment(storage_commitment_bytes!(b"storage commitment 2")) + .calculated_state_commitment() + .transaction_commitment(transaction_commitment_bytes!(b"tx commitment 2")) + .l1_da_mode(L1DataAvailabilityMode::Blob) + .receipt_commitment(receipt_commitment_bytes!(b"block 2 receipt commitment")) .finalize_with_hash(block_hash_bytes!(b"block 2 hash")); let headers = vec![genesis, header1, header2]; diff --git a/crates/storage/src/connection/event.rs b/crates/storage/src/connection/event.rs index 2fe0c31f0c..ba528dd5fa 100644 --- a/crates/storage/src/connection/event.rs +++ b/crates/storage/src/connection/event.rs @@ -445,9 +445,9 @@ mod tests { .collect::>(); let header = BlockHeader::builder() - .with_sequencer_address(sequencer_address!("0x1234")) - .with_timestamp(BlockTimestamp::new_or_panic(0)) - .with_state_commitment(state_commitment!("0x1234")) + .sequencer_address(sequencer_address!("0x1234")) + .timestamp(BlockTimestamp::new_or_panic(0)) + .state_commitment(state_commitment!("0x1234")) .finalize_with_hash(block_hash!("0x1234")); // Note: hashes are reverse ordered to trigger the sorting bug. diff --git a/crates/storage/src/connection/signature.rs b/crates/storage/src/connection/signature.rs index a6280192a5..ade76abfdd 100644 --- a/crates/storage/src/connection/signature.rs +++ b/crates/storage/src/connection/signature.rs @@ -79,7 +79,7 @@ mod tests { let tx = connection.transaction().unwrap(); let genesis = BlockHeader::builder() - .with_number(BlockNumber::new_or_panic(0)) + .number(BlockNumber::new_or_panic(0)) .finalize_with_hash(block_hash_bytes!(b"genesis")); let genesis_signature = BlockCommitmentSignature { r: block_commitment_signature_elem_bytes!(b"genesis r"), diff --git a/crates/storage/src/test_utils.rs b/crates/storage/src/test_utils.rs index 6c2308314c..3bc59b832a 100644 --- a/crates/storage/src/test_utils.rs +++ b/crates/storage/src/test_utils.rs @@ -35,15 +35,15 @@ pub(crate) fn create_blocks() -> [BlockHeader; NUM_BLOCKS] { let index_as_felt = Felt::from_be_slice(&[i as u8]).unwrap(); BlockHeader::builder() - .with_number(BlockNumber::GENESIS + i as u64) - .with_timestamp(BlockTimestamp::new_or_panic(i as u64 + 500)) - .with_class_commitment(class_commitment) - .with_storage_commitment(storage_commitment) - .with_calculated_state_commitment() - .with_eth_l1_gas_price(GasPrice::from(i as u64)) - .with_sequencer_address(SequencerAddress(index_as_felt)) - .with_transaction_commitment(TransactionCommitment(index_as_felt)) - .with_event_commitment(EventCommitment(index_as_felt)) + .number(BlockNumber::GENESIS + i as u64) + .timestamp(BlockTimestamp::new_or_panic(i as u64 + 500)) + .class_commitment(class_commitment) + .storage_commitment(storage_commitment) + .calculated_state_commitment() + .eth_l1_gas_price(GasPrice::from(i as u64)) + .sequencer_address(SequencerAddress(index_as_felt)) + .transaction_commitment(TransactionCommitment(index_as_felt)) + .event_commitment(EventCommitment(index_as_felt)) .finalize_with_hash(BlockHash(Felt::from_hex_str(&"a".repeat(i + 3)).unwrap())) }) .collect::>() From bbeb75ef884b68b4a486a6129e326b63b5f4dfcd Mon Sep 17 00:00:00 2001 From: Krzysztof Lis Date: Wed, 4 Sep 2024 17:17:10 +0200 Subject: [PATCH 014/282] refactor(gateway-client/builder): remove the with_ prefix --- crates/gateway-client/src/builder.rs | 70 ++++++++++++++-------------- crates/gateway-client/src/lib.rs | 58 +++++++++++------------ 2 files changed, 65 insertions(+), 63 deletions(-) diff --git a/crates/gateway-client/src/builder.rs b/crates/gateway-client/src/builder.rs index 955671736a..07faae44d6 100644 --- a/crates/gateway-client/src/builder.rs +++ b/crates/gateway-client/src/builder.rs @@ -44,14 +44,16 @@ pub mod stage { pub struct Method; /// Specify the request parameters: - /// - [at_block](super::Request::with_block) - /// - [with_class_hash](super::Request::with_class_hash) - /// - [with_optional_token](super::Request::with_optional_token) - /// - [with_transaction_hash](super::Request::with_transaction_hash) - /// - [add_param](super::Request::add_param) (allows adding custom (name, - /// value) parameter) + /// - [block](super::Request::block) + /// - [class_hash](super::Request::class_hash) + /// - [optional_token](super::Request::optional_token) + /// - [transaction_hash](super::Request::transaction_hash) + /// - [param](super::Request::param) (allows adding custom (name, value) + /// parameter) + /// - [block_tag](super::Request::block_tag) (allows specifying the block + /// tag, either `latest` or `pending`) /// - /// and then specify the [retry behavior](super::Request::with_retry). + /// and then specify the [retry behavior](super::Request::retry). pub struct Params { pub meta: RequestMetadata, } @@ -101,7 +103,7 @@ mod request_macros { /// Generates methods with names from the list. /// - /// Each generated method delegates the call to `with_method`. + /// Each generated method delegates the call to `method`. macro_rules! method_defs { ($($x:ident),+ $(,)?) => { $(request_macros::method!($x);)+ @@ -110,11 +112,11 @@ mod request_macros { /// Generates one method with `name`. /// - /// The generated method delegates the call to `with_method`. + /// The generated method delegates the call to `method`. macro_rules! method { ($name:ident) => { pub fn $name(self) -> Request<'a, stage::Params> { - self.with_method(stringify!($name)) + self.method(stringify!($name)) } }; } @@ -150,7 +152,7 @@ impl<'a> Request<'a, stage::Method> { ); /// Appends the given method to the request url. - fn with_method(mut self, method: &'static str) -> Request<'a, stage::Params> { + fn method(mut self, method: &'static str) -> Request<'a, stage::Params> { self.url .path_segments_mut() .expect("Base URL is valid") @@ -168,7 +170,7 @@ impl<'a> Request<'a, stage::Method> { } impl<'a> Request<'a, stage::Params> { - pub fn with_block>(self, block: B) -> Self { + pub fn block>(self, block: B) -> Self { use std::borrow::Cow; let block: BlockId = block.into(); @@ -184,36 +186,36 @@ impl<'a> Request<'a, stage::Params> { BlockId::Pending => ("blockNumber", Cow::from("pending"), BlockTag::Pending), }; - self.update_tag(tag).add_param(name, &value) + self.block_tag(tag).param(name, &value) } - pub fn with_class_hash(self, class_hash: ClassHash) -> Self { - self.add_param("classHash", &class_hash.0.to_hex_str()) + pub fn class_hash(self, class_hash: ClassHash) -> Self { + self.param("classHash", &class_hash.0.to_hex_str()) } - pub fn with_optional_token(self, token: Option<&str>) -> Self { + pub fn optional_token(self, token: Option<&str>) -> Self { match token { - Some(token) => self.add_param("token", token), + Some(token) => self.param("token", token), None => self, } } - pub fn with_transaction_hash(self, hash: TransactionHash) -> Self { - self.add_param("transactionHash", &hash.0.to_hex_str()) + pub fn transaction_hash(self, hash: TransactionHash) -> Self { + self.param("transactionHash", &hash.0.to_hex_str()) } - pub fn add_param(mut self, name: &str, value: &str) -> Self { + pub fn param(mut self, name: &str, value: &str) -> Self { self.url.query_pairs_mut().append_pair(name, value); self } - pub fn update_tag(mut self, tag: BlockTag) -> Self { + pub fn block_tag(mut self, tag: BlockTag) -> Self { self.state.meta.tag = tag; self } /// Sets the request retry behavior. - pub fn with_retry(self, retry: bool) -> Request<'a, stage::Final> { + pub fn retry(self, retry: bool) -> Request<'a, stage::Final> { Request { url: self.url, client: self.client, @@ -717,16 +719,16 @@ mod tests { let _: serde_json::Value = client .clone() .gateway_request() - .with_method("") - .with_retry(false) + .method("") + .retry(false) .get() .await?; let _: serde_json::Value = client .clone() .feeder_gateway_request() - .with_method("") - .with_retry(false) + .method("") + .retry(false) .get() .await?; @@ -743,16 +745,16 @@ mod tests { let _: bytes::Bytes = client .clone() .gateway_request() - .with_method("") - .with_retry(false) + .method("") + .retry(false) .get_as_bytes() .await?; let _: bytes::Bytes = client .clone() .feeder_gateway_request() - .with_method("") - .with_retry(false) + .method("") + .retry(false) .get_as_bytes() .await?; @@ -769,16 +771,16 @@ mod tests { let _: serde_json::Value = client .clone() .gateway_request() - .with_method("") - .with_retry(false) + .method("") + .retry(false) .post_with_json(&json!({}), None) .await?; let _: serde_json::Value = client .clone() .feeder_gateway_request() - .with_method("") - .with_retry(false) + .method("") + .retry(false) .post_with_json(&json!({}), None) .await?; diff --git a/crates/gateway-client/src/lib.rs b/crates/gateway-client/src/lib.rs index db5f893bc6..70d9d47f84 100644 --- a/crates/gateway-client/src/lib.rs +++ b/crates/gateway-client/src/lib.rs @@ -325,9 +325,9 @@ impl GatewayApi for Client { let result: Dto = self .feeder_gateway_request() .get_state_update() - .with_block(BlockId::Pending) - .add_param("includeBlock", "true") - .with_retry(self.retry) + .block(BlockId::Pending) + .param("includeBlock", "true") + .retry(self.retry) .get() .await?; @@ -348,9 +348,9 @@ impl GatewayApi for Client { let header: BlockHeader = self .feeder_gateway_request() .get_block() - .with_block(block) - .add_param("headerOnly", "true") - .with_retry(self.retry) + .block(block) + .param("headerOnly", "true") + .retry(self.retry) .get() .await?; @@ -365,9 +365,9 @@ impl GatewayApi for Client { ) -> Result { self.feeder_gateway_request() .get_class_by_hash() - .with_class_hash(class_hash) - .with_block(BlockId::Pending) - .with_retry(self.retry) + .class_hash(class_hash) + .block(BlockId::Pending) + .retry(self.retry) .get_as_bytes() .await } @@ -380,9 +380,9 @@ impl GatewayApi for Client { ) -> Result { self.feeder_gateway_request() .get_compiled_class_by_class_hash() - .with_class_hash(class_hash) - .with_block(BlockId::Pending) - .with_retry(self.retry) + .class_hash(class_hash) + .block(BlockId::Pending) + .retry(self.retry) .get_as_bytes() .await } @@ -395,8 +395,8 @@ impl GatewayApi for Client { ) -> Result { self.feeder_gateway_request() .get_transaction() - .with_transaction_hash(transaction_hash) - .with_retry(self.retry) + .transaction_hash(transaction_hash) + .retry(self.retry) .get() .await } @@ -422,9 +422,9 @@ impl GatewayApi for Client { let result: Dto = self .feeder_gateway_request() .get_state_update() - .with_block(block) - .add_param("includeBlock", "true") - .with_retry(self.retry) + .block(block) + .param("includeBlock", "true") + .retry(self.retry) .get() .await?; Ok((result.block, result.state_update.into())) @@ -435,7 +435,7 @@ impl GatewayApi for Client { async fn eth_contract_addresses(&self) -> Result { self.feeder_gateway_request() .get_contract_addresses() - .with_retry(self.retry) + .retry(self.retry) .get() .await } @@ -452,7 +452,7 @@ impl GatewayApi for Client { // client instead. self.gateway_request() .add_transaction() - .with_retry(false) + .retry(false) .post_with_json( &request::add_transaction::AddTransaction::Invoke(invoke), Some(Duration::MAX), @@ -474,8 +474,8 @@ impl GatewayApi for Client { self.gateway_request() .add_transaction() // mainnet requires a token (but testnet does not so its optional). - .with_optional_token(token.as_deref()) - .with_retry(false) + .optional_token(token.as_deref()) + .retry(false) .post_with_json( &request::add_transaction::AddTransaction::Declare(declare), Some(Duration::MAX), @@ -494,7 +494,7 @@ impl GatewayApi for Client { // client instead. self.gateway_request() .add_transaction() - .with_retry(false) + .retry(false) .post_with_json( &request::add_transaction::AddTransaction::DeployAccount(deploy), Some(Duration::MAX), @@ -506,8 +506,8 @@ impl GatewayApi for Client { async fn block_traces(&self, block: BlockId) -> Result { self.feeder_gateway_request() .get_block_traces() - .with_block(block) - .with_retry(self.retry) + .block(block) + .retry(self.retry) .get() .await } @@ -519,8 +519,8 @@ impl GatewayApi for Client { ) -> Result { self.feeder_gateway_request() .get_transaction_trace() - .with_transaction_hash(transaction) - .with_retry(self.retry) + .transaction_hash(transaction) + .retry(self.retry) .get() .await } @@ -529,8 +529,8 @@ impl GatewayApi for Client { async fn signature(&self, block: BlockId) -> Result { self.feeder_gateway_request() .get_signature() - .with_block(block) - .with_retry(self.retry) + .block(block) + .retry(self.retry) .get() .await } @@ -539,7 +539,7 @@ impl GatewayApi for Client { async fn public_key(&self) -> Result { self.feeder_gateway_request() .get_public_key() - .with_retry(self.retry) + .retry(self.retry) .get() .await } From 41bf4818b78911e4ff17a19afb813f9b36e90980 Mon Sep 17 00:00:00 2001 From: Krzysztof Lis Date: Thu, 5 Sep 2024 18:27:23 +0200 Subject: [PATCH 015/282] chore: clippy --- crates/common/src/lib.rs | 2 +- crates/executor/src/simulate.rs | 5 ++--- crates/gateway-client/src/metrics.rs | 4 +--- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/crates/common/src/lib.rs b/crates/common/src/lib.rs index c6c64d8137..300427ae07 100644 --- a/crates/common/src/lib.rs +++ b/crates/common/src/lib.rs @@ -424,7 +424,7 @@ impl ChainId { pub fn as_str(&self) -> &str { std::str::from_utf8(self.0.as_be_bytes()) .expect("valid utf8") - .trim_start_matches(|c| c == '\0') + .trim_start_matches('\0') } pub const MAINNET: Self = Self::from_slice_unwrap(b"SN_MAIN"); diff --git a/crates/executor/src/simulate.rs b/crates/executor/src/simulate.rs index c87e8aac08..4cec1283d2 100644 --- a/crates/executor/src/simulate.rs +++ b/crates/executor/src/simulate.rs @@ -198,12 +198,11 @@ pub fn trace( cache.cache_set(block_hash, CacheItem::CachedErr(err.clone())); err })?; - let state_diff = - to_state_diff(&mut tx_state, tx_declared_deprecated_class_hash).map_err(|e| { + let state_diff = to_state_diff(&mut tx_state, tx_declared_deprecated_class_hash) + .inspect_err(|_| { // Remove the cache entry so it's no longer inflight. let mut cache = cache.0.lock().unwrap(); cache.cache_remove(&block_hash); - e })?; tx_state.commit(); diff --git a/crates/gateway-client/src/metrics.rs b/crates/gateway-client/src/metrics.rs index 98b71a4eb7..58c3a633da 100644 --- a/crates/gateway-client/src/metrics.rs +++ b/crates/gateway-client/src/metrics.rs @@ -166,7 +166,7 @@ pub async fn with_metrics( metrics::histogram!(METRIC_REQUESTS_LATENCY, elapsed, "method" => meta.method); - result.map_err(|e| { + result.inspect_err(|e| { increment(METRIC_FAILED_REQUESTS, meta); match &e { @@ -191,7 +191,5 @@ pub async fn with_metrics( } SequencerError::ReqwestError(_) => {} } - - e }) } From a337ebf61c3d0c6a731ca5ff44b553348372964f Mon Sep 17 00:00:00 2001 From: Krzysztof Lis Date: Fri, 6 Sep 2024 11:20:19 +0200 Subject: [PATCH 016/282] fix(p2p): initial 60s delay in get_random_peers --- crates/p2p/src/client/peer_agnostic.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/crates/p2p/src/client/peer_agnostic.rs b/crates/p2p/src/client/peer_agnostic.rs index 77eaf283a2..6b8d0aa226 100644 --- a/crates/p2p/src/client/peer_agnostic.rs +++ b/crates/p2p/src/client/peer_agnostic.rs @@ -1489,10 +1489,14 @@ struct Decaying { } impl Decaying { + const DEFAULT_TIMEOUT: Duration = Duration::from_secs(60); + pub fn new(timeout: Duration) -> Self { Self { data: Default::default(), - last_update: Instant::now(), + last_update: Instant::now() + .checked_sub(Self::DEFAULT_TIMEOUT * 2) + .expect("Still valid Instant"), timeout, } } @@ -1515,6 +1519,6 @@ impl Decaying { impl Default for Decaying { fn default() -> Self { - Self::new(Duration::from_secs(60)) + Self::new(Self::DEFAULT_TIMEOUT) } } From 1630c84ecc32436b49789c872e07f7d6e3954fa6 Mon Sep 17 00:00:00 2001 From: Krzysztof Lis Date: Fri, 6 Sep 2024 11:48:12 +0200 Subject: [PATCH 017/282] fix(p2p): retry until we get at least 1 peer --- crates/p2p/src/client/peer_agnostic.rs | 35 ++++++++++++++++++++------ 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/crates/p2p/src/client/peer_agnostic.rs b/crates/p2p/src/client/peer_agnostic.rs index 6b8d0aa226..928bcf76e7 100644 --- a/crates/p2p/src/client/peer_agnostic.rs +++ b/crates/p2p/src/client/peer_agnostic.rs @@ -116,14 +116,32 @@ impl Client { return peers.iter().copied().collect::>(); } - let mut peers = self - .inner - .get_closest_peers(PeerId::random()) - .await - .unwrap_or_default(); - - // We could be on the list - peers.remove(self.inner.peer_id()); + // TODO known peers abstraction should not poll + // + // Loop until we find at least a single peer. + // 1. After the process is spawned the first outgoing query may start earlier + // than the `kad` protocol is pushed in from `identify/push` resulting in a + // `kind: ConnectionRefused, error: "protocol not supported"` error. + // 2. Initially there may be no other peers but maybe we're running a local test + // and the other peer pops up in a few seconds. + // Either way we don't want to wait for the bootstrap timeout or the + // `Decaying::DEFAULT_TIMEOUT`, whichever kicks in first. + let peers = loop { + let mut peers = self + .inner + .get_closest_peers(PeerId::random()) + .await + .unwrap_or_default(); + // We could be on the list + peers.remove(self.inner.peer_id()); + + if peers.is_empty() { + tracing::info!("No peers found in DHT, retrying"); + tokio::time::sleep(Duration::from_secs(3)).await; + } else { + break peers; + } + }; let peers_vec = peers.iter().copied().collect::>(); @@ -131,6 +149,7 @@ impl Client { peers_vec }; peers.shuffle(&mut rand::thread_rng()); + peers } } From 072d7279a60630f0e04c0fc67680d4cd4cbf02a0 Mon Sep 17 00:00:00 2001 From: sistemd Date: Fri, 6 Sep 2024 14:07:20 +0200 Subject: [PATCH 018/282] should have rebased --- crates/rpc/src/method/estimate_fee.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/rpc/src/method/estimate_fee.rs b/crates/rpc/src/method/estimate_fee.rs index 4e659cf92f..02f3d53e2e 100644 --- a/crates/rpc/src/method/estimate_fee.rs +++ b/crates/rpc/src/method/estimate_fee.rs @@ -540,7 +540,7 @@ mod tests { invoke_v0_transaction, invoke_v3_transaction, ], - simulation_flags: SimulationFlags(vec![]), + simulation_flags: vec![], block_id: BlockId::Number(last_block_header.number), }; let result = super::estimate_fee(context, input).await.unwrap(); @@ -625,7 +625,7 @@ mod tests { invoke_v0_transaction, invoke_v3_transaction, ], - simulation_flags: SimulationFlags(vec![]), + simulation_flags: vec![], block_id: BlockId::Number(last_block_header.number), }; let result = super::estimate_fee(context, input).await.unwrap(); From 077493f89c7969a7a348b380af561d5cebb2f393 Mon Sep 17 00:00:00 2001 From: Krisztian Kovacs Date: Fri, 6 Sep 2024 09:26:48 +0200 Subject: [PATCH 019/282] chore(pathfinder/sync): log queue stats on trace level --- crates/pathfinder/src/sync/stream.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/crates/pathfinder/src/sync/stream.rs b/crates/pathfinder/src/sync/stream.rs index 6fa66faec9..e81a6951a3 100644 --- a/crates/pathfinder/src/sync/stream.rs +++ b/crates/pathfinder/src/sync/stream.rs @@ -161,10 +161,8 @@ impl SyncReceiver { let elements_per_sec = count as f32 / t.elapsed().as_secs_f32(); let queue_fullness = queue_capacity - self.inner.capacity(); let input_queue = Fullness(queue_fullness, queue_capacity); - tracing::debug!( - "Stage: {}, queue: {}, {elements_per_sec:.0} items/s", - S::NAME, - input_queue + tracing::trace!(stage=%S::NAME, %input_queue, %elements_per_sec, + "Stage metrics" ); output From 711f223e931065466eb0b18c580e8e275519561c Mon Sep 17 00:00:00 2001 From: Krisztian Kovacs Date: Fri, 6 Sep 2024 09:27:07 +0200 Subject: [PATCH 020/282] chore(pathfinder/sync): fix transactions chunk log line --- crates/pathfinder/src/sync/checkpoint.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/crates/pathfinder/src/sync/checkpoint.rs b/crates/pathfinder/src/sync/checkpoint.rs index 676a3a96dc..d7fb84335b 100644 --- a/crates/pathfinder/src/sync/checkpoint.rs +++ b/crates/pathfinder/src/sync/checkpoint.rs @@ -327,10 +327,7 @@ async fn handle_transaction_stream( .pipe(transactions::VerifyCommitment, 10) .pipe(transactions::Store::new(storage.connection()?, start), 10) .into_stream() - .inspect_ok(|x| { - tracing::info!(tail=%x.data, "Transactions chunk - synced") - }) + .inspect_ok(|x| tracing::info!(tail=%x.data, "Transactions chunk synced")) .try_fold((), |_, _| std::future::ready(Ok(()))) .await .map_err(SyncError::from_v2)?; From 6fcf2886f116e2c8570f0f775e412c2fc12d11a2 Mon Sep 17 00:00:00 2001 From: Krisztian Kovacs Date: Fri, 6 Sep 2024 10:53:51 +0200 Subject: [PATCH 021/282] chore(p2p/main_loop): log all handled events --- crates/p2p/src/main_loop.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/p2p/src/main_loop.rs b/crates/p2p/src/main_loop.rs index 0fa4e522be..772c970145 100644 --- a/crates/p2p/src/main_loop.rs +++ b/crates/p2p/src/main_loop.rs @@ -203,6 +203,8 @@ impl MainLoop { } async fn handle_event(&mut self, event: SwarmEvent) { + tracing::trace!(?event, "Handling swarm event"); + match event { // =========================== // Connection management From 0084bfc794896b50a5b34c4f602334a257bdff04 Mon Sep 17 00:00:00 2001 From: Krisztian Kovacs Date: Tue, 10 Sep 2024 14:34:24 +0200 Subject: [PATCH 022/282] fix(storage): add missing index for block signatures When serving block header sync requests over P2P we're looking up these signatures via their block number. Without this index this SELECT results in a full table scan, making performance really bad. --- crates/storage/src/schema.rs | 2 ++ crates/storage/src/schema/revision_0063.rs | 12 ++++++++++++ 2 files changed, 14 insertions(+) create mode 100644 crates/storage/src/schema/revision_0063.rs diff --git a/crates/storage/src/schema.rs b/crates/storage/src/schema.rs index 9917f7d177..3542ab73b2 100644 --- a/crates/storage/src/schema.rs +++ b/crates/storage/src/schema.rs @@ -22,6 +22,7 @@ mod revision_0059; mod revision_0060; mod revision_0061; mod revision_0062; +mod revision_0063; pub(crate) use base::base_schema; @@ -52,6 +53,7 @@ pub fn migrations() -> &'static [MigrationFn] { revision_0060::migrate, revision_0061::migrate, revision_0062::migrate, + revision_0063::migrate, ] } diff --git a/crates/storage/src/schema/revision_0063.rs b/crates/storage/src/schema/revision_0063.rs new file mode 100644 index 0000000000..c2e5dcc966 --- /dev/null +++ b/crates/storage/src/schema/revision_0063.rs @@ -0,0 +1,12 @@ +use anyhow::Context; + +pub(crate) fn migrate(tx: &rusqlite::Transaction<'_>) -> anyhow::Result<()> { + tracing::info!("Adding index for block signatures"); + + tx.execute_batch( + "CREATE UNIQUE INDEX block_signatures_block_number ON block_signatures(block_number);", + ) + .context("Adding block_signatures(block_number) index")?; + + Ok(()) +} From ec08a6cde4f4d7f1b24724754305c9bac5615f25 Mon Sep 17 00:00:00 2001 From: Krisztian Kovacs Date: Tue, 10 Sep 2024 14:36:59 +0200 Subject: [PATCH 023/282] chore(rpc/fixtures): migrade mainnet DB fixture --- crates/rpc/fixtures/mainnet.sqlite | Bin 454656 -> 454656 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/crates/rpc/fixtures/mainnet.sqlite b/crates/rpc/fixtures/mainnet.sqlite index 5a5c55ab4ccb97dabf20e67a133032dd33b07c95..1769b73e3b45c706396306085e98f24cd19f287b 100644 GIT binary patch delta 246 zcmZp8Al>jldV;iyI|BnlJ`e{0u>ue`0Wk{@!vG_YG5z2KW(8Jv2ED3+#+23+#?}<3 z))eNgDJ+ZMGs?Gfd|=tm@qzV^KBM4tRs}X?CYEK}wG`O4FflQn+WtU^?HHqNBd;gB zxVtW6qj6?lN@_(?PJVKBd~s%aUSdgUQEG8KL?Ev;Hz~CUU6ObD!$w9CdvswGJqrGQ S3g`+nkPX$`Zl=n1>J$L$7*A#Z delta 120 zcmZp8Al>jldV;j73j+f~J`e{0u_6#R0x=5^!vG_YVK-S&L7vrxK`&maF{L$yu{DLM zHHCR=3d`d6j0)`>A6T|?d|>^f&nPgRRe?>JiGg9emIB)rCMKq+?GKdLjxkQZ-p4p$ KyO}E6sZ#)}6&)-9 From bd69d71bd9fce1416ba5492741a0048b08ca4a10 Mon Sep 17 00:00:00 2001 From: t00ts Date: Wed, 4 Sep 2024 13:12:44 +0200 Subject: [PATCH 024/282] refactor(ethereum): new subscription-based client using alloy --- Cargo.lock | 1305 ++++++++++++++++- crates/common/src/lib.rs | 2 + crates/common/src/message.rs | 12 + crates/crypto/src/algebra/field/felt.rs | 6 + crates/ethereum/Cargo.toml | 12 +- .../ethereum/abi/starknet_core_contract.json | 952 ++++++++++++ crates/ethereum/src/lib.rs | 476 +++--- crates/ethereum/src/starknet.rs | 29 + crates/ethereum/src/utils.rs | 21 + crates/pathfinder/src/state/sync.rs | 4 + crates/pathfinder/src/state/sync/l1.rs | 47 +- 11 files changed, 2483 insertions(+), 383 deletions(-) create mode 100644 crates/common/src/message.rs create mode 100644 crates/ethereum/abi/starknet_core_contract.json create mode 100644 crates/ethereum/src/starknet.rs create mode 100644 crates/ethereum/src/utils.rs diff --git a/Cargo.lock b/Cargo.lock index 7d3c71c2b1..5c698d8644 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -101,6 +101,514 @@ version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" +[[package]] +name = "alloy" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f13f1940c81e269e84ddb58f3b611be9660fbbfe39d4338aa2984dc3df0c402" +dependencies = [ + "alloy-consensus", + "alloy-contract", + "alloy-core", + "alloy-eips", + "alloy-genesis", + "alloy-network", + "alloy-provider", + "alloy-pubsub", + "alloy-rpc-client", + "alloy-rpc-types", + "alloy-serde", + "alloy-transport", + "alloy-transport-http", + "alloy-transport-ws", +] + +[[package]] +name = "alloy-chains" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb07629a5d0645d29f68d2fb6f4d0cf15c89ec0965be915f303967180929743f" +dependencies = [ + "num_enum", + "strum 0.26.3", +] + +[[package]] +name = "alloy-consensus" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4177d135789e282e925092be8939d421b701c6d92c0a16679faa659d9166289d" +dependencies = [ + "alloy-eips", + "alloy-primitives", + "alloy-rlp", + "alloy-serde", + "c-kzg", + "serde", +] + +[[package]] +name = "alloy-contract" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3be15f92fdb7490b164697a1d9b395cb7a3afa8fb15feed732ec5a6ff8db5f4" +dependencies = [ + "alloy-dyn-abi", + "alloy-json-abi", + "alloy-network", + "alloy-network-primitives", + "alloy-primitives", + "alloy-provider", + "alloy-pubsub", + "alloy-rpc-types-eth", + "alloy-sol-types", + "alloy-transport", + "futures", + "futures-util", + "thiserror", +] + +[[package]] +name = "alloy-core" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e6dbb79f4e3285cc87f50c0d4be9a3a812643623b2e3558d425b41cbd795ceb" +dependencies = [ + "alloy-dyn-abi", + "alloy-json-abi", + "alloy-primitives", + "alloy-sol-types", +] + +[[package]] +name = "alloy-dyn-abi" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5b68572f5dfa99ede0a491d658c9842626c956b840d0b97d0bbc9637742504" +dependencies = [ + "alloy-json-abi", + "alloy-primitives", + "alloy-sol-type-parser", + "alloy-sol-types", + "const-hex", + "itoa", + "serde", + "serde_json", + "winnow 0.6.16", +] + +[[package]] +name = "alloy-eip2930" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0069cf0642457f87a01a014f6dc29d5d893cd4fd8fddf0c3cdfad1bb3ebafc41" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "serde", +] + +[[package]] +name = "alloy-eip7702" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37d319bb544ca6caeab58c39cea8921c55d924d4f68f2c60f24f914673f9a74a" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "serde", +] + +[[package]] +name = "alloy-eips" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "499ee14d296a133d142efd215eb36bf96124829fe91cf8f5d4e5ccdd381eae00" +dependencies = [ + "alloy-eip2930", + "alloy-eip7702", + "alloy-primitives", + "alloy-rlp", + "alloy-serde", + "c-kzg", + "derive_more 1.0.0", + "once_cell", + "serde", + "sha2", +] + +[[package]] +name = "alloy-genesis" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b85dfc693e4a1193f0372a8f789df12ab51fcbe7be0733baa04939a86dd813b" +dependencies = [ + "alloy-primitives", + "alloy-serde", + "serde", +] + +[[package]] +name = "alloy-json-abi" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "299d2a937b6c60968df3dad2a988b0f0e03277b344639a4f7a31bd68e6285e59" +dependencies = [ + "alloy-primitives", + "alloy-sol-type-parser", + "serde", + "serde_json", +] + +[[package]] +name = "alloy-json-rpc" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4207166c79cfdf7f3bed24bbc84f5c7c5d4db1970f8c82e3fcc76257f16d2166" +dependencies = [ + "alloy-primitives", + "alloy-sol-types", + "serde", + "serde_json", + "thiserror", + "tracing", +] + +[[package]] +name = "alloy-network" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe2802d5b8c632f18d68c352073378f02a3407c1b6a4487194e7d21ab0f002" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-json-rpc", + "alloy-network-primitives", + "alloy-primitives", + "alloy-rpc-types-eth", + "alloy-serde", + "alloy-signer", + "alloy-sol-types", + "async-trait", + "auto_impl", + "futures-utils-wasm", + "thiserror", +] + +[[package]] +name = "alloy-network-primitives" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "396c07726030fa0f9dab5da8c71ccd69d5eb74a7fe1072b7ae453a67e4fe553e" +dependencies = [ + "alloy-primitives", + "alloy-serde", + "serde", +] + +[[package]] +name = "alloy-primitives" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a767e59c86900dd7c3ce3ecef04f3ace5ac9631ee150beb8b7d22f7fa3bbb2d7" +dependencies = [ + "alloy-rlp", + "bytes", + "cfg-if", + "const-hex", + "derive_more 0.99.18", + "hex-literal", + "itoa", + "k256", + "keccak-asm", + "proptest", + "rand", + "ruint", + "serde", + "tiny-keccak", +] + +[[package]] +name = "alloy-provider" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1376948df782ffee83a54cac4b2aba14134edd997229a3db97da0a606586eb5c" +dependencies = [ + "alloy-chains", + "alloy-consensus", + "alloy-eips", + "alloy-json-rpc", + "alloy-network", + "alloy-network-primitives", + "alloy-primitives", + "alloy-pubsub", + "alloy-rpc-client", + "alloy-rpc-types-eth", + "alloy-transport", + "alloy-transport-http", + "alloy-transport-ws", + "async-stream", + "async-trait", + "auto_impl", + "dashmap", + "futures", + "futures-utils-wasm", + "lru 0.12.4", + "pin-project", + "reqwest", + "serde", + "serde_json", + "thiserror", + "tokio", + "tracing", + "url", +] + +[[package]] +name = "alloy-pubsub" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa73f976e7b6341f3f8a404241cf04f883d40212cd4f2633c66d99de472e262c" +dependencies = [ + "alloy-json-rpc", + "alloy-primitives", + "alloy-transport", + "bimap", + "futures", + "serde", + "serde_json", + "tokio", + "tokio-stream", + "tower", + "tracing", +] + +[[package]] +name = "alloy-rlp" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26154390b1d205a4a7ac7352aa2eb4f81f391399d4e2f546fb81a2f8bb383f62" +dependencies = [ + "alloy-rlp-derive", + "arrayvec", + "bytes", +] + +[[package]] +name = "alloy-rlp-derive" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d0f2d905ebd295e7effec65e5f6868d153936130ae718352771de3e7d03c75c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", +] + +[[package]] +name = "alloy-rpc-client" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02378418a429f8a14a0ad8ffaa15b2d25ff34914fc4a1e366513c6a3800e03b3" +dependencies = [ + "alloy-json-rpc", + "alloy-primitives", + "alloy-pubsub", + "alloy-transport", + "alloy-transport-http", + "alloy-transport-ws", + "futures", + "pin-project", + "reqwest", + "serde", + "serde_json", + "tokio", + "tokio-stream", + "tower", + "tracing", + "url", +] + +[[package]] +name = "alloy-rpc-types" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9ae4c4fbd37d9996f501fbc7176405aab97ae3a5772789be06ef0e7c4dad6dd" +dependencies = [ + "alloy-rpc-types-eth", + "alloy-serde", + "serde", +] + +[[package]] +name = "alloy-rpc-types-eth" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15bb3506ab1cf415d4752778c93e102050399fb8de97b7da405a5bf3e31f5f3b" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-network-primitives", + "alloy-primitives", + "alloy-rlp", + "alloy-serde", + "alloy-sol-types", + "itertools 0.13.0", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "alloy-serde" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae417978015f573b4a8c02af17f88558fb22e3fccd12e8a910cf6a2ff331cfcb" +dependencies = [ + "alloy-primitives", + "serde", + "serde_json", +] + +[[package]] +name = "alloy-signer" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b750c9b61ac0646f8f4a61231c2732a337b2c829866fc9a191b96b7eedf80ffe" +dependencies = [ + "alloy-primitives", + "async-trait", + "auto_impl", + "elliptic-curve", + "k256", + "thiserror", +] + +[[package]] +name = "alloy-sol-macro" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "183bcfc0f3291d9c41a3774172ee582fb2ce6eb6569085471d8f225de7bb86fc" +dependencies = [ + "alloy-sol-macro-expander", + "alloy-sol-macro-input", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.72", +] + +[[package]] +name = "alloy-sol-macro-expander" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71c4d842beb7a6686d04125603bc57614d5ed78bf95e4753274db3db4ba95214" +dependencies = [ + "alloy-json-abi", + "alloy-sol-macro-input", + "const-hex", + "heck 0.5.0", + "indexmap 2.2.6", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.72", + "syn-solidity", + "tiny-keccak", +] + +[[package]] +name = "alloy-sol-macro-input" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1306e8d3c9e6e6ecf7a39ffaf7291e73a5f655a2defd366ee92c2efebcdf7fee" +dependencies = [ + "alloy-json-abi", + "const-hex", + "dunce", + "heck 0.5.0", + "proc-macro2", + "quote", + "serde_json", + "syn 2.0.72", + "syn-solidity", +] + +[[package]] +name = "alloy-sol-type-parser" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4691da83dce9c9b4c775dd701c87759f173bd3021cbf2e60cde00c5fe6d7241" +dependencies = [ + "serde", + "winnow 0.6.16", +] + +[[package]] +name = "alloy-sol-types" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "577e262966e92112edbd15b1b2c0947cc434d6e8311df96d3329793fe8047da9" +dependencies = [ + "alloy-json-abi", + "alloy-primitives", + "alloy-sol-macro", + "const-hex", + "serde", +] + +[[package]] +name = "alloy-transport" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2799749ca692ae145f54968778877afd7c95e788488f176cfdfcf2a8abeb2062" +dependencies = [ + "alloy-json-rpc", + "base64 0.22.1", + "futures-util", + "futures-utils-wasm", + "serde", + "serde_json", + "thiserror", + "tokio", + "tower", + "tracing", + "url", +] + +[[package]] +name = "alloy-transport-http" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc10c4dd932f66e0db6cc5735241e0c17a6a18564b430bbc1839f7db18587a93" +dependencies = [ + "alloy-json-rpc", + "alloy-transport", + "reqwest", + "serde_json", + "tower", + "tracing", + "url", +] + +[[package]] +name = "alloy-transport-ws" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65e732028930aa17b7edd464a9711365417635e984028fcc7176393ccea22c00" +dependencies = [ + "alloy-pubsub", + "alloy-transport", + "futures", + "http 1.1.0", + "rustls", + "serde_json", + "tokio", + "tokio-tungstenite 0.23.1", + "tracing", + "ws_stream_wasm", +] + [[package]] name = "android-tzdata" version = "0.1.1" @@ -183,14 +691,32 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "defd9a439d56ac24968cca0571f598a61bc8c55f71d50a89cda591cb750670ba" dependencies = [ - "ark-ff", - "ark-poly", - "ark-serialize", - "ark-std", + "ark-ff 0.4.2", + "ark-poly", + "ark-serialize 0.4.2", + "ark-std 0.4.0", + "derivative", + "hashbrown 0.13.2", + "itertools 0.10.5", + "num-traits 0.2.19", + "zeroize", +] + +[[package]] +name = "ark-ff" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b3235cc41ee7a12aaaf2c575a2ad7b46713a8a50bda2fc3b003a04845c05dd6" +dependencies = [ + "ark-ff-asm 0.3.0", + "ark-ff-macros 0.3.0", + "ark-serialize 0.3.0", + "ark-std 0.3.0", "derivative", - "hashbrown 0.13.2", - "itertools 0.10.5", + "num-bigint 0.4.6", "num-traits 0.2.19", + "paste", + "rustc_version 0.3.3", "zeroize", ] @@ -200,20 +726,30 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba" dependencies = [ - "ark-ff-asm", - "ark-ff-macros", - "ark-serialize", - "ark-std", + "ark-ff-asm 0.4.2", + "ark-ff-macros 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", "derivative", - "digest", + "digest 0.10.7", "itertools 0.10.5", "num-bigint 0.4.6", "num-traits 0.2.19", "paste", - "rustc_version", + "rustc_version 0.4.0", "zeroize", ] +[[package]] +name = "ark-ff-asm" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db02d390bf6643fb404d3d22d31aee1c4bc4459600aef9113833d17e786c6e44" +dependencies = [ + "quote", + "syn 1.0.109", +] + [[package]] name = "ark-ff-asm" version = "0.4.2" @@ -224,6 +760,18 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "ark-ff-macros" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fd794a08ccb318058009eefdf15bcaaaaf6f8161eb3345f907222bac38b20" +dependencies = [ + "num-bigint 0.4.6", + "num-traits 0.2.19", + "quote", + "syn 1.0.109", +] + [[package]] name = "ark-ff-macros" version = "0.4.2" @@ -243,9 +791,9 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d320bfc44ee185d899ccbadfa8bc31aab923ce1558716e1997a1e74057fe86bf" dependencies = [ - "ark-ff", - "ark-serialize", - "ark-std", + "ark-ff 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", "derivative", "hashbrown 0.13.2", ] @@ -257,8 +805,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c02e954eaeb4ddb29613fee20840c2bbc85ca4396d53e33837e11905363c5f2" dependencies = [ "ark-ec", - "ark-ff", - "ark-std", + "ark-ff 0.4.2", + "ark-std 0.4.0", ] [[package]] @@ -268,8 +816,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3975a01b0a6e3eae0f72ec7ca8598a6620fc72fa5981f6f5cca33b7cd788f633" dependencies = [ "ark-ec", - "ark-ff", - "ark-std", + "ark-ff 0.4.2", + "ark-std 0.4.0", +] + +[[package]] +name = "ark-serialize" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d6c2b318ee6e10f8c2853e73a83adc0ccb88995aa978d8a3408d492ab2ee671" +dependencies = [ + "ark-std 0.3.0", + "digest 0.9.0", ] [[package]] @@ -279,8 +837,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" dependencies = [ "ark-serialize-derive", - "ark-std", - "digest", + "ark-std 0.4.0", + "digest 0.10.7", "num-bigint 0.4.6", ] @@ -295,6 +853,16 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "ark-std" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1df2c09229cbc5a028b1d70e00fdb2acee28b1055dfb5ca73eea49c5a25c4e7c" +dependencies = [ + "num-traits 0.2.19", + "rand", +] + [[package]] name = "ark-std" version = "0.4.0" @@ -596,6 +1164,28 @@ dependencies = [ "wasm-bindgen-futures", ] +[[package]] +name = "async-stream" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", +] + [[package]] name = "async-task" version = "4.7.1" @@ -613,6 +1203,17 @@ dependencies = [ "syn 2.0.72", ] +[[package]] +name = "async_io_stream" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d7b9decdf35d8908a7e3ef02f64c5e9b1695e230154c0e8de3969142d9b94c" +dependencies = [ + "futures", + "pharos", + "rustc_version 0.4.0", +] + [[package]] name = "asynchronous-codec" version = "0.6.2" @@ -667,6 +1268,17 @@ dependencies = [ "winapi", ] +[[package]] +name = "auto_impl" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", +] + [[package]] name = "autocfg" version = "1.3.0" @@ -732,7 +1344,7 @@ dependencies = [ "sha1", "sync_wrapper 1.0.1", "tokio", - "tokio-tungstenite", + "tokio-tungstenite 0.21.0", "tower", "tower-layer", "tower-service", @@ -810,6 +1422,12 @@ version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270" +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + [[package]] name = "base64" version = "0.13.1" @@ -845,6 +1463,12 @@ dependencies = [ "regex", ] +[[package]] +name = "bimap" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "230c5f1ca6a325a32553f8640d31ac9b49f2411e901e427570154868b46da4f7" + [[package]] name = "bincode" version = "2.0.0-rc.3" @@ -915,7 +1539,7 @@ version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" dependencies = [ - "digest", + "digest 0.10.7", ] [[package]] @@ -935,7 +1559,7 @@ checksum = "0fb99b6d20e12f5dff17a2b53e3e6cab54766357a638f90dafcb43c0ac933d4b" dependencies = [ "anyhow", "ark-ec", - "ark-ff", + "ark-ff 0.4.2", "ark-secp256k1", "ark-secp256r1", "cached", @@ -944,7 +1568,7 @@ dependencies = [ "cairo-lang-starknet-classes", "cairo-lang-utils 2.7.1", "cairo-vm", - "derive_more", + "derive_more 0.99.18", "indexmap 2.2.6", "itertools 0.10.5", "keccak", @@ -991,6 +1615,18 @@ dependencies = [ "siphasher 1.0.1", ] +[[package]] +name = "blst" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4378725facc195f1a538864863f6de233b500a8862747e7f165078a419d5e874" +dependencies = [ + "cc", + "glob", + "threadpool", + "zeroize", +] + [[package]] name = "bs58" version = "0.5.1" @@ -1058,6 +1694,21 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "c-kzg" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0307f72feab3300336fb803a57134159f6e20139af1357f36c54cb90d8e8928" +dependencies = [ + "blst", + "cc", + "glob", + "hex", + "libc", + "once_cell", + "serde", +] + [[package]] name = "cached" version = "0.44.0" @@ -1269,7 +1920,7 @@ dependencies = [ "cairo-lang-utils 2.7.1", "indoc 2.0.5", "salsa", - "semver", + "semver 1.0.23", "smol_str 0.2.2", "thiserror", ] @@ -1507,7 +2158,7 @@ dependencies = [ "cairo-lang-utils 2.7.1", "path-clean 1.0.1", "salsa", - "semver", + "semver 1.0.23", "serde", "smol_str 0.2.2", ] @@ -1883,7 +2534,7 @@ version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef5bbbabd509ce88abc67436973d3377e099269dbd14578fa84fce884a74fa23" dependencies = [ - "ark-ff", + "ark-ff 0.4.2", "ark-secp256k1", "ark-secp256r1", "cairo-lang-casm 2.7.1", @@ -3006,6 +3657,19 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32b13ea120a812beba79e34316b3942a857c86ec1593cb34f27bb28272ce2cca" +[[package]] +name = "const-hex" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8a24a26d37e1ffd45343323dc9fe6654ceea44c12f2fcb3d7ac29e610bc6" +dependencies = [ + "cfg-if", + "cpufeatures", + "hex", + "proptest", + "serde", +] + [[package]] name = "const-oid" version = "0.9.6" @@ -3179,6 +3843,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" dependencies = [ "generic-array", + "rand_core", "subtle", "zeroize", ] @@ -3212,9 +3877,9 @@ dependencies = [ "cfg-if", "cpufeatures", "curve25519-dalek-derive", - "digest", + "digest 0.10.7", "fiat-crypto", - "rustc_version", + "rustc_version 0.4.0", "subtle", "zeroize", ] @@ -3312,6 +3977,20 @@ dependencies = [ "ordered-float", ] +[[package]] +name = "dashmap" +version = "6.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "804c8821570c3f8b70230c2ba75ffa5c0f9a4189b9a432b6656c536712acae28" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core 0.9.10", +] + [[package]] name = "data-encoding" version = "2.6.0" @@ -3392,7 +4071,27 @@ dependencies = [ "convert_case 0.4.0", "proc-macro2", "quote", - "rustc_version", + "rustc_version 0.4.0", + "syn 2.0.72", +] + +[[package]] +name = "derive_more" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" +dependencies = [ + "proc-macro2", + "quote", "syn 2.0.72", ] @@ -3423,6 +4122,15 @@ dependencies = [ "nu-ansi-term", ] +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + [[package]] name = "digest" version = "0.10.7" @@ -3430,6 +4138,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", + "const-oid", "crypto-common", "subtle", ] @@ -3490,12 +4199,32 @@ dependencies = [ "syn 2.0.72", ] +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + [[package]] name = "dyn-clone" version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest 0.10.7", + "elliptic-curve", + "rfc6979", + "signature", + "spki", +] + [[package]] name = "ed25519" version = "2.2.3" @@ -3527,6 +4256,25 @@ version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest 0.10.7", + "ff", + "generic-array", + "group", + "pkcs8", + "rand_core", + "sec1", + "subtle", + "zeroize", +] + [[package]] name = "ena" version = "0.14.3" @@ -3676,6 +4424,17 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" +[[package]] +name = "fastrlp" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139834ddba373bbdd213dffe02c8d110508dcf1726c2be27e8d1f7d7e1856418" +dependencies = [ + "arrayvec", + "auto_impl", + "bytes", +] + [[package]] name = "ff" version = "0.13.0" @@ -3764,6 +4523,21 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "form_urlencoded" version = "1.2.1" @@ -3941,6 +4715,12 @@ dependencies = [ "slab", ] +[[package]] +name = "futures-utils-wasm" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42012b0f064e01aa58b545fe3727f90f7dd4020f4a3ea735b50344965f5a57e9" + [[package]] name = "gateway-test-utils" version = "0.14.2" @@ -3982,6 +4762,7 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", ] [[package]] @@ -4054,6 +4835,17 @@ dependencies = [ "minilp", ] +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core", + "subtle", +] + [[package]] name = "h2" version = "0.3.26" @@ -4224,6 +5016,15 @@ name = "hex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +dependencies = [ + "serde", +] + +[[package]] +name = "hex-literal" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" [[package]] name = "hex_fmt" @@ -4292,7 +5093,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest", + "digest 0.10.7", ] [[package]] @@ -4492,6 +5293,22 @@ dependencies = [ "tokio-io-timeout", ] +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper 1.4.1", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + [[package]] name = "hyper-util" version = "0.1.6" @@ -4792,6 +5609,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.11" @@ -4836,6 +5662,19 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "k256" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "956ff9b67e26e1a6a866cb758f12c6f8746208489e3e4a4b5580802f2f0a587b" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "once_cell", + "sha2", +] + [[package]] name = "keccak" version = "0.1.5" @@ -4845,6 +5684,16 @@ dependencies = [ "cpufeatures", ] +[[package]] +name = "keccak-asm" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "422fbc7ff2f2f5bdffeb07718e5a5324dca72b0c9293d50df4026652385e3314" +dependencies = [ + "digest 0.10.7", + "sha3-asm", +] + [[package]] name = "keccak-hash" version = "0.10.0" @@ -5874,12 +6723,29 @@ dependencies = [ ] [[package]] -name = "nanorand" -version = "0.7.0" +name = "nanorand" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" +dependencies = [ + "getrandom", +] + +[[package]] +name = "native-tls" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" +checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" dependencies = [ - "getrandom", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", ] [[package]] @@ -6127,6 +6993,26 @@ dependencies = [ "libc", ] +[[package]] +name = "num_enum" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", +] + [[package]] name = "num_threads" version = "0.1.7" @@ -6172,12 +7058,50 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" +[[package]] +name = "openssl" +version = "0.10.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" +dependencies = [ + "bitflags 2.6.0", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", +] + [[package]] name = "openssl-probe" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +[[package]] +name = "openssl-sys" +version = "0.9.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "ordered-float" version = "2.10.1" @@ -6435,7 +7359,7 @@ dependencies = [ "rayon", "reqwest", "rstest", - "semver", + "semver 1.0.23", "serde", "serde_json", "serde_with", @@ -6495,7 +7419,7 @@ dependencies = [ "pathfinder-common", "pathfinder-crypto", "rstest", - "semver", + "semver 1.0.23", "serde", "serde_json", "starknet-gateway-test-fixtures", @@ -6506,7 +7430,7 @@ dependencies = [ name = "pathfinder-crypto" version = "0.14.2" dependencies = [ - "ark-ff", + "ark-ff 0.4.2", "assert_matches", "bitvec", "criterion", @@ -6523,18 +7447,18 @@ dependencies = [ name = "pathfinder-ethereum" version = "0.14.2" dependencies = [ + "alloy", "anyhow", "async-trait", "const-decoder", + "futures", "hex", - "httpmock", "keccak-hash", "pathfinder-common", "pathfinder-crypto", "primitive-types", "reqwest", "serde_json", - "thiserror", "tokio", "tracing", ] @@ -6626,7 +7550,7 @@ dependencies = [ "test-log", "thiserror", "tokio", - "tokio-tungstenite", + "tokio-tungstenite 0.21.0", "tower", "tower-http", "tracing", @@ -6697,7 +7621,7 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" dependencies = [ - "digest", + "digest 0.10.7", "hmac", "password-hash", "sha2", @@ -6719,6 +7643,17 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +[[package]] +name = "pest" +version = "2.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd53dff83f26735fdc1ca837098ccf133605d794cdae66acfc2bfac3ec809d95" +dependencies = [ + "memchr", + "thiserror", + "ucd-trie", +] + [[package]] name = "petgraph" version = "0.6.5" @@ -6729,6 +7664,16 @@ dependencies = [ "indexmap 2.2.6", ] +[[package]] +name = "pharos" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9567389417feee6ce15dd6527a8a1ecac205ef62c2932bcf3d9f6fc5b78b414" +dependencies = [ + "futures", + "rustc_version 0.4.0", +] + [[package]] name = "phf" version = "0.11.2" @@ -7045,6 +7990,30 @@ dependencies = [ "toml_edit 0.21.1", ] +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro2" version = "1.0.86" @@ -7125,7 +8094,7 @@ checksum = "5bb182580f71dd070f88d01ce3de9f4da5021db7115d2e1c3605a754153b77c1" dependencies = [ "bytes", "heck 0.5.0", - "itertools 0.12.1", + "itertools 0.13.0", "log", "multimap", "once_cell", @@ -7158,7 +8127,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "18bec9b0adc4eba778b33684b7ba3e7137789434769ee3ce3930463ef904cfca" dependencies = [ "anyhow", - "itertools 0.12.1", + "itertools 0.13.0", "proc-macro2", "quote", "syn 2.0.72", @@ -7505,11 +8474,13 @@ dependencies = [ "http-body-util", "hyper 1.4.1", "hyper-rustls", + "hyper-tls", "hyper-util", "ipnet", "js-sys", "log", "mime", + "native-tls", "once_cell", "percent-encoding", "pin-project-lite", @@ -7523,6 +8494,7 @@ dependencies = [ "serde_urlencoded", "sync_wrapper 1.0.1", "tokio", + "tokio-native-tls", "tokio-rustls", "tower-service", "url", @@ -7582,6 +8554,16 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rlp" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb919243f34364b6bd2fc10ef797edbfa75f33c252e7998527479c6d6b47e1ec" +dependencies = [ + "bytes", + "rustc-hex", +] + [[package]] name = "rstest" version = "0.18.2" @@ -7591,7 +8573,7 @@ dependencies = [ "futures", "futures-timer", "rstest_macros", - "rustc_version", + "rustc_version 0.4.0", ] [[package]] @@ -7606,7 +8588,7 @@ dependencies = [ "quote", "regex", "relative-path", - "rustc_version", + "rustc_version 0.4.0", "syn 2.0.72", "unicode-ident", ] @@ -7627,6 +8609,36 @@ dependencies = [ "tokio", ] +[[package]] +name = "ruint" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c3cc4c2511671f327125da14133d0c5c5d137f006a1017a16f557bc85b16286" +dependencies = [ + "alloy-rlp", + "ark-ff 0.3.0", + "ark-ff 0.4.2", + "bytes", + "fastrlp", + "num-bigint 0.4.6", + "num-traits 0.2.19", + "parity-scale-codec", + "primitive-types", + "proptest", + "rand", + "rlp", + "ruint-macro", + "serde", + "valuable", + "zeroize", +] + +[[package]] +name = "ruint-macro" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18" + [[package]] name = "rusqlite" version = "0.32.1" @@ -7669,13 +8681,22 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" +[[package]] +name = "rustc_version" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee" +dependencies = [ + "semver 0.11.0", +] + [[package]] name = "rustc_version" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "semver", + "semver 1.0.23", ] [[package]] @@ -7906,6 +8927,20 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + [[package]] name = "security-framework" version = "2.11.1" @@ -7929,6 +8964,15 @@ dependencies = [ "libc", ] +[[package]] +name = "semver" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" +dependencies = [ + "semver-parser", +] + [[package]] name = "semver" version = "1.0.23" @@ -7938,6 +8982,21 @@ dependencies = [ "serde", ] +[[package]] +name = "semver-parser" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7" +dependencies = [ + "pest", +] + +[[package]] +name = "send_wrapper" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" + [[package]] name = "serde" version = "1.0.204" @@ -8060,7 +9119,7 @@ checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", "cpufeatures", - "digest", + "digest 0.10.7", ] [[package]] @@ -8071,7 +9130,7 @@ checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" dependencies = [ "cfg-if", "cpufeatures", - "digest", + "digest 0.10.7", ] [[package]] @@ -8080,10 +9139,20 @@ version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" dependencies = [ - "digest", + "digest 0.10.7", "keccak", ] +[[package]] +name = "sha3-asm" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57d79b758b7cb2085612b11a235055e485605a5103faccdd633f35bd7aee69dd" +dependencies = [ + "cc", + "cfg-if", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -8108,6 +9177,7 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ + "digest 0.10.7", "rand_core", ] @@ -8197,7 +9267,7 @@ dependencies = [ "curve25519-dalek", "rand_core", "ring 0.17.8", - "rustc_version", + "rustc_version 0.4.0", "sha2", "subtle", ] @@ -8333,7 +9403,7 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7abf1b44ec5b18d87c1ae5f54590ca9d0699ef4dd5b2ffa66fc97f24613ec585" dependencies = [ - "ark-ff", + "ark-ff 0.4.2", "crypto-bigint", "getrandom", "hex", @@ -8426,7 +9496,7 @@ checksum = "1b505c9c076d9fce854304bd743c93ea540ebea6b16ec96819b07343a3aa2c7c" dependencies = [ "bitvec", "cairo-lang-starknet-classes", - "derive_more", + "derive_more 0.99.18", "hex", "indexmap 2.2.6", "itertools 0.12.1", @@ -8485,6 +9555,15 @@ version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +dependencies = [ + "strum_macros 0.26.4", +] + [[package]] name = "strum_macros" version = "0.24.3" @@ -8511,6 +9590,19 @@ dependencies = [ "syn 2.0.72", ] +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.72", +] + [[package]] name = "subtle" version = "2.6.1" @@ -8539,6 +9631,18 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn-solidity" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "284c41c2919303438fcf8dede4036fd1e82d4fc0fbb2b279bd2a1442c909ca92" +dependencies = [ + "paste", + "proc-macro2", + "quote", + "syn 2.0.72", +] + [[package]] name = "sync_wrapper" version = "0.1.2" @@ -8726,6 +9830,15 @@ dependencies = [ "once_cell", ] +[[package]] +name = "threadpool" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" +dependencies = [ + "num_cpus", +] + [[package]] name = "time" version = "0.3.36" @@ -8833,6 +9946,16 @@ dependencies = [ "syn 2.0.72", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + [[package]] name = "tokio-retry" version = "0.3.0" @@ -8876,7 +9999,23 @@ dependencies = [ "futures-util", "log", "tokio", - "tungstenite", + "tungstenite 0.21.0", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6989540ced10490aaf14e6bad2e3d33728a2813310a0c71d1574304c49631cd" +dependencies = [ + "futures-util", + "log", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tungstenite 0.23.0", + "webpki-roots", ] [[package]] @@ -9113,12 +10252,38 @@ dependencies = [ "utf-8", ] +[[package]] +name = "tungstenite" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e2ce1e47ed2994fd43b04c8f618008d4cabdd5ee34027cf14f9d918edd9c8" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http 1.1.0", + "httparse", + "log", + "rand", + "rustls", + "rustls-pki-types", + "sha1", + "thiserror", + "utf-8", +] + [[package]] name = "typenum" version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +[[package]] +name = "ucd-trie" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" + [[package]] name = "uint" version = "0.9.5" @@ -9374,7 +10539,7 @@ dependencies = [ "serde_json", "serde_urlencoded", "tokio", - "tokio-tungstenite", + "tokio-tungstenite 0.21.0", "tokio-util", "tower-service", "tracing", @@ -9478,6 +10643,15 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki-roots" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd7c23921eeb1713a4e851530e9b9756e4fb0e89978582942612524cf09f01cd" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "widestring" version = "1.1.0" @@ -9720,6 +10894,25 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "ws_stream_wasm" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7999f5f4217fe3818726b66257a4475f71e74ffd190776ad053fa159e50737f5" +dependencies = [ + "async_io_stream", + "futures", + "js-sys", + "log", + "pharos", + "rustc_version 0.4.0", + "send_wrapper", + "thiserror", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "wyz" version = "0.5.1" diff --git a/crates/common/src/lib.rs b/crates/common/src/lib.rs index 300427ae07..fceb01cc1f 100644 --- a/crates/common/src/lib.rs +++ b/crates/common/src/lib.rs @@ -20,6 +20,7 @@ pub mod event; pub mod hash; mod header; mod macros; +pub mod message; pub mod prelude; pub mod receipt; pub mod signature; @@ -29,6 +30,7 @@ pub mod transaction; pub mod trie; pub use header::{BlockHeader, BlockHeaderBuilder, L1DataAvailabilityMode, SignedBlockHeader}; +pub use message::L1ToL2MessageHash; pub use signature::BlockCommitmentSignature; pub use state_update::StateUpdate; diff --git a/crates/common/src/message.rs b/crates/common/src/message.rs new file mode 100644 index 0000000000..63c91ab624 --- /dev/null +++ b/crates/common/src/message.rs @@ -0,0 +1,12 @@ +use primitive_types::H256; + +use crate::BlockNumber; + +/// An L1 -> L2 message hash with the block and tx where it was sent +#[derive(Debug, Clone)] +pub struct L1ToL2MessageHash { + pub message_hash: H256, + pub l1_tx_hash: H256, + pub l1_block_number: BlockNumber, + pub is_finalized: bool, +} diff --git a/crates/crypto/src/algebra/field/felt.rs b/crates/crypto/src/algebra/field/felt.rs index 4aa7fddb76..98e99eb682 100644 --- a/crates/crypto/src/algebra/field/felt.rs +++ b/crates/crypto/src/algebra/field/felt.rs @@ -237,6 +237,12 @@ impl From for Felt { } } +impl From<[u8; 32]> for Felt { + fn from(bytes: [u8; 32]) -> Self { + Self(bytes) + } +} + impl TryInto for Felt { type Error = OverflowError; diff --git a/crates/ethereum/Cargo.toml b/crates/ethereum/Cargo.toml index 527447e456..bba11d2fe0 100644 --- a/crates/ethereum/Cargo.toml +++ b/crates/ethereum/Cargo.toml @@ -5,12 +5,17 @@ authors = { workspace = true } edition = { workspace = true } license = { workspace = true } rust-version = { workspace = true } -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +alloy = { version = "0.3", features = [ + "contract", + "rpc-types", + "provider-ws", +] } anyhow = { workspace = true } async-trait = { workspace = true } const-decoder = { workspace = true } +futures = { workspace = true } hex = { workspace = true } keccak-hash = { workspace = true } pathfinder-common = { path = "../common" } @@ -18,10 +23,7 @@ pathfinder-crypto = { path = "../crypto" } primitive-types = { workspace = true } reqwest = { workspace = true, features = ["json"] } serde_json = { workspace = true } -thiserror = { workspace = true } -tokio = { workspace = true } +tokio = { workspace = true, features = ["macros"] } tracing = { workspace = true } [dev-dependencies] -httpmock = { workspace = true } -tokio = { workspace = true, features = ["macros"] } diff --git a/crates/ethereum/abi/starknet_core_contract.json b/crates/ethereum/abi/starknet_core_contract.json new file mode 100644 index 0000000000..185bbabd82 --- /dev/null +++ b/crates/ethereum/abi/starknet_core_contract.json @@ -0,0 +1,952 @@ +[ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "changedBy", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "oldAggregatorProgramHash", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newAggregatorProgramHash", + "type": "uint256" + } + ], + "name": "AggregatorProgramHashChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "changedBy", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "oldConfigHash", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newConfigHash", + "type": "uint256" + } + ], + "name": "ConfigHashChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "fromAddress", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "toAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256[]", + "name": "payload", + "type": "uint256[]" + } + ], + "name": "ConsumedMessageToL1", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "fromAddress", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "toAddress", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "selector", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256[]", + "name": "payload", + "type": "uint256[]" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + } + ], + "name": "ConsumedMessageToL2", + "type": "event" + }, + { + "anonymous": false, + "inputs": [], + "name": "Finalized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "fromAddress", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "toAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256[]", + "name": "payload", + "type": "uint256[]" + } + ], + "name": "LogMessageToL1", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "fromAddress", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "toAddress", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "selector", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256[]", + "name": "payload", + "type": "uint256[]" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "fee", + "type": "uint256" + } + ], + "name": "LogMessageToL2", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "acceptedGovernor", + "type": "address" + } + ], + "name": "LogNewGovernorAccepted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "nominatedGovernor", + "type": "address" + } + ], + "name": "LogNominatedGovernor", + "type": "event" + }, + { + "anonymous": false, + "inputs": [], + "name": "LogNominationCancelled", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "operator", + "type": "address" + } + ], + "name": "LogOperatorAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "operator", + "type": "address" + } + ], + "name": "LogOperatorRemoved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "removedGovernor", + "type": "address" + } + ], + "name": "LogRemovedGovernor", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bytes32", + "name": "stateTransitionFact", + "type": "bytes32" + } + ], + "name": "LogStateTransitionFact", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "globalRoot", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "int256", + "name": "blockNumber", + "type": "int256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "blockHash", + "type": "uint256" + } + ], + "name": "LogStateUpdate", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "fromAddress", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "toAddress", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "selector", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256[]", + "name": "payload", + "type": "uint256[]" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + } + ], + "name": "MessageToL2Canceled", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "fromAddress", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "toAddress", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "selector", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256[]", + "name": "payload", + "type": "uint256[]" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + } + ], + "name": "MessageToL2CancellationStarted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "changedBy", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "oldProgramHash", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newProgramHash", + "type": "uint256" + } + ], + "name": "ProgramHashChanged", + "type": "event" + }, + { + "inputs": [], + "name": "aggregatorProgramHash", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "toAddress", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "selector", + "type": "uint256" + }, + { + "internalType": "uint256[]", + "name": "payload", + "type": "uint256[]" + }, + { + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + } + ], + "name": "cancelL1ToL2Message", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "configHash", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "fromAddress", + "type": "uint256" + }, + { + "internalType": "uint256[]", + "name": "payload", + "type": "uint256[]" + } + ], + "name": "consumeMessageFromL2", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "finalize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "getMaxL1MsgFee", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "identify", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "isFinalized", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "isFrozen", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "user", + "type": "address" + } + ], + "name": "isOperator", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "msgHash", + "type": "bytes32" + } + ], + "name": "l1ToL2MessageCancellations", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "l1ToL2MessageNonce", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "msgHash", + "type": "bytes32" + } + ], + "name": "l1ToL2Messages", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "msgHash", + "type": "bytes32" + } + ], + "name": "l2ToL1Messages", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "messageCancellationDelay", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "programHash", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOperator", + "type": "address" + } + ], + "name": "registerOperator", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "toAddress", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "selector", + "type": "uint256" + }, + { + "internalType": "uint256[]", + "name": "payload", + "type": "uint256[]" + } + ], + "name": "sendMessageToL2", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "newAggregatorProgramHash", + "type": "uint256" + } + ], + "name": "setAggregatorProgramHash", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "newConfigHash", + "type": "uint256" + } + ], + "name": "setConfigHash", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "delayInSeconds", + "type": "uint256" + } + ], + "name": "setMessageCancellationDelay", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "newProgramHash", + "type": "uint256" + } + ], + "name": "setProgramHash", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "starknetAcceptGovernance", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "starknetCancelNomination", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "user", + "type": "address" + } + ], + "name": "starknetIsGovernor", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newGovernor", + "type": "address" + } + ], + "name": "starknetNominateNewGovernor", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "governorForRemoval", + "type": "address" + } + ], + "name": "starknetRemoveGovernor", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "toAddress", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "selector", + "type": "uint256" + }, + { + "internalType": "uint256[]", + "name": "payload", + "type": "uint256[]" + }, + { + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + } + ], + "name": "startL1ToL2MessageCancellation", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "stateBlockHash", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "stateBlockNumber", + "outputs": [ + { + "internalType": "int256", + "name": "", + "type": "int256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "stateRoot", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "removedOperator", + "type": "address" + } + ], + "name": "unregisterOperator", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256[]", + "name": "programOutput", + "type": "uint256[]" + }, + { + "internalType": "uint256", + "name": "onchainDataHash", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "onchainDataSize", + "type": "uint256" + } + ], + "name": "updateState", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256[]", + "name": "programOutput", + "type": "uint256[]" + }, + { + "internalType": "bytes[]", + "name": "kzgProofs", + "type": "bytes[]" + } + ], + "name": "updateStateKzgDA", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] \ No newline at end of file diff --git a/crates/ethereum/src/lib.rs b/crates/ethereum/src/lib.rs index aec38d2c7c..6fb16e3e16 100644 --- a/crates/ethereum/src/lib.rs +++ b/crates/ethereum/src/lib.rs @@ -1,18 +1,61 @@ +use std::future::Future; + +use alloy::eips::{BlockId, BlockNumberOrTag, RpcBlockHash}; +use alloy::primitives::{Address, B256}; +use alloy::providers::{Provider, ProviderBuilder, WsConnect}; +use alloy::rpc::types::Log; use anyhow::Context; -use pathfinder_common::{BlockHash, BlockNumber, EthereumChain, StateCommitment}; -use pathfinder_crypto::Felt; +use futures::StreamExt; +use pathfinder_common::{ + BlockHash, + BlockNumber, + EthereumChain, + L1ToL2MessageHash, + StateCommitment, +}; use primitive_types::{H160, H256, U256}; +use reqwest::{IntoUrl, Url}; +use starknet::StarknetCoreContract; +use tokio::select; + +use crate::utils::*; +mod starknet; +mod utils; + +/// Starknet core contract addresses pub mod core_addr { use const_decoder::Decoder; + /// Ethereum address of the Starknet core contract on Mainnet pub const MAINNET: [u8; 20] = Decoder::Hex.decode(b"c662c410C0ECf747543f5bA90660f6ABeBD9C8c4"); + + /// Ethereum address of the Starknet core contract on Sepolia testnet pub const SEPOLIA_TESTNET: [u8; 20] = Decoder::Hex.decode(b"E2Bb56ee936fd6433DC0F6e7e3b8365C906AA057"); + + /// Ethereum address of the Starknet core contract on Sepolia integration + /// testnet pub const SEPOLIA_INTEGRATION: [u8; 20] = Decoder::Hex.decode(b"4737c0c1B4D5b1A687B42610DdabEE781152359c"); } +/// Events that can be emitted by the Ethereum client +#[derive(Debug)] +pub enum EthereumEvent { + StateUpdate(EthereumStateUpdate), + MessageUpdate(MessageUpdate), +} + +/// Message update from Ethereum +#[derive(Debug)] +pub enum MessageUpdate { + Sent(L1ToL2MessageHash), + Finalized(L1ToL2MessageHash), + Reverted(L1ToL2MessageHash), +} + +/// State update from Ethereum #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub struct EthereumStateUpdate { pub state_root: StateCommitment, @@ -20,337 +63,170 @@ pub struct EthereumStateUpdate { pub block_hash: BlockHash, } +/// Ethereum API trait #[async_trait::async_trait] pub trait EthereumApi { async fn get_starknet_state(&self, address: &H160) -> anyhow::Result; async fn get_chain(&self) -> anyhow::Result; + async fn listen(&self, address: &H160, callback: F) -> anyhow::Result<()> + where + F: Fn(EthereumEvent) -> Fut + Send + 'static, + Fut: Future + Send + 'static; } +/// Ethereum client #[derive(Clone, Debug)] pub struct EthereumClient { - http: reqwest::Client, - url: reqwest::Url, + url: Url, } -const HTTP_OK: u16 = 200; - impl EthereumClient { - pub fn with_password(mut url: reqwest::Url, password: &str) -> anyhow::Result { - url.set_password(Some(password)) - .map_err(|_| anyhow::anyhow!("Setting password failed"))?; - Self::new(url) - } - - pub fn new(url: reqwest::Url) -> anyhow::Result { + /// Creates a new [EthereumClient] + pub fn new(url: U) -> anyhow::Result { Ok(Self { - http: reqwest::ClientBuilder::new().build()?, - url, + url: url.into_url()?, }) } - async fn get_finalized_block_hash(&self) -> anyhow::Result { - self.call_ethereum(serde_json::json!({ - "jsonrpc": "2.0", - "method": "eth_getBlockByNumber", - "params": [ - "finalized", - false - ], - "id": 0 - })) - .await - .and_then(|value| get_h256(&value["hash"])) + /// Creates a new password-protected [EthereumClient] + pub fn with_password(url: U, password: &str) -> anyhow::Result { + let mut url = url.into_url()?; + url.set_password(Some(password)) + .map_err(|_| anyhow::anyhow!("Setting password failed"))?; + Self::new(url) } - async fn call_starknet_contract( - &self, - block_hash: &str, - address: &str, - signature: &str, - ) -> anyhow::Result { - let data = encode_ethereum_call_data(signature.as_bytes()); - self.call_ethereum(serde_json::json!({ - "jsonrpc": "2.0", - "method": "eth_call", - "params": [ - { - "to": address, - "value": "0x0", - "data": data - }, - {"blockHash": block_hash} - ], - "id": 0 - })) - .await + /// Returns the hash of the last finalized block + async fn get_finalized_block_hash(&self) -> anyhow::Result { + // Create a WebSocket connection + let ws = WsConnect::new(self.url.clone()); + let provider = ProviderBuilder::new().on_ws(ws).await?; + + // Fetch the finalized block hash + provider + .get_block_by_number(BlockNumberOrTag::Finalized, false) + .await? + .map(|block| { + let block_hash: [u8; 32] = block.header.hash.into(); + H256::from(block_hash) + }) + .context("Failed to fetch finalized block hash") } - async fn call_ethereum(&self, value: serde_json::Value) -> anyhow::Result { - let res = self.http.post(self.url.clone()).json(&value).send().await?; - - let status = res.status(); - let (code, message) = (status.as_u16(), status.as_str()); - if code != HTTP_OK { - tracing::error!(code, message, "Ethereum call failed"); - anyhow::bail!(code); - } - - let response: serde_json::Value = res.json().await?; - Ok(response["result"].clone()) - } } #[async_trait::async_trait] impl EthereumApi for EthereumClient { - async fn get_starknet_state(&self, address: &H160) -> anyhow::Result { - let hash = self.get_finalized_block_hash().await?; - let hash = format!("0x{}", hex::encode(hash.as_bytes())); - let addr = format!("0x{}", hex::encode(address.as_bytes())); - Ok(EthereumStateUpdate { - state_root: self - .call_starknet_contract(&hash, &addr, "stateRoot()") - .await - .and_then(|value| get_h256(&value)) - .and_then(get_felt) - .map(StateCommitment)?, - block_hash: self - .call_starknet_contract(&hash, &addr, "stateBlockHash()") - .await - .and_then(|value| get_h256(&value)) - .and_then(get_felt) - .map(BlockHash)?, - block_number: self - .call_starknet_contract(&hash, &addr, "stateBlockNumber()") - .await - .and_then(|value| get_u256(&value)) - .and_then(get_number)?, - }) - } - - async fn get_chain(&self) -> anyhow::Result { - let id = self - .call_ethereum(serde_json::json!({ - "jsonrpc": "2.0", - "method": "eth_chainId", - "params": [], - "id": 0 - })) - .await - .and_then(|value| get_u256(&value))?; - Ok(match id { - x if x == U256::from(1u32) => EthereumChain::Mainnet, - x if x == U256::from(11155111u32) => EthereumChain::Sepolia, - x => EthereumChain::Other(x), - }) - } -} - -fn encode_ethereum_call_data(signature: &[u8]) -> String { - let mut output: [u8; 32] = Default::default(); - keccak_hash::keccak_256(signature, &mut output[..]); - format!("0x{}", hex::encode(&output[0..4])) -} - -fn get_h256(value: &serde_json::Value) -> anyhow::Result { - use std::str::FromStr; - value - .as_str() - .map(lpad64) - .and_then(|val| H256::from_str(&val).ok()) - .context("Failed to fetch H256") -} - -fn get_u256(value: &serde_json::Value) -> anyhow::Result { - use std::str::FromStr; - value - .as_str() - .map(lpad64) - .and_then(|val| U256::from_str(&val).ok()) - .context("Failed to fetch U256") -} - -fn get_felt(value: H256) -> anyhow::Result { - let felt = Felt::from_be_slice(value.as_bytes())?; - Ok(felt) -} - -fn get_number(value: U256) -> anyhow::Result { - let value = value.as_u64(); - BlockNumber::new(value).context("Failed to read u64 from U256") -} + /// Listens for Ethereum events and notifies the caller using the provided + /// callback + async fn listen(&self, address: &H160, callback: F) -> anyhow::Result<()> + where + F: Fn(EthereumEvent) -> Fut + Send + 'static, + Fut: Future + Send + 'static, + { + // Create a WebSocket connection + let ws = WsConnect::new(self.url.clone()); + let provider = ProviderBuilder::new().on_ws(ws).await?; + + // Create the StarknetCoreContract instance + let address = Address::new((*address).into()); + let core_contract = StarknetCoreContract::new(address, provider.clone()); + + // Listen for L1 to L2 message events + let mut logs = provider + .subscribe_logs(&core_contract.LogMessageToL2_filter().filter) + .await? + .into_stream(); + + // Listen for state update events + let mut state_updates = provider + .subscribe_logs(&core_contract.LogStateUpdate_filter().filter) + .await? + .into_stream(); + + loop { + select! { + Some(state_update) = state_updates.next() => { + + // Decode the state update + let state_update: Log = state_update.log_decode()?; + let state_update = EthereumStateUpdate { + block_number: get_block_number(state_update.inner.blockNumber), + block_hash: get_block_hash(state_update.inner.blockHash), + state_root: get_state_root(state_update.inner.globalRoot), + }; + + // Emit the state update + callback(EthereumEvent::StateUpdate(state_update)).await; + + } + Some(log) = logs.next() => { + + // Decode the message + let log: Log = log.log_decode()?; + let l1_block_number = log.block_number.context("Block number not found")?; + + // Create L1ToL2MessageHash from the log data + let msg = L1ToL2MessageHash { + message_hash: H256::from(log.inner.message_hash().to_be_bytes()), + l1_tx_hash: log.transaction_hash.map(|hash| H256::from(hash.0)).unwrap_or_default(), + l1_block_number: BlockNumber::new_or_panic(l1_block_number), + is_finalized: false, + }; + + // TODO: All actions need to be done accordingly (e.g. reorgs and finalizations) + callback(EthereumEvent::MessageUpdate(MessageUpdate::Sent(msg))).await; + + } + } + } -fn lpad64(value: &str) -> String { - let input = value.strip_prefix("0x").unwrap_or(value); - let prefix = if value.starts_with("0x") { "0x" } else { "" }; - if input.len() == 64 { - format!("{prefix}{input}") - } else { - format!("{prefix}{input:0>64}") + //anyhow::bail!("Ethereum client stopped unexpectedly") } -} -#[cfg(test)] -mod tests { - use std::str::FromStr; - - use httpmock::prelude::*; - use primitive_types::H160; - use reqwest::Url; - - use super::*; - - #[tokio::test] - #[ignore = "live ethereum call"] - async fn test_live() -> anyhow::Result<()> { - let address = H160::from(core_addr::MAINNET); - - let url = Url::parse("https://eth.llamarpc.com")?; - let client = EthereumClient::new(url)?; + /// Get the Starknet state + async fn get_starknet_state(&self, address: &H160) -> anyhow::Result { + // Create a WebSocket connection + let ws = WsConnect::new(self.url.clone()); + let provider = ProviderBuilder::new().on_ws(ws).await?; - let state = client.get_starknet_state(&address).await?; - println!("{state:#?}"); + // Create the StarknetCoreContract instance + let address = Address::new((*address).into()); + let contract = StarknetCoreContract::new(address, provider); - let chain = client.get_chain().await?; - println!("{chain:?}"); + // Get the finalized block hash + let finalized_block_hash = self.get_finalized_block_hash().await?; + let block_hash = B256::from(finalized_block_hash.0); + let block_id = BlockId::Hash(RpcBlockHash::from_hash(block_hash, None)); - Ok(()) - } + // Call the contract methods + let state_root = contract.stateRoot().block(block_id).call().await?; + let block_hash = contract.stateBlockHash().block(block_id).call().await?; + let block_number = contract.stateBlockNumber().block(block_id).call().await?; - #[tokio::test] - async fn test_chain_id() -> anyhow::Result<()> { - let server = MockServer::start_async().await; - - let mock = server.mock(|when, then| { - when.path("/") - .method(POST) - .header("Content-type", "application/json") - .body(r#"{"id":0,"jsonrpc":"2.0","method":"eth_chainId","params":[]}"#); - then.status(200) - .header("Content-type", "application/json") - .body(r#"{"jsonrpc":"2.0","id":0,"result":"0x1"}"#); - }); - - let url = Url::parse(&server.url("/"))?; - let eth = EthereumClient::new(url)?; - let chain_id = eth.get_chain().await?; - - mock.assert(); - assert_eq!(chain_id, EthereumChain::Mainnet); - Ok(()) + // Return the state update + Ok(EthereumStateUpdate { + state_root: get_state_root(state_root._0), + block_hash: get_block_hash(block_hash._0.into()), + block_number: get_block_number(block_number._0), + }) } - #[tokio::test] - async fn test_get_starknet_state() -> anyhow::Result<()> { - let server = MockServer::start_async().await; - - let mock_ethereum_block = server.mock(|when, then| { - when.path("/") - .method(POST) - .header("Content-type", "application/json") - .body(r#"{"id":0,"jsonrpc":"2.0","method":"eth_getBlockByNumber","params":["finalized",false]}"#); - then.status(200) - .header("Content-type", "application/json") - .body(r#"{"jsonrpc":"2.0","id":0,"result":{"number":"0x1048e0e","hash":"0x9921984fd976f261e0d70618b51e3db3724b9f4d28d0534c3483dd2162f13fff"}}"#); - }); - - let mock_block_number = server.mock(|when, then| { - when.path("/") - .method(POST) - .header("Content-type", "application/json") - .body(r#"{"id":0,"jsonrpc":"2.0","method":"eth_call","params":[{"data":"0x35befa5d","to":"0xc662c410c0ecf747543f5ba90660f6abebd9c8c4","value":"0x0"},{"blockHash":"0x9921984fd976f261e0d70618b51e3db3724b9f4d28d0534c3483dd2162f13fff"}]}"#); - then.status(200) - .header("Content-type", "application/json") - .body(r#"{"jsonrpc":"2.0","id":0,"result":"0x0000000000000000000000000000000000000000000000000000000000007eeb"}"#); - }); - - let mock_block_hash = server.mock(|when, then| { - when.path("/") - .method(POST) - .header("Content-type", "application/json") - .body(r#"{"id":0,"jsonrpc":"2.0","method":"eth_call","params":[{"data":"0x382d83e3","to":"0xc662c410c0ecf747543f5ba90660f6abebd9c8c4","value":"0x0"},{"blockHash":"0x9921984fd976f261e0d70618b51e3db3724b9f4d28d0534c3483dd2162f13fff"}]}"#); - then.status(200) - .header("Content-type", "application/json") - .body(r#"{"jsonrpc":"2.0","id":0,"result":"0x02a4651c1ba5151c48ebeb4477216b04d7a65058a5b99e5fbc602507ae933d2f"}"#); - }); - - let mock_state_root = server.mock(|when, then| { - when.path("/") - .method(POST) - .header("Content-type", "application/json") - .body(r#"{"id":0,"jsonrpc":"2.0","method":"eth_call","params":[{"data":"0x9588eca2","to":"0xc662c410c0ecf747543f5ba90660f6abebd9c8c4","value":"0x0"},{"blockHash":"0x9921984fd976f261e0d70618b51e3db3724b9f4d28d0534c3483dd2162f13fff"}]}"#); - then.status(200) - .header("Content-type", "application/json") - .body(r#"{"jsonrpc":"2.0","id":0,"result":"0x02a4651c1ba5151c48ebeb4477216b04d7a65058a5b99e5fbc602507ae933d2f"}"#); - }); - - let url = Url::parse(&server.url("/"))?; - let eth = EthereumClient::new(url)?; - - let block_number = U256::from_str_radix("0x7eeb", 16)?; - let block_hash = - H256::from_str("0x02a4651c1ba5151c48ebeb4477216b04d7a65058a5b99e5fbc602507ae933d2f")?; - let global_root = - H256::from_str("0x02a4651c1ba5151c48ebeb4477216b04d7a65058a5b99e5fbc602507ae933d2f")?; - let expected = EthereumStateUpdate { - state_root: StateCommitment(get_felt(global_root)?), - block_number: get_number(block_number)?, - block_hash: BlockHash(get_felt(block_hash)?), - }; - - let addr = H160::from_slice(&core_addr::MAINNET); - let state = eth.get_starknet_state(&addr).await?; - - mock_ethereum_block.assert(); - mock_block_number.assert(); - mock_block_hash.assert(); - mock_state_root.assert(); - assert_eq!(state, expected); - Ok(()) - } + /// Get the Ethereum chain + async fn get_chain(&self) -> anyhow::Result { + // Create a WebSocket connection + let ws = WsConnect::new(self.url.clone()); + let provider = ProviderBuilder::new().on_ws(ws).await?; - #[test] - fn test_h256() { - assert!(H256::from_str( - "0x0000000000000000000000000000000000000000000000000000000000007eeb" - ) - .is_ok()); - assert!(H256::from_str("0x7eeb").is_err()); - - let expected = - H256::from_str("0x0000000000000000000000000000000000000000000000000000000000007eeb") - .unwrap(); - assert_eq!(H256::from_str(&lpad64("0x7eeb")).unwrap(), expected); - } + // Get the chain ID + let chain_id = provider.get_chain_id().await?; + let chain_id = U256::from(chain_id); - #[test] - fn test_lpad64() { - for (input, expected) in [ - ( - "0x0000000000000000000000000000000000000000000000000000000000007eeb", - "0x0000000000000000000000000000000000000000000000000000000000007eeb", - ), - ( - "0000000000000000000000000000000000000000000000000000000000007eeb", - "0000000000000000000000000000000000000000000000000000000000007eeb", - ), - ( - "7eeb", - "0000000000000000000000000000000000000000000000000000000000007eeb", - ), - ( - "0x7eeb", - "0x0000000000000000000000000000000000000000000000000000000000007eeb", - ), - ( - "", - "0000000000000000000000000000000000000000000000000000000000000000", - ), - ( - "0x", - "0x0000000000000000000000000000000000000000000000000000000000000000", - ), - ] { - assert_eq!(lpad64(input), expected, "for input: {}", input); - } + // Map the chain ID to the corresponding Ethereum chain + Ok(match chain_id { + x if x == U256::from(1u32) => EthereumChain::Mainnet, + x if x == U256::from(11155111u32) => EthereumChain::Sepolia, + x => EthereumChain::Other(x), + }) } } diff --git a/crates/ethereum/src/starknet.rs b/crates/ethereum/src/starknet.rs new file mode 100644 index 0000000000..d407fef139 --- /dev/null +++ b/crates/ethereum/src/starknet.rs @@ -0,0 +1,29 @@ +alloy::sol!( + #[allow(missing_docs)] + #[sol(rpc)] + StarknetCoreContract, + "abi/starknet_core_contract.json" +); + +impl StarknetCoreContract::LogMessageToL2 { + pub fn message_hash(&self) -> alloy::primitives::U256 { + let mut hash = alloy::primitives::Keccak256::new(); + + // This is an ethereum address: pad the 160 bits to 32 bytes to match a felt. + hash.update([0u8; 12]); + hash.update(self.fromAddress); + hash.update(self.toAddress.to_be_bytes::<32>()); + hash.update(self.nonce.to_be_bytes::<32>()); + hash.update(self.selector.to_be_bytes::<32>()); + + // Pad the u64 to 32 bytes to match a felt. + hash.update([0u8; 24]); + hash.update((self.payload.len() as u64).to_be_bytes()); + + for elem in &self.payload { + hash.update(elem.to_be_bytes::<32>()); + } + + hash.finalize().into() + } +} diff --git a/crates/ethereum/src/utils.rs b/crates/ethereum/src/utils.rs new file mode 100644 index 0000000000..576cc4bb54 --- /dev/null +++ b/crates/ethereum/src/utils.rs @@ -0,0 +1,21 @@ +use pathfinder_common::prelude::*; +use pathfinder_crypto::Felt; + +/// Converts a `Signed<256, 4>` integer to a `BlockNumber` +pub(crate) fn get_block_number(block_number: alloy::primitives::Signed<256, 4>) -> BlockNumber { + let block_number = block_number.as_i64(); + debug_assert!(block_number >= 0, "Received negative block number"); + BlockNumber::new_or_panic(block_number as u64) +} + +/// Converts an `alloy` block hash to a `pathfinder` block hash +pub(crate) fn get_block_hash(block_hash: alloy::primitives::Uint<256, 4>) -> BlockHash { + let bytes: [u8; 32] = block_hash.to_be_bytes(); + BlockHash(bytes.into()) +} + +/// Converts a `Signed<256, 4>` integer to a `StateCommitment` +pub(crate) fn get_state_root(state_root: alloy::primitives::Uint<256, 4>) -> StateCommitment { + let bytes: [u8; 32] = state_root.to_be_bytes(); + StateCommitment(Felt::from(bytes)) +} diff --git a/crates/pathfinder/src/state/sync.rs b/crates/pathfinder/src/state/sync.rs index 93f32d91c8..dbe55a890c 100644 --- a/crates/pathfinder/src/state/sync.rs +++ b/crates/pathfinder/src/state/sync.rs @@ -15,6 +15,7 @@ use pathfinder_common::state_update::{ContractUpdate, SystemContractUpdate}; use pathfinder_common::{ BlockCommitmentSignature, Chain, + L1ToL2MessageHash, PublicKey, ReceiptCommitment, StateDiffCommitment, @@ -67,6 +68,8 @@ pub enum SyncEvent { }, /// A new L2 pending update was polled. Pending((Arc, Arc)), + /// A new L1 to L2 message was finalized. + L1ToL2Message(L1ToL2MessageHash), } pub struct SyncContext { @@ -676,6 +679,7 @@ async fn consumer( tracing::debug!("Updated pending data"); } } + L1ToL2Message(_) => todo!(), } } diff --git a/crates/pathfinder/src/state/sync/l1.rs b/crates/pathfinder/src/state/sync/l1.rs index a3c5efb3e4..dd91125c88 100644 --- a/crates/pathfinder/src/state/sync/l1.rs +++ b/crates/pathfinder/src/state/sync/l1.rs @@ -1,26 +1,27 @@ -use std::num::NonZeroU64; use std::time::Duration; use pathfinder_common::Chain; -use pathfinder_ethereum::{EthereumApi, EthereumStateUpdate}; -use pathfinder_retry::Retry; +use pathfinder_ethereum::{EthereumApi, EthereumEvent}; use primitive_types::H160; use tokio::sync::mpsc; +use tracing::info; use crate::state::sync::SyncEvent; #[derive(Clone)] pub struct L1SyncContext { pub ethereum: EthereumClient, + /// The Ethereum chain to sync from pub chain: Chain, /// The Starknet core contract address on Ethereum pub core_address: H160, + /// The interval at which to poll for updates on finalized blocks pub poll_interval: Duration, } /// Syncs L1 state update logs. Emits [Ethereum state -/// update](EthereumStateUpdate) which should be handled to update storage and -/// respond to queries. +/// update](pathfinder_ethereum::EthereumStateUpdate) which should be handled to +/// update storage and respond to queries. pub async fn sync( tx_event: mpsc::Sender, context: L1SyncContext, @@ -35,23 +36,25 @@ where poll_interval, } = context; - let mut previous = EthereumStateUpdate::default(); - - loop { - let state_update = Retry::exponential( - || async { ethereum.get_starknet_state(&core_address).await }, - NonZeroU64::new(1).unwrap(), - ) - .factor(NonZeroU64::new(2).unwrap()) - .max_delay(poll_interval / 2) - .when(|_| true) + // Listen for state updates and send them to the event channel + let tx_event = std::sync::Arc::new(tx_event); + ethereum + .listen(&core_address, move |event| { + let tx_event = tx_event.clone(); + async move { + match event { + EthereumEvent::StateUpdate(state_update) => { + info!("State update: {:?}", state_update); + let _ = tx_event.send(SyncEvent::L1Update(state_update)).await; + } + EthereumEvent::MessageUpdate(msg_update) => { + info!("Message update: {:?}", msg_update); + //todo!() + } + } + } + }) .await?; - if previous != state_update { - previous = state_update; - tx_event.send(SyncEvent::L1Update(state_update)).await?; - } - - tokio::time::sleep(poll_interval).await; - } + Ok(()) } From b035e21e3280873ee9a8369516f8b3e2f1d0bb02 Mon Sep 17 00:00:00 2001 From: t00ts Date: Fri, 6 Sep 2024 08:22:05 +0200 Subject: [PATCH 025/282] feat(storage): store L1 to L2 message logs --- crates/common/src/lib.rs | 2 +- crates/common/src/message.rs | 8 ++----- crates/ethereum/src/lib.rs | 28 ++++------------------ crates/pathfinder/src/state/sync.rs | 17 ++++++++++--- crates/pathfinder/src/state/sync/l1.rs | 6 ++--- crates/storage/src/connection.rs | 1 + crates/storage/src/connection/message.rs | 22 +++++++++++++++++ crates/storage/src/schema.rs | 2 ++ crates/storage/src/schema/revision_0063.rs | 16 +++++++++++++ 9 files changed, 66 insertions(+), 36 deletions(-) create mode 100644 crates/storage/src/connection/message.rs create mode 100644 crates/storage/src/schema/revision_0063.rs diff --git a/crates/common/src/lib.rs b/crates/common/src/lib.rs index fceb01cc1f..d80564b6f5 100644 --- a/crates/common/src/lib.rs +++ b/crates/common/src/lib.rs @@ -30,7 +30,7 @@ pub mod transaction; pub mod trie; pub use header::{BlockHeader, BlockHeaderBuilder, L1DataAvailabilityMode, SignedBlockHeader}; -pub use message::L1ToL2MessageHash; +pub use message::L1ToL2MessageLog; pub use signature::BlockCommitmentSignature; pub use state_update::StateUpdate; diff --git a/crates/common/src/message.rs b/crates/common/src/message.rs index 63c91ab624..6a3ede47ed 100644 --- a/crates/common/src/message.rs +++ b/crates/common/src/message.rs @@ -1,12 +1,8 @@ use primitive_types::H256; -use crate::BlockNumber; - -/// An L1 -> L2 message hash with the block and tx where it was sent +/// An L1 -> L2 message hash with the L1 tx hash where it was sent #[derive(Debug, Clone)] -pub struct L1ToL2MessageHash { +pub struct L1ToL2MessageLog { pub message_hash: H256, pub l1_tx_hash: H256, - pub l1_block_number: BlockNumber, - pub is_finalized: bool, } diff --git a/crates/ethereum/src/lib.rs b/crates/ethereum/src/lib.rs index 6fb16e3e16..5b961016cf 100644 --- a/crates/ethereum/src/lib.rs +++ b/crates/ethereum/src/lib.rs @@ -6,13 +6,7 @@ use alloy::providers::{Provider, ProviderBuilder, WsConnect}; use alloy::rpc::types::Log; use anyhow::Context; use futures::StreamExt; -use pathfinder_common::{ - BlockHash, - BlockNumber, - EthereumChain, - L1ToL2MessageHash, - StateCommitment, -}; +use pathfinder_common::{BlockHash, BlockNumber, EthereumChain, L1ToL2MessageLog, StateCommitment}; use primitive_types::{H160, H256, U256}; use reqwest::{IntoUrl, Url}; use starknet::StarknetCoreContract; @@ -44,15 +38,7 @@ pub mod core_addr { #[derive(Debug)] pub enum EthereumEvent { StateUpdate(EthereumStateUpdate), - MessageUpdate(MessageUpdate), -} - -/// Message update from Ethereum -#[derive(Debug)] -pub enum MessageUpdate { - Sent(L1ToL2MessageHash), - Finalized(L1ToL2MessageHash), - Reverted(L1ToL2MessageHash), + MessageLog(L1ToL2MessageLog), } /// State update from Ethereum @@ -112,7 +98,6 @@ impl EthereumClient { }) .context("Failed to fetch finalized block hash") } - } #[async_trait::async_trait] @@ -164,18 +149,15 @@ impl EthereumApi for EthereumClient { // Decode the message let log: Log = log.log_decode()?; - let l1_block_number = log.block_number.context("Block number not found")?; // Create L1ToL2MessageHash from the log data - let msg = L1ToL2MessageHash { + let msg = L1ToL2MessageLog { message_hash: H256::from(log.inner.message_hash().to_be_bytes()), l1_tx_hash: log.transaction_hash.map(|hash| H256::from(hash.0)).unwrap_or_default(), - l1_block_number: BlockNumber::new_or_panic(l1_block_number), - is_finalized: false, }; - // TODO: All actions need to be done accordingly (e.g. reorgs and finalizations) - callback(EthereumEvent::MessageUpdate(MessageUpdate::Sent(msg))).await; + // Emit the message log + callback(EthereumEvent::MessageLog(msg)).await; } } diff --git a/crates/pathfinder/src/state/sync.rs b/crates/pathfinder/src/state/sync.rs index dbe55a890c..0a327acbcf 100644 --- a/crates/pathfinder/src/state/sync.rs +++ b/crates/pathfinder/src/state/sync.rs @@ -15,7 +15,7 @@ use pathfinder_common::state_update::{ContractUpdate, SystemContractUpdate}; use pathfinder_common::{ BlockCommitmentSignature, Chain, - L1ToL2MessageHash, + L1ToL2MessageLog, PublicKey, ReceiptCommitment, StateDiffCommitment, @@ -69,7 +69,7 @@ pub enum SyncEvent { /// A new L2 pending update was polled. Pending((Arc, Arc)), /// A new L1 to L2 message was finalized. - L1ToL2Message(L1ToL2MessageHash), + L1ToL2Message(L1ToL2MessageLog), } pub struct SyncContext { @@ -679,7 +679,18 @@ async fn consumer( tracing::debug!("Updated pending data"); } } - L1ToL2Message(_) => todo!(), + L1ToL2Message(msg) => { + tracing::trace!("Inserting new L1 to L2 message log: {:?}", msg); + tokio::task::block_in_place(|| { + let tx = db_conn + .transaction() + .context("Creating database transaction")?; + tx.insert_l1_to_l2_message_log(&msg) + .context("Inserting L1 to L2 message log")?; + tx.commit().context("Committing database transaction") + }) + .with_context(|| format!("Insert L1 to L2 message log: {:?}", msg))?; + } } } diff --git a/crates/pathfinder/src/state/sync/l1.rs b/crates/pathfinder/src/state/sync/l1.rs index dd91125c88..7efacb9d75 100644 --- a/crates/pathfinder/src/state/sync/l1.rs +++ b/crates/pathfinder/src/state/sync/l1.rs @@ -47,9 +47,9 @@ where info!("State update: {:?}", state_update); let _ = tx_event.send(SyncEvent::L1Update(state_update)).await; } - EthereumEvent::MessageUpdate(msg_update) => { - info!("Message update: {:?}", msg_update); - //todo!() + EthereumEvent::MessageLog(log) => { + info!("Message log: {:?}", log); + let _ = tx_event.send(SyncEvent::L1ToL2Message(log)).await; } } } diff --git a/crates/storage/src/connection.rs b/crates/storage/src/connection.rs index b46f035a7a..563de4f0ab 100644 --- a/crates/storage/src/connection.rs +++ b/crates/storage/src/connection.rs @@ -4,6 +4,7 @@ mod block; mod class; mod ethereum; mod event; +mod message; mod reference; mod reorg_counter; mod signature; diff --git a/crates/storage/src/connection/message.rs b/crates/storage/src/connection/message.rs new file mode 100644 index 0000000000..a0dd3e5053 --- /dev/null +++ b/crates/storage/src/connection/message.rs @@ -0,0 +1,22 @@ +use anyhow::Context; +use pathfinder_common::L1ToL2MessageLog; +use primitive_types::H256; + +use super::Transaction; +use crate::prelude::*; + +impl Transaction<'_> { + pub fn insert_l1_to_l2_message_log(&self, message: &L1ToL2MessageLog) -> anyhow::Result<()> { + self.inner() + .execute( + "INSERT INTO l1_to_l2_message_logs (msg_hash, l1_tx_hash) VALUES (?, ?)", + params![ + &message.message_hash.as_bytes(), + &message.l1_tx_hash.as_bytes(), + ], + ) + .context("Inserting L1 to L2 message log")?; + Ok(()) + } + +} diff --git a/crates/storage/src/schema.rs b/crates/storage/src/schema.rs index 9917f7d177..3542ab73b2 100644 --- a/crates/storage/src/schema.rs +++ b/crates/storage/src/schema.rs @@ -22,6 +22,7 @@ mod revision_0059; mod revision_0060; mod revision_0061; mod revision_0062; +mod revision_0063; pub(crate) use base::base_schema; @@ -52,6 +53,7 @@ pub fn migrations() -> &'static [MigrationFn] { revision_0060::migrate, revision_0061::migrate, revision_0062::migrate, + revision_0063::migrate, ] } diff --git a/crates/storage/src/schema/revision_0063.rs b/crates/storage/src/schema/revision_0063.rs new file mode 100644 index 0000000000..17dca7ba07 --- /dev/null +++ b/crates/storage/src/schema/revision_0063.rs @@ -0,0 +1,16 @@ +use anyhow::Context; + +pub(crate) fn migrate(tx: &rusqlite::Transaction<'_>) -> anyhow::Result<()> { + tracing::info!("Adding table(s) to store L1->L2 message data"); + + tx.execute_batch( + r" + CREATE TABLE l1_to_l2_message_logs ( + msg_hash BLOB NOT NULL PRIMARY KEY, + l1_tx_hash BLOB NOT NULL + );", + ) + .context("Adding table to store L1 to L2 message data")?; + + Ok(()) +} From db088ac14c030d2904c9508d8510b286da103b22 Mon Sep 17 00:00:00 2001 From: t00ts Date: Fri, 6 Sep 2024 12:54:48 +0200 Subject: [PATCH 026/282] feat(rpc): store l1_handler <-> l2 tx mapping for `starknet_getMessagesStatus` --- crates/ethereum/src/lib.rs | 2 +- crates/pathfinder/src/state/sync.rs | 12 +++++++++ crates/storage/src/connection.rs | 1 + crates/storage/src/connection/l1_handler.rs | 22 +++++++++++++++ crates/storage/src/connection/message.rs | 30 +++++++++++++++++++++ crates/storage/src/schema/revision_0063.rs | 11 ++++++++ 6 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 crates/storage/src/connection/l1_handler.rs diff --git a/crates/ethereum/src/lib.rs b/crates/ethereum/src/lib.rs index 5b961016cf..a298c01468 100644 --- a/crates/ethereum/src/lib.rs +++ b/crates/ethereum/src/lib.rs @@ -189,7 +189,7 @@ impl EthereumApi for EthereumClient { // Return the state update Ok(EthereumStateUpdate { state_root: get_state_root(state_root._0), - block_hash: get_block_hash(block_hash._0.into()), + block_hash: get_block_hash(block_hash._0), block_number: get_block_number(block_number._0), }) } diff --git a/crates/pathfinder/src/state/sync.rs b/crates/pathfinder/src/state/sync.rs index 0a327acbcf..1f03ae3edb 100644 --- a/crates/pathfinder/src/state/sync.rs +++ b/crates/pathfinder/src/state/sync.rs @@ -12,6 +12,7 @@ use std::time::{Duration, Instant}; use anyhow::Context; use pathfinder_common::prelude::*; use pathfinder_common::state_update::{ContractUpdate, SystemContractUpdate}; +use pathfinder_common::transaction::TransactionVariant; use pathfinder_common::{ BlockCommitmentSignature, Chain, @@ -948,6 +949,17 @@ async fn l2_update( .insert_transaction_data(header.number, &transactions_data, Some(&events_data)) .context("Insert transaction data into database")?; + // Associate L1 handler transactions with L2 transactions + for tx in &block.transactions { + if let TransactionVariant::L1Handler(l1_handler_tx) = &tx.variant { + if let Some(l1_tx_hash) = transaction.fetch_and_remove_l1_to_l2_message_log( + &l1_handler_tx.calculate_message_hash(), + )? { + transaction.insert_l1_handler_tx(&l1_tx_hash, &tx.hash)?; + } + } + } + // Insert state updates transaction .insert_state_update(block.block_number, &state_update) diff --git a/crates/storage/src/connection.rs b/crates/storage/src/connection.rs index 563de4f0ab..6a21b45afe 100644 --- a/crates/storage/src/connection.rs +++ b/crates/storage/src/connection.rs @@ -4,6 +4,7 @@ mod block; mod class; mod ethereum; mod event; +mod l1_handler; mod message; mod reference; mod reorg_counter; diff --git a/crates/storage/src/connection/l1_handler.rs b/crates/storage/src/connection/l1_handler.rs new file mode 100644 index 0000000000..1347543e67 --- /dev/null +++ b/crates/storage/src/connection/l1_handler.rs @@ -0,0 +1,22 @@ +use anyhow::Context; +use pathfinder_common::TransactionHash; +use primitive_types::H256; + +use super::Transaction; +use crate::prelude::*; + +impl Transaction<'_> { + pub fn insert_l1_handler_tx( + &self, + l1_tx_hash: &H256, + l2_tx_hash: &TransactionHash, + ) -> anyhow::Result<()> { + self.inner() + .execute( + "INSERT INTO l1_handler_txs (l1_tx_hash, l2_tx_hash) VALUES (?, ?)", + params![&l1_tx_hash.as_bytes(), l2_tx_hash,], + ) + .context("Inserting L1 handler tx")?; + Ok(()) + } +} diff --git a/crates/storage/src/connection/message.rs b/crates/storage/src/connection/message.rs index a0dd3e5053..51553fa7ac 100644 --- a/crates/storage/src/connection/message.rs +++ b/crates/storage/src/connection/message.rs @@ -6,6 +6,7 @@ use super::Transaction; use crate::prelude::*; impl Transaction<'_> { + /// Inserts an L1 to L2 message log into the database. pub fn insert_l1_to_l2_message_log(&self, message: &L1ToL2MessageLog) -> anyhow::Result<()> { self.inner() .execute( @@ -19,4 +20,33 @@ impl Transaction<'_> { Ok(()) } + /// Fetches the L1 tx hash for a given message hash and removes the entry + /// from the database. + pub fn fetch_and_remove_l1_to_l2_message_log( + &self, + message_hash: &H256, + ) -> anyhow::Result> { + let mut stmt = self + .inner() + .prepare_cached("SELECT l1_tx_hash FROM l1_to_l2_message_logs WHERE msg_hash = ?") + .context("Preparing fetch L1 to L2 message log statement")?; + + let message = stmt + .query_row(params![&message_hash.as_bytes().to_vec()], |row| { + Ok(H256::from_slice(&row.get::<_, Vec>(0)?)) + }) + .optional() + .context("Querying L1 to L2 message log")?; + + if message.is_some() { + self.inner() + .execute( + "DELETE FROM l1_to_l2_message_logs WHERE msg_hash = ?", + params![&message_hash.as_bytes().to_vec()], + ) + .context("Deleting L1 to L2 message log")?; + } + + Ok(message) + } } diff --git a/crates/storage/src/schema/revision_0063.rs b/crates/storage/src/schema/revision_0063.rs index 17dca7ba07..75bc05da95 100644 --- a/crates/storage/src/schema/revision_0063.rs +++ b/crates/storage/src/schema/revision_0063.rs @@ -12,5 +12,16 @@ pub(crate) fn migrate(tx: &rusqlite::Transaction<'_>) -> anyhow::Result<()> { ) .context("Adding table to store L1 to L2 message data")?; + tx.execute_batch( + r" + CREATE TABLE l1_handler_txs ( + l1_tx_hash BLOB NOT NULL, + l2_tx_hash BLOB NOT NULL + ); + CREATE INDEX idx_l1_handler_txs_l1_tx_hash ON l1_handler_txs(l1_tx_hash); + ", + ) + .context("Adding table and index to store L1 handler tx data")?; + Ok(()) } From 00a987cd65041e1339b28dab600177b16cae5ca4 Mon Sep 17 00:00:00 2001 From: t00ts Date: Fri, 6 Sep 2024 13:33:47 +0200 Subject: [PATCH 027/282] refactor(ethereum): remove L1 poll interval from L1 sync context --- crates/pathfinder/src/state/sync.rs | 1 - crates/pathfinder/src/state/sync/l1.rs | 5 ----- 2 files changed, 6 deletions(-) diff --git a/crates/pathfinder/src/state/sync.rs b/crates/pathfinder/src/state/sync.rs index 1f03ae3edb..e400f1feea 100644 --- a/crates/pathfinder/src/state/sync.rs +++ b/crates/pathfinder/src/state/sync.rs @@ -103,7 +103,6 @@ where ethereum: value.ethereum.clone(), chain: value.chain, core_address: value.core_address, - poll_interval: value.l1_poll_interval, } } } diff --git a/crates/pathfinder/src/state/sync/l1.rs b/crates/pathfinder/src/state/sync/l1.rs index 7efacb9d75..d3acb8269c 100644 --- a/crates/pathfinder/src/state/sync/l1.rs +++ b/crates/pathfinder/src/state/sync/l1.rs @@ -1,5 +1,3 @@ -use std::time::Duration; - use pathfinder_common::Chain; use pathfinder_ethereum::{EthereumApi, EthereumEvent}; use primitive_types::H160; @@ -15,8 +13,6 @@ pub struct L1SyncContext { pub chain: Chain, /// The Starknet core contract address on Ethereum pub core_address: H160, - /// The interval at which to poll for updates on finalized blocks - pub poll_interval: Duration, } /// Syncs L1 state update logs. Emits [Ethereum state @@ -33,7 +29,6 @@ where ethereum, chain: _, core_address, - poll_interval, } = context; // Listen for state updates and send them to the event channel From d03d02326c28c04aabed1299b588896ab218ba8e Mon Sep 17 00:00:00 2001 From: t00ts Date: Mon, 9 Sep 2024 15:04:10 +0200 Subject: [PATCH 028/282] refactor: address PR comments --- crates/ethereum/src/lib.rs | 9 -------- crates/ethereum/src/utils.rs | 5 ++-- crates/pathfinder/src/state/sync.rs | 12 ---------- crates/storage/src/connection.rs | 1 - crates/storage/src/connection/l1_handler.rs | 22 ------------------ crates/storage/src/connection/transaction.rs | 24 ++++++++++++++++++-- 6 files changed, 24 insertions(+), 49 deletions(-) delete mode 100644 crates/storage/src/connection/l1_handler.rs diff --git a/crates/ethereum/src/lib.rs b/crates/ethereum/src/lib.rs index a298c01468..83f23d7fd8 100644 --- a/crates/ethereum/src/lib.rs +++ b/crates/ethereum/src/lib.rs @@ -132,7 +132,6 @@ impl EthereumApi for EthereumClient { loop { select! { Some(state_update) = state_updates.next() => { - // Decode the state update let state_update: Log = state_update.log_decode()?; let state_update = EthereumStateUpdate { @@ -140,30 +139,22 @@ impl EthereumApi for EthereumClient { block_hash: get_block_hash(state_update.inner.blockHash), state_root: get_state_root(state_update.inner.globalRoot), }; - // Emit the state update callback(EthereumEvent::StateUpdate(state_update)).await; - } Some(log) = logs.next() => { - // Decode the message let log: Log = log.log_decode()?; - // Create L1ToL2MessageHash from the log data let msg = L1ToL2MessageLog { message_hash: H256::from(log.inner.message_hash().to_be_bytes()), l1_tx_hash: log.transaction_hash.map(|hash| H256::from(hash.0)).unwrap_or_default(), }; - // Emit the message log callback(EthereumEvent::MessageLog(msg)).await; - } } } - - //anyhow::bail!("Ethereum client stopped unexpectedly") } /// Get the Starknet state diff --git a/crates/ethereum/src/utils.rs b/crates/ethereum/src/utils.rs index 576cc4bb54..79261d0407 100644 --- a/crates/ethereum/src/utils.rs +++ b/crates/ethereum/src/utils.rs @@ -3,9 +3,8 @@ use pathfinder_crypto::Felt; /// Converts a `Signed<256, 4>` integer to a `BlockNumber` pub(crate) fn get_block_number(block_number: alloy::primitives::Signed<256, 4>) -> BlockNumber { - let block_number = block_number.as_i64(); - debug_assert!(block_number >= 0, "Received negative block number"); - BlockNumber::new_or_panic(block_number as u64) + let block_number = block_number.as_u64(); + BlockNumber::new_or_panic(block_number) } /// Converts an `alloy` block hash to a `pathfinder` block hash diff --git a/crates/pathfinder/src/state/sync.rs b/crates/pathfinder/src/state/sync.rs index e400f1feea..ad15b47dc3 100644 --- a/crates/pathfinder/src/state/sync.rs +++ b/crates/pathfinder/src/state/sync.rs @@ -12,7 +12,6 @@ use std::time::{Duration, Instant}; use anyhow::Context; use pathfinder_common::prelude::*; use pathfinder_common::state_update::{ContractUpdate, SystemContractUpdate}; -use pathfinder_common::transaction::TransactionVariant; use pathfinder_common::{ BlockCommitmentSignature, Chain, @@ -948,17 +947,6 @@ async fn l2_update( .insert_transaction_data(header.number, &transactions_data, Some(&events_data)) .context("Insert transaction data into database")?; - // Associate L1 handler transactions with L2 transactions - for tx in &block.transactions { - if let TransactionVariant::L1Handler(l1_handler_tx) = &tx.variant { - if let Some(l1_tx_hash) = transaction.fetch_and_remove_l1_to_l2_message_log( - &l1_handler_tx.calculate_message_hash(), - )? { - transaction.insert_l1_handler_tx(&l1_tx_hash, &tx.hash)?; - } - } - } - // Insert state updates transaction .insert_state_update(block.block_number, &state_update) diff --git a/crates/storage/src/connection.rs b/crates/storage/src/connection.rs index 6a21b45afe..563de4f0ab 100644 --- a/crates/storage/src/connection.rs +++ b/crates/storage/src/connection.rs @@ -4,7 +4,6 @@ mod block; mod class; mod ethereum; mod event; -mod l1_handler; mod message; mod reference; mod reorg_counter; diff --git a/crates/storage/src/connection/l1_handler.rs b/crates/storage/src/connection/l1_handler.rs deleted file mode 100644 index 1347543e67..0000000000 --- a/crates/storage/src/connection/l1_handler.rs +++ /dev/null @@ -1,22 +0,0 @@ -use anyhow::Context; -use pathfinder_common::TransactionHash; -use primitive_types::H256; - -use super::Transaction; -use crate::prelude::*; - -impl Transaction<'_> { - pub fn insert_l1_handler_tx( - &self, - l1_tx_hash: &H256, - l2_tx_hash: &TransactionHash, - ) -> anyhow::Result<()> { - self.inner() - .execute( - "INSERT INTO l1_handler_txs (l1_tx_hash, l2_tx_hash) VALUES (?, ?)", - params![&l1_tx_hash.as_bytes(), l2_tx_hash,], - ) - .context("Inserting L1 handler tx")?; - Ok(()) - } -} diff --git a/crates/storage/src/connection/transaction.rs b/crates/storage/src/connection/transaction.rs index 1f68feaf43..34afba9d96 100644 --- a/crates/storage/src/connection/transaction.rs +++ b/crates/storage/src/connection/transaction.rs @@ -3,7 +3,7 @@ use anyhow::Context; use pathfinder_common::event::Event; use pathfinder_common::receipt::Receipt; -use pathfinder_common::transaction::Transaction as StarknetTransaction; +use pathfinder_common::transaction::{Transaction as StarknetTransaction, TransactionVariant}; use pathfinder_common::{BlockHash, BlockNumber, TransactionHash}; use super::{EventsForBlock, TransactionDataForBlock, TransactionWithReceipt}; @@ -117,6 +117,13 @@ impl Transaction<'_> { :block_number, :idx)", ) .context("Preparing insert transaction hash statement")?; + let mut insert_l1_handler_tx_stmt = self + .inner() + .prepare_cached( + "INSERT INTO l1_handler_txs (l1_tx_hash, l2_tx_hash) VALUES (:l1_tx_hash, \ + :l2_tx_hash)", + ) + .context("Preparing insert L1 handler tx statement")?; for (idx, (transaction, ..)) in transactions.iter().enumerate() { let idx: i64 = idx.try_into()?; @@ -126,7 +133,6 @@ impl Transaction<'_> { ":idx": &idx, ])?; } - let transactions_with_receipts: Vec<_> = transactions .iter() .map(|(transaction, receipt)| dto::TransactionWithReceiptV2 { @@ -173,6 +179,20 @@ impl Transaction<'_> { .context("Inserting events into Bloom filter")?; } + // Associate L1 handler transactions with L2 transactions + for (transaction, _) in transactions.iter() { + if let TransactionVariant::L1Handler(l1_handler_tx) = &transaction.variant { + if let Some(l1_tx_hash) = self.fetch_and_remove_l1_to_l2_message_log( + &l1_handler_tx.calculate_message_hash(), + )? { + insert_l1_handler_tx_stmt.execute(named_params![ + ":l1_tx_hash": &l1_tx_hash.as_bytes(), + ":l2_tx_hash": &transaction.hash, + ])?; + } + } + } + Ok(()) } From 7c50da4ea5ea5f2e7c15544913ea35afbdc97470 Mon Sep 17 00:00:00 2001 From: t00ts Date: Tue, 10 Sep 2024 12:53:55 +0200 Subject: [PATCH 029/282] refactor(ethereum): re-introduce L1 poll interval and just emit State Updates when they belong in a finalized block --- crates/ethereum/src/lib.rs | 58 +++++++++++++++++-- .../pathfinder/src/bin/pathfinder/config.rs | 2 +- crates/pathfinder/src/state/sync.rs | 1 + crates/pathfinder/src/state/sync/l1.rs | 9 ++- 4 files changed, 62 insertions(+), 8 deletions(-) diff --git a/crates/ethereum/src/lib.rs b/crates/ethereum/src/lib.rs index 83f23d7fd8..292eb6ada9 100644 --- a/crates/ethereum/src/lib.rs +++ b/crates/ethereum/src/lib.rs @@ -1,4 +1,6 @@ +use std::collections::BTreeMap; use std::future::Future; +use std::time::Duration; use alloy::eips::{BlockId, BlockNumberOrTag, RpcBlockHash}; use alloy::primitives::{Address, B256}; @@ -54,7 +56,12 @@ pub struct EthereumStateUpdate { pub trait EthereumApi { async fn get_starknet_state(&self, address: &H160) -> anyhow::Result; async fn get_chain(&self) -> anyhow::Result; - async fn listen(&self, address: &H160, callback: F) -> anyhow::Result<()> + async fn listen( + &mut self, + address: &H160, + poll_interval: Duration, + callback: F, + ) -> anyhow::Result<()> where F: Fn(EthereumEvent) -> Fut + Send + 'static, Fut: Future + Send + 'static; @@ -64,6 +71,7 @@ pub trait EthereumApi { #[derive(Clone, Debug)] pub struct EthereumClient { url: Url, + pending_state_updates: BTreeMap, } impl EthereumClient { @@ -71,6 +79,7 @@ impl EthereumClient { pub fn new(url: U) -> anyhow::Result { Ok(Self { url: url.into_url()?, + pending_state_updates: BTreeMap::new(), }) } @@ -103,8 +112,14 @@ impl EthereumClient { #[async_trait::async_trait] impl EthereumApi for EthereumClient { /// Listens for Ethereum events and notifies the caller using the provided - /// callback - async fn listen(&self, address: &H160, callback: F) -> anyhow::Result<()> + /// callback. State updates will only be emitted once they belong to a + /// finalized block. + async fn listen( + &mut self, + address: &H160, + poll_interval: Duration, + callback: F, + ) -> anyhow::Result<()> where F: Fn(EthereumEvent) -> Fut + Send + 'static, Fut: Future + Send + 'static, @@ -129,18 +144,38 @@ impl EthereumApi for EthereumClient { .await? .into_stream(); + // Poll regularly for finalized block number + let provider_clone = provider.clone(); + let (finalized_block_tx, mut finalized_block_rx) = + tokio::sync::mpsc::channel::(1); + tokio::spawn(async move { + let mut interval = tokio::time::interval(poll_interval); + loop { + interval.tick().await; + if let Ok(Some(finalized_block)) = provider_clone + .get_block_by_number(BlockNumberOrTag::Finalized, false) + .await + { + let block_number = BlockNumber::new_or_panic(finalized_block.header.number); + let _ = finalized_block_tx.send(block_number).await.unwrap(); + } + } + }); + + // Add the finalized block stream to the select! macro loop { select! { Some(state_update) = state_updates.next() => { // Decode the state update + let eth_block = state_update.block_number.expect("missing eth block number"); let state_update: Log = state_update.log_decode()?; let state_update = EthereumStateUpdate { block_number: get_block_number(state_update.inner.blockNumber), block_hash: get_block_hash(state_update.inner.blockHash), state_root: get_state_root(state_update.inner.globalRoot), }; - // Emit the state update - callback(EthereumEvent::StateUpdate(state_update)).await; + // Store state update + let _ = self.pending_state_updates.insert(eth_block, state_update); } Some(log) = logs.next() => { // Decode the message @@ -153,6 +188,19 @@ impl EthereumApi for EthereumClient { // Emit the message log callback(EthereumEvent::MessageLog(msg)).await; } + Some(block_number) = finalized_block_rx.recv() => { + // Collect all state updates up to (and including) the finalized block + let pending_state_updates: Vec = self.pending_state_updates + .range(..=block_number.get()) + .map(|(_, &update)| update) + .collect(); + // Remove emitted updates from the map + self.pending_state_updates.retain(|&k, _| k > block_number.get()); + // Emit the state updates + for state_update in pending_state_updates { + callback(EthereumEvent::StateUpdate(state_update)).await; + } + } } } } diff --git a/crates/pathfinder/src/bin/pathfinder/config.rs b/crates/pathfinder/src/bin/pathfinder/config.rs index 1a2c60563c..c42dc1b1b7 100644 --- a/crates/pathfinder/src/bin/pathfinder/config.rs +++ b/crates/pathfinder/src/bin/pathfinder/config.rs @@ -135,7 +135,7 @@ Examples: #[arg( long = "sync.l1-poll-interval", long_help = "L1 state poll interval in seconds", - default_value = "30", + default_value = "120", env = "PATHFINDER_L1_POLL_INTERVAL_SECONDS" )] l1_poll_interval: std::num::NonZeroU64, diff --git a/crates/pathfinder/src/state/sync.rs b/crates/pathfinder/src/state/sync.rs index ad15b47dc3..0a327acbcf 100644 --- a/crates/pathfinder/src/state/sync.rs +++ b/crates/pathfinder/src/state/sync.rs @@ -102,6 +102,7 @@ where ethereum: value.ethereum.clone(), chain: value.chain, core_address: value.core_address, + poll_interval: value.l1_poll_interval, } } } diff --git a/crates/pathfinder/src/state/sync/l1.rs b/crates/pathfinder/src/state/sync/l1.rs index d3acb8269c..f69b53b287 100644 --- a/crates/pathfinder/src/state/sync/l1.rs +++ b/crates/pathfinder/src/state/sync/l1.rs @@ -1,3 +1,5 @@ +use std::time::Duration; + use pathfinder_common::Chain; use pathfinder_ethereum::{EthereumApi, EthereumEvent}; use primitive_types::H160; @@ -13,6 +15,8 @@ pub struct L1SyncContext { pub chain: Chain, /// The Starknet core contract address on Ethereum pub core_address: H160, + /// The interval at which to poll for updates on finalized blocks + pub poll_interval: Duration, } /// Syncs L1 state update logs. Emits [Ethereum state @@ -26,15 +30,16 @@ where T: EthereumApi + Clone, { let L1SyncContext { - ethereum, + mut ethereum, chain: _, core_address, + poll_interval, } = context; // Listen for state updates and send them to the event channel let tx_event = std::sync::Arc::new(tx_event); ethereum - .listen(&core_address, move |event| { + .listen(&core_address, poll_interval, move |event| { let tx_event = tx_event.clone(); async move { match event { From 740d925efbb1183989483036967182ebdec8aa83 Mon Sep 17 00:00:00 2001 From: t00ts Date: Tue, 10 Sep 2024 13:10:58 +0200 Subject: [PATCH 030/282] refactor(ethereum): remove pending state updates as per the `removed` flag --- crates/ethereum/src/lib.rs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/crates/ethereum/src/lib.rs b/crates/ethereum/src/lib.rs index 292eb6ada9..d54c67228d 100644 --- a/crates/ethereum/src/lib.rs +++ b/crates/ethereum/src/lib.rs @@ -169,13 +169,18 @@ impl EthereumApi for EthereumClient { // Decode the state update let eth_block = state_update.block_number.expect("missing eth block number"); let state_update: Log = state_update.log_decode()?; - let state_update = EthereumStateUpdate { - block_number: get_block_number(state_update.inner.blockNumber), - block_hash: get_block_hash(state_update.inner.blockHash), - state_root: get_state_root(state_update.inner.globalRoot), - }; - // Store state update - let _ = self.pending_state_updates.insert(eth_block, state_update); + let block_number = get_block_number(state_update.inner.blockNumber); + // Add or remove to/from pending state updates accordingly + if !state_update.removed { + let state_update = EthereumStateUpdate { + block_number, + block_hash: get_block_hash(state_update.inner.blockHash), + state_root: get_state_root(state_update.inner.globalRoot), + }; + self.pending_state_updates.insert(eth_block, state_update); + } else { + self.pending_state_updates.remove(ð_block); + } } Some(log) = logs.next() => { // Decode the message From ac6dd5e5e8ea56abd08ccabd52c1885711dd757d Mon Sep 17 00:00:00 2001 From: t00ts Date: Tue, 10 Sep 2024 14:51:19 +0200 Subject: [PATCH 031/282] revert: remove L1->L2 log related changes for future PR --- crates/pathfinder/src/state/sync.rs | 12 +---- crates/storage/src/connection.rs | 1 - crates/storage/src/connection/message.rs | 52 -------------------- crates/storage/src/connection/transaction.rs | 23 +-------- crates/storage/src/schema.rs | 2 - crates/storage/src/schema/revision_0063.rs | 27 ---------- 6 files changed, 3 insertions(+), 114 deletions(-) delete mode 100644 crates/storage/src/connection/message.rs delete mode 100644 crates/storage/src/schema/revision_0063.rs diff --git a/crates/pathfinder/src/state/sync.rs b/crates/pathfinder/src/state/sync.rs index 0a327acbcf..326f905b1d 100644 --- a/crates/pathfinder/src/state/sync.rs +++ b/crates/pathfinder/src/state/sync.rs @@ -680,16 +680,8 @@ async fn consumer( } } L1ToL2Message(msg) => { - tracing::trace!("Inserting new L1 to L2 message log: {:?}", msg); - tokio::task::block_in_place(|| { - let tx = db_conn - .transaction() - .context("Creating database transaction")?; - tx.insert_l1_to_l2_message_log(&msg) - .context("Inserting L1 to L2 message log")?; - tx.commit().context("Committing database transaction") - }) - .with_context(|| format!("Insert L1 to L2 message log: {:?}", msg))?; + tracing::trace!("Got a new L1 to L2 message log: {:?}", msg); + // todo!() } } } diff --git a/crates/storage/src/connection.rs b/crates/storage/src/connection.rs index 563de4f0ab..b46f035a7a 100644 --- a/crates/storage/src/connection.rs +++ b/crates/storage/src/connection.rs @@ -4,7 +4,6 @@ mod block; mod class; mod ethereum; mod event; -mod message; mod reference; mod reorg_counter; mod signature; diff --git a/crates/storage/src/connection/message.rs b/crates/storage/src/connection/message.rs deleted file mode 100644 index 51553fa7ac..0000000000 --- a/crates/storage/src/connection/message.rs +++ /dev/null @@ -1,52 +0,0 @@ -use anyhow::Context; -use pathfinder_common::L1ToL2MessageLog; -use primitive_types::H256; - -use super::Transaction; -use crate::prelude::*; - -impl Transaction<'_> { - /// Inserts an L1 to L2 message log into the database. - pub fn insert_l1_to_l2_message_log(&self, message: &L1ToL2MessageLog) -> anyhow::Result<()> { - self.inner() - .execute( - "INSERT INTO l1_to_l2_message_logs (msg_hash, l1_tx_hash) VALUES (?, ?)", - params![ - &message.message_hash.as_bytes(), - &message.l1_tx_hash.as_bytes(), - ], - ) - .context("Inserting L1 to L2 message log")?; - Ok(()) - } - - /// Fetches the L1 tx hash for a given message hash and removes the entry - /// from the database. - pub fn fetch_and_remove_l1_to_l2_message_log( - &self, - message_hash: &H256, - ) -> anyhow::Result> { - let mut stmt = self - .inner() - .prepare_cached("SELECT l1_tx_hash FROM l1_to_l2_message_logs WHERE msg_hash = ?") - .context("Preparing fetch L1 to L2 message log statement")?; - - let message = stmt - .query_row(params![&message_hash.as_bytes().to_vec()], |row| { - Ok(H256::from_slice(&row.get::<_, Vec>(0)?)) - }) - .optional() - .context("Querying L1 to L2 message log")?; - - if message.is_some() { - self.inner() - .execute( - "DELETE FROM l1_to_l2_message_logs WHERE msg_hash = ?", - params![&message_hash.as_bytes().to_vec()], - ) - .context("Deleting L1 to L2 message log")?; - } - - Ok(message) - } -} diff --git a/crates/storage/src/connection/transaction.rs b/crates/storage/src/connection/transaction.rs index 34afba9d96..7cdfbd5144 100644 --- a/crates/storage/src/connection/transaction.rs +++ b/crates/storage/src/connection/transaction.rs @@ -3,7 +3,7 @@ use anyhow::Context; use pathfinder_common::event::Event; use pathfinder_common::receipt::Receipt; -use pathfinder_common::transaction::{Transaction as StarknetTransaction, TransactionVariant}; +use pathfinder_common::transaction::Transaction as StarknetTransaction; use pathfinder_common::{BlockHash, BlockNumber, TransactionHash}; use super::{EventsForBlock, TransactionDataForBlock, TransactionWithReceipt}; @@ -117,13 +117,6 @@ impl Transaction<'_> { :block_number, :idx)", ) .context("Preparing insert transaction hash statement")?; - let mut insert_l1_handler_tx_stmt = self - .inner() - .prepare_cached( - "INSERT INTO l1_handler_txs (l1_tx_hash, l2_tx_hash) VALUES (:l1_tx_hash, \ - :l2_tx_hash)", - ) - .context("Preparing insert L1 handler tx statement")?; for (idx, (transaction, ..)) in transactions.iter().enumerate() { let idx: i64 = idx.try_into()?; @@ -179,20 +172,6 @@ impl Transaction<'_> { .context("Inserting events into Bloom filter")?; } - // Associate L1 handler transactions with L2 transactions - for (transaction, _) in transactions.iter() { - if let TransactionVariant::L1Handler(l1_handler_tx) = &transaction.variant { - if let Some(l1_tx_hash) = self.fetch_and_remove_l1_to_l2_message_log( - &l1_handler_tx.calculate_message_hash(), - )? { - insert_l1_handler_tx_stmt.execute(named_params![ - ":l1_tx_hash": &l1_tx_hash.as_bytes(), - ":l2_tx_hash": &transaction.hash, - ])?; - } - } - } - Ok(()) } diff --git a/crates/storage/src/schema.rs b/crates/storage/src/schema.rs index 3542ab73b2..9917f7d177 100644 --- a/crates/storage/src/schema.rs +++ b/crates/storage/src/schema.rs @@ -22,7 +22,6 @@ mod revision_0059; mod revision_0060; mod revision_0061; mod revision_0062; -mod revision_0063; pub(crate) use base::base_schema; @@ -53,7 +52,6 @@ pub fn migrations() -> &'static [MigrationFn] { revision_0060::migrate, revision_0061::migrate, revision_0062::migrate, - revision_0063::migrate, ] } diff --git a/crates/storage/src/schema/revision_0063.rs b/crates/storage/src/schema/revision_0063.rs deleted file mode 100644 index 75bc05da95..0000000000 --- a/crates/storage/src/schema/revision_0063.rs +++ /dev/null @@ -1,27 +0,0 @@ -use anyhow::Context; - -pub(crate) fn migrate(tx: &rusqlite::Transaction<'_>) -> anyhow::Result<()> { - tracing::info!("Adding table(s) to store L1->L2 message data"); - - tx.execute_batch( - r" - CREATE TABLE l1_to_l2_message_logs ( - msg_hash BLOB NOT NULL PRIMARY KEY, - l1_tx_hash BLOB NOT NULL - );", - ) - .context("Adding table to store L1 to L2 message data")?; - - tx.execute_batch( - r" - CREATE TABLE l1_handler_txs ( - l1_tx_hash BLOB NOT NULL, - l2_tx_hash BLOB NOT NULL - ); - CREATE INDEX idx_l1_handler_txs_l1_tx_hash ON l1_handler_txs(l1_tx_hash); - ", - ) - .context("Adding table and index to store L1 handler tx data")?; - - Ok(()) -} From 61a966c934cf1e86573e7e75f449d8d71cd9cb6f Mon Sep 17 00:00:00 2001 From: t00ts Date: Wed, 11 Sep 2024 13:19:38 +0200 Subject: [PATCH 032/282] chore: update ethereum.url config to use websocket schema --- crates/pathfinder/src/bin/pathfinder/config.rs | 6 +++--- crates/pathfinder/src/bin/pathfinder/main.rs | 15 +++++++++++++-- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/crates/pathfinder/src/bin/pathfinder/config.rs b/crates/pathfinder/src/bin/pathfinder/config.rs index c42dc1b1b7..17109ae3f2 100644 --- a/crates/pathfinder/src/bin/pathfinder/config.rs +++ b/crates/pathfinder/src/bin/pathfinder/config.rs @@ -44,11 +44,11 @@ struct Cli { #[arg( long = "ethereum.url", - long_help = r"This should point to the HTTP RPC endpoint of your Ethereum entry-point, typically a local Ethereum client or a hosted gateway service such as Infura or Cloudflare. + long_help = r"This should point to the WS RPC endpoint of your Ethereum entry-point, typically a local Ethereum client or a hosted gateway service such as Infura, Alchemy or Cloudflare. Examples: - infura: https://mainnet.infura.io/v3/ - geth: https://localhost:8545", + alchemy: wss://eth-mainnet.g.alchemy.com/v2/ + geth: wss://localhost:8545", value_name = "HTTP(s) URL", value_hint = clap::ValueHint::Url, env = "PATHFINDER_ETHEREUM_API_URL", diff --git a/crates/pathfinder/src/bin/pathfinder/main.rs b/crates/pathfinder/src/bin/pathfinder/main.rs index 24910b76fe..ae2fbe461b 100644 --- a/crates/pathfinder/src/bin/pathfinder/main.rs +++ b/crates/pathfinder/src/bin/pathfinder/main.rs @@ -20,7 +20,7 @@ use pathfinder_storage::Storage; use primitive_types::H160; use starknet_gateway_client::GatewayApi; use tokio::signal::unix::{signal, SignalKind}; -use tracing::info; +use tracing::{info, warn}; use crate::config::{NetworkConfig, StateTries}; @@ -645,7 +645,18 @@ struct EthereumContext { impl EthereumContext { /// Configure an [EthereumContext]'s transport and read the chain ID using /// it. - async fn setup(url: reqwest::Url, password: &Option) -> anyhow::Result { + async fn setup(mut url: reqwest::Url, password: &Option) -> anyhow::Result { + // Make sure the URL is a WS URL + if url.scheme().eq("http") { + warn!("The provided Ethereum URL is using HTTP, converting to WS"); + url.set_scheme("ws") + .map_err(|_| anyhow::anyhow!("Failed to set Ethereum URL scheme to ws"))?; + } else if url.scheme().eq("https") { + warn!("The provided Ethereum URL is using HTTPS, converting to WSS"); + url.set_scheme("wss") + .map_err(|_| anyhow::anyhow!("Failed to set Ethereum URL scheme to wss"))?; + } + let client = if let Some(password) = password.as_ref() { EthereumClient::with_password(url, password).context("Creating Ethereum client")? } else { From 0d32ed4cadd6e3554ea294564d836756e5bd44d3 Mon Sep 17 00:00:00 2001 From: t00ts Date: Wed, 11 Sep 2024 13:21:52 +0200 Subject: [PATCH 033/282] docs(changelog): ethereum rpc url now uses websockets --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f625a69ec..5642e270df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Pathfinder now fetches data concurrently from the feeder gateway when catching up. The `--gateway.fetch-concurrency` CLI option can be used to limit how many blocks are fetched concurrently (the default is 8). +### Changed + +- Ethereum RPC API now requires Websocket endpoints (prev. HTTP). If an HTTP url is provided instead, Pathfinder will attempt to connect vía Websocket protocol at that same url. + + ## [0.14.2] - 2024-09-03 ### Fixed From f68b3d752349efa95a3c82f3fa0a9b934c930719 Mon Sep 17 00:00:00 2001 From: t00ts Date: Wed, 11 Sep 2024 15:36:09 +0200 Subject: [PATCH 034/282] fix: remove logs --- crates/pathfinder/src/state/sync/l1.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/pathfinder/src/state/sync/l1.rs b/crates/pathfinder/src/state/sync/l1.rs index f69b53b287..3808624131 100644 --- a/crates/pathfinder/src/state/sync/l1.rs +++ b/crates/pathfinder/src/state/sync/l1.rs @@ -44,11 +44,9 @@ where async move { match event { EthereumEvent::StateUpdate(state_update) => { - info!("State update: {:?}", state_update); let _ = tx_event.send(SyncEvent::L1Update(state_update)).await; } EthereumEvent::MessageLog(log) => { - info!("Message log: {:?}", log); let _ = tx_event.send(SyncEvent::L1ToL2Message(log)).await; } } From 978c22da2b9822dee91354ccb6e183810999e00c Mon Sep 17 00:00:00 2001 From: t00ts Date: Wed, 11 Sep 2024 15:36:09 +0200 Subject: [PATCH 035/282] fix: remove logs --- crates/pathfinder/src/state/sync/l1.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/crates/pathfinder/src/state/sync/l1.rs b/crates/pathfinder/src/state/sync/l1.rs index f69b53b287..c6b4a76a31 100644 --- a/crates/pathfinder/src/state/sync/l1.rs +++ b/crates/pathfinder/src/state/sync/l1.rs @@ -4,7 +4,6 @@ use pathfinder_common::Chain; use pathfinder_ethereum::{EthereumApi, EthereumEvent}; use primitive_types::H160; use tokio::sync::mpsc; -use tracing::info; use crate::state::sync::SyncEvent; @@ -44,11 +43,9 @@ where async move { match event { EthereumEvent::StateUpdate(state_update) => { - info!("State update: {:?}", state_update); let _ = tx_event.send(SyncEvent::L1Update(state_update)).await; } EthereumEvent::MessageLog(log) => { - info!("Message log: {:?}", log); let _ = tx_event.send(SyncEvent::L1ToL2Message(log)).await; } } From 4f366fc81e9d399c22053e635ac8165e90b4dba3 Mon Sep 17 00:00:00 2001 From: sistemd Date: Thu, 12 Sep 2024 13:29:31 +0200 Subject: [PATCH 036/282] framework for RPC subscriptions --- Cargo.lock | 960 ++++++++++-------- crates/pathfinder/src/bin/pathfinder/main.rs | 12 +- crates/pathfinder/src/state/sync.rs | 25 +- crates/rpc/Cargo.toml | 1 + crates/rpc/src/context.rs | 6 + crates/rpc/src/jsonrpc.rs | 19 +- crates/rpc/src/jsonrpc/router.rs | 555 ++-------- crates/rpc/src/jsonrpc/router/method.rs | 381 +++++++ crates/rpc/src/jsonrpc/router/subscription.rs | 479 +++++++++ crates/rpc/src/lib.rs | 17 + crates/rpc/src/method.rs | 1 + crates/rpc/src/method/subscribe_new_heads.rs | 159 +++ crates/rpc/src/subscription_message.rs | 0 crates/rpc/src/v08.rs | 38 + crates/storage/src/connection/block.rs | 21 + 15 files changed, 1782 insertions(+), 892 deletions(-) create mode 100644 crates/rpc/src/jsonrpc/router/method.rs create mode 100644 crates/rpc/src/jsonrpc/router/subscription.rs create mode 100644 crates/rpc/src/method/subscribe_new_heads.rs create mode 100644 crates/rpc/src/subscription_message.rs create mode 100644 crates/rpc/src/v08.rs diff --git a/Cargo.lock b/Cargo.lock index 5c698d8644..60d58e78c0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -15,18 +15,18 @@ dependencies = [ [[package]] name = "addr2line" -version = "0.22.0" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" +checksum = "f5fb1d8e4442bd405fdfd1dacb42792696b0cf9cb15882e5d097b742a676d375" dependencies = [ "gimli", ] [[package]] -name = "adler" -version = "1.0.2" +name = "adler2" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" [[package]] name = "aead" @@ -103,9 +103,9 @@ checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" [[package]] name = "alloy" -version = "0.3.1" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f13f1940c81e269e84ddb58f3b611be9660fbbfe39d4338aa2984dc3df0c402" +checksum = "c37d89f69cb43901949ba29307ada8b9e3b170f94057ad4c04d6fd169d24d65f" dependencies = [ "alloy-consensus", "alloy-contract", @@ -125,9 +125,9 @@ dependencies = [ [[package]] name = "alloy-chains" -version = "0.1.29" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb07629a5d0645d29f68d2fb6f4d0cf15c89ec0965be915f303967180929743f" +checksum = "2b4f201b0ac8f81315fbdc55269965a8ddadbc04ab47fa65a1a468f9a40f7a5f" dependencies = [ "num_enum", "strum 0.26.3", @@ -135,9 +135,9 @@ dependencies = [ [[package]] name = "alloy-consensus" -version = "0.3.1" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4177d135789e282e925092be8939d421b701c6d92c0a16679faa659d9166289d" +checksum = "1468e3128e07c7afe4ff13c17e8170c330d12c322f8924b8bf6986a27e0aad3d" dependencies = [ "alloy-eips", "alloy-primitives", @@ -149,9 +149,9 @@ dependencies = [ [[package]] name = "alloy-contract" -version = "0.3.1" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3be15f92fdb7490b164697a1d9b395cb7a3afa8fb15feed732ec5a6ff8db5f4" +checksum = "335d62de1a887f1b780441f8a3037f39c9fb26839cc9acd891c9b80396145cd5" dependencies = [ "alloy-dyn-abi", "alloy-json-abi", @@ -170,9 +170,9 @@ dependencies = [ [[package]] name = "alloy-core" -version = "0.8.0" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e6dbb79f4e3285cc87f50c0d4be9a3a812643623b2e3558d425b41cbd795ceb" +checksum = "88b095eb0533144b4497e84a9cc3e44a5c2e3754a3983c0376a55a2f9183a53e" dependencies = [ "alloy-dyn-abi", "alloy-json-abi", @@ -182,9 +182,9 @@ dependencies = [ [[package]] name = "alloy-dyn-abi" -version = "0.8.0" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba5b68572f5dfa99ede0a491d658c9842626c956b840d0b97d0bbc9637742504" +checksum = "4004925bff5ba0a11739ae84dbb6601a981ea692f3bd45b626935ee90a6b8471" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -194,7 +194,7 @@ dependencies = [ "itoa", "serde", "serde_json", - "winnow 0.6.16", + "winnow", ] [[package]] @@ -221,9 +221,9 @@ dependencies = [ [[package]] name = "alloy-eips" -version = "0.3.1" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "499ee14d296a133d142efd215eb36bf96124829fe91cf8f5d4e5ccdd381eae00" +checksum = "0c35df7b972b06f1b2f4e8b7a53328522fa788054a9d3e556faf2411c5a51d5a" dependencies = [ "alloy-eip2930", "alloy-eip7702", @@ -239,9 +239,9 @@ dependencies = [ [[package]] name = "alloy-genesis" -version = "0.3.1" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b85dfc693e4a1193f0372a8f789df12ab51fcbe7be0733baa04939a86dd813b" +checksum = "0b7210f9206c0fa2a83c824cf8cb6c962126bc9fdc4f41ade1932f14150ef5f6" dependencies = [ "alloy-primitives", "alloy-serde", @@ -250,9 +250,9 @@ dependencies = [ [[package]] name = "alloy-json-abi" -version = "0.8.0" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "299d2a937b6c60968df3dad2a988b0f0e03277b344639a4f7a31bd68e6285e59" +checksum = "9996daf962fd0a90d3c93b388033228865953b92de7bb1959b891d78750a4091" dependencies = [ "alloy-primitives", "alloy-sol-type-parser", @@ -262,9 +262,9 @@ dependencies = [ [[package]] name = "alloy-json-rpc" -version = "0.3.1" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4207166c79cfdf7f3bed24bbc84f5c7c5d4db1970f8c82e3fcc76257f16d2166" +checksum = "8866562186d237f1dfeaf989ef941a24764f764bf5c33311e37ead3519c6a429" dependencies = [ "alloy-primitives", "alloy-sol-types", @@ -276,9 +276,9 @@ dependencies = [ [[package]] name = "alloy-network" -version = "0.3.1" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbe2802d5b8c632f18d68c352073378f02a3407c1b6a4487194e7d21ab0f002" +checksum = "abe714e233f9eaf410de95a9af6bcd05d3a7f8c8de7a0817221e95a6b642a080" dependencies = [ "alloy-consensus", "alloy-eips", @@ -297,10 +297,11 @@ dependencies = [ [[package]] name = "alloy-network-primitives" -version = "0.3.1" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "396c07726030fa0f9dab5da8c71ccd69d5eb74a7fe1072b7ae453a67e4fe553e" +checksum = "8c5a38117974c5776a45e140226745a0b664f79736aa900995d8e4121558e064" dependencies = [ + "alloy-eips", "alloy-primitives", "alloy-serde", "serde", @@ -308,15 +309,15 @@ dependencies = [ [[package]] name = "alloy-primitives" -version = "0.8.0" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a767e59c86900dd7c3ce3ecef04f3ace5ac9631ee150beb8b7d22f7fa3bbb2d7" +checksum = "411aff151f2a73124ee473708e82ed51b2535f68928b6a1caa8bc1246ae6f7cd" dependencies = [ "alloy-rlp", "bytes", "cfg-if", "const-hex", - "derive_more 0.99.18", + "derive_more 1.0.0", "hex-literal", "itoa", "k256", @@ -330,9 +331,9 @@ dependencies = [ [[package]] name = "alloy-provider" -version = "0.3.1" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1376948df782ffee83a54cac4b2aba14134edd997229a3db97da0a606586eb5c" +checksum = "c65633d6ef83c3626913c004eaf166a6dd50406f724772ea8567135efd6dc5d3" dependencies = [ "alloy-chains", "alloy-consensus", @@ -366,9 +367,9 @@ dependencies = [ [[package]] name = "alloy-pubsub" -version = "0.3.1" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa73f976e7b6341f3f8a404241cf04f883d40212cd4f2633c66d99de472e262c" +checksum = "949db89abae6193b44cc90ebf2eeb74eb8d2a474383c5e62b45bdcd362e84f8f" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -379,7 +380,7 @@ dependencies = [ "serde_json", "tokio", "tokio-stream", - "tower", + "tower 0.5.1", "tracing", ] @@ -402,14 +403,14 @@ checksum = "4d0f2d905ebd295e7effec65e5f6868d153936130ae718352771de3e7d03c75c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.77", ] [[package]] name = "alloy-rpc-client" -version = "0.3.1" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02378418a429f8a14a0ad8ffaa15b2d25ff34914fc4a1e366513c6a3800e03b3" +checksum = "d5fc328bb5d440599ba1b5aa44c0b9ab0625fbc3a403bb5ee94ed4a01ba23e07" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -424,16 +425,16 @@ dependencies = [ "serde_json", "tokio", "tokio-stream", - "tower", + "tower 0.5.1", "tracing", "url", ] [[package]] name = "alloy-rpc-types" -version = "0.3.1" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9ae4c4fbd37d9996f501fbc7176405aab97ae3a5772789be06ef0e7c4dad6dd" +checksum = "8f8ff679f94c497a8383f2cd09e2a099266e5f3d5e574bc82b4b379865707dbb" dependencies = [ "alloy-rpc-types-eth", "alloy-serde", @@ -442,9 +443,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-eth" -version = "0.3.1" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15bb3506ab1cf415d4752778c93e102050399fb8de97b7da405a5bf3e31f5f3b" +checksum = "9a59b1d7c86e0a653e7f3d29954f6de5a2878d8cfd1f010ff93be5c2c48cd3b1" dependencies = [ "alloy-consensus", "alloy-eips", @@ -461,9 +462,9 @@ dependencies = [ [[package]] name = "alloy-serde" -version = "0.3.1" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae417978015f573b4a8c02af17f88558fb22e3fccd12e8a910cf6a2ff331cfcb" +checksum = "51db8a6428a2159e01b7a43ec7aac801edd0c4db1d4de06f310c288940f16fd3" dependencies = [ "alloy-primitives", "serde", @@ -472,9 +473,9 @@ dependencies = [ [[package]] name = "alloy-signer" -version = "0.3.1" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b750c9b61ac0646f8f4a61231c2732a337b2c829866fc9a191b96b7eedf80ffe" +checksum = "bebc1760c13592b7ba3fcd964abba546b8d6a9f10d15e8d92a8263731be33f36" dependencies = [ "alloy-primitives", "async-trait", @@ -486,42 +487,42 @@ dependencies = [ [[package]] name = "alloy-sol-macro" -version = "0.8.0" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "183bcfc0f3291d9c41a3774172ee582fb2ce6eb6569085471d8f225de7bb86fc" +checksum = "0458ccb02a564228fcd76efb8eb5a520521a8347becde37b402afec9a1b83859" dependencies = [ "alloy-sol-macro-expander", "alloy-sol-macro-input", - "proc-macro-error", + "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.77", ] [[package]] name = "alloy-sol-macro-expander" -version = "0.8.0" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71c4d842beb7a6686d04125603bc57614d5ed78bf95e4753274db3db4ba95214" +checksum = "2bc65475025fc1e84bf86fc840f04f63fcccdcf3cf12053c99918e4054dfbc69" dependencies = [ "alloy-json-abi", "alloy-sol-macro-input", "const-hex", "heck 0.5.0", - "indexmap 2.2.6", - "proc-macro-error", + "indexmap 2.5.0", + "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.77", "syn-solidity", "tiny-keccak", ] [[package]] name = "alloy-sol-macro-input" -version = "0.8.0" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1306e8d3c9e6e6ecf7a39ffaf7291e73a5f655a2defd366ee92c2efebcdf7fee" +checksum = "6ed10f0715a0b69fde3236ff3b9ae5f6f7c97db5a387747100070d3016b9266b" dependencies = [ "alloy-json-abi", "const-hex", @@ -530,25 +531,25 @@ dependencies = [ "proc-macro2", "quote", "serde_json", - "syn 2.0.72", + "syn 2.0.77", "syn-solidity", ] [[package]] name = "alloy-sol-type-parser" -version = "0.8.0" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4691da83dce9c9b4c775dd701c87759f173bd3021cbf2e60cde00c5fe6d7241" +checksum = "3edae8ea1de519ccba896b6834dec874230f72fe695ff3c9c118e90ec7cff783" dependencies = [ "serde", - "winnow 0.6.16", + "winnow", ] [[package]] name = "alloy-sol-types" -version = "0.8.0" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "577e262966e92112edbd15b1b2c0947cc434d6e8311df96d3329793fe8047da9" +checksum = "1eb88e4da0a1b697ed6a9f811fdba223cf4d5c21410804fd1707836af73a462b" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -559,9 +560,9 @@ dependencies = [ [[package]] name = "alloy-transport" -version = "0.3.1" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2799749ca692ae145f54968778877afd7c95e788488f176cfdfcf2a8abeb2062" +checksum = "fd5dc4e902f1860d54952446d246ac05386311ad61030a2b906ae865416d36e0" dependencies = [ "alloy-json-rpc", "base64 0.22.1", @@ -571,31 +572,31 @@ dependencies = [ "serde_json", "thiserror", "tokio", - "tower", + "tower 0.5.1", "tracing", "url", ] [[package]] name = "alloy-transport-http" -version = "0.3.1" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc10c4dd932f66e0db6cc5735241e0c17a6a18564b430bbc1839f7db18587a93" +checksum = "1742b94bb814f1ca6b322a6f9dd38a0252ff45a3119e40e888fb7029afa500ce" dependencies = [ "alloy-json-rpc", "alloy-transport", "reqwest", "serde_json", - "tower", + "tower 0.5.1", "tracing", "url", ] [[package]] name = "alloy-transport-ws" -version = "0.3.1" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65e732028930aa17b7edd464a9711365417635e984028fcc7176393ccea22c00" +checksum = "e8ed861e7030001364c8ffa2db63541f7bae275a6e636de7616c20f2fd3dc0c3" dependencies = [ "alloy-pubsub", "alloy-transport", @@ -681,9 +682,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.86" +version = "1.0.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" +checksum = "4e1496f8fb1fbf272686b8d37f523dab3e4a7443300055e74cdaa449f3114356" [[package]] name = "ark-ec" @@ -736,7 +737,7 @@ dependencies = [ "num-bigint 0.4.6", "num-traits 0.2.19", "paste", - "rustc_version 0.4.0", + "rustc_version 0.4.1", "zeroize", ] @@ -881,9 +882,9 @@ checksum = "9d151e35f61089500b617991b791fc8bfd237ae50cd5950803758a179b41e67a" [[package]] name = "arrayvec" -version = "0.7.4" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] name = "ascii-canvas" @@ -896,9 +897,9 @@ dependencies = [ [[package]] name = "asn1-rs" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ad1373757efa0f70ec53939aabc7152e1591cb485208052993070ac8d2429d" +checksum = "5493c3bedbacf7fd7382c6346bbd66687d12bbaad3a89a2d2c303ee6cf20b048" dependencies = [ "asn1-rs-derive", "asn1-rs-impl", @@ -912,13 +913,13 @@ dependencies = [ [[package]] name = "asn1-rs-derive" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7378575ff571966e99a744addeff0bff98b8ada0dedf1956d59e634db95eaac1" +checksum = "965c2d33e53cb6b267e148a4cb0760bc01f4904c1cd4bb4002a085bb016d1490" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.77", "synstructure", ] @@ -930,7 +931,7 @@ checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.77", ] [[package]] @@ -984,13 +985,13 @@ dependencies = [ [[package]] name = "async-executor" -version = "1.13.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7ebdfa2ebdab6b1760375fa7d6f382b9f486eac35fc994625a00e89280bdbb7" +checksum = "30ca9a001c1e8ba5149f91a74362376cc6bc5b919d92d988668657bd570bdcec" dependencies = [ "async-task", "concurrent-queue", - "fastrand 2.1.0", + "fastrand 2.1.1", "futures-lite 2.3.0", "slab", ] @@ -1015,7 +1016,7 @@ checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" dependencies = [ "async-channel 2.3.1", "async-executor", - "async-io 2.3.3", + "async-io 2.3.4", "async-lock 3.4.0", "blocking", "futures-lite 2.3.0", @@ -1044,9 +1045,9 @@ dependencies = [ [[package]] name = "async-io" -version = "2.3.3" +version = "2.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d6baa8f0178795da0e71bc42c9e5d13261aac7ee549853162e66a241ba17964" +checksum = "444b0228950ee6501b3568d3c93bf1176a1fdbc3b758dcd9475046d30f4dc7e8" dependencies = [ "async-lock 3.4.0", "cfg-if", @@ -1054,11 +1055,11 @@ dependencies = [ "futures-io", "futures-lite 2.3.0", "parking", - "polling 3.7.2", - "rustix 0.38.34", + "polling 3.7.3", + "rustix 0.38.37", "slab", "tracing", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1094,9 +1095,9 @@ dependencies = [ [[package]] name = "async-object-pool" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aeb901c30ebc2fc4ab46395bbfbdba9542c16559d853645d75190c3056caf3bc" +checksum = "333c456b97c3f2d50604e8b2624253b7f787208cb72eb75e64b0ad11b221652c" dependencies = [ "async-std", ] @@ -1114,45 +1115,65 @@ dependencies = [ "cfg-if", "event-listener 3.1.0", "futures-lite 1.13.0", - "rustix 0.38.34", + "rustix 0.38.37", "windows-sys 0.48.0", ] +[[package]] +name = "async-process" +version = "2.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8a07789659a4d385b79b18b9127fc27e1a59e1e89117c78c5ea3b806f016374" +dependencies = [ + "async-channel 2.3.1", + "async-io 2.3.4", + "async-lock 3.4.0", + "async-signal", + "async-task", + "blocking", + "cfg-if", + "event-listener 5.3.1", + "futures-lite 2.3.0", + "rustix 0.38.37", + "tracing", + "windows-sys 0.59.0", +] + [[package]] name = "async-signal" -version = "0.2.9" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfb3634b73397aa844481f814fad23bbf07fdb0eabec10f2eb95e58944b1ec32" +checksum = "637e00349800c0bdf8bfc21ebbc0b6524abea702b0da4168ac00d070d0c0b9f3" dependencies = [ - "async-io 2.3.3", + "async-io 2.3.4", "async-lock 3.4.0", "atomic-waker", "cfg-if", "futures-core", "futures-io", - "rustix 0.38.34", + "rustix 0.38.37", "signal-hook-registry", "slab", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "async-std" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62565bb4402e926b29953c785397c6dc0391b7b446e45008b0049eb43cec6f5d" +checksum = "c634475f29802fde2b8f0b505b1bd00dfe4df7d4a000f0b36f7671197d5c3615" dependencies = [ "async-attributes", "async-channel 1.9.0", "async-global-executor", - "async-io 1.13.0", - "async-lock 2.8.0", - "async-process", + "async-io 2.3.4", + "async-lock 3.4.0", + "async-process 2.2.4", "crossbeam-utils", "futures-channel", "futures-core", "futures-io", - "futures-lite 1.13.0", + "futures-lite 2.3.0", "gloo-timers", "kv-log-macro", "log", @@ -1183,7 +1204,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.77", ] [[package]] @@ -1194,13 +1215,13 @@ checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" [[package]] name = "async-trait" -version = "0.1.81" +version = "0.1.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" +checksum = "a27b8a3a6e1a44fa4c8baf1f653e4172e81486d4941f2237e20dc2d0cf4ddff1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.77", ] [[package]] @@ -1211,7 +1232,7 @@ checksum = "b6d7b9decdf35d8908a7e3ef02f64c5e9b1695e230154c0e8de3969142d9b94c" dependencies = [ "futures", "pharos", - "rustc_version 0.4.0", + "rustc_version 0.4.1", ] [[package]] @@ -1276,7 +1297,7 @@ checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.77", ] [[package]] @@ -1308,7 +1329,7 @@ dependencies = [ "rustversion", "serde", "sync_wrapper 0.1.2", - "tower", + "tower 0.4.13", "tower-layer", "tower-service", ] @@ -1345,7 +1366,7 @@ dependencies = [ "sync_wrapper 1.0.1", "tokio", "tokio-tungstenite 0.21.0", - "tower", + "tower 0.4.13", "tower-layer", "tower-service", "tracing", @@ -1398,22 +1419,22 @@ dependencies = [ "heck 0.4.1", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.77", ] [[package]] name = "backtrace" -version = "0.3.73" +version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" dependencies = [ "addr2line", - "cc", "cfg-if", "libc", "miniz_oxide", "object", "rustc-demangle", + "windows-targets 0.52.6", ] [[package]] @@ -1569,7 +1590,7 @@ dependencies = [ "cairo-lang-utils 2.7.1", "cairo-vm", "derive_more 0.99.18", - "indexmap 2.2.6", + "indexmap 2.5.0", "itertools 0.10.5", "keccak", "log", @@ -1666,9 +1687,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.6.1" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a12916984aab3fa6e39d655a33e09c0071eb36d6ab3aea5c2d78551f1df6d952" +checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" dependencies = [ "serde", ] @@ -2474,7 +2495,7 @@ checksum = "124402d8fad2a033bb36910dd7d0651f3100845c63dce679c58797a8cb0448c2" dependencies = [ "cairo-lang-debug 2.7.1", "quote", - "syn 2.0.72", + "syn 2.0.77", ] [[package]] @@ -2525,7 +2546,7 @@ dependencies = [ "serde", "smol_str 0.2.2", "thiserror", - "toml 0.8.17", + "toml 0.8.19", ] [[package]] @@ -2652,7 +2673,7 @@ dependencies = [ "once_cell", "salsa", "smol_str 0.2.2", - "toml 0.8.17", + "toml 0.8.19", ] [[package]] @@ -3404,7 +3425,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55a394e545f1500bea093d01be40895d3234faaa24d9585d08a509c514cabd88" dependencies = [ "hashbrown 0.14.5", - "indexmap 2.2.6", + "indexmap 2.5.0", "itertools 0.12.1", "num-bigint 0.4.6", "num-traits 0.2.19", @@ -3452,12 +3473,13 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.1.7" +version = "1.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26a5c3fd7bfa1ce3897a3a3501d362b2d87b7f2583ebcb4a949ec25911025cbc" +checksum = "b62ac837cdb5cb22e10a256099b4fc502b1dfe560cb282963a974d7abd80e476" dependencies = [ "jobserver", "libc", + "shlex", ] [[package]] @@ -3545,9 +3567,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.11" +version = "4.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35723e6a11662c2afb578bcf0b88bf6ea8e21282a953428f240574fcc3a2b5b3" +checksum = "3e5a21b8495e732f1b3c364c9949b201ca7bae518c502c80256c96ad79eaf6ac" dependencies = [ "clap_builder", "clap_derive", @@ -3555,9 +3577,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.11" +version = "4.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49eb96cbfa7cfa35017b7cd548c75b14c3118c98b423041d70562665e07fb0fa" +checksum = "8cf2dd12af7a047ad9d6da2b6b249759a22a7abc0f474c1dae1777afa4b21a73" dependencies = [ "anstream", "anstyle", @@ -3568,14 +3590,14 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.11" +version = "4.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d029b67f89d30bbb547c89fd5161293c0aec155fc691d7924b64550662db93e" +checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.77", ] [[package]] @@ -3678,18 +3700,18 @@ checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] name = "const_format" -version = "0.2.32" +version = "0.2.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3a214c7af3d04997541b18d432afaff4c455e79e2029079647e72fc2bd27673" +checksum = "50c655d81ff1114fb0dcdea9225ea9f0cc712a6f8d189378e82bdf62a473a64b" dependencies = [ "const_format_proc_macros", ] [[package]] name = "const_format_proc_macros" -version = "0.2.32" +version = "0.2.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7f6ff08fd20f4f299298a28e2dfa8a8ba1036e6cd2460ac1de7b425d76f2500" +checksum = "eff1a44b93f47b1bac19a27932f5c591e43d1ba357ee4f61526c8a25603f0eb1" dependencies = [ "proc-macro2", "quote", @@ -3729,9 +3751,9 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "core2" @@ -3744,9 +3766,9 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.12" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" dependencies = [ "libc", ] @@ -3879,7 +3901,7 @@ dependencies = [ "curve25519-dalek-derive", "digest 0.10.7", "fiat-crypto", - "rustc_version 0.4.0", + "rustc_version 0.4.1", "subtle", "zeroize", ] @@ -3892,7 +3914,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.77", ] [[package]] @@ -3940,7 +3962,7 @@ dependencies = [ "proc-macro2", "quote", "strsim 0.11.1", - "syn 2.0.72", + "syn 2.0.77", ] [[package]] @@ -3962,7 +3984,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core 0.20.10", "quote", - "syn 2.0.72", + "syn 2.0.77", ] [[package]] @@ -3979,9 +4001,9 @@ dependencies = [ [[package]] name = "dashmap" -version = "6.0.1" +version = "6.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "804c8821570c3f8b70230c2ba75ffa5c0f9a4189b9a432b6656c536712acae28" +checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" dependencies = [ "cfg-if", "crossbeam-utils", @@ -4071,8 +4093,8 @@ dependencies = [ "convert_case 0.4.0", "proc-macro2", "quote", - "rustc_version 0.4.0", - "syn 2.0.72", + "rustc_version 0.4.1", + "syn 2.0.77", ] [[package]] @@ -4092,7 +4114,8 @@ checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.77", + "unicode-xid", ] [[package]] @@ -4172,7 +4195,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.77", ] [[package]] @@ -4196,7 +4219,7 @@ dependencies = [ "darling 0.20.10", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.77", ] [[package]] @@ -4295,14 +4318,14 @@ dependencies = [ [[package]] name = "enum-as-inner" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ffccbb6966c05b32ef8fbac435df276c4ae4d3dc55a8cd0eb9745e6c12f546a" +checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" dependencies = [ - "heck 0.4.1", + "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.77", ] [[package]] @@ -4420,9 +4443,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" +checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" [[package]] name = "fastrlp" @@ -4490,9 +4513,9 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "flate2" -version = "1.0.30" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" +checksum = "324a1be68054ef05ad64b861cc9eaf1d623d2d8cb25b4bf2cb9cdd902b4bf253" dependencies = [ "crc32fast", "miniz_oxide", @@ -4639,7 +4662,7 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" dependencies = [ - "fastrand 2.1.0", + "fastrand 2.1.1", "futures-core", "futures-io", "parking", @@ -4654,7 +4677,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.77", ] [[package]] @@ -4751,7 +4774,7 @@ checksum = "553630feadf7b76442b0849fd25fdf89b860d933623aec9693fed19af0400c78" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.77", ] [[package]] @@ -4790,9 +4813,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.29.0" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" +checksum = "32085ea23f3234fc7846555e85283ba4de91e21016dc0455a16286d87a292d64" [[package]] name = "glob" @@ -4802,9 +4825,9 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "globset" -version = "0.4.14" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1" +checksum = "15f1ce686646e7f1e19bf7d5533fe443a45dbfb990e00629110797578b42fb19" dependencies = [ "aho-corasick", "bstr", @@ -4815,9 +4838,9 @@ dependencies = [ [[package]] name = "gloo-timers" -version = "0.2.6" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" dependencies = [ "futures-channel", "futures-core", @@ -4858,7 +4881,7 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.12", - "indexmap 2.2.6", + "indexmap 2.5.0", "slab", "tokio", "tokio-util", @@ -4867,9 +4890,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa82e28a107a8cc405f0839610bdc9b15f1e25ec7d696aa5cf173edbcb1486ab" +checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205" dependencies = [ "atomic-waker", "bytes", @@ -4877,7 +4900,7 @@ dependencies = [ "futures-core", "futures-sink", "http 1.1.0", - "indexmap 2.2.6", + "indexmap 2.5.0", "slab", "tokio", "tokio-util", @@ -5251,7 +5274,7 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "h2 0.4.5", + "h2 0.4.6", "http 1.1.0", "http-body 1.0.1", "httparse", @@ -5265,16 +5288,16 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.2" +version = "0.27.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee4be2c948921a1a5320b629c4193916ed787a7f7f293fd3f7f5a6c9de74155" +checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" dependencies = [ "futures-util", "http 1.1.0", "hyper 1.4.1", "hyper-util", "rustls", - "rustls-native-certs", + "rustls-native-certs 0.8.0", "rustls-pki-types", "tokio", "tokio-rustls", @@ -5311,9 +5334,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.6" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ab92f4f49ee4fb4f997c784b7a2e0fa70050211e0b6a287f898c3c9785ca956" +checksum = "da62f120a8a37763efb0cf8fdf264b884c7b8b9ac8660b900c8661030c00e6ba" dependencies = [ "bytes", "futures-channel", @@ -5324,7 +5347,7 @@ dependencies = [ "pin-project-lite", "socket2 0.5.7", "tokio", - "tower", + "tower 0.4.13", "tower-service", "tracing", ] @@ -5400,7 +5423,7 @@ version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6b0422c86d7ce0e97169cc42e04ae643caf278874a7a3c87b8150a220dc7e1e" dependencies = [ - "async-io 2.3.3", + "async-io 2.3.4", "core-foundation", "fnv", "futures", @@ -5435,9 +5458,9 @@ dependencies = [ [[package]] name = "ignore" -version = "0.4.22" +version = "0.4.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b46810df39e66e925525d6e38ce1e7f6e1d208f72dc39757880fcb66e2c58af1" +checksum = "6d89fd380afde86567dfba715db065673989d6253f42b88179abd3eae47bda4b" dependencies = [ "crossbeam-deque", "globset", @@ -5497,9 +5520,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.2.6" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" dependencies = [ "equivalent", "hashbrown 0.14.5", @@ -5556,22 +5579,22 @@ dependencies = [ "socket2 0.5.7", "widestring", "windows-sys 0.48.0", - "winreg 0.50.0", + "winreg", ] [[package]] name = "ipnet" -version = "2.9.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" +checksum = "187674a687eed5fe42285b40c6291f9a01517d415fad1c3cbc6a9f778af7fcd4" [[package]] name = "is-terminal" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" +checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" dependencies = [ - "hermit-abi 0.3.9", + "hermit-abi 0.4.0", "libc", "windows-sys 0.52.0", ] @@ -5655,9 +5678,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.69" +version = "0.3.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" dependencies = [ "wasm-bindgen", ] @@ -5686,9 +5709,9 @@ dependencies = [ [[package]] name = "keccak-asm" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "422fbc7ff2f2f5bdffeb07718e5a5324dca72b0c9293d50df4026652385e3314" +checksum = "505d1856a39b200489082f90d897c3f07c455563880bc5952e38eabf731c83b6" dependencies = [ "digest 0.10.7", "sha3-asm", @@ -5814,9 +5837,9 @@ checksum = "db13adb97ab515a3691f56e4dbab09283d0b86cb45abd991d8634a9d6f501760" [[package]] name = "libc" -version = "0.2.155" +version = "0.2.158" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" +checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" [[package]] name = "libm" @@ -6285,7 +6308,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.77", ] [[package]] @@ -6362,9 +6385,9 @@ dependencies = [ [[package]] name = "libp2p-yamux" -version = "0.45.1" +version = "0.45.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "200cbe50349a44760927d50b431d77bed79b9c0a3959de1af8d24a63434b71e5" +checksum = "ddd5265f6b80f94d48a3963541aad183cc598a645755d2f1805a373e41e0716b" dependencies = [ "either", "futures", @@ -6597,18 +6620,18 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.7.4" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" dependencies = [ - "adler", + "adler2", ] [[package]] name = "mio" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4569e456d394deccd22ce1c1913e6ea0e54519f577285001215d33557431afe4" +checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" dependencies = [ "hermit-abi 0.3.9", "libc", @@ -7010,7 +7033,7 @@ checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.77", ] [[package]] @@ -7024,18 +7047,18 @@ dependencies = [ [[package]] name = "object" -version = "0.36.2" +version = "0.36.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f203fa8daa7bb185f760ae12bd8e097f63d17041dcdcaf675ac54cdf863170e" +checksum = "084f1a5821ac4c651660a94a7153d27ac9d8a53736203f58b31945ded098070a" dependencies = [ "memchr", ] [[package]] name = "oid-registry" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c958dd45046245b9c3c2547369bb634eb461670b2e7e0de552905801a648d1d" +checksum = "a8d8034d9489cdaf79228eb9f6a3b8d7bb32ba00d6645ebd48eef4077ceb5bd9" dependencies = [ "asn1-rs", ] @@ -7081,7 +7104,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.77", ] [[package]] @@ -7138,7 +7161,7 @@ dependencies = [ "pathfinder-crypto", "pretty_assertions_sorted", "primitive-types", - "prost 0.13.1", + "prost 0.13.2", "rand", "rayon", "rstest", @@ -7170,9 +7193,9 @@ dependencies = [ "pathfinder-crypto", "pretty_assertions_sorted", "primitive-types", - "prost 0.13.1", + "prost 0.13.2", "prost-build", - "prost-types 0.13.1", + "prost-types 0.13.2", "rand", "serde_json", "tagged", @@ -7235,9 +7258,9 @@ dependencies = [ [[package]] name = "parking" -version = "2.2.0" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" [[package]] name = "parking_lot" @@ -7282,7 +7305,7 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.3", + "redox_syscall 0.5.4", "smallvec", "windows-targets 0.52.6", ] @@ -7517,6 +7540,7 @@ dependencies = [ "axum 0.7.5", "base64 0.13.1", "bytes", + "dashmap", "flate2", "futures", "gateway-test-utils", @@ -7551,7 +7575,7 @@ dependencies = [ "thiserror", "tokio", "tokio-tungstenite 0.21.0", - "tower", + "tower 0.4.13", "tower-http", "tracing", "tracing-subscriber", @@ -7645,9 +7669,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.7.11" +version = "2.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd53dff83f26735fdc1ca837098ccf133605d794cdae66acfc2bfac3ec809d95" +checksum = "9c73c26c01b8c87956cea613c907c9d6ecffd8d18a2a5908e5de0adfaa185cea" dependencies = [ "memchr", "thiserror", @@ -7661,7 +7685,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset", - "indexmap 2.2.6", + "indexmap 2.5.0", ] [[package]] @@ -7671,7 +7695,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e9567389417feee6ce15dd6527a8a1ecac205ef62c2932bcf3d9f6fc5b78b414" dependencies = [ "futures", - "rustc_version 0.4.0", + "rustc_version 0.4.1", ] [[package]] @@ -7704,7 +7728,7 @@ dependencies = [ "phf_shared 0.11.2", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.77", ] [[package]] @@ -7748,7 +7772,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.77", ] [[package]] @@ -7765,12 +7789,12 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "piper" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae1d5c74c9876f070d3e8fd503d748c7d974c3e48da8f41350fa5222ef9b4391" +checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" dependencies = [ "atomic-waker", - "fastrand 2.1.0", + "fastrand 2.1.1", "futures-io", ] @@ -7792,9 +7816,9 @@ checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" [[package]] name = "plotters" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a15b6eccb8484002195a3e44fe65a4ce8e93a625797a063735536fd59cb01cf3" +checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" dependencies = [ "num-traits 0.2.19", "plotters-backend", @@ -7805,15 +7829,15 @@ dependencies = [ [[package]] name = "plotters-backend" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "414cec62c6634ae900ea1c56128dfe87cf63e7caece0852ec76aba307cebadb7" +checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" [[package]] name = "plotters-svg" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81b30686a7d9c3e010b84284bdd26a29f2138574f52f5eb6f794fc0ad924e705" +checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" dependencies = [ "plotters-backend", ] @@ -7836,17 +7860,17 @@ dependencies = [ [[package]] name = "polling" -version = "3.7.2" +version = "3.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3ed00ed3fbf728b5816498ecd316d1716eecaced9c0c8d2c5a6740ca214985b" +checksum = "cc2790cd301dec6cd3b7a025e4815cf825724a51c98dccfe6a3e55f05ffb6511" dependencies = [ "cfg-if", "concurrent-queue", "hermit-abi 0.4.0", "pin-project-lite", - "rustix 0.38.34", + "rustix 0.38.37", "tracing", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -7895,12 +7919,11 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" -version = "0.2.19" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2288c0e17cc8d342c712bb43a257a80ebffce59cdb33d5000d8348f3ec02528b" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" dependencies = [ "zerocopy", - "zerocopy-derive", ] [[package]] @@ -7961,12 +7984,12 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.2.20" +version = "0.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e" +checksum = "479cf940fbbb3426c32c5d5176f62ad57549a0bb84773423ba8be9d089f5faba" dependencies = [ "proc-macro2", - "syn 2.0.72", + "syn 2.0.77", ] [[package]] @@ -7983,35 +8006,33 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "3.1.0" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" +checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" dependencies = [ - "toml_edit 0.21.1", + "toml_edit", ] [[package]] -name = "proc-macro-error" -version = "1.0.4" +name = "proc-macro-error-attr2" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" dependencies = [ - "proc-macro-error-attr", "proc-macro2", "quote", - "syn 1.0.109", - "version_check", ] [[package]] -name = "proc-macro-error-attr" -version = "1.0.4" +name = "proc-macro-error2" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" dependencies = [ + "proc-macro-error-attr2", "proc-macro2", "quote", - "version_check", + "syn 2.0.77", ] [[package]] @@ -8043,7 +8064,7 @@ checksum = "440f724eba9f6996b75d63681b0a92b06947f1457076d503a4d2e2c8f56442b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.77", ] [[package]] @@ -8078,19 +8099,19 @@ dependencies = [ [[package]] name = "prost" -version = "0.13.1" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13db3d3fde688c61e2446b4d843bc27a7e8af269a69440c0308021dc92333cc" +checksum = "3b2ecbe40f08db5c006b5764a2645f7f3f141ce756412ac9e1dd6087e6d32995" dependencies = [ "bytes", - "prost-derive 0.13.1", + "prost-derive 0.13.2", ] [[package]] name = "prost-build" -version = "0.13.1" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bb182580f71dd070f88d01ce3de9f4da5021db7115d2e1c3605a754153b77c1" +checksum = "f8650aabb6c35b860610e9cff5dc1af886c9e25073b7b1712a68972af4281302" dependencies = [ "bytes", "heck 0.5.0", @@ -8100,10 +8121,10 @@ dependencies = [ "once_cell", "petgraph", "prettyplease", - "prost 0.13.1", - "prost-types 0.13.1", + "prost 0.13.2", + "prost-types 0.13.2", "regex", - "syn 2.0.72", + "syn 2.0.77", "tempfile", ] @@ -8122,15 +8143,15 @@ dependencies = [ [[package]] name = "prost-derive" -version = "0.13.1" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18bec9b0adc4eba778b33684b7ba3e7137789434769ee3ce3930463ef904cfca" +checksum = "acf0c195eebb4af52c752bec4f52f645da98b6e92077a04110c7f349477ae5ac" dependencies = [ "anyhow", "itertools 0.13.0", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.77", ] [[package]] @@ -8144,11 +8165,11 @@ dependencies = [ [[package]] name = "prost-types" -version = "0.13.1" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cee5168b05f49d4b0ca581206eb14a7b22fafd963efe729ac48eb03266e25cc2" +checksum = "60caa6738c7369b940c3d49246a8d1749323674c65cb13010134f5c9bad5b519" dependencies = [ - "prost 0.13.1", + "prost 0.13.2", ] [[package]] @@ -8210,17 +8231,18 @@ dependencies = [ [[package]] name = "quinn" -version = "0.11.2" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4ceeeeabace7857413798eb1ffa1e9c905a9946a57d81fb69b4b71c4d8eb3ad" +checksum = "8c7c5fdde3cdae7203427dc4f0a68fe0ed09833edc525a03456b153b79828684" dependencies = [ "bytes", "futures-io", "pin-project-lite", "quinn-proto", "quinn-udp", - "rustc-hash", + "rustc-hash 2.0.0", "rustls", + "socket2 0.5.7", "thiserror", "tokio", "tracing", @@ -8228,14 +8250,14 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.11.3" +version = "0.11.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddf517c03a109db8100448a4be38d498df8a210a99fe0e1b9eaf39e78c640efe" +checksum = "fadfaed2cd7f389d0161bb73eeb07b7b78f8691047a6f3e73caaeae55310a4a6" dependencies = [ "bytes", "rand", "ring 0.17.8", - "rustc-hash", + "rustc-hash 2.0.0", "rustls", "slab", "thiserror", @@ -8245,21 +8267,22 @@ dependencies = [ [[package]] name = "quinn-udp" -version = "0.5.4" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bffec3605b73c6f1754535084a85229fa8a30f86014e6c81aeec4abb68b0285" +checksum = "4fe68c2e9e1a1234e218683dbdf9f9dfcb094113c5ac2b938dfcb9bab4c4140b" dependencies = [ "libc", "once_cell", "socket2 0.5.7", - "windows-sys 0.52.0", + "tracing", + "windows-sys 0.59.0", ] [[package]] name = "quote" -version = "1.0.36" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] @@ -8389,18 +8412,18 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" +checksum = "0884ad60e090bf1345b93da0a5de8923c93884cd03f40dfcfddd3b4bee661853" dependencies = [ "bitflags 2.6.0", ] [[package]] name = "redox_users" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ "getrandom", "libredox", @@ -8409,9 +8432,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.5" +version = "1.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" +checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" dependencies = [ "aho-corasick", "memchr", @@ -8459,16 +8482,16 @@ checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" [[package]] name = "reqwest" -version = "0.12.5" +version = "0.12.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7d6d2a27d57148378eb5e111173f4276ad26340ecc5c49a4a2152167a2d6a37" +checksum = "f8f4955649ef5c38cc7f9e8aa41761d48fb9677197daea9984dc54f56aad5e63" dependencies = [ "base64 0.22.1", "bytes", "encoding_rs", "futures-core", "futures-util", - "h2 0.4.5", + "h2 0.4.6", "http 1.1.0", "http-body 1.0.1", "http-body-util", @@ -8486,7 +8509,7 @@ dependencies = [ "pin-project-lite", "quinn", "rustls", - "rustls-native-certs", + "rustls-native-certs 0.7.3", "rustls-pemfile", "rustls-pki-types", "serde", @@ -8501,7 +8524,7 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "winreg 0.52.0", + "windows-registry", ] [[package]] @@ -8573,7 +8596,7 @@ dependencies = [ "futures", "futures-timer", "rstest_macros", - "rustc_version 0.4.0", + "rustc_version 0.4.1", ] [[package]] @@ -8588,8 +8611,8 @@ dependencies = [ "quote", "regex", "relative-path", - "rustc_version 0.4.0", - "syn 2.0.72", + "rustc_version 0.4.1", + "syn 2.0.77", "unicode-ident", ] @@ -8655,9 +8678,9 @@ dependencies = [ [[package]] name = "rust_decimal" -version = "1.35.0" +version = "1.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1790d1c4c0ca81211399e0e0af16333276f375209e71a37b67698a373db5b47a" +checksum = "b082d80e3e3cc52b2ed634388d436fe1f4de6af5786cc2de9ba9737527bdf555" dependencies = [ "arrayvec", "num-traits 0.2.19", @@ -8675,6 +8698,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc-hash" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" + [[package]] name = "rustc-hex" version = "2.1.0" @@ -8692,9 +8721,9 @@ dependencies = [ [[package]] name = "rustc_version" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ "semver 1.0.23", ] @@ -8724,9 +8753,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.34" +version = "0.38.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" dependencies = [ "bitflags 2.6.0", "errno", @@ -8737,23 +8766,36 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.12" +version = "0.23.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c58f8c84392efc0a126acce10fa59ff7b3d2ac06ab451a33f2741989b806b044" +checksum = "f2dabaac7466917e566adb06783a81ca48944c6898a1b08b9374106dd671f4c8" dependencies = [ "once_cell", "ring 0.17.8", "rustls-pki-types", - "rustls-webpki 0.102.6", + "rustls-webpki 0.102.8", "subtle", "zeroize", ] [[package]] name = "rustls-native-certs" -version = "0.7.1" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a88d6d420651b496bdd98684116959239430022a115c1240e6c3993be0b15fba" +checksum = "e5bfb394eeed242e909609f56089eecfe5fda225042e8b171791b9c95f5931e5" +dependencies = [ + "openssl-probe", + "rustls-pemfile", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcaf18a4f2be7326cd874a5fa579fae794320a0f388d365dca7e480e55f83f8a" dependencies = [ "openssl-probe", "rustls-pemfile", @@ -8764,9 +8806,9 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "2.1.2" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" +checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425" dependencies = [ "base64 0.22.1", "rustls-pki-types", @@ -8774,9 +8816,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" +checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0" [[package]] name = "rustls-webpki" @@ -8790,9 +8832,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.102.6" +version = "0.102.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e6b52d4fda176fd835fdc55a835d4a89b8499cad995885a21149d5ad62f852e" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" dependencies = [ "ring 0.17.8", "rustls-pki-types", @@ -8846,7 +8888,7 @@ dependencies = [ "log", "oorandom", "parking_lot 0.11.2", - "rustc-hash", + "rustc-hash 1.1.0", "salsa-macros", "smallvec", ] @@ -8874,11 +8916,11 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +checksum = "e9aaafd5a2b6e3d657ff009d82fbd630b6bd54dd4eb06f21693925cdf80f9b8b" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -8912,7 +8954,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 2.0.72", + "syn 2.0.77", ] [[package]] @@ -8999,22 +9041,22 @@ checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" [[package]] name = "serde" -version = "1.0.204" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.204" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.77", ] [[package]] @@ -9025,14 +9067,14 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.77", ] [[package]] name = "serde_json" -version = "1.0.121" +version = "1.0.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ab380d7d9f22ef3f21ad3e6c1ebe8e4fc7a2000ccba2e4d71fc96f15b2cb609" +checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" dependencies = [ "itoa", "memchr", @@ -9091,7 +9133,7 @@ dependencies = [ "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.2.6", + "indexmap 2.5.0", "serde", "serde_derive", "serde_json", @@ -9108,7 +9150,7 @@ dependencies = [ "darling 0.20.10", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.77", ] [[package]] @@ -9145,9 +9187,9 @@ dependencies = [ [[package]] name = "sha3-asm" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57d79b758b7cb2085612b11a235055e485605a5103faccdd633f35bd7aee69dd" +checksum = "c28efc5e327c837aa837c59eae585fc250715ef939ac32881bcc11677cd02d46" dependencies = [ "cc", "cfg-if", @@ -9162,6 +9204,12 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "signal-hook-registry" version = "1.4.2" @@ -9232,7 +9280,7 @@ dependencies = [ "async-io 1.13.0", "async-lock 2.8.0", "async-net", - "async-process", + "async-process 1.8.1", "blocking", "futures-lite 1.13.0", ] @@ -9267,7 +9315,7 @@ dependencies = [ "curve25519-dalek", "rand_core", "ring 0.17.8", - "rustc_version 0.4.0", + "rustc_version 0.4.1", "sha2", "subtle", ] @@ -9376,7 +9424,7 @@ checksum = "bbc159a1934c7be9761c237333a57febe060ace2bc9e3b337a59a37af206d19f" dependencies = [ "starknet-curve 0.4.2", "starknet-ff", - "syn 2.0.72", + "syn 2.0.77", ] [[package]] @@ -9498,7 +9546,7 @@ dependencies = [ "cairo-lang-starknet-classes", "derive_more 0.99.18", "hex", - "indexmap 2.2.6", + "indexmap 2.5.0", "itertools 0.12.1", "once_cell", "primitive-types", @@ -9587,7 +9635,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.72", + "syn 2.0.77", ] [[package]] @@ -9600,7 +9648,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.72", + "syn 2.0.77", ] [[package]] @@ -9622,9 +9670,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.72" +version = "2.0.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af" +checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" dependencies = [ "proc-macro2", "quote", @@ -9633,14 +9681,14 @@ dependencies = [ [[package]] name = "syn-solidity" -version = "0.8.0" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "284c41c2919303438fcf8dede4036fd1e82d4fc0fbb2b279bd2a1442c909ca92" +checksum = "4b95156f8b577cb59dc0b1df15c6f29a10afc5f8a7ac9786b0b5c68c19149278" dependencies = [ "paste", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.77", ] [[package]] @@ -9654,6 +9702,9 @@ name = "sync_wrapper" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" +dependencies = [ + "futures-core", +] [[package]] name = "synstructure" @@ -9663,7 +9714,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.77", ] [[package]] @@ -9713,14 +9764,15 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tempfile" -version = "3.10.1" +version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" +checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" dependencies = [ "cfg-if", - "fastrand 2.1.0", - "rustix 0.38.34", - "windows-sys 0.52.0", + "fastrand 2.1.1", + "once_cell", + "rustix 0.38.37", + "windows-sys 0.59.0", ] [[package]] @@ -9749,7 +9801,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7" dependencies = [ - "rustix 0.38.34", + "rustix 0.38.37", "windows-sys 0.48.0", ] @@ -9777,7 +9829,7 @@ checksum = "5999e24eaa32083191ba4e425deb75cdf25efefabe5aaccb7446dd0d4122a3f5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.77", ] [[package]] @@ -9797,7 +9849,7 @@ checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.77", ] [[package]] @@ -9908,9 +9960,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.39.2" +version = "1.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daa4fb1bc778bd6f04cbfc4bb2d06a7396a8f299dc33ea1900cedaa316f467b1" +checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" dependencies = [ "backtrace", "bytes", @@ -9943,7 +9995,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.77", ] [[package]] @@ -9980,9 +10032,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.15" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" +checksum = "4f4e6ce100d0eb49a2734f8c0812bcd324cf357d21810932c5df6b96ef2b86f1" dependencies = [ "futures-core", "pin-project-lite", @@ -10020,9 +10072,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.11" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" +checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" dependencies = [ "bytes", "futures-core", @@ -10042,14 +10094,14 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.17" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a44eede9b727419af8095cb2d72fab15487a541f54647ad4414b34096ee4631" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.22.18", + "toml_edit", ] [[package]] @@ -10063,26 +10115,15 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.21.1" +version = "0.22.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" +checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" dependencies = [ - "indexmap 2.2.6", - "toml_datetime", - "winnow 0.5.40", -] - -[[package]] -name = "toml_edit" -version = "0.22.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1490595c74d930da779e944f5ba2ecdf538af67df1a9848cbd156af43c1b7cf0" -dependencies = [ - "indexmap 2.2.6", + "indexmap 2.5.0", "serde", "serde_spanned", "toml_datetime", - "winnow 0.6.16", + "winnow", ] [[package]] @@ -10107,7 +10148,7 @@ dependencies = [ "prost 0.11.9", "tokio", "tokio-stream", - "tower", + "tower 0.4.13", "tower-layer", "tower-service", "tracing", @@ -10133,6 +10174,20 @@ dependencies = [ "tracing", ] +[[package]] +name = "tower" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2873938d487c3cfb9aed7546dc9f2711d867c9f90c46b889989a2cb84eba6b4f" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper 0.1.2", + "tower-layer", + "tower-service", +] + [[package]] name = "tower-http" version = "0.5.2" @@ -10145,7 +10200,7 @@ dependencies = [ "http-body 1.0.1", "http-body-util", "pin-project-lite", - "tower", + "tower 0.4.13", "tower-layer", "tower-service", "tracing", @@ -10154,15 +10209,15 @@ dependencies = [ [[package]] name = "tower-layer" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" [[package]] name = "tower-service" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" @@ -10184,7 +10239,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.77", ] [[package]] @@ -10328,9 +10383,9 @@ checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" [[package]] name = "unicode-normalization" @@ -10349,9 +10404,9 @@ checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" [[package]] name = "unicode-xid" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" +checksum = "229730647fbc343e3a80e463c1db7f78f3855d3f3739bee0dda773c9a037c90a" [[package]] name = "universal-hash" @@ -10559,34 +10614,35 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" dependencies = [ "cfg-if", + "once_cell", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.77", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.42" +version = "0.4.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" +checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed" dependencies = [ "cfg-if", "js-sys", @@ -10596,9 +10652,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -10606,28 +10662,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.77", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" +checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" [[package]] name = "web-sys" -version = "0.3.69" +version = "0.3.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" +checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" dependencies = [ "js-sys", "wasm-bindgen", @@ -10645,9 +10701,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.26.3" +version = "0.26.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd7c23921eeb1713a4e851530e9b9756e4fb0e89978582942612524cf09f01cd" +checksum = "0bd24728e5af82c6c4ec1b66ac4844bdf8156257fccda846ec58b42cd0cdbe6a" dependencies = [ "rustls-pki-types", ] @@ -10676,11 +10732,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -10717,6 +10773,36 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-registry" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" +dependencies = [ + "windows-result", + "windows-strings", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result", + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.48.0" @@ -10735,6 +10821,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-targets" version = "0.48.5" @@ -10858,18 +10953,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.5.40" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" -dependencies = [ - "memchr", -] - -[[package]] -name = "winnow" -version = "0.6.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b480ae9340fc261e6be3e95a1ba86d54ae3f9171132a73ce8d4bbaf68339507c" +checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" dependencies = [ "memchr", ] @@ -10884,16 +10970,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "winreg" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" -dependencies = [ - "cfg-if", - "windows-sys 0.48.0", -] - [[package]] name = "ws_stream_wasm" version = "0.7.4" @@ -10905,7 +10981,7 @@ dependencies = [ "js-sys", "log", "pharos", - "rustc_version 0.4.0", + "rustc_version 0.4.1", "send_wrapper", "thiserror", "wasm-bindgen", @@ -10953,9 +11029,9 @@ dependencies = [ [[package]] name = "xml-rs" -version = "0.8.20" +version = "0.8.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "791978798f0597cfc70478424c2b4fdc2b7a8024aaff78497ef00f24ef674193" +checksum = "af4e2e2f7cba5a093896c1e150fbfe177d1883e7448200efb81d40b9d339ef26" [[package]] name = "xmltree" @@ -11045,7 +11121,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.77", ] [[package]] @@ -11065,7 +11141,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.77", ] [[package]] @@ -11103,7 +11179,7 @@ version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcf2b778a664581e31e389454a7072dab1647606d44f7feea22cd5abb9c9f3f9" dependencies = [ - "zstd-safe 7.2.0", + "zstd-safe 7.2.1", ] [[package]] @@ -11118,18 +11194,18 @@ dependencies = [ [[package]] name = "zstd-safe" -version = "7.2.0" +version = "7.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa556e971e7b568dc775c136fc9de8c779b1c2fc3a63defaafadffdbd3181afa" +checksum = "54a3ab4db68cea366acc5c897c7b4d4d1b8994a9cd6e6f841f8964566a419059" dependencies = [ "zstd-sys", ] [[package]] name = "zstd-sys" -version = "2.0.12+zstd.1.5.6" +version = "2.0.13+zstd.1.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a4e40c320c3cb459d9a9ff6de98cff88f4751ee9275d140e2be94a2b74e4c13" +checksum = "38ff0f21cfee8f97d94cef41359e0c89aa6113028ab0291aa8ca0038995a95aa" dependencies = [ "cc", "pkg-config", diff --git a/crates/pathfinder/src/bin/pathfinder/main.rs b/crates/pathfinder/src/bin/pathfinder/main.rs index ae2fbe461b..55bec79b74 100644 --- a/crates/pathfinder/src/bin/pathfinder/main.rs +++ b/crates/pathfinder/src/bin/pathfinder/main.rs @@ -15,7 +15,7 @@ use pathfinder_lib::monitoring::{self}; use pathfinder_lib::state; use pathfinder_lib::state::SyncContext; use pathfinder_rpc::context::WebsocketContext; -use pathfinder_rpc::SyncState; +use pathfinder_rpc::{Notifications, SyncState}; use pathfinder_storage::Storage; use primitive_types::H160; use starknet_gateway_client::GatewayApi; @@ -217,6 +217,8 @@ Hint: This is usually caused by exceeding the file descriptor limit of your syst custom_versioned_constants: config.custom_versioned_constants.take(), }; + let notifications = Notifications::default(); + let context = pathfinder_rpc::context::RpcContext::new( rpc_storage, execution_storage, @@ -224,6 +226,7 @@ Hint: This is usually caused by exceeding the file descriptor limit of your syst pathfinder_context.network_id, pathfinder_context.gateway.clone(), rx_pending.clone(), + notifications.clone(), rpc_config, ); @@ -264,6 +267,7 @@ Hint: This is usually caused by exceeding the file descriptor limit of your syst &config, tx_pending, rpc_server.get_topic_broadcasters().cloned(), + notifications, gossiper, gateway_public_key, p2p_client, @@ -497,6 +501,7 @@ fn start_sync( config: &config::Config, tx_pending: tokio::sync::watch::Sender, websocket_txs: Option, + notifications: Notifications, gossiper: state::Gossiper, gateway_public_key: pathfinder_common::PublicKey, p2p_client: Option, @@ -511,6 +516,7 @@ fn start_sync( config, tx_pending, websocket_txs, + notifications, gossiper, gateway_public_key, ) @@ -538,6 +544,7 @@ fn start_sync( config: &config::Config, tx_pending: tokio::sync::watch::Sender, websocket_txs: Option, + notifications: Notifications, gossiper: state::Gossiper, gateway_public_key: pathfinder_common::PublicKey, _p2p_client: Option, @@ -551,6 +558,7 @@ fn start_sync( config, tx_pending, websocket_txs, + notifications, gossiper, gateway_public_key, ) @@ -565,6 +573,7 @@ fn start_feeder_gateway_sync( config: &config::Config, tx_pending: tokio::sync::watch::Sender, websocket_txs: Option, + notifications: Notifications, gossiper: state::Gossiper, gateway_public_key: pathfinder_common::PublicKey, ) -> tokio::task::JoinHandle> { @@ -581,6 +590,7 @@ fn start_feeder_gateway_sync( pending_data: tx_pending, block_validation_mode: state::l2::BlockValidationMode::Strict, websocket_txs, + notifications, block_cache_size: 1_000, restart_delay: config.debug.restart_delay, verify_tree_hashes: config.verify_tree_hashes, diff --git a/crates/pathfinder/src/state/sync.rs b/crates/pathfinder/src/state/sync.rs index 326f905b1d..dbebfbdf74 100644 --- a/crates/pathfinder/src/state/sync.rs +++ b/crates/pathfinder/src/state/sync.rs @@ -25,7 +25,7 @@ use pathfinder_ethereum::{EthereumApi, EthereumStateUpdate}; use pathfinder_merkle_tree::contract_state::update_contract_state; use pathfinder_merkle_tree::{ClassCommitmentTree, StorageCommitmentTree}; use pathfinder_rpc::v02::types::syncing::{self, NumberedBlock, Syncing}; -use pathfinder_rpc::{PendingData, SyncState, TopicBroadcasters}; +use pathfinder_rpc::{Notifications, PendingData, SyncState, TopicBroadcasters}; use pathfinder_storage::{Connection, Storage, Transaction, TransactionBehavior}; use primitive_types::H160; use starknet_gateway_client::GatewayApi; @@ -85,6 +85,7 @@ pub struct SyncContext { pub pending_data: WatchSender, pub block_validation_mode: l2::BlockValidationMode, pub websocket_txs: Option, + pub notifications: Notifications, pub block_cache_size: usize, pub restart_delay: Duration, pub verify_tree_hashes: bool, @@ -193,6 +194,7 @@ where pending_data, block_validation_mode: _, websocket_txs, + notifications, block_cache_size, restart_delay, verify_tree_hashes: _, @@ -271,6 +273,7 @@ where pending_data, verify_tree_hashes: context.verify_tree_hashes, websocket_txs, + notifications, }; let mut consumer_handle = tokio::spawn(consumer(event_receiver, consumer_context, tx_current)); @@ -443,6 +446,7 @@ struct ConsumerContext { pub pending_data: WatchSender, pub verify_tree_hashes: bool, pub websocket_txs: Option, + pub notifications: Notifications, } async fn consumer( @@ -456,6 +460,7 @@ async fn consumer( pending_data, verify_tree_hashes, mut websocket_txs, + mut notifications, } = context; let mut last_block_start = std::time::Instant::now(); @@ -522,6 +527,7 @@ async fn consumer( verify_tree_hashes, storage.clone(), &mut websocket_txs, + &mut notifications, ) .await .with_context(|| format!("Update L2 state to {block_number}"))?; @@ -851,6 +857,7 @@ async fn l2_update( // parallel contract state updates storage: Storage, websocket_txs: &mut Option, + notifications: &mut Notifications, ) -> anyhow::Result<()> { tokio::task::block_in_place(move || { let transaction = connection @@ -974,7 +981,7 @@ async fn l2_update( .context("Commit database transaction")?; if let Some(sender) = websocket_txs { - if let Err(e) = sender.new_head.send_if_receiving(header.into()) { + if let Err(e) = sender.new_head.send_if_receiving(header.clone().into()) { tracing::error!(error=?e, "Failed to send header over websocket broadcaster."); // Disable websocket entirely so that the closed channel doesn't spam this // error. It is unlikely that any error here wouldn't simply repeat @@ -991,6 +998,13 @@ async fn l2_update( } } + notifications + .block_headers + .send(header.into()) + // Ignore errors in case nobody is listening. New listeners may subscribe in the + // future. + .ok(); + Ok(()) })?; @@ -1366,6 +1380,7 @@ mod tests { pending_data: tx, verify_tree_hashes: false, websocket_txs: None, + notifications: Default::default(), }; let (tx, _rx) = tokio::sync::watch::channel(Default::default()); @@ -1415,6 +1430,7 @@ mod tests { pending_data: tx, verify_tree_hashes: false, websocket_txs: None, + notifications: Default::default(), }; let (tx, _rx) = tokio::sync::watch::channel(Default::default()); @@ -1478,6 +1494,7 @@ mod tests { pending_data: tx, verify_tree_hashes: false, websocket_txs: None, + notifications: Default::default(), }; let (tx, _rx) = tokio::sync::watch::channel(Default::default()); @@ -1526,6 +1543,7 @@ mod tests { pending_data: tx, verify_tree_hashes: false, websocket_txs: None, + notifications: Default::default(), }; let (tx, _rx) = tokio::sync::watch::channel(Default::default()); @@ -1563,6 +1581,7 @@ mod tests { pending_data: tx, verify_tree_hashes: false, websocket_txs: None, + notifications: Default::default(), }; let (tx, _rx) = tokio::sync::watch::channel(Default::default()); @@ -1603,6 +1622,7 @@ mod tests { pending_data: tx, verify_tree_hashes: false, websocket_txs: None, + notifications: Default::default(), }; let (tx, _rx) = tokio::sync::watch::channel(Default::default()); @@ -1646,6 +1666,7 @@ mod tests { pending_data: tx, verify_tree_hashes: false, websocket_txs: None, + notifications: Default::default(), }; let (tx, _rx) = tokio::sync::watch::channel(Default::default()); diff --git a/crates/rpc/Cargo.toml b/crates/rpc/Cargo.toml index 7ddd16ed0e..60ad832ca5 100644 --- a/crates/rpc/Cargo.toml +++ b/crates/rpc/Cargo.toml @@ -12,6 +12,7 @@ anyhow = { workspace = true } async-trait = { workspace = true } axum = { workspace = true, features = ["ws", "macros"] } base64 = { workspace = true } +dashmap = "6.1.0" flate2 = { workspace = true } futures = { workspace = true } http = { workspace = true } diff --git a/crates/rpc/src/context.rs b/crates/rpc/src/context.rs index 2eaad96a13..a085f29982 100644 --- a/crates/rpc/src/context.rs +++ b/crates/rpc/src/context.rs @@ -6,6 +6,7 @@ use pathfinder_executor::{TraceCache, VersionedConstants}; use pathfinder_storage::Storage; pub use crate::jsonrpc::websocket::WebsocketContext; +use crate::jsonrpc::Notifications; use crate::pending::{PendingData, PendingWatcher}; use crate::SyncState; @@ -30,10 +31,12 @@ pub struct RpcContext { pub chain_id: ChainId, pub sequencer: SequencerClient, pub websocket: Option, + pub notifications: Notifications, pub config: RpcConfig, } impl RpcContext { + #[allow(clippy::too_many_arguments)] pub fn new( storage: Storage, execution_storage: Storage, @@ -41,6 +44,7 @@ impl RpcContext { chain_id: ChainId, sequencer: SequencerClient, pending_data: tokio_watch::Receiver, + notifications: Notifications, config: RpcConfig, ) -> Self { let pending_data = PendingWatcher::new(pending_data); @@ -53,6 +57,7 @@ impl RpcContext { pending_data, sequencer, websocket: None, + notifications, config, } } @@ -111,6 +116,7 @@ impl RpcContext { chain_id, sequencer.disable_retry_for_tests(), rx, + Notifications::default(), config, ) } diff --git a/crates/rpc/src/jsonrpc.rs b/crates/rpc/src/jsonrpc.rs index 3d6740b3ee..0ef8543e87 100644 --- a/crates/rpc/src/jsonrpc.rs +++ b/crates/rpc/src/jsonrpc.rs @@ -4,10 +4,13 @@ mod response; mod router; pub mod websocket; +use std::sync::Arc; + pub use error::RpcError; pub use request::RpcRequest; pub use response::RpcResponse; -pub use router::{rpc_handler, RpcRouter, RpcRouterBuilder}; +pub use router::{rpc_handler, RpcRouter, RpcRouterBuilder, RpcSubscriptionFlow}; +use tokio::sync::broadcast; #[derive(Debug, PartialEq, Clone)] pub enum RequestId { @@ -22,3 +25,17 @@ impl RequestId { self == &RequestId::Notification } } + +/// Channels used to notify the RPC of new events. Used by the RPC subscription +/// system. +#[derive(Debug, Clone)] +pub struct Notifications { + pub block_headers: broadcast::Sender>, +} + +impl Default for Notifications { + fn default() -> Self { + let (block_headers, _) = broadcast::channel(1024); + Self { block_headers } + } +} diff --git a/crates/rpc/src/jsonrpc/router.rs b/crates/rpc/src/jsonrpc/router.rs index ec7b9739ad..721f6c3938 100644 --- a/crates/rpc/src/jsonrpc/router.rs +++ b/crates/rpc/src/jsonrpc/router.rs @@ -1,30 +1,37 @@ use std::collections::HashMap; use std::num::NonZeroUsize; -use axum::async_trait; -use axum::extract::State; +use axum::extract::{State, WebSocketUpgrade}; use axum::http::StatusCode; use axum::response::IntoResponse; use futures::{Future, FutureExt, StreamExt}; use http::HeaderValue; -use serde_json::value::RawValue; -use tracing::Instrument; +use method::RpcMethodEndpoint; +pub use subscription::RpcSubscriptionFlow; +use subscription::{handle_json_rpc_socket, RpcSubscriptionEndpoint}; use crate::context::RpcContext; use crate::jsonrpc::error::RpcError; -use crate::jsonrpc::request::{RawParams, RpcRequest}; -use crate::jsonrpc::response::{RpcResponse, RpcResult}; +use crate::jsonrpc::request::RpcRequest; +use crate::jsonrpc::response::RpcResponse; use crate::RpcVersion; +mod method; +mod subscription; + +pub use method::handle_json_rpc_body; + #[derive(Clone)] pub struct RpcRouter { pub context: RpcContext, - methods: &'static HashMap<&'static str, Box>, + method_endpoints: &'static HashMap<&'static str, Box>, + subscription_endpoints: &'static HashMap<&'static str, Box>, version: RpcVersion, } pub struct RpcRouterBuilder { - methods: HashMap<&'static str, Box>, + method_endpoints: HashMap<&'static str, Box>, + subscription_endpoints: HashMap<&'static str, Box>, version: RpcVersion, } @@ -32,38 +39,55 @@ impl RpcRouterBuilder { /// Registers an RPC method. /// /// Panics if the method was already registered. - pub fn register>( + pub fn register>( mut self, method_name: &'static str, method: M, ) -> Self { - if self - .methods - .insert(method_name, IntoRpcMethod::into_method(method)) - .is_some() - { - panic!("'{method_name}' is already registered"); + match IntoRpcEndpoint::into_endpoint(method).0 { + RpcEndpointInner::Method(method) => { + if self.method_endpoints.insert(method_name, method).is_some() { + panic!("'{method_name}' is already registered"); + } + if self.subscription_endpoints.contains_key(method_name) { + panic!("'{method_name}' is already registered as a subscription"); + } + } + RpcEndpointInner::Subscription(subscription) => { + if self + .subscription_endpoints + .insert(method_name, subscription) + .is_some() + { + panic!("'{method_name}' is already registered"); + } + if self.method_endpoints.contains_key(method_name) { + panic!("'{method_name}' is already registered as a method"); + } + } } self } pub fn build(self, context: RpcContext) -> RpcRouter { - // Intentionally leak the hashmap to give it a static lifetime. - // + // Intentionally leak the hashmaps to give them a static lifetime. // Since the router is expected to be long lived, this shouldn't be an issue. - let methods = Box::new(self.methods); + let methods = Box::new(self.method_endpoints); let methods = Box::leak(methods); - + let subscriptions = Box::new(self.subscription_endpoints); + let subscriptions = Box::leak(subscriptions); RpcRouter { context, - methods, + method_endpoints: methods, + subscription_endpoints: subscriptions, version: self.version, } } fn new(version: RpcVersion) -> Self { RpcRouterBuilder { - methods: Default::default(), + method_endpoints: Default::default(), + subscription_endpoints: Default::default(), version, } } @@ -92,7 +116,8 @@ impl RpcRouter { // Also grab the method_name as it is a static str, which is required by the // metrics. - let Some((&method_name, method)) = self.methods.get_key_value(request.method.as_ref()) + let Some((&method_name, method)) = + self.method_endpoints.get_key_value(request.method.as_ref()) else { return Some(RpcResponse::method_not_found(request.id)); }; @@ -156,31 +181,41 @@ fn is_utf8_encoded_json(headers: http::HeaderMap) -> bool { pub async fn rpc_handler( State(state): State, headers: http::HeaderMap, + ws: Option, body: axum::body::Bytes, ) -> impl axum::response::IntoResponse { - // Only utf8 json content allowed. - if !is_utf8_encoded_json(headers) { - return StatusCode::UNSUPPORTED_MEDIA_TYPE.into_response(); - } - - let mut response = match handle_json_rpc_body(&state, body.as_ref()).await { - Ok(responses) => match responses { - RpcResponses::Empty => ().into_response(), - RpcResponses::Single(response) => response.into_response(), - RpcResponses::Multiple(responses) => { - serde_json::to_string(&responses).unwrap().into_response() + match ws { + Some(ws) => ws.on_upgrade(|ws| async move { + handle_json_rpc_socket(state, ws).await; + }), + None => { + // Only utf8 json content allowed. + if !is_utf8_encoded_json(headers) { + return StatusCode::UNSUPPORTED_MEDIA_TYPE.into_response(); } - }, - Err(RpcRequestError::ParseError(e)) => RpcResponse::parse_error(e).into_response(), - Err(RpcRequestError::InvalidRequest(e)) => RpcResponse::invalid_request(e).into_response(), - }; - use http::header::CONTENT_TYPE; - static APPLICATION_JSON: HeaderValue = HeaderValue::from_static("application/json"); - response - .headers_mut() - .insert(CONTENT_TYPE, APPLICATION_JSON.clone()); - response + let mut response = match handle_json_rpc_body(&state, body.as_ref()).await { + Ok(responses) => match responses { + RpcResponses::Empty => ().into_response(), + RpcResponses::Single(response) => response.into_response(), + RpcResponses::Multiple(responses) => { + serde_json::to_string(&responses).unwrap().into_response() + } + }, + Err(RpcRequestError::ParseError(e)) => RpcResponse::parse_error(e).into_response(), + Err(RpcRequestError::InvalidRequest(e)) => { + RpcResponse::invalid_request(e).into_response() + } + }; + + use http::header::CONTENT_TYPE; + static APPLICATION_JSON: HeaderValue = HeaderValue::from_static("application/json"); + response + .headers_mut() + .insert(CONTENT_TYPE, APPLICATION_JSON.clone()); + response + } + } } pub(super) enum RpcRequestError { @@ -201,425 +236,52 @@ impl serde::ser::Serialize for RpcResponses { S: serde::Serializer, { match self { - Self::Empty => ().serialize(serializer), - Self::Single(response) => response.serialize(serializer), - Self::Multiple(responses) => responses.serialize(serializer), + Self::Empty => serde::ser::Serialize::serialize(&(), serializer), + Self::Single(response) => serde::ser::Serialize::serialize(response, serializer), + Self::Multiple(responses) => serde::ser::Serialize::serialize(responses, serializer), } } } -/// Helper to scope the responses so we can set the content-type afterwards -/// instead of dealing with branches / early exits. -pub(super) async fn handle_json_rpc_body( - state: &RpcRouter, - body: &[u8], -) -> Result { - // Unfortunately due to this https://github.com/serde-rs/json/issues/497 - // we cannot use an enum with borrowed raw values inside to do a single - // deserialization for us. Instead we have to distinguish manually - // between a single request and a batch request which we do by checking - // the first byte. - if body.first() != Some(&b'[') { - let request = match serde_json::from_slice::<&RawValue>(body) { - Ok(request) => request, - Err(e) => { - return Err(RpcRequestError::ParseError(e.to_string())); - } - }; - - match state.run_request(request.get()).await { - Some(response) => Ok(RpcResponses::Single(response)), - None => Ok(RpcResponses::Empty), - } - } else { - let requests = match serde_json::from_slice::>(body) { - Ok(requests) => requests, - Err(e) => { - return Err(RpcRequestError::ParseError(e.to_string())); - } - }; - - if requests.is_empty() { - return Err(RpcRequestError::InvalidRequest( - "A batch request must contain at least one request".to_owned(), - )); - } +pub struct RpcEndpoint(RpcEndpointInner); - let responses = run_concurrently( - state.context.config.batch_concurrency_limit, - requests.into_iter().enumerate(), - |(idx, request)| { - state - .run_request(request.get()) - .instrument(tracing::debug_span!("batch", idx)) - }, - ) - .await - .flatten() - .collect::>(); - - // All requests were notifications. - if responses.is_empty() { - return Ok(RpcResponses::Empty); - } - - Ok(RpcResponses::Multiple(responses)) - } +enum RpcEndpointInner { + Method(Box), + Subscription(Box), } -#[axum::async_trait] -pub trait RpcMethod: Send + Sync { - async fn invoke<'a>( - &self, - state: RpcContext, - input: RawParams<'a>, - version: RpcVersion, - ) -> RpcResult; +/// This trait is implemented for all types that implemented +/// [`subscription::RpcSubscriptionEndpoint`], or for async functions. You can +/// find the particular list in the [`method`] module. +/// +/// (The generic parameters are there to allow multiple different blanket +/// implementations of the trait - this allows the trait to be implemented for +/// different `Fn` signatures. It's a Rust hack.) +pub trait IntoRpcEndpoint { + fn into_endpoint(self) -> RpcEndpoint; } -/// Utility trait which automates the serde of an RPC methods input and output. -/// -/// This trait is sealed to prevent attempts at implementing it manually. This -/// will likely clash with the existing blanket implementations with very -/// unhelpful error messages. -/// -/// This trait is automatically implemented for the following methods: -/// ``` -/// async fn input_and_context( -/// ctx: RpcContext, -/// input: impl Deserialize, -/// ) -> Result>; -/// async fn input_only(input: impl Deserialize) -> Result>; -/// async fn context_only(ctx: RpcContext) -> Result>; -/// ``` -/// -/// The generics allow us to achieve a form of variadic specialization and can -/// be ignored by callers. See [sealed::Sealed] to add more method signatures or -/// more information on how this works. -pub trait IntoRpcMethod<'a, I, O, S>: sealed::Sealed { - fn into_method(self) -> Box; +impl IntoRpcEndpoint<(), (), ()> for RpcEndpoint { + fn into_endpoint(self) -> RpcEndpoint { + self + } } -impl<'a, T, I, O, S> IntoRpcMethod<'a, I, O, S> for T +impl IntoRpcEndpoint<(), (), T> for T where - T: sealed::Sealed, + T: RpcMethodEndpoint + 'static, { - fn into_method(self) -> Box { - sealed::Sealed::::into_method(self) + fn into_endpoint(self) -> RpcEndpoint { + RpcEndpoint(RpcEndpointInner::Method(Box::new(self))) } } -mod sealed { - use std::marker::PhantomData; - - use super::*; - use crate::dto::serialize::{SerializeForVersion, Serializer}; - use crate::dto::DeserializeForVersion; - use crate::jsonrpc::error::RpcError; - use crate::RpcVersion; - - /// Sealed implementation of [RpcMethod]. - /// - /// The generics allow for a form of specialization over a methods Input, - /// Output and State by treating each as a tuple. Varying the tuple - /// length allows us to target a specific method signature. This same - /// could be achieved with a single generic but it becomes less clear as - /// each permutation would require a different tuple length. - /// - /// By convention, the lack of a type is equivalent to the unit tuple (). So - /// if we want to target functions with no input params, no input state - /// and an output: - /// ```rust - /// Sealed - /// ``` - pub trait Sealed { - fn into_method(self) -> Box; - } - - /// ``` - /// async fn example(RpcContext, impl Deserialize, RpcVersion) -> Result> - /// ``` - impl<'a, F, Input, Output, Error, Fut> - Sealed<((), (), Input), ((), (), Output), ((), (), RpcContext)> for F - where - F: Fn(RpcContext, Input, RpcVersion) -> Fut + Sync + Send + 'static, - Input: DeserializeForVersion + Send + Sync + 'static, - Output: SerializeForVersion + Send + Sync + 'static, - Error: Into + Send + Sync + 'static, - Fut: Future> + Send, - { - fn into_method(self) -> Box { - struct Helper { - f: F, - _marker: PhantomData<(Input, Output, Error)>, - } - - #[axum::async_trait] - impl RpcMethod for Helper - where - F: Fn(RpcContext, Input, RpcVersion) -> Fut + Sync + Send, - Input: DeserializeForVersion + Send + Sync, - Output: SerializeForVersion + Send + Sync, - Error: Into + Send + Sync, - Fut: Future> + Send, - { - async fn invoke<'a>( - &self, - state: RpcContext, - input: RawParams<'a>, - version: RpcVersion, - ) -> RpcResult { - let input = input.deserialize_for_version(version)?; - (self.f)(state, input, version) - .await - .map_err(Into::into)? - .serialize(Serializer::new(version)) - .map_err(|e| RpcError::InternalError(e.into())) - } - } - - Box::new(Helper { - f: self, - _marker: Default::default(), - }) - } - } - - /// ``` - /// async fn example(RpcContext, impl Deserialize) -> Result> - /// ``` - impl<'a, F, Input, Output, Error, Fut> Sealed<((), Input), ((), Output), ((), RpcContext)> for F - where - F: Fn(RpcContext, Input) -> Fut + Sync + Send + 'static, - Input: DeserializeForVersion + Send + Sync + 'static, - Output: SerializeForVersion + Send + Sync + 'static, - Error: Into + Send + Sync + 'static, - Fut: Future> + Send, - { - fn into_method(self) -> Box { - struct Helper { - f: F, - _marker: PhantomData<(Input, Output, Error)>, - } - - #[axum::async_trait] - impl RpcMethod for Helper - where - F: Fn(RpcContext, Input) -> Fut + Sync + Send, - Input: DeserializeForVersion + Send + Sync, - Output: SerializeForVersion + Send + Sync, - Error: Into + Send + Sync, - Fut: Future> + Send, - { - async fn invoke<'a>( - &self, - state: RpcContext, - input: RawParams<'a>, - version: RpcVersion, - ) -> RpcResult { - let input = input.deserialize_for_version(version)?; - (self.f)(state, input) - .await - .map_err(Into::into)? - .serialize(Serializer::new(version)) - .map_err(|e| RpcError::InternalError(e.into())) - } - } - - Box::new(Helper { - f: self, - _marker: Default::default(), - }) - } - } - - /// ``` - /// async fn example(impl Deserialize) -> Result> - /// ``` - #[async_trait] - impl<'a, F, Input, Output, Error, Fut> Sealed<((), Input), ((), Output), ()> for F - where - F: Fn(Input) -> Fut + Sync + Send + 'static, - Input: DeserializeForVersion + Sync + Send + 'static, - Output: SerializeForVersion + Sync + Send + 'static, - Error: Into + Sync + Send + 'static, - Fut: Future> + Send, - { - fn into_method(self) -> Box { - struct Helper { - f: F, - _marker: PhantomData<(Input, Output, Error)>, - } - - #[axum::async_trait] - impl RpcMethod for Helper - where - F: Fn(Input) -> Fut + Sync + Send, - Input: DeserializeForVersion + Send + Sync, - Output: SerializeForVersion + Send + Sync, - Error: Into + Send + Sync, - Fut: Future> + Send, - { - async fn invoke<'a>( - &self, - _state: RpcContext, - input: RawParams<'a>, - version: RpcVersion, - ) -> RpcResult { - let input = input.deserialize_for_version(version)?; - (self.f)(input) - .await - .map_err(Into::into)? - .serialize(Serializer::new(version)) - .map_err(|e| RpcError::InternalError(e.into())) - } - } - - Box::new(Helper { - f: self, - _marker: Default::default(), - }) - } - } - - /// ``` - /// async fn example(RpcContext) -> Result> - /// ``` - #[async_trait] - impl<'a, F, Output, Error, Fut> Sealed<(), ((), Output), ((), RpcContext)> for F - where - F: Fn(RpcContext) -> Fut + Sync + Send + 'static, - Output: SerializeForVersion + Sync + Send + 'static, - Error: Into + Send + Sync + 'static, - Fut: Future> + Send, - { - fn into_method(self) -> Box { - struct Helper { - f: F, - _marker: PhantomData<(Output, Error)>, - } - - #[axum::async_trait] - impl RpcMethod for Helper - where - F: Fn(RpcContext) -> Fut + Sync + Send, - Output: SerializeForVersion + Send + Sync, - Error: Into + Send + Sync, - Fut: Future> + Send, - { - async fn invoke<'a>( - &self, - state: RpcContext, - input: RawParams<'a>, - version: RpcVersion, - ) -> RpcResult { - if !input.is_empty() { - return Err(RpcError::InvalidParams( - "This method takes no inputs".to_owned(), - )); - } - (self.f)(state) - .await - .map_err(Into::into)? - .serialize(Serializer::new(version)) - .map_err(|e| RpcError::InternalError(e.into())) - } - } - - Box::new(Helper { - f: self, - _marker: Default::default(), - }) - } - } - - /// ``` - /// async fn example() -> Result> - /// ``` - #[async_trait] - impl<'a, F, Output, Error, Fut> Sealed<(), (), ((), Output)> for F - where - F: Fn() -> Fut + Sync + Send + 'static, - Output: SerializeForVersion + Sync + Send + 'static, - Error: Into + Sync + Send + 'static, - Fut: Future> + Send, - { - fn into_method(self) -> Box { - struct Helper { - f: F, - _marker: PhantomData<(Output, Error)>, - } - - #[axum::async_trait] - impl RpcMethod for Helper - where - F: Fn() -> Fut + Sync + Send, - Output: SerializeForVersion + Send + Sync, - Error: Into + Send + Sync, - Fut: Future> + Send, - { - async fn invoke<'a>( - &self, - _state: RpcContext, - input: RawParams<'a>, - version: RpcVersion, - ) -> RpcResult { - if !input.is_empty() { - return Err(RpcError::InvalidParams( - "This method takes no inputs".to_owned(), - )); - } - (self.f)() - .await - .map_err(Into::into)? - .serialize(Serializer::new(version)) - .map_err(|e| RpcError::InternalError(e.into())) - } - } - - Box::new(Helper { - f: self, - _marker: Default::default(), - }) - } - } - - /// ``` - /// fn example() -> &'static str - /// ``` - #[async_trait] - impl<'a, F> Sealed<(), (), ((), (), &'static str)> for F - where - F: Fn() -> &'static str + Sync + Send + 'static, - { - fn into_method(self) -> Box { - struct Helper { - f: F, - } - - #[axum::async_trait] - impl RpcMethod for Helper - where - F: Fn() -> &'static str + Sync + Send, - { - async fn invoke<'a>( - &self, - _state: RpcContext, - input: RawParams<'a>, - version: RpcVersion, - ) -> RpcResult { - if !input.is_empty() { - return Err(RpcError::InvalidParams( - "This method takes no inputs".to_owned(), - )); - } - (self.f)() - .serialize(Serializer::new(version)) - .map_err(|e| RpcError::InternalError(e.into())) - } - } - Box::new(Helper { f: self }) - } +impl IntoRpcEndpoint<(), T, ()> for T +where + T: RpcSubscriptionEndpoint + 'static, +{ + fn into_endpoint(self) -> RpcEndpoint { + RpcEndpoint(RpcEndpointInner::Subscription(Box::new(self))) } } @@ -696,6 +358,7 @@ mod tests { use serde_json::{json, Value}; use super::*; + use crate::jsonrpc::response::RpcResult; async fn spawn_server(router: RpcRouter) -> String { let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap(); diff --git a/crates/rpc/src/jsonrpc/router/method.rs b/crates/rpc/src/jsonrpc/router/method.rs new file mode 100644 index 0000000000..74eec56272 --- /dev/null +++ b/crates/rpc/src/jsonrpc/router/method.rs @@ -0,0 +1,381 @@ +//! Implementations for RPC non-subscription method handlers. + +use std::future::Future; +use std::marker::PhantomData; + +use axum::async_trait; +use serde_json::value::RawValue; +use tracing::Instrument; + +use super::{ + run_concurrently, + IntoRpcEndpoint, + RpcEndpoint, + RpcRequestError, + RpcResponses, + RpcRouter, +}; +use crate::context::RpcContext; +use crate::dto::serialize::{SerializeForVersion, Serializer}; +use crate::dto::DeserializeForVersion; +use crate::jsonrpc::request::RawParams; +use crate::jsonrpc::response::RpcResult; +use crate::jsonrpc::router::RpcEndpointInner; +use crate::jsonrpc::{RpcError, RpcResponse}; +use crate::RpcVersion; + +#[axum::async_trait] +pub(super) trait RpcMethodEndpoint: Send + Sync { + async fn invoke<'a>( + &self, + state: RpcContext, + input: RawParams<'a>, + version: RpcVersion, + ) -> RpcResult; +} + +/// Helper to scope the responses so we can set the content-type afterwards +/// instead of dealing with branches / early exits. +pub async fn handle_json_rpc_body( + state: &RpcRouter, + body: &[u8], +) -> Result { + // Unfortunately due to this https://github.com/serde-rs/json/issues/497 + // we cannot use an enum with borrowed raw values inside to do a single + // deserialization for us. Instead we have to distinguish manually + // between a single request and a batch request which we do by checking + // the first byte. + if body.first() != Some(&b'[') { + let request = match serde_json::from_slice::<&RawValue>(body) { + Ok(request) => request, + Err(e) => { + return Err(RpcRequestError::ParseError(e.to_string())); + } + }; + + match state.run_request(request.get()).await { + Some(response) => Ok(RpcResponses::Single(response)), + None => Ok(RpcResponses::Empty), + } + } else { + let requests = match serde_json::from_slice::>(body) { + Ok(requests) => requests, + Err(e) => { + return Err(RpcRequestError::ParseError(e.to_string())); + } + }; + + if requests.is_empty() { + return Err(RpcRequestError::InvalidRequest( + "A batch request must contain at least one request".to_owned(), + )); + } + + let responses = run_concurrently( + state.context.config.batch_concurrency_limit, + requests.into_iter().enumerate(), + |(idx, request)| { + state + .run_request(request.get()) + .instrument(tracing::debug_span!("batch", idx)) + }, + ) + .await + .flatten() + .collect::>(); + + // All requests were notifications. + if responses.is_empty() { + return Ok(RpcResponses::Empty); + } + + Ok(RpcResponses::Multiple(responses)) + } +} + +/// ``` +/// async fn example(RpcContext, impl DeserializeForVersion, RpcVersion) -> Result> +/// ``` +impl<'a, F, Input, Output, Error, Fut> + IntoRpcEndpoint<((), (), Input), ((), (), Output), ((), (), RpcContext)> for F +where + F: Fn(RpcContext, Input, RpcVersion) -> Fut + Sync + Send + 'static, + Input: DeserializeForVersion + Send + Sync + 'static, + Output: SerializeForVersion + Send + Sync + 'static, + Error: Into + Send + Sync + 'static, + Fut: Future> + Send, +{ + fn into_endpoint(self) -> RpcEndpoint { + struct Helper { + f: F, + _marker: PhantomData<(Input, Output, Error)>, + } + + #[axum::async_trait] + impl RpcMethodEndpoint for Helper + where + F: Fn(RpcContext, Input, RpcVersion) -> Fut + Sync + Send, + Input: DeserializeForVersion + Send + Sync, + Output: SerializeForVersion + Send + Sync, + Error: Into + Send + Sync, + Fut: Future> + Send, + { + async fn invoke<'a>( + &self, + state: RpcContext, + input: RawParams<'a>, + version: RpcVersion, + ) -> RpcResult { + let input = input.deserialize_for_version(version)?; + (self.f)(state, input, version) + .await + .map_err(Into::into)? + .serialize(Serializer::new(version)) + .map_err(|e| RpcError::InternalError(e.into())) + } + } + + RpcEndpoint(RpcEndpointInner::Method(Box::new(Helper { + f: self, + _marker: Default::default(), + }))) + } +} + +/// ``` +/// async fn example(RpcContext, impl Deserialize) -> Result> +/// ``` +impl<'a, F, Input, Output, Error, Fut> IntoRpcEndpoint<((), Input), ((), Output), ((), RpcContext)> + for F +where + F: Fn(RpcContext, Input) -> Fut + Sync + Send + 'static, + Input: DeserializeForVersion + Send + Sync + 'static, + Output: SerializeForVersion + Send + Sync + 'static, + Error: Into + Send + Sync + 'static, + Fut: Future> + Send, +{ + fn into_endpoint(self) -> RpcEndpoint { + struct Helper { + f: F, + _marker: PhantomData<(Input, Output, Error)>, + } + + #[axum::async_trait] + impl RpcMethodEndpoint for Helper + where + F: Fn(RpcContext, Input) -> Fut + Sync + Send, + Input: DeserializeForVersion + Send + Sync, + Output: SerializeForVersion + Send + Sync, + Error: Into + Send + Sync, + Fut: Future> + Send, + { + async fn invoke<'a>( + &self, + state: RpcContext, + input: RawParams<'a>, + version: RpcVersion, + ) -> RpcResult { + let input = input.deserialize_for_version(version)?; + (self.f)(state, input) + .await + .map_err(Into::into)? + .serialize(Serializer::new(version)) + .map_err(|e| RpcError::InternalError(e.into())) + } + } + + RpcEndpoint(RpcEndpointInner::Method(Box::new(Helper { + f: self, + _marker: Default::default(), + }))) + } +} + +/// ``` +/// async fn example(impl Deserialize) -> Result> +/// ``` +#[async_trait] +impl<'a, F, Input, Output, Error, Fut> IntoRpcEndpoint<((), Input), ((), Output), ()> for F +where + F: Fn(Input) -> Fut + Sync + Send + 'static, + Input: DeserializeForVersion + Sync + Send + 'static, + Output: SerializeForVersion + Sync + Send + 'static, + Error: Into + Sync + Send + 'static, + Fut: Future> + Send, +{ + fn into_endpoint(self) -> RpcEndpoint { + struct Helper { + f: F, + _marker: PhantomData<(Input, Output, Error)>, + } + + #[axum::async_trait] + impl RpcMethodEndpoint for Helper + where + F: Fn(Input) -> Fut + Sync + Send, + Input: DeserializeForVersion + Send + Sync, + Output: SerializeForVersion + Send + Sync, + Error: Into + Send + Sync, + Fut: Future> + Send, + { + async fn invoke<'a>( + &self, + _state: RpcContext, + input: RawParams<'a>, + version: RpcVersion, + ) -> RpcResult { + let input = input.deserialize_for_version(version)?; + (self.f)(input) + .await + .map_err(Into::into)? + .serialize(Serializer::new(version)) + .map_err(|e| RpcError::InternalError(e.into())) + } + } + + RpcEndpoint(RpcEndpointInner::Method(Box::new(Helper { + f: self, + _marker: Default::default(), + }))) + } +} + +/// ``` +/// async fn example(RpcContext) -> Result> +/// ``` +#[async_trait] +impl<'a, F, Output, Error, Fut> IntoRpcEndpoint<(), ((), Output), ((), RpcContext)> for F +where + F: Fn(RpcContext) -> Fut + Sync + Send + 'static, + Output: SerializeForVersion + Sync + Send + 'static, + Error: Into + Send + Sync + 'static, + Fut: Future> + Send, +{ + fn into_endpoint(self) -> RpcEndpoint { + struct Helper { + f: F, + _marker: PhantomData<(Output, Error)>, + } + + #[axum::async_trait] + impl RpcMethodEndpoint for Helper + where + F: Fn(RpcContext) -> Fut + Sync + Send, + Output: SerializeForVersion + Send + Sync, + Error: Into + Send + Sync, + Fut: Future> + Send, + { + async fn invoke<'a>( + &self, + state: RpcContext, + input: RawParams<'a>, + version: RpcVersion, + ) -> RpcResult { + if !input.is_empty() { + return Err(RpcError::InvalidParams( + "This method takes no inputs".to_owned(), + )); + } + (self.f)(state) + .await + .map_err(Into::into)? + .serialize(Serializer::new(version)) + .map_err(|e| RpcError::InternalError(e.into())) + } + } + + RpcEndpoint(RpcEndpointInner::Method(Box::new(Helper { + f: self, + _marker: Default::default(), + }))) + } +} + +/// ``` +/// async fn example() -> Result> +/// ``` +#[async_trait] +impl<'a, F, Output, Error, Fut> IntoRpcEndpoint<(), (), ((), Output)> for F +where + F: Fn() -> Fut + Sync + Send + 'static, + Output: SerializeForVersion + Sync + Send + 'static, + Error: Into + Sync + Send + 'static, + Fut: Future> + Send, +{ + fn into_endpoint(self) -> RpcEndpoint { + struct Helper { + f: F, + _marker: PhantomData<(Output, Error)>, + } + + #[axum::async_trait] + impl RpcMethodEndpoint for Helper + where + F: Fn() -> Fut + Sync + Send, + Output: SerializeForVersion + Send + Sync, + Error: Into + Send + Sync, + Fut: Future> + Send, + { + async fn invoke<'a>( + &self, + _state: RpcContext, + input: RawParams<'a>, + version: RpcVersion, + ) -> RpcResult { + if !input.is_empty() { + return Err(RpcError::InvalidParams( + "This method takes no inputs".to_owned(), + )); + } + (self.f)() + .await + .map_err(Into::into)? + .serialize(Serializer::new(version)) + .map_err(|e| RpcError::InternalError(e.into())) + } + } + + RpcEndpoint(RpcEndpointInner::Method(Box::new(Helper { + f: self, + _marker: Default::default(), + }))) + } +} + +/// ``` +/// fn example() -> &'static str +/// ``` +#[async_trait] +impl<'a, F> IntoRpcEndpoint<(), (), ((), (), &'static str)> for F +where + F: Fn() -> &'static str + Sync + Send + 'static, +{ + fn into_endpoint(self) -> RpcEndpoint { + struct Helper { + f: F, + } + + #[axum::async_trait] + impl RpcMethodEndpoint for Helper + where + F: Fn() -> &'static str + Sync + Send, + { + async fn invoke<'a>( + &self, + _state: RpcContext, + input: RawParams<'a>, + version: RpcVersion, + ) -> RpcResult { + if !input.is_empty() { + return Err(RpcError::InvalidParams( + "This method takes no inputs".to_owned(), + )); + } + (self.f)() + .serialize(Serializer::new(version)) + .map_err(|e| RpcError::InternalError(e.into())) + } + } + RpcEndpoint(RpcEndpointInner::Method(Box::new(Helper { f: self }))) + } +} diff --git a/crates/rpc/src/jsonrpc/router/subscription.rs b/crates/rpc/src/jsonrpc/router/subscription.rs new file mode 100644 index 0000000000..426db0b64f --- /dev/null +++ b/crates/rpc/src/jsonrpc/router/subscription.rs @@ -0,0 +1,479 @@ +use std::sync::Arc; + +use axum::extract::ws::{Message, WebSocket}; +use dashmap::DashMap; +use futures::{SinkExt, StreamExt}; +use pathfinder_common::{BlockId, BlockNumber}; +use tokio::sync::mpsc; + +use super::RpcRouter; +use crate::context::RpcContext; +use crate::dto::serialize::SerializeForVersion; +use crate::dto::DeserializeForVersion; +use crate::jsonrpc::{RpcError, RpcRequest, RpcResponse}; +use crate::{RpcVersion, SubscriptionId}; + +#[axum::async_trait] +pub(super) trait RpcSubscriptionEndpoint: Send + Sync { + // Start the subscription. + async fn invoke( + &self, + state: RpcContext, + input: serde_json::Value, + subscription_id: SubscriptionId, + subscriptions: Arc>>, + version: RpcVersion, + tx: mpsc::Sender>, + ) -> Result<(), RpcError>; +} + +/// This trait is the main entry point for subscription endpoint +/// implementations. +/// +/// Many subscription endpoints allow for historical data to be streamed before +/// starting to stream active updates. This is done by having the subscription +/// request pass a `block` parameter indicating the block to start from. This +/// trait is designed to make it easy to implement this behavior, and difficult +/// to make mistakes (e.g. race conditions or accidentally dropping messages). +/// +/// The `catch_up` method is used to stream historical data, while the +/// `subscribe` method is used to subscribe to active updates. The +/// `starting_block` method extracts the first block to start streaming from. +/// This will probably always just be the `block` field of the request. +/// +/// If a subscription endpoint does not need to stream historical data, it +/// should always return an empty vec from `catch_up`. +/// +/// The flow is implemented as follows: +/// - Catch up from the starting block to the latest block known to pathfinder, +/// in batches. Call that block K. +/// - Subscribe to active updates. Fetch the first update, along with the block +/// number that it applies to. +/// - Catch up from block K to the block just before the first active update. +/// This is done to ensure that no blocks are missed between the previous +/// catch-up and the subscription. +/// - Stream the first active update, and then keep streaming the rest. +#[axum::async_trait] +pub trait RpcSubscriptionFlow: Send + Sync { + type Request: crate::dto::DeserializeForVersion + Send + Sync + 'static; + type Notification: crate::dto::serialize::SerializeForVersion + Send + Sync + 'static; + + /// The value for the `method` field of the subscription notification. + fn subscription_name() -> &'static str; + + /// The block to start streaming from. + fn starting_block(req: &Self::Request) -> BlockId; + + /// Fetch historical data from the `from` block to the `to` block. The + /// range is inclusive on both ends. If there is no historical data in the + /// range, return an empty vec. + async fn catch_up( + state: &RpcContext, + req: &Self::Request, + from: BlockNumber, + to: BlockNumber, + ) -> Result, RpcError>; + + /// Subscribe to active updates. + async fn subscribe(state: RpcContext, tx: mpsc::Sender<(Self::Notification, BlockNumber)>); +} + +#[axum::async_trait] +impl RpcSubscriptionEndpoint for T +where + T: RpcSubscriptionFlow + 'static, +{ + async fn invoke( + &self, + state: RpcContext, + input: serde_json::Value, + subscription_id: SubscriptionId, + subscriptions: Arc>>, + version: RpcVersion, + tx: mpsc::Sender>, + ) -> Result<(), RpcError> { + let req = T::Request::deserialize(crate::dto::Value::new(input, version)) + .map_err(|e| RpcError::InvalidParams(e.to_string()))?; + let tx = SubscriptionSender { + subscription_id, + subscriptions, + tx, + subscription_name: T::subscription_name(), + version, + _phantom: Default::default(), + }; + + // Catch up to the latest block in batches of BATCH_SIZE. + let first_block = pathfinder_storage::BlockId::try_from(T::starting_block(&req)) + .map_err(|e| RpcError::InvalidParams(e.to_string()))?; + let storage = state.storage.clone(); + let mut current_block = tokio::task::spawn_blocking(move || -> Result<_, RpcError> { + let mut conn = storage.connection().map_err(RpcError::InternalError)?; + let db = conn.transaction().map_err(RpcError::InternalError)?; + db.block_number(first_block) + .map_err(RpcError::InternalError)? + .ok_or_else(|| RpcError::InvalidParams("Block not found".to_string())) + }) + .await + .map_err(|e| RpcError::InternalError(e.into()))??; + const BATCH_SIZE: u64 = 64; + loop { + let messages = + T::catch_up(&state, &req, current_block, current_block + BATCH_SIZE).await?; + if messages.is_empty() { + // Caught up. + break; + } + for (message, block_number) in messages { + if tx.send(message).await.is_err() { + // Subscription closing. + return Ok(()); + } + current_block = block_number; + } + // Increment the current block by 1 because the catch_up range is inclusive. + current_block += 1; + } + + // Subscribe to new blocks. Receive the first subscription message. + let (tx1, mut rx1) = mpsc::channel::<(T::Notification, BlockNumber)>(1024); + tokio::spawn(T::subscribe(state.clone(), tx1)); + let (first_message, block_number) = match rx1.recv().await { + Some(msg) => msg, + None => { + // Subscription closing. + return Ok(()); + } + }; + + // Catch up from the latest block that we already caught up to, to the first + // block that will be streamed from the subscription. This way we don't miss any + // blocks. Because the catch_up range is inclusive, we need to subtract 1 from + // the block number. + if let Some(block_number) = block_number.parent() { + let messages = T::catch_up(&state, &req, current_block, block_number).await?; + for (message, _) in messages { + if tx.send(message).await.is_err() { + // Subscription closing. + return Ok(()); + } + } + } + + // Send the first subscription message and then forward the rest. + if tx.send(first_message).await.is_err() { + // Subscription closing. + return Ok(()); + } + let mut last_block = block_number; + tokio::spawn(async move { + while let Some((message, block_number)) = rx1.recv().await { + if block_number.get() > last_block.get() + 1 { + // One or more blocks have been skipped. This is likely due to a race condition + // resulting from a reorg. This message should be ignored. + continue; + } + if tx.send(message).await.is_err() { + // Subscription closing. + break; + } + last_block = block_number; + } + }); + Ok(()) + } +} + +pub async fn handle_json_rpc_socket(state: RpcRouter, ws: WebSocket) { + let subscriptions: Arc>> = + Default::default(); + // Send messages to the websocket using an mpsc channel. + let (tx, mut rx) = mpsc::channel::>(1024); + let (mut ws_tx, mut ws_rx) = ws.split(); + tokio::spawn(async move { + while let Some(msg) = rx.recv().await { + match msg { + Ok(msg) => { + if ws_tx.send(msg).await.is_err() { + break; + } + } + Err(e) => { + if ws_tx + .send(Message::Text(serde_json::to_string(&e).unwrap())) + .await + .is_err() + { + break; + } + } + } + } + }); + // Read and handle messages from the websocket. + tokio::spawn(async move { + loop { + let request = match ws_rx.next().await { + Some(Ok(Message::Text(msg))) => msg.into_bytes(), + Some(Ok(Message::Binary(bytes))) => bytes, + Some(Ok(Message::Pong(_) | Message::Ping(_))) => continue, + Some(Ok(Message::Close(_))) | None => { + // Websocket closed. + return; + } + Some(Err(e)) => { + tracing::trace!(error = ?e, "Error receiving websocket message"); + return; + } + }; + + let rpc_request = match serde_json::from_slice::>(&request) { + Ok(request) => request, + Err(err) => { + if tx + .send(Err(RpcResponse::parse_error(err.to_string()))) + .await + .is_err() + { + // Connection is closing. + break; + } + continue; + } + }; + + if rpc_request.method == "starknet_unsubscribe" { + // End the subscription. + let Some(params) = rpc_request.params.0 else { + if tx + .send(Err(RpcResponse::invalid_params( + rpc_request.id, + "Missing params for starknet_unsubscribe".to_string(), + ))) + .await + .is_err() + { + break; + } + continue; + }; + let params = match serde_json::from_str::(params.get()) { + Ok(params) => params, + Err(err) => { + if tx + .send(Err(RpcResponse::invalid_params( + rpc_request.id, + err.to_string(), + ))) + .await + .is_err() + { + break; + } + continue; + } + }; + let Some((_, handle)) = subscriptions.remove(¶ms.subscription_id) else { + if tx + .send(Err(RpcResponse::invalid_params( + rpc_request.id, + "Subscription not found".to_string(), + ))) + .await + .is_err() + { + break; + } + continue; + }; + handle.abort(); + metrics::increment_counter!("rpc_method_calls_total", "method" => "starknet_unsubscribe", "version" => state.version.to_str()); + } + + // Also grab the method_name as it is a static str, which is required by the + // metrics. + let Some((&method_name, endpoint)) = state + .subscription_endpoints + .get_key_value(rpc_request.method.as_ref()) + else { + tx.send(Ok(Message::Text( + serde_json::to_string(&RpcResponse::method_not_found(rpc_request.id)).unwrap(), + ))) + .await + .ok(); + continue; + }; + metrics::increment_counter!("rpc_method_calls_total", "method" => method_name, "version" => state.version.to_str()); + + let params = match serde_json::to_value(rpc_request.params) { + Ok(params) => params, + Err(_e) => { + if tx + .send(Ok(Message::Text( + serde_json::to_string(&RpcError::InvalidParams( + "Invalid params".to_string(), + )) + .unwrap(), + ))) + .await + .is_err() + { + break; + } + continue; + } + }; + + // Start the subscription. + let subscription_id = SubscriptionId::next(); + let context = state.context.clone(); + let version = state.version; + let tx = tx.clone(); + let req_id = rpc_request.id; + if tx + .send(Ok(Message::Text( + serde_json::to_string(&RpcResponse { + output: Ok( + serde_json::to_value(&SubscriptionIdResult { subscription_id }) + .unwrap(), + ), + id: req_id.clone(), + }) + .unwrap(), + ))) + .await + .is_err() + { + break; + } + let handle = tokio::spawn({ + let subscriptions = subscriptions.clone(); + async move { + if let Err(e) = endpoint + .invoke( + context, + params, + subscription_id, + subscriptions, + version, + tx.clone(), + ) + .await + { + tx.send(Err(RpcResponse { + output: Err(e), + id: req_id, + })) + .await + .ok(); + } + } + }); + if subscriptions.insert(subscription_id, handle).is_some() { + panic!("subscription id overflow"); + } + } + }); +} + +#[derive(Debug, serde::Deserialize)] +#[serde(deny_unknown_fields)] +struct StarknetUnsubscribeParams { + subscription_id: SubscriptionId, +} + +#[derive(Debug, serde::Serialize)] +struct SubscriptionIdResult { + subscription_id: SubscriptionId, +} + +#[derive(Debug)] +struct SubscriptionSender { + subscription_id: SubscriptionId, + subscriptions: Arc>>, + tx: mpsc::Sender>, + subscription_name: &'static str, + version: RpcVersion, + _phantom: std::marker::PhantomData, +} + +impl Clone for SubscriptionSender { + fn clone(&self) -> Self { + Self { + subscription_id: self.subscription_id, + subscriptions: self.subscriptions.clone(), + tx: self.tx.clone(), + subscription_name: self.subscription_name, + version: self.version, + _phantom: Default::default(), + } + } +} + +impl SubscriptionSender { + pub async fn send(&self, value: T) -> Result<(), mpsc::error::SendError<()>> { + if !self.subscriptions.contains_key(&self.subscription_id) { + // Race condition due to the subscription ending. + return Ok(()); + } + let notification = RpcNotification { + jsonrpc: "2.0", + method: self.subscription_name, + params: SubscriptionResult { + subscription_id: self.subscription_id, + result: value, + }, + } + .serialize(crate::dto::serialize::Serializer::new(self.version)) + .unwrap(); + let data = serde_json::to_string(¬ification).unwrap(); + self.tx + .send(Ok(Message::Text(data))) + .await + .map_err(|_| mpsc::error::SendError(())) + } +} + +#[derive(Debug)] +struct RpcNotification { + jsonrpc: &'static str, + method: &'static str, + params: SubscriptionResult, +} + +#[derive(Debug)] +pub struct SubscriptionResult { + subscription_id: SubscriptionId, + result: T, +} + +impl crate::dto::serialize::SerializeForVersion for RpcNotification +where + T: crate::dto::serialize::SerializeForVersion, +{ + fn serialize( + &self, + serializer: crate::dto::serialize::Serializer, + ) -> Result { + let mut serializer = serializer.serialize_struct()?; + serializer.serialize_field("jsonrpc", &self.jsonrpc)?; + serializer.serialize_field("method", &self.method)?; + serializer.serialize_field("params", &self.params)?; + serializer.end() + } +} + +impl crate::dto::serialize::SerializeForVersion for SubscriptionResult +where + T: crate::dto::serialize::SerializeForVersion, +{ + fn serialize( + &self, + serializer: crate::dto::serialize::Serializer, + ) -> Result { + let mut serializer = serializer.serialize_struct()?; + serializer.serialize_field("subscription_id", &self.subscription_id)?; + serializer.serialize_field("result", &self.result)?; + serializer.end() + } +} diff --git a/crates/rpc/src/lib.rs b/crates/rpc/src/lib.rs index 4e13bdf6c2..abeced32dd 100644 --- a/crates/rpc/src/lib.rs +++ b/crates/rpc/src/lib.rs @@ -15,9 +15,11 @@ pub mod v02; pub mod v03; pub mod v06; pub mod v07; +pub mod v08; use std::net::SocketAddr; use std::result::Result; +use std::sync::atomic::{AtomicU32, Ordering}; use anyhow::Context; use axum::error_handling::HandleErrorLayer; @@ -26,6 +28,7 @@ use axum::response::IntoResponse; use context::RpcContext; pub use executor::compose_executor_transaction; use http_body::Body; +pub use jsonrpc::Notifications; use pathfinder_common::AllowedOrigins; pub use pending::PendingData; use tokio::sync::RwLock; @@ -45,6 +48,7 @@ pub enum RpcVersion { V06, #[default] V07, + V08, PathfinderV01, } @@ -53,6 +57,7 @@ impl RpcVersion { match self { RpcVersion::V06 => "v0.6", RpcVersion::V07 => "v0.7", + RpcVersion::V08 => "v0.8", RpcVersion::PathfinderV01 => "v0.1", } } @@ -162,11 +167,13 @@ impl RpcServer { let v06_routes = v06::register_routes().build(self.context.clone()); let v07_routes = v07::register_routes().build(self.context.clone()); + let v08_routes = v08::register_routes().build(self.context.clone()); let pathfinder_routes = pathfinder::register_routes().build(self.context.clone()); let default_router = match self.default_version { RpcVersion::V06 => v06_routes.clone(), RpcVersion::V07 => v07_routes.clone(), + RpcVersion::V08 => v08_routes.clone(), RpcVersion::PathfinderV01 => { anyhow::bail!("Did not expect default RPC version to be Pathfinder v0.1") } @@ -230,6 +237,16 @@ impl Default for SyncState { } } +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)] +pub struct SubscriptionId(pub u32); + +impl SubscriptionId { + pub fn next() -> Self { + static COUNTER: AtomicU32 = AtomicU32::new(0); + SubscriptionId(COUNTER.fetch_add(1, Ordering::Relaxed)) + } +} + #[cfg(test)] pub mod test_utils { use std::collections::HashMap; diff --git a/crates/rpc/src/method.rs b/crates/rpc/src/method.rs index 17a857b6d7..039f8d73cd 100644 --- a/crates/rpc/src/method.rs +++ b/crates/rpc/src/method.rs @@ -23,6 +23,7 @@ pub mod get_transaction_by_hash; pub mod get_transaction_receipt; pub mod get_transaction_status; pub mod simulate_transactions; +pub mod subscribe_new_heads; pub mod syncing; pub mod trace_block_transactions; pub mod trace_transaction; diff --git a/crates/rpc/src/method/subscribe_new_heads.rs b/crates/rpc/src/method/subscribe_new_heads.rs new file mode 100644 index 0000000000..9f51013858 --- /dev/null +++ b/crates/rpc/src/method/subscribe_new_heads.rs @@ -0,0 +1,159 @@ +use std::sync::Arc; + +use axum::async_trait; +use pathfinder_common::{BlockId, BlockNumber}; +use tokio::sync::mpsc; + +use crate::context::RpcContext; +use crate::jsonrpc::{RpcError, RpcSubscriptionFlow}; + +pub struct SubscribeNewHeads; + +#[derive(Debug)] +pub struct Request { + block: BlockId, +} + +impl crate::dto::DeserializeForVersion for Request { + fn deserialize(value: crate::dto::Value) -> Result { + value.deserialize_map(|value| { + Ok(Self { + block: value.deserialize_serde("block")?, + }) + }) + } +} + +#[derive(Debug)] +pub struct Message(Arc); + +impl crate::dto::serialize::SerializeForVersion for Message { + fn serialize( + &self, + serializer: crate::dto::serialize::Serializer, + ) -> Result { + crate::dto::BlockHeader(&self.0).serialize(serializer) + } +} + +#[async_trait] +impl RpcSubscriptionFlow for SubscribeNewHeads { + type Request = Request; + type Notification = Message; + + fn subscription_name() -> &'static str { + "starknet_subscriptionNewHeads" + } + + fn starting_block(req: &Self::Request) -> BlockId { + req.block + } + + async fn catch_up( + state: &RpcContext, + _req: &Self::Request, + from: BlockNumber, + to: BlockNumber, + ) -> Result, RpcError> { + let storage = state.storage.clone(); + let headers = tokio::task::spawn_blocking(move || -> Result<_, RpcError> { + let mut conn = storage.connection().map_err(RpcError::InternalError)?; + let db = conn.transaction().map_err(RpcError::InternalError)?; + db.block_range(from, to).map_err(RpcError::InternalError) + }) + .await + .map_err(|e| RpcError::InternalError(e.into()))??; + Ok(headers + .into_iter() + .map(|header| { + let block_number = header.number; + (Message(header.into()), block_number) + }) + .collect()) + } + + async fn subscribe(state: RpcContext, tx: mpsc::Sender<(Self::Notification, BlockNumber)>) { + let mut rx = state.notifications.block_headers.subscribe(); + while let Ok(header) = rx.recv().await { + let block_number = header.number; + if tx.send((Message(header), block_number)).await.is_err() { + break; + } + } + } +} + +#[cfg(test)] +mod tests { + // TODO Remove this + #![allow(dead_code)] + + use std::time::Duration; + + use pathfinder_common::{BlockHash, BlockHeader, BlockNumber, ChainId}; + use pathfinder_crypto::Felt; + use pathfinder_storage::StorageBuilder; + use starknet_gateway_client::Client; + + use crate::context::{RpcConfig, RpcContext}; + use crate::pending::PendingWatcher; + use crate::v02::types::syncing::Syncing; + use crate::{Notifications, SyncState}; + + #[test] + fn happy_path_with_historic_blocks() {} + + #[test] + fn happy_path_with_historic_blocks_batching() {} + + #[test] + fn happy_path_with_no_historic_blocks() {} + + #[test] + fn race_condition_with_historic_blocks() {} + + #[test] + fn unsubscribe() {} + + fn setup(num_blocks: u64) -> RpcContext { + let storage = StorageBuilder::in_memory().unwrap(); + let mut conn = storage.connection().unwrap(); + let db = conn.transaction().unwrap(); + for i in 1..num_blocks { + let header = sample_header(i); + db.insert_block_header(&header).unwrap(); + } + db.commit().unwrap(); + let (_, pending_data) = tokio::sync::watch::channel(Default::default()); + let notifications = Notifications::default(); + RpcContext { + cache: Default::default(), + storage, + execution_storage: StorageBuilder::in_memory().unwrap(), + pending_data: PendingWatcher::new(pending_data), + sync_status: SyncState { + status: Syncing::False(false).into(), + } + .into(), + chain_id: ChainId::MAINNET, + sequencer: Client::mainnet(Duration::from_secs(10)), + websocket: None, + notifications, + config: RpcConfig { + batch_concurrency_limit: 1.try_into().unwrap(), + get_events_max_blocks_to_scan: 1.try_into().unwrap(), + get_events_max_uncached_bloom_filters_to_load: 1.try_into().unwrap(), + custom_versioned_constants: None, + }, + } + } + + fn sample_header(i: u64) -> BlockHeader { + BlockHeader { + hash: BlockHash(Felt::from_u64(i)), + parent_hash: BlockHash::ZERO, + number: BlockNumber::new_or_panic(i), + ..Default::default() + } + } +} diff --git a/crates/rpc/src/subscription_message.rs b/crates/rpc/src/subscription_message.rs new file mode 100644 index 0000000000..e69de29bb2 diff --git a/crates/rpc/src/v08.rs b/crates/rpc/src/v08.rs new file mode 100644 index 0000000000..831a913e88 --- /dev/null +++ b/crates/rpc/src/v08.rs @@ -0,0 +1,38 @@ +use crate::jsonrpc::{RpcRouter, RpcRouterBuilder}; +use crate::method::subscribe_new_heads::SubscribeNewHeads; + +#[rustfmt::skip] +pub fn register_routes() -> RpcRouterBuilder { + RpcRouter::builder(crate::RpcVersion::V07) + .register("starknet_blockHashAndNumber", crate::method::block_hash_and_number) + .register("starknet_blockNumber", crate::method::block_number) + .register("starknet_chainId", crate::method::chain_id) + .register("starknet_getBlockTransactionCount", crate::method::get_block_transaction_count) + .register("starknet_getClass", crate::method::get_class) + .register("starknet_getClassAt", crate::method::get_class_at) + .register("starknet_getClassHashAt", crate::method::get_class_hash_at) + .register("starknet_getEvents", crate::method::get_events) + .register("starknet_getNonce", crate::method::get_nonce) + .register("starknet_getStateUpdate", crate::method::get_state_update) + .register("starknet_getStorageAt", crate::method::get_storage_at) + .register("starknet_syncing", crate::method::syncing) + .register("starknet_getTransactionReceipt", crate::method::get_transaction_receipt) + .register("starknet_getTransactionStatus", crate::method::get_transaction_status) + .register("starknet_call", crate::method::call) + .register("starknet_addDeclareTransaction", crate::method::add_declare_transaction) + .register("starknet_addDeployAccountTransaction", crate::method::add_deploy_account_transaction) + .register("starknet_addInvokeTransaction", crate::method::add_invoke_transaction) + .register("starknet_getTransactionByBlockIdAndIndex", crate::method::get_transaction_by_block_id_and_index) + .register("starknet_getTransactionByHash", crate::method::get_transaction_by_hash) + .register("starknet_estimateFee", crate::method::estimate_fee) + .register("starknet_estimateMessageFee", crate::method::estimate_message_fee) + .register("starknet_getBlockWithTxHashes", crate::method::get_block_with_tx_hashes) + .register("starknet_getBlockWithTxs", crate::method::get_block_with_txs) + .register("starknet_simulateTransactions", crate::method::simulate_transactions) + .register("starknet_traceBlockTransactions", crate::method::trace_block_transactions) + .register("starknet_traceTransaction", crate::method::trace_transaction) + .register("starknet_getBlockWithReceipts", crate::method::get_block_with_receipts) + .register("pathfinder_getProof", crate::pathfinder::methods::get_proof) + .register("starknet_subscribeNewHeads", SubscribeNewHeads) + .register("starknet_specVersion", || "0.8.0") +} diff --git a/crates/storage/src/connection/block.rs b/crates/storage/src/connection/block.rs index d3c15b60f4..97ed7916c9 100644 --- a/crates/storage/src/connection/block.rs +++ b/crates/storage/src/connection/block.rs @@ -358,6 +358,27 @@ impl Transaction<'_> { Ok(header) } + /// Return all blocks from a range, inclusive on both ends. + pub fn block_range( + &self, + from: BlockNumber, + to: BlockNumber, + ) -> anyhow::Result> { + let sql = + "SELECT * FROM block_headers WHERE number >= $1 AND number <= $2 ORDER BY number ASC"; + let mut stmt = self + .inner() + .prepare_cached(sql) + .context("Preparing block header query")?; + let mut headers = Vec::new(); + let mut rows = stmt.query(params![&from, &to])?; + while let Some(row) = rows.next()? { + let header = parse_row_as_header(row)?; + headers.push(header); + } + Ok(headers) + } + pub fn state_commitment(&self, block: BlockId) -> anyhow::Result> { let sql = match block { BlockId::Latest => { From d0e19966493a913ad962cb7e224a49beb3170822 Mon Sep 17 00:00:00 2001 From: sistemd Date: Thu, 12 Sep 2024 13:29:31 +0200 Subject: [PATCH 037/282] handle comments --- Cargo.toml | 1 + crates/rpc/Cargo.toml | 2 +- crates/rpc/src/jsonrpc/router/subscription.rs | 8 +++++-- crates/rpc/src/method/subscribe_new_heads.rs | 24 +++++++++++++++---- crates/rpc/src/v08.rs | 2 +- crates/storage/src/connection/block.rs | 2 +- 6 files changed, 30 insertions(+), 9 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5e2ebe81b9..206b292f0b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -69,6 +69,7 @@ console-subscriber = "0.1.10" const-decoder = "0.3.0" const_format = "0.2.31" criterion = "0.5.1" +dashmap = "6.1" env_logger = "0.10.0" fake = "2.8.0" ff = "0.13" diff --git a/crates/rpc/Cargo.toml b/crates/rpc/Cargo.toml index 60ad832ca5..fb33758168 100644 --- a/crates/rpc/Cargo.toml +++ b/crates/rpc/Cargo.toml @@ -12,7 +12,7 @@ anyhow = { workspace = true } async-trait = { workspace = true } axum = { workspace = true, features = ["ws", "macros"] } base64 = { workspace = true } -dashmap = "6.1.0" +dashmap = { workspace = true } flate2 = { workspace = true } futures = { workspace = true } http = { workspace = true } diff --git a/crates/rpc/src/jsonrpc/router/subscription.rs b/crates/rpc/src/jsonrpc/router/subscription.rs index 426db0b64f..88e19d4dfc 100644 --- a/crates/rpc/src/jsonrpc/router/subscription.rs +++ b/crates/rpc/src/jsonrpc/router/subscription.rs @@ -10,6 +10,7 @@ use super::RpcRouter; use crate::context::RpcContext; use crate::dto::serialize::SerializeForVersion; use crate::dto::DeserializeForVersion; +use crate::error::ApplicationError; use crate::jsonrpc::{RpcError, RpcRequest, RpcResponse}; use crate::{RpcVersion, SubscriptionId}; @@ -112,7 +113,7 @@ where let db = conn.transaction().map_err(RpcError::InternalError)?; db.block_number(first_block) .map_err(RpcError::InternalError)? - .ok_or_else(|| RpcError::InvalidParams("Block not found".to_string())) + .ok_or_else(|| ApplicationError::BlockNotFound.into()) }) .await .map_err(|e| RpcError::InternalError(e.into()))??; @@ -216,7 +217,10 @@ pub async fn handle_json_rpc_socket(state: RpcRouter, ws: WebSocket) { let request = match ws_rx.next().await { Some(Ok(Message::Text(msg))) => msg.into_bytes(), Some(Ok(Message::Binary(bytes))) => bytes, - Some(Ok(Message::Pong(_) | Message::Ping(_))) => continue, + Some(Ok(Message::Pong(_) | Message::Ping(_))) => { + // Ping and pong messages are handled automatically by axum. + continue; + } Some(Ok(Message::Close(_))) | None => { // Websocket closed. return; diff --git a/crates/rpc/src/method/subscribe_new_heads.rs b/crates/rpc/src/method/subscribe_new_heads.rs index 9f51013858..66aa6f9b70 100644 --- a/crates/rpc/src/method/subscribe_new_heads.rs +++ b/crates/rpc/src/method/subscribe_new_heads.rs @@ -74,10 +74,26 @@ impl RpcSubscriptionFlow for SubscribeNewHeads { async fn subscribe(state: RpcContext, tx: mpsc::Sender<(Self::Notification, BlockNumber)>) { let mut rx = state.notifications.block_headers.subscribe(); - while let Ok(header) = rx.recv().await { - let block_number = header.number; - if tx.send((Message(header), block_number)).await.is_err() { - break; + loop { + match rx.recv().await { + Ok(header) => { + let block_number = header.number; + if tx + .send((Message(header.into()), block_number)) + .await + .is_err() + { + break; + } + } + Err(e) => { + tracing::debug!( + "Error receiving block header from notifications channel, node might be \ + lagging: {:?}", + e + ); + break; + } } } } diff --git a/crates/rpc/src/v08.rs b/crates/rpc/src/v08.rs index 831a913e88..33c046e389 100644 --- a/crates/rpc/src/v08.rs +++ b/crates/rpc/src/v08.rs @@ -3,7 +3,7 @@ use crate::method::subscribe_new_heads::SubscribeNewHeads; #[rustfmt::skip] pub fn register_routes() -> RpcRouterBuilder { - RpcRouter::builder(crate::RpcVersion::V07) + RpcRouter::builder(crate::RpcVersion::V08) .register("starknet_blockHashAndNumber", crate::method::block_hash_and_number) .register("starknet_blockNumber", crate::method::block_number) .register("starknet_chainId", crate::method::chain_id) diff --git a/crates/storage/src/connection/block.rs b/crates/storage/src/connection/block.rs index 97ed7916c9..06f89f6158 100644 --- a/crates/storage/src/connection/block.rs +++ b/crates/storage/src/connection/block.rs @@ -358,7 +358,7 @@ impl Transaction<'_> { Ok(header) } - /// Return all blocks from a range, inclusive on both ends. + /// Return all block headers from a range, inclusive on both ends. pub fn block_range( &self, from: BlockNumber, From 56cbb61063fb93b139ef1f0626ffefe71f3bff64 Mon Sep 17 00:00:00 2001 From: sistemd Date: Thu, 12 Sep 2024 14:08:36 +0200 Subject: [PATCH 038/282] clippy --- crates/rpc/src/method/subscribe_new_heads.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/crates/rpc/src/method/subscribe_new_heads.rs b/crates/rpc/src/method/subscribe_new_heads.rs index 66aa6f9b70..4847cf6f62 100644 --- a/crates/rpc/src/method/subscribe_new_heads.rs +++ b/crates/rpc/src/method/subscribe_new_heads.rs @@ -78,11 +78,7 @@ impl RpcSubscriptionFlow for SubscribeNewHeads { match rx.recv().await { Ok(header) => { let block_number = header.number; - if tx - .send((Message(header.into()), block_number)) - .await - .is_err() - { + if tx.send((Message(header), block_number)).await.is_err() { break; } } From 9bc374d0ec2ff3725450e55255ffded0c5bde116 Mon Sep 17 00:00:00 2001 From: t00ts Date: Thu, 12 Sep 2024 15:26:35 +0200 Subject: [PATCH 039/282] feat: manually fetch initial Ethereum state on sync start --- crates/pathfinder/src/state/sync/l1.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/crates/pathfinder/src/state/sync/l1.rs b/crates/pathfinder/src/state/sync/l1.rs index c6b4a76a31..104a785c95 100644 --- a/crates/pathfinder/src/state/sync/l1.rs +++ b/crates/pathfinder/src/state/sync/l1.rs @@ -35,7 +35,11 @@ where poll_interval, } = context; - // Listen for state updates and send them to the event channel + // Fetch the current Starknet state from Ethereum + let state_update = ethereum.get_starknet_state(&core_address).await?; + let _ = tx_event.send(SyncEvent::L1Update(state_update)).await; + + // Subscribe to subsequent state updates and message logs let tx_event = std::sync::Arc::new(tx_event); ethereum .listen(&core_address, poll_interval, move |event| { From 217aa1d5bc95cad163a3ccbd87b8e0c541b6ed0b Mon Sep 17 00:00:00 2001 From: sistemd Date: Thu, 12 Sep 2024 17:00:01 +0200 Subject: [PATCH 040/282] shrink down v08 module --- crates/rpc/src/v08.rs | 29 ----------------------------- 1 file changed, 29 deletions(-) diff --git a/crates/rpc/src/v08.rs b/crates/rpc/src/v08.rs index 33c046e389..f80a312bc8 100644 --- a/crates/rpc/src/v08.rs +++ b/crates/rpc/src/v08.rs @@ -4,35 +4,6 @@ use crate::method::subscribe_new_heads::SubscribeNewHeads; #[rustfmt::skip] pub fn register_routes() -> RpcRouterBuilder { RpcRouter::builder(crate::RpcVersion::V08) - .register("starknet_blockHashAndNumber", crate::method::block_hash_and_number) - .register("starknet_blockNumber", crate::method::block_number) - .register("starknet_chainId", crate::method::chain_id) - .register("starknet_getBlockTransactionCount", crate::method::get_block_transaction_count) - .register("starknet_getClass", crate::method::get_class) - .register("starknet_getClassAt", crate::method::get_class_at) - .register("starknet_getClassHashAt", crate::method::get_class_hash_at) - .register("starknet_getEvents", crate::method::get_events) - .register("starknet_getNonce", crate::method::get_nonce) - .register("starknet_getStateUpdate", crate::method::get_state_update) - .register("starknet_getStorageAt", crate::method::get_storage_at) - .register("starknet_syncing", crate::method::syncing) - .register("starknet_getTransactionReceipt", crate::method::get_transaction_receipt) - .register("starknet_getTransactionStatus", crate::method::get_transaction_status) - .register("starknet_call", crate::method::call) - .register("starknet_addDeclareTransaction", crate::method::add_declare_transaction) - .register("starknet_addDeployAccountTransaction", crate::method::add_deploy_account_transaction) - .register("starknet_addInvokeTransaction", crate::method::add_invoke_transaction) - .register("starknet_getTransactionByBlockIdAndIndex", crate::method::get_transaction_by_block_id_and_index) - .register("starknet_getTransactionByHash", crate::method::get_transaction_by_hash) - .register("starknet_estimateFee", crate::method::estimate_fee) - .register("starknet_estimateMessageFee", crate::method::estimate_message_fee) - .register("starknet_getBlockWithTxHashes", crate::method::get_block_with_tx_hashes) - .register("starknet_getBlockWithTxs", crate::method::get_block_with_txs) - .register("starknet_simulateTransactions", crate::method::simulate_transactions) - .register("starknet_traceBlockTransactions", crate::method::trace_block_transactions) - .register("starknet_traceTransaction", crate::method::trace_transaction) - .register("starknet_getBlockWithReceipts", crate::method::get_block_with_receipts) - .register("pathfinder_getProof", crate::pathfinder::methods::get_proof) .register("starknet_subscribeNewHeads", SubscribeNewHeads) .register("starknet_specVersion", || "0.8.0") } From cfb0a7c18f46805317abf01c4eb380fb476fd79a Mon Sep 17 00:00:00 2001 From: Krisztian Kovacs Date: Tue, 10 Sep 2024 13:59:45 +0200 Subject: [PATCH 041/282] fix(p2p/client/peer_agnostic): limit sync request block ranges Some nodes (like Juno) do not limit how many responses they return for a sync query. Since Pathfinder requests the whole gap (possibly hundreds of thousands of blocks) by default this leads to a sync request that then times out (because we currently have a "global" timeout on the sync protocol streams). This change makes sure that we never request more than 500 blocks worth of data at once. Closes #2121 --- crates/p2p/src/client/peer_agnostic.rs | 44 ++++++++++++++++++++------ 1 file changed, 34 insertions(+), 10 deletions(-) diff --git a/crates/p2p/src/client/peer_agnostic.rs b/crates/p2p/src/client/peer_agnostic.rs index 928bcf76e7..23de02ea89 100644 --- a/crates/p2p/src/client/peer_agnostic.rs +++ b/crates/p2p/src/client/peer_agnostic.rs @@ -590,6 +590,9 @@ impl BlockClient for Client { } } +/// Maximum number of blocks to request in a single request +const MAX_BLOCKS_COUNT: u64 = 500; + mod header_stream { use super::*; @@ -696,13 +699,14 @@ mod header_stream { } fn make_request(start: i64, stop: i64, dir: Direction) -> BlockHeadersRequest { - let limit = start.max(stop) - start.min(stop) + 1; + let limit = start.abs_diff(stop) + 1; + let limit = limit.min(MAX_BLOCKS_COUNT); BlockHeadersRequest { iteration: Iteration { start: u64::try_from(start).expect("start >= 0").into(), direction: dir, - limit: limit.try_into().expect("limit >= 0"), + limit, step: 1.into(), }, } @@ -839,11 +843,16 @@ mod transaction_stream { } fn make_request(start: BlockNumber, stop: BlockNumber) -> TransactionsRequest { + let start = start.get(); + let stop = stop.get(); + let limit = start.abs_diff(stop) + 1; + let limit = limit.min(MAX_BLOCKS_COUNT); + TransactionsRequest { iteration: Iteration { - start: start.get().into(), + start: start.into(), direction: Direction::Forward, - limit: stop.get() - start.get() + 1, + limit, step: 1.into(), }, } @@ -1055,11 +1064,16 @@ mod state_diff_stream { } fn make_request(start: BlockNumber, stop: BlockNumber) -> StateDiffsRequest { + let start = start.get(); + let stop = stop.get(); + let limit = start.abs_diff(stop) + 1; + let limit = limit.min(MAX_BLOCKS_COUNT); + StateDiffsRequest { iteration: Iteration { - start: start.get().into(), + start: start.into(), direction: Direction::Forward, - limit: stop.get() - start.get() + 1, + limit, step: 1.into(), }, } @@ -1189,11 +1203,16 @@ mod class_definition_stream { } fn make_request(start: BlockNumber, stop: BlockNumber) -> ClassesRequest { + let start = start.get(); + let stop = stop.get(); + let limit = start.abs_diff(stop) + 1; + let limit = limit.min(MAX_BLOCKS_COUNT); + ClassesRequest { iteration: Iteration { - start: start.get().into(), + start: start.into(), direction: Direction::Forward, - limit: stop.get() - start.get() + 1, + limit, step: 1.into(), }, } @@ -1366,11 +1385,16 @@ mod event_stream { } fn make_request(start: BlockNumber, stop: BlockNumber) -> EventsRequest { + let start = start.get(); + let stop = stop.get(); + let limit = start.abs_diff(stop) + 1; + let limit = limit.min(MAX_BLOCKS_COUNT); + EventsRequest { iteration: Iteration { - start: start.get().into(), + start: start.into(), direction: Direction::Forward, - limit: stop.get() - start.get() + 1, + limit, step: 1.into(), }, } From a03ec652f87fa4bad56b175acb59a5af7c65717b Mon Sep 17 00:00:00 2001 From: Krisztian Kovacs Date: Thu, 12 Sep 2024 11:07:22 +0200 Subject: [PATCH 042/282] fix(p2p_proto/receipt): make gas consumption data optional Juno does not send gas costs for old transactions so we should make these optional and default these to zero. --- crates/p2p/src/client/conv.rs | 28 ++++++++++++++++++++-------- crates/p2p_proto/src/receipt.rs | 12 ++++++++---- 2 files changed, 28 insertions(+), 12 deletions(-) diff --git a/crates/p2p/src/client/conv.rs b/crates/p2p/src/client/conv.rs index 2dd4deae6e..79da47c18b 100644 --- a/crates/p2p/src/client/conv.rs +++ b/crates/p2p/src/client/conv.rs @@ -326,10 +326,10 @@ impl ToDto for (&TransactionVariant, Receipt) { }, steps: e.n_steps.try_into().unwrap(), memory_holes: e.n_memory_holes.try_into().unwrap(), - l1_gas: da.l1_gas.into(), - l1_data_gas: da.l1_data_gas.into(), - total_l1_gas: total.l1_gas.into(), - total_l1_data_gas: total.l1_data_gas.into(), + l1_gas: Some(da.l1_gas.into()), + l1_data_gas: Some(da.l1_data_gas.into()), + total_l1_gas: Some(total.l1_gas.into()), + total_l1_data_gas: Some(total.l1_data_gas.into()), } }, revert_reason, @@ -716,13 +716,25 @@ impl TryFrom<(p2p_proto::receipt::Receipt, TransactionIndex)> for crate::client: n_steps: common.execution_resources.steps.into(), n_memory_holes: common.execution_resources.memory_holes.into(), data_availability: L1Gas { - l1_gas: GasPrice::try_from(common.execution_resources.l1_gas)?.0, - l1_data_gas: GasPrice::try_from(common.execution_resources.l1_data_gas)?.0, + l1_gas: GasPrice::try_from( + common.execution_resources.l1_gas.unwrap_or_default(), + )? + .0, + l1_data_gas: GasPrice::try_from( + common.execution_resources.l1_data_gas.unwrap_or_default(), + )? + .0, }, total_gas_consumed: L1Gas { - l1_gas: GasPrice::try_from(common.execution_resources.total_l1_gas)?.0, + l1_gas: GasPrice::try_from( + common.execution_resources.total_l1_gas.unwrap_or_default(), + )? + .0, l1_data_gas: GasPrice::try_from( - common.execution_resources.total_l1_data_gas, + common + .execution_resources + .total_l1_data_gas + .unwrap_or_default(), )? .0, }, diff --git a/crates/p2p_proto/src/receipt.rs b/crates/p2p_proto/src/receipt.rs index e9ea8fc6f3..3994a76c02 100644 --- a/crates/p2p_proto/src/receipt.rs +++ b/crates/p2p_proto/src/receipt.rs @@ -29,10 +29,14 @@ pub struct ExecutionResources { pub builtins: execution_resources::BuiltinCounter, pub steps: u32, pub memory_holes: u32, - pub l1_gas: Felt, - pub l1_data_gas: Felt, - pub total_l1_gas: Felt, - pub total_l1_data_gas: Felt, + #[optional] + pub l1_gas: Option, + #[optional] + pub l1_data_gas: Option, + #[optional] + pub total_l1_gas: Option, + #[optional] + pub total_l1_data_gas: Option, } pub mod execution_resources { From 0d06ce67064454e30a886f13b26ddeb956966533 Mon Sep 17 00:00:00 2001 From: Krisztian Kovacs Date: Thu, 12 Sep 2024 11:47:55 +0200 Subject: [PATCH 043/282] fix(p2p_proto/receipt): L1 handler message hashes are 256 bit hashes This change introduces a `Hash256` type to describe fields which represent 256 bit hash values (like the output of Keccak256). --- crates/p2p/src/client/conv.rs | 6 ++--- crates/p2p_proto/proto/common.proto | 6 +++++ crates/p2p_proto/proto/receipt.proto | 2 +- crates/p2p_proto/src/common.rs | 37 ++++++++++++++++++++++++++++ crates/p2p_proto/src/receipt.rs | 4 +-- 5 files changed, 49 insertions(+), 6 deletions(-) diff --git a/crates/p2p/src/client/conv.rs b/crates/p2p/src/client/conv.rs index 2dd4deae6e..ed53c94d61 100644 --- a/crates/p2p/src/client/conv.rs +++ b/crates/p2p/src/client/conv.rs @@ -7,7 +7,7 @@ use std::io::Read; use anyhow::Context; use p2p_proto::class::{Cairo0Class, Cairo1Class, Cairo1EntryPoints, SierraEntryPoint}; -use p2p_proto::common::{Address, Hash}; +use p2p_proto::common::{Address, Hash, Hash256}; use p2p_proto::receipt::execution_resources::BuiltinCounter; use p2p_proto::receipt::{ DeclareTransactionReceipt, @@ -364,9 +364,9 @@ impl ToDto for (&TransactionVariant, Receipt) { TransactionVariant::InvokeV0(_) | TransactionVariant::InvokeV1(_) | TransactionVariant::InvokeV3(_) => Invoke(InvokeTransactionReceipt { common }), - TransactionVariant::L1Handler(_) => L1Handler(L1HandlerTransactionReceipt { + TransactionVariant::L1Handler(tx) => L1Handler(L1HandlerTransactionReceipt { common, - msg_hash: Hash(Felt::ZERO), // TODO what is this + msg_hash: Hash256(tx.calculate_message_hash()), }), } } diff --git a/crates/p2p_proto/proto/common.proto b/crates/p2p_proto/proto/common.proto index 43fdf509f5..ced2ebeed8 100644 --- a/crates/p2p_proto/proto/common.proto +++ b/crates/p2p_proto/proto/common.proto @@ -6,10 +6,16 @@ message Felt252 { bytes elements = 1; } +// A hash value represented as a Felt252 message Hash { bytes elements = 1; } +// A 256 bit hash value (like Keccak256) +message Hash256 { + bytes elements = 1; +} + message Hashes { repeated Hash items = 1; } diff --git a/crates/p2p_proto/proto/receipt.proto b/crates/p2p_proto/proto/receipt.proto index 7744ba710d..664093ba1d 100644 --- a/crates/p2p_proto/proto/receipt.proto +++ b/crates/p2p_proto/proto/receipt.proto @@ -58,7 +58,7 @@ message Receipt { message L1Handler { Common common = 1; - starknet.common.Hash msg_hash = 2; + starknet.common.Hash256 msg_hash = 2; } message Declare { diff --git a/crates/p2p_proto/src/common.rs b/crates/p2p_proto/src/common.rs index a5f72754fa..4f1e3bc705 100644 --- a/crates/p2p_proto/src/common.rs +++ b/crates/p2p_proto/src/common.rs @@ -4,6 +4,7 @@ use std::num::NonZeroU64; use fake::Dummy; use libp2p_identity::PeerId; use pathfinder_crypto::Felt; +use primitive_types::H256; use rand::Rng; use crate::{proto, ToProtobuf, TryFromProtobuf}; @@ -11,6 +12,15 @@ use crate::{proto, ToProtobuf, TryFromProtobuf}; #[derive(Debug, Copy, Clone, PartialEq, Eq, Dummy, std::hash::Hash, Default)] pub struct Hash(pub Felt); +#[derive(Debug, Copy, Clone, PartialEq, Eq, std::hash::Hash, Default)] +pub struct Hash256(pub primitive_types::H256); + +impl Dummy for Hash256 { + fn dummy_with_rng(_: &T, rng: &mut R) -> Self { + Self(H256::random_using(rng)) + } +} + #[derive(Debug, Clone, PartialEq, Eq, ToProtobuf, TryFromProtobuf, Dummy)] #[protobuf(name = "crate::proto::common::Hashes")] pub struct Hashes { @@ -135,6 +145,33 @@ impl TryFromProtobuf for Hash { } } +impl ToProtobuf for Hash256 { + fn to_protobuf(self) -> proto::common::Hash256 { + proto::common::Hash256 { + elements: self.0.as_fixed_bytes().into(), + } + } +} + +impl TryFromProtobuf for Hash256 { + fn try_from_protobuf( + input: proto::common::Hash256, + field_name: &'static str, + ) -> Result { + if input.elements.len() != H256::len_bytes() { + return Err(std::io::Error::new( + std::io::ErrorKind::InvalidData, + format!( + "Invalid field element {field_name}: expected to be {} bytes long", + H256::len_bytes() + ), + )); + } + let hash = H256::from_slice(&input.elements); + Ok(Hash256(hash)) + } +} + impl ToProtobuf for Address { fn to_protobuf(self) -> proto::common::Address { proto::common::Address { diff --git a/crates/p2p_proto/src/receipt.rs b/crates/p2p_proto/src/receipt.rs index e9ea8fc6f3..59260a857d 100644 --- a/crates/p2p_proto/src/receipt.rs +++ b/crates/p2p_proto/src/receipt.rs @@ -2,7 +2,7 @@ use fake::Dummy; use pathfinder_crypto::Felt; use primitive_types::H160; -use crate::common::Hash; +use crate::common::Hash256; use crate::{proto, proto_field, ToProtobuf, TryFromProtobuf}; #[derive(Debug, Clone, PartialEq, Eq, ToProtobuf, TryFromProtobuf, Dummy)] @@ -76,7 +76,7 @@ pub struct InvokeTransactionReceipt { #[protobuf(name = "crate::proto::receipt::receipt::L1Handler")] pub struct L1HandlerTransactionReceipt { pub common: ReceiptCommon, - pub msg_hash: Hash, + pub msg_hash: Hash256, } #[derive(Debug, Clone, PartialEq, Eq, ToProtobuf, TryFromProtobuf, Dummy)] From a95bae354ee50a72886a66ef361ab8a3e3971a98 Mon Sep 17 00:00:00 2001 From: Krisztian Kovacs Date: Thu, 12 Sep 2024 17:48:34 +0200 Subject: [PATCH 044/282] feat(storage/state_update): store re-declared Cairo 0 class information This change adds a new `redeclared_classes` table storing block numbers at which class re-declarations did happen. When inserting a state update we take care of adding new rows here if the state update contains a re-declaration -- and then when retrieving the state update from storage we add re-declared classes to the set of Cairo classes declared at the block. --- crates/storage/src/connection/state_update.rs | 108 +++++++++++++++--- crates/storage/src/schema.rs | 2 + crates/storage/src/schema/revision_0064.rs | 44 +++++++ 3 files changed, 141 insertions(+), 13 deletions(-) create mode 100644 crates/storage/src/schema/revision_0064.rs diff --git a/crates/storage/src/connection/state_update.rs b/crates/storage/src/connection/state_update.rs index f30024f5ab..b263dd5b45 100644 --- a/crates/storage/src/connection/state_update.rs +++ b/crates/storage/src/connection/state_update.rs @@ -120,10 +120,18 @@ impl Transaction<'_> { .prepare_cached( r"INSERT INTO class_definitions (block_number, hash) VALUES (?1, ?2) ON CONFLICT(hash) - DO UPDATE SET block_number=excluded.block_number - WHERE block_number IS NULL", + DO UPDATE SET block_number=IFNULL(block_number,excluded.block_number) + RETURNING block_number", ) .context("Preparing class hash and block number upsert statement")?; + + let mut insert_redeclared_class = self + .inner() + .prepare_cached( + r"INSERT INTO redeclared_classes (class_hash, block_number) VALUES (?, ?)", + ) + .context("Preparing redeclared class insert statement")?; + // OR IGNORE is required to handle legacy syncing logic, where the casm // definition is inserted before the state update let mut insert_casm_hash = self @@ -212,6 +220,20 @@ impl Transaction<'_> { .keys() .map(|sierra| ClassHash(sierra.0)); let cairo = declared_cairo_classes.iter().copied(); + + let declared_classes = sierra.chain(cairo); + + for class in declared_classes { + let declared_at = upsert_declared_at + .query_row(params![&block_number, &class], |row| { + row.get_block_number(0) + })?; + if declared_at != block_number { + tracing::debug!(%declared_at, %block_number, class_hash=%class, "Re-declared class"); + insert_redeclared_class.execute(params![&class, &block_number])?; + } + } + // Older cairo 0 classes were never declared, but instead got implicitly // declared on first deployment. Until such classes disappear we need to // cater for them here. This works because the sql only updates the row @@ -223,10 +245,10 @@ impl Transaction<'_> { _ => None, }); - let declared_classes = sierra.chain(cairo).chain(deployed); - - for class in declared_classes { - upsert_declared_at.execute(params![&block_number, &class])?; + for class in deployed { + let _ = upsert_declared_at.query_row(params![&block_number, &class], |row| { + row.get_block_number(0) + })?; } for (sierra_hash, casm_hash) in declared_sierra_classes { @@ -393,6 +415,22 @@ impl Transaction<'_> { }; } + let mut stmt = self + .inner() + .prepare_cached(r"SELECT class_hash FROM redeclared_classes WHERE block_number = ?") + .context("Preparing re-declared class query statement")?; + + let mut redeclared_classes = stmt + .query_map(params![&block_number], |row| row.get_class_hash(0)) + .context("Querying re-declared classes")?; + while let Some(class_hash) = redeclared_classes + .next() + .transpose() + .context("Iterating over re-declared classes")? + { + state_update = state_update.with_declared_cairo_class(class_hash); + } + let mut stmt = self .inner().prepare_cached( r"SELECT @@ -480,13 +518,12 @@ impl Transaction<'_> { let mut stmt = self .inner() .prepare_cached( - r" - SELECT COUNT(block_number) - FROM canonical_blocks - LEFT JOIN class_definitions ON canonical_blocks.number = class_definitions.block_number - WHERE number >= ? - GROUP BY canonical_blocks.number - ORDER BY canonical_blocks.number ASC + r"SELECT + (SELECT COUNT(block_number) FROM class_definitions WHERE block_number=block_headers.number) + + (SELECT COUNT(block_number) FROM redeclared_classes WHERE block_number=block_headers.number) + FROM block_headers + WHERE block_headers.number >= ? + ORDER BY block_headers.number ASC LIMIT ?", ) .context("Preparing get number of declared classes statement")?; @@ -533,6 +570,23 @@ impl Transaction<'_> { result.push(class_hash); } + let mut stmt = self + .inner() + .prepare_cached(r"SELECT class_hash FROM redeclared_classes WHERE block_number = ?") + .context("Preparing re-declared class query")?; + + let mut redeclared_classes = stmt + .query_map(params![&block_number], |row| row.get_class_hash(0)) + .context("Querying re-declared classes")?; + + while let Some(class_hash) = redeclared_classes + .next() + .transpose() + .context("Iterating over re-declared classes")? + { + result.push(class_hash) + } + Ok(Some(result)) } @@ -1214,6 +1268,34 @@ mod tests { assert_eq!(non_existent, None); } + #[test] + fn redeclared_classes() { + let (mut db, _state_update, header) = setup(); + + let tx = db.transaction().unwrap(); + let new_header = header + .child_builder() + .finalize_with_hash(block_hash!("0xabcdee")); + let new_state_update = StateUpdate::default() + .with_block_hash(new_header.hash) + .with_declared_cairo_class(CAIRO_HASH); + tx.insert_block_header(&new_header).unwrap(); + tx.insert_state_update(new_header.number, &new_state_update) + .unwrap(); + + tx.commit().unwrap(); + + let tx = db.transaction().unwrap(); + + let result = tx.state_update(new_header.number.into()).unwrap().unwrap(); + assert_eq!(result, new_state_update); + + let result = tx + .declared_classes_counts(new_header.number, NonZeroUsize::new(1).unwrap()) + .unwrap(); + assert_eq!(result[0], 1); + } + #[test] fn reverse_state_update() { let (mut db, _state_update, header) = setup(); diff --git a/crates/storage/src/schema.rs b/crates/storage/src/schema.rs index 3542ab73b2..33ae2c9a74 100644 --- a/crates/storage/src/schema.rs +++ b/crates/storage/src/schema.rs @@ -23,6 +23,7 @@ mod revision_0060; mod revision_0061; mod revision_0062; mod revision_0063; +mod revision_0064; pub(crate) use base::base_schema; @@ -54,6 +55,7 @@ pub fn migrations() -> &'static [MigrationFn] { revision_0061::migrate, revision_0062::migrate, revision_0063::migrate, + revision_0064::migrate, ] } diff --git a/crates/storage/src/schema/revision_0064.rs b/crates/storage/src/schema/revision_0064.rs new file mode 100644 index 0000000000..4e8cd38eea --- /dev/null +++ b/crates/storage/src/schema/revision_0064.rs @@ -0,0 +1,44 @@ +use anyhow::Context; + +/// Add a new table for storing data about re-declared classes. +/// +/// The Starknet feeder gateway returns inconsistent state diffs for some +/// blocks: `old_declared_contracts` _may_ contain classes that have already +/// been declared in previous blocks. +/// +/// For example state update for block 6356 has class +/// [0x0699053487675242dc0958e192c17fe4dd57d22238ad78e2e1807fa7919ffde0](https://sepolia.starkscan.co/class/0x0699053487675242dc0958e192c17fe4dd57d22238ad78e2e1807fa7919ffde0) +/// in `old_declared_contracts`. However, that class was in fact first declared +/// in block 6355 and then re-declared in 6356. This is an inconsistency in the +/// state diff: even though a second DECLARE transaction was included in that +/// block for the class it is not a diff so it should not have been included in +/// the state diff at all. +/// +/// Pathfinder stores only the very first block a contract was declared at, so +/// we cannot reproduce this (inconsistent) state diff at all -- which poses a +/// problem that the state diff commitment calculated by Pathfinder using our +/// representation of the state diff won't match the one from the feeder gateway +/// (and Juno). +/// +/// This migration adds a new `redeclared_classes` table storing class +/// re-declarations data. When inserting a state update we take care of adding +/// new rows here if the state update contains a re-declaration -- and then when +/// retrieving the state update from storage we add re-declared classes to the +/// set of Cairo classes declared at the block. +/// +/// This allows us to reproduce the exact same state diff as the one we've +/// received from the feeder gateway (or from other nodes via P2P). +pub(crate) fn migrate(tx: &rusqlite::Transaction<'_>) -> anyhow::Result<()> { + tracing::info!("Adding redeclared_classes table"); + + tx.execute_batch( + r"CREATE TABLE redeclared_classes ( + class_hash BLOB NOT NULL, + block_number INTEGER NOT NULL REFERENCES block_headers(number) ON DELETE CASCADE + ); + CREATE INDEX redeclared_classes_block_number ON redeclared_classes(block_number);", + ) + .context("Adding redeclared_classes table")?; + + Ok(()) +} From 0148dbc728f1f7634e413a57cbdf6f9750490d12 Mon Sep 17 00:00:00 2001 From: Krisztian Kovacs Date: Fri, 13 Sep 2024 16:42:21 +0200 Subject: [PATCH 045/282] chore(rpc/fixtures): upgrade mainnet fixture --- crates/rpc/fixtures/mainnet.sqlite | Bin 454656 -> 454656 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/crates/rpc/fixtures/mainnet.sqlite b/crates/rpc/fixtures/mainnet.sqlite index 1769b73e3b45c706396306085e98f24cd19f287b..b9141b83d70f978c5640fdfeac191949ca1e0fbc 100644 GIT binary patch delta 417 zcmZp8Al>jldV;j77Xt%BJ`e{0u^bS$05J;?!vG_Y;V@ZHL7vg8F`+eqacct80{%q- z3T*~VZ3fKC3|I^{C9nu=XDMJ|ZeWz1&hdf8nK8fJ=L5@jpAW1%^cjVxvnsHuvM?WE zHr}qMz_x*ji6MOYf=0&R`bIu)c5!b*#zx)Dyp+_6qSTbsE3HI6C>bqS*klKtY2`0SSP87N3z=oS`t?EE^a&7j)P-rmxjulbz0{ S&t^Q`U!9F>`!W@_sM7#V8G?-f delta 3697 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 From db49ec6774d223cab131f70808095a97ace445cb Mon Sep 17 00:00:00 2001 From: Krisztian Kovacs Date: Thu, 12 Sep 2024 12:06:25 +0200 Subject: [PATCH 046/282] chore(pathfinder/sync): add logging for P2P stage failures In case one of the pipe stages fails we should print a log message with all information we have in addition to returning an error. --- .../pathfinder/src/sync/class_definitions.rs | 30 +++++++++++++++---- crates/pathfinder/src/sync/events.rs | 2 ++ crates/pathfinder/src/sync/headers.rs | 2 ++ crates/pathfinder/src/sync/state_updates.rs | 23 ++++++++++---- crates/pathfinder/src/sync/track.rs | 20 +++++++++++-- crates/pathfinder/src/sync/transactions.rs | 17 ++++++++--- 6 files changed, 77 insertions(+), 17 deletions(-) diff --git a/crates/pathfinder/src/sync/class_definitions.rs b/crates/pathfinder/src/sync/class_definitions.rs index 0d69b563a2..6a111971a1 100644 --- a/crates/pathfinder/src/sync/class_definitions.rs +++ b/crates/pathfinder/src/sync/class_definitions.rs @@ -123,8 +123,10 @@ impl ProcessStage for VerifyLayout { definition, } => { let layout = GwClassDefinition::Cairo( - serde_json::from_slice::>(&definition) - .map_err(|_| SyncError2::BadClassLayout)?, + serde_json::from_slice::>(&definition).map_err(|e| { + tracing::debug!(%block_number, error=%e, "Bad class layout"); + SyncError2::BadClassLayout + })?, ); Ok(ClassWithLayout { block_number, @@ -137,8 +139,10 @@ impl ProcessStage for VerifyLayout { sierra_definition, } => { let layout = GwClassDefinition::Sierra( - serde_json::from_slice::>(&sierra_definition) - .map_err(|_| SyncError2::BadClassLayout)?, + serde_json::from_slice::>(&sierra_definition).map_err(|e| { + tracing::debug!(%block_number, error=%e, "Bad class layout"); + SyncError2::BadClassLayout + })?, ); Ok(ClassWithLayout { block_number, @@ -227,12 +231,14 @@ impl ProcessStage for VerifyDeclaredAt { } if self.current.block_number != input.block_number { + tracing::debug!(expected_block_number=%self.current.block_number, block_number=%input.block_number, class_hash=%input.hash, "Unexpected class definition"); return Err(SyncError2::UnexpectedClass); } if self.current.classes.remove(&input.hash) { Ok(input) } else { + tracing::debug!(block_number=%input.block_number, class_hash=%input.hash, "Unexpected class definition"); Err(SyncError2::UnexpectedClass) } } @@ -427,6 +433,7 @@ impl ProcessStage for VerifyClassHashes { match class.definition { CompiledClassDefinition::Cairo(_) => { if !declared_classes.cairo.remove(&class.hash) { + tracing::debug!(class_hash=%class.hash, "Class hash not found in declared classes"); return Err(SyncError2::ClassDefinitionsDeclarationsMismatch); } } @@ -435,13 +442,26 @@ impl ProcessStage for VerifyClassHashes { declared_classes .sierra .remove(&hash) - .ok_or(SyncError2::ClassDefinitionsDeclarationsMismatch)?; + .ok_or_else(|| { + tracing::debug!(class_hash=%class.hash, "Class hash not found in declared classes"); + SyncError2::ClassDefinitionsDeclarationsMismatch})?; } } } if declared_classes.cairo.is_empty() && declared_classes.sierra.is_empty() { Ok(input) } else { + let missing: Vec = declared_classes + .cairo + .into_iter() + .chain( + declared_classes + .sierra + .into_values() + .map(|casm_hash| ClassHash(casm_hash.0)), + ) + .collect(); + tracing::trace!(?missing, "Expected class definitions are missing"); Err(SyncError2::ClassDefinitionsDeclarationsMismatch) } } diff --git a/crates/pathfinder/src/sync/events.rs b/crates/pathfinder/src/sync/events.rs index 19517b3d1d..c26e7141a2 100644 --- a/crates/pathfinder/src/sync/events.rs +++ b/crates/pathfinder/src/sync/events.rs @@ -166,10 +166,12 @@ impl ProcessStage for VerifyCommitment { } } if ordered_events.len() != events.len() { + tracing::debug!(expected=%ordered_events.len(), actual=%events.len(), "Number of events received does not match expected number of events"); return Err(SyncError2::EventsTransactionsMismatch); } let actual = calculate_event_commitment(&ordered_events, version)?; if actual != event_commitment { + tracing::debug!(expected=%event_commitment, actual=%actual, "Event commitment mismatch"); return Err(SyncError2::EventCommitmentMismatch); } Ok(events) diff --git a/crates/pathfinder/src/sync/headers.rs b/crates/pathfinder/src/sync/headers.rs index 9e17304884..cc20653080 100644 --- a/crates/pathfinder/src/sync/headers.rs +++ b/crates/pathfinder/src/sync/headers.rs @@ -178,6 +178,7 @@ impl ProcessStage for ForwardContinuity { let header = &input.header; if header.number != self.next || header.parent_hash != self.parent_hash { + tracing::debug!(expected_block_number=%self.next, actual_block_number=%header.number, expected_parent_block_hash=%self.parent_hash, actual_parent_block_hash=%header.parent_hash, "Block chain discontinuity"); return Err(SyncError2::Discontinuity); } @@ -209,6 +210,7 @@ impl ProcessStage for BackwardContinuity { let number = self.number.ok_or(SyncError2::Discontinuity)?; if input.header.number != number || input.header.hash != self.hash { + tracing::debug!(expected_block_number=%number, actual_block_number=%input.header.number, expected_block_hash=%self.hash, actual_block_hash=%input.header.hash, "Block chain discontinuity"); return Err(SyncError2::Discontinuity); } diff --git a/crates/pathfinder/src/sync/state_updates.rs b/crates/pathfinder/src/sync/state_updates.rs index fb46f2b79e..83b4cd817f 100644 --- a/crates/pathfinder/src/sync/state_updates.rs +++ b/crates/pathfinder/src/sync/state_updates.rs @@ -89,7 +89,7 @@ impl FetchCommitmentFromDb { impl ProcessStage for FetchCommitmentFromDb { const NAME: &'static str = "StateDiff::FetchCommitmentFromDb"; type Input = (T, BlockNumber); - type Output = (T, StarknetVersion, StateDiffCommitment); + type Output = (T, BlockNumber, StarknetVersion, StateDiffCommitment); fn map(&mut self, (data, block_number): Self::Input) -> Result { let mut db = self @@ -104,7 +104,7 @@ impl ProcessStage for FetchCommitmentFromDb { .state_diff_commitment(block_number) .context("Fetching state diff commitment")? .ok_or(SyncError2::StateDiffCommitmentNotFound)?; - Ok((data, version, commitment)) + Ok((data, block_number, version, commitment)) } } @@ -112,14 +112,20 @@ pub struct VerifyCommitment; impl ProcessStage for VerifyCommitment { const NAME: &'static str = "StateDiff::Verify"; - type Input = (StateUpdateData, StarknetVersion, StateDiffCommitment); + type Input = ( + StateUpdateData, + BlockNumber, + StarknetVersion, + StateDiffCommitment, + ); type Output = StateUpdateData; fn map(&mut self, input: Self::Input) -> Result { - let (state_diff, version, expected_commitment) = input; - let actual = state_diff.compute_state_diff_commitment(version); + let (state_diff, block_number, version, expected_commitment) = input; + let actual_commitment = state_diff.compute_state_diff_commitment(version); - if actual != expected_commitment { + if actual_commitment != expected_commitment { + tracing::debug!(%block_number, %expected_commitment, %actual_commitment, "State diff commitment mismatch"); return Err(SyncError2::StateDiffCommitmentMismatch); } @@ -168,6 +174,11 @@ impl ProcessStage for UpdateStarknetState { .context("Querying state commitment")? .context("State commitment not found")?; if state_commitment != expected_state_commitment { + tracing::debug!( + actual_storage_commitment=%storage_commitment, + actual_class_commitment=%class_commitment, + actual_state_commitment=%state_commitment, + "State root mismatch"); return Err(SyncError2::StateRootMismatch); } diff --git a/crates/pathfinder/src/sync/track.rs b/crates/pathfinder/src/sync/track.rs index 53e05b75f0..88c1cd7fb6 100644 --- a/crates/pathfinder/src/sync/track.rs +++ b/crates/pathfinder/src/sync/track.rs @@ -343,7 +343,14 @@ struct TransactionSource

{ } impl

TransactionSource

{ - fn spawn(self) -> SyncReceiver<(TransactionData, StarknetVersion, TransactionCommitment)> + fn spawn( + self, + ) -> SyncReceiver<( + TransactionData, + BlockNumber, + StarknetVersion, + TransactionCommitment, + )> where P: Clone + BlockClient + Send + 'static, { @@ -394,6 +401,7 @@ impl

TransactionSource

{ peer, ( transactions_vec, + header.number, header.starknet_version, header.transaction_commitment, ), @@ -497,7 +505,14 @@ struct StateDiffSource

{ } impl

StateDiffSource

{ - fn spawn(self) -> SyncReceiver<(StateUpdateData, StarknetVersion, StateDiffCommitment)> + fn spawn( + self, + ) -> SyncReceiver<( + StateUpdateData, + BlockNumber, + StarknetVersion, + StateDiffCommitment, + )> where P: Clone + BlockClient + Send + 'static, { @@ -527,6 +542,7 @@ impl

StateDiffSource

{ peer, ( state_diff, + header.header.number, header.header.starknet_version, header.header.state_diff_commitment, ), diff --git a/crates/pathfinder/src/sync/transactions.rs b/crates/pathfinder/src/sync/transactions.rs index 6fbad83ccf..ab9034d795 100644 --- a/crates/pathfinder/src/sync/transactions.rs +++ b/crates/pathfinder/src/sync/transactions.rs @@ -29,6 +29,7 @@ pub struct UnverifiedTransactions { pub expected_commitment: TransactionCommitment, pub transactions: Vec<(Transaction, Receipt)>, pub version: StarknetVersion, + pub block_number: BlockNumber, } pub(super) async fn next_missing( @@ -75,12 +76,17 @@ pub struct CalculateHashes(pub ChainId); impl ProcessStage for CalculateHashes { const NAME: &'static str = "Transactions::Hashes"; - type Input = (TransactionData, StarknetVersion, TransactionCommitment); + type Input = ( + TransactionData, + BlockNumber, + StarknetVersion, + TransactionCommitment, + ); type Output = UnverifiedTransactions; fn map(&mut self, input: Self::Input) -> Result { use rayon::prelude::*; - let (transactions, version, expected_commitment) = input; + let (transactions, block_number, version, expected_commitment) = input; let transactions = transactions .into_par_iter() .map(|(tv, r)| { @@ -104,6 +110,7 @@ impl ProcessStage for CalculateHashes { expected_commitment, transactions, version, + block_number, }) } } @@ -125,7 +132,7 @@ impl FetchCommitmentFromDb { impl ProcessStage for FetchCommitmentFromDb { const NAME: &'static str = "Transactions::FetchCommitmentFromDb"; type Input = (T, BlockNumber); - type Output = (T, StarknetVersion, TransactionCommitment); + type Output = (T, BlockNumber, StarknetVersion, TransactionCommitment); fn map(&mut self, (data, block_number): Self::Input) -> Result { let mut db = self @@ -140,7 +147,7 @@ impl ProcessStage for FetchCommitmentFromDb { .transaction_commitment(block_number) .context("Fetching transaction commitment")? .ok_or(SyncError2::TransactionCommitmentNotFound)?; - Ok((data, version, commitment)) + Ok((data, block_number, version, commitment)) } } @@ -156,10 +163,12 @@ impl ProcessStage for VerifyCommitment { expected_commitment, transactions, version, + block_number, } = transactions; let txs: Vec<_> = transactions.iter().map(|(t, _)| t.clone()).collect(); let actual = calculate_transaction_commitment(&txs, version)?; if actual != expected_commitment { + tracing::debug!(%block_number, %expected_commitment, actual_commitment=%actual, "Transaction commitment mismatch"); return Err(SyncError2::TransactionCommitmentMismatch); } Ok(transactions) From 240ee72563c5428fadc58bdeb21c2c4e5ec741d7 Mon Sep 17 00:00:00 2001 From: Krisztian Kovacs Date: Mon, 16 Sep 2024 14:05:55 +0200 Subject: [PATCH 047/282] chore(pathfinder/sync): don't log sync status on info level --- crates/pathfinder/src/sync/checkpoint.rs | 9 +++------ crates/pathfinder/src/sync/track.rs | 2 +- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/crates/pathfinder/src/sync/checkpoint.rs b/crates/pathfinder/src/sync/checkpoint.rs index d7fb84335b..604fdfb11e 100644 --- a/crates/pathfinder/src/sync/checkpoint.rs +++ b/crates/pathfinder/src/sync/checkpoint.rs @@ -141,8 +141,6 @@ impl Sync { /// No guarantees are made about any headers newer than the anchor. #[tracing::instrument(level = "debug", skip(self, anchor))] async fn sync_headers(&self, anchor: EthereumStateUpdate) -> Result<(), SyncError> { - tracing::info!(?anchor); - while let Some(gap) = headers::next_gap(self.storage.clone(), anchor.block_number, anchor.block_hash) .await @@ -289,7 +287,6 @@ async fn handle_header_stream( public_key: PublicKey, storage: Storage, ) -> Result<(), SyncError> { - tracing::info!("Syncing headers"); InfallibleSource::from_stream(stream) .spawn() .pipe(headers::BackwardContinuity::new(head.0, head.1), 10) @@ -327,7 +324,7 @@ async fn handle_transaction_stream( .pipe(transactions::VerifyCommitment, 10) .pipe(transactions::Store::new(storage.connection()?, start), 10) .into_stream() - .inspect_ok(|x| tracing::info!(tail=%x.data, "Transactions chunk synced")) + .inspect_ok(|x| tracing::debug!(tail=%x.data, "Transactions chunk synced")) .try_fold((), |_, _| std::future::ready(Ok(()))) .await .map_err(SyncError::from_v2)?; @@ -357,7 +354,7 @@ async fn handle_state_diff_stream( 10, ) .into_stream() - .inspect_ok(|x| tracing::info!(tail=%x.data, "State diff synced")) + .inspect_ok(|x| tracing::debug!(tail=%x.data, "State diff synced")) .try_fold((), |_, _| std::future::ready(Ok(()))) .await .map_err(SyncError::from_v2)?; @@ -407,7 +404,7 @@ async fn handle_event_stream( .try_chunks(100) .map_err(|e| e.1) .and_then(|x| events::persist(storage.clone(), x)) - .inspect_ok(|x| tracing::info!(tail=%x, "Events chunk synced")) + .inspect_ok(|x| tracing::debug!(tail=%x, "Events chunk synced")) // Drive stream to completion. .try_fold((), |_, _| std::future::ready(Ok(()))) .await?; diff --git a/crates/pathfinder/src/sync/track.rs b/crates/pathfinder/src/sync/track.rs index 88c1cd7fb6..98b27073e8 100644 --- a/crates/pathfinder/src/sync/track.rs +++ b/crates/pathfinder/src/sync/track.rs @@ -812,7 +812,7 @@ impl ProcessStage for StoreBlock { .context("Committing transaction") .map_err(Into::into); - tracing::info!(number=%block_number, "Block stored"); + tracing::debug!(number=%block_number, "Block stored"); result } From 148aa99b478dce4c6d3c1cb3fbc63967262ec8be Mon Sep 17 00:00:00 2001 From: sistemd Date: Tue, 17 Sep 2024 18:10:44 +0200 Subject: [PATCH 048/282] starknet_subscribeNewHeads tests --- crates/rpc/src/jsonrpc.rs | 2 + crates/rpc/src/jsonrpc/router.rs | 7 +- crates/rpc/src/jsonrpc/router/subscription.rs | 189 ++++++++---- crates/rpc/src/lib.rs | 2 +- crates/rpc/src/method/subscribe_new_heads.rs | 287 ++++++++++++++++-- crates/rpc/src/v02/types.rs | 2 +- 6 files changed, 399 insertions(+), 90 deletions(-) diff --git a/crates/rpc/src/jsonrpc.rs b/crates/rpc/src/jsonrpc.rs index 0ef8543e87..a002166915 100644 --- a/crates/rpc/src/jsonrpc.rs +++ b/crates/rpc/src/jsonrpc.rs @@ -9,6 +9,8 @@ use std::sync::Arc; pub use error::RpcError; pub use request::RpcRequest; pub use response::RpcResponse; +#[cfg(test)] +pub use router::handle_json_rpc_socket; pub use router::{rpc_handler, RpcRouter, RpcRouterBuilder, RpcSubscriptionFlow}; use tokio::sync::broadcast; diff --git a/crates/rpc/src/jsonrpc/router.rs b/crates/rpc/src/jsonrpc/router.rs index 721f6c3938..5982822300 100644 --- a/crates/rpc/src/jsonrpc/router.rs +++ b/crates/rpc/src/jsonrpc/router.rs @@ -7,8 +7,8 @@ use axum::response::IntoResponse; use futures::{Future, FutureExt, StreamExt}; use http::HeaderValue; use method::RpcMethodEndpoint; -pub use subscription::RpcSubscriptionFlow; -use subscription::{handle_json_rpc_socket, RpcSubscriptionEndpoint}; +pub use subscription::{handle_json_rpc_socket, RpcSubscriptionFlow}; +use subscription::{split_ws, RpcSubscriptionEndpoint}; use crate::context::RpcContext; use crate::jsonrpc::error::RpcError; @@ -186,7 +186,8 @@ pub async fn rpc_handler( ) -> impl axum::response::IntoResponse { match ws { Some(ws) => ws.on_upgrade(|ws| async move { - handle_json_rpc_socket(state, ws).await; + let (ws_tx, ws_rx) = split_ws(ws); + handle_json_rpc_socket(state, ws_tx, ws_rx); }), None => { // Only utf8 json content allowed. diff --git a/crates/rpc/src/jsonrpc/router/subscription.rs b/crates/rpc/src/jsonrpc/router/subscription.rs index 88e19d4dfc..3774123610 100644 --- a/crates/rpc/src/jsonrpc/router/subscription.rs +++ b/crates/rpc/src/jsonrpc/router/subscription.rs @@ -14,6 +14,7 @@ use crate::error::ApplicationError; use crate::jsonrpc::{RpcError, RpcRequest, RpcResponse}; use crate::{RpcVersion, SubscriptionId}; +/// See [`RpcSubscriptionFlow`]. #[axum::async_trait] pub(super) trait RpcSubscriptionEndpoint: Send + Sync { // Start the subscription. @@ -62,7 +63,9 @@ pub trait RpcSubscriptionFlow: Send + Sync { /// The value for the `method` field of the subscription notification. fn subscription_name() -> &'static str; - /// The block to start streaming from. + /// The block to start streaming from. If the subscription endpoint does not + /// support catching up, the value returned by this method is + /// irrelevant. fn starting_block(req: &Self::Request) -> BlockId; /// Fetch historical data from the `from` block to the `to` block. The @@ -104,37 +107,55 @@ where _phantom: Default::default(), }; - // Catch up to the latest block in batches of BATCH_SIZE. - let first_block = pathfinder_storage::BlockId::try_from(T::starting_block(&req)) - .map_err(|e| RpcError::InvalidParams(e.to_string()))?; - let storage = state.storage.clone(); - let mut current_block = tokio::task::spawn_blocking(move || -> Result<_, RpcError> { - let mut conn = storage.connection().map_err(RpcError::InternalError)?; - let db = conn.transaction().map_err(RpcError::InternalError)?; - db.block_number(first_block) - .map_err(RpcError::InternalError)? - .ok_or_else(|| ApplicationError::BlockNotFound.into()) - }) - .await - .map_err(|e| RpcError::InternalError(e.into()))??; - const BATCH_SIZE: u64 = 64; - loop { - let messages = - T::catch_up(&state, &req, current_block, current_block + BATCH_SIZE).await?; - if messages.is_empty() { - // Caught up. - break; + let first_block = T::starting_block(&req); + + let current_block = match &first_block { + BlockId::Pending => { + return Err(RpcError::InvalidParams( + "Pending block is not supported for new heads subscription".to_string(), + )); } - for (message, block_number) in messages { - if tx.send(message).await.is_err() { - // Subscription closing. - return Ok(()); + BlockId::Latest => { + // No need to catch up. The code below will subscribe to new blocks. + BlockNumber::MAX + } + BlockId::Number(_) | BlockId::Hash(_) => { + // Catch up to the latest block in batches of BATCH_SIZE. + let first_block = pathfinder_storage::BlockId::try_from(T::starting_block(&req)) + .map_err(|e| RpcError::InvalidParams(e.to_string()))?; + let storage = state.storage.clone(); + let mut current_block = + tokio::task::spawn_blocking(move || -> Result<_, RpcError> { + let mut conn = storage.connection().map_err(RpcError::InternalError)?; + let db = conn.transaction().map_err(RpcError::InternalError)?; + db.block_number(first_block) + .map_err(RpcError::InternalError)? + .ok_or_else(|| ApplicationError::BlockNotFound.into()) + }) + .await + .map_err(|e| RpcError::InternalError(e.into()))??; + const BATCH_SIZE: u64 = 64; + loop { + let messages = + T::catch_up(&state, &req, current_block, current_block + BATCH_SIZE) + .await?; + if messages.is_empty() { + // Caught up. + break; + } + for (message, block_number) in messages { + if tx.send(message).await.is_err() { + // Subscription closing. + return Ok(()); + } + current_block = block_number; + } + // Increment the current block by 1 because the catch_up range is inclusive. + current_block += 1; } - current_block = block_number; + current_block } - // Increment the current block by 1 because the catch_up range is inclusive. - current_block += 1; - } + }; // Subscribe to new blocks. Receive the first subscription message. let (tx1, mut rx1) = mpsc::channel::<(T::Notification, BlockNumber)>(1024); @@ -185,22 +206,28 @@ where } } -pub async fn handle_json_rpc_socket(state: RpcRouter, ws: WebSocket) { - let subscriptions: Arc>> = - Default::default(); - // Send messages to the websocket using an mpsc channel. - let (tx, mut rx) = mpsc::channel::>(1024); - let (mut ws_tx, mut ws_rx) = ws.split(); +type WsSender = mpsc::Sender>; +type WsReceiver = mpsc::Receiver>; + +/// Split a websocket into an MPSC sender and receiver. +/// These two are later passed to [`handle_json_rpc_socket`]. This separation +/// serves to allow easier testing. The sender sends `Result<_, RpcResponse>` +/// purely for convenience, and the [`RpcResponse`] will be encoded into a +/// [`Message::Text`]. +pub fn split_ws(ws: WebSocket) -> (WsSender, WsReceiver) { + let (mut ws_sender, mut ws_receiver) = ws.split(); + // Send messages to the websocket using an MPSC channel. + let (sender_tx, mut sender_rx) = mpsc::channel::>(1024); tokio::spawn(async move { - while let Some(msg) = rx.recv().await { + while let Some(msg) = sender_rx.recv().await { match msg { Ok(msg) => { - if ws_tx.send(msg).await.is_err() { + if ws_sender.send(msg).await.is_err() { break; } } Err(e) => { - if ws_tx + if ws_sender .send(Message::Text(serde_json::to_string(&e).unwrap())) .await .is_err() @@ -211,10 +238,29 @@ pub async fn handle_json_rpc_socket(state: RpcRouter, ws: WebSocket) { } } }); + // Receive messages from the websocket using an MPSC channel. + let (receiver_tx, receiver_rx) = mpsc::channel::>(1024); + tokio::spawn(async move { + while let Some(msg) = ws_receiver.next().await { + if receiver_tx.send(msg).await.is_err() { + break; + } + } + }); + (sender_tx, receiver_rx) +} + +pub fn handle_json_rpc_socket( + state: RpcRouter, + ws_tx: mpsc::Sender>, + mut ws_rx: mpsc::Receiver>, +) { + let subscriptions: Arc>> = + Default::default(); // Read and handle messages from the websocket. tokio::spawn(async move { loop { - let request = match ws_rx.next().await { + let request = match ws_rx.recv().await { Some(Ok(Message::Text(msg))) => msg.into_bytes(), Some(Ok(Message::Binary(bytes))) => bytes, Some(Ok(Message::Pong(_) | Message::Ping(_))) => { @@ -234,7 +280,7 @@ pub async fn handle_json_rpc_socket(state: RpcRouter, ws: WebSocket) { let rpc_request = match serde_json::from_slice::>(&request) { Ok(request) => request, Err(err) => { - if tx + if ws_tx .send(Err(RpcResponse::parse_error(err.to_string()))) .await .is_err() @@ -245,13 +291,14 @@ pub async fn handle_json_rpc_socket(state: RpcRouter, ws: WebSocket) { continue; } }; + let req_id = rpc_request.id; if rpc_request.method == "starknet_unsubscribe" { // End the subscription. let Some(params) = rpc_request.params.0 else { - if tx + if ws_tx .send(Err(RpcResponse::invalid_params( - rpc_request.id, + req_id, "Missing params for starknet_unsubscribe".to_string(), ))) .await @@ -264,11 +311,8 @@ pub async fn handle_json_rpc_socket(state: RpcRouter, ws: WebSocket) { let params = match serde_json::from_str::(params.get()) { Ok(params) => params, Err(err) => { - if tx - .send(Err(RpcResponse::invalid_params( - rpc_request.id, - err.to_string(), - ))) + if ws_tx + .send(Err(RpcResponse::invalid_params(req_id, err.to_string()))) .await .is_err() { @@ -278,9 +322,9 @@ pub async fn handle_json_rpc_socket(state: RpcRouter, ws: WebSocket) { } }; let Some((_, handle)) = subscriptions.remove(¶ms.subscription_id) else { - if tx + if ws_tx .send(Err(RpcResponse::invalid_params( - rpc_request.id, + req_id, "Subscription not found".to_string(), ))) .await @@ -291,7 +335,21 @@ pub async fn handle_json_rpc_socket(state: RpcRouter, ws: WebSocket) { continue; }; handle.abort(); + if ws_tx + .send(Ok(Message::Text( + serde_json::to_string(&RpcResponse { + output: Ok(true.into()), + id: req_id.clone(), + }) + .unwrap(), + ))) + .await + .is_err() + { + break; + } metrics::increment_counter!("rpc_method_calls_total", "method" => "starknet_unsubscribe", "version" => state.version.to_str()); + continue; } // Also grab the method_name as it is a static str, which is required by the @@ -300,11 +358,12 @@ pub async fn handle_json_rpc_socket(state: RpcRouter, ws: WebSocket) { .subscription_endpoints .get_key_value(rpc_request.method.as_ref()) else { - tx.send(Ok(Message::Text( - serde_json::to_string(&RpcResponse::method_not_found(rpc_request.id)).unwrap(), - ))) - .await - .ok(); + ws_tx + .send(Ok(Message::Text( + serde_json::to_string(&RpcResponse::method_not_found(req_id)).unwrap(), + ))) + .await + .ok(); continue; }; metrics::increment_counter!("rpc_method_calls_total", "method" => method_name, "version" => state.version.to_str()); @@ -312,7 +371,7 @@ pub async fn handle_json_rpc_socket(state: RpcRouter, ws: WebSocket) { let params = match serde_json::to_value(rpc_request.params) { Ok(params) => params, Err(_e) => { - if tx + if ws_tx .send(Ok(Message::Text( serde_json::to_string(&RpcError::InvalidParams( "Invalid params".to_string(), @@ -332,9 +391,8 @@ pub async fn handle_json_rpc_socket(state: RpcRouter, ws: WebSocket) { let subscription_id = SubscriptionId::next(); let context = state.context.clone(); let version = state.version; - let tx = tx.clone(); - let req_id = rpc_request.id; - if tx + let ws_tx = ws_tx.clone(); + if ws_tx .send(Ok(Message::Text( serde_json::to_string(&RpcResponse { output: Ok( @@ -360,16 +418,17 @@ pub async fn handle_json_rpc_socket(state: RpcRouter, ws: WebSocket) { subscription_id, subscriptions, version, - tx.clone(), + ws_tx.clone(), ) .await { - tx.send(Err(RpcResponse { - output: Err(e), - id: req_id, - })) - .await - .ok(); + ws_tx + .send(Err(RpcResponse { + output: Err(e), + id: req_id, + })) + .await + .ok(); } } }); diff --git a/crates/rpc/src/lib.rs b/crates/rpc/src/lib.rs index abeced32dd..db0824cda6 100644 --- a/crates/rpc/src/lib.rs +++ b/crates/rpc/src/lib.rs @@ -238,7 +238,7 @@ impl Default for SyncState { } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)] -pub struct SubscriptionId(pub u32); +pub(crate) struct SubscriptionId(pub u32); impl SubscriptionId { pub fn next() -> Self { diff --git a/crates/rpc/src/method/subscribe_new_heads.rs b/crates/rpc/src/method/subscribe_new_heads.rs index 4847cf6f62..0a1009361b 100644 --- a/crates/rpc/src/method/subscribe_new_heads.rs +++ b/crates/rpc/src/method/subscribe_new_heads.rs @@ -97,48 +97,173 @@ impl RpcSubscriptionFlow for SubscribeNewHeads { #[cfg(test)] mod tests { - // TODO Remove this - #![allow(dead_code)] - use std::time::Duration; + use axum::extract::ws::Message; use pathfinder_common::{BlockHash, BlockHeader, BlockNumber, ChainId}; use pathfinder_crypto::Felt; use pathfinder_storage::StorageBuilder; use starknet_gateway_client::Client; + use tokio::sync::mpsc; use crate::context::{RpcConfig, RpcContext}; + use crate::jsonrpc::{handle_json_rpc_socket, RpcResponse, RpcRouter}; use crate::pending::PendingWatcher; use crate::v02::types::syncing::Syncing; - use crate::{Notifications, SyncState}; + use crate::{v08, Notifications, SubscriptionId, SyncState}; - #[test] - fn happy_path_with_historic_blocks() {} + #[tokio::test] + async fn happy_path_with_historic_blocks() { + happy_path_test(1000).await; + } - #[test] - fn happy_path_with_historic_blocks_batching() {} + #[tokio::test] + async fn happy_path_with_historic_blocks_no_batching() { + happy_path_test(10).await; + } - #[test] - fn happy_path_with_no_historic_blocks() {} + #[tokio::test] + async fn happy_path_with_historic_blocks_batching_edge_case() { + happy_path_test(128).await; + } - #[test] - fn race_condition_with_historic_blocks() {} + #[tokio::test] + async fn happy_path_with_no_historic_blocks() { + happy_path_test(0).await; + } - #[test] - fn unsubscribe() {} + #[tokio::test] + async fn race_condition_with_historic_blocks() { + let num_blocks = 1000; + let router = setup(num_blocks); + let (sender_tx, mut sender_rx) = mpsc::channel(1024); + let (receiver_tx, receiver_rx) = mpsc::channel(1024); + handle_json_rpc_socket(router.clone(), sender_tx, receiver_rx); + receiver_tx + .send(Ok(Message::Text( + serde_json::json!({ + "jsonrpc": "2.0", + "id": 1, + "method": "starknet_subscribeNewHeads", + "params": {"block": {"block_number": 0}} + }) + .to_string(), + ))) + .await + .unwrap(); + let res = sender_rx.recv().await.unwrap().unwrap(); + let subscription_id = match res { + Message::Text(json) => { + let json: serde_json::Value = serde_json::from_str(&json).unwrap(); + assert_eq!(json["jsonrpc"], "2.0"); + assert_eq!(json["id"], 1); + json["result"]["subscription_id"].as_u64().unwrap() + } + _ => panic!("Expected text message"), + }; + for i in 0..num_blocks { + let expected = sample_new_heads_message(i, subscription_id); + let header = sender_rx.recv().await.unwrap().unwrap(); + let json: serde_json::Value = match header { + Message::Text(json) => serde_json::from_str(&json).unwrap(), + _ => panic!("Expected text message"), + }; + assert_eq!(json, expected); + } + // Ensure that the background task processes beyond the catch-up phase. + for _ in 0..10 { + tokio::task::yield_now().await; + } + // Insert more blocks before the active updates kick in. This simulates a + // real-world race condition. + for i in 0..num_blocks { + let mut conn = router.context.storage.connection().unwrap(); + let db = conn.transaction().unwrap(); + let header = sample_header(i + num_blocks); + db.insert_block_header(&header).unwrap(); + db.commit().unwrap(); + } + for i in 0..10 { + router + .context + .notifications + .block_headers + .send(sample_header(i + 2 * num_blocks).into()) + .unwrap(); + if i == 0 { + // First, expect all the newly inserted blocks. + for j in 0..num_blocks { + let expected = sample_new_heads_message(j + num_blocks, subscription_id); + let header = sender_rx.recv().await.unwrap().unwrap(); + let json: serde_json::Value = match header { + Message::Text(json) => serde_json::from_str(&json).unwrap(), + _ => panic!("Expected text message"), + }; + assert_eq!(json, expected); + } + } + // Then, expect the block updates. + let expected = sample_new_heads_message(i + 2 * num_blocks, subscription_id); + let header = sender_rx.recv().await.unwrap().unwrap(); + let json: serde_json::Value = match header { + Message::Text(json) => serde_json::from_str(&json).unwrap(), + _ => panic!("Expected text message"), + }; + assert_eq!(json, expected); + } + assert!(sender_rx.is_empty()); + } - fn setup(num_blocks: u64) -> RpcContext { + #[tokio::test] + async fn unsubscribe() { + let (tx, mut rx, subscription_id, router) = happy_path_test(0).await; + tx.send(Ok(Message::Text( + serde_json::json!({ + "jsonrpc": "2.0", + "id": 100, + "method": "starknet_unsubscribe", + "params": {"subscription_id": subscription_id.0} + }) + .to_string(), + ))) + .await + .unwrap(); + let res = rx.recv().await.unwrap().unwrap(); + let json: serde_json::Value = match res { + Message::Text(json) => serde_json::from_str(&json).unwrap(), + _ => panic!("Expected text message"), + }; + assert_eq!( + json, + serde_json::json!({"jsonrpc": "2.0", "id": 100, "result": true}) + ); + router + .context + .notifications + .block_headers + .send(sample_header(10).into()) + // Might error if the receiver is closed. + .ok(); + // Give time for background tasks to process. + for _ in 0..10 { + tokio::task::yield_now().await; + } + // Since the subscription was cancelled, no more messages should be received. + assert!(rx.is_empty()); + } + + fn setup(num_blocks: u64) -> RpcRouter { let storage = StorageBuilder::in_memory().unwrap(); let mut conn = storage.connection().unwrap(); let db = conn.transaction().unwrap(); - for i in 1..num_blocks { + for i in 0..num_blocks { let header = sample_header(i); db.insert_block_header(&header).unwrap(); } db.commit().unwrap(); let (_, pending_data) = tokio::sync::watch::channel(Default::default()); let notifications = Notifications::default(); - RpcContext { + let ctx = RpcContext { cache: Default::default(), storage, execution_storage: StorageBuilder::in_memory().unwrap(), @@ -157,15 +282,137 @@ mod tests { get_events_max_uncached_bloom_filters_to_load: 1.try_into().unwrap(), custom_versioned_constants: None, }, + }; + v08::register_routes().build(ctx) + } + + async fn happy_path_test( + num_blocks: u64, + ) -> ( + mpsc::Sender>, + mpsc::Receiver>, + SubscriptionId, + RpcRouter, + ) { + let router = setup(num_blocks); + let (sender_tx, mut sender_rx) = mpsc::channel(1024); + let (receiver_tx, receiver_rx) = mpsc::channel(1024); + handle_json_rpc_socket(router.clone(), sender_tx, receiver_rx); + let params = if num_blocks == 0 { + serde_json::json!( + {"block": "latest"} + ) + } else { + serde_json::json!( + {"block": {"block_number": 0}} + ) + }; + receiver_tx + .send(Ok(Message::Text( + serde_json::json!({ + "jsonrpc": "2.0", + "id": 1, + "method": "starknet_subscribeNewHeads", + "params": params + }) + .to_string(), + ))) + .await + .unwrap(); + let res = sender_rx.recv().await.unwrap().unwrap(); + let subscription_id = match res { + Message::Text(json) => { + let json: serde_json::Value = serde_json::from_str(&json).unwrap(); + assert_eq!(json["jsonrpc"], "2.0"); + assert_eq!(json["id"], 1); + json["result"]["subscription_id"].as_u64().unwrap() + } + _ => panic!("Expected text message"), + }; + for i in 0..num_blocks { + let expected = sample_new_heads_message(i, subscription_id); + let header = sender_rx.recv().await.unwrap().unwrap(); + let json: serde_json::Value = match header { + Message::Text(json) => serde_json::from_str(&json).unwrap(), + _ => panic!("Expected text message"), + }; + assert_eq!(json, expected); } + for i in 0..10 { + retry(|| { + router + .context + .notifications + .block_headers + .send(sample_header(i).into()) + }) + .await + .unwrap(); + let expected = sample_new_heads_message(i, subscription_id); + let header = sender_rx.recv().await.unwrap().unwrap(); + let json: serde_json::Value = match header { + Message::Text(json) => serde_json::from_str(&json).unwrap(), + _ => panic!("Expected text message"), + }; + assert_eq!(json, expected); + } + assert!(sender_rx.is_empty()); + ( + receiver_tx, + sender_rx, + SubscriptionId(subscription_id.try_into().unwrap()), + router, + ) } - fn sample_header(i: u64) -> BlockHeader { + fn sample_header(block_number: u64) -> BlockHeader { BlockHeader { - hash: BlockHash(Felt::from_u64(i)), + hash: BlockHash(Felt::from_u64(block_number)), + number: BlockNumber::new_or_panic(block_number), parent_hash: BlockHash::ZERO, - number: BlockNumber::new_or_panic(i), ..Default::default() } } + + fn sample_new_heads_message(block_number: u64, subscription_id: u64) -> serde_json::Value { + let hash = Felt::from_u64(block_number); + serde_json::json!({ + "jsonrpc":"2.0", + "method":"starknet_subscriptionNewHeads", + "params": { + "result": { + "block_hash": hash, + "block_number": block_number, + "l1_da_mode": "CALLDATA", + "l1_data_gas_price": { "price_in_fri": "0x0", "price_in_wei": "0x0" }, + "l1_gas_price":{ "price_in_fri": "0x0", "price_in_wei": "0x0" }, + "new_root": "0x0", + "parent_hash": "0x0", + "sequencer_address": "0x0", + "starknet_version": "", + "timestamp": 0 + }, + "subscription_id": subscription_id + } + }) + } + + // Retry to let other tasks make progress. + async fn retry(cb: impl Fn() -> Result) -> Result + where + E: std::fmt::Debug, + { + for i in 0..10 { + match cb() { + Ok(result) => return Ok(result), + Err(e) => { + if i == 9 { + return Err(e); + } + tokio::task::yield_now().await; + } + } + } + unreachable!() + } } diff --git a/crates/rpc/src/v02/types.rs b/crates/rpc/src/v02/types.rs index 1d75e3b521..ef3eecab3d 100644 --- a/crates/rpc/src/v02/types.rs +++ b/crates/rpc/src/v02/types.rs @@ -1026,7 +1026,7 @@ pub mod request { assert_eq!(serde_json::to_value(&txs).unwrap(), json_fixture); assert_eq!( - crate::dto::Value::new(dbg!(json_fixture), crate::RpcVersion::V07) + crate::dto::Value::new(json_fixture, crate::RpcVersion::V07) .deserialize_array( ::deserialize ) From 661b24a75dcc01d8b9c31305b55c44d9603fca58 Mon Sep 17 00:00:00 2001 From: t00ts Date: Wed, 18 Sep 2024 15:11:23 +0200 Subject: [PATCH 049/282] remove `native-tls` dependency from `alloy` --- Cargo.lock | 114 +------------------------------------ crates/ethereum/Cargo.toml | 3 +- 2 files changed, 4 insertions(+), 113 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 60d58e78c0..4f331d7c74 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -111,13 +111,11 @@ dependencies = [ "alloy-contract", "alloy-core", "alloy-eips", - "alloy-genesis", "alloy-network", "alloy-provider", "alloy-pubsub", "alloy-rpc-client", "alloy-rpc-types", - "alloy-serde", "alloy-transport", "alloy-transport-http", "alloy-transport-ws", @@ -237,17 +235,6 @@ dependencies = [ "sha2", ] -[[package]] -name = "alloy-genesis" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7210f9206c0fa2a83c824cf8cb6c962126bc9fdc4f41ade1932f14150ef5f6" -dependencies = [ - "alloy-primitives", - "alloy-serde", - "serde", -] - [[package]] name = "alloy-json-abi" version = "0.8.3" @@ -4546,21 +4533,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - [[package]] name = "form_urlencoded" version = "1.2.1" @@ -5302,6 +5274,7 @@ dependencies = [ "tokio", "tokio-rustls", "tower-service", + "webpki-roots", ] [[package]] @@ -5316,22 +5289,6 @@ dependencies = [ "tokio-io-timeout", ] -[[package]] -name = "hyper-tls" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" -dependencies = [ - "bytes", - "http-body-util", - "hyper 1.4.1", - "hyper-util", - "native-tls", - "tokio", - "tokio-native-tls", - "tower-service", -] - [[package]] name = "hyper-util" version = "0.1.8" @@ -6754,23 +6711,6 @@ dependencies = [ "getrandom", ] -[[package]] -name = "native-tls" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" -dependencies = [ - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", -] - [[package]] name = "ndarray" version = "0.13.1" @@ -7081,50 +7021,12 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" -[[package]] -name = "openssl" -version = "0.10.66" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" -dependencies = [ - "bitflags 2.6.0", - "cfg-if", - "foreign-types", - "libc", - "once_cell", - "openssl-macros", - "openssl-sys", -] - -[[package]] -name = "openssl-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.77", -] - [[package]] name = "openssl-probe" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" -[[package]] -name = "openssl-sys" -version = "0.9.103" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - [[package]] name = "ordered-float" version = "2.10.1" @@ -8497,13 +8399,11 @@ dependencies = [ "http-body-util", "hyper 1.4.1", "hyper-rustls", - "hyper-tls", "hyper-util", "ipnet", "js-sys", "log", "mime", - "native-tls", "once_cell", "percent-encoding", "pin-project-lite", @@ -8517,13 +8417,13 @@ dependencies = [ "serde_urlencoded", "sync_wrapper 1.0.1", "tokio", - "tokio-native-tls", "tokio-rustls", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", + "webpki-roots", "windows-registry", ] @@ -9998,16 +9898,6 @@ dependencies = [ "syn 2.0.77", ] -[[package]] -name = "tokio-native-tls" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" -dependencies = [ - "native-tls", - "tokio", -] - [[package]] name = "tokio-retry" version = "0.3.0" diff --git a/crates/ethereum/Cargo.toml b/crates/ethereum/Cargo.toml index bba11d2fe0..aedcef4e8f 100644 --- a/crates/ethereum/Cargo.toml +++ b/crates/ethereum/Cargo.toml @@ -7,10 +7,11 @@ license = { workspace = true } rust-version = { workspace = true } [dependencies] -alloy = { version = "0.3", features = [ +alloy = { version = "0.3", default-features = false, features = [ "contract", "rpc-types", "provider-ws", + "reqwest-rustls-tls", ] } anyhow = { workspace = true } async-trait = { workspace = true } From fb9d64e0bc7a11de76184273c54df5220c89168c Mon Sep 17 00:00:00 2001 From: Krisztian Kovacs Date: Wed, 18 Sep 2024 14:38:36 +0200 Subject: [PATCH 050/282] chore(Dockerfile): enable passing extra cargo arguments This is handy for enabling extra features for example. --- Dockerfile | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index fed5dd5679..7ce519791c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,6 +18,7 @@ COPY . . RUN cargo chef prepare --recipe-path recipe.json FROM --platform=$BUILDPLATFORM cargo-chef AS rust-builder +ARG CARGO_EXTRA_ARGS ARG TARGETARCH COPY ./build/prepare.sh prepare.sh RUN TARGETARCH=${TARGETARCH} ./prepare.sh @@ -26,7 +27,7 @@ RUN TARGETARCH=${TARGETARCH} ./prepare.sh # input required for cargo chef cook, the command that will build out our dependencies. COPY --from=rust-planner /usr/src/pathfinder/recipe.json recipe.json COPY ./build/cargo-chef-cook.sh ./cargo-chef-cook.sh -RUN TARGETARCH=${TARGETARCH} ./cargo-chef-cook.sh --profile release-lto --recipe-path recipe.json --package pathfinder --bin pathfinder +RUN TARGETARCH=${TARGETARCH} ./cargo-chef-cook.sh --profile release-lto --recipe-path recipe.json --package pathfinder --bin pathfinder ${CARGO_EXTRA_ARGS} # Compile the actual libraries and binary now COPY . . @@ -34,7 +35,7 @@ ARG PATHFINDER_FORCE_VERSION COPY ./build/cargo-build.sh ./cargo-build.sh RUN TARGETARCH=${TARGETARCH} \ PATHFINDER_FORCE_VERSION=${PATHFINDER_FORCE_VERSION} \ - ./cargo-build.sh --locked --profile release-lto --package pathfinder --bin pathfinder \ + ./cargo-build.sh --locked --profile release-lto --package pathfinder --bin pathfinder ${CARGO_EXTRA_ARGS} \ && cp target/*-unknown-linux-gnu/release-lto/pathfinder pathfinder-${TARGETARCH} ####################### From 8aea70212d22934d16b8e51a060439e633a30246 Mon Sep 17 00:00:00 2001 From: Krisztian Kovacs Date: Wed, 18 Sep 2024 14:42:09 +0200 Subject: [PATCH 051/282] chore(github): add workflow for building a P2P-enabled Docker image --- .github/workflows/docker-p2p.yml | 69 ++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 .github/workflows/docker-p2p.yml diff --git a/.github/workflows/docker-p2p.yml b/.github/workflows/docker-p2p.yml new file mode 100644 index 0000000000..136a47fd1e --- /dev/null +++ b/.github/workflows/docker-p2p.yml @@ -0,0 +1,69 @@ +# Builds and uploads a P2P-enabled Docker image whenever triggered manually + +name: Docker - P2P + +on: + workflow_dispatch: + +env: + # Workaround for https://github.com/rust-lang/cargo/issues/8719#issuecomment-1516492970 + CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse + +jobs: + # Build a docker image unless this was triggered by a release. + build-image-p2p: + runs-on: pathfinder-large-ubuntu + steps: + - name: Determine Docker image metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: eqlabs/pathfinder + - name: Checkout sources + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Generate version + id: generate_version + run: | + echo -n "pathfinder_version=" >> $GITHUB_OUTPUT + git describe --tags --dirty >> $GITHUB_OUTPUT + - name: Set up QEMU + id: qemu + uses: docker/setup-qemu-action@v3 + with: + platforms: arm64 + - name: Set up Docker Buildx + id: buildx + uses: docker/setup-buildx-action@v3 + with: + buildkitd-config-inline: | + [worker.oci] + max-parallelism = 4 + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_HUB_USERNAME }} + password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} + # Required for git security reasons. See https://github.com/rustyhorde/vergen/pull/126#issuecomment-1201088162 + - name: Vergen git safe directory + run: git config --global --add safe.directory /workspace + - name: Build + id: docker_build + uses: docker/build-push-action@v5 + with: + context: . + platforms: | + linux/amd64 + file: ./Dockerfile + build-args: | + PATHFINDER_FORCE_VERSION=${{ steps.generate_version.outputs.pathfinder_version }} + CARGO_EXTRA_ARGS="--features p2p" + builder: ${{ steps.buildx.outputs.name }} + push: true + labels: ${{ steps.meta.outputs.labels }} + tags: | + eqlabs/pathfinder:snapshot-p2p-${{ github.sha }} + eqlabs/pathfinder:latest-p2p + cache-from: type=gha + cache-to: type=gha,mode=max From 0340dc75da2e28ecee7b6c7a07ed408457745dff Mon Sep 17 00:00:00 2001 From: Krzysztof Lis Date: Fri, 13 Sep 2024 18:15:59 +0200 Subject: [PATCH 052/282] refactor: split p2p::test_utils into more files --- crates/p2p/src/client/peer_aware.rs | 4 +- crates/p2p/src/main_loop.rs | 13 ++- crates/p2p/src/test_utils.rs | 143 +----------------------- crates/p2p/src/test_utils/main_loop.rs | 106 ++++++++++++++++++ crates/p2p/src/test_utils/peer_aware.rs | 38 +++++++ 5 files changed, 155 insertions(+), 149 deletions(-) create mode 100644 crates/p2p/src/test_utils/main_loop.rs create mode 100644 crates/p2p/src/test_utils/peer_aware.rs diff --git a/crates/p2p/src/client/peer_aware.rs b/crates/p2p/src/client/peer_aware.rs index f25addefc5..dc89e545f0 100644 --- a/crates/p2p/src/client/peer_aware.rs +++ b/crates/p2p/src/client/peer_aware.rs @@ -226,7 +226,7 @@ impl Client { } #[cfg(test)] - pub(crate) fn for_test(&self) -> test_utils::Client { - test_utils::Client::new(self.sender.clone()) + pub(crate) fn for_test(&self) -> test_utils::peer_aware::Client { + test_utils::peer_aware::Client::new(self.sender.clone()) } } diff --git a/crates/p2p/src/main_loop.rs b/crates/p2p/src/main_loop.rs index 772c970145..8897399e19 100644 --- a/crates/p2p/src/main_loop.rs +++ b/crates/p2p/src/main_loop.rs @@ -959,13 +959,13 @@ impl MainLoop { /// No-op outside tests async fn handle_event_for_test(&mut self, _event: SwarmEvent) { #[cfg(test)] - test_utils::handle_event(&self.event_sender, _event).await + test_utils::main_loop::handle_event(&self.event_sender, _event).await } /// No-op outside tests async fn handle_test_command(&mut self, _command: TestCommand) { #[cfg(test)] - test_utils::handle_command( + test_utils::main_loop::handle_command( self.swarm.behaviour_mut(), _command, &mut self._pending_test_queries.inner, @@ -976,7 +976,7 @@ impl MainLoop { /// Handle the final stage of the query, no-op outside tests async fn test_query_completed(&mut self, _id: QueryId, _result: QueryResult) { #[cfg(test)] - test_utils::query_completed( + test_utils::main_loop::query_completed( &mut self._pending_test_queries.inner, &self.event_sender, _id, @@ -988,18 +988,19 @@ impl MainLoop { /// Handle all stages except the final one, no-op outside tests async fn test_query_progressed(&mut self, _id: QueryId, _result: QueryResult) { #[cfg(test)] - test_utils::query_progressed(&self._pending_test_queries.inner, _id, _result).await + test_utils::main_loop::query_progressed(&self._pending_test_queries.inner, _id, _result) + .await } } /// No-op outside tests async fn send_test_event(_event_sender: &mpsc::Sender, _event: TestEvent) { #[cfg(test)] - test_utils::send_event(_event_sender, _event).await + test_utils::main_loop::send_event(_event_sender, _event).await } #[derive(Debug, Default)] struct TestQueries { #[cfg(test)] - inner: test_utils::PendingQueries, + inner: test_utils::main_loop::PendingQueries, } diff --git a/crates/p2p/src/test_utils.rs b/crates/p2p/src/test_utils.rs index 152c84fc7b..8ccfb61121 100644 --- a/crates/p2p/src/test_utils.rs +++ b/crates/p2p/src/test_utils.rs @@ -1,141 +1,2 @@ -use std::collections::{HashMap, HashSet}; - -use libp2p::kad::{QueryId, QueryResult}; -use libp2p::swarm::SwarmEvent; -use libp2p::{gossipsub, PeerId}; -use tokio::sync::{mpsc, oneshot}; - -use super::{behaviour, Command, Event, TestCommand, TestEvent}; -use crate::peers::Peer; - -#[derive(Clone)] -pub(super) struct Client { - sender: mpsc::Sender, -} - -impl Client { - pub(super) fn new(sender: mpsc::Sender) -> Self { - Self { sender } - } -} - -impl Client { - pub async fn get_peers_from_dht(&self) -> HashSet { - let (sender, receiver) = oneshot::channel(); - self.sender - .send(Command::_Test(TestCommand::GetPeersFromDHT(sender))) - .await - .expect("Command receiver not to be dropped"); - receiver.await.expect("Sender not to be dropped") - } - - pub async fn get_connected_peers(&self) -> HashMap { - let (sender, receiver) = oneshot::channel(); - self.sender - .send(Command::_Test(TestCommand::GetConnectedPeers(sender))) - .await - .expect("Command receiver not to be dropped"); - receiver.await.expect("Sender not to be dropped") - } -} - -pub(super) async fn handle_event( - event_sender: &mpsc::Sender, - event: SwarmEvent, -) { - match event { - SwarmEvent::NewListenAddr { address, .. } => { - send_event(event_sender, TestEvent::NewListenAddress(address)).await; - } - SwarmEvent::Behaviour(behaviour::Event::Gossipsub(gossipsub::Event::Subscribed { - peer_id, - topic, - })) => { - send_event( - event_sender, - TestEvent::Subscribed { - remote: peer_id, - topic: topic.into_string(), - }, - ) - .await; - } - _ => {} - } -} - -pub(super) async fn handle_command( - behavior: &mut behaviour::Behaviour, - command: TestCommand, - _pending_test_queries: &mut PendingQueries, -) { - match command { - TestCommand::GetPeersFromDHT(sender) => { - let peers = behavior - .kademlia_mut() - .kbuckets() - // Cannot .into_iter() a KBucketRef, hence the inner collect followed by flat_map - .map(|kbucket_ref| { - kbucket_ref - .iter() - .map(|entry_ref| *entry_ref.node.key.preimage()) - .collect::>() - }) - .flat_map(|peers_in_bucket| peers_in_bucket.into_iter()) - .collect::>(); - sender.send(peers).expect("Receiver not to be dropped") - } - TestCommand::GetConnectedPeers(sender) => { - let peers = behavior - .peers() - .filter_map(|(peer_id, peer)| { - if peer.is_connected() { - Some((peer_id, peer.clone())) - } else { - None - } - }) - .collect(); - sender.send(peers).expect("Receiver not to be dropped") - } - } -} - -pub(super) async fn send_event(event_sender: &mpsc::Sender, event: TestEvent) { - event_sender - .send(Event::Test(event)) - .await - .expect("Event receiver not to be dropped"); -} - -pub(super) async fn query_completed( - _pending_test_queries: &mut PendingQueries, - event_sender: &mpsc::Sender, - _id: QueryId, - result: QueryResult, -) { - if let QueryResult::StartProviding(result) = result { - use libp2p::kad::AddProviderOk; - - let result = match result { - Ok(AddProviderOk { key }) => Ok(key), - Err(error) => Err(error.into_key()), - }; - send_event(event_sender, TestEvent::StartProvidingCompleted(result)).await - } -} - -pub(super) async fn query_progressed( - _pending_test_queries: &PendingQueries, - _id: QueryId, - _result: QueryResult, -) { - // QueryResult::GetProviders used to be handled here, but now just keeping - // this fn as a placeholder for future query types in tests. -} - -#[derive(Debug, Default)] -pub(super) struct PendingQueries { - // QueryResult::GetProviders used to be handled here, but now just keeping this struct - // as a placeholder for future query types in tests. -} +pub mod main_loop; +pub mod peer_aware; diff --git a/crates/p2p/src/test_utils/main_loop.rs b/crates/p2p/src/test_utils/main_loop.rs new file mode 100644 index 0000000000..ba5c04c835 --- /dev/null +++ b/crates/p2p/src/test_utils/main_loop.rs @@ -0,0 +1,106 @@ +use std::collections::HashSet; + +use libp2p::gossipsub; +use libp2p::kad::{QueryId, QueryResult}; +use libp2p::swarm::SwarmEvent; +use tokio::sync::mpsc; + +use crate::{behaviour, Event, TestCommand, TestEvent}; + +pub async fn handle_event(event_sender: &mpsc::Sender, event: SwarmEvent) { + match event { + SwarmEvent::NewListenAddr { address, .. } => { + send_event(event_sender, TestEvent::NewListenAddress(address)).await; + } + SwarmEvent::Behaviour(behaviour::Event::Gossipsub(gossipsub::Event::Subscribed { + peer_id, + topic, + })) => { + send_event( + event_sender, + TestEvent::Subscribed { + remote: peer_id, + topic: topic.into_string(), + }, + ) + .await; + } + _ => {} + } +} + +pub async fn handle_command( + behavior: &mut behaviour::Behaviour, + command: TestCommand, + _pending_test_queries: &mut PendingQueries, +) { + match command { + TestCommand::GetPeersFromDHT(sender) => { + let peers = behavior + .kademlia_mut() + .kbuckets() + // Cannot .into_iter() a KBucketRef, hence the inner collect followed by flat_map + .map(|kbucket_ref| { + kbucket_ref + .iter() + .map(|entry_ref| *entry_ref.node.key.preimage()) + .collect::>() + }) + .flat_map(|peers_in_bucket| peers_in_bucket.into_iter()) + .collect::>(); + sender.send(peers).expect("Receiver not to be dropped") + } + TestCommand::GetConnectedPeers(sender) => { + let peers = behavior + .peers() + .filter_map(|(peer_id, peer)| { + if peer.is_connected() { + Some((peer_id, peer.clone())) + } else { + None + } + }) + .collect(); + sender.send(peers).expect("Receiver not to be dropped") + } + } +} + +pub async fn send_event(event_sender: &mpsc::Sender, event: TestEvent) { + event_sender + .send(Event::Test(event)) + .await + .expect("Event receiver not to be dropped"); +} + +pub async fn query_completed( + _pending_test_queries: &mut PendingQueries, + event_sender: &mpsc::Sender, + _id: QueryId, + result: QueryResult, +) { + if let QueryResult::StartProviding(result) = result { + use libp2p::kad::AddProviderOk; + + let result = match result { + Ok(AddProviderOk { key }) => Ok(key), + Err(error) => Err(error.into_key()), + }; + send_event(event_sender, TestEvent::StartProvidingCompleted(result)).await + } +} + +pub async fn query_progressed( + _pending_test_queries: &PendingQueries, + _id: QueryId, + _result: QueryResult, +) { + // QueryResult::GetProviders used to be handled here, but now just keeping + // this fn as a placeholder for future query types in tests. +} + +#[derive(Debug, Default)] +pub struct PendingQueries { + // QueryResult::GetProviders used to be handled here, but now just keeping this struct + // as a placeholder for future query types in tests. +} diff --git a/crates/p2p/src/test_utils/peer_aware.rs b/crates/p2p/src/test_utils/peer_aware.rs new file mode 100644 index 0000000000..40de726449 --- /dev/null +++ b/crates/p2p/src/test_utils/peer_aware.rs @@ -0,0 +1,38 @@ +use std::collections::{HashMap, HashSet}; + +use libp2p::PeerId; +use tokio::sync::{mpsc, oneshot}; + +use crate::peers::Peer; +use crate::{Command, TestCommand}; + +#[derive(Clone)] +pub struct Client { + sender: mpsc::Sender, +} + +impl Client { + pub fn new(sender: mpsc::Sender) -> Self { + Self { sender } + } +} + +impl Client { + pub async fn get_peers_from_dht(&self) -> HashSet { + let (sender, receiver) = oneshot::channel(); + self.sender + .send(Command::_Test(TestCommand::GetPeersFromDHT(sender))) + .await + .expect("Command receiver not to be dropped"); + receiver.await.expect("Sender not to be dropped") + } + + pub async fn get_connected_peers(&self) -> HashMap { + let (sender, receiver) = oneshot::channel(); + self.sender + .send(Command::_Test(TestCommand::GetConnectedPeers(sender))) + .await + .expect("Command receiver not to be dropped"); + receiver.await.expect("Sender not to be dropped") + } +} From aa71b845fc15899f62de2ccc4320e18b3fd764b3 Mon Sep 17 00:00:00 2001 From: Krzysztof Lis Date: Wed, 4 Sep 2024 10:47:44 +0200 Subject: [PATCH 053/282] refactor(p2p): add builders to the api --- crates/p2p/src/behaviour.rs | 134 ++---------- crates/p2p/src/behaviour/builder.rs | 187 +++++++++++++++++ crates/p2p/src/builder.rs | 71 +++++++ crates/p2p/src/lib.rs | 30 +-- crates/p2p/src/sync.rs | 303 ++++++++++++++++++++++++++- crates/p2p/src/tests.rs | 170 ++++++++++----- crates/p2p_stream/src/lib.rs | 29 ++- crates/p2p_stream/tests/utils/mod.rs | 8 +- 8 files changed, 721 insertions(+), 211 deletions(-) create mode 100644 crates/p2p/src/behaviour/builder.rs create mode 100644 crates/p2p/src/builder.rs diff --git a/crates/p2p/src/behaviour.rs b/crates/p2p/src/behaviour.rs index 14fb2c82c4..be011faf49 100644 --- a/crates/p2p/src/behaviour.rs +++ b/crates/p2p/src/behaviour.rs @@ -1,12 +1,10 @@ -use std::collections::hash_map::DefaultHasher; use std::collections::HashMap; -use std::hash::{Hash, Hasher}; use std::net::IpAddr; use std::time::{Duration, Instant}; use std::{cmp, task}; use libp2p::core::Endpoint; -use libp2p::gossipsub::{self, IdentTopic, MessageAuthenticity, MessageId}; +use libp2p::gossipsub::{self, IdentTopic}; use libp2p::kad::store::MemoryStore; use libp2p::kad::{self}; use libp2p::multiaddr::Protocol; @@ -23,7 +21,7 @@ use libp2p::swarm::{ THandlerOutEvent, ToSwarm, }; -use libp2p::{autonat, dcutr, identify, identity, ping, relay, Multiaddr, PeerId, StreamProtocol}; +use libp2p::{autonat, dcutr, identify, identity, ping, relay, Multiaddr, PeerId}; use p2p_proto::class::{ClassesRequest, ClassesResponse}; use p2p_proto::event::{EventsRequest, EventsResponse}; use p2p_proto::header::{BlockHeadersRequest, BlockHeadersResponse}; @@ -31,6 +29,10 @@ use p2p_proto::state::{StateDiffsRequest, StateDiffsResponse}; use p2p_proto::transaction::{TransactionsRequest, TransactionsResponse}; use pathfinder_common::ChainId; +mod builder; + +pub use builder::Builder; + use crate::peers::{Connectivity, Direction, KeyedNetworkGroup, Peer, PeerSet}; use crate::secret::Secret; use crate::sync::codec; @@ -40,6 +42,8 @@ pub fn kademlia_protocol_name(chain_id: ChainId) -> String { format!("/starknet/kad/{}/1.0.0", chain_id.as_str()) } +pub type BehaviourWithRelayTransport = (Behaviour, relay::client::Transport); + pub struct Behaviour { cfg: Config, peers: PeerSet, @@ -58,11 +62,11 @@ pub struct Inner { identify: identify::Behaviour, kademlia: kad::Behaviour, gossipsub: gossipsub::Behaviour, - headers_sync: p2p_stream::Behaviour, - classes_sync: p2p_stream::Behaviour, - state_diffs_sync: p2p_stream::Behaviour, - transactions_sync: p2p_stream::Behaviour, - events_sync: p2p_stream::Behaviour, + header_sync: p2p_stream::Behaviour, + class_sync: p2p_stream::Behaviour, + state_diff_sync: p2p_stream::Behaviour, + transaction_sync: p2p_stream::Behaviour, + event_sync: p2p_stream::Behaviour, } impl NetworkBehaviour for Behaviour { @@ -432,98 +436,8 @@ impl NetworkBehaviour for Behaviour { } impl Behaviour { - pub fn new( - identity: &identity::Keypair, - chain_id: ChainId, - swarm: crate::Client, - cfg: Config, - ) -> (Self, relay::client::Transport) { - const PROVIDER_PUBLICATION_INTERVAL: Duration = Duration::from_secs(600); - - let mut kademlia_config = kad::Config::default(); - kademlia_config.set_record_ttl(Some(Duration::from_secs(0))); - kademlia_config.set_provider_record_ttl(Some(PROVIDER_PUBLICATION_INTERVAL * 3)); - kademlia_config.set_provider_publication_interval(Some(PROVIDER_PUBLICATION_INTERVAL)); - // This makes sure that the DHT we're implementing is incompatible with the - // "default" IPFS DHT from libp2p. - if cfg.kad_names.is_empty() { - kademlia_config.set_protocol_names(vec![StreamProtocol::try_from_owned( - kademlia_protocol_name(chain_id), - ) - .unwrap()]); - } else { - kademlia_config.set_protocol_names( - cfg.kad_names - .iter() - .cloned() - .map(StreamProtocol::try_from_owned) - .collect::, _>>() - .expect("valid protocol names"), - ); - } - - let peer_id = identity.public().to_peer_id(); - - let kademlia = - kad::Behaviour::with_config(peer_id, MemoryStore::new(peer_id), kademlia_config); - - // FIXME: find out how we should derive message id - let message_id_fn = |message: &gossipsub::Message| { - let mut s = DefaultHasher::new(); - message.data.hash(&mut s); - MessageId::from(s.finish().to_string()) - }; - let gossipsub_config = libp2p::gossipsub::ConfigBuilder::default() - .message_id_fn(message_id_fn) - .build() - .expect("valid gossipsub config"); - - let gossipsub = gossipsub::Behaviour::new( - MessageAuthenticity::Signed(identity.clone()), - gossipsub_config, - ) - .expect("valid gossipsub params"); - - let p2p_stream_cfg = p2p_stream::Config::default() - .with_request_timeout(cfg.stream_timeout) - .with_max_concurrent_streams(cfg.max_concurrent_streams); - let headers_sync = request_response_behavior::(p2p_stream_cfg); - let classes_sync = request_response_behavior::(p2p_stream_cfg); - let state_diffs_sync = request_response_behavior::(p2p_stream_cfg); - let transactions_sync = request_response_behavior::(p2p_stream_cfg); - let events_sync = request_response_behavior::(p2p_stream_cfg); - - let (relay_transport, relay) = relay::client::new(peer_id); - - ( - Self { - peers: PeerSet::new(cfg.eviction_timeout), - cfg, - swarm, - secret: Secret::new(identity), - inner: Inner { - relay, - autonat: autonat::Behaviour::new(peer_id, Default::default()), - dcutr: dcutr::Behaviour::new(peer_id), - ping: ping::Behaviour::new(ping::Config::new()), - identify: identify::Behaviour::new( - identify::Config::new( - identify::PROTOCOL_NAME.to_string(), - identity.public(), - ) - .with_agent_version(format!("pathfinder/{}", env!("CARGO_PKG_VERSION"))), - ), - kademlia, - gossipsub, - headers_sync, - classes_sync, - state_diffs_sync, - transactions_sync, - events_sync, - }, - }, - relay_transport, - ) + pub fn builder(identity: identity::Keypair, chain_id: ChainId, cfg: Config) -> Builder { + Builder::new(identity, chain_id, cfg) } pub fn provide_capability(&mut self, capability: &str) -> anyhow::Result<()> { @@ -811,23 +725,23 @@ impl Behaviour { } pub fn headers_sync_mut(&mut self) -> &mut p2p_stream::Behaviour { - &mut self.inner.headers_sync + &mut self.inner.header_sync } pub fn classes_sync_mut(&mut self) -> &mut p2p_stream::Behaviour { - &mut self.inner.classes_sync + &mut self.inner.class_sync } pub fn state_diffs_sync_mut(&mut self) -> &mut p2p_stream::Behaviour { - &mut self.inner.state_diffs_sync + &mut self.inner.state_diff_sync } pub fn transactions_sync_mut(&mut self) -> &mut p2p_stream::Behaviour { - &mut self.inner.transactions_sync + &mut self.inner.transaction_sync } pub fn events_sync_mut(&mut self) -> &mut p2p_stream::Behaviour { - &mut self.inner.events_sync + &mut self.inner.event_sync } pub fn peers(&self) -> impl Iterator { @@ -856,14 +770,6 @@ impl Behaviour { } } -fn request_response_behavior(cfg: p2p_stream::Config) -> p2p_stream::Behaviour -where - C: Default + p2p_stream::Codec + Clone + Send, - C::Protocol: Default, -{ - p2p_stream::Behaviour::new(std::iter::once(C::Protocol::default()), cfg) -} - #[allow(dead_code)] #[derive(Debug)] pub enum Event { diff --git a/crates/p2p/src/behaviour/builder.rs b/crates/p2p/src/behaviour/builder.rs new file mode 100644 index 0000000000..92e0149d4e --- /dev/null +++ b/crates/p2p/src/behaviour/builder.rs @@ -0,0 +1,187 @@ +use std::hash::{DefaultHasher, Hash, Hasher}; +use std::time::Duration; + +use libp2p::gossipsub::{MessageAuthenticity, MessageId}; +use libp2p::kad::store::MemoryStore; +use libp2p::{autonat, dcutr, gossipsub, identify, identity, kad, ping, relay, StreamProtocol}; +use pathfinder_common::ChainId; + +use super::{Behaviour, BehaviourWithRelayTransport}; +use crate::behaviour::Inner; +use crate::peers::PeerSet; +use crate::secret::Secret; +use crate::sync::codec; +use crate::{kademlia_protocol_name, Config}; + +pub struct Builder { + identity: identity::Keypair, + chain_id: ChainId, + cfg: Config, + header_sync: Option>, + class_sync: Option>, + state_diff_sync: Option>, + transaction_sync: Option>, + event_sync: Option>, +} + +impl Builder { + pub fn new(identity: identity::Keypair, chain_id: ChainId, cfg: Config) -> Self { + Self { + identity, + chain_id, + cfg, + header_sync: None, + class_sync: None, + state_diff_sync: None, + transaction_sync: None, + event_sync: None, + } + } + + #[allow(unused)] + pub fn header_sync_behaviour( + mut self, + behaviour: p2p_stream::Behaviour, + ) -> Self { + self.header_sync = Some(behaviour); + self + } + + #[allow(unused)] + pub fn class_sync_behaviour( + mut self, + behaviour: p2p_stream::Behaviour, + ) -> Self { + self.class_sync = Some(behaviour); + self + } + + #[allow(unused)] + pub fn state_diff_sync_behaviour( + mut self, + behaviour: p2p_stream::Behaviour, + ) -> Self { + self.state_diff_sync = Some(behaviour); + self + } + + #[allow(unused)] + pub fn transaction_sync_behaviour( + mut self, + behaviour: p2p_stream::Behaviour, + ) -> Self { + self.transaction_sync = Some(behaviour); + self + } + + #[allow(unused)] + pub fn event_sync_behaviour(mut self, behaviour: p2p_stream::Behaviour) -> Self { + self.event_sync = Some(behaviour); + self + } + + pub fn build(self, client: crate::Client) -> BehaviourWithRelayTransport { + let Self { + identity, + chain_id, + cfg, + header_sync, + class_sync, + state_diff_sync, + transaction_sync, + event_sync, + } = self; + + const PROVIDER_PUBLICATION_INTERVAL: Duration = Duration::from_secs(600); + + let mut kademlia_config = kad::Config::default(); + kademlia_config.set_record_ttl(Some(Duration::from_secs(0))); + kademlia_config.set_provider_record_ttl(Some(PROVIDER_PUBLICATION_INTERVAL * 3)); + kademlia_config.set_provider_publication_interval(Some(PROVIDER_PUBLICATION_INTERVAL)); + // This makes sure that the DHT we're implementing is incompatible with the + // "default" IPFS DHT from libp2p. + if cfg.kad_names.is_empty() { + kademlia_config.set_protocol_names(vec![StreamProtocol::try_from_owned( + kademlia_protocol_name(chain_id), + ) + .unwrap()]); + } else { + kademlia_config.set_protocol_names( + cfg.kad_names + .iter() + .cloned() + .map(StreamProtocol::try_from_owned) + .collect::, _>>() + .expect("valid protocol names"), + ); + } + + let peer_id = identity.public().to_peer_id(); + let secret = Secret::new(&identity); + let public_key = identity.public(); + + let kademlia = + kad::Behaviour::with_config(peer_id, MemoryStore::new(peer_id), kademlia_config); + + // FIXME: find out how we should derive message id + let message_id_fn = |message: &gossipsub::Message| { + let mut s = DefaultHasher::new(); + message.data.hash(&mut s); + MessageId::from(s.finish().to_string()) + }; + let gossipsub_config = libp2p::gossipsub::ConfigBuilder::default() + .message_id_fn(message_id_fn) + .build() + .expect("valid gossipsub config"); + let gossipsub = + gossipsub::Behaviour::new(MessageAuthenticity::Signed(identity), gossipsub_config) + .expect("valid gossipsub params"); + + let (relay_transport, relay) = relay::client::new(peer_id); + + let p2p_stream_cfg = p2p_stream::Config::default() + .request_timeout(cfg.stream_timeout) + .max_concurrent_streams(cfg.max_concurrent_streams); + + let header_sync = header_sync + .unwrap_or_else(|| p2p_stream::Behaviour::::new(p2p_stream_cfg)); + let class_sync = class_sync + .unwrap_or_else(|| p2p_stream::Behaviour::::new(p2p_stream_cfg)); + let state_diff_sync = state_diff_sync + .unwrap_or_else(|| p2p_stream::Behaviour::::new(p2p_stream_cfg)); + let transaction_sync = transaction_sync + .unwrap_or_else(|| p2p_stream::Behaviour::::new(p2p_stream_cfg)); + let event_sync = event_sync + .unwrap_or_else(|| p2p_stream::Behaviour::::new(p2p_stream_cfg)); + + ( + Behaviour { + peers: PeerSet::new(cfg.eviction_timeout), + cfg, + swarm: client, + secret, + inner: Inner { + relay, + autonat: autonat::Behaviour::new(peer_id, Default::default()), + dcutr: dcutr::Behaviour::new(peer_id), + ping: ping::Behaviour::new(ping::Config::new()), + identify: identify::Behaviour::new( + identify::Config::new(identify::PROTOCOL_NAME.to_string(), public_key) + .with_agent_version(format!( + "pathfinder/{}", + env!("CARGO_PKG_VERSION") + )), + ), + kademlia, + gossipsub, + header_sync, + class_sync, + state_diff_sync, + transaction_sync, + event_sync, + }, + }, + relay_transport, + ) + } +} diff --git a/crates/p2p/src/builder.rs b/crates/p2p/src/builder.rs new file mode 100644 index 0000000000..73f31c0e61 --- /dev/null +++ b/crates/p2p/src/builder.rs @@ -0,0 +1,71 @@ +use std::time::Duration; + +use libp2p::identity::Keypair; +use libp2p::{swarm, Swarm}; +use pathfinder_common::ChainId; +use tokio::sync::mpsc; + +use crate::behaviour::{self, Behaviour}; +use crate::client::peer_aware::Client; +use crate::main_loop::MainLoop; +use crate::{transport, Config, EventReceiver}; + +pub struct Builder { + keypair: Keypair, + cfg: Config, + chain_id: ChainId, + behaviour_builder: Option, +} + +impl Builder { + pub fn new(keypair: Keypair, cfg: Config, chain_id: ChainId) -> Self { + Self { + keypair, + cfg, + chain_id, + behaviour_builder: None, + } + } +} + +impl Builder { + #[allow(unused)] + pub fn behaviour_builder(mut self, behaviour_builder: behaviour::Builder) -> Self { + self.behaviour_builder = Some(behaviour_builder); + self + } + + pub fn build(self) -> (Client, EventReceiver, MainLoop) { + let Self { + keypair, + cfg, + chain_id, + behaviour_builder, + } = self; + + let local_peer_id = keypair.public().to_peer_id(); + + let (command_sender, command_receiver) = mpsc::channel(1); + let client = Client::new(command_sender, local_peer_id); + + let (behaviour, relay_transport) = behaviour_builder + .unwrap_or_else(|| Behaviour::builder(keypair.clone(), chain_id, cfg.clone())) + .build(client.clone()); + + let swarm = Swarm::new( + transport::create(&keypair, relay_transport), + behaviour, + local_peer_id, + swarm::Config::with_tokio_executor() + .with_idle_connection_timeout(Duration::from_secs(3600 * 365)), // A YEAR + ); + + let (event_sender, event_receiver) = mpsc::channel(1); + + ( + client, + event_receiver, + MainLoop::new(swarm, command_receiver, event_sender, cfg, chain_id), + ) + } +} diff --git a/crates/p2p/src/lib.rs b/crates/p2p/src/lib.rs index a3228544e6..443d80048f 100644 --- a/crates/p2p/src/lib.rs +++ b/crates/p2p/src/lib.rs @@ -7,7 +7,8 @@ use ipnet::IpNet; use libp2p::gossipsub::IdentTopic; use libp2p::identity::Keypair; use libp2p::kad::RecordKey; -use libp2p::{swarm, Multiaddr, PeerId, Swarm}; +use libp2p::{Multiaddr, PeerId}; +use main_loop::MainLoop; use p2p_proto::class::{ClassesRequest, ClassesResponse}; use p2p_proto::event::{EventsRequest, EventsResponse}; use p2p_proto::header::{BlockHeadersRequest, BlockHeadersResponse, NewBlock}; @@ -18,6 +19,7 @@ use peers::Peer; use tokio::sync::{mpsc, oneshot}; mod behaviour; +mod builder; pub mod client; mod main_loop; mod peer_data; @@ -31,36 +33,14 @@ mod tests; mod transport; pub use behaviour::kademlia_protocol_name; +use builder::Builder; use client::peer_aware::Client; pub use libp2p; -use main_loop::MainLoop; pub use peer_data::PeerData; pub use sync::protocol::PROTOCOLS; pub fn new(keypair: Keypair, cfg: Config, chain_id: ChainId) -> (Client, EventReceiver, MainLoop) { - let local_peer_id = keypair.public().to_peer_id(); - - let (command_sender, command_receiver) = mpsc::channel(1); - let client = Client::new(command_sender, local_peer_id); - - let (behaviour, relay_transport) = - behaviour::Behaviour::new(&keypair, chain_id, client.clone(), cfg.clone()); - - let swarm = Swarm::new( - transport::create(&keypair, relay_transport), - behaviour, - local_peer_id, - swarm::Config::with_tokio_executor() - .with_idle_connection_timeout(Duration::from_secs(3600 * 365)), // A YEAR - ); - - let (event_sender, event_receiver) = mpsc::channel(1); - - ( - client, - event_receiver, - MainLoop::new(swarm, command_receiver, event_sender, cfg, chain_id), - ) + Builder::new(keypair, cfg, chain_id).build() } /// P2P limitations. diff --git a/crates/p2p/src/sync.rs b/crates/p2p/src/sync.rs index d5081d5f1f..81e7277ac3 100644 --- a/crates/p2p/src/sync.rs +++ b/crates/p2p/src/sync.rs @@ -35,8 +35,10 @@ pub mod protocol { pub(crate) mod codec { use std::marker::PhantomData; + use std::sync::{Arc, Mutex}; use async_trait::async_trait; + use futures::future::BoxFuture; use futures::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; use p2p_proto::{class, event, header, proto, state, transaction, ToProtobuf, TryFromProtobuf}; use p2p_stream::Codec; @@ -91,20 +93,45 @@ pub(crate) mod codec { ONE_MIB, >; - #[derive(Clone, Debug)] - pub struct SyncCodec( + #[derive(Clone)] + pub struct ProdCodec( PhantomData<(Protocol, Req, Resp, ProstReq, ProstResp)>, ); - impl Default for SyncCodec { + impl Default for ProdCodec { fn default() -> Self { Self(Default::default()) } } + /// An enum to prevent _generic parameter explosion_ in the outer + /// behaviour. + /// + /// [`SyncCodec::ForTest`] falls back to [`SyncCodec::Prod`] unless the + /// caller explicitly sets a read/write factory. + #[derive(Clone)] + pub enum SyncCodec { + Prod(ProdCodec), + #[cfg(test)] + ForTest(TestCodec), + } + + impl Default for SyncCodec { + fn default() -> Self { + Self::Prod(Default::default()) + } + } + + #[cfg(test)] + impl SyncCodec { + pub fn for_test() -> Self { + Self::ForTest(Default::default()) + } + } + #[async_trait] impl Codec - for SyncCodec + for ProdCodec where Protocol: AsRef + Send + Clone, Req: TryFromProtobuf + ToProtobuf + Send, @@ -193,4 +220,272 @@ pub(crate) mod codec { Ok(()) } } + + #[async_trait] + impl Codec + for SyncCodec + where + Protocol: AsRef + Send + Sync + Clone, + Req: TryFromProtobuf + ToProtobuf + Send, + Resp: TryFromProtobuf + ToProtobuf + Send, + ProstReq: prost::Message + Default, + ProstResp: prost::Message + Default, + { + type Protocol = Protocol; + type Request = Req; + type Response = Resp; + + async fn read_request( + &mut self, + protocol: &Self::Protocol, + io: &mut T, + ) -> std::io::Result + where + T: AsyncRead + Unpin + Send, + { + match self { + Self::Prod(codec) => codec.read_request(protocol, io).await, + #[cfg(test)] + Self::ForTest(codec) => codec.read_request(protocol, io).await, + } + } + + async fn read_response( + &mut self, + protocol: &Self::Protocol, + io: &mut T, + ) -> std::io::Result + where + T: AsyncRead + Unpin + Send, + { + match self { + Self::Prod(codec) => codec.read_response(protocol, io).await, + #[cfg(test)] + Self::ForTest(codec) => codec.read_response(protocol, io).await, + } + } + + async fn write_request( + &mut self, + protocol: &Self::Protocol, + io: &mut T, + request: Self::Request, + ) -> std::io::Result<()> + where + T: AsyncWrite + Unpin + Send, + { + match self { + Self::Prod(codec) => codec.write_request(protocol, io, request).await, + #[cfg(test)] + Self::ForTest(codec) => codec.write_request(protocol, io, request).await, + } + } + + async fn write_response( + &mut self, + protocol: &Self::Protocol, + io: &mut T, + response: Self::Response, + ) -> std::io::Result<()> + where + T: AsyncWrite + Unpin + Send, + { + match self { + Self::Prod(codec) => codec.write_response(protocol, io, response).await, + #[cfg(test)] + Self::ForTest(codec) => codec.write_response(protocol, io, response).await, + } + } + } + + pub type TypeErasedReadFun = Box< + dyn FnMut(&mut TypeErasedAsyncRead<'_>) -> BoxFuture<'static, std::io::Result> + Send, + >; + pub type TypeErasedWriteFun = Box< + dyn FnMut(&mut TypeErasedAsyncWrite<'_>, T) -> BoxFuture<'static, std::io::Result<()>> + + Send, + >; + pub type TypeErasedReadFactory = Box TypeErasedReadFun + Send>; + pub type TypeErasedWriteFactory = Box TypeErasedWriteFun + Send>; + + #[allow(unused)] + pub struct TypeErasedAsyncRead<'a>(Box); + #[allow(unused)] + pub struct TypeErasedAsyncWrite<'a>(Box); + + impl<'a, A> From for TypeErasedAsyncRead<'a> + where + A: AsyncRead + Unpin + Send + 'a, + { + fn from(x: A) -> Self { + Self(Box::new(x)) + } + } + + impl<'a, A> From for TypeErasedAsyncWrite<'a> + where + A: AsyncWrite + Unpin + Send + 'a, + { + fn from(x: A) -> Self { + Self(Box::new(x)) + } + } + + /// Falls back to [`SyncCodec::Prod`] unless the caller expliticly sets a + /// read/write factory. + #[derive(Clone)] + pub struct TestCodec { + read_request_factory: Option>>>, + read_response_factory: Option>>>, + write_request_factory: Option>>>, + write_response_factory: Option>>>, + _x: PhantomData<(Protocol, ProstReq, ProstResp)>, + } + + impl Default for TestCodec { + fn default() -> Self { + Self { + read_request_factory: None, + read_response_factory: None, + write_request_factory: None, + write_response_factory: None, + _x: Default::default(), + } + } + } + + #[cfg(test)] + #[allow(unused)] + impl + SyncCodec + { + pub fn set_read_request_factory(mut self, factory: TypeErasedReadFactory) -> Self { + if let Self::ForTest(codec) = &mut self { + codec.read_request_factory = Some(Arc::new(Mutex::new(factory))); + } + self + } + + pub fn set_read_response_factory(mut self, factory: TypeErasedReadFactory) -> Self { + if let Self::ForTest(codec) = &mut self { + codec.read_response_factory = Some(Arc::new(Mutex::new(factory))); + } + self + } + + pub fn set_write_request_factory(mut self, factory: TypeErasedWriteFactory) -> Self { + if let Self::ForTest(codec) = &mut self { + codec.write_request_factory = Some(Arc::new(Mutex::new(factory))); + } + self + } + + pub fn set_write_response_factory(mut self, factory: TypeErasedWriteFactory) -> Self { + if let Self::ForTest(codec) = &mut self { + codec.write_response_factory = Some(Arc::new(Mutex::new(factory))); + } + self + } + } + + #[async_trait] + impl Codec + for TestCodec + where + P: AsRef + Send + Sync + Clone, + Req: TryFromProtobuf + ToProtobuf + Send, + Resp: TryFromProtobuf + ToProtobuf + Send, + ProstReq: prost::Message + Default, + ProstResp: prost::Message + Default, + { + type Protocol = P; + type Request = Req; + type Response = Resp; + + async fn read_request( + &mut self, + protocol: &Self::Protocol, + io: &mut T, + ) -> std::io::Result + where + T: AsyncRead + Unpin + Send, + { + match self.read_request_factory.as_ref() { + Some(factory) => { + let mut async_fn = factory.lock().unwrap()(); + async_fn(&mut io.into()).await + } + None => { + ProdCodec::::default() + .read_request(protocol, io) + .await + } + } + } + + async fn read_response( + &mut self, + protocol: &Self::Protocol, + io: &mut T, + ) -> std::io::Result + where + T: AsyncRead + Unpin + Send, + { + match self.read_response_factory.as_ref() { + Some(factory) => { + let mut async_fn = factory.lock().unwrap()(); + async_fn(&mut io.into()).await + } + None => { + ProdCodec::::default() + .read_response(protocol, io) + .await + } + } + } + + async fn write_request( + &mut self, + protocol: &Self::Protocol, + io: &mut T, + request: Self::Request, + ) -> std::io::Result<()> + where + T: AsyncWrite + Unpin + Send, + { + match self.write_request_factory.as_ref() { + Some(factory) => { + let mut async_fn = factory.lock().unwrap()(); + async_fn(&mut io.into(), request).await + } + None => { + ProdCodec::::default() + .write_request(protocol, io, request) + .await + } + } + } + + async fn write_response( + &mut self, + protocol: &Self::Protocol, + io: &mut T, + response: Self::Response, + ) -> std::io::Result<()> + where + T: AsyncWrite + Unpin + Send, + { + match self.write_response_factory.as_ref() { + Some(factory) => { + let mut async_fn = factory.lock().unwrap()(); + async_fn(&mut io.into(), response).await + } + None => { + ProdCodec::::default() + .write_response(protocol, io, response) + .await + } + } + } + } } diff --git a/crates/p2p/src/tests.rs b/crates/p2p/src/tests.rs index 21ae635309..8460c5d3d5 100644 --- a/crates/p2p/src/tests.rs +++ b/crates/p2p/src/tests.rs @@ -5,7 +5,7 @@ use std::time::Duration; use anyhow::{Context, Result}; use fake::{Fake, Faker}; -use futures::{SinkExt, StreamExt}; +use futures::{FutureExt, SinkExt, StreamExt}; use libp2p::identity::Keypair; use libp2p::multiaddr::Protocol; use libp2p::{Multiaddr, PeerId}; @@ -19,7 +19,8 @@ use rstest::rstest; use tokio::task::JoinHandle; use crate::peers::Peer; -use crate::{BootstrapConfig, Config, Event, EventReceiver, RateLimit, TestEvent}; +use crate::sync::codec; +use crate::{BootstrapConfig, Builder, Config, Event, EventReceiver, RateLimit, TestEvent}; #[allow(dead_code)] #[derive(Debug)] @@ -31,12 +32,67 @@ struct TestPeer { pub main_loop_jh: JoinHandle<()>, } -impl TestPeer { - #[must_use] - pub fn new(cfg: Config, keypair: Keypair) -> Self { +#[derive(Default)] +struct TestPeerBuilder { + // cfg: Option, + // keypair: Option, + p2p_builder: Option, +} + +impl Config { + pub fn for_test() -> Self { + Self { + direct_connection_timeout: Duration::from_secs(0), + relay_connection_timeout: Duration::from_secs(0), + max_inbound_direct_peers: 10, + max_inbound_relayed_peers: 10, + max_outbound_peers: 10, + low_watermark: 10, + ip_whitelist: vec!["::/0".parse().unwrap(), "0.0.0.0/0".parse().unwrap()], + bootstrap: Default::default(), + eviction_timeout: Duration::from_secs(15 * 60), + inbound_connections_rate_limit: RateLimit { + max: 1000, + interval: Duration::from_secs(1), + }, + kad_names: Default::default(), + stream_timeout: Duration::from_secs(10), + max_concurrent_streams: 100, + } + } +} + +impl TestPeerBuilder { + // pub fn config(mut self, cfg: Config) -> Self { + // self.cfg = Some(cfg); + // self + // } + + // pub fn keypair(mut self, keypair: Keypair) -> Self { + // self.keypair = Some(keypair); + // self + // } + + pub fn p2p_builder(mut self, p2p_builder: Builder) -> Self { + self.p2p_builder = Some(p2p_builder); + self + } + + pub fn build(self, keypair: Keypair, cfg: Config) -> TestPeer { + let Self { + // cfg, + // keypair, + p2p_builder, + } = self; + + // let keypair = keypair.unwrap_or_else(Keypair::generate_ed25519); + // let cfg = cfg.unwrap_or_else(Config::for_test); + let peer_id = keypair.public().to_peer_id(); - let (client, mut event_receiver, main_loop) = - crate::new(keypair.clone(), cfg, ChainId::SEPOLIA_TESTNET); + + let (client, mut event_receiver, main_loop) = p2p_builder + .unwrap_or_else(|| crate::Builder::new(keypair.clone(), cfg, ChainId::SEPOLIA_TESTNET)) + .build(); // Ensure that the channel keeps being polled to move the main loop forward. // Store the polled events into a buffered channel instead. @@ -48,7 +104,7 @@ impl TestPeer { }); let main_loop_jh = tokio::spawn(main_loop.run()); - Self { + TestPeer { keypair, peer_id, client, @@ -56,6 +112,23 @@ impl TestPeer { main_loop_jh, } } +} + +impl TestPeer { + pub fn builder() -> TestPeerBuilder { + Default::default() + } + + /// Create a new peer with a random keypair + #[must_use] + pub fn new(cfg: Config) -> Self { + Self::builder().build(Keypair::generate_ed25519(), cfg) + } + + #[must_use] + pub fn with_keypair(keypair: Keypair, cfg: Config) -> Self { + Self::builder().build(keypair, cfg) + } /// Start listening on a specified address pub async fn start_listening_on(&mut self, addr: Multiaddr) -> Result { @@ -89,28 +162,9 @@ impl TestPeer { } impl Default for TestPeer { + /// Create a new peer with a random keypair and default test config fn default() -> Self { - Self::new( - Config { - direct_connection_timeout: Duration::from_secs(0), - relay_connection_timeout: Duration::from_secs(0), - max_inbound_direct_peers: 10, - max_inbound_relayed_peers: 10, - max_outbound_peers: 10, - low_watermark: 10, - ip_whitelist: vec!["::/0".parse().unwrap(), "0.0.0.0/0".parse().unwrap()], - bootstrap: Default::default(), - eviction_timeout: Duration::from_secs(15 * 60), - inbound_connections_rate_limit: RateLimit { - max: 1000, - interval: Duration::from_secs(1), - }, - kad_names: Default::default(), - stream_timeout: Duration::from_secs(10), - max_concurrent_streams: 100, - }, - Keypair::generate_ed25519(), - ) + Self::builder().build(Keypair::generate_ed25519(), Config::for_test()) } } @@ -272,9 +326,9 @@ async fn periodic_bootstrap() { stream_timeout: Duration::from_secs(10), max_concurrent_streams: 100, }; - let mut boot = TestPeer::new(cfg.clone(), Keypair::generate_ed25519()); - let mut peer1 = TestPeer::new(cfg.clone(), Keypair::generate_ed25519()); - let mut peer2 = TestPeer::new(cfg.clone(), Keypair::generate_ed25519()); + let mut boot = TestPeer::new(cfg.clone()); + let mut peer1 = TestPeer::new(cfg.clone()); + let mut peer2 = TestPeer::new(cfg.clone()); let mut boot_addr = boot.start_listening().await.unwrap(); boot_addr.push(Protocol::P2p(boot.peer_id)); @@ -364,7 +418,7 @@ async fn periodic_bootstrap() { // Start a new peer and connect to the other peers, immediately reaching the low // watermark. - let mut peer3 = TestPeer::new(cfg, Keypair::generate_ed25519()); + let mut peer3 = TestPeer::new(cfg); peer3 .client @@ -425,8 +479,8 @@ async fn reconnect_too_quickly() { max_concurrent_streams: 100, }; - let mut peer1 = TestPeer::new(cfg.clone(), Keypair::generate_ed25519()); - let mut peer2 = TestPeer::new(cfg, Keypair::generate_ed25519()); + let mut peer1 = TestPeer::new(cfg.clone()); + let mut peer2 = TestPeer::new(cfg); let addr2 = peer2.start_listening().await.unwrap(); tracing::info!(%peer2.peer_id, %addr2); @@ -530,9 +584,9 @@ async fn duplicate_connection() { max_concurrent_streams: 100, }; let keypair = Keypair::generate_ed25519(); - let mut peer1 = TestPeer::new(cfg.clone(), keypair.clone()); - let mut peer1_copy = TestPeer::new(cfg.clone(), keypair); - let mut peer2 = TestPeer::new(cfg, Keypair::generate_ed25519()); + let mut peer1 = TestPeer::with_keypair(keypair.clone(), cfg.clone()); + let mut peer1_copy = TestPeer::with_keypair(keypair.clone(), cfg.clone()); + let mut peer2 = TestPeer::new(cfg); let addr2 = peer2.start_listening().await.unwrap(); tracing::info!(%peer2.peer_id, %addr2); @@ -619,13 +673,13 @@ async fn outbound_peer_eviction() { max_concurrent_streams: 100, }; - let mut peer = TestPeer::new(cfg.clone(), Keypair::generate_ed25519()); - let mut outbound1 = TestPeer::new(cfg.clone(), Keypair::generate_ed25519()); - let mut outbound2 = TestPeer::new(cfg.clone(), Keypair::generate_ed25519()); - let mut outbound3 = TestPeer::new(cfg.clone(), Keypair::generate_ed25519()); - let mut outbound4 = TestPeer::new(cfg.clone(), Keypair::generate_ed25519()); - let inbound1 = TestPeer::new(cfg.clone(), Keypair::generate_ed25519()); - let inbound2 = TestPeer::new(cfg, Keypair::generate_ed25519()); + let mut peer = TestPeer::new(cfg.clone()); + let mut outbound1 = TestPeer::new(cfg.clone()); + let mut outbound2 = TestPeer::new(cfg.clone()); + let mut outbound3 = TestPeer::new(cfg.clone()); + let mut outbound4 = TestPeer::new(cfg.clone()); + let inbound1 = TestPeer::new(cfg.clone()); + let inbound2 = TestPeer::new(cfg); let peer_addr = peer.start_listening().await.unwrap(); tracing::info!(%peer.peer_id, %peer_addr); @@ -751,11 +805,11 @@ async fn inbound_peer_eviction() { max_concurrent_streams: 100, }; - let mut peer = TestPeer::new(cfg.clone(), Keypair::generate_ed25519()); + let mut peer = TestPeer::new(cfg.clone()); let inbound_peers = (0..26) - .map(|_| TestPeer::new(cfg.clone(), Keypair::generate_ed25519())) + .map(|_| TestPeer::new(cfg.clone())) .collect::>(); - let mut outbound1 = TestPeer::new(cfg, Keypair::generate_ed25519()); + let mut outbound1 = TestPeer::new(cfg); let peer_addr = peer.start_listening().await.unwrap(); tracing::info!(%peer.peer_id, %peer_addr); @@ -839,9 +893,9 @@ async fn evicted_peer_reconnection() { max_concurrent_streams: 100, }; - let mut peer1 = TestPeer::new(cfg.clone(), Keypair::generate_ed25519()); - let mut peer2 = TestPeer::new(cfg.clone(), Keypair::generate_ed25519()); - let mut peer3 = TestPeer::new(cfg, Keypair::generate_ed25519()); + let mut peer1 = TestPeer::new(cfg.clone()); + let mut peer2 = TestPeer::new(cfg.clone()); + let mut peer3 = TestPeer::new(cfg); let addr1 = peer1.start_listening().await.unwrap(); tracing::info!(%peer1.peer_id, %addr1); @@ -932,8 +986,8 @@ async fn ip_whitelist() { stream_timeout: Duration::from_secs(10), max_concurrent_streams: 100, }; - let mut peer1 = TestPeer::new(cfg.clone(), Keypair::generate_ed25519()); - let peer2 = TestPeer::new(cfg.clone(), Keypair::generate_ed25519()); + let mut peer1 = TestPeer::new(cfg.clone()); + let peer2 = TestPeer::new(cfg.clone()); let addr1 = peer1.start_listening().await.unwrap(); tracing::info!(%peer1.peer_id, %addr1); @@ -968,7 +1022,7 @@ async fn ip_whitelist() { stream_timeout: Duration::from_secs(10), max_concurrent_streams: 100, }; - let mut peer3 = TestPeer::new(cfg, Keypair::generate_ed25519()); + let mut peer3 = TestPeer::new(cfg); let addr3 = peer3.start_listening().await.unwrap(); tracing::info!(%peer3.peer_id, %addr3); @@ -1006,10 +1060,10 @@ async fn rate_limit() { max_concurrent_streams: 100, }; - let mut peer1 = TestPeer::new(cfg.clone(), Keypair::generate_ed25519()); - let peer2 = TestPeer::new(cfg.clone(), Keypair::generate_ed25519()); - let peer3 = TestPeer::new(cfg.clone(), Keypair::generate_ed25519()); - let peer4 = TestPeer::new(cfg, Keypair::generate_ed25519()); + let mut peer1 = TestPeer::new(cfg.clone()); + let peer2 = TestPeer::new(cfg.clone()); + let peer3 = TestPeer::new(cfg.clone()); + let peer4 = TestPeer::new(cfg); let addr1 = peer1.start_listening().await.unwrap(); tracing::info!(%peer1.peer_id, %addr1); diff --git a/crates/p2p_stream/src/lib.rs b/crates/p2p_stream/src/lib.rs index 592b38c8e6..cd39f37b6b 100644 --- a/crates/p2p_stream/src/lib.rs +++ b/crates/p2p_stream/src/lib.rs @@ -252,14 +252,14 @@ impl Default for Config { impl Config { /// Sets the timeout for inbound and outbound requests. - pub fn with_request_timeout(mut self, v: Duration) -> Self { + pub fn request_timeout(mut self, v: Duration) -> Self { self.request_timeout = v; self } /// Sets the upper bound for the number of concurrent inbound + outbound /// streams. - pub fn with_max_concurrent_streams(mut self, num_streams: usize) -> Self { + pub fn max_concurrent_streams(mut self, num_streams: usize) -> Self { self.max_concurrent_streams = num_streams; self } @@ -296,13 +296,17 @@ impl Behaviour where TCodec: Codec + Default + Clone + Send + 'static, { - /// Creates a new `Behaviour` for the given protocols and configuration, - /// using [`Default`] to construct the codec. - pub fn new(protocols: I, cfg: Config) -> Self + /// Creates a new `Behaviour` for the given configuration, + /// using [`Default`] to construct the codec and the protocol. + pub fn new(cfg: Config) -> Self where - I: IntoIterator, + TCodec::Protocol: Default, { - Self::with_codec(TCodec::default(), protocols, cfg) + Self::with_codec_and_protocols( + TCodec::default(), + std::iter::once(TCodec::Protocol::default()), + cfg, + ) } } @@ -310,9 +314,18 @@ impl Behaviour where TCodec: Codec + Clone + Send + 'static, { + /// Creates a new `Behaviour` with a default protocol name for the given + /// codec and configuration. + pub fn with_codec(codec: TCodec, cfg: Config) -> Self + where + TCodec::Protocol: Default, + { + Self::with_codec_and_protocols(codec, std::iter::once(TCodec::Protocol::default()), cfg) + } + /// Creates a new `Behaviour` for the given /// protocols, codec and configuration. - pub fn with_codec(codec: TCodec, protocols: I, cfg: Config) -> Self + pub fn with_codec_and_protocols(codec: TCodec, protocols: I, cfg: Config) -> Self where I: IntoIterator, { diff --git a/crates/p2p_stream/tests/utils/mod.rs b/crates/p2p_stream/tests/utils/mod.rs index 77ada54efa..14efc25867 100644 --- a/crates/p2p_stream/tests/utils/mod.rs +++ b/crates/p2p_stream/tests/utils/mod.rs @@ -203,11 +203,15 @@ pub fn new_swarm_with_timeout( timeout: Duration, ) -> (PeerId, Swarm>) { let protocols = iter::once(StreamProtocol::new("/test/1")); - let cfg = p2p_stream::Config::default().with_request_timeout(timeout); + let cfg = p2p_stream::Config::default().request_timeout(timeout); // SwarmExt::new_ephemeral uses async::std let swarm = new_ephemeral_with_tokio_executor(|_| { - p2p_stream::Behaviour::::new(protocols, cfg) + p2p_stream::Behaviour::::with_codec_and_protocols( + TestCodec::default(), + protocols, + cfg, + ) }); let peed_id = *swarm.local_peer_id(); From 73417b1508648510fec2c3a0abc8266d5ff7c602 Mon Sep 17 00:00:00 2001 From: Krzysztof Lis Date: Fri, 13 Sep 2024 18:29:55 +0200 Subject: [PATCH 054/282] chore: move test codec into test utils --- crates/p2p/src/sync.rs | 204 ++---------------------------- crates/p2p/src/test_utils.rs | 1 + crates/p2p/src/test_utils/sync.rs | 199 +++++++++++++++++++++++++++++ crates/p2p/src/tests.rs | 19 +-- 4 files changed, 211 insertions(+), 212 deletions(-) create mode 100644 crates/p2p/src/test_utils/sync.rs diff --git a/crates/p2p/src/sync.rs b/crates/p2p/src/sync.rs index 81e7277ac3..ac6fff98d2 100644 --- a/crates/p2p/src/sync.rs +++ b/crates/p2p/src/sync.rs @@ -35,10 +35,8 @@ pub mod protocol { pub(crate) mod codec { use std::marker::PhantomData; - use std::sync::{Arc, Mutex}; use async_trait::async_trait; - use futures::future::BoxFuture; use futures::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; use p2p_proto::{class, event, header, proto, state, transaction, ToProtobuf, TryFromProtobuf}; use p2p_stream::Codec; @@ -113,7 +111,16 @@ pub(crate) mod codec { pub enum SyncCodec { Prod(ProdCodec), #[cfg(test)] - ForTest(TestCodec), + ForTest( + crate::test_utils::sync::TestCodec< + Protocol, + Req, + Resp, + ProstReq, + ProstResp, + RESPONSE_SIZE_LIMIT, + >, + ), } impl Default for SyncCodec { @@ -297,195 +304,4 @@ pub(crate) mod codec { } } } - - pub type TypeErasedReadFun = Box< - dyn FnMut(&mut TypeErasedAsyncRead<'_>) -> BoxFuture<'static, std::io::Result> + Send, - >; - pub type TypeErasedWriteFun = Box< - dyn FnMut(&mut TypeErasedAsyncWrite<'_>, T) -> BoxFuture<'static, std::io::Result<()>> - + Send, - >; - pub type TypeErasedReadFactory = Box TypeErasedReadFun + Send>; - pub type TypeErasedWriteFactory = Box TypeErasedWriteFun + Send>; - - #[allow(unused)] - pub struct TypeErasedAsyncRead<'a>(Box); - #[allow(unused)] - pub struct TypeErasedAsyncWrite<'a>(Box); - - impl<'a, A> From for TypeErasedAsyncRead<'a> - where - A: AsyncRead + Unpin + Send + 'a, - { - fn from(x: A) -> Self { - Self(Box::new(x)) - } - } - - impl<'a, A> From for TypeErasedAsyncWrite<'a> - where - A: AsyncWrite + Unpin + Send + 'a, - { - fn from(x: A) -> Self { - Self(Box::new(x)) - } - } - - /// Falls back to [`SyncCodec::Prod`] unless the caller expliticly sets a - /// read/write factory. - #[derive(Clone)] - pub struct TestCodec { - read_request_factory: Option>>>, - read_response_factory: Option>>>, - write_request_factory: Option>>>, - write_response_factory: Option>>>, - _x: PhantomData<(Protocol, ProstReq, ProstResp)>, - } - - impl Default for TestCodec { - fn default() -> Self { - Self { - read_request_factory: None, - read_response_factory: None, - write_request_factory: None, - write_response_factory: None, - _x: Default::default(), - } - } - } - - #[cfg(test)] - #[allow(unused)] - impl - SyncCodec - { - pub fn set_read_request_factory(mut self, factory: TypeErasedReadFactory) -> Self { - if let Self::ForTest(codec) = &mut self { - codec.read_request_factory = Some(Arc::new(Mutex::new(factory))); - } - self - } - - pub fn set_read_response_factory(mut self, factory: TypeErasedReadFactory) -> Self { - if let Self::ForTest(codec) = &mut self { - codec.read_response_factory = Some(Arc::new(Mutex::new(factory))); - } - self - } - - pub fn set_write_request_factory(mut self, factory: TypeErasedWriteFactory) -> Self { - if let Self::ForTest(codec) = &mut self { - codec.write_request_factory = Some(Arc::new(Mutex::new(factory))); - } - self - } - - pub fn set_write_response_factory(mut self, factory: TypeErasedWriteFactory) -> Self { - if let Self::ForTest(codec) = &mut self { - codec.write_response_factory = Some(Arc::new(Mutex::new(factory))); - } - self - } - } - - #[async_trait] - impl Codec - for TestCodec - where - P: AsRef + Send + Sync + Clone, - Req: TryFromProtobuf + ToProtobuf + Send, - Resp: TryFromProtobuf + ToProtobuf + Send, - ProstReq: prost::Message + Default, - ProstResp: prost::Message + Default, - { - type Protocol = P; - type Request = Req; - type Response = Resp; - - async fn read_request( - &mut self, - protocol: &Self::Protocol, - io: &mut T, - ) -> std::io::Result - where - T: AsyncRead + Unpin + Send, - { - match self.read_request_factory.as_ref() { - Some(factory) => { - let mut async_fn = factory.lock().unwrap()(); - async_fn(&mut io.into()).await - } - None => { - ProdCodec::::default() - .read_request(protocol, io) - .await - } - } - } - - async fn read_response( - &mut self, - protocol: &Self::Protocol, - io: &mut T, - ) -> std::io::Result - where - T: AsyncRead + Unpin + Send, - { - match self.read_response_factory.as_ref() { - Some(factory) => { - let mut async_fn = factory.lock().unwrap()(); - async_fn(&mut io.into()).await - } - None => { - ProdCodec::::default() - .read_response(protocol, io) - .await - } - } - } - - async fn write_request( - &mut self, - protocol: &Self::Protocol, - io: &mut T, - request: Self::Request, - ) -> std::io::Result<()> - where - T: AsyncWrite + Unpin + Send, - { - match self.write_request_factory.as_ref() { - Some(factory) => { - let mut async_fn = factory.lock().unwrap()(); - async_fn(&mut io.into(), request).await - } - None => { - ProdCodec::::default() - .write_request(protocol, io, request) - .await - } - } - } - - async fn write_response( - &mut self, - protocol: &Self::Protocol, - io: &mut T, - response: Self::Response, - ) -> std::io::Result<()> - where - T: AsyncWrite + Unpin + Send, - { - match self.write_response_factory.as_ref() { - Some(factory) => { - let mut async_fn = factory.lock().unwrap()(); - async_fn(&mut io.into(), response).await - } - None => { - ProdCodec::::default() - .write_response(protocol, io, response) - .await - } - } - } - } } diff --git a/crates/p2p/src/test_utils.rs b/crates/p2p/src/test_utils.rs index 8ccfb61121..cd357a3de4 100644 --- a/crates/p2p/src/test_utils.rs +++ b/crates/p2p/src/test_utils.rs @@ -1,2 +1,3 @@ pub mod main_loop; pub mod peer_aware; +pub mod sync; diff --git a/crates/p2p/src/test_utils/sync.rs b/crates/p2p/src/test_utils/sync.rs new file mode 100644 index 0000000000..794fc0b60e --- /dev/null +++ b/crates/p2p/src/test_utils/sync.rs @@ -0,0 +1,199 @@ +use std::marker::PhantomData; +use std::sync::{Arc, Mutex}; + +use async_trait::async_trait; +use futures::future::BoxFuture; +use futures::{AsyncRead, AsyncWrite}; +use p2p_proto::{ToProtobuf, TryFromProtobuf}; +use p2p_stream::Codec; + +use crate::sync::codec::{ProdCodec, SyncCodec}; + +pub type TypeErasedReadFun = + Box) -> BoxFuture<'static, std::io::Result> + Send>; +pub type TypeErasedWriteFun = Box< + dyn FnMut(&mut TypeErasedAsyncWrite<'_>, T) -> BoxFuture<'static, std::io::Result<()>> + Send, +>; +pub type TypeErasedReadFactory = Box TypeErasedReadFun + Send>; +pub type TypeErasedWriteFactory = Box TypeErasedWriteFun + Send>; + +#[allow(unused)] +pub struct TypeErasedAsyncRead<'a>(Box); +#[allow(unused)] +pub struct TypeErasedAsyncWrite<'a>(Box); + +impl<'a, A> From for TypeErasedAsyncRead<'a> +where + A: AsyncRead + Unpin + Send + 'a, +{ + fn from(x: A) -> Self { + Self(Box::new(x)) + } +} + +impl<'a, A> From for TypeErasedAsyncWrite<'a> +where + A: AsyncWrite + Unpin + Send + 'a, +{ + fn from(x: A) -> Self { + Self(Box::new(x)) + } +} + +/// Falls back to [`SyncCodec::Prod`] unless the caller expliticly sets a +/// read/write factory. +#[derive(Clone)] +pub struct TestCodec { + read_request_factory: Option>>>, + read_response_factory: Option>>>, + write_request_factory: Option>>>, + write_response_factory: Option>>>, + _x: PhantomData<(Protocol, ProstReq, ProstResp)>, +} + +impl Default for TestCodec { + fn default() -> Self { + Self { + read_request_factory: None, + read_response_factory: None, + write_request_factory: None, + write_response_factory: None, + _x: Default::default(), + } + } +} + +#[cfg(test)] +#[allow(unused)] +impl + SyncCodec +{ + pub fn set_read_request_factory(mut self, factory: TypeErasedReadFactory) -> Self { + if let Self::ForTest(codec) = &mut self { + codec.read_request_factory = Some(Arc::new(Mutex::new(factory))); + } + self + } + + pub fn set_read_response_factory(mut self, factory: TypeErasedReadFactory) -> Self { + if let Self::ForTest(codec) = &mut self { + codec.read_response_factory = Some(Arc::new(Mutex::new(factory))); + } + self + } + + pub fn set_write_request_factory(mut self, factory: TypeErasedWriteFactory) -> Self { + if let Self::ForTest(codec) = &mut self { + codec.write_request_factory = Some(Arc::new(Mutex::new(factory))); + } + self + } + + pub fn set_write_response_factory(mut self, factory: TypeErasedWriteFactory) -> Self { + if let Self::ForTest(codec) = &mut self { + codec.write_response_factory = Some(Arc::new(Mutex::new(factory))); + } + self + } +} + +#[async_trait] +impl Codec + for TestCodec +where + P: AsRef + Send + Sync + Clone, + Req: TryFromProtobuf + ToProtobuf + Send, + Resp: TryFromProtobuf + ToProtobuf + Send, + ProstReq: prost::Message + Default, + ProstResp: prost::Message + Default, +{ + type Protocol = P; + type Request = Req; + type Response = Resp; + + async fn read_request( + &mut self, + protocol: &Self::Protocol, + io: &mut T, + ) -> std::io::Result + where + T: AsyncRead + Unpin + Send, + { + match self.read_request_factory.as_ref() { + Some(factory) => { + let mut async_fn = factory.lock().unwrap()(); + async_fn(&mut io.into()).await + } + None => { + ProdCodec::::default() + .read_request(protocol, io) + .await + } + } + } + + async fn read_response( + &mut self, + protocol: &Self::Protocol, + io: &mut T, + ) -> std::io::Result + where + T: AsyncRead + Unpin + Send, + { + match self.read_response_factory.as_ref() { + Some(factory) => { + let mut async_fn = factory.lock().unwrap()(); + async_fn(&mut io.into()).await + } + None => { + ProdCodec::::default() + .read_response(protocol, io) + .await + } + } + } + + async fn write_request( + &mut self, + protocol: &Self::Protocol, + io: &mut T, + request: Self::Request, + ) -> std::io::Result<()> + where + T: AsyncWrite + Unpin + Send, + { + match self.write_request_factory.as_ref() { + Some(factory) => { + let mut async_fn = factory.lock().unwrap()(); + async_fn(&mut io.into(), request).await + } + None => { + ProdCodec::::default() + .write_request(protocol, io, request) + .await + } + } + } + + async fn write_response( + &mut self, + protocol: &Self::Protocol, + io: &mut T, + response: Self::Response, + ) -> std::io::Result<()> + where + T: AsyncWrite + Unpin + Send, + { + match self.write_response_factory.as_ref() { + Some(factory) => { + let mut async_fn = factory.lock().unwrap()(); + async_fn(&mut io.into(), response).await + } + None => { + ProdCodec::::default() + .write_response(protocol, io, response) + .await + } + } + } +} diff --git a/crates/p2p/src/tests.rs b/crates/p2p/src/tests.rs index 8460c5d3d5..8944f033d7 100644 --- a/crates/p2p/src/tests.rs +++ b/crates/p2p/src/tests.rs @@ -63,30 +63,13 @@ impl Config { } impl TestPeerBuilder { - // pub fn config(mut self, cfg: Config) -> Self { - // self.cfg = Some(cfg); - // self - // } - - // pub fn keypair(mut self, keypair: Keypair) -> Self { - // self.keypair = Some(keypair); - // self - // } - pub fn p2p_builder(mut self, p2p_builder: Builder) -> Self { self.p2p_builder = Some(p2p_builder); self } pub fn build(self, keypair: Keypair, cfg: Config) -> TestPeer { - let Self { - // cfg, - // keypair, - p2p_builder, - } = self; - - // let keypair = keypair.unwrap_or_else(Keypair::generate_ed25519); - // let cfg = cfg.unwrap_or_else(Config::for_test); + let Self { p2p_builder } = self; let peer_id = keypair.public().to_peer_id(); From 3d0ddd875130f25fc62278bc581de33c1653cc93 Mon Sep 17 00:00:00 2001 From: Krzysztof Lis Date: Fri, 13 Sep 2024 18:48:05 +0200 Subject: [PATCH 055/282] chore: move test peer to test_utils --- crates/p2p/src/test_utils.rs | 1 + crates/p2p/src/test_utils/peer.rs | 140 ++++++++++++++++++++++ crates/p2p/src/tests.rs | 188 +++++------------------------- 3 files changed, 171 insertions(+), 158 deletions(-) create mode 100644 crates/p2p/src/test_utils/peer.rs diff --git a/crates/p2p/src/test_utils.rs b/crates/p2p/src/test_utils.rs index cd357a3de4..1ab88b1562 100644 --- a/crates/p2p/src/test_utils.rs +++ b/crates/p2p/src/test_utils.rs @@ -1,3 +1,4 @@ pub mod main_loop; +pub mod peer; pub mod peer_aware; pub mod sync; diff --git a/crates/p2p/src/test_utils/peer.rs b/crates/p2p/src/test_utils/peer.rs new file mode 100644 index 0000000000..8ba7f8be1e --- /dev/null +++ b/crates/p2p/src/test_utils/peer.rs @@ -0,0 +1,140 @@ +use std::collections::HashMap; +use std::fmt::Debug; +use std::str::FromStr; +use std::time::Duration; + +use anyhow::{Context, Result}; +use libp2p::identity::Keypair; +use libp2p::{Multiaddr, PeerId}; +use pathfinder_common::ChainId; +use tokio::task::JoinHandle; + +use crate::peers::Peer; +use crate::{Builder, Config, Event, RateLimit, TestEvent}; + +#[allow(dead_code)] +#[derive(Debug)] +pub struct TestPeer { + pub keypair: Keypair, + pub peer_id: PeerId, + pub client: crate::Client, + pub event_receiver: crate::EventReceiver, + pub main_loop_jh: JoinHandle<()>, +} + +#[derive(Default)] +pub struct TestPeerBuilder { + p2p_builder: Option, +} + +impl Config { + pub fn for_test() -> Self { + Self { + direct_connection_timeout: Duration::from_secs(0), + relay_connection_timeout: Duration::from_secs(0), + max_inbound_direct_peers: 10, + max_inbound_relayed_peers: 10, + max_outbound_peers: 10, + low_watermark: 10, + ip_whitelist: vec!["::/0".parse().unwrap(), "0.0.0.0/0".parse().unwrap()], + bootstrap: Default::default(), + eviction_timeout: Duration::from_secs(15 * 60), + inbound_connections_rate_limit: RateLimit { + max: 1000, + interval: Duration::from_secs(1), + }, + kad_names: Default::default(), + stream_timeout: Duration::from_secs(10), + max_concurrent_streams: 100, + } + } +} + +impl TestPeerBuilder { + pub fn p2p_builder(mut self, p2p_builder: Builder) -> Self { + self.p2p_builder = Some(p2p_builder); + self + } + + pub fn build(self, keypair: Keypair, cfg: Config) -> TestPeer { + let Self { p2p_builder } = self; + + let peer_id = keypair.public().to_peer_id(); + + let (client, mut event_receiver, main_loop) = p2p_builder + .unwrap_or_else(|| crate::Builder::new(keypair.clone(), cfg, ChainId::SEPOLIA_TESTNET)) + .build(); + + // Ensure that the channel keeps being polled to move the main loop forward. + // Store the polled events into a buffered channel instead. + let (buf_sender, buf_receiver) = tokio::sync::mpsc::channel(1024); + tokio::spawn(async move { + while let Some(event) = event_receiver.recv().await { + buf_sender.send(event).await.unwrap(); + } + }); + + let main_loop_jh = tokio::spawn(main_loop.run()); + TestPeer { + keypair, + peer_id, + client, + event_receiver: buf_receiver, + main_loop_jh, + } + } +} + +impl TestPeer { + pub fn builder() -> TestPeerBuilder { + Default::default() + } + + /// Create a new peer with a random keypair + #[must_use] + pub fn new(cfg: Config) -> Self { + Self::builder().build(Keypair::generate_ed25519(), cfg) + } + + #[must_use] + pub fn with_keypair(keypair: Keypair, cfg: Config) -> Self { + Self::builder().build(keypair, cfg) + } + + /// Start listening on a specified address + pub async fn start_listening_on(&mut self, addr: Multiaddr) -> Result { + self.client + .start_listening(addr) + .await + .context("Start listening failed")?; + + let event = tokio::time::timeout(Duration::from_secs(1), self.event_receiver.recv()) + .await + .context("Timedout while waiting for new listen address")? + .context("Event channel closed")?; + + let addr = match event { + Event::Test(TestEvent::NewListenAddress(addr)) => addr, + _ => anyhow::bail!("Unexpected event: {event:?}"), + }; + Ok(addr) + } + + /// Start listening on localhost with port automatically assigned + pub async fn start_listening(&mut self) -> Result { + self.start_listening_on(Multiaddr::from_str("/ip4/127.0.0.1/tcp/0").unwrap()) + .await + } + + /// Get peer IDs of the connected peers + pub async fn connected(&self) -> HashMap { + self.client.for_test().get_connected_peers().await + } +} + +impl Default for TestPeer { + /// Create a new peer with a random keypair and default test config + fn default() -> Self { + Self::builder().build(Keypair::generate_ed25519(), Config::for_test()) + } +} diff --git a/crates/p2p/src/tests.rs b/crates/p2p/src/tests.rs index 8944f033d7..f31c232f0e 100644 --- a/crates/p2p/src/tests.rs +++ b/crates/p2p/src/tests.rs @@ -1,14 +1,10 @@ -use std::collections::HashMap; use std::fmt::Debug; -use std::str::FromStr; use std::time::Duration; -use anyhow::{Context, Result}; use fake::{Fake, Faker}; use futures::{FutureExt, SinkExt, StreamExt}; use libp2p::identity::Keypair; use libp2p::multiaddr::Protocol; -use libp2p::{Multiaddr, PeerId}; use p2p_proto::class::{ClassesRequest, ClassesResponse}; use p2p_proto::event::{EventsRequest, EventsResponse}; use p2p_proto::header::{BlockHeadersRequest, BlockHeadersResponse, NewBlock}; @@ -16,140 +12,10 @@ use p2p_proto::state::{StateDiffsRequest, StateDiffsResponse}; use p2p_proto::transaction::{TransactionsRequest, TransactionsResponse}; use pathfinder_common::ChainId; use rstest::rstest; -use tokio::task::JoinHandle; -use crate::peers::Peer; use crate::sync::codec; -use crate::{BootstrapConfig, Builder, Config, Event, EventReceiver, RateLimit, TestEvent}; - -#[allow(dead_code)] -#[derive(Debug)] -struct TestPeer { - pub keypair: Keypair, - pub peer_id: PeerId, - pub client: crate::Client, - pub event_receiver: crate::EventReceiver, - pub main_loop_jh: JoinHandle<()>, -} - -#[derive(Default)] -struct TestPeerBuilder { - // cfg: Option, - // keypair: Option, - p2p_builder: Option, -} - -impl Config { - pub fn for_test() -> Self { - Self { - direct_connection_timeout: Duration::from_secs(0), - relay_connection_timeout: Duration::from_secs(0), - max_inbound_direct_peers: 10, - max_inbound_relayed_peers: 10, - max_outbound_peers: 10, - low_watermark: 10, - ip_whitelist: vec!["::/0".parse().unwrap(), "0.0.0.0/0".parse().unwrap()], - bootstrap: Default::default(), - eviction_timeout: Duration::from_secs(15 * 60), - inbound_connections_rate_limit: RateLimit { - max: 1000, - interval: Duration::from_secs(1), - }, - kad_names: Default::default(), - stream_timeout: Duration::from_secs(10), - max_concurrent_streams: 100, - } - } -} - -impl TestPeerBuilder { - pub fn p2p_builder(mut self, p2p_builder: Builder) -> Self { - self.p2p_builder = Some(p2p_builder); - self - } - - pub fn build(self, keypair: Keypair, cfg: Config) -> TestPeer { - let Self { p2p_builder } = self; - - let peer_id = keypair.public().to_peer_id(); - - let (client, mut event_receiver, main_loop) = p2p_builder - .unwrap_or_else(|| crate::Builder::new(keypair.clone(), cfg, ChainId::SEPOLIA_TESTNET)) - .build(); - - // Ensure that the channel keeps being polled to move the main loop forward. - // Store the polled events into a buffered channel instead. - let (buf_sender, buf_receiver) = tokio::sync::mpsc::channel(1024); - tokio::spawn(async move { - while let Some(event) = event_receiver.recv().await { - buf_sender.send(event).await.unwrap(); - } - }); - - let main_loop_jh = tokio::spawn(main_loop.run()); - TestPeer { - keypair, - peer_id, - client, - event_receiver: buf_receiver, - main_loop_jh, - } - } -} - -impl TestPeer { - pub fn builder() -> TestPeerBuilder { - Default::default() - } - - /// Create a new peer with a random keypair - #[must_use] - pub fn new(cfg: Config) -> Self { - Self::builder().build(Keypair::generate_ed25519(), cfg) - } - - #[must_use] - pub fn with_keypair(keypair: Keypair, cfg: Config) -> Self { - Self::builder().build(keypair, cfg) - } - - /// Start listening on a specified address - pub async fn start_listening_on(&mut self, addr: Multiaddr) -> Result { - self.client - .start_listening(addr) - .await - .context("Start listening failed")?; - - let event = tokio::time::timeout(Duration::from_secs(1), self.event_receiver.recv()) - .await - .context("Timedout while waiting for new listen address")? - .context("Event channel closed")?; - - let addr = match event { - Event::Test(TestEvent::NewListenAddress(addr)) => addr, - _ => anyhow::bail!("Unexpected event: {event:?}"), - }; - Ok(addr) - } - - /// Start listening on localhost with port automatically assigned - pub async fn start_listening(&mut self) -> Result { - self.start_listening_on(Multiaddr::from_str("/ip4/127.0.0.1/tcp/0").unwrap()) - .await - } - - /// Get peer IDs of the connected peers - pub async fn connected(&self) -> HashMap { - self.client.for_test().get_connected_peers().await - } -} - -impl Default for TestPeer { - /// Create a new peer with a random keypair and default test config - fn default() -> Self { - Self::builder().build(Keypair::generate_ed25519(), Config::for_test()) - } -} +use crate::test_utils::peer::TestPeer; +use crate::{BootstrapConfig, Config, Event, EventReceiver, RateLimit, TestEvent}; /// [`MainLoop`](p2p::MainLoop)'s event channel size is 1, so we need to consume /// all events as soon as they're sent otherwise the main loop will stall. @@ -187,13 +53,19 @@ async fn wait_for_event( None } -async fn exhaust_events(event_receiver: &mut EventReceiver) { +/// Consume all events that have accumulated for the peer so far. You don't care +/// about any of those events in the queue __right now__, but later you may do +/// something that triggers new events for this peer, which you may care for. +async fn consume_accumulated_events(event_receiver: &mut EventReceiver) { while event_receiver.try_recv().is_ok() {} } +/// Consume all events from a peer to keep its main loop going. You don't care +/// about any of those events. +/// /// [`MainLoop`](p2p::MainLoop)'s event channel size is 1, so we need to consume /// all events as soon as they're sent otherwise the main loop will stall -fn consume_events(mut event_receiver: EventReceiver) { +fn consume_all_events_forever(mut event_receiver: EventReceiver) { tokio::spawn(async move { while (event_receiver.recv().await).is_some() {} }); } @@ -236,7 +108,7 @@ async fn dial() { peer1.client.dial(peer2.peer_id, addr2).await.unwrap(); - exhaust_events(&mut peer1.event_receiver).await; + consume_accumulated_events(&mut peer1.event_receiver).await; let peers_of1: Vec<_> = peer1.connected().await.into_keys().collect(); let peers_of2: Vec<_> = peer2.connected().await.into_keys().collect(); @@ -257,7 +129,7 @@ async fn disconnect() { peer1.client.dial(peer2.peer_id, addr2).await.unwrap(); - exhaust_events(&mut peer1.event_receiver).await; + consume_accumulated_events(&mut peer1.event_receiver).await; let peers_of1: Vec<_> = peer1.connected().await.into_keys().collect(); let peers_of2: Vec<_> = peer2.connected().await.into_keys().collect(); @@ -339,7 +211,7 @@ async fn periodic_bootstrap() { _ => None, }; - consume_events(boot.event_receiver); + consume_all_events_forever(boot.event_receiver); let peer_id2 = peer2.peer_id; @@ -362,7 +234,7 @@ async fn periodic_bootstrap() { .await; }); - consume_events(peer1.event_receiver); + consume_all_events_forever(peer1.event_receiver); assert_eq!( boot.client.for_test().get_peers_from_dht().await, @@ -419,7 +291,7 @@ async fn periodic_bootstrap() { .await .unwrap(); - exhaust_events(&mut peer3.event_receiver).await; + consume_accumulated_events(&mut peer3.event_receiver).await; // The low watermark is reached for peer3, so no more bootstrap attempts are // made. @@ -675,11 +547,11 @@ async fn outbound_peer_eviction() { let outbound_addr4 = outbound4.start_listening().await.unwrap(); tracing::info!(%outbound4.peer_id, %outbound_addr4); - consume_events(outbound1.event_receiver); - consume_events(outbound2.event_receiver); - consume_events(outbound3.event_receiver); - consume_events(outbound4.event_receiver); - consume_events(inbound1.event_receiver); + consume_all_events_forever(outbound1.event_receiver); + consume_all_events_forever(outbound2.event_receiver); + consume_all_events_forever(outbound3.event_receiver); + consume_all_events_forever(outbound4.event_receiver); + consume_all_events_forever(inbound1.event_receiver); // Open one inbound connection. This connection is never touched. inbound1 @@ -698,7 +570,7 @@ async fn outbound_peer_eviction() { .await .unwrap(); - exhaust_events(&mut peer.event_receiver).await; + consume_accumulated_events(&mut peer.event_receiver).await; // Trying to open another one fails, because no peers are marked as not useful, // and hence no peer can be evicted. @@ -819,7 +691,7 @@ async fn inbound_peer_eviction() { assert_eq!(connected.len(), 26); assert!(connected.contains_key(&outbound1.peer_id)); - exhaust_events(&mut peer.event_receiver).await; + consume_accumulated_events(&mut peer.event_receiver).await; // Trying to open another one causes an eviction. inbound_peers @@ -913,7 +785,7 @@ async fn evicted_peer_reconnection() { let result = peer1.client.dial(peer2.peer_id, addr2.clone()).await; assert!(result.is_err()); - exhaust_events(&mut peer2.event_receiver).await; + consume_accumulated_events(&mut peer2.event_receiver).await; // In this case there is no peer ID when connecting, so the connection gets // closed after being established. @@ -975,7 +847,7 @@ async fn ip_whitelist() { let addr1 = peer1.start_listening().await.unwrap(); tracing::info!(%peer1.peer_id, %addr1); - consume_events(peer2.event_receiver); + consume_all_events_forever(peer2.event_receiver); // Can't open the connection because peer2 is bound to 127.0.0.1 and peer1 only // allows 127.0.0.2. @@ -1051,10 +923,10 @@ async fn rate_limit() { let addr1 = peer1.start_listening().await.unwrap(); tracing::info!(%peer1.peer_id, %addr1); - consume_events(peer1.event_receiver); - consume_events(peer2.event_receiver); - consume_events(peer3.event_receiver); - consume_events(peer4.event_receiver); + consume_all_events_forever(peer1.event_receiver); + consume_all_events_forever(peer2.event_receiver); + consume_all_events_forever(peer3.event_receiver); + consume_all_events_forever(peer4.event_receiver); // Two connections can be opened, but the third one is rate limited. @@ -1085,7 +957,7 @@ async fn provide_capability(#[case] peers: (TestPeer, TestPeer)) { Event::Test(TestEvent::StartProvidingCompleted(_)) => Some(()), _ => None, }); - consume_events(peer2.event_receiver); + consume_all_events_forever(peer2.event_receiver); peer1.client.provide_capability("blah").await.unwrap(); peer1_started_providing.recv().await; @@ -1169,7 +1041,7 @@ macro_rules! define_test { }); // This is to keep peer2's event loop going - consume_events(peer2.event_receiver); + consume_all_events_forever(peer2.event_receiver); // Peer2 sends the request to peer1, and waits for the response receiver let mut rx = peer2 From 0fe4fe4f2a5917e6b1de1dce8e1b743a57f68dc6 Mon Sep 17 00:00:00 2001 From: Krzysztof Lis Date: Fri, 13 Sep 2024 18:53:57 +0200 Subject: [PATCH 056/282] test(p2p): add a test scaffolding --- crates/p2p/src/tests.rs | 85 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/crates/p2p/src/tests.rs b/crates/p2p/src/tests.rs index f31c232f0e..fd483f383c 100644 --- a/crates/p2p/src/tests.rs +++ b/crates/p2p/src/tests.rs @@ -1123,3 +1123,88 @@ define_test!( InboundEventsSyncRequest, send_events_sync_request ); + +#[test_log::test(tokio::test)] +async fn always_propagate_stream_errors_to_caller() { + let mut peer1 = TestPeer::default(); + + let codec = codec::Headers::for_test().set_read_response_factory(Box::new(move || { + Box::new(|_| { + async { + Err(std::io::Error::new( + std::io::ErrorKind::Other, + "stream error", + )) + } + .boxed() + }) + })); + let keypair = Keypair::generate_ed25519(); + let cfg = Config::for_test(); + let chain_id = ChainId::SEPOLIA_TESTNET; + let behaviour_builder = crate::behaviour::Builder::new(keypair.clone(), chain_id, cfg.clone()) + .header_sync_behaviour(p2p_stream::Behaviour::with_codec(codec, Default::default())); + let p2p_builder = crate::Builder::new(keypair.clone(), cfg.clone(), chain_id) + .behaviour_builder(behaviour_builder); + let peer2 = TestPeer::builder() + .p2p_builder(p2p_builder) + .build(keypair, cfg); + + let server_addr = peer1.start_listening().await.unwrap(); + + tracing::info!(%peer1.peer_id, %server_addr, "Server"); + tracing::info!(%peer2.peer_id, "Client"); + + peer2.client.dial(peer1.peer_id, server_addr).await.unwrap(); + + // Fake some request for peer2 to send to peer1 + let expected_request = Faker.fake::(); + + // Filter peer1's events to fish out the request from peer2 and the channel that + // peer1 will use to send the responses + // This is also to keep peer1's event loop going + let mut tx_ready = filter_events(peer1.event_receiver, move |event| match event { + Event::InboundHeadersSyncRequest { + from, + channel, + request: actual_request, + } => { + // Peer 1 should receive the request from peer2 + assert_eq!(from, peer2.peer_id); + // Received request should match what peer2 sent + assert_eq!(expected_request, actual_request); + Some(channel) + } + _ => None, + }); + + // This is to keep peer2's event loop going + consume_all_events_forever(peer2.event_receiver); + + // Peer2 sends the request to peer1, and waits for the response receiver + let mut rx = peer2 + .client + .send_headers_sync_request(peer1.peer_id, expected_request) + .await + .expect(&format!( + "sending request using: {}, line: {}", + // std::stringify!($req_fn), + "TODO", + line!() + )); + + // Peer1 waits for response channel to be ready + let mut tx = tx_ready.recv().await.expect(&format!( + "waiting for response channel to be ready, line: {}", + line!() + )); + + let expected_response = Faker.fake::(); + // Peer1 sends 1 response, but peer2's codec is mocked to fail upon reception + tx.send(expected_response.clone()) + .await + .expect(&format!("sending expected response, line: {}", line!())); // Peer2 waits for the response + + // TODO this should be an error + let _actual_response = rx.next().await.unwrap(); +} From 57884d5c278267cffbf16ad5e5244a3e0813a3d0 Mon Sep 17 00:00:00 2001 From: Krzysztof Lis Date: Sat, 14 Sep 2024 00:09:28 +0200 Subject: [PATCH 057/282] feat(p2p_stream): response receiver carries std::io::Result --- Cargo.lock | 2 +- crates/p2p_stream/Cargo.toml | 2 +- crates/p2p_stream/src/handler.rs | 23 ++++++++-- crates/p2p_stream/src/lib.rs | 2 +- crates/p2p_stream/tests/error_reporting.rs | 53 +++++----------------- crates/p2p_stream/tests/sanity.rs | 13 +----- crates/p2p_stream/tests/utils/mod.rs | 6 ++- 7 files changed, 39 insertions(+), 62 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4f331d7c74..6aafb4303b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7126,9 +7126,9 @@ dependencies = [ "libp2p-plaintext", "libp2p-swarm-test", "rstest", + "test-log", "tokio", "tracing", - "tracing-subscriber", "void", ] diff --git a/crates/p2p_stream/Cargo.toml b/crates/p2p_stream/Cargo.toml index b908d0974d..9902c982a1 100644 --- a/crates/p2p_stream/Cargo.toml +++ b/crates/p2p_stream/Cargo.toml @@ -26,4 +26,4 @@ libp2p-plaintext = { workspace = true } libp2p-swarm-test = { workspace = true } rstest = { workspace = true } tokio = { workspace = true, features = ["macros", "time"] } -tracing-subscriber = { workspace = true } +test-log = { workspace = true, features = ["trace"] } diff --git a/crates/p2p_stream/src/handler.rs b/crates/p2p_stream/src/handler.rs index 7e556ba04a..fbfc192e24 100644 --- a/crates/p2p_stream/src/handler.rs +++ b/crates/p2p_stream/src/handler.rs @@ -81,9 +81,15 @@ where )>, /// A channel for signalling that an outbound request has been sent. Cloned /// for each outbound request. - outbound_sender: mpsc::Sender<(OutboundRequestId, mpsc::Receiver)>, + outbound_sender: mpsc::Sender<( + OutboundRequestId, + mpsc::Receiver>, + )>, /// The [`mpsc::Receiver`] for the above sender. - outbound_receiver: mpsc::Receiver<(OutboundRequestId, mpsc::Receiver)>, + outbound_receiver: mpsc::Receiver<( + OutboundRequestId, + mpsc::Receiver>, + )>, inbound_request_id: Arc, @@ -220,14 +226,21 @@ where match codec.read_response(&protocol, &mut stream).await { Ok(response) => { rs_send - .send(response) + .send(Ok(response)) .await .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?; } // The stream is closed, there's nothing more to receive Err(error) if error.kind() == io::ErrorKind::UnexpectedEof => break, // An error occurred, propagate it - Err(error) => return Err(error), + Err(error) => { + let error_clone = io::Error::new(error.kind(), error.to_string()); + rs_send + .send(Err(error_clone)) + .await + .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?; + return Err(error); + } } } @@ -312,7 +325,7 @@ where /// The ID of the outbound request. request_id: OutboundRequestId, /// The channel through which we can receive the responses. - receiver: mpsc::Receiver, + receiver: mpsc::Receiver>, }, /// An outbound response stream to an inbound request was closed. OutboundResponseStreamClosed(InboundRequestId), diff --git a/crates/p2p_stream/src/lib.rs b/crates/p2p_stream/src/lib.rs index cd39f37b6b..92474331f8 100644 --- a/crates/p2p_stream/src/lib.rs +++ b/crates/p2p_stream/src/lib.rs @@ -103,7 +103,7 @@ pub enum Event { /// The ID of the outbound request. request_id: OutboundRequestId, /// The channel through which we can receive the responses. - channel: mpsc::Receiver, + channel: mpsc::Receiver>, }, /// An outbound request failed. OutboundFailure { diff --git a/crates/p2p_stream/tests/error_reporting.rs b/crates/p2p_stream/tests/error_reporting.rs index 1543a55d1f..e9f1aecbd1 100644 --- a/crates/p2p_stream/tests/error_reporting.rs +++ b/crates/p2p_stream/tests/error_reporting.rs @@ -4,7 +4,6 @@ use std::time::Duration; use futures::prelude::*; use libp2p_swarm_test::SwarmExt; use p2p_stream::{InboundFailure, OutboundFailure}; -use tracing_subscriber::EnvFilter; pub mod utils; @@ -20,12 +19,8 @@ use utils::{ Action, }; -#[tokio::test] +#[test_log::test(tokio::test)] async fn report_outbound_failure_on_read_response_failure() { - let _ = tracing_subscriber::fmt() - .with_env_filter(EnvFilter::from_default_env()) - .try_init(); - let (peer1_id, mut swarm1) = new_swarm(); let (peer2_id, mut swarm2) = new_swarm(); @@ -60,7 +55,9 @@ async fn report_outbound_failure_on_read_response_failure() { assert_eq!(peer, peer1_id); assert_eq!(req_id_done, req_id); - assert!(resp_channel.next().await.is_none()); + assert!( + matches!(resp_channel.next().await, Some(Err(x)) if x.kind() == io::ErrorKind::Other && x.to_string() == "FailOnReadResponse") + ); let (peer, req_id_done, error) = wait_outbound_failure(&mut swarm2).await.unwrap(); assert_eq!(peer, peer1_id); @@ -82,12 +79,8 @@ async fn report_outbound_failure_on_read_response_failure() { tokio::join!(server_task, client_task); } -#[tokio::test] +#[test_log::test(tokio::test)] async fn report_outbound_failure_on_write_request_failure() { - let _ = tracing_subscriber::fmt() - .with_env_filter(EnvFilter::from_default_env()) - .try_init(); - let (peer1_id, mut swarm1) = new_swarm(); let (_peer2_id, mut swarm2) = new_swarm(); @@ -129,12 +122,8 @@ async fn report_outbound_failure_on_write_request_failure() { client_task.await; } -#[tokio::test] +#[test_log::test(tokio::test)] async fn report_outbound_timeout_on_read_response_timeout() { - let _ = tracing_subscriber::fmt() - .with_env_filter(EnvFilter::from_default_env()) - .try_init(); - // `swarm1` needs to have a bigger timeout to avoid racing let (peer1_id, mut swarm1) = new_swarm_with_timeout(Duration::from_millis(200)); let (peer2_id, mut swarm2) = new_swarm_with_timeout(Duration::from_millis(100)); @@ -179,12 +168,8 @@ async fn report_outbound_timeout_on_read_response_timeout() { tokio::join!(server_task, client_task); } -#[tokio::test] +#[test_log::test(tokio::test)] async fn report_inbound_closure_on_read_request_failure() { - let _ = tracing_subscriber::fmt() - .with_env_filter(EnvFilter::from_default_env()) - .try_init(); - let (peer1_id, mut swarm1) = new_swarm(); let (_peer2_id, mut swarm2) = new_swarm(); @@ -226,12 +211,8 @@ async fn report_inbound_closure_on_read_request_failure() { client_task.await; } -#[tokio::test] +#[test_log::test(tokio::test)] async fn report_inbound_failure_on_write_response_failure() { - let _ = tracing_subscriber::fmt() - .with_env_filter(EnvFilter::from_default_env()) - .try_init(); - let (peer1_id, mut swarm1) = new_swarm(); let (peer2_id, mut swarm2) = new_swarm(); @@ -286,12 +267,8 @@ async fn report_inbound_failure_on_write_response_failure() { tokio::join!(client_task, server_task); } -#[tokio::test] +#[test_log::test(tokio::test)] async fn report_inbound_timeout_on_write_response_timeout() { - let _ = tracing_subscriber::fmt() - .with_env_filter(EnvFilter::from_default_env()) - .try_init(); - // `swarm2` needs to have a bigger timeout to avoid racing let (peer1_id, mut swarm1) = new_swarm_with_timeout(Duration::from_millis(100)); let (peer2_id, mut swarm2) = new_swarm_with_timeout(Duration::from_millis(200)); @@ -342,12 +319,8 @@ async fn report_inbound_timeout_on_write_response_timeout() { tokio::join!(client_task, server_task); } -#[tokio::test] +#[test_log::test(tokio::test)] async fn report_outbound_timeout_on_write_request_timeout() { - let _ = tracing_subscriber::fmt() - .with_env_filter(EnvFilter::from_default_env()) - .try_init(); - // `swarm1` needs to have a bigger timeout to avoid racing let (peer1_id, mut swarm1) = new_swarm_with_timeout(Duration::from_millis(200)); let (_peer2_id, mut swarm2) = new_swarm_with_timeout(Duration::from_millis(100)); @@ -381,12 +354,8 @@ async fn report_outbound_timeout_on_write_request_timeout() { client_task.await; } -#[tokio::test] +#[test_log::test(tokio::test)] async fn report_outbound_timeout_on_read_request_timeout() { - let _ = tracing_subscriber::fmt() - .with_env_filter(EnvFilter::from_default_env()) - .try_init(); - // `swarm2` needs to have a bigger timeout to avoid racing let (peer1_id, mut swarm1) = new_swarm_with_timeout(Duration::from_millis(200)); let (_peer2_id, mut swarm2) = new_swarm_with_timeout(Duration::from_millis(100)); diff --git a/crates/p2p_stream/tests/sanity.rs b/crates/p2p_stream/tests/sanity.rs index e6e64e8d3b..636597f068 100644 --- a/crates/p2p_stream/tests/sanity.rs +++ b/crates/p2p_stream/tests/sanity.rs @@ -5,7 +5,6 @@ use futures::prelude::*; use libp2p::PeerId; use libp2p_swarm_test::SwarmExt; use rstest::rstest; -use tracing_subscriber::EnvFilter; pub mod utils; @@ -78,26 +77,18 @@ async fn server_request_to_client() -> Scenario { #[rstest] #[case::client_request_to_server(client_request_to_server())] #[case::server_request_to_client(server_request_to_client())] -#[tokio::test] +#[test_log::test(tokio::test)] async fn sanity( #[values(0, 1, (2..10000).fake())] num_responses: usize, #[case] #[future] scenario: Scenario, ) { - let _ = tracing_subscriber::fmt() - .with_env_filter(EnvFilter::from_default_env()) - .try_init(); - let Scenario { mut requester, mut responder, } = scenario.await; - let _ = tracing_subscriber::fmt() - .with_env_filter(EnvFilter::from_default_env()) - .try_init(); - let responder_task = async move { let (peer, req_id, action, mut resp_tx) = wait_inbound_request(&mut responder.swarm).await.unwrap(); @@ -139,7 +130,7 @@ async fn sanity( for i in 0..num_responses { assert_eq!( - resp_rx.next().await.unwrap(), + resp_rx.next().await.unwrap().unwrap(), Action::SanityResponse(i as u32) ); } diff --git a/crates/p2p_stream/tests/utils/mod.rs b/crates/p2p_stream/tests/utils/mod.rs index 14efc25867..402fb1750e 100644 --- a/crates/p2p_stream/tests/utils/mod.rs +++ b/crates/p2p_stream/tests/utils/mod.rs @@ -252,7 +252,11 @@ pub async fn wait_inbound_request( pub async fn wait_outbound_request_sent_awaiting_responses( swarm: &mut Swarm>, -) -> Result<(PeerId, OutboundRequestId, mpsc::Receiver)> { +) -> Result<( + PeerId, + OutboundRequestId, + mpsc::Receiver>, +)> { loop { match swarm.select_next_some().await.try_into_behaviour_event() { Ok(p2p_stream::Event::OutboundRequestSentAwaitingResponses { From 0c36ed70c5717ac98f500af8367b38c5538cf269 Mon Sep 17 00:00:00 2001 From: Krzysztof Lis Date: Sat, 14 Sep 2024 00:22:14 +0200 Subject: [PATCH 058/282] feat(p2p): response is wrapped in std::io::Result --- crates/p2p/src/client/peer_agnostic.rs | 17 +++++++++++++---- crates/p2p/src/client/peer_aware.rs | 2 +- crates/p2p/src/lib.rs | 15 ++++++++++----- crates/p2p/src/main_loop.rs | 10 +++++----- 4 files changed, 29 insertions(+), 15 deletions(-) diff --git a/crates/p2p/src/client/peer_agnostic.rs b/crates/p2p/src/client/peer_agnostic.rs index 23de02ea89..098b2de3e5 100644 --- a/crates/p2p/src/client/peer_agnostic.rs +++ b/crates/p2p/src/client/peer_agnostic.rs @@ -605,7 +605,8 @@ mod header_stream { ) -> impl Stream> where PF: Future> + Send, - RF: Future>> + Send, + RF: Future>>> + + Send, { let start: i64 = start.get().try_into().expect("block number <= i64::MAX"); let stop: i64 = stop.get().try_into().expect("block number <= i64::MAX"); @@ -655,14 +656,14 @@ mod header_stream { async fn handle_response( peer: PeerId, - signed_header: BlockHeadersResponse, + signed_header: std::io::Result, direction: Direction, start: &mut i64, stop: i64, tx: mpsc::Sender>, ) -> Action { match signed_header { - BlockHeadersResponse::Header(hdr) => match SignedBlockHeader::try_from_dto(*hdr) { + Ok(BlockHeadersResponse::Header(hdr)) => match SignedBlockHeader::try_from_dto(*hdr) { Ok(hdr) => { if done(direction, *start, stop) { tracing::debug!(%peer, "Header stream Fin missing, got extra header instead"); @@ -687,12 +688,20 @@ mod header_stream { Action::NextPeer } }, - BlockHeadersResponse::Fin => { + Ok(BlockHeadersResponse::Fin) => { tracing::debug!(%peer, "Header stream Fin"); if done(direction, *start, stop) { return Action::TerminateStream; } + Action::NextPeer + } + Err(error) => { + tracing::debug!(%peer, %error, "Header stream failed"); + if done(direction, *start, stop) { + return Action::TerminateStream; + } + Action::NextPeer } } diff --git a/crates/p2p/src/client/peer_aware.rs b/crates/p2p/src/client/peer_aware.rs index dc89e545f0..38ea84d3e4 100644 --- a/crates/p2p/src/client/peer_aware.rs +++ b/crates/p2p/src/client/peer_aware.rs @@ -30,7 +30,7 @@ macro_rules! impl_send { &self, peer_id: PeerId, request: $req_type, - ) -> anyhow::Result> { + ) -> anyhow::Result>> { let (sender, receiver) = oneshot::channel(); self.sender .send(Command::$req_command { diff --git a/crates/p2p/src/lib.rs b/crates/p2p/src/lib.rs index 443d80048f..2cddc3f823 100644 --- a/crates/p2p/src/lib.rs +++ b/crates/p2p/src/lib.rs @@ -133,27 +133,32 @@ enum Command { SendHeadersSyncRequest { peer_id: PeerId, request: BlockHeadersRequest, - sender: oneshot::Sender>>, + sender: oneshot::Sender< + anyhow::Result>>, + >, }, SendClassesSyncRequest { peer_id: PeerId, request: ClassesRequest, - sender: oneshot::Sender>>, + sender: oneshot::Sender>>>, }, SendStateDiffsSyncRequest { peer_id: PeerId, request: StateDiffsRequest, - sender: oneshot::Sender>>, + sender: + oneshot::Sender>>>, }, SendTransactionsSyncRequest { peer_id: PeerId, request: TransactionsRequest, - sender: oneshot::Sender>>, + sender: oneshot::Sender< + anyhow::Result>>, + >, }, SendEventsSyncRequest { peer_id: PeerId, request: EventsRequest, - sender: oneshot::Sender>>, + sender: oneshot::Sender>>>, }, PublishPropagationMessage { topic: IdentTopic, diff --git a/crates/p2p/src/main_loop.rs b/crates/p2p/src/main_loop.rs index 8897399e19..7e17bf948f 100644 --- a/crates/p2p/src/main_loop.rs +++ b/crates/p2p/src/main_loop.rs @@ -57,23 +57,23 @@ pub struct MainLoop { struct PendingRequests { pub headers: HashMap< OutboundRequestId, - oneshot::Sender>>, + oneshot::Sender>>>, >, pub classes: HashMap< OutboundRequestId, - oneshot::Sender>>, + oneshot::Sender>>>, >, pub state_diffs: HashMap< OutboundRequestId, - oneshot::Sender>>, + oneshot::Sender>>>, >, pub transactions: HashMap< OutboundRequestId, - oneshot::Sender>>, + oneshot::Sender>>>, >, pub events: HashMap< OutboundRequestId, - oneshot::Sender>>, + oneshot::Sender>>>, >, } From 7e466a96832ad76f87d4cf428db3508e1c029b25 Mon Sep 17 00:00:00 2001 From: Krzysztof Lis Date: Sun, 15 Sep 2024 14:53:18 +0200 Subject: [PATCH 059/282] feat(p2p): response is wrapped in std::io::Result, part 2 --- crates/p2p/src/client/peer_agnostic.rs | 141 ++++++++++++------ .../p2p/src/client/peer_agnostic/fixtures.rs | 4 +- crates/p2p/src/client/peer_agnostic/traits.rs | 12 +- crates/p2p/src/client/types.rs | 33 +++- crates/p2p/src/tests.rs | 3 +- crates/pathfinder/src/sync/track.rs | 46 ++++-- 6 files changed, 169 insertions(+), 70 deletions(-) diff --git a/crates/p2p/src/client/peer_agnostic.rs b/crates/p2p/src/client/peer_agnostic.rs index 098b2de3e5..8267e864a9 100644 --- a/crates/p2p/src/client/peer_agnostic.rs +++ b/crates/p2p/src/client/peer_agnostic.rs @@ -6,7 +6,7 @@ use std::sync::Arc; use std::time::{Duration, Instant}; use futures::channel::mpsc as fmpsc; -use futures::{Stream, StreamExt}; +use futures::{Stream, StreamExt, TryStreamExt}; use libp2p::PeerId; use p2p_proto::class::{ClassesRequest, ClassesResponse}; use p2p_proto::common::{Direction, Iteration}; @@ -61,8 +61,9 @@ use crate::client::types::{ ClassDefinition, ClassDefinitionsError, EventsForBlockByTransaction, - IncorrectStateDiffCount, + EventsResponseStreamFailure, Receipt, + StateDiffsError, TransactionData, }; use crate::peer_data::PeerData; @@ -321,12 +322,14 @@ impl BlockClient for Client { }; let stream = stream - .take_while(|x| std::future::ready(!matches!(x, &TransactionsResponse::Fin))) + .try_take_while(|x| { + std::future::ready(Ok(!matches!(x, &TransactionsResponse::Fin))) + }) .enumerate() - .map(|(i, x)| -> anyhow::Result<_> { + .map(move |(i, x)| -> anyhow::Result<_> { match x { - TransactionsResponse::Fin => unreachable!("Already handled Fin above"), - TransactionsResponse::TransactionWithReceipt(tx_with_receipt) => Ok(( + Ok(TransactionsResponse::Fin) => unreachable!("Already handled Fin above"), + Ok(TransactionsResponse::TransactionWithReceipt(tx_with_receipt)) => Ok(( TransactionVariant::try_from_dto(tx_with_receipt.transaction)?, Receipt::try_from(( tx_with_receipt.receipt, @@ -334,6 +337,10 @@ impl BlockClient for Client { .ok_or_else(|| anyhow::anyhow!("Invalid transaction index"))?, ))?, )), + Err(error) => { + tracing::debug!(%peer, %error, "Transaction response stream failed"); + Err(error.into()) + } } }); @@ -347,7 +354,7 @@ impl BlockClient for Client { self, block: BlockNumber, state_diff_length: u64, - ) -> Result, IncorrectStateDiffCount> { + ) -> Result, StateDiffsError> { let request = StateDiffsRequest { iteration: Iteration { start: block.get().into(), @@ -374,18 +381,18 @@ impl BlockClient for Client { while let Some(resp) = stream.next().await { match resp { - StateDiffsResponse::ContractDiff(ContractDiff { + Ok(StateDiffsResponse::ContractDiff(ContractDiff { address, nonce, class_hash, values, domain: _, - }) => { + })) => { match current_count.checked_sub(values.len().try_into().unwrap()) { Some(x) => current_count = x, None => { tracing::debug!(%peer, "Too many storage diffs: {} > {}", values.len(), current_count); - return Err(IncorrectStateDiffCount(peer)); + return Err(StateDiffsError::IncorrectStateDiffCount(peer)); } } let address = ContractAddress(address.0); @@ -416,7 +423,7 @@ impl BlockClient for Client { Some(x) => current_count = x, None => { tracing::debug!(%peer, "Too many nonce updates"); - return Err(IncorrectStateDiffCount(peer)); + return Err(StateDiffsError::IncorrectStateDiffCount(peer)); } } update.nonce = Some(ContractNonce(nonce)); @@ -427,22 +434,22 @@ impl BlockClient for Client { Some(x) => current_count = x, None => { tracing::debug!(%peer, "Too many deployed contracts"); - return Err(IncorrectStateDiffCount(peer)); + return Err(StateDiffsError::IncorrectStateDiffCount(peer)); } } update.class = Some(ContractClassUpdate::Deploy(class_hash)); } } } - StateDiffsResponse::DeclaredClass(DeclaredClass { + Ok(StateDiffsResponse::DeclaredClass(DeclaredClass { class_hash, compiled_class_hash, - }) => { + })) => { match current_count.checked_sub(1) { Some(x) => current_count = x, None => { tracing::debug!(%peer, "Too many declared classes"); - return Err(IncorrectStateDiffCount(peer)); + return Err(StateDiffsError::IncorrectStateDiffCount(peer)); } } if let Some(compiled_class_hash) = compiled_class_hash { @@ -455,13 +462,17 @@ impl BlockClient for Client { .insert(ClassHash(class_hash.0)); } } - StateDiffsResponse::Fin => { + Ok(StateDiffsResponse::Fin) => { if current_count != 0 { tracing::debug!(%peer, "Too few storage diffs"); - return Err(IncorrectStateDiffCount(peer)); + return Err(StateDiffsError::IncorrectStateDiffCount(peer)); } return Ok(Some((peer, state_diff))); } + Err(error) => { + tracing::debug!(%peer, %error, "State diff response stream failed"); + return Err(StateDiffsError::ResponseStreamFailure(peer, error)); + } } } } @@ -500,10 +511,10 @@ impl BlockClient for Client { while let Some(resp) = stream.next().await { match resp { - ClassesResponse::Class(p2p_proto::class::Class::Cairo0 { + Ok(ClassesResponse::Class(p2p_proto::class::Class::Cairo0 { class, domain: _, - }) => { + })) => { let definition = CairoDefinition::try_from_dto(class) .map_err(|_| ClassDefinitionsError::CairoDefinitionError(peer))?; class_definitions.push(ClassDefinition::Cairo { @@ -511,10 +522,10 @@ impl BlockClient for Client { definition: definition.0, }); } - ClassesResponse::Class(p2p_proto::class::Class::Cairo1 { + Ok(ClassesResponse::Class(p2p_proto::class::Class::Cairo1 { class, domain: _, - }) => { + })) => { let definition = SierraDefinition::try_from_dto(class) .map_err(|_| ClassDefinitionsError::SierraDefinitionError(peer))?; class_definitions.push(ClassDefinition::Sierra { @@ -522,10 +533,15 @@ impl BlockClient for Client { sierra_definition: definition.0, }); } - ClassesResponse::Fin => { + Ok(ClassesResponse::Fin) => { tracing::debug!(%peer, "Received FIN in class definitions source"); break; } + Err(error) => { + tracing::debug!(%peer, %error, "Class definition + response stream failed"); + return Err(ClassDefinitionsError::ResponseStreamFailure(peer, error)); + } } current_count = match current_count.checked_sub(1) { @@ -551,7 +567,10 @@ impl BlockClient for Client { async fn events_for_block( self, block: BlockNumber, - ) -> Option<(PeerId, impl Stream)> { + ) -> Option<( + PeerId, + impl Stream>, + )> { let request = EventsRequest { iteration: Iteration { start: block.get().into(), @@ -574,13 +593,17 @@ impl BlockClient for Client { }; let stream = stream - .take_while(|x| std::future::ready(!matches!(x, &EventsResponse::Fin))) - .map(|x| match x { - EventsResponse::Fin => unreachable!("Already handled Fin above"), - EventsResponse::Event(event) => ( + .try_take_while(|x| std::future::ready(Ok(!matches!(x, &EventsResponse::Fin)))) + .map(move |x| match x { + Ok(EventsResponse::Fin) => unreachable!("Already handled Fin above"), + Ok(EventsResponse::Event(event)) => Ok(( TransactionHash(event.transaction_hash.0), Event::from_dto(event), - ), + )), + Err(error) => { + tracing::debug!(%peer, %error, "Events response stream failed"); + Err(EventsResponseStreamFailure(peer, error)) + } }); return Some((peer, stream)); @@ -747,7 +770,8 @@ mod transaction_stream { ) -> impl Stream> where PF: Future> + Send, - RF: Future>> + Send, + RF: Future>>> + + Send, { tracing::trace!(?start, ?stop, "Streaming Transactions"); @@ -825,14 +849,14 @@ mod transaction_stream { /// Return None if the caller should move to the next peer fn handle_response( peer: PeerId, - response: TransactionsResponse, + response: std::io::Result, txn_idx: TransactionIndex, ) -> Option<(TransactionVariant, Receipt)> { match response { - TransactionsResponse::TransactionWithReceipt(TransactionWithReceipt { + Ok(TransactionsResponse::TransactionWithReceipt(TransactionWithReceipt { transaction, receipt, - }) => { + })) => { if let (Ok(t), Ok(r)) = ( TransactionVariant::try_from_dto(transaction), Receipt::try_from((receipt, txn_idx)), @@ -844,10 +868,14 @@ mod transaction_stream { None } } - TransactionsResponse::Fin => { + Ok(TransactionsResponse::Fin) => { // This peer will not give us more blocks, move to the next peer None } + Err(error) => { + tracing::debug!(%peer, %error, "Transaction response stream failed"); + None + } } } @@ -923,7 +951,8 @@ mod state_diff_stream { ) -> impl Stream> where PF: Future> + Send, - RF: Future>> + Send, + RF: Future>>> + + Send, { tracing::trace!(?start, ?stop, "Streaming state diffs"); @@ -999,18 +1028,18 @@ mod state_diff_stream { /// Returns None if the caller should move to the next peer fn handle_response( peer: PeerId, - response: StateDiffsResponse, + response: std::io::Result, state_diff: &mut StateUpdateData, progress: &mut BlockProgress, ) -> Option<()> { match response { - StateDiffsResponse::ContractDiff(ContractDiff { + Ok(StateDiffsResponse::ContractDiff(ContractDiff { address, nonce, class_hash, values, domain: _, - }) => { + })) => { let address = ContractAddress(address.0); progress.checked_sub_assign(values.len())?; @@ -1047,10 +1076,10 @@ mod state_diff_stream { } } } - StateDiffsResponse::DeclaredClass(DeclaredClass { + Ok(StateDiffsResponse::DeclaredClass(DeclaredClass { class_hash, compiled_class_hash, - }) => { + })) => { progress.checked_sub_assign(1)?; if let Some(compiled_class_hash) = compiled_class_hash { @@ -1063,10 +1092,14 @@ mod state_diff_stream { .insert(ClassHash(class_hash.0)); } } - StateDiffsResponse::Fin => { + Ok(StateDiffsResponse::Fin) => { tracing::debug!(%peer, "Received FIN, continuing with next peer"); return None; } + Err(error) => { + tracing::debug!(%peer, %error, "State diff response stream failed"); + return None; + } } Some(()) @@ -1138,7 +1171,8 @@ mod class_definition_stream { ) -> impl Stream> where PF: Future> + Send, - RF: Future>> + Send, + RF: Future>>> + + Send, { tracing::trace!(?start, ?stop, "Streaming classes"); @@ -1232,11 +1266,11 @@ mod class_definition_stream { /// Returns `None` if the caller should move to the next peer fn handle_response( peer: PeerId, - response: ClassesResponse, + response: std::io::Result, block_number: BlockNumber, ) -> Option { match response { - ClassesResponse::Class(p2p_proto::class::Class::Cairo0 { class, domain: _ }) => { + Ok(ClassesResponse::Class(p2p_proto::class::Class::Cairo0 { class, domain: _ })) => { let Ok(CairoDefinition(definition)) = CairoDefinition::try_from_dto(class) else { // TODO punish the peer tracing::debug!(%peer, "Cairo definition failed to parse"); @@ -1248,7 +1282,7 @@ mod class_definition_stream { definition, }) } - ClassesResponse::Class(p2p_proto::class::Class::Cairo1 { class, domain: _ }) => { + Ok(ClassesResponse::Class(p2p_proto::class::Class::Cairo1 { class, domain: _ })) => { let Ok(SierraDefinition(definition)) = SierraDefinition::try_from_dto(class) else { // TODO punish the peer tracing::debug!(%peer, "Sierra definition failed to parse"); @@ -1260,10 +1294,14 @@ mod class_definition_stream { sierra_definition: definition, }) } - ClassesResponse::Fin => { + Ok(ClassesResponse::Fin) => { tracing::debug!(%peer, "Received FIN, continuing with next peer"); None } + Err(error) => { + tracing::debug!(%peer, %error, "Class definition response stream failed"); + None + } } } @@ -1318,7 +1356,8 @@ mod event_stream { ) -> impl Stream> where PF: Future> + Send, - RF: Future>> + Send, + RF: Future>>> + + Send, { tracing::trace!(?start, ?stop, "Streaming events"); @@ -1414,12 +1453,12 @@ mod event_stream { /// Returns true if the caller should move to the next peer fn handle_response( peer: PeerId, - response: EventsResponse, + response: std::io::Result, current_txn: &mut Option, events: &mut Vec<(TransactionHash, Vec)>, ) -> bool { match response { - EventsResponse::Event(event) => { + Ok(EventsResponse::Event(event)) => { let txn_hash = TransactionHash(event.transaction_hash.0); let Ok(event) = Event::try_from_dto(event) else { // TODO punish the peer @@ -1441,10 +1480,14 @@ mod event_stream { false } - EventsResponse::Fin => { + Ok(EventsResponse::Fin) => { tracing::debug!(%peer, "Received FIN, continuing with next peer"); true } + Err(error) => { + tracing::debug!(%peer, %error, "Event response stream failed"); + true + } } } diff --git a/crates/p2p/src/client/peer_agnostic/fixtures.rs b/crates/p2p/src/client/peer_agnostic/fixtures.rs index ae1e56da3f..f2b5f1d047 100644 --- a/crates/p2p/src/client/peer_agnostic/fixtures.rs +++ b/crates/p2p/src/client/peer_agnostic/fixtures.rs @@ -84,13 +84,13 @@ pub fn unzip_fixtures( #[allow(clippy::type_complexity)] pub async fn send_request( responses: Arc, TestPeer>>>>, -) -> anyhow::Result> { +) -> anyhow::Result>> { let mut guard = responses.lock().await; match guard.pop_front() { Some(Ok(responses)) => { let (mut tx, rx) = mpsc::channel(responses.len() + 1); for r in responses { - tx.send(r).await.unwrap(); + tx.send(Ok(r)).await.unwrap(); } Ok(rx) } diff --git a/crates/p2p/src/client/peer_agnostic/traits.rs b/crates/p2p/src/client/peer_agnostic/traits.rs index f8c6bf1619..c1e6fc3a13 100644 --- a/crates/p2p/src/client/peer_agnostic/traits.rs +++ b/crates/p2p/src/client/peer_agnostic/traits.rs @@ -9,8 +9,9 @@ use crate::client::types::{ ClassDefinition, ClassDefinitionsError, EventsForBlockByTransaction, - IncorrectStateDiffCount, + EventsResponseStreamFailure, Receipt, + StateDiffsError, TransactionData, }; use crate::PeerData; @@ -89,7 +90,7 @@ pub trait BlockClient { self, block: BlockNumber, state_diff_length: u64, - ) -> impl Future, IncorrectStateDiffCount>> + Send; + ) -> impl Future, StateDiffsError>> + Send; fn class_definitions_for_block( self, @@ -100,5 +101,10 @@ pub trait BlockClient { fn events_for_block( self, block: BlockNumber, - ) -> impl Future + Send)>> + Send; + ) -> impl Future< + Output = Option<( + PeerId, + impl Stream> + Send, + )>, + > + Send; } diff --git a/crates/p2p/src/client/types.rs b/crates/p2p/src/client/types.rs index 9c23994685..4ce8427e00 100644 --- a/crates/p2p/src/client/types.rs +++ b/crates/p2p/src/client/types.rs @@ -121,11 +121,21 @@ impl TryFromDto for SignedBlockHeader { } #[derive(Debug)] -pub struct IncorrectStateDiffCount(pub PeerId); +pub enum StateDiffsError { + IncorrectStateDiffCount(PeerId), + ResponseStreamFailure(PeerId, std::io::Error), +} -impl std::fmt::Display for IncorrectStateDiffCount { +impl std::fmt::Display for StateDiffsError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "Incorrect state diff count from peer {}", self.0) + match self { + StateDiffsError::IncorrectStateDiffCount(peer) => { + write!(f, "Incorrect state diff count from peer {}", peer) + } + StateDiffsError::ResponseStreamFailure(peer, err) => { + write!(f, "Failed to read state diffs from peer {}: {}", peer, err) + } + } } } @@ -134,6 +144,7 @@ pub enum ClassDefinitionsError { IncorrectClassDefinitionCount(PeerId), CairoDefinitionError(PeerId), SierraDefinitionError(PeerId), + ResponseStreamFailure(PeerId, std::io::Error), } impl std::fmt::Display for ClassDefinitionsError { @@ -148,6 +159,22 @@ impl std::fmt::Display for ClassDefinitionsError { ClassDefinitionsError::SierraDefinitionError(peer) => { write!(f, "Sierra class definition error from peer {}", peer) } + ClassDefinitionsError::ResponseStreamFailure(peer, err) => { + write!( + f, + "Failed to read class definitions from peer {}: {}", + peer, err + ) + } } } } + +#[derive(Debug)] +pub struct EventsResponseStreamFailure(pub PeerId, pub std::io::Error); + +impl std::fmt::Display for EventsResponseStreamFailure { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Failed to read events from peer {}: {}", self.0, self.1) + } +} diff --git a/crates/p2p/src/tests.rs b/crates/p2p/src/tests.rs index fd483f383c..0c643ce430 100644 --- a/crates/p2p/src/tests.rs +++ b/crates/p2p/src/tests.rs @@ -1071,7 +1071,8 @@ macro_rules! define_test { let actual_response = rx .next() .await - .expect(&format!("receiving actual response, line: {}", line!())); + .expect(&format!("receiving actual response, line: {}", line!())) + .expect(&format!("response should be Ok(), line: {}", line!())); // See if they match assert_eq!( expected_response, diff --git a/crates/pathfinder/src/sync/track.rs b/crates/pathfinder/src/sync/track.rs index 98b27073e8..2bc6a5c910 100644 --- a/crates/pathfinder/src/sync/track.rs +++ b/crates/pathfinder/src/sync/track.rs @@ -9,7 +9,8 @@ use p2p::client::peer_agnostic::Client as P2PClient; use p2p::client::types::{ ClassDefinition as P2PClassDefinition, ClassDefinitionsError, - IncorrectStateDiffCount, + EventsResponseStreamFailure, + StateDiffsError, TransactionData, }; use p2p::PeerData; @@ -461,13 +462,21 @@ impl

EventSource

{ // Receive the exact amount of expected events for this block. for _ in 0..event_count { - let Some((tx_hash, event)) = events.next().await else { - let err = PeerData::new(peer, SyncError2::TooFewEvents); - let _ = tx.send(Err(err)).await; - return; - }; - - block_events.entry(tx_hash).or_default().push(event); + match events.next().await { + Some(Ok((tx_hash, event))) => { + block_events.entry(tx_hash).or_default().push(event); + } + Some(Err(_)) => { + let err = PeerData::new(peer, SyncError2::InvalidDto); + let _ = tx.send(Err(err)).await; + return; + } + None => { + let err = PeerData::new(peer, SyncError2::TooFewEvents); + let _ = tx.send(Err(err)).await; + return; + } + } } // Ensure that the stream is exhausted. @@ -529,11 +538,16 @@ impl

StateDiffSource

{ match state_diff { Ok(Some(state_diff)) => break state_diff, Ok(None) => {} - Err(IncorrectStateDiffCount(peer)) => { + Err(StateDiffsError::IncorrectStateDiffCount(peer)) => { let err = PeerData::new(peer, SyncError2::IncorrectStateDiffCount); let _ = tx.send(Err(err)).await; return; } + Err(StateDiffsError::ResponseStreamFailure(peer, error)) => { + let err = PeerData::new(peer, SyncError2::InvalidDto); + let _ = tx.send(Err(err)).await; + return; + } } }; @@ -601,6 +615,9 @@ impl

ClassSource

{ ClassDefinitionsError::SierraDefinitionError(peer) => { PeerData::new(peer, SyncError2::SierraDefinitionError) } + ClassDefinitionsError::ResponseStreamFailure(peer, error) => { + PeerData::new(peer, SyncError2::InvalidDto) + } }; let _ = tx.send(Err(err)).await; return; @@ -824,8 +841,9 @@ mod tests { use p2p::client::types::{ ClassDefinition, ClassDefinitionsError, - IncorrectStateDiffCount, + EventsResponseStreamFailure, Receipt as P2PReceipt, + StateDiffsError, }; use p2p::libp2p::PeerId; use p2p::PeerData; @@ -991,7 +1009,7 @@ mod tests { self, block: BlockNumber, state_diff_length: u64, - ) -> Result, IncorrectStateDiffCount> { + ) -> Result, StateDiffsError> { let sd: StateUpdateData = self .blocks .iter() @@ -1039,7 +1057,10 @@ mod tests { async fn events_for_block( self, block: BlockNumber, - ) -> Option<(PeerId, impl Stream + Send)> { + ) -> Option<( + PeerId, + impl Stream> + Send, + )> { let e = self .blocks .iter() @@ -1048,6 +1069,7 @@ mod tests { .transaction_data .iter() .flat_map(|(t, _, e)| e.iter().map(move |e| (t.hash, e.clone()))) + .map(Ok) .collect::>(); Some((PeerId::random(), stream::iter(e))) From 5c785a0fcc8e46582605582d3f063be56dcd9f94 Mon Sep 17 00:00:00 2001 From: Krzysztof Lis Date: Sun, 15 Sep 2024 15:22:57 +0200 Subject: [PATCH 060/282] chore: depsort, clippy, doc --- crates/p2p/src/sync.rs | 4 ++-- crates/p2p/src/tests.rs | 26 +++++++++++++++----------- crates/p2p_stream/Cargo.toml | 2 +- crates/p2p_stream/tests/utils/mod.rs | 6 +----- 4 files changed, 19 insertions(+), 19 deletions(-) diff --git a/crates/p2p/src/sync.rs b/crates/p2p/src/sync.rs index ac6fff98d2..6aaf592db3 100644 --- a/crates/p2p/src/sync.rs +++ b/crates/p2p/src/sync.rs @@ -105,8 +105,8 @@ pub(crate) mod codec { /// An enum to prevent _generic parameter explosion_ in the outer /// behaviour. /// - /// [`SyncCodec::ForTest`] falls back to [`SyncCodec::Prod`] unless the - /// caller explicitly sets a read/write factory. + /// `SyncCodec::ForTest` falls back to [`SyncCodec::Prod`] unless the caller + /// explicitly sets a read/write factory. #[derive(Clone)] pub enum SyncCodec { Prod(ProdCodec), diff --git a/crates/p2p/src/tests.rs b/crates/p2p/src/tests.rs index 0c643ce430..bb2835be76 100644 --- a/crates/p2p/src/tests.rs +++ b/crates/p2p/src/tests.rs @@ -1187,24 +1187,28 @@ async fn always_propagate_stream_errors_to_caller() { .client .send_headers_sync_request(peer1.peer_id, expected_request) .await - .expect(&format!( - "sending request using: {}, line: {}", - // std::stringify!($req_fn), - "TODO", - line!() - )); + .unwrap_or_else(|_| { + panic!( + "sending request using: {}, line: {}", + // std::stringify!($req_fn), + "TODO", + line!() + ) + }); // Peer1 waits for response channel to be ready - let mut tx = tx_ready.recv().await.expect(&format!( - "waiting for response channel to be ready, line: {}", - line!() - )); + let mut tx = tx_ready.recv().await.unwrap_or_else(|| { + panic!( + "waiting for response channel to be ready, line: {}", + line!() + ) + }); let expected_response = Faker.fake::(); // Peer1 sends 1 response, but peer2's codec is mocked to fail upon reception tx.send(expected_response.clone()) .await - .expect(&format!("sending expected response, line: {}", line!())); // Peer2 waits for the response + .unwrap_or_else(|_| panic!("sending expected response, line: {}", line!())); // Peer2 waits for the response // TODO this should be an error let _actual_response = rx.next().await.unwrap(); diff --git a/crates/p2p_stream/Cargo.toml b/crates/p2p_stream/Cargo.toml index 9902c982a1..c198dea324 100644 --- a/crates/p2p_stream/Cargo.toml +++ b/crates/p2p_stream/Cargo.toml @@ -25,5 +25,5 @@ libp2p = { workspace = true, features = ["identify", "noise", "tcp", "tokio", "y libp2p-plaintext = { workspace = true } libp2p-swarm-test = { workspace = true } rstest = { workspace = true } -tokio = { workspace = true, features = ["macros", "time"] } test-log = { workspace = true, features = ["trace"] } +tokio = { workspace = true, features = ["macros", "time"] } diff --git a/crates/p2p_stream/tests/utils/mod.rs b/crates/p2p_stream/tests/utils/mod.rs index 402fb1750e..6ad1214d0e 100644 --- a/crates/p2p_stream/tests/utils/mod.rs +++ b/crates/p2p_stream/tests/utils/mod.rs @@ -207,11 +207,7 @@ pub fn new_swarm_with_timeout( // SwarmExt::new_ephemeral uses async::std let swarm = new_ephemeral_with_tokio_executor(|_| { - p2p_stream::Behaviour::::with_codec_and_protocols( - TestCodec::default(), - protocols, - cfg, - ) + p2p_stream::Behaviour::::with_codec_and_protocols(TestCodec, protocols, cfg) }); let peed_id = *swarm.local_peer_id(); From 5cd74bfb39cade9e9274ff4da501dfb4a34f8f27 Mon Sep 17 00:00:00 2001 From: Krzysztof Lis Date: Sun, 15 Sep 2024 15:42:50 +0200 Subject: [PATCH 061/282] test(p2p): always propagate codec error to caller --- crates/p2p/src/tests.rs | 531 +++++++++++++++++++++++++--------------- 1 file changed, 337 insertions(+), 194 deletions(-) diff --git a/crates/p2p/src/tests.rs b/crates/p2p/src/tests.rs index bb2835be76..5ec9061d74 100644 --- a/crates/p2p/src/tests.rs +++ b/crates/p2p/src/tests.rs @@ -1003,213 +1003,356 @@ async fn subscription_and_propagation(#[case] peers: (TestPeer, TestPeer)) { assert_eq!(msg, expected); } -/// Defines a sync test case named [`$test_name`], where there are 2 peers: -/// - peer2 sends a request to peer1 -/// - peer1 responds with a random number of responses -/// - request is of type [`$req_type`] and is sent using [`$req_fn`] -/// - response is of type [`$res_type`] -/// - [`$event_variant`] is the event that tells peer1 that it received peer2's -/// request -macro_rules! define_test { - ($test_name:ident, $req_type:ty, $res_type:ty, $event_variant:ident, $req_fn:ident) => { - #[rstest] - #[case::server_to_client(server_to_client().await)] - #[case::client_to_server(client_to_server().await)] - #[test_log::test(tokio::test)] - async fn $test_name(#[case] peers: (TestPeer, TestPeer)) { - let _ = env_logger::builder().is_test(true).try_init(); - let (peer1, peer2) = peers; - // Fake some request for peer2 to send to peer1 - let expected_request = Faker.fake::<$req_type>(); - - // Filter peer1's events to fish out the request from peer2 and the channel that - // peer1 will use to send the responses - // This is also to keep peer1's event loop going - let mut tx_ready = filter_events(peer1.event_receiver, move |event| match event { - Event::$event_variant { - from, - channel, - request: actual_request, - } => { - // Peer 1 should receive the request from peer2 - assert_eq!(from, peer2.peer_id); - // Received request should match what peer2 sent - assert_eq!(expected_request, actual_request); - Some(channel) - } - _ => None, - }); - - // This is to keep peer2's event loop going - consume_all_events_forever(peer2.event_receiver); - - // Peer2 sends the request to peer1, and waits for the response receiver - let mut rx = peer2 - .client - .$req_fn(peer1.peer_id, expected_request) - .await - .expect(&format!( - "sending request using: {}, line: {}", - std::stringify!($req_fn), +mod successful_sync { + use super::*; + + /// Defines a test case named [`$test_name`], where there are 2 peers: + /// - peer2 sends a request to peer1 + /// - peer1 responds with a random number of responses + /// - request is of type [`$req_type`] and is sent using [`$req_fn`] + /// - response is of type [`$res_type`] + /// - [`$event_variant`] is the event that tells peer1 that it received + /// peer2's request + macro_rules! define_test { + ($test_name:ident, $req_type:ty, $res_type:ty, $event_variant:ident, $req_fn:ident) => { + #[rstest] + #[case::server_to_client(server_to_client().await)] + #[case::client_to_server(client_to_server().await)] + #[test_log::test(tokio::test)] + async fn $test_name(#[case] peers: (TestPeer, TestPeer)) { + let _ = env_logger::builder().is_test(true).try_init(); + let (peer1, peer2) = peers; + // Fake some request for peer2 to send to peer1 + let expected_request = Faker.fake::<$req_type>(); + + // Filter peer1's events to fish out the request from peer2 and the channel that + // peer1 will use to send the responses + // This is also to keep peer1's event loop going + let mut tx_ready = filter_events(peer1.event_receiver, move |event| match event { + Event::$event_variant { + from, + channel, + request: actual_request, + } => { + // Peer 1 should receive the request from peer2 + assert_eq!(from, peer2.peer_id); + // Received request should match what peer2 sent + assert_eq!(expected_request, actual_request); + Some(channel) + } + _ => None, + }); + + // This is to keep peer2's event loop going + consume_all_events_forever(peer2.event_receiver); + + // Peer2 sends the request to peer1, and waits for the response receiver + let mut rx = peer2 + .client + .$req_fn(peer1.peer_id, expected_request) + .await + .expect(&format!( + "sending request using: {}, line: {}", + std::stringify!($req_fn), + line!() + )); + + // Peer1 waits for response channel to be ready + let mut tx = tx_ready.recv().await.expect(&format!( + "waiting for response channel to be ready, line: {}", line!() )); - // Peer1 waits for response channel to be ready - let mut tx = tx_ready.recv().await.expect(&format!( - "waiting for response channel to be ready, line: {}", - line!() - )); - - // Peer1 sends a random number of responses to Peer2 - for _ in 0usize..(1..100).fake() { - let expected_response = Faker.fake::<$res_type>(); - // Peer1 sends the response - tx.send(expected_response.clone()) - .await - .expect(&format!("sending expected response, line: {}", line!())); - // Peer2 waits for the response - let actual_response = rx - .next() - .await - .expect(&format!("receiving actual response, line: {}", line!())) - .expect(&format!("response should be Ok(), line: {}", line!())); - // See if they match - assert_eq!( - expected_response, - actual_response, - "response mismatch, line: {}", - line!() - ); + // Peer1 sends a random number of responses to Peer2 + for _ in 0usize..(1..100).fake() { + let expected_response = Faker.fake::<$res_type>(); + // Peer1 sends the response + tx.send(expected_response.clone()) + .await + .expect(&format!("sending expected response, line: {}", line!())); + // Peer2 waits for the response + let actual_response = rx + .next() + .await + .expect(&format!("receiving actual response, line: {}", line!())) + .expect(&format!("response should be Ok(), line: {}", line!())); + // See if they match + assert_eq!( + expected_response, + actual_response, + "response mismatch, line: {}", + line!() + ); + } } - } - }; + }; + } + + define_test!( + sync_headers, + BlockHeadersRequest, + BlockHeadersResponse, + InboundHeadersSyncRequest, + send_headers_sync_request + ); + + define_test!( + sync_classes, + ClassesRequest, + ClassesResponse, + InboundClassesSyncRequest, + send_classes_sync_request + ); + + define_test!( + sync_state_diffs, + StateDiffsRequest, + StateDiffsResponse, + InboundStateDiffsSyncRequest, + send_state_diffs_sync_request + ); + + define_test!( + sync_transactions, + TransactionsRequest, + TransactionsResponse, + InboundTransactionsSyncRequest, + send_transactions_sync_request + ); + + define_test!( + sync_events, + EventsRequest, + EventsResponse, + InboundEventsSyncRequest, + send_events_sync_request + ); } -define_test!( - sync_headers, - BlockHeadersRequest, - BlockHeadersResponse, - InboundHeadersSyncRequest, - send_headers_sync_request -); - -define_test!( - sync_classes, - ClassesRequest, - ClassesResponse, - InboundClassesSyncRequest, - send_classes_sync_request -); - -define_test!( - sync_state_diffs, - StateDiffsRequest, - StateDiffsResponse, - InboundStateDiffsSyncRequest, - send_state_diffs_sync_request -); - -define_test!( - sync_transactions, - TransactionsRequest, - TransactionsResponse, - InboundTransactionsSyncRequest, - send_transactions_sync_request -); - -define_test!( - sync_events, - EventsRequest, - EventsResponse, - InboundEventsSyncRequest, - send_events_sync_request -); +mod propagate_codec_errors_to_caller { + use super::*; + use crate::test_utils::sync::TypeErasedReadFactory; -#[test_log::test(tokio::test)] -async fn always_propagate_stream_errors_to_caller() { - let mut peer1 = TestPeer::default(); + enum BadPeer { + Server, + Client, + } - let codec = codec::Headers::for_test().set_read_response_factory(Box::new(move || { - Box::new(|_| { - async { - Err(std::io::Error::new( - std::io::ErrorKind::Other, - "stream error", + enum BadCodec { + Headers, + Transactions, + StateDiffs, + Classes, + Events, + } + + fn error_factory() -> TypeErasedReadFactory { + Box::new(|| { + Box::new(|_| { + async { + Err(std::io::Error::new( + std::io::ErrorKind::Other, + "stream error", + )) + } + .boxed() + }) + }) + } + + async fn create_peers(bad_peer: BadPeer, bad_codec: BadCodec) -> (TestPeer, TestPeer) { + let good = TestPeer::default(); + + let keypair = Keypair::generate_ed25519(); + let cfg = Config::for_test(); + let chain_id = ChainId::SEPOLIA_TESTNET; + + let bb = crate::behaviour::Builder::new(keypair.clone(), chain_id, cfg.clone()); + let bb = match bad_codec { + BadCodec::Headers => bb.header_sync_behaviour(p2p_stream::Behaviour::with_codec( + codec::Headers::for_test().set_read_response_factory(error_factory()), + Default::default(), + )), + BadCodec::Transactions => { + bb.transaction_sync_behaviour(p2p_stream::Behaviour::with_codec( + codec::Transactions::for_test().set_read_response_factory(error_factory()), + Default::default(), )) } - .boxed() - }) - })); - let keypair = Keypair::generate_ed25519(); - let cfg = Config::for_test(); - let chain_id = ChainId::SEPOLIA_TESTNET; - let behaviour_builder = crate::behaviour::Builder::new(keypair.clone(), chain_id, cfg.clone()) - .header_sync_behaviour(p2p_stream::Behaviour::with_codec(codec, Default::default())); - let p2p_builder = crate::Builder::new(keypair.clone(), cfg.clone(), chain_id) - .behaviour_builder(behaviour_builder); - let peer2 = TestPeer::builder() - .p2p_builder(p2p_builder) - .build(keypair, cfg); - - let server_addr = peer1.start_listening().await.unwrap(); - - tracing::info!(%peer1.peer_id, %server_addr, "Server"); - tracing::info!(%peer2.peer_id, "Client"); - - peer2.client.dial(peer1.peer_id, server_addr).await.unwrap(); - - // Fake some request for peer2 to send to peer1 - let expected_request = Faker.fake::(); - - // Filter peer1's events to fish out the request from peer2 and the channel that - // peer1 will use to send the responses - // This is also to keep peer1's event loop going - let mut tx_ready = filter_events(peer1.event_receiver, move |event| match event { - Event::InboundHeadersSyncRequest { - from, - channel, - request: actual_request, - } => { - // Peer 1 should receive the request from peer2 - assert_eq!(from, peer2.peer_id); - // Received request should match what peer2 sent - assert_eq!(expected_request, actual_request); - Some(channel) - } - _ => None, - }); + BadCodec::StateDiffs => { + bb.state_diff_sync_behaviour(p2p_stream::Behaviour::with_codec( + codec::StateDiffs::for_test().set_read_response_factory(error_factory()), + Default::default(), + )) + } + BadCodec::Classes => bb.class_sync_behaviour(p2p_stream::Behaviour::with_codec( + codec::Classes::for_test().set_read_response_factory(error_factory()), + Default::default(), + )), + BadCodec::Events => bb.event_sync_behaviour(p2p_stream::Behaviour::with_codec( + codec::Events::for_test().set_read_response_factory(error_factory()), + Default::default(), + )), + }; + + let p2p_builder = + crate::Builder::new(keypair.clone(), cfg.clone(), chain_id).behaviour_builder(bb); + let bad = TestPeer::builder() + .p2p_builder(p2p_builder) + .build(keypair, cfg); + + let (mut server, client) = match bad_peer { + BadPeer::Server => (bad, good), + BadPeer::Client => (good, bad), + }; + + let server_addr = server.start_listening().await.unwrap(); + + tracing::info!(%server.peer_id, %server_addr, "Server"); + tracing::info!(%client.peer_id, "Client"); + + client + .client + .dial(server.peer_id, server_addr) + .await + .unwrap(); - // This is to keep peer2's event loop going - consume_all_events_forever(peer2.event_receiver); + (server, client) + } - // Peer2 sends the request to peer1, and waits for the response receiver - let mut rx = peer2 - .client - .send_headers_sync_request(peer1.peer_id, expected_request) - .await - .unwrap_or_else(|_| { - panic!( - "sending request using: {}, line: {}", - // std::stringify!($req_fn), - "TODO", - line!() - ) - }); + async fn server_to_bad_client(bad_codec: BadCodec) -> (TestPeer, TestPeer) { + create_peers(BadPeer::Client, bad_codec).await + } - // Peer1 waits for response channel to be ready - let mut tx = tx_ready.recv().await.unwrap_or_else(|| { - panic!( - "waiting for response channel to be ready, line: {}", - line!() - ) - }); + async fn client_to_bad_server(bad_codec: BadCodec) -> (TestPeer, TestPeer) { + let (s, c) = create_peers(BadPeer::Server, bad_codec).await; + (c, s) + } - let expected_response = Faker.fake::(); - // Peer1 sends 1 response, but peer2's codec is mocked to fail upon reception - tx.send(expected_response.clone()) - .await - .unwrap_or_else(|_| panic!("sending expected response, line: {}", line!())); // Peer2 waits for the response + /// Defines a test case named [`$test_name`], where there are 2 peers: + /// - peer2 sends a request to peer1 + /// - peer1 responds with a random response + /// - peer2's codec is mocked to fail upon reception, simulating peer1 + /// sending garbage in response + /// - request is of type [`$req_type`] and is sent using [`$req_fn`] + /// - response is of type [`$res_type`] + /// - [`$event_variant`] is the event that tells peer1 that it received + /// peer2's request + /// - [`$bad_codec`] is the codec that will be mocked to fail upon reception + macro_rules! define_test { + ($test_name:ident, $req_type:ty, $res_type:ty, $event_variant:ident, $req_fn:ident, $bad_codec:expr) => { + #[rstest] + #[case::server_to_client(server_to_bad_client($bad_codec).await)] + #[case::client_to_server(client_to_bad_server($bad_codec).await)] + #[test_log::test(tokio::test)] + async fn $test_name(#[case] peers: (TestPeer, TestPeer)) { + let (peer1, peer2) = peers; + + // Fake some request for peer2 to send to peer1 + let expected_request = Faker.fake::<$req_type>(); + + // Filter peer1's events to fish out the request from peer2 and the channel that + // peer1 will use to send the responses + // This is also to keep peer1's event loop going + let mut tx_ready = filter_events(peer1.event_receiver, move |event| match event { + Event::$event_variant { + from, + channel, + request: actual_request, + } => { + // Peer 1 should receive the request from peer2 + assert_eq!(from, peer2.peer_id); + // Received request should match what peer2 sent + assert_eq!(expected_request, actual_request); + Some(channel) + } + _ => None, + }); + + // This is to keep peer2's event loop going + consume_all_events_forever(peer2.event_receiver); + + // Peer2 sends the request to peer1, and waits for the response receiver + let mut rx = peer2 + .client + .$req_fn(peer1.peer_id, expected_request) + .await + .unwrap_or_else(|_| { + panic!( + "sending request using: {}, line: {}", + std::stringify!($req_fn), + // "TODO", + line!() + ) + }); + + // Peer1 waits for response channel to be ready + let mut tx = tx_ready.recv().await.unwrap_or_else(|| { + panic!( + "waiting for response channel to be ready, line: {}", + line!() + ) + }); - // TODO this should be an error - let _actual_response = rx.next().await.unwrap(); + let expected_response = Faker.fake::<$res_type>(); + // Peer1 sends 1 response, but peer2's codec is mocked to fail upon reception + // simulating peer1 sending garbage in response + tx.send(expected_response.clone()) + .await + .unwrap_or_else(|_| panic!("sending expected response, line: {}", line!())); + + // Peer2 waits for the response + let actual_response = rx.next().await.unwrap(); + eprintln!("actual_response {:?}", actual_response); + assert!( + matches!(actual_response, Err(e) if e.kind() == std::io::ErrorKind::Other && e.to_string() == "stream error") + ); + } + }; + } + + define_test!( + sync_headers, + BlockHeadersRequest, + BlockHeadersResponse, + InboundHeadersSyncRequest, + send_headers_sync_request, + BadCodec::Headers + ); + + define_test!( + sync_classes, + ClassesRequest, + ClassesResponse, + InboundClassesSyncRequest, + send_classes_sync_request, + BadCodec::Classes + ); + + define_test!( + sync_state_diffs, + StateDiffsRequest, + StateDiffsResponse, + InboundStateDiffsSyncRequest, + send_state_diffs_sync_request, + BadCodec::StateDiffs + ); + + define_test!( + sync_transactions, + TransactionsRequest, + TransactionsResponse, + InboundTransactionsSyncRequest, + send_transactions_sync_request, + BadCodec::Transactions + ); + + define_test!( + sync_events, + EventsRequest, + EventsResponse, + InboundEventsSyncRequest, + send_events_sync_request, + BadCodec::Events + ); } From aeb94beb937a2e2c5771b3529f38641e4ba7bef2 Mon Sep 17 00:00:00 2001 From: Krzysztof Lis Date: Thu, 29 Aug 2024 15:37:23 +0200 Subject: [PATCH 062/282] feat: update libp2p to 0.54.1 --- Cargo.lock | 289 ++++++++++++++++++++++------------- Cargo.toml | 2 +- crates/p2p/src/behaviour.rs | 11 +- crates/p2p/src/main_loop.rs | 5 +- crates/p2p_stream/src/lib.rs | 2 + 5 files changed, 199 insertions(+), 110 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6aafb4303b..c2eb5d32f0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5806,20 +5806,19 @@ checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" [[package]] name = "libp2p" -version = "0.53.2" +version = "0.54.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "681fb3f183edfbedd7a57d32ebe5dcdc0b9f94061185acf3c30249349cc6fc99" +checksum = "bbbe80f9c7e00526cd6b838075b9c171919404a4732cb2fa8ece0a093223bfc4" dependencies = [ "bytes", "either", "futures", "futures-timer", "getrandom", - "instant", "libp2p-allow-block-list", "libp2p-autonat", "libp2p-connection-limits", - "libp2p-core", + "libp2p-core 0.42.0", "libp2p-dcutr", "libp2p-dns", "libp2p-gossipsub", @@ -5833,10 +5832,10 @@ dependencies = [ "libp2p-quic", "libp2p-relay", "libp2p-request-response", - "libp2p-swarm", - "libp2p-tcp", + "libp2p-swarm 0.45.1", + "libp2p-tcp 0.42.0", "libp2p-upnp", - "libp2p-yamux", + "libp2p-yamux 0.46.0", "multiaddr", "pin-project", "rw-stream-sink", @@ -5845,46 +5844,52 @@ dependencies = [ [[package]] name = "libp2p-allow-block-list" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "107b238b794cb83ab53b74ad5dcf7cca3200899b72fe662840cfb52f5b0a32e6" +checksum = "d1027ccf8d70320ed77e984f273bc8ce952f623762cb9bf2d126df73caef8041" dependencies = [ - "libp2p-core", + "libp2p-core 0.42.0", "libp2p-identity", - "libp2p-swarm", + "libp2p-swarm 0.45.1", "void", ] [[package]] name = "libp2p-autonat" -version = "0.12.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d95151726170e41b591735bf95c42b888fe4aa14f65216a9fbf0edcc04510586" +checksum = "a083675f189803d0682a2726131628e808144911dad076858bfbe30b13065499" dependencies = [ "async-trait", - "asynchronous-codec 0.6.2", + "asynchronous-codec 0.7.0", + "bytes", + "either", "futures", + "futures-bounded", "futures-timer", - "instant", - "libp2p-core", + "libp2p-core 0.42.0", "libp2p-identity", "libp2p-request-response", - "libp2p-swarm", + "libp2p-swarm 0.45.1", "quick-protobuf", - "quick-protobuf-codec 0.2.0", + "quick-protobuf-codec 0.3.1", "rand", + "rand_core", + "thiserror", "tracing", + "void", + "web-time", ] [[package]] name = "libp2p-connection-limits" -version = "0.3.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7cd50a78ccfada14de94cbacd3ce4b0138157f376870f13d3a8422cd075b4fd" +checksum = "8d003540ee8baef0d254f7b6bfd79bac3ddf774662ca0abf69186d517ef82ad8" dependencies = [ - "libp2p-core", + "libp2p-core 0.42.0", "libp2p-identity", - "libp2p-swarm", + "libp2p-swarm 0.45.1", "void", ] @@ -5893,6 +5898,34 @@ name = "libp2p-core" version = "0.41.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5a8920cbd8540059a01950c1e5c96ea8d89eb50c51cd366fc18bdf540a6e48f" +dependencies = [ + "either", + "fnv", + "futures", + "futures-timer", + "libp2p-identity", + "multiaddr", + "multihash", + "multistream-select", + "once_cell", + "parking_lot 0.12.3", + "pin-project", + "quick-protobuf", + "rand", + "rw-stream-sink", + "smallvec", + "thiserror", + "tracing", + "unsigned-varint 0.8.0", + "void", + "web-time", +] + +[[package]] +name = "libp2p-core" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a61f26c83ed111104cd820fe9bc3aaabbac5f1652a1d213ed6e900b7918a1298" dependencies = [ "either", "fnv", @@ -5919,37 +5952,37 @@ dependencies = [ [[package]] name = "libp2p-dcutr" -version = "0.11.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4f7bb7fa2b9e6cad9c30a6f67e3ff5c1e4b658c62b6375e35861a85f9c97bf3" +checksum = "3236a2e24cbcf2d05b398b003ed920e1e8cedede13784d90fa3961b109647ce0" dependencies = [ - "asynchronous-codec 0.6.2", + "asynchronous-codec 0.7.0", "either", "futures", "futures-bounded", "futures-timer", - "instant", - "libp2p-core", + "libp2p-core 0.42.0", "libp2p-identity", - "libp2p-swarm", - "lru 0.11.1", + "libp2p-swarm 0.45.1", + "lru", "quick-protobuf", - "quick-protobuf-codec 0.2.0", + "quick-protobuf-codec 0.3.1", "thiserror", "tracing", "void", + "web-time", ] [[package]] name = "libp2p-dns" -version = "0.41.1" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d17cbcf7160ff35c3e8e560de4a068fe9d6cb777ea72840e48eb76ff9576c4b6" +checksum = "97f37f30d5c7275db282ecd86e54f29dd2176bd3ac656f06abf43bedb21eb8bd" dependencies = [ "async-trait", "futures", "hickory-resolver", - "libp2p-core", + "libp2p-core 0.42.0", "libp2p-identity", "parking_lot 0.12.3", "smallvec", @@ -5958,12 +5991,12 @@ dependencies = [ [[package]] name = "libp2p-gossipsub" -version = "0.46.1" +version = "0.47.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d665144a616dadebdc5fff186b1233488cdcd8bfb1223218ff084b6d052c94f7" +checksum = "b4e830fdf24ac8c444c12415903174d506e1e077fbe3875c404a78c5935a8543" dependencies = [ "asynchronous-codec 0.7.0", - "base64 0.21.7", + "base64 0.22.1", "byteorder", "bytes", "either", @@ -5972,10 +6005,9 @@ dependencies = [ "futures-ticker", "getrandom", "hex_fmt", - "instant", - "libp2p-core", + "libp2p-core 0.42.0", "libp2p-identity", - "libp2p-swarm", + "libp2p-swarm 0.45.1", "prometheus-client", "quick-protobuf", "quick-protobuf-codec 0.3.1", @@ -5986,23 +6018,24 @@ dependencies = [ "smallvec", "tracing", "void", + "web-time", ] [[package]] name = "libp2p-identify" -version = "0.44.2" +version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5d635ebea5ca0c3c3e77d414ae9b67eccf2a822be06091b9c1a0d13029a1e2f" +checksum = "1711b004a273be4f30202778856368683bd9a83c4c7dcc8f848847606831a4e3" dependencies = [ "asynchronous-codec 0.7.0", "either", "futures", "futures-bounded", "futures-timer", - "libp2p-core", + "libp2p-core 0.42.0", "libp2p-identity", - "libp2p-swarm", - "lru 0.12.4", + "libp2p-swarm 0.45.1", + "lru", "quick-protobuf", "quick-protobuf-codec 0.3.1", "smallvec", @@ -6032,9 +6065,9 @@ dependencies = [ [[package]] name = "libp2p-kad" -version = "0.45.3" +version = "0.46.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cc5767727d062c4eac74dd812c998f0e488008e82cce9c33b463d38423f9ad2" +checksum = "ced237d0bd84bbebb7c2cad4c073160dacb4fe40534963c32ed6d4c6bb7702a3" dependencies = [ "arrayvec", "asynchronous-codec 0.7.0", @@ -6044,10 +6077,9 @@ dependencies = [ "futures", "futures-bounded", "futures-timer", - "instant", - "libp2p-core", + "libp2p-core 0.42.0", "libp2p-identity", - "libp2p-swarm", + "libp2p-swarm 0.45.1", "quick-protobuf", "quick-protobuf-codec 0.3.1", "rand", @@ -6058,21 +6090,22 @@ dependencies = [ "tracing", "uint", "void", + "web-time", ] [[package]] name = "libp2p-mdns" -version = "0.45.1" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49007d9a339b3e1d7eeebc4d67c05dbf23d300b7d091193ec2d3f26802d7faf2" +checksum = "14b8546b6644032565eb29046b42744aee1e9f261ed99671b2c93fb140dba417" dependencies = [ "data-encoding", "futures", "hickory-proto", "if-watch", - "libp2p-core", + "libp2p-core 0.42.0", "libp2p-identity", - "libp2p-swarm", + "libp2p-swarm 0.45.1", "rand", "smallvec", "socket2 0.5.7", @@ -6083,13 +6116,12 @@ dependencies = [ [[package]] name = "libp2p-metrics" -version = "0.14.1" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdac91ae4f291046a3b2660c039a2830c931f84df2ee227989af92f7692d3357" +checksum = "77ebafa94a717c8442d8db8d3ae5d1c6a15e30f2d347e0cd31d057ca72e42566" dependencies = [ "futures", - "instant", - "libp2p-core", + "libp2p-core 0.42.0", "libp2p-dcutr", "libp2p-gossipsub", "libp2p-identify", @@ -6097,22 +6129,23 @@ dependencies = [ "libp2p-kad", "libp2p-ping", "libp2p-relay", - "libp2p-swarm", + "libp2p-swarm 0.45.1", "pin-project", "prometheus-client", + "web-time", ] [[package]] name = "libp2p-noise" -version = "0.44.0" +version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecd0545ce077f6ea5434bcb76e8d0fe942693b4380aaad0d34a358c2bd05793" +checksum = "36b137cb1ae86ee39f8e5d6245a296518912014eaa87427d24e6ff58cfc1b28c" dependencies = [ "asynchronous-codec 0.7.0", "bytes", "curve25519-dalek", "futures", - "libp2p-core", + "libp2p-core 0.42.0", "libp2p-identity", "multiaddr", "multihash", @@ -6130,20 +6163,20 @@ dependencies = [ [[package]] name = "libp2p-ping" -version = "0.44.1" +version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1de5a6cf64fba7f7e8f2102711c9c6c043a8e56b86db8cd306492c517da3fb3" +checksum = "005a34420359223b974ee344457095f027e51346e992d1e0dcd35173f4cdd422" dependencies = [ "either", "futures", "futures-timer", - "instant", - "libp2p-core", + "libp2p-core 0.42.0", "libp2p-identity", - "libp2p-swarm", + "libp2p-swarm 0.45.1", "rand", "tracing", "void", + "web-time", ] [[package]] @@ -6155,7 +6188,7 @@ dependencies = [ "asynchronous-codec 0.6.2", "bytes", "futures", - "libp2p-core", + "libp2p-core 0.41.3", "libp2p-identity", "quick-protobuf", "quick-protobuf-codec 0.2.0", @@ -6164,15 +6197,15 @@ dependencies = [ [[package]] name = "libp2p-quic" -version = "0.10.3" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c67296ad4e092e23f92aea3d2bdb6f24eab79c0929ed816dfb460ea2f4567d2b" +checksum = "46352ac5cd040c70e88e7ff8257a2ae2f891a4076abad2c439584a31c15fd24e" dependencies = [ "bytes", "futures", "futures-timer", "if-watch", - "libp2p-core", + "libp2p-core 0.42.0", "libp2p-identity", "libp2p-tls", "parking_lot 0.12.3", @@ -6188,9 +6221,9 @@ dependencies = [ [[package]] name = "libp2p-relay" -version = "0.17.2" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d1c667cfabf3dd675c8e3cea63b7b98434ecf51721b7894cbb01d29983a6a9b" +checksum = "10df23d7f5b5adcc129f4a69d6fbd05209e356ccf9e8f4eb10b2692b79c77247" dependencies = [ "asynchronous-codec 0.7.0", "bytes", @@ -6198,9 +6231,9 @@ dependencies = [ "futures", "futures-bounded", "futures-timer", - "libp2p-core", + "libp2p-core 0.42.0", "libp2p-identity", - "libp2p-swarm", + "libp2p-swarm 0.45.1", "quick-protobuf", "quick-protobuf-codec 0.3.1", "rand", @@ -6213,22 +6246,22 @@ dependencies = [ [[package]] name = "libp2p-request-response" -version = "0.26.3" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c314fe28368da5e3a262553fb0ad575c1c8934c461e10de10265551478163836" +checksum = "1356c9e376a94a75ae830c42cdaea3d4fe1290ba409a22c809033d1b7dcab0a6" dependencies = [ "async-trait", "futures", "futures-bounded", "futures-timer", - "instant", - "libp2p-core", + "libp2p-core 0.42.0", "libp2p-identity", - "libp2p-swarm", + "libp2p-swarm 0.45.1", "rand", "smallvec", "tracing", "void", + "web-time", ] [[package]] @@ -6243,10 +6276,31 @@ dependencies = [ "futures", "futures-timer", "instant", - "libp2p-core", + "libp2p-core 0.41.3", + "libp2p-identity", + "lru", + "multistream-select", + "once_cell", + "rand", + "smallvec", + "tracing", + "void", +] + +[[package]] +name = "libp2p-swarm" +version = "0.45.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7dd6741793d2c1fb2088f67f82cf07261f25272ebe3c0b0c311e0c6b50e851a" +dependencies = [ + "either", + "fnv", + "futures", + "futures-timer", + "libp2p-core 0.42.0", "libp2p-identity", "libp2p-swarm-derive", - "lru 0.12.4", + "lru", "multistream-select", "once_cell", "rand", @@ -6254,13 +6308,14 @@ dependencies = [ "tokio", "tracing", "void", + "web-time", ] [[package]] name = "libp2p-swarm-derive" -version = "0.34.2" +version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5daceb9dd908417b6dfcfe8e94098bc4aac54500c282e78120b885dadc09b999" +checksum = "206e0aa0ebe004d778d79fb0966aa0de996c19894e2c0605ba2f8524dd4443d8" dependencies = [ "heck 0.5.0", "proc-macro2", @@ -6277,12 +6332,12 @@ dependencies = [ "async-trait", "futures", "futures-timer", - "libp2p-core", + "libp2p-core 0.41.3", "libp2p-identity", "libp2p-plaintext", - "libp2p-swarm", - "libp2p-tcp", - "libp2p-yamux", + "libp2p-swarm 0.44.2", + "libp2p-tcp 0.41.0", + "libp2p-yamux 0.45.1", "rand", "tracing", ] @@ -6298,7 +6353,23 @@ dependencies = [ "futures-timer", "if-watch", "libc", - "libp2p-core", + "libp2p-core 0.41.3", + "libp2p-identity", + "socket2 0.5.7", + "tracing", +] + +[[package]] +name = "libp2p-tcp" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad964f312c59dcfcac840acd8c555de8403e295d39edf96f5240048b5fcaa314" +dependencies = [ + "futures", + "futures-timer", + "if-watch", + "libc", + "libp2p-core 0.42.0", "libp2p-identity", "socket2 0.5.7", "tokio", @@ -6307,13 +6378,13 @@ dependencies = [ [[package]] name = "libp2p-tls" -version = "0.4.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b7b831e55ce2aa6c354e6861a85fdd4dd0a2b97d5e276fabac0e4810a71776" +checksum = "47b23dddc2b9c355f73c1e36eb0c3ae86f7dc964a3715f0731cfad352db4d847" dependencies = [ "futures", "futures-rustls", - "libp2p-core", + "libp2p-core 0.42.0", "libp2p-identity", "rcgen", "ring 0.17.8", @@ -6326,15 +6397,15 @@ dependencies = [ [[package]] name = "libp2p-upnp" -version = "0.2.2" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cccf04b0e3ff3de52d07d5fd6c3b061d0e7f908ffc683c32d9638caedce86fc8" +checksum = "01bf2d1b772bd3abca049214a3304615e6a36fa6ffc742bdd1ba774486200b8f" dependencies = [ "futures", "futures-timer", "igd-next", - "libp2p-core", - "libp2p-swarm", + "libp2p-core 0.42.0", + "libp2p-swarm 0.45.1", "tokio", "tracing", "void", @@ -6348,7 +6419,22 @@ checksum = "ddd5265f6b80f94d48a3963541aad183cc598a645755d2f1805a373e41e0716b" dependencies = [ "either", "futures", - "libp2p-core", + "libp2p-core 0.41.3", + "thiserror", + "tracing", + "yamux 0.12.1", + "yamux 0.13.3", +] + +[[package]] +name = "libp2p-yamux" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "788b61c80789dba9760d8c669a5bedb642c8267555c803fabd8396e4ca5c5882" +dependencies = [ + "either", + "futures", + "libp2p-core 0.42.0", "thiserror", "tracing", "yamux 0.12.1", @@ -6413,15 +6499,6 @@ dependencies = [ "value-bag", ] -[[package]] -name = "lru" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a83fb7698b3643a0e34f9ae6f2e8f0178c0fd42f8b59d493aa271ff3a5bf21" -dependencies = [ - "hashbrown 0.14.5", -] - [[package]] name = "lru" version = "0.12.4" @@ -6907,7 +6984,7 @@ checksum = "e238432a7881ec7164503ccc516c014bf009be7984cde1ba56837862543bdec3" dependencies = [ "bitvec", "either", - "lru 0.12.4", + "lru", "num-bigint 0.4.6", "num-integer", "num-modular", diff --git a/Cargo.toml b/Cargo.toml index 206b292f0b..86653addb8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -84,7 +84,7 @@ hyper = "1.0.0" ipnet = "2.9.0" jemallocator = "0.5.4" keccak-hash = "0.10.0" -libp2p = { version = "0.53.0", default-features = false } +libp2p = { version = "0.54.1", default-features = false } libp2p-identity = "0.2.2" libp2p-plaintext = "0.41.0" libp2p-swarm-test = "0.3.0" diff --git a/crates/p2p/src/behaviour.rs b/crates/p2p/src/behaviour.rs index be011faf49..fd9be48eec 100644 --- a/crates/p2p/src/behaviour.rs +++ b/crates/p2p/src/behaviour.rs @@ -3,6 +3,7 @@ use std::net::IpAddr; use std::time::{Duration, Instant}; use std::{cmp, task}; +use libp2p::core::transport::PortUse; use libp2p::core::Endpoint; use libp2p::gossipsub::{self, IdentTopic}; use libp2p::kad::store::MemoryStore; @@ -121,6 +122,7 @@ impl NetworkBehaviour for Behaviour { peer: PeerId, addr: &Multiaddr, role_override: Endpoint, + port_use: PortUse, ) -> Result, ConnectionDenied> { // Disconnect peers without an IP address. Self::get_ip(addr)?; @@ -128,8 +130,13 @@ impl NetworkBehaviour for Behaviour { self.check_duplicate_connection(peer)?; self.prevent_evicted_peer_reconnections(peer)?; - self.inner - .handle_established_outbound_connection(connection_id, peer, addr, role_override) + self.inner.handle_established_outbound_connection( + connection_id, + peer, + addr, + role_override, + port_use, + ) } fn on_swarm_event(&mut self, event: FromSwarm<'_>) { diff --git a/crates/p2p/src/main_loop.rs b/crates/p2p/src/main_loop.rs index 7e17bf948f..441416dce8 100644 --- a/crates/p2p/src/main_loop.rs +++ b/crates/p2p/src/main_loop.rs @@ -289,6 +289,7 @@ impl MainLoop { observed_addr, .. }, + .. } = *e { // Important change in libp2p-v0.52 compared to v0.51: @@ -436,7 +437,9 @@ impl MainLoop { use libp2p::kad::GetClosestPeersOk; let result = match result { - Ok(GetClosestPeersOk { peers, .. }) => Ok(peers), + Ok(GetClosestPeersOk { peers, .. }) => { + Ok(peers.into_iter().map(|p| p.peer_id).collect()) + } Err(e) => Err(e.into()), }; diff --git a/crates/p2p_stream/src/lib.rs b/crates/p2p_stream/src/lib.rs index 92474331f8..8124ea513a 100644 --- a/crates/p2p_stream/src/lib.rs +++ b/crates/p2p_stream/src/lib.rs @@ -63,6 +63,7 @@ use std::{fmt, io}; pub use codec::Codec; use futures::channel::mpsc; use handler::Handler; +use libp2p::core::transport::PortUse; use libp2p::core::{ConnectedPoint, Endpoint, Multiaddr}; use libp2p::identity::PeerId; use libp2p::swarm::behaviour::{AddressChange, ConnectionClosed, DialFailure, FromSwarm}; @@ -630,6 +631,7 @@ where peer: PeerId, remote_address: &Multiaddr, _: Endpoint, + _: PortUse, ) -> Result, ConnectionDenied> { let mut handler = Handler::new( self.protocols.clone(), From 59ce223390f625c3182a49127250201a635efc00 Mon Sep 17 00:00:00 2001 From: Krzysztof Lis Date: Fri, 30 Aug 2024 10:39:12 +0200 Subject: [PATCH 063/282] test: align with libp2p update --- Cargo.lock | 241 ++++++---------------- Cargo.toml | 4 +- crates/p2p/src/behaviour.rs | 7 +- crates/p2p/src/behaviour/builder.rs | 34 ++- crates/p2p/src/bin/bootstrap/behaviour.rs | 10 +- crates/p2p/src/bin/bootstrap/main.rs | 1 + crates/p2p_stream/Cargo.toml | 1 + 7 files changed, 91 insertions(+), 207 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c2eb5d32f0..f1e2b8b830 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -341,7 +341,7 @@ dependencies = [ "dashmap", "futures", "futures-utils-wasm", - "lru 0.12.4", + "lru", "pin-project", "reqwest", "serde", @@ -1222,19 +1222,6 @@ dependencies = [ "rustc_version 0.4.1", ] -[[package]] -name = "asynchronous-codec" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4057f2c32adbb2fc158e22fb38433c8e9bbf76b75a4732c7c0cbaf695fb65568" -dependencies = [ - "bytes", - "futures-sink", - "futures-util", - "memchr", - "pin-project-lite", -] - [[package]] name = "asynchronous-codec" version = "0.7.0" @@ -5818,7 +5805,7 @@ dependencies = [ "libp2p-allow-block-list", "libp2p-autonat", "libp2p-connection-limits", - "libp2p-core 0.42.0", + "libp2p-core", "libp2p-dcutr", "libp2p-dns", "libp2p-gossipsub", @@ -5832,10 +5819,10 @@ dependencies = [ "libp2p-quic", "libp2p-relay", "libp2p-request-response", - "libp2p-swarm 0.45.1", - "libp2p-tcp 0.42.0", + "libp2p-swarm", + "libp2p-tcp", "libp2p-upnp", - "libp2p-yamux 0.46.0", + "libp2p-yamux", "multiaddr", "pin-project", "rw-stream-sink", @@ -5848,9 +5835,9 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d1027ccf8d70320ed77e984f273bc8ce952f623762cb9bf2d126df73caef8041" dependencies = [ - "libp2p-core 0.42.0", + "libp2p-core", "libp2p-identity", - "libp2p-swarm 0.45.1", + "libp2p-swarm", "void", ] @@ -5861,18 +5848,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a083675f189803d0682a2726131628e808144911dad076858bfbe30b13065499" dependencies = [ "async-trait", - "asynchronous-codec 0.7.0", + "asynchronous-codec", "bytes", "either", "futures", "futures-bounded", "futures-timer", - "libp2p-core 0.42.0", + "libp2p-core", "libp2p-identity", "libp2p-request-response", - "libp2p-swarm 0.45.1", + "libp2p-swarm", "quick-protobuf", - "quick-protobuf-codec 0.3.1", + "quick-protobuf-codec", "rand", "rand_core", "thiserror", @@ -5887,38 +5874,10 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d003540ee8baef0d254f7b6bfd79bac3ddf774662ca0abf69186d517ef82ad8" dependencies = [ - "libp2p-core 0.42.0", + "libp2p-core", "libp2p-identity", - "libp2p-swarm 0.45.1", - "void", -] - -[[package]] -name = "libp2p-core" -version = "0.41.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5a8920cbd8540059a01950c1e5c96ea8d89eb50c51cd366fc18bdf540a6e48f" -dependencies = [ - "either", - "fnv", - "futures", - "futures-timer", - "libp2p-identity", - "multiaddr", - "multihash", - "multistream-select", - "once_cell", - "parking_lot 0.12.3", - "pin-project", - "quick-protobuf", - "rand", - "rw-stream-sink", - "smallvec", - "thiserror", - "tracing", - "unsigned-varint 0.8.0", + "libp2p-swarm", "void", - "web-time", ] [[package]] @@ -5956,17 +5915,17 @@ version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3236a2e24cbcf2d05b398b003ed920e1e8cedede13784d90fa3961b109647ce0" dependencies = [ - "asynchronous-codec 0.7.0", + "asynchronous-codec", "either", "futures", "futures-bounded", "futures-timer", - "libp2p-core 0.42.0", + "libp2p-core", "libp2p-identity", - "libp2p-swarm 0.45.1", + "libp2p-swarm", "lru", "quick-protobuf", - "quick-protobuf-codec 0.3.1", + "quick-protobuf-codec", "thiserror", "tracing", "void", @@ -5982,7 +5941,7 @@ dependencies = [ "async-trait", "futures", "hickory-resolver", - "libp2p-core 0.42.0", + "libp2p-core", "libp2p-identity", "parking_lot 0.12.3", "smallvec", @@ -5995,7 +5954,7 @@ version = "0.47.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4e830fdf24ac8c444c12415903174d506e1e077fbe3875c404a78c5935a8543" dependencies = [ - "asynchronous-codec 0.7.0", + "asynchronous-codec", "base64 0.22.1", "byteorder", "bytes", @@ -6005,12 +5964,12 @@ dependencies = [ "futures-ticker", "getrandom", "hex_fmt", - "libp2p-core 0.42.0", + "libp2p-core", "libp2p-identity", - "libp2p-swarm 0.45.1", + "libp2p-swarm", "prometheus-client", "quick-protobuf", - "quick-protobuf-codec 0.3.1", + "quick-protobuf-codec", "rand", "regex", "serde", @@ -6027,17 +5986,17 @@ version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1711b004a273be4f30202778856368683bd9a83c4c7dcc8f848847606831a4e3" dependencies = [ - "asynchronous-codec 0.7.0", + "asynchronous-codec", "either", "futures", "futures-bounded", "futures-timer", - "libp2p-core 0.42.0", + "libp2p-core", "libp2p-identity", - "libp2p-swarm 0.45.1", + "libp2p-swarm", "lru", "quick-protobuf", - "quick-protobuf-codec 0.3.1", + "quick-protobuf-codec", "smallvec", "thiserror", "tracing", @@ -6070,18 +6029,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced237d0bd84bbebb7c2cad4c073160dacb4fe40534963c32ed6d4c6bb7702a3" dependencies = [ "arrayvec", - "asynchronous-codec 0.7.0", + "asynchronous-codec", "bytes", "either", "fnv", "futures", "futures-bounded", "futures-timer", - "libp2p-core 0.42.0", + "libp2p-core", "libp2p-identity", - "libp2p-swarm 0.45.1", + "libp2p-swarm", "quick-protobuf", - "quick-protobuf-codec 0.3.1", + "quick-protobuf-codec", "rand", "serde", "sha2", @@ -6103,9 +6062,9 @@ dependencies = [ "futures", "hickory-proto", "if-watch", - "libp2p-core 0.42.0", + "libp2p-core", "libp2p-identity", - "libp2p-swarm 0.45.1", + "libp2p-swarm", "rand", "smallvec", "socket2 0.5.7", @@ -6121,7 +6080,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77ebafa94a717c8442d8db8d3ae5d1c6a15e30f2d347e0cd31d057ca72e42566" dependencies = [ "futures", - "libp2p-core 0.42.0", + "libp2p-core", "libp2p-dcutr", "libp2p-gossipsub", "libp2p-identify", @@ -6129,7 +6088,7 @@ dependencies = [ "libp2p-kad", "libp2p-ping", "libp2p-relay", - "libp2p-swarm 0.45.1", + "libp2p-swarm", "pin-project", "prometheus-client", "web-time", @@ -6141,11 +6100,11 @@ version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "36b137cb1ae86ee39f8e5d6245a296518912014eaa87427d24e6ff58cfc1b28c" dependencies = [ - "asynchronous-codec 0.7.0", + "asynchronous-codec", "bytes", "curve25519-dalek", "futures", - "libp2p-core 0.42.0", + "libp2p-core", "libp2p-identity", "multiaddr", "multihash", @@ -6170,9 +6129,9 @@ dependencies = [ "either", "futures", "futures-timer", - "libp2p-core 0.42.0", + "libp2p-core", "libp2p-identity", - "libp2p-swarm 0.45.1", + "libp2p-swarm", "rand", "tracing", "void", @@ -6181,17 +6140,17 @@ dependencies = [ [[package]] name = "libp2p-plaintext" -version = "0.41.0" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67330af40b67217e746d42551913cfb7ad04c74fa300fb329660a56318590b3f" +checksum = "5b63d926c6be56a2489e0e7316b17fe95a70bc5c4f3e85740bb3e67c0f3c6a44" dependencies = [ - "asynchronous-codec 0.6.2", + "asynchronous-codec", "bytes", "futures", - "libp2p-core 0.41.3", + "libp2p-core", "libp2p-identity", "quick-protobuf", - "quick-protobuf-codec 0.2.0", + "quick-protobuf-codec", "tracing", ] @@ -6205,7 +6164,7 @@ dependencies = [ "futures", "futures-timer", "if-watch", - "libp2p-core 0.42.0", + "libp2p-core", "libp2p-identity", "libp2p-tls", "parking_lot 0.12.3", @@ -6225,17 +6184,17 @@ version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10df23d7f5b5adcc129f4a69d6fbd05209e356ccf9e8f4eb10b2692b79c77247" dependencies = [ - "asynchronous-codec 0.7.0", + "asynchronous-codec", "bytes", "either", "futures", "futures-bounded", "futures-timer", - "libp2p-core 0.42.0", + "libp2p-core", "libp2p-identity", - "libp2p-swarm 0.45.1", + "libp2p-swarm", "quick-protobuf", - "quick-protobuf-codec 0.3.1", + "quick-protobuf-codec", "rand", "static_assertions", "thiserror", @@ -6254,9 +6213,9 @@ dependencies = [ "futures", "futures-bounded", "futures-timer", - "libp2p-core 0.42.0", + "libp2p-core", "libp2p-identity", - "libp2p-swarm 0.45.1", + "libp2p-swarm", "rand", "smallvec", "tracing", @@ -6264,40 +6223,18 @@ dependencies = [ "web-time", ] -[[package]] -name = "libp2p-swarm" -version = "0.44.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80cae6cb75f89dbca53862f9ebe0b9f463aa7b302762fcfaafb9e51dcc9b0f7e" -dependencies = [ - "async-std", - "either", - "fnv", - "futures", - "futures-timer", - "instant", - "libp2p-core 0.41.3", - "libp2p-identity", - "lru", - "multistream-select", - "once_cell", - "rand", - "smallvec", - "tracing", - "void", -] - [[package]] name = "libp2p-swarm" version = "0.45.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7dd6741793d2c1fb2088f67f82cf07261f25272ebe3c0b0c311e0c6b50e851a" dependencies = [ + "async-std", "either", "fnv", "futures", "futures-timer", - "libp2p-core 0.42.0", + "libp2p-core", "libp2p-identity", "libp2p-swarm-derive", "lru", @@ -6325,51 +6262,35 @@ dependencies = [ [[package]] name = "libp2p-swarm-test" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a73027f1bdabd15d08b2c7954911cd56a6265c476763b2ceb10d9dc5ea4366b2" +checksum = "ea4e1d1d92421dc4c90cad42e3cd24f50fd210191c9f126d41bd483a09567f67" dependencies = [ "async-trait", "futures", "futures-timer", - "libp2p-core 0.41.3", + "libp2p-core", "libp2p-identity", "libp2p-plaintext", - "libp2p-swarm 0.44.2", - "libp2p-tcp 0.41.0", - "libp2p-yamux 0.45.1", + "libp2p-swarm", + "libp2p-tcp", + "libp2p-yamux", "rand", "tracing", ] -[[package]] -name = "libp2p-tcp" -version = "0.41.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b2460fc2748919adff99ecbc1aab296e4579e41f374fb164149bd2c9e529d4c" -dependencies = [ - "async-io 1.13.0", - "futures", - "futures-timer", - "if-watch", - "libc", - "libp2p-core 0.41.3", - "libp2p-identity", - "socket2 0.5.7", - "tracing", -] - [[package]] name = "libp2p-tcp" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad964f312c59dcfcac840acd8c555de8403e295d39edf96f5240048b5fcaa314" dependencies = [ + "async-io 2.3.4", "futures", "futures-timer", "if-watch", "libc", - "libp2p-core 0.42.0", + "libp2p-core", "libp2p-identity", "socket2 0.5.7", "tokio", @@ -6384,7 +6305,7 @@ checksum = "47b23dddc2b9c355f73c1e36eb0c3ae86f7dc964a3715f0731cfad352db4d847" dependencies = [ "futures", "futures-rustls", - "libp2p-core 0.42.0", + "libp2p-core", "libp2p-identity", "rcgen", "ring 0.17.8", @@ -6404,28 +6325,13 @@ dependencies = [ "futures", "futures-timer", "igd-next", - "libp2p-core 0.42.0", - "libp2p-swarm 0.45.1", + "libp2p-core", + "libp2p-swarm", "tokio", "tracing", "void", ] -[[package]] -name = "libp2p-yamux" -version = "0.45.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd5265f6b80f94d48a3963541aad183cc598a645755d2f1805a373e41e0716b" -dependencies = [ - "either", - "futures", - "libp2p-core 0.41.3", - "thiserror", - "tracing", - "yamux 0.12.1", - "yamux 0.13.3", -] - [[package]] name = "libp2p-yamux" version = "0.46.0" @@ -6434,7 +6340,7 @@ checksum = "788b61c80789dba9760d8c669a5bedb642c8267555c803fabd8396e4ca5c5882" dependencies = [ "either", "futures", - "libp2p-core 0.42.0", + "libp2p-core", "thiserror", "tracing", "yamux 0.12.1", @@ -8182,26 +8088,13 @@ dependencies = [ "byteorder", ] -[[package]] -name = "quick-protobuf-codec" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ededb1cd78531627244d51dd0c7139fbe736c7d57af0092a76f0ffb2f56e98" -dependencies = [ - "asynchronous-codec 0.6.2", - "bytes", - "quick-protobuf", - "thiserror", - "unsigned-varint 0.7.2", -] - [[package]] name = "quick-protobuf-codec" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "15a0580ab32b169745d7a39db2ba969226ca16738931be152a3209b409de2474" dependencies = [ - "asynchronous-codec 0.7.0", + "asynchronous-codec", "bytes", "quick-protobuf", "thiserror", @@ -10390,10 +10283,6 @@ name = "unsigned-varint" version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6889a77d49f1f013504cec6bf97a2c730394adedaeb1deb5ea08949a50541105" -dependencies = [ - "asynchronous-codec 0.6.2", - "bytes", -] [[package]] name = "unsigned-varint" diff --git a/Cargo.toml b/Cargo.toml index 86653addb8..469d1770c6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -86,8 +86,8 @@ jemallocator = "0.5.4" keccak-hash = "0.10.0" libp2p = { version = "0.54.1", default-features = false } libp2p-identity = "0.2.2" -libp2p-plaintext = "0.41.0" -libp2p-swarm-test = "0.3.0" +libp2p-plaintext = "0.42.0" +libp2p-swarm-test = "0.4.0" metrics = "0.20.1" metrics-exporter-prometheus = "0.11.0" mime = "0.3" diff --git a/crates/p2p/src/behaviour.rs b/crates/p2p/src/behaviour.rs index fd9be48eec..be3cbc511f 100644 --- a/crates/p2p/src/behaviour.rs +++ b/crates/p2p/src/behaviour.rs @@ -22,7 +22,7 @@ use libp2p::swarm::{ THandlerOutEvent, ToSwarm, }; -use libp2p::{autonat, dcutr, identify, identity, ping, relay, Multiaddr, PeerId}; +use libp2p::{autonat, dcutr, identify, identity, ping, relay, Multiaddr, PeerId, StreamProtocol}; use p2p_proto::class::{ClassesRequest, ClassesResponse}; use p2p_proto::event::{EventsRequest, EventsResponse}; use p2p_proto::header::{BlockHeadersRequest, BlockHeadersResponse}; @@ -39,8 +39,9 @@ use crate::secret::Secret; use crate::sync::codec; use crate::Config; -pub fn kademlia_protocol_name(chain_id: ChainId) -> String { - format!("/starknet/kad/{}/1.0.0", chain_id.as_str()) +pub fn kademlia_protocol_name(chain_id: ChainId) -> StreamProtocol { + StreamProtocol::try_from_owned(format!("/starknet/kad/{}/1.0.0", chain_id.as_str())) + .expect("Starts with /") } pub type BehaviourWithRelayTransport = (Behaviour, relay::client::Transport); diff --git a/crates/p2p/src/behaviour/builder.rs b/crates/p2p/src/behaviour/builder.rs index 92e0149d4e..d79d33e2a0 100644 --- a/crates/p2p/src/behaviour/builder.rs +++ b/crates/p2p/src/behaviour/builder.rs @@ -94,27 +94,25 @@ impl Builder { const PROVIDER_PUBLICATION_INTERVAL: Duration = Duration::from_secs(600); - let mut kademlia_config = kad::Config::default(); - kademlia_config.set_record_ttl(Some(Duration::from_secs(0))); - kademlia_config.set_provider_record_ttl(Some(PROVIDER_PUBLICATION_INTERVAL * 3)); - kademlia_config.set_provider_publication_interval(Some(PROVIDER_PUBLICATION_INTERVAL)); // This makes sure that the DHT we're implementing is incompatible with the // "default" IPFS DHT from libp2p. - if cfg.kad_names.is_empty() { - kademlia_config.set_protocol_names(vec![StreamProtocol::try_from_owned( - kademlia_protocol_name(chain_id), - ) - .unwrap()]); + let protocol_name = if cfg.kad_names.is_empty() { + kademlia_protocol_name(chain_id) } else { - kademlia_config.set_protocol_names( - cfg.kad_names - .iter() - .cloned() - .map(StreamProtocol::try_from_owned) - .collect::, _>>() - .expect("valid protocol names"), - ); - } + // TODO change config to use 1 protocol name + cfg.kad_names + .iter() + .cloned() + .map(StreamProtocol::try_from_owned) + .collect::, _>>() + .expect("valid protocol names") + .swap_remove(0) + }; + + let mut kademlia_config = kad::Config::new(protocol_name); + kademlia_config.set_record_ttl(Some(Duration::from_secs(0))); + kademlia_config.set_provider_record_ttl(Some(PROVIDER_PUBLICATION_INTERVAL * 3)); + kademlia_config.set_provider_publication_interval(Some(PROVIDER_PUBLICATION_INTERVAL)); let peer_id = identity.public().to_peer_id(); let secret = Secret::new(&identity); diff --git a/crates/p2p/src/bin/bootstrap/behaviour.rs b/crates/p2p/src/bin/bootstrap/behaviour.rs index a2cc7f47d2..3b040a3e3b 100644 --- a/crates/p2p/src/bin/bootstrap/behaviour.rs +++ b/crates/p2p/src/bin/bootstrap/behaviour.rs @@ -3,7 +3,7 @@ use std::time::Duration; use libp2p::kad::store::MemoryStore; use libp2p::kad::{self}; use libp2p::swarm::NetworkBehaviour; -use libp2p::{autonat, dcutr, identify, identity, ping, relay, StreamProtocol}; +use libp2p::{autonat, dcutr, identify, identity, ping, relay}; use p2p::kademlia_protocol_name; use pathfinder_common::ChainId; @@ -22,16 +22,10 @@ impl BootstrapBehaviour { pub fn new(pub_key: identity::PublicKey, chain_id: ChainId) -> Self { const PROVIDER_PUBLICATION_INTERVAL: Duration = Duration::from_secs(600); - let mut kademlia_config = kad::Config::default(); + let mut kademlia_config = kad::Config::new(kademlia_protocol_name(chain_id)); kademlia_config.set_record_ttl(Some(Duration::from_secs(0))); kademlia_config.set_provider_record_ttl(Some(PROVIDER_PUBLICATION_INTERVAL * 3)); kademlia_config.set_provider_publication_interval(Some(PROVIDER_PUBLICATION_INTERVAL)); - // FIXME: this make sure that the DHT we're implementing is incompatible with - // the "default" IPFS DHT from libp2p. - kademlia_config.set_protocol_names(vec![StreamProtocol::try_from_owned( - kademlia_protocol_name(chain_id), - ) - .unwrap()]); let kademlia = kad::Behaviour::with_config( pub_key.to_peer_id(), diff --git a/crates/p2p/src/bin/bootstrap/main.rs b/crates/p2p/src/bin/bootstrap/main.rs index e3fdd878c6..9ad159a99a 100644 --- a/crates/p2p/src/bin/bootstrap/main.rs +++ b/crates/p2p/src/bin/bootstrap/main.rs @@ -141,6 +141,7 @@ async fn main() -> anyhow::Result<()> { observed_addr, .. }, + .. } = *e { // Important change in libp2p-v0.52 compared to v0.51: diff --git a/crates/p2p_stream/Cargo.toml b/crates/p2p_stream/Cargo.toml index c198dea324..c682221e6d 100644 --- a/crates/p2p_stream/Cargo.toml +++ b/crates/p2p_stream/Cargo.toml @@ -27,3 +27,4 @@ libp2p-swarm-test = { workspace = true } rstest = { workspace = true } test-log = { workspace = true, features = ["trace"] } tokio = { workspace = true, features = ["macros", "time"] } +# tracing-subscriber = { workspace = true, features = ["env-filter"] } From 55fbdbc92f216d4050c20083412ccff6731d1047 Mon Sep 17 00:00:00 2001 From: Krzysztof Lis Date: Mon, 16 Sep 2024 15:52:10 +0200 Subject: [PATCH 064/282] test: ignore evicted_peer_reconnection until eviction is fixed --- crates/p2p/src/tests.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/p2p/src/tests.rs b/crates/p2p/src/tests.rs index 5ec9061d74..35ea76fac1 100644 --- a/crates/p2p/src/tests.rs +++ b/crates/p2p/src/tests.rs @@ -723,6 +723,7 @@ async fn inbound_peer_eviction() { } /// Ensure that evicted peers can't reconnect too quickly. +#[ignore = "TODO fix eviction and low watermark logic after updating to libp2p 0.54.1"] #[test_log::test(tokio::test)] async fn evicted_peer_reconnection() { let cfg = Config { From 60757b2123f927c2288db4fedd88502f96a23bcd Mon Sep 17 00:00:00 2001 From: Krzysztof Lis Date: Fri, 30 Aug 2024 10:39:12 +0200 Subject: [PATCH 065/282] test: align with libp2p update --- Cargo.lock | 1 + crates/p2p_stream/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index f1e2b8b830..d688a1b2db 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7112,6 +7112,7 @@ dependencies = [ "test-log", "tokio", "tracing", + "tracing-subscriber", "void", ] diff --git a/crates/p2p_stream/Cargo.toml b/crates/p2p_stream/Cargo.toml index c682221e6d..fbc45333c5 100644 --- a/crates/p2p_stream/Cargo.toml +++ b/crates/p2p_stream/Cargo.toml @@ -27,4 +27,4 @@ libp2p-swarm-test = { workspace = true } rstest = { workspace = true } test-log = { workspace = true, features = ["trace"] } tokio = { workspace = true, features = ["macros", "time"] } -# tracing-subscriber = { workspace = true, features = ["env-filter"] } +tracing-subscriber = { workspace = true, features = ["env-filter"] } From f8e8af4549dd79e50905f305520913a9ec7f8755 Mon Sep 17 00:00:00 2001 From: Krzysztof Lis Date: Tue, 17 Sep 2024 13:27:32 +0200 Subject: [PATCH 066/282] feat: allow one custom kad name in config --- crates/p2p/src/behaviour.rs | 5 +++++ crates/p2p/src/behaviour/builder.rs | 17 +++++------------ crates/p2p/src/bin/bootstrap/main.rs | 5 +++-- crates/p2p/src/builder.rs | 2 +- crates/p2p/src/lib.rs | 4 ++-- crates/p2p/src/main_loop.rs | 11 +++-------- crates/p2p/src/test_utils/peer.rs | 2 +- crates/p2p/src/tests.rs | 18 +++++++++--------- crates/pathfinder/src/bin/pathfinder/config.rs | 14 +++++++------- crates/pathfinder/src/bin/pathfinder/main.rs | 2 +- 10 files changed, 37 insertions(+), 43 deletions(-) diff --git a/crates/p2p/src/behaviour.rs b/crates/p2p/src/behaviour.rs index be3cbc511f..499ef71380 100644 --- a/crates/p2p/src/behaviour.rs +++ b/crates/p2p/src/behaviour.rs @@ -39,6 +39,7 @@ use crate::secret::Secret; use crate::sync::codec; use crate::Config; +/// The default kademlia protocol name for a given Starknet chain. pub fn kademlia_protocol_name(chain_id: ChainId) -> StreamProtocol { StreamProtocol::try_from_owned(format!("/starknet/kad/{}/1.0.0", chain_id.as_str())) .expect("Starts with /") @@ -724,6 +725,10 @@ impl Behaviour { }); } + pub fn kademlia(&self) -> &kad::Behaviour { + &self.inner.kademlia + } + pub fn kademlia_mut(&mut self) -> &mut kad::Behaviour { &mut self.inner.kademlia } diff --git a/crates/p2p/src/behaviour/builder.rs b/crates/p2p/src/behaviour/builder.rs index d79d33e2a0..94bb378d14 100644 --- a/crates/p2p/src/behaviour/builder.rs +++ b/crates/p2p/src/behaviour/builder.rs @@ -96,18 +96,11 @@ impl Builder { // This makes sure that the DHT we're implementing is incompatible with the // "default" IPFS DHT from libp2p. - let protocol_name = if cfg.kad_names.is_empty() { - kademlia_protocol_name(chain_id) - } else { - // TODO change config to use 1 protocol name - cfg.kad_names - .iter() - .cloned() - .map(StreamProtocol::try_from_owned) - .collect::, _>>() - .expect("valid protocol names") - .swap_remove(0) - }; + let protocol_name = cfg + .kad_name + .clone() + .map(|x| StreamProtocol::try_from_owned(x).expect("valid protocol name")) + .unwrap_or_else(|| kademlia_protocol_name(chain_id)); let mut kademlia_config = kad::Config::new(protocol_name); kademlia_config.set_record_ttl(Some(Duration::from_secs(0))); diff --git a/crates/p2p/src/bin/bootstrap/main.rs b/crates/p2p/src/bin/bootstrap/main.rs index 9ad159a99a..092834d439 100644 --- a/crates/p2p/src/bin/bootstrap/main.rs +++ b/crates/p2p/src/bin/bootstrap/main.rs @@ -9,7 +9,6 @@ use libp2p::core::upgrade; use libp2p::identity::Keypair; use libp2p::swarm::{Config, SwarmEvent}; use libp2p::{dns, identify, noise, Multiaddr, Swarm, Transport}; -use p2p::kademlia_protocol_name; use pathfinder_common::ChainId; use serde::Deserialize; use zeroize::Zeroizing; @@ -161,9 +160,11 @@ async fn main() -> anyhow::Result<()> { swarm.add_external_address(observed_addr); + let my_kad_names = swarm.behaviour().kademlia.protocol_names(); + if protocols .iter() - .any(|p| p.as_ref() == kademlia_protocol_name(chain_id)) + .any(|p| my_kad_names.contains(p)) { for addr in listen_addrs { swarm.behaviour_mut().kademlia.add_address(&peer_id, addr); diff --git a/crates/p2p/src/builder.rs b/crates/p2p/src/builder.rs index 73f31c0e61..4a428ae935 100644 --- a/crates/p2p/src/builder.rs +++ b/crates/p2p/src/builder.rs @@ -65,7 +65,7 @@ impl Builder { ( client, event_receiver, - MainLoop::new(swarm, command_receiver, event_sender, cfg, chain_id), + MainLoop::new(swarm, command_receiver, event_sender, cfg), ) } } diff --git a/crates/p2p/src/lib.rs b/crates/p2p/src/lib.rs index 2cddc3f823..f2f23c2774 100644 --- a/crates/p2p/src/lib.rs +++ b/crates/p2p/src/lib.rs @@ -65,8 +65,8 @@ pub struct Config { pub ip_whitelist: Vec, pub bootstrap: BootstrapConfig, pub inbound_connections_rate_limit: RateLimit, - /// Alternative protocol names for Kademlia - pub kad_names: Vec, + /// Custom protocol name for Kademlia + pub kad_name: Option, /// Request timeout for p2p-stream pub stream_timeout: Duration, /// Applies to each of the p2p-stream protocols separately diff --git a/crates/p2p/src/main_loop.rs b/crates/p2p/src/main_loop.rs index 441416dce8..7a25ce1f0a 100644 --- a/crates/p2p/src/main_loop.rs +++ b/crates/p2p/src/main_loop.rs @@ -24,7 +24,6 @@ use p2p_proto::state::StateDiffsResponse; use p2p_proto::transaction::TransactionsResponse; use p2p_proto::{ToProtobuf, TryFromProtobuf}; use p2p_stream::{self, OutboundRequestId}; -use pathfinder_common::ChainId; use tokio::sync::{mpsc, oneshot}; use tokio::time::Duration; @@ -47,7 +46,6 @@ pub struct MainLoop { // 2. update the sync head info of our peers using a different mechanism // request_sync_status: HashSetDelay, pending_queries: PendingQueries, - chain_id: ChainId, /// Ongoing Kademlia bootstrap query. ongoing_bootstrap: Option, _pending_test_queries: TestQueries, @@ -89,7 +87,6 @@ impl MainLoop { command_receiver: mpsc::Receiver, event_sender: mpsc::Sender, cfg: Config, - chain_id: ChainId, ) -> Self { Self { cfg, @@ -99,7 +96,6 @@ impl MainLoop { pending_dials: Default::default(), pending_sync_requests: Default::default(), pending_queries: Default::default(), - chain_id, ongoing_bootstrap: None, _pending_test_queries: Default::default(), } @@ -313,10 +309,9 @@ impl MainLoop { self.swarm.add_external_address(observed_addr); - if protocols - .iter() - .any(|p| p.as_ref() == behaviour::kademlia_protocol_name(self.chain_id)) - { + let my_kad_names = self.swarm.behaviour().kademlia().protocol_names(); + + if protocols.iter().any(|p| my_kad_names.contains(p)) { for addr in &listen_addrs { self.swarm .behaviour_mut() diff --git a/crates/p2p/src/test_utils/peer.rs b/crates/p2p/src/test_utils/peer.rs index 8ba7f8be1e..72c4defdb7 100644 --- a/crates/p2p/src/test_utils/peer.rs +++ b/crates/p2p/src/test_utils/peer.rs @@ -43,7 +43,7 @@ impl Config { max: 1000, interval: Duration::from_secs(1), }, - kad_names: Default::default(), + kad_name: Default::default(), stream_timeout: Duration::from_secs(10), max_concurrent_streams: 100, } diff --git a/crates/p2p/src/tests.rs b/crates/p2p/src/tests.rs index 35ea76fac1..1723a627b5 100644 --- a/crates/p2p/src/tests.rs +++ b/crates/p2p/src/tests.rs @@ -177,7 +177,7 @@ async fn periodic_bootstrap() { max: 1000, interval: Duration::from_secs(1), }, - kad_names: Default::default(), + kad_name: Default::default(), stream_timeout: Duration::from_secs(10), max_concurrent_streams: 100, }; @@ -329,7 +329,7 @@ async fn reconnect_too_quickly() { max: 1000, interval: Duration::from_secs(1), }, - kad_names: Default::default(), + kad_name: Default::default(), stream_timeout: Duration::from_secs(10), max_concurrent_streams: 100, }; @@ -434,7 +434,7 @@ async fn duplicate_connection() { max: 1000, interval: Duration::from_secs(1), }, - kad_names: Default::default(), + kad_name: Default::default(), stream_timeout: Duration::from_secs(10), max_concurrent_streams: 100, }; @@ -523,7 +523,7 @@ async fn outbound_peer_eviction() { max: 1000, interval: Duration::from_secs(1), }, - kad_names: Default::default(), + kad_name: Default::default(), stream_timeout: Duration::from_secs(10), max_concurrent_streams: 100, }; @@ -655,7 +655,7 @@ async fn inbound_peer_eviction() { max: 1000, interval: Duration::from_secs(1), }, - kad_names: Default::default(), + kad_name: Default::default(), stream_timeout: Duration::from_secs(10), max_concurrent_streams: 100, }; @@ -744,7 +744,7 @@ async fn evicted_peer_reconnection() { max: 1000, interval: Duration::from_secs(1), }, - kad_names: Default::default(), + kad_name: Default::default(), stream_timeout: Duration::from_secs(10), max_concurrent_streams: 100, }; @@ -838,7 +838,7 @@ async fn ip_whitelist() { max: 1000, interval: Duration::from_secs(1), }, - kad_names: Default::default(), + kad_name: Default::default(), stream_timeout: Duration::from_secs(10), max_concurrent_streams: 100, }; @@ -874,7 +874,7 @@ async fn ip_whitelist() { max: 1000, interval: Duration::from_secs(1), }, - kad_names: Default::default(), + kad_name: Default::default(), stream_timeout: Duration::from_secs(10), max_concurrent_streams: 100, }; @@ -911,7 +911,7 @@ async fn rate_limit() { max: 2, interval: RATE_LIMIT_INTERVAL, }, - kad_names: Default::default(), + kad_name: Default::default(), stream_timeout: Duration::from_secs(10), max_concurrent_streams: 100, }; diff --git a/crates/pathfinder/src/bin/pathfinder/config.rs b/crates/pathfinder/src/bin/pathfinder/config.rs index 17109ae3f2..36abcb42fd 100644 --- a/crates/pathfinder/src/bin/pathfinder/config.rs +++ b/crates/pathfinder/src/bin/pathfinder/config.rs @@ -473,13 +473,13 @@ Example: ip_whitelist: Vec, #[arg( - long = "p2p.experimental.kad-names", - long_help = "Comma separated list of custom Kademlia protocol names.", + long = "p2p.experimental.kad-name", + long_help = "Custom Kademlia protocol name.", value_name = "LIST", value_delimiter = ',', - env = "PATHFINDER_P2P_EXPERIMENTAL_KAD_NAMES" + env = "PATHFINDER_P2P_EXPERIMENTAL_KAD_NAME" )] - kad_names: Vec, + kad_name: Option, #[arg( long = "p2p.experimental.l1-checkpoint-override-json-path", @@ -731,7 +731,7 @@ pub struct P2PConfig { pub max_outbound_connections: usize, pub ip_whitelist: Vec, pub low_watermark: usize, - pub kad_names: Vec, + pub kad_name: Option, pub l1_checkpoint_override: Option, pub stream_timeout: Duration, pub max_concurrent_streams: usize, @@ -850,7 +850,7 @@ impl P2PConfig { .exit() } - if args.kad_names.iter().any(|x| !x.starts_with('/')) { + if args.kad_name.iter().any(|x| !x.starts_with('/')) { Cli::command() .error( ErrorKind::ValueValidation, @@ -878,7 +878,7 @@ impl P2PConfig { predefined_peers: parse_multiaddr_vec("p2p.predefined-peers", args.predefined_peers), ip_whitelist: args.ip_whitelist, low_watermark: 0, - kad_names: args.kad_names, + kad_name: args.kad_name, l1_checkpoint_override, stream_timeout: Duration::from_secs(args.stream_timeout.into()), max_concurrent_streams: args.max_concurrent_streams, diff --git a/crates/pathfinder/src/bin/pathfinder/main.rs b/crates/pathfinder/src/bin/pathfinder/main.rs index 55bec79b74..de86086f74 100644 --- a/crates/pathfinder/src/bin/pathfinder/main.rs +++ b/crates/pathfinder/src/bin/pathfinder/main.rs @@ -453,7 +453,7 @@ async fn start_p2p( max: 10, interval: Duration::from_secs(1), }, - kad_names: config.kad_names, + kad_name: config.kad_name, stream_timeout: config.stream_timeout, max_concurrent_streams: config.max_concurrent_streams, }, From faf433d874fb98796c0c099e094a588e031a4306 Mon Sep 17 00:00:00 2001 From: Krzysztof Lis Date: Thu, 19 Sep 2024 10:34:03 +0200 Subject: [PATCH 067/282] fixup(config): fix value_name and remove value_delimiter --- crates/pathfinder/src/bin/pathfinder/config.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/pathfinder/src/bin/pathfinder/config.rs b/crates/pathfinder/src/bin/pathfinder/config.rs index 36abcb42fd..34d78da537 100644 --- a/crates/pathfinder/src/bin/pathfinder/config.rs +++ b/crates/pathfinder/src/bin/pathfinder/config.rs @@ -475,8 +475,7 @@ Example: #[arg( long = "p2p.experimental.kad-name", long_help = "Custom Kademlia protocol name.", - value_name = "LIST", - value_delimiter = ',', + value_name = "PROTOCOL_NAME", env = "PATHFINDER_P2P_EXPERIMENTAL_KAD_NAME" )] kad_name: Option, From 1bcdf06d8f751c80a46eb01ce74509678cee74cd Mon Sep 17 00:00:00 2001 From: Krisztian Kovacs Date: Thu, 19 Sep 2024 15:06:45 +0200 Subject: [PATCH 068/282] chore(github): fix P2P Docker build --- .github/workflows/docker-p2p.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker-p2p.yml b/.github/workflows/docker-p2p.yml index 136a47fd1e..48523ccb82 100644 --- a/.github/workflows/docker-p2p.yml +++ b/.github/workflows/docker-p2p.yml @@ -58,7 +58,7 @@ jobs: file: ./Dockerfile build-args: | PATHFINDER_FORCE_VERSION=${{ steps.generate_version.outputs.pathfinder_version }} - CARGO_EXTRA_ARGS="--features p2p" + CARGO_EXTRA_ARGS=--features p2p builder: ${{ steps.buildx.outputs.name }} push: true labels: ${{ steps.meta.outputs.labels }} From f9e755917ece8bf361c2d0e21f2111b59d250de1 Mon Sep 17 00:00:00 2001 From: sistemd Date: Fri, 20 Sep 2024 12:05:19 +0200 Subject: [PATCH 069/282] reorgs in starknet_subscribeNewHeads --- crates/pathfinder/src/state/sync.rs | 40 +++++- crates/pathfinder/src/state/sync/l2.rs | 1 - crates/rpc/src/dto/block.rs | 21 +++ crates/rpc/src/jsonrpc.rs | 24 +++- crates/rpc/src/jsonrpc/router.rs | 2 +- crates/rpc/src/jsonrpc/router/subscription.rs | 77 ++++++---- crates/rpc/src/lib.rs | 2 +- crates/rpc/src/method.rs | 2 + crates/rpc/src/method/subscribe_new_heads.rs | 134 ++++++++++++++---- 9 files changed, 245 insertions(+), 58 deletions(-) diff --git a/crates/pathfinder/src/state/sync.rs b/crates/pathfinder/src/state/sync.rs index dbebfbdf74..7070d6c5e4 100644 --- a/crates/pathfinder/src/state/sync.rs +++ b/crates/pathfinder/src/state/sync.rs @@ -25,7 +25,7 @@ use pathfinder_ethereum::{EthereumApi, EthereumStateUpdate}; use pathfinder_merkle_tree::contract_state::update_contract_state; use pathfinder_merkle_tree::{ClassCommitmentTree, StorageCommitmentTree}; use pathfinder_rpc::v02::types::syncing::{self, NumberedBlock, Syncing}; -use pathfinder_rpc::{Notifications, PendingData, SyncState, TopicBroadcasters}; +use pathfinder_rpc::{Notifications, PendingData, Reorg, SyncState, TopicBroadcasters}; use pathfinder_storage::{Connection, Storage, Transaction, TransactionBehavior}; use primitive_types::H160; use starknet_gateway_client::GatewayApi; @@ -603,7 +603,7 @@ async fn consumer( } Reorg(reorg_tail) => { tracing::trace!("Reorg L2 state to block {}", reorg_tail); - l2_reorg(&mut db_conn, reorg_tail) + l2_reorg(&mut db_conn, reorg_tail, &mut notifications) .await .with_context(|| format!("Reorg L2 state to {reorg_tail:?}"))?; @@ -1011,7 +1011,11 @@ async fn l2_update( Ok(()) } -async fn l2_reorg(connection: &mut Connection, reorg_tail: BlockNumber) -> anyhow::Result<()> { +async fn l2_reorg( + connection: &mut Connection, + reorg_tail: BlockNumber, + notifications: &mut Notifications, +) -> anyhow::Result<()> { tokio::task::block_in_place(move || { let transaction = connection .transaction_with_behavior(TransactionBehavior::Immediate) @@ -1023,6 +1027,15 @@ async fn l2_reorg(connection: &mut Connection, reorg_tail: BlockNumber) -> anyho .context("Latest block number is none during reorg")? .0; + let reorg_tail_hash = transaction + .block_hash(reorg_tail.into()) + .context("Fetching first block hash")? + .context("Expected first block hash to exist")?; + let head_hash = transaction + .block_hash(head.into()) + .context("Fetching last block hash")? + .context("Expected last block hash to exist")?; + transaction .increment_reorg_counter() .context("Incrementing reorg counter")?; @@ -1076,7 +1089,26 @@ async fn l2_reorg(connection: &mut Connection, reorg_tail: BlockNumber) -> anyho } } - transaction.commit().context("Commit database transaction") + transaction + .commit() + .context("Commit database transaction")?; + + notifications + .reorgs + .send( + Reorg { + first_block_number: reorg_tail, + first_block_hash: reorg_tail_hash, + last_block_number: head, + last_block_hash: head_hash, + } + .into(), + ) + // Ignore errors in case nobody is listening. New listeners may subscribe in the + // future. + .ok(); + + Ok(()) }) } diff --git a/crates/pathfinder/src/state/sync/l2.rs b/crates/pathfinder/src/state/sync/l2.rs index 7f0a73faee..c7b1d3abdf 100644 --- a/crates/pathfinder/src/state/sync/l2.rs +++ b/crates/pathfinder/src/state/sync/l2.rs @@ -1123,7 +1123,6 @@ async fn reorg( #[cfg(test)] mod tests { - mod sync { use std::sync::LazyLock; diff --git a/crates/rpc/src/dto/block.rs b/crates/rpc/src/dto/block.rs index 635e297186..8c1db353df 100644 --- a/crates/rpc/src/dto/block.rs +++ b/crates/rpc/src/dto/block.rs @@ -2,6 +2,7 @@ use pathfinder_common::{GasPrice, L1DataAvailabilityMode}; use serde::de::Error; use super::serialize::SerializeStruct; +use crate::Reorg; #[derive(Debug)] pub struct BlockHeader<'a>(pub &'a pathfinder_common::BlockHeader); @@ -135,3 +136,23 @@ impl crate::dto::serialize::SerializeForVersion for ResourcePrice { serializer.end() } } + +impl crate::dto::serialize::SerializeForVersion for Reorg { + fn serialize( + &self, + serializer: super::serialize::Serializer, + ) -> Result { + let mut serializer = serializer.serialize_struct()?; + serializer.serialize_field("first_block_number", &self.first_block_number.get())?; + serializer.serialize_field( + "first_block_hash", + &crate::dto::Felt(&self.first_block_hash.0), + )?; + serializer.serialize_field("last_block_number", &self.last_block_number.get())?; + serializer.serialize_field( + "last_block_hash", + &crate::dto::Felt(&self.last_block_hash.0), + )?; + serializer.end() + } +} diff --git a/crates/rpc/src/jsonrpc.rs b/crates/rpc/src/jsonrpc.rs index a002166915..105b885774 100644 --- a/crates/rpc/src/jsonrpc.rs +++ b/crates/rpc/src/jsonrpc.rs @@ -7,11 +7,18 @@ pub mod websocket; use std::sync::Arc; pub use error::RpcError; +use pathfinder_common::{BlockHash, BlockNumber}; pub use request::RpcRequest; pub use response::RpcResponse; #[cfg(test)] pub use router::handle_json_rpc_socket; -pub use router::{rpc_handler, RpcRouter, RpcRouterBuilder, RpcSubscriptionFlow}; +pub use router::{ + rpc_handler, + RpcRouter, + RpcRouterBuilder, + RpcSubscriptionFlow, + SubscriptionMessage, +}; use tokio::sync::broadcast; #[derive(Debug, PartialEq, Clone)] @@ -33,11 +40,24 @@ impl RequestId { #[derive(Debug, Clone)] pub struct Notifications { pub block_headers: broadcast::Sender>, + pub reorgs: broadcast::Sender>, +} + +#[derive(Debug, Clone)] +pub struct Reorg { + pub first_block_number: BlockNumber, + pub first_block_hash: BlockHash, + pub last_block_number: BlockNumber, + pub last_block_hash: BlockHash, } impl Default for Notifications { fn default() -> Self { let (block_headers, _) = broadcast::channel(1024); - Self { block_headers } + let (reorgs, _) = broadcast::channel(1024); + Self { + block_headers, + reorgs, + } } } diff --git a/crates/rpc/src/jsonrpc/router.rs b/crates/rpc/src/jsonrpc/router.rs index 5982822300..a864fca40f 100644 --- a/crates/rpc/src/jsonrpc/router.rs +++ b/crates/rpc/src/jsonrpc/router.rs @@ -7,7 +7,7 @@ use axum::response::IntoResponse; use futures::{Future, FutureExt, StreamExt}; use http::HeaderValue; use method::RpcMethodEndpoint; -pub use subscription::{handle_json_rpc_socket, RpcSubscriptionFlow}; +pub use subscription::{handle_json_rpc_socket, RpcSubscriptionFlow, SubscriptionMessage}; use subscription::{split_ws, RpcSubscriptionEndpoint}; use crate::context::RpcContext; diff --git a/crates/rpc/src/jsonrpc/router/subscription.rs b/crates/rpc/src/jsonrpc/router/subscription.rs index 3774123610..6feee7e64c 100644 --- a/crates/rpc/src/jsonrpc/router/subscription.rs +++ b/crates/rpc/src/jsonrpc/router/subscription.rs @@ -60,9 +60,6 @@ pub trait RpcSubscriptionFlow: Send + Sync { type Request: crate::dto::DeserializeForVersion + Send + Sync + 'static; type Notification: crate::dto::serialize::SerializeForVersion + Send + Sync + 'static; - /// The value for the `method` field of the subscription notification. - fn subscription_name() -> &'static str; - /// The block to start streaming from. If the subscription endpoint does not /// support catching up, the value returned by this method is /// irrelevant. @@ -76,10 +73,25 @@ pub trait RpcSubscriptionFlow: Send + Sync { req: &Self::Request, from: BlockNumber, to: BlockNumber, - ) -> Result, RpcError>; + ) -> Result>, RpcError>; /// Subscribe to active updates. - async fn subscribe(state: RpcContext, tx: mpsc::Sender<(Self::Notification, BlockNumber)>); + async fn subscribe( + state: RpcContext, + tx: mpsc::Sender>, + ); +} + +#[derive(Debug)] +pub struct SubscriptionMessage { + /// [`RpcSubscriptionFlow::Notification`] to be sent to the client. + pub notification: T, + /// The block number of the notification. If the notification does not have + /// a block number, this value does not matter. + pub block_number: BlockNumber, + /// The value for the `method` field of the subscription notification sent + /// to the client. + pub subscription_name: &'static str, } #[axum::async_trait] @@ -102,7 +114,6 @@ where subscription_id, subscriptions, tx, - subscription_name: T::subscription_name(), version, _phantom: Default::default(), }; @@ -143,12 +154,16 @@ where // Caught up. break; } - for (message, block_number) in messages { - if tx.send(message).await.is_err() { + for msg in messages { + if tx + .send(msg.notification, msg.subscription_name) + .await + .is_err() + { // Subscription closing. return Ok(()); } - current_block = block_number; + current_block = msg.block_number; } // Increment the current block by 1 because the catch_up range is inclusive. current_block += 1; @@ -158,9 +173,9 @@ where }; // Subscribe to new blocks. Receive the first subscription message. - let (tx1, mut rx1) = mpsc::channel::<(T::Notification, BlockNumber)>(1024); + let (tx1, mut rx1) = mpsc::channel::>(1024); tokio::spawn(T::subscribe(state.clone(), tx1)); - let (first_message, block_number) = match rx1.recv().await { + let first_msg = match rx1.recv().await { Some(msg) => msg, None => { // Subscription closing. @@ -172,10 +187,14 @@ where // block that will be streamed from the subscription. This way we don't miss any // blocks. Because the catch_up range is inclusive, we need to subtract 1 from // the block number. - if let Some(block_number) = block_number.parent() { + if let Some(block_number) = first_msg.block_number.parent() { let messages = T::catch_up(&state, &req, current_block, block_number).await?; - for (message, _) in messages { - if tx.send(message).await.is_err() { + for msg in messages { + if tx + .send(msg.notification, msg.subscription_name) + .await + .is_err() + { // Subscription closing. return Ok(()); } @@ -183,23 +202,31 @@ where } // Send the first subscription message and then forward the rest. - if tx.send(first_message).await.is_err() { + if tx + .send(first_msg.notification, first_msg.subscription_name) + .await + .is_err() + { // Subscription closing. return Ok(()); } - let mut last_block = block_number; + let mut last_block = first_msg.block_number; tokio::spawn(async move { - while let Some((message, block_number)) = rx1.recv().await { - if block_number.get() > last_block.get() + 1 { + while let Some(msg) = rx1.recv().await { + if msg.block_number.get() > last_block.get() + 1 { // One or more blocks have been skipped. This is likely due to a race condition // resulting from a reorg. This message should be ignored. continue; } - if tx.send(message).await.is_err() { + if tx + .send(msg.notification, msg.subscription_name) + .await + .is_err() + { // Subscription closing. break; } - last_block = block_number; + last_block = msg.block_number; } }); Ok(()) @@ -455,7 +482,6 @@ struct SubscriptionSender { subscription_id: SubscriptionId, subscriptions: Arc>>, tx: mpsc::Sender>, - subscription_name: &'static str, version: RpcVersion, _phantom: std::marker::PhantomData, } @@ -466,7 +492,6 @@ impl Clone for SubscriptionSender { subscription_id: self.subscription_id, subscriptions: self.subscriptions.clone(), tx: self.tx.clone(), - subscription_name: self.subscription_name, version: self.version, _phantom: Default::default(), } @@ -474,14 +499,18 @@ impl Clone for SubscriptionSender { } impl SubscriptionSender { - pub async fn send(&self, value: T) -> Result<(), mpsc::error::SendError<()>> { + pub async fn send( + &self, + value: T, + subscription_name: &'static str, + ) -> Result<(), mpsc::error::SendError<()>> { if !self.subscriptions.contains_key(&self.subscription_id) { // Race condition due to the subscription ending. return Ok(()); } let notification = RpcNotification { jsonrpc: "2.0", - method: self.subscription_name, + method: subscription_name, params: SubscriptionResult { subscription_id: self.subscription_id, result: value, diff --git a/crates/rpc/src/lib.rs b/crates/rpc/src/lib.rs index db0824cda6..f8aceee372 100644 --- a/crates/rpc/src/lib.rs +++ b/crates/rpc/src/lib.rs @@ -28,7 +28,7 @@ use axum::response::IntoResponse; use context::RpcContext; pub use executor::compose_executor_transaction; use http_body::Body; -pub use jsonrpc::Notifications; +pub use jsonrpc::{Notifications, Reorg}; use pathfinder_common::AllowedOrigins; pub use pending::PendingData; use tokio::sync::RwLock; diff --git a/crates/rpc/src/method.rs b/crates/rpc/src/method.rs index 039f8d73cd..225fc5764a 100644 --- a/crates/rpc/src/method.rs +++ b/crates/rpc/src/method.rs @@ -56,3 +56,5 @@ pub use simulate_transactions::simulate_transactions; pub use syncing::syncing; pub use trace_block_transactions::trace_block_transactions; pub use trace_transaction::trace_transaction; + +const REORG_SUBSCRIPTION_NAME: &str = "starknet_subscriptionReorg"; diff --git a/crates/rpc/src/method/subscribe_new_heads.rs b/crates/rpc/src/method/subscribe_new_heads.rs index 0a1009361b..2d46adbbe4 100644 --- a/crates/rpc/src/method/subscribe_new_heads.rs +++ b/crates/rpc/src/method/subscribe_new_heads.rs @@ -4,8 +4,10 @@ use axum::async_trait; use pathfinder_common::{BlockId, BlockNumber}; use tokio::sync::mpsc; +use super::REORG_SUBSCRIPTION_NAME; use crate::context::RpcContext; -use crate::jsonrpc::{RpcError, RpcSubscriptionFlow}; +use crate::jsonrpc::{RpcError, RpcSubscriptionFlow, SubscriptionMessage}; +use crate::Reorg; pub struct SubscribeNewHeads; @@ -25,26 +27,30 @@ impl crate::dto::DeserializeForVersion for Request { } #[derive(Debug)] -pub struct Message(Arc); +pub enum Message { + BlockHeader(Arc), + Reorg(Arc), +} impl crate::dto::serialize::SerializeForVersion for Message { fn serialize( &self, serializer: crate::dto::serialize::Serializer, ) -> Result { - crate::dto::BlockHeader(&self.0).serialize(serializer) + match self { + Self::BlockHeader(header) => crate::dto::BlockHeader(header).serialize(serializer), + Self::Reorg(reorg) => reorg.serialize(serializer), + } } } +const SUBSCRIPTION_NAME: &str = "starknet_subscriptionNewHeads"; + #[async_trait] impl RpcSubscriptionFlow for SubscribeNewHeads { type Request = Request; type Notification = Message; - fn subscription_name() -> &'static str { - "starknet_subscriptionNewHeads" - } - fn starting_block(req: &Self::Request) -> BlockId { req.block } @@ -54,7 +60,7 @@ impl RpcSubscriptionFlow for SubscribeNewHeads { _req: &Self::Request, from: BlockNumber, to: BlockNumber, - ) -> Result, RpcError> { + ) -> Result>, RpcError> { let storage = state.storage.clone(); let headers = tokio::task::spawn_blocking(move || -> Result<_, RpcError> { let mut conn = storage.connection().map_err(RpcError::InternalError)?; @@ -67,28 +73,66 @@ impl RpcSubscriptionFlow for SubscribeNewHeads { .into_iter() .map(|header| { let block_number = header.number; - (Message(header.into()), block_number) + SubscriptionMessage { + notification: Message::BlockHeader(header.into()), + block_number, + subscription_name: SUBSCRIPTION_NAME, + } }) .collect()) } - async fn subscribe(state: RpcContext, tx: mpsc::Sender<(Self::Notification, BlockNumber)>) { - let mut rx = state.notifications.block_headers.subscribe(); + async fn subscribe( + state: RpcContext, + tx: mpsc::Sender>, + ) { + let mut headers = state.notifications.block_headers.subscribe(); + let mut reorgs = state.notifications.reorgs.subscribe(); loop { - match rx.recv().await { - Ok(header) => { - let block_number = header.number; - if tx.send((Message(header), block_number)).await.is_err() { - break; + tokio::select! { + reorg = reorgs.recv() => { + match reorg { + Ok(reorg) => { + let block_number = reorg.first_block_number; + if tx.send(SubscriptionMessage { + notification: Message::Reorg(reorg), + block_number, + subscription_name: REORG_SUBSCRIPTION_NAME, + }).await.is_err() { + break; + } + } + Err(e) => { + tracing::debug!( + "Error receiving reorg from notifications channel, node might be \ + lagging: {:?}", + e + ); + break; + } } } - Err(e) => { - tracing::debug!( - "Error receiving block header from notifications channel, node might be \ - lagging: {:?}", - e - ); - break; + header = headers.recv() => { + match header { + Ok(header) => { + let block_number = header.number; + if tx.send(SubscriptionMessage { + notification: Message::BlockHeader(header), + block_number, + subscription_name: SUBSCRIPTION_NAME, + }).await.is_err() { + break; + } + } + Err(e) => { + tracing::debug!( + "Error receiving block header from notifications channel, node might be \ + lagging: {:?}", + e + ); + break; + } + } } } } @@ -100,7 +144,7 @@ mod tests { use std::time::Duration; use axum::extract::ws::Message; - use pathfinder_common::{BlockHash, BlockHeader, BlockNumber, ChainId}; + use pathfinder_common::{felt, BlockHash, BlockHeader, BlockNumber, ChainId}; use pathfinder_crypto::Felt; use pathfinder_storage::StorageBuilder; use starknet_gateway_client::Client; @@ -110,7 +154,7 @@ mod tests { use crate::jsonrpc::{handle_json_rpc_socket, RpcResponse, RpcRouter}; use crate::pending::PendingWatcher; use crate::v02::types::syncing::Syncing; - use crate::{v08, Notifications, SubscriptionId, SyncState}; + use crate::{v08, Notifications, Reorg, SubscriptionId, SyncState}; #[tokio::test] async fn happy_path_with_historic_blocks() { @@ -132,6 +176,46 @@ mod tests { happy_path_test(0).await; } + #[tokio::test] + async fn reorg() { + let (_, mut rx, subscription_id, router) = happy_path_test(0).await; + router + .context + .notifications + .reorgs + .send( + Reorg { + first_block_number: BlockNumber::new_or_panic(1), + first_block_hash: BlockHash(felt!("0x1")), + last_block_number: BlockNumber::new_or_panic(2), + last_block_hash: BlockHash(felt!("0x2")), + } + .into(), + ) + .unwrap(); + let res = rx.recv().await.unwrap().unwrap(); + let json: serde_json::Value = match res { + Message::Text(json) => serde_json::from_str(&json).unwrap(), + _ => panic!("Expected text message"), + }; + assert_eq!( + json, + serde_json::json!({ + "jsonrpc": "2.0", + "method": "starknet_subscriptionReorg", + "params": { + "result": { + "first_block_hash": "0x1", + "first_block_number": 1, + "last_block_hash": "0x2", + "last_block_number": 2 + }, + "subscription_id": subscription_id.0 + } + }) + ); + } + #[tokio::test] async fn race_condition_with_historic_blocks() { let num_blocks = 1000; From 400ed097e31ec917522ed27bec5931dcbf44f9a2 Mon Sep 17 00:00:00 2001 From: sistemd Date: Fri, 20 Sep 2024 12:05:19 +0200 Subject: [PATCH 070/282] attempting to fix ci --- crates/rpc/src/method/subscribe_new_heads.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/rpc/src/method/subscribe_new_heads.rs b/crates/rpc/src/method/subscribe_new_heads.rs index 2d46adbbe4..3b83084c7a 100644 --- a/crates/rpc/src/method/subscribe_new_heads.rs +++ b/crates/rpc/src/method/subscribe_new_heads.rs @@ -493,7 +493,7 @@ mod tests { if i == 9 { return Err(e); } - tokio::task::yield_now().await; + tokio::time::sleep(Duration::from_secs(i)).await; } } } From 775a949ae2f4df33a0a260d739117989afb0ccd1 Mon Sep 17 00:00:00 2001 From: sistemd Date: Fri, 20 Sep 2024 12:05:19 +0200 Subject: [PATCH 071/282] fix flaky tests --- crates/rpc/src/method/subscribe_new_heads.rs | 65 ++++++++++++-------- 1 file changed, 40 insertions(+), 25 deletions(-) diff --git a/crates/rpc/src/method/subscribe_new_heads.rs b/crates/rpc/src/method/subscribe_new_heads.rs index 3b83084c7a..b1c36cee6d 100644 --- a/crates/rpc/src/method/subscribe_new_heads.rs +++ b/crates/rpc/src/method/subscribe_new_heads.rs @@ -219,7 +219,7 @@ mod tests { #[tokio::test] async fn race_condition_with_historic_blocks() { let num_blocks = 1000; - let router = setup(num_blocks); + let router = setup(num_blocks).await; let (sender_tx, mut sender_rx) = mpsc::channel(1024); let (receiver_tx, receiver_rx) = mpsc::channel(1024); handle_json_rpc_socket(router.clone(), sender_tx, receiver_rx); @@ -260,20 +260,28 @@ mod tests { } // Insert more blocks before the active updates kick in. This simulates a // real-world race condition. - for i in 0..num_blocks { - let mut conn = router.context.storage.connection().unwrap(); - let db = conn.transaction().unwrap(); - let header = sample_header(i + num_blocks); - db.insert_block_header(&header).unwrap(); - db.commit().unwrap(); - } + let storage = router.context.storage.clone(); + tokio::task::spawn_blocking(move || { + for i in 0..num_blocks { + let mut conn = storage.connection().unwrap(); + let db = conn.transaction().unwrap(); + let header = sample_header(i + num_blocks); + db.insert_block_header(&header).unwrap(); + db.commit().unwrap(); + } + }) + .await + .unwrap(); for i in 0..10 { - router - .context - .notifications - .block_headers - .send(sample_header(i + 2 * num_blocks).into()) - .unwrap(); + retry(|| { + router + .context + .notifications + .block_headers + .send(sample_header(i + 2 * num_blocks).into()) + }) + .await + .unwrap(); if i == 0 { // First, expect all the newly inserted blocks. for j in 0..num_blocks { @@ -336,15 +344,22 @@ mod tests { assert!(rx.is_empty()); } - fn setup(num_blocks: u64) -> RpcRouter { + async fn setup(num_blocks: u64) -> RpcRouter { let storage = StorageBuilder::in_memory().unwrap(); - let mut conn = storage.connection().unwrap(); - let db = conn.transaction().unwrap(); - for i in 0..num_blocks { - let header = sample_header(i); - db.insert_block_header(&header).unwrap(); - } - db.commit().unwrap(); + tokio::task::spawn_blocking({ + let storage = storage.clone(); + move || { + let mut conn = storage.connection().unwrap(); + let db = conn.transaction().unwrap(); + for i in 0..num_blocks { + let header = sample_header(i); + db.insert_block_header(&header).unwrap(); + } + db.commit().unwrap(); + } + }) + .await + .unwrap(); let (_, pending_data) = tokio::sync::watch::channel(Default::default()); let notifications = Notifications::default(); let ctx = RpcContext { @@ -378,7 +393,7 @@ mod tests { SubscriptionId, RpcRouter, ) { - let router = setup(num_blocks); + let router = setup(num_blocks).await; let (sender_tx, mut sender_rx) = mpsc::channel(1024); let (receiver_tx, receiver_rx) = mpsc::channel(1024); handle_json_rpc_socket(router.clone(), sender_tx, receiver_rx); @@ -486,11 +501,11 @@ mod tests { where E: std::fmt::Debug, { - for i in 0..10 { + for i in 0..25 { match cb() { Ok(result) => return Ok(result), Err(e) => { - if i == 9 { + if i == 24 { return Err(e); } tokio::time::sleep(Duration::from_secs(i)).await; From ffcf60f88ef8618cbbdb0834a4bc899799403290 Mon Sep 17 00:00:00 2001 From: Krisztian Kovacs Date: Fri, 20 Sep 2024 11:15:14 +0200 Subject: [PATCH 072/282] fix(pathfinder/state/revert): always apply reverse state updates When reverting Merkle tries to a previous state we have been checking if we still have the tree root for the revert target. This seems like a good idea, because if we _still_ have the trie(s) for the target block then why bother applying reverse state updates from the current state? It turns out that's not so simple. First, trie nodes added in blocks that are being deleted (right after the revert when pruning the block range that has been reorged away) will be permanently leaked. There's no way around that because we do not keep track of per-block trie node _additions_, only _removals_. Second -- and this is _much_ more problematic -- the way we were handling trie removals data was just plain wrong. When reverting trie state to block N we've just moved removals data for all blocks > N into our target block. That removals data was effectively nodes that have been deleted from the trie after block N. The problem with this is that when we were then removing trie nodes based on the removals data of block N later, we might be deleting nodes that were still reachable at that block! This change fixes this by removing the special case: when doing a revert we are _always_ just applying a reverse diff to the _current_ state of the trie. This makes sure that we'll compute and insert new state roots for the target block and that the _removals_ data for that block will indeed only contain nodes that are unreachable (either by having been removed at a previous reorged-away block _or_ by the reverse update). This change has been tested using the `feeder_gateway` tool triggering thousands of multi-block reorgs while syncing Sepolia testnet block range 113k to 194k. Without this fix this end-to-end test was consistently reproducing the "missing node" issue. Closes: #2170 --- crates/pathfinder/src/state/sync/revert.rs | 173 ++++++++++----------- crates/storage/src/connection/trie.rs | 2 +- 2 files changed, 84 insertions(+), 91 deletions(-) diff --git a/crates/pathfinder/src/state/sync/revert.rs b/crates/pathfinder/src/state/sync/revert.rs index 06d4580d8c..6abe0c2042 100644 --- a/crates/pathfinder/src/state/sync/revert.rs +++ b/crates/pathfinder/src/state/sync/revert.rs @@ -49,8 +49,7 @@ pub fn revert_starknet_state( target_block, target_header.class_commitment, )?; - - transaction.coalesce_trie_nodes(target_block) + transaction.coalesce_trie_removals(target_block) } /// Revert all contract/global storage trie updates. @@ -63,55 +62,52 @@ fn revert_contract_updates( target_block: BlockNumber, expected_storage_commitment: StorageCommitment, ) -> anyhow::Result<()> { - if !transaction.storage_root_exists(target_block)? { - let updates = transaction.reverse_contract_updates(head, target_block)?; - - let mut global_tree = StorageCommitmentTree::load(transaction, head) - .context("Loading global storage tree")?; - - for (contract_address, contract_update) in updates { - let state_hash = pathfinder_merkle_tree::contract_state::revert_contract_state( - transaction, - contract_address, - head, - target_block, - contract_update, - )?; - - transaction - .insert_contract_state_hash(target_block, contract_address, state_hash) - .context("Inserting reverted contract state hash")?; - - global_tree - .set(contract_address, state_hash) - .context("Updating contract state hash in global tree")?; - } - - tracing::debug!("Applied reverse updates, committing global state tree"); - - let (storage_commitment, trie_update) = global_tree - .commit() - .context("Committing global state tree")?; - - if expected_storage_commitment != storage_commitment { - anyhow::bail!( - "Storage commitment mismatch: expected {}, calculated {}", - expected_storage_commitment, - storage_commitment - ); - } - - let root_idx = transaction - .insert_storage_trie(&trie_update, target_block) - .context("Persisting storage trie")?; + let updates = transaction.reverse_contract_updates(head, target_block)?; + + let mut global_tree = + StorageCommitmentTree::load(transaction, head).context("Loading global storage tree")?; + + for (contract_address, contract_update) in updates { + let state_hash = pathfinder_merkle_tree::contract_state::revert_contract_state( + transaction, + contract_address, + head, + target_block, + contract_update, + )?; transaction - .insert_storage_root(target_block, root_idx) - .context("Inserting storage root index")?; - tracing::debug!(%target_block, %storage_commitment, "Committed global state tree"); - } else { - tracing::debug!(%target_block, "State tree root node exists"); + .insert_contract_state_hash(target_block, contract_address, state_hash) + .context("Inserting reverted contract state hash")?; + + global_tree + .set(contract_address, state_hash) + .context("Updating contract state hash in global tree")?; + } + + tracing::debug!("Applied reverse updates, committing global state tree"); + + let (storage_commitment, trie_update) = global_tree + .commit() + .context("Committing global state tree")?; + + if expected_storage_commitment != storage_commitment { + anyhow::bail!( + "Storage commitment mismatch: expected {}, calculated {}", + expected_storage_commitment, + storage_commitment + ); } + + let root_idx = transaction + .insert_storage_trie(&trie_update, target_block) + .context("Persisting storage trie")?; + + transaction + .insert_storage_root(target_block, root_idx) + .context("Inserting storage root index")?; + tracing::debug!(%target_block, %storage_commitment, "Committed global state tree"); + Ok(()) } @@ -122,51 +118,48 @@ fn revert_class_updates( target_block: BlockNumber, expected_class_commitment: ClassCommitment, ) -> anyhow::Result<()> { - if !transaction.class_root_exists(target_block)? { - let updates = transaction.reverse_sierra_class_updates(head, target_block)?; - - let mut class_tree = ClassCommitmentTree::load(transaction, head) - .context("Loading class commitment trie")?; - - for (class_hash, casm_update) in updates { - let new_value = match casm_update { - None => { - // The class must be removed - ClassCommitmentLeafHash::ZERO - } - Some(casm_hash) => { - // Class hash has changed. Note that the class commitment leaf must have already - // been added to storage. - pathfinder_common::calculate_class_commitment_leaf_hash(casm_hash) - } - }; - - class_tree - .set(class_hash, new_value) - .context("Updating class commitment trie")?; - } - - let (class_commitment, trie_update) = - class_tree.commit().context("Committing class trie")?; - - if expected_class_commitment != class_commitment { - anyhow::bail!( - "Storage commitment mismatch: expected {}, calculated {}", - expected_class_commitment, - class_commitment - ); - } - - let root_idx = transaction - .insert_class_trie(&trie_update, target_block) - .context("Persisting class trie")?; + let updates = transaction.reverse_sierra_class_updates(head, target_block)?; + + let mut class_tree = + ClassCommitmentTree::load(transaction, head).context("Loading class commitment trie")?; + + for (class_hash, casm_update) in updates { + let new_value = match casm_update { + None => { + // The class must be removed + ClassCommitmentLeafHash::ZERO + } + Some(casm_hash) => { + // Class hash has changed. Note that the class commitment leaf must have already + // been added to storage. + pathfinder_common::calculate_class_commitment_leaf_hash(casm_hash) + } + }; + + class_tree + .set(class_hash, new_value) + .context("Updating class commitment trie")?; + } - transaction - .insert_class_root(target_block, root_idx) - .context("Inserting class root index")?; + let (class_commitment, trie_update) = class_tree.commit().context("Committing class trie")?; - tracing::debug!(%target_block, %class_commitment, "Committed class trie"); + if expected_class_commitment != class_commitment { + anyhow::bail!( + "Storage commitment mismatch: expected {}, calculated {}", + expected_class_commitment, + class_commitment + ); } + let root_idx = transaction + .insert_class_trie(&trie_update, target_block) + .context("Persisting class trie")?; + + transaction + .insert_class_root(target_block, root_idx) + .context("Inserting class root index")?; + + tracing::debug!(%target_block, %class_commitment, "Committed class trie"); + Ok(()) } diff --git a/crates/storage/src/connection/trie.rs b/crates/storage/src/connection/trie.rs index 7c55c451e9..11fe530526 100644 --- a/crates/storage/src/connection/trie.rs +++ b/crates/storage/src/connection/trie.rs @@ -366,7 +366,7 @@ impl Transaction<'_> { Ok(()) } - pub fn coalesce_trie_nodes(&self, target_block: BlockNumber) -> anyhow::Result<()> { + pub fn coalesce_trie_removals(&self, target_block: BlockNumber) -> anyhow::Result<()> { self.coalesce_removed_trie_nodes(target_block, "trie_contracts")?; self.coalesce_removed_trie_nodes(target_block, "trie_storage")?; self.coalesce_removed_trie_nodes(target_block, "trie_class") From a460aca2b668d1056c140b9b407ce3f0dc47f805 Mon Sep 17 00:00:00 2001 From: Krisztian Kovacs Date: Fri, 20 Sep 2024 15:12:37 +0200 Subject: [PATCH 073/282] fix(merkle-tree/revert): default contract root to zero for contracts with empty storage During a revert it's possible that we're reverting state for a contract whose storage is completely empty. Such contracts have _no_ contract root in the database so in case loading the contract root fails we should default that to zero instead of failing. --- crates/merkle-tree/src/contract_state.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/merkle-tree/src/contract_state.rs b/crates/merkle-tree/src/contract_state.rs index 33f7333131..4bde30d203 100644 --- a/crates/merkle-tree/src/contract_state.rs +++ b/crates/merkle-tree/src/contract_state.rs @@ -207,7 +207,7 @@ pub fn revert_contract_state( } else { transaction .contract_root(head, contract_address)? - .context("Fetching current contract root")? + .unwrap_or(ContractRoot::ZERO) }; let state_hash = if contract_address.is_system_contract() && root == ContractRoot::ZERO From 0c21d1069d72d9682a556f11b498564588dcc6ec Mon Sep 17 00:00:00 2001 From: Krisztian Kovacs Date: Fri, 20 Sep 2024 17:38:51 +0200 Subject: [PATCH 074/282] chore: update CHANGELOG --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5642e270df..b6f8b8eaca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Ethereum RPC API now requires Websocket endpoints (prev. HTTP). If an HTTP url is provided instead, Pathfinder will attempt to connect vía Websocket protocol at that same url. +### Fixed + +- Pathfinder occasionally corrupts its Merkle trie storage during reorgs and then stops later with a "Node X at height Y is missing" or "Stored node's hash is missing" error. ## [0.14.2] - 2024-09-03 From 05b69f5366f7e1995713667eaf49394681810425 Mon Sep 17 00:00:00 2001 From: Jim McDonald Date: Fri, 20 Sep 2024 22:24:19 +0100 Subject: [PATCH 075/282] Provide flag to disable the version update check. --- crates/pathfinder/src/bin/pathfinder/config.rs | 11 +++++++++++ crates/pathfinder/src/bin/pathfinder/main.rs | 4 +++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/crates/pathfinder/src/bin/pathfinder/config.rs b/crates/pathfinder/src/bin/pathfinder/config.rs index 34d78da537..f01f4b5197 100644 --- a/crates/pathfinder/src/bin/pathfinder/config.rs +++ b/crates/pathfinder/src/bin/pathfinder/config.rs @@ -149,6 +149,15 @@ Examples: )] color: Color, + #[arg( + long = "disable-version-update-check", + long_help = "Disable the periodic version update check.", + default_value = "false", + env = "PATHFINDER_DISABLE_VERSION_UPDATE_CHECK", + value_name = "BOOL" + )] + disable_version_update_check: bool, + #[cfg(feature = "p2p")] #[clap(flatten)] p2p: P2PCli, @@ -684,6 +693,7 @@ pub struct Config { pub poll_interval: std::time::Duration, pub l1_poll_interval: std::time::Duration, pub color: Color, + pub disable_version_update_check: bool, pub p2p: P2PConfig, pub debug: DebugConfig, pub verify_tree_hashes: bool, @@ -970,6 +980,7 @@ impl Config { poll_interval: Duration::from_secs(cli.poll_interval.get()), l1_poll_interval: Duration::from_secs(cli.l1_poll_interval.get()), color: cli.color, + disable_version_update_check: cli.disable_version_update_check, p2p: P2PConfig::parse_or_exit(cli.p2p), debug: DebugConfig::parse(cli.debug), verify_tree_hashes: cli.verify_tree_node_data, diff --git a/crates/pathfinder/src/bin/pathfinder/main.rs b/crates/pathfinder/src/bin/pathfinder/main.rs index de86086f74..7ab60493bd 100644 --- a/crates/pathfinder/src/bin/pathfinder/main.rs +++ b/crates/pathfinder/src/bin/pathfinder/main.rs @@ -289,7 +289,9 @@ Hint: This is usually caused by exceeding the file descriptor limit of your syst tokio::spawn(std::future::pending()) }; - tokio::spawn(update::poll_github_for_releases()); + if !config.disable_version_update_check { + tokio::spawn(update::poll_github_for_releases()); + } let mut term_signal = signal(SignalKind::terminate())?; let mut int_signal = signal(SignalKind::interrupt())?; From 8efd56b03d3d2067647beacb3884a0fe852a084f Mon Sep 17 00:00:00 2001 From: Jim McDonald Date: Fri, 20 Sep 2024 22:37:13 +0100 Subject: [PATCH 076/282] Update changelog. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5642e270df..3a6e46c2a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Pathfinder now fetches data concurrently from the feeder gateway when catching up. The `--gateway.fetch-concurrency` CLI option can be used to limit how many blocks are fetched concurrently (the default is 8). +- `--disable-version-update-check` CLI option has been added to disable the periodic checking for a new version. ### Changed From d4a18aacd62616eb3753cb6452e085378ca54d50 Mon Sep 17 00:00:00 2001 From: Krisztian Kovacs Date: Mon, 23 Sep 2024 16:08:31 +0200 Subject: [PATCH 077/282] Revert "feat: emit from batch up to the failed block" This reverts commit fcacc67d602f5e1fb8526c1b7fd1064fc0de5e81. --- crates/pathfinder/src/state/sync/l2.rs | 52 +++++++------------------- 1 file changed, 14 insertions(+), 38 deletions(-) diff --git a/crates/pathfinder/src/state/sync/l2.rs b/crates/pathfinder/src/state/sync/l2.rs index c7b1d3abdf..3c681b9290 100644 --- a/crates/pathfinder/src/state/sync/l2.rs +++ b/crates/pathfinder/src/state/sync/l2.rs @@ -667,17 +667,11 @@ where async move { let t_block = std::time::Instant::now(); - let (block, state_update) = sequencer - .state_update_with_block(block_number) - .await - .map_err(|e| (block_number, e.into()))?; + let (block, state_update) = sequencer.state_update_with_block(block_number).await?; let t_block = t_block.elapsed(); let t_signature = std::time::Instant::now(); - let signature = sequencer - .signature(block_number.into()) - .await - .map_err(|e| (block_number, e.into()))?; + let signature = sequencer.signature(block_number.into()).await?; let t_signature = t_signature.elapsed(); let span = tracing::Span::current(); @@ -742,16 +736,14 @@ where ) = rx .await .expect("Panic on rayon thread while verifying block") - .context("Verifying block contents") - .map_err(|e| (block_number, e))?; + .context("Verifying block contents")?; let t_declare = std::time::Instant::now(); let downloaded_classes = download_new_classes(&state_update, &sequencer, storage) .await .with_context(|| { format!("Handling newly declared classes for block {block_number:?}") - }) - .map_err(|e| (block_number, e))?; + })?; let t_declare = t_declare.elapsed(); let timings = Timings { @@ -760,7 +752,7 @@ where signature_download: t_signature, }; - Ok::<_, (BlockNumber, anyhow::Error)>(( + Ok::<_, anyhow::Error>(( block, state_update, signature, @@ -795,28 +787,17 @@ where futures::stream::iter(futures_chunk).buffer_unordered(fetch_concurrency.get()); let mut ordered_blocks = BTreeMap::new(); - let mut failed_block = None; while let Some(result) = stream.next().await { - let ok = match result { - Ok(x) => x, - Err((block, error)) => { - // We've hit an error, so we stop the loop and return. `head` has been updated - // to the last synced block so our "tracking" sync will just - // continue from there. - tracing::info!( - %block, %error, - "Error during bulk syncing blocks, falling back to normal sync", - ); - - if block == start { - return Ok(()); - } else { - // We can still emit up to the failed block - failed_block = Some(block); - continue; - } - } + let Ok(ok) = result else { + // We've hit an error, so we stop the loop and return. `head` has been updated + // to the last synced block so our "tracking" sync will just + // continue from there. + tracing::info!( + "Error during bulk syncing blocks, falling back to normal sync: {}", + result.err().unwrap() + ); + return Ok(()); }; ordered_blocks.insert(ok.0.block_number.get(), ok); @@ -886,11 +867,6 @@ where .await .context("Event channel closed")?; } - - match failed_block { - Some(x) if start == x.get() => return Ok(()), - _ => {} - } } } From 057e46b432babaf8672c9e4edc009b9199bc5643 Mon Sep 17 00:00:00 2001 From: Krisztian Kovacs Date: Tue, 24 Sep 2024 11:25:43 +0200 Subject: [PATCH 078/282] Revert "test: emit from batch up to the failed block" This reverts commit 96704576715783ce678e619c91b417259708c2e5. --- crates/pathfinder/src/state/sync/l2.rs | 107 +++++++++---------------- 1 file changed, 40 insertions(+), 67 deletions(-) diff --git a/crates/pathfinder/src/state/sync/l2.rs b/crates/pathfinder/src/state/sync/l2.rs index 3c681b9290..7f09ba6dfb 100644 --- a/crates/pathfinder/src/state/sync/l2.rs +++ b/crates/pathfinder/src/state/sync/l2.rs @@ -3041,77 +3041,50 @@ mod tests { assert_matches!(result, Ok(Some((BLOCK1_NUMBER, BLOCK1_HASH, _)))); } - mod no_such_block { - use pretty_assertions_sorted::{assert_eq, assert_eq_sorted}; - - use super::*; - - #[tokio::test] - async fn first_in_batch() { - let (tx_event, mut rx_event) = tokio::sync::mpsc::channel(1); - let mut mock = MockGatewayApi::new(); - - // Downloading the genesis block fails with block not found - expect_state_update_with_block_no_sequence( - &mut mock, - BLOCK0_NUMBER, - Err(block_not_found()), - ); - - // Let's run the UUT - let jh = spawn_bulk_sync(tx_event, mock); - - assert!(rx_event.recv().await.is_none()); + #[tokio::test] + async fn no_such_block() { + let (tx_event, mut rx_event) = tokio::sync::mpsc::channel(1); + let mut mock = MockGatewayApi::new(); - let result = jh.await.unwrap(); - assert_matches!(result, Ok(None)); - } + // Download the genesis block with respective state update and contracts + expect_state_update_with_block_no_sequence( + &mut mock, + BLOCK0_NUMBER, + Ok((BLOCK0.clone(), STATE_UPDATE0.clone())), + ); + expect_class_by_hash_no_sequence( + &mut mock, + CONTRACT0_HASH, + Ok(CONTRACT0_DEF.clone()), + ); + expect_signature_no_sequence( + &mut mock, + BLOCK0_NUMBER.into(), + Ok(BLOCK0_SIGNATURE.clone()), + ); + // Downloading block 1 fails with block not found + expect_state_update_with_block_no_sequence( + &mut mock, + BLOCK1_NUMBER, + Err(block_not_found()), + ); - #[tokio::test] - async fn further_in_batch() { - let (tx_event, mut rx_event) = tokio::sync::mpsc::channel(1); - let mut mock = MockGatewayApi::new(); + // Let's run the UUT + let jh = spawn_bulk_sync(tx_event, mock); - // Download the genesis block with respective state update and contracts - expect_state_update_with_block_no_sequence( - &mut mock, - BLOCK0_NUMBER, - Ok((BLOCK0.clone(), STATE_UPDATE0.clone())), - ); - expect_class_by_hash_no_sequence( - &mut mock, - CONTRACT0_HASH, - Ok(CONTRACT0_DEF.clone()), - ); - expect_signature_no_sequence( - &mut mock, - BLOCK0_NUMBER.into(), - Ok(BLOCK0_SIGNATURE.clone()), - ); - // Downloading block 1 fails with block not found - expect_state_update_with_block_no_sequence( - &mut mock, - BLOCK1_NUMBER, - Err(block_not_found()), - ); + assert_matches!(rx_event.recv().await.unwrap(), + SyncEvent::CairoClass { hash, .. } => { + assert_eq!(hash, CONTRACT0_HASH); + }); + assert_matches!(rx_event.recv().await.unwrap(), SyncEvent::Block((block, _), state_update, signature, _, _) => { + assert_eq!(*block, *BLOCK0); + assert_eq_sorted!(*state_update, *STATE_UPDATE0); + assert_eq!(*signature, BLOCK0_SIGNATURE.signature()); + }); - // Let's run the UUT - let jh = spawn_bulk_sync(tx_event, mock); - - assert_matches!(rx_event.recv().await.unwrap(), - SyncEvent::CairoClass { hash, .. } => { - assert_eq!(hash, CONTRACT0_HASH); - }); - assert_matches!(rx_event.recv().await.unwrap(), SyncEvent::Block((block, _), state_update, signature, _, _) => { - assert_eq!(*block, *BLOCK0); - assert_eq_sorted!(*state_update, *STATE_UPDATE0); - assert_eq!(*signature, BLOCK0_SIGNATURE.signature()); - }); - - // Bulk sync should _not_ fail if the block is not found - let result = jh.await.unwrap(); - assert_matches!(result, Ok(Some((BLOCK0_NUMBER, BLOCK0_HASH, _)))); - } + // Bulk sync should _not_ fail if the block is not found + let result = jh.await.unwrap(); + assert_matches!(result, Ok(Some((BLOCK0_NUMBER, BLOCK0_HASH, _)))); } } } From 82969f480b5f098569239545a2d671fa1276ddc7 Mon Sep 17 00:00:00 2001 From: Krisztian Kovacs Date: Tue, 24 Sep 2024 11:25:46 +0200 Subject: [PATCH 079/282] Revert "revert: "test: fixup no_such_block"" This reverts commit 477e557a0b1ad8c42757838f98e0bb7e8bcb61fa. --- crates/pathfinder/src/state/sync/l2.rs | 59 ++++++++++++++++++++------ 1 file changed, 45 insertions(+), 14 deletions(-) diff --git a/crates/pathfinder/src/state/sync/l2.rs b/crates/pathfinder/src/state/sync/l2.rs index 7f09ba6dfb..a6ec2f9d79 100644 --- a/crates/pathfinder/src/state/sync/l2.rs +++ b/crates/pathfinder/src/state/sync/l2.rs @@ -1554,6 +1554,19 @@ mod tests { .return_once(move |_| returned_result); } + fn expect_state_update_with_block_no_sequence_at_most_once( + mock: &mut MockGatewayApi, + block: BlockNumber, + returned_result: Result<(reply::Block, StateUpdate), SequencerError>, + ) { + use mockall::predicate::eq; + + mock.expect_state_update_with_block() + .with(eq(block)) + .times(..=1) + .return_once(move |_| returned_result); + } + /// Convenience wrapper fn expect_block_header( mock: &mut MockGatewayApi, @@ -1600,6 +1613,19 @@ mod tests { .return_once(|_| returned_result); } + fn expect_signature_no_sequence_at_most_once( + mock: &mut MockGatewayApi, + block: BlockId, + returned_result: Result, + ) { + use mockall::predicate::eq; + + mock.expect_signature() + .with(eq(block)) + .times(..=1) + .return_once(|_| returned_result); + } + /// Convenience wrapper fn expect_class_by_hash( mock: &mut MockGatewayApi, @@ -1626,6 +1652,17 @@ mod tests { .return_once(|_| returned_result); } + fn expect_class_by_hash_no_sequence_at_most_once( + mock: &mut MockGatewayApi, + class_hash: ClassHash, + returned_result: Result, + ) { + mock.expect_pending_class_by_hash() + .withf(move |x| x == &class_hash) + .times(..=1) + .return_once(|_| returned_result); + } + /// Convenience wrapper fn block_not_found() -> SequencerError { SequencerError::StarknetError(StarknetError { @@ -3046,18 +3083,19 @@ mod tests { let (tx_event, mut rx_event) = tokio::sync::mpsc::channel(1); let mut mock = MockGatewayApi::new(); - // Download the genesis block with respective state update and contracts - expect_state_update_with_block_no_sequence( + // Downloading the genesis block data is racing against the failure of block 1, + // hence "at most once" + expect_state_update_with_block_no_sequence_at_most_once( &mut mock, BLOCK0_NUMBER, Ok((BLOCK0.clone(), STATE_UPDATE0.clone())), ); - expect_class_by_hash_no_sequence( + expect_class_by_hash_no_sequence_at_most_once( &mut mock, CONTRACT0_HASH, Ok(CONTRACT0_DEF.clone()), ); - expect_signature_no_sequence( + expect_signature_no_sequence_at_most_once( &mut mock, BLOCK0_NUMBER.into(), Ok(BLOCK0_SIGNATURE.clone()), @@ -3072,19 +3110,12 @@ mod tests { // Let's run the UUT let jh = spawn_bulk_sync(tx_event, mock); - assert_matches!(rx_event.recv().await.unwrap(), - SyncEvent::CairoClass { hash, .. } => { - assert_eq!(hash, CONTRACT0_HASH); - }); - assert_matches!(rx_event.recv().await.unwrap(), SyncEvent::Block((block, _), state_update, signature, _, _) => { - assert_eq!(*block, *BLOCK0); - assert_eq_sorted!(*state_update, *STATE_UPDATE0); - assert_eq!(*signature, BLOCK0_SIGNATURE.signature()); - }); + // The entire unemitted, yet cached batch is rejected + assert!(rx_event.recv().await.is_none()); // Bulk sync should _not_ fail if the block is not found let result = jh.await.unwrap(); - assert_matches!(result, Ok(Some((BLOCK0_NUMBER, BLOCK0_HASH, _)))); + assert_matches!(result, Ok(None)); } } } From e5276ded8ede74b5443faa9a0973f3f5bf073105 Mon Sep 17 00:00:00 2001 From: Krzysztof Lis Date: Mon, 23 Sep 2024 17:06:04 +0200 Subject: [PATCH 080/282] feat: use libp2p::kad internal bootstrap trigger --- crates/p2p/src/behaviour/builder.rs | 1 + crates/p2p/src/lib.rs | 19 +-- crates/p2p/src/main_loop.rs | 127 ++++++++----------- crates/p2p/src/test_utils/peer.rs | 2 +- crates/p2p/src/tests.rs | 47 ++----- crates/pathfinder/src/bin/pathfinder/main.rs | 2 +- 6 files changed, 72 insertions(+), 126 deletions(-) diff --git a/crates/p2p/src/behaviour/builder.rs b/crates/p2p/src/behaviour/builder.rs index 94bb378d14..1463c38e5c 100644 --- a/crates/p2p/src/behaviour/builder.rs +++ b/crates/p2p/src/behaviour/builder.rs @@ -106,6 +106,7 @@ impl Builder { kademlia_config.set_record_ttl(Some(Duration::from_secs(0))); kademlia_config.set_provider_record_ttl(Some(PROVIDER_PUBLICATION_INTERVAL * 3)); kademlia_config.set_provider_publication_interval(Some(PROVIDER_PUBLICATION_INTERVAL)); + kademlia_config.set_periodic_bootstrap_interval(Some(cfg.bootstrap_period)); let peer_id = identity.public().to_peer_id(); let secret = Secret::new(&identity); diff --git a/crates/p2p/src/lib.rs b/crates/p2p/src/lib.rs index f2f23c2774..a6fff638de 100644 --- a/crates/p2p/src/lib.rs +++ b/crates/p2p/src/lib.rs @@ -63,7 +63,9 @@ pub struct Config { /// How long to prevent evicted peers from reconnecting. pub eviction_timeout: Duration, pub ip_whitelist: Vec, - pub bootstrap: BootstrapConfig, + /// If the number of peers is below the low watermark, the node will attempt + /// periodic bootstrapping at this interval. + pub bootstrap_period: Duration, pub inbound_connections_rate_limit: RateLimit, /// Custom protocol name for Kademlia pub kad_name: Option, @@ -79,21 +81,6 @@ pub struct RateLimit { pub interval: Duration, } -#[derive(Copy, Clone, Debug)] -pub struct BootstrapConfig { - pub start_offset: Duration, - pub period: Duration, -} - -impl Default for BootstrapConfig { - fn default() -> Self { - Self { - start_offset: Duration::from_secs(5), - period: Duration::from_secs(2 * 60), - } - } -} - pub type HeadTx = tokio::sync::watch::Sender>; pub type HeadRx = tokio::sync::watch::Receiver>; diff --git a/crates/p2p/src/main_loop.rs b/crates/p2p/src/main_loop.rs index 7a25ce1f0a..dd6c5219dd 100644 --- a/crates/p2p/src/main_loop.rs +++ b/crates/p2p/src/main_loop.rs @@ -1,18 +1,11 @@ use std::collections::{HashMap, HashSet}; use std::fmt::Debug; +use std::num::NonZeroUsize; use futures::channel::mpsc::Receiver as ResponseReceiver; use futures::StreamExt; use libp2p::gossipsub::{self, IdentTopic}; -use libp2p::kad::{ - self, - BootstrapError, - BootstrapOk, - ProgressStep, - QueryId, - QueryInfo, - QueryResult, -}; +use libp2p::kad::{self, BootstrapError, BootstrapOk, QueryId, QueryResult}; use libp2p::multiaddr::Protocol; use libp2p::swarm::dial_opts::DialOpts; use libp2p::swarm::SwarmEvent; @@ -46,8 +39,6 @@ pub struct MainLoop { // 2. update the sync head info of our peers using a different mechanism // request_sync_status: HashSetDelay, pending_queries: PendingQueries, - /// Ongoing Kademlia bootstrap query. - ongoing_bootstrap: Option, _pending_test_queries: TestQueries, } @@ -96,26 +87,16 @@ impl MainLoop { pending_dials: Default::default(), pending_sync_requests: Default::default(), pending_queries: Default::default(), - ongoing_bootstrap: None, _pending_test_queries: Default::default(), } } pub async fn run(mut self) { - // Delay bootstrap so that by the time we attempt it we've connected to the - // bootstrap node - let bootstrap_start = tokio::time::Instant::now() + self.cfg.bootstrap.start_offset; - let mut bootstrap_interval = - tokio::time::interval_at(bootstrap_start, self.cfg.bootstrap.period); - let mut network_status_interval = tokio::time::interval(Duration::from_secs(5)); let mut peer_status_interval = tokio::time::interval(Duration::from_secs(30)); let me = *self.swarm.local_peer_id(); loop { - let bootstrap_interval_tick = bootstrap_interval.tick(); - tokio::pin!(bootstrap_interval_tick); - let network_status_interval_tick = network_status_interval.tick(); tokio::pin!(network_status_interval_tick); @@ -162,31 +143,6 @@ impl MainLoop { dht, ); } - _ = bootstrap_interval_tick => { - tracing::debug!("Checking low watermark"); - if let Some(query_id) = self.ongoing_bootstrap { - match self.swarm.behaviour_mut().kademlia_mut().query_mut(&query_id) { - Some(mut query) if matches!(query.info(), QueryInfo::Bootstrap { - step: ProgressStep { last: false, .. }, .. } - ) => { - tracing::debug!("Previous bootstrap still in progress, aborting it"); - query.finish(); - continue; - } - _ => self.ongoing_bootstrap = None, - } - } - if self.swarm.behaviour_mut().outbound_peers().count() < self.cfg.low_watermark { - if let Ok(query_id) = self.swarm.behaviour_mut().kademlia_mut().bootstrap() { - self.ongoing_bootstrap = Some(query_id); - send_test_event( - &self.event_sender, - TestEvent::KademliaBootstrapStarted, - ) - .await; - } - } - } command = self.command_receiver.recv() => { match command { Some(c) => self.handle_command(c).await, @@ -397,7 +353,6 @@ impl MainLoop { Err(peer) } }; - self.ongoing_bootstrap = None; send_test_event( &self.event_sender, TestEvent::KademliaBootstrapCompleted(result), @@ -451,31 +406,61 @@ impl MainLoop { } _ => self.test_query_completed(id, result).await, } - } else if let QueryResult::GetProviders(result) = result { - use libp2p::kad::GetProvidersOk; - - let result = match result { - Ok(GetProvidersOk::FoundProviders { providers, .. }) => Ok(providers), - Ok(_) => Ok(Default::default()), - Err(_) => { - unreachable!( - "when a query times out libp2p makes it the last stage" - ) - } - }; - - let sender = self - .pending_queries - .get_providers - .get(&id) - .expect("Query to be pending"); - - sender - .send(result) - .await - .expect("Receiver not to be dropped"); } else { - self.test_query_progressed(id, result).await; + match result { + QueryResult::GetProviders(result) => { + use libp2p::kad::GetProvidersOk; + + let result = match result { + Ok(GetProvidersOk::FoundProviders { providers, .. }) => { + Ok(providers) + } + Ok(_) => Ok(Default::default()), + Err(_) => { + unreachable!( + "when a query times out libp2p makes it the last stage" + ) + } + }; + + let sender = self + .pending_queries + .get_providers + .get(&id) + .expect("Query to be pending"); + + sender + .send(result) + .await + .expect("Receiver not to be dropped"); + } + QueryResult::Bootstrap(_) => { + tracing::debug!("Checking low watermark"); + // Starting from libp2p-v0.54.1 bootstrap queries are started + // automatically in the kad behaviour: + // - periodically, + // - after a peer is added to the routing table. + // If we have enough peers, we just stop any ongoing bootstrap + // query initiated by libp2p. + if self.swarm.behaviour_mut().outbound_peers().count() + >= self.cfg.low_watermark + { + self.swarm + .behaviour_mut() + .kademlia_mut() + .query_mut(&id) + .expect("Query to be active") + .finish(); + } else if step.count == NonZeroUsize::new(1).expect("1>0") { + send_test_event( + &self.event_sender, + TestEvent::KademliaBootstrapStarted, + ) + .await; + } + } + _ => self.test_query_progressed(id, result).await, + } } } kad::Event::RoutingUpdated { diff --git a/crates/p2p/src/test_utils/peer.rs b/crates/p2p/src/test_utils/peer.rs index 72c4defdb7..2fa056c317 100644 --- a/crates/p2p/src/test_utils/peer.rs +++ b/crates/p2p/src/test_utils/peer.rs @@ -37,7 +37,7 @@ impl Config { max_outbound_peers: 10, low_watermark: 10, ip_whitelist: vec!["::/0".parse().unwrap(), "0.0.0.0/0".parse().unwrap()], - bootstrap: Default::default(), + bootstrap_period: Duration::from_secs(2 * 60), eviction_timeout: Duration::from_secs(15 * 60), inbound_connections_rate_limit: RateLimit { max: 1000, diff --git a/crates/p2p/src/tests.rs b/crates/p2p/src/tests.rs index 1723a627b5..a7cd8e388a 100644 --- a/crates/p2p/src/tests.rs +++ b/crates/p2p/src/tests.rs @@ -15,7 +15,7 @@ use rstest::rstest; use crate::sync::codec; use crate::test_utils::peer::TestPeer; -use crate::{BootstrapConfig, Config, Event, EventReceiver, RateLimit, TestEvent}; +use crate::{Config, Event, EventReceiver, RateLimit, TestEvent}; /// [`MainLoop`](p2p::MainLoop)'s event channel size is 1, so we need to consume /// all events as soon as they're sent otherwise the main loop will stall. @@ -168,10 +168,7 @@ async fn periodic_bootstrap() { max_inbound_relayed_peers: 10, max_outbound_peers: 10, low_watermark: 3, - bootstrap: BootstrapConfig { - period: BOOTSTRAP_PERIOD, - start_offset: Duration::from_secs(1), - }, + bootstrap_period: BOOTSTRAP_PERIOD, eviction_timeout: Duration::from_secs(15 * 60), inbound_connections_rate_limit: RateLimit { max: 1000, @@ -320,10 +317,7 @@ async fn reconnect_too_quickly() { max_inbound_relayed_peers: 10, max_outbound_peers: 10, low_watermark: 0, - bootstrap: BootstrapConfig { - period: Duration::from_millis(500), - start_offset: Duration::from_secs(10), - }, + bootstrap_period: Duration::from_secs(500), eviction_timeout: Duration::from_secs(15 * 60), inbound_connections_rate_limit: RateLimit { max: 1000, @@ -425,10 +419,7 @@ async fn duplicate_connection() { max_outbound_peers: 10, // Don't open connections automatically. low_watermark: 0, - bootstrap: BootstrapConfig { - period: Duration::from_millis(500), - start_offset: Duration::from_secs(10), - }, + bootstrap_period: Duration::from_millis(500), eviction_timeout: Duration::from_secs(15 * 60), inbound_connections_rate_limit: RateLimit { max: 1000, @@ -514,10 +505,7 @@ async fn outbound_peer_eviction() { max_outbound_peers: 2, // Don't open connections automatically. low_watermark: 0, - bootstrap: BootstrapConfig { - period: Duration::from_millis(500), - start_offset: Duration::from_secs(10), - }, + bootstrap_period: Duration::from_millis(500), eviction_timeout: Duration::from_secs(15 * 60), inbound_connections_rate_limit: RateLimit { max: 1000, @@ -646,10 +634,7 @@ async fn inbound_peer_eviction() { max_outbound_peers: 100, // Don't open connections automatically. low_watermark: 0, - bootstrap: BootstrapConfig { - period: Duration::from_millis(500), - start_offset: Duration::from_secs(10), - }, + bootstrap_period: Duration::from_millis(500), eviction_timeout: Duration::from_secs(15 * 60), inbound_connections_rate_limit: RateLimit { max: 1000, @@ -735,10 +720,7 @@ async fn evicted_peer_reconnection() { max_outbound_peers: 1, // Don't open connections automatically. low_watermark: 0, - bootstrap: BootstrapConfig { - period: Duration::from_millis(500), - start_offset: Duration::from_secs(10), - }, + bootstrap_period: Duration::from_millis(500), eviction_timeout: Duration::from_secs(15 * 60), inbound_connections_rate_limit: RateLimit { max: 1000, @@ -829,10 +811,7 @@ async fn ip_whitelist() { max_outbound_peers: 10, // Don't open connections automatically. low_watermark: 0, - bootstrap: BootstrapConfig { - period: Duration::from_millis(500), - start_offset: Duration::from_secs(10), - }, + bootstrap_period: Duration::from_millis(500), eviction_timeout: Duration::from_secs(15 * 60), inbound_connections_rate_limit: RateLimit { max: 1000, @@ -865,10 +844,7 @@ async fn ip_whitelist() { max_outbound_peers: 10, // Don't open connections automatically. low_watermark: 0, - bootstrap: BootstrapConfig { - period: Duration::from_millis(500), - start_offset: Duration::from_secs(10), - }, + bootstrap_period: Duration::from_millis(500), eviction_timeout: Duration::from_secs(15 * 60), inbound_connections_rate_limit: RateLimit { max: 1000, @@ -902,10 +878,7 @@ async fn rate_limit() { max_outbound_peers: 10, // Don't open connections automatically. low_watermark: 0, - bootstrap: BootstrapConfig { - period: Duration::from_millis(500), - start_offset: Duration::from_secs(10), - }, + bootstrap_period: Duration::from_millis(500), eviction_timeout: Duration::from_secs(15 * 60), inbound_connections_rate_limit: RateLimit { max: 2, diff --git a/crates/pathfinder/src/bin/pathfinder/main.rs b/crates/pathfinder/src/bin/pathfinder/main.rs index 7ab60493bd..58c83e83fc 100644 --- a/crates/pathfinder/src/bin/pathfinder/main.rs +++ b/crates/pathfinder/src/bin/pathfinder/main.rs @@ -449,7 +449,7 @@ async fn start_p2p( max_outbound_peers: config.max_outbound_connections, low_watermark: config.low_watermark, ip_whitelist: config.ip_whitelist, - bootstrap: Default::default(), + bootstrap_period: Duration::from_secs(2 * 60), eviction_timeout: config.eviction_timeout, inbound_connections_rate_limit: p2p::RateLimit { max: 10, From d768cf330d3bc5fc31eb5b26d2a9213b7cc95c64 Mon Sep 17 00:00:00 2001 From: Krzysztof Lis Date: Mon, 23 Sep 2024 23:31:16 +0200 Subject: [PATCH 081/282] test(p2p): reduce boilerplate --- crates/p2p/src/test_utils/peer.rs | 7 +- crates/p2p/src/tests.rs | 141 ++---------------------------- 2 files changed, 13 insertions(+), 135 deletions(-) diff --git a/crates/p2p/src/test_utils/peer.rs b/crates/p2p/src/test_utils/peer.rs index 2fa056c317..c7749d84fa 100644 --- a/crates/p2p/src/test_utils/peer.rs +++ b/crates/p2p/src/test_utils/peer.rs @@ -35,9 +35,10 @@ impl Config { max_inbound_direct_peers: 10, max_inbound_relayed_peers: 10, max_outbound_peers: 10, - low_watermark: 10, - ip_whitelist: vec!["::/0".parse().unwrap(), "0.0.0.0/0".parse().unwrap()], - bootstrap_period: Duration::from_secs(2 * 60), + // Don't open connections automatically. + low_watermark: 0, + ip_whitelist: vec!["::1/0".parse().unwrap(), "0.0.0.0/0".parse().unwrap()], + bootstrap_period: Duration::from_millis(500), eviction_timeout: Duration::from_secs(15 * 60), inbound_connections_rate_limit: RateLimit { max: 1000, diff --git a/crates/p2p/src/tests.rs b/crates/p2p/src/tests.rs index a7cd8e388a..e4293c5ab8 100644 --- a/crates/p2p/src/tests.rs +++ b/crates/p2p/src/tests.rs @@ -161,22 +161,9 @@ async fn periodic_bootstrap() { const BOOTSTRAP_PERIOD: Duration = Duration::from_millis(500); let cfg = Config { - direct_connection_timeout: Duration::from_secs(0), - relay_connection_timeout: Duration::from_secs(0), - ip_whitelist: vec!["::1/0".parse().unwrap(), "0.0.0.0/0".parse().unwrap()], - max_inbound_direct_peers: 10, - max_inbound_relayed_peers: 10, - max_outbound_peers: 10, low_watermark: 3, bootstrap_period: BOOTSTRAP_PERIOD, - eviction_timeout: Duration::from_secs(15 * 60), - inbound_connections_rate_limit: RateLimit { - max: 1000, - interval: Duration::from_secs(1), - }, - kad_name: Default::default(), - stream_timeout: Duration::from_secs(10), - max_concurrent_streams: 100, + ..Config::for_test() }; let mut boot = TestPeer::new(cfg.clone()); let mut peer1 = TestPeer::new(cfg.clone()); @@ -311,21 +298,7 @@ async fn reconnect_too_quickly() { const CONNECTION_TIMEOUT: Duration = Duration::from_secs(1); let cfg = Config { direct_connection_timeout: CONNECTION_TIMEOUT, - relay_connection_timeout: Duration::from_secs(0), - ip_whitelist: vec!["::1/0".parse().unwrap(), "0.0.0.0/0".parse().unwrap()], - max_inbound_direct_peers: 10, - max_inbound_relayed_peers: 10, - max_outbound_peers: 10, - low_watermark: 0, - bootstrap_period: Duration::from_secs(500), - eviction_timeout: Duration::from_secs(15 * 60), - inbound_connections_rate_limit: RateLimit { - max: 1000, - interval: Duration::from_secs(1), - }, - kad_name: Default::default(), - stream_timeout: Duration::from_secs(10), - max_concurrent_streams: 100, + ..Config::for_test() }; let mut peer1 = TestPeer::new(cfg.clone()); @@ -412,22 +385,7 @@ async fn duplicate_connection() { const CONNECTION_TIMEOUT: Duration = Duration::from_millis(50); let cfg = Config { direct_connection_timeout: CONNECTION_TIMEOUT, - relay_connection_timeout: Duration::from_secs(0), - ip_whitelist: vec!["::1/0".parse().unwrap(), "0.0.0.0/0".parse().unwrap()], - max_inbound_direct_peers: 10, - max_inbound_relayed_peers: 10, - max_outbound_peers: 10, - // Don't open connections automatically. - low_watermark: 0, - bootstrap_period: Duration::from_millis(500), - eviction_timeout: Duration::from_secs(15 * 60), - inbound_connections_rate_limit: RateLimit { - max: 1000, - interval: Duration::from_secs(1), - }, - kad_name: Default::default(), - stream_timeout: Duration::from_secs(10), - max_concurrent_streams: 100, + ..Config::for_test() }; let keypair = Keypair::generate_ed25519(); let mut peer1 = TestPeer::with_keypair(keypair.clone(), cfg.clone()); @@ -497,23 +455,10 @@ async fn duplicate_connection() { #[test_log::test(tokio::test)] async fn outbound_peer_eviction() { let cfg = Config { - direct_connection_timeout: Duration::from_secs(0), - relay_connection_timeout: Duration::from_secs(0), - ip_whitelist: vec!["::1/0".parse().unwrap(), "0.0.0.0/0".parse().unwrap()], max_inbound_direct_peers: 2, max_inbound_relayed_peers: 0, max_outbound_peers: 2, - // Don't open connections automatically. - low_watermark: 0, - bootstrap_period: Duration::from_millis(500), - eviction_timeout: Duration::from_secs(15 * 60), - inbound_connections_rate_limit: RateLimit { - max: 1000, - interval: Duration::from_secs(1), - }, - kad_name: Default::default(), - stream_timeout: Duration::from_secs(10), - max_concurrent_streams: 100, + ..Config::for_test() }; let mut peer = TestPeer::new(cfg.clone()); @@ -626,23 +571,10 @@ async fn outbound_peer_eviction() { #[test_log::test(tokio::test)] async fn inbound_peer_eviction() { let cfg = Config { - direct_connection_timeout: Duration::from_secs(0), - relay_connection_timeout: Duration::from_secs(0), - ip_whitelist: vec!["::1/0".parse().unwrap(), "0.0.0.0/0".parse().unwrap()], max_inbound_direct_peers: 25, max_inbound_relayed_peers: 0, max_outbound_peers: 100, - // Don't open connections automatically. - low_watermark: 0, - bootstrap_period: Duration::from_millis(500), - eviction_timeout: Duration::from_secs(15 * 60), - inbound_connections_rate_limit: RateLimit { - max: 1000, - interval: Duration::from_secs(1), - }, - kad_name: Default::default(), - stream_timeout: Duration::from_secs(10), - max_concurrent_streams: 100, + ..Config::for_test() }; let mut peer = TestPeer::new(cfg.clone()); @@ -712,23 +644,10 @@ async fn inbound_peer_eviction() { #[test_log::test(tokio::test)] async fn evicted_peer_reconnection() { let cfg = Config { - direct_connection_timeout: Duration::from_secs(0), - relay_connection_timeout: Duration::from_secs(0), - ip_whitelist: vec!["::1/0".parse().unwrap(), "0.0.0.0/0".parse().unwrap()], max_inbound_direct_peers: 1000, max_inbound_relayed_peers: 0, max_outbound_peers: 1, - // Don't open connections automatically. - low_watermark: 0, - bootstrap_period: Duration::from_millis(500), - eviction_timeout: Duration::from_secs(15 * 60), - inbound_connections_rate_limit: RateLimit { - max: 1000, - interval: Duration::from_secs(1), - }, - kad_name: Default::default(), - stream_timeout: Duration::from_secs(10), - max_concurrent_streams: 100, + ..Config::for_test() }; let mut peer1 = TestPeer::new(cfg.clone()); @@ -803,23 +722,8 @@ async fn evicted_peer_reconnection() { #[test_log::test(tokio::test)] async fn ip_whitelist() { let cfg = Config { - direct_connection_timeout: Duration::from_secs(0), - relay_connection_timeout: Duration::from_secs(0), ip_whitelist: vec!["127.0.0.2/32".parse().unwrap()], - max_inbound_direct_peers: 10, - max_inbound_relayed_peers: 10, - max_outbound_peers: 10, - // Don't open connections automatically. - low_watermark: 0, - bootstrap_period: Duration::from_millis(500), - eviction_timeout: Duration::from_secs(15 * 60), - inbound_connections_rate_limit: RateLimit { - max: 1000, - interval: Duration::from_secs(1), - }, - kad_name: Default::default(), - stream_timeout: Duration::from_secs(10), - max_concurrent_streams: 100, + ..Config::for_test() }; let mut peer1 = TestPeer::new(cfg.clone()); let peer2 = TestPeer::new(cfg.clone()); @@ -836,23 +740,8 @@ async fn ip_whitelist() { // Start another peer accepting connections from 127.0.0.1. let cfg = Config { - direct_connection_timeout: Duration::from_secs(0), - relay_connection_timeout: Duration::from_secs(0), ip_whitelist: vec!["127.0.0.1/32".parse().unwrap()], - max_inbound_direct_peers: 10, - max_inbound_relayed_peers: 10, - max_outbound_peers: 10, - // Don't open connections automatically. - low_watermark: 0, - bootstrap_period: Duration::from_millis(500), - eviction_timeout: Duration::from_secs(15 * 60), - inbound_connections_rate_limit: RateLimit { - max: 1000, - interval: Duration::from_secs(1), - }, - kad_name: Default::default(), - stream_timeout: Duration::from_secs(10), - max_concurrent_streams: 100, + ..Config::for_test() }; let mut peer3 = TestPeer::new(cfg); @@ -870,23 +759,11 @@ async fn rate_limit() { const RATE_LIMIT_INTERVAL: Duration = Duration::from_secs(1); let cfg = Config { - direct_connection_timeout: Duration::from_secs(0), - relay_connection_timeout: Duration::from_secs(0), - ip_whitelist: vec!["::1/0".parse().unwrap(), "0.0.0.0/0".parse().unwrap()], - max_inbound_direct_peers: 10, - max_inbound_relayed_peers: 10, - max_outbound_peers: 10, - // Don't open connections automatically. - low_watermark: 0, - bootstrap_period: Duration::from_millis(500), - eviction_timeout: Duration::from_secs(15 * 60), inbound_connections_rate_limit: RateLimit { max: 2, interval: RATE_LIMIT_INTERVAL, }, - kad_name: Default::default(), - stream_timeout: Duration::from_secs(10), - max_concurrent_streams: 100, + ..Config::for_test() }; let mut peer1 = TestPeer::new(cfg.clone()); From a5694f8c3490708e31e94d4049ff7c2ee331b23d Mon Sep 17 00:00:00 2001 From: Krzysztof Lis Date: Tue, 24 Sep 2024 11:59:41 +0200 Subject: [PATCH 082/282] feat(p2p): remove low_watermark --- crates/p2p/src/builder.rs | 4 +-- crates/p2p/src/lib.rs | 4 --- crates/p2p/src/main_loop.rs | 24 ++++--------- crates/p2p/src/test_utils/peer.rs | 2 -- crates/p2p/src/tests.rs | 36 ------------------- .../pathfinder/src/bin/pathfinder/config.rs | 22 ------------ crates/pathfinder/src/bin/pathfinder/main.rs | 1 - 7 files changed, 8 insertions(+), 85 deletions(-) diff --git a/crates/p2p/src/builder.rs b/crates/p2p/src/builder.rs index 4a428ae935..6d7d864430 100644 --- a/crates/p2p/src/builder.rs +++ b/crates/p2p/src/builder.rs @@ -49,7 +49,7 @@ impl Builder { let client = Client::new(command_sender, local_peer_id); let (behaviour, relay_transport) = behaviour_builder - .unwrap_or_else(|| Behaviour::builder(keypair.clone(), chain_id, cfg.clone())) + .unwrap_or_else(|| Behaviour::builder(keypair.clone(), chain_id, cfg)) .build(client.clone()); let swarm = Swarm::new( @@ -65,7 +65,7 @@ impl Builder { ( client, event_receiver, - MainLoop::new(swarm, command_receiver, event_sender, cfg), + MainLoop::new(swarm, command_receiver, event_sender), ) } } diff --git a/crates/p2p/src/lib.rs b/crates/p2p/src/lib.rs index a6fff638de..a2225e3e08 100644 --- a/crates/p2p/src/lib.rs +++ b/crates/p2p/src/lib.rs @@ -56,10 +56,6 @@ pub struct Config { pub max_inbound_relayed_peers: usize, /// Maximum number of outbound peers. pub max_outbound_peers: usize, - /// The minimum number of peers to maintain. If the number of outbound peers - /// drops below this number, the node will attempt to connect to more - /// peers. - pub low_watermark: usize, /// How long to prevent evicted peers from reconnecting. pub eviction_timeout: Duration, pub ip_whitelist: Vec, diff --git a/crates/p2p/src/main_loop.rs b/crates/p2p/src/main_loop.rs index dd6c5219dd..93d7fc6835 100644 --- a/crates/p2p/src/main_loop.rs +++ b/crates/p2p/src/main_loop.rs @@ -22,10 +22,9 @@ use tokio::time::Duration; #[cfg(test)] use crate::test_utils; -use crate::{behaviour, Command, Config, EmptyResultSender, Event, TestCommand, TestEvent}; +use crate::{behaviour, Command, EmptyResultSender, Event, TestCommand, TestEvent}; pub struct MainLoop { - cfg: crate::Config, swarm: libp2p::swarm::Swarm, command_receiver: mpsc::Receiver, event_sender: mpsc::Sender, @@ -77,10 +76,8 @@ impl MainLoop { swarm: libp2p::swarm::Swarm, command_receiver: mpsc::Receiver, event_sender: mpsc::Sender, - cfg: Config, ) -> Self { Self { - cfg, swarm, command_receiver, event_sender, @@ -438,20 +435,11 @@ impl MainLoop { tracing::debug!("Checking low watermark"); // Starting from libp2p-v0.54.1 bootstrap queries are started // automatically in the kad behaviour: - // - periodically, - // - after a peer is added to the routing table. - // If we have enough peers, we just stop any ongoing bootstrap - // query initiated by libp2p. - if self.swarm.behaviour_mut().outbound_peers().count() - >= self.cfg.low_watermark - { - self.swarm - .behaviour_mut() - .kademlia_mut() - .query_mut(&id) - .expect("Query to be active") - .finish(); - } else if step.count == NonZeroUsize::new(1).expect("1>0") { + // 1. periodically, + // 2. after a peer is added to the routing table, if the number of + // peers in the DHT is lower than 20. See `bootstrap_on_low_peers` for more details: + // https://github.com/libp2p/rust-libp2p/blob/d7beb55f672dce54017fa4b30f67ecb8d66b9810/protocols/kad/src/behaviour.rs#L1401). + if step.count == NonZeroUsize::new(1).expect("1>0") { send_test_event( &self.event_sender, TestEvent::KademliaBootstrapStarted, diff --git a/crates/p2p/src/test_utils/peer.rs b/crates/p2p/src/test_utils/peer.rs index c7749d84fa..72911864fb 100644 --- a/crates/p2p/src/test_utils/peer.rs +++ b/crates/p2p/src/test_utils/peer.rs @@ -35,8 +35,6 @@ impl Config { max_inbound_direct_peers: 10, max_inbound_relayed_peers: 10, max_outbound_peers: 10, - // Don't open connections automatically. - low_watermark: 0, ip_whitelist: vec!["::1/0".parse().unwrap(), "0.0.0.0/0".parse().unwrap()], bootstrap_period: Duration::from_millis(500), eviction_timeout: Duration::from_secs(15 * 60), diff --git a/crates/p2p/src/tests.rs b/crates/p2p/src/tests.rs index e4293c5ab8..3a80f46c73 100644 --- a/crates/p2p/src/tests.rs +++ b/crates/p2p/src/tests.rs @@ -161,7 +161,6 @@ async fn periodic_bootstrap() { const BOOTSTRAP_PERIOD: Duration = Duration::from_millis(500); let cfg = Config { - low_watermark: 3, bootstrap_period: BOOTSTRAP_PERIOD, ..Config::for_test() }; @@ -254,41 +253,6 @@ async fn periodic_bootstrap() { peer2.client.for_test().get_peers_from_dht().await, [boot.peer_id, peer1.peer_id].into() ); - - // Start a new peer and connect to the other peers, immediately reaching the low - // watermark. - let mut peer3 = TestPeer::new(cfg); - - peer3 - .client - .dial(boot.peer_id, boot_addr.clone()) - .await - .unwrap(); - peer3 - .client - .dial(peer1.peer_id, addr1.clone()) - .await - .unwrap(); - peer3 - .client - .dial(peer2.peer_id, addr2.clone()) - .await - .unwrap(); - - consume_accumulated_events(&mut peer3.event_receiver).await; - - // The low watermark is reached for peer3, so no more bootstrap attempts are - // made. - let timeout = tokio::time::timeout( - BOOTSTRAP_PERIOD + Duration::from_millis(100), - wait_for_event(&mut peer3.event_receiver, |event| match event { - Event::Test(TestEvent::KademliaBootstrapStarted) => Some(()), - _ => None, - }), - ) - .await; - - assert!(timeout.is_err()); } /// Test that if a peer attempts to reconnect too quickly, the connection is diff --git a/crates/pathfinder/src/bin/pathfinder/config.rs b/crates/pathfinder/src/bin/pathfinder/config.rs index f01f4b5197..6a6a3c01b5 100644 --- a/crates/pathfinder/src/bin/pathfinder/config.rs +++ b/crates/pathfinder/src/bin/pathfinder/config.rs @@ -458,17 +458,6 @@ Example: )] max_outbound_connections: u32, - #[arg( - long = "p2p.low-watermark", - long_help = "The minimum number of outbound peers to maintain. If the number of outbound \ - peers drops below this number, the node will attempt to connect to more \ - peers.", - value_name = "LOW_WATERMARK", - env = "PATHFINDER_LOW_WATERMARK", - default_value = "20" - )] - low_watermark: u32, - #[arg( long = "p2p.ip-whitelist", long_help = "Comma separated list of IP addresses or IP address ranges (in CIDR) to \ @@ -739,7 +728,6 @@ pub struct P2PConfig { pub max_inbound_relayed_connections: usize, pub max_outbound_connections: usize, pub ip_whitelist: Vec, - pub low_watermark: usize, pub kad_name: Option, pub l1_checkpoint_override: Option, pub stream_timeout: Duration, @@ -850,15 +838,6 @@ impl P2PConfig { .exit() } - if args.low_watermark > args.max_outbound_connections { - Cli::command() - .error( - ErrorKind::ValueValidation, - "p2p.low-watermark must be less than or equal to p2p.max_outbound_connections", - ) - .exit() - } - if args.kad_name.iter().any(|x| !x.starts_with('/')) { Cli::command() .error( @@ -886,7 +865,6 @@ impl P2PConfig { ), predefined_peers: parse_multiaddr_vec("p2p.predefined-peers", args.predefined_peers), ip_whitelist: args.ip_whitelist, - low_watermark: 0, kad_name: args.kad_name, l1_checkpoint_override, stream_timeout: Duration::from_secs(args.stream_timeout.into()), diff --git a/crates/pathfinder/src/bin/pathfinder/main.rs b/crates/pathfinder/src/bin/pathfinder/main.rs index 58c83e83fc..6ad4a4651a 100644 --- a/crates/pathfinder/src/bin/pathfinder/main.rs +++ b/crates/pathfinder/src/bin/pathfinder/main.rs @@ -447,7 +447,6 @@ async fn start_p2p( max_inbound_direct_peers: config.max_inbound_direct_connections, max_inbound_relayed_peers: config.max_inbound_relayed_connections, max_outbound_peers: config.max_outbound_connections, - low_watermark: config.low_watermark, ip_whitelist: config.ip_whitelist, bootstrap_period: Duration::from_secs(2 * 60), eviction_timeout: config.eviction_timeout, From 913dfa423d01607d249fda1f16cec90be0e3150a Mon Sep 17 00:00:00 2001 From: sistemd Date: Wed, 25 Sep 2024 14:44:01 +0200 Subject: [PATCH 083/282] implement starknet_subscribePendingTransactions --- crates/rpc/src/jsonrpc/router/subscription.rs | 112 ++-- crates/rpc/src/method.rs | 1 + crates/rpc/src/method/subscribe_new_heads.rs | 112 +++- .../method/subscribe_pending_transactions.rs | 521 ++++++++++++++++++ crates/rpc/src/pending.rs | 2 +- crates/rpc/src/v08.rs | 6 +- 6 files changed, 698 insertions(+), 56 deletions(-) create mode 100644 crates/rpc/src/method/subscribe_pending_transactions.rs diff --git a/crates/rpc/src/jsonrpc/router/subscription.rs b/crates/rpc/src/jsonrpc/router/subscription.rs index 6feee7e64c..c7dc988c6f 100644 --- a/crates/rpc/src/jsonrpc/router/subscription.rs +++ b/crates/rpc/src/jsonrpc/router/subscription.rs @@ -1,4 +1,5 @@ use std::sync::Arc; +use std::time::Duration; use axum::extract::ws::{Message, WebSocket}; use dashmap::DashMap; @@ -11,7 +12,7 @@ use crate::context::RpcContext; use crate::dto::serialize::SerializeForVersion; use crate::dto::DeserializeForVersion; use crate::error::ApplicationError; -use crate::jsonrpc::{RpcError, RpcRequest, RpcResponse}; +use crate::jsonrpc::{RequestId, RpcError, RpcRequest, RpcResponse}; use crate::{RpcVersion, SubscriptionId}; /// See [`RpcSubscriptionFlow`]. @@ -20,11 +21,11 @@ pub(super) trait RpcSubscriptionEndpoint: Send + Sync { // Start the subscription. async fn invoke( &self, - state: RpcContext, + router: RpcRouter, input: serde_json::Value, subscription_id: SubscriptionId, subscriptions: Arc>>, - version: RpcVersion, + req_id: RequestId, tx: mpsc::Sender>, ) -> Result<(), RpcError>; } @@ -57,12 +58,12 @@ pub(super) trait RpcSubscriptionEndpoint: Send + Sync { /// - Stream the first active update, and then keep streaming the rest. #[axum::async_trait] pub trait RpcSubscriptionFlow: Send + Sync { - type Request: crate::dto::DeserializeForVersion + Send + Sync + 'static; + type Request: crate::dto::DeserializeForVersion + Clone + Send + Sync + 'static; type Notification: crate::dto::serialize::SerializeForVersion + Send + Sync + 'static; /// The block to start streaming from. If the subscription endpoint does not - /// support catching up, the value returned by this method is - /// irrelevant. + /// support catching up, this method should always return + /// [`BlockId::Latest`]. fn starting_block(req: &Self::Request) -> BlockId; /// Fetch historical data from the `from` block to the `to` block. The @@ -78,6 +79,7 @@ pub trait RpcSubscriptionFlow: Send + Sync { /// Subscribe to active updates. async fn subscribe( state: RpcContext, + req: Self::Request, tx: mpsc::Sender>, ); } @@ -101,20 +103,20 @@ where { async fn invoke( &self, - state: RpcContext, + router: RpcRouter, input: serde_json::Value, subscription_id: SubscriptionId, subscriptions: Arc>>, - version: RpcVersion, - tx: mpsc::Sender>, + req_id: RequestId, + ws_tx: mpsc::Sender>, ) -> Result<(), RpcError> { - let req = T::Request::deserialize(crate::dto::Value::new(input, version)) + let req = T::Request::deserialize(crate::dto::Value::new(input, router.version)) .map_err(|e| RpcError::InvalidParams(e.to_string()))?; let tx = SubscriptionSender { subscription_id, subscriptions, - tx, - version, + tx: ws_tx.clone(), + version: router.version, _phantom: Default::default(), }; @@ -128,13 +130,32 @@ where } BlockId::Latest => { // No need to catch up. The code below will subscribe to new blocks. + // Only needs to send the subscription ID to the client. + if ws_tx + .send(Ok(Message::Text( + serde_json::to_string(&RpcResponse { + output: Ok(serde_json::to_value(&SubscriptionIdResult { + subscription_id, + }) + .unwrap()), + id: req_id.clone(), + }) + .unwrap(), + ))) + .await + .is_err() + { + return Ok(()); + } BlockNumber::MAX } BlockId::Number(_) | BlockId::Hash(_) => { // Catch up to the latest block in batches of BATCH_SIZE. + + // Load the first block number, return an error if it's invalid. let first_block = pathfinder_storage::BlockId::try_from(T::starting_block(&req)) .map_err(|e| RpcError::InvalidParams(e.to_string()))?; - let storage = state.storage.clone(); + let storage = router.context.storage.clone(); let mut current_block = tokio::task::spawn_blocking(move || -> Result<_, RpcError> { let mut conn = storage.connection().map_err(RpcError::InternalError)?; @@ -145,11 +166,34 @@ where }) .await .map_err(|e| RpcError::InternalError(e.into()))??; + + // Send the subscription ID to the client. + if ws_tx + .send(Ok(Message::Text( + serde_json::to_string(&RpcResponse { + output: Ok(serde_json::to_value(&SubscriptionIdResult { + subscription_id, + }) + .unwrap()), + id: req_id.clone(), + }) + .unwrap(), + ))) + .await + .is_err() + { + return Ok(()); + } + const BATCH_SIZE: u64 = 64; loop { - let messages = - T::catch_up(&state, &req, current_block, current_block + BATCH_SIZE) - .await?; + let messages = T::catch_up( + &router.context, + &req, + current_block, + current_block + BATCH_SIZE, + ) + .await?; if messages.is_empty() { // Caught up. break; @@ -174,7 +218,10 @@ where // Subscribe to new blocks. Receive the first subscription message. let (tx1, mut rx1) = mpsc::channel::>(1024); - tokio::spawn(T::subscribe(state.clone(), tx1)); + { + let req = req.clone(); + tokio::spawn(T::subscribe(router.context.clone(), req, tx1)); + } let first_msg = match rx1.recv().await { Some(msg) => msg, None => { @@ -188,7 +235,7 @@ where // blocks. Because the catch_up range is inclusive, we need to subtract 1 from // the block number. if let Some(block_number) = first_msg.block_number.parent() { - let messages = T::catch_up(&state, &req, current_block, block_number).await?; + let messages = T::catch_up(&router.context, &req, current_block, block_number).await?; for msg in messages { if tx .send(msg.notification, msg.subscription_name) @@ -415,36 +462,19 @@ pub fn handle_json_rpc_socket( }; // Start the subscription. + let state = state.clone(); let subscription_id = SubscriptionId::next(); - let context = state.context.clone(); - let version = state.version; let ws_tx = ws_tx.clone(); - if ws_tx - .send(Ok(Message::Text( - serde_json::to_string(&RpcResponse { - output: Ok( - serde_json::to_value(&SubscriptionIdResult { subscription_id }) - .unwrap(), - ), - id: req_id.clone(), - }) - .unwrap(), - ))) - .await - .is_err() - { - break; - } let handle = tokio::spawn({ let subscriptions = subscriptions.clone(); async move { if let Err(e) = endpoint .invoke( - context, + state, params, subscription_id, - subscriptions, - version, + subscriptions.clone(), + req_id.clone(), ws_tx.clone(), ) .await @@ -456,6 +486,10 @@ pub fn handle_json_rpc_socket( })) .await .ok(); + while subscriptions.remove(&subscription_id).is_none() { + // Race condition, the insert has not yet happened. + tokio::time::sleep(Duration::from_secs(1)).await; + } } } }); diff --git a/crates/rpc/src/method.rs b/crates/rpc/src/method.rs index 225fc5764a..388602b728 100644 --- a/crates/rpc/src/method.rs +++ b/crates/rpc/src/method.rs @@ -24,6 +24,7 @@ pub mod get_transaction_receipt; pub mod get_transaction_status; pub mod simulate_transactions; pub mod subscribe_new_heads; +pub mod subscribe_pending_transactions; pub mod syncing; pub mod trace_block_transactions; pub mod trace_transaction; diff --git a/crates/rpc/src/method/subscribe_new_heads.rs b/crates/rpc/src/method/subscribe_new_heads.rs index b1c36cee6d..367faf9a90 100644 --- a/crates/rpc/src/method/subscribe_new_heads.rs +++ b/crates/rpc/src/method/subscribe_new_heads.rs @@ -11,28 +11,28 @@ use crate::Reorg; pub struct SubscribeNewHeads; -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Request { - block: BlockId, + block: Option, } impl crate::dto::DeserializeForVersion for Request { fn deserialize(value: crate::dto::Value) -> Result { value.deserialize_map(|value| { Ok(Self { - block: value.deserialize_serde("block")?, + block: value.deserialize_optional_serde("block")?, }) }) } } #[derive(Debug)] -pub enum Message { +pub enum Notification { BlockHeader(Arc), Reorg(Arc), } -impl crate::dto::serialize::SerializeForVersion for Message { +impl crate::dto::serialize::SerializeForVersion for Notification { fn serialize( &self, serializer: crate::dto::serialize::Serializer, @@ -49,10 +49,10 @@ const SUBSCRIPTION_NAME: &str = "starknet_subscriptionNewHeads"; #[async_trait] impl RpcSubscriptionFlow for SubscribeNewHeads { type Request = Request; - type Notification = Message; + type Notification = Notification; fn starting_block(req: &Self::Request) -> BlockId { - req.block + req.block.unwrap_or(BlockId::Latest) } async fn catch_up( @@ -74,7 +74,7 @@ impl RpcSubscriptionFlow for SubscribeNewHeads { .map(|header| { let block_number = header.number; SubscriptionMessage { - notification: Message::BlockHeader(header.into()), + notification: Notification::BlockHeader(header.into()), block_number, subscription_name: SUBSCRIPTION_NAME, } @@ -84,6 +84,7 @@ impl RpcSubscriptionFlow for SubscribeNewHeads { async fn subscribe( state: RpcContext, + _req: Self::Request, tx: mpsc::Sender>, ) { let mut headers = state.notifications.block_headers.subscribe(); @@ -95,7 +96,7 @@ impl RpcSubscriptionFlow for SubscribeNewHeads { Ok(reorg) => { let block_number = reorg.first_block_number; if tx.send(SubscriptionMessage { - notification: Message::Reorg(reorg), + notification: Notification::Reorg(reorg), block_number, subscription_name: REORG_SUBSCRIPTION_NAME, }).await.is_err() { @@ -117,7 +118,7 @@ impl RpcSubscriptionFlow for SubscribeNewHeads { Ok(header) => { let block_number = header.number; if tx.send(SubscriptionMessage { - notification: Message::BlockHeader(header), + notification: Notification::BlockHeader(header), block_number, subscription_name: SUBSCRIPTION_NAME, }).await.is_err() { @@ -151,14 +152,14 @@ mod tests { use tokio::sync::mpsc; use crate::context::{RpcConfig, RpcContext}; - use crate::jsonrpc::{handle_json_rpc_socket, RpcResponse, RpcRouter}; + use crate::jsonrpc::{handle_json_rpc_socket, RequestId, RpcError, RpcResponse, RpcRouter}; use crate::pending::PendingWatcher; use crate::v02::types::syncing::Syncing; use crate::{v08, Notifications, Reorg, SubscriptionId, SyncState}; #[tokio::test] async fn happy_path_with_historic_blocks() { - happy_path_test(1000).await; + happy_path_test(2000).await; } #[tokio::test] @@ -306,6 +307,88 @@ mod tests { assert!(sender_rx.is_empty()); } + #[tokio::test] + async fn invalid_subscription() { + let router = setup(0).await; + let (sender_tx, mut sender_rx) = mpsc::channel(1024); + let (receiver_tx, receiver_rx) = mpsc::channel(1024); + handle_json_rpc_socket(router.clone(), sender_tx, receiver_rx); + receiver_tx + .send(Ok(Message::Text( + serde_json::json!({ + "jsonrpc": "2.0", + "id": 1, + "method": "starknet_subscribeNewHeads", + }) + .to_string(), + ))) + .await + .unwrap(); + let res = sender_rx.recv().await.unwrap(); + let res = match res { + Ok(_) => panic!("Expected Err, got Ok"), + Err(e) => e, + }; + assert_eq!( + res, + RpcResponse { + output: Err(RpcError::InvalidParams( + "expected object or array".to_string() + )), + id: RequestId::Number(1), + } + ); + } + + #[tokio::test] + async fn subscribe_no_params() { + let router = setup(0).await; + let (sender_tx, mut sender_rx) = mpsc::channel(1024); + let (receiver_tx, receiver_rx) = mpsc::channel(1024); + handle_json_rpc_socket(router.clone(), sender_tx, receiver_rx); + receiver_tx + .send(Ok(Message::Text( + serde_json::json!({ + "jsonrpc": "2.0", + "id": 1, + "method": "starknet_subscribeNewHeads", + "params": {} + }) + .to_string(), + ))) + .await + .unwrap(); + let res = sender_rx.recv().await.unwrap().unwrap(); + let subscription_id = match res { + Message::Text(json) => { + let json: serde_json::Value = serde_json::from_str(&json).unwrap(); + assert_eq!(json["jsonrpc"], "2.0"); + assert_eq!(json["id"], 1); + json["result"]["subscription_id"].as_u64().unwrap() + } + _ => panic!("Expected text message"), + }; + for i in 0..10 { + retry(|| { + router + .context + .notifications + .block_headers + .send(sample_header(i).into()) + }) + .await + .unwrap(); + let expected = sample_new_heads_message(i, subscription_id); + let header = sender_rx.recv().await.unwrap().unwrap(); + let json: serde_json::Value = match header { + Message::Text(json) => serde_json::from_str(&json).unwrap(), + _ => panic!("Expected text message"), + }; + assert_eq!(json, expected); + } + assert!(sender_rx.is_empty()); + } + #[tokio::test] async fn unsubscribe() { let (tx, mut rx, subscription_id, router) = happy_path_test(0).await; @@ -501,11 +584,12 @@ mod tests { where E: std::fmt::Debug, { - for i in 0..25 { + const RETRIES: u64 = 25; + for i in 0..RETRIES { match cb() { Ok(result) => return Ok(result), Err(e) => { - if i == 24 { + if i == RETRIES - 1 { return Err(e); } tokio::time::sleep(Duration::from_secs(i)).await; diff --git a/crates/rpc/src/method/subscribe_pending_transactions.rs b/crates/rpc/src/method/subscribe_pending_transactions.rs new file mode 100644 index 0000000000..b8b35608d7 --- /dev/null +++ b/crates/rpc/src/method/subscribe_pending_transactions.rs @@ -0,0 +1,521 @@ +use std::collections::HashSet; + +use axum::async_trait; +use pathfinder_common::transaction::Transaction; +use pathfinder_common::{BlockId, BlockNumber, ContractAddress, TransactionHash}; +use tokio::sync::mpsc; + +use crate::context::RpcContext; +use crate::jsonrpc::{RpcError, RpcSubscriptionFlow, SubscriptionMessage}; + +pub struct SubscribePendingTransactions; + +#[derive(Debug, Clone)] +pub struct Request { + transaction_details: Option, + sender_address: Option>, +} + +impl crate::dto::DeserializeForVersion for Request { + fn deserialize(value: crate::dto::Value) -> Result { + value.deserialize_map(|value| { + Ok(Self { + transaction_details: value.deserialize_optional_serde("transaction_details")?, + sender_address: value + .deserialize_optional_array("sender_address", |addr| { + Ok(ContractAddress(addr.deserialize()?)) + })? + .map(|addrs| addrs.into_iter().collect()), + }) + }) + } +} + +#[derive(Debug)] +pub enum Notification { + Transaction(Box), + TransactionHash(TransactionHash), +} + +impl crate::dto::serialize::SerializeForVersion for Notification { + fn serialize( + &self, + serializer: crate::dto::serialize::Serializer, + ) -> Result { + match self { + Notification::Transaction(transaction) => { + crate::dto::Transaction(transaction).serialize(serializer) + } + Notification::TransactionHash(transaction_hash) => { + transaction_hash.0.serialize(serializer) + } + } + } +} + +const SUBSCRIPTION_NAME: &str = "starknet_subscriptionPendingTransactions"; + +#[async_trait] +impl RpcSubscriptionFlow for SubscribePendingTransactions { + type Request = Request; + type Notification = Notification; + + fn starting_block(_req: &Self::Request) -> BlockId { + // Rollback is not supported. + BlockId::Latest + } + + async fn catch_up( + _state: &RpcContext, + _req: &Self::Request, + _from: BlockNumber, + _to: BlockNumber, + ) -> Result>, RpcError> { + Ok(vec![]) + } + + async fn subscribe( + state: RpcContext, + req: Self::Request, + tx: mpsc::Sender>, + ) { + let mut pending_data = state.pending_data.0.clone(); + // Last block sent to the subscriber. Initial value doesn't really matter + let mut last_block = BlockNumber::GENESIS; + // Hashes of transactions that have already been sent to the subscriber, as part + // of `last_block` block. It is necessary to keep track of this because the + // pending data updates might include new transactions for the same + // block number. + let mut sent_txs = HashSet::new(); + loop { + let pending = pending_data.borrow_and_update().clone(); + if pending.number != last_block { + last_block = pending.number; + sent_txs.clear(); + } + for transaction in pending.block.transactions.iter() { + if sent_txs.contains(&transaction.hash) { + continue; + } + // Filter the transactions by sender address. + if let Some(sender_address) = &req.sender_address { + use pathfinder_common::transaction::TransactionVariant::*; + let address = match &transaction.variant { + DeclareV0(tx) => tx.sender_address, + DeclareV1(tx) => tx.sender_address, + DeclareV2(tx) => tx.sender_address, + DeclareV3(tx) => tx.sender_address, + DeployV0(tx) => tx.contract_address, + DeployV1(tx) => tx.contract_address, + DeployAccountV1(tx) => tx.contract_address, + DeployAccountV3(tx) => tx.contract_address, + InvokeV0(tx) => tx.sender_address, + InvokeV1(tx) => tx.sender_address, + InvokeV3(tx) => tx.sender_address, + L1Handler(tx) => tx.contract_address, + }; + if !sender_address.contains(&address) { + continue; + } + } + let notification = match req.transaction_details { + Some(true) => Notification::Transaction(transaction.clone().into()), + Some(false) | None => Notification::TransactionHash(transaction.hash), + }; + sent_txs.insert(transaction.hash); + if tx + .send(SubscriptionMessage { + notification, + block_number: pending.number, + subscription_name: SUBSCRIPTION_NAME, + }) + .await + .is_err() + { + // Subscription has been closed. + return; + } + } + if pending_data.changed().await.is_err() { + tracing::debug!("Pending data channel closed, stopping subscription"); + break; + } + } + } +} + +#[cfg(test)] +mod tests { + use std::time::Duration; + + use axum::extract::ws::Message; + use pathfinder_common::transaction::{DeclareTransactionV0V1, Transaction, TransactionVariant}; + use pathfinder_common::{ + contract_address, + transaction_hash, + BlockNumber, + ChainId, + ContractAddress, + TransactionHash, + }; + use pathfinder_storage::StorageBuilder; + use starknet_gateway_client::Client; + use starknet_gateway_types::reply::PendingBlock; + use tokio::sync::{mpsc, watch}; + + use crate::context::{RpcConfig, RpcContext}; + use crate::jsonrpc::{handle_json_rpc_socket, RpcResponse}; + use crate::pending::PendingWatcher; + use crate::v02::types::syncing::Syncing; + use crate::{v08, Notifications, PendingData, SyncState}; + + #[tokio::test] + async fn no_filtering_no_details() { + let Setup { + tx, + mut rx, + pending_data_tx, + } = setup(); + tx.send(Ok(Message::Text( + serde_json::json!({ + "jsonrpc": "2.0", + "id": 1, + "method": "starknet_subscribePendingTransactions", + "params": {} + }) + .to_string(), + ))) + .await + .unwrap(); + let response = rx.recv().await.unwrap().unwrap(); + let subscription_id = match response { + Message::Text(json) => { + let json: serde_json::Value = serde_json::from_str(&json).unwrap(); + assert_eq!(json["jsonrpc"], "2.0"); + assert_eq!(json["id"], 1); + json["result"]["subscription_id"].as_u64().unwrap() + } + _ => { + panic!("Expected text message"); + } + }; + pending_data_tx + .send(sample_block( + BlockNumber::GENESIS, + vec![ + (contract_address!("0x1"), transaction_hash!("0x1")), + (contract_address!("0x2"), transaction_hash!("0x2")), + ], + )) + .unwrap(); + assert_eq!( + recv(&mut rx).await, + sample_message_no_details("0x1", subscription_id) + ); + assert_eq!( + recv(&mut rx).await, + sample_message_no_details("0x2", subscription_id) + ); + assert!(rx.is_empty()); + pending_data_tx + .send(sample_block( + BlockNumber::GENESIS, + vec![ + (contract_address!("0x1"), transaction_hash!("0x1")), + (contract_address!("0x2"), transaction_hash!("0x2")), + (contract_address!("0x3"), transaction_hash!("0x3")), + (contract_address!("0x4"), transaction_hash!("0x4")), + (contract_address!("0x5"), transaction_hash!("0x5")), + ], + )) + .unwrap(); + // Assert that same transactions are not sent twice. + assert_eq!( + recv(&mut rx).await, + sample_message_no_details("0x3", subscription_id) + ); + assert_eq!( + recv(&mut rx).await, + sample_message_no_details("0x4", subscription_id) + ); + assert_eq!( + recv(&mut rx).await, + sample_message_no_details("0x5", subscription_id) + ); + // Assert that transactions from new blocks are sent correctly. + pending_data_tx + .send(sample_block( + BlockNumber::GENESIS + 1, + vec![(contract_address!("0x1"), transaction_hash!("0x1"))], + )) + .unwrap(); + assert_eq!( + recv(&mut rx).await, + sample_message_no_details("0x1", subscription_id) + ); + assert!(rx.is_empty()); + } + + #[tokio::test] + async fn no_filtering_with_details() { + let Setup { + tx, + mut rx, + pending_data_tx, + } = setup(); + tx.send(Ok(Message::Text( + serde_json::json!({ + "jsonrpc": "2.0", + "id": 1, + "method": "starknet_subscribePendingTransactions", + "params": { + "transaction_details": true + } + }) + .to_string(), + ))) + .await + .unwrap(); + let response = rx.recv().await.unwrap().unwrap(); + let subscription_id = match response { + Message::Text(json) => { + let json: serde_json::Value = serde_json::from_str(&json).unwrap(); + assert_eq!(json["jsonrpc"], "2.0"); + assert_eq!(json["id"], 1); + json["result"]["subscription_id"].as_u64().unwrap() + } + _ => { + panic!("Expected text message"); + } + }; + assert!(rx.is_empty()); + pending_data_tx + .send(sample_block( + BlockNumber::GENESIS, + vec![ + (contract_address!("0x1"), transaction_hash!("0x3")), + (contract_address!("0x2"), transaction_hash!("0x4")), + ], + )) + .unwrap(); + assert_eq!( + recv(&mut rx).await, + sample_message_with_details("0x1", "0x3", subscription_id) + ); + assert_eq!( + recv(&mut rx).await, + sample_message_with_details("0x2", "0x4", subscription_id) + ); + assert!(rx.is_empty()); + } + + #[tokio::test] + async fn filtering_one_address() { + let Setup { + tx, + mut rx, + pending_data_tx, + } = setup(); + tx.send(Ok(Message::Text( + serde_json::json!({ + "jsonrpc": "2.0", + "id": 1, + "method": "starknet_subscribePendingTransactions", + "params": { + "sender_address": ["0x1"] + } + }) + .to_string(), + ))) + .await + .unwrap(); + let response = rx.recv().await.unwrap().unwrap(); + let subscription_id = match response { + Message::Text(json) => { + let json: serde_json::Value = serde_json::from_str(&json).unwrap(); + assert_eq!(json["jsonrpc"], "2.0"); + assert_eq!(json["id"], 1); + json["result"]["subscription_id"].as_u64().unwrap() + } + _ => { + panic!("Expected text message"); + } + }; + assert!(rx.is_empty()); + pending_data_tx + .send(sample_block( + BlockNumber::GENESIS, + vec![ + (contract_address!("0x1"), transaction_hash!("0x1")), + (contract_address!("0x2"), transaction_hash!("0x2")), + ], + )) + .unwrap(); + assert_eq!( + recv(&mut rx).await, + sample_message_no_details("0x1", subscription_id) + ); + assert!(rx.is_empty()); + } + + #[tokio::test] + async fn filtering_two_addresses() { + let Setup { + tx, + mut rx, + pending_data_tx, + } = setup(); + tx.send(Ok(Message::Text( + serde_json::json!({ + "jsonrpc": "2.0", + "id": 1, + "method": "starknet_subscribePendingTransactions", + "params": { + "sender_address": ["0x1", "0x2"] + } + }) + .to_string(), + ))) + .await + .unwrap(); + let response = rx.recv().await.unwrap().unwrap(); + let subscription_id = match response { + Message::Text(json) => { + let json: serde_json::Value = serde_json::from_str(&json).unwrap(); + assert_eq!(json["jsonrpc"], "2.0"); + assert_eq!(json["id"], 1); + json["result"]["subscription_id"].as_u64().unwrap() + } + _ => { + panic!("Expected text message"); + } + }; + assert!(rx.is_empty()); + pending_data_tx + .send(sample_block( + BlockNumber::GENESIS, + vec![ + (contract_address!("0x1"), transaction_hash!("0x3")), + (contract_address!("0x2"), transaction_hash!("0x4")), + (contract_address!("0x3"), transaction_hash!("0x5")), + ], + )) + .unwrap(); + assert_eq!( + recv(&mut rx).await, + sample_message_no_details("0x3", subscription_id) + ); + assert_eq!( + recv(&mut rx).await, + sample_message_no_details("0x4", subscription_id) + ); + assert!(rx.is_empty()); + } + + async fn recv(rx: &mut mpsc::Receiver>) -> serde_json::Value { + let res = rx.recv().await.unwrap().unwrap(); + match res { + Message::Text(json) => serde_json::from_str(&json).unwrap(), + _ => panic!("Expected text message"), + } + } + + fn sample_block( + block_number: BlockNumber, + txs: Vec<(ContractAddress, TransactionHash)>, + ) -> PendingData { + PendingData { + block: PendingBlock { + transactions: txs + .into_iter() + .map(|(sender_address, hash)| Transaction { + variant: TransactionVariant::DeclareV0(DeclareTransactionV0V1 { + sender_address, + ..Default::default() + }), + hash, + }) + .collect(), + ..Default::default() + } + .into(), + number: block_number, + ..Default::default() + } + } + + fn sample_message_no_details(hash: &str, subscription_id: u64) -> serde_json::Value { + serde_json::json!({ + "jsonrpc":"2.0", + "method":"starknet_subscriptionPendingTransactions", + "params": { + "result": hash, + "subscription_id": subscription_id + } + }) + } + + fn sample_message_with_details( + sender_address: &str, + hash: &str, + subscription_id: u64, + ) -> serde_json::Value { + serde_json::json!({ + "jsonrpc":"2.0", + "method":"starknet_subscriptionPendingTransactions", + "params": { + "result": { + "class_hash": "0x0", + "max_fee": "0x0", + "sender_address": sender_address, + "signature": [], + "transaction_hash": hash, + "type": "DECLARE", + "version": "0x0" + }, + "subscription_id": subscription_id + } + }) + } + + fn setup() -> Setup { + let storage = StorageBuilder::in_memory().unwrap(); + let (pending_data_tx, pending_data) = tokio::sync::watch::channel(Default::default()); + let notifications = Notifications::default(); + let ctx = RpcContext { + cache: Default::default(), + storage, + execution_storage: StorageBuilder::in_memory().unwrap(), + pending_data: PendingWatcher::new(pending_data), + sync_status: SyncState { + status: Syncing::False(false).into(), + } + .into(), + chain_id: ChainId::MAINNET, + sequencer: Client::mainnet(Duration::from_secs(10)), + websocket: None, + notifications, + config: RpcConfig { + batch_concurrency_limit: 1.try_into().unwrap(), + get_events_max_blocks_to_scan: 1.try_into().unwrap(), + get_events_max_uncached_bloom_filters_to_load: 1.try_into().unwrap(), + custom_versioned_constants: None, + }, + }; + let router = v08::register_routes().build(ctx); + let (sender_tx, sender_rx) = mpsc::channel(1024); + let (receiver_tx, receiver_rx) = mpsc::channel(1024); + handle_json_rpc_socket(router.clone(), sender_tx, receiver_rx); + Setup { + tx: receiver_tx, + rx: sender_rx, + pending_data_tx, + } + } + + struct Setup { + tx: mpsc::Sender>, + rx: mpsc::Receiver>, + pending_data_tx: watch::Sender, + } +} diff --git a/crates/rpc/src/pending.rs b/crates/rpc/src/pending.rs index 2c5ac75636..2fc503ba8e 100644 --- a/crates/rpc/src/pending.rs +++ b/crates/rpc/src/pending.rs @@ -9,7 +9,7 @@ use tokio::sync::watch::Receiver as WatchReceiver; /// Provides the latest [PendingData] which is consistent with a given /// view of storage. #[derive(Clone)] -pub struct PendingWatcher(WatchReceiver); +pub struct PendingWatcher(pub WatchReceiver); #[derive(Clone, Default, Debug, PartialEq)] pub struct PendingData { diff --git a/crates/rpc/src/v08.rs b/crates/rpc/src/v08.rs index f80a312bc8..def8860c74 100644 --- a/crates/rpc/src/v08.rs +++ b/crates/rpc/src/v08.rs @@ -1,9 +1,11 @@ use crate::jsonrpc::{RpcRouter, RpcRouterBuilder}; use crate::method::subscribe_new_heads::SubscribeNewHeads; +use crate::method::subscribe_pending_transactions::SubscribePendingTransactions; #[rustfmt::skip] pub fn register_routes() -> RpcRouterBuilder { RpcRouter::builder(crate::RpcVersion::V08) - .register("starknet_subscribeNewHeads", SubscribeNewHeads) - .register("starknet_specVersion", || "0.8.0") + .register("starknet_subscribeNewHeads", SubscribeNewHeads) + .register("starknet_subscribePendingTransactions", SubscribePendingTransactions) + .register("starknet_specVersion", || "0.8.0") } From 0cd34b1d0f0c60040616c956d7080b2f9b2e3b14 Mon Sep 17 00:00:00 2001 From: sistemd Date: Wed, 25 Sep 2024 14:44:01 +0200 Subject: [PATCH 084/282] fix version --- crates/rpc/src/v08.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/rpc/src/v08.rs b/crates/rpc/src/v08.rs index def8860c74..7cb46f5a94 100644 --- a/crates/rpc/src/v08.rs +++ b/crates/rpc/src/v08.rs @@ -7,5 +7,5 @@ pub fn register_routes() -> RpcRouterBuilder { RpcRouter::builder(crate::RpcVersion::V08) .register("starknet_subscribeNewHeads", SubscribeNewHeads) .register("starknet_subscribePendingTransactions", SubscribePendingTransactions) - .register("starknet_specVersion", || "0.8.0") + .register("starknet_specVersion", || "0.8.0-rc0") } From 87d06d6752a60ee7618f99bcb5acb43dd7e14386 Mon Sep 17 00:00:00 2001 From: Jim McDonald Date: Wed, 25 Sep 2024 17:42:02 +0100 Subject: [PATCH 085/282] Add process_start_time_seconds metric. --- CHANGELOG.md | 1 + crates/pathfinder/src/bin/pathfinder/main.rs | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a394752189..657f2430f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Pathfinder now fetches data concurrently from the feeder gateway when catching up. The `--gateway.fetch-concurrency` CLI option can be used to limit how many blocks are fetched concurrently (the default is 8). - `--disable-version-update-check` CLI option has been added to disable the periodic checking for a new version. +- add `process_start_time_seconds` metric showing the unix timestamp when the process started. ### Changed diff --git a/crates/pathfinder/src/bin/pathfinder/main.rs b/crates/pathfinder/src/bin/pathfinder/main.rs index 6ad4a4651a..1145cecba4 100644 --- a/crates/pathfinder/src/bin/pathfinder/main.rs +++ b/crates/pathfinder/src/bin/pathfinder/main.rs @@ -5,6 +5,7 @@ use std::num::NonZeroU32; use std::path::PathBuf; use std::sync::atomic::AtomicBool; use std::sync::Arc; +use std::time::{SystemTime, UNIX_EPOCH}; use anyhow::Context; use metrics_exporter_prometheus::PrometheusBuilder; @@ -642,6 +643,11 @@ async fn spawn_monitoring( metrics::gauge!("pathfinder_build_info", 1.0, "version" => VERGEN_GIT_DESCRIBE); + match SystemTime::now().duration_since(UNIX_EPOCH) { + Ok(duration) => metrics::gauge!("process_start_time_seconds", duration.as_secs() as f64), + Err(err) => tracing::error!("Failed to read system time: {:?}", err), + } + let (_, handle) = monitoring::spawn_server(address, readiness, sync_state, prometheus_handle).await?; Ok(handle) From cb812cac2527c5296ef2fe38d28ca802f13d8f49 Mon Sep 17 00:00:00 2001 From: Krzysztof Lis Date: Tue, 24 Sep 2024 16:26:11 +0200 Subject: [PATCH 086/282] feat(p2p): fix peer eviction after libp2p update --- crates/p2p/src/behaviour.rs | 4 +- crates/p2p/src/behaviour/builder.rs | 2 +- crates/p2p/src/lib.rs | 5 +- crates/p2p/src/test_utils/peer.rs | 2 +- crates/p2p/src/tests.rs | 47 ++++++++++--------- .../pathfinder/src/bin/pathfinder/config.rs | 13 +++++ crates/pathfinder/src/bin/pathfinder/main.rs | 2 +- 7 files changed, 48 insertions(+), 27 deletions(-) diff --git a/crates/p2p/src/behaviour.rs b/crates/p2p/src/behaviour.rs index 499ef71380..6427ece777 100644 --- a/crates/p2p/src/behaviour.rs +++ b/crates/p2p/src/behaviour.rs @@ -682,7 +682,9 @@ impl Behaviour { /// Prevent evicted peers from reconnecting too quickly. fn prevent_evicted_peer_reconnections(&self, peer_id: PeerId) -> Result<(), ConnectionDenied> { let timeout = if cfg!(test) { - Duration::from_secs(1) + // Needs to be large enough to mitigate the possibility of failing explicit + // dials in tests, due to implicit dials triggered by automatic bootstrapping. + Duration::from_secs(15) } else { Duration::from_secs(30) }; diff --git a/crates/p2p/src/behaviour/builder.rs b/crates/p2p/src/behaviour/builder.rs index 1463c38e5c..39d41dd4de 100644 --- a/crates/p2p/src/behaviour/builder.rs +++ b/crates/p2p/src/behaviour/builder.rs @@ -106,7 +106,7 @@ impl Builder { kademlia_config.set_record_ttl(Some(Duration::from_secs(0))); kademlia_config.set_provider_record_ttl(Some(PROVIDER_PUBLICATION_INTERVAL * 3)); kademlia_config.set_provider_publication_interval(Some(PROVIDER_PUBLICATION_INTERVAL)); - kademlia_config.set_periodic_bootstrap_interval(Some(cfg.bootstrap_period)); + kademlia_config.set_periodic_bootstrap_interval(cfg.bootstrap_period); let peer_id = identity.public().to_peer_id(); let secret = Secret::new(&identity); diff --git a/crates/p2p/src/lib.rs b/crates/p2p/src/lib.rs index a2225e3e08..b1c473d20c 100644 --- a/crates/p2p/src/lib.rs +++ b/crates/p2p/src/lib.rs @@ -60,8 +60,9 @@ pub struct Config { pub eviction_timeout: Duration, pub ip_whitelist: Vec, /// If the number of peers is below the low watermark, the node will attempt - /// periodic bootstrapping at this interval. - pub bootstrap_period: Duration, + /// periodic bootstrapping at this interval. If `None`, periodic bootstrap + /// is disabled and only automatic bootstrap remains. + pub bootstrap_period: Option, pub inbound_connections_rate_limit: RateLimit, /// Custom protocol name for Kademlia pub kad_name: Option, diff --git a/crates/p2p/src/test_utils/peer.rs b/crates/p2p/src/test_utils/peer.rs index 72911864fb..364f4f557a 100644 --- a/crates/p2p/src/test_utils/peer.rs +++ b/crates/p2p/src/test_utils/peer.rs @@ -36,7 +36,7 @@ impl Config { max_inbound_relayed_peers: 10, max_outbound_peers: 10, ip_whitelist: vec!["::1/0".parse().unwrap(), "0.0.0.0/0".parse().unwrap()], - bootstrap_period: Duration::from_millis(500), + bootstrap_period: None, eviction_timeout: Duration::from_secs(15 * 60), inbound_connections_rate_limit: RateLimit { max: 1000, diff --git a/crates/p2p/src/tests.rs b/crates/p2p/src/tests.rs index 3a80f46c73..53a3310891 100644 --- a/crates/p2p/src/tests.rs +++ b/crates/p2p/src/tests.rs @@ -161,7 +161,7 @@ async fn periodic_bootstrap() { const BOOTSTRAP_PERIOD: Duration = Duration::from_millis(500); let cfg = Config { - bootstrap_period: BOOTSTRAP_PERIOD, + bootstrap_period: Some(BOOTSTRAP_PERIOD), ..Config::for_test() }; let mut boot = TestPeer::new(cfg.clone()); @@ -604,19 +604,19 @@ async fn inbound_peer_eviction() { } /// Ensure that evicted peers can't reconnect too quickly. -#[ignore = "TODO fix eviction and low watermark logic after updating to libp2p 0.54.1"] #[test_log::test(tokio::test)] async fn evicted_peer_reconnection() { let cfg = Config { max_inbound_direct_peers: 1000, max_inbound_relayed_peers: 0, - max_outbound_peers: 1, + max_outbound_peers: 21, + bootstrap_period: None, ..Config::for_test() }; let mut peer1 = TestPeer::new(cfg.clone()); let mut peer2 = TestPeer::new(cfg.clone()); - let mut peer3 = TestPeer::new(cfg); + let mut peer3 = TestPeer::new(cfg.clone()); let addr1 = peer1.start_listening().await.unwrap(); tracing::info!(%peer1.peer_id, %addr1); @@ -625,13 +625,29 @@ async fn evicted_peer_reconnection() { let addr3 = peer3.start_listening().await.unwrap(); tracing::info!(%peer3.peer_id, %addr3); + // We need these peers to fill the DHT of peer1 up to the watermark that + // triggers "automatic bootstrap" so that it does not interfere with the test + // https://github.com/libp2p/rust-libp2p/pull/4838 + // https://github.com/libp2p/rust-libp2p/blob/d7beb55f672dce54017fa4b30f67ecb8d66b9810/protocols/kad/src/behaviour.rs#L1401). + let twenty_peers = (0..20) + .map(|_| TestPeer::new(cfg.clone())) + .collect::>(); + + for mut peer in twenty_peers.into_iter() { + let addr = peer.start_listening().await.unwrap(); + consume_all_events_forever(peer.event_receiver); + + peer1.client.dial(peer.peer_id, addr).await.unwrap(); + } + // Connect peer1 to peer2, then to peer3. Because the outbound connection limit - // is 1, peer2 will be evicted when peer1 connects to peer3. + // is 21, peer2 will be evicted when peer1 connects to peer3. peer1 .client .dial(peer2.peer_id, addr2.clone()) .await .unwrap(); + peer1.client.not_useful(peer2.peer_id).await; peer1.client.dial(peer3.peer_id, addr3).await.unwrap(); @@ -653,26 +669,15 @@ async fn evicted_peer_reconnection() { consume_accumulated_events(&mut peer2.event_receiver).await; - // In this case there is no peer ID when connecting, so the connection gets - // closed after being established. - peer2 + // peer2 can be reconnected after a timeout. + tokio::time::sleep(Duration::from_secs(40)).await; + + peer1 .client - .dial(peer1.peer_id, addr1.clone()) + .dial(peer2.peer_id, addr2.clone()) .await .unwrap(); - wait_for_event(&mut peer2.event_receiver, |event| match event { - Event::Test(TestEvent::ConnectionClosed { remote, .. }) if remote == peer1.peer_id => { - Some(()) - } - _ => None, - }) - .await; - - // peer2 can be reconnected after a timeout. - tokio::time::sleep(Duration::from_secs(1)).await; - peer1.client.dial(peer2.peer_id, addr2).await.unwrap(); - // peer3 gets evicted. wait_for_event(&mut peer1.event_receiver, |event| match event { Event::Test(TestEvent::ConnectionClosed { remote, .. }) if remote == peer3.peer_id => { Some(()) diff --git a/crates/pathfinder/src/bin/pathfinder/config.rs b/crates/pathfinder/src/bin/pathfinder/config.rs index 6a6a3c01b5..e1afcfb85f 100644 --- a/crates/pathfinder/src/bin/pathfinder/config.rs +++ b/crates/pathfinder/src/bin/pathfinder/config.rs @@ -838,6 +838,19 @@ impl P2PConfig { .exit() } + // The low watermark is defined in `bootstrap_on_low_peers` + // https://github.com/libp2p/rust-libp2p/blob/d7beb55f672dce54017fa4b30f67ecb8d66b9810/protocols/kad/src/behaviour.rs#L1401). + // as the K value of 20 + // https://github.com/libp2p/rust-libp2p/blob/d7beb55f672dce54017fa4b30f67ecb8d66b9810/protocols/kad/src/lib.rs#L93 + if args.max_outbound_connections <= 20 { + Cli::command() + .error( + ErrorKind::ValueValidation, + "p2p.max-outbound-connections must be at least 21", + ) + .exit() + } + if args.kad_name.iter().any(|x| !x.starts_with('/')) { Cli::command() .error( diff --git a/crates/pathfinder/src/bin/pathfinder/main.rs b/crates/pathfinder/src/bin/pathfinder/main.rs index 6ad4a4651a..751271f289 100644 --- a/crates/pathfinder/src/bin/pathfinder/main.rs +++ b/crates/pathfinder/src/bin/pathfinder/main.rs @@ -448,7 +448,7 @@ async fn start_p2p( max_inbound_relayed_peers: config.max_inbound_relayed_connections, max_outbound_peers: config.max_outbound_connections, ip_whitelist: config.ip_whitelist, - bootstrap_period: Duration::from_secs(2 * 60), + bootstrap_period: Some(Duration::from_secs(2 * 60)), eviction_timeout: config.eviction_timeout, inbound_connections_rate_limit: p2p::RateLimit { max: 10, From 71bc270a74d0fd178bd2f4cc2977c6c7673802c2 Mon Sep 17 00:00:00 2001 From: Krisztian Kovacs Date: Wed, 25 Sep 2024 17:20:04 +0200 Subject: [PATCH 087/282] fix(rpc/syncing): `starknet_syncing` should return false when caught up This change modifies the `starknet_syncing` method to just return false when we're caught up. "Caught up" means within 5 blocks of the tip of the chain for now. Closes: #2253 --- crates/rpc/src/method/syncing.rs | 75 +++++++++++++++++++++++++++++++- 1 file changed, 74 insertions(+), 1 deletion(-) diff --git a/crates/rpc/src/method/syncing.rs b/crates/rpc/src/method/syncing.rs index b7e17917c1..58be864434 100644 --- a/crates/rpc/src/method/syncing.rs +++ b/crates/rpc/src/method/syncing.rs @@ -7,7 +7,17 @@ pub struct Output(Syncing); pub async fn syncing(context: RpcContext) -> Result { // Scoped so I don't have to think too hard about mutex guard drop semantics. - let value = { context.sync_status.status.read().await.clone() }; + let value = match *context.sync_status.status.read().await { + Syncing::False(_) => Syncing::False(false), + Syncing::Status(status) => { + if status.highest.number.get() - status.current.number.get() < 6 { + // In case we're (almost) caught up we just return false. + Syncing::False(false) + } else { + Syncing::Status(status) + } + } + }; Ok(Output(value)) } @@ -23,3 +33,66 @@ impl crate::dto::serialize::SerializeForVersion for Output { } } } + +#[cfg(test)] +mod tests { + use pathfinder_common::{block_hash, BlockNumber}; + + use super::*; + use crate::v02::types::syncing::{NumberedBlock, Status}; + + #[tokio::test] + async fn not_started_yet() { + let context = RpcContext::for_tests(); + + *context.sync_status.status.write().await = Syncing::False(false); + + assert_eq!(syncing(context).await.unwrap().0, Syncing::False(false)); + } + + #[tokio::test] + async fn caught_up() { + let context = RpcContext::for_tests(); + + *context.sync_status.status.write().await = Syncing::Status(Status { + starting: NumberedBlock { + hash: block_hash!("0xaaaa"), + number: BlockNumber::new_or_panic(0), + }, + current: NumberedBlock { + hash: block_hash!("0xaaaa"), + number: BlockNumber::new_or_panic(0), + }, + highest: NumberedBlock { + hash: block_hash!("0xaaaa"), + number: BlockNumber::new_or_panic(0), + }, + }); + + assert_eq!(syncing(context).await.unwrap().0, Syncing::False(false)); + } + + #[tokio::test] + async fn syncing_in_progress() { + let context = RpcContext::for_tests(); + + let status = Status { + starting: NumberedBlock { + hash: block_hash!("0xaaaa"), + number: BlockNumber::new_or_panic(0), + }, + current: NumberedBlock { + hash: block_hash!("0xbbbb"), + number: BlockNumber::new_or_panic(2), + }, + highest: NumberedBlock { + hash: block_hash!("0xcccc"), + number: BlockNumber::new_or_panic(10), + }, + }; + + *context.sync_status.status.write().await = Syncing::Status(status); + + assert_eq!(syncing(context).await.unwrap().0, Syncing::Status(status)); + } +} From fb30f35c23becfd4d4cc9e13e1a23aa28e5498da Mon Sep 17 00:00:00 2001 From: Jim McDonald Date: Thu, 26 Sep 2024 16:00:58 +0100 Subject: [PATCH 088/282] Add metric to README. --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 7a6cf5ad86..e1b7945592 100644 --- a/README.md +++ b/README.md @@ -317,6 +317,10 @@ This endpoint is useful for Docker nodes which only want to present themselves a `/metrics` provides a [Prometheus](https://prometheus.io/) metrics scrape endpoint. Currently the following metrics are available: +#### Process metrics + +- `process_start_time_seconds` provides the unix timestamp at which pathfinder started + #### RPC related counters - `rpc_method_calls_total`, From d3a36ed313aca75c34918435e4cbe6fdb32352c7 Mon Sep 17 00:00:00 2001 From: Herman Obst Demaestri Date: Thu, 26 Sep 2024 10:57:12 -0700 Subject: [PATCH 089/282] remove ClassTrieStorage as was a duplication of ClassStorage --- crates/merkle-tree/src/class.rs | 40 +-------------------------------- 1 file changed, 1 insertion(+), 39 deletions(-) diff --git a/crates/merkle-tree/src/class.rs b/crates/merkle-tree/src/class.rs index b3cd3b4783..265ae4fedd 100644 --- a/crates/merkle-tree/src/class.rs +++ b/crates/merkle-tree/src/class.rs @@ -89,7 +89,7 @@ impl<'tx> ClassCommitmentTree<'tx> { return Ok(None); }; - let storage = ClassTrieStorage { + let storage = ClassStorage { tx, block: Some(block), }; @@ -98,44 +98,6 @@ impl<'tx> ClassCommitmentTree<'tx> { } } -struct ClassTrieStorage<'tx> { - tx: &'tx Transaction<'tx>, - block: Option, -} - -impl crate::storage::Storage for ClassTrieStorage<'_> { - fn get(&self, index: u64) -> anyhow::Result> { - self.tx.class_trie_node(index) - } - - fn hash(&self, index: u64) -> anyhow::Result> { - self.tx.class_trie_node_hash(index) - } - - fn leaf(&self, path: &BitSlice) -> anyhow::Result> { - assert!(path.len() == 251); - - let Some(block) = self.block else { - return Ok(None); - }; - - let sierra = - ClassHash(Felt::from_bits(path).context("Mapping leaf path to contract address")?); - - let casm = self - .tx - .casm_hash_at(block.into(), sierra) - .context("Querying CASM hash")?; - let Some(casm) = casm else { - return Ok(None); - }; - - let value = self.tx.class_commitment_leaf(block, &casm)?.map(|x| x.0); - - Ok(value) - } -} - struct ClassStorage<'tx> { tx: &'tx Transaction<'tx>, block: Option, From 045cd7e81ed8b388112c4d5029c4c5b671fa4858 Mon Sep 17 00:00:00 2001 From: Herman Obst Demaestri Date: Thu, 26 Sep 2024 11:38:57 -0700 Subject: [PATCH 090/282] implement DeserializeForVersion for GetClassProofInput --- crates/rpc/src/pathfinder/methods/get_proof.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/crates/rpc/src/pathfinder/methods/get_proof.rs b/crates/rpc/src/pathfinder/methods/get_proof.rs index e05df4c6b7..96295d3fb6 100644 --- a/crates/rpc/src/pathfinder/methods/get_proof.rs +++ b/crates/rpc/src/pathfinder/methods/get_proof.rs @@ -35,6 +35,17 @@ impl crate::dto::DeserializeForVersion for GetProofInput { } } +impl crate::dto::DeserializeForVersion for GetClassProofInput { + fn deserialize(value: crate::dto::Value) -> Result { + value.deserialize_map(|value| { + Ok(Self { + block_id: value.deserialize("block_id")?, + class_hash: ClassHash(value.deserialize("class_hash")?), + }) + }) + } +} + // FIXME: allow `generate_rpc_error_subset!` to work with enum struct variants. // This may not actually be possible though. #[derive(Debug)] From 1c0f51baa860464786856d91a6c56529e990cb1d Mon Sep 17 00:00:00 2001 From: Krisztian Kovacs Date: Fri, 27 Sep 2024 12:07:07 +0200 Subject: [PATCH 091/282] chore(gateway-types/class_hash): remove unused `extract_abi_code_hash` --- crates/gateway-types/src/class_hash.rs | 39 -------------------------- 1 file changed, 39 deletions(-) diff --git a/crates/gateway-types/src/class_hash.rs b/crates/gateway-types/src/class_hash.rs index 9ec2fb4a8c..d19b3b8ffd 100644 --- a/crates/gateway-types/src/class_hash.rs +++ b/crates/gateway-types/src/class_hash.rs @@ -55,45 +55,6 @@ fn parse_contract_definition( }) } -/// Sibling functionality to only [`compute_class_hash`], returning also the -/// ABI, and bytecode parts as json bytes. -/// -/// NOTE: This function is deprecated. We no longer store ABI and bytecode in -/// the database, and this function is only used by _old_ database migration -/// steps. -pub fn extract_abi_code_hash( - contract_definition_dump: &[u8], -) -> Result<(Vec, Vec, ClassHash)> { - let contract_definition = parse_contract_definition(contract_definition_dump) - .context("Failed to parse contract definition")?; - - match contract_definition { - json::ContractDefinition::Sierra(contract_definition) => { - let abi = serde_json::to_vec(&contract_definition.abi) - .context("Serialize contract_definition.abi")?; - let code = serde_json::to_vec(&contract_definition.sierra_program) - .context("Serialize contract_definition.sierra_program")?; - - let hash = - compute_sierra_class_hash(contract_definition).context("Compute class hash")?; - - Ok((abi, code, hash)) - } - json::ContractDefinition::Cairo(contract_definition) => { - // just in case we'd accidentally modify these in the compute_class_hash0 - let abi = serde_json::to_vec(&contract_definition.abi) - .context("Serialize contract_definition.abi")?; - let code = serde_json::to_vec(&contract_definition.program.data) - .context("Serialize contract_definition.program.data")?; - - let hash = - compute_cairo_class_hash(contract_definition).context("Compute class hash")?; - - Ok((abi, code, hash)) - } - } -} - pub mod from_parts { use std::collections::HashMap; From a5247f47efd205bb7cfffed74eb6623c9fb1de9d Mon Sep 17 00:00:00 2001 From: sistemd Date: Fri, 27 Sep 2024 13:01:25 +0200 Subject: [PATCH 092/282] allow GET requests for websocket, handle optional params correctly --- crates/rpc/src/dto.rs | 4 + crates/rpc/src/jsonrpc/router.rs | 5 ++ crates/rpc/src/jsonrpc/router/subscription.rs | 19 +++-- crates/rpc/src/lib.rs | 2 + crates/rpc/src/method/subscribe_new_heads.rs | 78 ++++++++++++------- .../method/subscribe_pending_transactions.rs | 27 ++++--- crates/rpc/src/v08.rs | 1 + 7 files changed, 88 insertions(+), 48 deletions(-) diff --git a/crates/rpc/src/dto.rs b/crates/rpc/src/dto.rs index 5ae371c0ac..b86a932e6f 100644 --- a/crates/rpc/src/dto.rs +++ b/crates/rpc/src/dto.rs @@ -52,6 +52,10 @@ impl Value { self.data.is_string() } + pub fn is_null(&self) -> bool { + self.data.is_null() + } + pub fn deserialize(self) -> Result { T::deserialize(self) } diff --git a/crates/rpc/src/jsonrpc/router.rs b/crates/rpc/src/jsonrpc/router.rs index a864fca40f..030b248fab 100644 --- a/crates/rpc/src/jsonrpc/router.rs +++ b/crates/rpc/src/jsonrpc/router.rs @@ -181,6 +181,7 @@ fn is_utf8_encoded_json(headers: http::HeaderMap) -> bool { pub async fn rpc_handler( State(state): State, headers: http::HeaderMap, + method: http::Method, ws: Option, body: axum::body::Bytes, ) -> impl axum::response::IntoResponse { @@ -190,6 +191,10 @@ pub async fn rpc_handler( handle_json_rpc_socket(state, ws_tx, ws_rx); }), None => { + if method != http::Method::POST { + return StatusCode::METHOD_NOT_ALLOWED.into_response(); + } + // Only utf8 json content allowed. if !is_utf8_encoded_json(headers) { return StatusCode::UNSUPPORTED_MEDIA_TYPE.into_response(); diff --git a/crates/rpc/src/jsonrpc/router/subscription.rs b/crates/rpc/src/jsonrpc/router/subscription.rs index c7dc988c6f..14a0b41503 100644 --- a/crates/rpc/src/jsonrpc/router/subscription.rs +++ b/crates/rpc/src/jsonrpc/router/subscription.rs @@ -58,20 +58,22 @@ pub(super) trait RpcSubscriptionEndpoint: Send + Sync { /// - Stream the first active update, and then keep streaming the rest. #[axum::async_trait] pub trait RpcSubscriptionFlow: Send + Sync { - type Request: crate::dto::DeserializeForVersion + Clone + Send + Sync + 'static; + /// `params` field of the subscription request. + type Params: crate::dto::DeserializeForVersion + Clone + Send + Sync + 'static; + /// The notification type to be sent to the client. type Notification: crate::dto::serialize::SerializeForVersion + Send + Sync + 'static; /// The block to start streaming from. If the subscription endpoint does not /// support catching up, this method should always return /// [`BlockId::Latest`]. - fn starting_block(req: &Self::Request) -> BlockId; + fn starting_block(params: &Self::Params) -> BlockId; /// Fetch historical data from the `from` block to the `to` block. The /// range is inclusive on both ends. If there is no historical data in the /// range, return an empty vec. async fn catch_up( state: &RpcContext, - req: &Self::Request, + params: &Self::Params, from: BlockNumber, to: BlockNumber, ) -> Result>, RpcError>; @@ -79,7 +81,7 @@ pub trait RpcSubscriptionFlow: Send + Sync { /// Subscribe to active updates. async fn subscribe( state: RpcContext, - req: Self::Request, + params: Self::Params, tx: mpsc::Sender>, ); } @@ -110,7 +112,7 @@ where req_id: RequestId, ws_tx: mpsc::Sender>, ) -> Result<(), RpcError> { - let req = T::Request::deserialize(crate::dto::Value::new(input, router.version)) + let req = T::Params::deserialize(crate::dto::Value::new(input, router.version)) .map_err(|e| RpcError::InvalidParams(e.to_string()))?; let tx = SubscriptionSender { subscription_id, @@ -444,11 +446,12 @@ pub fn handle_json_rpc_socket( let params = match serde_json::to_value(rpc_request.params) { Ok(params) => params, - Err(_e) => { + Err(e) => { if ws_tx .send(Ok(Message::Text( - serde_json::to_string(&RpcError::InvalidParams( - "Invalid params".to_string(), + serde_json::to_string(&RpcResponse::invalid_params( + req_id, + e.to_string(), )) .unwrap(), ))) diff --git a/crates/rpc/src/lib.rs b/crates/rpc/src/lib.rs index f8aceee372..afd07524af 100644 --- a/crates/rpc/src/lib.rs +++ b/crates/rpc/src/lib.rs @@ -188,6 +188,8 @@ impl RpcServer { .with_state(v06_routes.clone()) .route("/rpc/v0_7", post(rpc_handler)) .with_state(v07_routes.clone()) + .route("/rpc/v0_8", post(rpc_handler).get(rpc_handler)) + .with_state(v08_routes.clone()) .route("/rpc/pathfinder/v0.1", post(rpc_handler)) .route("/rpc/pathfinder/v0_1", post(rpc_handler)) .with_state(pathfinder_routes.clone()); diff --git a/crates/rpc/src/method/subscribe_new_heads.rs b/crates/rpc/src/method/subscribe_new_heads.rs index 367faf9a90..1dae8d55d6 100644 --- a/crates/rpc/src/method/subscribe_new_heads.rs +++ b/crates/rpc/src/method/subscribe_new_heads.rs @@ -12,16 +12,20 @@ use crate::Reorg; pub struct SubscribeNewHeads; #[derive(Debug, Clone)] -pub struct Request { +pub struct Params { block: Option, } -impl crate::dto::DeserializeForVersion for Request { +impl crate::dto::DeserializeForVersion for Option { fn deserialize(value: crate::dto::Value) -> Result { + if value.is_null() { + // It is OK to omit the params. + return Ok(None); + } value.deserialize_map(|value| { - Ok(Self { + Ok(Some(Params { block: value.deserialize_optional_serde("block")?, - }) + })) }) } } @@ -48,16 +52,19 @@ const SUBSCRIPTION_NAME: &str = "starknet_subscriptionNewHeads"; #[async_trait] impl RpcSubscriptionFlow for SubscribeNewHeads { - type Request = Request; + type Params = Option; type Notification = Notification; - fn starting_block(req: &Self::Request) -> BlockId { - req.block.unwrap_or(BlockId::Latest) + fn starting_block(params: &Self::Params) -> BlockId { + params + .as_ref() + .and_then(|req| req.block) + .unwrap_or(BlockId::Latest) } async fn catch_up( state: &RpcContext, - _req: &Self::Request, + _params: &Self::Params, from: BlockNumber, to: BlockNumber, ) -> Result>, RpcError> { @@ -84,7 +91,7 @@ impl RpcSubscriptionFlow for SubscribeNewHeads { async fn subscribe( state: RpcContext, - _req: Self::Request, + _params: Self::Params, tx: mpsc::Sender>, ) { let mut headers = state.notifications.block_headers.subscribe(); @@ -152,7 +159,7 @@ mod tests { use tokio::sync::mpsc; use crate::context::{RpcConfig, RpcContext}; - use crate::jsonrpc::{handle_json_rpc_socket, RequestId, RpcError, RpcResponse, RpcRouter}; + use crate::jsonrpc::{handle_json_rpc_socket, RpcResponse, RpcRouter}; use crate::pending::PendingWatcher; use crate::v02::types::syncing::Syncing; use crate::{v08, Notifications, Reorg, SubscriptionId, SyncState}; @@ -308,8 +315,8 @@ mod tests { } #[tokio::test] - async fn invalid_subscription() { - let router = setup(0).await; + async fn subscribe_no_params() { + let router = setup(0); let (sender_tx, mut sender_rx) = mpsc::channel(1024); let (receiver_tx, receiver_rx) = mpsc::channel(1024); handle_json_rpc_socket(router.clone(), sender_tx, receiver_rx); @@ -324,25 +331,40 @@ mod tests { ))) .await .unwrap(); - let res = sender_rx.recv().await.unwrap(); - let res = match res { - Ok(_) => panic!("Expected Err, got Ok"), - Err(e) => e, - }; - assert_eq!( - res, - RpcResponse { - output: Err(RpcError::InvalidParams( - "expected object or array".to_string() - )), - id: RequestId::Number(1), + let res = sender_rx.recv().await.unwrap().unwrap(); + let subscription_id = match res { + Message::Text(json) => { + let json: serde_json::Value = serde_json::from_str(&json).unwrap(); + assert_eq!(json["jsonrpc"], "2.0"); + assert_eq!(json["id"], 1); + json["result"]["subscription_id"].as_u64().unwrap() } - ); + _ => panic!("Expected text message"), + }; + for i in 0..10 { + retry(|| { + router + .context + .notifications + .block_headers + .send(sample_header(i).into()) + }) + .await + .unwrap(); + let expected = sample_new_heads_message(i, subscription_id); + let header = sender_rx.recv().await.unwrap().unwrap(); + let json: serde_json::Value = match header { + Message::Text(json) => serde_json::from_str(&json).unwrap(), + _ => panic!("Expected text message"), + }; + assert_eq!(json, expected); + } + assert!(sender_rx.is_empty()); } #[tokio::test] - async fn subscribe_no_params() { - let router = setup(0).await; + async fn subscribe_empty_params() { + let router = setup(0); let (sender_tx, mut sender_rx) = mpsc::channel(1024); let (receiver_tx, receiver_rx) = mpsc::channel(1024); handle_json_rpc_socket(router.clone(), sender_tx, receiver_rx); @@ -351,8 +373,8 @@ mod tests { serde_json::json!({ "jsonrpc": "2.0", "id": 1, + "params": {}, "method": "starknet_subscribeNewHeads", - "params": {} }) .to_string(), ))) diff --git a/crates/rpc/src/method/subscribe_pending_transactions.rs b/crates/rpc/src/method/subscribe_pending_transactions.rs index b8b35608d7..8276bf3aae 100644 --- a/crates/rpc/src/method/subscribe_pending_transactions.rs +++ b/crates/rpc/src/method/subscribe_pending_transactions.rs @@ -10,23 +10,26 @@ use crate::jsonrpc::{RpcError, RpcSubscriptionFlow, SubscriptionMessage}; pub struct SubscribePendingTransactions; -#[derive(Debug, Clone)] -pub struct Request { +#[derive(Debug, Clone, Default)] +pub struct Params { transaction_details: Option, sender_address: Option>, } -impl crate::dto::DeserializeForVersion for Request { +impl crate::dto::DeserializeForVersion for Option { fn deserialize(value: crate::dto::Value) -> Result { + if value.is_null() { + return Ok(None); + } value.deserialize_map(|value| { - Ok(Self { + Ok(Some(Params { transaction_details: value.deserialize_optional_serde("transaction_details")?, sender_address: value .deserialize_optional_array("sender_address", |addr| { Ok(ContractAddress(addr.deserialize()?)) })? .map(|addrs| addrs.into_iter().collect()), - }) + })) }) } } @@ -57,17 +60,17 @@ const SUBSCRIPTION_NAME: &str = "starknet_subscriptionPendingTransactions"; #[async_trait] impl RpcSubscriptionFlow for SubscribePendingTransactions { - type Request = Request; + type Params = Option; type Notification = Notification; - fn starting_block(_req: &Self::Request) -> BlockId { + fn starting_block(_params: &Self::Params) -> BlockId { // Rollback is not supported. BlockId::Latest } async fn catch_up( _state: &RpcContext, - _req: &Self::Request, + _params: &Self::Params, _from: BlockNumber, _to: BlockNumber, ) -> Result>, RpcError> { @@ -76,9 +79,10 @@ impl RpcSubscriptionFlow for SubscribePendingTransactions { async fn subscribe( state: RpcContext, - req: Self::Request, + params: Self::Params, tx: mpsc::Sender>, ) { + let params = params.unwrap_or_default(); let mut pending_data = state.pending_data.0.clone(); // Last block sent to the subscriber. Initial value doesn't really matter let mut last_block = BlockNumber::GENESIS; @@ -98,7 +102,7 @@ impl RpcSubscriptionFlow for SubscribePendingTransactions { continue; } // Filter the transactions by sender address. - if let Some(sender_address) = &req.sender_address { + if let Some(sender_address) = ¶ms.sender_address { use pathfinder_common::transaction::TransactionVariant::*; let address = match &transaction.variant { DeclareV0(tx) => tx.sender_address, @@ -118,7 +122,7 @@ impl RpcSubscriptionFlow for SubscribePendingTransactions { continue; } } - let notification = match req.transaction_details { + let notification = match params.transaction_details { Some(true) => Notification::Transaction(transaction.clone().into()), Some(false) | None => Notification::TransactionHash(transaction.hash), }; @@ -181,7 +185,6 @@ mod tests { "jsonrpc": "2.0", "id": 1, "method": "starknet_subscribePendingTransactions", - "params": {} }) .to_string(), ))) diff --git a/crates/rpc/src/v08.rs b/crates/rpc/src/v08.rs index 7cb46f5a94..6dfb026ce1 100644 --- a/crates/rpc/src/v08.rs +++ b/crates/rpc/src/v08.rs @@ -5,6 +5,7 @@ use crate::method::subscribe_pending_transactions::SubscribePendingTransactions; #[rustfmt::skip] pub fn register_routes() -> RpcRouterBuilder { RpcRouter::builder(crate::RpcVersion::V08) + .register("starknet_syncing", crate::method::syncing) .register("starknet_subscribeNewHeads", SubscribeNewHeads) .register("starknet_subscribePendingTransactions", SubscribePendingTransactions) .register("starknet_specVersion", || "0.8.0-rc0") From 6e69cdbfb70553ce3af8177b31e8fc89fec0b5a0 Mon Sep 17 00:00:00 2001 From: sistemd Date: Fri, 27 Sep 2024 13:01:25 +0200 Subject: [PATCH 093/282] handle non-subscription methods from websocket --- crates/rpc/src/jsonrpc/router/subscription.rs | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/crates/rpc/src/jsonrpc/router/subscription.rs b/crates/rpc/src/jsonrpc/router/subscription.rs index 14a0b41503..3158fc8f72 100644 --- a/crates/rpc/src/jsonrpc/router/subscription.rs +++ b/crates/rpc/src/jsonrpc/router/subscription.rs @@ -12,6 +12,7 @@ use crate::context::RpcContext; use crate::dto::serialize::SerializeForVersion; use crate::dto::DeserializeForVersion; use crate::error::ApplicationError; +use crate::jsonrpc::router::{handle_json_rpc_body, RpcRequestError, RpcResponses}; use crate::jsonrpc::{RequestId, RpcError, RpcRequest, RpcResponse}; use crate::{RpcVersion, SubscriptionId}; @@ -353,6 +354,9 @@ pub fn handle_json_rpc_socket( } }; + // TODO Might be an array of requests, probably very worth it to factor out + // another function at this point, also needs concurrency, see fn + // run_concurrently in router, probably can just reuse that let rpc_request = match serde_json::from_slice::>(&request) { Ok(request) => request, Err(err) => { @@ -369,6 +373,33 @@ pub fn handle_json_rpc_socket( }; let req_id = rpc_request.id; + // Handle JSON-RPC non-subscription methods. + if state + .method_endpoints + .contains_key(rpc_request.method.as_ref()) + { + let response = match handle_json_rpc_body(&state, &request).await { + Ok(RpcResponses::Empty) => { + // No response. + continue; + } + Ok(RpcResponses::Single(response)) => serde_json::to_string(&response).unwrap(), + Ok(RpcResponses::Multiple(responses)) => { + serde_json::to_string(&responses).unwrap() + } + Err(RpcRequestError::ParseError(e)) => { + serde_json::to_string(&RpcResponse::parse_error(e)).unwrap() + } + Err(RpcRequestError::InvalidRequest(e)) => { + serde_json::to_string(&RpcResponse::invalid_request(e)).unwrap() + } + }; + if ws_tx.send(Ok(Message::Text(response))).await.is_err() { + break; + } + continue; + } + if rpc_request.method == "starknet_unsubscribe" { // End the subscription. let Some(params) = rpc_request.params.0 else { From 8da194dde12832bf0f6072dbb9b75f25b5b2bc72 Mon Sep 17 00:00:00 2001 From: sistemd Date: Fri, 27 Sep 2024 13:01:25 +0200 Subject: [PATCH 094/282] ignore leading whitespace when checking for batch requests --- crates/rpc/src/jsonrpc/router.rs | 2 ++ crates/rpc/src/jsonrpc/router/method.rs | 10 +++++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/crates/rpc/src/jsonrpc/router.rs b/crates/rpc/src/jsonrpc/router.rs index 030b248fab..6f0ee934f3 100644 --- a/crates/rpc/src/jsonrpc/router.rs +++ b/crates/rpc/src/jsonrpc/router.rs @@ -464,6 +464,8 @@ mod tests { .build(RpcContext::for_tests()) } + // TODO Update these to also run on websocket + #[rstest] #[case::with_positional_params( json!({"jsonrpc": "2.0", "method": "subtract", "params": [42, 23], "id": 2}), diff --git a/crates/rpc/src/jsonrpc/router/method.rs b/crates/rpc/src/jsonrpc/router/method.rs index 74eec56272..312462aaa8 100644 --- a/crates/rpc/src/jsonrpc/router/method.rs +++ b/crates/rpc/src/jsonrpc/router/method.rs @@ -40,13 +40,16 @@ pub async fn handle_json_rpc_body( state: &RpcRouter, body: &[u8], ) -> Result { + let body = std::str::from_utf8(body).map_err(|e| RpcRequestError::ParseError(e.to_string()))?; + let body = body.trim_start(); // Unfortunately due to this https://github.com/serde-rs/json/issues/497 // we cannot use an enum with borrowed raw values inside to do a single // deserialization for us. Instead we have to distinguish manually // between a single request and a batch request which we do by checking // the first byte. - if body.first() != Some(&b'[') { - let request = match serde_json::from_slice::<&RawValue>(body) { + if !body.starts_with('[') { + // Not a batch request. + let request = match serde_json::from_str::<&RawValue>(body) { Ok(request) => request, Err(e) => { return Err(RpcRequestError::ParseError(e.to_string())); @@ -58,7 +61,8 @@ pub async fn handle_json_rpc_body( None => Ok(RpcResponses::Empty), } } else { - let requests = match serde_json::from_slice::>(body) { + // Batch request. + let requests = match serde_json::from_str::>(body) { Ok(requests) => requests, Err(e) => { return Err(RpcRequestError::ParseError(e.to_string())); From 0e87adb713f44059d195d299b75494d7d56fa04a Mon Sep 17 00:00:00 2001 From: sistemd Date: Fri, 27 Sep 2024 13:01:25 +0200 Subject: [PATCH 095/282] implement batch requests for ws --- crates/rpc/src/jsonrpc/router.rs | 70 +- crates/rpc/src/jsonrpc/router/subscription.rs | 628 ++++++++++-------- crates/rpc/src/lib.rs | 5 +- crates/rpc/src/method/subscribe_new_heads.rs | 4 +- 4 files changed, 411 insertions(+), 296 deletions(-) diff --git a/crates/rpc/src/jsonrpc/router.rs b/crates/rpc/src/jsonrpc/router.rs index 6f0ee934f3..802c2fbcb6 100644 --- a/crates/rpc/src/jsonrpc/router.rs +++ b/crates/rpc/src/jsonrpc/router.rs @@ -360,6 +360,7 @@ where #[cfg(test)] mod tests { + use futures::SinkExt; use serde::{Deserialize, Serialize}; use serde_json::{json, Value}; @@ -373,7 +374,7 @@ mod tests { tokio::spawn(async { let router = axum::Router::new() - .route("/", axum::routing::post(rpc_handler)) + .route("/", axum::routing::post(rpc_handler).get(rpc_handler)) .with_state(router); axum::serve(listener, router.into_make_service()).await }); @@ -382,10 +383,9 @@ mod tests { } /// Spawns an RPC server with the given router and queries it with the given - /// request. + /// request over HTTP. async fn serve_and_query(router: RpcRouter, request: Value) -> Value { let url = spawn_server(router).await; - let client = reqwest::Client::new(); client .post(url.clone()) @@ -398,6 +398,24 @@ mod tests { .unwrap() } + /// Spawns an RPC server with the given router and queries it with the given + /// request over a WS connection. + async fn serve_and_query_ws(router: RpcRouter, request: Value) -> Value { + let url = spawn_server(router).await.replace("http", "ws"); + let (mut ws, _) = tokio_tungstenite::connect_async(url).await.unwrap(); + ws.send(tokio_tungstenite::tungstenite::Message::Text( + request.to_string(), + )) + .await + .unwrap(); + let tokio_tungstenite::tungstenite::Message::Text(response) = + ws.next().await.unwrap().unwrap() + else { + panic!("Expected a text response"); + }; + serde_json::from_str(&response).unwrap() + } + mod specification_tests { //! Test cases lifted directly from the [RPC specification](https://www.jsonrpc.org/specification). use pretty_assertions_sorted::assert_eq; @@ -464,8 +482,6 @@ mod tests { .build(RpcContext::for_tests()) } - // TODO Update these to also run on websocket - #[rstest] #[case::with_positional_params( json!({"jsonrpc": "2.0", "method": "subtract", "params": [42, 23], "id": 2}), @@ -548,8 +564,9 @@ mod tests { )] #[tokio::test] async fn specification_test(#[case] request: Value, #[case] expected: Value) { - let response = serve_and_query(spec_router(), request).await; - + let response = serve_and_query(spec_router(), request.clone()).await; + assert_eq!(response, expected); + let response = serve_and_query_ws(spec_router(), request).await; assert_eq!(response, expected); } @@ -605,6 +622,23 @@ mod tests { let expected = serde_json::json!({"jsonrpc": "2.0", "error": {"code": -32700, "data": {"reason": reason}, "message": "Parse error"}, "id": null}); assert_eq!(res, expected); + + let url = spawn_server(spec_router()).await; + let (mut ws, _) = tokio_tungstenite::connect_async(url.replace("http", "ws")) + .await + .unwrap(); + ws.send(tokio_tungstenite::tungstenite::Message::Text( + request.to_string(), + )) + .await + .unwrap(); + let tokio_tungstenite::tungstenite::Message::Text(response) = + ws.next().await.unwrap().unwrap() + else { + panic!("Expected a text response"); + }; + let res: serde_json::Value = serde_json::from_str(&response).unwrap(); + assert_eq!(res, expected); } } @@ -635,7 +669,16 @@ mod tests { ), ) .await; + let expected = serde_json::json!({"jsonrpc": "2.0", "error": {"code": -32603, "message": "Internal error"}, "id": 1}); + assert_eq!(response, expected); + let response = serve_and_query_ws( + panic_router(), + json!( + {"jsonrpc": "2.0", "method": "panic", "id": 1} + ), + ) + .await; let expected = serde_json::json!({"jsonrpc": "2.0", "error": {"code": -32603, "message": "Internal error"}, "id": 1}); assert_eq!(response, expected); } @@ -650,7 +693,20 @@ mod tests { ]), ) .await; + let expected = serde_json::json!([ + {"jsonrpc": "2.0", "error": {"code": -32603, "message": "Internal error"}, "id": 1}, + {"jsonrpc": "2.0", "result": "Success", "id": 2}, + ]); + assert_eq!(response, expected); + let response = serve_and_query_ws( + panic_router(), + json!([ + {"jsonrpc": "2.0", "method": "panic", "id": 1}, + {"jsonrpc": "2.0", "method": "success", "id": 2}, + ]), + ) + .await; let expected = serde_json::json!([ {"jsonrpc": "2.0", "error": {"code": -32603, "message": "Internal error"}, "id": 1}, {"jsonrpc": "2.0", "result": "Success", "id": 2}, diff --git a/crates/rpc/src/jsonrpc/router/subscription.rs b/crates/rpc/src/jsonrpc/router/subscription.rs index 3158fc8f72..e2dcc4ed11 100644 --- a/crates/rpc/src/jsonrpc/router/subscription.rs +++ b/crates/rpc/src/jsonrpc/router/subscription.rs @@ -1,18 +1,18 @@ use std::sync::Arc; -use std::time::Duration; use axum::extract::ws::{Message, WebSocket}; use dashmap::DashMap; use futures::{SinkExt, StreamExt}; use pathfinder_common::{BlockId, BlockNumber}; -use tokio::sync::mpsc; +use serde_json::value::RawValue; +use tokio::sync::{mpsc, RwLock}; +use tracing::Instrument; -use super::RpcRouter; +use super::{run_concurrently, RpcRouter}; use crate::context::RpcContext; use crate::dto::serialize::SerializeForVersion; use crate::dto::DeserializeForVersion; use crate::error::ApplicationError; -use crate::jsonrpc::router::{handle_json_rpc_body, RpcRequestError, RpcResponses}; use crate::jsonrpc::{RequestId, RpcError, RpcRequest, RpcResponse}; use crate::{RpcVersion, SubscriptionId}; @@ -20,15 +20,17 @@ use crate::{RpcVersion, SubscriptionId}; #[axum::async_trait] pub(super) trait RpcSubscriptionEndpoint: Send + Sync { // Start the subscription. - async fn invoke( - &self, - router: RpcRouter, - input: serde_json::Value, - subscription_id: SubscriptionId, - subscriptions: Arc>>, - req_id: RequestId, - tx: mpsc::Sender>, - ) -> Result<(), RpcError>; + async fn invoke(&self, params: InvokeParams) -> Result, RpcError>; +} + +pub(super) struct InvokeParams { + router: RpcRouter, + input: serde_json::Value, + subscription_id: SubscriptionId, + subscriptions: Arc>>, + req_id: RequestId, + ws_tx: mpsc::Sender>, + lock: Arc>, } /// This trait is the main entry point for subscription endpoint @@ -106,13 +108,16 @@ where { async fn invoke( &self, - router: RpcRouter, - input: serde_json::Value, - subscription_id: SubscriptionId, - subscriptions: Arc>>, - req_id: RequestId, - ws_tx: mpsc::Sender>, - ) -> Result<(), RpcError> { + InvokeParams { + router, + input, + subscription_id, + subscriptions, + req_id, + ws_tx, + lock, + }: InvokeParams, + ) -> Result, RpcError> { let req = T::Params::deserialize(crate::dto::Value::new(input, router.version)) .map_err(|e| RpcError::InvalidParams(e.to_string()))?; let tx = SubscriptionSender { @@ -125,147 +130,133 @@ where let first_block = T::starting_block(&req); - let current_block = match &first_block { + let mut current_block = match &first_block { BlockId::Pending => { return Err(RpcError::InvalidParams( - "Pending block is not supported for new heads subscription".to_string(), + "Pending block not supported".to_string(), )); } BlockId::Latest => { // No need to catch up. The code below will subscribe to new blocks. - // Only needs to send the subscription ID to the client. - if ws_tx - .send(Ok(Message::Text( - serde_json::to_string(&RpcResponse { - output: Ok(serde_json::to_value(&SubscriptionIdResult { - subscription_id, - }) - .unwrap()), - id: req_id.clone(), - }) - .unwrap(), - ))) - .await - .is_err() - { - return Ok(()); - } BlockNumber::MAX } BlockId::Number(_) | BlockId::Hash(_) => { - // Catch up to the latest block in batches of BATCH_SIZE. - // Load the first block number, return an error if it's invalid. - let first_block = pathfinder_storage::BlockId::try_from(T::starting_block(&req)) + let first_block = pathfinder_storage::BlockId::try_from(first_block) .map_err(|e| RpcError::InvalidParams(e.to_string()))?; let storage = router.context.storage.clone(); - let mut current_block = - tokio::task::spawn_blocking(move || -> Result<_, RpcError> { - let mut conn = storage.connection().map_err(RpcError::InternalError)?; - let db = conn.transaction().map_err(RpcError::InternalError)?; - db.block_number(first_block) - .map_err(RpcError::InternalError)? - .ok_or_else(|| ApplicationError::BlockNotFound.into()) - }) - .await - .map_err(|e| RpcError::InternalError(e.into()))??; + tokio::task::spawn_blocking(move || -> Result<_, RpcError> { + let mut conn = storage.connection().map_err(RpcError::InternalError)?; + let db = conn.transaction().map_err(RpcError::InternalError)?; + db.block_number(first_block) + .map_err(RpcError::InternalError)? + .ok_or_else(|| ApplicationError::BlockNotFound.into()) + }) + .await + .map_err(|e| RpcError::InternalError(e.into()))?? + } + }; - // Send the subscription ID to the client. - if ws_tx - .send(Ok(Message::Text( - serde_json::to_string(&RpcResponse { - output: Ok(serde_json::to_value(&SubscriptionIdResult { - subscription_id, - }) - .unwrap()), - id: req_id.clone(), - }) - .unwrap(), - ))) - .await - .is_err() + Ok(tokio::spawn(async move { + // This lock ensures that the streaming of subscriptions doesn't start before + // the caller sends the success response for the subscription request. + let _guard = lock.read().await; + + // Catch up to the latest block in batches of BATCH_SIZE. + const BATCH_SIZE: u64 = 64; + loop { + let messages = match T::catch_up( + &router.context, + &req, + current_block, + current_block + BATCH_SIZE, + ) + .await { - return Ok(()); - } - - const BATCH_SIZE: u64 = 64; - loop { - let messages = T::catch_up( - &router.context, - &req, - current_block, - current_block + BATCH_SIZE, - ) - .await?; - if messages.is_empty() { - // Caught up. - break; - } - for msg in messages { - if tx - .send(msg.notification, msg.subscription_name) + Ok(messages) => messages, + Err(e) => { + tx.send_err(e, req_id.clone()) .await - .is_err() - { - // Subscription closing. - return Ok(()); - } - current_block = msg.block_number; + // Could error if the subscription is closing. + .ok(); + return; } - // Increment the current block by 1 because the catch_up range is inclusive. - current_block += 1; + }; + if messages.is_empty() { + // Caught up. + break; } - current_block + for msg in messages { + if tx + .send(msg.notification, msg.subscription_name) + .await + .is_err() + { + // Subscription closing. + return; + } + current_block = msg.block_number; + } + // Increment the current block by 1 because the catch_up range is inclusive. + current_block += 1; } - }; - // Subscribe to new blocks. Receive the first subscription message. - let (tx1, mut rx1) = mpsc::channel::>(1024); - { - let req = req.clone(); - tokio::spawn(T::subscribe(router.context.clone(), req, tx1)); - } - let first_msg = match rx1.recv().await { - Some(msg) => msg, - None => { - // Subscription closing. - return Ok(()); + // Subscribe to new blocks. Receive the first subscription message. + let (tx1, mut rx1) = mpsc::channel::>(1024); + { + let req = req.clone(); + tokio::spawn(T::subscribe(router.context.clone(), req, tx1)); } - }; - - // Catch up from the latest block that we already caught up to, to the first - // block that will be streamed from the subscription. This way we don't miss any - // blocks. Because the catch_up range is inclusive, we need to subtract 1 from - // the block number. - if let Some(block_number) = first_msg.block_number.parent() { - let messages = T::catch_up(&router.context, &req, current_block, block_number).await?; - for msg in messages { - if tx - .send(msg.notification, msg.subscription_name) - .await - .is_err() - { + let first_msg = match rx1.recv().await { + Some(msg) => msg, + None => { // Subscription closing. - return Ok(()); + return; + } + }; + + // Catch up from the latest block that we already caught up to, to the first + // block that will be streamed from the subscription. This way we don't miss any + // blocks. Because the catch_up range is inclusive, we need to subtract 1 from + // the block number. + if let Some(block_number) = first_msg.block_number.parent() { + let messages = + match T::catch_up(&router.context, &req, current_block, block_number).await { + Ok(messages) => messages, + Err(e) => { + tx.send_err(e, req_id.clone()) + .await + // Could error if the subscription is closing. + .ok(); + return; + } + }; + for msg in messages { + if tx + .send(msg.notification, msg.subscription_name) + .await + .is_err() + { + // Subscription closing. + return; + } } } - } - // Send the first subscription message and then forward the rest. - if tx - .send(first_msg.notification, first_msg.subscription_name) - .await - .is_err() - { - // Subscription closing. - return Ok(()); - } - let mut last_block = first_msg.block_number; - tokio::spawn(async move { + // Send the first subscription message and then forward the rest. + if tx + .send(first_msg.notification, first_msg.subscription_name) + .await + .is_err() + { + // Subscription closing. + return; + } + let mut last_block = first_msg.block_number; while let Some(msg) = rx1.recv().await { if msg.block_number.get() > last_block.get() + 1 { - // One or more blocks have been skipped. This is likely due to a race condition - // resulting from a reorg. This message should be ignored. + // One or more blocks have been skipped. This is likely due to a race + // condition resulting from a reorg. This message should be ignored. continue; } if tx @@ -278,8 +269,7 @@ where } last_block = msg.block_number; } - }); - Ok(()) + })) } } @@ -338,8 +328,21 @@ pub fn handle_json_rpc_socket( tokio::spawn(async move { loop { let request = match ws_rx.recv().await { - Some(Ok(Message::Text(msg))) => msg.into_bytes(), - Some(Ok(Message::Binary(bytes))) => bytes, + Some(Ok(Message::Text(msg))) => msg, + Some(Ok(Message::Binary(bytes))) => match String::from_utf8(bytes) { + Ok(msg) => msg, + Err(e) => { + if ws_tx + .send(Err(RpcResponse::parse_error(e.to_string()))) + .await + .is_err() + { + // Connection is closing. + break; + } + continue; + } + }, Some(Ok(Message::Pong(_) | Message::Ping(_))) => { // Ping and pong messages are handled automatically by axum. continue; @@ -354,184 +357,225 @@ pub fn handle_json_rpc_socket( } }; - // TODO Might be an array of requests, probably very worth it to factor out - // another function at this point, also needs concurrency, see fn - // run_concurrently in router, probably can just reuse that - let rpc_request = match serde_json::from_slice::>(&request) { - Ok(request) => request, - Err(err) => { - if ws_tx - .send(Err(RpcResponse::parse_error(err.to_string()))) - .await - .is_err() - { - // Connection is closing. - break; - } - continue; - } - }; - let req_id = rpc_request.id; - - // Handle JSON-RPC non-subscription methods. - if state - .method_endpoints - .contains_key(rpc_request.method.as_ref()) - { - let response = match handle_json_rpc_body(&state, &request).await { - Ok(RpcResponses::Empty) => { - // No response. + // This lock ensures that the streaming of subscriptions doesn't start before we + // send the success response for the subscription request. Once this write guard + // is dropped, all of the read guards can proceed. + let lock = Arc::new(RwLock::new(())); + let _guard = lock.write().await; + + // Unfortunately due to this https://github.com/serde-rs/json/issues/497 + // we cannot use an enum with borrowed raw values inside to do a single + // deserialization for us. Instead we have to distinguish manually + // between a single request and a batch request which we do by checking + // the first byte. + let request = request.trim_start(); + if !request.starts_with('[') { + let raw_value: &RawValue = match serde_json::from_str(request) { + Ok(raw_value) => raw_value, + Err(e) => { + if ws_tx + .send(Err(RpcResponse::parse_error(e.to_string()))) + .await + .is_err() + { + // Connection is closing. + break; + } continue; } - Ok(RpcResponses::Single(response)) => serde_json::to_string(&response).unwrap(), - Ok(RpcResponses::Multiple(responses)) => { - serde_json::to_string(&responses).unwrap() - } - Err(RpcRequestError::ParseError(e)) => { - serde_json::to_string(&RpcResponse::parse_error(e)).unwrap() + }; + match handle_request( + &state, + raw_value, + subscriptions.clone(), + ws_tx.clone(), + lock.clone(), + ) + .await + { + Ok(Some(response)) | Err(response) => { + if ws_tx + .send(Ok(Message::Text(serde_json::to_string(&response).unwrap()))) + .await + .is_err() + { + // Connection is closing. + break; + } } - Err(RpcRequestError::InvalidRequest(e)) => { - serde_json::to_string(&RpcResponse::invalid_request(e)).unwrap() + Ok(None) => { + // No response. + continue; } - }; - if ws_tx.send(Ok(Message::Text(response))).await.is_err() { - break; } - continue; - } - - if rpc_request.method == "starknet_unsubscribe" { - // End the subscription. - let Some(params) = rpc_request.params.0 else { - if ws_tx - .send(Err(RpcResponse::invalid_params( - req_id, - "Missing params for starknet_unsubscribe".to_string(), - ))) - .await - .is_err() - { - break; - } - continue; - }; - let params = match serde_json::from_str::(params.get()) { - Ok(params) => params, - Err(err) => { + } else { + // Batch request. + let requests = match serde_json::from_str::>(request) { + Ok(requests) => requests, + Err(e) => { if ws_tx - .send(Err(RpcResponse::invalid_params(req_id, err.to_string()))) + .send(Err(RpcResponse::parse_error(e.to_string()))) .await .is_err() { + // Connection is closing. break; } continue; } }; - let Some((_, handle)) = subscriptions.remove(¶ms.subscription_id) else { + + if requests.is_empty() { + // According to the JSON-RPC spec, a batch request cannot be empty. if ws_tx - .send(Err(RpcResponse::invalid_params( - req_id, - "Subscription not found".to_string(), + .send(Err(RpcResponse::invalid_request( + "A batch request must contain at least one request".to_owned(), ))) .await .is_err() { + // Connection is closing. break; } + } + + let responses = run_concurrently( + state.context.config.batch_concurrency_limit, + requests.into_iter().enumerate(), + { + |(idx, request)| { + let state = &state; + let ws_tx = ws_tx.clone(); + let subscriptions = subscriptions.clone(); + let lock = lock.clone(); + async move { + match handle_request(state, request, subscriptions, ws_tx, lock) + .instrument(tracing::debug_span!("ws batch", idx)) + .await + { + Ok(Some(response)) | Err(response) => Some(response), + Ok(None) => None, + } + } + } + }, + ) + .await + .flatten() + .collect::>(); + + // All requests were notifications, no response needed. + if responses.is_empty() { continue; - }; - handle.abort(); + } + if ws_tx .send(Ok(Message::Text( - serde_json::to_string(&RpcResponse { - output: Ok(true.into()), - id: req_id.clone(), - }) - .unwrap(), + serde_json::to_string(&responses).unwrap(), ))) .await .is_err() { + // Connection is closing. break; } - metrics::increment_counter!("rpc_method_calls_total", "method" => "starknet_unsubscribe", "version" => state.version.to_str()); - continue; } + } + }); +} - // Also grab the method_name as it is a static str, which is required by the - // metrics. - let Some((&method_name, endpoint)) = state - .subscription_endpoints - .get_key_value(rpc_request.method.as_ref()) - else { - ws_tx - .send(Ok(Message::Text( - serde_json::to_string(&RpcResponse::method_not_found(req_id)).unwrap(), - ))) - .await - .ok(); - continue; - }; - metrics::increment_counter!("rpc_method_calls_total", "method" => method_name, "version" => state.version.to_str()); +/// Handle a single request. Returns `Result` for convenience, so that the `?` +/// operator could be used in the body of the function. Returns `Ok(None)` if +/// the request was a notification (i.e. no response is needed). +async fn handle_request( + state: &RpcRouter, + raw_request: &RawValue, + subscriptions: Arc>>, + ws_tx: mpsc::Sender>, + lock: Arc>, +) -> Result, RpcResponse> { + let rpc_request = serde_json::from_str::>(raw_request.get()) + .map_err(|e| RpcResponse::invalid_request(e.to_string()))?; + let req_id = rpc_request.id; + + // Ignore notification requests. + if req_id.is_notification() { + return Ok(None); + } - let params = match serde_json::to_value(rpc_request.params) { - Ok(params) => params, - Err(e) => { - if ws_tx - .send(Ok(Message::Text( - serde_json::to_string(&RpcResponse::invalid_params( - req_id, - e.to_string(), - )) - .unwrap(), - ))) - .await - .is_err() - { - break; - } - continue; - } - }; + // Handle JSON-RPC non-subscription methods. + if state + .method_endpoints + .contains_key(rpc_request.method.as_ref()) + { + return Ok(state.run_request(raw_request.get()).await); + } - // Start the subscription. - let state = state.clone(); - let subscription_id = SubscriptionId::next(); - let ws_tx = ws_tx.clone(); - let handle = tokio::spawn({ - let subscriptions = subscriptions.clone(); - async move { - if let Err(e) = endpoint - .invoke( - state, - params, - subscription_id, - subscriptions.clone(), - req_id.clone(), - ws_tx.clone(), - ) - .await - { - ws_tx - .send(Err(RpcResponse { - output: Err(e), - id: req_id, - })) - .await - .ok(); - while subscriptions.remove(&subscription_id).is_none() { - // Race condition, the insert has not yet happened. - tokio::time::sleep(Duration::from_secs(1)).await; - } - } - } - }); + // Handle starknet_unsubscribe. + if rpc_request.method == "starknet_unsubscribe" { + // End the subscription. + let params = rpc_request.params.0.ok_or_else(|| { + RpcResponse::invalid_params( + req_id.clone(), + "Missing params for starknet_unsubscribe".to_string(), + ) + })?; + let params = serde_json::from_str::(params.get()) + .map_err(|e| RpcResponse::invalid_params(req_id.clone(), e.to_string()))?; + let (_, handle) = subscriptions + .remove(¶ms.subscription_id) + .ok_or_else(|| { + RpcResponse::invalid_params(req_id.clone(), "Subscription not found".to_string()) + })?; + handle.abort(); + metrics::increment_counter!("rpc_method_calls_total", "method" => "starknet_unsubscribe", "version" => state.version.to_str()); + return Ok(Some(RpcResponse { + output: Ok(true.into()), + id: req_id, + })); + } + + let (&method_name, endpoint) = state + .subscription_endpoints + .get_key_value(rpc_request.method.as_ref()) + .ok_or_else(|| RpcResponse::method_not_found(req_id.clone()))?; + metrics::increment_counter!("rpc_method_calls_total", "method" => method_name, "version" => state.version.to_str()); + + let params = serde_json::to_value(rpc_request.params) + .map_err(|e| RpcResponse::invalid_params(req_id.clone(), e.to_string()))?; + + // Start the subscription. + let state = state.clone(); + let subscription_id = SubscriptionId::next(); + let ws_tx = ws_tx.clone(); + match endpoint + .invoke(InvokeParams { + router: state, + input: params, + subscription_id, + subscriptions: subscriptions.clone(), + req_id: req_id.clone(), + ws_tx: ws_tx.clone(), + lock, + }) + .await + { + Ok(handle) => { if subscriptions.insert(subscription_id, handle).is_some() { panic!("subscription id overflow"); } + Ok(Some(RpcResponse { + output: Ok( + serde_json::to_value(&SubscriptionIdResult { subscription_id }).unwrap(), + ), + id: req_id, + })) } - }); + Err(e) => Err(RpcResponse { + output: Err(e), + id: req_id, + }), + } } #[derive(Debug, serde::Deserialize)] @@ -546,12 +590,12 @@ struct SubscriptionIdResult { } #[derive(Debug)] -struct SubscriptionSender { - subscription_id: SubscriptionId, - subscriptions: Arc>>, - tx: mpsc::Sender>, - version: RpcVersion, - _phantom: std::marker::PhantomData, +pub struct SubscriptionSender { + pub subscription_id: SubscriptionId, + pub subscriptions: Arc>>, + pub tx: mpsc::Sender>, + pub version: RpcVersion, + pub _phantom: std::marker::PhantomData, } impl Clone for SubscriptionSender { @@ -592,6 +636,20 @@ impl SubscriptionSender { .await .map_err(|_| mpsc::error::SendError(())) } + + pub async fn send_err( + &self, + err: RpcError, + req_id: RequestId, + ) -> Result<(), mpsc::error::SendError<()>> { + self.tx + .send(Err(RpcResponse { + output: Err(err), + id: req_id, + })) + .await + .map_err(|_| mpsc::error::SendError(())) + } } #[derive(Debug)] diff --git a/crates/rpc/src/lib.rs b/crates/rpc/src/lib.rs index afd07524af..28a85a0e73 100644 --- a/crates/rpc/src/lib.rs +++ b/crates/rpc/src/lib.rs @@ -188,8 +188,9 @@ impl RpcServer { .with_state(v06_routes.clone()) .route("/rpc/v0_7", post(rpc_handler)) .with_state(v07_routes.clone()) - .route("/rpc/v0_8", post(rpc_handler).get(rpc_handler)) - .with_state(v08_routes.clone()) + // TODO Uncomment once RPC 0.8 is ready. + //.route("/rpc/v0_8", post(rpc_handler).get(rpc_handler)) + //.with_state(v08_routes.clone()) .route("/rpc/pathfinder/v0.1", post(rpc_handler)) .route("/rpc/pathfinder/v0_1", post(rpc_handler)) .with_state(pathfinder_routes.clone()); diff --git a/crates/rpc/src/method/subscribe_new_heads.rs b/crates/rpc/src/method/subscribe_new_heads.rs index 1dae8d55d6..61f4c65705 100644 --- a/crates/rpc/src/method/subscribe_new_heads.rs +++ b/crates/rpc/src/method/subscribe_new_heads.rs @@ -316,7 +316,7 @@ mod tests { #[tokio::test] async fn subscribe_no_params() { - let router = setup(0); + let router = setup(0).await; let (sender_tx, mut sender_rx) = mpsc::channel(1024); let (receiver_tx, receiver_rx) = mpsc::channel(1024); handle_json_rpc_socket(router.clone(), sender_tx, receiver_rx); @@ -364,7 +364,7 @@ mod tests { #[tokio::test] async fn subscribe_empty_params() { - let router = setup(0); + let router = setup(0).await; let (sender_tx, mut sender_rx) = mpsc::channel(1024); let (receiver_tx, receiver_rx) = mpsc::channel(1024); handle_json_rpc_socket(router.clone(), sender_tx, receiver_rx); From 244afb37a0f0eda221df53033b58d840d553ec48 Mon Sep 17 00:00:00 2001 From: sistemd Date: Fri, 27 Sep 2024 13:01:25 +0200 Subject: [PATCH 096/282] comment --- crates/rpc/src/v08.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/rpc/src/v08.rs b/crates/rpc/src/v08.rs index 6dfb026ce1..9d5d8e5fad 100644 --- a/crates/rpc/src/v08.rs +++ b/crates/rpc/src/v08.rs @@ -5,6 +5,8 @@ use crate::method::subscribe_pending_transactions::SubscribePendingTransactions; #[rustfmt::skip] pub fn register_routes() -> RpcRouterBuilder { RpcRouter::builder(crate::RpcVersion::V08) + // TODO This is for testing - figure out if this should be removed or included when + // implementing RPC spec 0.8. .register("starknet_syncing", crate::method::syncing) .register("starknet_subscribeNewHeads", SubscribeNewHeads) .register("starknet_subscribePendingTransactions", SubscribePendingTransactions) From 1c3f2069e084ae165b754bce6590f6ed1f8b12ac Mon Sep 17 00:00:00 2001 From: sistemd Date: Fri, 27 Sep 2024 13:01:25 +0200 Subject: [PATCH 097/282] remove unnecessary comment --- crates/rpc/src/v08.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/rpc/src/v08.rs b/crates/rpc/src/v08.rs index 9d5d8e5fad..6dfb026ce1 100644 --- a/crates/rpc/src/v08.rs +++ b/crates/rpc/src/v08.rs @@ -5,8 +5,6 @@ use crate::method::subscribe_pending_transactions::SubscribePendingTransactions; #[rustfmt::skip] pub fn register_routes() -> RpcRouterBuilder { RpcRouter::builder(crate::RpcVersion::V08) - // TODO This is for testing - figure out if this should be removed or included when - // implementing RPC spec 0.8. .register("starknet_syncing", crate::method::syncing) .register("starknet_subscribeNewHeads", SubscribeNewHeads) .register("starknet_subscribePendingTransactions", SubscribePendingTransactions) From d1a604a463030c1ba1457fc4d553eb7a15322b0c Mon Sep 17 00:00:00 2001 From: Krisztian Kovacs Date: Mon, 30 Sep 2024 09:32:22 +0200 Subject: [PATCH 098/282] chore(README): update DB snapshot links This updates the links with recent snapshots and removes old (pre-0.14.0) snapshot versions. --- README.md | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index e1b7945592..7bee658dad 100644 --- a/README.md +++ b/README.md @@ -168,7 +168,7 @@ rclone copy -P pathfinder-snapshots:pathfinder-snapshots/sepolia-testnet_0.11.0_ We're storing database snapshots as SQLite database files compressed with [zstd](https://github.com/facebook/zstd). You can uncompress the files you've downloaded using the following command: ```shell -zstd -T0 -d sepolia-testnet_0.13.0_74494_pruned.sqlite.zst -o testnet-sepolia.sqlite +zstd -T0 -d sepolia-testnet_0.14.0_209745_pruned.sqlite.zst -o testnet-sepolia.sqlite ``` This produces uncompressed database file `testnet-sepolia.sqlite` that can then be used by pathfinder. @@ -177,13 +177,10 @@ This produces uncompressed database file `testnet-sepolia.sqlite` that can then | Network | Block | Pathfinder version required | Mode | Filename | Download URL | Compressed size | SHA2-256 checksum of compressed file | | --------------- | ------ | --------------------------- | ------- | -------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- | --------------- | ------------------------------------------------------------------ | -| Sepolia testnet | 74494 | >= 0.13.0 | archive | `sepolia-testnet_0.13.0_74494_archive.sqlite.zst` | [Download](https://pub-1fac64c3c0334cda85b45bcc02635c32.r2.dev/sepolia-testnet_0.13.0_74494_archive.sqlite.zst) | 5.83 GB | `03faf036e24e31cad0d5bd6c32fdeb2d52329c86cb90eb1b3b1fef91173ca63c` | -| Sepolia testnet | 74494 | >= 0.13.0 | pruned | `sepolia-testnet_0.13.0_74494_pruned.sqlite.zst` | [Download](https://pub-1fac64c3c0334cda85b45bcc02635c32.r2.dev/sepolia-testnet_0.13.0_74494_pruned.sqlite.zst) | 2.22 GB | `0a74d47739939b43090f44f852ef14c48a8d4b303b03186d2e5ef74b1093b3f7` | -| Sepolia testnet | 121145 | >= 0.14.0 | archive | `sepolia-testnet_0.14.0_121145_archive.sqlite.zst` | [Download](https://pub-1fac64c3c0334cda85b45bcc02635c32.r2.dev/sepolia-testnet_0.14.0_121145_archive.sqlite.zst) | 11.98 GB | `6ff4e1a3d6e91edaceac35c8b17c42d3f3d0a93bd29954bb0fafa250c6e74071` | -| Sepolia testnet | 121057 | >= 0.14.0 | pruned | `sepolia-testnet_0.14.0_121057_pruned.sqlite.zst` | [Download](https://pub-1fac64c3c0334cda85b45bcc02635c32.r2.dev/sepolia-testnet_0.14.0_121057_pruned.sqlite.zst) | 4.05 GB | `f38c486a76699250c21a3fd7e7bcb695a7bc094387903b4137094a68d9371065` | -| Mainnet | 649680 | >= 0.13.0 | archive | `mainnet_0.13.0_649680_archive.sqlite.zst` | [Download](https://pub-1fac64c3c0334cda85b45bcc02635c32.r2.dev/mainnet_0.13.0_649680_archive.sqlite.zst) | 400.08 GB | `d01766380fff47f3c08199d0e5c7039ea6fc412081bac026ca99d24f52e6a923` | -| Mainnet | 649680 | >= 0.13.0 | pruned | `mainnet_0.13.0_649680_pruned.sqlite.zst` | [Download](https://pub-1fac64c3c0334cda85b45bcc02635c32.r2.dev/mainnet_0.13.0_649680_pruned.sqlite.zst) | 64.85 GB | `fd68a09672abcc37068ecf892ecd028e08456744a6e636027878f65bd801b991` | -| Mainnet | 670000 | >= 0.14.0 | pruned | `mainnet_0.14.0_670000_pruned.sqlite.zst` | [Download](https://pub-1fac64c3c0334cda85b45bcc02635c32.r2.dev/mainnet_0.14.0_670000_pruned.sqlite.zst) | 68.58 GB | `b859ae1a7c6a996b3cf1666f7a13bec2fc0450829cdda5d3198e1ef2af4bc8e1` | +| Mainnet | 751397 | >= 0.14.0 | pruned | `mainnet_0.14.0_751397_pruned.sqlite.zst` | [Download](https://pub-1fac64c3c0334cda85b45bcc02635c32.r2.dev/mainnet_0.14.0_751397_pruned.sqlite.zst) | 71.03 GB | `2f9aa8b98086c12a1ce14e89ddfe02ebf320a7ba47e63829056a405866568113` | +| Mainnet | 751250 | >= 0.14.0 | archive | `mainnet_0.14.0_751250_archive.sqlite.zst` | [Download](https://pub-1fac64c3c0334cda85b45bcc02635c32.r2.dev/mainnet_0.14.0_751250_archive.sqlite.zst) | 433.13 GB | `3540087b326b58437fd12bcf427eaeb6323f3efc3def56816b7e5fc06d2633ae` | +| Sepolia testnet | 209745 | >= 0.14.0 | pruned | `sepolia-testnet_0.14.0_209745_pruned.sqlite.zst` | [Download](https://pub-1fac64c3c0334cda85b45bcc02635c32.r2.dev/sepolia-testnet_0.14.0_209745_pruned.sqlite.zst) | 5.71 GB | `5cc9a13079a36ee09c04824f6b30b5ce16cd2d26039b23c7c7937f57a76ba19b` | +| Sepolia testnet | 209758 | >= 0.14.0 | archive | `sepolia-testnet_0.14.0_209758_archive.sqlite.zst` | [Download](https://pub-1fac64c3c0334cda85b45bcc02635c32.r2.dev/sepolia-testnet_0.14.0_209758_archive.sqlite.zst) | 18.93 GB | `3c24a6e9e5294d738f5976e2c949ebac42ed3fc4865a21893df44897fe803686` | ## Configuration From 94f30c0568b2b28024d167f99c7954390e258c65 Mon Sep 17 00:00:00 2001 From: Krisztian Kovacs Date: Mon, 30 Sep 2024 11:09:41 +0200 Subject: [PATCH 099/282] chore(README): remove Pathfinder 0.9 DB incompatibility note --- README.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/README.md b/README.md index 7bee658dad..338f53844a 100644 --- a/README.md +++ b/README.md @@ -25,12 +25,6 @@ This includes any documentation issues, feature requests and bugs that you may e For help or to submit bug reports or feature requests, please open an issue or alternatively visit the Starknet [discord channel](https://discord.com/invite/QypNMzkHbc). -## Database compatibility - -**pathfinder 0.9.0 contains a change in our Merkle tree storage implementation that makes it impossible to use database files produced by earlier versions.** - -This means that upgrading to 0.9.0 will require either a full re-sync _or_ downloading a [database snapshot](#database-snapshots). - ## Running with Docker The `pathfinder` node can be run in the provided Docker image. From 2b1789d7023b004674530cad420ac44778108720 Mon Sep 17 00:00:00 2001 From: Krisztian Kovacs Date: Mon, 30 Sep 2024 11:11:45 +0200 Subject: [PATCH 100/282] chore(README): remove JSON-RPC versions 0.4 and 0.5 --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index 338f53844a..ca01e28de5 100644 --- a/README.md +++ b/README.md @@ -267,11 +267,9 @@ This can be used to interact with a custom Starknet gateway, or to use a gateway You can interact with Starknet using the JSON-RPC API. Pathfinder supports the official Starknet RPC API and in addition supplements this with its own pathfinder specific extensions such as `pathfinder_getProof`. -Currently, pathfinder supports `v0.4`, `v0.5`, `v0.6` and `v0.7` versions of the Starknet JSON-RPC specification. +Currently, pathfinder supports `v0.6` and `v0.7` versions of the Starknet JSON-RPC specification. The `path` of the URL used to access the JSON-RPC server determines which version of the API is served: -- the `v0.4.0` API is exposed on the `/rpc/v0.4` and `/rpc/v0_4` path -- the `v0.5.1` API is exposed on the `/`, `/rpc/v0.5` and `/rpc/v0_5` path - the `v0.6.0` API is exposed on the `/rpc/v0_6` path via HTTP and on `/ws/rpc/v0_6` via Websocket - the `v0.7.0` API is exposed on the `/rpc/v0_7` path via HTTP and on `/ws/rpc/v0_7` via Websocket - the pathfinder extension API is exposed on `/rpc/pathfinder/v0.1` and `/rpc/pathfinder/v0_1` via HTTP and `/ws/rpc/pathfinder/v0_1` via Websocket. From e50cbe2830d5c919861fe16f3635554bd2381932 Mon Sep 17 00:00:00 2001 From: Krisztian Kovacs Date: Mon, 30 Sep 2024 11:12:11 +0200 Subject: [PATCH 101/282] chore(README): avoid using very old version numbers --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ca01e28de5..3f8aa29e1c 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,7 @@ sudo docker stop pathfinder When pathfinder detects there has been a new release, it will log a message similar to: ``` -WARN New pathfinder release available! Please consider updating your node! release=0.4.5 +WARN New pathfinder release available! Please consider updating your node! release=0.14.3 ``` You can try pulling the latest docker image to update it: From 3a4fda2cda2dfabac1c96b80408c0790a03ca6c1 Mon Sep 17 00:00:00 2001 From: Krisztian Kovacs Date: Mon, 30 Sep 2024 11:13:02 +0200 Subject: [PATCH 102/282] chore(README): consistently use latest Sepolia snapshot name in example --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3f8aa29e1c..056595e67c 100644 --- a/README.md +++ b/README.md @@ -152,7 +152,7 @@ acl = private You can then download a compressed database using the command: ```shell -rclone copy -P pathfinder-snapshots:pathfinder-snapshots/sepolia-testnet_0.11.0_47191.sqlite.zst . +rclone copy -P pathfinder-snapshots:pathfinder-snapshots/sepolia-testnet_0.14.0_209745_pruned.sqlite.zst . ``` ### Uncompressing database snapshots From 687933033f9cb006c5395d453b7afa27fe6b638d Mon Sep 17 00:00:00 2001 From: Krisztian Kovacs Date: Mon, 30 Sep 2024 11:20:56 +0200 Subject: [PATCH 103/282] chore(README): add HW requirements section --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 056595e67c..45c983589b 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,14 @@ This includes any documentation issues, feature requests and bugs that you may e For help or to submit bug reports or feature requests, please open an issue or alternatively visit the Starknet [discord channel](https://discord.com/invite/QypNMzkHbc). +## Hardware Requirements + +Pathfinder hardware requirements depend greatly on your use case. The _recommended_ configuration that allows for fast syncing and JSON-RPC queries (with limited concurrency) is: + +- 4 CPU cores +- 8 GiB RAM +- 250 GiB SSD storage + ## Running with Docker The `pathfinder` node can be run in the provided Docker image. From 21e1ef204f06b7a826c26577772953fa174e7d8d Mon Sep 17 00:00:00 2001 From: Krzysztof Lis Date: Mon, 30 Sep 2024 14:11:55 +0200 Subject: [PATCH 104/282] feat(p2p): do not add dialed peer to the DHT explicitly This happens automatically when connection is established. --- crates/p2p/src/main_loop.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/crates/p2p/src/main_loop.rs b/crates/p2p/src/main_loop.rs index 93d7fc6835..d9ea739354 100644 --- a/crates/p2p/src/main_loop.rs +++ b/crates/p2p/src/main_loop.rs @@ -752,10 +752,6 @@ impl MainLoop { if let std::collections::hash_map::Entry::Vacant(e) = self.pending_dials.entry(peer_id) { - self.swarm - .behaviour_mut() - .kademlia_mut() - .add_address(&peer_id, addr.clone()); match self.swarm.dial( // Dial a known peer with a given address only if it's not connected yet // and we haven't started dialing yet. From 8a09f4421d1180383e7b24deb49583824fa4af8b Mon Sep 17 00:00:00 2001 From: Krisztian Kovacs Date: Tue, 1 Oct 2024 11:56:51 +0200 Subject: [PATCH 105/282] chore: remove support for pre-0.13.2 block signatures Now that all networks have been upgraded to 0.13.2 we can remove support for pre-0.13.2 block signatures (that used a different algorithm for state diff commitment). Old signatures were computed over the combined hash of the block hash and the state diff commitment, while the new hashes are always computed over the block hash only. --- crates/common/src/signature.rs | 74 +------ crates/gateway-client/src/lib.rs | 2 +- crates/gateway-types/src/reply.rs | 113 +--------- crates/pathfinder/examples/feeder_gateway.rs | 10 +- crates/pathfinder/src/state/sync/l2.rs | 211 ++++++------------- crates/pathfinder/src/sync/headers.rs | 6 +- 6 files changed, 87 insertions(+), 329 deletions(-) diff --git a/crates/common/src/signature.rs b/crates/common/src/signature.rs index f60c07b1e2..ad600d54aa 100644 --- a/crates/common/src/signature.rs +++ b/crates/common/src/signature.rs @@ -1,9 +1,6 @@ use fake::Dummy; -use pathfinder_crypto::hash::poseidon_hash_many; -use pathfinder_crypto::signature::{ecdsa_verify_partial, SignatureError}; -use pathfinder_crypto::Felt; -use crate::{BlockCommitmentSignatureElem, BlockHash, PublicKey, StateDiffCommitment}; +use crate::{BlockCommitmentSignatureElem, BlockHash, PublicKey}; #[derive(Default, Debug, Clone, PartialEq, Eq, Dummy)] pub struct BlockCommitmentSignature { @@ -12,69 +9,24 @@ pub struct BlockCommitmentSignature { } impl BlockCommitmentSignature { - // TODO remove fallback to pre-0.13.2 verification method after 0.13.2 is rolled - // out on mainnet. pub fn verify( &self, public_key: PublicKey, block_hash: BlockHash, - state_diff_commitment: StateDiffCommitment, - ) -> Result<(), SignatureError> { - self.verify_0_13_2(public_key, block_hash) - .or_else(|_| self.verify_pre_0_13_2(public_key, block_hash, state_diff_commitment)) - } - - fn verify_pre_0_13_2( - &self, - public_key: PublicKey, - block_hash: BlockHash, - state_diff_commitment: StateDiffCommitment, - ) -> Result<(), SignatureError> { - let msg = Felt::from(poseidon_hash_many(&[ - block_hash.0.into(), - state_diff_commitment.0.into(), - ])); - ecdsa_verify_partial(public_key.0, msg, self.r.0, self.s.0) - } - - fn verify_0_13_2( - &self, - public_key: PublicKey, - block_hash: BlockHash, - ) -> Result<(), SignatureError> { - ecdsa_verify_partial(public_key.0, block_hash.0, self.r.0, self.s.0) + ) -> Result<(), pathfinder_crypto::signature::SignatureError> { + pathfinder_crypto::signature::ecdsa_verify_partial( + public_key.0, + block_hash.0, + self.r.0, + self.s.0, + ) } } #[cfg(test)] mod test { use crate::macro_prelude::*; - use crate::{BlockCommitmentSignature, StateDiffCommitment}; - - #[test] - fn pre_0_13_2_verification_method() { - // From https://alpha-mainnet.starknet.io/feeder_gateway/get_public_key - let public_key = - public_key!("0x48253ff2c3bed7af18bde0b611b083b39445959102d4947c51c4db6aa4f4e58"); - // From https://alpha-mainnet.starknet.io/feeder_gateway/get_signature?blockNumber=635000 - let block_hash = - block_hash!("0x164685e53f274ecb60a3941303a6ee913aeb9016fbca5ba65c1cb8bdaee17a0"); - let state_diff_commitment = state_diff_commitment!( - "0x620efa2bd1149abe485486efa35c29571b4883f9f54a7f4938389276b0579ff" - ); - let signature = BlockCommitmentSignature { - r: block_commitment_signature_elem!( - "0x222679bdc9007386f5c2de62e89839b442799f356b24657af77e2a8aa87e74d" - ), - s: block_commitment_signature_elem!( - "0x16c9d0bab74fe4f268705559e16ebb36a5326e75c6a580e6ad3194fb9a0b1ed" - ), - }; - - signature - .verify(public_key, block_hash, state_diff_commitment) - .unwrap(); - } + use crate::BlockCommitmentSignature; #[test] fn _0_13_2_verification_method_for_the_last_0_13_1_1_block() { @@ -96,9 +48,7 @@ mod test { // Use some fake state diff commitment which should be ignored by the // verification method because there should be no fallback to the // pre-0.13.2 verification method in this case. - signature - .verify(public_key, block_hash, StateDiffCommitment::ZERO) - .unwrap(); + signature.verify(public_key, block_hash).unwrap(); } #[test] @@ -121,8 +71,6 @@ mod test { // Use some fake state diff commitment which should be ignored by the // verification method because there should be no fallback to the // pre-0.13.2 verification method in this case. - signature - .verify(public_key, block_hash, StateDiffCommitment::ZERO) - .unwrap(); + signature.verify(public_key, block_hash).unwrap(); } } diff --git a/crates/gateway-client/src/lib.rs b/crates/gateway-client/src/lib.rs index 70d9d47f84..0b32b9abfa 100644 --- a/crates/gateway-client/src/lib.rs +++ b/crates/gateway-client/src/lib.rs @@ -1164,7 +1164,7 @@ mod tests { let (_jh, url) = setup([( "/feeder_gateway/get_signature?blockNumber=350000", ( - starknet_gateway_test_fixtures::v0_12_2::signature::BLOCK_350000, + starknet_gateway_test_fixtures::v0_13_2::signature::SEPOLIA_INTEGRATION_35748, 200, ), )]); diff --git a/crates/gateway-types/src/reply.rs b/crates/gateway-types/src/reply.rs index f86871d6a9..bedee96d29 100644 --- a/crates/gateway-types/src/reply.rs +++ b/crates/gateway-types/src/reply.rs @@ -2204,55 +2204,16 @@ pub mod add_transaction { } #[derive(Clone, Debug, Deserialize, PartialEq, serde::Serialize)] -#[serde(untagged)] -pub enum BlockSignature { - /// Starknet <=0.13.1.1 - // TODO V0 does not say much, is V_0_13_1_1 a better name? - V0(BlockSignatureV0), - /// Starknet >= 0.13.2 - // TODO V1 does not say much, is V_0_13_2 a better name? - V1(BlockSignatureV1), -} - -#[derive(Clone, Debug, Deserialize, PartialEq, serde::Serialize)] -pub struct BlockSignatureV1 { +pub struct BlockSignature { pub block_hash: BlockHash, pub signature: [BlockCommitmentSignatureElem; 2], } -#[derive(Clone, Debug, Deserialize, PartialEq, serde::Serialize)] -pub struct BlockSignatureV0 { - pub block_number: BlockNumber, - pub signature: [BlockCommitmentSignatureElem; 2], - pub signature_input: BlockSignatureInput, -} - -#[derive(Clone, Debug, Deserialize, PartialEq, serde::Serialize)] -pub struct BlockSignatureInput { - pub block_hash: BlockHash, - pub state_diff_commitment: StateDiffCommitment, -} - impl BlockSignature { - pub fn block_hash(&self) -> BlockHash { - match self { - BlockSignature::V0(v0) => v0.signature_input.block_hash, - BlockSignature::V1(v1) => v1.block_hash, - } - } - pub fn signature(&self) -> pathfinder_common::BlockCommitmentSignature { - let s = match self { - BlockSignature::V0(v0) => v0.signature, - BlockSignature::V1(v1) => v1.signature, - }; - pathfinder_common::BlockCommitmentSignature { r: s[0], s: s[1] } - } - - pub fn state_diff_commitment(&self) -> Option { - match self { - BlockSignature::V0(v0) => Some(v0.signature_input.state_diff_commitment), - BlockSignature::V1(_) => None, + pathfinder_common::BlockCommitmentSignature { + r: self.signature[0], + s: self.signature[1], } } } @@ -2427,57 +2388,16 @@ mod tests { } mod block_signature { - use pathfinder_common::{ - block_commitment_signature_elem, - block_hash, - state_diff_commitment, - BlockNumber, - StarknetVersion, - }; + use pathfinder_common::{block_commitment_signature_elem, block_hash}; - use super::super::{ - BlockSignature, - BlockSignatureInput, - BlockSignatureV0, - BlockSignatureV1, - StateUpdate, - }; - - #[test] - fn parse_pre_starknet_0_13_2() { - let json = starknet_gateway_test_fixtures::v0_12_2::signature::BLOCK_350000; - - let expected = BlockSignature::V0(BlockSignatureV0 { - block_number: BlockNumber::new_or_panic(350000), - signature: [ - block_commitment_signature_elem!( - "0x95e98f5b91d39ae2b1bf77447a4fc01725352ae8b0b2c0a3fe09d43d1d9e57" - ), - block_commitment_signature_elem!( - "0x541b2db8dae6d5ae24b34e427d251edc2e94dcffddd85f207e1b51f2f4bb1ef" - ), - ], - signature_input: BlockSignatureInput { - block_hash: block_hash!( - "0x6f7342a680d7f99bdfdd859f587c75299e7ffabe62c071ded3a6d8a34cb132c" - ), - state_diff_commitment: state_diff_commitment!( - "0x432e8e2ad833548e1c1077fc298991b055ba1e6f7a17dd332db98f4f428c56c" - ), - }, - }); - - let signature: BlockSignature = serde_json::from_str(json).unwrap(); - - assert_eq!(signature, expected); - } + use super::super::BlockSignature; #[test] fn parse_starknet_0_13_2() { let json = starknet_gateway_test_fixtures::v0_13_2::signature::SEPOLIA_INTEGRATION_35748; - let expected = BlockSignature::V1(BlockSignatureV1 { + let expected = BlockSignature { block_hash: block_hash!( "0x1ea2a9cfa3df5297d58c0a04d09d276bc68d40fe64701305bbe2ed8f417e869" ), @@ -2489,28 +2409,11 @@ mod tests { "0x3e67cfbc5b179ba55a3b687228d8fe40626233f6691b4aabe308fcd6d71dcdb" ), ], - }); + }; let signature: BlockSignature = serde_json::from_str(json).unwrap(); assert_eq!(signature, expected); } - - #[test] - fn state_diff_commitment() { - let signature_json = starknet_gateway_test_fixtures::v0_12_2::signature::BLOCK_350000; - let signature: BlockSignature = serde_json::from_str(signature_json).unwrap(); - - let state_update_json = - starknet_gateway_test_fixtures::v0_12_2::state_update::BLOCK_350000; - let state_update: StateUpdate = serde_json::from_str(state_update_json).unwrap(); - - let state_update = pathfinder_common::StateUpdate::from(state_update); - - assert_eq!( - state_update.compute_state_diff_commitment(StarknetVersion::new(0, 12, 2, 0)), - signature.state_diff_commitment().unwrap() - ) - } } } diff --git a/crates/pathfinder/examples/feeder_gateway.rs b/crates/pathfinder/examples/feeder_gateway.rs index 9d12ebc359..f1c1203eca 100644 --- a/crates/pathfinder/examples/feeder_gateway.rs +++ b/crates/pathfinder/examples/feeder_gateway.rs @@ -492,12 +492,10 @@ fn resolve_signature( s: BlockCommitmentSignatureElem::ZERO, }); - Ok(starknet_gateway_types::reply::BlockSignature::V1( - starknet_gateway_types::reply::BlockSignatureV1 { - block_hash: header.hash, - signature: [signature.r, signature.s], - }, - )) + Ok(starknet_gateway_types::reply::BlockSignature { + block_hash: header.hash, + signature: [signature.r, signature.s], + }) } #[tracing::instrument(level = "trace", skip(tx))] diff --git a/crates/pathfinder/src/state/sync/l2.rs b/crates/pathfinder/src/state/sync/l2.rs index a6ec2f9d79..49f705e118 100644 --- a/crates/pathfinder/src/state/sync/l2.rs +++ b/crates/pathfinder/src/state/sync/l2.rs @@ -274,56 +274,14 @@ where // An extra sanity check for the signature API. anyhow::ensure!( - block.block_hash == signature.block_hash(), + block.block_hash == signature.block_hash, "Signature block hash mismatch, actual {:x}, expected {:x}", - signature.block_hash().0, + signature.block_hash.0, block.block_hash.0, ); - // Always compute the state diff commitment from the state update. - // If any of the feeder gateway replies (block or signature) contain a state - // diff commitment, check if the value matches. If it doesn't, just log the - // fact. - let version = block.starknet_version; - let (computed_state_diff_commitment, state_update) = - tokio::task::spawn_blocking(move || { - let commitment = state_update.compute_state_diff_commitment(version); - (commitment, state_update) - }) - .await?; - - let fgw_state_diff_commitment = match ( - block.state_diff_commitment, - signature.state_diff_commitment(), - ) { - (Some(x), None) | (None, Some(x)) => Some(x), - // This should never happen, but if it does, we treat it as a sanity check for the API. - (Some(x), Some(y)) => { - anyhow::ensure!( - x == y, - "Conflicting feeder gateway responses: state diff commitment in block {:x} vs \ - in signature {:x}", - y.0, - x.0, - ); - Some(x) - } - _ => None, - }; - - if let Some(x) = fgw_state_diff_commitment { - if x != computed_state_diff_commitment { - tracing::warn!( - "State diff commitment mismatch: computed {:x}, feeder gateway {:x}", - computed_state_diff_commitment.0, - x.0 - ); - } - } - - let signature = signature.signature(); - // Check block commitment signature + let signature: BlockCommitmentSignature = signature.signature(); let (signature, state_update) = match block_validation_mode { BlockValidationMode::Strict => { let block_hash = block.block_hash; @@ -332,7 +290,6 @@ where .verify( sequencer_public_key, block_hash, - computed_state_diff_commitment, ); (verify_result, signature, state_update) }).await?; @@ -344,6 +301,15 @@ where BlockValidationMode::AllowMismatch => (signature, state_update), }; + // Always compute the state diff commitment from the state update. + let version = block.starknet_version; + let (computed_state_diff_commitment, state_update) = + tokio::task::spawn_blocking(move || { + let commitment = state_update.compute_state_diff_commitment(version); + (commitment, state_update) + }) + .await?; + head = Some((next, block.block_hash, state_update.state_commitment)); blocks.push(next, block.block_hash, state_update.state_commitment); @@ -699,7 +665,6 @@ where )| { verify_signature( block.block_hash, - state_diff_commitment, &signature, sequencer_public_key, BlockValidationMode::AllowMismatch, @@ -1027,16 +992,13 @@ fn verify_transaction_hashes( /// Check block commitment signature. fn verify_signature( block_hash: BlockHash, - state_diff_commitment: StateDiffCommitment, signature: &BlockSignature, sequencer_public_key: PublicKey, mode: BlockValidationMode, ) -> Result<(), pathfinder_crypto::signature::SignatureError> { let signature = signature.signature(); match mode { - BlockValidationMode::Strict => { - signature.verify(sequencer_public_key, block_hash, state_diff_commitment) - } + BlockValidationMode::Strict => signature.verify(sequencer_public_key, block_hash), BlockValidationMode::AllowMismatch => Ok(()), } } @@ -1196,113 +1158,64 @@ mod tests { const STORAGE_VAL0_V2: StorageValue = storage_value_bytes!(b"contract 0 storage val 0 v2"); const STORAGE_VAL1: StorageValue = storage_value_bytes!(b"contract 1 storage val 0"); - const BLOCK0_SIGNATURE: reply::BlockSignature = - reply::BlockSignature::V0(reply::BlockSignatureV0 { - block_number: BLOCK0_NUMBER, - signature: [ - block_commitment_signature_elem_bytes!(b"block 0 signature r"), - block_commitment_signature_elem_bytes!(b"block 0 signature s"), - ], - signature_input: reply::BlockSignatureInput { - block_hash: BLOCK0_HASH, - state_diff_commitment: state_diff_commitment_bytes!( - b"block 0 state diff commitment" - ), - }, - }); + const BLOCK0_SIGNATURE: reply::BlockSignature = reply::BlockSignature { + block_hash: BLOCK0_HASH, + signature: [ + block_commitment_signature_elem_bytes!(b"block 0 signature r"), + block_commitment_signature_elem_bytes!(b"block 0 signature s"), + ], + }; // const BLOCK0_COMMITMENT_SIGNATURE: BlockCommitmentSignature = // BlockCommitmentSignature { r: BLOCK0_SIGNATURE.signature[0], // s: BLOCK0_SIGNATURE.signature[1], // }; - const BLOCK0_SIGNATURE_V2: reply::BlockSignature = - reply::BlockSignature::V0(reply::BlockSignatureV0 { - block_number: BLOCK0_NUMBER, - signature: [ - block_commitment_signature_elem_bytes!(b"block 0 signature r 2"), - block_commitment_signature_elem_bytes!(b"block 0 signature s 2"), - ], - signature_input: reply::BlockSignatureInput { - block_hash: BLOCK0_HASH_V2, - state_diff_commitment: state_diff_commitment_bytes!( - b"block 0 state diff commitment 2" - ), - }, - }); + const BLOCK0_SIGNATURE_V2: reply::BlockSignature = reply::BlockSignature { + block_hash: BLOCK0_HASH_V2, + signature: [ + block_commitment_signature_elem_bytes!(b"block 0 signature r 2"), + block_commitment_signature_elem_bytes!(b"block 0 signature s 2"), + ], + }; - const BLOCK1_SIGNATURE: reply::BlockSignature = - reply::BlockSignature::V0(reply::BlockSignatureV0 { - block_number: BLOCK1_NUMBER, - signature: [ - block_commitment_signature_elem_bytes!(b"block 1 signature r"), - block_commitment_signature_elem_bytes!(b"block 1 signature s"), - ], - signature_input: reply::BlockSignatureInput { - block_hash: BLOCK1_HASH, - state_diff_commitment: state_diff_commitment_bytes!( - b"block 1 state diff commitment" - ), - }, - }); + const BLOCK1_SIGNATURE: reply::BlockSignature = reply::BlockSignature { + block_hash: BLOCK1_HASH, + signature: [ + block_commitment_signature_elem_bytes!(b"block 1 signature r"), + block_commitment_signature_elem_bytes!(b"block 1 signature s"), + ], + }; // const BLOCK1_COMMITMENT_SIGNATURE: BlockCommitmentSignature = // BlockCommitmentSignature { r: BLOCK1_SIGNATURE.signature[0], // s: BLOCK1_SIGNATURE.signature[1], // }; - const BLOCK1_SIGNATURE_V2: reply::BlockSignature = - reply::BlockSignature::V0(reply::BlockSignatureV0 { - block_number: BLOCK1_NUMBER, - signature: [ - block_commitment_signature_elem_bytes!(b"block 1 signature r 2"), - block_commitment_signature_elem_bytes!(b"block 1 signature s 2"), - ], - signature_input: reply::BlockSignatureInput { - block_hash: BLOCK1_HASH_V2, - state_diff_commitment: state_diff_commitment_bytes!( - b"block 1 state diff commitment 2" - ), - }, - }); - const BLOCK2_SIGNATURE: reply::BlockSignature = - reply::BlockSignature::V0(reply::BlockSignatureV0 { - block_number: BLOCK2_NUMBER, - signature: [ - block_commitment_signature_elem_bytes!(b"block 2 signature r"), - block_commitment_signature_elem_bytes!(b"block 2 signature s"), - ], - signature_input: reply::BlockSignatureInput { - block_hash: BLOCK2_HASH, - state_diff_commitment: state_diff_commitment_bytes!( - b"block 2 state diff commitment" - ), - }, - }); - const BLOCK2_SIGNATURE_V2: reply::BlockSignature = - reply::BlockSignature::V0(reply::BlockSignatureV0 { - block_number: BLOCK2_NUMBER, - signature: [ - block_commitment_signature_elem_bytes!(b"block 2 signature r 2"), - block_commitment_signature_elem_bytes!(b"block 2 signature s 2"), - ], - signature_input: reply::BlockSignatureInput { - block_hash: BLOCK2_HASH_V2, - state_diff_commitment: state_diff_commitment_bytes!( - b"block 2 state diff commitment 2" - ), - }, - }); - const BLOCK3_SIGNATURE: reply::BlockSignature = - reply::BlockSignature::V0(reply::BlockSignatureV0 { - block_number: BLOCK3_NUMBER, - signature: [ - block_commitment_signature_elem_bytes!(b"block 3 signature r"), - block_commitment_signature_elem_bytes!(b"block 3 signature s"), - ], - signature_input: reply::BlockSignatureInput { - block_hash: BLOCK3_HASH, - state_diff_commitment: state_diff_commitment_bytes!( - b"block 3 state diff commitment" - ), - }, - }); + const BLOCK1_SIGNATURE_V2: reply::BlockSignature = reply::BlockSignature { + block_hash: BLOCK1_HASH_V2, + signature: [ + block_commitment_signature_elem_bytes!(b"block 1 signature r 2"), + block_commitment_signature_elem_bytes!(b"block 1 signature s 2"), + ], + }; + const BLOCK2_SIGNATURE: reply::BlockSignature = reply::BlockSignature { + block_hash: BLOCK2_HASH, + signature: [ + block_commitment_signature_elem_bytes!(b"block 2 signature r"), + block_commitment_signature_elem_bytes!(b"block 2 signature s"), + ], + }; + const BLOCK2_SIGNATURE_V2: reply::BlockSignature = reply::BlockSignature { + block_hash: BLOCK2_HASH_V2, + signature: [ + block_commitment_signature_elem_bytes!(b"block 2 signature r 2"), + block_commitment_signature_elem_bytes!(b"block 2 signature s 2"), + ], + }; + const BLOCK3_SIGNATURE: reply::BlockSignature = reply::BlockSignature { + block_hash: BLOCK3_HASH, + signature: [ + block_commitment_signature_elem_bytes!(b"block 3 signature r"), + block_commitment_signature_elem_bytes!(b"block 3 signature s"), + ], + }; fn spawn_sync_default( tx_event: mpsc::Sender, diff --git a/crates/pathfinder/src/sync/headers.rs b/crates/pathfinder/src/sync/headers.rs index cc20653080..ea8c801a44 100644 --- a/crates/pathfinder/src/sync/headers.rs +++ b/crates/pathfinder/src/sync/headers.rs @@ -296,11 +296,7 @@ impl VerifyHashAndSignature { fn verify_signature(&self, header: &SignedBlockHeader) -> bool { header .signature - .verify( - self.public_key, - header.header.hash, - header.header.state_diff_commitment, - ) + .verify(self.public_key, header.header.hash) .is_ok() } } From c496c232e6810d3835df907e4ef874f156a6b1f0 Mon Sep 17 00:00:00 2001 From: Krisztian Kovacs Date: Tue, 1 Oct 2024 13:31:17 +0200 Subject: [PATCH 106/282] chore: remove pre-0.13.2 state diff commitment algorithm We don't use this anymore as the feeder gateway always returns signatures computed over the block hash only after the Starknet 0.13.2 upgrade (even for pre-0.13.2 blocks). --- crates/common/src/state_update.rs | 313 ++++-------------- .../examples/compute_pre0132_hashes.rs | 2 +- .../examples/recompute_state_diff_length.rs | 5 +- crates/pathfinder/src/state/block_hash.rs | 3 +- crates/pathfinder/src/state/sync/l2.rs | 14 +- .../pathfinder/src/sync/checkpoint/fixture.rs | 4 +- crates/pathfinder/src/sync/state_updates.rs | 19 +- crates/pathfinder/src/sync/track.rs | 10 +- crates/storage/src/fake.rs | 4 +- 9 files changed, 79 insertions(+), 295 deletions(-) diff --git a/crates/common/src/state_update.rs b/crates/common/src/state_update.rs index ab67e28a6c..3e6ac221ed 100644 --- a/crates/common/src/state_update.rs +++ b/crates/common/src/state_update.rs @@ -9,7 +9,6 @@ use crate::{ ContractAddress, ContractNonce, SierraHash, - StarknetVersion, StateCommitment, StateDiffCommitment, StorageAddress, @@ -263,13 +262,12 @@ impl StateUpdate { }) } - pub fn compute_state_diff_commitment(&self, version: StarknetVersion) -> StateDiffCommitment { + pub fn compute_state_diff_commitment(&self) -> StateDiffCommitment { state_diff_commitment::compute( &self.contract_updates, &self.system_contract_updates, &self.declared_cairo_classes, &self.declared_sierra_classes, - version, ) } @@ -289,13 +287,12 @@ impl StateUpdate { } impl StateUpdateData { - pub fn compute_state_diff_commitment(&self, version: StarknetVersion) -> StateDiffCommitment { + pub fn compute_state_diff_commitment(&self) -> StateDiffCommitment { state_diff_commitment::compute( &self.contract_updates, &self.system_contract_updates, &self.declared_cairo_classes, &self.declared_sierra_classes, - version, ) } @@ -342,7 +339,7 @@ impl From for StateUpdateData { mod state_diff_commitment { use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; - use pathfinder_crypto::hash::{poseidon_hash_many, PoseidonHasher}; + use pathfinder_crypto::hash::PoseidonHasher; use pathfinder_crypto::MontFelt; use super::{ContractUpdate, SystemContractUpdate}; @@ -351,126 +348,23 @@ mod state_diff_commitment { CasmHash, ClassHash, ContractAddress, - ContractNonce, SierraHash, - StarknetVersion, StateDiffCommitment, - StorageAddress, - StorageValue, }; /// Compute the state diff commitment used in block commitment signatures. /// - /// How to compute the value is documented in [this Starknet Community article](https://community.starknet.io/t/introducing-p2p-authentication-and-mismatch-resolution-in-v0-12-2/97993). + /// How to compute the value is documented in the [Starknet documentation](https://docs.starknet.io/architecture-and-concepts/network-architecture/block-structure/#state_diff_hash). pub fn compute( contract_updates: &HashMap, system_contract_updates: &HashMap, declared_cairo_classes: &HashSet, declared_sierra_classes: &HashMap, - version: StarknetVersion, ) -> StateDiffCommitment { - if version < StarknetVersion::new(0, 13, 2, 0) { - StateDiffCommitment( - poseidon_hash_many(&[ - // state_diff_version - MontFelt::ZERO, - compute_hash_of_deployed_contracts(contract_updates), - compute_hash_of_declared_classes(declared_sierra_classes), - compute_hash_of_old_declared_classes(declared_cairo_classes), - // number_of_DA_modes - MontFelt::ONE, - // DA_mode_0 - MontFelt::ZERO, - compute_hash_of_storage_domain_state_diff( - contract_updates, - system_contract_updates, - ), - ]) - .into(), - ) - } else { - let mut hasher = PoseidonHasher::new(); - hasher.write(felt_bytes!(b"STARKNET_STATE_DIFF0").into()); - // Hash the deployed contracts. - let deployed_contracts: BTreeMap<_, _> = contract_updates - .iter() - .filter_map(|(address, update)| { - update - .class - .as_ref() - .map(|update| (*address, update.class_hash())) - }) - .collect(); - hasher.write(MontFelt::from(deployed_contracts.len() as u64)); - for (address, class_hash) in deployed_contracts { - hasher.write(MontFelt::from(address.0)); - hasher.write(MontFelt::from(class_hash.0)); - } - // Hash the declared classes. - let declared_classes: BTreeSet<_> = declared_sierra_classes - .iter() - .map(|(sierra, casm)| (*sierra, *casm)) - .collect(); - hasher.write(MontFelt::from(declared_classes.len() as u64)); - for (sierra, casm) in declared_classes { - hasher.write(MontFelt::from(sierra.0)); - hasher.write(MontFelt::from(casm.0)); - } - // Hash the old declared classes. - let deprecated_declared_classes: BTreeSet<_> = - declared_cairo_classes.iter().copied().collect(); - hasher.write(MontFelt::from(deprecated_declared_classes.len() as u64)); - for class_hash in deprecated_declared_classes { - hasher.write(MontFelt::from(class_hash.0)); - } - hasher.write(MontFelt::ONE); - hasher.write(MontFelt::ZERO); - // Hash the storage diffs. - let storage_diffs: BTreeMap<_, _> = contract_updates - .iter() - .map(|(address, update)| (address, &update.storage)) - .chain( - system_contract_updates - .iter() - .map(|(address, update)| (address, &update.storage)), - ) - .filter_map(|(address, storage)| { - if storage.is_empty() { - None - } else { - let updates: BTreeMap<_, _> = - storage.iter().map(|(key, value)| (*key, *value)).collect(); - Some((*address, updates)) - } - }) - .collect(); - hasher.write(MontFelt::from(storage_diffs.len() as u64)); - for (address, updates) in storage_diffs { - hasher.write(MontFelt::from(address.0)); - hasher.write(MontFelt::from(updates.len() as u64)); - for (key, value) in updates { - hasher.write(MontFelt::from(key.0)); - hasher.write(MontFelt::from(value.0)); - } - } - // Hash the nonce updates. - let nonces: BTreeMap<_, _> = contract_updates - .iter() - .filter_map(|(address, update)| update.nonce.map(|nonce| (*address, nonce))) - .collect(); - hasher.write(MontFelt::from(nonces.len() as u64)); - for (address, nonce) in nonces { - hasher.write(MontFelt::from(address.0)); - hasher.write(MontFelt::from(nonce.0)); - } - StateDiffCommitment(hasher.finish().into()) - } - } - - fn compute_hash_of_deployed_contracts( - contract_updates: &HashMap, - ) -> MontFelt { - let deployed_contracts: BTreeMap = contract_updates + let mut hasher = PoseidonHasher::new(); + hasher.write(felt_bytes!(b"STARKNET_STATE_DIFF0").into()); + // Hash the deployed contracts. + let deployed_contracts: BTreeMap<_, _> = contract_updates .iter() .filter_map(|(address, update)| { update @@ -479,153 +373,69 @@ mod state_diff_commitment { .map(|update| (*address, update.class_hash())) }) .collect(); - - let number_of_deployed_contracts = deployed_contracts.len() as u64; - - deployed_contracts - .iter() - .fold( - { - let mut hasher = PoseidonHasher::new(); - hasher.write(number_of_deployed_contracts.into()); - hasher - }, - |mut hasher, (address, class_hash)| { - hasher.write(address.0.into()); - hasher.write(class_hash.0.into()); - hasher - }, - ) - .finish() - } - - fn compute_hash_of_declared_classes( - declared_sierra_classes: &HashMap, - ) -> MontFelt { - let declared_classes: BTreeSet<(SierraHash, CasmHash)> = declared_sierra_classes + hasher.write(MontFelt::from(deployed_contracts.len() as u64)); + for (address, class_hash) in deployed_contracts { + hasher.write(MontFelt::from(address.0)); + hasher.write(MontFelt::from(class_hash.0)); + } + // Hash the declared classes. + let declared_classes: BTreeSet<_> = declared_sierra_classes .iter() .map(|(sierra, casm)| (*sierra, *casm)) .collect(); - - let number_of_declared_classes = declared_classes.len() as u64; - - declared_classes - .iter() - .fold( - { - let mut hasher = PoseidonHasher::new(); - hasher.write(number_of_declared_classes.into()); - hasher - }, - |mut hasher, (sierra, casm)| { - hasher.write(sierra.0.into()); - hasher.write(casm.0.into()); - hasher - }, - ) - .finish() - } - - fn compute_hash_of_old_declared_classes( - declared_cairo_classes: &HashSet, - ) -> MontFelt { - let declared_classes: BTreeSet = + hasher.write(MontFelt::from(declared_classes.len() as u64)); + for (sierra, casm) in declared_classes { + hasher.write(MontFelt::from(sierra.0)); + hasher.write(MontFelt::from(casm.0)); + } + // Hash the old declared classes. + let deprecated_declared_classes: BTreeSet<_> = declared_cairo_classes.iter().copied().collect(); - - let number_of_declared_classes = declared_classes.len() as u64; - - declared_classes + hasher.write(MontFelt::from(deprecated_declared_classes.len() as u64)); + for class_hash in deprecated_declared_classes { + hasher.write(MontFelt::from(class_hash.0)); + } + hasher.write(MontFelt::ONE); + hasher.write(MontFelt::ZERO); + // Hash the storage diffs. + let storage_diffs: BTreeMap<_, _> = contract_updates .iter() - .fold( - { - let mut hasher = PoseidonHasher::new(); - hasher.write(number_of_declared_classes.into()); - hasher - }, - |mut hasher, class_hash| { - hasher.write(class_hash.0.into()); - hasher - }, - ) - .finish() - } - - fn compute_hash_of_storage_domain_state_diff( - contract_updates: &HashMap, - system_contract_updates: &HashMap, - ) -> MontFelt { - let storage_diffs = contract_updates.iter().filter_map(|(address, update)| { - if update.storage.is_empty() { - None - } else { - let updates = update - .storage + .map(|(address, update)| (address, &update.storage)) + .chain( + system_contract_updates .iter() - .map(|(key, value)| (*key, *value)) - .collect(); - Some((*address, updates)) + .map(|(address, update)| (address, &update.storage)), + ) + .filter_map(|(address, storage)| { + if storage.is_empty() { + None + } else { + let updates: BTreeMap<_, _> = + storage.iter().map(|(key, value)| (*key, *value)).collect(); + Some((*address, updates)) + } + }) + .collect(); + hasher.write(MontFelt::from(storage_diffs.len() as u64)); + for (address, updates) in storage_diffs { + hasher.write(MontFelt::from(address.0)); + hasher.write(MontFelt::from(updates.len() as u64)); + for (key, value) in updates { + hasher.write(MontFelt::from(key.0)); + hasher.write(MontFelt::from(value.0)); } - }); - let system_storage_diffs = - system_contract_updates - .iter() - .filter_map(|(address, update)| { - if update.storage.is_empty() { - None - } else { - let updates: BTreeMap = update - .storage - .iter() - .map(|(key, value)| (*key, *value)) - .collect(); - - Some((*address, updates)) - } - }); - let storage_diffs: BTreeMap> = - storage_diffs.chain(system_storage_diffs).collect(); - - let number_of_updated_contracts = storage_diffs.len() as u64; - - let mut hasher = storage_diffs.iter().fold( - { - let mut hasher = PoseidonHasher::new(); - hasher.write(number_of_updated_contracts.into()); - hasher - }, - |mut hasher, (address, updates)| { - hasher.write(address.0.into()); - let number_of_updates = updates.len() as u64; - hasher.write(number_of_updates.into()); - - updates.iter().fold(hasher, |mut hasher, (key, value)| { - hasher.write(key.0.into()); - hasher.write(value.0.into()); - hasher - }) - }, - ); - - let nonces: BTreeMap = contract_updates + } + // Hash the nonce updates. + let nonces: BTreeMap<_, _> = contract_updates .iter() .filter_map(|(address, update)| update.nonce.map(|nonce| (*address, nonce))) .collect(); - - let number_of_updated_nonces = nonces.len() as u64; - - let hasher = nonces.iter().fold( - { - hasher.write(number_of_updated_nonces.into()); - hasher - }, - |mut hasher, (address, nonce)| { - hasher.write(address.0.into()); - hasher.write(nonce.0.into()); - hasher - }, - ); - - hasher.finish() + hasher.write(MontFelt::from(nonces.len() as u64)); + for (address, nonce) in nonces { + hasher.write(MontFelt::from(address.0)); + hasher.write(MontFelt::from(nonce.0)); + } + StateDiffCommitment(hasher.finish().into()) } } @@ -929,7 +739,6 @@ mod tests { &Default::default(), &declared_cairo_classes, &declared_sierra_classes, - StarknetVersion::new(0, 13, 2, 0) ) ); } diff --git a/crates/pathfinder/examples/compute_pre0132_hashes.rs b/crates/pathfinder/examples/compute_pre0132_hashes.rs index a7b0c11789..d11e1d3221 100644 --- a/crates/pathfinder/examples/compute_pre0132_hashes.rs +++ b/crates/pathfinder/examples/compute_pre0132_hashes.rs @@ -103,7 +103,7 @@ fn main() -> anyhow::Result<()> { let state_update = tx .state_update(block_id)? .context("Fetching state update")?; - header.state_diff_commitment = state_update.compute_state_diff_commitment(VERSION_CUTOFF); + header.state_diff_commitment = state_update.compute_state_diff_commitment(); drop(tx); diff --git a/crates/pathfinder/examples/recompute_state_diff_length.rs b/crates/pathfinder/examples/recompute_state_diff_length.rs index 0743cd95fe..4381b160eb 100644 --- a/crates/pathfinder/examples/recompute_state_diff_length.rs +++ b/crates/pathfinder/examples/recompute_state_diff_length.rs @@ -27,9 +27,6 @@ fn main() -> anyhow::Result<()> { for block_number in 0..latest_block_number.get() { let block_number = BlockNumber::new_or_panic(block_number); let block_id = pathfinder_storage::BlockId::Number(block_number); - let version = tx - .block_version(block_number)? - .context("Fetching block version")?; let state_update = tx .state_update(block_id)? .context("Fetching state update")?; @@ -38,7 +35,7 @@ fn main() -> anyhow::Result<()> { .context("Fetching block header")?; let state_diff_length = state_update.state_diff_length(); - let state_diff_commitment = state_update.compute_state_diff_commitment(version); + let state_diff_commitment = state_update.compute_state_diff_commitment(); if state_diff_length != header.state_diff_length || state_diff_commitment != header.state_diff_commitment diff --git a/crates/pathfinder/src/state/block_hash.rs b/crates/pathfinder/src/state/block_hash.rs index dadd89e306..89cdf367d0 100644 --- a/crates/pathfinder/src/state/block_hash.rs +++ b/crates/pathfinder/src/state/block_hash.rs @@ -1083,8 +1083,7 @@ mod tests { serde_json::from_str(v0_13_2::state_update::SEPOLIA_INTEGRATION_35748).unwrap(); let state_update: pathfinder_common::StateUpdate = state_update.into(); let state_diff_length = state_update.state_diff_length(); - let state_diff_commitment = - state_update.compute_state_diff_commitment(StarknetVersion::new(0, 13, 2, 0)); + let state_diff_commitment = state_update.compute_state_diff_commitment(); assert_eq!(state_diff_length, block.state_diff_length.unwrap()); assert_eq!(state_diff_commitment, block.state_diff_commitment.unwrap()); diff --git a/crates/pathfinder/src/state/sync/l2.rs b/crates/pathfinder/src/state/sync/l2.rs index 49f705e118..c4942d54b4 100644 --- a/crates/pathfinder/src/state/sync/l2.rs +++ b/crates/pathfinder/src/state/sync/l2.rs @@ -302,10 +302,9 @@ where }; // Always compute the state diff commitment from the state update. - let version = block.starknet_version; let (computed_state_diff_commitment, state_update) = tokio::task::spawn_blocking(move || { - let commitment = state_update.compute_state_diff_commitment(version); + let commitment = state_update.compute_state_diff_commitment(); (commitment, state_update) }) .await?; @@ -484,8 +483,8 @@ async fn download_block( // Check if commitments and block hash are correct let verify_hash = tokio::task::spawn_blocking(move || -> anyhow::Result<_> { - let state_diff_commitment = StateUpdateData::from(state_update.clone()) - .compute_state_diff_commitment(block.starknet_version); + let state_diff_commitment = + StateUpdateData::from(state_update.clone()).compute_state_diff_commitment(); let state_update = Box::new(state_update); let state_diff_length = state_update.state_diff_length(); @@ -909,8 +908,8 @@ fn verify_block_and_state_update( StateDiffCommitment, )> { // Check if commitments and block hash are correct - let state_diff_commitment = StateUpdateData::from(state_update.clone()) - .compute_state_diff_commitment(block.starknet_version); + let state_diff_commitment = + StateUpdateData::from(state_update.clone()).compute_state_diff_commitment(); let state_diff_length = state_update.state_diff_length(); let verify_result = verify_gateway_block_commitments_and_hash( @@ -949,8 +948,7 @@ fn verify_block_and_state_update( // If any of the feeder gateway replies (block or signature) contain a state // diff commitment, check if the value matches. If it doesn't, just log the // fact. - let version = block.starknet_version; - let computed_state_diff_commitment = state_update.compute_state_diff_commitment(version); + let computed_state_diff_commitment = state_update.compute_state_diff_commitment(); if let Some(x) = block.state_diff_commitment { if x != computed_state_diff_commitment { diff --git a/crates/pathfinder/src/sync/checkpoint/fixture.rs b/crates/pathfinder/src/sync/checkpoint/fixture.rs index 12c5e285dd..847ae54327 100644 --- a/crates/pathfinder/src/sync/checkpoint/fixture.rs +++ b/crates/pathfinder/src/sync/checkpoint/fixture.rs @@ -84,7 +84,7 @@ pub fn blocks() -> [Block; 2] { event_count: 0, l1_da_mode: L1DataAvailabilityMode::Calldata, receipt_commitment: receipt_commitment!("0x0200A173F6AECAB11A7166EFB0BF8F4362A8403CA32292695A37B322793F1302"), - state_diff_commitment: state_diff_commitment!("0x06C4A7559B57CADED12AD2275F78C4AC310FF54B2E233D25C9CF4891C251B450"), + state_diff_commitment: state_diff_commitment!("0x00A8AC20EF93DBE185D09AE31DE4EA3372ECF753F14EBAAE97ADAB22B1AB72F2"), state_diff_length: 25, }, signature: BlockCommitmentSignature { @@ -1200,7 +1200,7 @@ pub fn blocks() -> [Block; 2] { event_count: 0, l1_da_mode: L1DataAvailabilityMode::Calldata, receipt_commitment: receipt_commitment!("0x00FB6833B56FCA428975B0DF7875F35B7EADBD26B517DAF1B9702E1D85665065"), - state_diff_commitment: state_diff_commitment!("0x013BEED68D79C0FF1D6B465660BCF245A7F0EC11AF5E9C6564FBA30543705FE3"), + state_diff_commitment: state_diff_commitment!("0x05D83BBEEDF35B7D310A43B11F3623DD5D705FF09A7FBC8B634222E083433CAE"), state_diff_length: 12, }, signature: BlockCommitmentSignature { diff --git a/crates/pathfinder/src/sync/state_updates.rs b/crates/pathfinder/src/sync/state_updates.rs index 83b4cd817f..b20406977d 100644 --- a/crates/pathfinder/src/sync/state_updates.rs +++ b/crates/pathfinder/src/sync/state_updates.rs @@ -89,22 +89,18 @@ impl FetchCommitmentFromDb { impl ProcessStage for FetchCommitmentFromDb { const NAME: &'static str = "StateDiff::FetchCommitmentFromDb"; type Input = (T, BlockNumber); - type Output = (T, BlockNumber, StarknetVersion, StateDiffCommitment); + type Output = (T, BlockNumber, StateDiffCommitment); fn map(&mut self, (data, block_number): Self::Input) -> Result { let mut db = self .db .transaction() .context("Creating database transaction")?; - let version = db - .block_version(block_number) - .context("Fetching starknet version")? - .ok_or(SyncError2::StarknetVersionNotFound)?; let commitment = db .state_diff_commitment(block_number) .context("Fetching state diff commitment")? .ok_or(SyncError2::StateDiffCommitmentNotFound)?; - Ok((data, block_number, version, commitment)) + Ok((data, block_number, commitment)) } } @@ -112,17 +108,12 @@ pub struct VerifyCommitment; impl ProcessStage for VerifyCommitment { const NAME: &'static str = "StateDiff::Verify"; - type Input = ( - StateUpdateData, - BlockNumber, - StarknetVersion, - StateDiffCommitment, - ); + type Input = (StateUpdateData, BlockNumber, StateDiffCommitment); type Output = StateUpdateData; fn map(&mut self, input: Self::Input) -> Result { - let (state_diff, block_number, version, expected_commitment) = input; - let actual_commitment = state_diff.compute_state_diff_commitment(version); + let (state_diff, block_number, expected_commitment) = input; + let actual_commitment = state_diff.compute_state_diff_commitment(); if actual_commitment != expected_commitment { tracing::debug!(%block_number, %expected_commitment, %actual_commitment, "State diff commitment mismatch"); diff --git a/crates/pathfinder/src/sync/track.rs b/crates/pathfinder/src/sync/track.rs index 2bc6a5c910..9ef04d6273 100644 --- a/crates/pathfinder/src/sync/track.rs +++ b/crates/pathfinder/src/sync/track.rs @@ -514,14 +514,7 @@ struct StateDiffSource

{ } impl

StateDiffSource

{ - fn spawn( - self, - ) -> SyncReceiver<( - StateUpdateData, - BlockNumber, - StarknetVersion, - StateDiffCommitment, - )> + fn spawn(self) -> SyncReceiver<(StateUpdateData, BlockNumber, StateDiffCommitment)> where P: Clone + BlockClient + Send + 'static, { @@ -557,7 +550,6 @@ impl

StateDiffSource

{ ( state_diff, header.header.number, - header.header.starknet_version, header.header.state_diff_commitment, ), ))) diff --git a/crates/storage/src/fake.rs b/crates/storage/src/fake.rs index 4f5406e1e6..ecd2e59fa0 100644 --- a/crates/storage/src/fake.rs +++ b/crates/storage/src/fake.rs @@ -383,7 +383,6 @@ pub mod init { SignedBlockHeader { header: BlockHeader { - starknet_version, state_diff_length, state_diff_commitment, .. @@ -424,8 +423,7 @@ pub mod init { cairo_defs.extend(implicitly_declared); *state_diff_length = state_update.state_diff_length(); - *state_diff_commitment = - state_update.compute_state_diff_commitment(*starknet_version); + *state_diff_commitment = state_update.compute_state_diff_commitment(); } // Compute the block hash, update parent block hash with the correct value From 8ed6ce6e1b933d554b3d598afdcdec41efd82750 Mon Sep 17 00:00:00 2001 From: Krzysztof Lis Date: Tue, 1 Oct 2024 09:16:05 +0200 Subject: [PATCH 107/282] test(p2p): add additional check in evicted_peer_reconnection --- crates/p2p/src/tests.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/crates/p2p/src/tests.rs b/crates/p2p/src/tests.rs index 53a3310891..971792aff1 100644 --- a/crates/p2p/src/tests.rs +++ b/crates/p2p/src/tests.rs @@ -595,6 +595,9 @@ async fn inbound_peer_eviction() { .await .unwrap(); + // Let it settle. + tokio::time::sleep(Duration::from_secs(1)).await; + let connected = peer.connected().await; // 25 inbound and 1 outbound peer. assert_eq!(connected.len(), 26); @@ -685,6 +688,13 @@ async fn evicted_peer_reconnection() { _ => None, }) .await; + + // Let it settle. + tokio::time::sleep(Duration::from_secs(1)).await; + + let connected_to_peer1 = peer1.connected().await; + assert!(connected_to_peer1.contains_key(&peer2.peer_id)); + assert!(!connected_to_peer1.contains_key(&peer3.peer_id)); } /// Test that peers can only connect if they are whitelisted. From 40f74feab3ac486f2307bd36f8c5d36da9ab163c Mon Sep 17 00:00:00 2001 From: Krzysztof Lis Date: Mon, 30 Sep 2024 17:28:18 +0200 Subject: [PATCH 108/282] test(p2p): add short peer id utility Intended for debugging. --- crates/p2p/src/lib.rs | 1 + crates/p2p/src/short_id.rs | 115 +++++++++++++++++++++++++++++++++++++ 2 files changed, 116 insertions(+) create mode 100644 crates/p2p/src/short_id.rs diff --git a/crates/p2p/src/lib.rs b/crates/p2p/src/lib.rs index b1c473d20c..622d3fa12c 100644 --- a/crates/p2p/src/lib.rs +++ b/crates/p2p/src/lib.rs @@ -25,6 +25,7 @@ mod main_loop; mod peer_data; mod peers; mod secret; +mod short_id; mod sync; #[cfg(test)] mod test_utils; diff --git a/crates/p2p/src/short_id.rs b/crates/p2p/src/short_id.rs new file mode 100644 index 0000000000..ca828a67f3 --- /dev/null +++ b/crates/p2p/src/short_id.rs @@ -0,0 +1,115 @@ +#![allow(dead_code)] + +use std::fmt::{Debug, Display}; +use std::str; + +use libp2p::PeerId; + +/// A debug-friendly representation of a `PeerId` that only keeps the last two +/// characters of the base58 representation. +/// +/// ### Important +/// +/// Do not use it in production, given a large number of peers collisions +/// __will__ occur. +#[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct ShortId([u8; 2]); + +impl Debug for ShortId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(str::from_utf8(&self.0).expect("Base58 characters are ASCII")) + } +} + +impl Display for ShortId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + Debug::fmt(self, f) + } +} + +impl From<&PeerId> for ShortId { + fn from(peer_id: &PeerId) -> Self { + let s = peer_id.to_base58(); + let s = s.as_bytes(); + let mut short = [0; 2]; + short.copy_from_slice(&s[s.len() - 2..]); + ShortId(short) + } +} + +impl From for ShortId { + fn from(peer_id: PeerId) -> Self { + ShortId::from(&peer_id) + } +} + +pub mod prelude { + use std::collections::{BTreeSet, HashMap, HashSet}; + + use libp2p::PeerId; + + use super::ShortId; + + pub trait Short { + fn short(&self) -> ShortId; + } + + impl Short for PeerId { + fn short(&self) -> ShortId { + ShortId::from(self) + } + } + + pub trait IntoKeysShort { + fn into_keys_short(self) -> BTreeSet; + } + + impl IntoKeysShort for T + where + T: IntoIterator, + U: Iterator, + V: PeerIdField, + { + fn into_keys_short(self) -> BTreeSet { + self.into_iter().map(|x| x.peer_id().short()).collect() + } + } + + pub trait KeysShort { + fn keys_short(&self) -> BTreeSet; + } + + impl KeysShort for Vec { + fn keys_short(&self) -> BTreeSet { + self.iter().map(Short::short).collect() + } + } + + impl KeysShort for HashSet { + fn keys_short(&self) -> BTreeSet { + self.iter().map(Short::short).collect() + } + } + + impl KeysShort for HashMap { + fn keys_short(&self) -> BTreeSet { + self.keys().map(Short::short).collect() + } + } + + pub trait PeerIdField { + fn peer_id(&self) -> PeerId; + } + + impl PeerIdField for (PeerId, T) { + fn peer_id(&self) -> PeerId { + self.0 + } + } + + impl PeerIdField for PeerId { + fn peer_id(&self) -> PeerId { + *self + } + } +} From d28412fe6eaa3b05cc0e2464d5627d34d7c58d08 Mon Sep 17 00:00:00 2001 From: Krzysztof Lis Date: Tue, 1 Oct 2024 12:52:52 +0200 Subject: [PATCH 109/282] test(p2p): mitigate automatic bootstrap in inbound_peer_eviction test --- crates/p2p/src/tests.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/p2p/src/tests.rs b/crates/p2p/src/tests.rs index 971792aff1..e90b68c531 100644 --- a/crates/p2p/src/tests.rs +++ b/crates/p2p/src/tests.rs @@ -567,6 +567,10 @@ async fn inbound_peer_eviction() { .unwrap(); } + // Let the automatic bootstrap "noise" fade away as in some circumstances it can + // cause additional dials that interrupt the flow of the test + tokio::time::sleep(Duration::from_secs(5)).await; + let connected = peer.connected().await; // 25 inbound and 1 outbound peer. assert_eq!(connected.len(), 26); From 6fb5d218c9054160ea5dcd2c27cc128c732d2fe6 Mon Sep 17 00:00:00 2001 From: Krzysztof Lis Date: Tue, 1 Oct 2024 12:53:22 +0200 Subject: [PATCH 110/282] refactor(p2p/behaviour): disconnect peers via ToSwarm::CloseConnection --- crates/p2p/src/behaviour.rs | 38 ++++++++++++----------------- crates/p2p/src/behaviour/builder.rs | 4 +-- crates/p2p/src/builder.rs | 2 +- 3 files changed, 19 insertions(+), 25 deletions(-) diff --git a/crates/p2p/src/behaviour.rs b/crates/p2p/src/behaviour.rs index 6427ece777..9cffb7ae6c 100644 --- a/crates/p2p/src/behaviour.rs +++ b/crates/p2p/src/behaviour.rs @@ -1,4 +1,4 @@ -use std::collections::HashMap; +use std::collections::{HashMap, VecDeque}; use std::net::IpAddr; use std::time::{Duration, Instant}; use std::{cmp, task}; @@ -11,6 +11,7 @@ use libp2p::kad::{self}; use libp2p::multiaddr::Protocol; use libp2p::swarm::behaviour::ConnectionEstablished; use libp2p::swarm::{ + CloseConnection, ConnectionClosed, ConnectionDenied, ConnectionId, @@ -50,9 +51,9 @@ pub type BehaviourWithRelayTransport = (Behaviour, relay::client::Transport); pub struct Behaviour { cfg: Config, peers: PeerSet, - swarm: crate::Client, secret: Secret, inner: Inner, + pending_events: VecDeque::ToSwarm, THandlerInEvent>>, } #[derive(NetworkBehaviour)] @@ -174,11 +175,9 @@ impl NetworkBehaviour for Behaviour { useful: true, }, ); - let swarm = self.swarm.clone(); - tokio::spawn(async move { - if let Err(err) = swarm.disconnect(peer_id).await { - tracing::debug!(%peer_id, %err, "Failed to disconnect peer"); - } + self.pending_events.push_back(ToSwarm::CloseConnection { + peer_id, + connection: CloseConnection::All, }); return; }; @@ -275,7 +274,10 @@ impl NetworkBehaviour for Behaviour { &mut self, cx: &mut task::Context<'_>, ) -> task::Poll>> { - self.inner.poll(cx) + match self.pending_events.pop_front() { + Some(event) => task::Poll::Ready(event), + None => self.inner.poll(cx), + } } fn handle_pending_inbound_connection( @@ -538,13 +540,9 @@ impl Behaviour { }; peer.evicted = true; }); - tokio::spawn({ - let swarm = self.swarm.clone(); - async move { - if let Err(e) = swarm.disconnect(peer_id).await { - tracing::debug!(%peer_id, %e, "Failed to disconnect evicted peer"); - } - } + self.pending_events.push_back(ToSwarm::CloseConnection { + peer_id, + connection: CloseConnection::All, }); Ok(()) @@ -667,13 +665,9 @@ impl Behaviour { }; peer.evicted = true; }); - tokio::spawn({ - let swarm = self.swarm.clone(); - async move { - if let Err(e) = swarm.disconnect(peer_id).await { - tracing::debug!(%peer_id, %e, "Failed to disconnect evicted peer"); - } - } + self.pending_events.push_back(ToSwarm::CloseConnection { + peer_id, + connection: CloseConnection::All, }); Ok(()) diff --git a/crates/p2p/src/behaviour/builder.rs b/crates/p2p/src/behaviour/builder.rs index 39d41dd4de..556c178b67 100644 --- a/crates/p2p/src/behaviour/builder.rs +++ b/crates/p2p/src/behaviour/builder.rs @@ -80,7 +80,7 @@ impl Builder { self } - pub fn build(self, client: crate::Client) -> BehaviourWithRelayTransport { + pub fn build(self) -> BehaviourWithRelayTransport { let Self { identity, chain_id, @@ -150,7 +150,6 @@ impl Builder { Behaviour { peers: PeerSet::new(cfg.eviction_timeout), cfg, - swarm: client, secret, inner: Inner { relay, @@ -172,6 +171,7 @@ impl Builder { transaction_sync, event_sync, }, + pending_events: Default::default(), }, relay_transport, ) diff --git a/crates/p2p/src/builder.rs b/crates/p2p/src/builder.rs index 6d7d864430..6b88354685 100644 --- a/crates/p2p/src/builder.rs +++ b/crates/p2p/src/builder.rs @@ -50,7 +50,7 @@ impl Builder { let (behaviour, relay_transport) = behaviour_builder .unwrap_or_else(|| Behaviour::builder(keypair.clone(), chain_id, cfg)) - .build(client.clone()); + .build(); let swarm = Swarm::new( transport::create(&keypair, relay_transport), From c1bde90d5b11e6e0a4c2d729f0950adca0155def Mon Sep 17 00:00:00 2001 From: Krzysztof Lis Date: Tue, 1 Oct 2024 23:06:51 +0200 Subject: [PATCH 111/282] test(p2p): decrease timeout in evicted_peer_reconnection And fix a bug with dummy peers being dropped in a loop. --- crates/p2p/src/behaviour.rs | 2 +- crates/p2p/src/tests.rs | 13 +++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/crates/p2p/src/behaviour.rs b/crates/p2p/src/behaviour.rs index 9cffb7ae6c..c4ff91b62f 100644 --- a/crates/p2p/src/behaviour.rs +++ b/crates/p2p/src/behaviour.rs @@ -678,7 +678,7 @@ impl Behaviour { let timeout = if cfg!(test) { // Needs to be large enough to mitigate the possibility of failing explicit // dials in tests, due to implicit dials triggered by automatic bootstrapping. - Duration::from_secs(15) + Duration::from_secs(5) } else { Duration::from_secs(30) }; diff --git a/crates/p2p/src/tests.rs b/crates/p2p/src/tests.rs index e90b68c531..250dda7924 100644 --- a/crates/p2p/src/tests.rs +++ b/crates/p2p/src/tests.rs @@ -636,17 +636,19 @@ async fn evicted_peer_reconnection() { // triggers "automatic bootstrap" so that it does not interfere with the test // https://github.com/libp2p/rust-libp2p/pull/4838 // https://github.com/libp2p/rust-libp2p/blob/d7beb55f672dce54017fa4b30f67ecb8d66b9810/protocols/kad/src/behaviour.rs#L1401). - let twenty_peers = (0..20) + let mut twenty_peers = (0..20) .map(|_| TestPeer::new(cfg.clone())) .collect::>(); - for mut peer in twenty_peers.into_iter() { + for peer in &mut twenty_peers { let addr = peer.start_listening().await.unwrap(); - consume_all_events_forever(peer.event_receiver); - peer1.client.dial(peer.peer_id, addr).await.unwrap(); } + // Let the automatic bootstrap "noise" fade away as in some circumstances it can + // cause additional dials that interrupt the flow of the test + tokio::time::sleep(Duration::from_secs(5)).await; + // Connect peer1 to peer2, then to peer3. Because the outbound connection limit // is 21, peer2 will be evicted when peer1 connects to peer3. peer1 @@ -654,7 +656,6 @@ async fn evicted_peer_reconnection() { .dial(peer2.peer_id, addr2.clone()) .await .unwrap(); - peer1.client.not_useful(peer2.peer_id).await; peer1.client.dial(peer3.peer_id, addr3).await.unwrap(); @@ -677,7 +678,7 @@ async fn evicted_peer_reconnection() { consume_accumulated_events(&mut peer2.event_receiver).await; // peer2 can be reconnected after a timeout. - tokio::time::sleep(Duration::from_secs(40)).await; + tokio::time::sleep(Duration::from_secs(7)).await; peer1 .client From 1b38659a3647b6f0e843de7a64f7192fe0a410b8 Mon Sep 17 00:00:00 2001 From: sistemd Date: Wed, 2 Oct 2024 15:31:53 +0200 Subject: [PATCH 112/282] implement starknet_subscribeEvents --- crates/gateway-types/src/reply.rs | 2 +- crates/pathfinder/src/state/sync.rs | 8 +- crates/rpc/src/dto/event.rs | 38 +- crates/rpc/src/jsonrpc.rs | 7 +- crates/rpc/src/jsonrpc/router.rs | 4 +- crates/rpc/src/jsonrpc/router/subscription.rs | 170 ++-- crates/rpc/src/jsonrpc/websocket/data.rs | 24 +- crates/rpc/src/jsonrpc/websocket/logic.rs | 3 +- crates/rpc/src/lib.rs | 4 +- crates/rpc/src/method.rs | 1 + crates/rpc/src/method/get_events.rs | 149 ++-- crates/rpc/src/method/subscribe_events.rs | 790 ++++++++++++++++++ crates/rpc/src/method/subscribe_new_heads.rs | 27 +- .../method/subscribe_pending_transactions.rs | 11 +- crates/rpc/src/v03/method/get_events.rs | 16 +- crates/rpc/src/v08.rs | 2 + crates/storage/src/connection.rs | 1 - crates/storage/src/connection/event.rs | 115 ++- 18 files changed, 1136 insertions(+), 236 deletions(-) create mode 100644 crates/rpc/src/method/subscribe_events.rs diff --git a/crates/gateway-types/src/reply.rs b/crates/gateway-types/src/reply.rs index bedee96d29..42c4de7e89 100644 --- a/crates/gateway-types/src/reply.rs +++ b/crates/gateway-types/src/reply.rs @@ -23,7 +23,7 @@ pub use transaction::DataAvailabilityMode; /// Used to deserialize replies to Starknet block requests. #[serde_as] -#[derive(Clone, Debug, Deserialize, PartialEq, Eq, serde::Serialize)] +#[derive(Clone, Debug, Deserialize, PartialEq, Eq, serde::Serialize, Default)] #[serde(deny_unknown_fields)] pub struct Block { pub block_hash: BlockHash, diff --git a/crates/pathfinder/src/state/sync.rs b/crates/pathfinder/src/state/sync.rs index 7070d6c5e4..862b46ab18 100644 --- a/crates/pathfinder/src/state/sync.rs +++ b/crates/pathfinder/src/state/sync.rs @@ -990,7 +990,7 @@ async fn l2_update( return Ok(()); } if sender.l2_blocks.receiver_count() > 0 { - if let Err(e) = sender.l2_blocks.send(block.into()) { + if let Err(e) = sender.l2_blocks.send(block.clone().into()) { tracing::error!(error=?e, "Failed to send block over websocket broadcaster."); *websocket_txs = None; return Ok(()); @@ -1004,6 +1004,12 @@ async fn l2_update( // Ignore errors in case nobody is listening. New listeners may subscribe in the // future. .ok(); + notifications + .l2_blocks + .send(block.into()) + // Ignore errors in case nobody is listening. New listeners may subscribe in the + // future. + .ok(); Ok(()) })?; diff --git a/crates/rpc/src/dto/event.rs b/crates/rpc/src/dto/event.rs index 6df5977627..ec5e4d1a1d 100644 --- a/crates/rpc/src/dto/event.rs +++ b/crates/rpc/src/dto/event.rs @@ -3,53 +3,17 @@ use pathfinder_common::{ContractAddress, EventData, EventKey}; use crate::dto; use crate::dto::serialize::{self, SerializeForVersion, Serializer}; -pub struct EventsChunk<'a>(pub &'a crate::method::get_events::types::GetEventsResult); - -pub struct EmittedEvent<'a>(pub &'a crate::method::get_events::types::EmittedEvent); pub struct Event<'a> { pub address: &'a ContractAddress, pub keys: &'a [EventKey], pub data: &'a [EventData], } + pub struct EventContext<'a> { pub keys: &'a [EventKey], pub data: &'a [EventData], } -impl SerializeForVersion for EventsChunk<'_> { - fn serialize(&self, serializer: Serializer) -> Result { - let mut serializer = serializer.serialize_struct()?; - - serializer.serialize_iter( - "events", - self.0.events.len(), - &mut self.0.events.iter().map(EmittedEvent), - )?; - serializer.serialize_optional("continuation_token", self.0.continuation_token.as_ref())?; - - serializer.end() - } -} - -impl SerializeForVersion for EmittedEvent<'_> { - fn serialize(&self, serializer: Serializer) -> Result { - let mut serializer = serializer.serialize_struct()?; - - serializer.flatten(&Event { - address: &self.0.from_address, - keys: &self.0.keys, - data: &self.0.data, - })?; - - serializer - .serialize_optional("block_hash", self.0.block_hash.as_ref().map(dto::BlockHash))?; - serializer.serialize_optional("block_number", self.0.block_number.map(dto::BlockNumber))?; - serializer.serialize_field("transaction_hash", &dto::TxnHash(&self.0.transaction_hash))?; - - serializer.end() - } -} - impl SerializeForVersion for Event<'_> { fn serialize(&self, serializer: Serializer) -> Result { let mut serializer = serializer.serialize_struct()?; diff --git a/crates/rpc/src/jsonrpc.rs b/crates/rpc/src/jsonrpc.rs index 105b885774..b579b90bb6 100644 --- a/crates/rpc/src/jsonrpc.rs +++ b/crates/rpc/src/jsonrpc.rs @@ -11,14 +11,16 @@ use pathfinder_common::{BlockHash, BlockNumber}; pub use request::RpcRequest; pub use response::RpcResponse; #[cfg(test)] -pub use router::handle_json_rpc_socket; +pub use router::{handle_json_rpc_socket, CATCH_UP_BATCH_SIZE}; pub use router::{ rpc_handler, + CatchUp, RpcRouter, RpcRouterBuilder, RpcSubscriptionFlow, SubscriptionMessage, }; +use starknet_gateway_types::reply::Block; use tokio::sync::broadcast; #[derive(Debug, PartialEq, Clone)] @@ -40,6 +42,7 @@ impl RequestId { #[derive(Debug, Clone)] pub struct Notifications { pub block_headers: broadcast::Sender>, + pub l2_blocks: broadcast::Sender>, pub reorgs: broadcast::Sender>, } @@ -54,9 +57,11 @@ pub struct Reorg { impl Default for Notifications { fn default() -> Self { let (block_headers, _) = broadcast::channel(1024); + let (l2_blocks, _) = broadcast::channel(1024); let (reorgs, _) = broadcast::channel(1024); Self { block_headers, + l2_blocks, reorgs, } } diff --git a/crates/rpc/src/jsonrpc/router.rs b/crates/rpc/src/jsonrpc/router.rs index 802c2fbcb6..c3213c453c 100644 --- a/crates/rpc/src/jsonrpc/router.rs +++ b/crates/rpc/src/jsonrpc/router.rs @@ -7,7 +7,9 @@ use axum::response::IntoResponse; use futures::{Future, FutureExt, StreamExt}; use http::HeaderValue; use method::RpcMethodEndpoint; -pub use subscription::{handle_json_rpc_socket, RpcSubscriptionFlow, SubscriptionMessage}; +#[cfg(test)] +pub use subscription::CATCH_UP_BATCH_SIZE; +pub use subscription::{handle_json_rpc_socket, CatchUp, RpcSubscriptionFlow, SubscriptionMessage}; use subscription::{split_ws, RpcSubscriptionEndpoint}; use crate::context::RpcContext; diff --git a/crates/rpc/src/jsonrpc/router/subscription.rs b/crates/rpc/src/jsonrpc/router/subscription.rs index e2dcc4ed11..7bad2beb7a 100644 --- a/crates/rpc/src/jsonrpc/router/subscription.rs +++ b/crates/rpc/src/jsonrpc/router/subscription.rs @@ -16,6 +16,8 @@ use crate::error::ApplicationError; use crate::jsonrpc::{RequestId, RpcError, RpcRequest, RpcResponse}; use crate::{RpcVersion, SubscriptionId}; +pub const CATCH_UP_BATCH_SIZE: u64 = 64; + /// See [`RpcSubscriptionFlow`]. #[axum::async_trait] pub(super) trait RpcSubscriptionEndpoint: Send + Sync { @@ -66,6 +68,12 @@ pub trait RpcSubscriptionFlow: Send + Sync { /// The notification type to be sent to the client. type Notification: crate::dto::serialize::SerializeForVersion + Send + Sync + 'static; + /// Validate the subscription parameters. If the parameters are invalid, + /// return an error. + fn validate_params(_params: &Self::Params) -> Result<(), RpcError> { + Ok(()) + } + /// The block to start streaming from. If the subscription endpoint does not /// support catching up, this method should always return /// [`BlockId::Latest`]. @@ -73,13 +81,15 @@ pub trait RpcSubscriptionFlow: Send + Sync { /// Fetch historical data from the `from` block to the `to` block. The /// range is inclusive on both ends. If there is no historical data in the - /// range, return an empty vec. + /// range, return an empty vec. If the subscription endpoint does not + /// support catching up, this method should always return + /// `Ok(CatchUp::default())`. async fn catch_up( state: &RpcContext, params: &Self::Params, from: BlockNumber, to: BlockNumber, - ) -> Result>, RpcError>; + ) -> Result, RpcError>; /// Subscribe to active updates. async fn subscribe( @@ -89,6 +99,26 @@ pub trait RpcSubscriptionFlow: Send + Sync { ); } +pub struct CatchUp { + pub messages: Vec>, + /// [`SubscriptionMessage`] already contains a `block_number` field, but + /// `messages` can be empty (e.g. due to some filtering logic), so the last + /// block caught up to must be sent separately. + /// + /// If there are no blocks in the block range given to + /// [`RpcSubscriptionFlow::catch_up`], this field should be [`None`]. + pub last_block: Option, +} + +impl Default for CatchUp { + fn default() -> Self { + Self { + messages: Default::default(), + last_block: Default::default(), + } + } +} + #[derive(Debug)] pub struct SubscriptionMessage { /// [`RpcSubscriptionFlow::Notification`] to be sent to the client. @@ -118,8 +148,11 @@ where lock, }: InvokeParams, ) -> Result, RpcError> { - let req = T::Params::deserialize(crate::dto::Value::new(input, router.version)) + let params = T::Params::deserialize(crate::dto::Value::new(input, router.version)) .map_err(|e| RpcError::InvalidParams(e.to_string()))?; + + T::validate_params(¶ms)?; + let tx = SubscriptionSender { subscription_id, subscriptions, @@ -128,9 +161,9 @@ where _phantom: Default::default(), }; - let first_block = T::starting_block(&req); + let first_block = T::starting_block(¶ms); - let mut current_block = match &first_block { + let mut current_block = match first_block { BlockId::Pending => { return Err(RpcError::InvalidParams( "Pending block not supported".to_string(), @@ -138,14 +171,14 @@ where } BlockId::Latest => { // No need to catch up. The code below will subscribe to new blocks. - BlockNumber::MAX + None } - BlockId::Number(_) | BlockId::Hash(_) => { + first_block @ (BlockId::Number(_) | BlockId::Hash(_)) => { // Load the first block number, return an error if it's invalid. let first_block = pathfinder_storage::BlockId::try_from(first_block) .map_err(|e| RpcError::InvalidParams(e.to_string()))?; let storage = router.context.storage.clone(); - tokio::task::spawn_blocking(move || -> Result<_, RpcError> { + let current_block = tokio::task::spawn_blocking(move || -> Result<_, RpcError> { let mut conn = storage.connection().map_err(RpcError::InternalError)?; let db = conn.transaction().map_err(RpcError::InternalError)?; db.block_number(first_block) @@ -153,7 +186,8 @@ where .ok_or_else(|| ApplicationError::BlockNotFound.into()) }) .await - .map_err(|e| RpcError::InternalError(e.into()))?? + .map_err(|e| RpcError::InternalError(e.into()))??; + Some(current_block) } }; @@ -163,49 +197,54 @@ where let _guard = lock.read().await; // Catch up to the latest block in batches of BATCH_SIZE. - const BATCH_SIZE: u64 = 64; - loop { - let messages = match T::catch_up( - &router.context, - &req, - current_block, - current_block + BATCH_SIZE, - ) - .await - { - Ok(messages) => messages, - Err(e) => { - tx.send_err(e, req_id.clone()) + if let Some(current_block) = current_block.as_mut() { + loop { + // -1 because the end is inclusive, otherwise we get batches of + // `CATCH_UP_BATCH_SIZE + 1` which probably doesn't really + // matter, but it's misleading. + let end = *current_block + CATCH_UP_BATCH_SIZE - 1; + let catch_up = + match T::catch_up(&router.context, ¶ms, *current_block, end).await { + Ok(messages) => messages, + Err(e) => { + tx.send_err(e, req_id.clone()) + .await + // Could error if the subscription is closing. + .ok(); + return; + } + }; + let last_block = match catch_up.last_block { + Some(last_block) => last_block, + None => { + // `None` means that there were no messages for the given block range. + break; + } + }; + for msg in catch_up.messages { + if tx + .send(msg.notification, msg.subscription_name) .await - // Could error if the subscription is closing. - .ok(); - return; + .is_err() + { + // Subscription closing. + return; + } } - }; - if messages.is_empty() { - // Caught up. - break; - } - for msg in messages { - if tx - .send(msg.notification, msg.subscription_name) - .await - .is_err() - { - // Subscription closing. - return; + // Increment by 1 because the catch_up range is inclusive. + *current_block = last_block + 1; + if last_block < end { + // This was the last batch. + break; } - current_block = msg.block_number; } - // Increment the current block by 1 because the catch_up range is inclusive. - current_block += 1; } // Subscribe to new blocks. Receive the first subscription message. let (tx1, mut rx1) = mpsc::channel::>(1024); { - let req = req.clone(); - tokio::spawn(T::subscribe(router.context.clone(), req, tx1)); + let params = params.clone(); + tokio::spawn(T::subscribe(router.context.clone(), params, tx1)); } let first_msg = match rx1.recv().await { Some(msg) => msg, @@ -218,29 +257,36 @@ where // Catch up from the latest block that we already caught up to, to the first // block that will be streamed from the subscription. This way we don't miss any // blocks. Because the catch_up range is inclusive, we need to subtract 1 from - // the block number. - if let Some(block_number) = first_msg.block_number.parent() { - let messages = - match T::catch_up(&router.context, &req, current_block, block_number).await { - Ok(messages) => messages, - Err(e) => { - tx.send_err(e, req_id.clone()) - .await - // Could error if the subscription is closing. - .ok(); + // the block number (i.e. take its parent). + let end = first_msg.block_number.parent(); + match (current_block, end) { + (Some(current_block), Some(end)) if current_block <= end => { + let catch_up = + match T::catch_up(&router.context, ¶ms, current_block, end).await { + Ok(messages) => messages, + Err(e) => { + tx.send_err(e, req_id.clone()) + .await + // Could error if the subscription is closing. + .ok(); + return; + } + }; + for msg in catch_up.messages { + if tx + .send(msg.notification, msg.subscription_name) + .await + .is_err() + { + // Subscription closing. return; } - }; - for msg in messages { - if tx - .send(msg.notification, msg.subscription_name) - .await - .is_err() - { - // Subscription closing. - return; } } + _ => { + // Either the range is empty or catch-up is not supported by + // the endpoint (`current_block` is `None`). + } } // Send the first subscription message and then forward the rest. diff --git a/crates/rpc/src/jsonrpc/websocket/data.rs b/crates/rpc/src/jsonrpc/websocket/data.rs index bb7d8a86a8..81574969d1 100644 --- a/crates/rpc/src/jsonrpc/websocket/data.rs +++ b/crates/rpc/src/jsonrpc/websocket/data.rs @@ -2,14 +2,20 @@ use std::sync::Arc; -use pathfinder_common::{EventKey, TransactionHash}; +use pathfinder_common::{ + BlockHash, + BlockNumber, + ContractAddress, + EventData, + EventKey, + TransactionHash, +}; use serde::ser::Error; use serde::{Deserialize, Serialize}; use serde_json::Value; use crate::jsonrpc::router::RpcResponses; use crate::jsonrpc::{RequestId, RpcError, RpcResponse}; -use crate::method::get_events::types::EmittedEvent; #[derive(Debug, Deserialize, Serialize)] #[serde(tag = "kind")] @@ -96,6 +102,20 @@ pub(super) enum ResponseEvent { RpcError(RpcError), } +/// Describes an emitted event returned by starknet_getEvents +#[serde_with::skip_serializing_none] +#[derive(Clone, Debug, Serialize, PartialEq, Eq)] +pub struct EmittedEvent { + pub data: Vec, + pub keys: Vec, + pub from_address: ContractAddress, + /// [`None`] for pending events. + pub block_hash: Option, + /// [`None`] for pending events. + pub block_number: Option, + pub transaction_hash: TransactionHash, +} + #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum TransactionStatusUpdate { diff --git a/crates/rpc/src/jsonrpc/websocket/logic.rs b/crates/rpc/src/jsonrpc/websocket/logic.rs index 9cec1ae66b..92cc17a563 100644 --- a/crates/rpc/src/jsonrpc/websocket/logic.rs +++ b/crates/rpc/src/jsonrpc/websocket/logic.rs @@ -23,7 +23,7 @@ use tokio::sync::broadcast::error::RecvError; use tokio::sync::{broadcast, mpsc, watch}; use tracing::error; -use super::{Params, TransactionStatusUpdate}; +use super::{EmittedEvent, Params, TransactionStatusUpdate}; use crate::error::ApplicationError; use crate::jsonrpc::request::RawParams; use crate::jsonrpc::router::RpcRequestError; @@ -34,7 +34,6 @@ use crate::jsonrpc::websocket::data::{ SubscriptionItem, }; use crate::jsonrpc::{RequestId, RpcError, RpcRequest, RpcRouter}; -use crate::method::get_events::types::EmittedEvent; use crate::{BlockHeader, PendingData}; const SUBSCRIBE_METHOD: &str = "pathfinder_subscribe"; diff --git a/crates/rpc/src/lib.rs b/crates/rpc/src/lib.rs index 28a85a0e73..3e7a4b86b9 100644 --- a/crates/rpc/src/lib.rs +++ b/crates/rpc/src/lib.rs @@ -189,8 +189,8 @@ impl RpcServer { .route("/rpc/v0_7", post(rpc_handler)) .with_state(v07_routes.clone()) // TODO Uncomment once RPC 0.8 is ready. - //.route("/rpc/v0_8", post(rpc_handler).get(rpc_handler)) - //.with_state(v08_routes.clone()) + .route("/rpc/v0_8", post(rpc_handler).get(rpc_handler)) + .with_state(v08_routes.clone()) .route("/rpc/pathfinder/v0.1", post(rpc_handler)) .route("/rpc/pathfinder/v0_1", post(rpc_handler)) .with_state(pathfinder_routes.clone()); diff --git a/crates/rpc/src/method.rs b/crates/rpc/src/method.rs index 388602b728..8814fe746e 100644 --- a/crates/rpc/src/method.rs +++ b/crates/rpc/src/method.rs @@ -23,6 +23,7 @@ pub mod get_transaction_by_hash; pub mod get_transaction_receipt; pub mod get_transaction_status; pub mod simulate_transactions; +pub mod subscribe_events; pub mod subscribe_new_heads; pub mod subscribe_pending_transactions; pub mod syncing; diff --git a/crates/rpc/src/method/get_events.rs b/crates/rpc/src/method/get_events.rs index a2870ac7f3..417ec6e255 100644 --- a/crates/rpc/src/method/get_events.rs +++ b/crates/rpc/src/method/get_events.rs @@ -1,14 +1,27 @@ use std::str::FromStr; use anyhow::Context; -use pathfinder_common::{BlockId, BlockNumber, ContractAddress, EventKey}; +use pathfinder_common::{ + BlockHash, + BlockId, + BlockNumber, + ContractAddress, + EventData, + EventKey, + TransactionHash, +}; use pathfinder_storage::EventFilterError; use starknet_gateway_types::reply::PendingBlock; use tokio::task::JoinHandle; use crate::context::RpcContext; +use crate::dto::serialize::{self, SerializeForVersion, Serializer}; +use crate::dto::{self}; use crate::pending::PendingData; +pub const EVENT_KEY_FILTER_LIMIT: usize = 16; +pub const EVENT_PAGE_SIZE_LIMIT: usize = 1024; + #[derive(Debug)] pub enum GetEventsError { Internal(anyhow::Error), @@ -90,7 +103,7 @@ impl crate::dto::DeserializeForVersion for EventFilter { pub async fn get_events( context: RpcContext, input: GetEventsInput, -) -> Result { +) -> Result { // The [Block::Pending] in ranges makes things quite complicated. This // implementation splits the ranges into the following buckets: // @@ -124,12 +137,15 @@ pub async fn get_events( None => None, }; - if request.keys.len() > pathfinder_storage::EVENT_KEY_FILTER_LIMIT { + if request.keys.len() > EVENT_KEY_FILTER_LIMIT { return Err(GetEventsError::TooManyKeysInFilter { - limit: pathfinder_storage::EVENT_KEY_FILTER_LIMIT, + limit: EVENT_KEY_FILTER_LIMIT, requested: request.keys.len(), }); } + if request.chunk_size > EVENT_PAGE_SIZE_LIMIT { + return Err(GetEventsError::PageSizeTooBig); + } let storage = context.storage.clone(); @@ -154,7 +170,7 @@ pub async fn get_events( // Handle the trivial (1), (2) and (4a) cases. match (&request.from_block, &request.to_block) { (Some(Pending), id) if !matches!(id, Some(Pending) | None) => { - return Ok(types::GetEventsResult { + return Ok(GetEventsResult { events: Vec::new(), continuation_token: None, }); @@ -174,7 +190,7 @@ pub async fn get_events( // `from_block` is larger than or equal to pending block's number if from_block >= &pending.number { - return Ok(types::GetEventsResult { + return Ok(GetEventsResult { events: Vec::new(), continuation_token: None, }); @@ -209,13 +225,11 @@ pub async fn get_events( context.config.get_events_max_uncached_bloom_filters_to_load, ) .map_err(|e| match e { - EventFilterError::PageSizeTooBig(_) => GetEventsError::PageSizeTooBig, - EventFilterError::TooManyMatches => GetEventsError::Custom(e.into()), EventFilterError::Internal(e) => GetEventsError::Internal(e), EventFilterError::PageSizeTooSmall => GetEventsError::Custom(e.into()), })?; - let mut events = types::GetEventsResult { + let mut events = GetEventsResult { events: page.events.into_iter().map(|e| e.into()).collect(), continuation_token: page.continuation_token.map(|token| { ContinuationToken { @@ -294,7 +308,7 @@ fn get_pending_events( request: &EventFilter, pending: &PendingData, continuation_token: Option, -) -> Result { +) -> Result { let current_offset = match continuation_token { Some(continuation_token) => continuation_token.offset_in_block(pending.number)?, None => 0, @@ -329,7 +343,7 @@ fn get_pending_events( ) }; - Ok(types::GetEventsResult { + Ok(GetEventsResult { events, continuation_token, }) @@ -399,7 +413,7 @@ fn map_from_block_to_number( /// returns true if this was the last pending data i.e. `is_last_page`. fn append_pending_events( pending_block: &PendingBlock, - dst: &mut Vec, + dst: &mut Vec, skip: usize, amount: usize, address: Option, @@ -439,7 +453,7 @@ fn append_pending_events( .skip(skip) // We need to take an extra event to determine is_last_page. .take(amount + 1) - .map(|(event, tx_hash)| types::EmittedEvent { + .map(|(event, tx_hash)| EmittedEvent { data: event.data.clone(), keys: event.keys.clone(), from_address: event.from_address, @@ -524,53 +538,71 @@ impl ContinuationToken { #[derive(Debug, Eq, PartialEq)] struct ParseContinuationTokenError; -pub mod types { - use pathfinder_common::{ - BlockHash, - BlockNumber, - ContractAddress, - EventData, - EventKey, - TransactionHash, - }; - use serde::Serialize; - - /// Describes an emitted event returned by starknet_getEvents - #[derive(Clone, Debug, Serialize, PartialEq, Eq)] - #[serde(deny_unknown_fields)] - pub struct EmittedEvent { - pub data: Vec, - pub keys: Vec, - pub from_address: ContractAddress, - /// [None] for pending events. - pub block_hash: Option, - /// [None] for pending events. - pub block_number: Option, - pub transaction_hash: TransactionHash, - } +/// Describes an emitted event returned by starknet_getEvents +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct EmittedEvent { + pub data: Vec, + pub keys: Vec, + pub from_address: ContractAddress, + /// [`None`] for pending events. + pub block_hash: Option, + /// [`None`] for pending events. + pub block_number: Option, + pub transaction_hash: TransactionHash, +} - impl From for EmittedEvent { - fn from(event: pathfinder_storage::EmittedEvent) -> Self { - Self { - data: event.data, - keys: event.keys, - from_address: event.from_address, - block_hash: Some(event.block_hash), - block_number: Some(event.block_number), - transaction_hash: event.transaction_hash, - } +impl From for EmittedEvent { + fn from(event: pathfinder_storage::EmittedEvent) -> Self { + Self { + data: event.data, + keys: event.keys, + from_address: event.from_address, + block_hash: Some(event.block_hash), + block_number: Some(event.block_number), + transaction_hash: event.transaction_hash, } } +} + +// Result type for starknet_getEvents +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct GetEventsResult { + pub events: Vec, + /// Offset, measured in events, which points to the chunk that follows + /// currently requested chunk (`events`) + pub continuation_token: Option, +} + +impl SerializeForVersion for EmittedEvent { + fn serialize(&self, serializer: Serializer) -> Result { + let mut serializer = serializer.serialize_struct()?; + + serializer.serialize_iter("data", self.data.len(), &mut self.data.iter().map(|d| d.0))?; + serializer.serialize_iter("keys", self.keys.len(), &mut self.keys.iter().map(|d| d.0))?; + serializer.serialize_field("from_address", &dto::Address(&self.from_address))?; + serializer + .serialize_optional("block_hash", self.block_hash.as_ref().map(dto::BlockHash))?; + serializer.serialize_optional("block_number", self.block_number.map(dto::BlockNumber))?; + serializer.serialize_field("transaction_hash", &dto::TxnHash(&self.transaction_hash))?; + + serializer.end() + } +} + +impl SerializeForVersion for &'_ EmittedEvent { + fn serialize(&self, serializer: Serializer) -> Result { + (*self).serialize(serializer) + } +} + +impl SerializeForVersion for GetEventsResult { + fn serialize(&self, serializer: Serializer) -> Result { + let mut serializer = serializer.serialize_struct()?; + + serializer.serialize_iter("events", self.events.len(), &mut self.events.iter())?; + serializer.serialize_optional("continuation_token", self.continuation_token.as_ref())?; - // Result type for starknet_getEvents - #[serde_with::skip_serializing_none] - #[derive(Clone, Debug, Serialize, PartialEq, Eq)] - #[serde(deny_unknown_fields)] - pub struct GetEventsResult { - pub events: Vec, - /// Offset, measured in events, which points to the chunk that follows - /// currently requested chunk (`events`) - pub continuation_token: Option, + serializer.end() } } @@ -581,8 +613,7 @@ mod tests { use pretty_assertions_sorted::assert_eq; use serde_json::json; - use super::types::{EmittedEvent, GetEventsResult}; - use super::*; + use super::{EmittedEvent, GetEventsResult, *}; use crate::dto::DeserializeForVersion; use crate::RpcVersion; @@ -796,7 +827,7 @@ mod tests { async fn get_events_with_too_many_keys_in_filter() { let (context, _) = setup(); - let limit = pathfinder_storage::EVENT_KEY_FILTER_LIMIT; + let limit = EVENT_KEY_FILTER_LIMIT; let keys = [vec![event_key!("01")]] .iter() diff --git a/crates/rpc/src/method/subscribe_events.rs b/crates/rpc/src/method/subscribe_events.rs new file mode 100644 index 0000000000..2d485decf0 --- /dev/null +++ b/crates/rpc/src/method/subscribe_events.rs @@ -0,0 +1,790 @@ +use std::sync::Arc; + +use axum::async_trait; +use pathfinder_common::{BlockId, BlockNumber, ContractAddress, EventKey}; +use tokio::sync::mpsc; + +use super::get_events::EVENT_KEY_FILTER_LIMIT; +use super::REORG_SUBSCRIPTION_NAME; +use crate::context::RpcContext; +use crate::error::ApplicationError; +use crate::jsonrpc::{CatchUp, RpcError, RpcSubscriptionFlow, SubscriptionMessage}; +use crate::method::get_events::EmittedEvent; +use crate::Reorg; + +pub struct SubscribeEvents; + +#[derive(Debug, Clone, Default)] +pub struct Params { + from_address: Option, + keys: Option>>, + block: Option, +} + +impl crate::dto::DeserializeForVersion for Option { + fn deserialize(value: crate::dto::Value) -> Result { + if value.is_null() { + // Params are optional. + return Ok(None); + } + value.deserialize_map(|value| { + Ok(Some(Params { + from_address: value + .deserialize_optional("from_address")? + .map(ContractAddress), + keys: value.deserialize_optional_array("keys", |value| { + value.deserialize_array(|value| Ok(EventKey(value.deserialize()?))) + })?, + block: value.deserialize_optional_serde("block")?, + })) + }) + } +} + +#[derive(Debug)] +pub enum Notification { + EmittedEvent(crate::method::get_events::EmittedEvent), + Reorg(Arc), +} + +impl crate::dto::serialize::SerializeForVersion for Notification { + fn serialize( + &self, + serializer: crate::dto::serialize::Serializer, + ) -> Result { + match self { + Notification::EmittedEvent(event) => event.serialize(serializer), + Notification::Reorg(reorg) => reorg.serialize(serializer), + } + } +} + +const SUBSCRIPTION_NAME: &str = "starknet_subscriptionEvents"; + +#[async_trait] +impl RpcSubscriptionFlow for SubscribeEvents { + type Params = Option; + type Notification = Notification; + + fn validate_params(params: &Self::Params) -> Result<(), RpcError> { + if let Some(params) = params { + if let Some(keys) = ¶ms.keys { + if keys.len() > EVENT_KEY_FILTER_LIMIT { + return Err(RpcError::ApplicationError( + ApplicationError::TooManyKeysInFilter { + limit: EVENT_KEY_FILTER_LIMIT, + requested: keys.len(), + }, + )); + } + } + } + Ok(()) + } + + fn starting_block(params: &Self::Params) -> BlockId { + params + .as_ref() + .and_then(|req| req.block) + .unwrap_or(BlockId::Latest) + } + + async fn catch_up( + state: &RpcContext, + params: &Self::Params, + from: BlockNumber, + to: BlockNumber, + ) -> Result, RpcError> { + let params = params.clone().unwrap_or_default(); + let storage = state.storage.clone(); + let (events, last_block) = tokio::task::spawn_blocking(move || -> Result<_, RpcError> { + let mut conn = storage.connection().map_err(RpcError::InternalError)?; + let db = conn.transaction().map_err(RpcError::InternalError)?; + db.events_in_range( + from, + to, + params.from_address, + params.keys.unwrap_or_default(), + ) + .map_err(RpcError::InternalError) + }) + .await + .map_err(|e| RpcError::InternalError(e.into()))??; + let messages = events + .into_iter() + .map(|event| { + let block_number = event.block_number; + SubscriptionMessage { + notification: Notification::EmittedEvent(event.into()), + block_number, + subscription_name: SUBSCRIPTION_NAME, + } + }) + .collect(); + Ok(CatchUp { + messages, + last_block, + }) + } + + async fn subscribe( + state: RpcContext, + params: Self::Params, + tx: mpsc::Sender>, + ) { + let mut blocks = state.notifications.l2_blocks.subscribe(); + let mut reorgs = state.notifications.reorgs.subscribe(); + let params = params.unwrap_or_default(); + let keys = params.keys.unwrap_or_default(); + let key_filter_is_empty = keys.iter().flatten().count() == 0; + loop { + tokio::select! { + reorg = reorgs.recv() => { + match reorg { + Ok(reorg) => { + let block_number = reorg.first_block_number; + if tx.send(SubscriptionMessage { + notification: Notification::Reorg(reorg), + block_number, + subscription_name: REORG_SUBSCRIPTION_NAME, + }).await.is_err() { + break; + } + } + Err(e) => { + tracing::debug!( + "Error receiving reorg from notifications channel, node might be \ + lagging: {:?}", + e + ); + break; + } + } + } + block = blocks.recv() => { + match block { + Ok(block) => { + let block_number = block.block_number; + let block_hash = block.block_hash; + for (receipt, events) in block.transaction_receipts.iter() { + for event in events { + // Check if the event matches the filter. + if let Some(from_address) = params.from_address { + if event.from_address != from_address { + continue; + } + } + let matches_keys = if key_filter_is_empty { + true + } else if event.keys.len() < keys.len() { + false + } else { + event + .keys + .iter() + .zip(keys.iter()) + .all(|(key, filter)| filter.is_empty() || filter.contains(key)) + }; + if !matches_keys { + continue; + } + if tx.send(SubscriptionMessage { + notification: Notification::EmittedEvent(EmittedEvent { + data: event.data.clone(), + keys: event.keys.clone(), + from_address: event.from_address, + block_hash: Some(block_hash), + block_number: Some(block_number), + transaction_hash: receipt.transaction_hash, + }), + block_number, + subscription_name: SUBSCRIPTION_NAME, + }).await.is_err() { + break; + } + } + } + } + Err(e) => { + tracing::debug!( + "Error receiving block header from notifications channel, node might be \ + lagging: {:?}", + e + ); + break; + } + } + } + } + } + } +} + +#[cfg(test)] +mod tests { + use std::time::Duration; + + use axum::extract::ws::Message; + use pathfinder_common::event::Event; + use pathfinder_common::receipt::Receipt; + use pathfinder_common::transaction::{Transaction, TransactionVariant}; + use pathfinder_common::{ + felt, + BlockHash, + BlockHeader, + BlockNumber, + ChainId, + ContractAddress, + EventData, + EventKey, + TransactionHash, + TransactionIndex, + }; + use pathfinder_crypto::Felt; + use pathfinder_storage::StorageBuilder; + use starknet_gateway_client::Client; + use starknet_gateway_types::reply::Block; + use tokio::sync::mpsc; + + use crate::context::{RpcConfig, RpcContext}; + use crate::jsonrpc::{handle_json_rpc_socket, RpcRouter}; + use crate::pending::PendingWatcher; + use crate::v02::types::syncing::Syncing; + use crate::{v08, Notifications, Reorg, SyncState}; + + #[tokio::test] + async fn no_filtering() { + let num_blocks = 2000; + let router = setup(num_blocks).await; + let (sender_tx, mut sender_rx) = mpsc::channel(1024); + let (receiver_tx, receiver_rx) = mpsc::channel(1024); + handle_json_rpc_socket(router.clone(), sender_tx, receiver_rx); + let params = serde_json::json!( + {"block": {"block_number": 0}} + ); + receiver_tx + .send(Ok(Message::Text( + serde_json::json!({ + "jsonrpc": "2.0", + "id": 1, + "method": "starknet_subscribeEvents", + "params": params + }) + .to_string(), + ))) + .await + .unwrap(); + let res = sender_rx.recv().await.unwrap().unwrap(); + let subscription_id = match res { + Message::Text(json) => { + let json: serde_json::Value = serde_json::from_str(&json).unwrap(); + assert_eq!(json["jsonrpc"], "2.0"); + assert_eq!(json["id"], 1); + json["result"]["subscription_id"].as_u64().unwrap() + } + _ => panic!("Expected text message"), + }; + for i in 0..num_blocks { + let expected = sample_event_message(i, subscription_id); + let event = sender_rx.recv().await.unwrap().unwrap(); + let json: serde_json::Value = match event { + Message::Text(json) => serde_json::from_str(&json).unwrap(), + _ => panic!("Expected text message"), + }; + assert_eq!(json, expected); + } + for i in 0..10 { + retry(|| { + router + .context + .notifications + .l2_blocks + .send(sample_block(i).into()) + }) + .await + .unwrap(); + let expected = sample_event_message(i, subscription_id); + let event = sender_rx.recv().await.unwrap().unwrap(); + let json: serde_json::Value = match event { + Message::Text(json) => serde_json::from_str(&json).unwrap(), + _ => panic!("Expected text message"), + }; + assert_eq!(json, expected); + } + assert!(sender_rx.is_empty()); + } + + #[tokio::test] + async fn filter_from_address() { + let router = setup(2000).await; + let (sender_tx, mut sender_rx) = mpsc::channel(1024); + let (receiver_tx, receiver_rx) = mpsc::channel(1024); + handle_json_rpc_socket(router.clone(), sender_tx, receiver_rx); + let params = serde_json::json!( + { + "block": {"block_number": 0}, + "from_address": "0x90", + } + ); + receiver_tx + .send(Ok(Message::Text( + serde_json::json!({ + "jsonrpc": "2.0", + "id": 1, + "method": "starknet_subscribeEvents", + "params": params + }) + .to_string(), + ))) + .await + .unwrap(); + let res = sender_rx.recv().await.unwrap().unwrap(); + let subscription_id = match res { + Message::Text(json) => { + let json: serde_json::Value = serde_json::from_str(&json).unwrap(); + assert_eq!(json["jsonrpc"], "2.0"); + assert_eq!(json["id"], 1); + json["result"]["subscription_id"].as_u64().unwrap() + } + _ => panic!("Expected text message"), + }; + let expected = sample_event_message(0x90, subscription_id); + let event = sender_rx.recv().await.unwrap().unwrap(); + let json: serde_json::Value = match event { + Message::Text(json) => serde_json::from_str(&json).unwrap(), + _ => panic!("Expected text message"), + }; + assert_eq!(json, expected); + retry(|| { + router + .context + .notifications + .l2_blocks + .send(sample_block(0x8f).into()) + }) + .await + .unwrap(); + router + .context + .notifications + .l2_blocks + .send(sample_block(0x90).into()) + .unwrap(); + let expected = sample_event_message(0x90, subscription_id); + let event = sender_rx.recv().await.unwrap().unwrap(); + let json: serde_json::Value = match event { + Message::Text(json) => serde_json::from_str(&json).unwrap(), + _ => panic!("Expected text message"), + }; + assert_eq!(json, expected); + assert!(sender_rx.is_empty()); + } + + #[tokio::test] + async fn filter_keys() { + let router = setup(2000).await; + let (sender_tx, mut sender_rx) = mpsc::channel(1024); + let (receiver_tx, receiver_rx) = mpsc::channel(1024); + handle_json_rpc_socket(router.clone(), sender_tx, receiver_rx); + let params = serde_json::json!( + { + "block": {"block_number": 0}, + "keys": [["0x90"], [], ["0x92", "0x93"]], + } + ); + receiver_tx + .send(Ok(Message::Text( + serde_json::json!({ + "jsonrpc": "2.0", + "id": 1, + "method": "starknet_subscribeEvents", + "params": params + }) + .to_string(), + ))) + .await + .unwrap(); + let res = sender_rx.recv().await.unwrap().unwrap(); + let subscription_id = match res { + Message::Text(json) => { + let json: serde_json::Value = serde_json::from_str(&json).unwrap(); + assert_eq!(json["jsonrpc"], "2.0"); + assert_eq!(json["id"], 1); + json["result"]["subscription_id"].as_u64().unwrap() + } + _ => panic!("Expected text message"), + }; + let expected = sample_event_message(0x90, subscription_id); + let event = sender_rx.recv().await.unwrap().unwrap(); + let json: serde_json::Value = match event { + Message::Text(json) => serde_json::from_str(&json).unwrap(), + _ => panic!("Expected text message"), + }; + assert_eq!(json, expected); + retry(|| { + router + .context + .notifications + .l2_blocks + .send(sample_block(0x8f).into()) + }) + .await + .unwrap(); + router + .context + .notifications + .l2_blocks + .send(sample_block(0x90).into()) + .unwrap(); + let expected = sample_event_message(0x90, subscription_id); + let event = sender_rx.recv().await.unwrap().unwrap(); + let json: serde_json::Value = match event { + Message::Text(json) => serde_json::from_str(&json).unwrap(), + _ => panic!("Expected text message"), + }; + assert_eq!(json, expected); + assert!(sender_rx.is_empty()); + } + + #[tokio::test] + async fn filter_from_address_and_keys() { + let router = setup(2000).await; + let (sender_tx, mut sender_rx) = mpsc::channel(1024); + let (receiver_tx, receiver_rx) = mpsc::channel(1024); + handle_json_rpc_socket(router.clone(), sender_tx, receiver_rx); + let params = serde_json::json!( + { + "block": {"block_number": 0}, + "from_address": "0x90", + "keys": [["0x90"], [], ["0x92", "0x93"]], + } + ); + receiver_tx + .send(Ok(Message::Text( + serde_json::json!({ + "jsonrpc": "2.0", + "id": 1, + "method": "starknet_subscribeEvents", + "params": params + }) + .to_string(), + ))) + .await + .unwrap(); + let res = sender_rx.recv().await.unwrap().unwrap(); + let subscription_id = match res { + Message::Text(json) => { + let json: serde_json::Value = serde_json::from_str(&json).unwrap(); + assert_eq!(json["jsonrpc"], "2.0"); + assert_eq!(json["id"], 1); + json["result"]["subscription_id"].as_u64().unwrap() + } + _ => panic!("Expected text message"), + }; + let expected = sample_event_message(0x90, subscription_id); + let event = sender_rx.recv().await.unwrap().unwrap(); + let json: serde_json::Value = match event { + Message::Text(json) => serde_json::from_str(&json).unwrap(), + _ => panic!("Expected text message"), + }; + assert_eq!(json, expected); + retry(|| { + router + .context + .notifications + .l2_blocks + .send(sample_block(0x8f).into()) + }) + .await + .unwrap(); + router + .context + .notifications + .l2_blocks + .send(sample_block(0x90).into()) + .unwrap(); + let expected = sample_event_message(0x90, subscription_id); + let event = sender_rx.recv().await.unwrap().unwrap(); + let json: serde_json::Value = match event { + Message::Text(json) => serde_json::from_str(&json).unwrap(), + _ => panic!("Expected text message"), + }; + assert_eq!(json, expected); + assert!(sender_rx.is_empty()); + } + + #[tokio::test] + async fn too_many_keys_filter() { + let router = setup(2000).await; + let (sender_tx, mut sender_rx) = mpsc::channel(1024); + let (receiver_tx, receiver_rx) = mpsc::channel(1024); + handle_json_rpc_socket(router.clone(), sender_tx, receiver_rx); + let params = serde_json::json!( + { + "block": {"block_number": 0}, + "from_address": "0x90", + "keys": [ + ["0x91"], + ["0x92"], + ["0x93"], + ["0x94"], + ["0x95"], + ["0x96"], + ["0x97"], + ["0x98"], + ["0x99"], + ["0x9a"], + ["0x9b"], + ["0x9c"], + ["0x9d"], + ["0x9e"], + ["0x9f"], + ["0xa0"], + ["0xa1"], + ], + } + ); + receiver_tx + .send(Ok(Message::Text( + serde_json::json!({ + "jsonrpc": "2.0", + "id": 1, + "method": "starknet_subscribeEvents", + "params": params + }) + .to_string(), + ))) + .await + .unwrap(); + let res = sender_rx.recv().await.unwrap().unwrap(); + match res { + Message::Text(json) => { + let json: serde_json::Value = serde_json::from_str(&json).unwrap(); + assert_eq!( + json, + serde_json::json!({ + "jsonrpc": "2.0", + "id": 1, + "error": { + "code": 34, + "data": { "limit": 16, "requested": 17 }, + "message": "Too many keys provided in a filter" + } + }), + ); + } + _ => panic!("Expected text message"), + } + } + + #[tokio::test] + async fn reorg() { + let router = setup(0).await; + let (sender_tx, mut sender_rx) = mpsc::channel(1024); + let (receiver_tx, receiver_rx) = mpsc::channel(1024); + handle_json_rpc_socket(router.clone(), sender_tx, receiver_rx); + receiver_tx + .send(Ok(Message::Text( + serde_json::json!({ + "jsonrpc": "2.0", + "id": 1, + "method": "starknet_subscribeEvents", + }) + .to_string(), + ))) + .await + .unwrap(); + let res = sender_rx.recv().await.unwrap().unwrap(); + let subscription_id = match res { + Message::Text(json) => { + let json: serde_json::Value = serde_json::from_str(&json).unwrap(); + assert_eq!(json["jsonrpc"], "2.0"); + assert_eq!(json["id"], 1); + json["result"]["subscription_id"].as_u64().unwrap() + } + _ => panic!("Expected text message"), + }; + retry(|| { + router.context.notifications.reorgs.send( + Reorg { + first_block_number: BlockNumber::new_or_panic(1), + first_block_hash: BlockHash(felt!("0x1")), + last_block_number: BlockNumber::new_or_panic(2), + last_block_hash: BlockHash(felt!("0x2")), + } + .into(), + ) + }) + .await + .unwrap(); + let res = sender_rx.recv().await.unwrap().unwrap(); + let json: serde_json::Value = match res { + Message::Text(json) => serde_json::from_str(&json).unwrap(), + _ => panic!("Expected text message"), + }; + assert_eq!( + json, + serde_json::json!({ + "jsonrpc": "2.0", + "method": "starknet_subscriptionReorg", + "params": { + "result": { + "first_block_hash": "0x1", + "first_block_number": 1, + "last_block_hash": "0x2", + "last_block_number": 2 + }, + "subscription_id": subscription_id + } + }) + ); + } + + async fn setup(num_blocks: u64) -> RpcRouter { + let storage = StorageBuilder::in_memory().unwrap(); + tokio::task::spawn_blocking({ + let storage = storage.clone(); + move || { + let mut conn = storage.connection().unwrap(); + let db = conn.transaction().unwrap(); + for i in 0..num_blocks { + let header = sample_header(i); + db.insert_block_header(&header).unwrap(); + let tx = sample_transaction(i); + let receipt = sample_receipt(i); + let event = sample_event(i); + db.insert_transaction_data( + BlockNumber::new_or_panic(i), + &[(tx, receipt)], + Some(&[vec![event]]), + ) + .unwrap(); + } + db.commit().unwrap(); + } + }) + .await + .unwrap(); + let (_, pending_data) = tokio::sync::watch::channel(Default::default()); + let notifications = Notifications::default(); + let ctx = RpcContext { + cache: Default::default(), + storage, + execution_storage: StorageBuilder::in_memory().unwrap(), + pending_data: PendingWatcher::new(pending_data), + sync_status: SyncState { + status: Syncing::False(false).into(), + } + .into(), + chain_id: ChainId::MAINNET, + sequencer: Client::mainnet(Duration::from_secs(10)), + websocket: None, + notifications, + config: RpcConfig { + batch_concurrency_limit: 64.try_into().unwrap(), + get_events_max_blocks_to_scan: 1024.try_into().unwrap(), + get_events_max_uncached_bloom_filters_to_load: 1024.try_into().unwrap(), + custom_versioned_constants: None, + }, + }; + v08::register_routes().build(ctx) + } + + fn sample_header(block_number: u64) -> BlockHeader { + BlockHeader { + hash: BlockHash(Felt::from_u64(block_number)), + number: BlockNumber::new_or_panic(block_number), + ..Default::default() + } + } + + fn sample_event(block_number: u64) -> Event { + Event { + data: vec![ + EventData(Felt::from_u64(block_number)), + EventData(Felt::from_u64(block_number + 1)), + EventData(Felt::from_u64(block_number + 2)), + ], + from_address: ContractAddress(Felt::from_u64(block_number)), + keys: vec![ + EventKey(Felt::from_u64(block_number)), + EventKey(Felt::from_u64(block_number + 1)), + EventKey(Felt::from_u64(block_number + 2)), + ], + } + } + + fn sample_transaction(block_number: u64) -> Transaction { + Transaction { + hash: TransactionHash(Felt::from_u64(block_number)), + variant: TransactionVariant::DeclareV0(Default::default()), + } + } + + fn sample_receipt(block_number: u64) -> Receipt { + Receipt { + transaction_hash: TransactionHash(Felt::from_u64(block_number)), + transaction_index: TransactionIndex::new_or_panic(0), + ..Default::default() + } + } + + fn sample_block(block_number: u64) -> Block { + Block { + block_hash: BlockHash(Felt::from_u64(block_number)), + block_number: BlockNumber::new_or_panic(block_number), + transaction_receipts: vec![( + sample_receipt(block_number), + vec![sample_event(block_number)], + )], + transactions: vec![sample_transaction(block_number)], + ..Default::default() + } + } + + fn sample_event_message(block_number: u64, subscription_id: u64) -> serde_json::Value { + serde_json::json!({ + "jsonrpc":"2.0", + "method":"starknet_subscriptionEvents", + "params": { + "result": { + "keys": [ + Felt::from_u64(block_number), + Felt::from_u64(block_number + 1), + Felt::from_u64(block_number + 2), + ], + "data": [ + Felt::from_u64(block_number), + Felt::from_u64(block_number + 1), + Felt::from_u64(block_number + 2), + ], + "from_address": Felt::from_u64(block_number), + "block_number": block_number, + "block_hash": Felt::from_u64(block_number), + "transaction_hash": Felt::from_u64(block_number), + }, + "subscription_id": subscription_id + } + }) + } + + // Retry to let other tasks make progress. + async fn retry(cb: impl Fn() -> Result) -> Result + where + E: std::fmt::Debug, + { + const RETRIES: u64 = 25; + for i in 0..RETRIES { + match cb() { + Ok(result) => return Ok(result), + Err(e) => { + if i == RETRIES - 1 { + return Err(e); + } + tokio::time::sleep(Duration::from_secs(i)).await; + } + } + } + unreachable!() + } +} diff --git a/crates/rpc/src/method/subscribe_new_heads.rs b/crates/rpc/src/method/subscribe_new_heads.rs index 61f4c65705..51e411c712 100644 --- a/crates/rpc/src/method/subscribe_new_heads.rs +++ b/crates/rpc/src/method/subscribe_new_heads.rs @@ -6,7 +6,7 @@ use tokio::sync::mpsc; use super::REORG_SUBSCRIPTION_NAME; use crate::context::RpcContext; -use crate::jsonrpc::{RpcError, RpcSubscriptionFlow, SubscriptionMessage}; +use crate::jsonrpc::{CatchUp, RpcError, RpcSubscriptionFlow, SubscriptionMessage}; use crate::Reorg; pub struct SubscribeNewHeads; @@ -19,7 +19,7 @@ pub struct Params { impl crate::dto::DeserializeForVersion for Option { fn deserialize(value: crate::dto::Value) -> Result { if value.is_null() { - // It is OK to omit the params. + // Params are optional. return Ok(None); } value.deserialize_map(|value| { @@ -67,7 +67,7 @@ impl RpcSubscriptionFlow for SubscribeNewHeads { _params: &Self::Params, from: BlockNumber, to: BlockNumber, - ) -> Result>, RpcError> { + ) -> Result, RpcError> { let storage = state.storage.clone(); let headers = tokio::task::spawn_blocking(move || -> Result<_, RpcError> { let mut conn = storage.connection().map_err(RpcError::InternalError)?; @@ -76,7 +76,7 @@ impl RpcSubscriptionFlow for SubscribeNewHeads { }) .await .map_err(|e| RpcError::InternalError(e.into()))??; - Ok(headers + let messages: Vec<_> = headers .into_iter() .map(|header| { let block_number = header.number; @@ -86,7 +86,12 @@ impl RpcSubscriptionFlow for SubscribeNewHeads { subscription_name: SUBSCRIPTION_NAME, } }) - .collect()) + .collect(); + let last_block = messages.last().map(|m| m.block_number); + Ok(CatchUp { + messages, + last_block, + }) } async fn subscribe( @@ -159,7 +164,7 @@ mod tests { use tokio::sync::mpsc; use crate::context::{RpcConfig, RpcContext}; - use crate::jsonrpc::{handle_json_rpc_socket, RpcResponse, RpcRouter}; + use crate::jsonrpc::{handle_json_rpc_socket, RpcResponse, RpcRouter, CATCH_UP_BATCH_SIZE}; use crate::pending::PendingWatcher; use crate::v02::types::syncing::Syncing; use crate::{v08, Notifications, Reorg, SubscriptionId, SyncState}; @@ -171,12 +176,14 @@ mod tests { #[tokio::test] async fn happy_path_with_historic_blocks_no_batching() { - happy_path_test(10).await; + happy_path_test(CATCH_UP_BATCH_SIZE - 5).await; } #[tokio::test] - async fn happy_path_with_historic_blocks_batching_edge_case() { - happy_path_test(128).await; + async fn happy_path_with_historic_blocks_batching_edge_cases() { + happy_path_test(2 * CATCH_UP_BATCH_SIZE).await; + happy_path_test(2 * (CATCH_UP_BATCH_SIZE - 1)).await; + happy_path_test(2 * (CATCH_UP_BATCH_SIZE + 1)).await; } #[tokio::test] @@ -589,7 +596,7 @@ mod tests { "block_number": block_number, "l1_da_mode": "CALLDATA", "l1_data_gas_price": { "price_in_fri": "0x0", "price_in_wei": "0x0" }, - "l1_gas_price":{ "price_in_fri": "0x0", "price_in_wei": "0x0" }, + "l1_gas_price": { "price_in_fri": "0x0", "price_in_wei": "0x0" }, "new_root": "0x0", "parent_hash": "0x0", "sequencer_address": "0x0", diff --git a/crates/rpc/src/method/subscribe_pending_transactions.rs b/crates/rpc/src/method/subscribe_pending_transactions.rs index 8276bf3aae..5939c557fe 100644 --- a/crates/rpc/src/method/subscribe_pending_transactions.rs +++ b/crates/rpc/src/method/subscribe_pending_transactions.rs @@ -6,7 +6,7 @@ use pathfinder_common::{BlockId, BlockNumber, ContractAddress, TransactionHash}; use tokio::sync::mpsc; use crate::context::RpcContext; -use crate::jsonrpc::{RpcError, RpcSubscriptionFlow, SubscriptionMessage}; +use crate::jsonrpc::{CatchUp, RpcError, RpcSubscriptionFlow, SubscriptionMessage}; pub struct SubscribePendingTransactions; @@ -64,7 +64,7 @@ impl RpcSubscriptionFlow for SubscribePendingTransactions { type Notification = Notification; fn starting_block(_params: &Self::Params) -> BlockId { - // Rollback is not supported. + // Catch-up is not supported. BlockId::Latest } @@ -73,8 +73,9 @@ impl RpcSubscriptionFlow for SubscribePendingTransactions { _params: &Self::Params, _from: BlockNumber, _to: BlockNumber, - ) -> Result>, RpcError> { - Ok(vec![]) + ) -> Result, RpcError> { + // Catch-up is not supported. + Ok(Default::default()) } async fn subscribe( @@ -84,7 +85,7 @@ impl RpcSubscriptionFlow for SubscribePendingTransactions { ) { let params = params.unwrap_or_default(); let mut pending_data = state.pending_data.0.clone(); - // Last block sent to the subscriber. Initial value doesn't really matter + // Last block sent to the subscriber. Initial value doesn't really matter. let mut last_block = BlockNumber::GENESIS; // Hashes of transactions that have already been sent to the subscriber, as part // of `last_block` block. It is necessary to keep track of this because the diff --git a/crates/rpc/src/v03/method/get_events.rs b/crates/rpc/src/v03/method/get_events.rs index f90deb6345..2ec3867dc8 100644 --- a/crates/rpc/src/v03/method/get_events.rs +++ b/crates/rpc/src/v03/method/get_events.rs @@ -8,6 +8,7 @@ use starknet_gateway_types::reply::PendingBlock; use tokio::task::JoinHandle; use crate::context::RpcContext; +use crate::method::get_events::{EVENT_KEY_FILTER_LIMIT, EVENT_PAGE_SIZE_LIMIT}; use crate::pending::PendingData; #[derive(Debug)] @@ -113,12 +114,15 @@ pub async fn get_events( None => None, }; - if request.keys.len() > pathfinder_storage::EVENT_KEY_FILTER_LIMIT { + if request.keys.len() > EVENT_KEY_FILTER_LIMIT { return Err(GetEventsError::TooManyKeysInFilter { - limit: pathfinder_storage::EVENT_KEY_FILTER_LIMIT, + limit: EVENT_KEY_FILTER_LIMIT, requested: request.keys.len(), }); } + if request.chunk_size > EVENT_PAGE_SIZE_LIMIT { + return Err(GetEventsError::PageSizeTooBig); + } let storage = context.storage.clone(); @@ -182,8 +186,6 @@ pub async fn get_events( context.config.get_events_max_uncached_bloom_filters_to_load, ) .map_err(|e| match e { - EventFilterError::PageSizeTooBig(_) => GetEventsError::PageSizeTooBig, - EventFilterError::TooManyMatches => GetEventsError::Custom(e.into()), EventFilterError::Internal(e) => GetEventsError::Internal(e), EventFilterError::PageSizeTooSmall => GetEventsError::Custom(e.into()), })?; @@ -515,9 +517,9 @@ mod types { pub data: Vec, pub keys: Vec, pub from_address: ContractAddress, - /// [None] for pending events. + /// [`None`] for pending events. pub block_hash: Option, - /// [None] for pending events. + /// [`None`] for pending events. pub block_number: Option, pub transaction_hash: TransactionHash, } @@ -766,7 +768,7 @@ mod tests { async fn get_events_with_too_many_keys_in_filter() { let (context, _) = setup(); - let limit = pathfinder_storage::EVENT_KEY_FILTER_LIMIT; + let limit = EVENT_KEY_FILTER_LIMIT; let keys = [vec![event_key!("01")]] .iter() diff --git a/crates/rpc/src/v08.rs b/crates/rpc/src/v08.rs index 6dfb026ce1..9af633102d 100644 --- a/crates/rpc/src/v08.rs +++ b/crates/rpc/src/v08.rs @@ -1,4 +1,5 @@ use crate::jsonrpc::{RpcRouter, RpcRouterBuilder}; +use crate::method::subscribe_events::SubscribeEvents; use crate::method::subscribe_new_heads::SubscribeNewHeads; use crate::method::subscribe_pending_transactions::SubscribePendingTransactions; @@ -8,5 +9,6 @@ pub fn register_routes() -> RpcRouterBuilder { .register("starknet_syncing", crate::method::syncing) .register("starknet_subscribeNewHeads", SubscribeNewHeads) .register("starknet_subscribePendingTransactions", SubscribePendingTransactions) + .register("starknet_subscribeEvents", SubscribeEvents) .register("starknet_specVersion", || "0.8.0-rc0") } diff --git a/crates/storage/src/connection.rs b/crates/storage/src/connection.rs index b46f035a7a..60159f265e 100644 --- a/crates/storage/src/connection.rs +++ b/crates/storage/src/connection.rs @@ -16,7 +16,6 @@ pub use event::{ EventFilter, EventFilterError, PageOfEvents, - KEY_FILTER_LIMIT as EVENT_KEY_FILTER_LIMIT, PAGE_SIZE_LIMIT as EVENT_PAGE_SIZE_LIMIT, }; use pathfinder_common::event::Event; diff --git a/crates/storage/src/connection/event.rs b/crates/storage/src/connection/event.rs index ba528dd5fa..52fce6f21c 100644 --- a/crates/storage/src/connection/event.rs +++ b/crates/storage/src/connection/event.rs @@ -15,9 +15,8 @@ use crate::prelude::*; use crate::ReorgCounter; pub const PAGE_SIZE_LIMIT: usize = 1_024; -pub const KEY_FILTER_LIMIT: usize = 16; -#[derive(Debug)] +#[derive(Debug, Default)] pub struct EventFilter { pub from_block: Option, pub to_block: Option, @@ -41,12 +40,8 @@ pub struct EmittedEvent { pub enum EventFilterError { #[error(transparent)] Internal(#[from] anyhow::Error), - #[error("requested page size is too big, supported maximum is {0}")] - PageSizeTooBig(usize), #[error("requested page size is too small, supported minimum is 1")] PageSizeTooSmall, - #[error("Event query too broad. Reduce the block range or add more keys.")] - TooManyMatches, } impl From for EventFilterError { @@ -89,6 +84,75 @@ impl Transaction<'_> { Ok(()) } + /// Return all of the events in the given block range, filtered by the given + /// keys and contract address. Along with the events, return the last + /// block number that was scanned, which may be smaller than `to_block` + /// if there are no more blocks in the database. + pub fn events_in_range( + &self, + from_block: BlockNumber, + to_block: BlockNumber, + contract_address: Option, + keys: Vec>, + ) -> anyhow::Result<(Vec, Option)> { + let key_filter_is_empty = keys.iter().flatten().count() == 0; + let reorg_counter = self.reorg_counter()?; + let mut emitted_events = Vec::new(); + let mut block_number = from_block; + let filter = EventFilter { + contract_address, + keys, + page_size: usize::MAX - 1, + ..Default::default() + }; + loop { + // Stop if we're past the last block. + if block_number > to_block { + return Ok((emitted_events, Some(to_block))); + } + + // Check bloom filter + if !key_filter_is_empty || contract_address.is_some() { + let bloom = self.load_bloom(reorg_counter, block_number)?; + match bloom { + Filter::Missing => {} + Filter::Cached(bloom) => { + if !bloom.check_filter(&filter) { + tracing::trace!("Bloom filter did not match"); + block_number += 1; + continue; + } + } + Filter::Loaded(bloom) => { + if !bloom.check_filter(&filter) { + tracing::trace!("Bloom filter did not match"); + block_number += 1; + continue; + } + } + } + } + + match self.scan_block_into( + block_number, + &filter, + key_filter_is_empty, + 0, + &mut emitted_events, + )? { + BlockScanResult::NoSuchBlock if block_number == from_block => { + return Ok((emitted_events, None)); + } + BlockScanResult::NoSuchBlock => { + return Ok((emitted_events, Some(block_number.parent().unwrap()))); + } + BlockScanResult::Done { .. } => {} + } + + block_number += 1; + } + } + #[tracing::instrument(skip(self))] pub fn events( &self, @@ -96,10 +160,6 @@ impl Transaction<'_> { max_blocks_to_scan: NonZeroUsize, max_uncached_bloom_filters_to_load: NonZeroUsize, ) -> Result { - if filter.page_size > PAGE_SIZE_LIMIT { - return Err(EventFilterError::PageSizeTooBig(PAGE_SIZE_LIMIT)); - } - if filter.page_size < 1 { return Err(EventFilterError::PageSizeTooSmall); } @@ -381,7 +441,6 @@ enum Filter { mod tests { use std::sync::LazyLock; - use assert_matches::assert_matches; use pathfinder_common::macro_prelude::*; use pathfinder_common::receipt::Receipt; use pathfinder_common::{transaction as common, BlockHeader, BlockTimestamp, EntryPoint, Fee}; @@ -884,40 +943,6 @@ mod tests { ); } - #[test] - fn get_events_with_invalid_page_size() { - let (storage, _) = test_utils::setup_test_storage(); - let mut connection = storage.connection().unwrap(); - let tx = connection.transaction().unwrap(); - - let filter = EventFilter { - from_block: None, - to_block: None, - contract_address: None, - keys: vec![], - page_size: 0, - offset: 0, - }; - let result = tx.events(&filter, *MAX_BLOCKS_TO_SCAN, *MAX_BLOOM_FILTERS_TO_LOAD); - assert!(result.is_err()); - assert_matches!(result.unwrap_err(), EventFilterError::PageSizeTooSmall); - - let filter = EventFilter { - from_block: None, - to_block: None, - contract_address: None, - keys: vec![], - page_size: PAGE_SIZE_LIMIT + 1, - offset: 0, - }; - let result = tx.events(&filter, *MAX_BLOCKS_TO_SCAN, *MAX_BLOOM_FILTERS_TO_LOAD); - assert!(result.is_err()); - assert_matches!( - result.unwrap_err(), - EventFilterError::PageSizeTooBig(PAGE_SIZE_LIMIT) - ); - } - #[test] fn get_events_by_key_with_paging() { let (storage, test_data) = test_utils::setup_test_storage(); From d09db6af1e35d00c3a3cd2f20836d1817461a30f Mon Sep 17 00:00:00 2001 From: sistemd Date: Wed, 2 Oct 2024 15:31:53 +0200 Subject: [PATCH 113/282] use EVENT_KEY_FILTER_LIMIT from bloom module --- crates/rpc/src/method/get_events.rs | 3 +-- crates/rpc/src/method/subscribe_events.rs | 2 +- crates/rpc/src/v03/method/get_events.rs | 4 ++-- crates/storage/src/lib.rs | 1 + 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/rpc/src/method/get_events.rs b/crates/rpc/src/method/get_events.rs index 417ec6e255..ab59924e00 100644 --- a/crates/rpc/src/method/get_events.rs +++ b/crates/rpc/src/method/get_events.rs @@ -10,7 +10,7 @@ use pathfinder_common::{ EventKey, TransactionHash, }; -use pathfinder_storage::EventFilterError; +use pathfinder_storage::{EventFilterError, EVENT_KEY_FILTER_LIMIT}; use starknet_gateway_types::reply::PendingBlock; use tokio::task::JoinHandle; @@ -19,7 +19,6 @@ use crate::dto::serialize::{self, SerializeForVersion, Serializer}; use crate::dto::{self}; use crate::pending::PendingData; -pub const EVENT_KEY_FILTER_LIMIT: usize = 16; pub const EVENT_PAGE_SIZE_LIMIT: usize = 1024; #[derive(Debug)] diff --git a/crates/rpc/src/method/subscribe_events.rs b/crates/rpc/src/method/subscribe_events.rs index 2d485decf0..54ed5f2258 100644 --- a/crates/rpc/src/method/subscribe_events.rs +++ b/crates/rpc/src/method/subscribe_events.rs @@ -2,9 +2,9 @@ use std::sync::Arc; use axum::async_trait; use pathfinder_common::{BlockId, BlockNumber, ContractAddress, EventKey}; +use pathfinder_storage::EVENT_KEY_FILTER_LIMIT; use tokio::sync::mpsc; -use super::get_events::EVENT_KEY_FILTER_LIMIT; use super::REORG_SUBSCRIPTION_NAME; use crate::context::RpcContext; use crate::error::ApplicationError; diff --git a/crates/rpc/src/v03/method/get_events.rs b/crates/rpc/src/v03/method/get_events.rs index 2ec3867dc8..8eb1853028 100644 --- a/crates/rpc/src/v03/method/get_events.rs +++ b/crates/rpc/src/v03/method/get_events.rs @@ -2,13 +2,13 @@ use std::str::FromStr; use anyhow::Context; use pathfinder_common::{BlockId, BlockNumber, ContractAddress, EventKey}; -use pathfinder_storage::EventFilterError; +use pathfinder_storage::{EventFilterError, EVENT_KEY_FILTER_LIMIT}; use serde::Deserialize; use starknet_gateway_types::reply::PendingBlock; use tokio::task::JoinHandle; use crate::context::RpcContext; -use crate::method::get_events::{EVENT_KEY_FILTER_LIMIT, EVENT_PAGE_SIZE_LIMIT}; +use crate::method::get_events::EVENT_PAGE_SIZE_LIMIT; use crate::pending::PendingData; #[derive(Debug)] diff --git a/crates/storage/src/lib.rs b/crates/storage/src/lib.rs index 8df00b5e7f..0dde97aca1 100644 --- a/crates/storage/src/lib.rs +++ b/crates/storage/src/lib.rs @@ -17,6 +17,7 @@ use std::path::{Path, PathBuf}; use std::sync::Arc; use anyhow::Context; +pub use bloom::EVENT_KEY_FILTER_LIMIT; pub use connection::*; use pathfinder_common::{BlockHash, BlockNumber}; use r2d2::Pool; From 7968ba788b781799cbe00e6561d245a49871ed88 Mon Sep 17 00:00:00 2001 From: Herman Obst Demaestri Date: Wed, 2 Oct 2024 11:14:41 -0700 Subject: [PATCH 114/282] fix clippy --- crates/merkle-tree/src/class.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/merkle-tree/src/class.rs b/crates/merkle-tree/src/class.rs index 265ae4fedd..5aaf7966f5 100644 --- a/crates/merkle-tree/src/class.rs +++ b/crates/merkle-tree/src/class.rs @@ -1,6 +1,4 @@ use anyhow::Context; -use bitvec::order::Msb0; -use bitvec::prelude::BitSlice; use pathfinder_common::hash::PoseidonHash; use pathfinder_common::trie::TrieNode; use pathfinder_common::{ From 754073de19b8dc5b8f3da79b0f75862e75259766 Mon Sep 17 00:00:00 2001 From: Krzysztof Lis Date: Thu, 3 Oct 2024 12:40:48 +0200 Subject: [PATCH 115/282] feat: add config option to skip casm compilation and download from fgw instead --- .../pathfinder/src/bin/pathfinder/config.rs | 11 +++++ crates/pathfinder/src/bin/pathfinder/main.rs | 1 + crates/pathfinder/src/state/sync.rs | 5 +++ crates/pathfinder/src/state/sync/class.rs | 45 ++++++++++++------- crates/pathfinder/src/state/sync/l2.rs | 31 +++++++++---- crates/pathfinder/src/state/sync/pending.rs | 12 ++++- 6 files changed, 80 insertions(+), 25 deletions(-) diff --git a/crates/pathfinder/src/bin/pathfinder/config.rs b/crates/pathfinder/src/bin/pathfinder/config.rs index e1afcfb85f..e10ea9e047 100644 --- a/crates/pathfinder/src/bin/pathfinder/config.rs +++ b/crates/pathfinder/src/bin/pathfinder/config.rs @@ -291,6 +291,15 @@ This should only be enabled for debugging purposes as it adds substantial proces env = "PATHFINDER_RPC_CUSTOM_VERSIONED_CONSTANTS_JSON_PATH" )] custom_versioned_constants_path: Option, + + #[arg( + long = "sync.fetch-casm-from-fgw", + long_help = "Do not compile classes locally, instead fetch them from the feeder gateway", + env = "PATHFINDER_SYNC_FETCH_CASM_FROM_FGW", + default_value = "false", + action=ArgAction::Set + )] + fetch_casm_from_fgw: bool, } #[derive(clap::ValueEnum, Debug, Clone, Copy, PartialEq)] @@ -697,6 +706,7 @@ pub struct Config { pub state_tries: Option, pub custom_versioned_constants: Option, pub feeder_gateway_fetch_concurrency: NonZeroUsize, + pub fetch_casm_from_fgw: bool, } pub struct Ethereum { @@ -989,6 +999,7 @@ impl Config { custom_versioned_constants: cli .custom_versioned_constants_path .map(parse_versioned_constants_or_exit), + fetch_casm_from_fgw: cli.fetch_casm_from_fgw, } } } diff --git a/crates/pathfinder/src/bin/pathfinder/main.rs b/crates/pathfinder/src/bin/pathfinder/main.rs index a7161497c3..fb29790a40 100644 --- a/crates/pathfinder/src/bin/pathfinder/main.rs +++ b/crates/pathfinder/src/bin/pathfinder/main.rs @@ -599,6 +599,7 @@ fn start_feeder_gateway_sync( gossiper, sequencer_public_key: gateway_public_key, fetch_concurrency: config.feeder_gateway_fetch_concurrency, + fetch_casm_from_fgw: config.fetch_casm_from_fgw, }; tokio::spawn(state::sync(sync_context, state::l1::sync, state::l2::sync)) diff --git a/crates/pathfinder/src/state/sync.rs b/crates/pathfinder/src/state/sync.rs index 862b46ab18..4a38e1d59f 100644 --- a/crates/pathfinder/src/state/sync.rs +++ b/crates/pathfinder/src/state/sync.rs @@ -92,6 +92,7 @@ pub struct SyncContext { pub gossiper: Gossiper, pub sequencer_public_key: PublicKey, pub fetch_concurrency: std::num::NonZeroUsize, + pub fetch_casm_from_fgw: bool, } impl From<&SyncContext> for L1SyncContext @@ -121,6 +122,7 @@ where storage: value.storage.clone(), sequencer_public_key: value.sequencer_public_key, fetch_concurrency: value.fetch_concurrency, + fetch_casm_from_fgw: value.fetch_casm_from_fgw, } } } @@ -201,6 +203,7 @@ where gossiper, sequencer_public_key: _, fetch_concurrency: _, + fetch_casm_from_fgw, } = context; let mut db_conn = storage @@ -284,6 +287,7 @@ where storage.clone(), rx_latest.clone(), rx_current.clone(), + fetch_casm_from_fgw, )); /// Delay before restarting L1 or L2 tasks if they fail. This delay helps @@ -305,6 +309,7 @@ where storage.clone(), rx_latest.clone(), rx_current.clone(), + fetch_casm_from_fgw, )); }, _ = &mut latest_handle => { diff --git a/crates/pathfinder/src/state/sync/class.rs b/crates/pathfinder/src/state/sync/class.rs index 379d4a18d8..051746bb68 100644 --- a/crates/pathfinder/src/state/sync/class.rs +++ b/crates/pathfinder/src/state/sync/class.rs @@ -17,6 +17,7 @@ pub enum DownloadedClass { pub async fn download_class( sequencer: &SequencerClient, class_hash: ClassHash, + fetch_casm_from_fgw: bool, ) -> Result { use starknet_gateway_types::class_hash::compute_class_hash; @@ -60,26 +61,40 @@ pub async fn download_class( // The work-around ignores compilation errors on integration, and instead // replaces the casm definition with empty bytes. let span = tracing::Span::current(); - let (send, recv) = tokio::sync::oneshot::channel(); - rayon::spawn(move || { - let _span = span.entered(); - let compile_result = pathfinder_compiler::compile_to_casm(&definition) - .context("Compiling Sierra class"); - let _ = send.send((compile_result, definition)); - }); - let (casm_definition, sierra_definition) = recv.await.expect("Panic on rayon thread"); - - let casm_definition = match casm_definition { - Ok(casm_definition) => casm_definition, - Err(error) => { - tracing::info!(class_hash=%hash, ?error, "CASM compilation failed, falling back to fetching from gateway"); + let (sierra_definition, casm_definition) = if fetch_casm_from_fgw { + ( + definition, sequencer .pending_casm_by_hash(class_hash) .await .with_context(|| format!("Downloading CASM {}", class_hash.0))? - .to_vec() - } + .to_vec(), + ) + } else { + let (send, recv) = tokio::sync::oneshot::channel(); + rayon::spawn(move || { + let _span = span.entered(); + let compile_result = pathfinder_compiler::compile_to_casm(&definition) + .context("Compiling Sierra class"); + + let _ = send.send((compile_result, definition)); + }); + let (casm_definition, sierra_definition) = + recv.await.expect("Panic on rayon thread"); + + let casm_definition = match casm_definition { + Ok(casm_definition) => casm_definition, + Err(error) => { + tracing::info!(class_hash=%hash, ?error, "CASM compilation failed, falling back to fetching from gateway"); + sequencer + .pending_casm_by_hash(class_hash) + .await + .with_context(|| format!("Downloading CASM {}", class_hash.0))? + .to_vec() + } + }; + (sierra_definition, casm_definition) }; Ok(DownloadedClass::Sierra { diff --git a/crates/pathfinder/src/state/sync/l2.rs b/crates/pathfinder/src/state/sync/l2.rs index c4942d54b4..9532e32319 100644 --- a/crates/pathfinder/src/state/sync/l2.rs +++ b/crates/pathfinder/src/state/sync/l2.rs @@ -101,6 +101,7 @@ pub struct L2SyncContext { pub storage: Storage, pub sequencer_public_key: PublicKey, pub fetch_concurrency: std::num::NonZeroUsize, + pub fetch_casm_from_fgw: bool, } pub async fn sync( @@ -132,6 +133,7 @@ where storage, sequencer_public_key, fetch_concurrency: _, + fetch_casm_from_fgw, } = context; // Start polling head of chain @@ -235,9 +237,14 @@ where // Download and emit newly declared classes. let t_declare = std::time::Instant::now(); - let downloaded_classes = download_new_classes(&state_update, &sequencer, storage.clone()) - .await - .with_context(|| format!("Handling newly declared classes for block {next:?}"))?; + let downloaded_classes = download_new_classes( + &state_update, + &sequencer, + storage.clone(), + fetch_casm_from_fgw, + ) + .await + .with_context(|| format!("Handling newly declared classes for block {next:?}"))?; emit_events_for_downloaded_classes( &tx_event, downloaded_classes, @@ -379,6 +386,7 @@ pub async fn download_new_classes( state_update: &StateUpdate, sequencer: &impl GatewayApi, storage: Storage, + fetch_casm_from_fgw: bool, ) -> Result, anyhow::Error> { let deployed_classes = state_update .contract_updates @@ -432,7 +440,7 @@ pub async fn download_new_classes( let futures = require_downloading.into_iter().map(|class_hash| { async move { - download_class(sequencer, class_hash) + download_class(sequencer, class_hash, fetch_casm_from_fgw) .await .with_context(|| format!("Downloading class {}", class_hash.0)) } @@ -606,6 +614,7 @@ where storage, sequencer_public_key, fetch_concurrency, + fetch_casm_from_fgw, } = context; let mut start = match head { @@ -703,11 +712,12 @@ where .context("Verifying block contents")?; let t_declare = std::time::Instant::now(); - let downloaded_classes = download_new_classes(&state_update, &sequencer, storage) - .await - .with_context(|| { - format!("Handling newly declared classes for block {block_number:?}") - })?; + let downloaded_classes = + download_new_classes(&state_update, &sequencer, storage, fetch_casm_from_fgw) + .await + .with_context(|| { + format!("Handling newly declared classes for block {block_number:?}") + })?; let t_declare = t_declare.elapsed(); let timings = Timings { @@ -1229,6 +1239,7 @@ mod tests { storage, sequencer_public_key: PublicKey::ZERO, fetch_concurrency: std::num::NonZeroUsize::new(1).unwrap(), + fetch_casm_from_fgw: false, }; let latest = tokio::sync::watch::channel(Default::default()); @@ -1256,6 +1267,7 @@ mod tests { storage, sequencer_public_key: PublicKey::ZERO, fetch_concurrency: std::num::NonZeroUsize::new(2).unwrap(), + fetch_casm_from_fgw: false, }; tokio::spawn(async move { @@ -1734,6 +1746,7 @@ mod tests { storage: StorageBuilder::in_memory().unwrap(), sequencer_public_key: PublicKey::ZERO, fetch_concurrency: std::num::NonZeroUsize::new(1).unwrap(), + fetch_casm_from_fgw: false, }; let latest_track = tokio::sync::watch::channel(Default::default()); diff --git a/crates/pathfinder/src/state/sync/pending.rs b/crates/pathfinder/src/state/sync/pending.rs index da5bbfaf62..4045212555 100644 --- a/crates/pathfinder/src/state/sync/pending.rs +++ b/crates/pathfinder/src/state/sync/pending.rs @@ -17,6 +17,7 @@ pub async fn poll_pending( storage: Storage, latest: watch::Receiver<(BlockNumber, BlockHash)>, current: watch::Receiver<(BlockNumber, BlockHash)>, + fetch_casm_from_fgw: bool, ) { let mut prev_tx_count = 0; let mut prev_hash = BlockHash::default(); @@ -56,7 +57,14 @@ pub async fn poll_pending( // fail when querying a desync'd feeder gateway which isn't aware of the // new pending classes. In this case, ignore the new pending data as it // is incomplete. - match super::l2::download_new_classes(&state_update, &sequencer, storage.clone()).await { + match super::l2::download_new_classes( + &state_update, + &sequencer, + storage.clone(), + fetch_casm_from_fgw, + ) + .await + { Err(e) => tracing::debug!(reason=?e, "Failed to download pending classes"), Ok(downloaded_classes) => { if let Err(e) = super::l2::emit_events_for_downloaded_classes( @@ -197,6 +205,7 @@ mod tests { StorageBuilder::in_memory().unwrap(), latest, current, + false, ) .await }); @@ -269,6 +278,7 @@ mod tests { StorageBuilder::in_memory().unwrap(), rx_latest, rx_current, + false, ) .await }); From 507c294094446d95a1935adfe16be4bc7f1418cd Mon Sep 17 00:00:00 2001 From: Jim McDonald Date: Thu, 3 Oct 2024 18:19:06 +0100 Subject: [PATCH 116/282] Add --log-output-json CLI option. --- CHANGELOG.md | 1 + Cargo.lock | 13 +++++++++ Cargo.toml | 2 +- .../pathfinder/src/bin/pathfinder/config.rs | 11 ++++++++ crates/pathfinder/src/bin/pathfinder/main.rs | 27 ++++++++++++++----- 5 files changed, 46 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 37f05d3106..5278b0c80d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `--disable-version-update-check` CLI option has been added to disable the periodic checking for a new version. - Add `pathfinder_getClassProof` endpoint to retrieve the Merkle proof of any class hash in the class trie. - add `process_start_time_seconds` metric showing the unix timestamp when the process started. +- `--log-output-json` CLI option has been added to output the Pathfinder log in line-delimited JSON. ### Changed diff --git a/Cargo.lock b/Cargo.lock index d84b5ff4f4..49494faabe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10124,6 +10124,16 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "tracing-serde" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc6b213177105856957181934e4920de57730fc69bf42c37ee5bb664d406d9e1" +dependencies = [ + "serde", + "tracing-core", +] + [[package]] name = "tracing-subscriber" version = "0.3.18" @@ -10134,6 +10144,8 @@ dependencies = [ "nu-ansi-term", "once_cell", "regex", + "serde", + "serde_json", "sharded-slab", "smallvec", "thread_local", @@ -10141,6 +10153,7 @@ dependencies = [ "tracing", "tracing-core", "tracing-log", + "tracing-serde", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 5a06185904..4d2d2bd60d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -136,7 +136,7 @@ tokio-tungstenite = "0.21" tower = { version = "0.4.13", default-features = false } tower-http = { version = "0.5.2", default-features = false } tracing = "0.1.37" -tracing-subscriber = "0.3.18" +tracing-subscriber = { version = "0.3.18", features = ["json"] } unsigned-varint = "0.8.0" url = "2.4.1" vergen = { version = "8", default-features = false } diff --git a/crates/pathfinder/src/bin/pathfinder/config.rs b/crates/pathfinder/src/bin/pathfinder/config.rs index e1afcfb85f..f90f08f674 100644 --- a/crates/pathfinder/src/bin/pathfinder/config.rs +++ b/crates/pathfinder/src/bin/pathfinder/config.rs @@ -149,6 +149,15 @@ Examples: )] color: Color, + #[arg( + long = "log-output-json", + long_help = "This flag controls when to use colors in the output logs.", + default_value = "false", + env = "PATHFINDER_LOG_OUTPUT_JSON", + value_name = "BOOL" + )] + log_output_json: bool, + #[arg( long = "disable-version-update-check", long_help = "Disable the periodic version update check.", @@ -682,6 +691,7 @@ pub struct Config { pub poll_interval: std::time::Duration, pub l1_poll_interval: std::time::Duration, pub color: Color, + pub log_output_json: bool, pub disable_version_update_check: bool, pub p2p: P2PConfig, pub debug: DebugConfig, @@ -971,6 +981,7 @@ impl Config { poll_interval: Duration::from_secs(cli.poll_interval.get()), l1_poll_interval: Duration::from_secs(cli.l1_poll_interval.get()), color: cli.color, + log_output_json: cli.log_output_json, disable_version_update_check: cli.disable_version_update_check, p2p: P2PConfig::parse_or_exit(cli.p2p), debug: DebugConfig::parse(cli.debug), diff --git a/crates/pathfinder/src/bin/pathfinder/main.rs b/crates/pathfinder/src/bin/pathfinder/main.rs index a7161497c3..cde9105b96 100644 --- a/crates/pathfinder/src/bin/pathfinder/main.rs +++ b/crates/pathfinder/src/bin/pathfinder/main.rs @@ -51,7 +51,11 @@ async fn async_main() -> anyhow::Result<()> { let mut config = config::Config::parse(); - setup_tracing(config.color, config.debug.pretty_log); + setup_tracing( + config.color, + config.debug.pretty_log, + config.log_output_json, + ); info!( // this is expected to be $(last_git_tag)-$(commits_since)-$(commit_hash) @@ -335,7 +339,7 @@ Hint: This is usually caused by exceeding the file descriptor limit of your syst } #[cfg(feature = "tokio-console")] -fn setup_tracing(color: config::Color, pretty_log: bool) { +fn setup_tracing(color: config::Color, pretty_log: bool, json_log: bool) { use tracing_subscriber::prelude::*; // EnvFilter isn't really a Filter, so this we need this ugly workaround for @@ -347,7 +351,12 @@ fn setup_tracing(color: config::Color, pretty_log: bool) { let filter = tracing_subscriber::filter::dynamic_filter_fn(move |m, c| env_filter.enabled(m, c.clone())); - if pretty_log { + if json_log { + tracing_subscriber::registry() + .with(fmt_layer.json().flatten_event(true).with_filter(filter)) + .with(console_subscriber::spawn()) + .init(); + } else if pretty_log { tracing_subscriber::registry() .with(fmt_layer.pretty().with_filter(filter)) .with(console_subscriber::spawn()) @@ -361,7 +370,7 @@ fn setup_tracing(color: config::Color, pretty_log: bool) { } #[cfg(not(feature = "tokio-console"))] -fn setup_tracing(color: config::Color, pretty_log: bool) { +fn setup_tracing(color: config::Color, pretty_log: bool, json_log: bool) { use time::macros::format_description; let time_fmt = format_description!("[year]-[month]-[day]T[hour]:[minute]:[second]"); @@ -373,10 +382,14 @@ fn setup_tracing(color: config::Color, pretty_log: bool) { .with_timer(time_fmt) .with_ansi(color.is_color_enabled()); - if pretty_log { - subscriber.pretty().init(); + if json_log { + subscriber.json().flatten_event(true).init(); } else { - subscriber.compact().init(); + if pretty_log { + subscriber.pretty().init(); + } else { + subscriber.compact().init(); + } } } From 2f049ce1a7627c0e146d44fa049fa21b72789a83 Mon Sep 17 00:00:00 2001 From: Krisztian Kovacs Date: Fri, 4 Oct 2024 11:15:31 +0200 Subject: [PATCH 117/282] fix(pathfinder/sync): fix checkpoint sync continuation point `checkpoint_sync()` returns the point from where tracking sync should start. If the latest L1 confirmed state (checkpoint) was behind what we already had in storage (because tracking sync has started previously) then this continuation point was wrong: it was always the block after the latest L1 confirmed state, which then caused tracking sync to fail on the first block. --- crates/pathfinder/src/sync.rs | 19 ++++++++++--------- crates/pathfinder/src/sync/checkpoint.rs | 18 ++++++++++++++++-- 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/crates/pathfinder/src/sync.rs b/crates/pathfinder/src/sync.rs index fc5c067f18..4d65817a99 100644 --- a/crates/pathfinder/src/sync.rs +++ b/crates/pathfinder/src/sync.rs @@ -81,7 +81,7 @@ impl Sync { async fn checkpoint_sync(&self) -> anyhow::Result<(BlockNumber, BlockHash)> { let mut checkpoint = self.get_checkpoint().await?; - loop { + Ok(loop { let result = checkpoint::Sync { storage: self.storage.clone(), p2p: self.p2p.clone(), @@ -97,10 +97,13 @@ impl Sync { .await; // Handle the error - if let Err(err) = result { - self.handle_error(err).await; - continue; - } + let continue_from = match result { + Ok(continue_from) => continue_from, + Err(err) => { + self.handle_error(err).await; + continue; + } + }; // Initial sync might take so long, that the latest checkpoint is actually far // ahead again. Repeat until we are within some margin of L1. @@ -110,10 +113,8 @@ impl Sync { continue; } - break; - } - - Ok((checkpoint.block_number + 1, checkpoint.block_hash)) + break continue_from; + }) } /// Run the track sync until it completes successfully, requires the diff --git a/crates/pathfinder/src/sync/checkpoint.rs b/crates/pathfinder/src/sync/checkpoint.rs index 604fdfb11e..2038ddf924 100644 --- a/crates/pathfinder/src/sync/checkpoint.rs +++ b/crates/pathfinder/src/sync/checkpoint.rs @@ -92,7 +92,13 @@ impl Sync { } /// Syncs using p2p until the given Ethereum checkpoint. - pub async fn run(&self, checkpoint: EthereumStateUpdate) -> Result<(), SyncError> { + /// + /// Returns the block number and its parent hash where tracking sync is + /// expected to continue. + pub async fn run( + &self, + checkpoint: EthereumStateUpdate, + ) -> Result<(BlockNumber, BlockHash), SyncError> { use pathfinder_ethereum::EthereumApi; let local_state = LocalState::from_db(self.storage.clone(), checkpoint) @@ -129,7 +135,15 @@ impl Sync { self.sync_class_definitions(head).await?; self.sync_events(head).await?; - Ok(()) + let local_state = LocalState::from_db(self.storage.clone(), checkpoint) + .await + .context("Querying local state after checkpoint sync")?; + let (next_block_number, last_block_hash) = local_state + .latest_header + .map(|(number, hash)| (number + 1, hash)) + .unwrap_or((BlockNumber::GENESIS, BlockHash::ZERO)); + + Ok((next_block_number, last_block_hash)) } /// Syncs all headers in reverse chronological order, from the anchor point From a2e291df9ef418f6630bbc18cf8b5e2c1dcd8b78 Mon Sep 17 00:00:00 2001 From: Krzysztof Lis Date: Fri, 4 Oct 2024 11:33:06 +0200 Subject: [PATCH 118/282] doc: explain what anchor and checkpoint mean in LocalState --- crates/pathfinder/src/sync/checkpoint.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/crates/pathfinder/src/sync/checkpoint.rs b/crates/pathfinder/src/sync/checkpoint.rs index 604fdfb11e..a01d7d2486 100644 --- a/crates/pathfinder/src/sync/checkpoint.rs +++ b/crates/pathfinder/src/sync/checkpoint.rs @@ -576,8 +576,14 @@ impl CheckpointAnalysis { } struct LocalState { + /// The highest header in our local storage. latest_header: Option<(BlockNumber, BlockHash)>, + /// The highest L1 state update __in our local storage__. + /// + /// An L1 state update is Starknet's block number, hash and state root as + /// recorded on Ethereum. anchor: Option, + /// The highest L1 state update __fetched from Ethereum at the moment__. checkpoint: Option<(BlockNumber, BlockHash)>, } From dd7312de50c43ad9cebea533221a00d5ad14dbc8 Mon Sep 17 00:00:00 2001 From: Krzysztof Lis Date: Fri, 4 Oct 2024 11:55:53 +0200 Subject: [PATCH 119/282] fix: incorrect error description in checkpoint analyze --- crates/pathfinder/src/sync/checkpoint.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/pathfinder/src/sync/checkpoint.rs b/crates/pathfinder/src/sync/checkpoint.rs index a01d7d2486..4ca0c7a9ab 100644 --- a/crates/pathfinder/src/sync/checkpoint.rs +++ b/crates/pathfinder/src/sync/checkpoint.rs @@ -535,7 +535,7 @@ impl CheckpointAnalysis { %checkpoint, %anchor, "Ethereum checkpoint is older than the local anchor. This indicates a serious inconsistency in the Ethereum source used by this sync and the previous sync." ); - anyhow::bail!("Ethereum checkpoint hash did not match local anchor."); + anyhow::bail!("Ethereum checkpoint is older than the local anchor."); } CheckpointAnalysis::ExceedsLocalChain { local, From 8ca492248f2a9246e9578e48c8cb83afc19c9c49 Mon Sep 17 00:00:00 2001 From: Krisztian Kovacs Date: Wed, 2 Oct 2024 12:38:02 +0200 Subject: [PATCH 120/282] refactor: move `verify_gateway_block_commitments_and_hash` to L2 sync --- crates/pathfinder/src/state/block_hash.rs | 21 +++---- crates/pathfinder/src/state/sync/l2.rs | 77 ++++++++++++++++++++++- 2 files changed, 85 insertions(+), 13 deletions(-) diff --git a/crates/pathfinder/src/state/block_hash.rs b/crates/pathfinder/src/state/block_hash.rs index e69800755a..893bd63157 100644 --- a/crates/pathfinder/src/state/block_hash.rs +++ b/crates/pathfinder/src/state/block_hash.rs @@ -47,18 +47,6 @@ impl VerifyResult { } } -/// Verify the block hash value. -/// -/// The method to compute the block hash is documented -/// [here](https://docs.starknet.io/docs/Blocks/header/#block-hash). -/// -/// Unfortunately that'a not-fully-correct description, since the transaction -/// commitment Merkle tree is not constructed directly with the transaction -/// hashes, but with a hash computed from the transaction hash and the signature -/// values (for invoke transactions). -/// -/// See the `compute_block_hash.py` helper script that uses the cairo-lang -/// Python implementation to compute the block hash for details. pub fn verify_gateway_block_commitments_and_hash( block: &Block, state_diff_commitment: StateDiffCommitment, @@ -221,6 +209,15 @@ impl BlockHeaderData { } } +/// Verify the block hash value. +/// +/// The method to compute the block hash is documented +/// [here](https://docs.starknet.io/docs/Blocks/header/#block-hash). +/// +/// Unfortunately that'a not-fully-correct description, since the transaction +/// commitment Merkle tree is not constructed directly with the transaction +/// hashes, but with a hash computed from the transaction hash and the signature +/// values (for invoke transactions). pub fn verify_block_hash( header: BlockHeaderData, chain: Chain, diff --git a/crates/pathfinder/src/state/sync/l2.rs b/crates/pathfinder/src/state/sync/l2.rs index 9532e32319..0797cbcfeb 100644 --- a/crates/pathfinder/src/state/sync/l2.rs +++ b/crates/pathfinder/src/state/sync/l2.rs @@ -28,7 +28,14 @@ use starknet_gateway_types::reply::{Block, BlockSignature, Status}; use tokio::sync::mpsc; use tracing::Instrument; -use crate::state::block_hash::{verify_gateway_block_commitments_and_hash, VerifyResult}; +use crate::state::block_hash::{ + calculate_event_commitment, + calculate_receipt_commitment, + calculate_transaction_commitment, + verify_block_hash, + BlockHeaderData, + VerifyResult, +}; use crate::state::sync::class::{download_class, DownloadedClass}; use crate::state::sync::SyncEvent; @@ -1011,6 +1018,74 @@ fn verify_signature( } } +/// Verify that the block hash matches the actual contents. +pub fn verify_gateway_block_commitments_and_hash( + block: &Block, + state_diff_commitment: StateDiffCommitment, + state_diff_length: u64, + chain: Chain, + chain_id: ChainId, +) -> anyhow::Result { + let mut bhd = + BlockHeaderData::from_gateway_block(block, state_diff_commitment, state_diff_length)?; + + let computed_transaction_commitment = + calculate_transaction_commitment(&block.transactions, block.starknet_version)?; + + // Older blocks on mainnet don't carry a precalculated transaction commitment. + if block.transaction_commitment == TransactionCommitment::ZERO { + // Update with the computed transaction commitment, verification is not + // possible. + bhd.transaction_commitment = computed_transaction_commitment; + } else if computed_transaction_commitment != bhd.transaction_commitment { + tracing::debug!(%computed_transaction_commitment, actual_transaction_commitment=%bhd.transaction_commitment, "Transaction commitment mismatch"); + return Ok(VerifyResult::Mismatch); + } + + let computed_receipt_commitment = calculate_receipt_commitment( + block + .transaction_receipts + .iter() + .map(|(r, _)| r.clone()) + .collect::>() + .as_slice(), + )?; + + // Older blocks on mainnet don't carry a precalculated receipt commitment. + if let Some(receipt_commitment) = block.receipt_commitment { + if computed_receipt_commitment != receipt_commitment { + tracing::debug!(%computed_receipt_commitment, actual_receipt_commitment=%receipt_commitment, "Receipt commitment mismatch"); + return Ok(VerifyResult::Mismatch); + } + } else { + // Update with the computed transaction commitment, verification is not + // possible. + bhd.receipt_commitment = computed_receipt_commitment; + } + + let event_commitment = calculate_event_commitment( + &block + .transaction_receipts + .iter() + .map(|(receipt, events)| (receipt.transaction_hash, events.as_slice())) + .collect::>(), + block.starknet_version, + )?; + + // Older blocks on mainnet don't carry a precalculated event + // commitment. + if block.event_commitment == EventCommitment::ZERO { + // Update with the computed transaction commitment, verification is not + // possible. + bhd.event_commitment = event_commitment; + } else if event_commitment != block.event_commitment { + tracing::debug!(computed_event_commitment=%event_commitment, actual_event_commitment=%block.event_commitment, "Event commitment mismatch"); + return Ok(VerifyResult::Mismatch); + } + + verify_block_hash(bhd, chain, chain_id) +} + async fn reorg( head: &(BlockNumber, BlockHash, StateCommitment), chain: Chain, From 24f307435670d0f2558f817a93a41527eb4a3c41 Mon Sep 17 00:00:00 2001 From: Krisztian Kovacs Date: Wed, 2 Oct 2024 12:44:28 +0200 Subject: [PATCH 121/282] refactor: `verify_block_hash` should _not_ return its inputs `verify_block_hash` does now pure verification on its inputs. --- .../examples/verify_block_hashes.rs | 2 +- crates/pathfinder/src/state/block_hash.rs | 20 ++++++++----------- crates/pathfinder/src/state/sync/l2.rs | 17 +++++++++++++--- crates/pathfinder/src/sync/headers.rs | 2 +- 4 files changed, 24 insertions(+), 17 deletions(-) diff --git a/crates/pathfinder/examples/verify_block_hashes.rs b/crates/pathfinder/examples/verify_block_hashes.rs index 6f0d95ee58..c606825de8 100644 --- a/crates/pathfinder/examples/verify_block_hashes.rs +++ b/crates/pathfinder/examples/verify_block_hashes.rs @@ -77,7 +77,7 @@ fn main() -> anyhow::Result<()> { let result = verify_block_hash(bhd, chain, chain_id)?; match result { - VerifyResult::Match(_) => {} + VerifyResult::Match => {} VerifyResult::Mismatch => { println!( "Block hash mismatch at block number {block_number} hash {}", diff --git a/crates/pathfinder/src/state/block_hash.rs b/crates/pathfinder/src/state/block_hash.rs index 893bd63157..9c0621add2 100644 --- a/crates/pathfinder/src/state/block_hash.rs +++ b/crates/pathfinder/src/state/block_hash.rs @@ -37,13 +37,13 @@ const V_0_13_2: StarknetVersion = StarknetVersion::new(0, 13, 2, 0); #[derive(Debug, PartialEq, Eq)] pub enum VerifyResult { - Match((TransactionCommitment, EventCommitment, ReceiptCommitment)), + Match, Mismatch, } impl VerifyResult { pub fn is_match(&self) -> bool { - matches!(self, Self::Match(_)) + matches!(self, Self::Match) } } @@ -254,11 +254,7 @@ pub fn verify_block_hash( Ok(match verified { false => VerifyResult::Mismatch, - true => VerifyResult::Match(( - header.transaction_commitment, - header.event_commitment, - header.receipt_commitment, - )), + true => VerifyResult::Match, }) } @@ -820,7 +816,7 @@ mod tests { ChainId::MAINNET ) .unwrap(), - VerifyResult::Match(_) + VerifyResult::Match ); } @@ -840,7 +836,7 @@ mod tests { ChainId::MAINNET ) .unwrap(), - VerifyResult::Match(_) + VerifyResult::Match ); } @@ -861,7 +857,7 @@ mod tests { ChainId::MAINNET ) .unwrap(), - VerifyResult::Match(_) + VerifyResult::Match ); } @@ -879,7 +875,7 @@ mod tests { ChainId::MAINNET ) .unwrap(), - VerifyResult::Match(_) + VerifyResult::Match ); } @@ -899,7 +895,7 @@ mod tests { ChainId::MAINNET ) .unwrap(), - VerifyResult::Match(_) + VerifyResult::Match ); } diff --git a/crates/pathfinder/src/state/sync/l2.rs b/crates/pathfinder/src/state/sync/l2.rs index 0797cbcfeb..5f682779f2 100644 --- a/crates/pathfinder/src/state/sync/l2.rs +++ b/crates/pathfinder/src/state/sync/l2.rs @@ -34,7 +34,6 @@ use crate::state::block_hash::{ calculate_transaction_commitment, verify_block_hash, BlockHeaderData, - VerifyResult, }; use crate::state::sync::class::{download_class, DownloadedClass}; use crate::state::sync::SyncEvent; @@ -1018,8 +1017,13 @@ fn verify_signature( } } +enum VerifyResult { + Match((TransactionCommitment, EventCommitment, ReceiptCommitment)), + Mismatch, +} + /// Verify that the block hash matches the actual contents. -pub fn verify_gateway_block_commitments_and_hash( +fn verify_gateway_block_commitments_and_hash( block: &Block, state_diff_commitment: StateDiffCommitment, state_diff_length: u64, @@ -1083,7 +1087,14 @@ pub fn verify_gateway_block_commitments_and_hash( return Ok(VerifyResult::Mismatch); } - verify_block_hash(bhd, chain, chain_id) + Ok(match verify_block_hash(bhd, chain, chain_id)? { + crate::state::block_hash::VerifyResult::Match => VerifyResult::Match(( + computed_transaction_commitment, + event_commitment, + computed_receipt_commitment, + )), + crate::state::block_hash::VerifyResult::Mismatch => VerifyResult::Mismatch, + }) } async fn reorg( diff --git a/crates/pathfinder/src/sync/headers.rs b/crates/pathfinder/src/sync/headers.rs index ea8c801a44..d519e54cf7 100644 --- a/crates/pathfinder/src/sync/headers.rs +++ b/crates/pathfinder/src/sync/headers.rs @@ -281,7 +281,7 @@ impl VerifyHashAndSignature { self.chain_id, ); match result { - Ok(VerifyResult::Match(_)) => true, + Ok(VerifyResult::Match) => true, Ok(VerifyResult::Mismatch) => { tracing::debug!(block_number=%header.number, expected_block_hash=%header.hash, "Block hash mismatch"); false From d15966520f35693ae13bde1ddebdfc89d644e711 Mon Sep 17 00:00:00 2001 From: Krisztian Kovacs Date: Wed, 2 Oct 2024 13:28:37 +0200 Subject: [PATCH 122/282] refactor(pathfinder/sync/l2): don't compute state diff commitment twice We're already computing the state diff commitment so that we can verify the block hash -- there's no need to re-compute the same value again before emitting the Block event. --- crates/pathfinder/src/state/sync/l2.rs | 40 ++++++++++++++------------ 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/crates/pathfinder/src/state/sync/l2.rs b/crates/pathfinder/src/state/sync/l2.rs index 5f682779f2..1670c36f79 100644 --- a/crates/pathfinder/src/state/sync/l2.rs +++ b/crates/pathfinder/src/state/sync/l2.rs @@ -164,7 +164,7 @@ where let t_block = std::time::Instant::now(); - let (block, commitments, state_update) = loop { + let (block, commitments, state_update, state_diff_commitment) = loop { match download_block( next, chain, @@ -175,8 +175,8 @@ where ) .await? { - DownloadBlock::Block(block, commitments, state_update) => { - break (block, commitments, state_update) + DownloadBlock::Block(block, commitments, state_update, state_diff_commitment) => { + break (block, commitments, state_update, state_diff_commitment) } DownloadBlock::AtHead => { // Wait for the latest block to change. @@ -314,14 +314,6 @@ where BlockValidationMode::AllowMismatch => (signature, state_update), }; - // Always compute the state diff commitment from the state update. - let (computed_state_diff_commitment, state_update) = - tokio::task::spawn_blocking(move || { - let commitment = state_update.compute_state_diff_commitment(); - (commitment, state_update) - }) - .await?; - head = Some((next, block.block_hash, state_update.state_commitment)); blocks.push(next, block.block_hash, state_update.state_commitment); @@ -336,7 +328,7 @@ where (block, commitments), state_update, Box::new(signature), - Box::new(computed_state_diff_commitment), + Box::new(state_diff_commitment), timings, )) .await @@ -465,6 +457,7 @@ enum DownloadBlock { Box, (TransactionCommitment, EventCommitment, ReceiptCommitment), Box, + StateDiffCommitment, ), AtHead, Reorg, @@ -511,16 +504,21 @@ async fn download_block( chain_id, ) .with_context(move || format!("Verify block {block_number}"))?; - Ok((block, state_update, verify_result)) + Ok((block, state_update, state_diff_commitment, verify_result)) }); - let (block, state_update, verify_result) = + let (block, state_update, state_diff_commitment, verify_result) = verify_hash.await.context("Verify block hash")??; match (block.status, verify_result, mode) { ( Status::AcceptedOnL1 | Status::AcceptedOnL2, VerifyResult::Match(commitments), _, - ) => Ok(DownloadBlock::Block(block, commitments, state_update)), + ) => Ok(DownloadBlock::Block( + block, + commitments, + state_update, + state_diff_commitment, + )), ( Status::AcceptedOnL1 | Status::AcceptedOnL2, VerifyResult::Mismatch, @@ -529,6 +527,7 @@ async fn download_block( block, Default::default(), state_update, + state_diff_commitment, )), (_, VerifyResult::Mismatch, BlockValidationMode::Strict) => { Err(anyhow!("Block hash mismatch")) @@ -573,7 +572,7 @@ async fn download_block( }; match result { - Ok(DownloadBlock::Block(block, commitments, state_update)) => { + Ok(DownloadBlock::Block(block, commitments, state_update, state_diff_commitment)) => { use rayon::prelude::*; let (send, recv) = tokio::sync::oneshot::channel(); @@ -596,7 +595,12 @@ async fn download_block( let block = recv.await.expect("Panic on rayon thread")?; - Ok(DownloadBlock::Block(block, commitments, state_update)) + Ok(DownloadBlock::Block( + block, + commitments, + state_update, + state_diff_commitment, + )) } Ok(DownloadBlock::AtHead | DownloadBlock::Reorg) | Err(_) => result, } @@ -1131,7 +1135,7 @@ async fn reorg( .await .with_context(|| format!("Download block {previous_block_number} from sequencer"))? { - DownloadBlock::Block(block, _, _) if block.block_hash == previous.0 => { + DownloadBlock::Block(block, _, _, _) if block.block_hash == previous.0 => { break Some((previous_block_number, previous.0, previous.1)); } _ => {} From cb98dbd371f33243e9eb97c40bfd2e45896befb8 Mon Sep 17 00:00:00 2001 From: Krisztian Kovacs Date: Wed, 2 Oct 2024 13:37:53 +0200 Subject: [PATCH 123/282] refactor(pathfinder/sync/l2): verify transaction hashes before verifying block hash --- crates/pathfinder/src/state/sync/l2.rs | 61 ++++++++++---------------- 1 file changed, 24 insertions(+), 37 deletions(-) diff --git a/crates/pathfinder/src/state/sync/l2.rs b/crates/pathfinder/src/state/sync/l2.rs index 1670c36f79..294e74bdd5 100644 --- a/crates/pathfinder/src/state/sync/l2.rs +++ b/crates/pathfinder/src/state/sync/l2.rs @@ -480,14 +480,34 @@ async fn download_block( sequencer: &impl GatewayApi, mode: BlockValidationMode, ) -> anyhow::Result { + use rayon::prelude::*; use starknet_gateway_types::error::KnownStarknetErrorCode::BlockNotFound; - let result = sequencer.state_update_with_block(block_number).await; - - let result = match result { + match sequencer.state_update_with_block(block_number).await { Ok((block, state_update)) => { let block = Box::new(block); + // Verify that transaction hashes match transaction contents. + // Block hash is verified using these transaction hashes so we have to make + // sure these are correct first. + let (send, recv) = tokio::sync::oneshot::channel(); + rayon::spawn(move || { + let result = block + .transactions + .par_iter() + .enumerate() + .try_for_each(|(i, txn)| { + if !txn.verify_hash(chain_id) { + anyhow::bail!("Transaction hash mismatch: block {block_number} idx {i}") + }; + Ok(()) + }) + .map(|_| block); + + let _ = send.send(result); + }); + let block = recv.await.expect("Panic on rayon thread")?; + // Check if commitments and block hash are correct let verify_hash = tokio::task::spawn_blocking(move || -> anyhow::Result<_> { let state_diff_commitment = @@ -508,6 +528,7 @@ async fn download_block( }); let (block, state_update, state_diff_commitment, verify_result) = verify_hash.await.context("Verify block hash")??; + match (block.status, verify_result, mode) { ( Status::AcceptedOnL1 | Status::AcceptedOnL2, @@ -569,40 +590,6 @@ async fn download_block( } } Err(other) => Err(other).context("Download block from sequencer"), - }; - - match result { - Ok(DownloadBlock::Block(block, commitments, state_update, state_diff_commitment)) => { - use rayon::prelude::*; - - let (send, recv) = tokio::sync::oneshot::channel(); - - rayon::spawn(move || { - let result = block - .transactions - .par_iter() - .enumerate() - .try_for_each(|(i, txn)| { - if !txn.verify_hash(chain_id) { - anyhow::bail!("Transaction hash mismatch: block {block_number} idx {i}") - }; - Ok(()) - }) - .map(|_| block); - - let _ = send.send(result); - }); - - let block = recv.await.expect("Panic on rayon thread")?; - - Ok(DownloadBlock::Block( - block, - commitments, - state_update, - state_diff_commitment, - )) - } - Ok(DownloadBlock::AtHead | DownloadBlock::Reorg) | Err(_) => result, } } From 4494ad157f56d7571ade92c8ea1f2b764bf93a1b Mon Sep 17 00:00:00 2001 From: Krisztian Kovacs Date: Wed, 2 Oct 2024 14:21:37 +0200 Subject: [PATCH 124/282] feat(pathfinder/sync/l2): calculate 0.13.2 commitments for pre-0.13.2 blocks For pre-0.13.2 blocks we actually have to re-compute the transaction and event commitments: after we've verified that the block hash is correct we no longer need the legacy commitments. The P2P protocol requires that all commitments in block headers are the 0.13.2 variants for legacy blocks, so we need to have those in our storage. --- crates/pathfinder/src/state/sync/l2.rs | 63 +++++++++++++++++--------- 1 file changed, 42 insertions(+), 21 deletions(-) diff --git a/crates/pathfinder/src/state/sync/l2.rs b/crates/pathfinder/src/state/sync/l2.rs index 294e74bdd5..9266d383c8 100644 --- a/crates/pathfinder/src/state/sync/l2.rs +++ b/crates/pathfinder/src/state/sync/l2.rs @@ -16,6 +16,7 @@ use pathfinder_common::{ PublicKey, ReceiptCommitment, SierraHash, + StarknetVersion, StateCommitment, StateDiffCommitment, StateUpdate, @@ -1037,14 +1038,12 @@ fn verify_gateway_block_commitments_and_hash( return Ok(VerifyResult::Mismatch); } - let computed_receipt_commitment = calculate_receipt_commitment( - block - .transaction_receipts - .iter() - .map(|(r, _)| r.clone()) - .collect::>() - .as_slice(), - )?; + let receipts = block + .transaction_receipts + .iter() + .map(|(r, _)| r.clone()) + .collect::>(); + let computed_receipt_commitment = calculate_receipt_commitment(receipts.as_slice())?; // Older blocks on mainnet don't carry a precalculated receipt commitment. if let Some(receipt_commitment) = block.receipt_commitment { @@ -1058,14 +1057,13 @@ fn verify_gateway_block_commitments_and_hash( bhd.receipt_commitment = computed_receipt_commitment; } - let event_commitment = calculate_event_commitment( - &block - .transaction_receipts - .iter() - .map(|(receipt, events)| (receipt.transaction_hash, events.as_slice())) - .collect::>(), - block.starknet_version, - )?; + let events_with_tx_hashes = block + .transaction_receipts + .iter() + .map(|(receipt, events)| (receipt.transaction_hash, events.as_slice())) + .collect::>(); + let event_commitment = + calculate_event_commitment(&events_with_tx_hashes, block.starknet_version)?; // Older blocks on mainnet don't carry a precalculated event // commitment. @@ -1079,11 +1077,34 @@ fn verify_gateway_block_commitments_and_hash( } Ok(match verify_block_hash(bhd, chain, chain_id)? { - crate::state::block_hash::VerifyResult::Match => VerifyResult::Match(( - computed_transaction_commitment, - event_commitment, - computed_receipt_commitment, - )), + crate::state::block_hash::VerifyResult::Match => { + // For pre-0.13.2 blocks we actually have to re-compute some commitments: after + // we've verified that the block hash is correct we no longer need + // the legacy commitments. The P2P protocol requires that all + // commitments in block headers are the 0.13.2 variants for legacy + // blocks. + const V_0_13_2: StarknetVersion = StarknetVersion::new(0, 13, 2, 0); + let (transaction_commitment, event_commitment, receipt_commitment) = + if block.starknet_version < V_0_13_2 { + let transaction_commitment = + calculate_transaction_commitment(&block.transactions, V_0_13_2)?; + let event_commitment = + calculate_event_commitment(&events_with_tx_hashes, V_0_13_2)?; + ( + transaction_commitment, + event_commitment, + computed_receipt_commitment, + ) + } else { + ( + computed_transaction_commitment, + event_commitment, + computed_receipt_commitment, + ) + }; + + VerifyResult::Match((transaction_commitment, event_commitment, receipt_commitment)) + } crate::state::block_hash::VerifyResult::Mismatch => VerifyResult::Mismatch, }) } From fb2b90b37ca5ce7b93d1102084362270b2fb6ee1 Mon Sep 17 00:00:00 2001 From: Krisztian Kovacs Date: Wed, 2 Oct 2024 15:21:00 +0200 Subject: [PATCH 125/282] refactor(common): add StarknetVersion::V_0_13_2 constant --- crates/common/src/lib.rs | 2 ++ crates/pathfinder/src/state/block_hash.rs | 29 +++++++++------- crates/pathfinder/src/state/sync/l2.rs | 41 ++++++++++++----------- crates/storage/src/fake.rs | 2 +- 4 files changed, 42 insertions(+), 32 deletions(-) diff --git a/crates/common/src/lib.rs b/crates/common/src/lib.rs index d80564b6f5..21dd5859b2 100644 --- a/crates/common/src/lib.rs +++ b/crates/common/src/lib.rs @@ -461,6 +461,8 @@ impl StarknetVersion { let [a, b, c, d] = version.to_le_bytes(); StarknetVersion(a, b, c, d) } + + pub const V_0_13_2: Self = Self::new(0, 13, 2, 0); } impl FromStr for StarknetVersion { diff --git a/crates/pathfinder/src/state/block_hash.rs b/crates/pathfinder/src/state/block_hash.rs index 9c0621add2..2027b840d6 100644 --- a/crates/pathfinder/src/state/block_hash.rs +++ b/crates/pathfinder/src/state/block_hash.rs @@ -33,7 +33,6 @@ use sha3::Digest; use starknet_gateway_types::reply::Block; const V_0_11_1: StarknetVersion = StarknetVersion::new(0, 11, 1, 0); -const V_0_13_2: StarknetVersion = StarknetVersion::new(0, 13, 2, 0); #[derive(Debug, PartialEq, Eq)] pub enum VerifyResult { @@ -233,7 +232,7 @@ pub fn verify_block_hash( let computed_hash = compute_final_hash_pre_0_7(&header, chain_id); computed_hash == header.hash - } else if header.starknet_version < V_0_13_2 { + } else if header.starknet_version < StarknetVersion::V_0_13_2 { let computed_hash = compute_final_hash_pre_0_13_2(&header); if computed_hash == header.hash { true @@ -462,7 +461,7 @@ pub fn calculate_transaction_commitment( .map(|tx| { if version < V_0_11_1 { calculate_transaction_hash_with_signature_pre_0_11_1(tx) - } else if version < V_0_13_2 { + } else if version < StarknetVersion::V_0_13_2 { calculate_transaction_hash_with_signature_pre_0_13_2(tx) } else { calculate_transaction_hash_with_signature(tx) @@ -470,7 +469,7 @@ pub fn calculate_transaction_commitment( }) .collect(); - if version < V_0_13_2 { + if version < StarknetVersion::V_0_13_2 { calculate_commitment_root::(final_hashes).map(TransactionCommitment) } else { calculate_commitment_root::(final_hashes).map(TransactionCommitment) @@ -664,7 +663,7 @@ pub fn calculate_event_commitment( .par_iter() .flat_map(|(tx_hash, events)| events.par_iter().map(|e| (*tx_hash, e))) .map(|(tx_hash, e)| { - if version < V_0_13_2 { + if version < StarknetVersion::V_0_13_2 { calculate_event_hash_pre_0_13_2(e) } else { calculate_event_hash(e, tx_hash) @@ -672,7 +671,7 @@ pub fn calculate_event_commitment( }) .collect(); - if version < V_0_13_2 { + if version < StarknetVersion::V_0_13_2 { calculate_commitment_root::(event_hashes).map(EventCommitment) } else { calculate_commitment_root::(event_hashes).map(EventCommitment) @@ -948,8 +947,11 @@ mod tests { "0x0282b635972328bd1cfa86496fe920d20bd9440cd78ee8dc90ae2b383d664dcf" )); assert_eq!( - calculate_transaction_commitment(&[transaction.clone(), transaction], V_0_13_2) - .unwrap(), + calculate_transaction_commitment( + &[transaction.clone(), transaction], + StarknetVersion::V_0_13_2 + ) + .unwrap(), expected ); } @@ -961,9 +963,12 @@ mod tests { let events = &[get_event(0), get_event(1), get_event(2)]; let expected = felt!("0x069bb140ddbbeb01d81c7201ecfb933031306e45dab9c77ff9f9ba3cd4c2b9c3"); assert_eq!( - calculate_event_commitment(&[(transaction_hash!("0x1234"), events)], V_0_13_2) - .unwrap() - .0, + calculate_event_commitment( + &[(transaction_hash!("0x1234"), events)], + StarknetVersion::V_0_13_2 + ) + .unwrap() + .0, expected ); @@ -1048,7 +1053,7 @@ mod tests { eth_l1_gas_price: GasPrice(7), strk_l1_data_gas_price: GasPrice(10), eth_l1_data_gas_price: GasPrice(9), - starknet_version: V_0_13_2, + starknet_version: StarknetVersion::V_0_13_2, starknet_version_str: "10".to_string(), parent_hash: BlockHash(11u64.into()), transaction_commitment: TransactionCommitment(felt!( diff --git a/crates/pathfinder/src/state/sync/l2.rs b/crates/pathfinder/src/state/sync/l2.rs index 9266d383c8..804efabd12 100644 --- a/crates/pathfinder/src/state/sync/l2.rs +++ b/crates/pathfinder/src/state/sync/l2.rs @@ -1083,25 +1083,28 @@ fn verify_gateway_block_commitments_and_hash( // the legacy commitments. The P2P protocol requires that all // commitments in block headers are the 0.13.2 variants for legacy // blocks. - const V_0_13_2: StarknetVersion = StarknetVersion::new(0, 13, 2, 0); - let (transaction_commitment, event_commitment, receipt_commitment) = - if block.starknet_version < V_0_13_2 { - let transaction_commitment = - calculate_transaction_commitment(&block.transactions, V_0_13_2)?; - let event_commitment = - calculate_event_commitment(&events_with_tx_hashes, V_0_13_2)?; - ( - transaction_commitment, - event_commitment, - computed_receipt_commitment, - ) - } else { - ( - computed_transaction_commitment, - event_commitment, - computed_receipt_commitment, - ) - }; + let (transaction_commitment, event_commitment, receipt_commitment) = if block + .starknet_version + < StarknetVersion::V_0_13_2 + { + let transaction_commitment = calculate_transaction_commitment( + &block.transactions, + StarknetVersion::V_0_13_2, + )?; + let event_commitment = + calculate_event_commitment(&events_with_tx_hashes, StarknetVersion::V_0_13_2)?; + ( + transaction_commitment, + event_commitment, + computed_receipt_commitment, + ) + } else { + ( + computed_transaction_commitment, + event_commitment, + computed_receipt_commitment, + ) + }; VerifyResult::Match((transaction_commitment, event_commitment, receipt_commitment)) } diff --git a/crates/storage/src/fake.rs b/crates/storage/src/fake.rs index ecd2e59fa0..39c9d2235c 100644 --- a/crates/storage/src/fake.rs +++ b/crates/storage/src/fake.rs @@ -222,7 +222,7 @@ pub mod init { for i in 0..n { let mut header: BlockHeader = Faker.fake_with_rng(rng); - header.starknet_version = StarknetVersion::new(0, 13, 2, 0); + header.starknet_version = StarknetVersion::V_0_13_2; header.number = BlockNumber::new_or_panic(i.try_into().expect("u64 is at least as wide as usize")); header.storage_commitment = Default::default(); From 51a6db265f838cb62cdaba51ef666e3a6cf8d713 Mon Sep 17 00:00:00 2001 From: Krisztian Kovacs Date: Tue, 1 Oct 2024 11:34:01 +0200 Subject: [PATCH 126/282] feat: add well-known 0.13.2-style block hashes for Sepolia testnet We will use these 0.13.2 block hashes for all pre-0.13.2 blocks to verify data we receive from peers via P2P. --- Cargo.lock | 8 ++++++ Cargo.toml | 1 + crates/block-hashes/Cargo.toml | 11 ++++++++ .../fixtures/sepolia_block_hashes.bin | Bin 0 -> 2761952 bytes crates/block-hashes/src/lib.rs | 21 +++++++++++++++ crates/block-hashes/src/sepolia.rs | 16 ++++++++++++ .../examples/compute_pre0132_hashes.rs | 24 ++++++++++++------ 7 files changed, 73 insertions(+), 8 deletions(-) create mode 100644 crates/block-hashes/Cargo.toml create mode 100644 crates/block-hashes/fixtures/sepolia_block_hashes.bin create mode 100644 crates/block-hashes/src/lib.rs create mode 100644 crates/block-hashes/src/sepolia.rs diff --git a/Cargo.lock b/Cargo.lock index d84b5ff4f4..8c5bc1fd62 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7290,6 +7290,14 @@ dependencies = [ "zstd 0.13.2", ] +[[package]] +name = "pathfinder-block-hashes" +version = "0.14.4" +dependencies = [ + "pathfinder-common", + "pathfinder-crypto", +] + [[package]] name = "pathfinder-common" version = "0.14.4" diff --git a/Cargo.toml b/Cargo.toml index 5a06185904..ade45cb123 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,6 @@ [workspace] members = [ + "crates/block-hashes", "crates/common", "crates/compiler", "crates/crypto", diff --git a/crates/block-hashes/Cargo.toml b/crates/block-hashes/Cargo.toml new file mode 100644 index 0000000000..88811da7a7 --- /dev/null +++ b/crates/block-hashes/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "pathfinder-block-hashes" +version = { workspace = true } +authors = { workspace = true } +edition = { workspace = true } +license = { workspace = true } +rust-version = { workspace = true } + +[dependencies] +pathfinder-common = { path = "../common" } +pathfinder-crypto = { path = "../crypto" } diff --git a/crates/block-hashes/fixtures/sepolia_block_hashes.bin b/crates/block-hashes/fixtures/sepolia_block_hashes.bin new file mode 100644 index 0000000000000000000000000000000000000000..3e71607f2924ee0a91765371247f27b3283f6e92 GIT binary patch literal 2761952 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

?l$(aJ)2nkx?f+Yo*^6&Yn2G_L za-YyNX&eLT@c^l4rVLDd?Nf3aP<4sE|0wS`+xrIA`pb` zuWo0wh>MtLs&(F3boKBX#n>OMIb%S5%P0kVLi{ix&fUZ^3L911Y*p;hJ$t>L1>Da# ztH-CyzS{zD^x7&Nh$eQNKWexcNXJSRb#VNTe0j~@tD#qogop*U6z+|hN>O^SwMU$h zQ*81Eqh?|&HE#3bDFCaG5x7aJF3N=nwC&EI3vi^}ft21Me-rFog%{ zm+JxPdPP}KVy+s!_VTV1T(}J53=p-!PJ4&F=6C@BbEzi~5tBc9Gzz1C6}%7rcIiHD;Ryw7bS5bxZd3t? z&QZ|r@>IS^n9?V{Y}12?j758;#$E-NDx2?$O?kup*U9R$E^Arzm{P|X;3K|p;4MvU z9f|-p^wi+c(0YlZdv`<_^yXy2xMGn&^lXN4hMl(jKK=*j#|s;xBv-FrBk0o~TM-Jm zAckLP+KWd3JR_8yKk@`qz!PxpSf_2VRHBJK zy?}f8**#T^RQ+9djPJNp7-0qR7%kFfZiT0o_9kKiO`GYNWy>D|05N`ql%Uy%ptA?B zsHjC&luT8zV7Xj)(D(=SS zS=kaM4RKst(9j1c69_?qdqb(p?@-BKMQJ%6^{#vy-6RAD4sI6NDMtyL z@~O)vDlXz`I>!B}sLbf9BHu;aMP~<|>I*{S{y!!1$7&k)K-T{)w~GAqf~c)YD{?in zw*de~Y=PIvy}x#`&1Q-2k2p6=njI&C-P*$}maWmGta$|1DfO;9%-_z$!?jReQ|z{J zDv|3AvI5a3cksjghxP%-?FEIpl;wd(F?|-|UZO)!{h6I*T9>394;z`g$Il0;rm)lO zoRn>+cw2#ypB@QE(aIx;0rTB0TXINGEt~?@2cSX{#nBg2t2?L=Uk3&JcY?`fM05|2 zw@)-cT&OLa;l09^s2fbeMR9Ch$F zTtSnq7oGiAqbLNTf<6_G!tAdFY$vHwQILCzK<~PN;@JiayriWN_?ZXne2W(#06u|E z-5i>63oQ$;Lw{mc;wI4-tKR~*on{7%m}@;`bV>kyv%bB;E#A*Jr6uq_W5H$cs*QM> zvqb_fAikeP3QM#CNkU{X)K{T*oLrR`1d6g^tbn>@ZMFa!g#q@Hs08CBAfgPD^U*%O zyX@tGF_S^2qn znvo}-MKRe=^m*L$kJi_)aQl(u2(p`-uXzTa1p2HMTxtDoF0)!5eD|tO1bkj{)4Qo0 zt>hvRpBp{T0{Zm`<%$HzDky$-yr%$3k8vqH8M5oOSLc+J z0MD25NQMCkAmsoZMe@|ioOl4-*##uAYk6;9TrpCyh?+(A)Sb%-NjMROAft)wU>yS1 zI}$2aKVD2145?)V6P%)M;T_}=DuI2@RAe; zmvqm`DMT;MZGQ(jDLwe4a9v9W@$5+1Ue$g@JGf<0cvi%XV(OujH0TG~aEaKew#6Zm z;o5mrx>R)*)SfQuP2e4fMm=C*N16`0ZOVy}XD$Z+I8_GmmSUNW;)k18fILPS4}SLh z5d`pO1b!_W0XIkE#j^&%XwbAxSMYyGN#J?rb-L_`?Zxu}XWPCA_mZwWocaZ)VU0U} zn%L9>)ieSV-Tv|nKoC!P2-8W6u(Tldj(&&_(&P#;@5yxacwfTCpxGIaz+?6M=LOf}0a1?0y) zxcO)m%7d~X=5fM`>sdCFJPHJRhjeZ1E1y^LwkdAX^1jw-DBrnQLCd|=4xd=pROSR< zdZ&=(M#`clU+qD4}=4!!fsI3AO7?@KCbVT?61h|B^}X0{+=jgtW}oI{ZAvG5hx#4J6R){tp*N0|En zQ$Vc043&TYjcZzE_*6Q$t->(2j?r@)Q%vNB+v{vDPU{7T&H)hun01=rOQ4ljOK{_S zO*jGXN6NBth{Z{siU zvXk%Z%0YP#k-+^0)T=*z&R!nHR|MqLB0WOc==p+6%3%uG?#69$yUa-dNb4WJdqD|L zsN)MCa_E>Is+JTXV+Ix<+FwRv-IWLgzU_+iNqqlS^mco*$=wA+&ESk`Z2t zxiX{&H2Uu4#a~jXnStQY&M`s%byaE~ae8#cwq_%q_!B1s;6)EdYL#pp=lH|N$Rulp zpR*lsj8aM}_!TS)TFq+&f1lU9D%pn%b6j3z7h5LOo4NVM;XifOk3QPlpfCIY&D1J{ zQ;=*^{bPifap3!^p1At_1sWzV0z3SJ9vcAwu+7ng0=*sTW2`-inTMVa?bJWW`)X-3 zD}OX;_Vl>{sw_bdyL^00^5*?`erBzY#!)cBn!)ZJcuyIxoU0B3yjeml3hfu>pYy*H zH2ICkhdOePB^PU#pr4t~;3FvmyM+G|RCrEnrygaZ7CD*gskkoB#LNNzQRi+?c)3dj zx98sLD!T0r91UZ!?>lAHLM~$Q!X!_DFJe)f#$ww8MAQl8XAP(_o(1gkS4Pfk%9zRW z?2vLm$NzvVf4*%5Bxq-SCJqkx?^BqJz~@*iHkHkQJh()EyVP8Ob=VFEOj?Lqkov=b z*xL!+7KxGZ3xH&yuDA?tRewNe>XiouUy;$%Kk2C7-65PZq>J$DDbm?nov3Ypw++8( zP#(qw_cwAzmeRnj_+OP*);lW}Vsq7v=_;~W!HCf-#T~!{C1g!Vu>afeY#=KU&X!u9 z8;p|xX%eWbDBs4wQqy1tP-~B^RaCf#?up#{r4m1i@uaop7mXIhJ?w*c9*y1vxl#TG zo1gn%wrcL!2*2N$Z`=g^7v@eS<)y84IW30*3tQ>!+GOls?qfB&Qhg?#Ors)3hJe>c zTNa(t$z|mRd~%4Wm}3#*HYo83pbSSV&XWhw0-;BSazuZui(tgW>@=pjT}Q z0v;~`cy6VixBP)CPfTsGtNfe-t|n>qawG2&EOdMaf5t(U>XmTpz*L^}%dzeog~m+< z(r>!5(RWACKbvGuc|Ip|8OqD4|JYCk2jDIGg)sDF z=#Xb;mYzOZmD>hVY(XGt!sckymxDC{oeFURuEGSCqi!uZuCuaZM_sGy`|A>{b6i0cs9z^HhckVCw^C2fLU=c#1l+;|KHM1!M)V$bV5&ze{epfK zh}r^cCThKPiRoPE&_JF6L$NyEMllWGw23v^Oe>lp(^uE7E8x`NLcC7vA_YwbYMMbM z`5QF$2T7D4hQcAhno3BgBr8#)=svmIES7HpS&P5tWxJ7E)Dnn7E=l191PI{%@Y{nM zg_Ef9F$jPM%hIv0gJ9Lznv~Xf5~`!gL7tC?cbqxYhVCFm2amA?TYeBr#6oR zwXjaZhn%^TL?;kT%Kx{qzL`Dc$ZOnQseWBNkeVO_xa=jWzT!Atc3uxg4DcYjoKHCq z31w*|?MT^^T~iMSdM9^Sc8F{XJ^oN!R_w{Ut511PdN()nK4Ff2z<@OdGc7H-^76yZ zuh$GnNoknxT^h|`IhgJVzkGKO0EpD)D`~wzbod4*pXZp14 zJ=+)rPg7Y7RRPr1Nk;zV!U8;iUcJUAG>#|RDf`KXORBgA=aS9!9EERCej7@u zz~T5cV+AU5^_)|nT*vWE=cw-k9$Q;PgUFXH;t5!?qx7_)@6FfC+#3$y1h-7xJ?oeN zZ4`fH;j;kgl}bYNIT6d_=@$j#PE-IcxQ}R8A6N1K9y29Eyzd*ZSG&f`S=FQFu+nVE zjTwt66i$qr3ajA;ODcg0Sic-bl`@@5rwrJ=O9A#q3?4%g^snDKvk?vjcu4ZGG(5#s zUJ1+CqBE;qL^qND5Ny#Ne(*tN)j2l4g9S+tccsHptmBg1qtHt z(zetC(o)?s5DP{F%FpHfSb$aQ5g;o(sf&02-MJyIDy>QGk%Y-S!Tj=+Si^}@3U zyS2pb9Om|(BqRuMx4}#T!!8UJw^hgA=GPs*w@!Xa3ej5iD30QLlqgkul8cZ52#(B! zik3nJ$K;??@B!mSO~w0dZF2?65Y%+~V%FvZ&#zz42`}2v6JhPEH@0T8+bon$!|sT3 z)bI3t?#PY-6F`6r^Qz-_^SA>TQ76`{+KKcp) z@?`TSoEhA*rR+SUg9RO(pZ+bCg40X2+rBg2h};JS)P=x8`D}Q&0uIV{T7pbxS zIc*pM;`;MqF;?yv*VR8UuvI9xi2*5v-{nCJtCvS*ee|yd&nQZVYj+9FS=Xb0geca6 za{v&7iQ3-(#nCr}NrspJ(VE+@7fkUhh;XTlV*!-_p>=lU2r9yIee(hvSrQ`xjz0~k z6f6=7oF$9wC#bNsFnu4Hl1?65_>gDw;;2&xfZ^JnbmbpG^IDNtvvhBE?N&!Nb*2W) zb*^Cr{#sE3dJ_zHFEpwCnNw%jJ=yGHMY~T??#^SVLDl-N1#>0@9w>++FcZO>RY0;W z5p&r#1pX3V*pM`{fUsUuR~d7Y zCnWPqNBM{XD2&3_XS96>48DoT*p1ks15m{RG0YIsUG|TN6?Zc%$jTZ z8*dJznO^o7tx85N-~#l$F41(m$)hO*;H%@a?HF!(xqma<>J8ft=8mkrhf5B5y>_ul z68PT$;r*77X_JOx`fYp*kFK#NG`{Nj?a#W_jK39*$42%87*N(KdljKv4%<-wH@U+q zWm1dxCeLOo7%Xjjw@M@jJ3Nvhgq|a2bT}cnEuD!x4To#Z9h;P!_oARTFhjBghVDF> z$a7St(nI?YPJ^V@%wr)|-2~8z?hO%gCM4$sfevCzz_So1#tRMl3}$vP+>6T}9)#d< z@Ach@z+Y_uwi-dYX#GfVlE?>&MfhGlDR8dN9ksVzs0?K4d_O<}^xeTeLQn*+^fUe@ zejSsHC9FACwH++NnZ!zb~y5qG2o z5!o6^iX;c(7@U?ipOZ*VO|#5?>xE&k4WU+A(4iOt_MdjMXT8@T_2E)2IXF8IbR}}_ zQ^jUd5j~_XcpDqGMCuK+TqxAfEwr)1wYUq<4@!h@{9_|%2dDv0gVI`jYDd4m~5|F zY)R9|9tttSS~N7%`Yyrx)c*DZP0vg*IHdP!mh3{K@_#V{g}cZ!msRN$TPk`&nQ+cs{b=z3jpw6h z?56zkxo4T2H()}uT_*{i`Gr1^cReg8F%J&}9QGGGY`)^REjl6l`tHEO?mXfJPUbMN zD8vT=BzC<9lcxAw8$p%1DaYVjuwyfb9n%+3GcXw@Jp>wBP@1;`E!Cn|n!HA#K16{K zt-*{tH4%=#LT8Kz8{h<%!Ij$qhYSj=GDRrTkI}o5akM%=V95r0o~~y~|1`T>jF+Sb zd~)_5nh%XM0=vr6@M|Gu55HW#kldmqJ06UI<=(LeUqoPKs?!H+RgJ>32h-No@%+Ix z8JjH-l0)%700V{w^1=AFlz~x}HxSAg(=P}&F40V!t=YcB;smzv8bF5ww^p@=F}2c0 zRt3_aNIMLrdT61vP_0r2dNeMT z?`4Sq!db}osq&`#w9h7T)7EpD* zU>|77q`3U%&REWPW{LHj*cJ5xO)(Y#%&3^alilFjSPOOE|H{EfL7NLo>=EOPgC375 z;mB43K0;5s?Aql$E5`+{X^uAF_6D$9A96u0fAXlydeprKVNnkmqalA=lq<@ABMv$= z+zFl&N?go@loj?^YR_;7&Fd{&Y&%R$ppTGib8|!qT0b-~z&+%v_v!`qY84y@gaQ`o z_S@lhgHKKB)IAvZD}RvhF~ylgs<;xDO}m?Zpl{Liu5Zd*%d6<%Sr zSJ&UCQW(PpwVW{n@*36Pzk4YxqE3K_V!6^uji4zULv|x5V$J;oB1>DMPO=I8Em#^F zy}MSqH4hNbh^=pUO1?b-Dc#=!%e!xWrBC$8Z+j^z(mSYp{>$Q?sTAHexUSIq@t{@* zjFyIhYz;Bj#a|>fOFd-bnO(&frBli@<;yyPG?c+W6J5; zlZ`PKcIeXss;{2uO&W%eOt5au&zYPCtTSEHPI(^B5`FsyTw7=lOF|@D-DMvpunlgb zw0{x;BC#bNrFz;X*Hk=5DOw&-CE_l0^?1-v#;J^GWM<(9#z)gPJ;txRh_dpu!8z<$ z8O$;XNhp#sOUn>{l(Gc?6fHjkVvm!~H3cW6>pOQ{Q-42kM&;P;#0X$x$ZszOsYQdq zzOWgz_RA&OP;@Pd@%;@x{7#{N8)A>N0T?U96fl*v+`nBu3H+S0UEkyr8q z5Hh)k{eJ_GkXvE{Je-#dq7uM9rFP%0a6zf1A&RY(h(m3pn+$N{fI?K zJ`04#Yp_@M!o&ry1p`Y$Hi*LJGAeKiZ*4vBfCuPqY_v*Gs##OroA znPfb3F`h^Jp!3$cjxkYMGnXFL$^u*Fbb(?9Bb4?oiPo_&n?zZePlAjq@}L`ns19q! z4+Q;5ZPh0O#KO6t>X50RVZnqdfTlh=%sAMW1p@OHdoT)UlVJ1*nSTm$`P()aiF`Pm zYn+PDqFs7VEZW(pj}@>8;Y_#zrv_=GCS5IU*K4p798J!QmK4b_(r6QbS#Kr$vXadM zZ*k4~$%d3%yCv9CFTCN752;BcLHyHC{fc*!iOmxL%{lpCaGn`>pY}F4K6n1Fu8}jb zi|{fP+_^OLa3?tch9&pw(%9k;%^AjJZ{~rQSn-WPN0-&Q%8!>OA9)W3Na?j8*ukS1 zf}O{?h00?GrvW^7{=k|u?-;CenMA(_19%*!McxgLFD6?_Bs?W(1jLMw`vq~+lN1uF z6=$Ufbxv{BZFg76&VQ0fZT`4oYA*A2Wfks`RP8wUy$-hk910wZs0+PL*}Rhl=QIlN zIN^LWv@9%P0(AyZLX3F=fqq1AkNP}2{*APB?aYCRH&B$IJj1CAj<&$p3*veMbC|ch z5y-8G(QqJBM3tZna@7lsc6_t>d^x$9wa+_$E z;LgZ|KcbQYZl_Iz1^T)Wd6{CD#DeJSxqad*7|AFU?P~o>U~Ja}>)hb^`@vJVN?!SP z$GjsH7VHV8U8tiq$m5hOhI{b^POcgxRYNR=y=llT;oO+rgh0dLE)BIp0 z&eH7{1`T7^u= zG%#EOsyWXl#n`a;Z!yvHi`g;90L@q4=QmkU?pHP1nm*$_3?&)f$@h7SV_}5n% zxd}|m@qej@xC`9}D#~e1T-!sX zxx-lyK(%xPbT3Gv7aai0yl5CaWsHXy^-uGrvVU~=M1C!83ewXBhVhhQ>+_Q?X%i%- zeRon&_S?q`WqjKV!s{(-P3CL>wMi4Dv6dy=NSoEUH3grx)HH->ZNw*aMCFC`$#Y!* zd7e7Qd=n15IAJQGYm};4-$d4)-0%WJk&v#<~ZXS{67)4Su_7HXV*GRY>>T7K}pY7LfjiSDpT;%v2891PnvilYMqiiBGP@J3@v?2n=mf~LpjNb4{aBQ*wDFrj50_V-zG;wz{gT5Lij>N(nBYQrqlc^fR&oPYTtP<2i;}-XAE!EU z_%ex?zu3)%F#33N=Ls!jZ)D`XBby$=2qN zv?~Jyz@ltso;v}p7q!KMr|D4v^W=wH!h2uL&2w4=N7te!-1ftoFe@mqBe!EbnqI90 z=v1kdSli!1J4{`WWwI}dpz=SwMl+!-i&lut+$n-h^kG47KRBI}E+25aDN_rpw&Wb3|+^oJo zGm=jl>~`V=kt(B#qa?#*J^MKZlT<|=enAN}K(PJ?9d&vR@U*@Gfy=?jHbt`1TtX^t zJeszR<6u}MOv5Ca>P^)p?q$dV_r@9D+1wizttb*-I>4iuF)>@?qLC7Z(^jb|3oR!D z4_@IvL(2ez_=^|FK8BDkFkE?Azk$p7J%r5q&F94f(cUc-Sv$n9EPf1}nd0z=l_Wlu z00bu_gp$#Tjzu5$HFzAp8WwLk!hE{NQnt_wq zeA9K|2c}}AcxhLscB3s3(`>|}q0v^+bY8y)@Z)4hVm%k;_4Lwp*HY)0lyr&Nk>aN88->)-bXY^Yeb7|D|ZPf7QY+Ou4V zy4O`QfKzbiG~uYMkhZ(n!ctp>(Mucx?8PE5eI&5ecb~Gj)`8FW3HQ3Jiuxh1l-r&o zt-2cmD&j%Vn-if2x#KAP^z^tFezs3}p{)bDp1>6QLf?P_-u}rDMyO6>c0*n?I`QYj zeM+Ab?7a`!)wvQ);}YcpaeHc;2Jtm*apn;w_&)&kc<1?ngLW$YU zp$1P#@byLmjH}x-VPx_vnZB~m%~h6j+lYIA{ugJf<*!Qelc&)Dq9rZr>KPt7VUEJk z0~i)W2>jK8H;)0Gq50Lo-Xx_1!7>7oe#bHV_g~X@!4w)_9s9-AYBUZ%o8TW{Kzi^&WKxk7~-yPDZ!uUakK};@&>g za)xsU3X=~K_7_yczmV7eJBM58sa@rt<{8Hs{zMy}5QBXLG97kTrA1B^H@l)KK>Tgp zv-n-r&vQTK;P4kfR_4(B)s9 zxjc_JFA-RFFThoC#I8T{pce%`hMq1uE>-jZ`z>w2!V%G+VDNz!|JJbXZcn_=kU5M# zqiIPTYx|}LSQnZ1%aaQSXPbV?^+JQ`H+lZI=74kWt{DK?6obD6Jjl;Byk3puo7#)h ztnL$WTRA+nIDI2u^Nv2qB(#VE(GmUQCdnE6jmO+m@AbC8o}d)M@OEQlmwUbQE&s*@ zu_QaI{~?d|wm}qU&!R7Nq#KUzb#4MlqlwcOr?NK($&agW<%Y;N{#girQ7OV)Zgp+^ zvp$&qDWsiSkp+7OwelJ5U4ua-$H7L3QA01!P>+^m%v_d~V}UBfmMnDwb^L#7BP!oU zk=oed)wpQy$3#Ic6JAbsUq37+7G1psuhum2_Q`E$7!i1mE_ICmgtE_B<&pGY zEQz%PR=Hkg3u~yxb^xHesW4#dy9*>A7AD+S*xz>E2AggKjo^J@kV|`^aLvbj(|*0L z_xN)vUq4vPlAkgzzb94&w>q_-e|!&*^D&-r&==qGfhVtxH2UYI&?G?gr{uN*V%OQH zJ7dOUs+V93YeSwO(zsU1|1$l}qTegYpsKY5ERN?%|paTf# zDDMnR!%36p8*2Yr=kX)A=Kq^$G6aAlk6ePu^utc1N zhK^du4Q%2A!2IY*{^sLrn03jo~dWsMr(k>Qt03VK$&pdJyWM<{}dOJ zi>8dnisX}Bm8OK*JKt!;kNw>Q_O#KsHG9G=;%)_Spe< z4V>jFaRMS?xbFyP+7|Q!E#fd@q6)(RT{h-J8V0}7%6$3|AY0b%9g3ztjX}qMm~sE0 zez1=KD0bA}zSUNZevT1BMCU(-BIdw6J9ECk#7iJVk`PS-P@jpzaCAv{@bNf-nm#Hg z-GfHia$VZp$3C*E5+2b6yG&SC=pDXt4j>xK7;n67k8Iim)XTK6P9nZV_1TmL<-n-@ zC!q-ZSG6C~z``5=j;2){*BWXZk@_0BGtL488QzWH{X<>Qdicj}FA$_~waODgezx|O zwxV(X3!HlfPPgg8GuiBZW_6EKR?vee?0GY3xxpRKv^m{-0}(O-e8zV+O+*;Ps+WD% zjRSGu?{WBxaY0LOs4-gHOY|)T+vylGxLVdQz_oBSYP@b^O!$`K)W!g(l8d}QyQVt< zTj3jP`1QAI>1+{F!;U~Rf=bQXQL!juo-Zzn-O2C`fX*u|G(=27f$bO$a1H5n6xb}LaRQBT`g?6nbrHtyUAY4g^a92esR=tTFXGr^tj#eVkT(;N2FUMT9SMsp_se7vTf zkhG}-=;RbR0+*lrR~4^rQ=e66eqG`?HcZCv)CMi)L2aT3f$UB=LSu*vHCx4P`FXc@ z#c7OL%Qa8&D1IKITIOa1j`wB!%Vs_6G7{t1Lad=AqUv-63x6~(a?BF3onUALxMLM% zFBwt2V*3MkB&o#|3-SbXC-IS%GWd>^3wX%}Ht)y$(d=xnAbW&1*8za=8WuVGdKl>8 z1gVScS&bobgKi8>u}9=({Ly+oc}xQE24~YP5Xw0I(Ydu+pqKG7~L-j*#Ki z^e2+}_33+jx8hw=2^TI7^4KjLo{$tz>30s$rf z`nGQ*m)F{$S@R!-aT-cy{-Z{7Y*tCi{%OQjnC`d*NjsRACR=32EpD@tyAQWA=K_m8Wm$f?H zo-klgQzbbA6VbxiHsrZzJTpm<*wSaZqL-1M^WLTqacjKtf3&0k4Uk(5J@QZUOstQ* zGFDNCHa`r{4!n$wQ-kODp3mU~2W$zv0ynyvM4uqInF)!s!fwjo?6Qw48khS`uDYZF zS3>Bv-Ia=BCUCEloM~FIZR^&R+kJ+cxzx5kV#)XcU}1b2PZKrM|8PwtpPGo>CxfiZ zO`hluqYa;OB(nqta)z3BS1nbS2KXqGrJc%R5=|lbtsWBnh4a3svSc~mw1F;!PDT?`$+>Hza0hU?TrlgpC9Q(5K5k+)6cCz(Mg6C((_F&qe~6YGKGP&fNxJKk@Jd)9paB*ab=l&iyXFpqa`q{xC7_gxN;;iA^mh)PV_Xz2n5irfi ztaMM7UKQK&SNj>f2em^0>0|5dY=;5COaD=H1o8H4NdCK`wl3)v}&wm-@RPURdkMWL(C{M`Ede8M3r#j$XKhur-B z{B0q$8CVhrSq*M_i{e*?7k~1JySEn$M##(p@7J|g1{3p$er)kfR|KJg+N$PsT>xej!*CEHhlDu* zhW$_A>64kHPH##Ff<)JLDo{#r{VGp`f7mQWK#J+- zghjV{-V~Lw0^*4U2^4Td23EKuEfhs^?hPYU84CSWK_v1rTluaZOl-RVP@*&T*=BeS z%=+OenD(nc!78PBe9LN5);;X&nv)9!cHz@XgE9hhB1J+L20Qy4V7RO7;Au1SaI*Tn zNK$M9!N01}U*pzu(wTuXfz~On%gJT8y;7r9Dd*IGG0^9)rX4M0~IB~Q{?4qs&sbFdp8z}dB zW_N|l%JbLVR@RLK7Gu1;7ECfwnxU7;D5_bDV4pFPC*oX$qzfS^3+`A1H0 zW$&*q+?xM$!j^%@c0<22WTkZhl9L+6yw*)3 z5(s(-!{)F7oq*s2FvB6UB>r=4o3Dv8bifFBc4l9UmEb>=ssrEwr=iEF-d*^?iV)E1 zQCH8EWhX_AUzjPmC$8xdCM<^oW5nExIt9BhUhqJm6)UVV;O!bmqUGD;fFDT{{M@nu zk$-GoZpDa~@SWMmTJ%*Bh_3Ed6&2bSBt1h~Vum>a-m~iR?w|1dY{4Kc_02x5<|fl4 z^FydYV~BQG3_Obl;b`Z+&+So5qkg+h@5TsB{>8BtOk|tOjq{8(C-r}Nny2wUi zkMb`w)$tEhaKcf|L&b(tH(`tG99owK-t+W}R)MI?;k*hI7+3|^){mx75h0j1N};PGS*H4hSl+F`IdV|Gi2J6MH1+i42Rk)AH#Tj`s;k(m-lu8nS%6w zXszeFcKsJgltxw%*Zq{+YkestJ7BOyvu z$cL!o#_E`E0Y@$t6vo;vYl$BO_r_5+rtN)T^5;rI&Ftr}DBsVf*t;clLNdSj`}OAl zBWYlaOtZX%^KGlv88{`xB(;J%I&YkFzv=1ItB5fHz?gO5Y7Q8i`Zsy?nVyGA zQH03=JzO_%=NTEMoz0s&gL2x9o&Ko~^872jGQU(Cin_T3xrBs0_5X2|11Z5XoC3ds zWpK%#=Lv$7^p3Lt@@+~3DBa(rrSkZszQgHy78@*Y3fxqwXN2^vKwbvBYdksv<_r`O zzEL7Nxs#ieo^t{?s~P*b%@JODF81t(ej{)P!i_AKz*3tjYr6QcHxF)x6mu6YRznK3 zI>E+MTd3v++ihUL$GTcqKmuE=%Y?(O^uePUE`1ycqyq8+`W$Z0Wj1JvLtt{Gv{CBIh(ukn3hdO(*}oDauwLq;&8E z_Z@ka1^L=Rw3*TON)x^za+=!9S;VF?e)n3af?VqWQ1F>CTPGb+O}EC^Xb#zJ3vedl z?;>%1I*hbXD-WFqbM{%(vKAM*t2L7mhG(!kYT{mJrssOwNql1+5ZA5)VGv$63k*ym zCTa(XcX=X1+t4%B+#0X`s`+zUbsQT7)ftZB_!fwE^D%8$t2zCv<3QMkZ+=Fx3TG_t z8yAlPUY`nj>9T4X)<=j=+*%vUB`dFcU77pUW~3jU;Csae6hJWZ3?L8nVsR5Wx^v{7 z)eP~~=a9$-!`KFY;Ga5HbNWC2w+!Feov-pUB3*ZJ>z9lgi0SG(#OgH+Z*w~ z1JsbIQX0s5H%KP;pZVuQxz9G%#pM^Do~I51f-h^{>44NU!m1=`2uipgJaPXnJm?pFYXY2xNN*=kTq-lno zVC#XG{jz|R2yr0k*zW+XNe*%bln;d(jR&VRFvMlb zcyWvXgrnrYn~}cZW834lDOizniTk^h$29vF%>qOm`Z!brytGq)Gz+gt$)lo$z&e%$ z#!SV6wf=ht?E_xTHkX)Td^^N#=N?k2qv7K0w4R8X zeEJ3XA`ssOp!rUjl?d?6_Vu6`X3#jnG84J=Sop$Xu^?pnGS)=^M2rC5$!~nN!jIJP zKl90mma3d-t1RR@vZ(>oB&P)fe-G7MUk&dN{GB>IAQn&bHs9;5OHHHP_E9C7L?eG&(FW0_JvLg0{h3{W4UqSp?f zN0mwd+N3%)1BXKzll~#L3j-V%Ga+`4}+Ds7!+3st(tf( z2R~V6wbIYeH!jF@vBaiq3_z{}7|Uq0}t*-aIb@X5FCW;V50ksmO>@ zz%Q7)+?+CRmD)+ZoX;Su2TgSaxSpgiUewqIvRf>B=u4#8I;{Gt;Tsu(fY4etBf+Bt zlK{K4l0*Ilg}@$&Be4zp{9(1`QAuzdeLiBx&s#?Xh-D*7tH8|3dd#;G9Wr8`xnr(5 z02sb9i{pKYTiV?OO#$?a)ei5xs9=_{Y$qt{p?^Gg?1HicWiUOi(NM?+;;m|VJ2_%| zQQU2b(W@P}px1dZEM0JO8(PfVUE{X^R~JjiM7)e;^~epf3m}#h2|qS3#;DeGRPpUc z8nBE6(hd}rtJzi4D*W9099-PY`qExk_Z>k;oU*LF$LGlgIHt|31Cl39K?Jqi738Aq z)+mKy$)HY-i{C(AcEnlk3LcOnV}*xyscB$%^bQ zJ>?4NP1@zrb093dk};?(rZwbG&HXS7jP#`eL>Ytuwr|Q!#P*EdRnr8*XeucS8!it83pMc4tgKS2=$n{tVkkie zE}43|qLtUy4<9#nRgpvmH8ZxqR>*j+1j%uCawGLgtAT{J;E(ty2z&Zh3amc{z)qge z>7MZ<{NThcZSwTb?u+Z{L(WZ)_NB(ryc$9R%=AOKIX7W%39d4G&$WxFLp~2nKtih4 z7eLq~GSH;~U8}L^-PQ@lQBIxJ2mrY;Fe_wauR{Z&GO4s%1;)INfg+#ap-_-lqjjIO~(p$$?WeB}SZ*j^- zXeaz>I0A2z!ar*RC)h`Ga*{K%78yXt54pdVSbMT*mQf4IHtW8_lsZuZ?-4FR{A#5* zyVYvFWM8P>xpWQp&v01k{vciGM}ZavARHg9o>n3_H^U4C+d*%VEEn(fvCF1#*^i~+ zPoF9S)IOY7oe}04GQ4>%op~y9sy2o(U;1&aCrcsDE`l5e#+ZPe<5&9`A=A5-Kx{9W z=~-Y}>8op`1(OtriT5_y=iky5>0nrkE29nsT$3aWHJmcU5V~K>}bHdwopjFVv zHpM}X0D_o(L%t?x)&n5+_}$P0*4iP|h4f#AhxKqGk!ySLq6nAGF@;Hxu)DOR!^Qpw z!t~~aWLEfEU`$`wfgJpp@7$P@B4H|4bI5uF_KOn&Cu=0_dhVoWgy|?FJi%69>P{Pi zP?tu&yEm`t_Ae0vA*VRUQ+0Q-0*vCxH{Mssk_?^mr1zQc%u~s=00!X#=$6ZHL&QXI zZ|uq)82WR1a4mx1z3auu>|6QoRqCGw%||heuJTZnnBBF`aex>+MzRq&Ku{zcaHpeT z@F~!Zt9SLhe!9w7!idug@^uNbv`y{-x8(BR zxy{CbI_QXDYt{kPTjyPdZ#zi48?s{B{o0}c&V=2+NYgq6$Cs1C3Eb$aXsBy*j8SDQu4R1-b{2moQ9_aP=dNn`pT zuJNry5AuEc11r{P3*fWG_P{CzT?&i1nUMVWK|-T*^U>R1+|PjNbgwEQ9gC;b#tByi ztR`Uqe~IkSq0=>nCFJYSTDibTy;znz5cfMD77En__ZdT_Vqv{4GjQT{Fc}w$+2Zds zi~+|8*MqQ-aGK==JT0CiKdd-PEjDUBNkz)Iaz4e_gDI|;%#G&d;=k<$=jyVlvCP8Y z^&Hz%xnt(z$p)7{6&#-CDwjum@x)*Q_%^6yC_3&F=ZbbYhQ-g$Upg9(tgKoWKec+8w1$EZb{&PC_sWp5* zChlKmrZ+9XJml&GyYsA)=M?t02UdGDWUKiw^HfQt4p0`LXs{n?J64O4W-a*kSRIP4 z222LS+mRaGZp9GdnrcY8w70sfJZf$9UCch#=)$Nf0MQD=^!tqYxT$Iz8(nPNJg`D; z;?ys}bl<29e|#Ki1~BT5*)TzBa*l;1#(pnsYo#NEv5$L4vspcIsR_9s2GDd4Kg_bx z67QJ=HPhQSrfV@8xoTqgSa(|kkTBx@20#=X_K)*VIx=!4c@F(Dd}z9JGW<Aloz(H5g*1utX4q)X0A3w% z!odHGaOS;ZcKQt|d=av*mPPiE_fPocuF$+r0L_#ZdD=g+p*6AH8=q;+RBmx&>0g=7 zNwwgD>$Gv`1#Us~EUs6U*?>7F_(ab`8sI9k=nS>4LXoE-xCJed2amlouG(*?vPlL> zn$C#TR+ME%1X{bNJUs17&NC2G0@64TI)SyLSwOm?G)T*FjtQ-acKbY#eY_D7%KSVE z0dWjJvFe*OK6?5yQ4z|Y1(q(GPwq)ANcQtk{u`{90TT#Ptj~(-M4t;cYX|qkfkL2- z@X-IBzWXA&uF(T%2UWZuy%~`dnbGFCLH~KC)qn}wzcko}#kVga=-z4L21V@QVmnCK z=i9=S9Mk#~M`L`>Avilcu)ukbpq-VQ1Dd7Vp_xK7%6L&fA3Arax+ly8jjvr}UeAAZ zVV?$&1uE21UTcZON<(7KhhS_>MCH)(YAutSDS^!S{o^JC1z=8l(q*Kb3bsC=H4Znp zT9|Astnk}E>3>aBG9o(O0q?UX+mV_}3C;X$lZjHRA89GQB;#GE{2I&`4Z&#e1y;K1 z^2OMd!do8TRtxsqnL}Bepy*c^ECo8q1AVcv1&aT3;zk|JlTvhFW^B6Y^D_T)1RNiW z$jyU3U?i?i2dSDT(u_p=StS#Aw!I0N5}BQ8|LJXYpU1+l!gB{^1$*FS6cK%(mIN-N z_E42d01)YVNRy`GL_6T65J_w91NBe`XJo#5nhag7Mz2K$El`h8K?qR~!M~;1Im$3 zN)kwh0;hEl7Evt4L}AE~%RI90g;J*X$U_oVVQJJ=tM>^52E;1TroAFM2yo50p@(NR zCOV3CBnavLA947Bf0{4p20)JoD3Yb@pEsc=pOAGbF}lQ4do=1Q$3*R!z#*<413J}4 zxO-VS-5&aQ420@AM252LP!S*u?fL?wJs)E=0A!zobK^JbpzzQ7Ipdk7?M@iDj6K1hZ6zjYl4aLP+22elwm{)g-%e)~m(w0tIZA4*Qaa;nvtu$-m=iye%& z0BEWIDu8W>;g&ieLP>ofV@q9cSm5=w8DtUzfaL&P2S%JEoW3P1qq>hZS7>$~;0*K8 zd5!gP3@dr|IK-TD2eUOClQYvNNYRKv^etL7U`;(C06^=gokeO*#iu58c0G4~fb}TDu_D&*pmlgj0uXOvED4SRD&~FHm+ceC ztD>2a>fbxP%ZFq0`uZU40fe(XgP)hLZEFZ6#@!R~s|APuN6PdBR z)@i3hU3%u0;{xD1`yv?ZkM_%Jw*TPB^SG3y686=301S78MMhy z1Nrp*dA0~;FCQjx$=27|bbR$U5dn7`id_~q8rO@V0t~H@*BoZGUgY80*bw!hm(-kF z+SD++6pN{6YdG}32J>@6vPGn7HGVx>q&xZwX%qnN(gD4~LX{wF)rCnV2N6RfW{mvs zY$zvja84C_@cTqi?e31_ga7y5F8p_w1{y=eJBW>znTYDE#T;4pv?iX|>{%m^TlC`= zlj=(|1)GWzK!?qOnPK`XG;)tP3Aru+DxY=(uh~D~UG>4W2C~Ps5Jf`_ZEDTCO1dE1 zu0TQLz*OJpJ#Q|@D0Q~$0VK2hAQ&@4@00l@U4|6-1eh=Gi*XI3c$%Si7Jfm-0Lb98 zh`8CEl4)sFD~CFe@~XB3_4oBL((NsS)Kaf*0;7TzQ?Ut>#Q7*K%E7sT+@52Ur}ky} z)59BfFv60115~=oiDX;iPv5@@u*}uGiR8jS`PWqg3cT6q6=x831o%H#fz)@8{squZ zHi8Nt%ObQDi+$*1mT4zK?l64*2LAVf(d6p`6KP-vA65G5!4ueAMeYJWHi#Q{%bldoaqj>;fol`&mx}-Qa1gs-C)pI1vN3Y z+P7Q11WpTK?RNn?R}P-zq*Nm%!lohL_l|4VKxn1&`*v7t1%VbD2EUy9nRmLdGU%o% zYX#1_ch)R&#`*NYCZJ0s0h)7AQMoU$2aWHfL;D+TnABUXuQ#^8D}r|gc|gB00^5&N zxAAr#a;%}-$Kqy{-!74lo~RZ(O!6n*PTLA-11z`*bCQ~jFqv6tja-&n>@(X}90u|R z(*{8yN1(z&2hURdf!N#9_^)T4vCB*Kqjd9emV5|TDsf)}-oIq10_B}j&@ov#c&bKm>Ur7|HAM1)Y)Dq7gH& zv3u8a!r;UH?Xtd69%re8m_E02to)&!0qL$_v?gBq5-Qgc0H5n`kX;xZrw2Sv%aS2$ zf<%ry1>P7SEMoUgjW}um+Xig=*Pn#4aaaKXB=xF{6eF_`29T7$5X)Ez`$7reio|_#A08h^R!&MI8rce#^{HRm0SF+ap!)&+%&V0Ppn|7;a>sFTR7e5czhj0tyTf=1 zlYgz11ors`@Ldh_${u)niuSb_1iQ*+yF`ySU`A7W^)>tPP1fu6_BZk7<>rCrRDL7 z245{YSEE|uMUoj;Yo{=*;Sa*t z5~l7a;45bi$L_KB1}pr2e>IQ|?Dk^$`qJ} z?e0e;h>a(;Dc2F^9&ZpuG}7tDXz0k40|0YP&?BkSh0AY$WMRVCWQs9PA@;W*ADrk= zpaX*^0sDG}7=F5Nqrl^|T-EiOuYCPfY5??=F5P`TdnRo!1_m>bG>OuM^@JXGN0K=? z2CbCY%AV>_f6km8Da@~C1=|D6ZpGRXFa)+spE!i17>P>k7cPea*&sjftE#$r1_Z`6 z#T~SBb&Q^J$P)(NmqSkqv|pHOc2z$~UQ4?;1U-0Gv>;4d9u2rEH&pzZRT)>1l9GA; z+(ae-%Qw|s2T`Q0LdiTBuaE-@-W*o50%tj*C;+I;DH)YQCeovi0SrEy!wSF<(y;_C zw)1QI>^+r*t@f9*P;U>-WcSES0*R(@xAnDbgGvfUv||m4U}Q+P?B3_<@oVufxZ$*@ z1{mwt>u!teGMXQ$IUEi(-6e}yNSP#tXQAXt8dbFiw!ZR(I+X7ahx@B6}*;?fanBN&I^mn#`3vI|}>CZ*; zJz|;_1wX?7SEbJAUwVz!^s-?Eo50jBCb7Y?QT!GZa8B@O0er$hE^$EV(1I3Pz>yj}!V_Sz9p2ayFE|Mjx#fPu-H5jKU`eSlNJlLf}dss(clKcfqP1^wt- zGb(qkvExFkg0w;0Lk?CyUG(*v|9A=#=k^`D&SftWx*0P9EyI2Z10`*WB$w^SOOq0!} zJDbuh^|idkJ-6ghN;-GfJX!b6kIMUk9b|9+ zV5;Dt>6n?KozB2a1_DjhJF2yFNK?;-e@sWV@TYtQ8rC+xa>+6tI99Xv1EB1A8Hze1z7_t>IBcJh0kQu5 zlkNo|q56!?jKlPrB5sFAH8I=$Ds|pQ|=&C03XN z0vt>6lor^-X&BG2SX6ZMtkcN`k@W=|^D=mf{SHgY0y0>5WeW9sfe)ON3NtN9)?T4g zGdE`9aO;i8r>i}b0{Vl7WOD=lw;44#iQai&{<1gl>W4lDspa^i5gN@d1);ovvyH>S z1FgtgI6UEUi6qb*v{f5)7o_2aU7b8%0`HsY*1}hehlcp#pmSKX5E@wZ-QuG!vxu>! zZpQk+23iI*>Ht5%_(Uimo|7I!y<$sC`fpT}^4+YpHo=8i1_iv6soDLJ!pG+J!p2@C zb`=9)VAWwNxgX^xSx=mG0I&t!%gAEp_5F;J1OC_U3tJ=E4$djG=!Q$pdx^5Y24zU1 zu2j-}nhT2ra(lTa?yU$GrRCW+JjFgVLp#zw0DP9CAkOs)HSEy{+>Y+H+h7w7(;)RR z;L8iYw1aU%14krfgT&>wmP)*)d8&Y`pFjFGg7^McYXYd*t0F`o@i0<}$ z-7+3zeQg;=r%g zpf9qn0w-@0mUnW8@N(64p#10j!Q)_Am0VJX>Nb9cy1X@)0y{M9LI0|AGq*b!>E?S)N_(tqU(((sg;75Z~$!BnKEeo|$j2XaUL$y#OA zRmXUnYo~i}pPw7Bt(u|6pBA~i)S+dU2BY_>pxKxRlAa?AIIDR{^wx#bBxM6>QMEv4 zy7&D70V3HBWG$$hfDD^MtDt1u+zez{%aVzWqpkQTOb+i zJ&$3)-7P;pb8ts{2J<%1WAB|0ueE80^$u9#unBrA8OU`q>sw9o6pIt`0HkUQTf@Eb zg^HPk5}p+=Y0-4Wv<8YTjh0L@sIb2d11nnv0ls0o-^je}YvHh%wTyF9J~YJ;-}g=e zrT_yG1yL?PE7F7^aw=^QetlZPsDqK*p1`RN;^+#E6#MP70Nq!!ijfnPrn^tR(h?Z< ze5*yLbXSaiKM$7;Fl5D01K@R+f%gIi&T~AT(BA{4a#Q>Bl7F3V_Fx^5MJ{0;1$|M< zo(sdU{?N(-{s=+pPsDoYZey86uRla@E2T<50hWmO^DIQ+-*9|!_JUDC4X_04EMR)B z=mklf3m7re1wVN3eU(rLBFVI$3D|si_vrX?ss>zdc^4vx3!#Qx12Pa1E7HB-2ZwBY zSpMe=+E#}^iNCmK!~l`=?AI-G0PH`Ee5vY80QN1ES$wD#;vxkAJY5u(V5OID9&28y z0^x|)6_H`&t)bUpEu&XKCG?oo|A6bZn>#GL=6WCI0ErNf@Rup=Ew&e5Ec8H_>UIU_ zT#JSutK4xl^VLV-1dvoSv<6Is5&xC&IcqYR=Uu5S*7A=POJtv*%?g- z7GFPHfM3x0K=*3`Q5_cQIFb0h-M0KS1~bwD4d8ffYXmWaR{$M6NMvvlT9Y|-;Q-eV zm`5uQ1Fo<46fbi{!W6;G&F^$?dGpW>3IN7S2jvp2FsOXj03lMkc90DTkyc>0@7e&Y zi*RbTlcLkvtJ^~G3d<->0r1FgSeq`+M|HZ>4x!=9@k;r6g<{1)z4z9kR!T4h1ZGk- zvTI)wPbw#rmCoejA;n!YVmDVnp76<_T7eCp1+(574QH(VP;(PMf`3HrKt*+3>`dSh z6TrSX9Q0QI1(un=Kb$v9sk!du4o6^Dswh0SHs?Y|$2cF4a>=K30Mps=DEy~|z4?lE z(&N)I**cr>c%PoI0DT`*d^Z611y}h8QEKR7WSJP9RPl*~(tJSe4i{rX$7miJvlnhi z1d&e9I#!Qyf+T{Q$Bl9?PwJHAZx6si(8f4Ra^Ge_2gwB62Vfj9gPPyKf;~hkNulPu z$;Q04s=3;lW71@@2C9Wqt8E31Kc7e3h~&tMHIOGfyac4sLTP}z7kx%I2H|ObN&pvE z;FsC>fqr;hrVdx0PYqy~L%1C9ptzFxt zbGgVkU8C)h4$v~?|My?L1tkLJm*os&Ynf(p5h{4!;V;}$t4A9LkZkd3Ez)6+1%79L z#O8a7`jmZpTvB~FQpX?QMn%HsQS?R+I@2ZS28aRhC7{L7C#_M2y0U{*_L)eQ*`_pH zyzH$)+tO}q0b(A^k9y(uAC9y-U~Re7P*fP%431-%bzCmte`5u71V`7?^Y94$WWfJ) z;x&m2Fz@~gKjtC60p@LvIIUIO2epAJ2go1E<86&;uqt=UG~3MxDh$7&b}KySVZXs) z1CcI%!ZE1l8Ly*)>*w>={(VrvHe);>0JUcz1Tsty4I;*yV>X^TrkY=Ta9XRr zg>n0fdI9!N&P>S~0_;-Pi@FuQI4@V~M9ic=keQVuN2Lvmiy+d(_71LhpPdyo*mam0%^1lLBU{4o6L~Xg0=zu zGe8aKr9J`xL*{PH25v5KLU3H7}90FMpU{@y+0 zkP9_5eBbH&blz2LZM|d53W!Octj_*!2lnVlhh4wneM)7*@67L5oCioOl#BrkS#Y(xNKv&!0Cm9bUuZ1wHkC zDGguY)BeCF&MNy%MrB93H$PW1xT}q{N&2m62AGhJF^KF~pgsYe!@w{9IboHdn!7P>r01G&ITWc-FjO@R6! z|5GDh+HwHCdJbMNieTt^Iz3WhjLm2Ql?fzYH!Qf+s6T<2Rkr91Ke6Y>Mk8w7(}4x z8a{N?9=QIjuDho->TGRs00#ovQM=Z|^Oc1f1yS+pw5xXp)HvUqcVeS|!)S2A1&?Vu zUyzN-TJVkJqBJ@N zlyV6TfA+*52aAiT6um|xvtCZ)7+B|Hx@^d|l~(rD<}f6_Mn+$*1(TM6(8hY)23ns^ zg?g-1>c!ckVLSKW3OeOx@d!P01lDC`a;A<(m7|xZ^f-5u8~RK8XkimA1;c|CQ4r3_ z1L_qiL37a}0dhVUD^*WK)M=hf$&~fpgBp60r%0|0KM2-@0X2T8}P&!9jL0S_V? znTl>n8!8}HyxcPJ1v04M?QgnuD7DjadGNQa$@V(XODksEx-L;9pWYZe2C?~$*3&SUz-LytzpU4HgN2 z1lB-FU4ZmFYsx#`+DBcw?8jWT%d)#<&&*DKrISzl1+xG{YbA2NpTlMU%{G|C%_M!|Iy?-z#i0<&4!vKdH}hn zMm(cP1dk;FKcEPlsLD;*pw~-Fg_R*WpViBpGJRdY`5-5Nh` z1&)%;>Jafx#QNO$&Fl1%qdMNgytn=fT71*p8++%`20@8A40-V8IB0qWeacf*bth{J zx3Q?G(5}1k?O1U#149^x4e_EuPA_Q#KckSuPh$A2Ir({?_Ud^?sa;Vq1`KG6-LET7 z=-P>_M%Bp-Pfxyb*6f_f3SZQyr$I0T1&0YO-N|r&t{Egfc$<1}6)b^?TJ;Bk`=~{kDQ9|KGt^k0zWY`B;Jb*Iqza4C&l%8QhnKhb3$)e z)mqY>^Gxr}0MMin`c;^M7MU15P9`9&vkm+c$ARtOPF2In+EdltOFT z1R=)VRzvLU4DbR)o{6zK-_+A4MYl-8l~j76k-oKn12$WZa19ShTE9p3)jcOxMPi2Y z&=TKkHQ0@xh5v=Ir0r_fPW`IKh%wL->tvtA8 z%s~xU?K`eU77%$OJXSoZ2I=oDF(peg+2j42Lc6Sn&-~wME{#H?l)Z`K{c1V~11c3y zM6>m88GosxxkP0I1YNRnk2_%j))qPeU>Ku61_N`71;XdIWI4zQ3R5*RCh&@@wU^IR zP$Ys=e8;np27%KB1+M1A##yg3Iaqw_sNw&WhJL{8h&8KWIb2_Z0GcK!Tc2em<+(zL zFF1eBYki%l5L=1^D+hkJ^mIk757^>Gtu*N2DQblLN4h0I3tzFD9c(LnUQtYGd#2zkWqx9ek%0NJ-*X>oon zI6ijv{lq6v_MMjFSwYyddun!i0EbeFm>LRYBGl*S^)cZ`G681g^C^5QfZ(n^ zZLH^s0CMM+8NGDi)5CNe0l!|SUhofl>T0gu3mgVvqyqE*0d0Ad*D2sFgT9^P2`4m= zFddYwvPbkRE{P<26)tj-1k~&bic$70y#UWd3*&<}l1)Z$TwHC@c!UWFT09z~0SeYh zfd8@frY<|np_d3B!em+b(aX!g6}(1E}1HtsU@Oi6q4yyys523b}lKI#z)24)0u(ivqrX z27I9!AIZi5pprUNBd^qHxK*2cTqd+s>xXHK)1o9}qadGISR9oD z*c)&Rm0S*W3<5esL_MQP1Pv-CU1w}mXFQnq>5NEwcE2!6>eH2n{M`xgP|YR`1vO9; zUUCgE0g}kKM@Bdz1J>Q0(ho**+x@m{)jpqS1r+X7m89rf54@V6C@4W(tm_O|@>$5i zCiiE`aa3dB24SGmQ^iu{bc)aw04B4GPWW(OpDG#Uf)rQBWoZw)0g*n}(&O**p)$!m z6ZHZy4f;d-CQMYmZNlBVUZy4Q1S>wG7c9}rWcIbKS)>1~IzSq8x&l-o560C62BOYr z0(F9xVcnplRg5>DU%$kb^UMl1yOe=1vHLh(HTXuba;Pc9(PN}#x5N7 zr*^duh*$V|-Bm292Z*L}aczRhwxS8wcncgEhMb-mq$jft35IZ-p;Zl`0~)a$CFASb ziKslcvP#U;h4Hb1iHs>%`h9)~Erp0NNPnN>eZ|czg{bic)QXt<0lc&Kd0*1EH+hQ*ksf*WoR|$FiYH z-IqUo;I)9t;DP)xKXLY_1JS@{!O`=esy1?iO%8-+4x6qfS)H7_4u~1_tB4*i0$Ufn zV+44gOeVE~2!_hH*ZS#i{K_!7ceV;@yAv`c1gR_9hSsL8)*X*SYr|dNx$wi-tGYmI zK{crJ;Q2M<1N?g?+2N$@b?k3ejOAy%?OeWOJYd!;JA#M4g-BN81+P6^BglNV-6p*E z_D04%L-F;-r*A}HN2pCQW7=q51VY_O%%)RCwB3)2(6N=5tw$$vY@$&{&4Z}(#4fN) z26+roW+5g^wmD1qR(mE3_bJr4s)*;4fhcM!Zs9v}0-k2BZc8GAz@3V|r%cGGo~Oxt z6J{Pk^6!W11_H%y0jxSzU-LBUD!BWsK5lGsk zwjUL41_%x7j&P}-_I1)SPQlVJ!a{u~5S)?aw{-&xKpZ>i2Zk^Z(3Rn@(N}tx-cPfi zJ3*5a2ZtNAkx1}o#k)lT0Rl8%t9sLm_n9|s6pPKegBTi?$q?j6q*CV@*?K940?E-e zN?M?tWe=1>VSa**+&-FKGoQw^p*ac2$^Bq_15UY<6mINO*NW=d0L?)EM?14oE-Ei* zA;BH_!BGH+0tXx2w9@Es9W)`0M;d;y*wl*w$`q%QQ<1USETY{y25Y?k9;|BVaWIi^ zn-1oxXOruWboO7UqJEaTt--ij18ME=Td}E19j~Mt-EQkTrc?H2Vj$J^a|_3q_H8JIOr)HUq=(i zPHlB2FWb&7)7sUT17=Gu^*3O-<&~!unhg`)Fr->gx~P!H(SOo-?r4Lq1o4BrcOj0Y zRW`%!`l9dIJsPN0@B|qYaR>l0i(I({2Oz!k@=&|`n3`ASjG@OuidNoM*pP(O{zSYV zHRi+Y0ffrt8%>90FX|RXc!+M~iXJ$fXLQjTR6G&sIQe6R1F=}qnplyzpTRvDyLJt^ z8x+Soh@G`MAWNBe#j2L(+L-u=eHIl~+;2()x#Vwbijmq71+0A}mFA88}#0yKjx zE$Zf7xu>M-w5P|!RS1W(UP4Lcp1;tK6hWcB1fs+C0du;E0{CSGu~9ilIGFQ+yFB3| z2*aOl_T8k>1pIv0pV)LEP>e3FR@OMW`7;hx$1zo-D*L}#X{h%g0Fb@N$w`EBXD1iS zUVQVec#=UlW{TpV3NPc|Tbj^=kF@eQ%Pg z1uLQ}@l7u-1T~m89~>dQ%fXflj;O31=8AdId$ftH2H*CZ{fPRMjmvua2H5CfU@kGw zuseN4{Q)`_1}%-22E;($L1w6BR>-zqqP#S#o>+viFtMK48;8~lv81!y0ysP6ndT(A zlaJrBw{WsaEE8!-k8W8_UQD8NHkvSJ2F?vM-p+rkgNE`778jEH9lk3}NmZh!VFF(D z!n2hYg))yWN}woaL*n=F5Sx1^7V0T@1A6OA@;H9J3mb{;1Zzf? zPO}tIthJmo=V>jms6jyX1Fscl?7TA+BGv&ZAW!vZRn zSP?UHPw9rV1l~qNnxrqF;d+R$-zcxRyYhBOSMtWPW2joaV+hvW1BUOB0UkG8-u>kC z9qO*g`_oS2-E*PG<$^?gGKt%f2F}YLA6T92gI2>LO!p@EI4d%k4`YdlWXZj@w^kqC z2D@K29QfU*v9R0J#kSl-LyA!IGTK_hraAO_u_S2B1qYG=e2s#sJ>8*$R5z+7ss2~U zw>*ds&ERfJfO=aa2J-Jq!vCgu2GIb8ky{?!2eRK3S@?C2HGK~G$2&r;2BnKn%hQE1 zfxh+IlGTLacILTg^EMu7-E4;qdkSIh0cOLOplW{gX7=wO1UpIU0Nv(FBe9~7lUMUJGLeGOAg97aJM=hZHc!kB22q2zc?bir~A^_o?>01E*DwoV_@XPE@j z+85A`&z3@=kpU~grY}7Yxn+f?0)Ly41Ko>9be4525Vf~wRXlvC%QN~v z9T`PtA91ao;jor-0;>H(lP$J?5SlRMQGt1i6TU zlmxoEv!;xwZpaHn{(~;ZrvB!T_*%f^30_1{x$!r~ORB-_c zu4eWD5=Vt$9V@<*w(hE&d2~T{0u;o*!ht5f z1V_Nu*;EKd+;O8!zD`f!>CoC7bAjgx5EjiQLaO%x1Fm?@+V+Ul#CWH`yW4E5m_K7N z1PAqdi|vf8;Y0s7J&pX;MYN94?Y0`WKd;wpX3N1UBR(|OuVG+%*Z0O|3;7B9aG zxYXc6aa^r+l$~lF{+s>5LfieKI|a-(1g6uU6`22ZojU0lHnYN>$r`G+@L(KF;`z!w z547xFB{Ip-8K*@#aS^JS z0SeVJX}C^tT;YNx%PJr9mJs}41%Oc3*%EO3kY|S&24nHfp8*4RYYMK91MWMWZybhT zZVE}Ec*9#859>r)1JuABq?X^xh|8*m;46@O}cZxdqJp~*I%o; zU}AUQ2KDaqj(MdN1b6ADfkUS_%ga8CB%v|j;5HN>{Oh9>1vkUUGE-qqyxJS%Ulry| zpj|`WFhSiPXe`!IOCv(z0*f|$dY#+iH|nw-P7Ab-F^5IQO0vfo*g3Y=*4xn51bx^O zbZ7n&8+}MQ$DuXZ-L-LPunE_P`8ioj)KiKU0%zd3R(-5A?w))p;Lg|L1n6y9<^QKa zbtGYxB+{dt0WY9rqjU|;LWeKhA(ufI*&FBVz0(Q^M6A>7Cj}0g0eux7GB5L2h^RCr zG{7!>MIP)F94rKQ_#KR~r!p4!2S66R^5R9I#_;3_!)B2-tWL-H%gu0bfVqiZ)!N zLrjNai~TY)Y%r+s0JF`~%nwCt1=yI{R`~&^GrzuXT>6h*jW=0cQgd`)7mE*{rGR79QyDc0AU#$J5ovWIwSG z3>7a*0b2AAT>$8NQD6z)zDeKK3*FO)cMX5wi1<%P#UAGP&0^wFXx2=_htF)4|t7wGU4Dvs%tpi|o1*~3y z1P&|G54!~(Y0}n7xNx0rKGUWV=&2!t$&_~t0qkD4-P1meS5)tda+hBX6&jHn^QQ(qD!Pll`TN0JjbuK-4v^{FQP? zC`-7y$WL|ls1(J54#O?NCeyMX0cy=qwk0uqH$f!=x}f5`?4K13{cAi9W9W~h8VZ|y z0w>b`LVC+}H+GTHP!Il!5nirpY77WhW*Jn1;YG=X0tuUnK7dC+uUBd^+>{Wl=4!O` zEzVQ35>o_ zgR|P?1Y>yG0baZfF3{)MRS<7lpmcY3_DJ~QBy@J`*g$5L1ooSQvq)Fb>A9K4o_ys$ zp{8qLa;<$X+7(OJz}#|!1%m$VJSJpfJ^5l*DdPuI7@s7mkjlgzfObLLn02^k1GcmT z3T_`X%q(0FrxnU@oIQnR2)YqHf?|6`eDgUh1k{op!jC2FU_-5}hyu5X zt;ml|^<%uNlE6oW>Rup?1~v!qMg~1kE0{#Kb{(2LXB#tNr$tGwZvY)v9D$gA0ppvc z{_j8&<;xx6L3|~)zNgEcGXe7m!b^SoY1Xe01V<3Y-x#Xw-c+qw^M)D>$GZ~$I(*sc zfdOD=(Td9I1KRoN$Al^}fl0$v-!)gz7tQxf!gzr-1vWBzb7nuk2X9e?p}Ci{DFRi^ z+Y)#QS+kLq(Q_DyBtDd^G~z^=0glwtnN6clQ-BbepP-qQ*fZquRS1>dl5ednBBUh? z0_dX1Bj};=A7O$jvVcw`-HbxMuvj5#zLIj>GyLr)1)&zEBfOIV3_>wpdtQCwr-lHp z>AB=#j`3cD;D_~q1_0e_WKJvd0GfBE$L#U^hG#aAh!m2eydoA1P(QNp!7+ReUoX+9*DE04WZBfJ>o(nK{eV_+?~J{2i#sC0C0!L?PK)m zDdyqY?@vV!|C?n&6`7C!nbYIE1WRrE&D$sVd9mcsG$ujvXDm`MG{1kf4`BzA(p3cY z0VqP^MFG^s1iqu`dToXQB77oW1N|^3LKEC@0YMBP2XhZD79U2Nn%Z$_Xc>b=7VjTW z03-rf+InNP2-+X60R)j=*Eip+rFsy^i+hf)w_ZNTpkz~*LJ7$Mw1PV>5gsgq| zWM74t#&Cpd%A*ke-xgk)%NGnfYyQVl1dol`a8XYrZLv-g!P6cLtK;91BA3f^22ckYb;aBwIR!woFPaC^{Ni<^Xw!sktbr*#0>0bF^f5LMSCfKYFA#>V zEV{}kJ_0}=drXMfwJwrJ0rtrciY|hBZMA!PY>ssP+#a3baj4@kYKkc9UCeTP1ReC% zT1|BgU+-IGjz~J@Cax0PYtt;opMr&R12X{r0uMeKAovG^TUVZZcR3w$R;I-!NMmsD z*fzDd?j-HK1s4|BD)9ig+dV7cJCL!j3L2uL`@+-W6sjz|o2V`D1RfT^0dF!!3co0fkga=onqC?F4eb@sD3m7W}Z{e5tMOF+t18F|{2X1dfvcML@d0 zxXsz+NCm?kB%yq}{5xv56wB-N&O}J56A^+xPhivRReZVZ$Q<_*R8CK z1;RuC2^amuT`4TO*S{eIGkRkC*#pWdqI|JRp#7<}F~gZYWN)g1qf;qZfY6tu0%|xn z)CE%gSS=Qz3(pFB{fi(WJGC+bZ`&UsT*`@+_-vV6U;@TPgv6CR+JgA4oq|QGrgJP5 z-5fXMDq);|kPY|75CiEpwgrbhAr3m0OiUz&s^VU4Df2i*L5sKgt8vN9Rsg@T3m%@Ry+UhzAgx?epx-f z=R;LcT3gdNu`r6TS*;L5QH8|(z>?aejf8MaDvwY9mKL&*2 zz_l>-c?y*#%1!<{+e*j7cyh3|g6%HmIcC5E$H~g7JJ5f(PcGr~tSMCRM_B z3g(no6ZI09XTpYR12VUJ0U4lKbpjUrC8Yu@GLl>nNIc34aY1utDg%sRKUH`7Qcj(a zY60m>`w0b}sLtl4c%)+HH;X_>kVg{&KFh)fi8_a>JOa8hGQx9B&{~+E_z?xNLwp9{ z`1_2poOfz7=`UnOodKzc6r+QcdfWKXoTg^^C&U>_M$VIRHaU7N(C{j045F9S_7t~1*S6{ftr+K@g2ZnF~Yr=0dm z@NOf#d)HtaKQX;$I;XOi0{|qUt!mgyixj1hG2gKsJqK9HmZZ!An9B`;^#=O6(gR2o zues17?epTINy@-rS&8w9Y>8nk3Oq7~KkMy>aR6PEJ9o&)1$YHQ8#Ng;n7}xQrqlk# z>LI$dDFLcU}S7k(( zB5cCOF3BhaD76f+4jd||+AxkmkOBHZCO&;o9vsN}~8u27u2<&XV4d*QJ=xYsBt`B5J00jQ|6J5Dw z!lPz(){_{@&t|VdbO4}% zd-IeY2X!AYi=2=n^z#ShM$qAsVdl+)X#R~B^aj+%_1`IAe7y!z*=4y*u?ajE4A)~i zQh1iz*z9Ing9SC`0iUKlX?Ms1CkhyiC`x#;&^4BB!~Qs_-;Pj$x&y}opsGQGBFKuq zl0*{h_ETo%0!M6ClqM8zg&3h1^95S9mYM|eWU(`K+j4K3;D!CUjm-|iD|KOlcQh!T znEuw>AfveY7sBN3$r<{ zT;bca5dK8yHjq&;qy!|YB5MSh$V`WcYar41;wIm<_ON5YoaDG)#f{ZF><4fo_J6gv zo?WCeW*v3lhS_T6oXYmKS`OV#IsEmi+XNj8PIQ+|L-BdIae+=z@wj3G-_%tG9$g;R zinV9Z0RS50T5_ag3eTc|8bYTiu4}H;5?$$XK~^7MV*JbPD+SlDUp4`Cr_3Eohw$T- zfr5m2r&`R>^n*yS1L&!200r;mJVL5l2&qG}*fx^j(MlE?jqZCq}qi^?*gH^K>)!EP(>3bA?xr7763`(ZQu=W zV?WZpY;+v>E(2V4!q-yy>)E3f>j(eQ=h{AE(MmIHtf^@WaF+7E(*ma+RE?<6?JYMI zBVAQ_c;n*D<>frH;%s##oS-AUNdU zQUm?;mM)X5PIAP1f#II8harl;I?n2dySQPr9S z*?04zgMetLmUuy6Ap1lzq5YO--~|3T?zxQO zAM^BlsZKMG>YDl?Be{1#W?L)#&J!}dkObR~06s(PQ^!xXq`>5Ry(Aet**DTNCJU65 zN^-hxatBD*`m~W+R>@HBK$>tD@M&E}M8}>1B&%8gHC(Xc8wbmehh6y*!QT*E*T-N} z5YuQPkHvqhjhdmVnZnt-cLu{dXMdCUN7hyJS?6Im39uu+6uK@#&x8Q^1}1GywgvF5 zMnV#t08^8qTnSEeejP;6H(=it=wU zf}_&F&+_$fnkepOx=3o>x13Z!9s$Ck6hNoQn6iH|C+(fe=R6(OS-0fibCAGn7}&u0 z2>@K4s$^PW?hfF{zjAd%ZNl8jOxtnX5jzo20AR{{Y6fTBdUm&PZ$l2;-l&(D_%a2n z--;!Z>)TLZ#5zQ4$_2YThW#P&tug2J9sPG$Y0h~%e5+&;1}9(j3wjwg!v<>F(X;#{ z7T7P8fOC!jPqMSJvV{KCdLijv%S@l<*#pR2jO-!vfhjceCw{=q)=Tk)(J$oICH_1! z$Hgk}9|uN0?rt1Qkv0QhIO7NlV5=GMrQ@%%=AY6!K4DhF#s}D=JflSTo6NqeEg#hR zjK8@w80MwQ6Cg!0K0~IuuLZewz$dmz^(*9!mz`N~W5lH3X7$Z|4Ovp(K9B;olK`0I zf+j1T`HTQ1DF}l0D&gh7iB}otL1$@o(mWDS9|23GoMtE&?*8_lpCAW;fu^+pG%}W^ z`7Jn0$fXbUYXbE$U9Pt5GCE!~i*IRsw<>>xYxAZ2%Qa$T>&E2a?@Eqb+fKr zGvJ@w3aPOx43ihYG(4FUBzj^0hz7)ed*8L;UQ*Gkv={n34r*rWCb4e<;l^C-bg3B5nj?_xZ^%ScWEB&O%c`N6gwF3F}4&z!r+Q@RVM|5EM zHIZGT5F#G#Y56cNuO{`^omiQpg__*ks+dD3c7B*EkT6tSOxdrC454jNO!uF3&%Zx(s|fR5>O>1 z2oX{;7tu~&HvqMixcX8_Db*3=XCnipKSbB*hiyq2q@1*{@UO@a(*dWH_<9=f4(}(j z*Jr@|GjihOYi@1+wt{I^>mzfP2L$&~?lz{_`1Xz787loqGCU|rvj;&B4~V8B1kTBR z*#ovf6$y5Lu}~6HGNFo01bSVmVqy{L8XDj1? zl>hqiEb%sK1^lUd1^`qf*a=J5JNn_7fC$nEy{i#T1Z6$e0in_B-Nqn1r3b5;*cD+p zLcI6NTY%ayF#Y77vv*N}4d-3D<*xtA#nMZ*Xa?M2R&j9Fo(x~>l{1{h zwGU`C^|?gm`NL8JwwiLPg#@sCmp$)7B8SU0D`K?{XmGW>OY#CrPm^KK`yki)=mD=X z1;F z^9403Oj7Vy+?*fP8O>QZjNb(h(ObOiBO#Ea$r~86uLDrt0`!yU8S7NZe0SjoB82|s z#V*WcU9p#NBe|n4L<83p`OJ(Z+#CrL;=Uusey9$nFGc+F(6t&9U%--!!T>Nu2Kybl z%CP&(^`ISnab}Mo&VyJUuBZ@)I4-!X_5i8v$7<RU?ed0+Ok>}Q}&*aY`XW(5C$b6 zyG^O0k*A3f*z)-Xb#-wGyY==dVu#_D0-DU@<_Ez(S3@_>vs6SgB}2zCMkpf$8#h~ zB?gb9q@Gc>a2xx!es>a!EEZ#LkJ%pC*%n6wR<_`mV*_KX#^dvF6Cp+97CWWQI%;)| z0tUCgl?f|u?%r;8>H zjkiD3K{ggIP!3R?^V$ zX7BT>lLx7{sg!&`Ku?_+Ele`|U7r%}1HQ2D+)+*c?0{*Uw+Cm7Eu-xHd*7b+Kkc2r z{l2p3CIIX2?YjI=7uMyCCyDXc-g(N=!)4IYoj=G6{$lq^wf%!M72tHlmm~V zK%mWq(R57}0Z|pSLOpbm1ahfvIhXE5(fo&EvDtWxxZ2!G= z%-fCU4o%^~F48Rff*I`J-2taTtoIQ4stt+xU=TF9dwCdUB#J;3f-?V?-@PB!O94z= z$^D3Zn!o?^5n{GP+Qqb))-y+hL~^QPTvUpsVFY4@)SvV7+*Xoo*AKWMDD>08UB{g5 zbUgky_e3lP6a>GD)H1^)RbC*bHsywzHM?|jVGl0AAf!;B?&#X^?UX1(hQ{^22JPeBmW-w;7};*ad<908oNam{D?$@g{WmDobYPoG}S zBZVO0XX4Lje=_znGzO26%8#?|-nze9f$&l3AxS5-B{tH0EVDR1;m;HZy8?so=vkVi zb4?56||hZ(+{qik*cC@z!z5}E(X%WClQHIFY+%Xl4X9s zt{cu48@j|9DwwI;JTaA7)&&Azpby!nuT)s^o6IXsBePL~`vc^>G@oRp*=%ij*aV6+ z;A#nz08D{WelAqVe9F}@H<_>@YDh^^+(KZQBmxQ6D-JAQZuk>0Mr1?GOO`u(^$E#4 zt(}Pz44$H#=L3ZE)y?scrV9b``bi)F$P`rua;cZ+Tg!~YbGh6xY!QTo8JtbfZ4w*ZMHhhSp#0DKb%P?J_Q(bNvRkxZOBFDEe5gFw()Xl*53Br@E76Cj$=AzCh@4RLi=PaG=!r ziDA;wR?4u=qBJLiOnRZ$^#B_KE!#H2%2RaZ;rdAi;TkAuQ6cv)_MA^Hn?mFxVFjR| zDU50)n$IwUQAx-Ivock(TsRiFaEQ)b=kP3*`UG*NU+B2#WF?w#jUF$V#}5n50>Ijf z`UuUu5KYeI?FKJn(HexN0D};CCS@XC&d7zqVkEpy=UBwz+o8b_JOcoXqP`x+?CrW4 z$j<3=xPC2zD>$4JEuT1z-*`)+6ap=e)?D0wdk-r!-X*3xG3DMSkIns3QEk39e1@$>tXNOpou980K`d2E+E|a# zQ~G4P5eFjlC%SkbvSFHyR_BJ>hLk-6s*Uglg8#cvNgY=)>5OX675#u5qI1Uyx`Oi_;FW0C)pdNU*Mg?4? zA|m`S$X(XsDoMT!KgO^d7ztPmC&SO@%I(Ox{{uHc@l}S$>sg5x;V$EV*LsW^YT2Ru zf5C;zvQzkW*#K5>r~R+tq4phmM0f1Tos$Cv4aOpQVywIqZH+GlkB@*OXE9RIm-%X^P=3y z;s-nxblaNeFS?Pehf%BsPG;;v^c?Sv7!j07eFZh~%m-Q0+e+Wz7mjc}@?Mu&ayL0| z=834Qh{+rL5k|bty98^5P%M!5o15{Zn|Nz#UVOH)qGNs09e;tH(M*Bq{09@Ib+qmP z>?Fp_A&EO%G&J{TMGnUSMl*fIO$|gkRe;$ua)LYWYQS;f|SYBOi zZR!DkaN3GylL^Vd6Gz9(4g)3=;j8z$R*7}0y_u!cYV`@Zi`d_&49`@RbXj04jW~MH`YV=+?S-cJSq4HWsnSPtZ}J+mpMZT8*;}~=N;z@< zH`<+xh-6=cWB`U^j(X>Df1IeX3gTcsHU6cZrpS(Lsw}rO;&1GJP6tgCP^hQZvcMy3 zV?9u-*Xw6vbmZ@B=Yga#!NgcBV6PR_s&X`&D_Tn%rbb{SWig(%kdQX0ziX9hK!i^sngY1ma4Ew?*2 zJK5SQEq7Ox?j{ReLvjj00fI>I=Ty~%_P=;wqK6{|98c(~*HiwT}e6fF03IvY6 z_aFl_$t70ELp{&%{-d>$l-aj>5>C!}yHk&CF9t=#YGslF7t*bPdFfCKOJDr{M$qft z)4HCpPZ+;Bum(K0u-FUduyFuUGXXokBG=ltQU3>0;J#Oh!wBhh0R!pK)8ss|tq7%l zG|w>oU|BaQ41Hdn`!Co$;bY|iNCKU*w@-95*P)=59%UdH{kq(Iw_k0w<*(QC{Ctpg zUjYP_aKaeF_aw)S#;D_8Qh&j`p@_XdqW(RFqE?EyF(>U>sk-` zC}x}Q?*QjI5jbzJn1dkhezahKaA`uQx7XPkm(vpIy;L2XAOa^v_{rRg!m(vklUvws z2J5e6|L6`raH~W6o}9BdiUmOboZyHz@bcZ`i&o`3VA&IW%eu@Y8VwsMbT&DYCj`mo zUH2IIl9-oxrT?hghj%=MdH>X_b++i?nBAfCHX8=)Z4XvN&DUbuadkR@oj7*a_D9!Jv|tEMN2=c42m*gxm!JHsp2A!JIY_^y zVdY35IiloWvbIm)X`i%7Is~b`Y2~?LDDCTH}xuE-E>*0M$M)gB0ZN^^LZ~)pmkG}E)J)0}m%2sUNxi<9;x2HD(UmjLr7`=H#8Vo0@f1bJ`XYZrQbwgW7Q)n5FyN_w&=0Fx-S#ebb$ zbOXrc?NVh_QpP|&3<076?GB81jH}l_W4+(yQFhoX`Ttwzb@ zCOK@k`62!_&Px_b-Ur6mf1j0y77^Vh4gvVaN{J1QPJoNpS4{$f8;0~y4OV-dPhEt* zIh(h97y->aOR$bS)z5Q(+iz|}zOE>@_lV~#Iy#zBa?I%$?-gZ7CERu>( zPHCS3HYXGD7yv?X?*_c4%>>{yVW3}iCM1nXXF&*?cg`(~CpV?}F$k-Sr0Y#T#RDRx z8g-eUhfl_yLy3?!>$clt!ku5{D@^kva;&fMTLM?R^D{tmx`G0Ty$J}!2hN|S6ilO+ zZ1(kuP>0Xr%9l>6^O#uO0S^9pFOk2_w{4Vad-zu-Srn)d&0eY!doZy4&{RH9j zKs@Q<)2gnJ#s=0EpNi99(OiA{nhZL0JhUkMeFI=Pipa?%h9Kj3mvX#N3AiTRz1*3D z0wH7L!)@n;$pAZXBA6*OoQwxi>TGUbs|GIw#Q7NG^DvuF-?4Anod%rTUv1N_AQqaw z0KXpf|8hbGbIu%}9hY8lB2eY*pa2Jpe4|E7L_}}g%cU~`S4I^IL{t+vNR+v`$sY!z zYyxKs;d*Tbz^3KCc+-xN%Fe8aYBf%dvuNArv2hXN^8nG$o@P{t%JZ(h!uLw4zYyKB zFpGqvB?7e{gPrgB$_3QfV9p#mBY(wS-FCPT(G(e6;aIgolWKmg#kav3Ea zpTEf^DL5ilcP(Gey9zTnik?6?BAlwQV8b zp49jW-2^U<ME`(K-PKz*62WG z%=7C~pxeyLKeq+jJRSsb7z2d(Y@;IP%E#%Vz@D9?g;8u+>}$uLPYd~n6Q8G$Z~_gOxQ7Eg4$=}L_EA>NGvIg7IeaNMlFVjIND$Mh4PJzvyRvv}v zA|E}gEdZ&cHUfU8TjdE0O#PEWnSLwva5;zJ)XiKHcnjWj?!3%uvIj*X^`Dv?7~QX8 z^0%8*4aJhtI&h3x%{yqTEC#+zegu7qq968x&4JuAYFH}t_Aixr>kXrUuLj8#)Ggi7jGph zb_!)J6O6R8-F)A2lmI8%GX5=W*PMsVFG}5Tz&Wsdw53~B65;RpeU^ZyDFPczn3J>* zF(q;CxL?MJ$p?fAF5Ri66H;hu%`}mtX9ZznE3fl%a9bYgm71c0Bw_&0X+D-X0?cv1 zG@6Q2;{@a3?QlET4ViExlc{>eo*y?h#mBE;ONDcob!g_sy#p)sWGl-mHE@i&wWmy< zleF-WJWAtCx?1bHX6Ed_O#+BTs?Z0LXVE&O(Rax-)^;k_)C4Q? zZKcc5&A63!yWb`~lkS1#6EEPYvOKK-t72Vy-3DuK<#E!I%ePrCJCE;8eITe;oS`*swj|+A#Mr z-Us{E7yc*GSdcL-u)>Db?U`C#;(@wlD{vwCD%l*Wivy-RirK!vr#H)b8?3*)+2bb_ zsBFf3LualWx5Ryl3I&CDeVXey&;cJ6tB&aXIOKFyy35->e5wbCUJTIf0R=DyUI3pO zLpqFGEG5l)(de&QwDo^?l1G*15JKdW5C<(Q$4gBbww;Y8oP#hmUD1~Hj78eKn%Qei zl_D~C;srl)2xpC+{;ZThXs2TQ6y>oAz7)79p3&TV+Cf9QO69afPA2{=Spdo( z7cv-h1O*0VBk9wJAv_E>>x?{~1Geg(;WLHLfC7OIJLZ5FP^7ocP^e-qECF;KcM@=6 za-D%M+&pXri`XmiBJ-KLtFQUG*hKK?yFyQ2 z?E>kSd`K`=B}5Gp4Mv!Ma`>(@C#_w{;y&zK{pl=W%L3MfFDcYtq904iHnVBQtk4vV zq}26nBEF$67ndR#Py`4LosH86ZhHdzc91R4G6cLJ<K4rpr03XH1OR(#i3~;l z+4$+gK2}T@38^~vBq8Jv*fJq`Rz8WdGyxCl@D(>IFQ66lO&>nPibZQ+wk`zp8U8I} zPTgP>mjp8}=^rQ@Gf&RdaAl6qIA1Yp_x{ZM!qoz-d7cF3cm;+XA94#wg9$tN5jF2d zyaNMnTt(s?l98di|+oDFB&`Pg;bXz&ehL@3P zJOJsI@g*Et_7*1d)E)DhS{jpoC1ZHV82sH=LTnbF;lR%hHu{5Uj<$n?jd^) z4bX#$X~awxTJkWxoda3DtSu}|plEZ-8wZx7y|?t73_|;z+T%VUBB44VPZ~F8Omw=0 z${e7MM*xMy&0@?6LxivIxS4X)j>d-(z6TFW^!PW)Lb7~bItN~~<{e-8S!+Y>X0q_2 z@y_`iBHZTtvpyq0&|r`DV*zzmQOO~s9dFjH@fw^Mg!zfQCl#IJ|AsTe9VG)A(*px+ zNSt(N-Xajer5A$-r_82fDqtnM-XGKy1ZRT&It15c41F~hpvIY@c{8X<%?*nU7 z<1g7@D1Xd%(r>TZ=R#XkpXqW4K?RQ9`q;bmRy19>iVmNXiNcWj$;P^V*RrAdPTcCU zTLW|E;kzhSqgiJ`$}Aos1jw;vS|+GXu+GN2n;Ji75(2r=K99I`c4fN}1S79l9EXc~ zdw<>mO^-IB_n6C+nFW_J{fj6=b1m>9c_BE5aQ8>f?UV=2Qv#6?Eh`@E{saMcWM)dm zWutlTtC3)IkdV}zy5)>?0hknnJUQ=^Yy{Y4$6>35CQn5dghNxI>^_?vL;Yes(I{9L z4~_w;h6d^%c(XW8VncMV^se%ICtSo924oHNhgfokU+fiCUjtBA(JA6gi+@_USMiN{ zia|a-*RT1gu$%c!e*tvg&IT}gt#Ec$$AfHGZVaZLZ@C`?2oK#mDIL;UMjQAYKLbIA zCoD|@2?OG#_He4j|1>VBQ!J=@9CxyJRE(gd4+C>E@Ha5hH;CE9C}pc22)nmw>?haW z^?V~Aiqzyjj{tdUlzI^sHG|~;YlX4V(g1NPfn_+tMe{)XKRD9rY@szTh<9PDzc7%Pw~Osy z4hJcMGvy|(Cn#bAfQbj_CyjJ3EC6fnAee?qTQiluV*trVPMyw%j43t{(`LDY_Ry}; zLbHr37Pk=!*y=n_&;?p2CEMivNL!_?d&?hICN$$d21}iLN#}(P{T-sq_5cxQlMUZU zLF>GZm`Gd%rj~u(oJvj7X7G#Z&9O4fNC004>hm@@*4HH9ELQ*Jw5pzd$tFMR7kCdw zqKX)*SO=Qj8TP^ti5nHu(TRqE{MQ&THdWWItsig8l^>~_%?A_EhI$^D?_GXhDyeC>~#(~7vyKdbnFq6F_>*&w;yBNbI$pgxx79gPaYPLe@S zM3=#+f1Rb+PXk!{;K0WNPtoPwGg<36yeDK7NqBu@o0Q@n9K7z56TSK|Tn?Vb(V zPGFHx{$NyPiOVXaXK=Aq*Un-tM+5&A_m7Y3+crCTZ9HA6%UI{AA%|_NWpUR84%a?s zvH|`s9fW-34Rok@LQk-gwjuCS z`&%!XMsmdwhtXQ(d}wNb8v%{8dpY8t%5w7!>%*$?apKeeAxER@8kX8XQ=A!BV*zu! zGlZ4tcq^dNR%AIvwH~QoB6^q%BG5yK7w5eXy#_tDa*>x^lpz(*b2|$L-8)HuD!C~9 zSh(mnpPEdk{R7VeD3n8ncJp{L9^3n`#ZuB?AAsQopYMpg!TxG=tOVb?$^0|u+P#hz zg-Y>-=+(6Pl06kv3?s@H_ydH4rUr%YF+5pJu1Oymm5&1La@lrju*r~B8m?_vcFtRP z6#-N!CUfabCuOP$#!;KVZzvYXdYYKrxJ?6IYZ&RY(+pI9Y;aBt1RBSQN$6`}yD|(x9o}0{}95RZEqdTB^ine_}C*n1tE(Ev#T9Fw5 zF}2XIE)ToCtM4OLYJcOF>*bzpYB)y2V+Dw-gbBE>-WDPQ$%q5t;k-A16Rt8fvp`m; z>U11d9|Hdl)jMDd?6Ts|MjCuNLt@&RbG|y%+O4jGn8(7Lk^}DJ8bi9WPN_jQ-yd=` z5O z_51#s`#t~q%u6$*8$}q0kw4m-kHs$jaZ&ulkN~Y~lq5;dGYbZcx9w`Ff!UXL<3Nff zkn_Sw?f9Np$pOgcT?2wNKEHY7eNbmKvlwEkl*X%70G*)#C)b=L`~!bIy&8z+#0Q!P zW1@w4(NsMS;_Z*}`(qC|*4uT?y#*hKeX&7*O*mkhG}G-^Yi?+V!Cd@Prjca6>Mi@oLCS!o2k%~;Rcvfq#~w6 zd{Er|;-hr>RlH$$xjF8@Q$5d0z%g{mQvk%P{pElg@}Hjew5qK06dfNa*p$hK@BGwT zK9Jp_tOtC!^Tt3dr*p|0b!~z$p|(Z(@zxSxj$hygjKf~)>+p-@+w7^g(ac*Py_vNt#lMh_ex#Oe+R77%=}<1`UU_( z{85pWjcWXj1_!T3ltl3g$|~kpcFu?~-3uLw`zXIRn;IKqJL;~T~C8Ja+w}nDP z;d{S)>6}px>vUPO|6Z?i#{Z2bm;)nfxpg-Oi&U8uVeqa@T12Xy*r$YLgt?g&ryHd| z3kQfnD;?TfUvAAqO)%7!UU-EWy#c>eiay+;&s+#YBL^qKf()%F!XwNK1pPL175bwq z1GyvS?Y}0)9c%p5>j90LC=Ed>9az1^|LfmCvd{(#I-2Z6Myl1BO@srp%>wg#No8W` z0LA@hebVqFIyqi*W{OF>$8s%{*^G2IV*;Ooiz?-NrXm<>0&h8xI-z8zW818H;N2@7 z+@CtcIswS*|4rX+cVoA=cnVa{#h7v_b=aFc*>%|jbiM>H+yQi6ot*KPuc4&F75$)r z>bw%Z+TbH;bV3x)!}64hI|n7DMISS-7NSIIfsd|sRX^;cr(AlR#PEjj+W%P+rUjgU zUw3Perzz^MN2ExjDqG=*k>x(&jUF1T>CNvkbOkB`Bqz3OPUb?87_*u=x|5<|v*`)M zdkG)sRbs~IE(Va*dR;e@F_GEq1`V#*QYL;0&WMUv*w$sMYA;rY835%R9m8;cM3aJT zRajl8Q}`uwsYKe*4b?1K1KxI_LI$3aOv_7A+|d!zO22s2Y|@%yq05#fVU6ez*g3RN z>I4r(nT^zh44#?$QAKMgtedMjBDK$$s?5xYfrTkveFAnbG-claYfD2hJAEX`htZkd zx6%H?2by2S0q8>2jsY~{5{WzOb>2C!WdA}7P2h_GYA-*P+Zsh(S)0$osRJ=>G}Vxp zD>3^}mrQVl)4gj~5E__l_r^N^?TWDR;sw08vJa4yBIx^JTMuP32VydI7cFv5Pa}_1 zfVLm`nE^a$VMBysN)Z{ov$cL4-rJ)465|{AR?WHIp%nXa7XU5V7b%Tl>>Uk?*eGWt zV1ZXD@)o0aywWj`e8v__zXyfiw9ua9!*C;j8tvxv4JU`yX{#3P2KwB!UV+&5YXrkF z>Ggpvx1cM;<20*V#27AMx+`L_0HTBwIcNrSe*)UtEkNLxK5=g&#KRaPWu>!GEcXy^ z^V!PtNWs}!D+ejHxG7tH=baNL@5sA_UhR9@5o(U!>S`yS^$i4UQg!7<;rI%<*A;VG|yY^ z!VhH699{s0+6%SU6#>R_8sT<8lg}H#3q1wX6)-=HnToGW7QEV$4GFY~@&&pRzBNCC z{ft*hErgQqko-6;nH;M6N(2;Oi&#!VMFWm$C8ipJb@Q8-P$80AX9SauPTai)tW`6A z3x$emuLSlk2?Q~C!`f-1E0njlB_>+gXKXu^^W_ubywR8O`vxk8_9f>fy?LU=%s>72 zqv#y{1^KWbfgXPv8dbD&asU0wKitV#M%)n|7b-B7;KaWPP{Fy&lwy;n76L@p z;88_!<^n5%jQaoiw|OmmyGhecf-cr5+{?0lY6jRV1Hx=h1e4=W;U=yl{oIx)K3>m& zq#X#;y_{HG?F6ji@X%Hx?v|Nv03UjFv>Q+M09>Qea{&fk765#Jc>r%^XZD|bUide5 zg8_wOe)I1(>@dwa7?QXE@49pUPXiTC`OXSts)??uA++qJ%hqh5AU_(?98I=|-S+C? z$_9h}y@}vhM;YshEwmL6Dj=D(z^)wm1W~>tSw_b=Cj}ngAJRwEIR=wW?G6+-!`Ya9|iPO>gh4@b9P;^xmDa`D%=g|9zAGQ_mLYd$sOPR$p^P23$JdX z3e`AyF48aEO7=c97do-@AR?7yU7Q>2Z3QX_#+2W&i!#r^RH*aQZaw(sg@OU^U+UCl zO1EP|QUt;DrQ~gDaqrUpG#-pYwLlBGl#WJfb|$3er-U8%wgWCp(<~YM|Dc4}Lo*Z% z#`T^e;{3sQt_g_?Y1{RLa0WgRhU7Xm*=BhqOEw-(f@z3w_hsfXI>1L2$|Xu}KLXp+ zYj+V$4{{Zmvd{n2ZiCqP3wIPhEqc795Gm%!)B#^Rh;}||$%5_CZ{~{UCv>ve8176M9kX3R^~c)xb?rj(gjlyjX7A4L6$FbmVt(SN=C>;}H6*QXK0yiJR@ zFRE~A`08~+g~zFOnWoomGPFJwbqDiq%6zKWN26ALS;j;hk0p0@rG{PKXXrz2n*tNj zVFR#>Jy!6X$xbSPZz4$-L~r)T;u!AD7pp%em*HI+v<3LbST}k5y{qEk`L>XCBimMz zci=DjH>SNP0mObdg8@$pZv&BE=x2^-Kp2;7Q#)!yx3%(`X^-5^z(p|m*8uWZXom~X zgA)pfi3o5)_6zo@w>AF)Z*CdcSr2i%D0_+1P6cZmLOQgYrW(xM%X8+vb%I?=g@i13 zVKG_GD94GQn+36qc3N-p7aY2;tvx1U^a|Z3T1A;lpdi76B#q(BGy?BdyWoD+mXi$y zBPGn}NTC(k*e*Wb(vjVTbZJ=U%K|um+F-!h9Cd@^RHE?T* zas^X6)yh(R#?*_#(ako0{RtIw*OR&9o`8 zL{*rbTLtMK)_}t_HzZenE?=HcpZHfy-kJpwbT-;uDbJLr2L=iBP8m=c8GL$Qb*6(l z4}`f;K9akNOi#bxV0yaL2DJBs@p3yy|7pN20T5*0aOhH~Sq zs-a9KMN{A29|Ch)!{j9`H8?iIBXwQZrW(^Hp(d=LGHFv1h+(F=(+59`D~VWc z9>}j!X_Q0KP=2G}?tN(Rcr4f6cEbXdddeK1h{lgqq z?J*M$*dnD+17j5`--^!%2#C z@2;%2?bvN+P(1kFWNjUQkm}pDeW^!$LWlx+YgnrfZMT^P1ZW4Oar=H4mr0jwi<+` zIhG!6>3t@CKs#H5Mgk<33$uvu?E+H?WMkMkr8O1p6^jYe^x#G|(Pz1Qf`Nki)s1&m zssqz}+UZ{Hlm7_f#(k@Sr6Rq@GlRJKvaI$f3l>u$=maU5TSfrPaVXhtb;s-+lQg^$ z137@a*!8dg+|F?N4hKXW7jSyX8ng(PRvTY{hbY;*j_5vmYA0HE9y&bjcLEmkSGth* z@T^{g(*tCP z+Lr-BZnQUj-C_Ya6TbN@aw+8~cgX;dqcLKEP6x#-&UJae=D60SEssVmv#(?1uql&u;9?DSvGi6vT*|u=@GSNZ zCk4Xb7_vz@T~Rg=@GoAF!<{)NAI3cr+$tj|b2eG-EC7M>_xQ`dqaA5wIOaqvly@9= zu7V}eo}>Nis6UbV2nSf)KTi&x2>>}j#=o)%@q^e^Or(iu-pFaCvJ;x@?0VxsUPV(V{8r}niX;JCQv<>=6jXVu%7R&$6?}*R%UBs; zO$J)nTJR?CvWEZ)sFN*@uLs5rSa1R1t4CVyGeSDhZ zVNPs>f#t_~)rZG~x42yQSDwN4DOGIb|t2_4;@uk!@}bENwNbmb6*Ha;IMV&(pS8~gi~0debbc3Kla<@R1p@;VQJc)t$&A}3xlG_ZUEy<2;7$Uz zQi@fNB7x~0%;__ZJ!vbp$N%3X!{gv0O5+h}`;7sx z)r=q5!_4c^E9wFFW$a|K>^SU4o){gK(p>kg=br-6k+F;S??}wTBHRRde)e2TF)f>-fQVACld&b!O z5t4+sPL2Y1yvTpuP`ag@d8}uN zH>Z!-*MCu_)pNfT9MA?FBgX-&;hu%7v?q|tM&dr2&n?L7Ofe*EWIltPBieg%{I>#T z&2W;A6{Ob6YD!8Zx?KxAmH&at*GPz9?ANJ%{B8i#TFC?ua(1> zQUP)<&c7@R!e*__buRKA-G-z!t4D1f800!|rI%~>7|@B7R8S~mq~ZGk0B z=!bbu`0Xq$Q=w$e0br+K&0afW78@e!;n)F>pl6P1FqckP3XB@{yeGq$$xkKZoQFJc z>{Ki7!wdxzgxpSm@xLn?meexcg^Lh=pz1q*U8%E*6?C25PX_@L7d~2t-N61z@%X<} zYuVQAb9?H9C6?x)bxr^TpppVT56^BWx6b+dB=Lx};9g=>I<#>PBvgpQw zlv7MC9H9JX5={a2RqKMd0-&H?fmMVXOIDD6qNZF;J^~#&=Csw2`#%R>z5c|%+^7|< z$adRAcVI4>Z@xaG%@F!^U~S){cXb9*PLgWASMA_W0tCjiN<2^QB{mTH#+4cy%qa%yXF8?} zACkK+Maeh*Ctte76-|}7{eaC_>*LEhL6rc~AVy$5tZ-1LuwHL1(==X4u+A?vPYQx8 zHd1|~2+0QvESTh#514o`dO_{`P-6K4z1NqAlGcUI^aP{g{!;-94rtBoUVw{6CjO)kp@JqoMi%RJutOKq4$+O~xEzSoH3= z@Dq&45|=`A7dZfB3-17%d(LHUkwxc6yrne+ssIC6H)Si(=0K5_ z;^lAHXx0&wKC4(;f<@>j)K(OR!;uDwq2{oT*5+!)z}8EK`drtJ_cEKwK!EBdn&_tM z8LtBa$Q>#>4co~mO$;=&;?kCVDM0P1@KI z_HxC~sIu$dzlYgmiU?!qZle^*a(AR^rk4XZWgdGYyJL5IGt;c%G(J}-{HEyB4$Huu zF}11bW%U6T1Zy2_zldo6nCDB?;L}1jZ5~{hX#Qh-_jm&8q!m};2hCD){I_w8p zr01o4cM4(RFdV=wg=OMil8%R@-%fK6xcM4#?L-IWaf4rPA3%tzb%b(s`b@V!o^5Dp zHXP4mvC*`m_!5AAiJjv+u)vdLb4)^{mw#QNQ)Q0Ir6_$L7KTp&WtA0?BBDmwhuCHkCy=s6@sQa1xs zHW;_6y3qnCu>)O4KJ!jx-C6H4`1Nc+)kE>NOBtz+UFG}(F#-ggJVy*EDms%YVG2LL zE3Z9-o9{DN@k@9dGgaH|j^zRe%6lZ$TBkWVU<|vGCL{cEkBjzN6(Y%xIPr`O{zM08 zfBpgLPZ?940Rxql6y-tMk|I5BBnNO~+df&f4Hp2eKv)ArPBv5Ufm_J;yG9Gk-$uFC z)@y4)a0Mmw>=p#3N>WNzKT+CXp8F3foQop6YBhoC1S?8bm;@1Kt_qEHGln3}1 z_LLKCa*GF@dsXBYRu{XCiNQmB?3xFf9u)kUd84ska|s$=30oZ?FW{$(r3e@X&-Fo4 zHv0uQj*7+{iT@v9&A+w=PYUAifERjmF{-68(lLX|N+Ab_I2b)tJaTK8uGI*MMCRDS zx{w(x9Y3pv!QQQ(BvJ)lD0cc|q_Ch@01s|j?uVmD|1IY=SoNfSXhMUbR@ny2)Z^LM=;X5u3uH^U7#R#r-lAQ z{~rChA4UdP8;OqUENr(YQ}~;3lMTBH&E_qySV=Lzufr<%q~QWmY){1J!QQe0^(sp~ zUd+QLE4_E_Rs34dKb!ee{YL|(qh%+YbCM(byAR?KR%>q%&^U$M)$;pr3Yf*e?7jw0 zuKt7p!}1vCK>F=TEnDZgnz94&&HJ>}AzL=DN>K+h_C!sn?M-2yKursxt_3LpsL&ZO zIT832i7!oNC5QlodSBf?ng0`CRoG%k1Agcp@8)h))!#m#MD25QJn(e8@wvd zB6F)yYFh2Uko{N5Y@@8|_|xf|E0+2KDg^~qJIy}NRzgo(lKS0fU+S`zAsEW96He)| z_s71uV+aF>t3`%Nt2eG)Fv!bu)@QJt-wplI2+8^do-d-{vG51*kl8^OJwPrZ+ND*= zi$@ra?UusICiqJCGlc`{M3M#;{@y%W5N#g&)4_7?M`Pw&zjt;sbX}Nm+6vyySStXn z;pdkAe#94NFW3`$0Mi5Bx%CGO&{Xf#M#)d)4X6PM=LwQ>P!@8FURvfxds!byK(=W% zxJr7)p2q8#)(`-q)rYLqj#Y~n)mv=9!Yly{XU{ZgU0QRxYPQy=pxp*i1jWb`ncS;{ z^1?5j#+~L+^%J2`kS#Yi+!Q5%xg`f@#3VH$JzGgTwjl9)*wuE87Cd$y{dob;^r?QJ zh${v*oa~eWu;!Wkr!R%Pt9NwcsD*Hs*^;SvVX}K@1Gxnh1)0xO(^AQ=IVgXggtG-# z{FEbHE4SDCcEx7Xi>ClQ3NY2qgTY~fl^mcQkm#qxs<@=P5iAo@1*2WoJwyg0pO2$A z%Ju&m6(WFhbwlL|O8igoqM-kA%5?2ey#fQY`J80@(v7jsr)K&y93QiQQb6l^&1U)w0vcoI;l?Al# z7Rjkq8&6i@B@+eYSgT`lg?G8Fg2V%zQed(Ua2%wr8pYjj{x)d<4F3Y;L$9ceL4rZm>xn|&#%G=zB57Z1e{}qEFv6-fDMV4GS4^7bH*=Zp+BqZv&YCqXeTqHt{zrb${w0iq=x>NEhuuiR3B3I|w}gRTCe zrCNVCmYN0Y`e;lYjC!kbmMH;k8GD7WBFVv@;W=E3{8+_2cb=A$WU6yFR*)_!^~(S` zUu|kY&*2h~b7lt-Ro*^~ux&xyNLqJW4d18tEu04Kexa6Ji%x)qqtpGqX|RYK>|5iT zJ~Dr*uM=)YqgnyE*{mpFcdGe{{oWkOvJ$#675G==wTRw%4#&EB-TVOc0hxY?h+C*i zwt}sYw2IM70*_wlm9nZr)F-Pt_)7+!&c0$Y{oneEOZ87F(<#UGU=VH~cB5$bHDqBQ zH_8IZf@YK#Vo(0@(}m|0vO5b1X|?gTs0w0^>j{*Bh)@D$!UC_ou8Z78oX$jC))Tse z!aB8XZHh+W3mTJoBP#(_+Xb{=zDb`Puwv%DNR`S4jETuwVVai@?0jCyWbgrl=4?T` z2Ov`lpwIKN0<=)=2zH+8u)@8Q7R(#!w!{T68&mLU6~Vss=VGoowCYgHkmM;YaY;6Y z7PY~OXgLFZ|B8dh?)Ze^wIIfTScBNUPVF=C%WuXw${)Is%)rJNbqA7;l9d&hP zC#of$1KnFt!G43TUsc4({tgE}GdOJNGlS?ae57{;xvXaW>cSu*I6b?G%yh4ak}3um zr>ZaCZ?c|qjlN>7Cy}0kEr{Sf=>;ergX>)ieD?wB{a7#yjCFtZZWMxfcB8=kd^Yvl zr!zN>FC-H6Q#=8W8R48ViAUkU2gjmCww({WVdj{rWo;o=4OGwy!}bSoLk%l^x+<4X z5a*P-QJ^?Y%w^P z2y+LdcqFL=BP-Q|kihJhWj9oCyaC6pXhzjq)2siDzeAwgq4n zpymR+DHteN%Re{(`RxN+-<${IP8M#OB7fmNO<2-1P!ud=+h*Kg#~|0lu8Id@LcL#S zuUvBFBcUg(OW;@~JP&2CfC3RRXUBBV^aBEPzJ@dX6uxDM@DCBKi=$3Kq^gf}euERx z>{iKJtdjhRo03saAwb)m|x@iw694P|Emv6^t=kWTV zf>C-Sa@u-U-iYw89Qww?Z^Q$CA-_Yrnq?NlHGG)S^w;;85JkdRH-&H+c%TJm?6CpX zV!HUCAt$HQp$Z6xRen=wKPAw6_detbD}VU{?=A<_0Al{@U9TL+ab+?f z=wNz{7TA{YeKuH0--WjFp5XOt^oj)Rn5muyMajhY*DaaxO~k(LQ~9fgTrC*PqBs(J zr(y(PB)U*pO^jDp<&#@q<$}))c+XKBwUQpONl-T1N&W>q8I>Msh0zxvk#XXct*~{^ za3!D|3GL3HI{*=T&1wav64Q`T1k2Uh%DeZX$^6ck>$^xp?oR<^^o!aEH9ZF7!Jiu2 z#Vg9n3f_V;8F2us?d>By?kE-IS3?_?EKnm`X_kj>m9Xjo91e;ehL*8lam?-USN~_D2crXEtA36c(Y!&0J{n>V zPpFR36c8#RmM!p8&GNJyB^fdzcoaI3|U`hVFB zj+>WeN!<;VrEE6N6et0%2{hOxx0i_DIg^e33JBHrV1is!r+J&C@9`eHhiC%{z6vS2Y*AaDgPmIUModO~*?*l>u zK*F1CBe4h1zP_?!+TSr(yMyYe+vvsoY8!pFjc|LvE=ePJ>vRI=J{xI%Aa)%iZ-lvz zU`XsZKkmGTINS{>puea^oGSwzLF_@s4ukyq!q;t;QcH$MUsF24uNTjDFs48tqO z$xIFHU4(r}6%3+()LSd6A5X^!S*ZdKs@eGB+ME+&aN3%s<|HIH4YA^&`3M3Kd5M|R zyrBf#N6p+!^yPfvS6W8S!rh+6&`Kt%V?aSoOdLNePU`}@z5@OUa(4i;(&%n$~*aRHf?<#2GSwIyMAvrw5YfhNez8w3g% z?aXS^jBx^!2_5aQBL=YD>AWrhsX8i+C51jeCGGoiB`mN$ied+geNjl1Sds17%t&$n zv?2M-0JZFBg=VBW80cDg!S)1&&b#$w>vl2WLsl=haf<;- zXv%T-?>?>LT94?n>(b|jj;SQ!x(Il{1&zREt_KDwnuP@|{;P$vC+@1b?O5|fN2*+_@zy}39ezU}33CZlXP*Tq}rhOz=g&)Q;B*o>{= zTXxbewzdVN*q|s#z&uxP-ZEN_?QVNS@5&9Yg@eaNs1&5T0ahhB}(l-DJkj z{94F9v}yxJIB{Dlf$&cReJ;=fXo(uPdcBtjDMMbc%91dN1il8z8_ve!XaWyM;uG$F zY}$$~NbfYNVBj@sd^#Ax-s=QKp%cBi!AeWR5{LcQ_$3}kkSp~jSavE4yP&Ywo5lq1 z#W*1RQIgTG6me<|z^liyjnzlOd}eq7!<6)#w6O;pa>id}s{>W`yn3pz3yNtsADFkD zj1I55f&*xv)rtVnnyozpakn|vS9P zlRMiVns;99pRj})9^nUL=G|vi{$B)iY2p7Nyp51n@1oHBZd8Rqw2kJ{6?P+l2@&8{ z`UC{GI?p#%#yPeH} zgLFu~$WxC$L=4W44i#OGsf#a}JayK=GHUu-NmUq$mOYj{CI^m7dQI-uIFmoB^D{efQ%AOwfDK z#{}V%O&`+5gJlugayUg;cwUJ;_|l2aM4MKp7I ziL?zmq$-Dt!8-v8wyVK&QC>~Y!1aznQKOG4bE_zK`TxeXHA$0xTG0SK;z^g(Mtsf!{w~IgC9Fl*c$>XR&rJD+$qpAGSuqaiKzr~?#xfo z`%MJEg2&&R2rWPn=g41C6@a$IScx3EBxVJMy1rbj_#e(9Q{zaLwFIG1m<-J++Wub{<6tp~mv?RcSAYXTT^*92_gYgP*KLp( zNJ@Xar0LH(g%rvcjn*>OevbzU0z-kz`3wXjSOrKk7Q22M@r#oSNH?=duQ{U^l2-?t zN^HGBa2kZ<>A$ciznnp!mB39muF|7_S?m=4J!uA-k76;P30`R}yI3e_B~D`H(y)eb zA;gwh93Gi2Of;m6Je_3SaFyd|Dh(h?}Ib`d6jCMp8S@bsFbrr~d(O$9Oe^@!$RAhK_i}FGb@YBiZ`@8=NA9=Heg9 z`8fh$&aB4zpE;reP!KYVUUsBb@%ZCm6Cy)wY7^)VX zsymcAMS21DbPEgk_~zhn{s^4M1s|Dibd2A`($XYQYmwd1YoG)-i;@ypKK4c?6DS*6 z);dxHi@UPkHKT%U{(%6_3utZ9B-|euo60gWU0$i1ltA1TIT`|a7>W%tt^TJj zZhN-!-&kVB)+AI|58Z(r)kg7r%%1>~l1OV#s1|#{HLgZGLx$#J*>!Evj38BS3?PdR z0y_my;QoL{;qw4rp&49NA9@Q?17nF3O46S2mii$neYggUhm4|x+8oEMK)6N|X1*}= zz^Cg~>3gRnobo_9_oo2kTC6LRwEv&HF}y?qSqoOmJ>%nNmlvrdS~*9}NJ#_Y=B&W& zX*c?De)4rD3ss6R#U819eJxmAKalXL6`ut@csJoAeWboAeYYDJ9&oD!Z#!)P7k^qOMCiiG^B+%=&R6O%cGN#ez2pF$Y|8sg$~Ypiz3e&u=4?PEc;jC5FB3`l2EJ=>j3(=uOy;i-r%&b!q{U zJy*QTo4y}LRYnvBdFMM2p?<*Nl7uy>@$jsGWn3zP~?YGW8jWi`oS zps_OxwX^hzx26X}^Id*R|LYYwJa2Q~2|l~$zUl?c$*3WXx#f8+@W%p6CThTy;D1A(IFYWFR6z)aZvW3hvD7N36G9Uyibk_fkUF~p zhb+(2gbD)g6z=oey}Dd3bRP)kcpkN=;@p*DXbY?Oet~yFceeuq58$mvChSx^cEm}| z`Wfr(A24%<*qNRoovNHGWH19MX)uxj55@=RfLR4UVl92|>p<(S0`T3vr&+vZ4GQv+p%x8zy`Kix`Xv{3~l?h)_4ZaThyks^BhDriT?s0Om?SvDF<0F*`-4Gnien=mXbfCGjc&kl_d$e zD*yvqHQx_LgX>iTxzUsi^9cU9q_^UFie(CSv%9PpYp@03gwN9>bHL9#z{uM`?1TCV z>Rhg1IB!0upKs@lk=xH*H?HoFRn)LYc%-8h#=V2bqHjDfB^-2Ra`%%d!btu-%M>Z>B3`3@CIple7 zH#E64V9)YO|AzPVL~#bpDDS+$U+2a&|< z6sNd)2)I7gBoe)N5n6A7a_p|u?0o|F*Z^gDhN-3LxZp%yw>r4g(RBRs%AczN-za7l zh*ARB7D}{KpfH!;6YAGfFAXo@)AE~J}-V6E)q z0-?sw;A}^;*X15TGTi~0lh&Tcx12-MP*AG6VLfYMN5zm4qb;Uys`ZA<4g?18qB>BS zQ8Ag^5bS@7S?(*D_bWbw)72DPwt>qzu2}&ZYH5Pwt$;g=*fclgvvX_Up>k~-7O4p} zTuk`tU1bBaQ~u;=yE{V5NO%p*8h#AckD{GS70GWLhc#sFiDCh;&XGZpwHOAdwp_5F zytIa&jld7t4{Rxzg}>dG1XBSaVuh$(#vQB9=)DYni1VLzkBlg=GBCcUQWy|(7u*H2 z@!p$KP8s)xuyrD``fb*hwELkuj8c!OUu2qMAR7R8i$3&T2g)P4Yx>N}G?lnF3(jUP^wPMEHhEO}L@nU)N>0eU@Ks}VWkr^k(VEskc0!9+bkaWvy*BpM|_jVpqcI$ z_}L558Et0|(D#q0o2+$O4Y zxCB&7G+M8=vSL`#NVO@Pj)7#te5J&&S-}TP!~7z9C4{o;CY8D{sFT9`a>{dJ2;de6 z;oJ(h8YKmcH$tLo#`yP@ulVi<8-|GlH}*82E8KzYA>SbnT{H#ik5U1iAvLWM|A*CP zs||ZPW5{>|c%fW$WsP(72DJc}lNK=DWn#8iLtSO)tp&IRYU%ruy@6Fi=^Va;X08K= zv RwbZW0|LWY7tY-(SP=4ko;Q<`7UCzFs=WY2!Z$h4`Yy_d*7No3W7kO{dGkA7QIBMJTxU13F1+kz?tmj(gg4sAd{`WyRQf+!EZ&W;RJV~o_!WZ~WyQMn?k&c5;2zH+Rfh!T z{`IvBzZFWa|AfKa4!{y2oVE-bzu1NHPaGz(;`0KOwMRW69?wQ9fx;r@BDl>Y&4*lE z3BjaSuHYtG1(*YLrBT@0JyxJ$1uI<`121mu4dFDi22pOW_f@} z1a9152nDqHEDJ<+;KU*CXvlD*7Wo6q@>a6mT+2*z+<2(j?v=B|u71*M;1jqvmWXi3VAW?}ZE|AmiGuY<5 z>>{4e`v;QAkOPv6n`s2XUc=mofds;_V%a%`3giZ~901fQ0fDa?w!lBIt8oO=crP37 z5unwZ4P}kk(W7k6;i^Qg9`aARy=cd}qOJt$icYx1paU)YpdE!uQpnl?foGsAsRq&= zO#eOSQ5Of}UA;@ISRysT4WX)*&FF%?Wsl$JoJKuJrJe#L-{%A?4h{rODyV2|5&#V= zdm*&?uus1+wsnOe^?-qD7gz)O9ry>+>l%ojep(TPbZauIH@_MrT3R+PUfar};#UMX z(~?gv)Z*AB9J>vmE8I8>h?5}AKh^a0X%;M?*t-A?y8`|AL^%aAXQOWVZmLI2p@`x! zf$a^~nd2TEJuLug0YQ^Min1v)zC9X!^%jTQ5iHI5Dgwog^=_?E{pu_^FT2sjE#O+a!+)Kc!Yg^5Pc|dUA`k)S%pZl!=@{#3AvdwZmjMF)#ya zKy=A<;GSCuYcQuIBC4>+yt96d2Ie;F>Nyc^IZOqt-cEk6w<_?#r?KzJ;=R@KSyM4n zNIu=BhIMNXY}W^)^@0{tPsEuIPJeolsqC*iMCX5%VeDk$7Ao51&q)cOtk|BMJT>Zd2!f3fI>u6P zW%vQ+HLKzLEWj)Txi=NOhk-h<=y-A9uwrMJ##^e<4g;769yfI5ho8gqgiK+)g=4w2< zV%)I;lE?0{A}Rrer~udvas8*YgE}F?S|0&4M~gw}=hYJhEgv5HmRfZ$ZJ|4a>b&5?_fDqcG z{cW$Mb6>vpQY~>$#0_iIC<6uYc`Z)&*SwY9rJS1%=|hLU*E{a|d1bUkq86|lAOr!Z z7be2soVZrV9ce(#s4`9SOe*J-GrK|JQw~=Rh6(`V{T|@ik@Lk%QL3+$?HGpnkJNA@ zol%r46seAI%RvGVFC>kd$%|1UaD8HR>fltZMy^9V!!hY6mMn#JBfA0Cxg!I{d@09t z89_g;ua;Fr_Cm^(W;}6>%Owm4@&yB6iKSnT2{HjM_ang+1uYYy5#JNy6E)8{r<~no zktYV^A%{7K#wEhE8Ipj^@F@JG{>zYjWGJ4>ERg0rCAULia z`;QOi=QxeEv|9qCLAwHgI)(doX&F8hNdmnE!P9^K%F5-`2rz!=G1)cOq2JH6>c{<{|?2NX!9bO5K;w@bk(xzAAH8Y>#{&0>Q%D!1*l!L&5YJ* z`FCTk96STQ$I=2)I!cRh3u$T6=nLr7wa(eZwI$k95@C^h_p$?J%l($Y=6m`&Sp{bE zHuMVSHSI3YD{fVM(B=B(@3sX3F7m#*mFXNu^f(ac`*d%l`KR88cvbDQ|6JF}YMTLO z;CzF7kcmTcM!K9^Un~x4HP<*+M3ie@fLkU;41We}sJ4Zd7g0I$8K860aZ~aZe4U(f zh7U+(E!tUibuS0u-|DG;3T|x11wEd`jywT&+an|wGK_d*SoH0F((3?KDlmdoS@wrQ ze*hHMsyH5%Ll{@(Z_q_vHorR{o$LqI_^!UBwtl<8{X#~fZE@K>Ee;?hoStmyGjQP@ zOO63)GEq$SHKpdGJI@DfZxq@l=-0%p|B&Ao*b_NF&^G{L7yrQBEZy9F;AON%vxbr0 zju+N;;LYqhKp@)2k$(nUqzbl-RZYd^w#_~YIq#jh3wtqut04|5_J=!wHK_dozBkiEwYt$YtutM_yR`6enV$0Vz4uRHKE z`tbE0EMNwLsu5cTveATd2r{;I9wvq>GzQ+4*%c_w6?LrpK4t>d;Q5qCU)BF_Vo^YZ z8He;h+S}{*Gwp!9JOHn4N5cZb)GP}niiUhcp>Nu)oBU1{Uk|hJ=V^prJc_6NfOkTMRFQ;uPm)Vqzz=}cfDyr;oM!W=&y{%Ci8;q%u zAvh~_avMOauK`#JHRpw_2}WHAW_bmM_?YL6ecjQ=AM&DK_~3YrOXVY7?yUm46F<=q!=3fXw1h9wv)Kc^ zl4QRx@_2`;iyBZ(WDgI11A+e98leNkgbK^*i9Q03m6krQyZ0W(O5-2$(v&Q1SsE={ z%$XW=XU%izkaGa`))Lcoy=f# zR{G_`M0}{98R3pCA!ap|2rGy!B$%ZN>!1WaL3-d2-l{(Op*OIiwgCGQ_@`)<f$}tHpvmml1v# zPkc0IOIjOf0$&||e)vkA0n`N9F??FMrlS^unTcpJ4k~!T;0~{Kd>@yeF*(2DDwqe1 zg}_8?{<{Q~o%Kd>ViKGZdtMfK%(}EERA+KT)?tz-fElRt{bvJEkP3_WeKG}z zm|AK>(@v3sEW%h>XiTBMC-SYyTW|vEz--e&?_D2Y9EyU!?<~Ba47m2AUC5?FxIkEs z%EJc_c_X&M-^w@7DV5HVUcK&BVUPKo=cl>ueo+-2%;yKFADua!s<>35nibEX7ntpa zYsLK?h)@Zip{{7?H8BRf)rY$dnq(B~v-3^X4Y2Ry=>U+|1{O9*pr)LC?$ZNmeU}Q0 zbaPdGd5C%Jg-k?myjDMyhBU9T%A&C|^%DZh1H--+Vm>so!f26{_?UKx9Wg%`#+&@) z+DvMt$G8HHn_v;mHL&x|>!44es{4X#(7_6;gTeSWZNhw8_`v|{MuMAoPy@+!8$9|! zpDArbF+Xo<9>LoMAz-hLA? z`XIN=NWnb%!8~?i|A_ZJ znny$-_jExL(Z=sj!`?oh@D>1{$9Mwf5PFbt@xJn8E-A;%Pw6$rJ>A&5v9?Vmc;3292JWy!dopbCfTH zlGrqD9jwEWPj&*Tqxg^Uq@5x7jbe{qC`3Pg@Uu{PtPcOjXOlF#9V`cYsqt1y@N>cS zy+!Rn%KD{0!O-|odR+ZSlh44Vw)F@2mo*50=&w^QFk&f)K(qdSEa(@zpyQbH$Yd}L zwuJ?8mZ_$S<`dp!A1rq;`P}HzotesTrO>AUnQswivRAo+&Fm8u=c{qg$dIojch_O5ZF>cj z2{ug%Q|gYcID;`AYP7tI)UWI_GYKHG18S?`_s|1yoiR&4{%;ySFH~R7Hb_YpZE_@_;=~x>io6y6CMQ?%3OKIT_EbLDjaUHw8bKwMoww~cMDE;)x z3&BZ;mR*%l7XEEIblQ_^44+_H?r8?W*A0zB7ikXrh7y)@guYlIblA`ox;_ERC-M<@ zMoj|B+m&+d4=IV`LBS-weliUhu(|_ZVf%?o-Gmjuk1PRfiKV<#wXpRP)Jx>LezTbH z_Q>1(IXNBV^#Fh;QZoU~776}(IKt<}_7cWcs9a5xN5saw<887c=b28zcYX)#0Jm+?kr0Xb^F#r%Z^>VP@1>Ki$Q$-e(+u&c@SL*Z?R^%sNR8e>f_njVH+h_g+^M3w zCuAE~xr+Rl;(JOhO9?veD<2wlca#CSV_AqR0Q|wZ188lU1ikS zJh3K>>HS|k?*biy0zLw3)!s6x^e=0x)ZUFK5rV|R*y2BK^LaJ_8?9X}0NMdw9MtuQ zSu_8Ba8JgqBfmaw`T`nT>K|v`Mu>zZ2=f9$`sa&+wRr-IVEEm7P4xy=!@Bx@B3+i_ zTbc%jOO^)fcbPS6QSQAg0v_7-d1Qp8&bXSo@?G|}#y(L!cOC=Zh3h`P|4>cc4lZ8M zXEaQVODQX;hi1)u~Kyt>1mX5VVNQdlVXJCoMV-eQOSNRKW(HxVpgUYY>Q zb4d-3&n50tCa~1);&fo2A@oEkhe7d*&_n(6!4Cu2)K2c>Ur!ICW8GHms{fcWV%i4T z9X%5@E3yHCL;C~3(n|0;srX3DU=P5TqOC0ZX5Po6ngPUo^!Qx|&=3S(6?1<>@4>2;rhD~&su2cu3g~A}nRGiQAzFBYV(TXaZSItEkS9LOc&X7;J zO?m~)997=;J=kaqR(gSOy4Jq5Okfjjjllq~-3=n7b5aIkPPOjDpSMXN=6xr{0q^sX z3b|Q5g1Q~)e%n`&fNlm!xp|d_y{+;76NG`crLL-|96b*OVWueV>$zlNon`yde>z(0S5Wkr{g&FnXk6Lkm%61 z#lgmC*R2NUR0fv`xwT*YTe=!T(_^1;^R5K-8$u=M^ff5-^0@^uKug899_ab+slx5x zY^rFh1ad5`n~{BdDP1K3P$~uUU@MYmWqD=k)tI@`Bgj43a$DuVfQH{%+bHf3l zC8ytp6dK?XN+fY*;-(hFSBDs9*i}FQ^V|h3Z{;QIKqdaJu-vaDG*3QIt7aK= zP4bKt&5=d@mFELBUnM)O_jilLa4ImSo49O(9mf*Y3gvU3r1KKL?xhBajvDz507@nP z`EMtH33_0%ZLOV|$a^tX3~6-s;>88{K`0J^{`|(2SPzEhk!MjjBXMAtC1I3EjhimE z&)@~CsgdKr^Wh^?`sISX2SRY((E~16IOL^43P-DQ#5o24Q9!Q0fkj!;hPiplZE()s zG=TH3&GAVEwXdWJB8dkssdE?twh{0OI`Ki($Sx?-IcBYR&%}TqBOM`DZ_xlC(Xhe= zi6s4!SAEcJXntTzyzlLGQvG0H>@p)O-k8z)uitC~e#FapdavoReDbc6(sTY}!})#J z{KR2=XCeKK)jF~OTHp!L62C_!#q=UeT5M%3@s4;zdU_qN69d5=z-goeXEpH_qE-)? z!OO)_7QRcNzu?U>T*DReuq$r&Sf<+p#awUa=*9r71PgkF9?oOhVNWe5(fW;u+q{Ng zr>xlk)Gvdf`B8!F)B(VE5{I>bP5BCQ5u6Knr7VNq$2{PdGGoYgk?jf< zV|d;r$kPe{4*Lh4zXPU*^#+jBAMnZ!c!cBTShozdyi|MRR<>mXcK5Gt(c>%R3u@d{ zc4MXOqe+MpGF579A)9%2&6;>B0B{ z%5q&ExJ8&LrUt!DM3HFN~#nqV+SB$sGxT3>-% zflsE2xYh%FqOsiukm1#rWCD5ZwA#MO^XN%e>{eP;H)?ml{4w7*M6NXht>L|>>hR$E zbQLiT1se%;DW7zLI>J8H=^WTIP?tgl;=!R(7t6y)gh}f9$+weKZn{&L5t$TY7ft9v zP0+dm`KHC2H?vOyQ^w`+XS`Rw&IWM5kpLlhapwzy*(#j|NklTZtOE#AKu$|RW|v(N zGSu(e0~N3~KF-btbo8IF8s!@k`m@u* z^NhEo+ON=caN{8{sYQV}r)ahYy^id&FFkGNC}$E%*n6j2gkd?U2(R4v*6Ro{SABpqKoOT4cA1Vi!Q1`w{wxpme)SaJ@l($n#dWy zk48NJKxTFhrEG%?oyhG5a|yRky)1nftPQM4fi$$h#BC+$t~S@~d-*PMT0# zRs;TowhzBE4I$x>0F{dax1yCs`%K-pzLAUt6Y7)qDmrJoT{(2*_(v*s<4M#3(yE)> z3J25^Ga{k@(H4tgXIozw|h zWH0+;$gMsGDJJCUmdOu0mfW1`S!?Fu^^nAzF9BuY)%w4?JvxB|D8T{4Ke;6Gm1~tq zJjr+oX@jz(g>3Ui+hHn0pFRKraYr!A3bGlkH;`2S+9-9&R`$+a{}OdkKLcdW%|rD8 zE1sL6A|ed_NIMG@PT_iqc!sZ|>0li_o*toy66HPzxd)%Qpqyv<8d+>PTlzXqJDJPC z3Cm`>8-!bQ8I;KY)1<19WvTVVhh{+pcj0R_(}8$?J^O z7K9GDZ>u6_4EVbN6fTKF<=E;2si;;5rQzc>PpA)6_;BjBEn5TO-zN)8#3$>8(jf*T zT%lV53RA!mnsS~Nv4BMJzxjH_d6&l?0g_W+JeR=Y=1oA|XWuTq^`v3iA*XqMpT2~coEGy0)xexH zx7+eC>Q8sfD8=+i%UsaqYI0ck7ptY#v7E&FD>!ybl#p1 zbqU*Alj@rV>EOBVZyR2w40`rf+Knc z67{2jY&1m^hto=luVQfNm=Kr3)x&s_n4lzWaSR&(oeSaK5%;Ge^M1a%`=vdb@{d|w zx5quWM?hY%)JBd5h5Q^zNFvf@l9=;;{wBYDSHJ^W_K28)h4yh6^&3M6YpSY=1l;2g z8cooK8cwVI6mPf25ohg>r&t$xFb!)2sjS`mjyPOBuJxkJ43Arts)&1Rm6pyll=#1< z>LHQyRb>A;@R>Q1*6cR2)RTCFqCEcXauwd8Z4FeOSPr<^GP{X zw>DRZhZE5qR*T3^Qe1>#jt6uEAKp<0V}4=*#3PMsqUR#3Pvby%xj3|HuGn-gkZR!s zhqwp@Jfq(xYea~SJ#5F#|FJG+kSFOf12Tgc%LF|DR((ZmMFAD3&~TXf(Rf&oIy|@i z#MT>}rpUBRS@a+S$m;Zf!;)i0**2TgXenK$qZuYxn!!k>6myBN4a3RtRf759!9^SJTfw~qeofT`IkuTw4GC}Vw>^d$Gyy`CM%tll5_LO2 zXIRw*3011~D0Xlpr5*hephfJ|>&;;kp@HeLK3`NgvKqVuvS^&k0cM-JuLD54vx~i+ z#|FG{5@o{6pi$k9ac`*xHG(mvd*TL#01pDn4+D0Bz1?B+YKQ`6vUB=mxz;!W)%1Ff zOMg2n)xARB$I)R#N}yLGE15Hev4ch7pb(-5`Lp!~EJSV7`$w0V2kzPc>```@r((#} z(Hi%1BXE)g@r0D8Z1*$$3@SrX-Q7H)k#^#8R1CN|#ifJ57JU{^8U*P6owp3_t4ft|Irl5huSrgCKj zRI!`E{8N>x(W&>KyS#i1c?X_%x}4CNW<~&rdR55=5wzKak^E0XXM}^0CxU^pz|gK2 zRns%t(MY9eGix{nexy(Pqo%vAb}wM^ZsT$msoOa6+9+3>#KkLYj6t#o&?59dUm`-D zLS7O$$u-LHHCa514oh_%TmRDGTV+xO!%cKr@+C>T5D#4=>OR7tr>}r}{o0G}vMr`6 zdvNpzX{({-Z#tkr*rJ=)sM3Aw4Fvv}2&=ykg?p%xiZB)j(tUn3v0D6AsGyq^?N5^C z|1G*Mb`TK9-(03Sx3xe9+#ltC%yau;T%+3A@j95InlkSIqCuul!siO15kOf1o|W&U z7``#tK4>Glg4Z~A0iNJ3AYw$o3SO7u=I%lO)B`vy=JatnXlS7KaQ??$dj%h<>Us%{ zUpG!r1=kS)a2zYFFCLRWaiQTG~`ZI1~^D^0>zr*6t)Kh z=Dq;#S&~_WHDp+4+DW*hodupY#4YA?1Oa5DjI!kf8*F7<13&vWW~If9UNspuOO-&{ z7(n}Ui49WLen?XTmF3Wh?}m9+ zz#u3Ly%krKk(S7%b;00i=77B8{zNGO2{F?X#1$0dMQVtRXd%jFs}BdWZ0 zX*Uxji^i2MS9$mPed+uJX{D(Zuj_93a(H{c8dLth2)C|Hh!d^8UMfYs5Bin?#)O4< zt+M`vne$?f>*SGpb=3+8B*#06Gq>6p<*-=-<{*3%`-^$nzjceeL`AaU9;X@=9MMUX zBHn6k($V7pjVPrN*dR+e4_e>wH6{T-o(E!#&R&nG;Fky9_0doRzL^(;=6C*W@|q6N zna(dG3r&NXWuu$@4^GhqVHZyU>pBOD><*@@sN4Y7^b5kVsewyXc`?o*+a<&@Zbf?2j+KYmq+;lRM!uG&Juko7uvEnv7_EdpHlO4`*YF`w*(l(vSw=9&#(A3Q=zSRnxsP}Fn2{pSl*!2+WuG=a#V-G9ESnIh zOizk8K9#y)URC+i_FDvrtv# z%7|SUeLW|?M84(<+bpFafLVAyhDp8$^0y45VxeQ;brb;fyHaAu=IVCeEo#b!u(s{P z^ki=X7&H)I{|I`pr+nj5L2(D8FX^Wk&UF`vCaJ?=eK!RMI2m3)ahO$hI|o^w-bY62 zyfVNk&DDLrN&@AD?rF&ep7_S;Up%VOP82V46!H=NZyyzkcN5%f_(Et%ayR=1VK(1T z0fu=r1xZ@1pfUvN(R5d)`_Zi)Kd;w|gY7R%y+eC(6tjf`89d zRT@;+61Q;zRH%&J__Ppna~iu~^PTa3i%aC)m+4hBiqKglCch~LGgEwZrR%yc_9o=X zGb*{FWR2j>Yrt*%Y;0B#Z0J@8^f*>$1bLMV-&xErA=c&O?Ww9R;(5qdN)Yhlqf7z1C zp8YUPVN%lrhJU{~1xv3L1vLds-aL!r4Do*$W0g?E9E#YalfO9!^&#Xy2Smcq*0eRQ z2b#vJ`~`!ZjV&L$<#h zFDZ6&J?!T)2Iu|(I#dgP)JmHk53=8QNqd!!@>3D9i(q5F^cE$9CNWk5D~YDcOUOrf z${XL;)hCS!BUY%Oia(+h$xt4iyP69H%`D#b_yNAC*LAn27l7D3D803nHM~5Vx5*gU zVDMT2t=3Uq{o=)Mf`qgDU6r_huCLhr<*n-PblIg+){z?m+#uSiRnKn6QHJtc!EQdJoZ)f~(r z5X1NivrLXDuGWX)O8FC#HDFT*P;jnv5P~G~hi9hk?@=$0!8VJ1W2RP{PZa~|<`2dN z<$=oJCALlL$ixKrmWeiwfhx(pD+>KxL`k_j=d)7>v5IAURV`K?yWm%dggUr=%5ono zjqP5ejUeriM)bV{oCpW(j2lM8+E5EMT5E)A-S@t%sXC)KlT^rrvYBHA#8KFbi;^=z z5ZMR*{+YWx)3lKg@F44DhmX+*!EoCEk`eT>PJ@kA;c1#NM5Hv7OswT}$V2>gGUEg- zTxr`AK1W7!mFN%~OeoKL&Z=|o7_B08i2Gb-gJ zWcvHhM#or!@Hy{EkUy0$>ZMyGh6$7K)Uq!G_D^wPBl763Hb09aC-(_EPvH4)nw#b@ zD)CQ_;&;yj&KJ>e)ShPE+tdjPs}4XMY0K>j@4!%P4!8)RV;H&v7Lp)&gNqW|e~Z#h zbUq*7MJqEKaS}JKXBPPGGNd2{J^bh|W8$Q=rxnaqN>|wAaJGtsJ^sDsPc~mmVEyd{ zd0feCO=SJ7v0u!tr@V&+Pf7H~$y$LFr=x|l5A%ivOI$b4)Mf53(ihGRb7?-Mcd`sr zS?tfnod0l`JM@|br_6K3CqvH(#=xghSHkPv*J1=({vv{$&}X)t1u(D&B)gZLtdqVq zFGVLf$0J;uPQ!YSM98~}Dfv?Wo@JT?*av&KATexLeqS1M-9ch(>u{4crX_UG>3*v^M$iX~&&~^7ek?hmJTXBnJ(A5y$O4^y7WU5pPds!> z@q(3!m31YYEEr`|ABC6X%MjXI2zYLYgzH!XCB7X!O=aQe`={c|_5RLm#PBD%U-L~S z5Vkm@fFjBTuh4AR#IUm%GXgt{UjT)aEgC+Sh!~;ED=OU=Tt?LcP;I`g$f&fISuGIK z3)NO^!3Jt;e4Z0&mT{h@>gBlw<+Pd2`p+bTGF}W4HVD7KcabHA@*^;@ME@DP&68;d zdO+$ZU?!S+vP-aK#_q?1Kp+jI?&X+p8UzWd47}6_Xk}m#05+x9tRr7QPS|(WNcQ-CqPPdjXgIWxm zYC+@HOp#lJ-ya@I_>oKju%+x=hK)GG7#JEiUeuK3z?FhmMupUmbs!L_IecURy1pB3 zpTg!1x5gE4{}b3Adp^LDPfdmYrabe1ELm~^hv6JKw@}HV;VoFF>)3MJcVokb4=WJKO}453_)Nx?razgOKK$Kbwh>D*=12&kl4Bh-R!h)FV-J0N^({c zQk898JFs?sV$ALc#k%Sh5;xESsC#)MNe(jKbxG3r$o^oH?>iQMu9{rK&N4lcf0Zy4vS5q{=K z6G?hW^}&h7H}m(M1b?~&DImoDLY(iT+a1{_D14xFxu7B5rgt(m7qu!|)8q&OoWTAy z@!4H_H26#}(580s@`cUbK5es^V&KK>;B}Y-UDRMlCpQuv?RD?T;hf4|2!6(1n7`^2 ziukB_GbFwOt-Bg=NN$@Ig2>jKL-+kZh@cXTBfU(Uu7#uCz}031_o)XX$jXug04ASu z_Cj!J{NHk_3l{AHyyt#>g*W*DAe9A_C_P2aGiRm9?obhcE_(N)$aywqUEgCFjk}}; zrB?n&P7-ik32)2Iig7xs1Dz4qzCj6(^8Ih8hNMOX`p8I^)*|%yR|Dw!u)7}2bnWx+ zDzvN5d-szH%hT)!lm^YUB11O>SD&heMRF0BqUmayhk_ps5b&o~c{(lx6_98AMhsWb zrgW)XhL&fx23}h!4U{Q25i`jO)1EK~$_74bge}XSFSQpgZZTJvTy=y?2V2I}8n2-6 zvebeE8$trP=1@@w*-Jg~&c5^9;ACbta2QoCt^ka_=f6w@Dx^wGPNDpZQVTxBasXqD ze-bg@-_MH6WUUROeR3%V7D>lbke9Z4_l+?#t`IkrnslG>#QToAp{T&KKGrz}l3QM$ z6>GYEORik_O4Et_k+O4HsU6ZOx5Xv9z!`7_lg~ltrR@Mc54}u>O*$i)YwvcavW;my+pN~>zY^vtvbNcRIhh13ogTDsP-sH3Fj_G%cY4a3Wy>RISPw>W{bnoP!r&;8o zGU8VU^%)G)ZTR~Dt~~Yv(7E2=YhNtMH%1KG^75-Q;n$D{mvh~0#?*LJeYcSnsXH#k zVGR@H`lr}KLPC!H_>LU`4yLXm+fIs#dgOSKSTTLzm%4@SozT5KJKHzb%R)i}imAWO zvYSu@^uhsL!3dZP>$``v$3?fyv!j2GpfZ~SyjO1nvyn|Y2|(okDGS($evvvKZQ#Fw z=tZe@eswVO|LY(XKAYcG*et}K`CA$$XF&5X`Z*B%1ha%PW#+N{THjgQfN+FS6w-Ss3 zE$i@~Mrs4ISs9;@048kLV1zQRjs@P$G_2@8nX#7xmFW1TidZ46C#(x@fob;$Refb* z<^!44KrZ2EMr|+#+|EC-r9vy1K7GV90$M`QWygI6>ct%xS0P+;pWcE7>~~mz(EK3X zltM~9PqU`=yr0&h+IeQ37F|-UdDJKc4*mxqK}$v;Mlse3!*9XvF-<9L?t52`@I_}K zoe*OJW3!l`yOJ`gDMP5#W`j#Cifxp8*BT}W9SPV)rtnM$|61=_p}Zndt;m;Ky+%X? z*ZQMOhP7I|nfbHZl*WqyPcNaM#ysHgKsGJ#Vb2yPEUdc5lBtx*24_LqMEVQ{M2t5g z|IASV6`aG>i~BN}ztp2Sz2JfmH81=MqG#a+aUpAFZ?!<{vLF$GoYkYGk(E%q(jX`y zgk=bAg8_8{gOo`I8l~Yx#!ZboeX5SEz8=9B#Y7aNA?8iLy0$F>-6aD7H)_hSmP^HA zUv?!Yr>HrC##XnShABwcI-l6toSBp3-7r0SXz_}y9dbRb>* z-a%+dFj#$KkKH;3oJ_mS9?Q=JBX>z;&;+=2)gXWv)@fL6aMtu}C~@0up4aQA;z}5S7lSzHi_Dh2&cAX6O_>#; zE)Qb5pV}U^N6q-(7_<7wU?qFiiTP=EKTEYeb?fjBC znF9K!@2S0v$TjH>RQgRV3FsF*nrlV{RdiSYLDr%nMbKsJ5mA)yNl*neTOKZr^r|M7 z6Iox@$@B{H*@|DFzV!g;wvZs zsbynpy_Ws^rDCKdhh;|)Rb@J&Cmv&WS}oxY3Ka+i^dK3>ra0v`D-uK8{;@{b6?TyS{5~)=Osy17oTJq?089)jSwN z2{sOJjr3u;+hcrNk`*QFITJBSfe+&X%`mU1rc7rv4{bx6#}a^KR-6-`Vfgz@EoS_g zg*8Y8@Lo_#R|H}icQZZXBh+Axhm&GwUf;T-d@Dg=4^QRv0AmHL%V$Cigu1FZPCU6}9+gcq0 zEcOK!@6l{WxPa<*3N{&pi4dKJ?N59-oZ~jrBy$G`)AJTRQ$+zz$#<(zE-YVSyd2)z zbTpbs* zX5i8}0@fZm-1sVSKrtV>$b1OI;~6glfL^^W16ar#(dOS&)JKHN&Fjl*27Vb(Vi)yn zR$H?JqLp5*RpEzRJ&Pfb80EQO)B~i6)QQEoJU^^Q%qIZ@nw5mSEr$?k1A#uQCdPqj zgAC$dT<>|~rB#NxsRrQ&gq(O9-CxsxKjbzhhUW5u{>OXyyP+q?-MST6`5May?aySk z{CAqHq74wY;>DaJ)`*xm{~U>Qg3<6h@9^~j5E$0LgZwCI<4kpq-PiTva~0hoNkezv z_Q%qJ)}NRI91X)>RIco$7#+DiAV)J+XxwEfz(HQc1*r9cxbRK_oVg=a#R*)xtv6H~ z&R-*B2g{_ibb>L-;VQ@MLoFo)DAd=XK8zRbZRj>a*QzWqq$wnucz5V`ywz!SUQAj8 z@#gN3kIySp9WB!o98;mgWhGB*4G0_6j$i}R5o7+Hu{8MZcw zN-w>He;8o54#sf`e@rg{d7%|hG3z+wmgr8ngN>xR=Yz-0R|Bnd(+F?n+~w^Acoi@F zD(GKGyZ=>m-%SEB^u(OPA^9aec*;G4%Li%!4FoXK@I*v9^4mYfdB~~ZS3r$H&Adft zKb}4$O26g-VD$g#P3xwE4dwj(+%r5X+s`5Y$7SplvSr`ul zO*MODNkw+?pB@K4+rT}uW{%(THfJ%PKB*M9esjSE9@F|TajyC2aQ5spvM)Q4*^z8S z6DFDmv5fdCr`0Y1V8gbEl6_a-L=!j#<0i)Th1sz{oq=9z7`wdy*agf2WciI}Sz!b4 zWA{0HHsgT|S>W(16)!xd5tyOal+tVf_c*sti5D&~;oz0qwtpMzIz(->mqjjy+IznA zI`qK=1+e(?0slP<371<$YwHECW-dOqR;@ZgF#l%L5LW;;R& z&zjza8@EsoihdOhi$*TMvQH)lj6A^qyAxFAP-Q$OhL9IZB~w<$fVwpa-TP9Aqqb=P zQV$&+6RK}uM+z#@4atvP>IJII>~&>MGVR;`3PM~0Fxg0OQnFm3tSYaF$CstM^o%ku z&R)~X(r^c{1?UC`(N1`Rw6_N$x-E$Cpuk=&`pri4oyROV+PAy`Lo##*A9N!*ukl0h zS?x4gNZIt9Y<_3S95hRaSPI>NOX^Dn2@6(Um+APF9yo6s;aC)8j#rmvbMsuB{8YJe^<>&H$Gpa8Iy=8!ElJA#&OeifVrbhO(E z!bjL{Uoi20{NU!+gp87fRZQ~g4ij-RD*&Cs(#*rN(G-pC^X_emzW@tk$u#dD7Dba5Yd&99YpiplK=>^n+b*%Ebr( zNLjCmc4{SxN%M$&Ii%z3|IoSV77FQ=e7}?q$%CN! zHIVkqnL1}Vjt6H4Dy&%xP8*+_#$o~jYk#MudZkL+H3VJ=NOUed%sm$ake?xKufUaN zgi=jbqd0>($+yBPT(I&ij5QSa%#G6njrC50?<}7d0Y$Yo6-i@>$L0k5`!zpQBHT!$ z@7@stgfPX-gSG7G^5lof#@w*Zu2P0|ld16X@Q?7=s~;=_)SKiRZV!51F1SnYA0gvw zYG)Vsq){~;=ILG4<*jJ|TG@ar7T26a#c0ae?#wAP)p4)PjFaiqi^ZZY4xoVEA_4V3Atyt|wL=g`D1NA*}f^F$itN#nHj z&6}olZQKe0IbMkG^*PL`2AICB*Nj`a%$y@K7UX%d{%eAKgza_*8!pi=!UhHi#nbJa zbNE-}D#)ywP29lE!Iu@Sy<$QG3avkSwU_Kk4fFyBe8oKvewI=`T2_9zn&mB|D}>zy zw4ui+W=BtkrJg|vB~`z@nt~6^uMnnJ1RxD;o+{x2nJemws)Pr( zd0BQg-$Xmy&OMU_e>Eh*3t;QNI>Wt8k36kw4P;sV1-i$eG{LTIbS?q|m5dIF;__+Q zhAk;is}%Hc3_@nEuC;qdtz6LvL;M;ABXlMFMfCfoRESj{j~SxuQWb06I#BhLy%*c6 z!=^0(;q~R_Tm$Ikgj5MU8P4V@GM@3%}vj?MrR&+F`yJfv9n z;w!gJP8N+X0wyVxnfx^X7x?<}`3N%lV1*W9j6?&~l3_}vHgXTi#MjVg*M-^y1mPsu zED2VG8mSHy)Ub#Bn4pQ}4l+uNITr_4`G%JQf)>~gaKtosPvUX-%=1)ubXL29GPEGu zdGR7*REvoL{u%8E&3_1+e-DZ>6Z4Iw?7p}lwToP1)9UTBUK2(H|9d`QUE)$$6Vn!? zX|#+9G{bZd!JQ>Me==R0`^Y{7Tj?FP`CY!2k+X=DE+32rr?6iVwI$!v$%rSh8hNe- zQ2$yoT2ySw48V_N5GtM}6d+sZ&FbwUZ6JB~SZ`MbW4LE0NLkc->Iu5LK{vc^d8teL zO*QG$S&akHmbW$rgZ~SvG2!LN{C8qH(a5zxf&A|cV5U+4?-aKIEM48rUMklm<5AZWx)IFt zHf8S9i=7b#{I*@^=amQ2##LVD$X~PWMq>P5j^O{H|TFQZ+aSWMU@Da<%kw;ui>2?8rV= zrd&b5XG>j}}V3EqzE3q@HiEi7@#PG~dMf?Vk666mAiK|ccm z)s5e`a4@^kcYA&7-W6ft8pnFxJFzPNreFEYl$o zjYCo-G!H)s?rhZs62H(rNQvu@Jakewezmm-Em8s$a^u+e`Do#NleHrqja!R1vZiL~ zFRIV4opp%KmoB9M$7WOnE<6MatUn%` zhrL6rdaOGAM>!3g;|Yz;jN(=W-WRgcwBc@{Mx(2KqaJV7#;RA!hX$(DuL7cuY+geF zS)^$sQE$-P(0lm3aSeu`dHcPen*{*`$!=>4QDSQZdS{y?_k~}vi|*3y@ouNW$odDe z8#rjtGn(m!)<4n*((tRrxPG5J$fur$Nz>E-F-a>rNwPFk|9i6rY1#GlTJ|_H8 zC&F;}I_ZI7lx#XhYa1_48xjolXu;+Is_#5^ZVHz*+CjXQ(j-=`>2W0$Z)Hz{*lM(5 z07jSrhQ?Xt7oI=L*PSRqE*QLU4}VOr_Hs9=-iYw0cokIwehr@6Lk4Q|9jTXpcq4v0 zqYNb6VrUO*^Mi!Nh0AjUxeoYoYcK<)l4n8SGYm1$?XDhI7cN2|i%}tHV9){sAcM&h z>n|sj6C0x+U;?UuiDVFsJ@sv%FMOqzk>eAn9m~X3;YS?;e1~5a8hYNNl^Y$$ z6TQ{3AaCSaQUy|p8f9dqSa>@H4aFTCMnInGwZO^OA8aeHEs!UJ@*2|H7VPqmpnGYW?_A?R>BWw_@N= z&0n!RD}BR$#40&aj6A6LN+cVD?QzEdylEW-+DE6f=2_p7E=$>r*Sz}48lY3`I1#Q3 zy(Xy4Hih*AI=eLlR8>M>LLQH#b2#DAkB*8JJomr@6O6ypGmB@b{z*@ zs4+9JV@7GJe5netleqY+ry&sr-N$8`sXWDQ8 z^nOV>R`})N@bxJmZ)Z$W(L;389SLu3XZS4m=S!Gyfz=0j@!}&=b9)+2L5=Z z8h!+k_kNVJ{6)SGqR>8$%+5>*z}_hYZv-S>&aYE`it#L&-~#pZ`Y%TEl|Z8sjRS;t zLoiwa0VP>Tqw`53GmwZq>4q;GjJH0c4(X5c`vjumeI!=|B9To4dB|&c)<)1DntLxh z_WL>^d@31DQ%>iZ$=J9WYArM`O0a7rh?5*|!~9b@s=oUlGWdfA=QuTV z2M1z5ym?Ha1};ntzjHzFFIA{Ra}O|3?PV|mie)*`#7g8TrGfvx!O zCq(jgZbv5uVstv0hN+-2bgLdbUFMss0s2Aw&9pT*xvfH{E%^4;J1zj`aF25^y!a$lLpx*LYB~LCHw~5iEl8tHK7? z`u5pVw~dHxzx0Kj(>CgiKqby+48DT`szCfB&|o$DviNsEo=CyVm612Zedqdrm!7Gg ziLCJjH?)pbhY69&L=Ed9p}+Oujpo2o<-P%(pdR zq7)_8RBCeq4Q*i1?wQHr{KYNSnAO(tnX2YY^h$Oj;9~|-$L-<+KNXPK?e{JU8H!2s z9z6BXLL0RzP>mMEMTg&^g$0%bSzw8$qmMP@a~oxi;)RPk#zNI{lOw=-r>nbvd%52R z27wqrH5^NCuT%{|qN3UiE0Dp?K{=bC1MKfGJFt8PoA<)CA$`+-^`V-dPb=9^+PlGd z25P9>K2e)%%-E;}n$o*kF*nRJO{`VdcUC{Qdl)r+i}-<*#GaSk>8GxC}Ep5RO7m=w|bJxG0aukfw;3QRzb zjKg9BjEhr>`L4=!5SAKA4w8&a#CU|_=)_^A{{Nh0hi0?}^6&yFOJMXSgH82+1;RJX zjw-HI6zhde96D1shMS%SlO!B`eODVF( zqPvT{DA!Xb1zqb0W0l}!lROY{ELZ(b%&NQrz_Q_osCOu7Z$tK4$zP=^&QD;E%T)T| zOi}N2tjQb)KssIa8!5dznCdW3~^cZRs)a{XAvSMJI!Yp2`yNzS~2RfwHYq~ zSQeW2grP#fFHSRO9vPNni7>eb?FPzPNd&mn9s6tg(Iq!`EH{y4-iIh~8IR zuE?3(;4HDFYvJnyqt{m>|ReK%>2AbZLY9{|U*lA*yen!ix zo{{N}?`YG1p*kDJ7d0GcHMVo$VvAP?C1&vtAYhw5MD*| zWT&^#<&Yc5a>Czv+@A@M%AdXl4^Zr+y4t@)hUQ8^qL{1rfWQ6;U7HN>6b6;QH71G$ zj8Os}?$c1{Uvbn$BO~qUjEd}pD$5YndCsm&RL3p_!uX0`cg2B|f~-oTQ`cdZ_~nJ) zDm)l58%GwJ97jvkN&)Gg-Wwl!A+yHwA3p9dXIBzUM zwgMU1@1gP;9> zC@QA{P+JSR(Fyal=^P6|^F1L;f9W+qfedM1wJ;;Qy{_d0+YHUNz-XCk?^)eF-ek(k zi_keK8M0<`pO1ncxX&~PxsZ}>adgKGW0K*@f!;w_q7iC@*AT#=n=}dKyv`m1UM-&c zG_gW}r_6poMf&4%bcadtMI6vYC|RETJM%mTv5m0PO7>I{*w~WjT>?r@LDx6~1!rZ` zL$avoe8&O@%q^?QaiFXtc=QWbM*!MtJyd1 zr;4%%1t}>jGKBcImD~Bl_2HT#MncqX%DF=-0}ygV+F)V>*o;vOe%+B=fi3E|o+NN% zzjEfH)@{2er;jtrY^ja|IIlhS6;sIpr-0?sDMmI2K?;OQZ$22JJK}9>J1U_9ZD`4? zQCQ*efhUH`1S|dk(0G1Fb_lDfv1P+AnpY~w_V*+PiP(STW7c||++1;2NSx>CW%I5w{?Inyd5lL^Z?xeC z?FUQ^LG3ntyeQ`D#aQjiJ$ zBQzriBazGLSj99TAJe~~QDYRb1OP`sxW7jDR>W>!K6iL|AO8b~19%AaQ~p*DI=08% zoYZzR0Mc+Mym(S7EW$w{9ZCok1GtJbDr<`&hobU*?UttGbS=$d*Hho%GlbyRNxrqi z0XsbXk^yxXna?wA#>C6_fI7qFrlNgJ5&d5NQ)%nc14NbTo*OYGV&$A=v-Mzv|M1KH zhe?eJdXpw%LH*X82G%yK*PF{&YLN_NfxIEl#UN8aesF21&g4egYq6%=06W=_`pf1u zZottbulO~1kT#v2Jwhl$uLOr5|ayg1IAXeYsz{~Q>NsMqE*pN0OMj`0-_PqT`hNvVgO$>Fh}x~ zJiQmgTRzO#+d##%*6pa`0O(2m6*?4J(3XvCBe3`Hw|Ss@)egvHskcMh&A$m%1_W6o zCHw`1wsLToa1%)xG_A>eiIhVCs>`Br2!~*Z1sif#c!Z8AnO!hWIll@#U!?eWh$N|m z&HgF#59&FoEtDT`8;@b3|eTDphe2lUaWAP^B`XpUhacjzqG9K@s`_ZSbuTjv)t zwpekZ0F^jzK?XzjiRxFAK5~WB|0ue&9@fg_^*kBOv<-iF0ZB88Fg31?^s6#f#v7I{ zDMt3)m>ugZ6)T3IUDQYe1Q?Tgb>ZNX7sBMgmv@8N;#sN4sizIwcdDWl{J6{o0ss8e zWt!Jn3&<}uyr4;FD8@NUA;&w=R0xl_?2vQ5l z5+&<*S3nQ&1TG5OM(U}m1^VX^LF_aOT&y91Si@-r&cr=e(cxnE13Gib!e#*e5&L|$ zn?b+!Rcq|`N}ySq`wc&lQ`_L~0dq0iU1PD^otMXn;pB?DqHwng=SC$GH$=;B*v2mA z1@lwHHW3+$61t^Rdw)h^`OVd!3|^$XSLN_h)c+vm2aQxIs~tA=XwhA`*0bhZty3O0 z4HbM7^I2|t`ZOuC0SnI&1D5oq72uJFASc*%M_ADue&8w>4bVRT^?G;*1>6m*zCQSd z*%k7kTf@z z3>wq60LHql62GUcGDBpW23}_gIgqO$X_c+W_0*Ek}q^9&A-)pMWO9-71 zZ}om$ydpV^yZi8fH?nQ;1U{$Ja>8kC>4f~=hZiM=86XVSlQ82az8lVx`m;-b2d!0> zXu9JPhnom4R{Gw*Yu-l8jP^cxm%C*bDil$r2RXCayz3IdH+z2Zb{d{F6)Ja_n@M&( ze4gEk(0}As0^TZFCfXqORWyA#%a|^;_JXG^ku8K}S>*5(!yQoL25Y@CfnfuO4s^3oP z0ks?V&i+nIL#+D%1+02JPTn-7Trtm>4?C+M6WUEG1!U17*G)9Kubaj}6bV@~CXIw{ z+S}VGb0sr_HfHju0dD@yGsU4k{65>&W^nzL!B-iHW5v;~O0@LrG=tC);#||N3ur*mX@Lx~if4Bnz*}fB} z_H1aP1DZX!Elh&(sA|w!ixL&D=i4h9Etwj>pi~fCpefW<2c&5LgIZ8bBlG`Z9&GPt zm8(ymM%A&pEgc)d|0Ttl17{%mk)SOVT$cJYQ3daz>22%PvexaFG7&n=3s%tE2K%(n zANb_|KZQmt+XD={5E_48o+hC_b9%LkyCjG`1XlWmWcqT|eo3G2PANNOZO#RCWtmO3 z+2m&RqJFV{0u0B9y4`=Lcr~7TZ19z3c!2CFL=7)9`3&$f$TSPL0$d(mMn1CGuVehC zzIO4rw;hU()Lhk4#<4gM65R(E0?;UkPXs%wg0wPd?_qO|K!hQN1bz1hpoijfzhSfm z1TR7Hnb^%@64p0GHL>e~Y3Z`4fEx^dGY=~KT6cEf31_9sFi?7Yp-^1p9}NfFTGHv6xzj$0?pry zNY}OKrIH||hj;DyTEV1&Jx5}nhtq?4X$bR>2h#k%GEP>%Gal_jTq;L{*6X}$iOSF+ za@{V1oaU2{10-y4MPJ4t{7uE)pX*NNr&s!E)AT1I9+@MT>@W5(1WTC(I*Sy8!mQ&K z7X>aj1pKi$cq?Kays4%@sm>a)0?1qs1Hr}*T}>1qWCOMqXOnfMO~Fv=)#}TzOPi-L z1wC+9Zj5B{%@nPFa%zx3xI!P9T4JZdC|qnL%iunp0kZaTOR5&DwA)bs^C8e_j6}Y} zO4bLG?3_?k^7Fg>2V4lC`TodTUYB{8vnqA@0wswXHMhOcZD%^b#ti_=1JA)curJWd znA!6w?c^q)$Ionr(VMuB_5b8DPO(1r1;~V7fLACs^*$jp*8nDVpV%cUt%~>V?DFP zz(#WOue%qO;BzE3@6=mu&5x_?dP@HW*KGOz1qiOXwun<64OAf3S&cO^4EOP&wYN&C z#$ISAmO&-T20p-{#m3e2nxB;e=Z1aGi{Qg}m5fdc{*0RS7SFDz1e?IEZO#?adHT@7 zSG@otSG81~@xaW<5y?>sI}kd{2F`JK&{23=<-uOKy;mrG_gQ^V%OqkdT_gWd*ctDz z155p=iDbAq`n&U`{VE_*RG7+FTH>|keOEd+!Y?@(2Qh>Uj#f{t6jWr3-ke_RhA?13#~!?vNa>POC<^_VriG@S__)ltX3rv>ntyh8EQU10o~Z zuSY6CA(&y;QjDzP3_EdXNss-Rjsx4I2x9xW0n;ih7znFu3i~65Bl7f*5mFfBQ|(UO zfLy~3JSgJt1cYYNKHjckx_joTmg-vN{fgxEpLN$-A%+Wmdm#F}1E=B!m1{QiObO@c zrpvU*eXK(s8dy}6%x{=uVo3Y00SO1`-={zkL#!Emu}9V<1{xQ(0iR{x7GwT9n`Q;_qmLSOEc1|;}-871@JxX)Bw(K z{B`YAx9N*+Tek7yQTnPy97IgHR&N|e0OC=_ z&El-ckn^EyBb13G9Uw~8TTh%=yv>-wH+(ZY1{wN8j^F^|P~$TkWDd;7+aM|2uI40( zDSu!U;guyC$ zyoX*na_SVNDO005rraQaf#pG%0&u^m8hn`i24-y;GRX>m9@@ER{!0ehYu^CH|5Uqy z0!PV71O0bUr)dMl%*dFSK%9SkIi`A@s?)KPdX{YJM6t#b1(#NA?I!0Q zM1iGEpKJec)(S`95l9-|74^@yn-sKR1A3B=JiZvWzZ2awn}Yx2{`>`c47PW3ojr6i z*b%(308s@d&%Zf_lEGc-j&0!bnK?rk`Zlw$4{6w)sGpAG0q}i4QzdJlvLc=)IlyLk zFC9mlsd%3fwTX)tsv6pP2NHL@*;}8*iU@=Os~cbH<`#K2@-0!D+X+Y+*Xhz60^TM3 zca#+`;MC!2*O04`$~-)A$}01Ib_Ya+PqFUn1()Nfk#7jo<6e}00Y@L_J-H^`<9JrZ zAPJk5tgu6?0jdTp=`ghqmv~8na)N!>TzZ~BXHV0eBSCgDM)`Tw0eW@xxPf&#^i$*W zz1Aqma#ZOC0lhwtM}am!j;`Rf02;%hFw6pSh$m=mzeOgEp0B~j*#0A=e1x&7kYa^5 z0=XnFL954QI6q<&L~LILLo>D;6-79#H2COz){G~x1%yYg;MMR*K-#wQ^LPr7)n8l! zPZ1W;qPsYcS6^Fs0=4h`-YnmJ_TWQl*3giGe~C5P4_A6>JcmcLh-%CC1L34d7<0}h z>zeoisIau}!ZY{tEeF|a0n0P-V>DEd1Ru`i<))H{dw21hqqqVvwYPxQNG_1ZLT}F# z7vw7Z23)l@UWeEzgFou+7&gsM=jga zjp4q$U_-C zRPl=*U|O_H1Q^p>KCJ#3!9`8&1hbW@eU&3t9W#Vm1}h{4^lMCY22YraRGCn#xekx1 zHP+o1uy$AQ-7SKjKxXfnbaKE|14}u7M0dNp2l_`?yiDh*5P!kD^bso8j5g0A)XW1w;r6gZ03n;j<#emKimZ zjLQA?P@I%Kox7OX11Sl1xKdpN{KEnR;VQXaOTYiMlW+?BH`-D7j0}vk0b1e@k{jo~ z+{2$W3-9*rp1CU~7NlLD{Lv(Pp@0nK0WpvKnj_{!U2*atcR@Z3DP`llJ3S~4F_#%S z7fS;W0}K~AxJ89F@hYo|;zvuK=0Y)MM0kUFJ?7Pci3e{C!Y46tzN$23M zh>4>f=1s}C0c;})-pvpoDDU<^H{Ep;bd(wjlw8)iR*S0Yk^NIe1~OsjO^+qXiaX7w zuIq_lB!4ujff>UoxuZ}EMV5cS1Tk@-^5dj6<6=D9k$@F05=e(8cL8}E586DH35{pT z0Tq}1lJqB*K;I@(8(*8@lrElm_vu2~RX`h{$#W1Q0I>^BfqS2UpLfAGake^HX0L~q z%C#gMz+>TtVu`#J27q}oaf{w=)9gjo*l$g5*rYD9@5ZF6;@qAHhM;=`0BOhg6n$V2 zO=;j_ITM4+Kk#1YfnqXaIJ2W~A$3Sc>#zyAZoOxEz$Wmjww&Dx6;AOUeE z6)sP|08&H}lr$)MhYWf>|4#X8$rfw8as z1xvIvEc%`2HfvYn`9`HLIsaeT5C9w#B<%z+163^!ElFn`B8VW34zzm z$_ENt9Jfi!0@Hm-;T4S3`_+@mK+^nTTHf_D4#y74;@PQXnVhp@=YiV+FVvZLsw)y4J z1BCc{kwBa1^aHr;v5 z`1JMbq5H9oNNxEF1mL|A+O{R)fF@fv>mft#nt0f!?=uIUcSu65bye4`1F$Qm)9Ypi z!=Xy%Z-)z%thKpdjd@$DtG?Xlk-}-g0WxFVxiZFvc>4t8n5ixet|rM4N%@}WkY@Q& z4HH==1wC(h8^s5y7J0vcgKrz5VBZ~rfNxO~P4x&r=PU&81C)ztGxyKBL$e+!2lQP> z-Qm(5w8jmB)7ozZr;fZH0u?gvv_+_t_8S#Y*F&1Avmzr;F?2uQ0f{g)aMv0e2iF;q zVgtUsLo@?@fwhE_WZju4hlLm(d^43v=E1{n2da^zCo4sZYJ@3tez-?|`5ND+gj=E= zPYH&;nJ4^%28cUgv3Reb0lca~2Z$^U+f`Z_bmEKf zd5S{rM7D-|UO;<{A7jQz9q~7{1+DK!i(SMIj{_ypVM3@ZIF3-ILwqhTLooGW(geQW z2O?dM!d`LC$HSJ%W#D0;GoAdse_#R9Q>kYNTyZxS$^G zGRT(QE(F{@$U#YP5N~HH zB$B=)O@RM6Py;uHZ?Pwq0^f}!uP#k#TA!jrdUj;+Nyc#;#D>R$)&7nVX5J&*0=g8} z&KW1K=0Y_(7v8Vqv3=OBS;z3aZn*4TbOW3Z0-3w5$lBdRSMgTyFJv9MDR7Md<;coP zc;r=wm!Rl91;y@AcQ|>8mvfx9<+#0YOJA?ZD}Yb|bN=irw>l>)0#_do-w7wcmm`zj z79u;FDJWt&5iT4vZLI5X(I^gt1F81#6mY%-&ObxyX34*KnlC*|B)svS1YTg7(zOvfhMnH=$KnWhW`0ZGYw8mTuj?jD`e-;)lq} z!!LTs40q!m{O84=0?O^uWst~h7noN{(PGmCg#!4^iNBXPElp!oxOu8D0N@!2Qf!Z{ zdj&dAUK$~^!$PtnMK6)+#i6-+Sb4Tc2hl2)O3Htt=hlLmkyZ~u`)V;U6;BE$t7DLK zJN0|&09h3k?*|r?8~iS&yt|%hLAs8F63cYb&-)>y?2(f!1lO+trHi?V-ZGKFt%=e? znyeTWWA>)h1X^XdMlP}f1(!{w(FL5zHvw8mF(O7*{+I2mJS|5^8$IkcL|Yqm0v+ke z*|IZbI2q+Szch*25bGih%}_93FT{vM5E>dx1CMdh>!*dRiar8^GX-~Kc`KYM_g?Lj zXf;n8OIw1|2L1vq4i@|Kr~P#KcPp~Di&@bTs0-^MpH4=`Q995a1It?8Np`&}tid-f zmi591#^MIq6BO3?udg|z+T<8>0a%68mlc@!;+h@qNaRTt3yGuvluw6GzsS@N53nay z10^6wpIxyp=Z0o=S!Tt$9=eBM0a;ebh})4JA`5QX2E(Add>%?%$Cg31XIYs>@9M=T zu1fiWa5-#=75V~K1^$U>$e-MMl*mn0pco_E_u*Yhe`}7XqkKIsH@~uF1o}FWrsoJP zqRRt}91Kl-hluJUN9oReXzFz9P8nq*1-Ri1nK*92AGng^kK3c=0X~fu9!L5>Wppg& zCG^#s000r%c=mG3`BKrmQ9N?y^%=^9Lz_k>z^9ybB3HzK1RrJnu=1y0@@DJ@=d$pb zwl~nQoa}zTI;qn{t>RfP1@Bj|FR{KjFb0J=w$}7-C%G;0qm5kcRYU@~g-$MC1UwMK zzHOknZFXU#3=BZM)kUVYpzJqbY(zFp#92#b2S0W09cT*P`)vD*vV^%2<3}DLk=FKR z>p7^jw+2+V0Nz#DJG+Cr1%GVq8|D{Kya~Oc`)*3alEW&5tgWJOA`$9cHBRMT~m^qYh(7fl&r0kBpk!?9g195v9TfK%eO`vQhN zL#Dx;r9;rT!oHwB0~W#C+oiUV)`|Fbk?F$bEQ!hE6&EtzgjQKf(Wa`l1<)YeR6~j; z2dY-*K`sQ5$^QS)(vCl%<#biO2m>nk07g5nz^S5J#?~`7k$3XtR|jQ>r?}CE26XsF zQTMcx0uA5v#}d0pAN&Pc|l<00?nz2A-JWGJ$mGUjm2r`ilIht>YI8*F*%?DBxSLh0Y*td zj}V<{X$}Ae4NVb=_P3UO1@TsO~hYW}sW2SbAa<$>Tq%SfRkDv&$r%j|mh*ajn)2ut6zt%W$;1=Xo> z3`a43H3+n*><3cE(_3|!*NFc)DGIm=ryn9{1EQ5{Z_08ddNDaHi7DX-xFnQmZeen1pZhw%94Z?d`>P# zDzGnfRm1ZHefGq}%P=GX|Do;%06`ecPKh5HuZMpqG#QZzcBhO>uW%NS1#ShuiI}W! z0Cuk_fAv~GJ8wKKuM9Asd2PVZyDub$Y*y70oIAEv0u($E8cEl3)qTO`9dnQfKK2RC z_gOKjt&TR@@;KB51kPPKcjg;|DxE+NB!s}E!Tk5?t_si};eq;b;9iiP1%h4Iga&5+ zKMv9v3ux|F7=YT{ce2&rhS=OzCisPaNlYk#$S5!q;8M`&CM!~ z=oUQ60*~-w9Fl4C5!WWBYwUO5XWeAGi2D$U_9k0yEkvnA0&OlqaQJNv5d=qUht#~f zR`%3lMqqu2)s08octH0B0;r`Xq!LANa2&!7Ax7IU2ReV&s@9ku0cOzY?syf91X691 zT}Cy}jbCY7qu_>Y%O8&lHlD)Z;?CS$jJ*r`0QVtW(1 z4{?B0iG3Q-m`104MN6AZV+7d<1I6So9k;r6jrt{-ASx&xH=!lCu=9aQaA^rP04{S2&roxnP(>(Pj(%HUj! z(~E&M1PB2b=re5Zm8&SAl03{vy@#`EPtVf5>!o;_mR+vc1`m*71MX$P7}`6gf<|nL zk4A}_?F%vY!S&D2G}F|71(Z~!QwF9c<;1MmpO}_Jyz?#Ak@POsm_g5bdG*x21g@O) z9X~5F_IXl2sPgN+luO~)l0~lhfeg10wBqTZnRDoqlEHP=D}^}$xG-G zB%tv$v{bh+nu2Qs0R)I^EhI$*BxOKsult-sK9oBf1sToFO);hu zRY9-Mxwu%8T^WuSOxCuUS3*BQ5g-XL?{ zgo^b315`2&%-*H7jw+{cID8;i?t})_MQ0><(7wj(F2@n$#`jPY>Zph*4!B zqh(2gm9l(sOGM!lP83YA2eivoTZ^|75#n=pX2ygA?^u#Fy6xUI#cVT^8XDkA2N^H2 zjN+WQaYwV&)Sk^|XBW#GCbQ%nukgWk`0A?V0U3>^7=2&E5&fx$af3$~`(&wlquek! zksuK|S}+Ur1ejx{2B67TWtJYQEWF01;<~CGG?p;jjPI)G@mw*=2l<-^5-Z4Iro9b( zH%P{Xm!#U}R9~Qy6BMc%Dj5xn1}{lJY}HLCjYW{j-Wr?RsW(}Zo7Qm?Ll+7L);}Vb z28&bHR!G(eY;yCpN6|Jjn%op{@@`+24N}@n0_T}W1ZGzDN;-3)4$hcqA3c&NcqQsV z2U^7@l%)8n-H(t91Z7{bc0^5+za|vaajO+t+g0)lKm7AM>O~AMb{M2|0&c`D&+j_4 z6g&C~E3RgUdr-W*t6=^qaNd_|yAp*20eyuEqt!qZg*vP&XX>=AjbxjkqC;u7uKh4P zH%$w#2NlG5+0Eu^UkMy{DrQq6h&m!y7!^qwAy&MMWur@e0CNYH zZXnF@);-dO%A*Qc!5x2cfgWPYAL#nl+QAKXSq2pTAiHCPHG^ z$Ri3b$lGz81{!_`FHMh!{XBW^6k+$CoPeh5M+R~ng+19qZi&R90V~MO!V1zP26G+1 zcZmmB((!1Lhaanv!kbwa2+ivW1J(QjOZi~+PGua7=RSRmecd63E&y<(X;4~8d?%ba z1%i1cqt#i@Ky>$n40%>p1sIE6^U-$=l!^lsMh}0t1im7TyyTX1Q6c^`r;|D#_3a%+ zS6I8W->|eDXPDO32CF`)ir(EOoP{F9X^>7(431>w+&qW=U^v)SnNN|J1-p@%t%7}< zdPtUPE_Z+A)vk|v$ZX~`WUi-dwt1o#0A1Fo%=K`;9L}_&UF|{j`t3i2u{sf>PE|3@ z^pgF>1@-Y6eSI9K#xsK@l~CJz)W(?G+5WYMbV&6{3mn5V0t0(V=Z0Edlth2`jMCzh zbkIaFh25@i;X@#>0b;l=0CmGf;gJBy?qZs9HyX2MU=x9%j>WJ}v!k$Fj_DTW1vq%>#+gQlvmo?ko>TA~VA z-FFC>Nqs&G1W^tDk`s^ zcCLENq{rLUt8p$vKc{QsGZbU311FS67Z3zV4Bmuy50Nsg2k9cmD=d+sr zB_lMR7MGoT)sc7}>}%01D(bOR13H|zdYh%{w_09I(O;C1HoY@{IV={2thf!~H6;4C z1lW+d3+3Kbw~Pk~XiC;+6x2he4pU)?_^2mknPR561$Ztev%i83Ra>)!sgw%NJ&nIp zR~B3pPJi$#ViI1!2RC-y*)z4|!@0_&mK!y92l8B`3+gWXUfg;6Z!Q{F1~vG&oua1= zP;@S30slKiv&z5$rL*=6DRC=LOCqqM<8KK^4$WA}K-%=O2Zq;cfN^tZ%du(Yxhq1T zdW2y`<<~s*a5&YKkrCsx0Vyv&ic$-~^Tk__k>Mw>b{xIP!6$1YRJ*PBx(3kK;KM0IV!0T(vN1-9n2!030bu7pTeJO*RiV!~w?y}`+Y z004#%u~67@jTmFy0Y#S|mS&1E;tFz`Uc0fHJ9jyC0W2)h5@6;!=fq#ZF=TK{zZpe;Wqu{dC% zgDgNu1+Hng^1*9Zg;p&xfV=jg`-cx>?v~1Q?X5ZzKZ;ZAaXDCVU8_tlS~x<=}wyMI6Q)n2Px`V6|Y&sH$D!~-cVbWxzsuT+OpUw^YT2vf-lM31K|{1 z`iC(%a~6UGxA_hdS459qooK>?`7Fg0oy%Mq_;n)X3iW5$>PZ=bNGFg_$PY10-j|Bx7&DR9!^kR ze11;4N}a&|H%O~r)r{}mZussY1Yeu@lcNFDmJ986^gi9-lV3pczbPukY_i!M3!IVO z0{k(qxSy;RJX4N3_`HeXgogrc&&PSzngYnF@F|v}0hn>V}Is>2Av7)^mIPX*5teT6#I0{u~K;JRP1UiK|Q8~xw z;l$IXY1}3E5oGw(kjl-z`m&?0%-H6h1qz@Q8sc5)`5A;no0=4ew{q^Rror}+qK88zW-Qq1p^-F z0q@tcUYXYp|1RaCnyluz>4QmgZTiElqy(6h0$mqH(^8(mR=30HZ5O*A{mV+!aowAN zuT&BCB(jG-0=%GEH6sVX@;ny-jBM7wkdLG_Hwp!^C&J(qsHr=K1#NGg6KFIMZM4M+ zny^L03<92T#A~a|i&!=dH$sp_1()mZr~O|VXmQ?xIV<)HVLBX4AGRvV_t98?P8#p% z1Oo1TB1Xj*F0Pm&Zn2TvUoUp{r&=`a@xwt$ zQ;xV;dFlAXGu9_=04$nNKhW~(vYWcnec7#NkHH?KfjMhH21iBQ+rv1r z08AyNtmH*ih%zS+b?#=#NmK?4-u8oW=H@1@w|fQ(1iYggMFC!Ss(l@1Wr9ssb7o}5 z@Te^gDkH#R`8Z?e8isb1*b>_ZTO}&8PZfhdd#%0fEo3aHKg# zXy9&Weo_7R=u9ac>Kc>Dx&u1s9Q1stEWtxHG&vr_i@!JW-$)Uun3_FurXT}R)Sg9}?*2E4l@ z16$7m(jomSw}=>ogZOB6P1^@@(}c!T853->0_&4?C=!#UXit!?2$Md?DiT>~vuyY| z2dA*t?;lm<2Q8?fur#50q7PLBRi5&6zxhlqF>#D-Q|VA=B9jD;0P3{mMQIrL!95u* zTPOg#c!rAbov|U9!vMQ>`b`9Z1(5%P_{V;u#LY6ntj1UX zC#^18m!$IcAvwr(0DISydCj7;8QS#rvhB*mXcqS$y#v@|DiRDW{n8AX02dRr_lsV- z?~aQ4Ag8sA=c}}}^2s%s(*Zy+WbA4&0%O_p&aV0lQRf@%A-{{x9wgzZ9AB7WM)(l6 z@i8aV2iP5tgv4CNc0|ja$49 zmny75qpg3_7zpbP1W+iQh^CljeT!}d*gWlK0Kxo;aD7ft{^nLM-g?%?A)m^2mB)v7 zD<9fLs?zke0WRcgI%1qm%GLhq$*Cjj5Ihuq)_+fY7_`X+c%8`30BzQEkRLOweLPMn zn~5~tX|&!l1cXU%$a^CK88F%lZL2jiFkL+61GE^d zWSnqCn&fr1Hzec-&{2C23VZ*{bxKh(vK>M<08U%`&(S6tGIH8>S}NioBBn3~QC#rm zVkiV6<8gf@2M{W)Wxq2QHI6Cy`m!0q<9_V_nZgf0KEXFg7TAkj1>;Z3XAa=%NG&L+ z!}UtsvQu`d@j!KMK7TS)AKRqz1?6e-Md5a#m9fe$rXk;|QsSfAdJ=@-d}5YST9mAc z0)KcQ-xVQ-7W}>k-V5`a=s;qF9)I)0TC!T>u2th z;v}U~0nFPGLR#imR-Y`BT3fec9Rkc@;b9QzcO#S%UtI^-2bqSxE$FpFIh4>UG_lHw zPpbT8Z(>AiN>4UxZYnMY1y_lXsDg6})1Cv>kWq!$z@3*Kg{22<2wHTU5b{;71oJh> z!N3+pkfQ5?`%xgHsZ6+e=U?4vg^L^G@t06rHH^O+s8d%h0UwOi5P!sLzI4w{fr z|L8_HrNfF00+;1@wD!F@2Ykare#)~<@a`F1p{w@pzP#BZ?Ga)H2U^y%^8QnIl3-p5 zU6w}eOT*}1uzPXxdnklwiRl2q2QIRx6R#hVxU0caR*SDZiNWd6c=|apE#|##Qr$b6*!7*jyps)+TKOp?9^Y&=a!dY^N(gM z7NjuO0)CUfY8tU}usIK$2@1`FO0+y99{LPTrH&v(YTtbG)0Ea#3r>UyZsH;Lo1U9+m z5^=|`ho54nkt0=a@&<$`gC zIfIB@Op5xZVSE68RqS}JIssZH-$@2_24+Qqi0%2yVd4eVr?JNL<=o;SP1s|A2>rm~^8C0@tZ|EeWvLkbqf2 z+P#!fowDYfOCE)qvDaI{99OVG0!0B*piVMi%8u(4Iv0jD0jC8xXM_Jfk1Ye;3pMFk z1KdKcIk~A}Oij^Xbpo+BkSHZ=Rtpc3eP823WJq<_un%K+9Uj@AhDtTkn=0U$hcx2|07pX{Mk5<97BD`oJE6%g7j>D81g zs;!&y0UaDF8w`G`DwJ=I>TyZ@y{e$(6NYA1s{9F<{ub$t2P2WBi&(OghDEoz&z2qb zAxR8#N`b=Chc&ZThop~f0X*l1U?lZQ$m$G&-M)w3O~e`-T@Kpk+) zG)c{5!yc%V)1|!UkK$)rAo0WJ6Rz-N1u030b)A+bU8DR(9k^;u&X_cQl|b3tqXlR3 z@R=eY1a~%vDapkbg1==Z)D%apV>uR_nAbjaN>fSQEJh&|2M3nvs^`WI-UwOU9It}0 zk!#56ZpbC?_-)6e>*#3}1mg^Pj9L%6p!Pkp+8qGr!Ds-C;-re-5%7J`WZEka0vzr) zJUe2{EM)A80`0)yy;f?pZ2{H1H}g4LXxkgo01$5XKqzB; zBm4F=dI~{6rU=w=N;K=K7R7lo1|($HMKuQtip>P3`d^RGrjV&?Lc0N5Vmnev*hW4@ z0k=5#FkrV@9<${t23|igqOz9|=o}TDNCV*jffR{908IXBJ-4R-K&~b{%&*@Z%xYrl zyQg;!qZ9Z8;^p;t1-W$e)*Wx7aj4X=Jdt3a@(^1HTqymQ^7#b9_RjRo29<7aix!O9 z+-2rUmIlUv@9Dou&onSgW1I?swCr4n1Y!mQ&$JIj7DfI|^e=~sSN#&>Qjnl&E zgkaL71Gn6XU@UtOM-%3)j+EZIBTn3^$fW04x(c(Ffi-6g29V|qjijML;><%~b{BxB zpu|rQ3XVp2$S^}HzdZ390gj9$bPW-I6Ls!5qAwiz4B#9v*3zq~W=aNdV;|HJ12k*L z&P~#XOEpgM6@VFh7-;q(O;mz47+pE+qpSrr1Zob7&(b3GOLaV<;S1I$L6hM#kIIC3 z)O2O6A-MB+2dykPrwLPEDA9fN12BT4IJqDJ&F+K(B;HqAH*uqn0ASIKQTIPdHAG8( z;UrG(IwaAOA?{MAsj>VZ%i|&41ij9WEGQk&#TdsU#7L6w5~)?^qF>1Ti^=P4pX?#2 z17x8(tHAl^2d(r8`K!TXnaVu=O0fOahG5N_7-V0;0cv)$1bPe4t_UN(gjb-YQ}drE zn^6R%eOO)xbWnu+239y~Zy7PLVl7!p>fSHpMzCypO0KD zRLx)20I+wi<#H0C7M)BIPt;SW;&Sv)8(UUK=l15x`958Q2dsaw#gLNct`BHuh^Fii zQu}ZJp6=5`FgrKyz6Iyf2YJW&$&Mi>SL0bg7O-_tVBxaHO+(T^@P+MVrEb#C1ZR7Y z)dvJE1JiG1z!r=J;~4ts!R`Fg%)qpJ$JyD40UaTW5pqFHX)rU}Ud+3tQ+3H)hh)Ta zT>0#xHmyWZ2l>`@o{x@2{^thv-t{LGg80uCp);@$ZDb{Ha^4)j2X|<*M2~O57sRsx z9Xl^kqvUPi&gWUs%jZ1BQwrZr1S^yS)sB8{sqn#Dw6G^m#GV0BdS5aIJCMp0^Eaoh z0oQ~K9`R*ML*)Sw-0}q*Np$>`$%V)>1&@O>1|HZH0itD@-w^y{k3d;I4fF(;SnrzC z4=Z?o0qS9lVh*tI0||*}D(jjkFSgDMeo?5*ez}0(0PB52q{k|6v6knS03tejYQebh zYENfpxecyiSuEC0C`r*_LM?z!os`XKV?bm;{+JJ6Uj&JBTe(%S!ErJOagr`H2lQCZ!dPwG#J@1o*>n=w{;e9 zakv^nk_BrAdd$OvmVj3AR&3|P^i!;2dF>w#EJSwmw!bcWPX>~xDne0kFj!RW7vG3> z=Hq{1CRW&63I%$u971FI(E?Bvn{HOs&)L0c375#8uAmX9@b<%4?AZfYa2j_P@CU!| z)CgVvGc%n)O_(}I9J?PB^|j_|5EKU>JaZBnM+FQnGl=ln!0HYNy}FJYm5Va=qlJ9H zY=BrSH$W%kJOf*aixBk5IZ$RxoyvOo6_^Y)@nTEnGO3JOnb&OV!vP0Zs&iqRo^&Fc zKA`JUd3AeGNd()dIo_%M@Xtg{d;+3RARK)WVDErh7R-9xcR@v7Wi|@jXMzVE9MQHE{U_t~Ie+C&lfahb) z95HQ*$TYQTO_H4X{+Z4aV|dr7or3Vzg#g$wJ5-P!e&Y|5cRO)3#^+gNAgTNP9#u0> z>*N%AxCiE$;-654&*cIz3FnYP4s$V2T4{_dn&*)ued%s|>jW{nNWMobUeleTT?5Df zuwvWT9^2AVxfEr~>{)k?9R*P9R(*h}z=ji}J2orBczB`NvC%G?Pt_E8Y0)~GjRAw1 zbJ)n9@fCR>!>U3xoLWeHSy6Uff8PD>qSi6pkOPi&6GQkxp>iAj#AWxK9MRjG-{F+J z3UynnlR{T}76T@zv*Kl3fuU~x#g4#JMptwic0F%_57U>n7caqhk_Ha2-dB?K}+(l8nH`@9kU)|wKV zE4RtPXyO<$Dmno)MS@0WDFSRU$9Luqk_qxnOM%Ac3IYd@1t$adb#q1fa3f^iD+AFG zN4oYA#Wl$|Jpg}m-|oKU-90!968PsXuQhYGKn6Sw@__$O-YON<{oja94Nij*LN$2j zR&W2_2KP!?C;(mC%#@2ue!h?V<||x9Y4DddgWJ}AGB5_&3?U?DTLAvKI2)sm)i2T- zt==^Bg8#V=C?r`;7ZJhuBfAb4qXNLllvjNGY+Gahh;<8y4{7oD+E&;2OCj@-02LwH zwgNpKI%r1}H-|uVrdS~Ea&=)3r?m)>b2M;RajV=O3;>JqzL?I|l#y+9_b1FsOtU{3y})NQXa%V7xor?}^$oyfrP;(|X9Xqf2N1dZxht`y zNSaeY8v-Bs1P0<1zt#DU9NmN(ofgo6y}}3hDY9_J71>&rasfqTH>YA#D)#59(&^a=}0cpT`@hY8`ZOeC@ildFbl*#Z=er7c35rzV=i zfjgL3GbnyUO(Dh0Sj=+;Ff6}+zXo>u1UTv`v7C@@`sm0b77a315g}3%oDAXNO$rS6 z;Rn~|D#D#XQ1>vP!4Q=|K>bJVlf_riyZ}%>KDJIGR0XlD$N-1r0J-mx2;gnA>|n{^ zPe!1c)N`QXy+*gi769k{8v_d#rvb%hrryu$Z%h;-SFF#Z+=V>H-+u6=_Xad)6~qQ| zG)WsLS;xCA#Bi@~E!BGvAT!b94R%L8MFniLb1%0G;kdF_k`Uq(6 zKHx!Ot_7An$=buvD>VT>)ve4T!Res;gE9gcwNczW(&j)FOoDJ<`{k-`=w@&yn&fdf9Ovr2>cNfjNkmSp3mWBVsLHVM0p(qXK|lK^6f zHX=(_LbnT>pxnkf6eh0O?qNpmbP{NUK4=cXbpQe5D%XV|rYDW@+qGcEBPl2aLy9c& z4(`0@hxY1RHvuaV1+goYsD%wh4#Rli^uc@B2;1HN=**agZD=~KsscXzv1>ROP_O4i zV&W6lN&5&&AUI5D~mOumI*}>TA?vEa7Idr2d-aG z%><@JXC0>7%rmVPux##-T2CuIQ9e1aS_e ziywZ}xqf=?u6NCngdX+aAXGF2Yz?3@Lj$e!3M9&unNylMC@tJqE}s zJNlht&E*520^e{^-UmU_7dv0FwE21yt@-xo|iRDF7<++RGGD^|j58 zas)ZXX|Akd(K{pITnZ09V4vXtSIU4na`Qv~2xjwmWm7K`A(5!$b_?(wzx!FP_s;`ngmndZx65eIcAq&T~3 zi+h6DpmRAgG%z>VIYTbOyQU?mTybDf6FsX=A_g--vV!9(P4Z2`y10`)`pt; z$VC&s-Jl8N`2WHp`6sEMp#?#Lp5ZuKZN;(NJKzfmnM)OihgE*H5ku1?scU^E*8&at z8sL|PCyGE3tT0^ezf$hLc_%`dKg3YWec#>vkpN2xCpEfKA9UjyOKLVB3;Kr!+LqQ%To;w$J8cPQjVa${qJ7ymD4P_o+jD*^Nm4}nJbmQ@P_ zfCWr9-MU(+d*U>))KT;PQ_=XS)&$RKrV$&Un!*vj%Unv3XW4b*21}HeXn|3JP+eli zx&cvVpkh6_2HJOxOnpFK4|3jTqioD!%fm2!K`7m!4*`OKpfl82Czplclq|r3|A*SO ztQWLeEtlT-7?sab&<9hgK={+^tGaN8c8)FbHUhOKTgY=DM$c-1X#}6H~a0 z`Z(z#PyoArOXiRij=s6lJ=f)E6b72PP4T^@8MuewYp0#01p_KEZ+CuM*z#v``m@sO zU;dHaB5(J1i_vE< z=^>E^v59V}a*USg{5{CiFI}HRi~wTJ{@38&IpaM2P{c#L-5$=ym`6 zpaImwCcgoOSnrM`{c>pOk{S}aFqVihdwASv;M_k3Lk=07-&R}8U}anpO)P?LNGPs z=9nMKoji83>o>u@juP`7(gL-2bEKNc^E$Sa)>;e?=yH&J4%uU8>gJqX^8%5B;0NAP zKFgBDLnp8F=_6BB3L(K)i*=7SOy>G!aqJ7&EC9j3d>%b?izI-bpnb+-Sb3^Kq)Gpb zT`)(JdRmCx{Qw5P7I`nHfB7G%JN7Y(d19WegEuS}8~Lm6My$F8ZU8IRc48zhRe{BL z+wk^CcEhJ&>8MibncsYrH`aM`X9QZnUJ@ZLEYY{Kq>eWuOZQrrUB47V5$)QVueW^Z zLIp|zCUGjA(fvlctb6byT!#hJFbp&P*hhxch71i>fCN{5!a-#ywWKfiwuSVeqVD^O zhj-9C@7gy1i))R>!3C_`qIhm|soIn~mh6a?zYRpUPooSKatbJNO;Rgjsl&2$H0UxRF6#~;3UV2OS(g#dyG z5hyf=dPwwxHrBK&tM}A8$)^aNE98O>&Y*%%tO8zPep-eav9`iA484My-cKEssZ9Ti;u{bQYV$C z36?nfCqm_pG7S{iJE!(&yRCIsw|J>(o+CO*r ztpdgM|FibVbvGl*bU)VY-1p$;E7DzH^Z}>!u+-KkDh1U%5brfYOn4Z8OK7Z%yVu7X z`pNumUixY6N|a>^sRrYPT?*Z*v!|6Kec|M7G}J3IB<_XWTkWrB zOqTusD+3?xwnLk;&^J_MYFT1`sz0baXWS18F0di_j^|CU0|4w0YWQ&aKI{lA5ZsI{ z*UI^M-XDfDSric0Tys4U7YD2b#Ku;yl)TKH4AK~Ui(&Wb!OMw2^e0fP(jExh z^4d!qf3KEescQ(1-#9@MnzAcmmJ$(j23*E-+BHY$kS~ph?|Bf>8um(!Y^cA{QQfgaW&VEqMx@6EWk@Rt)n85qXIRs-jZOq94_S}H>!3R&I|YGZvIFqDCh5X?pE`g zRR;wbK%g=t4xM9;K&(uRwQ84`IvkeNp2`*3sP^^O9tIALu5`xJ>ryv1OMfVu{35JH zEe(WLrQg2rWr@~uAqFgTNxP_vA39r@EwCr!5k}`qH|JOVgku8D4s{)QPXHZv!XPtZ z{eI4bQ0bl%wEc>=W-7Jt8JWl$>AdLBe*$gY6(_Ehc?;!w`Gs}&)9g9x%k6zGpA+a@ zsf9eKss-q)WlJ?1Qoxlt{TJwf1>3O4alD#u!r{+?$3viCdIE~r*JNGSS^fwQD=9A~ zLU^O%_QV(m(@lUfZD>=^l>_WdLP$zT`pwS2^9rs7c^q59=1j$x39!2xC+4<7HU_CK z=FbE%7qjFdFcIg4@-Yb(284e-AqLKJ?q{+p1O)&U698#b58BcpWq!Sg^9M6!_-+eZ z0oc_k^A=ua(*x`yMka;+?R5DqNUapxFiQ)o#${1#@$?5Fz_8rB&;_G{e%_RQ<(j18 zo$wj^D^|O=KSg4r=qFQ#Drx^QgasOgPeKw;2~Y73^OsziuvfgbB*njBoOa3+B!oE= zDh2A1wQhlZZsqtl8+AMGHoYy0Eg^9Vx0~iC?s&rv=>Wbv=NNHXT%S?W|FBTq*9$t^ zUrGor!47y;>328zh5^?+R!tH&m&IP#*Ju8$5*@7zVZG1e=s8--l0=eB^#sS$^X^A* z`==bf6V!FQ)36b%Eg2`m&n?H{FG$fI903_ICd?V02ThkI=;BuHOGtxYRI3lP%De$P zN$%d>DgcNIc;D*Vl)bIl{-wVUW;LmAh7djHCCANhW*qd2_X1(*v7WaD)t2UHO%do| zymg!C5~WlOsN9Gh73_GOg$40tB=}dtD#-ln{?ms&kdhQ7gt(#lD|Nth3w@ z!&%%UsK@ns@&H?b(A``Asp+c8Dd_n;CIG>zGn^k6?v9G> zCt7P&ct|mawu`vv>Z95o2_qgt@C3=P3K+YxQKsBUd2B3HT8i~r9^a)KV#kGqML?3= zy#t=i{*u5g%6m!()}7E|VsLja+T>-jz0v#cpF}nA76C(*L``Xe)hw*QvJ{l@0!ia| zVqWo~{dfn<7~}??^8&}_xV019YBXFriR}xydE7yDw~uyWhaPg~Y5&TmNC!hmm3KlV zO{OORk{h?U4iMmQU+{zD{HzJESWfqVdI9!uCVwI^4$$rZGRgoLPP*w|T;m?S_Ay{` zk7yE)HwFpgCQIXsONjs4K(UVG{-D0M~w?btEk3vrUT>&-B#?ZF4hrud6nnNf= zo$sr3DMotYI4I6M)`Yh};0Ad@hyVQ`r6z`{p5E5RUE&tyDmkc~#gSBt7g+p}ya#n> z6|HGodR9>bST%L%8EgIvsq%(y#=y#@Hv!?o@!#qBE;!Pa{_aPtPhp$cc7cXJ zx(y`49-D;QD+bA``Z5T*;Yf6;pVHwSk8OsKY;vtn@<4jLHc1^)8UXd>U@`uAb=4`z zEMuf`Kv!4Bo-df_nMN)Q?1ieB(gMxw`)A##?8u*Wlh*dmY5`~v%sX`j(twe1=9F@? zNCH_>4&ESPMwV)1r8P_G6$xMn$zQsyFCvhOrf&Z<&<4QKH6`}#2rTodAwwFM8^o5FzY zPr@;)K4Z%uQVaa1K5uJEPL4wpmG+^#!Ueo+(5f{T1d?^$~WhEF#Cc87Y3`BaYI`t(TOX{V+||z zcCtQqH^R|ICT5E4yl_w>Oavj$gc~!2zcl+85O0<0setbYVJn2Ap7{w35SSc+djSle zCjK2mU5(^#g_b&}TJv;mrkeCQi2+CKUH6!(d;{Da8OdaUXWe=3z{h-ZPp2AAw|7am zwwBb>!}nke(E!03sh6O(JyCS_-*hXUJ~J)m`HxSR+j5 z-v)kTo6Eyyz+G&wlr5_4F*y8n#0o&@@4b0wu67JQi3h5c@u!*;Kd`M0-4RWYA(GtV z@nIiwGIX2o^QcNZ3Iwe!%;c)+FR`i_NvZ}AuBL*Yy|8p_>%inwDRI&bIR}vuqWfqw zdpL`=hlXi3hKIYSzk5{iA*&OcMNUo6J;Q4BOQohNsUG zq(BLJj09ebTEy-wIjryb&HaVR!Z?;u@RD%Bi0U@=>t4}R%0voPU8v$gnHgM8hU_&&Ir#PkFr*D5LjF)^O zaI_8r2AqifP6lO4buR9sLi_CrNInu{`Vx$|&YAh5i9lQ6lhM}oO$U31C|+{z2LA6E zk;?7(b{y$!KGG;sGZpwexl%tP5e9|*t-g@L`B3pGV^|gTK5vgWN?D|dJp)BW{jz6N zss~t|)1SsMK8bbE|C@#*@79#Um$Ac)vT(PjSB;`3DFQl1Epo8iHIsvCUZWNwSVf&S z8M78~Cz?vLPGy)ozX4DugANg~KeA6gi?9}a=ytrn-{U_p$#Y^^^EjROI$+Y-GHWZzwx3QjFaRmICmI=f;8e|L z=Yh(YO7m&~+=2&ZfFK9%|7-c7Ljjm?*6N?f(mlajqV%{$wk?_XgCc2FXt!V7%%KfVb9HYU`n@ zux&c2n}SW`h!wyAumFjOlq=Pw$F~(BkEK#(QTSTMEYLyJg^rC<^EqC@8304+)z1nt zqqq7@Mwtrj#lrl|wBZij7!@aMfLE_6LI6Ezmrx!Z>bg*q^1Q02>Lm5$DEl{6%m;*? z!kdt9qXr|M*CAa#f)nqLn!`U-UVh!*&@Vd%iS`v4I2jzUasoJTr~j3B-fClO?}p3w zzh&{Y@<7X9eJP*g6%RAc6abTa!4%f-l^||(D!0-lam=v{dw7B2?;vz=^Ulf%O;Mtn8d9PQp6cmL$&Ck_Fkm7|)^A+EHIg zYILgLmPnxK@7(x6f8x|H(w5{~bO8-jHF}4Rn6BCG`r}sZ>TLYdBNlL3+#@KuVM;w&>hk^%l@YOo%+09$!00*C+|srFx&P`r8;P4VtGHrxHlA_nzVf9+mh zB6c};xKUa6F+Hw@kBflWX3Bgkg(`}R%m9T9{3Qv7)f3juH23KOW0;EggB>#ZiGGxl z71h4rDh7j(9F=S9-%_4Oy$6Mm?2&3bRy;}JQeWC>4hSocW&u|w%XfdokRNctaRkza zh+-?Fk-Pfv%8;B#wO$VJN_WS4M1KKtT! z$b5*Ainwo@CNV_A-~a`MtaBN)L-I7B+U|U*q_)Kj7Nv6tKQEd$j;YGmf`&?5ZJ((S&NtIMeDrJLQ=C-_2dMH|(x z+W>mh7VC!rjf;Cv>8*@%~^>Q$Km) z?aC%qpsjdVq<+Kk>j8yrdkV-wJX32VJ8E_6CX)T5@)hUNwm0sUAs)0LjR&o`tb*l= z|F?fc)ny!7R=;I>wYeMm?EF5IGw3gIJq6*}D3+5|eq|pO7?^SXZ+MDNB{rEZ$PwEb zooz?BTLrKgFXuK>F%t5*44fEz-ov8O1GBE86b}a99Vgo}gO1>RkI(2LshlVWjYw6Em-fp0$RY&)nUf-A2FOz=}#svtjQQK>{m2 zlH7k*i&}|5GZlp1eBf#<12hkoM&5yG zs(~uC)ci>rZw6E)+905o#j7w(lPVAm+J3mZszE3ltJ@>&a6MfEfdsoK1PkCU=<*KE zkT(GBaFOQ8(ggC`V|QXVYq2vMZU406+bkxV3&!6|5ZXbkhBlaR*T>VD`-! zTaJb~q7PKrX64?hbk)a$re`QDBBJz;4+ZUt;0inT>MZyIipG#^+TSanR*+i7T__;s z>6e9W!UG=x!eml;E`*hxFzDtI=CaoLT>!P9od-_l_19bwKJ2JE z^c#ns>lgNe##06yRzipBxB$^>CPnN1$O*`L$3R4m3~32ze~A-+v1iOWQXgoCo(5Ng zCF&&KM)#;vCdf+Hqfi{2R&B}2MUN61Lq?NoI0h3NV&N)4+Uk>CVE9OeL*!@{jZZFg zqxxO1j@eJSMgz~=AAz^tL^Z_Vb&JAhwUJ>J(gs$H6Dg?7AP1~2$pV#2upfig1M`o+ z0iP|m5tO$u!azT6UImjm4t3nG0tR}j^i_?(a=1}wr|web9>4-_EN7>0qs8!k5}8$A z#{uR{6{&T;2D^v=rExhXz0+ch{=87Up|5(Qya8*^R{_*0)T|QI#?+2P8hL%)P`vws z=RTAnF;tgm3mP^tsIRti@lj9Le)1U=PF4M}EA|2l@Ibz0r6DgsSGc;)yA zYujDJ{{+cfqZIRnX^h`YUVmVOeP^bsz5}C4)$VM#c992^1zfM;JmiN7zl1&BhrM!O>1eDHoR0HqH(63!P!l{tU5!vzD5VmqtpR0F za{wsUG{aSl^dby^>ykDVzZrVgB%mvCYr__SsRbNjvf=`zK8`-)enz)NCJcg`3lwq< z$t@Nu%Xa?IRR<|cv)7DlF3SQ;pgwpPMR1e!QmdXdi0AQd*zxFHB?bjFZQ4ioWMO_c zlDiG1vWlCE%I0=fGk(SAI9c=YbO5OCW6KqU^0_^req5ciR=kv5s1?K}LA`Oe_vk3O zI|mYmSASRYm!T00g->vkh^#FOX3qw*4tNtPPQ)C*H+h4gg zFmMq>&=1A7^!f!E=>zXjJ3=$Ucot2b@wE>kO3SvLw}kg2dZ^K}TH~yrbOc{ZtL2QQ zK;Ne>d>4Mf+R3SPF7w0^0O@R=$L#4|`T*kM!m%6C>LieyiWUDBkOsE2g)iYZx?P04IRM_J zB9TTZ#qWEbytfYsz)rPa;skFAO*LL2gKf;p;PUi6*5*2~9S_{zd%=Onnc5vvYXgj$ zSNUm@`!O5-C=L$9^V76XZ$io3vW%Tbsa9{FCHkQSSJmEgwWvb zjR0OUa6vS%bwt9cws}5^w4|(QJZ>as2ChhjA17jfwgqkvnHp;+gd4$hFV(WGXNnh z(Bjp7jRH5mvMRgHQg*`uf(pM=a!b$$u+*x=8H%~%2BduY~#(E;GU zWnaTn5EQy(O^{NU-9{j4g$L_Fr9-mKgRtfcBF~AN5G;UcKR8IuZaRUd%ZQgU$pZSR z_Ij7UZw@V3R*8%JJSrvr!<=9DVOE0To8O8u#0G*L*C2Ry0_HH-%T^CLm{R;A$`hQU zSsJ^{{we0X}W}y_Pe+x5OXF?uXFE@NP~Z!vS-CuMdR0DgqK%3z(OULxdz}_F{CwRJ|pvgNy*A;OxWH$O3WN^%=#^p;=+&A|q^q<+kPzAXT5YMGb>z z7C`4?3j&lYLO!5%YMS(%6%lRY#r;XY^M(TR(ZVRuw$U%JGXg8FpM4dIKpFOFa*pSG zdo95q512EwyuCp3f6(o&`2$y4?15l1{8c!SncOG2@d`A z-Jg)=20MnVTLUiTB3oXXH^ow%#?8K4*~<(_A7#B{mJ1<%9kt}U433U)L!BZu_ghNCS}zu z5C9!|^-sgQKaFrqe=n75%=))=DkQ&SQC&MW8abMzj09hA&NsisH=iF_i$$@p_?G^& zrf_CETJ(VUb&!JYqXc7b)I7@06XXYa82*diM`fR*SN-=(333~*EYlsRS_6Cw!>6G} zcK0-CVD0oW013IWHThP6T%)uryip2@dIX-wo^|!T_Xv5}Whvxabkn z7k1uib_7t$!IWd{sg zm7b!b^*S)gkvm&WTF0Ml3_!a))jvKY#W$yDq6DDLt&I}&C!+bbNaWg8FBV?#K;Bw?Kq+6tB)j=fK1_nZ-3>l3? zu?G0ol~zA36cF3YpP!{u20|Cc-WC`wN_s=(UYr9-^#qw!1Sh!X+j-ZkgKpj;hjZs# z7|J^}*l~6kV*1j-Yyw&gTvOP4M}|W;$D~>oAOvu%hu9+bSMfd(eR3J5+D@|s3y022n2JrLikGQMP5*z{&e zL{s1)mI3cFe`*wNz_C{!sfPAdBnhkgw{qB64A@ACG~;L+Ujk|3Z1)=8ma^Y^qW>9` z9oPPXTBpGdSxkncjYKT6*9HMVvjY#?drna3;c&HG0U@8{P3k1Sy%FQJ4n@AfbcjsZEBvM~ATEvn1hHPMHWfbuFbh%A}OAc4%2)?}Os+XZ3InU;jp zbv{B!tC+LtcfK%_?<{hap6Zyu?Qm~zK?i1)AdHrL(DzJaiSSQTgWty<@wzFrbX)VFOn0-`uwhYl8 zGkFjP5_J+k>;MY<5@Ue?Cm%ah`K%bwEaC|zqYKF_&7tr_?3;1HCgo;R7h@d*S<`_Y(iBn=LvSVLza+7OB!HLW$M~$-bOj zlLeUq;|SxvT@CTv^bG|%w8oHi-9>z0+Q;%izLCv!8?oH))@ol%LofAK?m;+eAj~CBm z%wUGg)pR(uz(Q0PgZGK(Dq;1A2(Qyy}6 z_$nd4H~|erJL+%al{ZC1NbptZUs-Ro5RHO`N&wWXcLU1 zeZ>+SunvQ%bMP<45m20<#|H-67WiQxX+-0rS>*UmOOdj;1rKUHpc%FR1Zstsb_3U0 zfAOp;9B)46E?UJM6c%!MANCwLkTTL1DDtdR$N@;(IKnYeE0g5BbNi|HCy?T(J14a< zH!spXG}?MfQ3s`H$Ay*szDSsHsC=FTmm?6`S6V|YgtiZu6oGDz6K|#|xCVz^H1v*hvnrb_VVa5ZEAaJyzw+1>Vtk|jg^@pj zPtOY(VN`j==l~F+W=RW#68JN&@>vC$XyGVuO zC*Y}U)&p`<9&?GV6;==p!}6G0u7iBZ$@ZrfEVj-EJN%!ZD*;#;aNV42JYp&S<1F%8 z8uj!mWFhwvz!#uZ%(iC57ywJMg9zlJR=-r-l%{_W`}|a4MN}nYK93Tz+Jln5yZ=rczt+I# z{#d~>Z%VwXb^%ZL$x+ff8(+70mL;D3gkV$!*}-%>E*_4=-5e)GxC8ovl1`h|iW2eO z@CWR?8fM-t4J_Y~5!Nx8_j4%UO#sU7K~;vc=bC`FK>fSK04{ju1|XRDMYKKncmSG- z@dxM3EsCo$v#rgkrUn;!HUtTTAaw(^alOB4p8oGS@^}<<^p48VYyf?hXPa7S z>A13u&ms?Un=duL$4Z7~v+W_NI_Nqk32*Fc!#nh8 z6SdgQJXi4FXj9*_{TBNuYyen)rehY?k(~t;FjABfC9XxqaE{!k=Cql56!9?b90lfS zaZ-`uf0(~ztcVYHgKxxxe*55wa$Mu&jsbS6wgbfa1ZG%f8}14bKNwCC&@Rsyh7lyQ z^G4jTYV9`PR|H*RJ=n8x8Llr4a~;gR9;<=)qxJa&bzS)~xw}u_F9v|{wI`q1`ye`h z93aMZ-aZxwJ>cb1EVT1on-|}$AqP)L?~$e7LQ4D_ScvaXqD-bC{H_*SMoGVh(cAGV z#sOd4TT&n}xx%Y*tOf~p_GO3Fc45oV3i;+7vKD3iLIKgR;-3yZ@BSzdON3mt^Oao= zSYCfz8TyRnE%KZEfd$B`^$1Ezakj6b=|`o3l8j_8_SGEG*cOXS?WYRoh6aV|G-HSL z>^RyB)UsLNfYV9CM^Te<4@6|UySlkOy8$MlE#Pxsz68F}+A)|{xxet#{}nts0?4a1 zF=V{k9R!Lz49Y2;gcA60=9^(5%nYmym74cgu19c*Xmenh>H@}k_E{b=eSj*VkoYPB zuTF(&@hj*iYdgfUVK~Vt>IC(|9kok0vdngC7Nm2Ob(jH?*^gr2LK26 zKCL(u>@Xqg7{P>rytXT4aA(V{*2GzxNT{Bzt_GlP*mW>S_fjI%PVSu0S7q;jw2Om9 zu9Zn;a7To&TL%`+RdrLS_Fv-~ShF^$)&nxpI9jRQrj9?-v07?Ft_R9RHH0>P^Q|}d zzl6xOj7KJ zXHfq4B}e%Ll5{9P%R)%Xg23Q8blEWYKms5x!)&MzKad%|o0ei}+hK&&^KR3T4JS%} z7Y>Uf7X}8K(8A)c=w}HrkG)i5AIe$2l`>~^V#sPN)8aiFzyP2Qjg!s+5V#P1ga@8y zkb2*(*&Z+=&+JuTn-X5NLItM0c;fsOD#r#2c^dN{tgLee$uR-lw@Ah>KM+|Z2?qE? zrmPsMGJw4G61a!17`x13b?ws(sX^~LMWd>J?FZWwpW9m1=4@n&^L6IH(8czu*--HVDLaTT-WLt9J z=5_YOgt>}y!#0i!^aFSR5$*dPu?>erd-8fLYYbkQeCHj2Kha;JKsG4a2?aN1O_KtK zkQ~n8;R;;u+oSB=2`X@OQfSm?)(97?x&ozy)5)=mOU&GRN8!1b|HLWso714EEX80^ zl?emTvj%YyUHUrgWE4#(z~tELB~Z&VvnWcJufi|7Em3_S4FeBE{^(81h~zdK zg!K_T*6krWxKA`+mayu=+y%v*T_gB7<$2uvF3xPV8*yrH46FErvn}6U9(VKreF38? z#tmmzVLuZxt+10J#i@)_R4osolmsm=c}3vJLj@7CfAEm`Zzj2w%y0u^&`U^H_0V;@ z_h0u&v8gauoCP81EVG*YyDjR6b4TL`ZsKU81U;JH2=m(YMG`?qCqw0|D;XbkHsH30%#qV>9_bmd<$|%tR zJO?&MnxlQFRXBuX&H_gI*{7kjzKn~xq(8$4u7g~{T>y$tqu@%}ZCE*g8F}EH0WJ z{hO_^JO;qY)|a+8R{=J938p#__nv8w?0XgD5=jS~nK{N|wFQ#<8yjk8rFCgqjsHuX zM9X^BD`rK1$%o7Qi0x=RTmr4r_NmM86e=re>Hk%LSewXW90ig{y`!8a!1Imt{sEqj zX+kk*SA#z1AAk3CsWPSx3qd1j<|Td0xa7u6s0O5olQbs&K6DLlh5Z4E65 z%d)phkmJ+MS_E<~ikwqWs_A*14*pPmy6P0yA3=+=zdprHn;82-} z0tY0H$;!7Po+B~PYrlZwt0dpM*g!OjfmF#~hN!X@QF&V|?$aYcR*0h9`uTi?lu)yHYEi2=CElu3yU zF#z|j`05+`nOHrZwzYZ|!Zb-$z`K1zw+39L-aJs?@pShz-b>M;w6sYG$L?k)i5ndN z^ih12d;zCl3cgvP@EFB-cpl#9OUv{O=v-SC1m)%3f<7>)_h z4=kDairUYIt|1P@dbv+_u!6ya83UFE*@aNWVw;h{RVCp<6?uu*%>(?mvoKI1v??;@ zQvzHZVddgP|8VZ3wc{(WSV$p){c`%542<;VXvDwkmBqjJ{u$pJ$bM-Ka&{;jlTd%w*C{>OW=hc2|45e>_Q ziPGhd%>hbFbpqR>2lQ(npW@fP!f)&+5WrZ<4!Gg>;^idctOlZ`@gRAPFAUwxMjEFx zX6wuiu6l|_d39VebvFdk(gaI@r2cC}0W}7!a2(rx6C?#ZjV??e6K83GnWy8b#RTt5 z9Zl}!&-W#!ViU8q^A1Pff!vLSq_`FdkUDAM1@jLw>x#sY~j{(f$31G*s} zv2=p_rZ|NcA%knE9!~xF;#xa-YytItfXjtkgX@M5zQ>wtKNT`yc}jNcjFy6x$n>p3b9s6n&#;PXw`lKW3!v>uY@eezxy_?GjN)u?7pJ+M%cdZI9POkq&PlD6I8qtp}vxz`b)r zD>qaqD+gc}rkK=~a$eabT>3Tp0ZxCc@wzsPCHrYGz15AFq_ zbi~xbUtmL6_vEmb9|1DB+yKRKN9kFgKX~fc*g-a1V^}bFPA{O?y_d{pQciT)EdrJ` z1wZr1H7cS*ZsA$2QP8>EMNs(g$I0v9m!%8nu<0`JWr57zLoyXE@{@9jV z!{>9vN()i9mIBKW@Yy^>wdmn&^6E2*+( zyVi*VJqz?Lvi}^y2LW25F^zuIp9(D3e0){_b}4N-Z28Y+>VyH`+vAJFCIy2(uhstV z%b~XSaI4lmXaGGx!oMD+u{d%ek;x+=?>>@;czXu?pJHj+*}eNkICMsY$)sKMEPN3H ztQLp}CSmARs+R>}mr!+Xz*(>s0IO9cCld8}&mJ0Pyc9CZRg>*Ow9f+jRkH9=OT=+L zKL8?1hJOTdfoS!feLOCuPE(pnwipBN2bAINW`2BU!f?bn)8{)0H8HfoJXWuFrtuR^ zUq}U^6U9|@DSNUp2T+1I3>IX)7Qf>u3#{5i^cz_t08s#I&scMty%H3$aZ}TM<(+Ci zkI(VA)bHnJHgV`Se$(*=hyt3-QRfy(lpg}8CD2=%XtNmmF$QHt zb5IJ8YYD(uU-PLm@>e)MvmffTIjwAz; zsxjkiV;C%uZ|Rf{<8rGjK%}P-hXP~@eN4LByU79TgQzuh#9!%tE{6=`#$=Ruk4N|N zlEq?+-=ojG+nWG{NKK+EfHorzSdq?mKv!yxDaP;NQM|aCIcM=gj$QyObwQ2Az(cOh zUuG+ROx_3k8IirKVE+Lxv8_&S zJgNsj^X$~U@wk_s$(z2yX7jB=;i{O*0j>b}F?jZz+%O*IKhjG4$jkPBxlh}3se>Lm zAE>?4I70+g=4*fiD?F%)jRd4Z5LQq+jZFql6qXoEBwq7leBl8STw9RDY|r5*#n8_- z8tr{dXTy~pe|k7gJ-RPcP@4k=q?T2##3V#uo+9PedifBo^np;~)Hzi@T3UEA9fL<+vcfJH%S6CZS52QY;8;rMfo| zM@ZU6y~^m;v}6FO8%&O>W}vwRkj$70xeYm?oiEj-7*H8zUhpCm*qj81q+cb9<2Q-l z?;#7k7udgliDXTaUrm-l=MiVj%qaUSTx-bFk&Hg5&kF08qy z&Q=4a{o=00WabhG#wLq1q2P;N3mlo-5SqZk;LDL8?HvXZ5iEzeA_@v-xfLqniDrPm z?V$Q~MCHca8q-o`+(HKdu4}u79$-0r0>2Nw-oLliuYt`VZCkGubII<^>-GdH-HEvx zgNKGxk4BL6XJYh7-=~%tWa>_XWO}fZzghxfdqQG2qA@;hr6v(&cUuJG*rif0*;eyv z-~dw|n&<{=znYQ+7F{r(gQi8 zQG;Z^BpZ|uE>s;6rxWBcY#IU<7TV|NC2HI?u>3e#=MxEEb;o)R`<$kL^g+DtGhzTh zfXR_oUXBdV$_ri@%Daf%W>lw|57HKXDU0&6Xf*;@7jlu|>Ub)Zp>gT%Y-1kg(<&6V z9W!QBOp&0AFc1dh%E`wI)ZrxM2F2VC$8=lB1v{%ALT0xSw*a_8oh$=5P8{3iLxfah zpUk&uho=0205cDd3H(y!WaEUFm-GWNb8UeV&M1rE^uS1(m(7cwOuafeQ#OuN@qdLj zuO9$5jx)rPJ&`2^8Ivx@#Q~x6?nbXca6Uv>(J*l)`HcdwcC0;6qeXD~+Bcfm(oD%g zt0u0TP_;zu<^|g#BPam)=Y45+VY5ZnX+V{*Fi)99@-Zyqw- z5)g9s)u0B2B%8vcteBVcrw71Jh+{Zs_f?vPvK&nSAFYI?U^xeo?_gU(<^(+g#LVD? zARE5`@ZWVB^uaqXEyi-pPS~j)MlNmlg?O!7pWAHU#{L`~lD*Rn-gp)tf`%SFk|dcESg$)bkW?YtYce zgEa>|)Kg&6)#^@$YfA%U23ZFI#SR9JWNPFwNk^yjp!cz0s{$O=*kp2euuUy60$YlM;9-ma*G97tLaeE*x=9?;{>o-uY|Ih?zjZ^Tg zV5AuhrOn9JsmE>Q$T23VQL3ouF$VRC?cxUMRD;@#V4~<&%v)P?a;OZCd4>_J#jPfY zH*L1n5hnz4RBas}|6wYtWzL-k$Dn5ckDSOvxzOx6IO)DtN!bO>n1r5@0bVE=|NA0^ z$?0B~uFd3V3@*gHuBAkd9R~yM%_5qc>|Yo9BG*%V&q}O!DKT{%(yAP~>1>S>wTT9d z=}8t4Xxa6sd3ypg&VLVnXc@5;B!r2z*I;4u!G8v?xmWUpTktCl{D2F)Gs!_psPOp9 zM(7Y#d6?R>>=y-wWcSiN#md}joJ56`+|1udLd|uX#8UvivkMfv=V}15mAin6?_6i= z!wk4mK8j=BRWpka*NIzGyq9^%;{XMbogsavJL=c$KtogX+mP`Z%&0UC3RW@T`>_wN#9m~0h@f)Yf3Wx+EA{cUn5rD?4#Fz230-5_h zZGV)-apfZ)8H=Kg=4S$%3HW9L_@15dUF++rQbv;w*93H|OjWUfX;r3d<+cTmP1Amf z%-pcdPs3*XKVXuDvlx|)mU~5PIwO+yjJgD9?47E+J7!qRh{!|Mcw4J_+c@N(w+yf ztEf|`zVc2WlrybCLo^{=Si^{9+_Fhe1GRM4@^}Z*x_WAI9%F*(y%T-0j~pPN=o#%% z!TruWrNpGB2BZg@<72q~Q21Z>AH9iM8OAhar^e#0!*$5|QqvREREh@>E~nk+cLs)7 z;y_jpeQzCW+r@#7UNAFmJcA=<3Sa_?)*kz;_%v|R=bwg7Z?zMpxAOhgfIEHZ(@>se zJt+cc&zlg~x`G?}LD5ohjDLOZ+}2<*?QphIK_gcE5M2W2CaO%QV7Ox&fg*>wJW&j6*k$dCTEn|jrv5@h^V^5?b}4ngbx5}eL5#>YplaT((1PbesqTh^ve5p#MNA% z-AIFrcOwV!XRsLY(T|k(ar0haFmEZ_<<_Y`CE+Ur8&R>4JIDj<$Q6A8dw7**>%tT9 z(+bojPDK*~Mjt)j>xrmfzd`_jPBV~&e%(ts-i`WVD+wx81JgN;4=m-0mzOnovvUDz z2dNSKp$0tmmBlf}%E_5#JC6iJe1axGpH^*d?wt|fT+gv|XRGc!>vXvUPA_6!7y1`Ab~*Z^kU zOqU{#0y~sPjU86ddCGu-3zA7EEO!7(0nA5M6C>o(cUaeQ&$423!Q&Z_sLO9X+ZYX| z6Se|f?KhcYcb0Ztt9K>a-9($>U}1aE3$ybtO3c|XxfBCwCMJtkW+x|*augBQaIeT5 zAr{Q2CV^xA5y4fn0E`5rbIhrCeU4DVyk?Ia??E-;DuD(`i#D!2m1%t*luA~jy87uS^l{uPS8*BS8Bo8)v?aU zoWJ9$Gv5LuXsveBWjhc3557iL*1cXBBBb_@mon1~$ExQK@m>PtkO@Hhi?Rh&Fsb1# zcbp;Ov6L^4+nOX2Fo{shxk?7ZA)f+0*qR@u#oWH9VH;YRU82; z0IO)^)}U&isCQQ0gF7V3vs!GBd5D5f8ruL^z9<6H^*B)Gxow&*dyeXw@Yial_d~Wl zzUd}vEN zw4&MyB(Lh_J7NbaDtAsl#)_8pnb!vZRf8Lq%oFa?YZjsT zb0`6AbcFpzIS~$4C^n+vOqcy_$h@R_SYfW9Ux=P)A{hhiYtO57)TkTE;)-Soc8^$?87x# zqbFgVF=SBCTlsMoe@!J)`5BUCfKmpGQ)-0sm^Y>ff2>p-7Enh9UUgT`UCG($Qox3w zQXK>r-586e_t(He;hR)8wE-z$u`8*}qW8jZ=^xgZ3$_L`*-!<@A&284=|GUJ9gJun z3$8k!8DPjSs`|)*#XSNfR>W7-FdM1IYf~_P0-nGW({SilIY9U!{=u362Acw6w>1mr zp)n1R#d-fsR-Wz5@Djw|BOlt03kMY;^RfYyr?Uc27h<&(#1#Uu(DQ`@g3#A?k<`;e zINvz?LZJk47Hn6U(Fqyy^`zB8xPjNPBOU)O;SYSzIRCkhJlX*u#G|tUSq!h0H%|`~ zpJTV`Z#rR?h%Dya`!F(7=b{E%P){r!zwE)XuF7L|Y&&#-(G=11f(Ha+^rItb`OODV z*@JX(n6P~imeI7;)~TV)x<`Msgst}0)%I8+ziS5J9dGgvI$b#mq#X_T=ycz5uFs;> zuOakaAyWFJpri$8`yHjK?+#7WfBOc8?G`XGb7b_hIkTfErxW)F{DIP%qP@NFYDk zV3`7=_q4H8ftvbB@rnz{v_{%J=JT31RdSpf$eO*F`Bn$TD{cd5f_~TXtD4h}ba07I zFh4;Y-ea*i1*K5#kTeI|`zXf$dn!?lcI8J1g)KgQ?Nzz+HR6=gA7#U zQ`6#M1mFZ)_m^s%vcLO2(2@0bE7^%z8;jU!QQ|7^m-vP_)VT)0B~r@(`uc)WP*@!) z+5lNaoF$RPHwnY__~4qH`m_e(N^T?3=_XO#9lU?|+->obG&bOeVp4I=;SA%oRgD6e z>WRV6NZ*FqXmk$`HwSdnTBLjal%1Pj8x2Q(GK>M!b-BPgEHk~&ODv2~89h0Sro}Q? zP@i%AQ>f<88o&b?Ktd{`7Tmij>v{#D@~^kjBr61s;PTwS4`q)DOAiMSL;Ih1zk@yy z(L3R<>sZrOSuw_9ppHDF-9SdnRP+JhTy=*42WSYOEUgv3upmxIe>9f_My#kFqRXq| z+mZn#!CTKD`7K7(%Wt)bin`6qRuI_Cq3GcP?N1`nT89M0`!}HOX+c2$9AyM5xu$uP z{t$p3t2VwH+NVs`xS|Gz!_;;Ul}He2#ZwLbwS|8}&M(l$paef~s~HK^1egXqTI$?n zTJ4B$Uqr~VlWQ{z3E~)FfSWf-woVSc0N??pu>!V2v)=6PQsj7U3JFX}4pY`efJ?o# z3P#OPKG+66l7HRJ-30%KsP*nUyt}Zq+OuC{u^mb~fqYnh90mii|Hh-_*|a!qfAEBjMMmNwz5i-?==G<+O$m3D7Ir4sajR#<1Dm6>gPYyPtcr3iQ;B3IAI0f zqsw!k63;>UaKL-IM|T_qVeRmpM*6uma84ZKP6h-GYH%waQ%UC`AzgJlqq*@RfssWy zow`eTvaV{0WfTVMZUz;xoyfIkl(7$6>((mO`Kzl#B8n`y`IyMQBX3+BAWK3 znRf*#9Kd@4icc`^4JEgPCeE*}qT5oMMTI?RQR&{J^qm1$Tbrn>G-hcPsyYPnE>ee& zfRK^zq@7vAORu6=bgXXz0^Df^g5)AgUN{0$3<%v?+P}3T(WrLH24K5umak;)5|X2IXnnBb>t^8`yAdo(pwz+jMq6Jrbq+V zRwUwpw}v7ybM13;7G^Ie$r>BhNibf42b^Awz9R{$~aPA@g7fvHGBTXxR40oBQX)#%pQX0#m|l3>CK@PJ^)jH1D;`7{2bZBM!pX!#(IkE}-{>Ye_Z=?o7!U?zL%aRXg_A=yFup=lE zdsOJU^_{qOfBB-_vlRrAC9VxK(rbr2w(09BGPjc&?IA=n=%jd0 z7(wv<8YTu20nRrq2V5|v-m0wqpyy9;5GRSGtRDnb`g`D9AqxPuzxgQ~njzRgG;Fw> z%r*g%krnhJFlXiRlKmk!9`*)GZFiUxra|Qd))RfA|AGx-*NL#nFeUX5-Vw~`@=pUO znKx;EGE>>=nbd5$$x|Zk5slIuK%#<-pJr!R&G7@^y5zA*1Z!`l>wmO7sF&P!1P;>c z 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 z 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

s%ir8l4{R1k~Mx zrD?i??TE;Y`BABa2Ku$$W9yNB59W=q4t1k91-NYL589s;+%kAo6&vTKAYkrejK!S( zPdl!*6#)JP1p>YtQ~xI4JmLP$)rVTq;t_TuV zI1?p5A$Q(u@@mxy_WANSU}7Chme>f6H%1_?$O zXZ5l$x9snO9BFsB+EmqmY6-l=BeEUAYEe412J1ZcV(=~Qbf6C;_wcIX&t^d4o`m?b zT-d#K{NnlH1%;O!w(b)qFBCE8rZ-`9cd%<$=0upt9uAM#ZzWhq0&iQg4<&rtguPvp zqZL?MwdqNob040q-m|h8hp(@c0(DbxcrJS4B&1o-{iOF=5LgDJfzo!>l?;t{ea(LG z2g?k*B;g1$G2?w1Z-?2Czo;qO56r<^?^>b6_Z_*?Qu5 z;TgFBM_wt$0_a>O00KQDniLxD5_1-#ao*h$-S<1)>EX_;MxJsomqIYzcu) zWZa{To)as!i@9J-L9lvk0h!N4Yv@dJ#nW7xyBuJyg9pqkJ5(f76XefQdQr@}L z^%Fj12e(*k%+1uiOshk00qv5f%FFM<-^Qa%>FJgWvdJmme8Agb7Bw_Lue^0N1D`Gx zm{GxXf6I}A-++sX&S~(uMnji=L-t`~$4Xh62RDjVJkgR|5bcWW9P80a?#F-wIhKkz zSHLFlU|e3l15ru&#sayt2iV8byU9Vk(c*1i!|jN;CnCs+Kt=y|03CpDY&BXfnzHo^ zQw1{;?Ekl^(Z_;?IUL(>p<{nI0kR~&o0|65O9LK25{Y(w`pUT1!$G;~%F$IUB$Dl3 z0lH+Km~Z4Dt*;0EIt7Q6P`2@cWBC`{shi^>m%yeA0L$O5;aJn@kjpY~YN9X+Sn}~q z6xe>o!ck7$%mk#R1<;*!eIjlsH3p!eT@iBK3$kf;>TcO3RW@`{qY#??1odxFEb8g$ zXnm;Od6Kif0i6gvuj8aR+PK{85cbU82hUhsS$;5Ft<2@}K0a6=g|~M@pJtQeuhmL~ zhwsP60sIJ2%D2Y#Uyj$xIpD=dP5FEszs;~24veN(6=!1j1@gnvG+K$s-vw*vsy#pv z-cW49A6zocR}q6QTh3yO1$56Wq3usLbkH(o13gRg&mqv|b0KC~f>ESd1~gxT1?X{+ z1jnL|wg&V;Q}eH7cWg4!7-TvXy2DhZkZ2390&>(%0Z{BbyaatEsLDud-z%cfGNjKV zph->L3A_|g2BO@fD({`d)V+P9$5gJs35?N)-7!rdsDRaiqU7EY2VkVHOTb?qM`>t* zFq}r0+`Ix@OLg1bXDs|=s(4n-ttLT*C^|0}Qd8bBn1lg@}23#TJ2b7cVI&Yw{e_!DG6CiQn`G!vnk*i{aK7ygmA6t2PzDL zcPvL@*IWYgA-+>rL*u0kBI7ZH`FAFjmHj^2y;9Wz4P9Iw!**a2 z#N5#kYC{CT29{K<%W_i6tJ))<1ynSX(?LBYE13(OWhA7f33<&R1<_!Cw`F~iPk_%b$`0rP9&T4B|961>QHVsP;V1L}@l4HkY?_ku(Pg{Zg?QIDqT#p(6wF z2V6aMfuv61-Fw|X`d0iJK} z->QwDqoaFUIj19Iq@*O1T{c#F3pRJemjGoC1o=L0D!ng{vRy^ucQ+;XmlO%<3Y`$B z!qoJ5uTtv%0$OW!7d{GQ!kKpYPWHeBG*F<~GHoVmYMJXE88-GE1Xwx<@tdH5gvxz9 zjk%{|Yu4F=;wC-%a+t9IfVqCN1xfYnU8{mY;-m@w6^dsB^qTFEo2`83{W z&@%;0aw;#&*za-M1mjYJBa~l#(TF@n;s7hbA`L_0`0m~XyE3p)@ls%M0nH1hT-3oa zs;P^_u#0AH!{*w@=f`sPm8Fnk;OLEc1K*7GNZdVK#GM#5L%oI9Q|_PlC^N?@m{528 z;R+)J1YvWy-zoc!%6)a|jfkXOXvhkb2FoL26Sm)Q)r%$V0WpG`R?qOz(MpS0cxLf@ z1f*1rP{R1cD>eV^G0m?P0F^&=^=GALG9&{!sRGg2Ur^$RjOaUDN0P;Cdkm1*H+sh)FS>5 zV}~QYyqy6U08`xQ*Jx}h#OcRNRdj~cbI;X?HLqY}3sMB{w!!<;1WO?%0NZWvG|?7G zS5K*B9&v>FW%!#5df_VB@`*cE2IM=4T3kv}3`|^v^pw+F=bCl6p{b8kea`tZpfz+UQoRBitw60Nla<}ltB0mq6$451PNx%$7)bDe@G zVyB*KlKLe#lUpIz4Ra@F1me3D8cGx3x03G8P0#Oevt3GlUdB2y@w`l#sj2sZ0tK{x zT$9^%9xVy}>sof-1zE0->)2iPv23EhL{o5H1BCqr+#Atf@x>$D=F#RYCBq0DH^Q2x zEJh~UVQrqc22Z>?EA0)~aT|>%-beO=f;_LdBm4AAjQa1S)m#zw1nIpRb3I~MIoPjn z>LlegxPIWg<>^uUPVs{}SM!g82AV8ke*)OEMhkw?;DKYuHh4*Y&9&59HTccYA2PGk z1QvcS42;3vnR`zwOAdwINFp`R+4MFotMxrxB{ms>0X7ba^Vp&G+jAWcyi&haMSA4N zPsVq;X9cc=%>1eI1P5nyz7i0JQd~t2hwVYzJpli{sfc4sqYhd7DfInT12zL7iX%Xa zY4L^mlI{`lgEd#Ki>htma&@QXdN3Ku1YI&YDFssrZ1IbedjC1dSZbuKjY+@-s0`@= za4Kwr0K{JnbNWs>2Y7p4>r(8dZG_zU3}+z`p1@3Ia}5aP2cH@7$iLWW60pV}t01)s ze6mEMNrXfW;dU>=*8eDB2Jjf28}pu7xi7k)V*1I2g-_<9Q5oNrWVd$@v^r#h246B> z*0GI+dP09CsvUqsyDg+)@g=`EAWd>L2HqjC1)6+NW6ieG3_Z>9uKsA6;$t6wP=thz z`ZU=f#K-(h0~P*ivYre|e4SVqo)H;q`PyO!2qsy<3E`A91A98y1JS9;@TzO=3x=4g zuQ99nwd*5$gBx+fzKl1Nfwu!O1BJqB<8(Oicw36gu{m?t%|W@#MgYI=E9ZdJp4g&F z2mIA9aG9&#OT8`S8q!$5wa?rKl!24*p>rt?rY{T^2FiVwJxM5}V&0EBWcA4M)cl&e zm69|!=^dBN3j1uJ0%;M)uAr?}4FX)jlsH#ExDjAD>2})pi+VV`97Cx?0fDd;*tHm7 zGAVp<*MUe02+$izSZz{)SUcm06}R)q1utZook-HL4j={)qZ=JRAypB$H!471$fJ#B z;f*8(2T*WDB-C<|&L{UqNXZ<_Be;_kjP^V)zIPEjj=coL1%0rtO|G$y!(@gNQs+MZ zN$UW+02@-avuITVPF=$?08B(p?M5sGvV(1_%sLYGYSuYJmB~iL`+{TFWphD!1iFP2 zY+YM{e%zbO8++DvKuNpc+4@2%Ie3!LET)7g0-Ez%z0T)r!Q^qr(5D^yLQ;|s7FiW& zYFWcdo^g1Y0o0TyT!X<7)GEVcF_W9|c(JskTtbxz0a`5bf9}jzhyG}+wJR`lmwSt1 z=eQLegP`0?9Z5O4Vo%j+UJ@{Exl;D8qEU5GIR;1YoJLmK03{3nA=Z4m z6oW3`jbT8K%$HKsUl8K#d>>&l#W)(Q2jSFLp?Q5amlQxvpn4WG!#z?TaXw~1F`Zms zdhg-!1=J_fBHOx9F0=4jXO?>n?{=7=(r(wKw>Nd`AL8M>0x60&Ef6kM3M<{f&fhEz z!Oz`!h0vU1=ms(&&>4+P1+F%s(tQr%`AV1bEwZ(D``iUdQb&}J=@MB@2jf9PHwC`-TH^rH`P&f42s{Otj)`&H$bbasidvLr2HSMj+C*Bm+k4*N zH4Scyl@e7Fp9s;Hx*EFl)`9-u1`f{-@PmEihesex1uvCLCAE%4HP|>{VG$5f22iJt z11VS^2xHBLhW+sU(6IVKz$#j5g zul(okj^LA6e?_&&0OdCvIa2zN94!zDfFbV-fv7cGlUfpR@K_kHDf%Ki0D~jJ?c$rX zB1K%vlqV0Hjpk8s!(5w(N0^U8&S(8VP38664Gn0(OBwPm(&*Rh^<;XB z*Jut@0qFlscOUGPgL^x>HzND9O|)Ic{r;;f^sWl=umcth24&nutU4k-MrxVrQ|Il2XF@E zNxnVwCUVo(L-8k_#q8AY|VXW&s2FOWA60(w5(R@i1+F(sq=U=is z2H4gkOwBKSs5|_|27D8NFk1b;B%3FUhOtsNPmglaqC0`r18BA+Mc0EU00aYFjT?d=NM3k4mjKZ(`>d-^es@h_CUmKc?21+^~B%B=S1Ob)h}0AT`N z@8X^{-y#os=g{uELww3X1_dEr6%g?dkT z3LY6Qr6+3z@%A3Tzo$7G%L6IrG_Aw4?BMrh2Y~@jjZXf*p0k8u;>WBr&5$&_Mu&w8 zHSw+{S|j(C18#r9G`)D>s!?7l0|C#dgm+DdcF~7Zr^i!csn;ldr_?Q-mSm+QB_{ZR z29GC`RI~zhs&vK=$|NchPhYnsvqoMetp~qO5*K3a0NMfsZb-2W8e0yAKze>L=s>{; zYx~$BAXdNqveqdI0fpBPeNBHr#FQbGF9?HH99-`;FNLO^-Wv?(*!Ser0zcr(U6ja3 zj``gBm=b=Mq!3~j*Ebe%vTRiI`;`Hl1e&2@;w0F81_RR(tozJ0zTw7i483ifcQ+UX zm_!970j2ohzm6ApK^OE`AHT}_U)VH3#`XwE-zp*18f4e<1!53-AOpXn7QUuztH-A~ zUGQ+inT*%LW(G#1ywv;|1+jYCq7B2s9UKLcN~9YhPkfRNkLyx6{fg^mOslHj00;u! zkMEkg;Mdw%r^^3?0i*>68k6R-qCCkS*ipN60sJjaVIg~6ID@eFp*%1#ZDCPH4SX|8w?2;Z%~Kj80hyNtbXU24FCi>m z9rnbBA|K9qbN^CjIT`anmAa}C0-gUe0_05D%^qP*&vorOxX`8YDZzH&D|5CHWP|t> z2Tk{94zv1KEt)L>XMt}N5?#krm#)2;;#D9zVQ1Me0)1Py^y4KVG~F;CjrO-X!r%$N zrbeWuBJXY;yJrU30UpC+(_;o=1kI??h&@Zl+Ynmjx_*TO35sctCu+{L%h|CZ zZSjvp1w*F$ML1=4#9B-9P7& z=M2h?&O#vF1D_Qn1y6@RM*x9^D*h1uXylD=enD}p=?*RXH2wE%0aNYgL5>&_uFXTs zJ9I8w@rVOosY#wdiG*r+S|l9u0uMnuGf zl; z_w0&x0c0NK>&9iDtv5wUy6h2%xlxY|(Y}Va=BXH?IG4_q1Dy)V@G>NV6h-?S#!Q!VQoh&eUmrw3L_JX#kFf`_8$2Bsd81N6CihIb8KSBCs1a3RAeWyxMY zw$HI$XhKS61*_ztFIk~<=7H&cJUz%^`j9j~A^}>WNSjKk@R$}(27v|4tm#%ZuiphK zY%BTVb=5*~TM4N}BK1a41)&1-0uquFGCIMF4Bf^w!Jb`1C$f@Y6?Ff zL|wMcuTC<7wwa7k_0q@T4zT8f_HaEK1&iVf9QnesNfwYWTnNuYi78cffeH+B3jLk( zJ4tD42BlS(f^C$uGVurH#wAO76)kNoVbZZ&@N{;1oG^xauHIV4%O6A z2r31dY3;q6YOo}fql2L9)Q`m*1qo^{%MYUM|dPnjY}$N)P)#J_HO5uG7wS^8^+lHiB6r8$nyarC8oas=5&wOQ#6uh=#k z9-x9P`%h=Lh7Nh&^Al)+jK{(U2?XLeKegGKoGdF(^_6%#4X##q_t{XUg?}TK;m=4O z;0LR*PIB=)fqz5K+#eu`vfR&OPJj(gKUkl`G$H2Ogaz<)I!u^FrV8>sWuV@>DAFru z3f><&i`JDKN=%hgjJq@O&U;bn@aa#@o0Tw8`w zjln?~M+S-?(w}d%I+W_H%KrQ-$UgPtX`8-OgiJK5)K}Q6AO%@se;aT=3A&5cq~zeTMowWN#!c6GWe*pyA^`5n>4M>IsTn3<@+H=pjVpKv;pK8V_+kWxy3-A8YW%V_ z*5ERdM*xEIAwpO5ZMElK%-#M4^=uw3Kth{4zz?(+jkV_XYX_8-+B1F8E8=*&ZRWVq zNT3zg`Z#3E(kk<9p(iu(=L4XfG_d%0e$H`&d5e(RxLNOJXSn6K!rW9C&?$&0Vyh=mA+X+&gE=WeDrQ!JF7w=YfCBq34O! zQigH-h2~r7#Rs43mP+V}{J2!>Ua6uJ0$S}kv%eZq379&H06;rVfbwwqahSOuNX0l_ z^z$L4zZQUy?=RPdg8ev$t}JaUgcu7ec#0ve1mvQOLii+*4K-V z9&bC{j09{@)0;#%2Cw};oE7rbd3bIKD4~G%P)8%6oKqSJCI+EbYyAANPW`YZ$R2A@ z(2t-YqpM!9vo?la5ob=n>;VoS?9cTon%G7q1A#q&JJSeOV2F1RCuVW;o_jESodFld zV`MBZ=f4n@LIdyV!ww2Z$X9-b3o%7t1APg|zXf?}=V4}crZNyn>3l^zI4quPvH07e zdn+8WnO^!s{!tp*kOnaSYx(! zZkW5&(T$}^u^Nr)bmsC>=@6(7um$qE6=?3^JHm8m&1%UC9Rke|C^;U!gb~_OA?yM^we3n0dS1dq zoaC5*Ct6;9p$_h9+yF1pq=HceaIQNT0Q#lqr{D%tj{n(rSSej!Faod|69WFM5uQN{ zuUW9N{Cp&Jyh?>p!08>9$B|6e4j-|$Y6HZLd@-6Lp;9~Dp#XAgor>6U1y%bTQ)2#j zRZD`rtN`G4o&TS|+Ts@bZllq#HKHGbjabL{Jdv4L#AFnJF9RfAmZqWA80(zOO4st-Mrw<^(HcJ9cW^@FGBer?xG3ewDqpj*K1~8md|NqCpy9CyZ1jh!TV3R%XnO?|fuVbTT`B5!*CTLcmn8ikeZ{&=>z z-%nazCwCI@-~U3?g}B%U;yoE`3k5N7&mQJI%Zpj}6lGc!R|}gzhSZJkF zlLX^QU6m?q@ME-d`f&_t4Lp~`i~sdM_?SPf$V1cyFJqxQAU#+hHnJd{R%LI=`ZL^Qdszib_19 zVZW<7Lpb#XM^MEd=>$g#q5?uML}liO5+3bxSsqro#+km+m@UN$ZPAehR(qT>b_HTc zE9tsJTt|UuVnTga)Hfd9S=_KP!^Ja!ECFEc)B>>Bfk`pcw1b4z8ZuE5fOsj7C>rBt zYvEBK?OCD`%?1e->GY^RUfQwB|GflfJ0_yZExW+% zS_br!*6m}fBO630ZiCTb*|2CmBEwbe4IkIX-U2*3BLk>`dlJkYWU3vUO$GnmX34yd zg#}eE8Et9vP@40zLI8Hid^$BN-pXlh2Opjqq+8cT%gh>6ku-W}Nr@g)1_k*YbBVSK z_s`H@WF7m2FZ3MiUW5ZjwNI=aJYR^^u>oucu1u(uhss=Ch*FD0GansWwx(c;1f}gT z?gz!H$Ob`veoBM})SuHtn5y>Pucv&8=mV%BiI{1cN7N{SaRm6L>ITdB2b*Y44#5zt_Uy6=}kOprI{*{>#V>RKHIl~o6JoJkE=FPWU0Tu^s-9*Wi{RXMM z-frvI!HOZvmFI27tPs2b#SX&np>7?LJXm?f9{^R*p3ol%I|F-PfH{UyXEtph98IHY zoeX~)6TM)k#0P!4<()+M?wcsV1~^!=%7})T+(CWLhHjObR#Sk${Q|HteRVij=yN+3 zrR_j+z#g&DAS$@QeMUvA)yaCZ($+d`Dzs<$;=?rC7Q2zG(kbr-Vm{)b*>6uq* z83(OG;YxL3g=+2f$r{y8cgJ;yqT2XLEVGN=f|w>5UI7EMha1oGYDuT(LL&BR>yfTFB})LCt`a8- zneur-ZUS!`2ES1p&GFG%+^;>uo$_ydb_oYkvV&0~UUd{`uLgf3Kx%(bn}|4TK$DkV z^m*ElrRPV2__WbAtb4$X9|QwFYhE1RG*j7j&|9Gdq_=`E84g5I7IDmM8=zhTjRDZZ z4eF+CYF@Nxz-XT7Qc(&U@h|R%J*zv%Je+SthXp$b*E{N>*db0>hullmvb$r4B>WoY zg41@T1G*)2qz8zaVQMm5Ytm=UEE=?%A1Vqso}WJw_*vg(G&-z}mjyG|v;(q5!dW`L z-}DSCqOI@jz^0Xsz!w8!GGtgbpaF0{niwk_$G8D`f;EclZWr((Kq2|7sv2HP6_iCg zjRGL4NxK`n5Y>#w^#1)3y`~(G6;w(l6VHq#j7Yql)B&T$ln8S6Az`1#ibe02bRz95 zg`mMT3VZbR%T^%;sRMnLi)VYBpwwEng;Sv5{SyVhUGOnDEfHE+g2Eg=;{t#~+jnoX zja~N?d0)qN?M^qxQt63?xI=e7Lg+P9!~%G5E^91A)rO=8O%62lCWoPfd_d$Pdrh;3 z2jOh7Edy|3v^civ4M$R~!~MF6+cZg8gU=)D5TCf%-y3J3Gz9d4lT&*5G+nv4gW8dm zQ1MH_YQ%gU5!`dG6DXQ&wFUZ_Q|OrM)UN{2k$@VrLqp;y-#(%X^3VFN0n%LFZ&$4F=C#8)u*HMJ5^URMDl!Y)^2=97CaU7*aY zkp`xK6G`8xmin8}14n25mIE4;v9ro7*j2W2XaYiU;Wu6uAY6$yv~l)Ka#DOzF8uE8ay-oI%T_wG0|&1|ST?T# zZ}xj;p-d%U-s=IEDmYs*)=U~wb6j`uDF%eA-GF}>EEt7r@e&D^=qN21VY)Q!en*v| z%n`?$z5ye9VCQ^(((yqDX@UhF_AE58$gZbvwarS3)}ky3RRSPlG7f7%;!$WWAb+gQ z>1{UHBuaFRbe2{VWR9>1g#%P((Mu?&+ir1~*X)|fB6Jn)thVDBGQnw(94I$lxd+?^ zs3^dYa~#d$TI@v!F@#n;XwWW|5ERD)aoX=OCkC#c4QMucqjgEJiRB`hw-tdb?kew) zAx$U{6;&88$O3+Qyp8AmNbGc_yP}Htkt*DWGoJJP#{S;PVLFG2mIMFh$Ynh}O*m^z z265y03-nAHFxM(KR{A!bqekWw!vh&IWr2>8D>P-h-_M-vByKmdQ!1aoI0E9uaQ>fK z=mPuTqofhn%;Z$EY2+D@;TeMnKzyAd_>wmmxMh9zl>gb*icqr$}6T-a%N(AaK zkx5Ua#&&NA6u@(9#RKVK!~aryLFLIRM9-%8Wuc6oc(A-1|QTS#j|;cz;l&c!h4 z;3_VH9tJ3R5GHC4z>gAIm?%&br`pb3@-GK16{<`D9#qJU3j!16wAHG|0XEco9?k-x z6CA05-BS~;hh#{T+S`ja-2<4mJL_!8J2aVJxB0=$0>*|C1$y@1HL?W1^Gt4qng_|P z>A96|H!iyHe3D5iGAkNgcaH_NDs=K(ZHdp)M+NkyStA_Ds~Qo`(SnJ$%7MqM(&{17 z8?mo)CwP1NsRaFBlt9Jq4kNoj8*pN}&CbH*6bCpz`7lGqVQRB>PytE*F$Ob?ri|?@ zNrv})&(v~yfLKLMz;wY?O9J#cdI5d6-?08lK$-xd-?4^4iH3`lkJ-H6WY--hz)Rb_ zQ~-!_5QLs14o(-kYWen}hcip1}5qx4kuKWq`rOaw-K7N_0G1aNQGPFpUaJ*elU za-~Ca0{?P#QZ8b5;02breud+Asjy@YgRnOYGaNRsNG?16G!9;Yf4VUge*@|g^ShD^ zD8YzW-D}cCd-X3h8FIzrNRllXAl^cO)d09MKB#I%<+t%!;hw#<@UUU=C(_w`Xj)s9 z0<(zdw+F$7N)jyhnc&)>jH1NH!SoNnql&zk%FZKnVZv(aN(G6JE2tYT#0N=9Z=t8;k8+ z{)Zrslt4?ERcP_a_;%Lg^J?#NreWmL(fko{ry*@^iGQg4)Sphpg8(NyMIyZZ zQOMc=&z?be!zU?5hscjSr-pKD%pLUuPyoLPf)It;nKz^Gkfj}r;Pqc2^Yx(WCtY!O zlNWJ;js=deBt$oUx=jBL${C%SX9QS7;Zq?vZM4@o2M71eCTPT=sqk=;N& zEubV)Ugu`)R0`Oo)fxZVRt0B--c8%<-vCmKesi|>GBQOYkmRdkmocZC9Y7M^*aHve z3Kt!a&q^e9Rck#*OB0+QHb&w^6$Ev2_nsAs{^YYWh^{W;D21{ z{H)HJYixz%{{)qWl-BU5Jlyh@>;_+vWKBam8z@$6EZj=)5!as0CCfI5YKPx$EIpK( zK?1|q^z(fE>lP~w>OxkR1lJF51hnr_aNH;`1GimKHv_Ls2m-ZPDrv~BkAsAJp&T|_ zAxa6(Ie(nAaYOHoF$B}#m&Rkpl$Csp6v7>V7g3sao>G~NX>a_IL&TjF)dP*uTYPW& z&o_9m^Cyzu+97A|f=~T6o~ZTH!L?Zes|8uLfHCkRp;kuNHNf(jEdSBPt;d_68yk1F zu<`84tpvm`iPjXNQt-?a@oL(l(|e~hQ^73^X}2Hx5tCY{ivs*MiX)O5z=rhAPykIE zQsOY8^lS5_M(?KAvHA2oFaX`#lN&gww}F|heM}w)6TozPmV7JcHQAonbxIj|9|U#u z(Q|I)p^*~c+WhUf#lq}4^|ud0pqH*>wP0qN>H>Amf_SFgJGfcwRZI%XF$l`T_|ZJ~ zG~vBg>cVWB!UA@)^d1p7KoSzV!x%$yWNS=bVj1vc8#NaHy9T{h*8-E7m?;k#N*gnQ zKY$k%l;VdHw*QpC22StE0rl91ZUGiCfJ$m6`+A2wfeE}*vYb2@K!24U={3Qln2KpM z(gv%LCXMuSFatuR^LhUUq~t8R?n7zZ3Nv(wnx=q876o>uh^QoJ-@~RmF=r1h7X)M;UJ)^1XO z9E{wN69xH#6i<6tml6u+SNiQ^2URKZz%M1GO*7o+KDDsg%mItEi8i#hNtUsPOO;3{ zYCZ1zJf7d7u#dmhS|Vw800A%|$&%EZKigZ;? z`3QwT1+1FNCj&U#KvDLtIz`HGY7;PWtlZi%j^biqubf?Gia;QfFa&!Uf+6L8atOVl zBaVr`QUXt?8I(5h_`ALuY}UVsRtD+5d_lVI%1bG`IOxX59)Su2z7UH!1DUXOS=qY( zUIix^VR_bM*ekh7=fiM2>4bUlq+}%pH{Frx`sgkl%mZz(d$}Vc<@40;B1eNtEVpFy zgb>z=OhbX?(cdsaS^&%C1sSViA%ir2A)QRO>fbI2lYEI+7i;2O&QrvR(E#lphsyLR zFfoP;N00+GyV+fFTG5v#DN4I$oT(KlXa@z=Ad)Y_czJ3*G0?wUYza1LFl>o)5LhEn zm_|jf2mq=MiizOYM++=@yg=7(aibJbTJlD)&6eln;WBf^4+N(xVyk+5!?EA?^zGT= zM}?S5Fic!JJnQ^ThRz_>mH{mNe+ALX>va}mK0BsdkOQX#`;aioMq?64jo$)(MRe-o0YsWE zf@9Wv?_qeM#b;*012vx1lXtB>)F6pAJf9R;K(2-1lF!v%?EJ+6}fU4zXE z%nM9a82lK=0YC@of&!vhk~_`An2ZnelhJ?X;E!T04L;Q1*-y6n$OryR_X1vl-?qzu zB|R!AlA|b+vK4xNwhfbAJI8f##nW-x4g~V(A~TGFUpCod77v2BXWWs3&!_}6zj4Q2 zVJYf--v;=?0OFj!eizY6P+Ab~lxfr&>YgANNfT=I0f2xvs|7m6#o@@fVb~+tJ5-5q z>R@lKifnHcYpZteGPrnLuLSAN0tNQ|@Mm!q%NA8q^;Lwz6NG= zMAa6$!{dJ_esmOQy(W&wEQ5mo!(g!Nzufgbm<9+Y|H^pn3=oT(6jZmbixWb3>lXB? zteCFFaC5Dq>;-1>T`3+Z{17@3)xzi9P9}x~r_5riSt6tL7#zRm@ByxFiNhN0%rX93 zA;4vmV&|!3U^!9AM-sE!wfH>kCkFMzB8F9!ZFEh#O11=axvet868cym`4F-iyqV8& zPX)Lcj#!(k&>&NSE=N=|oKC|f{F9U3tvd^Xr{-ac{Q^k=zjJWSI^&XIb9~IX8SH-c zc}_yX6{D{Q-!UbkO9dNdr=TdqIB;s5R((xFyCX&--8pdGIX0k|r#Co`<^c$3M;$5k z@&Y8@<>sxUZ}&;x-6&L3Ha^?`G8`w?r3Nj}wGhMBmhq6BZp0k#*fdh1m&z!o2*g=# zSU@ICasjNEk#l`cseq(v?i|?m7G3)_U{a)HhKBK!_)(&gUjd2}Vz(3p{?wT7;jiLN zlj#Hh}-kfCKpA zFt0@0X32&ODd&jF&0g<;UvVYD*vF#>@rDn)3LOj*!rJXjrELz; zZQuhX!~Pit=|x(YaR;QD(MqTPJ+F4)2_N7@!Wd$qXsSf3=9#>1Pzb<=djhQbaLSWH z%^=UioOWr>bz2_Mdv!?D-V`4^34>xb!2x0__|^+IAIv%}9qpO+4c^*%XVZLuNOPz8 z2^bL^u>%+5w6<|xz=}e-PSBN$StZ6XPK0c3Z2z|>%tygrZ3eP-dAcI|st13t>VsgK z#llVXSwE6El6E=sa2*)lZ3kFrENe3cQ&=rXwN~ZWwt6@Hkg#&^H=;psRf8HZ$z#w+YBO7jsfMQ45;jC1zB40TVG_GoV=HG_tYhTL70a?V{L3G9l?4 z@4aLUrM@5=J#niP43U}S?F)}9RRE0>mW_|QEg7%Xc$cq(u1#U5a#KL*b~wi1NyF-p zlL6&AjV;lg7-#)Ew~*B$7g+GPayl(+UfZhQPV;da1_tBY0LGFsIcWm9`wqF`2ci)Z zo8G_#eo@@u)!V6@kOWEmY>g2_&)%^@$U{#dCk`Nj?C?<)hdY!8r%f%;oB;z|6Sd$; z!jtHMT#+ag%OHHQgZ!US-Xd~o@9^e!(Ex=!YS(9<`sr>N3jVA}g${J5z=$S8*n_S=LA}(#;W}kIhbKK^y8@g0o^g5Dd+U3ss~u*6+~3QRRMErKxtP0j&$I{9)zc1 zR`MIOZpAn!~JkkqzrUJor!si{yR38qYm1(!!#YEx6uElEl zseI!w?OC-gI|mhoQ=afKhmpIabIGP`*KcnyN*yrO&jTugp}=v#Yy+}$&$O>&sIN98 zy=*kAG};Ib)D4zW*u*CYT%Aa4V+0!h+wdG6%!o;ua7B(%?`F2zm?k(1pvZh`O+-tB zg93nK#uU}I8e8yzs|Oa{y5YA?dti?K@Hxx2>73ODXb z(`)L0#cg*&>XR0=kpMjAu`4>ih-a&;(?z{6eJD!LG_(Z|zDnzA;UUh40szIMPMWHd zu$;PG>{vMQh}x1I1a1(5iotQXiSW~Fwg!Q1PzIj53+O#l`|~W}bmjk0xm<0-;)XEo zSX0hwC<29s5f-8nApgzoapNXw9fa3aY;Xd8nQ_K!rOtjgSO?&qtf!RGOF=U|owiLY zN299Pao5GkXwy%%MoIWJhXj@T=7v8`mvcNsHnQnCTViA)sPGZOjNk#zB16~N(go9b z``8T6wFKs?)WB?wIBsXoIyEML>d`DitpmJM!Ul2mUsZ29#0jVfBa-In1hj~OB4v>2 zPTcB;WCg}SBLfj7<_Mym!11v3_Y-rA=}P8xdV>zZBVMGJX$=V%!UIw0I{R>@k=g>K zxn7|r_NnT^Ri`fHM61Jo*1R+V`9H!Ce(PX1AMqytx#fd=+g)8XI@2ly7Zq~b8YMi`(? zeV4Hq1Bt!OYSwTQ9|nl0qSCA28gF!6?^*)~>BYAq>+MiUq$4khh(`M#k^m?E;lW2% zh2)2pytia4u0mWg5d1>XTpr7OsvC8%_y@6tA~_r^o&S7drZS@DBYK54DNd&$eju@- zVhh=N>;*~)p-1_q>A2bI#OI%4U6%>Kf=R`n#=_hPX>pho=moF|7>~RE4FyO$^vAU) zIk)$DiL6{iA~3?&VbNhS(*s^$7@8Z7e)EQ{FelVvZ=+36H|v_Vt*Bm^Z!W3Z(*}d6 zTCUHWvZKWPca}IR0TR5>E=uKj{@D#7G&2?Mmju%1WYx}&W8X)_$ycCW;JQ9_?KUyf zZJS>t>2$GUd;&2dkbY%ZGxnwRstWC*GogF@Qmv!!nG|!RDFC^m1^|51$YaBHlIYm1 zX%0HhPlGp81gC|2IahR-rSMl4Z3H#{Kxw4k>E-0r^Epyd1~$!uoGv-d;01CLD`A8t zT>(4)ZN!UqGJXXkeBPX>_nUHPvO2|za$QsWzt8Zo3k11Cot3#i^w-lyzVw&K$ELgK zq-rF^lQ9^1V`%=D_y-@_kQconME6e*`>{Tuo|$UGZW9-;DqqTbM&{VjoCKJ)GOAvA zfUlz(huv7k;?EoYVX7IB-va}P!IPve%K-!(uMh<|Y=@Ktp`Ov*LdCqJ90`vH=QwW; z8|CDbTLLjzrO}iu*bBfW%)0dA=I}4B+j{WnFU{4q0R}(Yr~&wh4YBnx+M@|uzNP=F zC*aCw5B_Jf-J9gGqi9}baR-m>DBO*+A`&fF4dqHk;Blw&R0ze%1+%H!9F@U=iU-7s zQVb=nZpeiGa>!QCMF2N`SaKzY;sPfk_|jnxreasb+ZsQ=nC>AbTMWNOX#m;7>~G_l6Mw&0 z7nd%%vq`4^zVF6pqyO$GDJC-*I|r6R?qlKf{%tL;FdhuxcPRT$kBP7mvXfwY@gz9O z;{-fVv%nBzT|EGY#v^OqHc$!nm_dxP0H+pa3q@9A)0ER(=9Ic_7d5Q25{wpD3`QwgYL*Ugf|s zM@v@~VO;a2;ToHkBTIGYxb{k?QHlE~A^>TTv4i?%ZqfM7+p>%tk0FLia>!b*%f<@} zdsQFD1qWI`CEMjn6Vd4wQy|zjnFv|MYGN8=28L)R!E4Szv&jij> zec#yI4mEls(FAw$w@sa7^WT=xQyS%EB|Kf$*|^JJ z%%Mo4LuXQ~N(9Pu3x;u4z~UjV`I~~&b|;w9Y%PYvermhf%)Xg8)dsC`KB$moHTEo^ zk+LO}y-8ZfzPI7IH5E24ml1W7QUK`hU>W3JUx(?i{*cTj@@PtV##W++&K5TG7AM5J zVga_|>(>n&XTtoIJOU1U8?(U!ykg0*7ypU#_RJzzY6Y1Nw+-(g?@TH!*pH&fC-B7M zlf#Q!HKkPV_y)JnEFvWT zDD?bQ4mDb3b}t#$X4CEV8`GtfaxsBy;RLpI0MNPFsh*7$(H-3L4_94ozUBK?3t|5h z!QG7%Ck8J4ko8(+1DfM?><5zDRdlBb39y^kP;;)!vR`D9$^a@FijKMCzjRO`o!N@W zbRtsSxA2wJGGcQZT35rv#4k*C|+GkLVvItUl)n8 zJ0qIq$_eHXfC597P&6rm9Ii5cV?EKC#bs_l2(lfp=^4?USmq6!3k9;?Urpi(nM+-! zWH}WOLZ`7nWnB@^tn(hM%fUI@X9D$<3sCHg5H4f#g9mqlbUzQUdR~gCYp@&UX*)#a z_XkhJ+EfRs!jtBEuVXOje>7?7L_HbLAyW7!+Fgi?bpStZk!`2Oi-XAPeTsSuFo-bOd9L>{z&% zBPOik3CR2n@}P(w(n`LJ)kkCTW9Q`1_5!M)(~*GXh5XH5P1XCzi=vs~5w75`)H;=L z1Pm@T-vhSHy;awVR`tU|NXQb(gSY0}bm@PtDmX>t+V)wEF92LZ2FTz(Ah3+tBwNq{ zC|^ZF673Gm(Dk>5+b8hTW(4<_bqz_a-QtW(v$CX$M4Br|Bi>Lcz+!=>;1+L71qAf4 zSU2RH-*`>0=fc89N3iQ|l2=OPIRBj9fcw&xwFS|KJWlF}J$SSq!EYr{9XN_$#xO}~ z2s%0{iKjJ4+XZi9_cDax871NLaefmySvJ15JkNHT(yw^_UbV5O@BsN1Fp#u-1SeD4 zgj2Om1wwbVs}+HX_FUGh9vb#-eFZ4r_JAsCw6E}zHhXmK5xI<)d>xnbFG$K(!3OQ^ zQwIs-m2t5E@xP5pLYRvxmcZ&ikoXp9Y_+-gjy@GDI7j=^a>Ji${&C zxW?mMSaT#>phUoOKLKc^s$W~?y4(4DB0}>-E*5d+|Mc6Xa>D6U%qV}0AO~~u#s{6h zb#I3;gco(kEYqqHFJC1o`~FCD@7V(wxCO3KTX&e{$GxA|%_$H`ih3rV*Gje&3*5v} z&8Yk9%LU1RT*4ii&QI}8t)a0C@MjMU7ib&A);SiP5C9ilc?8n50wkDnMNHcuogWb4 zzuN4}$p9P8|L!V&AQJd#Q3cFAEVQRj2ppTv3Yc=-P~f*Zjo}TeymI}aBTq`t;0BtR zmD>mL0TPzr&1VcoPICKefOAMq65lN5BgsjE2L}%x>55r4SAwyBtc^8oc*ZGA=qdIl zX)zj^JpOPpq6hX=LxGagGDqv3mofi@B=5p&C9DH`N})KsO(#ac9sndA@rJkASSyNjeQ1iZj#?a$IXVd0~HTzd;V!s{>OfqzlGv zk!2)%Iz+S@4RNpN^N7CyS49j?NS`8OaR-g4t_9QiVwxW6O_Fp!gm+0F3_CQ;Gv1`K zf_W}JTL3A57~W#t#vehLBB=G`jg{478A7qYexkY-@#lW^gaF=jyWj3&f4QtAb9s@* z5u4hHu6{dwg=*!(PwCY=6bA*bj!<%R=9R@>0?CZ-4vln7(v`{k3veZ6iLURFG68aK z)iNGVJ>eThw2ZH^DYLDy7EJ`o0lLhe1!oNFivxGDAobi05tVB50K`KGWyZP5WQq96 zNr`0|GYU6P=?3Yuvya6Af>5x~N~rIo3lf$>AZWn|r4o<13>_8pF95OoqHeU1j$XC~ zYmPx4O;kCMH{-3Ui{%02b-^j5#|D-k;j^nz6gkt?a2M@>&mlc3$=-ff=P-24qCt3N z3k8)22o_2Jbyg?$I30ob3P=uvq2qN#9TvqUV4j7mWCFGrLQ)1+Fqz6uVyOwYx7uQt zLV@r!2uuo&r;bgH*FY2B+yXc16bAe6 zWrn{)74(VKHI-wf3Q{L(%6}aq-i7k54+A=R_p%fI|3?h$DEu`vZ&?y=##Qfn3b zI3Ga-Fa-v8=v+ai6tXLPTA_9S`?08UY1As789r!vfToGpTLz*S6dk~?<~2k1Fs#_; z?>{s?kB`W8BDlBj?)A1;YzH^eo}nC9=2>yf_ND*J5~L)rM{+QiMt7+-vKSM7Py`); z+*!-fl$NN_jhT>zTJiqZL+2^VRhP$>%)gNn^a4KCs*km6#~d@??(|ERPDkNGT)lFt zchW85gQ_i(bO6}@C#uuBSIEYDt?ENz-Yl)BFh3-($vZuD=w~c_IRGu>DLZd&>UfKh z`7}|@%krY(CcKzm0k)!!1aK6HFa*?t6!?8R-*EFmo$^7jfE2P_VccxP-TQR4ZQ%#* z`~V6R|9ph`k+8Wg_YeWxBQo(TyZ|G*+=M=hN^=%3m;la{Xbir+AU&EO0^rg`3Y8wp zj40^b$xF#)4^`SzR{{eQcU0%*p$9X3(5C=LlBJno@2aEIX$4|zT!pZzqyfnp!5f{s z(Zt9Od~dFkXC&k_2f|4Lv?FqU1%myU0xaJb!mKsUj)1npLUfZA7-#|=!+8tTqM0xsT6$K#U z7YZ(Tq6x}IvBOKICx)Z*L+dMwNDxRg9wdZVVE}h9udMc|%ueS6VXXrenOo?mq5|nR z`w)26tns=Mi2{*0F`%lp*$wyS3c%G-r%TagF5o@lbhGOQ=&#+3G61Jkg(z;Rq+mm( zTejW)Wuw(~y)XUrHp+&W&ZC3-j|LZ*+Nfq3i%tt;8NqfbJFVU%o333D{D5nP29X@JELC&;3LRm9hCP&k<8+ciS(uLb?It1J)wJCn*Yj`<^ZQXAh8 z@@0akq@FuMPGS?>js-2kD?7)_-i{YEOByU@d2k6t6<^LFLnQqn|R*}B$;R%q^9&> zJk9d-07}l!rUV1?_kq)x-WB=MP(;->i^1u>m$pDTKjjH`5RjX%1qZGD5>upB<3`JW z|4hU4=4!zMgr1vA9ASICBDX^X3eJUXNQ2`#0Bv@n+v@m zCbvHI`DaBTz1@bdheFw}cl)cm#p9wEqXBPN3<=2fg{nbPd~UVrNZMWrX9UYHrE96S zu~zsnOax{g?$`HAT;%2IYK_RLdl77100zG$={l+6#mXps-vtgkrh-Clt5Odh-)-#t ziosXTI?jgy?qkdC%^&Y+Bn7D0jC@C20+bE5>$Fd2I1Wi@LlRFv1{=%hCOHcW9RwFV z(DfLW1;L3&x};Gbz)r$bSptv#9hK`M#=i{X`UR&k65*vd4NAAGP_fYsr~d=@yzqO@ zyw_|sh;qBAs0A?|_v?9L@OF?-{={npnA`0kkT!{vN?Uy~(qv8BmjIqU64Xc;^W-g! zb6_j255@=5-w{aJC33+fTurVnA_w8waZUr$OFLv1wAaB#orJWR^(5?AY zK>}?JHgSSpO(*W!)A+VfPk9P3=3+$zs@*b95O$zv;vMN5_tv&q4By29 znB6lob?=zh!nLD2egWYQU2t8BiX+gz4ooZ!O1BIvoT2|qQ2K5>SPxsB&4Yw?PrUuPd z01l3hcFEeD_v+nIlMoI8Ru35qA1J58DcwED1D}va22y?ciLee^ zpalNFW~u@BUkB8<(>!`yVyI@3P5pS5 zOav#$X>!!xeAuorx;C4L;xc{ukpZl?d>_#^BJcORX$~Y z?T)Fj{l;LPf{EJQO9S5}0i_%2)HB>TRNzz-szx7HV~M$A<_7tfmVKizc?3xEtF3jx zXfe~E^J-~4K7lZh1tqa*&y-#>X0lf-dI8zqEH@y-YJsZkibypa5evpYz*qt8G43u+ zl^MTGhBPDgrAK(-VR3>umhX+zLn~0HEpRicPow z+59`kM7V1DE&|uhkrb>8Se51U0EPtpfz18-gd?xSR5>0u5Q)C|a|3O@7o`+V2z{D5 zSg^@l*KWq_t|{h)!)KgyJnO<4Xaysa{+g?HJo$jX3?NPHDiU>hL}%HvG2iBn%#Zmj z3eY1K3%iflZ*DF76EDJ&`@&C6UM8_bxMEkASL*fT_RYLzV9V{#`^E?6*;sJ}Eh(}TW2@^96$^qrJor*RD-=mcqGl9gUL!hR~s{&`; z*R}a+X7tEkmILY{RwT3>fDbnKmVXMhzSvJI#UR!y7!mjP5o?Grq6WjFR~b`i~$X7!ijX8Qb7u`m)*j} zzvPwpqpYA&ur9{%>VzKu{sMs8kxqucosZUFY0HH~{?tuR*Vk85TKDK?`;_yGu>nAY z5+C%wYdp{q+O{R@8OJ4($GY^Oy3qz6Ci_O?We2HrTE(H-MVQ)Lc?pF#guzi#oBK4j zSM#|?i=lUoQv_e?@Gn`EhN29L!1K67<@U1Jjn{C5dlI{8&_B2%a|hHGQRNTy#C$<2 ztii_nYRpjm5rWx?$@b7D!s`H~{{^fDadx}yLwCNo&bS_0(iuT?FsHzu|APt>uPXrO z#RfM5dbOCj(;@AGa&CqL(H?%(9WLctv)6tryN;jzX#_!iK_s+3J1gIyo#6-<2;5HwTdMs&0tx*gGK*Ga^on!#kp>Psv__2nlrM z>GnpL#{!dZBq6=o^h3{tcbsi0N9f!f{)h6-jsa}K9h|6OT?BJLtzAM_MDBS`y^G<) zFY4@P-KCkwdMPx3o2S^hxCa!KgTMC*)*h{0K98cbQTGcv6wSqAZ-h}&KM4Ur5eAN{ z7I2#X{tuu-rhhI|C@$A~$LYIBkdu`Ewd-uRfdm*Hmidyu^Qqu=xh+C%#Ur!3XL^#{ud6oj`Sy4h(2=rAh=`+?>I;buLAnBkp-Jp`QGb z?ge#r#&Tx0>Sw`Fe4V*~8ERUwH6qjxB|HR~p0W4Ut^;h618)T(Chr^3$XN-uw5bkX zNizbd?}+@m$Uk@?KLRin`nObIhriG5mikSp_vt!8!3Wo|j1$spA(g*cy9Qo^_#^9D zMb)VC7g!vTA&68B>6VnTQZOg z$BJSzI0FYzcpE3;MJd4|-VP&#~DJ%4WG)Jp1STzpIk04p<%^D`ab{TyIj8tt+%*i3VmD zXqbH8&!71?6x1w7=J_A_QmIvCm2*FD-*wpL<~No&xaZmslc+^TB!FKAe9*cRyB3QqK20 zZt;Y0yJQrj?FT~>3fs8q4vX%Oi$|GBCTnJr)~P?L*`oImCp&tysT3CllFGk3k%LN z76Z_3PKDhl-m}M_J%+at94~@VykbOTZhA#D*;uRBu%-|;v*+NccT`8nlC69 z;@p|AK8HR%x$hC>{sxUE9v23P=@`T4IrJ;K-{MSovoK;UZ@@QESi#fIfdl9JffoLb z*a2#t5Lv+$0?TrQImyp+~+ zHRRTkj0QiD)o9z?-h^i7Y3}o&yR~3M&5Ovu0XbN#m?*&o3<79H5CPwwH>|~MY`a|u zSd}rmD$uV#cqg-F#5)4h!vP{2^$)B9(BwpBF(5WKhE03lsDe$`zc2*4zepNU8Ud!} zq~g}AQv7l_*Cf$ZJyA>vGuFDJt1IX!UdL2-IH6zhqqyO z?e;AjZYqqV5r7!hTqzcXKbjUA{sKy8;BU_bDi{~Dr89s8`|sDxcN79ltvOD{e+t^> z)&%&tI%-9?GE6HYqH;hbm^faBj6hWhm}>my>XzKy9=x;prRs)U6&$}+3 zvSR|6Q{f=6Cy&}LreYzi(sTHu`8lePn+9}O1^`n)tiNbH47qd^f%DrOn`An3MPRF{ z!8&C6()^=TJ^4PHz{;0>{W`7iqb6 zIm!SV+=vNhjkY`cm@>+jqt&FNwDx@BijyZr_>-uLZ0G_WSTTB#IF6ob3jd^lLBc#a zMkITv92=FX?Xnr85d{F?{31!AE%$$l9?nluIYX84DW_1sT%Rbx8!ZC3M@RxV%ImYq0`_gs; zg0Kkulh*+IhCBV&l71w#T2Jtb>Y)8AXVpKuSV@YXeg2bW5z7NS2oBY$>~s%vq-};n zi+meS~ow%C&)4djw{-*(#$-v3ax>s3|vOY9M08v0Cu#>r-KT)rFZ4%ZL>a4CnMSuhV`H@PU)VFCi*ZmRd=+hXK0 zCr~v6Va?v{Rakay&=1WOtK)G7-x~yLI{K|<`xBBaxB3O%fMz48pPLA6a$9Fh!3h&lU{6=^i^|8N-0VqK4OI@4vj{qU{Ox3}OSjU2R*! z{GHMpEbdjlzppt-+w(O~5TaMp#dpnEE)fMA*<(!oza$If^$(O}2QI@a{n0DYtl}af z$1`~jtyconPqL&d05ZCz3u3%bj((Bn^jpJ(EhvV|X836oHu2JpYSj?#{h{LzDte z@*Rg^G7$5t&C_QF@4yEEW-P3QWp~E(o96XiyL|%v4WW{wN^jvz)Ek|d_Lp(`?RCd; zDsus6G#{*qlxhL>z1!ooE+;aWF%P>F$ztX{cU+nl&@FH{Kyrdg={*CG-1q_#Mk=U3 z(x+m8uvPb2-P;@^-S$~+ZR5yjQ+xrQ7}ZE*u;FYF1w|gwz4-f0Kp@qj&4&z#zNiV0 zEW87ZrEDbspn?e4M|Oztq8&Zch5CPb@Gbf=A*9_k2O0(NTXH(pV$E4scwCN7Q4f=S z;FBqMnP(H5;MluTuf7KiM__%mdZ*519>G~P(A_1*TQf&wfs?exEE{3|4a)`J*_=6?5{y0k*mRW;KM3QQT@tXHKOX`2MF;7xCt)Ew8!P@!DT z)q+(*uKpym(~o^t<|1nT2<`+;u*HKxtDPUE|6oV@`S?0w36%y1sLXkvHrG1f(b@rI zxKuEe1#`sMSC!x}n*wR3l>*f#V@^dPi3}7{O*8^Qu0cQ^#`kJ#h}uEEyo+UXF z>x^;S!S2w3UHAs*WyHF)&0?1H&TiYG)Z{B(DI2CD4xeZ^J-H zIz|((bOh_U;?vAbJpmiDhf*@3f;<2lR%hl%Zf)&<^Nae6#y_5VEjqEIC+ctXKMLQz zW8?+4kGq*x6={R+w`N`|Uuy+jz#EsXQRr+-KvtP8#03B$b|?I?i8dRO21x+VPVf;) zNm^+$q2Cw$E&6Q+5!nOOz&=daj}*u#$uPKmfgamy8KongW64bLH?$*%=XC>LLaZ^) z$rk|cLFD*qZ@|p)@DPYMY#lPz$pxT<}T(wYE(F}Lyby5p~-!LBH8TZDOduP0*d5)E2+&XeMS#!3ez zJqpuqt&0{}kS5$Ee9j%;G9; zk3RVu4N^EKF6jhd*bU{)oW0ZF{s`*Fbb{8r8lVcYXT(hhrXPhj_9_Hdc)u2~UZxM~ z6eC6;t!VyfI^y7K%Z-We;!J@%|jqM63 z?aKgs9s9qfkM7Kmp54>3)_) z`N^$RT6$T;o!fq;&e0g-FhuI+kN!`^#Xttyrz?&57nFH&LRvua4b|iiI&c6Su|r1s&|t)0Zs-?TT-h76mXqN2YGvbz+B;;YO_~Q*16(4~yVNxV zSmMCP@;NvAraT|8d+g<@2X1M!9wLc0P@C3&dQa0}x@kwv4}xZrnP> z@LvQ+8W>IuyqJ%R8webFJ{{Y_8-qWY6xCad-5z2>cvuET<{B&^#USR0_9+wy2`>H+}R8S=w2>(fO!V1NTJs1R7k9s)_x!S&-^0*lc9$?666 zU0}fDog^cg*h^x1l8RNyF?wuj->sG+WGj>NlVt<+#@Xx`r;H@zA(McGD^(k{)8GKs z&|f9FDkHQFAUp=n?7K3Bg$+t#=bpb40?g;iOz@oZWFh+(AoIB!zAVqQL|vlbZpY7+)OrfK!rBAwJOXkqA-kac3ud|oB`Y!34@AUlSEl~oGi`(Ex`i%GjcTt8r;^MV# zi3}K1RfNVEAy*s=yjlbw%HhNDGDbYZEMjl~3s25)32Kv(2T=FbJI2#M7$pZiR17HF zX1ch z(C-FCdJs`pb9aW2Uef^O-dmJGXLbdb?s7>B;N{$%lH#?$Y?1G;lNDMgwq7*29IiKl zY61h#PfaVK?nO>((hc9PtN(H6a=lzsbka41+B&=Zlp6=0{g0EN_W%|Qgwl&oA7MVs3zs6akKx70ell#Js z2D184E-*}nET#SG324ql@`}i5!{EVKWHkXs)dfRZcACx|ec(M%axwE~U~5s*k))kH zBXA@?QUU>m(MIaQ-}LI|E6=SK0PEX!=@Ke&jL~Rc!eLr@H1`KQPGmJQL16%Jy=!B) zU(HkL-jDT_)LyX^4SH8t_hM)G?FeF)ne zWw=x)bFU!XXgkFm7K7eml@AgtlCziNRfYm5l!J}EZk|@ak0n1gox}F|+&3MyWOC0$ zj^a8tT5$rfYx@K{!J)->%M`~q7xretq98(9vKA73Q~NjgeCq_o^rWT0{iQ@^EZ|0> z4rDzt*Mia7veE_)-Pq<`!x#fpJP!P7D(@<*MDVsTrv@ATy$K(lKMhO)cgN)u>HY;J zb7*{t`$)O5oOD?``D1>FC&eGD$ID=dsF~WtLE8k_6W4)=LyRfox8-7MrN{|R%o2!@ z{t6G&)IA!y5GVohk>Zp-m9nbb{cxH_S)iB_dGugE0a{8QAH`J_lBNM@YT|OiI0t0u zODt|W(&uB3bn!*wiFVi0N`=$zwc!S=b#v^DW5C7xy`7 zs+j_@7;l2}IWvrKPuBatMm#e_8M4`jl_PIa0u|nB?b!xDjY$w1w3nqhrykN~jjsSe zqXe~1VAKkd(K_f-+fN5w=!VlqF|w3&m8dugS~Vh4BPnCp!o@L!ko|P<)er&#xJLel zD3W3Rq;rTaG?c^h5;GlLCtThIRvY$&N zW7KxaVygx5!&wqu4-;gN%R&JrOy2s|z_xVx33z68ZS046^?L2YvoR+?rjl~p7*7niig`N7K5jQasU8G;^L$U$79y|m+`rtUn9&IfIzT}p$c$5j9LRsTywBD~$p*CE8+(ai<7J8wl4b=;o#|hZR`O>jwVe5_Dqg&Z+?*1>WHf zUOGIvfpv62!@Dhju%#=<23EjCj)|xy8-)Sg2GmWg|LlvM-;s;jW0pG2f#6t{kli@? z8C3Wv#2N)9uHB3NC*s&69VMbm9G2Cl&Dm4!N;W7%fhs(gufhdC2&4eO7UXz^7_isE zSR3`yF*MD~>?p7&kS&>5LwN=L@pTZ5R!IP#Xkfz{QEvSM8=N#lx!T}Vk1b-F<0k+M z5@HJTXytawlgS5#K#ODgkri<>vhw0eB2S7pt|b7fkrmTS5V%@J*ThkF3l;>n_SvGZzVCjS&^xP(T8}-Rf8>P%j#-D_-Y%K~e z*Ix(P>&rp`FuVm9Qqi83JCJ*CS3ptEzGp$ zM>+E{s$G(>m*nNJt=TJ49%Eeyn@~UKjxYo{3jbeh^YitkPtGTlV4DS9Hgii{5TL5g zKa`{m<9G)itCZ!gq(h-$<%cb~XZ{((A9G?b^G$2ns6KwVxzz-!9N8iT;Vll&S^7fg zID%OQ13_&MvkUE{=O1AqRDA{4*JjcjxArIPr_e{Gv#lGG*G|H7)}n^nc+Y8eC^Z79 z)5S~b=~`1H5Y$O!mGi=u#7`5oZFU#Uo+SEzV{rtpVf5^uz$ME&HeSl%g)>a*Dk75e z`Kr6wNHP|)br=I@`QN*fz@$p1tS1`ku|@I|6BRkJnyD5-tMA(4k}Lopgcl&XD@r=H zW~8);@J82;m16==gomIoxT`&uk`Ek)eCOFb zIx>}GcfX~P0(9A;4HcH!j9XLd&9s4 zuH6b>(_su8SX_x_I6VY5{z_*~Tf^X- z5|9A^ETo5Ys+m##0d|?Ng^!RC6ZHgDLeT3#RL^?z-Pcb{4ZJ|kv;v?Q1ir2!bIBrXl)=yBp0j6-W7bc{Jx%o-?{+?(PuiX>MJrY>c)=sVd&PA zTX#Js&3QH(%#O4kCawX&28My~;}!qe)J=ZbA5SBtv#WCaIG7DW`?-{GkYx(m2WjqsKN8@w}8wIhWC3uY+Bju{3DP#H1`r<NGKS1A*;;oJ**;Z1)9PjWmf`foO4O20{niqLHW*$Acft=CBQ6Bn75K zaD)Kj*+!~4LCC%slw4|)FGCon#nw0(qp=%n+p$(;)QABfZbLpfJX$w3W?_8YpW7dA zzoqD^?o#7Vm;nX0j{5~}1*<2nzWm)^FdzREG$xlt7sab|ls1u`lY_W-0kQ_DPs-Wb z^lEyh0NFlW&S^v;76xhuzw|N-}y$Mz?0xp3utK&2SO1)94AV ziA0YTRKfrfiuhDm?N|#tDE20DA#P#(^l4uDb_VI6HKC8I$58^8z7whH7i#W)*mH@W zTqo=xFLRxS&Mm~{tbKJl4P68ex7v@>oZ@h4qSf=q7eZ$^+SfL7F5pgNY^65NiX#GS z3DqCD!h`upk@ypXq>MA5mm~eg+{8x=EdRLr35W;%&hD^0O!llpUK`QkT8!;|=D8f{ zZ$L1McVsCTM{NW|#p`V^jzdl7zzLvBwQyYrRC(jwZIP$4(g%~40OJN!i4ZdTmpQn^ z^O{+*01E*}taa{KQ3)ne=NoV;JkS6j>>OWE5P`G}{rfR#QQ>R5ltyW)x4E_1_*F_D zp_Bkesl1s!w~-4geTc zHgN6jFK8ra^O*+4&cR3Md9$6@eQbQ2v!GY6NFRtKwBeD9crK! z=rQzGA9Zp7-E@H20#&n`)an2;L~kpnH@wTA-* zY}Ti1{O!_gq1!5Z6UZ_c;QZ`eiu5(NcrTc^?x|$-2zP6E)&C z6vo(jh~p3cmdOLbU_Xqq*#eMU`Wsz%&;6&3~iqvGQW1(p=p0MF<2w(5Zyn zSEft1JB18*9r$P54+7lFC?HmhwcSQ+!}JDSF1j7C3gd_gk3dA!QB*u&rY|7@DCF5{ zJ=GJ^Hk<~n+>U$k=^P~}(E3L{Kte$$iqKQx00KehOxUS8l&AnU^8>>GzL8+}g#0Gv z;o(Wz5A|R)JM2-wbdxPjUjF>_(>e;<&-tM$!L{eEJ2cS%N=9*W9hnk3l+%&8iL}x}cet z>x-!n#x4hYOGaiRtl0h2F-?U>m>zxpV(GPSrf(Qn{9=t7VxT8Z} zmSa|SZdHlj)Z44DimGe-{tO4Ja(u$$E+mx(qt_?3g~2%u zE_|2Ye;wL-oQIi%Jgo8&!Szr_Tk80l3}#MQPwK)#aIuJv?nwzp01vBS=sYmod0#odr})rP%*4>sTc%^OT^(D12TNYh>@{W z-&!JT8OG2Jwj_UlsFQ?nJ^}>IJ>8{~2x_mpwiK-XX4DOUNFZzU<+Yz){ANka4B`b; z+8DUW0&BxBIjCmNF7rw@+9k<;VLk7)FFN3nxN8M$nD0%?CK@PgC{Jv!ekPfLh4==@1UO|WG9E}7z53SAkDL>hP>GC23-gwDrXnCs z-hKeJGKbu)pAJ=WNE`D>?5;YQ{(mB!RV9hTW+v?I`8@`!LcVW&C3U^Hifa^UA*y%r zqwlTnT2cl~=Hm78n9u}4z?V*Kfw9-D3Szon+I;RyIpb)vf@>mi{6fp5V`B&PP#qQ- ze9h(860&+Xn@w$0Uh51y(#IcFX6ytmF4F^n5bW=R+J(WFtULv%y&yL8Lc+XIN23#9 zp5`5;Fdqg)WOY#;@YTg-Vl)6`c?cGL4%J>B^@jm}4yE|g43z|xc~VY(b-rc|##Py> z5;83qbK)PA5^sJ1zz&15ADRH>R|mEmk~-_Ey?}ll!(0@lM&U;I-U{@q35ab4G>fwJRiss0bc?sd(mCqMzMmx^< zSK0V9h+hj@R`&sIq^Qq~9h@$#EzLB3t8VXKyt1!^6aOey_1h62&X@(RajT#B@U+`r z7fY$RFo6T@@j2rsk*v`!?x4!LhT8ycOQXPZ#v+?Nu#<*+QKp~az8~8-jT9bQzgGgG zX-ER7NSswRC|fhs5Yvzr$$(FpH_UbAjf}~ZKe!HSu5t(Yh)L$#D25T4mzTp1bS1aI zk*WOf>J1kAQo^i5P|*UUAC3IHH?r$^VeEh>{BYh5(7vErl_DynbRc;R!WIAxsC%UA zxkSitgv*<}yCk_T4U?ZhLuS@!z#JOKcCrF`YjJfs-)BV#4 zjvwhQGsXtRl0E0kqtMw9gzt-R2#MU6Vm_duk0R(^* z$6-Z@YmfpZA82ZYsw+w9_ZM1SS^WX%tM+B?AN=CAxfcXrLL6>ZRIc=?KaLG>1pyT< z+vWqZ%e`F|>3D4&i|e#s43*(123j3}7y9c|_ejr*Tzmx;xIkr;`mImLBX8w-{gBE| zfjiK)Hp-nh`lPTti^T%&1qe4-t*=?CY^-?-WjOgycz|J|hIhtZilRW{WubV{&JnUBsnSz0i1Her%9B zm+BrW@SX-J*{TSU742DLV0vjk#JE6cZa@0jTnU`W(aD=ynMwq;wKckIO`vmCTbNed z(-)V<$W1}6nf-((kHJG(U#9_U?!xt;fy%O@Fpki{?cgZa8NtW>{E_LGFO3Oiu+Lc9Ud10nNwSWAlE}yFDhQ337zX z0$MG+Vq}X3!gB!bC{srvxfdHWn3gyVVj^vs`*8!Qn0NFpG?V`@ z_cj1c9l?lzcgM0%{fx7^O!1c6dJ;z3a(4Mk_tvZ|rTGB)cEvk^n*&SyYsX$_K9SPW zJyj2So65q9q-Xm%uYm@?nrd|G@6`0kQecNz(E_?V| z76}UMj%);FH1!Gr1kB}k{ZnL>%Zc;)~!X zrxRYVRBr@U_UO$)Kp9b;eIWxS)@AUt{kJ{C2XHH~%oVDqW;L8Ak_PT1}Vt3DtFtzG;lV$g_ zo}RI&mkxjXKPT@I=$S9f$tK5w=#H{Y!`C&!b}3k5A!(En&m*vE#HOa zn}dL(Xa94qvKM;c!6MKivQ_{c9`51_i1VheN1UO=Iaf&V{0@wco}1;Y#6(tfTciNx z*X8p^9^F)Iavg$e!c8*FK6 zC%z$wUan~eIDBW`k=IS6ts~XDUb_P8vskJMG)?AJRWRrshlNnFIqng}0s#BHuq*ZH zJBtFZSbBL4lkC3b>Ioldh^(dj4cJCvj?W<&ToN^ezKj8xQ<~A8|1x$*Yjq1zjJ0C5 z1sNhp?Srz4gAhdBsfPiiohwys=~>6;O19e3uJ+f}@%2##JAMluN6uS?CAI}|D|BX3 zh_O7a>FT^1g+K-ox`KA{%9JKuz+d}fZ(x0B)4;u#<$Fpp zL(3Bl9Lxveq=dm67ZwXy%qOHwzWu6!$sF#cs=44u4zsRezDER<6w1I`s0DKF zu{eWhe{zo1JE{SYNEHwqXlG99Sk~}wMDVgax0SvYCm~#5g#w)~c6bNt!UQ5W`t@9n zvT)4L^}d4cN|9Lhd>R8-rvrD;ESR!U-;AzHH42OCPzNT4oQ6?ZOp9bi^%tw4eti8JRKG)=1GZNY<(a^ICElLbvjACb)$6X zTWXoK)V=`Ljvysx(of(*&o3w_{fX$|**kAGB4VVaCj2`N*p3CY8?Sy&lVu$Mfzdq* zh7Fqfi2dF%0E~~rlFr*c@JR=C=}a=iS;Sm75S$qyIKogUb2jMaPhK0*AgH!3*Q*AW zCF5STCFzu8$LHX_Tra(lnpcFKdt&k%7Sn?;@zw)WWsadANm#zoj^2R*1$?uiXWN3O z+kHyOhfuI?3JC?Kf~02vRTZGzd~%7xKip8vVShF|7t5Wl4g<2tZlMQfbQ_%{OvT&U zXazBZlX~1j;=F!~q-Tl0F+|qQ*z*Lk)PlAX5`;(ThKI3;>-OLstBtpRl6||#pl=FZ zMuGwgzg@anjrg3A6t7HBo2)Jv}K%W zR5Dt0E*Y%-O~BRKh&KU8yd>_w9^88=ZnS^Ef5LKr)4^b~0DO0{;AQ`-ik|{(!4|)| zi+}pRMP$U?MC3C`)Eq$tug4od@k0iU#v}s$F%}N{f*XW%`_u19U>3Ggx1LypPq+?y za6LJ>C%pl-4CSNsl_zsT2)mqAFGkKob_RGtJ6>wSu@_|%gq;PGRk2!Ucg&nV1(4Kh zEE!s0J|S~*F|}YZVTn|cSizq z%23al^k@K`o}IPR+#SWPJ=@E6Mh{(M%q5-}%IF1YF{W|75a0|8i-NS1a1{EJAwYJ2 zt>(7o62403FewB2ybo(gR-z2dh?wy)I}qGX^FP7gV5&e{f-CUQCaHotmRgM%s-J{m}zibqI~mr)y90(hipjR<^2f6pHo$%Ox=XMF?K`BgS`YW~4-kBIzP3dW(d0)lV*;nfPidt(hTTVV%W77iw5BTtvBfiTWH z8qK^UtoL&fr;tEaG_zGSV4VgkW@^Xf%qBna?KZszNzG^{x7#qOKqnAHpOZ!<4%G%s zyU^w`Z<_Y{?3e5^jC(sORUX})aqQW+QWe(I4@d#7ZJlm0_a#J$9c@G1UsGZ`d#r1T zPZzoC9tfuS=LQ3!;JG*wT^0NusuYd7x)UYSa8w13jeNB-a{} z9&1{lmnFw8rhQMov$Q5sS_&k$fAPVk;N}FbyUI{b5qDkhL4Qf7sDO)FNYDGvlMd1M z^t47rt%(LH5d;NLv;uWYFo^tbh^Prfj-+r+Udi*|KhG3+$HO2r@|HYUpl53?;h6{e zm}Guv%mOdM%^(3Y2xA8q5PXLIxJu#Z{D+a_$UFpG#Ibx(H(A_=kBFma%?OL?5e}co zYMFAOm-alSQHulF186_|A*gwUcK(xx?cKh%yJ>2Ar)9$FMKQ$c8?prF3c#}L{@;85 zDu4eI@@{mr+D#cV4C?l4rE?cyVN3x|otq01p^^i^?Ub}W?~AouEX7I2eUGQGjl?Ci_k-feXm*fGBnn>SP2#>-U+vQdQ>Or8q zi%maZYC8^3rO53xQTzf~U&1cMjjLxKeD8*OLvfPYWI7hftYC5ag!$mB-Tnoc2eAwQ zy5i;(cn5)$`T@M7ceSm#cH$mj)KrpxFz^Mp-3Q!h(#mt<(z<{#6#!BUE5)(#t8vZX z6?)?FTz>;O6|lJy_Y97kSgbP9D0<4I0E8Uyr-mQe(1YEaJ+TG4cv0U{AtFv>wUKY@>WMOA(rUHe$UCvlAQ9`d?jC?Dz+e zl}h6JiunXeMBxv=Uh0or>5rU7+E^wy^S9Md5(WoM-Eb+bPQOZZtlOqjg#EgmX%oa6 zziyul8mn)A^fCr0p|`GnBt(SW@M>ho$g7=jpA{eb^8Oi*TnJ5|jK3CYD z?diqyIB9oPzcOK#$8`l0zO{9;{TDS4aip6GK=T?cWFaHyy)&di+T$R`Y(E0{QIztH zFQ2()f3oZy8KP`I0U;qn<^)s|CZB|DZjS{xfgNTpeU}jMr{y(n!O%%pXol43>>Hes zNbPfvS1kokML+av*TwCb&-V#Arp5?EKkRm~qa8Hg2}s(`Oz#A?`?Q0zbokZTUdS6)!pQ>cA2TQ~JUL)O*+>663u2iym$K8M^*pyeldXPtOz{SU z9h3`P{SJo(W|ZL4dr$c(%{mI7x`ceE8<9efffENuzmdZ7KlvrrDqy{!1OfC21YZTK!W%aDJs;E2*;TtMMFrG(L5=`q00QV(5UCYDVOX|4UAurR5;K!M^oT2mQYdyXrR}Ta# zb0z~NuU2fjfCvbbZ$Fr#HT_#TGUZBLt#-$eo`nGubrOCJ2&u+FTz{AC1OH2^LqfEK%tV#V`wZ&%Z*QCv9@YS%D4MjlRhBz^&njT)*b$1BFq zDLRJoexR8*g@yY&ec1vlztK4uhbsia)h`%?_9jPtEJoND;_1|kg=Tb1=j(bKV+J=G zJM#v@=H^S7wPDv%L$#_21-uCc*WggEGq!q-NH-j2$>UPjdrbHDAyh@7?m*pmX$tcC9=`VE-(kno^=)W zkOkd}n{Nxyn^r&!t5!~5nR85_5A;(g*yRVFkhP9EwRLhn;My(F`kfic&#G*}uh;?;}}Fw+C*m@U#FOs@y0ThRx}yE&*0;L_$&?@#jQXM0e&)Zc_( z90s_wtfnh^%V!4kBZoZfU8eEBHRL4H$slsc*l^$)X)K^oGY!vy+FJr0Qa|O83A0l9 zy|yc-+uN>oaSlh46CIVSCt!|$D=q_}D!zrwS}!2e6I|A_YLVIZ*$e%mv5N-0hkolO z=xhW+GHli3PcM6~ylBiNIz;Ar^6$!LWiw)*N|y!ac&PvoZHoyPr0)o`>zdMQaAFCe zFNL7~i%`xvPuO7sjbjeP{sDK!&1F1TgcS6 zaDWmzvP%V4k?Ihi74_s0nCj@bnh*O-oDKl`aHsjsvm>}{iZuerdixW><4;z&wM>-X zPnRZ}FnwE9U6qE99gm~bB?AL#c?K{we`)S=+r^m?cvIePDu_d{uRvV51xz4N3OtDXq@- zS6TrC8B71sNcYw-D>ZgEzMFM;GJMw#ZB3_{+#aP8v|IvD-SE$Bqlf0Y3o-q)M9=k3 z?gTMFtijdK8^8?RyRrdhIqp&>GxE>fj<@|OFoEYw`x8!<5&MjR3wrvT>C*=6R#on+ zVyJCTceWrEbOY{L2=y|R7(HC`aP0P{KWEUC@Qy-@>rw(Fn zc|I9pCb|GFwk3uF#Wi)2Fh{l6D=3D(pM05xe#e+1L3UrAV9^0~B$ZFo=9vN+!bmXC zMr|*s4H32GI5}O`GX`RNS)Bl40vym!s#JAh(e_*r8(yv1rCu*8;Pn|%<|G!(;e`Qf z=;}~ORNn{(dTx9(C!qeOoixdq3%ZBrX$EM&ln?{&*2r_g!GO2{so0wwfTyryNpxMj zyeL3L3k+|A{J{jx^u<;dRqBCC8RSi%msiEQ`gIfxCAG^&2N|_Snsx^r@0O<*Dgki` z{Aa7vig*&Y!p;N$9q9|dHn020cB%olrCujgPQk5_e>TOBGrUm*GxjX^)N_-7yR>;` z%*_CH=b3*9U%})U9y$|ohs95ZGWBDL0b++YueZ?#^ z9t&HRxNz*E^{^=&lf4Fb2cHM4=dd42 zo>F$x-83Jzh%2_GeE1oj6T||oGlDm{(JTQ#svVdURO<@rab2&UyB?5}nkX`^i zHcZ;BuAvkC;)+2fD5t`r^Uv*v9wvv(4|}Os87BgN<{D60-yxNHdpaWG>81G4RcqN_ zGngG$KV8o-F z*+c`@VA|Zm{1$Dg;?@SV%(*9wa7FuIDkMd)>o&n^lpP0FTQ0lBcd z-5sotm8SlKqAC2$+*7hn1~XBnmOD9YEN}&AtYlH*-5 z6~CN~k>BWH<9XK{<6Y{fi0TH<4USN8T0xf88=hUo2PeBs-`YN^bti$i#(IvF#AXAj z%WWq%$6E~~%9VnIH|!J$jLd8u%4t%e7&$LIzpnze6@ODE@3xnQ{Sq*M=>>dRzRs8& z;;z_ld6V zJ``AIfzmGCFlC;1|E+%2A;!-rM5zKfsgJ9vsvg?N9QBS-Ht-h?(b>$K>K)@dPb?F+ z_oV@w-EUrUSQ$`KL~&PYOqg!kFLr)5SblqhymFTsR?KUqhQmh2sFr3OAkqUAB3He6 zlh)&~TDrV%X3i2Eic>t4TBM4w6;vda>AL{<_-*v^lla;jdSx%4jeBAPH?b~+YFC%=G?@p@C$2(*>AcLMZyZn5gZ!7pm|_)bIr_@rLx2xgv5*ZZe%N z;FcZ1nQ$rN2~lQVB>2!IrRoAK_sG3Qm<~DTfY)#n3{{2(uO!kLC&G409s%^3_-h7a z{C8eV!eNef<4bC0+Pev%Z9zNux40_J)!O;D6!HW_2ugyfdfHwV1HBl`j>tz(EsJRt zlLX|}{V3MfluZFkN1rypzlF)YnWUtcoS_wGg+st2F20IGqWy};r-uM^dbxjRw#cF8 zArIhzqLJ4!_JRCZhvoI>&-qjB6_x`ga_u#xtXnn|bkPd~;3BpNFPsK(pbL}`YVDV-0v0;4LJm>U0{v-R9z;zRg6wfJVA24r>tIkv zo|>~-`!iQ^Tm)P}<{RdiVtrb2W?9~UYCr(6Xo&M>LDuYUVD#)g$aY*?4B)D0v98R2 z6;D*w`!E0!Wq$(KAB#49G$}L_Le#X+CPi|ldAH(9?Q?z54Y~xJeUS1V3b_u@Z*EB3 zN=y6M0;gQbkGu47gws&>;gSQSd_0S3*Qh^%GR7xg@(G8wO+73R!*vGce0d)(lA#98 z&Ign+K`vdvpYHFlHk0^cD)8>vU@>;+a(ZddScM0-8x_|FEF{Pe@MTt>KA2|sTW>~h ziU#oE`d%uV%XJ5}<@+LRGXH0OV3p7jjJ)NkWXA!)<*Ox0eQIu|la&U7TJn^7?`+-= zSCE2^LMk6gx4}gF0ehXptI(!$ycGrXE?HigIZ|itSbCc@2Lj|g+92w?P}vbLac8!} zfmZ|oJ6{6NzW1ci?v$u0+7HO1{_klG3}~_v^S##0`;G!IGCXo}*`9yt60ZAV= zB-~*jYV+3EoFN7U_;;yAYW#OK6@L05WDNCOd;q7Y2}^sGbFgUF&^`77&f_<+9dOBt zahi%BZZK1FnfUAK5!eN7(uD$2$8suoO7ek~pg9zg^TO*dfSNGw? zM40daga%tLg!pU25VV>cDJ=ukP(bf&D`Qtyn5LnDlOC-EQ@!*&E%UzJx0ruLA=V>( z8)UCpo>3cB$QGdwoBsj_t(Je)7lAGt9qm&7n_XmgOCE84w@A-nn4kKJfcOxVvDhWHMOZaPRm~g z=b}kz;;C%$fR^WN5g`ARH8<83#~k`jc=UFi*iBIc1H}YQl2sv!pH5}Bl|>*b#r2s_ z`HRWXldTE*XBNE$k`&3CXPYTwY^pzwoqDMhI8)%I9h6wl2x#8(ykATPK}1kW`p*AM zW2bZo%GSmG;#5h?;o)^*5AnN`ElZpPTEif<*GW-MtcE#u?Ixmyuh*drT$Ugx%<@Dp zw`;!wZ?DcFtG%MC^33BoQ9p9B?_<74%)|91NYZ^2U7%XVYJ^=czm2h}E! z9OiEwIE@Q>m{sV(11HG`vaF>#1HDx*u4~>2eT0FX-3?&MIQdv6Hp)+VG|^H4qNI$t zn#U&V#jxo})a3w-B}aKJ0twvoZR6IG;7)M>h=%+J{KnHBj(mplh3NbLq`5>7#b81LDv zt@UpMa}t0)-xWZ~@-0e$Wlwwu&tnlH+nOQh4n$>RQoB<7I5IH4+3VaNl3a9ls@D<* zXeLIC@*sg{`AIF07`zceILQ#ws93>dLSy121VVEJWw-fOZ1}cZb1pe#CL@a?F#hY& z63RKoSOHuPo%Ey!Pp#c3&di-pwkuei=3Xzv`61b?-^bn?G_YNXx;jh*qcj3E)C*UU zCfqq1QHu~MGUYlb%w=CBSB)uVE!EsEhOQ)DhOQ-^ALv8}9M6f#ou{_j0I@;jG87RME$x!M2 z&eB?Z23jMSW2%xYZ$38$G81a$CV;?f(KD@@o-kuyf%R<*nYN0v$jMU>sz}uWyY^~- ziQ4>(9*$$y7y?N&XnO#`kxff0IIj)3>8O|n!KI`F0}n4EADq=7V~2D|WwZ*6u{xph z8!^d%Mz;(Ap}i1b z#uF%)nsAkCdefYJC`$1;%x3BZ?F8lMV$~&K)eN+Cle?3JLyiiWYk+UgRbaRY?H# zU@08DV2FROJI@;jr6*z$2Il)RvYK+Y((4{lsyHY*G(|v+i*56WCkqM!cJHP9pG2>* zdR31oa%LcCjG++zvqB?Q)fy<2E`bOIsv5@kP$2<>5K?g(TkmwpkkiHR@YA|&I zvjY|YQbn`U$OfLOBxN&kp1Djc(v)!)+F!M9ENWpCYdW$5%=~Qw$Rt5Fh_n-*c8rn{ zrHBh8^k{zXdzHibKyl#%rMc)08dV!2CYN*(kM!|Cj{e$WViK6JcbNFEi{K{)0IfL= zB?>vG;m3nHgY>{RnlBm?y*|91&{X4>n!3{haJNYi;jC=3TMXx{*vL7tmMTtLP-x|X z#F-3&&517p9S&pdq-O;>@v_V@olAv*W(##c96g(L14Xi%^s3+i*e9t8!<`q2XJlO0 z7z1l!8#R|<0t`hPzHN?lq;}2(i4@8saQrt;__Tvy^jC5ML>2S7uJ_IzpQ*@-7|84a z&ajFZtEG>ey#x0Yb6N}b>WK#eM5~JuVPG;JKh3oSA*L-EW6I{5oi{&W409o2f()VD z8ahsR{V?E@e7$nc$1YJ}IY}pQU>M~v=p`M*#))hP4bWh!mWB99mSzcW&`tpnxWT+FE1)L6Ono*1;1u|TI*O!W&0l?E2B=)n z=Y*w-SsAhwD)U9Ldw6sQwv_{}lw^oZ;=CiY{7Xz+nzuQ_V~YAV%ekgPaTQ+#bdv~m zSI8+D>)H0Sv3-a3`k$EgCAfj5C>4!wYXGwW0Wa^_i$ZtW7F)nn5h$~DII8-{@6Bed zIP@6)$=?I6fcZz zFHH6xQ`O6im!m;Q@xb1^8Ctjl5RiNaJ)dClYD}EwY1T^511$>7>Ku2+jt@zZ3niuj zS-nAUIPFG*;zhdK-ujUVmh42oU&EWyctduAT&5xiMUyn=0ls@@1_n%BMepOCDU1+0 z%@V6eHDil@3(kxJ!ytJhra|E{&HvBrPC2Oo1E(|w|8x#D_%fy< z5>ez9j9JBw#1goW;<{Y{TQ!FQsFySZmzHruJB4itA{lYuJE#L=4Tq+6qqLltTC(mz z>*cfoV<~;zfD4;%3#WjlH24Sw>`+pBnVtZFIEc?7a35a>@e9y-u-#(LQcQ!tF1f&5+dJ=(dqoPEOppj=pd#Cj4L1_s&<)( zTat+!L-{BIw}c47k(aRX-DHYLV`6OO$xN|O$@A-C4y`5Rzw(m;7Y+N|1OQ-Tqgm3) zkRgLRWhi6}#%?ECN&n|4*9DRW90N{K!^ZOW4_#eT3S8fEPSo+ovsA7R3s<#qF|Bt4 zmZR^&F&`7-7|bWMto(iRB%BrVU`&3atNjY{EANQ_AZs1Ho$W2! zdbZDZ5|%mw(QQYdF#>|9zg{An&6t{t{%4GjUB5T{Ks=Iy zYcD?owh%;+vdsptaVmWt?L>+2u^&F-$8IWU^F^g9*KV}|FQip|L-|KM*d?YNMlXzRt>zN70IF7~zozcBXW_kAS!drkxDYzHfPCdbX`nf~Tb z>zxO(TXY5oLwa%>F->tv6z69eW1F<2f|dcmo_W4FII#gNKL^Dl${g|V=3scMEU&?%q; zFspTD_dmxXW)MNn@Su-FqM|;G zmWrO?>N)c;Pd`Wl=yQS`qU{LN5DDS{$4s}|u_oV~&`@%$)-Mdw2Js%4wfcmjX`vr4 z&<$Y%^vO;=HH88RwwiW6VtBK~U`@1Nm;tL5efZ=$wGE&H_f7!~Uv9)z_YM35$d{Qx zFer0>R}TgSQDPxoVj?**Ao%5sB`}@s&X%9Zk{vf^RT?T2OzkTJmHG<~ zyX!qEfCQY$*YNM{i0R(F$*|FNR6xwxYeQ-WXt8jGXCe$8S+<2Jh#)_aQS8v=NhPT3 zdQs1Z=3o&A1N9BB^b|s!x9;u_G>_-D(uxKnuscSm_$(UyRYa%(NwX`j5~42|L-(%$ z{}cJm&0V`69%wN|m)#Q={A8j4sqQ{v4Dnovi+40OwD`i(Rrj8o z1a`}-JvV_c%TO->1b^pjJmGZq@0q`*{TIs@cUterFzu*=;gC&+Mm-RPAGLifMT^MQ9 zATBg*Q&1}=;!I|c$#6{h9y*q}J*M*yOxNrI)vg(Zi-EJ}AYCtK4e4w;T;`g{z7*bJ zNgI_D@+(^gD+3%Akft3KPUJC6%U?$Uw@L-3oJ<=mBcF0#%IygUtg~a?Ru2qkamUfuTqVqA^g%GXDVMOwg(Nh=cP8RhD5E?u`S+4Ic!NLkka z-^iBVBe>N@rmZJ@WpRA^a1U7SqAsxjL8=fI49Y+P_8{8M>)o{+!WT8ykp(jQ@sq;0 ziH($fcRSv`VQmu}|WxynIclJoBP8kQK~qj44zCl%W&gbw5b| zLha4-2Ay_{!Z}}kqND|b+S>c&ZK7uZL`J_n$A5qF-za6+H^PlXJjdWC=!QfruAOJ8 zQ*&Mf8kIqqw>_cx`&%6NED&qy`_HRB-2#LYioiw0sL@9RLk)ak1l$X?PM0t3K3QQv zI_=}ie={tlD@?lWE-0b{mAgzW;COMd!&gXyF6uHIO+Vnz9XNC#T`xh!1p?p&`pUy1 zL*2oyi{qKZ!Ozz`Q40Ax$@m617@In0Ds+AW4g8Fve5hfkl>uKDl8V4z;j;iW%DA{k zf7-%qkl|wn=-`9Z;|TCV#0`a5LdZplL$HgV1YJ{zYgz6|BO>hpd6~L$Q!g5?P`+uF z3Otlj6GzXdFn^{FTw21#3fWj%eo_HSP%II z#oH#HIHoI9XRvPb(KYgb_*_E$zwin?7$9{M2QU=h@!IXPAqlHU~ z^4QR)i{ti?T)Y>NSsVcYNW|#X_dB!#_WjeeFO%pZw zG*+39#MECC@lDnQ{Lk;LO>gb2`1K;wi^FV?N-{A5XCn&+I9+aud<^jfFpwP>+D9b| z3ZpQn1xFdkSu9)TjD}!n4&q&~J3F!gx@&l~;xAB*&4uRZ86xtu&)O8dS*4DFfqS)> z4GcsDfkAPt(}`ErMwNMbu^9HhgBJRWgydF6zU_PKtCsKpmGcw%XYZBn)`;p+@A4nj zbb*mo0Od@S2VInXD_QLXLKaK+)>9Uo+|lpx%fZ4|4l)?}e|6u5SM>IF+FNJ>cjZAX z$fes);H-u9>eCD4%kP$te7WXx(`NW0=g!#aOXA>}i zN~7-LZ?zHUtU%=g>?4}ICvO=8WT(vq^>tPjq_|G_1w5o#H@1GB;uXsj%#|YOvUsjM zPu9Nxo-x%}ufC*a(%_QqLt_)XL!(HYyFf%G=hMhrla~$xtVa7+lhY-_MiqNLhX_nK z*3UTO$ot7^F@T_Y)kA1}D_({MZgGo`C{awRgs~$!m`R{dbV;w0=)QUqdvMr2a|`W^us7OXxL= z;^oZ`BxP6wE8MvxQfol}p*8$MvuOqiTYnqPGa5_)#4r`+CVzdk z)Cl)R&Hbd#IW%737&iBSD?({uZn<{^v>y8|WXPt#4URW${IP}zud=&{_}cc{5dN{j z!cemShC+@b@ZCvOmd)B#{_#YmG{60~;K4n`MobVSn5sho@TNi|m_YB~dX#063apD! z)}uWe*!Gf<*4c#@2Vb|K=9W#w7ED-t|M8^>+T=k6pin*5i`0or?#B{>8XLZ z2zx>e$+>Le)h-I&QX_+!2Oaz<1~svJ4-(!1~t(P5m?nKQiGJd$+%q%8}_wwb8p8Dkb&%H2qu6rXi<3a)TUFuX2M=7v zYHEG7q!@J*w*yE6Tmd})-$WrLx}E~Duqu(1%cy){L#n@@Jri0*E<+#!nDa8`22!>c zdI`6P%qV%`oGQn%buwTIIQx{Fy`+u=)fqXWVx2oL9yvm;VWcA@ksc&gLbdHbLhoEz zwHBoZ0`*ZLsC!}xrd1ZucMY4 zr?{#XniT+Pz~{vp^KPIW#5FZL82Ey`VVM~MKu!&ct#>6j>cA<<$ad&{7S*Kh)SMJz zwNJGY!Vk6r!By2^7Ia-p-5Uh*2~;ygZmNlly>v>sg*k^5uSEm{CI=r=neZ5EWK-j0 zvV>tkC{Y`GUrZQXInUIv3Qgz*ybiTm64+cQ{Z)ik8N&(=kCsDNz z^cle(L*Vc=-P14uh2mk4Y94Sc_08PQ!F%IZh)?OqJI<^ZYVGT8IGMKtz~k)1ANSr= z9^Fz=yZnRjb$;x7u>6IW%o?lBSzMC>=Q$zFmhfl;yneiF|7x?F`q6w z-@0Bd-7}L0oGIL{+!NF$;x2A zoyM_9TWysIVdn!Gyn`R)^3hnCSMdGTn{cu zDBHaUr}%h~(zC-;9>d!0tRcq;NklvrGjv!;sVr1)SS~CCY1N??l58c?{U517!6R(o z%`iSJ@UZe`iF=EsZ^VcI9R-5@>~mW^^puALizm}cPeuiCE}K?_4@zn8WG_zzK~CFJ zQl{_a*1wS^YVSJacQCfZPvY+_X4z|t+*l+8h;6kE(7Eq!;?oHKsvm9=#!34(d=@09 z#wA_h8;(E*y0olB;A`IR5AAmaveFf&|M@z8WZX4#I42oc8odDow1QHE*Sjq!aYY%l zN)paN>6YEq;~=?QJc!@hAKE+uI^xm_@nOMgsNrPX8sT_gY+X{Of7W=ncxl2uz_b4;3*F^wxt>^qp2(gCe-=4 zM@VyFEdbn7jNkV1W+{#{d{0>Cx-S_O6S@io1*Ns04G)XyAETD)2jZ`iw-h?%@{@y| zq~!!uZZE6^UzlN3DO)8Y4{5xeO^wdCI#pp4D3MJq;(}sud$%jJb1D4(+ zvIeX>S$i;aKoyeXC%xbFRHE+&^{XoXkp+9j)an+VR7Fp_MAo0MAl=BuS!e>|IhWl8 z(z){R%VYWth9NJFCIzJRKDFj}y@V)9Bs>aKk4!`XAyVL;A&eN7lTChJls+C@M}-aU zEi_!Ogo_-&ciC454wffiJu?6Tw6I?_*y0IsR>`+(U)B#MS@DciHiPg6=Dk&rd}ZVe zw9o(Mw#`Y>FCfO3p)ArS1n{V)UJU&Ju=Axbk@1tZT>8qGhQXC%TjoM(^-mjP-7>FW zTb{=U4_BBzJlt$1+s%{vFU&_vH0x>QI}J%2?o%S56QYm>17+}eO|@P9dcQ5U{j=7Z zu*{PQ2RFPl0Crx)17GO?iPxoR@G|m$hj$cVkH*1K;Q^Cpq8U`mLR+`8c+<@?~2aD#7#q|?%l zRFHUtK+PBh6#Owahg;WN;du_>54%?;&`)U=K;UEiNb#MV(g215Cx!fRn{Gl>Xv0uZ z31yZUP3pb-smFDd`WwBpCXYh}+)S^%Zf0NDL`3&UUB5=qN~6?`+|)@9L>}~-lzi5L6Npgo6W39kpvD*bzJJyr-fpn6&R-UGR{@^vg6qC~e)yXDnt?l`w z@>!xwKXSTK%gf1BX<|u|7gX#a<^hDXw*6Zl{hXnQnBYT*2 zr80#Iw2`DbMsoETaSx=pj?(=_U$#Em{|*WUKe#D)U9>Lr{;R@=p2WfdIb)j% ze0&>*g`u_rgq*0TSAuz;!8kKc2*9uWS9d)Tuw0laicZ&uke%WI;mASDZMhEL3*j!- zH=`Tym9o<#z#2oqp2#pXIx58hA@*>?d8?WAuL1*yoZq8BxWu@JmSY*bZ3^VHr;+ak zwO*`}$W^yd7-6sLsfALkaIo}Y4b_@jQ^9pU=6_uQYOl{g#6j)VL+XcAzBsN$W(DM5 ze-Q}xehEU;DPTzlr$>wLKD#F^{gpu&bx7h`+{?NkWi!7)1Jhy4wKv29XWVRFLEEVX zX=)0E1SgZiN&Q-~Mz`+@R?-V#bWU9aF@Z6R1t8i-%#hjS`z&!^zSRr z>E)5u7l73OvGs>ko2!h!> za`)WBvLw0+A5*v!U@ux=mtC*|@!akM{Jpx@diyh5=v^we%mi;{zN#8byiSP~MP=00x+C>EgRhwRZa=dFt;+}P>$YNY z3S*-)8;pe)F?j|6Jho~O($k?OC0d69a&o0bZU66p01N)?!i~^niRmJ$_BdriMMx7( z`f*ADLXk;- zzgsJrjrYChwRh>2_b<9aMpYhrf|4U268~p-cOSS6e^<7=? z8OeJb8XdXP+bZ$y(l>g%Em@Y%s+n zfpkpK+JWFh7?a?rUzuRJm?$y;vb?b$TiFh}#5Lkbx~Mx=%UaS3$(crQ=}aN$ip>$yO7A z)B(8;AG#BGGC-CAbzu*7QnMIc1TV^XBUt+fQwKjMFofFV&7xgR*puf2q%x&WA%Vm% zGxo}pORR`9oMqTt`PV#)fHoI+&-0f8wRE;mG8EDZM8F}$_JdKl13d)@k!RlO4TM$k zuJbGbKzzQKFv9K9N`yoUns;@GIz<2d%u(R)+3d@7?iDZwyz@rIto6|jYxo~LVb?&P zd?N)Braxv+)v64@5H}M8-P_#8;KNiT2$Iq`@=98`(&ZNEi`G%J*J(b$+ zTN7MYiak7>1}05Q3MyBlgG_KdLDUN;QM+UWtfpz9bsfruuD!b2(csa7TK{JF)-$jxC%d!&+!s|^w!Xw)Rt7?3u6^cE1YzlCz zYzQ;eCJ;NR<;_Mu?D%P%m&_6c>JcYs!yW7A$Nr*b~yk_|W9nhy=0f)EXjkSD#sx=*$M%Gx< z_%&`6!m%4&+S%#{vSp_RyJsQ%6v3e1SoO}oT?D>BDE3Cm1U&v?A~|aWlX@85wEVXd zC{AE1Cj*Bll%!bBTFI=JkUY=Rk`5mSg&v-{cP#nh5!|{Avfxq3+Ugcv-Dk<^jyADd zq7PID3j%4AI?8(F2YfEq&nI)rcYcy`e0c?veTjzfa1lKRl@9S_ZYP7FEdAS3RrN^B zkxPsOVWorVm?(mrI9413+{a`QC_xlxM1RG*YLJt)C}4ZnlkOqvBX=41*p3bb3hBtI zku#tG#&BU8`5Ut`gSPjfT**#xlj2B!?@+DRw`C8_yn2F3lS>GksLrT^x3oPY4|G9CU869uPWN(yaOOO!*+e}boJ8=JG^i+KwfVsG1uN()8bp=~|S-(RX(Jl?&H z?LCnOCMP&$psc<|ZZfa))XV8`wZNjaTl4N@sGaZLU5Qu#!UU&oJ;ddh53?rZjkC)v zUzXkae}IlISgg_hs(-rzifWhG%=T&f539O?3f+te2U)>&fLL z>5TKJ+6a}A?+j9lqBwrztMW9B8VF{v-5E6ps5N9$rJyHzuSwE54jiYop4IUEp3ktR z!VkUFYwxQ4@3N6%=54xhaczhN1vn(OU@vJUKK(z{*S4Q3)M*!}2THiWAIAm} z7`SN%^N6`7x2eP%IQgPHhy30pDriP$IphY?`2J*fT0S!b?3ZD78f)9W zs%fP#8T=$us^~6&S3n>Hg~KeKv@bWFVobm*eD|D}3}hneBiin^`DUh@`AUfbk?+Kr zq;|MCf{p%08PvWAo187(?509;l?M_IaTRI@A!4Chr}Un3B*iX>>~S@cEKZe>Jg27e z;?n9r!R3Y7`e$9ZD&%%gG&ieaa3ORn8O<^B-{y_@OgKU7DJ)aRL6 zG4CMyp^SVb(;X<@fIwgP=4sCZEm5)%cHJ4Xh8Ct-nA&;2ji;=8>>*H>`5um3v9?SD z*gi!nJ^JkW*}e+{!g$eZnr)I4-P>n_MKCPHruvWo*+dmjj1d|S=;95>pB0-|l2pu$ zTRZ5u?jvwzi>e(5YR98{9<{v51H+>CD`{aS@!g%BP-4@$eKFbh1ijh>cm-<6%*kYl z0>r4S&PO;z;0oHrE!qR2liQ6+9tT$e^G+nAm6@~c3oE$!AYGQM#LyQZ0re@?{OqEq zevHiq%o|8?Mj{?VY$98Yk(w{wj`X+uQS!bFgu*&@6w;1E9R#1KQVopPt zWVJwfPC^W`Oe!R+{EXoPgWI6=Y11w5h3o7KiWTTr5cK>T@NFM>vD-~?<6hJR{*T-v z%rWMRPpCS7hC`YxtEknGZFru5*9Umbsg1Z7$<>MaP51T0PsL&xQ zll&}E2FtGi8AN)Jc%|uZ=ZP&aeW(k57^XDJYo#8oA!y6J0vY<#4KSbXFfq=22VO5OOL(b+0cZEz8(N4>rnC&gsr?xz%`!{mH z!hapyV0@wZu~Xg$uGhyHo&xngX-eZHwihf&@}_UYN9u&{@NVxP8mcq|HK~*lbFZ=a=(hwW62zmq`m8UhXJW7o4S!3*729SpUPu+|MRA&y1bCWW0 zh`kz?CcO5sso4^H4%nD$hlPUzq%eS%oxc^!6=V#wg*u~ASN49d2yVGS@k=1b6znDj zpg>t6!6p@ky?D2tO;s64YNzo=f@BXvWk8ggMaDM*WW6?mHr% zYM+?I;97rp>tSh-i`xD@0QOvbnuBNoWuE4}GGF;3(LepElVqQ^Q2WJ!g%9?N^@96+v06DhNI zATGr_!5BYai3lSfnP4CR;J~?jNcHu6itRq9zj;9~g z7=N(`F~(z5EZ*qrLlSIdPrM=lbvzvqR|QVAGv8M-BbSu6VjsoKPrAO6E2`hOMF3z1 zz$eVk`=VqD({sOv!6WbTi|N>HBsvXz^LKkf0|Gn&&MZ+bVFg{M{fKum?Tq!R@T{BF z26m5@sQoZizSr^vNGDsL0N7W@!{ec{*-jS^&zJYbBWtL@&dX}CASmttgXuAe53?>2 zO%e*yPtav5RzJ$=rZgTUYYV>7bMK!9Dh8NYK+eWGV2C|~=ACOzrXO?FqTrsISJd}=&lQ&7TLhqO>r@PNy#Cb?m@5>d;i?oWV;*!xu}-%*g+U5WNxPH z{+vaiEhK~=Oa@*Wtrmi(zvP?%7H)^S-(Tje(+E30;6; z5Ejn^7(0t>ljDSJCxUYD!M|TwAuJ==&LeZhsQ(nAM+aR29m#X-SIL-uoZbbKtL=pK zZF3GXci`upE5}a%5WHmsT*pMaOFuJNi>;rOTgHlg5(0Fs&OyUHJNQ6#%=9 z02P4agRheFH^z_!Y)%NK#u){FwX^qTR9B4w7IEpcIRC%i)SBUeP{Y63vjpMSKpNDV z#1v7RG5)s%PF>vUpgO5@)PBd%TV}k-rH`Ed1P#-Ls`_xak3csCR)}#s+{L#acj#I& ztVyp(zuALPA`jeWIJ*>yYM?R)MH$JcV>>&rkD=L}JFBUF{JQo0bmu}3V~pwiI~bG! zIR>o?yPv^7+iUK0B++p75oFPh!NY4%14f;gFgcF`%(@nJVUv6)+S%mTs(;AodI+%3 z(fbG)n$S@2@hM9OYg`YSBGr02qa`=ElfzdIR8EN4b!y<86aD^^AQo~5e!9q$js_d( zp&jJKtJIfB3Y2%1cs|Y2s?!@&L|4H9`Jq}6|9GAo2W_`M*nY_hVHw5Kv$aQQVW0Hv zbqvm_m~?l2BVF$Sv#!`~pWj+laAoBVbX}3shxy z3;IKl{JIM3-M!N^0193LP&ZR&8@OA5UkY0(Xu?$m2`%NB-7b}R)qk4oDxYQo{l;wY z7J0fCAAFy6Ri2s15yz{SxHc0q(tjZK*t4%He2Eb9^7O9pmxjARwah&8@gT7pWHwJlxG!{q(R~|4vq|Gpt1*y;&iqO4- z^Q^Q-vI$Q+B5?IjdvWvy*cPySuFu*L&-&C#lw6?xRxi; zLMb}0(a6C9H4vxt%{-U}tFw!(SsR;NTuk|3m29ohg5^WROeH=8nIGy?`EkG{g*eJO z^^2z45h@4HZO+gA^10haM5pow^Tz;uF|7j~>hQD-0Y%ag=H=brYtMLJmw}{t9`-K* z3DYQ93-|e2E*|G_h&+D$-%o2j+>uJoDAJz@}5kn0uUIQw=c`2??%1+5mPTI59}m@ZtErkY>#n(JPQ?k33WbTN=~Jv z**BOc!RiO(IA=)NX@zzMl7JVShD`i7;jddCt+%-7uZul8XiY>_mAMkJY@quBP`q^p zqq)BM&EJNBCKdu}GCt)_$VWwlCi0~--UW&Tp(wzDD4K$K5$oI* zyQ0#bRvT#qP4KnW4d5bsfeR~ccA!rvznQ1IA#$fROKHXc$j4#?#*BcOm0M|GsuJgK zhw&eD5SuVTA)4~xo&#PBebX1>J<<}QIXs>(8gz;DX2Zz?n#@Y;LSIh}fK5)TfByYPuwM0Q zSJ4cd&FHs9Go`lx|J4Uo$TRnomwKE9P3+IYvIqYK^InNT%~!z2Z7Z$=%iNK5{?l2m zC$qKW%aZR;`Kk6DmV5XalD3(6=}iFzX7BRbQ;9DGu1R@2t9ZipxlV0A4YKs@&KQv>T=;|V}D-$$Qk#^zK8 zRp$o+!_uClDl2VF{Fbt_;t)=2pGUR3LeDHr+!pf%mRNqd^jx5_j1h&dR&nYUYc>o| zys~%XxLuNrAHPfmDBlIVq25mJ`<%8u`gY19eM`*&Sa&jt>gLggx!DW>AY=;UerO7y ziRGANWE;>tBt547m0ym)Agj^akHKsPR|*zoC=k%64lcybRSDbK$Nd(2iABgS*8F;= z2u%tDOTTojxMCpoV)Im|W!C#jAFB+~%@( zUkuLliuLGql8}>e7f;s&`XU&*_fO&~Y;fUd2JLs^)J-W)#VET8QKxS^ZOc-m*J;^HtCHbrol}yOmFo$Tt zT^M`RLa^snOif>mK*WOI<@(G5${3%PJC5xO9xxJ`B5ny#^e@~faS7lJ?_ZA}@v+JV zqOFJO-ZFpEdTT8=&Omwo2WNi`;c~*c<}2iulM@yIUqGyc*<$rgbp6GOz9(-uQ@EpT z6qYUmTd0@R@PumuJ_F3(tYYWS4?U_a{X~EUu6!X^i@Y9;46Ds!FAh`!m~uKBN+0G0 zwCWv+Gw}mxU~nFvf8s7BNs|!}Np)Zc@L98Mk3mk!0ss62dgVL@sq4@m-2>G;#dBd0 zVy1`yT;WipFF{0eR73*#_Zr@L$Tzv?Hhe-tV2z=%_==+c&4-@4WVI*^u8 zN01PT{QO>R@tMJ)Ja?vGaD38zvsg#&%8sEsfKECh)(Qd#j5Y^GR`$?e=~)*FNu zQs5+Jh-ubfYCz}(RtY7G%T(XshDN-NGP?S5&C*qSecR?j%X39{F*SMtIE65nRYEUO zn)rC-HR#Nn7?(Q++-)VHBNChKNy2*s$^80L_(c4IssDR1fj=IV;J7pjpjH8eqqX=J zWB&C3Ys2keqDSP5N=hOO;-c@KBe2}_cO7)}qd3q(aIQ-Na3_}<>%5g$bsx%tbKBPy z;`aC0a^?D(K$S$c4rhe~tq?-xM#Gx?F+89qTJ`RCh(5;z;I=~oNR|-nzK2jFHIG}3zSG$*bFN$f5b!gUg&+G=m^ z7B87y&qInObJGU!idWEGn_E}`>tzRR*Fn*<*9G1;)C&lPQ+`s`X_>UAmPTcE=t%eh zuc^jE>upA>K+g@(6ts=}7-->=m;%}htMy*~>fLq*x(OUE{N7$h$zsKs41A_Kc<{6X zL|?6#0q(4All6E35#K|9dg`RA>&otL5^km~-{H%ZXulbFlP5CBv#^l|*P+xBVsUM& ziPf?s{%dQY-2GM{I*3$DVqWnx!_}k%-GJ=QrUy&jH_NCdS8!3EcKG)ZW=KjCeb3P1 z&0u{73=z}+?1on9=T_f=ucz+=2IKRzh-JP+js%3}8CnQD#XHeWDN1o@>O zE1iYGJ&lNiKi_ab{m(R@oBGODkb0mK|sF0udQ%{*)oxBv0#PL z11k1b%W9paNSVAn3prn|6`48RVwyFu*`j^r!_)oe1rR&80VqxiVNA`uwl_0a-Ksa8 z4AMG9B%JP(!Uf?z0-d@eaExZCkZ)AB`;pC+6)Bwn6w{*G{;w|D2IFiJ0!%kji2LQM z{}UkoaExc2cIrGl1a9i2+a6PGA1I|{05n^-fOzx2fy_~ttQk@inZRogRbu0kyKgAx38lqIrq-fh8xCf18GNOg@zVU@fufQ+>qal z8Q%K>IX`k>P8|lILRm*r0|3Q>3UyMoPM>q1?E&-li5@pQ_AJip7sO^hTr_J;1RuKU zBDHkEzG(b-VF?^iOz&0VwTdugwS%4=O@Lry0}q?Y3}w`9^P^SEl<+G{WmR**yVC28 zRg0cuI~8R{0F*5ByZES~nEcEbfuK>@Bk-3WzFVi`+yKCEnxrmq0LBGkFcK5tk4 zc0A!N;eNI&pFssA2u;4ZUEnD~1k6B^z$JZQ5K|nB+HAjociF;E=~XN4@Zg@qzvDD& z1!QJBZEV9wYU>ZjWaW$A%>b#;Q2j4^1RrBzcZ~u^2V%nZK377!>QD>T;=l(zcdM4$ ze-XAZdCH}KD_c5)0vmA4T>01pq;vHXB5jQ}Brj5AYzr%J+t_3vt$qpA2Dt>tXRLl9 zI<(pMuD#$sJY9*>#<0I=m{FVR^%nw?2d>8e_UQBv*HBr=EgXw=dp}aiJ`b zE@v@C12;>4CDj*Y9iZA}CXmnAn2_^4PY0d%eJwAeLkmuB1~u9yF}@9G&M_cF<#kxI zwg`O4$bZ}fuF$<5y-P*U1U7Uz0|pg30LX8#E3$Ry%d58K*nb-jM)G-0q_xd%U6NWfqqzPr1bN1=f)=QQ z?I{2zD?rQa6F`;J0t?0(V48Vfhe_*-1a?)HE61DLKC2)ATY#4vd_d^ib*n)x&yfAGd048TvZC*6CSTTjc2Q8X}{O0IKmC z?Pt7dKf~K-t$QlaRb0`2s}saUln7xjvFq&D1EQ7`5WCn~sA#hBqv{V};f#RN!F%r7 z0%%jTAF5w&2g`IU8EItL9giN4>X0m8wYv_CF|h7|Lpx*_!aeWb0@Tj~tt4?Dc~$() z8zNWy`i=DmkimZg6j8ndz2Ykl02VM7w(v78?NGNh)n^T&6~$z0vJ zmb;Ka`z+FIB~|2o07PQL337#!fJO^_j=GazB-%QgoA|TL6(lQvrP!>~1Vqu-d{Ba9 z%|*6ra-3k4m5jxhrx!q*{I@Tm5TUw30{jHI1*SwTr|X5l51H;FBRG-)-iBV=-;zEi z<0Ko61MPCj2wLgeH>Cw%VD`VcRvxF3AUtPJ&Yl2&LJ6dY1_8X6?Ar+@Jxo+rb}8Os zz3hud;p0&K=vkVCfm_sR1SjSB07>J0f@!e0^90<_>A^3oN7p_Elry^9gv=2T2DI5J ziEmw;zh#AE8L1wlGL7(b=XSNT1(!5(36@um$*M49fJ4~cm&uKTW2CJUt>o%6^LB0RfzWwFYrBYb_q| zZ(`GGtRG3ZDP_po45sHebFAZ*1G5vN(t;h#j&{|rq*IrKc`!L}ZzWJSfZne#M#8=* z2fc>N;+zV?O?vZ)ttx|2{I6ZOSgBq{LJ>C~?)JCj1M&zz(d9~G#s+4muw}s?Q)P$B zB1$>c!)d@}g>AOq0`)|pvJ?)0)GwW6nQi&BJrIoqC+&s-yD;fHR1VXo0&^MIF5%A= zZSQ!7^ZlB&z%*MFLm*H^UxGTSTTTG`1sJmDKR2lGr}XmMn-(b#k^v>PBs@Sha&%Tc z`A3LO0^YWMX4Ef+hjO4bObc2GP~xd!Dy=<{9&O;6y)J znKi74$;L@$05u+e(|VwonJsl`K+ex8V;Y*4;L)e5bnKol$|MWB1wjRTs>MV!gNG#W z;{cGI-71NQjE?eZK6>V46TWVU0k?21e`0{?9+CbR8d{#N(rEdN70tovP=$!uMQt-( z2V>{OTY6TS+cjQiMEwnLP2|Us3jv~%6gaJRx5SUgSj|`9x%2Oi*HSb2d^h8 z(JEnB2kIoBwzQ$r?Hp8h2s zh4w3YxeXnr1tTIq!Gxi2s;}3uz?TkbMYp)4aaK~@>U%{LNNdsF1DvOR#q{0AZ-P0r zOwa$i0b|EUr6b8BRtzF-5cNa`0;4_=hwyI4aAFS;pgjg}Z8XG8CuO^=Y01EX~sFrJY2V6Lou^I+<)OyGX zq?GWvVRN}IytPVq0O;hJNfj$mPI=p%LI4BX;m6!jrw6>X7M>tSh{U_o14$$}AjmGP z@7tvjpnLY$mYheX_XQF1+{Yd714eU70q|~j-I8SMQx0R`(ORP<`G)rH538%wd62eG zgo8yR0l8wIbF~UfqEmFwwY6#eWId|PWwyF?DXf2lHEz&50G4nE^YP=4fRk$a#8cqt zC|(ke;IRuY0R>Kz#GHT|2ZTpdYva&PEn&LitYz?HQfG%^TM(m&%azR6g&9GF1FJQP zTCs!W8JPXoehWoGkmQZr^8u?Js!u>8qeoIu0Hda3KWaD`?4NqwvbsqchS)rmfNoHK zVGTvd!V&!M2VfDFEpTvV`8_oTK}s^A3>&`qh_@1$9p;GD;)U!0b)P zxbTH_kN1YUq@0?J$+Ca{I~JQL2eF_9U-$|dd86D=Htd(B9uh5$8SdZzmUnG_)p`W& z25)d1DTBB82<1sI;Kv4!u}rOP5==2O)|pu6(*~4u0l_+UUxRkCQ==0t0V3mk1aAH| z<##u63(k&NmSb@G1Ek7Xr?WX*l(Geapyt!0Jt}t_#d(GC$Q6V2iDpV72d5mZZQ&UC zPoDIxZb|tEq0NI{Cut;^Ro~}Lv&ISJ24!c%hMBqlis9$FQwy%PLS!pe2znDkcj0B+ zfVhC00$Al4h?2TZOKTO+^P=+}hKrg2k3-cvJ%~zKxpZkP0GVVly+qvzG#pgyxm+wa zAUDg4XSucJiI^@DqM-o(2TC%nB_>x)D9#r=-DV-OP?H3Y-5xiYdlwt;0&`ym21WN$ zO4gdhmr-r2p-sk;s?M@*FN##{jh4(=zltc^0l?-&R%I>f-*}iy2!Zo7Y~}^OQ~P}A zq;1IKUWmep0y<&jx}btAw=*GrY(~8^tG`znnup@($-xkjOhN<&0)-f_oneci0Gzph zZb1+0MQ$$Es6U?WTW1Qm^y=q=1P*`$sA3y}qi9lEj}Om~7PmA8-%}C&^Z|ShVHkdr z0KgA4^xh~i^rL-DoJS8_G5+*mS0*UYRc;4yNEvj-1brKHtwAmgBI~=*wDGml%HYi; z60yf0%~sCU&XM%X2kxE;>FS30VV9^)_3WmIK}CL~OzC*Ctb#FAM_%8*F=U?NnyOn11l??D?q>q|m9YzIVeH}MJAoQpVJb_cK%BJy zH)ud$05z2BJ`UXS6$X$H$FtNu_DvX(8kv`$;|-jg8Pq&z0QE=-^zmp=hF)end1Qlo zycBp}E|>)OAzGapZ#Dls0bUK^C*WHTecffL`1c>2O~N9c1g_W%qqt$1&Aji{1_kLJ zQC9RjEWLkq@om>V-b`4R-&zF^a7`WGxpLV+y9?$IsbQf0!TDWuI(+cA%l6T z^5G`*OI_~nl;Frw_H0SYcoPx@0A3{YFJ+ss2OVc3o?p0_7e#eSz9%}vw(5vSk-7b# z2LQU~Sp-F>=MO<-PkDt&wDEoU7GMsGhp%OKbL0-M1451qm65mW_O-Zabgo>5nk@^m`PX%m39exH1o-BfH@WV1 zj`f;Xh{EPA#{diZ^Fi4Quap<)=l9T;2X48Mx2!vfrdKGN@=`RPj!m?8x$WKXf_@~L zs`@li0S6pms>{h#Z>I0xzm1B}-%uMiPcb;$ldhEiM1th70AmT?D?O|IOXi+`M;gtb zLqDT8bu1l$b=j~80nQkS1l14Vx!7tjyrltI?!DwGgsNXU3#d67}DOc$~dU=RS_W8U)weBmu@WbUB0gbcOukSYdrX4bSiadp=m~|=^ z09m!av}i3(kg3Yk04uA_Tb49i;>T#x#&P>+F>u>~2vd$V-a+iHL`4^{2f^+0S|3o| ziK-_?ZZ)s$!4{~Y(vZf7AADW7b2vI{0r-9{!vQ7=UycD7O3L_Q=PSd#qRhSyCm0&U z3^+Z60M__ZtPMZ!$Y@2{$A+f_;tLzWgHT&X5sJd=FL5DG0`aKSXMqi#_du{r$RWVZ zl>Tv-5Zq(G0^25Wu#1K|2A-j}Mu}TpK0N`1 z8?`fodZDu+2epc_oagY6wVWMt!|Hpq4yx{$j6Ap%mP3@p z0qua_(Vi=zK7=9GuFUk<1(!O~BE5I0N$STsMl2n^#UE1Jsz56qGbv*FcqUU_13ZlYuIdzB z9q3N4bsf{_oRoBeZ~l!YAh`f!z%wY81X`#lY3%pKDe9MSMJ!$ulDGMQKAN$lZ0C&6XU`{pphzGI+kX%tvH5VdBg7fW#p1t5L)QzH&01dphFZ7MLZAfCy?q?X7 z4mb?vF32x4HWHSW4&#kU0gMYJ595IBsY6!wM-h#sqs1JW_WNwxPC@d=nr@Cr1w`T4 zBOV%%a7_jtR_Lhi1@t!d${f&}`-~l@;@4|(1e6nfq-)aUwQ<8ZMTisCP0`KZNbb?_ zk8Gm{`piyN1+RWMHuvNc9{c?>L9rRQIwF0J_X~3*nuqPXnGFHe2WQb^?zYsJnfsC5 z(2?K&Xh9=~)734iVn>FrNUMx?1B0y`Hs>qYDtic#6z$M+E0!Ynlp92q9~no6)Vy#8 z0r=PYni9h#bZ7I|4u^v2zjsjY9Y+Axb0i5qiwa}z0z`JR4XM&}*jpOdKzf+eiz9%< zSl&ogT6Z+$$H-Rf0)WY;h3Bp+j6}<9mLzG6@sC1fqWWF1`Ds~kV1C|h0vzz5@dFSU zCd>alvwfU0A(<6zE7hr}msH5Nmana42UBw?qq{3E9dMYKt~xz3$ju2{+|;V7Xknz) zODMjq0=+!#!DS^S)|mPieR8k`9$e$zn&lS&mecc>%Re$W1$PcUq7CA%>nvROK~PUR zmJu!0(2B8wdlO;>@B!ec0^E2mnv810RNrKkOG7D_HAIs%Rcr?gOAzBykSOSt04Nf= zE*GY@RY&Q8hnmF47!=Vu3!UBvh&s=PG5B#M6oE8uGt=wv1 zV2ZLpsAdJ}1Ef`{C`kQ)Bbvqv?1u8W3IKbz#{Drl?!!!-H^j6K0YySZ-?9y2^6sUVvqI zKney+Pn!yT>bv9$1;30$lQn64w4q+9<|6Zrc4qG2Okfu=5PzIrAY5H#P`$3k!7*2iS8UH4mM1DGNR1 zqfmD82UKI5U}kplFNqB2ZYg{8WvLB7v-V>&TqlEO665h&2icasp>5+%EyX{`nU(7L z+ys~l$AKV~$>b;`!sK)S1&kC&h}waAmPF9%abTRIHnH)s$H1Tqn7O>tbk)SQ0F1^U zL=hw$nf2dpKJh3=9Y^(5RG}VCrFk>d5RE;>1pMN;V(<%^)T;zs+(`I-3RV6EuJ$^AIg4tV*hk1$dm{e4k!-)?l_1GFujkW*)QgwSAvC~} z{;DP$02F6Q`+sX8Y0(gfg&65YDF?m;kde~3j;-}#UfboPvwf*wwg61$G@vM|n#bfw8~lDV1g;!3Y=j>uj*x ztZd%M)&1>E1?erb+qw_98?aC?@ML2 z9WUOS(8SVBa+M67nyhi(rxOoG2D~IZMWEoa#0*2cueH^xps)@tPkmwERDaMIti{Q* z0z#D$sdTtN`{njRhyGd<_+1<}i8i_n8!0SAjEO4@1%Ix8Q;qS7>9n4FfthS z_icOmzGrER)=grdWd>wloVC0=(RSVd2jz~3L8;MoH)PyFhkB4vDOx{Owt5`v3zO2iH7$kevrV`<`nBq6_*!*j*t@7>2YYYry} zqi%n(hpNCB?dB8>mp94O0f^V92mb}#^}7WE{}6V7TqO~k@-N-`S$HjB&_<|@tx}NY z0_e_yA@TzmZN9I?=NvB~V8FokE&6B~f34SpfkZdQ1|~TvYMALTDHK8*xWbnp>4)Ie z4m{DO-pgUtB>X0A0&n}6vr37JLOQHurZNn_iCeR#&VYT>KU4t_KZYi)1yt5pPAa?M z6O10`%AP5D9EBrn{>&dv+#ua_or`De0ZTuK0ZVxdBSei~tB!)t9fO2FRJ;kemR)xi zE&p=?0MEAa=xQhcv*_NlG2wtYfrRXMBLCxCqIXMA`E0*a18Ef&gEUtBJ;&xluuGx)deUSi;tm88k1tfyIYVi}Rw$TxRD6STp<<^@aSKhOok+QL&@}S;17GTR0>>?%|BSGX z5B$$OU1)A(asZJq0ge3l^outQ<+hcY4kn&s?Nfnsvi_`y_sLN{09k=K2YSUq+sZhn zg77MX&ah8FIsz#0Lp$va86>b9Nw+lh048N1hzvI?27RV9=r0vgmFRAyG^cQsP2vX|Mx1J^9Ur)@I9 zWo~EUV^%up2AjbSm#g74SN0Gj)RO!HrZSV_bpN%XO{({192ekU2bC#^MK|k zeNRezBHj)o4F8GHD4OqurZ7%91_r~&HMo6jm54Zg#Cor{u_ZU6i~<^O4EnR z0-9@_PU&=<}+?l0lAW*O`m~3bqV~MnVsnZK=>_V1lx#MOncc;B|#I31GPK;EHWPQqi|dP?X6VX zXq1adJuz)vIYLI;(M5AX0=}#5HQS~DO*;oYBi{ib@a_%>Mb9m%bl{ah2svs|15H0| zDe;|;E|}zGL1G>O7BO=6wHmi}#O^%W7rVEf07u_=z!CQHikeza>(tPG9$^O6B=YAo ztNJ7nujT)90i9$OWsNtR)NA8pxkP!ggH%s)MNQC$DNCOc);P<2OHH9p=6sBQ?ZLXCo1ORxR{ZB6UvC*wkPNb=ZK-R z01E!c$Yb*fC+r$T!76S-sN(h2#HshG4vR4akQGU-0DcECFZ=h>iw4{`A z@_+Hw2briBIr@a70)uG8)N2bYwWFW84*#^=TQn}6@Wi*VXK6@y3|`HM1J4BM3PM>f zv{c73Z3Kh`pP$wq(Q>qYzH~WhET@7|1azLidU({A{DnGy2D@KlHt0`|56h-bdE&Wx zO1#H+1%^f>PItvl9GYgGe;P?*NDd*+CF7d=JA`R)nY*dH22idY)HT|CrA|!;cCm;@~rf2bI{3-|q#^R6!9ykWP*9*hynyB^ZxeDz0e+*^C222jixe zjpOGtsb;@Q$$5O3@qd6$6JWZ|rMorUi&f*@1Yx#J0^p)6d6n)u$BT2uXDoS0$BP1zk8r9E zaa1R<0_im8SeGFM0t8}W`pK5V_rgSD9hWg@fX0+6;zGiSuBZ0AWG0be02Vn;Ll)#4 z+Mq9Nn)Sv5y2!UXJ;s7U*{MFX0LYyI1m&7Qwy!gbyfS2;VR3y)#I*c%ae5w7*mQt* zU|voo2XDbn$|T_hqWxBVfh_`f2(q{pvF1Vq)kY#MqdzPornP!m zxiF#j11I#Xsfnky@};$In5Xq#Ql7MFVw>JNF6}(6Hj=Iy0UhBh8>L%K+W1L~w{37? zVf$$FemYd8=0ciZZ{g?S0Gp#xuMjcem>NQ@+y+Xfl{x#;RiAg(@G8Omur$Rt0M_1O zqHxc;U2COl+pJx2=IS^%FPn^;gLw@(OQ;#o1JZfTy-BONV;|v1AljqIe~L90l^&tH z?CPxZl=E0}06NRUM#(RBv%HQjvmj|{X7rWmn|ctQIFY)VlyVU?0awnWO3ary09RD| znRhK^4xD6LyCt;g1ZU}CodlIa0%|?cnp}R{Ez$gSs4EKxvf`K!a5jZ z0N)CU=?|g-NPZ~P2jR-~%g0hV&5XrAbml$@0V``R2OZP;7saqka;Zx)Ff=_10mu>2 zWylUgXI#^1u7Yhs0@Erj0=}A-<(Po1seOyU)?IpV{aQLMwxIF&nBS#HnV$y_4wSV(u{R8a{F_+3OYB z1+EqJ3u98qU+Xg_fC4ie1B_>{QTCXGF2A_OnhmX82VSQaH!OhUBFLG6CrFkcjd}ec zci)bJnG!A`03CcY0Sg0WtCu%BPOpy70=)j>yHw`b2ds$6h5nljef}RhyjuzyNc`O`%!k#?cFYNB zZerYW0@_d8HmJ;%uYyVmC%m4HsaHuZ(>rBr?ZVMHV4Ctm0L|z<(_nFC{aUWr1g8KD_T8OS0jC6)zJKEIKDL#{Ia$xFN{&H_)Uyd}y^DXaf!TmO0tx*a zNEdH&1K27Z+Q8vdi_{()%EO|$r2qx8rzc`w0j4yejS{-^P}h7X_=cyk^>S#51ZUl$ zL16$AJ|}q81vp^pG72I?1>9M42+{ z0=AgkY)_TB=gO^giJ`@Hh3_?2_X2^-!U`^gGv)0T0dY%gLz$gbH*i`M!f_$+(EL{> z(;#*5)2E&r*Y`uS0*V@b*IG`gnEx=D)0D8Mx?i10vVqDvV7*P^$b5K@2D`)SD<`^_ z?DQVJ%P~1{cVBY7MD`;3s))C~)ig6N04*qtq}Jn-iUJ9S%!3I|t}Nka@dT`>Dxv!| z?Haxb1jc^QL-bcR9Odf$YbWhBITHS8^_H$3LMdGkK#wy{0BgN*dQ;_`e_mPq{v;vv zLr;9jb)stQL(Q54I?)2S0GyGQ-r2h!aVE{-GdtHD(W~U0>_y5>pxZ7?Z`@GR21%H6 z7heK9Y(`FBaBIS=`U~HN0I``80ne#-pAB&a13CY>E(aUUZ)M|Ft=m{ZoE(lodECP0 zj!;z84BLZr0mJzZgoV(kRS&VQl|Pl|LR-?2L(VN7Lybqn&JdM72mh)i1E0Ket^cO< z7XSy94CdB4OL#EF`n0;T;zqV(1roZ+NIVK>a_*29E9H?B0J@QO>xXYT3l{5Q%^($Z z0~GJ?%PADII`kMy-hXQ8BTN~Q3TFaIHNEmxT8&5#1tDTLQID6a9AYdmM_R*kjm$Uh z_dejt4QMlS5dNt918$xj;^zvx`H~duC0ke4|8prpI!TV}t>-*}dz3U2@iT2OqnZ=Tk%cQQvlwROmc9_s7L~U4A3x6x}rR zgAio91ivlLdyOkTQ^kz=7{5orLs;d-ZjoVR(uq~?bg1QI0ZzVNR=H3e5Wl)Z)fpUh zr&j`%K47!A3023v>0Y}R1$jQ*-!xehLS>eVW=&;UpyE?~e%1;-RJUlZhR{BZ1h*Ie zD00%Taq{b({^MojdwZcO2hNo_#SQ;@)2{lI1TJ?Nu!o?6iY@-;x2kgfMVut>LQuH* zPI?)^fLTm}0}i&N%i%9Lkef~JaBN#*vb72YQq1Q=3!TBh*gvk>05T6tP2wgV>EZ=< ztwB`_JENNN)8;1FGJKdu?S>iD0(uKM(GFWw78~8}Mf_;(Cay;##oSou-9^DztTo;w z1FtHSmE_8&*TCTT+A$lcbGC0;Q=Mz62P*LDu@xg92Hr;U?O|Q$tUPL_=b^!GY{+^G z@Th3Y?L+Q{^5DHG27go#d%ic2M%7Sar}+~Yk?G@0{MM zm7+A=0#a2;FSR`s0(1Vk#5=VS%M$tIV0<&L( ze14={-yi90N_p32&%4ib5lLY^nqUwnXGaxl1Ch*j4NX};$uGU~w50QwQR3@I2^kg4uDi^ML2g z0i^mGkq)8o)tKNET}Ng<*Vst%WG)>YFsROKNYio_0Fi@rnL+@}ENj0o-#W}q z-vW36!d8;?vXw0i20mY7QX`$Ll%8zTg1P_tYZ%>l-EHraY48;Km+uJi2M1)N>pT{! zArtvpJJ9!pPy!~~^9;3FWjagrLo8@A1cR7Z7b_;Od_5^*g&x?`OSl3C_=1~<*px%67cvnHL?N_Gg$K zK0Kq8&KohPKOE?HfYX$p1-BcBCCi0k8VVm8a)8Q#*C3RI$U_CeKp$ZdXuK4P0Yr+y z6rNpymga*(PNBZrCJ)Ajo>mj%;{OJ6wQEU`1c6!7SjlD(z0UA8s0G~w+mSfhR|xnL z!=}+8&E1D{01KBXb?WE~jiqlZ>z+$C9-m}9YUhpJN!(2b zhoe7B1Qh>{CGdDTH07EL(IAB7P2EI;TM}hN8A{$4rzdc& zL`xR4=HQu@gJX&%ySujn0{|fowb}c+x=41HQRr%{T%H9nzPb03_o$`}D5y3g28~Hg z2fYz~R=9hAy*wOAH7CZ-pel5Zk{z~lWz2*?1zx$%!HW6h1(OBP>E0(W7e-eT2nSM5 z;wSI^oPQ!B0GpiFI5O8$NYe*F>RIN%#6oi`xS!f^U8J=qJ`c&M0=J<+>dRa(XFjtJ zO+zzNF@$^sR(wu3-Saqmd-bhF22X#-f|Tk7I8ZTguErpyd;l>DTSm=*7G=_=>HAL7 z1e^DRvoyi`T>dMeb9@6u^tT_5N(5H1g*`mfo~!qN1I{qjE>&{}w-^8Z$H68Swq9yL zofak-Hy?DU8n3D+0HdaVuUBY&{Nyc_U#`$M+lYc|nK&=E!*8kK`4JI^0z>>O6ov~= zW{wK2w(4}#VN&sbJN(zvD^;X8Psl?D0{A4W?5eM#2y8f{jQMK_vnKkcw4(#-;U-tA zySt(C1s`|rCYk8_gXGJdV5RdX0V(r}z(TaX@a3W!rQ?7t0KbF?bdb4sCIF+Kx1A(x zJ|~?3(&=}t_LvZD7r^)?1kl}VY;A7Qa<*S=-j+d5xEh@~M$yIIm*&i>Fdjp}0S$Lq zf=+X1RU=hby7}-TWhjtpvvo2GvV7Rqy2@|c2Hz~A;t9W?oYQU~-zm@7+$gCFqFTQO zjt94+A^H7Z02O}J5m^@_+4m|f?|E26?NM8n`f5ALPFd`R+t+t!0?hQIhb~qV$kl~$ zL`Ao44nj`6CS4%W>(x<5%iv{Z2T`))yLG8#;+<=%Py-O3BrVb5-0W~o7lH_`5DgVp z2YOIf=wA7tsj`ix`A3x#R4m{QKejQ)6mOB6MPQJ|0rX;^B0e_u%*CIoO*1sGcM;Zqxwevh?>W+d1t^QnOQUO-Z6U4eLTYuz2HGUN*^*Z+aw`%> ztczqd0Hdam=g_Nf@qC{d?TA_1$vm^1rgqNEXoE0CYzq7{GKfayJIcR^F!4bdre&w$Ct~zdEuwrm7c(1wOwD z4Q~b~P6#zi(zeZv9wyg6@&^Yw=x z3Zk$VYUZr&0jeg2tWX6ycr$@^bG#WT%)G&wf%OatmleZFn#zyt1PMEv{aQ2QX!b!r zoc!@;I9e#})gUc!eh|o72T=h>1x*Kc6L<-+f#$kL4SeO`If=ol!=+l&Zvfl=G|Vf% z0#I41ehXX0EzP56ZOt0Z2Lx=P_=+^Q)EX^CuswT(nou=1V3Sf8}0(6Nv?Q>pGS1z zA7y1WO5T|~HMG~t4O)r}0^wx!OkYKH1w}W~%j;4{w(678|J*JOnt+C6!FQV(jCDvR6w#zp1l?va6yEG09h>#<$K(MN=V6zUY#g3amIeNLt@)juKu#J>_>{J0FxcKmrLmY z>*CePs0(6bSsR~l0j>n9z*$XO(s9s11AW~jF6d7+GCX_!R2xER(Gt#Qk-UNraAz#( zmuwST26=ML+xC1mwcxE+A~(}xYXiA!rvcwVYezt?Xjj+?0(Rj)TG?M>jf!(==B(MU z#eu%5V)y>-g?aqSzIkc}2N8m*-qvhtKd&ouy)#GC`31RR!0=x75sa?;fH~^e21t%` z5^bQemsEfW{x>d^ACIy!s#*%e+IzJ9LgrRj*n*Jgj{1l7Yz#A8jP3|ai@ohK*>lPg8JNO_G)f=B`|<=7+~1j0Dbhl@H& zo-@+_(G(V*Y)8zU)d2UW7*VKCQue0*0q)1Mb_7r=kI>S?d_seRjXvaf-&6%8 z+LHpN1Ah~#OZ&1BG{-ZnZJx@va{XX7s}8f3!BDEgkC9 z;kZI@ne7UN1ZU$F?8{H4w0Khm0~cp#`2H;heX>Mw_iQ<6HsRhm1yYB&W?K$v6E ze+Pwpsbfn9yf^jn2bAiFIMk|vqU|$nCyQ=+$3@?uQ5=Q-!EQ z5ZN&1U5i&TVep*Jk&;MdcwQjG7L2N2P-S1!WSrr9kx>0Ytub^i%ul5w~W2T zBjk5bSQFs800Dt;-|+J(Uq$U;e+1MR0CN?*N&L3YuWbD? z8BXDQWP4`+n#Lz^4%I>A@%&Sw03o7HbyQ_NR!&#LvUDOCUYm@NShfg!EK(e30;-xN z2DAno0~Blmx3Fx{viQplhIcB;aF`Jt!*<`GW6jLq0gzMR!xMufJL){)tPdot9oLaJ z3MjKg;9`v{ALk&%11!1C{5qH+-Z6O4jIj8Dh(X0iuCe@$Ive;%-Kl640#Y&&d`zW7 z1_;&EW1lGuN=o6~%{Dl4)0HIHhTP9N27JM&y6}vG{4-|&c2`^^I(C{CXzWYu{F{rQ z!l<360lRhsK{4^?fj9~t>14uu@#cGa57*1mNFuUBYSPa72OLo|z($k=g790LZnD#eyL$0P^%W=e*Z_k31b8pbTALD(|9&OyG+b5?P!o*$~1(2f5bc8EwImYdTnM z5QXXO2!d>7>c%nOugka$kzt^j29V%1*5mCa$k&k>(6T6`?qR7?=*=#*I>QEw!|*gA z2i>+Fn<`r-1T~d?1dB}8B2T%@LWr8oDYf&&AvRkI0IU#WSW>G>F7QiIXb@Ha=xq+E zO`sXh;&;|QI%=`f1DG95J{o!wiU!dRCiH~Aj}l=@8jE>0H7pe*l(LC^2RnQJn~-8h zyfVp%UTMhtb8*Tcdn$E7r493kN;VdS0`C$U z+0u{91R|^wTAJ<1_B_qk#XBXFi}@qWC-V87b}~AzyR?_z0^fG{5!G4l=ljGoIe8AK zW0(V0a}X1)0m$AH0a0Pu10V*2s*7s=su9C43zF-mb2#>K^wua?Ese9VnPm-10stgV z%hcFxm%9uy;Z`PCvm-@RsIzzyY<`jQz}=YC1T*G+(u@2ORdh>jM|G3ZE=v66=y-by z0fA1d0ThW22B|XZJR$wP77k-#+j<ybBPWx>)O$~x(!eco1zA>{eUuR06VbUymFNDEA_1AA1HGDDQ4(;^+j{1v z0kX4Hh86x2W)~<5isDzzL!TPEL9wY9MsvE=cZm5J0a+gSE<67JNemvm+pwp#Qd6Oj z{vkQ=se}zTT`vU&0v;00S50uQ+jQR5@av^l^f$kUYr|OP`WI#!rP=5xJwjlpLKO|LDq2J;tEY$VAjE!_l}LB8G(6?HdM2}Pyk;*rZ{ z){fBX0Bj4Q(9ut+NbkuJx#vFuu)as9oc_awqGMMNQX_mB0SbRVSDOXg58C^Sv=9pl zpcy7eY??z?3c?&jIvC6S2B`EH!9Ue-qbRwQDNHJptL^l9--WQ#wUwN_?YR+v1D$S) z;knyaR=CD%pOy4nXU!1^w!V9@(JFUU6-S`71O3>}{Tn*JGnY3mtDIoOEimHD^-C)! zrC({GE7~~B22}4f3JyDR#);;X_?nlxtelpYOGM87iWY8?DrU!Y!hB~n}qG2b33=E)OI^kduupKe~8dD-}gmI8_@EQO$>6yIncV0KnZU_^DD zzFGxoY%KJ>jOHDOmIG$QAk-UzIM4ExHy2eNfdoIUq$&Xu70H;->ZH#Obp>DewlLgz zpY!PqJr<}eSPW;;P&JC^IE35+Krro6jRL;pCc}Q?m4=&!KssVYt8~=jP0NFjnX<$a zwP?}fPXxVwgR}U zJq_f)eHxw~MhpqX;S6wZsqCMN_&Xwi<$`Q9{sYTuF`j0z@ToOd%N1Inm0Z?+^u*Y< zJ8T=xK>Zvlb^}Y#g%O5GMGL^q6v>TVW{eQ2l@_84-C!^(x{!(!I|I$j=vD+kZ{Mpv z|DI7*%jVuRW$28WyfApm4D?ErvjwC`Cp~$?e$kX8S1o5eRN7f)wUHg4SNCU;-s6^C z!UP{JBj?EJA$kv8mD#L*fe4Vs7)CHtTO{~Hpe(E$s{=v+pg!CBt}0oXzWk#$)3h!| z9HFB6h5D@Sx#KA74h4dua<6>TSPzAi(c4n#!s!+2M&C5Hr2&(1Rl9B$1{YtlQ zkIv2^`34p_cflx9)p-X@bQ1mQ+-8~zv(LiqLMF`953A~|I|Kns-n-eu`yd)-xIU5t zc0H(@!YlMKw=;6mR00EXfdqiPg@mM9@Pgmqvb-0mxG6Ca+|FlTRc|M|1Pmmuhyl># zejkdWUUc&oQz{rpLJGqS3(xui%dGR3q1HwLU<32l8_CmvVX-KPYPRxFGDpT6N5|qZw&E{0u1-WlhyaTv`UOHn;qL>OD zWDpTi3oIL>68BZ*biv*{Y_rJ@1;j!2f3m!+sQQiG`vvIs^DF5yiDIkx=c3u)19?|LgE8XV2FsVw{d~ z*8l#QJO?F~i52dZR8<)k8cZpvvpJmT!{Cglnwd*aNQv=DQUO*i-C(Gm9r8$W(e$#g z^w^ea1OT=4W#Ao8sIX4-P6q%+Iy~`trfe!?DlsqE2`;Ojd_Uv3GfjiM-E#}q$pkuf z#&H!RuNdzK2BIO8=8Ev{@d{7`m<{tnEdPXaJ_Z5w4r^*ba{#}(jiG1x;#$sB7>m{M zQpDA$DbAdL%>&_lB>@j|hbuX}mUOydTUO0*MUCQSSBA1}s$e)|BLmrYZMsYXfBj?z zgu*D=&nq?orya5JUcqhstS4Wf&;^?nBq0(Bc8P{k{Xj2r*6&vp28bEfaET^q7byL<}9i0h5CH1GaaY6Ygt(klBgaj{hX=q|4nIc?~S z%8A^a=p?`Et9Wle(g2VqZxRBLY@;}s>Vzvkh>xd4*rKHV1GLX+yP%V-O{guvvQ0S_IRjD_8RrEHyC)f9a&@^#8)LgRGQAkdSK5 zB$^5#J_ee!qsT=y5Jv9dn3zy*?oR>zjcX{{W6Mm1iV+Ai9RSW*j}wJZ-_;^q^Kw3E zz9w|aeh>KFw3%?`)H1#N0s*9?kN`y0WKatEfL>Ai-x;;}sYwxOjT8ug5l;FLG6J)h zWtW{5Li?hGKAZHN!_hRl^;|{tF@|`|LdEl6D*k42YtJ4H4N!il zzPgcKH@qt2Q3taKh1K+>d&JP=wjkk2A6?_CVLiLYbOnp-8($_aPy>bWeVWqkTPKJb zZa9>x5vt|C1+U)^+875YZ#xH=$p>BGYP)80zH*cR9U6{+A9<0k{(#rUeDd*9?s`N) ztpzkc)K3jj8n}8J7dvp*YCmbDJurj_63JWR03MLF2L||eU}DLtN+O}|FtSOTi$2HQ zcRRj1gRF=nKZHX==>(?GaUVUDh*m#8&QM8)*o`V0j8U{A+$Wfj=92Dk=LC_z&DHQA z0B;aVrBs0E0bt^Z!3l=4@TLyb56B~#+6QE*l0}8pzDggyzx(#C}w9A(@i~+QGR3C1q)k8~+occ{akJ||SnY=)+l?4Z4_Q>Gcq6c4p-uHFQ9=mPK~2r>}S z3t7h36m9_Mikm4DJS21c*Z%j=x#MSvf5>hq#B{+P#;wFkS!3Mkh z`Y*91>lMmfhxd+_kb6y@L!M>MtgmUV4NVLZ(E_?8LtyOvIWrakG;7@lnu@dM?E*xxNDGnSJmbkb8tSbB{YZU71KU@#?m5Ng!Y zFO)Ek!lsI3LbOnht{r@Gg0ckia{>a)L2E-00{x`y_X5rkUvwyK2Nc=8;Pk|nSJ#x_2LwX;wwP+7<)G-8j-r9eGfzU9 zdR$eku>@^E5$!yYbJ|E1Ki(PAQ`cskNsIEXvSfs0FzRH_&;aa(HW$$g zaHHXwOe^6F1or)Q{S?N)?K-1mhNyfuAO=`_Wv0?F$ivT^X)%k|v~UKOO#!~bUL5zE zChY+E{Q@yh0XsSku9tBV();sBxy#F_i#&^uX8n5Q4!2pU7zDlP+@-~qS^5XqpjCWb zVf4s$h6Qp5q_Di8?A%@lYzM}U18?_iZwFr$ME_Owd!XkRyQ@xP_DS5%KBzaELSDG*Gw2Y<$6_$Q>`1}5=aaid>h6xfk%-xP;RdP4xdkG}!5<=$TmgIF2v(={j35q7 zw+vvK-yc$}us6N=n%PCZn>8OylmN)EuQ^b{>bf=guE2m?xrpIhgiXuJTzJt_;p72< zR0cT9^Hpqs{#m4^l1Td*84zSOzeGpL_ta1i1IRs7p;i87qt{zwAygd$WdoB~@106W(G12D5TRkDJz{Q@!d3ID%*#lFRoJa9boZaMv=E4N$oYs1TU^ki8Mx+y(@~T^2-ht82)1ue{Oa z`-V%Sm-RNDI`f#Pl5SYO?FFkAe!!53m-sE<)L|tMgBZuFI7u&-6qbGDU-c_qg9NYH zXgvRZKjT#jVByS|k97L2y#A1w;?5@OEyMpqH@n zD-m|vhvANTZUictRN=ODfV~lK5ynpH$t~(`s|BNGu*B2Og+_R#0R+F8DE)Ws%3d0- zb{#H_zYgzmS+N?{#BF%dD6hCvAOL+z+uNv$$zJ)?KYTa0aOvMLOjcViQ1JDfWZM;p zZw5y#>nCgpXx_MN3J9i3h-?&q0n`>6Z6*4lL{)fUst5LSwMEq{2&*YN0BOA!YR{|7 z`T`Xqr8Cz_Q@9oy{{ZhqTfy1CDUjmR|5YL}KUqS1gA471OnlTpb$X>%fp&^1K+vL`Pb2cZ>jthPQXh#d-*~1Da4zB^ zwjoSuL}l4hfa9?XVO%ay)dOQajPGhEpE1{fopb8=v9@#4&aYTiRQFGzJA3|w zd4^!D!ZS2rp$5A$3SO*F4nKk~ii`&@vJ`-rdy^}SzDTe^I}XAEeg-Kehquc!=3;G5 zR9!!YO#yOl-&nquNiBmgd-(68w zt$vDsq+uFUk~^$a)C1T`@M0LQk(1o@(A-#FE+8h22f04W0@sW5Fxu0Q=LXsJsw4h6 z_5d$7isMHML?Iv5vZ}{}7DD>8-A4uudI$655B1CHgp#c%Xto_gi~RLuK7mmal$W-2 z;%!cZ=mRp69&8{U-!Ipwh`I#YP}r8$`@UJ1S2LP;MJ47L^2D zd{_IV`8)J+C`*Z#F>Z`9A_50U@ev&`z73Ya{5HH_=ao-Y2{K+`77-VkMI?jo>G+Mdl-3gH60j%^^ZLyd%7>fDWkJ3tdq3{;le zECWce`E}954i`MQtNMF19a0kl-X?)UoRefw5sZ_AL!fp~9Bf6=99%!YGMCM6x)deS1(uVACtljPZ2I3Jg z=51q{DippMjGigAhP9sfs|UT~Z9XV+r480WCK3ibU@0!I#KumXBPUD6D8w6#piD~j#08t@LXB9DoZDmAg8e#u`XuF!BrJUD zaQoeINO{wJIR$Bx$MID@GNj+|U@rD0-F^E}*)J@>Li7U`_JQnFTm(e;EKo=`Eky=@ zC&;wExL;F$IX{zhjs*bzADMCtNCt9gXmTlpz4>4LqXC7#kh6oN%6WO1-dzg*N&Ha+ zssT2@1N$`nxyeAg1*Sv4q-0^`Xwf`WbBS|KZEE6=LjuL;(%HN7ATZ55Ja%yRaagw! zD3lPxM($~2o&Vs+(FJ{tsgXo`iJA`mKTa+9HR$gtS^2SW?!ti~zDegje+RFky*^w_ zZbS6h6Ejhk$fq$3J8;Ztipukt0Xp#r z06wA^UC1&@Rfd!r%{vVT1ON)Hu}dgpw2S8z4#vJrixaAb=>MWB19uKad&~8Nl>!L* zPMKikY9@u>>2pOk<5bT9^O+fq4Lu|+KG?4|+XPk>Pc4?>kZ1$O)8n1Fv}TsbnQ*tG ziliMP>>z&dK?DPbv})6d)W#9f0p1aPjJ4-RzmVUkvA=p-F05nIyakbq0$8Yeu`g9w z;CyCmv~FkBFl_c9OV&0qOo(5`#MLBAppC-b#l0xyz{Z;mol)8qbJJ4 z5RpzPCa^F916a*Gqynoo?9f-@e)h56$a);yT=xZbb)jz?^t2}bBV|<{KLOGjgpEy) z_TGZ*LjGO#5MI*#yar;y6siFFJ-KHSwFgxr&k5)>^e-QMB`c%>9ZcY1tjd!$J(WYQ zdCxrg_yFQigf@fbpYEd0*+f^$Pqz=F9 z%wEltcAjLa9|iG5{ux@QvURakz)W6=#m_l_cy-i#w4d41zia4wVh3PW1BemhQm#q8 z^JVXjq5}!AVQu*y;|%EWmFBu?fCumcy!$rmOPO}PE@3;gzl`z#te5Z2Dw(ViLW0G# z^GNhe3M`3@aaHb#{=4Tfe9Ie941?ZLo-1N<|{4_&7B_bCA6e^Z^KD z7X=`*^g{H-sP}gQGxJ*{bP~sLkI%dGF(G$R=LDrq*S{FF)cBW+Qr=PZ2l!9$N~Paa zPO~QxLKToHHU+Vium_OtMV!fMCRzwuiWd3JExh*teLlx{gqvZ1(f}`uIN5CX2~%!c zWhq|#ZUPVrU+(A9zM-J9r#I)C&H`pK&P*a8L}!~|wvy7~!_tlMtZ-I)-%^MJmuCS_ z(E>=cgTC1Z$V_j9=C@Gm@T?Gh#`uAoz9PaDbl(S=5CWx*xUEz34i(C=gr%E? z>ffIA{8&G2>X==is5pne0ok#Nt%%^8t;D z$8n>rb}qxVMXO~n*|6_O?hNBRc)n;fn`z+eQ3Y!EE+s^W&`>NkU9jSp{rOx z4miOs-kDx@Z2cTDvZDsAL2!|P-vq^NaS-vKtb`hJIO*c+_-&*&zk-r-R;sS*G4>vr{=lpi(K}j~CZ46G9ijtcRd)VgSuTjCU9q zr6>b44qD!k*~@vUa9J3xz~1RS98Q;ZiU2MYLRAeuF$R8jIbwTNC7Hr&F7vk*I+(2) zWsbJU%>v5fSIOe8 z{GWMkNH_`UdXEvPN8F2?Jxpp7idNdv^DoNsGz6L4`GY9M>1c=_Ab)}k`jvpjtfl#7 z*IxfZ0uCdA-URw5?i6j=b}Eqgp~aS_6P97bJSEa#?zSD&b7ADyYXK}3eBqUQ!HS63 zsFZOU@IPk&UrC~@xGfNQYp5du1qK%%Cn8NY`33F@fX_=r-ehXfq`8T8&uNC(Z9eGf zQv-QrV$o=5FZdUa^Dh7)cM^B861D^wXWfc8&y(*SKNJ5yJ4jlbELB1&c< zGx!(&W9LR9C>Pzh#b80&KLf>_i+P?TKoQ0t){V;M{mVv+pU%1P^anY@qJlsgJts$Kp-2l0Dm;lPv8L>ViTHSre zwQWAH16cbL{|880+Ip~N@v?wV)(+iISqF!LUm5=;G?qD#umJXJp8|e;;Yh)-t&pn*7IP|0Ne1&$@njIDkGA;yN z0|gKI>|6@=m*=+Y5+?8lcSo}@zZm-TYH~7$A>Ru|4+llZK%iN;H-I<39!N;6iRs%5 z@b)sq8Hi5fRkihhcm@-evIk-^Ad3%Urg%f>p!x;^D_&xso`7vzuAaCFaR(O+O^%3Z z9v8&gTNEUMK>TCnBQlc6*|}3nWbUyyfQHGS z_wxxsZ2`JRk!8cqB$XJxHZ~B<*U|O(Z!x@hPgG4{=*KOP4@-@a*1gi|>P3l0mhu^67@t`-xy2BKOmC!#6s9Z=f`vM0mp&{Ql z9mVdw_IN;a>H1%tV0ypk8{`^_{|qb0cLo`pknp`54~jdIr5{CQP)~Nf65BP*$#r6S z9hx@FI{_QNqI^adsuTG=p~MT~`fTo0-gIXIS$`FnMZ+(QB>|!)AO$>`(8-DnvV7v~ zs5jv)Je#kqB%hG6M^^6rgA4Z`+hqIBWmpy;p+D*X>t2*zU4|736ey{s9Dc z3crla391)cvp3!$vFT>Y4oL<~rSxw98F31TS^%gxh|pmtqQ=H%s;qn-{KMP?H*q?W z+yOyNi)6l4DFZTh~_W+BYFw|vJ>1nXhkCxszfly|@gaGh>&C$(DVbp*9Wn&$WO9!sqvX|km<342U*A3{+Fw$vohu>t8^kw(FmmX=VEe1H2W{Pi)tQ<+DX+_)k%ptZN3MWd-mK$%JZkSkMdu)__v0H3xN$DnksF$G1!^w3t3c zj2dY z9|Q|muwmaowQi;i{7-Pwnl+cu-U!t(kQ^(&xr_P_NC02{3%0$(OY_zNHAUfk4Z;N0 z`mG$u&jzUsox5G<%LOytu`3M|*SE`K?Jgr35~ejgCRIC#aREDdPA#!WwgYFwr0DW% z3SRC+8Mdv&Cj){4dyo76!AW(n~zYiMW-Y5`lr| z;nXz8E)k6WYIJ}EqlER)n*~}!vk4G6?z95aC(uByzGh?p=ymFWC^g&;G&i#%djloc zm?=G7NH;b%H??-9AlT=P{{Zp~&8&la!6@znJ^^E)|7oje=%K1Ud)3n1CS;SrkJtd7 zX)54aK<0xeYXl2xZeplr*Rm*rYoJ|53m&Jf0hQhnEl?I>l?766tp&Q&s-H#+?@%H! zM3wodAP!?#$+?G$IG6~aIE`2EJOjbDoJ`(%W6N)oD517O`KI4l%Yh0E0K_Yudb!Nj zAqTh8ag|fc!?(o{P|=;AjsSmhtQqULNpOnuxLHzWPX@{GV}hv+P&HOn+)p@8G@u`z zyW`ad7UDr=TKpqMLjV?1ibFC2!dH_5@vt~jr?7*QzcFVfBSQo>d0ict*2n6QTKZjZWTN zr2P*77y}&ux9;zC0^8Wt3qY1Yb)aVUAF7~X?w3U)|EJ>Q_yRI*jwAS0uA)!cg|iGI z?xOxBg(0?Kh>kLTCEP+@ga>KXT&ryoA2R9f6!1v4y?AI2S9+*@M7F*Mp_5T&`~tzh zNZBoUZ5r4CBpUgCtVtx+5RIC)7CDzq>_qx6CcR)Bt;m35U*hX!mbd0HF6Cl`{TO=jza|G9rbaH)8{$de+c;G@su z+5!giC%p?4ZbKtv2SjHmgsBLS@JPMGpweds{h(Q_xdY`|`&+3+R@*WsNHN{KEXA9k z|KN3$zw%3vu~O+6a{*Y`^SSS5fSP1)Z92v`)6by<$>-GOi#29=U!Kw+D@Bfa5(R>q zKSYSZ$@*LPr-0j8I%8(#<^t9)Y#j0LNWjm5y8(LeULoEEfw`?V+xyBGN#H8_FUd9s z9M&6>X_l=AEdv;3nK5niX(>f_N7C_(E%R^3r(}cWDTH&*_4cVkp#zR;wSglkrkU`52<_L5U^6SLy8{KsC!gn7u}8VSr$2RDN@YVPBi{9= z8=TPKDK5`qW(Jlr9&7KPS|}P`07@{Te7WDh(F1le#our3JMGvJYXOT{+1A`<%+4kq|VDacAPbrWmnRDK9qdIXpgt%V)j3V zNd&ViXESmcSMC|xCO6iB8ExIaxE_EKb^nq*LlIc^s0BQR9mWp$nJrPDZkX!tn2!B8FFqTLtVj zHLG~jU4u?Xy0}oEnRwbn2joMAR=01AjJEPczz3~kraO6Jj$f)&b{)(=+X9i9V`-qz zXgqo+a7>6Bi~w{)zD{69lMs7u1&&dO555qy#781@al=bJ%T( zRkr@r{`c0vQKxX`G0(%t9m{zRn*>|bj^y3COZ~^imW*kVpnzkhXTDUMSj@d1EuOCV=hX|US<$`%1(kqq96|(GqIYqj|W>6jFfy& zV(8F~2>G^bPk3=5gHB_NLjH*i-90azaisxdDVx0~_7C54Ebi{OtYzDP2ufNQ*Nv_9P_`6bebXy1*`aCXU9WWiOLQSd|)djNrt?ACa z9%a;ic}@64O(B+qk(uf+ z>j)+|W&y`n3Whz@w)S?N>*2b~Z_>T8IOTC=0w|;-*71LLxB#xJNLON|xw}9`O2(}L z%F9Ddwd~0pL^>zii$zDL_!lL7dM2ko(7{d zKZjw5P2vJr@12}pJ0icgdaY-jd&dz^vxjU_1P3$7Q0&(;*56NcLgc{4Wz{%x8aNX= zA>02BXfbsTQ3N(^206Ey=PM5U0_k&9;m_O}%KXEDUb`fc&KCQ&2LXT3gXiySRB#CG z;ny8btPH}lS1EQ{>Dr=HZIk}Q%wV5Y0!XRe zj{)}UYggC|*X^pjD=r6}_@~jFkxe#O9kK0;UR05$fd=J6B3WHCPv?=g5(6p=8Ii`k zSw`nVQ99wixj|V1Y65+{GY^$i+`vb59&ZFzP(#~5PSk14r`EcVxd$RofY&wK ziRUGxG9Upi!m4E^0VjPB?J|>8H)-~T`~%ioX3=yTyI!I zF*OFbKmi{F`5N9d$ES=U0qC4hzU*l`WRj7gX_xJwXHd{Ty98yek!(X1Q3~gDZUO@t zphaCSEaUZ-o>9;`GT#4_@B+d*lxmW!UTHTBAE5=u_I4poKdDtFI_h>4*u~lsVg^yW z_j4MjsnVb-(!o!~WtsxwSuqr^(oK*k*7^&Bp#|;?!I4LysC|AIJ6#xtZKiX3krPX{ zLaX5Oaf2hyxuaI>CD?% zK1QYJs-BO|66^&)=T4#21eNX>%FSJjU-9?{^0w*&QPM> zC;)}pJn^VqzGA8$ha#uWkmL6DwJ6|jS?T9&@n*9FXe*@j&oS^_w zMW%%?3BCe}K@vb#>~nLw8hpt2R%F?-;{q2qypagc4;Jf0T+e{UpCpou4++M~^y^u! z$xFn1od-uzGBu7SsZ3qax37-mDV{nltKtl;vm<6x~c!npz*(%ddR{JDa+pxZLoG)9M z$M;g))6NN1?F3?~zBWuB#9~WM=Mk%y-Hu3ugbw-p=+krr{??t?a0W&214_5{gp9M7 z3r<`MKZ7elHbqwOmaoT~;LZwRkOHnWw!AJ{4in~X+xIb0+ba+h;Vhf@62^537!P9h zxCCs0#|e@z3)0f|8$n8`FQ&f7WoSslqtg*r@|d$WK>$%dOrQ=Af_~hRC}jGM2KY+< zc^RO-G{PuxUIiY6;sqi-^a%mw`XMQv7pQ`zbo~i%j%O2byZ@ZSw!T$KY5^a~gEox7)e`fsJ%=e11P9jP z8;(jc2ko0gF75&P&IX#4y`kp$ut6Z@OQt=|=$)WsJw5z`owgOo{v~k^#eJy=loOd7}UolR7$}yKMC}@=^@!$C~u!I@9QKqy}f|f=oU_-+$5&bcZXy zdc{({gLa8(l6qh4O2S9^0{{U!>kO-zRfi+1-rWftj1OeMNlz!xTlAmjo7n`wnFND7 zz)sdybnaMaIcJ*22I-_Y%UH>^u(Fk|X{K%BS_RB?68lk(>ZhUm_#a$)g z@&&$rEPi?|quH$N{ShK(yB>(U(11!i@bnlO$AO5dv;me^9l+;xLm@*3YNT#D&n&<_ zKdcRUGo8O_o4E;xj0QA*=9qes{Lfdg=d-i7v0tAQ_r%21^i<(rg0e{GZ36XZlWJp< z8ds1Dnn6s{D}?j129({AN>QLIMaLu$59(B%n>2<0!MrJd~Yvb68-4j&p@6gSe0|ZZb1nboRl1x6aWhj4d^-r?~$t4X59YS zoYx4<>x|P0ZelXno_%Tlw*`sv0T@~lc_c|$(>8iawWpTm=6@P))NhiDS|^qQzXz8u zQy&5>?%9{c#Kh>ey&UuGQn_7HdOyJ12gF}9MgSEz+Kxw=1VHw&2*83mx{S~Z{9q)m zN5VD3^UP-V+Xo7fnsXg3uU4Y4Cde`Bb1O_l@gMj#OlL5?tPoDu@dg~=4;Q}OyTW!V zb}|&)bq2i#{p%WX$d>W!`R53YwgpA7U-#r;;gt7(g;@!BM(w_YxE|F6Qb&Lc8>Eys zVFS8mjHbeh`3neOc;tF(JyxLP`~w-SqASB|ZqZ^%p8?YJSE-}0Cq~o64-LdhCmf(| ziHP|}6lI%u(vMjF90Kn9Ala9oy`~M-q_X#OZv@@w?TBp2?AdWhtpZyR2z076v_(#95Wg+wX1J6D{r_t zGEUbQ;{xO11wDQxVR`NrZ6iS462Ql?sk*gHlJ90A~bx~e-{Q7$k} zm|K zdssbEzn0uGdP__SozU-+9iT)liv;YkVgaiPyz$Ey8=5o09b(dFsmFaGDMrGcmp=aXo@8&Tt)rqaleb{ig1`=QA$_MM;D(voD ztx^R|C|3SqVZ**n8SQ_yS&NRePQrxOrVe^=j zLMtUXwgE$&r1@y29b1C!cR@5H1b0k?W(v7zdV%}(swX_=76krbbx@k@tX=jZq*8XN z1-;`>O5dtB<^WNtAKSBlO9106VEWFAmG%Q%P3{G!cRtXO>dpC-57cn+jmw*`^A;~)-yKUac7bDeIsxu&qkde-4t+h4PS4Z<4%Qxe z9H0}7B`G!>6>x8M_y;e3ZC0q9c9+6^LUtuB%Ur`n$u6$dPR=eFyMBHX(*?Z{6$bom zAUp*V4-5=T%LAqdabuXzU=06Fd2%qhyaH`!R7Nb0FXYHZVPJtn&}2qumngwnm4IHU z5Nf3)%L94m*L&!yS`EGlvi#j(mwZUp^4J7g?r}(FwD`8tuLdibs9-@c=mx&2?uc(Y z1BIL}ildgP#xh=$Uzy?7R|7GH@Z8E{d%A_mslIQ|G|`k$>aCX_BBcSj8U88JO9G-m zH%#bwdQ?fxVPrGwtiQ7Taele6sqo7_@nqafOabKyT(`R$`TdY?vh)U#W=VgG1{;%D>+-%xhpT?d})U6%eRt?qiWcM;B01g=~k1FZAfP;UqXN7LjtUjqV2W7S1JaQJ`J z${4dBzVG=TLd!j>jbHK5_OL=Z-UdaY6^Ozk_DwB(j2w8i+)(GBeA}1jd`WC^cG<*onjx0(%}HNcJxa+M$@|TmoPdoREq(qo%L?@WM6{ZS>AG zQEOuPA>lj=Ae1GZYXi4#q=l}rviJd*5w$j&fv7xe?d5r;ufot1&xt zL7cwii{kCyY=qAFr~@i(FZ87o)n>w-(OBli?FQ%riZ#}0!LNUOXgIYF z+bCoxI-T99HdEFdt}VzQ&<92f$+)rMjc@i3Fp2VWf-BCJw3haHJNcPsdQjQLTLjV2 zOeQ?VOD8@Vc1t1FOQ~_WRa^dON~VVDtRsIXngkeag7!%QGD}6ZJ_bBY{g^t7=d!`= zP)z9he>EoWa{>4eOHXWK8;#D_!I*W&%--;+EoCDx5QjO8oF-9mu zSBF({uxVwkoHz28pL|4@#x>?O6b6RdryfFk5km*f&$|RXyb$Wv{WTVd{In zJqARVE_D?nJ<_HvPVhDli5(6we3Ri1^uh&JbD0Aa_umcX}`A-ZDZoIddIpX0S(4~`&V0cJEQiYN807<1l|E1@v{T}YU&_+CyZv(jR9^#k-Mu94)^#?WH2*QO1 zyIk|)VFr2@H#;D+O9YyuUXL`g;Kc4d?g3qoH}>k$U20(KqO(R?Sm8#=R2!2cu#P=Ingz4nG(F|D3}>S!e_Iz^-v#Z497 z0w>5T=>q^QZ9CvDWWWB4QNu5bfLsB?J|)O8fSv_~uRTUs>H=S3c>_n42D{sgz(phQ z#OO&EZkmR@;CYembh``r)&-V~@(LTz%inDcYnw7ks6YKLlF2tY0s9w%k!aOr1qTPu zDXP*{hv71L{c6mDFAhBhxnu_7r0u4+t-fMw(*qk&c$r|RG6eTA^tizKkRDlv64 zi2e;69Es>?tN_K=w;|h?^Y1D5ZVzRwB=56^%X)6i22rcv@^>cv#s#1Bb^RIqrL?d? zW{-+~FS<2pu(#g{$VQlhMffQ@$pD4>?+f6_BZ*(FQ8G`b2YWBWUVIpb^htL23oMavX`Y($0^I^Omj(Ulk66`;5I zeo^m7URL%pT#p+?d)naQL4Sno228en*rK$Wn^L-Uk9B7mE$=p^`rGDGWrt z=Wqq}%{|67ymY%1u8;F6-$TIK_X4e!J z%(j(_zAV3W3j(0omKwXCo8c~Zb5{IU8Oh8vlD2&!(9*UoCJ=$VUk|6nk`bwUa@PfMnvEc zdfP0Y9jw^vmqX_BM+Awe>2G)>_`sf>cNT_)-Jd%T2{|m!cNl|$;)V;J`Udv9+G)AJ zkO=d6=Cg$IVk8SAwM(CB^=s-=k(rOM3jr#K#YBY)%=9fv7l_X#cN3IjNP2t8zvjpi zwJWs%EkM%0z=r~0Spx$9GL_La#|&&ASUoGORGS^6KFvK9xn3)u1G)wfjFWuCw&knC z(#9t({r|OZ5ls$2iTACb1wFI_+>HT9NXlCjlJpY|eC8XTBD7b`2+CjJWGF7tQTqs0 zS$PDeldLc#d{Q$DZeFceR%PRQXZEd>$NES&l9*OVq5uTKvAaM5RpZ3SLcPXbz&=NY zz`MeLy0(%#5n;)$fWrj}6a^-Csql)Y<5Gp;#%a4Vm|_?wXLsA z-+Xi*FyQKp#k5+PRR@vK^vCf&{&W1}&)oxQJPR+P7wHdbVqHap)*nn^7lv}BwU`wY zE;0~c7|W(5?TMP-0Vs3Lvxb`cc}r*ugta23o%FOiAV)# zy0?(D_!F~}i+*yT##ktAF8T+`(Yg#-jl%rKeQNV2XMr3xgNcpGUbP{*l=K%-sM7<{ zwfA508D9tTejY)1#qzYesDVSRcUh{tNq*12udoAQ-^{-dF&5%I_(|HoS`9c;pwk%t z{6sY;b^Hf=$(sTwo=JIb;E*|u&y$fTn8eMU5p~$;Dz}Eq8Db{`X8pR7m z<*>DFKmV>a=%M%t7w&JpH%_h$T>@Rc0fU*8WF{Y5kC z%Yg)Kg07B7FqGe3m%jpXCaPxkHz>Nf#|bJJ#fuM6HMszRPf>5||L;(rF<+9KD1P=9 z*kV>G2>`VIpIH6I$T$H?bK>%z_jRPLM)|`wD;emuh=e(3e{{!*!8qV_Ih6q_$7CI_ zWK3s-*>bg>X%Bm}GH=WAd;$febT>lUKvDu-T<5bWym5nuI&&S2BlDP*sotbpjkwf_ zZEn*5Saky@UdOoFBk@dg!Gkn2im9OGgXW~K-1^u{GmCp-$cF}?*E8Rs)hAH9=Xw8S zzXrRinm=SRpCAFAT;!;nfK~_kRPz6_2r4^;%BGTKc);E`n)=*icdK&SqP$oFe zoWMJf>oE)ob*%&-TgK$@wU3L*7(gOMK_64iFh3Usa!HN2r}w-}rlrMr!q5{0=>I}+B5>@NnNEeN8At4{+@B^bO)|Xho zEe!z_+#R&}yVTp&S))0rt>>9+w|(jUTV*Ht9G9O3r#S?>EwV+so^`k)q^w@3u|7}> z8`A8;mYg}Ze)guGBrgLI>o~b+C0Pi_`h6(ZN`&$K3}dP$y-s)@B>~#w%3lB}OE7Ap zJZZAEFX8n!B45 ztw!FwRapS@CO~Mz1F{7gZNb2}?ye?#+&JZrUv1~N&=x27SZ4t_#r7XLX*)t?5WKjA zVUk3#4VUtA)4$(MDV6_r3V#MM<4E2XXpuu+O*m@lc2%oz7c>pHQ;%xsvV`RCfoufd zk{k13T-{LJn%zi&zyP!CoO#UM6@b5b&Ct)Exj+T&1xr4N1NrR~fg~3z;zcd1%uXb6 z(TLqK4dAAZKBfii?OHW9)*$)|*~xs&-P%8JipeF33?j80>aplV_4NY!fD=9{>%iCR z_w8q)R{q|N>u__MftP!FgXdWlebfU*8XB6i<)Vv?g$UW>pK3-)EOBdSyUFSvqks>| zvgiQ@*3`?&mj?WYTkebtd_{L*V!I*NCC+;lUH1qQTciV>8NVqltGvmA&7>8jwH0ix z@TP_p?PiUcGfW@HjS&Top?fOD$6%uhe5dA^mXWlkAtb79J3(^R7;Q35L$?7v{0+iE z8;0IB*5?4AR&<$_-gE9PIX*o9rBeJa{#*eRau&(s`gD9fNX9kR4ouQX(FL9I%1A1x zup9a;7Uu&UDM|31_1k5g?G8E&nO*gdA2?8^Z9k+k^oWVbtX=>W7!eJU=cp4m;(mUt z5_4smK>SqQfu_aoBPY3lgBk?r()$&Sd4@4Bhy1de{pe=D$Q&E;!}vyJhI`n|0`mfJ zu3cwH^QsZSg`?dj!zfxt2yy`XHD&Dk_utY_ZS4Xd1;dMx{S7O=3rc*eRRW&iXHk(} zKk~YS=`47+rx*d2FE?f*U!Zve4eOM;Xz7h$6(BDfHnI%Kn=(9D*31Hm$dj<31hkJd zOz*2MAV&I&mtMT780Qfka#Kfv)#d>R5$(O5(TTTn?v*FkXymA?rg@jpzwLG7zS*e! z7pMT+`3Mf^2zO)oe2qH^0ZmjTnu}w4iP`#r0De`d;%Na~as}?gy5)0eD^0<_;9|N` zN=4V>?;BY{hZoPeTz97<{q>GN7_F~Tre8Y!c@NBt!%etr-NXd8-iz;v z>B;K+fb-^?>}{oRr4H^nW{%YrTTp+JYeEA~THa`D!l68JoQBa4r8aSWz@8r}V!eDS zDQAKq-E9P0K~QI-(ULBTlLP69oSNBPaiG@>=lOH{@UUHc}#;5_%_lK-0-$^PG zgMpwaMJotJdOxVi|JPAodGOC+3(@aDZVDt)fp?!^j;KXF#~am@)3E>|wJsZd+zOVa3&!Hgg2?g6 zznRetp+im93p7|yXNLe1Xqy%Cb7O(!l{OX2F{t z3^!m7ez>gyyVcTSyRVi-E{Xr4?DO>|23RWeyve74ONC+nlX1a+ z*&C~>Moa~O=ia%*=$(Pz2Q0O*Bk{RgtaK{#`EWl~4|?6jI^74gx+YYcl){yGpWaDY z5SKP6J%nsQ>D?}vbzO)wy6FQ95I0^`nS}(ssxW;HtmA;x&L+uvmBu|&wePWEpW*<$ z2aW>)u^iR7kgI*8%IAyR@@a_Iotv_lx5l;CH7N!bqe6D;<5eHtp&WgYo@dC7+@%o? z7$ewr;9ZR7SaAg$^z=u5I}dM2Y)cBux()G0!M7m=KA>hWI7CHE_g4XQ&ElK!Ly8G% zc^jf^Fw+$9g`icpwN{^~w$dJPh~o#VU*@&B1F!pmyr7Qx!~Ver*R4CZQ6z zKo{4D{Zzp*p$OIP+3qY5>uHfA3}^?j6u$r`ltm-?KME+Zpj2`5A%p&0BLkpSwtlGmBRpw*8S`Q`Qa+MzAbpeg!=@Sn>HP$>UlqcJN12p zt5{{-s1Bpk%wi_wA+1T1!gK{mmjb_3VfS-Jq-A`^u(3i*0qS(n_T{-t+b5a*>w^Vq z5%IJW&BbssCR~-hRg?;2n>D>;`o76pqT!ytWoHL3{X4C4`}F5l>pkPZtg}Vg`;#z> zUbkz^X2+Hf8e0H;-M|o1137gQB1EAep6PqGD(=Cs9ZN?;2D{W@B255Q(VvS91yxMr zPp))lFx@;1DfAX!ET$)@4rK{+bb) z|DpsXgQ!9_FuhCEV{{Dpr$>F=Nr*o`yG+={_xtDrY{~^+wGwGY)I|b@MfsanW>sxZ zU4zc8;=^amqkhDhYo)2T^d+Um06~Tk z;qxY=6xRgltWSj*(xMomSZzgya3hHxFh)xO-u!IL_)J>a9ij$};EC6Crg!gdxmAWn zpZ2CC){Z)a_j1=>EvhSlC({S|Ir5Sc2zQ(Zqg$z!sSVR^o>nkXf!7iFh^N;l(K`U? zEDE> z;-m$a@O(EEd7Ky&Ro2DEyuj>l>(1`4P^u~qcJ-YNs`vzyk>oqyNXPCQfq#$rcfjpE zl?AqdU)0*(;romevZ@D%8ovGuLEMz238E=!G``&Rb|HF!fR@d4E=w!i)c^$ByEyY$ z^u&TSR3*5CZQ7HERrl$eU)o_dmpExdcc=oTNVYp{1Y+L=L-{C@v-~n?H)#Eia zxUV@IPK^Z26B|}?)eR4-Q{B)yiEcsfL&9D(HXva4b9PSuG%E$n-6vYm7Lq?`AL+!( zQx~yu&eAloOiHxNkNDc%Z8P3+xJYxq= zY;@&PBf*Dj5qiaTnhqZXSSTmh!PgC|p?OWAgt-JSF5+lVul(d`&LvU;x;PZ&IZj;9 z2i$XS0O_SaOm+fOP9u_qT8`$xy%MFvF&N%^w2ws@_1txVEZD>kBpw3NWQ+S~Q!h?*Tm1|Zf3^TAV8%lSik#BFOT!BKyJa7#)>m`@d1mHQfGbGN z1t9`fBWUc3N^SAkFBJhgwv;6DPMWz*LLYMHnj0=x4)fp&PmwyBxX4K62<|G0Z12?zyN{i`hq z(G~b1(j-Um?G8^js*!H=ln{^s;;s3dYDeabMv==+DAVX9!sk2@r>CF-?5 z(d%3&zQ+TIe!vkP%pG74l-en?hfgmizHPsxd}VMpCWo+oir@t64gn8My>QC|j7V%V z7g?t2nE=v7cpWOjh@b)on*UG912s?~p%Splx@-?UD|FjwgK4K5wB0+%D^~z7{lNE@ z0HJ*rjA$(0C`m2WLFO|wwr41+MGoBSz}Exw6IS}@hAWmURfcs8*=D%a$~rKv9i(lI zZvzYG*6;;IIY*Q4j0j-dV|$H@%q^$W9uWZlGITM#fE z@jpCYQx*h>gj->M-EH*{j!qiwBL z{fnh9SAQ?XJhr?gW9#bO@}LrYar9UY8XfKYP*DnmWij7?2@Pb85z?u_%a5^NZ<%V7yV1O; z#YYEbF+!AF3@k^wT?<$zB_A-D%h*kY#23;_l06(7|3U|~i1};g_L5swu5rK8t-VJ9 zXaw!%#%vZz#=;aUBBuf!*?lKD5O_lJJ7CnRrJ|P`Cu+sb2t1kMFsu?G+@uG-6&%Vz zkp^Ydx}MAtZ@-YlfEE|5pNER_H5Ba_EjkCEbyC==@+P3R`vmLlLiSiT(}%ow(7MFw z3S63e0H+2S7B%eQ#R$!!sM^yTVobR-$uojps6<^WYd+PgtY8B4Pkn{@NoVxNxX9p( z;D;uto?iyh7i3AheM3xA>d7*JqB_z*&cQl&L3{<3r-~uLH(mn0Mx=umD!Rp9 z5`fXq>{$>|pjGxo&kMiYeHeo1oo@oK$EeA1Bs?Lt8QpIT>?yWeO+%;S{Bb?dI6V0s z*n0=5g`^Cyx5?~G!%=~O=Qq^rYMz~WIDIh_L#@9o3TXwm82gJu*D2F~Ru(Mbz);X# zZ=t($F~eO>G97;YxU~S(n}Fa&0757sj0r&O0xt2DAl+Yu)|_ zrfXy!*n?DKamx1Q#5k&6I~);A$g2fD z;QCGBL4oduuW@VfX$Kb}YuJ_BmCo7250XRVbx#Jp>&*kv)}QXoZjY??;zh4XN*RFzuuqXR8yn|rX?RKtRuW=;?BL!kt-{u($G9p-=qUd@hHD?-s7A3EyAT? z{iCGv11WLe?$!K9%OjWs^F9F}cvlA+dqG``gXwr#?25^wgZjUg)5&!~i2^$_LM!9+PoKEL}t1tM>;CKXW z`6x_9T>^S?Tz&<_pa588g^<|;4i6KYF`)pT7@KO7U%_Ai0)#FKd*}h6!TZ~AvPlhZ+mT&bI$CyY4>$yXl9Qc6{JyAug^S;JX z$2Zj8T&MHB+r|Hv=B9aztJmdfK1Ro#QPFs8B_r@x9ir4_Jper5&HA)TsL9EL)$jW(H& zOz1Nk1D1EL1%`GTq}hWCAaGA#D8)ggF{&johFjCGf=Iae;g zx3l-o5236y!UI{KyeQ6y<}1ILU*`8)ykOR3{0j%dQCsoy!BoC(KWvo9$ehGKb5A)(JvnUO6^3ZLmXj!NIMx5wHhHQxt0guMt>+ zv>I{KWz_MZm?By)vbFC(d|R%M#MlE4rnR3D2BHwUkhi?JrO^agH#Lq2k1QsGk*}Mi*Sqzuv8-)^X`Uwo#38td4o2w`gEJ#&T zeme=)zjpy7!E-#I%y@QD<1@Yaa&*usmd0Zl5b+Jrl=`gte7OMNF%Xads033U5+C1X zaP1CNI%X>x$mF0)n+B;9aF+oZhV%>kcwRkJp9EzCQ{0JW2r=Db&1;bOZ1lU=xO4%R zs#GplX4$U<=@*f(B$KIt9@8p$4}=?UI+Jo|_e}r|+vXX~aMSP*cC_W+O&=X5m8JIo z)%|FYuF^$49Y+LrBqi6Mu`8M&6H1Go0MCTdqNHa8t1{~PzHKuC_fY{h7IJ%ZV6wJC z-v{dG@&C@occIOSjIa!5jpu{`Z|ehq0~4d@sZDpe#}uL#3y6qHj)S?NnRRMwx2-#* z?N|cL@_FI>%=NH(O;#%Mn-l0MHITBPbfvk-xu-=ezgGn6D^>=fVJ}J3JU#8cW~#dd z3LT0)!^FQ|3PJUu05}ArBNSyX3sC{|oSA6oRv6+l^&(frlLhP-Jb{5j6c%{c5f-(H* zjis^mgOUkOERh6%b6f)*MBaMj_eyT7EX>LC!&oOMDgT=5SEzc-jt~R2pA42rJ3|Om z7(w9>e<+;tNBJKCEW@TDt#df}%dY^ds%Wg6uoCOV3U;@;=!Wt%Cnk$n3ufZY$Ps>r z%&`OquNdud7gKmuo4P=byqZeLmr>jHw%~cfN3mG%(S`+(oI#diS8r$MO)8So!lH{@ zUSU(|_F7^8vo7v(ncV>6AbxF=HZU1ctL~gkwWrzu-dCLy(bPXZdG8K9LdgeUElnSU zSLRw|3+&LhdEeJz=E}ab!~!Yr_(pE_ZD$8OIy7Z`xzu51=D4aQCA|c#sT8F*AkdEv zX3B#h)zAP+VgC&)==!`jV?fEzmx@#z6h~t$@1Cbmm`xl;`aS{b4OezdG3b|6n~(Zu z8y`}NYpsfY=NWMfXRXdV3t$3DaeGXWwLf= zBV`QbQ>&SeMN|QZJzwykP4Dnj{>Q<~w~H{3FJUcmrx&Xn$VfoCE}RGM-95~&q<+E| z>zeDa*`3X%OlyO1h}bMTghL%m9oz!;-xZmZ_44;vMCW&)#gT>ztz_>Yf}ms^;MRNh z4<`Z;@GMh26{*8?QszQj)P!7e+7XG&qBWeBwI+2v4;KN2VhH)~)d@m6Rw^@<$DS-# zJCo2`^Oz;!b$JJ5RRaRMSn*T#rrP8EDC`>&RH<=rxPcf|dd?xsMJ8@aiiQK%f?KE_ z&32Y&pl~Ijbj{bRurmB8eG%y+tpie~B_{;Mq}iPZ)+UD&tlZ)*gJk&Th$a#` z&a2LKc4VlN@6QICI!;6Rq}~{}gECa9Ra{)d?4tWt5fUV-+x3zSpymO}#(G+E*=8)_ zFHD8w*+4pitUtG7G^+wv`wr3T(659eM_UggkknI$V73N;xE_QCz?(^HJyK%3mUm89f)dGB(li}z=&ui0+!NHrHR6hWCHhGb|(X0~9Y-}!>1PoN0 z?5GqCdyx;Y$`1#1Lem0lFewFbp?g+lC(kM`8~ISMsKQp?)SS;R&Ea(F<-P?0fMY4H zxoGU-gy4*+ufmTa>&fd`9^if{1|`Q`^=|;{pXZHTO}~n(c40@T0ZTyhhKiq`W{lSJ zpWSTk#XtmA;l1-KrBp@8ozUC%LT2sD9FvSNY7k{L%(vD*~|I&FVB*Y6_18=N{jU@+Y`6uHT z>=pp9m!e~9to-?ni$&74LuQu5s-*9fX9d;eFk*G%o3R5-yDd}4Ujidk5vD`Mo{8zM z_tSXUzh`-(I_X3@%^3pns}F0O;XyGMseSNZOj5Oezk?;f7!e0eZsG^HbDa)3i7EuH{XlbGy`Ez1?~oBTT%f=UF29byC=80_R! zyZXBZ;DiP;eV;H(6e*+<4RzDx`W5MU3ac}~n>#hl0gZJ`I^qNvBD5sO66CgR+-C}o z`ssI9<(vKOT{%sL=2)_xe3zvvK>5!E1X>W2n#WuI}h1}&P+G=QT7CH=*i7lztnsh$DK(8)n zq?`b$TN0{bTSUWG3XbI4`J>IFIj@&aEy&52x|6(8zv2g?2nnMpi{{AElBlS7p(Le} z4+)y6S0VPqy#ul9TAKxN0y#i2>+E&u5uoR*E=K3^*^f6raqcDQRq2H7N%RAP|JSD< z`!EflN3j|4J*mj!xNv*}i23(Utx7#%>y!X@7UHWAhmxaU_h890ivlb1uD$D*{^dRm zvp<%Zt7ZnOm#b??rpLt4Us2;%|D5;$mEzRW{o|Dr&F{2ZG^+tH(?EwxC2c&3rgs4_ zc?E$QoROEiul(OX5iC`xqkaOss91)dePEwNa!E7(JIP?R*#IRqIW9M6)(&J421cKyaEFqK|j81c)sj2~()TDTg{i}?sEAC*UgmlFGqvacfW1xtMUcstx9m+FW5k% z%H}FY&etOAWbV=!v_2~a_{d5fJ6-@G`zU|E8xTb!VXfKedkD=594$QOYX`Xk0*gKt zl-UDi7EJ#&lVUh5H3f!Wm@*M*b}@-l7c-LBKG|WMk1YnOEcp?_E)t?Y&kT(KDaoYC z-O;L_q@CY&_P3o&3i$)!`2Q&^uyDgSaHobmseHTr9IjyC4a7BAIxtuRXfpzff{&^q z(KyoI+F=%qU46dKBdr-tDkKR@K>2TUO=JZ>c4#XYQ4n~#_Qc^YAbKEX~uBf=XR$wgFr7~@d-ar8E<3&~Vd7DSt z+;_)=^6vOz%M0v)+kW3aRA4S|Ale4?DuVGX{f!;WdX&L7sWVo9(%CpC*d9J_FTKK> zDF*^_tfrdPlX;m|_61JqInE zJb%4wV8p{3ZL^o{c!m#b--*FEp?i?qoLdH7u}8h99}B%jW3%B+X5d$j{EFt=aJJl& z#NTawUr_-6g59cm5!!@9lW9Sjg5jpnGQQSq`~QfEI@FNq15E{~Irt>qHT(`6{A6bp zS-)B=(31p;ipO%0kS&Qp)QbYGc!y@S_1kqET|kb28XFZlW{tDuqS+8*jhILu0OSLS zb2p8Hj5tJcn6Zl<<_Il2yC2Ljc`DN)5Cj6#wKf6B63_9R@Z%h{$FG=FO%C<-Ep;I) zxmP1)5YqINgsTC9+cNP1u5p`3(LW@x0sA1u)j<|0E{YiYFX?#6&pZGh2>fzJgzjLu zIIUHAT=#Fr+fLRm+^g_hsU!ygh%W*VD!!h9j7%O1c??W3@|)8aSS@XUi1H>WECg8z z6>kInzv?=6l?&#<=?(C_$qegv$C%zk>_!?V&QzFR9RdNe-hCo|2;VlP1(mKN-oy?9 zs&=*$OgxHKZFt;>tpEj)w}X0`1<9Qd#O%be6g>f?jzZm0o!OtW0fiS@47vi!CAH05 zB!Xl=48XAgy%mfr$wRK5hbH?&ehFMNnT7x{oJEo*7jnjOjd?ohy{xV!7KU59e+0>T zkY?OAFM$F65p3+wiu^#Qi|V}~ds}UtekdK94^Pw8J!d=&s>}y7oIB-;dcxKV3DQ4- zRdq^aOI*y;RA&ojggZ^%YJ~tiw~h=fj{x@PMt4k%l6@;=1=}7V_L3!v+_#NwmQevr zx3-YQ45lV<)C=oq7}+Qot1IGs^h=opTUt=`VATPf()l>KV4K1GZ<_g$YznnhUP}lK z_t-L4*&xgQ!zBlM1wKgF%DU){LE|F-DcI9=ws_g)qLo0f1F9#2z-$4Ek3`)_e|b|B zMB5sAZxm~3y4BqN!zm)swBI0br(FZSH~{WIXQ97*HJv%DhCd#s)M24vX!7_bGE8uQ zP*nzW7>(t#;>xaP|Ip+CbREcss+{GRPzr)Kz-TZMPGAHlWZ01#3ICB@*b~|~Y&)wl z`GZcG25KJ{c)Q3cJWc_|A@W*ZxW7vOC)kK!kxNkfBuhSo_{!3+%Ct-0lClD(7qPIg zQpwOjoZ*;Op`ne?5yDKUL$kt|gS2ZVdTIfkVrP3|jph6R`jw4}?k1=O>(b}k($b6A zsZmrQh1>*z69Ry6_q3c&SH)7gGoYI)w^Mzlp3QA1^d5PVIKl%bhTx%$6rm;_r9wtC za<4@LW>rDNZ@z647^O~w$h87>wpb;y-yvyl_V=VwvSB162 z8g4E&a_^S;$9Dl_#@JMIakSt7=&E;DXQuKPoB5{ow zZxt7(Fa*W4+#D7r#%D?!RvNm(wPy$DvNx(a2%na-+^9wg<=bMb4?B12i!O0~KBub) zso((Se(#8z5`xg2dWR6nL02XPeX_vr`fj^~`Z2XR#SH;VAB7kKOP@!Q&7{aQ@AdZA zQmJ&;Vsj1;pM=^dbRGr3O`;cwCz|gL`%buL!2skFK@1my@iHvlTgxuiNH+x2Yrm9u z$k(8Dp#`6CJtmdBO@vJq#IJ=hy)%`>hX4jh7^2Rl4B-SC=4^B?47I4=lQr!Ac>8=2 z5}b)JB@zOMSz;cnYFU#R!L$k-5)U#P#mo*2pSP=XFh&(1NEio+@Aj3kl!UJXPWW?A zAIF6khz8PdR`8V~xWlrSUY7t0hRlBJ=KU3ej^$%@v}x+1zSSqiC!Rh?fZ;-!fLR33 zJ>ov!Y0vJg)IEFoc>o>YE+EqqF;>lhB`9-pv~dMkwDuXY7SNMm62$zA5c=e@3t$_l zYheuPHi?7{LE!@1WA5ETB=7U+(ba+M(yiep_>U_zqqrS4z70i%9Bu;DrBN}yZM@t9 zkMzzr$88ZT>mYa%z_1-cK6t2*Fs%WAtbg5lxWAZ+t4QhEOm?bj_lvoPdkW=u{3c37 zL_Gl&2J#Ud^>%qOnu(+w5{_4HJEp>bPg-(VMd_ynl|}`8ZJP`HcA z273dcZsU!4FvwC0e6qe+?~Qp^e z(Offe*11Ml&J5@s+104zskj18iIl}HBdNv45pz9LEiLPV*(*ORo92Xv7M*K;&!YxB zy&O~XgA!tp>@V_3Bi&(_7b*WqeCud**)aBrh7bl8a(2c39PVyq^4@dd20kd=^e=Kq z5AFUVI!OeNs<#4+STF6(`Zx$9q8Do1X?vc_NAY)!|Mb@6Gl*MaY%B!E>8f3fln;-o z^fK3F+nzb{>90eCjS}mO5fueHX($7Ri~|R(j9pTEQGf!%djTLD#@g=TF%PoIBZ;uy zaq0tuiEc4_CZ5iJS=t%1w*mE=GTrq+M3dUT6PkTpkTU|xPxulDu4UT$1SGR z>5`$VT2=jFZmZqMoN@#@^nn;qn*JgZN5RPII&s&hEAHLX>@QyEPN8Dt3kNEHJ4(g1s$Sx`73*T*?KeXCYFH1<(ey3zrS7(s3rx z0>UP>Tmlvs;ynW*V^2Io{b5yz`&8U^$y z{RWWl6RlC;FL%OrUdC^r3z`M5mbcoU|t8XXxr1#!=! z)|U$opsN+4w6OJqp-6+xh`X1t!vQ+6RApTc3y zqn~8anqUBsGId1f=s`&NX${j=8Zmll0lphES+*i-_oz)r3)uvRfiW=Ysv~63#k@>P zaj;ho=JH4)>Nq6FMm%n6xc3B)t641&2s_@{*ol<^e_cYOidH2l8uav?=P0g+{nZ6% zK}LVez3pe0#8XVox%OY7s~wf^8Qedr1@bj~z32xwgsQ?EUC2QYKb}Y2PmQTj%bX=? zqE?M`8cC|?V^0KlRod&zsd-2F=kq9fcq8~ZkG|(Z%!Kgw82mo;DWL>hh13_YR~*eu z)yI*t%ql3PDVN7?sXwZ{5e}}_egXk!u81JPg~VG>c7=)ZHHNQ3?5Qp{3xM@chlVHz zL^}unj&GFXcNcRkRcLRU_1`5_>v#mEoYcCqwD)A6ac=_Z!Y+=uZ`C+Bew5S}5P;%f zGsSSD1z9Gh6wf%o4eAC^94yD7)_Xp?PcJ>|I|J&HmYHV>P2L>0%xTkLt@VLF>kSZViQxv(=}tNH zx?I?(kV(8BX#4>TG6Po6Uwn1HN&oKGk)shgphYZ|N^>XqiUcKMl)we}FY0WP)Exk2 zjeB1evgl)>2>!xhP$Fcg;;n3ApS=d>Nd9d#Ox8?r_sxJCg!3ob$8>-?9xD-jXR7l) zUiky`ed;bldc8PtL#$)>b*-8pS8$b$Crf{YbacsRS(*al;X0AhI5UZ!CpDV%o`jSc zmJm6nv0f7Pg2f4YOA!Z94fEYGI*;yzjt~`B`p>K(i@_MQk}#><#@j~+z>)^5+qADb z%-T}x57%ye!!Ks5Pi5>BAYEbLkA~&sp{E0rMvjUE=}g77ZFt zwaqNs0M(l-Y<`$@3-se0Zg~fpz-`CZsUI;dYG~A22+$~`{BRs@J{`ju zW>x@o3j_+#g#}P4z6iC7&V1=t|4DZv**Xbi_6aW*7pVs#1iMC#Rh{$1M?T!}4km{s zV$nC}@!t;x^DZ4SY-I(9+LE4Gkkr+>ofua0iFjW!6t4Cm*AaO_UU{N+VFL!J9xzTD z&Jy*;V|zkvK-vP62uW<7%-?md9~Bsk6)6ILQLt_n!^G)vk*1x!zsGC)vg4uN?TOt< zKGEmW@O}qR%h}nU1po7g8W1MrJ&7=VWT4zC=sq1S*HNw(@dg62NF6V z)`uFdU}Ek2&sWb9|bOQjC&#%}FC^vTN>82;pg`y)2gceTK zI|?nyO~)I^xSijNv$HdOeIwKmMbu9^6}Z40c}lOFYM1sMd&v6A-NPl4h8If0hwu@=FO zVS4qGyM4nD7Hc?M>;DE<*7%9H(Vxb&&z=o5YVVi3qP@W$SOIMD69#qfJVXZjnEkGx zoy+#P+8Niu>%`OklchTTD(r#t_j0pR#}Wo40Ap17^rPDDSPZpmSnRzsL>Ok8_RsR7R^HAF4nwrINt2=%-#eX+8wi z(+ee$SCvRzLHV?y znJcX7fU~+4k)k5%yf%$Z@XncJ9E%|jwTc7C%;@hZ!X?}K`IImbRh#v#N?_l)Xu~F9 z-!eTqu_OVr1D@M{&Ps^%z}hP&|5Z*rdug2>nFDx3k6}s>4~Yld+~BE_Eqr~IE9Ymd zr3wVgMwLxA#x;u#f^cP*k-P;(-jAzZzqUmYdr6@f*O#lglzmyYB*EjiZH;XgxfB8^ zDX?-%!gdD9YV$R{@@`uFT}6dv)_AgXS5<{++pz#ps=hRuUagIVaBpQBOYYSp>4Rxe zlVwToI;DS%O*;cZLeF~Z4`OY$a$%5BwlRHJsQfh+3B&3Ewa8$>^QoVfy@C5KQ^R8;T08ZpVG5c=P8l9Qv!!9?H}ra)YrWP zEu{wkii%>30^+ODQ<2D_78ni48=+4LLs-27){H?}&D{Zju}Y{TiT~*8hN?NeKF4*D z1c9@+Wod=3FQQayC2+IiZ&3_9j^MFkm1yyTMMUdI<+8|59%m_oj(Uq zpGptZnAB`%Sy*Xobst2S1#UBI^`%C0kR=#RScU*#wDkq@eqERS6C|){G(glxmp)OC zI!D}ogk-$@dNK!M*@kve#(@Nd%~nLwjMqChj4nt`srO^))yuv@w;6WujJ~Ol9UUSu>a&3zKsH=BU%Glt#Wpx=e?+^#vHJdLZO427fE$GRP8XQvK z-bX@-I9}N;OtR8P^>6^uzQp14%zhg}NMA4iiag$u!Rqk{s690Q23Z%jmj(omb*vu7 zc!syRlESvTPQc3accw^)E;Aa7hXBQt+O-7mF1vu9^Qrl5G5oTy6|1xKfq4WaSM)_)F z9R>y9WJYG8kjWD9oytcn^rQjAyn3mm{6?&$!zZ*T$n)rte>hRq!vB?cAVs+Svt|aB zR;;T0P=QyzVZQskcmbXm;$Ss9gb!m{<`D#hS_=l8Vzx!Jzy&-YZwjxhMIc;1POr}@ zt)MRD%Eu?(nP3HRz-O|)fU4oEYVD05Pedq5DFgH)rHq4OQn`=NQQrm4C+#JX)L~Pa zP>P@swn}p|f#+)A^L`h~TpmsEYBvI+d(5QEp4xt6L%ONF-wZcMfE}d*XoCcQ;Idni z2b=)`3@-Zd1m@cqd&*QsKgT!DzNunVw78?2tkr1n$1VrX1=?9%{gE*)O^?To7++pH zBNoLn>qj71t8_ie?@s~+FZr?M>yL5Fs!LiGxHxaa0F#)vRkg%C~Z>%*v5% z`{-5>jTNU!Qwn?^(2gAb(Ip1c0eAf=pXo^D?dyKeYUyt2yR_}bxpkdS1Q;hpWn%?s z%~ZbaVy~)u_JTk% z_>6p(EkoFQ?k};v2wVgxSBcY!7nrW|q_j@6qJjQ}-3%D=|O**zg9);m1BxK{y`X&uqOtqw*4%#)Qg|#V4RwJ4v4XuX8j~xnN{8f0R#4+uRdv&R)00Jwl=Ys+FwiRPfWh zhQ%C#3s+Qbev6<%j;O-Tf_Uy%CAl2|=OE$Y4@xPZ`eOd=_9uD)^eV5<5|V(kPxOx^ zM2DjR#Pg0z8brV1jp{vu-P^~V z$=Uy`bCrvW5@MVSO(@X?h)cd^=awn5ZZ6XL#l&(`Cg6jJ{16kVD%K|U=b`~>yfllFnszTYMf5r2PbODJh ztN50_IuBe2ZK}4FI5U=JNdyaluk*iW>Dz5k+u$P>F-EUcj``CA>aV-F#r`a?l``h3 z(T?V{sw<%Z7eN8pH%seD_^r3(gO_q6Iz91bubr{b<`U2Ja5nx2 z#{fPmT6538UEG#@MEw`uJUuws{Ob1y6m(?!f#9SCFU(*y)Hf{KYY+EQ`D!p@IJ|oN zLskddx^n3BwPBqCd97EmbFUEpq++enR+tVOxp#1KDduOJo6vdSe$b{fuVQsa6Dtz9czBnv=RNF&?Frm7%peg-7%VSFgZ(F z)fr5->I+dS(Ic@zjZSC>^iE}k%KhnN@l%s(B37{~7TRrQS+VKo7ogQ6c+?>U)bE~& zK?0Cs=lOuBi63tIjB?GK1D~yv5?V$O-U_1!pcGe592;f82~tT+1Q`~R%d2T8(`3qW z`%H>$tK;nhdtZaejeWLWEY3o$%Nx@V`S8#r@ma?ax~2$^SaVDg7(*tX?fDAbHzB^dN&4^s zhX2MVDwI1b9gqTKTjHR7F{ZDw-S9Kn&y03m#O>b#$n4iH&q9KHxj>Na09EYxagRMx zH2K>X>T)?I`mpT)H$`NJsKPq?A35R2ds&`Ll&`6Ij}|olKko5j5WGBUILu zamVZ7-M{l_V{Tj!;J?1C{L_>2oTVcN5hjrq4C`Nb7t;>Vd?#4sdQCPS&4itaH>krP zGP-#K_kAW>G?(83th5gM6HRsHs*~_|GUjdWLIbGWlhB3*39SuF0j=%mU{M)I18W7oF_ zeOO%@G8SziG~g$WR!(D#uxIgjI-^y=r`kGomNoeXc!>D24PmnvPj9N;f1Yn>lz2X) z7!m6jUJaf8QsW8-I}PqfS{yQ;JTf>+4gg9gI92T?!I9d0DMe7$zZ7Z(t9ek8S-(n! zdB0`S9t-L2#*?Ojk{MGu9v|mke}jDoDF74S4YFcIvwuoNA`Z9fcUd;spkVrs%CM{U`Pom2+dBeIY zsHQ0w7$`FC{~cx;gT{0Mi#HTG7HlS4WLu()jYfR6w}x4opNYzI8o*CU6)9o>Eam;N zw2Rxzy1IvsTkiAzW#>9TvgUpm7B3O65=xB%&b1O)`u%>SVldq>mLfDEHAhXKUPauq zFTA7Xpi#L7hypurn2{r)v93n_X!7a$2b$m;+O+k}W~j^qQjCTI_yL``wu?oIY*kBn zt+kCfzVrXaCOi4pEK&K?v!s#*F(fewdz_&BYBWsok4W980>u&Yj)V7HL55q9e=%zV zr95k8PvKl91Q#EGb9j2m1}!CQ`I|a4EJ=+9e4(%fVvdZ`@LwsBTvYNhZyl^rjgln` zJ=+_iC&#N=XMfHH=()E+J+TZgPvDWzdv^E;R8|;I;@0Mf<>lRBm#NJJV)u_fVkDIE zy?0(3#pl&(UDNYf+^o?yOHw1>R3u%D#ZA=o4#+-;OkCsmXlRLuL>Q1kTpK>P@o)(P3dyAz zb%$^iimj?uc7zv#_;89=(WO)CkJJw;o z0RJV_0i%Os&CYCR#QXHQz6*MuJ<+)YbD4*+K#r8KzXUf_*y^x8y;x|KBI~H(kzYi3 zpf_#>-^nT!7@MfFmVrR_ooMkkbU5@a6Om5anklt!15m8laii4dV*J_Vg^Utg;diF@@A_ZR1;Zj5yTO#oC1 zo~F}2{n8ICmy+*<(oD=PYPYq@5?V1>BuQHbRQy@aY-73+H9Hd|H43Ydc3gBu$?exo zn%ZETO5-Hw zWGoNm)Odrn3sQ_{=+wVU5>P7yF>!VeKU%~rz)}Pary~)vKHQTNvdc&*vbF+yJ)au{ zz!ryxGo=@VHB~`C0dmWy*Sx=7In~1?=GzaiXtc@!b-u0E;|)Brga1u*l_q5$298rZ z$1y>Mcz@n9=pm0;=ZsCXTx5N z3>r`(8DRGw#K_`)I@&1E&@)?!-^^hHE4z}WR}7%Te#68%JQLBv#EKFky1yN?IFo1j{bm?j*8U$VC;0Qd`G+HGEAGkih=!S~Ro zd-oj2(PT%JWF{0>3^7gVAeCmytg9LcE;PCTXV3`iJP%CiaN<2X79DqqNXHnaVB#9t z$maFmw|!IrW^v*SudH8+ZMF1k&Lf$7X(5wF4I?Ib56>G%V1_*arnV4sDNXpnNzyUU zE?oqQb1u_t#-Z$aiVATiqO(WytodnMT#y|G zHy9Nj6IqZ!gDVpN!ZU(V-+mh~D)jnLyYk`uR?Kn*z)3j-Xe4+#?9`5mCXR->)ula} z#8NgxF{gLkc4_$oX%Arv(TmmO`mM(Q_ULg;m2uk5$kjtT2DSkh>*|{W1Ysg_A%Ff_ zrVzLHz;VXZ0$$skn|Ah>M>l|HaXDZBMdr+#;;}UaurEko&OcwH{bAApSdjKy1xgL> zv4AiG2@gafwtkLSqx<2N2exa1=w2`DMp1bzS7SR%ON_{D0OfOv_DO`NGbb{8*2C z9nv4?B9>uYRLkmYm;l!ah}SIx+Gp}gqTM!mO$>Y+%=X$jS zxZ`sjQ6Z^D==TI4$N}*Jb>qhiDF7)2D+mq8Bv|U_*^a__|AqXj&t`NM>xPmo;ZA5K*Chi5Zz(~KZddKK z)EAi$fwKODHuXxa;P=s#ZO^Pu+B5~3oH}XZ3m;B z{+t|3hu||o!?B#Jn<-!eR+LU{Er%fnJ11r*bul4>N<7h7fg3>_=x7W9MqV5Qv6wkq zHpy8K?>}rJd65;;k~Ru6oGsy=%KW2bG=S{{2o+LcBi|D|$~yi8qq)@`40>Q*XDW1r zfrJ3HGGP${r-AK1%jokjSsuwZeDfWzCOqf5?pt6NiKJ$?K<3o~H375W*g>kM|G?~P zp5SAMEdYOY;G%i{QFtuta&x@^THQH~9}9eN)N0QrI9)tYF*BD~d>(dq2J;33?WEBIUA@i0Qif(2Y6fUmrbtLZn2tmA zCm>X-KUV=RZ`0EPgQqA2MHg@D*S?@ct5j0kNjX6T6Xm25OZ1h7+G-jC{BkpEImO#P zC7gbe8paGMB=nKN{2Cw~O}?x2wi+V=Bl3Z%KY9|}O08p@@Hj#4er%EBGkBUymT6T7 z>Ov(2YIKC%@dh8^1&b{owZDoF&vf>6PSSfx@!y zE8IRLwKYfRVG*SXh25wDw8LiDX#uja>zIcK_}ovHqS|ARrZr(7TA~(*e7Hgbry16w zUJZUztxNPgYTS$cU{7153NjDlpR!m!5r6Lh00m&ly)JcJlrmu6oR(|U^$&tFJsebi zyo8mW_TKXW&Cg=4bN1~|%60$L%P+UI&92RSdAmbyQRNLU3a(%SGoG%D{gn+5m)PGV zX#aAd;7IG27q(mLUtB_|WSzPOH6T42S_rfD-Ybz5DRw#5E?y^}N<*rXq*#A?j!0n! zPy$nPQ!3DZYvC8G10y29@A!^3J0hu&*=4M$&e>fBL%t!7K4d~A`d}bVNIcAg>GA!Q zbe4gS=p*_4IsME6e~9+S=98*oo1|~>*uNE3nm#lsknS7O@Wh^Er&KHl<7e=J1XxEf zqlg6iw09j&^QRi|)Jp*BWB#HIDoAJo7&Wm*km@QU`^c|m|~x9Ui*X{>@IRS z3FG+y>y{F=%`TV* z_``{oPH#wOqZJGhPjZzvYSWD&a%igDJh;;NqR#RJK!~`UWzfl)lVh7B-gBMU8IT;M zb*2Bn*&%{d7l5w=ObfhE%$2RgL@D_gmIYv@pv>xmQJclGF6ez5c>4wcx*yPZwM&2d zmV5|p!R^m$7g7mNDl^$uS{zGuBF9w%i@+&YX7^O+@4R8sMvJ17@&)aCC9u*#JvbQ1 zg-ucgY;WjVJE2>{LI_rbsT@k0^)`vF$WYn31s_1HG&}8?t+{hKl zAL`ZFyRSD0_wOt^|3KjdZfk=_z3P0LKHjqDWk9x#P`DB^7_)Xu1DpJqT7$3wrm)@5 z?gYwKB{J9fv$TrAYK`dQFCud0;V#YOH6uX)E_wTj*vH&aJ>ULpvR#Ye^5{<_!5eNo zJf?nrg$5=8P2#4*N5=lMfZyllARS23=O&GQb55ShWJz186@1A7E2dz%*Ko5*bpB{A zjAu>(eV8g5(=Krt4D!H;qpk=ByAzi7m(mqwD}I2)F1-A+J2;lhCF60BaxBp=Vy%k< z33WG1i%~i5?4t(KlHEFjP$Qtn)X)1bk6OU<6nj*|?x|FRf- zJ#DiiJ*BIW)K$0zt?z3jDM#q+W8{>pRU%wxCQ9=y&96lw*OGQwz<)dew#|iPenL9R zP2xO2vbRDkAPdDE#-9he16a6M7BQIx`9*b73U?>8>%J0_%liUYc-t?cnUhsoWdc^D zn}ECrZ;atRwmC2=0WwbHU+&0|#^_H5hsXSAGZjR51FMrN8SpO3 zYC9C*IRgRX@b#wIBaC1L5yRomM$d+a2<49dW2 z9BawG8Sd!;9{h|#H1L_iATbHoQHTl*e2!d926}+ptMPz|XFbvY-_-q+5_D^7;Wej( zcqF>kn5K7ER1tL2<~3STDUkaB*W}|$H#V=P6a*eG@G)Xys6Zr8EQt zJ*M

cVZ)ivMvNM(vi*2`Ke+vPJ*=5>k^4Xa8Pf#NtQ!>s^a>3zuD*c> zmD%EcLn4S1iFYu}a9AtcjjV}uB3HNv;w#DEhWj^*3ZrjW!9YVeIgR&n=P3Y2j6Q3I ze7yk#!BDivG+60vQ1&>lT{J|_JFwDs+1sS&V3W`OMk(b3Yn`M_2%_5Yj^bWiLd_+s z525&#(^T%s%g?k2(XxaFrGUsk^ta5*)2ir|RbizF$cVFCK=h0l18ZnvJ%b4V%*!TE zplRS|P)~q;%(1-Q=1tRYt@PMimP{6ROxAJ(z$HCiu%790FBWtnkdMym=m}Iy&bfi` zwNp1sn?vCS?U~H8J=m9Ir1d}2mz5;4gC)|4dvXED{Vt&b=UU2c>aGQWESX!>>sSCn*jO&+{LoGyV^rfcFC=~sxBZ$a1wM$p%3uli$a z_Y1pxZd4Pa+14Fv+pykS=YV5jd0vp(;x@mnj7;&*0Md*9M2$|Ub@ zRiY~ZUkea}r5i7Cy_6eB4W`!>z1-EbAcjn3YR;q&!?oTK{$5rZ!OjrH4|Nb)8$a12Qn~=Oj&5&(YmI}eW7My z@p;8mJ}&NyiW9!?fZHQdk;JzH(FHrD>XL3Kj^PJ_<$L^=_n#{779RVPKDorvJ_@S? z*;lggK+Sh=j}^yGvGBl~g7bk{B`XUUz+iXfl{tRd z@Eq38-&Ns4=7MZ7J%UYub4F`1TD)0Xjgl7yegTnihU*_(N8ZLg0;wc=ZzQ^pIWbb; z((`jHYr)h4O>m51QcgS7v9bRvJ#4(J~Fh)$GDAUlYxc4>ywCRdS*Q5r|@79nP z+BP}`VouCw`#d*BiP<_Dct%nIk%G}|6TbDQ6K-B>B;X-;BIlT_M+t1lFRtjRQR7Mi zhxtt{GLAONbqF5VKzse+;A-EU*pbFv5NHWz+&{wruj|0&`+tRuf<-5;Ckc?XM-JUY zMam?VU?}ynIHpSjj#h28qW{5%5k{>~;A!+dYVC;zk*x@oB)7XMkKMHa7S)+{O9O}C z<0vae^H+i}mb?MFlyJ37!+l_cTgnCm5q+5IV8;{M&*R(hqWr@bB-z?)Pm1GbMGkT` zdt%)M2tC{PK`cUO127PHV&!{_Tvb&{W~}ptQ%u{=%eZd^BL?nYLGO#Q5@(oW{h8OV zW1sQ=_rJPHOOi062x$S5qr5lkr=k@Ewuhh& zxQg@5XABqrn8jq8V7~=r;=1DJf9}uMyp7WU_<`=^$g;h&bnYZ93E%w@62a_N2!?aK z54`#d2Y~wn1W$6xAE5|-{sV2+ol!=CmmJorTxJ$BJeT9XX`x*M7#yYMA%YgKsd3F2 zK23`x+)`?1?PKiBjcDvN44L5s*IvbMoN(^B4yf~(2Hocjj{+ZX9ZXKG^^=M(cr9fH z0j3^+xjI)OoH?K~`*QF%WeEoXuj=bK6{F@60fun~banMU0 z-gi3w30O97#qS z<>mbV@)&^G!~`W1MU+~urMJ3N?GwXn`&<51^XvS`?eX#h>LqEY+I^p(SpAtK#r2r+ zIqv!Pl<1*i&1QSjv!u-i*!t{NN=uBdZpS@qJaa0m?X<{X65dbUNQiC$UR|OF0(2;( z8(Qu}a#9~h;RF7In8^P8aVo>5;~z`^9oy{#!QI>CCs!(in_H_RB}o_fw#^!1kK6UQ z+HAI8np+`e|dDm;>KyI3INZrI`t@Y%lHR6}_w< z(U%*0f!puELH)yvU-;WZG=cdAkX;Vggham4)k4^>TT`VM@R+#0|A`_$8DSk?fu=qO zqu(WASNXLp$v=1EII_-f6cZSyB+_$@)CW<*k2*X7KPiN38P{akZ!dQ&I0cY2@L+yP znXs?@ljyM7%0ylPB01ElsPjhRm&rY;z@%#JtB=Z%QnpKX-dF3lhE;zDbxIVyAIrEJ zH-1C^R`aOL)XMm@O-7P=H?U5k3B!E_z+0)vZJ=yqi)p{Vu`nh4G6FhT?U@67w6ckA z<`wY;9X<?IcnCkQDG?0_q4bHs2N%_zmi*az4{aJM2XR?wfmG zv3^nP)UE))dyFHZCnZ-y1p-v>)41kGTJqW7_FVeHik$M3Mib6E5 zTr3=^*%e#^*lM+WxU>gh8em$Mm>e$mp{|j7YJGdIm>Rd$r-o7mdr`*G9a3hUV{d3K z)V&p~AS`@ReEMWBThPItvDbD5D1D3vUyC|D!;Y@&sUT;;O| zs^idxaAu`2$B|1bm6@2pL$W~$=vkXf3Z~@W4ejm$9jD*7*V;d=96o|m*QG-hDgC`W z^>pZ3ZULZBSyeOzYq%I&J3!oMh_U&|oeeP#_7QEgC{LjmgV;UGu$svRF?F@wC-&N^ z0^@h@pK8x)c=(Td!bqx&@93#NXkMKK4`jw9F(Pb-XNF=up(-Zw~dru%+PrdBxOn(4sC z17sAb5z`|ihBd~!C^9SrVg1}!6mF<9+ZH}T@`kh+)`aG5fo?~ufIYeasfXdJ<{+1{bEue4|3~?7)S5@9m6|es8M+%WHZosM6R@2 zHF>s0LxIx#qzH@!%etn#@ah@gBYPFiXZoJZ^y4dAJJ1A26SX788y_A8wQwgLY%3`n zfL}-YlzDUn87?2WF2_o0gMMF`)y9PeBsXu@-YN=Dn_$hN5@eRY_zxf%lr+o<+nj}R zZL3JLwMElaxboR@%Q#H~=L0ZyH0f-sT ztd7f)wjT0Olq+V7JdMTmc_fB|6SY`qSthSQtyvX*JQ2T6nTqn0gT%Z#{WH|$} z5dPz*KZ{oc75yDuY-ktP4oQT%X@};qt=OK3bxU)4`L1gz%jn<%cnvg*g#~H&88@Qp z@E*|EfWJl{Hj>vUQXPN-#k;Zv)4Z3t47tcCkus8#6AxN6%IUwcExrz0bfVxW8g9S? z)FDewVXL|S9VR;jF8*9B-q%(^s6EgUczEWB%wZk`QK{Q+zDz`!HL^{uk;}R!yB*aQ zok|N)%1nb{a<@hYQ{1Q=+!BxpIf*fZR&^NQs+3yd85V_|JW{fmgA~ODWA9s5(t_tT z=Z2$ogCGFbGE*|SgT-whLV;!VtJE6?0bLK1KR?#Hgcpf^oFeKle6H%Y4W5Wnn^Uog z+A6aGkdkz*BxG>@O`#+(v8{pppXWU(MWFwDRedxQVh!B3HXXG(!ckCYoxg?pK zsbVB$89-fE*aOrL^kL&!4OVGCZQ458G^#QK?EYcbAt51{hNG%hs)bPPm^%F>4B{vQ zElska_HHr<2&+W&G4~2OhkP?;Ij75$KUhV=T7Pl52_asX6uM&uR!#Z%wbgpRVTz0Gav) z)5pq+(h^(F>6?XRavS+$!lBk*3Hn4pD@z?^9Nx_amB6R#e5z-8Xt*-0!p2%(`RN@$ z?LhN+!>K!@A|d+(30)6pplo0yd*PP5>yY<%y3I})W2OhtcV;RX$4H3+d*%LSz)iBQ zfsxU*V{G1~N-nJ)eVisQ#;XG!Kl{uDsCcu0i@*8)X`JZVEmyh0LaSJMykN;ME6~mo zE)4|*^B379x)^u<4+y8OGc^|;#P^WV9yj#p+0X1UB0AXuKM&^8K8zzuyqe#xyP|H| zS*LfmkP05=T<+$D)k%E^L4C>|ZlOJmnM|xuV+Q^&vco<*xv@}%0uj!GJ<;C+b?k(I zqSrcKCkJh%u>R%^KnJH;F(5l|;!;`=_IHK=nHMj-EL6{gF;Y?HQ=x~Gq#`~8_`N@#;gg!w+GKew7yt(Bl6ro7bXhZSoDv0}#mcS*k0B4&wiLn!gY(k* zF4YFzivubT9Bqq1<{EXrw1P$kNX!sS{lAyI+>X(ExAOaq=Mh!N+M4AEt@fMf!9zy| zkFqEnX}y@+mZ)6CeOvk*H;uuVwp{E<(Zd5KXj-uVik_bc=N_fx6FZ^~a?cNS;%bDb5A8*xC4(GWsd%73mEUKs3$@MSK{TnIKlc4 zY`T#s%ga6c&y+m77`l#-cQXYCqe(Ud!R=S~1U;|40u6>!D^q-HE^NP4_QXzR*>*h> zxWx+vhN{YFy(E=AB2f;dTD`9Nre9HcTKQ>ouEtZd;0U<`f55<}3>n|g<)L|qmNCqhCw`z4RkHAe9!nxG3$UkPhO=PDb%iQ+?5{FXZuvTF(?q?6X z>aGLawgo5Y(<6W0GVLZFqpIl*;dM=$Hs-7Ir9Lj1T`fzkWN}H>(pNioF~f?Z-DFq>y< zF@e+qV8IN9uWIAc3T4B$rxW7-JewbPc!EAbr4?-c;RS~TXm;;$Ba{rQ@-as_$~&)1 zeMTW)cltcl)~CtyTIqWRd>D@z+YvyJ)t+!bWNpXl_jUY^LQZzSj}`R==cN(@_RGtU zSiaDD@or5v5--(ZHh*QR?T}P!>MRXRZdernFE#2>;)3vi#yPM|L`WWFP`lliqt{p4 zx`4i;YC}H+4@q2nR$G&_vy?8>1*IDw$>VY6*~06-mY$~KO+|A7rmd_Js*ZQ-nEJB~ zZ6fc-nK11?ImO9*CmP4_?F{q;UjH7g+(Kh!xdx>`@I*W2E-%N;4(Gf!$H3wly-=G5 zMh$C-2VYT04SD`Y5To2MI`*FVNi`&+9$iZw2f9-OE?qf|I|tgsILhVQ`RLFG`b_~{ z7kQc}z=tVziLN;ZMc-w^(T7E1x5kon7kvQXOfq!iD99Z47#P?&3E30@zc61xJw9o~ z$G&|$1#;;R&Ck}!HJ1(+W0rCiemm6yNvuLm))YV|Q^)Yax4StIuHwyDR0gKuhfm&g zVXNB!|Df%nhzUkXJON1Wu=oc~kE_;P&xQr>#WMk{0r8Xq7Kr9-Ly<5jgv^&@dFn>G zL2fu95~<4y`v57XrBhS~h#zJ0qMZ$wgcgcULA>v<+r8d)8fU=qXes=D=F%+!i}C#$ z?~(u7RuS7DZaI>FPW5(Iu>v!x2ejvaq>)Voq40TfiqZ!OOwRQG(=)ENbw8z{96HSj z)Mu?yY~{xQ&}&F#Ju*y~lu_aGXuaib&}dhLDY(009qh|5MO`BRYwrhlI1MjgyKjq@ z%wHP|T*O|b1)79!-sU)fPG`;nTt#cEcGQO?0I7Y#hNEAgWmvEiW90Hd)IHEEiPnq) zCqgjjS7(aQ(41oDWVb0>#|t7-FNP~KPb-sOD+p=^Q5`6ySL2VGZ%KkC0)Tv{AxoF`}>4=i8t!#mmEV|K7meS^EEEKOl#W*^!V7xZ{xcg zVMd7316iD=D4fy4_c!z5xpU^y%@w!+uUkY;c#%u6&m~sZL`3c!J&DrJ!OKnXUXm65 zmXB=*q*)#%K%_RojrvU|_Shs!dk(bY!nyZ@5l21RTQ|(@rO!ampKkmV9ND6N8z7HkHOr(05_ZVF zLu<~UDLZ=sji$`YGc4e_mMyBqQuYsYCN50_j4YA>v!!nTf^R@VU?qL)m>tcVKj32t zYFQyG6hCDInVD?pM%0;jXyfPSe$Ce!WQU(b^Q zXkIlIR}lVj&xfe#oAEmxjeGoc6^LK7iDnb@bVYsuH`Vr(bd$ledHkC$TF#w)=ac@j zwwsCRx8~6HpyyWvv4Ge*6yY}#SVziD7&*0F4CLW5g6Q)`&(hP5%gj&*J#lQw7(*gP z1_OaS>x2`Emsc=IaMOkIW0B?dynK30J${QNs)nTe`sVe@duzhi{B8y=me2h z`NUxaEO#yf)>n0?*j;FqZ)%v!?}mgBH(mp^tNWhBmjcrQp^DfDW6~)qI$Oj9?4c`K$F%N>6YP_cNl8(0=ZLw8*1?^7 z5=uY+Bou=NqMXYiCgN0@)PzJ={D5I(q5V(p-G^q}3QQ@Crh+2_SuQy0fl=D#hn0Pa z)w@M>M;4dR&>PYB<&hg<6W+IBA!!Zy?>5ZB=%}kByoT zD*IDj{$qGOPetkh?;oPVr4MqaX%1Pak;i&|i{c}WnCco&VQR!69e+>+pS#`SB!{3X zM6O<_d}|?UT<=wDxs95HLSd-9J@;$}2oHB@YJ<-Ija{?N4@qb7z6mCZxsmMeoSG;v z75p^;Hfmy#?J(e!Uej*WDZBZ=OBbH0PC~6D#nL~6rW$1fQ)PtopsS$_+9gd|@| z?-1G0`O9V7I11a6acjs{^r8K@A;O#>lg=8i)wBlzj%BcA5-+Yf1+UBHW+34yFjb7Z zQIk9wlsWGkmcu*;mU%f!Y<-v5j*c(POVc|vr)K*mrYTnASx1YPzlq2K$k-j5fAQgP zQro_tq16LH^^#WoHvv*OMi%yg7EQANFvvopN1pc!nCu3LNL-FqHHzu({b;&Lbg!pg zwRSECRi5ew=637BZuXc6CZPDmRUyt-*BAH>gJe}vS8(bEDY$t7co4gLD2#*#)3pI%%JcD`LNT2lR{C(_kVkqhZJFn+tM@3D6YO1 zsJ7M;7p%Mmx`KJ$8$JrZ_zm}nU_`t zkxug4Az5JBq&MVg_Rn+=qdGEK8cR#Mf;*?87#j}*XB~?NRqs(CNFk+Rpn_~;ywXUN zNssU)KElJkcjmzZQ)e*6l`#oLXVhMHW~73mb5XEq5J&LbCbW#M#V+&*#^lz})L0To zrijK{z!5T=%M|@Oj)KjOl>Bco3-#>)oIB_(lL%S?^top%D|S)NgoxFWQ*v~6A;W~1 zC6dGf(Cp@oxU-Hd`MC|QNBO2+`0i+xM)7a?;m7k7tJRkPeWR%XYR}*B&!n+OOr4T` z!<_bL8LaRQr`iBVL1^d!T5A&EK({sQk@{!@%4HZR{~Gdt#8Ld zE|mO$ryC9fKUpZY9F_T&snN434CDsN9W4y2S;=#&Gi+hSdV=Z)wc&}@5kvIDsgHEm z6wEG@`q1GI8f>1fDbaStjM@4Gf>7N^7Dxo96A%}hCoz!NYnP2@74(vttfyCd0j<3Q zUw~&7DlmlnqcJuC+mN4?unSfN15~|o>ZO@KiJ}J9Xr-+3>Hw00 z*Yb~xFUs-Vhqki@Zm=8O)Mx;2VA#pdsr8JYp|5_I29z2e163cuVXapJX-PWok@9Zc z)88DybYnydL&k>ej+8TEArGbi#pEUc(%SK>Kqx|@C=ilmO{!lG%!f#U5DAxlXYH-H z1U~x)yy%?B79QN1uv2wyo31&T!Mwx0qH3v}IkcV}@v&kBH<|-Vja#|f?TDNwxXHhu zdLW6029)~-St?2Vt8x7T|Dp}&IPr8B7DM&Pb`0T7TQI0fT6_*u56Jg(^SUDe&lplz zHWCK}#(ke->RY02QpSKusNX2*9tvfz-K+8kZG>F_sCd44FbqwHpTQ)H>eLG%4oJ?6 z{6@BHce1Hk)lr6h_0El6UtedJ+prWO^@I6wHF&6tP~^xGi=c~cWJQ?hKrOwlZ>0imDuY6 zuL%2RkETK+CF+{>%Obi`#t*6nC@zlwlKS@iEz?~9#D?tzvg{&e!@rz7!vk_b+7fL) zdCnLGUBaAkvPcaAJBXD`8@1-n2TafIeJfgQ_y^;OO#%+IIL^1hZh`^?17Zb{F41%c z41VPp{ngpIGx!vulb_J4(yn027k{e)cvIwVcP0L^V+}g4;uR6mJojU;_im(}K)k5H zv+Vr>s2`;Ky#Ue;iD(CzyZ^Xw?7^Rxr4ESRAzVtiL~rvJHY(Et+5j2tCSYsa@5sY7L-^|PiGePHQkxW? zU=$%L{$maW0u@Su87!m!()X%?F`@Xz+@Z3fB4YQaCz~3CX>TG0_QhzcGo?PwYIbjw zcC{?f$78$Zrufn|*+JBsE;%*>U9N3~g$(8Ld@~#}Aza85cS!r(wvQnLay!fp%=5?r zdqfZoZp}`@16vYC{%ieezfNF#O!6CgXcrutp-G4b`4VYo2NaSz^(z)wD?l0o;o1ZL z7oW%6ong|>A%}hjizBrD+_R}7R1@fZk}OSQmrxx84OED53dw6ep5DI!SC!;t)qdkJ z__tGsNwmX)@KZR`wuwz!*ZgYKaA_<8Bf83XrE^Y?{xOnfQ1($rFK{5>BXb{2!^n9O z()yJLits8}QpdXJr}OlybG{@M6B4U$5mVX)V&b3^Uf$;fh^-YVKIdQOPvIH8EQ46? z6yT#T?8w1eg6I(aLAkC0frE8^&QU$-6Fpm=OSzGsTUjW02#lglINWMc=^Qu(>EoE1 zuKT>8@ezXrBZL)|ju|7YfH`@o>Z{h+7}ur(#uS_gaiVa!k}C;I7Oh4-5EK8PUF zAA3%zcnOIHw+yo_SN?fIx}^;13DdG{3hHN+ggVMhFVKo1O?qboc8&t)frzSVodbo?A9UAaKs`~3R(&s z#<|AdHGP?28ehKzUV|0BYr+4*3kBKzq6z#ng)EmA2(he$20O7@_#i?6uZh^vF~(~{ zdoIMofba__WvZ^(9wo!!C^$?*ryNKGOX(5~*M{ueLy48Bvbn8KW82jo@0A58uSb+Bf(m5n%4}qtv=QHp3S@yvv5n&+j#*I%?`f_OQKy=mu%O-9y z_ApZB=whJ!Kex*Ur+v)Vq}5HF?LuwP{tk5n?UZQYoHEV(a$`n^J%(63u)c$Oy1?M-Nd0qgPDXIDwgLHPBdF zjVY(sJPTZdY3aHY>CoqhKDn#_vKb=;NRpX4hd{Gn{10RLMuUh|psiS#h&HHqg{iy+ zc`B{BAb_;s07Yh_P&@{@9((p=*1a<$lFmO*y-^GXk~=N_lg_KDs{1yP@!Prn)`HfQ zB{!`L*4Z61GS$=uoSJ~P))I2J*wmZlzk+ny4SvZ{*)eWE2b$RVz9 zIe*srVSMf|GgXT{9CDZ9bD?Qegh#jrFg|e6bY%Hiqq;ON_!13XQ*hRNkXs>h_1F% zRhi{!vP9APStmq#DGN%D7-e|vpC=vJXjZ-eAXfB3EFSz8Ey-YnBua;qj~@3Rxd$vd zW>+Ux*{tjbxq#@QM!xm^E8Qj;CR+c(s-_xwWNl{`#S{2)7AH#qs6AM?JA1UpPgS^aCj(O66owrf$f5Y}0 z8|Ib!qY+pEmpc8Aj2;P+w+eg0Bbzax4D({$!nAmEgXp6XUb4Xe3V&qpq|eA^dUB3* zQF6i5FyRfZw#~hquz+w6wy41ZX}_0&0&^^VyTL-=<&L+eRJ16hZ*IiG_O4}Io0DM& zydH{ag+ALHPZnAP72e%7gVEPaC{wY%~JBc*8wmYOtuT<**7F_7KT-8qzF znuh`_0JBT=E|W?Isy0nglEJ(5$D^22$|uL&-GWDzWS!4A&VQ@*y_dIS$iUkNresmfck|e|QCoqZbgX8ro(pSqg~pS` z$BJ|Z(0<$nXYJpp?UOG4xz9y}PpEmerIuh74625}Ln|qo{NR`YE7@C)U-`XQcV?`b za0-pocL4xLqy|j?4Ix{6T6+Bj7p!U1Zr6XQDIgv3H&Qv2#YZ4ZeWBd@)p0rpn~U!T zKo>hci;!^Zcr^GH?rV9Iy`ir})$xj4xa2Rj()i{B8F5o>zr8dIaE4_Lro_IYCWiR%C!lS+!bCZz@MW#>zSnifo#8&de*DaB>bpX;o%#vArGt-f6f@V;)aCMae9Hy7G zn~V59#@+722cTTW-Zriq8SSJ*B~@BUO<*1Xbc`t1G$sYcl`#^;Efa2J_>jZ%ZEgFE z2q{qw6yrAsPUEoF)B6(Dka6Z0ZqPEEC#iuwxR0{twhGKU2Ai}1Jq!Slq?T`viu=>~ zV7IN=nWH-B`B84`#!pe#L$tC5w091qyCMbh0rRgoOQg^?9NGm1V{bp5Z;NBL%*^t!ZD-A01~w3fLVw3_d=p}sz!cJ#C+VTls> zR6S~!F&9rfAekTo==E`ZOl=p<`m{P;*W6Sk{I-&F8;BluZmgE;(sAhr#Y-J((MS&U z?c3(XB?b+8K&PF^2xN6LnndTA68SF!rk`8MyeQ(n(aPc)TRmayQIzR+ssgGB)09R! zO|ZTMrg4^>`(*e>0(m5HBh^i%jp5xj{qd3?(IZ)PZS%ng`7A;-Ioi8m>BCN2F%nKH zANrK=BN=NmK6s9=s@E9+LO{L00stJd%MrN-!3FJur~F1@hH(%HqTkRjy{b@dj8r;D z0kI3f@6wW~!b<)3(bYuHyys=Kpd-D{fNL3mhuGH$1t~TE{5!cijv8l;V7ud4k|oZ& z8$56fsA|z8E#LUdfn| zJ;ZS$0tle@k|MKh*LUH_2Wa|0ygt9J5XgdE=IkWPBX2Oq0lQBD1!vHs53{!%y4wyGVzw-*6^7LPfZ5)Hf87OO4uDOy13)NF6Z?f)YD@b+ z{kPDNFfv+`_5>P5m(D*_&w%Pb1(I6qEKm8MDb>vji<&-T0H-@3!cPFu=}yDpxrna# z1wKMZHOc=wMm%Q0SR2^zLaZz-e!zu_st+$-$?uu`01-bOvc2?insg}62`LkDNtsMI zhjOk=)52K@>3HY32hX4v1Ye_~d&CcLzEKuM;e}Nx`UAIe_#l@5X2dan043R#I%I`x z!MzNkszHI^>P3E#=mu zIf$Ii1Fzx>PVY43*U8^-Od_kAKF6B3^g9TRnUKY4X}h@M1CJSlF7>m@fhX)*X}VY1 zz=M$+0`-y|MR#}5^KTPi0Ok{TgojBS-RZF!uocKA8ddK<3PZMb()yEEo4DJ(1_DxQ zPpvJ?IWRL|R~twCUqB0AF+B?Y$*XsNDa44%2R&AEh?DkgC?Z|?!!mS@y_?qsaC#IK z=2uK4bds(M1xy6UJyP0Z5ll2*FGP3H4i(F+KhdMtLvhAbpYM!s1Fq8y?XqP3H&U0A zmN1n!`o)uYx}gGnefe~YGnEtE z0p#SZ;qZ1Y_iZ8&&h8VPE8@8j1?x&#BmQNF)hl&A1qE~yQ_!3IM2`MryVIh-gyK3O zj-3p$8^JooRE*2`1Ig)|l1KWf>Q*R5#Cet%6S!(vD=0ZkiokkLQ@<^g5F46E+nThGO@f~GFp8{SZj z>LZ=c1wrMH*f&=oo-ERh6?%J+)@?!xReySJl~fPp&Vqh&1>w~FFZ>ult_8`zB0npd zfVnp(WshWzi$cLWCx$*~04GrN$YP`@jl++|$3!UKg6{QGJ;}671pY|vxH+|s1xA`Z z+25%WahPBKg7hd2juIr2s#zTo7oq1F8ZrN71ZRm>%K(;Rrf}`#AlYWMT}#YW%aNwe zugxd*DRUk72azN@nJ5PmXTLPI3Li=nI+UqJA%SSooMj3)xfgyd0t)_FX#32b#_Zwo z^aSF3Mbody`?XZ2Ax&BTB`QOa28IS&A2s?1^bE4B%zqMo3n|J@+NBQHYPdn(q;6Qq z1v|ZcnNZeJKtA{^aZ|XZ6lbuG-K}q@ks&E`n*xjz07Z18Kjg`J9Tyf&0!)FE5vc+4 zJdDKQ@1=0&X0mW32D8>5Gf%3r3$LsZd4i>BfAwHL3(ei>vnv-))(AWh29rc?)S{sL z8(LYAu!J^eW@lKzDq_5WmJ5lXQR>&Ghpa;?tx8H}fIe_{XjtI|sXN zhPv+{V8_$61M9UyTMxg=YhHeQ2h(uZHH#Y0*X*vLYi;dwmXqV90_qc#;9oueLoI=rwM!rroJ}WiMAVL2AWXU<cPX7S+nGSZ_ecVxOp1+O1-oEF48Ewl<~mjTtNhq48+J0= z@YW(4>#afi*zOy>1-N7@S2b4zz}HPGOALjP1Mw?QmPO9as3Sk@9-0B~1R&dVfC>gc z;B6UABjv-jO;#Oj#a(8c)~MnEKSXuB1eHLY{H07b`#qEYSz*ZS5&XfW8m8J%%{LLH zKZgL>29T{l;?i7^X*=DR3!mHo!h0gpu7v^!*J=KcLXde z)2uR~ymft(Uu}_|23hF@?5pJV6RJ;nK8OS=Bod(`W?0KS?t5)bKvhTY2I5kD*Q!cp zFW1iZ`ILT`qzExZrdg}6<#;IQ25n!A1U3T(=G`#74(&O!o){SCvLURYgq$FNSEQlP zBur8E0wc)yp`EF&_E8a^-g7&5KWN%3#89fyZu1L!*J_QX0y6fqwH0>5Bv%?P`8!Uu z;dbRvf|dF=5RO-W`$tLW2W)v@EhHsWN&#sED#G+yD=JITo)ZyTis|ewkU{P%1Qy2p z@s?iwWB-%7dd`$C(s$1G>!0L{kOe);-ZQHf0%Y!uV^G0haU!L#Rbv7E^p$h`pnf)X zGRmz&AA`iJ0K_}g6(1 z1S|%(ApT|m&QU*sPNr7CcHf3m$rZj5ZL6SC?B8*M0EGo9J;`0TsI%btjf9C!M7KI;_n5;*RQd$EA1f7K@4#gFA zC5OSjPb;6JJv88}`(dILz~HR%&P1O}9?f>aKS#-~wKXLHN{t1o4<51=*e=boef*~VM&1p?v3R;v9|;qb_2jkYY^ z5v+`$p1m7i<6p9|p7d`E1@8s7hRL?rWiBs)-D|k`7|fnNj>#|ehk+k!9srF^0#EZU zwevMI8YFiJxjMMETbm^cepdv_vN}RlxY|wb0XkdE=7A3B9Mx&H+X1*4Y|E4Z{#v5G zs+nUb0ti@r0r4UUweOpc9bRD>r}6tzu}&`RpQOGtu}jIpBc>Ht1Q7Y45z+l6RRynp zx=DRPxFX!Ax*PLTNms4dDqEu;0Vnd&vk+OeQ&7GN1`Dlk?iGhZ76&zM+z>cQH#=F_ZtfL$0MK0;pyGXc0yh~_7fGT? z((r9P6H;7FqCC&X-4Em$1Ed+aaqVW#-Huj^Ulx(aATKx!5kFzlsiS}Uok)Kj2h*xq z7^2C-Kh+NQ*bQb!$BtmSI^-A=Z*>9&yneDw2XdKUmBkec5usO7l_8@ePNmClu6uZk^ug`;YWUM0n}lV`>vmd+)e5~ z9m(%Y#Fcr}`JdS*nB4 z#V(73ua)^PTFPhw0vMYz$cghbB~O}7%r7_ufE#_=l3EI9>ze$!xj%2D1ju1|6r?;o z`=7Vz|E_cWpHsy@2OTCjS+vQ|Jpaq%1u>!Rr^|qvs5mk~TU~>70{}27JCp=3BkHWH zw3}Xz2Q;GH2f76gRY0^)?xJ=o13EuCjN-CWMVOxxv^e1k22n~#1{A@c9ImV_Q}FP~ z1cd6PptI;U%p?doL)r3n096y5n39&!GDYDh(3MUfv&Yy86UXfR`4E^~lDv>Z4gW?BN5?t=JV0e?Hsfd(agm1&Jt#RbV|BG4E(hq&;}b<(C*!ku<0 z1OV-OPLS}tc0!be9j0+Va1KGRESGWlB=qG~?KfHa1w*Jnz4?E`7}7#PP;BU9lG7sl z#%u1|ACJ{LOGKfK10WOYsb4$jP@CfYq1;4V$1;izi^$e^i*xmPoa7H{j z^!020Bgz2J@rWLh1ow?{+x@M(7_7=8Om|NORt4ePwx-O_oKT%BoLBl=5KaH_rzKELF)>~2eq2FoFmN2Kj0Y;*g*$;A3B|*7(*3(F}Q1j zH>yb^0A2QL)}hYs^iHD;@LlyrW_0oj))$60~!8ca@&f&8^- z*Y%BJW+Wx|%X_9h*I;`61uYyQE$q%dJ5Dc_1*AH$Vyk8gK&AKu$e=EHd7A%q0TbPt zed>m{TBD+8mBb}k9|Vm-N~_ek{i8?VEI%^D1>UW)XCl)YtB95)+d_|b0{1rLM(I;z zrNHNun6wAR21Dv9X|tSsXy`ElJ3;*r*DE9pE`Cgi(~VP9ejt=n2SZ%v8yj1bef;UuRq=Un;R6@>==H&liIZqv)nTTh3No^wE~u z1elTTFYW>$(*%7dvhEvkYYD&|v07+BE1Z9K80I+11inol@8AXKPRJV<-rD*wbf=X; zz_l$e_*qF2JO2@h0lMPhc_fh)9LW7Y%PtkGvz|Y9tpYGI=^g@;#&KP416f)YK$DtU z*5Ie%t<89pwNFlgaXp`0|@p(EbX=pHb;wQTa!pF5`2)!BSrUF#p=&S zr>dKRnF|K@>m5OWHEIlX2>vPsfhc0MWdY z(vGugU+2d}4LfDTlLMjUk)0d6!2Kl!&1U4*^?N0*@72|L#t!m>3BEn3g#h(vA04)fVqW(>i^1y-b)cnq4~&p8=vYwp$DFZ}Y(BKuc?e2OPAvAN6#1EOF5L7rzOOlaVOgsm=U zPonS-uZT8tNOZIc8?lw41g{b^v#kCnn&BwGhqd|+WdHcI(O8XN@$tTd4&N%&2S74~ z9fK>=tY4&P(O-;In^(U90(~!usIR)b6U!2i0F1|;ZslVViPe^^ifD3qoilkHH-QcQ z+quJw2yuuV z0)}{-RFY@06jZHK>wNb&qu@w6I6(L?ID=pHZt+gE00qthF4Kl1X$klDdleetxdf7O zw67F(TY(rf3UVKyNV=0k{l4El`9c z^T~VYyV!R7;ZuSRP`8*PkD{%>syQ@`1LJSK<=kaquB%XCyC7ans2n-8@__0n4x~x+ z`U2HH1Qx>01BYAR8aMQO#4_T@*Ew%>q7rbSmjdR%e}1#O1e&R$!6r&XuQfq#XygZ= zN@5D7JeYw2kQ&kBU&jDu0+kY}999ag)GqvSA$_e9G!lR)Q*-u{FmYh*q3uYzVp{7OP*ax}w)F;je)B1XnNy&ou;T`ke@T z;lK#AvC@2^k6NH&^vG}Ylzzkb0rbV)uyCWL`|S(j{(>YCP-qEUzE**z%6v-Wa47fP$Q1v62XUlyweonX(Q6rJ01@wD$B4po&C8{%};4k<6E{iN)#|S#u!GcTvzRFMY#4n%PW5zkQ*0V)0G-mildjYkG1VU9r)+dM&z*9eWyWZPFt`UWy zr6CWkhaYga^`0=m1&C|2=Ju#z5eHV7IQ$RF2U4!d081H~kYN5x?|~3E1U1&sOWiwo zJpbKE_R+fsUl2_L_rfsm+!Ys=Bxio|2d6^b|2M0Gerc1zv0Q#7v(@g4T%hqJ>9aoy z`&MS`2XoBM#u*nc?#YF3=^MkGAU>$y1S?;*6CX~Jt_OQ%2mYGp+^nGD^P7*N&=BQk zo}IOZX<3Rg3p9@jUh=RQ0ATTHCzANwaOxt)1USMm0qvB}m|C$sH=S>Pi5cK01pPcm zjoHgCr3!h2M04j8|!+l-Z0zQ*`5;_X+7*^2akB(uU$9bWc#OgTq+>bm1C8Igv0T0`&`$v(a z(S7{+c&GFRqnli<2}6(%&-vw~@XdJ<1F1PP8Bl*KD4{UE!TDC5by$@9`>zEz?)&BJ zn0%m818c+|MAiier0(BDva#eAL)t( zKl^Czuw25kmS@^`p*NcU0<>7W=2R=`xuWHv+s0#3VeW&{HlF588`db$%1`Ez8^_|2(a{AcR zvAe}#bl&ZXAR=8Z8}Ul-&t?Z?2k|Z<{>5S_1%VL_UJ*X?V*Mq^9^h5kv$CvB70|E? z1svpx%&87<=dI4iRdq&Qw2 zDF~8~6*J5PlCcQ~0K;bo9V~Liauxz80&OqtSR?rsTh95$d2TzpkFIi!UjOR{pt+#x20g<~xi zW(HJ70?z5u)}qV7bjl19-`Ih>gJ0&-(eqdbr&7OT+%!ew@0QW#G0pxAgVQJPa!tB}_-7XN1MC=C zTlREo+nZKip}a6Fvewm(yz_K4F%Vv6dlrf52cAenY4h(1if4H;u9ad=K9zksD)I%( z)&lr|Ddo}V2a-D~bffz?u)kZ}g<8y1&>ej0H|^&Psz8&A_GrAt1uf5AeLXd~pY9p! z$iu8-A>rWAaU?RSc3k`hs+Qz-28c5nD)b1^Q04dn;@TFnR#%H%b-9q`6l~`f>U#pf z1yYC0<1Db{F%(*uO#`x2t))KZ86$elm%B$o12f>201^}^fq(#SUxRyV8FKSwcCdQF z0nbk;$Q=#YJpThn0s-`MiY0;3nRHMy4IC3^DT^m~)&63~+<&Z6_pOix1(HW3`1dj5 zF)F6u6FY0c4yo~}Qd|;t&FTOEYXu3%1DM0+I=1&Tz<}G#o2>QX3y= z)3%ZiAWGC91lf5K&n_}H0^v%M=AGZnAud7n@jcIJ#!bisr8dRRyXSXMNBMiV1k=IA zIC4oSirlnNICN z&r>h_da%PQ1VjC{NrN`CTN?B~s32Z)#^t;hVDaGJouEBl%Efcl1PHvcYdau62NY?E zy>}vgiuA`j%*k6Rn{d2_|2#)F0$i&C>r-o83J)cDWFL>JEgH7LGbjlW4ULn{*nx+3 z1Awi8`Yaby$}*Q`*3K{N288sS1Q)chVaznPSSnsQ!Gjf~9kaTY9?u@!_hc-TCW>ao0OQ`8AvJ

IDbN0al*zbM5ot&-B$NuixL?e{x;BY3 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 z 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 z

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

ocsQQSK_-%vOxY1OwSt?; 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%0DQ%@ZcyqmtlK%(Id*k`6^IhWy(dy81f?nL0qu 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

F8#1K9QtTw98> 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

sk3x%X7mY1MdV*jv=7Qj;s64X!4CGPc=yb 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

6A7QP3t z4qqtcEiSjcj<%vzYv_(L0;dxJiw6x(ARCCJWUFcC0^ ztYGv7StDsruh;#W(L5-ds8G-V=V<#NqxH07bbNgIR|^0E;(ZZkQ={oa&Nq2X6qH$4 zYB#3?C=t`cSX^avjE3_BE@xgfrursyWxJV@ec#eN2WpuH$oGkXSsf+~I&wM(8?d!C zb?ANAp|xm9mJvIV{hjRni`4RjL;_(10U?|LBV_^CN?=DBoeMGLf(ON*BcrQ|D<_9! zu}8%gf-!6X(8U|`hR2ws{J^xcCDthM3^NPVpmlO91@~n}(L_lA37uKJ=N7CM_sP4? zeNn`jhUzY!9Ogj&YyRrk)w?JMJd}pmIMcMf5EKr3aJm~@oz~J*hKcc|;)$;qXTx*_ zjloDibUMh-$OGr>5*@i6aO1yo!>l5rxGhYkPTdd!ptqRVbA}R+X%QcdyN@hf?tPDy z8@z;ZQ8$FRLByQ}ac8)K>*?+g-|!;fuH39MV@-OKY=S_ z%3gH?`dE6@b-WTDuRN`zz(|yZsQSDD$4Hppa5%?6T@apP*`{7OPiVtCD)gpR#Rz3G zN902W(Z=OjpA`zf9J}&9f+!c>tXAS9K2Q-_Y+o|L9y{{~0MJm>jCq}iCw|2M=3l;I~F z8*)C7;DeicOfeTC(lLW{QfS&3U10Xs@DJt&tw{SJS13DM8JNgxjV$d&L7_qDmy={{ z9klLPJfd=#hSf}Qg{%K?`nqhFl1_1-KGFGn-i`YzuASZBE_PyxfTZiP?{l74xTNfgOKgs4@tezTDU~9l}50*S8?@hVKn9d zK5~yw5kW$SjFr`^4*BgK?>As40|~3*#-Pf}PS`jFt!9`kK_!)0JVJvMC6nm+P3h(* zNPPXCS~dgK9sHsPYxo2*CZj5rz)4A8zW-`x`$ra;U<4=&OM}!*SCMW6$!7UeTYeX3 z#)tPeqNe#CKHt2)r`(_52-Ru%b)GB&nIpTS%DqUas}>pA*OUM%r+rnBX{=cA4kVLH z_WXhe$cIIL>c_2=JJR7NrT)hZulDpoB^ak$gQttwE9M&ji*5#4hU4d{K?y|s9$VHW zVE9K7a$~cq$cZNsR-k(akBxD1{ZpBahikm*Z%PADNSNu%_MyKBReGl1SP%OECcOL+ z;&w6}kZ;p8Tn%=l^&*$jX*_I<4Byf=s%oMFP*>I0iG^j+(eaHwVap@%+FcGo2gd2v zvZWEfdv{3&fNC{FTIk$}YS_(Her}7|T$Ig=$A;hp$ne4@V&2tZrW{Kj=uCg$zYxBu zOvu~n9VP}G%8U616)L`j%^75LsjU&iGzi!Ye_#2%s$pSna|O-uy~GaFShasYX{_#M-w8Xn^hg_D8WeyVD|VIG$|o06gdRW6FS|N@(sG zvq#Yb_oP_SbDp>4=IN%J>g4mm{-tftGu6g(k^5L$ryt)0x6P$q(z(5M7}=f zU^wc<)**CCYgYL4LNGxGh@lXpD>6GftOe%$#wK`&BouYug(sF%k2xd9 z6mFxminK zARb#8T3JA_)_%{PFUw#0-D!~kJ*6vQ)6*9jqIh2LSEo}F$4>92Ik6~Q7K}#S7@^Vx zfpG>H0NpWd1iG$IM416SPL^(*lYWBIJ3Am5rv<(P77%-hMhRJOWxtLrh>J^wvw+E+ zlbiE!uDdS>Ly!Ff3y3D%D!*}<V3v*JWQK`Gm(N z%@IjDhJkZg?%)r_YQ>BKX<1ii(D=DcM?lEsGETZ7!-=!~V{kvmq}!)2=cxDw)EGqT zpoV3}PH3G=O;N*)9d7hMk67u0G3?Qq)7Se36&7+Ca1{!(7b-gS6g|kS{j{o#lVoWT z_9Ae2vH<@EoH|0IOK@cpA?;RE)MUpO0VN@+RivbwiC~-;Cb*9PbX@D40&I&kM6V^O zMR_%WtP4UfCU`1huqho%(Oo11@y|fb@P2&1hN85|n~oOQc0%rGN1wF&L>lY}*%6cg z#A-S7Vg!gSm`RP28~1q;Y<0og*zXJ z?Df@;Z=NB=DvJKH-+~_geH|V-M1rsZ%7iG|{M4XFG008Sy5eBiwguOVTGhv0@*J*J zK@koFz4g%->OY<5WCVkLSI$9PCjza>K9p;L`>TLV7~bjxrQQnZkmi+5L{%KoV3b!i z1ZypR|FAe&=&rr>uf{9}JeBhCftBjm;inU21zvtdbaokr(x#t@XVO6dy+%y~-OwGD zXV1>9wb5mH&TiFLCp)N&(=tPA;i)WGv_j=yMi_8 zr)0VKSvY?JZ9n+hW(h3<ft+-*wZ}pMhk6!TO@rvhAHIsADGP6bIh@RJkba|}2nty?xew+C zkIsXK>6e@Xiky+ToF&Ov`qSc@2)Ik>l|aE<0xGu!)XdB6A|pZgMsqbCuLKQ?yGJvZ z+c>O$zB%bJIBq`&1$YY)QLSAjb7y!XF*e(lXfI9oHBJSb1ZA?Ny98zeEhe~oHUye& z98m((oZ{<~ea;RMRpK>NKs&t8+vJ1>0v{@lk5VC(1%CV9IU{YnB}hVfhoRmrgh=Q2 zi9RL;+V-Te#{$LRbH3Hs!kI$@mE-l+ z@V(-@B}fLy;t8JzU9u2YUKo7Ra*t_A5LbHu<{ggN{e*RNy3ztp?>FOq9yb16mti@? z_W;JK#!5wvtbQwZB+ESg}|+<%N(+ArX10+R$hKZL7-9bW7FMsNaY_>WUChYRH=0 zM&SWL-00#}`cr-c@3I?DxkmI-2}ejQ=NLl5h5&m6;Gwoi@Lqti2!q@QvcdJj*ez_! zAWn5N?jU$Eqe>k)-I(F8kmDRJ3Rqv4tzGXR)x-URswjzxi} zPcydzTtpu;rv?ghMJk?S^g1B3@N|;wlf70fc7Ldx;`DC^|2PuST0uIaf8vf_X-e}! z&F{c`H#4_%cQZ*1HpV&uj9c&iOpqHtdYdg!MHa3CfnF`4u$q5!_)jbKxCoB|GO;F* z-4g$eJXv#hR?mnb^e*~@+Ryp+)1>UM5uwiA5>AY3saJi`H5%dzZ+ z^noED1*hHE*_;lbA1$MRWgw*igEy@bMiZ+g|I4kaEK1u&cQiy0OjE=T`ZQ`MILa3U zK*CN}tqY=}h`e?FtB;~#<($nVa4JrW1f@yV zDGlFRfGKieM<`bA zLc1F*66m>^xp~fcWlJsY1`XADXDT=Vt2pqFLh215vo>w4QQbw1*lp>e85PXC?(E&u zeU*R&JTgmQgp7Cvg*##YLwOL>qxijY(n+7_sYM8LV_a(kdR)aeTj7bnyAi(*5*B8yxlGN`9bzD23 z=~J{JhP_#Lm5WbMVw>=kn7v2u2Wmw*rH|DG)6;ZHkv+f7V6*i!ZMKNHI~^TTzCA=^ zc&+%VHem+?2#*lGGy!IbXq!iLCRy5wZXId1zxN_gxOrkMtApYMy4PIAn3EyBDu8GB z!6m$(M%F`j-d3z2$bN%sT`~^-GYQ87H*gPOY~(yhB_tLlrWoA@=P3!D zEF-hke4;zdrE;+cF{U?M&Cy>b9T=B9I2u&7IvpRP3Es`gEZpe*Is3o{4nm0pTFMe7 zil_)lcz3LKgSV6hvDO5ZQZv?^I_?kwDUi`ZL^JK(*;Jfo3~TLE>>xmMh3VA>@7{u0 z%o*VT-luwsPg+TNkXq27DI8o`5pDbWJCkI1yS(c-IMzo7D3C)s%7jElkPq5SB(62vF*|homSi9gYFDNGmT4gVl>fD0s5TX92TY;p`Ur;06!58-q zWw==*F8+E0pRKt79n7I`B$>lnF>UmU+_Mapm$F`c;IlSl-Y{|i3t+V_n_HC_WJNwa zbEMRu?l~qU9zO1$D-$*uU}@F?6M!IIXS(}Fz6#$L?e)qsWh9Vvzvx-{=Tfv#F&@SQ zebkjZ&4-H8o)F^)R9{>415D+Y=&Qp*^v`YB=lV2srr+$04++{( zBVQy1+Ufe8Bh#t5pdKy3u+)QpogzW?>bfEO65d74pT8Xh2X+DztmzRdCV3wNr8;U6 z5xj>y89&_v&$9*9aHb#ugP%XM{E`J`Wlc`+yiIW#9t3-O5AZJ-kfWg2QM4Mxz$A}LIF z!ajYrJ1@@zPu#03ys@(o?XnDC^*G3)*Qp;R>gYen9mmbb;fn1BQ(9_SL_zRBxE3M6 zCDVKclRw26z2(H64!dT0;unSlt-;`B1C&;yx0TZP{nLnftLq-#mkuJ?z*~1Dzvbr# zqN)3KK2x=tb9vGmu4;CHiZ#Ba1}(wim)#N@f!CS)t}{e zyCC&WY)~nU)1uj3nKjuZ6tzX_|27f>2BKX(Mrye{PlW{ZpDAhCGYFjw&4dXw~^EoNaC9w3Sf$EM=wJ|bh}~%G!W-P z>PUgsqyiModJ=1b(f2Dj*`Q%**7UeoUVR+~O+!*+y!8Dhgy$MfOH6?U2Y3h-uppG~ z0pH2?DCvO!8C$xGZsgNg%t`o;$}Q(`TlC~dxdrN@GCMcew`bo4G38J5)Fy;;g9PqMXOp?+FfHnfbYhNlG?fgTrwKIi=`;v_Yw!^dqgu=@y zK9burG?dc_fpsJz!xnvn*E6Nh*ZR&=>)8ACKl zNltiwp|AJ{E8%Il9arBm&Hp@-ZQZ+DMYzUnqa~$u8jMX0W?rQR zgsss6l>iiT>iSj=)3K{;E*mEdw9r>>Z;ANN`X=!FlvJl>-Ml6dtuGwg((Zk&tn!-); zkAKz#=J_-h5Uy6?FhZrpGzDGKS~De*bAFiTo(adFS&KRc=SX^2<$P`mv(aO(g-Bj( zBt)j~WV=Xl$~yABAEyTekT)jbw;*fo>l_nf7RwqNmKVQzxP=uRihoKewDsx&xJ5rg z49(h!cwH*}0=r!r36d$JE|&E$*vvlw5ic(Snzn+e_bQZ5#gzu9Lzf5#-pw>}HMX!tm8;zyDh^im*opU4fAUVmp{Sg5!sig0!u9{AK24R6xvPf6 z5~yODKBBCF)Tsvq3BKp?6~ckbxLpxauI1(}vO_x9&^g&ov|7ZE8b3w^FwaOfZx-q! zp-WN8js{9KX$xq0?9Egofw0S`pNu#MDk}VJQX;S3>IAavhDQ-uhr~9OmT&2wLA#>I z2V1HDd;jkja_~auiPX6LP%&YK9HY;FIl*S!&x8CfaVKI2YfRYDx~z?)_0pQ3VVV)<{$W!&8Kj&EUU<&|JgtrXv;rI7Efd zT+Ddct0J*8faS-ifZ9(%+Z)!1jvh24m`gzgu?28;AUX}g!_4*iyN}50HXMXNHxDA7VI z)?}>pPktB6MUD^|I-cO$2S)E<7{X_)^ej_^Nwqa}{JA5FebZ4~c?np_YiSfa1Fe@; zau4PZ7-woT(Rc%|8NjEM1w{x()%kBGN60|?!Ch119OsWS+q<>J^&hdVd~w(J+d zV6(aVSPY$n2PR*o$_Ne?G~tz~Si(`YP>5uBXk%?qFX>yEj8@*62Ct34mB%?rKGIDy%M8ZUDXi!x`^enz72Z1t7F))kZlAd!$Tt^R#QxSNDYBD*ct%vj;K;r@7 z24!cw8-;u+vxrLF9cEGr^NJ)ST%g_X&RSxP4pr4rV>-QK)5eMug!d; z&q01{2jXBTfcy>Zv9SX;ot8azu@8zeAD8iM@Z>gj$$d8e2FBUqC~3R>Ph&WS2C z>2Y_w@YfU#H_{k^*o#yf1hfguTq+5k56Hccl4jjHzEn&9{Wn~v95!`cWkeS_2P1t9 zO-yC7+V$a;4OoC!}^h%n7Xc5P*rC-pG_2i}?1ZNoBEB(FJ`cfL@Zrc?H>E8z_f z(wo$?!G+P%0)#6ecoJYH{Lm6xb4fzxGBIHBT1UjC9Pd52CV#ji1_al?P5Z*$YHSfN zey(B;G6QNo$lWIRa1<>I~|wzLgRitv<$DrWI^lDZFCfItGRIG%H6+mg7>5 z1g`M;aXrhY0Oux6L=H23v69Kcx#^@I)R~nE1^|`A0g#%WBDqbSvkSCom=seQdkviD zUL5m#K9p+Rp@Soq1Wy1_UCwhhL?d;AF{u9EPH?kkS}H7yMupVRD{At70#c>hH;C}) zj-#p?i0s-q&xAb>)giIm=8qJUC%>7<0~&LQ>C~W6I3Kc#0XziZlBBPjw{=e>-Z^9B@&5e4fEwa@ zS0RMF+JjNL2jaVo7rQ&8s0bY1kd@R#s`S7bm=DHZKWygJ?Btv021F7TxRET~2d$s@ z6n{ro?je&Nn@S%w0jPWmPx%{vOj!&1hh_~8f~c)!gEiyV@}(g*{(1PZ{A zvS3O`sgiYMQigo@Yh8?d!19+0oi;jBc=zFz2UT6`)AU)fJfo9{_OSg-nlk)^kV9yMOYMlo|xfL$(GmP_vJgbQ4Plh6(` ztii(0ZGT!$%?!jK1_iL6x$mh2CX&em6pYJ+9m_*+{d+pkF4(fQYHyyu0al&JJ7$V8 zB&^_r94=W`PNp1TAIx2n6P9xs$dCwz0yR_FJJ5U`0BsA%DUvNs$tCgxO+AZw1v1mg zcEB$q0b6YPnsci3S!^${GdIEoJ#hWhRGC$Mn`JxLyoH*s0Nfi4(|t;eZa<*Cv;au=a2MF`hXU{eOI9tDcEvN>5k2)d0$~r6%7wUpC1dfCS0^Ij) zX#eCQjcx6rWp=~n+1M+%M2U0T1k{G~&G0NWD#j6v%T2N77$=ySuFR zG((;;N=xNw2dzeKt~|p1`{ZI=rxE>rR)DwE&lws8n??BZeN?@U2f_YRG1O)?fwY)o zrH%WO#q5MZ9nqX){w)r7bcz9=2MsZhBeoL($g=)?jfz=4`YQH{uO^ZBmSGA#t4OL) z1>Gj59WgKqBS5;eq8PFiokKi?Q1F}HVo4A^GK}1K0RSCN=oZlC4OE0_jokhNZxd-E z6T|=4AkUD^&83ep1*FD?CW<;V)dU7N8RznmT@M5Eb)z*elGcPz8Na{|2WP`L5eP^A<0+K(p!r7NadavGBB{NSIPc1$ z*8yoT2OT9URdf3&PCPyCfeszX>!ojxEH%J0)%;r7q#)8=2lO18>kouGN5>3+yD`D^ zJ(Bak=fSjVuqk{8qxQob1|a~_bL5l*pQ{u5OSXMO@1BlVFlFdJc1J!7b*Ig}QgMW%i zJ_r;CNC`3Jh+;Ld%iSv_F~k2q0UjsTu(JehmV8&`;M1$$n&eM6uiVO%hSPh*iS9iY z1;V5hwVs!ied*X@h`tyPB0N~|Eolh5Y{|k)i6;+z20oW@)eY62fmivk32p#1BFND_ z3!;&S=1g~FSq%!i0-3|tkuc08se#alsSjrOnHkwa;!J0VX6B0Rxjd zG4Ln2ER8i=!1T*FI)_56UbYSDIZBng0H6~5zvf(`Huq3$yx5=6S#4YhqIC^eVPuYA z1LtJF2UUVNcq8(i_pke^;@|*&@xP_Q3+TWHyk?H?4fv}G2Kt6{N43z~TIlwiHWS>L zzG`~^lK-W{)xKM3N7psz0~?>4nl{+kWE|o;y{MT`04aoG#OwiI#Lcc#0P^oS1$MUx z6-#PQ9t=9`3X|*{hY7TyIXd{7qnX@_zqm^O0&nlS9l}sXy22vw@Wy};I{4DRSj;RO z)Lf+fY(n400?8Fk@bB^FLEEdyHahTJaDA3O;IWYK1xtso&moWP2ZHT|;9J}dhuZG7 zW+9?-CGp$7&iH|4;mH_&_kLS*1X!VYb1AMD+*b>E0>L+kk1BU;|&?f*RnYlNwW%VVk<&@82 zauRId`#`zcM}E@e1ng{il?y7ZTxj}u^R3|LhQZ1Z{R4#0r3{vYll#GI2Jait;Q?nd zkSxo2oZuhmdX-Y1t)Rcl8x$Lt z<#&uU2X*UCUMU5e%mU7V`Q)_rUWwq)QPI<&jRP(Da@CDF1ArNHw%Kpw#jRe}pq_0v zGDB~SSzytrO1OU82(x@H1XL#Wsx!=xcp@g48Y{1R;}@au7YLSmLsX_Rx)(n_0N$HZ zcs2jxp6D{{n!*O8{cIzcvwBrAGk3ur;W7{=2R3LMWEgT6P!si~H8W`^M&t9dU>plJ zE1}1QZFf#(0CT}XCN^qU=9y-AuQ^`BJ-hbQG(t0@LltH3C8x6!1YaO2SZp>}`%E~4 zR5<&vNgA2!{gY@v-m=PHEp-b+1prAG@o%95%roH~7T)c_K!t`K+MkguO9I)VN3b30 z0I^fQK)vU94}tz>x(~U8>ST0Y*SI{gG)(pEdZlfA1Jq*eBj;NMs}*=A8a6P{;Se1E zbvVc$Z#y3W+z&2S2UFNJ-akSrqR$fNBs*GimKi^0%%vrv@9kBk=p~6a2WM-g)@U;} zhhsaj>p**iI|sk9S=^6)4|0QwV3Hkq1!D0T`GabO294jAozt=g$;wvf(nPCtAb_sP zxq+DS0Nzm*izVDYJn1yAuH{)>3t8d8e?WBXY@FNozB1LD0Y$jv{J!uTfAz)nw56uvlNxn;skn-G2YpZ*zsg9>=EG#M1?JaP=3QC9QBvbY1pcVK>LbXr2Bn`O_+Tvmju1Y)KQ}UB^a6`ZS%ldr zavxSFt$4BB27zYk#~3CQywY@3g+?1^eOC_DC@W|Xp!#I4lGxLo0K$9*H(%`U12)vv zJ88_)$MBqbidNJ-;H)!{KKKQnk;?lGoNF50$(=5(z`&||MC3$b2JOxLF z!^buNW=`Z*9cRZB1#+ud?^VWX7BGj5Vw=4-IKz|@)AX$izWlaLfCQA11W#3v9^XpK zMlgbk{lE)7>bW-fyYuU7vY$_iI)U`*0nU9GDA9k@7E9*TQ9)}K#xu5WKZ$-Dn>pU= zuf|Dy2Ha3qYRv_ei@U!}NEz|q=_W2=VPo;&WCR<#B7x1q1glQ@kza`vWzuV$eS$;n z(L1l2sF+C^M=%-a^Xf}t1u*29z|u2fAO90hf&uw~zB)9_nh|4?EkL&<`mt=>0cD1g zc(=#sV^`Y}c->M4k>LPiQi~c1l~W4?>X<;|0(!Oh_;j+7hkE+iPd95qN(hA*-tNv- z=ycbtj-5=N1_J&4lA7Mf`_%uZ6#6dJv^dPA=Erb~w+Fs;f*K9W0{FwnKluvA5;>>7 zdHFMb^(sjAezqtTc1*JlORU31Kly=%#6%1qoXBS0VN%C#G1c%>yQJ&h&Bj@^WzMi zI++@n#pMtc!N;MZ1aNiei-(!3qz-`Fo+Q1y*70ug&)aU2OTX7j1Pj5?12OpTJ8#){ zR!HF}I(vjYC>RVG3)XlbD&lEyjWp!p2R>Ih;0N)Cn@KCCtTx^@@mOMZp^s7ctQZDt zP0Cfy19tkRMW#p@gfqJn^PcKkM_ewvW##?aKWV-tS`A$t1?*T`}V9&7$csEb$))}1;E3V?ymuX@VzfSl#27C)U3o3j@dMta{ zpvm8?h;?VhvaHvFwg;x&?TJS<1gU9D=0hH`=a6BQwFzE03h&b=i`+b(A$(9@7O#pq z2LC1sHDBVP6u8?EGfiYxKlmhPd@F8OHN%l$!Jxlv2Y=U2DxPTLDC>fh*1w+hIj)GG zsKuM`el53-s%wW!1aY=P0TBIK6}fLcQ7-_tq@Ub)M1Ixq9&i#Gr{r4E?bs@$-k_1VeAZc9m=Db3(CpAsWyajP=)l+zYb> zEF!WnE=(j!0YP|l#NwDgryu7sUQL8f2qsEjs9Q`jm)99qBoIGR2P!wgNC@8@=v})L z;tv6Sm0Yq{tIiw6K3XoeKgg1^1)dkZ?^(d&yEE9SPHuzIs$Tn@`VIRwS{5)f5RZg@ z23^`_IK&P{IUb+er1ekx9yDg>qL5tJ@We7oUkX!UAC07v<*-ZTx>5SB~C(Y{@7 z@5*cgoyk_|`8-ae>kadV0z)$Pl888a8nL+Vcg7cUJeej&$RKaMo|hzb?3g4$1DB#E ziQY)l67*mVCIuZzkbLcvq7nYxed)I60T^2<0Ok!4q~u%u_Zom11|~f0BVnFH;1%@L zv3}hA@%G>#1NlI@^B41h0f}#3pInmt(t&(Sz(8kX3L#Q*@muMx1IKNovcg!0hrX$6 zVSntmqFrkO&8w07-!#Hp(E^m;0V12n+MOEixgrqg7Mo%KCR=4uva-&Mb^GVjqq|cD z1?SPU0x6+60c3v()TUBR|7LMTyh$T?jEhfr$J!1B1MZFHk1F1`^S#r|qiG_C6udoG zl`5I|xyWONwKD?72dZSMqJRl9&T@3dm&U>0(;C7LJbC3n65}yyU6F^}0p=B+X(&ZJ zPQF+-dML&Z#n?4S=M!{B1SRWhL21xW08!ap^L^&mgdIy}Dk`J?g`i@V+&z=*+{e!5 zF2JxI2c6YqRr^eL%da{vEP+xYQt|^*8C$Ahe6I&l2kchNnS%)Y1mufnwj$~$N6yV?g*3wFUC?1kf~+ zSHEW<@VU!f60^s27j}b*n15IFy?9cEtfI;l0zC_1Cy6!Ym*>>|M0?#{;kfG|?J))D z!kwxv^)4bQ0ARDGs}Q+e!z;9pDNjMsTL80Pn_y}94qZ50`B>S+0!3+Ag(VR!Wqt53 z)TrPx)kohpV$U6JJoLtbAq9R)1XgWG^+D+#?Srqn1I&_z`JPx}yOG6g-+ zu~tYB@ClDc(?UL|0ib{g59~$6qx%mSf3xgsrThJEaUTa)Gvy$lY3S&)0eZQ@b8tH) zP*4s+UBDp;zd)V7v9~ysH zkL{>=1iXFqDYimP=VZDseOmi6wPzGd&o!oM*bQN&M|ajY1xw6?oX0$3F9{9>Sm-hv z>mQXd4k-^HkqB=2n!23U1~`?S(+6TWJc^i#X#U3Xn_kGhq6kUE8W2${^<6Dj1hU#I zg~?AiCRLDD0>Z(G0AqKPtnv&A>$gl>@U~xX0}AgeT3w912qpyRX~S^^z@7GeVV#dzI${mM#7*mm3&!wWc&1A}lE{dd^|?HZ#au0G<24@7E0}0Qm@L)B7*1Xad58qo= z8FrJ9hW&tOQ}0pz1+8ZOBz^GvzKfF_gxpv+7YZoH+HA2F1h-_O#oVd61G!=t3()Lz zYQ(4@+K$aah2p~rEO&BaL_ef5vg z^z4C%Ed)20X)He_dT%Cu42QKRzpN93sM9?5`4j z)tN4LQ$ya@2A=s4L1KTwm6|7u=NMl(D;f{2gV(Ws)Jw=!4b0{TsQoSS1{0*V={w7mI2DIn{A*o>vo=b6B4u6_;4WE2eRNUi z011XN{v=Np+ZYnY=!4r3*eQ0}Xp4cj3`NX{7(C$D1T3~I2B5&d98!|>FgVEc*O`L4 z=`T;fBZ}p!BG|$+1mxO-zuFpk6^jSN^yElg_di06_v6fuoBwPpB;%Sp03^TaKVpmU z+L)ba>LEGR>2l01{A~lZ2vG1%d`> zp-?@waEzdBR8$Bm^V4_ea0+X(Y&fk14Yig^0qyobN(&cm35;gg@p8xl1QA~s_YQo| zt~*N}>E{^q1)O4-k*Mf!)kW{RMWB)NPyIXRh6B7N6HXrqjB50a0Sr~y@vkVN0MD7P z_(ZTQY_?S2h}XLeKordw+0u^80Ps8x7y3OyxB?Pp1ek>ku}|=DhGe*6v4Vn_0KA3= z24Lk48wH|OS$4Kh^t)qEN&{dwLHKm{BoFr=5|_}M29gQzq|cp-F_A0V#r1B7Ge{=^RwT^v-;rT0FV9>e_A@-Y~HL;=I1Igr}fu*k&kWYM|U2_Bp02%0o`DFs?iFr zItupk3PNnx>J3`RcXT~Hr2n$Dp$C^p16?5p^)G#AV<(a>*NivtKrig-RT9;*WAkJV zN(0Ir2T9K6{&1xA;-yT1-=CNgwWY+3M$p@%Hlt$+LSN2-0+>VnfHs)hjbx6gSCW<_ z2^Y4bafC0AypLrf+4*%06|We_z!&FP_(!-?p8o z9bc%w)mh@u1Ga-pE1CKj;+P_qP&>Wb%aNswQ~mfSdBbceTu3Sm0KNFJ9U49id<$0U zNC~fg^$8vlo`v0~^qhjw=_1#<{yQX{|+Lf=KdX? zi$2>lZLw2hGfOAN^FUAs2Dc)IRbkK`d8#hlDFwblU>#_^ptw>z5U@k-nZmdY0SV|o z&d&z1XcXLRI;5+BdHC+KCaKIx+OA*a_`Vy+0vDN&<3_Wd;={R!U$CJQ{!eovold%u zUu}ws8{I`v1^sfl_TDR9V&5#nWd)1zo89VKyVEKF*gVP_<4oo11U!FvEz{Yuq~o`8 zsFd|tB|_HOvRr!HMF6-0shpow1_m9knzg7{{5*?9s;*thA-3%i)EV+t>bMS)ilVxt z0Zwh(>)%*@pik&!dP7eWxS=$8;_P^vwtN?8HsW}-1|aWs$DI_b>FzuyzEcqkQiX0J z32FMvpqt5`#H?1F0&GNSUfA4Bl%uF>D=5=k7q3=2ba>74-l#2I(Obv31g%{z9@$T$ zpIIVz9Phnqu<8ZO-|{my-R%5QfGCOV1!cFXNS+Go<}1gx&CcueWWLav(?Z!_@W(W! zXe=ax1}XRf1as$qO>TaYK1iG~E69FrwlCCCb2Am+Y+*d?2ch~WuwLk1*|?yi@8O2~ zOTRrnK&nqZH|WfzC(^Vh1hxVPUdSWtwRe6Y4}Z9Iv<>#jw>PjRc&AUcKm9kg028Cn z1*MiCrkRdOsFX4qHb@#TeAUQad|>jZaZp#f1yZ+FS8E2d>jxdda8rEwni=j1GmjC?N z9>tzf3($Fvg^+?_v-g#D?hYjv0Dj1LYCuf?a~3h(ips3`2xU29inUsYN303fu)FG) z0_vBV0BW{Svxx;|z%%G<)&aN2P}l4FD3#pvbjadM0}vH}IcmAAYReM754uEJG+fD7 z!?9{1knZIwP)(h%a8#oT$?1#_7eltF;v`5ZS0n;329iy8_jiv~ z;S{dV$O33!B95-H{8EiyKBF~y59}bI1Pue88>jtqx9(`hi|`}UH^}l8iXRD}hkm-o zy?}ei0UcGvgyPxNnNYCklDPm1GVw2VPB)_c`C=7PAPV501b58XdqI{+}JkfG|C$ZYljvICS`p^0*M=_Q(} z)+UO3zwlac0SAeKtZAh3NfuTeny=gYidXg1cGPg0Zcqbb+_6Zk1jyKbiW@0E;R)zc zlU0kOPF@@L7YH$f0e5b`)C+5+1-bSGBc**3Ub88a;EMrqv|#}`mgpyH;eVa>%#&00 z07^kBe$_ELnsqJX6J-Y0=t`||Dw`GAUZMD>k@PB+0_^}}g&`HM__*x<^$z@%R|S}J zv}aRg(Zjw@>dU$;1Y6?%*U~g9f^t;&2FPVBa~@mK3G+cXueZGm+d%&w1}}uwiiRTj zC3(cWhhdC4lxZ^u7j@%A9#sm~2BKEnB-GDTI!wOMg*30^)y82H%hAR<9>#^W8N> z(&t7yaEu^Mo}JifMXTPT06W&5?0-MaAX8~epFTQ6hiYc6AbKouQ>oqIDazv%0B^v3 z7<+w65$(wgUN3XE*Hs!{#B$S={+{1M$lllR10&yOTBpBz7&V0yeu4WW@ctDwKl+8Z zi(+l1cZvp~0#?F5;w}4^P8?W*Ed*8koc#(46T7K+`g;j0K?YYA0?UX%pNN*>3X=zu zX6&X2niH>yDgtPZ+x7{u_ZyaL271#I`jF#Kq2>^1CzrdS>-6&7yT{~cjtBSw&1^0P z1{#fFL@+mkLh90MZ+c?XJg>k!+=&U0LTJaCETBZ92Y_bJ3KnB*V*F~2MggKU82rDO z2$UzQB3~u7MrHDv0k!g$Se(t1yT%+lTdwf2%Cha}@hiCsKB4BtBu>JT0E+SP*1K~d zL)KBVN0J0l|COn*bFG=`7(Ee8ClnE~2B+^{i>3Bj(IR9ZbIS+PGnJ4_dZs&OMfHw%1jp@1B)3nT}QA8d0hi`+`)=P z`;D;%lluUS0%@K1ji5GI1<=@h1Eq1B>tspLMgB3nWWBzZb+(EHx4r!fzqDkH1c*wH zgG>h*1OiS+?$7+ZoN#CjYY%GBBhk+rK}U7g0w)xF=FSM9$uo~xEFn{43M#jMF2h#2 zt|F%`edpS+0>XIC@igh1G)$C8V?*JGyN`z(WCQ#gK^$L$e0s-hk&Ex}B$Ytap zi3{;uc=xaH)4KAsS6SwGryfXr1e41FXwvZ?7c`feQCOsR+SAcB4hv3sUpu=%A1(Eq z12ixjHgZwFBzMv6TgvsslkGEc696&kKysi}1T(O50<{$@>sV3gsW}n7kmnh^3C^fv zHJ@QTJU!ClEh%w+1&doflI*Ps*f$N(fN2lOE{_B?`{AFVzNGMdM|ho4YgLD@;W|0L6g|slwlgcXygpjTSxF znbMPMjyh}5BA||Snam4F06&-8l*5VxepA3vyiCsAh7!9=SZklUN6|V!8<(rK0@k<0 z*vv!|CTo8+Ke3CymOdg&ofa65W(rK!ROmmb0bl!Q6H9s}F-Is|b*dFXuoa#+Q8&+cVnxDu z0{5)I+F-*=o+(#m_-Ps{qIPiCEVaUj=#uTxp>L`o0M}vT2w;Nd_TWOnY9??YE;DO2 z5fWsPAGHo1`p(T00t4`GSaS3Rm#wo386*bp1!Flav?}+_EX~DxTRjfK0Tz}KYMe3h zMgXsi?e1gDzZ$4%XsQtpkc;bUQr~MR0E<$#>_{WvAi01n_;x6Iq6GIh0PN1N#_p-4 zb4s-p2S|-TnZ4D#whU>dq4dZ;y=27u?63qY$XYZ)<~%x9yD{;xc$0u6GQtop=rQhurH2U1zBC9H=KlB10^ zA7VjFUu14ff8=9=Ha5eeJ6^1t0-;`)7@Zx^9cUw=?UL*BT6#_-2^rp#Jv=b^MK}h- z1|`-;Y(#;gXQIPG++>^UVB?_3hld?2gup}itLrbS2CAbsd0S*er5VRrtOPpdM!F5% z5x9tazbx7^sLT6~0F!y@aoa{{&f}jA&>F8NHr(ctUR!ZtY75#Wzq&6`23Y42BJ9-T zVCmyAkHGJ8gL?Ek03G}dJ_;4kf}|g}0`S2{#^{Cc_qmWHz`r(G0&5-50cwL1H1Q1~ zl?2)F0C{@%VQw|f2YQ(GCXZ(_lMqR$X75rp?AIE7tI0|D2mF@$76+!@d})=0xrYTf z{$iUL+#qb%zf0d_Kn?rS21lIVzPfJfNFty`*1J=NhfDRQk(V7_pO3uM>rHWX0LkwM z%Z`1Tq~`3QPbO70dV@yhP*KG{PxaqVhd0l*F*TdJngH&F9n9wH0bBGoyS@A~5r^s~;~>s3}{0xG8JE)8xc ztb^Y5HRzgdB zdv`G{0@eGNgc5Bz)&@DiiJ?8@t-ASL!t=)yS@yf4KK!Ce1&LuZhWC39#-@S0Eo}w> zWH{AgxJjU@Q3M1Y*)$dq1-N#D#EIcIOG+qm*k(YLbI09$&l41~Y*FJxYAnQD14YB* znV5E!AYcatQPF2)UilA`3xK*Gp$27Ok!+JC1Qr-p=SbDcdr$sbMBd;JbO6bJyfWj= zln#nEK>tz&0_PG6Uvu(h%w@OSu1#@fFg@>|V}%?|yFrG@KsHM%0|BxV_Xe`|hkqW< zr1%OhrGIK|iiQBp0>U|!%KcUn1;%z?Wi?{)uT+4iTB^^mJ^ENj$Xm>;?!X)9hTeoH z1(EPXVWD(|3JswhdLnEcZf!htXh`{=FV3O3y3}%Q1NRl$hNAU)heWdhmz~f~uJeBw z-VpR-NMDOHU3^5S1nV=w7bjX)X-Ui)-6C|52!jci;!#aFUHf&^$SE>W1tvDHXwz?q zXGO-snQ>>vnke$iC71eX1R>2A+0kro0XkrZs5_^Ba26T5nM?5c07m2DD1i&HQXJ;< z3Y=_+0gI?@FAwmv^->sxp@-R;Q42F7V%vi6Ds+Sylq|d40fM1$Q=SGJ=0!TY{{TaR z-?F0$`s}1yA{=E@KTgc|1Z%uPosxA)aP6DEMa%;xof$x7&&?@CC*836FK(-A0k`&x ztLlv1-^k`mH*emmb$$9ax|FN9OL#SlJdD$p0%;U>jzyLos<6?iF9iur?{`hK>b>l~ zqNk_A^C0DB1WC!$OW#3^tNcUta6^-_PoxVpROxv)>dbF2K1wW@x271=Q7LRBn zF+>gDbmR`}C#9YnM_=MC&8CJ(BE44o2YaNYgKLg_BWl$qJ7q_y$GUTzW&Hlsu6+6) z=4@7e1@*I6`mVWQE4-7SH1oW2@pxY_9#Na;bSKxHPgU%y0?Ia1j{=8e6>A-3Y!7H8 zM27HlDl{sNFTC70i>WK=2f*wWg&}7iaoLnW4j}r%!VJ{EnV8&X;5Nlou9cM71}G?< z^`poN;OeX=dzxy~>(s#g-s;n#Sx~-IG%zoa1%Np-Nb8P*UCE344n{JHb43McZ^b?b z1S$RShiMWi1l%&`heczpRR7@9tGwVVI$KyVC(TJ#tac>JQU$`k1cd-}X-0XssUi3| z3_RAfV-cXxB~t(*7X4|u^a{#>244oS+mIfmjh{UR%`$`^VqSfJ2g;zP5BVLMP6CvV z1G@|K@xPu#Zf$qix?SQy%IxfgYt2#o^o}-?t%_u~21aRk(0JwZh^K|qFG3|r{JbsC zQoc!WIq5J9C@|nw0lTk&G3jg<6!jM}U|0>FSU8~`|;WoN(q|7=zC1HojCX$Is*Pn)yJ z4;5x=1olG(T3$?+Khe2m5dTtq+_w0*nsq)+K&3hF?khjX14j_WoAzBqL|wCOk^lCO z?6^0C|0w7Ph}vW^Ao3ZQ0kTEw=UWMc{!2;!fJNZ~=sCM2uG-LM#rpzKE3ty$0JBhs zn|%$}hi9_Ci70C&T&3P!oSq{(H$&xKVt-|z0-oK^`Hs5=ce*rU!vVoByBKf~PkSgT zcP=hr=Ur392FkUcvvy^YPxVR;3|1e%-m#yW)6hUYVFV~?l?QQQ00Wc_{-*ml_zr-{ zFetfanx`);C(jubT$ivWrhU7t2guYJ*WlOn!S~lP;M!3D__hWAoIuGk03C!F6wJBn z0Y5Ry!b@i+LA&K~gc15-wZ9ltOOzT1fgmUCT@+@U1#&zE(ldXb(5M#-YOQu`|2JJq zEWdhX0-hnO5AA!21g%x6P~VeGG|?+S#Hsj4C=xlo_n@6qg59>69d3Rx1WOEYQN02( z;F{zxZD&`k>G^7&sj3`D>}}S>8IrDH0s}!QU6>iJr*M0o0JEPNdSBE$knFjea6*Gw z;g+pS1ZXNp2dm`(s<38|>dK4T&!)*1jOjVlyJ?k6S7I`x1TcCI z51Hbzc*E?8(%S~z8x_QSMyD1+*P$CI; zG2B>%Rt_lg1s*w-p?J{z}V3OkhePVgf`!8>%0g?^G&|$>;ugp~JxC;}9RWn9pGU@I7tAD#6)uBAyP>`DC2K^RtHaSr0psv)woVsR zAE{SC?DoGXnRr$7I?6q|qjH3ovtq2h0+-Wjw~;%A`g1bzkMS=e#&G{|j<&5-A8x|rE#pTny_1RpQmgUKZ{V_xqMHoB8US1}^O;Hq$Zl#l%q7BjuS1Yd-` zOJ)k`?+s=dc4%ZU<@!~2bQEzZ?qAZi6R7rn2e-9VnZ?35AoC{uSfR>LHJ}asY#pNs z=Ztnl?}#LE1m!iIJLzRikafMyYGDcS6u%2Xu^cH6!G@QpVL_uF1`X;TLyl77Ovz65 z_TDNj4ks8S1RiRn*Lt#ks*%{-a59OIAKWOkjdt?lZ3lociMX$u1q~LXQzj42 zY?w=3jjm++3qAXsZNW10nRdishZaHV01O=jNs;#`Ea#OEr0({5rIZG;+M~G~!PIk6 zb+|b41-q0c2`-Zo7#PmR8r+_bLizS|NzyUi>U{<)tvK0V11?7M0X=2bj9{-%Z4GBV z8^#cD;;gyB8H)cmuJ{rp1Adh4>u?K}cc%Eh`GLd_-^HCEso+ea*2gSKhY`qS1i>Vi ztr0hzD7f+hh74KAqY=MPF>xK>K6PFlbY*G71U^?bjg%*bQ8XgRV|y5T^j)Q?B+pb6 zJ~GM?cVT{^2XHj+Z1gOHDXT+R8^-pC&tP?`3jY3ci^B6&LDEzrBfTDPh0{>?r0XGn~ z=e>@tU4iuwlC$_PWrgQ_!h)G(zRaky0oyg<{|q#^oK&!UWGoWzj7GCQA)tBSPRap& zY}2(G0SHPUklZf_s}*K+^mck4`f_xa!?6I^==rU`ta0|d2eWf0AthS4qCKD|;h7k5!YW`8&<2S}>R zlJ&|VZeeM3ZsP%M5?+;p{~G)UO8i!3I9CwZ2U=%e?|n0>b80&Wj_u!HOKD9QD07U{ zn%060$m4=G1L=NvT;Oj3uQz#kcY&tR+$JpE9-85qizudum_$M0ryn?Q`0O?MjcRoI#|HBI_yP%u&Wi7bZf3Izvr>I9p z&(%)P1H*-BdzT+}zhC_FtAoX%VbT)w*KxI|iGZhgK#9Q(2Bf#t<SIBA0+L}|C}>o582-kCM&=_PLtb2aOL09* zX_|fFXl+h%19^DPm;>JJgPKOBZ(5uj_s4SesrXz-~12Pg0z=58LY)JuY41^9D>alFf(VOY`e2QHfMJF1gW zKq~{14@0kQP1@9!;R&q8$C(Y6-(8!e24kt{OH-R8Vmwr8j!)`2-jN)%_9FUDbB9^W zc&5-}1+b8b2G(;is^JpqrFr{ZY~Bu=JJL*=Tglxmqt_@G@mA{;-kS*w0 zgR#GF3InLO0bp$9xd|Mndai34PH=qlYGKwLbRo*DWV1K(j^A1N2OH=?@OTwfvwpQI zYkvtqT z26bz?JWpL>-LzFXR=;2$Pe>RaBT`pQJbz3PNT9WC2Q=FB=26?|Pt0RR}GJ*7Oi zgZt~{^pdR9_Foj%1~?z*Fsm}paqc}0_5h5_x5}^k{;-la2C%CHRO@Y>0~!acP1bDx zyQ^|5Nf%(19+vC>9XB?WX?Kr_EL;M>2h;g-e1b8A^Sj>~8vvoaD7J!gru*1dEE0lm zv-#+=022rBaDr7gv)G#1aGDk3cFaU;NI*!6uLr@(!{xL11D+U;TxXv`jA$Mfiur3v zuylu~w`wyP;BU=IC?CL12O}y~gz4!v6{X*9ZgsYCRJGJS2JyK6?+Q~U6qCNK1i3tb zQ8M+Mvpw3TF%O{6z{RIz0;1cV{j85K(EF>{1%m;K9dyRiuaPVENG`vHg_a>S%`ZtY zbaQ<;NCPCl1DLqfH6qAwAoxMniRz}rR16(5(@|k&Yz$J0{cE$0NM7k;mrtT)}_FLYfigNDGYp=GMfe}Kj3s5P^`qe z06!{eNRtY7^<80xf_Do`50lItkAZ5S8=(s&Bu2co0QFa70uwqWX|NE=*Njs`SbsMm zRo2F-qGGExM>%ak0TNIzHT{x_Lz;LH^;CbUbIZj5M;tmH=EN|w!`J@s1F?zo`piVw z>=yC%{3eZdX_FTHju%Zv9Aqx?K4vui2JH@`cn$CS^rY7rw8u`Bw*lv(Kcu!7M(2`y z>38!G0FU-_4OCD_Zd`HBhx#26H1Fx;k!BG%D+ciSE!VE11HiUVs+o++JsM--BLfAn zZG)acG_Phtfxc*U)y9rS0L_jwI(UEJ;9roWR#lhzr&z(EWVJEkcMO!b==Mr`!?KmfgGeb zMK8Y0&Kfmp2Lh1i(UzAXUhhgpuIlzA4{q4yqyRR>#gEtaf@h;V0!^1!x^_gRu49%V zvQoj^4GaKU3NFVz#Igvg0^)xl0bOhP0-xST7TqwTbuGn4vgt#4*D%xb-J4W@ApWW( z0|U)}s9hrjG-h1JfB2B5x?7qxh#OR(<1*wm0fCm#Tw^8pp(FNKJjT?zss0g^u zcL%m{0)Spo0uWxhf=qy?B0V(UD|O8=?8b)cn1N|w zw}^G+KCF3I1iQd$xFSC`!?)-E1YBY68z5*69`2ifLmKmRiA^{w1P!uh1NKqXNMepF zk1|;Ld4Uwq!Kq0#3g>t|Z|i!%0sBU^nh@&!#;gSl+02Mimc%)qxZs<5G_@yQfB;lL ztG__Q=m*zw#NPXq1vkhmM$Cx+W17x%Sy>x6(jupnaK&X)?FRmD^B}O1*oQ@L`;|Ir_7ISPHwAiOq z3;=oB^Lyw1{1Zm&Xm{g+*Ny*9q=W9d_Gr?4ri6(`OaqOC2O5aWpgkh>xRkcM0br}% zrMjNju|Z)(m_sOT6#%%55*}DL{9+|KCpWonMHQRvopX{dYH~nNoiR@l-vLzvB!!-o zrA@Stx!&OkAJ@4`WTFtmO=m;1!9pe4 z8HiFV;|Gm@MlIIlTOooW&JTTsqZ78Me^uUge~sB-TMTme}V*KV1M^j?*at1x4rw0fG{CF69%_;1$F8JcWxNC9j% zw&vhB?PC;tVm)O*rCtH#YwpbHGH|8C-u=&}PyzrA$U4-WZsRKmhg85Tpe(iv%JJ{s zI8N!hji|%O3IyEWVxpGmgARSXW&)Z}o>#?ouFqqkAbmJAA*mbz>CS=2h?o_AGE2JHXy5TSTmX z&;&tu^n)QJGeUh+JGX)PFk#@G>^csQve^_zL=-jve zq0-h`ILD-0#MCV7G6sSf-JRi~!I`6?s85-Wj>JrYRHe1g*K;$J`b{Aa2LN(;r+}Yw zpy>6DOYRUwq&Yuv8JftClt|L>r4YV~d!wxK#(8`URg{C98|n-$+c z!jil8#s`)@in>COLNP7|%F4Z!%^D@^FXs(^>xLrc5EwA@H3Iqz?{+}ZbQ9^NOAbEC z>wW>c9cQ5qoq7GP<&;?K1_16kz^@&A)bA($a+R6*YHh9e2W?SQ!y+JMNFs-8Lj=r; zaBzXVe`kl#2WOeG&ZxUZqu;w z((`lZxakDPwD9b;+GL$bTh~npzKWv- zFdZZW6#^ImJV}?rjRdAKKu29`T3TLd(h|@rL7i#pYl66`jsa15h zGe400pMF_ zYVz=2g#-RhdIcJZIxOF(4bD1s6Z~(TQ?90Cz&1n`@qtF>Ee)(J>;!?BTadf0Jb^=?yVsLg1^AZz`nJiL32m;le?J|2-E%#epOBFa1=L4Sst}p&O z{oG#tRdf^_oc#NM1l657D1?Gl9hwT|yaz;XmhxF|=vHN-p0mVdEKwR$&@|Kb4!Qv{ir~+TgFW0i&{Hn5%BpTX$616|7wJzPung>^@ zhl5U7+eJdkC{-hEysk(4Ul+zx%$zSiV!qR{@*^SN95Dk7H+C zUARC0gKD|ZG53S*Jdh^nrNni-NCy7DF*46n-i071_F2Kbx(X$0^LSEL51O;%hSRyh z83UBLX5C_+U0E6As@%Xi^&Lke4;hBgld`=00`{)osRD4-eFODn5pbR0dbe0SE}t|J z_tMH``jzuiYne`W7*WQAXd4 zywPgDKD&TcGuO23lT>vq<^=NmsBA>6OU&xu63CbI@&w4{u5(iA>A7|kM>Ib$5diaJ zQ=XXNg=8k_y^DC#HQHL51tuO{kNa2^5E00CdjhGPU1raJz8@VaaH)zAh&gv28G$Cf z>~Q|?2G_2AF9SKyiiN!qM|;9zx%~KO)22vkkhr5UkoJan@P(JY$_9J0N+v{E&E13y zZ3bS%P}rGA)4_E1NT)v%VKm41ZUz1lxw1pfb~UxC^c%dZ#7%;w&7?>%Zci+J58iJe z1_e#^O^zZv+DW>ZBcKu=>RmRlKP@I1a5qW7GGf-kg$2GX<*eZ{2Ufo@pbUjY4HOFV zUr-zdP5>Pm`NA8C6$JoB?;Cqi=%3jW3k$b@8+c{4Cs>KAfH*6ZM3V%4od#^PZ^;fA z8-@=f7)afM8mn%kO4KA^{vHvzQvB6@P=scl9KGi|1SW6 zK_sl~ik@(*9|w!~a?=8Tc;kB#8T0N8_G5C&57ccO)Ec%1lb$b3{RH?d_FX)hu!DsQ zti&Pt>_q3~4!Fjrkkto$$Yz)aD*%A)Uu;N9t}+pRE8ay@&i62 zo0|pRmwW1uC|w-JmCB+B1Y~kl3(ad!>1$nf{RDyKXPJa1fLYBw7dUMDL{t(#S;>!K zqK1I-WaInJv;hG@V@W0IrmGkS-wZ_UZ3fyuqi3S?tyOmZ6R#pZVFj|DXN_p2XQ2(0 zuSacD50ao%^sq=3lP|aHnxNCingTdLURoRqzBh?RyO&I?nXfY$2cj?yQoU_)uP+G!(U|v zW`mF~asv^@JWbNBkbyti0&VCAo$wt?O(U%w*Q% zS<19taedJLU;#f_Ww2~Cv;=pXo=cRL$^7tt3y^WO224xdIDM6UZHXMMkhN-55JWsoG~yw1Ag1 zwh8F?G;;wP?FFi${`Az(QWQnv{uksA^B+3!!?n&!3r^YKs+uL883dg83ItIkv_IdA zpA<$yr~hiQ!vii}N$Q;RFIx-J1qaJ;L|j;ag{agw@Fq*szwZi$S;&h~Nh?jI9#^-! zBmtZga;LZB^y1xh%qk8Knu5xmzfyje;b>m_dutfn;sLIr*@l3xr|tYL)WC|w2bK_m zS8>EEYLqDaBQKlJ?F3Z@a;k@1`u4u^@4a9fZ-5j`4`a+Jxh zX@=dQW&#DahV+zKhHhe6c30m5-$yEUpP{kSq+6FfU)_|W9)OW!JXcbgf#2iR@Gk4u(gD;nH(J3Iv1U z{@5;is$LTJ@-~>4*h|A{AC27Qs4c2%G?hZb(gm3qjK>%yWF79Tw>n}QX^%?e1*S z3)C%?n)XXw8wk4_BAMN{r4#)e{{ivoisB|YBeDM-Ys8R5gfzwXJ<)a_0t z0{2q~XQbNdk5@aX3OfujSUuaS59!okVP_42OyXYTQ7)?s$Iz#-vg4UjheSx z14)1M2n@(8Hl}U0Cl_R+_tc?5?6g%176z+;q8Zzg2Y_fCeCEe2OW0}Q80{1SZ9l31 z71I5~ZU8N12!?9~!vVj}e=y&=6nyV<5vhX)Dy{d8TF+zuJ_NBq_5EehW!inU3TACJ ztbRfC5*bGekGy94Z!6yO-~mI&Z#eDv4OJIf#eah3+cKx~CH75IVTBV4bGx>LEeH9| zRQE^HJw|KJCVPKYiISo``}~=MCd295B29)R>jICvmlEx^F&fkVWg{!?qsFmEXI>|G zw;fzrCGrESiv#o+E@MDcL>;W1c6uRjSK(6{M{IwO-C2QJH`l(Oa|Q*G>u5)SE(W7& z=K`JSQ?QHAcJHIQLP~0EYXHvQHv<3L>Jb9ZI1R}V(km1nn+|Z&Edhrn&%XMd6kSyn z2mr7|-UqRH|9*f;P4CYvO}B&F23*K&?!ulYJ9oQbcLdnk(DZ=8kA{pU4JQw-eoy4s zw*)F$pm~q`Lruv)iU-0d491JCct5~MA?7854Z1V61ku2QHsyHdebnjR3jg}3=5zUP?>3GBdPyI%xsBK-Z zq;dEQ&jb`cKb#d4%TsGuCytnoDpRH4;QcR#^kqeUMXN)Yr3EMFrN8X*gGMmMqGIHc z03|D&yqFB6|C8{n_a|qq2L_6LgXrcITit%U6rel6263oinH35mRJ)E%eD{X3S+x9JtjpT;TCIds{?^_RGB-Sby z@&HWMqpSpPcHb6hE#Umtrvfx$>vCb9YV+IgAh7q^b4^EqJJONl7#?ljg zfk~PXfQbB@>zz1MuCdYRp_Eecx@|jag9Pp{H4=@rOWWIN7l#K_XQ1z}%EP^ImfC!G zu)&N1T?Irq364)(-33k(@b{h%N$Q}D$^&}#u<}8HYq*(TQ3191md(|BiCLpkv}`fF zaBNI**l=D%Z->e(Rlpbi!w13ydy7#ofnvQGJ5mYhWNP20Ey9el;8O}cXUpz9WCJ;{ zDkBL$cOOme6gwXZ-} zL9-!H<&_bic!wn)g^!jgU<0jZe8}#(OJ>n33gEQQAxd zL|A5SfaG?=@ebwQZ~*h==)BdxyV#16W%9Rq^x(g1M~g*kw2DiypVIJN)du^yel~8x z|4KKrKlaBH>6d=NnXR5z9xJq^1E-1u!vZ%`oVl2-u7$QFqU_C+mTwa^!15H0-)(|d zB!PH&e*j9qNC@Z!^}JAAnSi7JL7Jv9MajSGXYi++{!N&HtHZh00Jk$^E$#x0*SW>9n{~=zKCD<7X!Mx z!u$-PLP$i#cN99dj@krK+TdV88nvQ^>V%gthz1P!VwGrj6R2nvF){%HoZk1_S5=UB zpP&A;d&|y|!UDu}i}3&nj7geIw#9Kbx=|^PYAW9LL{*L01zb3$0tR-QDkkKLXWmE# z8nDPU_v@G_^X4BMYzVoq&eWB}*$b$D5q)fZNAZ^r0u zFefXgw*=uEOi+vw-MI&+vYGggO~Q1~RvTJJ@I`I*J>$`84+5|ZBrZK4=MnSzp7l-D z&&h84wEiC3a?vr~FR;FUcLv@L^?By9-RUFw!j63Se7k*=er26PG>ds9*6(A5JO!uN zVm*zRD)UWqTvj^(LuM6q2U&f4F`LY;OX$VB4*)SUuL54DgtPN=>qy?sslanqs2+U{ z6B;LEr=CT%?FP45fJi(A4)#2xF{-LM?jg_F>V~4i#Izd=fRf_LNe6cMA(>coIl*L) z?hsGEGEJ5*^I~GYBDQ#1jCC@1W&ue!vyuZ6k>OX0)$z^0B~6<6u|J8%=;S};JBj;7 z{s!<1POn65{FG$~^XPeB>B`Nk!Eh0|voOWpESYC75ft2~@!=^55&) z)^wP?=*D_7caeY2$_8Oo$y_qUAkJBlQFq0`d(Oqg8c|*gI0a+Hx!)B<5CIcy5nk@V zQac%#I|A7T`J?tFmelt3M z7}-Zk?#iSsZ#QEIOkwWZUk31!nAFR;$Z{aV4`g*%7^TzX`udXx^+FZQLmPu390&Ve z0%OMnNFv}!43v%Hc?#oU6#c`GiUogQ(F~7O@&&i&=ya2R-ukrB#ztK_>KTo!rbNiQ zP-);4zN@wTTmj?AQBQFn)i7;KO`)!CyFoJdIMsN`RrWEs~jfxowqDNCK@&)nQg$5g2qxS9=Gu#Qr4OZNf z_N%2;gakZ}til|`=>gg{g&=e*TQNgqw8TTmfM+aRDijKGdnxkEp=+M(@ zVD0X%xlGz){bM6Ww1XiUhzHIQmS0t9SKYu3RX3?YDH+;9Y90fmfXaNeJFz6Uqd^QA<`a~9%>g*2**CQ6d@RzYuI0e6aYc(p41gJJ z!F!e7mX0)at0nGjDfuMhvGCkU?nPy0)RL;`@+x`!iw*}S2n zCS8(ctK9c`4@-)D!CLK>K?8w`aRXom3RvJr&;F7x zBL<`$C`&GUTKN81tVB4F{uE(bxp-8=zSSlryE7m zZLoL2b3!j3Pdw}Vm;{aJF9I%groggeR3gYx)LkGW0?x9kYHh!!-ea-Ti2#Gprd;7* ze{iDdia}f%2BukKt9j){H~!C{j>3rns{(&~#CdOi@fOItvRG+-(Yyzv`{RI-RV9<}l>RO@~h=&$lcmx8qv(Xmdv zQ!D=e^^9`j4Fju~XT7D@$|Ib1OXVmaD%L+7uDyT$Qb$No*KQIHtpXL|J_NtyR#oHz zqh@49pGwrtm#*@|1rk?-V^c75vjf4?U^tTVuyo8-s3iC)C~{{pml9m zmrUMWlf{QA@Yr6E8V0&W{C{^{RLwApvnHn49OsQoPj3fA84mA7D*n!K=>h4IJJB<~ zxtROSjNzk5vaz?uVV<=g%&dwIW7mg4LjWFHMB}a+bTSQ03&SJEU7cmdPe0qt*$joz z?+Bm|V3gF-acfdI^OTe_WfTStiQ^fgxU<270Br*iJEr(9m0!)2z zU6f?HB}kpIGZ)uG_9}Gtod*mYwn=-|AO8;@e%_;mDAfAboIsTe+gfMsffT8dpq)R(OWm5LBTL#womhbv8CwN(3-j+^Me*#mES>{-9 zZ)ttW^CDvj z2rtvp{niZunU7U$WDssq(=F(!<$WA=)&~HpOWjWoM$T3=Vxx(}(ak8Wng*+2@Nk8V zzJe$<1p$S73?@z|=#PywHmCeoi4O9R!l1`1ATS*!c|VsBIy9Z&Pnzd%z63o zAQ|@3WdcVk00SfNlM9l-FB`YQ_<*KPp13-Ao>e}Tv0$;2-P-hS+yj+6jmp?gt@|X_ ze_ftiI^}yonO$rXza141f6|Qk&;^8+#*kd|&`It4QX`T!!3j#J>C{%@iTiCP2YH%C zegK>;$N@q)$jL-cI_s*V4IQw1arRM-x#oW;4|C8{wFlA9%g|wYIct~p41|VgK-x3v zN=5)bH=9ni9Q;@yumib{dzcDf1X<=xmTe}g=fGF+@SfAt#zOv3Wj{|`JODtN&zG^IFX#&`ki_iZNp3Nc=F&2>rQ5K5WbeA zrDn=}g8|r~MP@f*>fkiYMxHw3(>NZ`ea=aYh)=%W!VPP4ZUcbiRBb@V0E>pYxbrYY z(DH6V5ILw{NoX{w5&mfF5d+h7M9jOlJM?zQVw+a@&~Vq))O43r7ik2*PO>r|ssS#H zgTc&TgDV8ubEu{92(ofvf9jdHXAKIz-NwfX^aQYueN!)yj198Q-l)a!OM~}iFT3uE z*X|E^tg6Sq=md=ehc_DDV_xc~@R{5knB)z2c*3ikLtfxglOrI5mIWCjMXF&?Mw5fb z+o1Ghs7(TTRV}-&giXIcuB!Yo3k4MlkXlWb&;R)xB(Bq2k^lK8J+~{c4%_V&R z3j|gLkuLDQ!Kk;WTtVP{zd{Jfxu)*&H-c~E*+-}#6#|^&r1pD4NO3vnT4 zfGlu$+76I$ok5rFw2&i5;~fA@Ma7CM!v~G@N{|*vS0 zVWMF*=L`l()t0l}W#P(!vtw0jePBT3KVEkE<^}N)Cgg7!<_;tun)@|Cc&$UCR{*&v zNz!^p2Gx6zr~>8)lX~BVoFy+kIUuNulZeZj#Y{u{&l;0+0ie>OX9A%XUgU@-yD95F zsdAGH-Y?}_G%$OfoFBC&<*Me>JOrfpMU2j9-Bdi1xEY9_9he2Z%by*1jHt^3*R`qN z>IALeC{sh(bfPN*EC3uM(Apzhb`82ihChZe5Gl%v00ytLv%U4BE&tc1nsRtEVsI9W zijxj4@*hA=s$H!*3kLoyVdb8N5<+9{Y(75`m`O?p#13-`xTq;yL1ClvW&q4h13q`T zXJDLB8x@8}1pW=OOxYeG*o{nf65q>dd;^Yg2AoM3*0ceIfZQ;@NIhS>-WM4Co?$>{ zhFz{wH3fqBXPbgeOn?d=2Gw$y?PNE~igKZ1*kRvP@>`&iCj(fXC$RBBoP>g~Qao{J zj~AT%-q~BjYEFf=W>m`IiviV+cT^KXyrS7T*B;R(>l8*ttW$C@O^x0udAGIP!~p*} z!K{s)&V(8Vp2}t_?y)2rUF9h%u0hcA6G-W>^95NTp&!gPm;jGIw*SWr1SlTw z;Eg|>o~Q2{V_;6vdrz%T(~VjN#08(HH4E-)5!}eZ%uTZvaM2;=z)f^+O5b@inZXcW z;sTQWLTer9qj0?~lXX~9#-(h$Xi=p@S5(We(S0AfHA9&0^#oA9v-K~e_^$#mfO1&}8B_(K2~v#)waDjnh|Q#)wF2`;r@W=;oCbcQ z2$)K!tK(Xpr?Wg zP6lDbEKaLTeY-7VLDj4kZ3ZS^ zc$8V|A}uAS$D5Zx+yHt!iy(5G4!ijmT2qr*p3SEoZsRdDsv*%!b zVqB+u2&Pt?OES||=jeF!M!t!)Sji9fW&so?+Wa7?7hI_SQ>0p2lg4 zc~KSIX#>x&`S@gYa-K8j=r^)M<+8MlIitC3C8h6ATY=#?5(J$Zgw`pFq1E}zXnExn zHhRn(by(%dC{s(&spJPBTP;t)#k9K9EIQWLO516I9k}FNiN#u>^@U zw;%=pEH08>2}?-K-loyxmwS$k*+vYF8=4U~3}f84fRO6~C8d(pZ9TRj%tpnxqT;SIc zcb}BoK7b!rR#L`PpRvPYvC%gr;$*=B2La4E8=goV?L2JcG4}jX?Y+A}77D|O==}=P zaBe66O#qh6`>e1e_Hu?Ee+V|(J>9JyUjSLkIP!~nYtLR0=>{A`k*`(X7qX0tEc_Lw zhcnl2sU#cVRtH~)KBl-)!UcyhAazG3G|{}>Tv%7c4yf_%#=#$oq);@}KA>4AvEfCdz-xPE(yF%i%CIHvou&-PZ!%##p%=ZIF+zg(;S6JYz zKBy`5R>*A$R|H79JJp1LsqV3eVplt0@frW}2NRSdn&ry`MvaghPy+UZxSCZw8SCm^ zi3drtYpQDXLQdRkjx?y6l%KU&jR3mvhb7?JM2GIP$}+YLAC~#$Ej{&aBP}z*`C=o9 ztpW5$-RSbx03iAMdLoN)E%t7X?rP+vWzz$Z{4QL{_5^*RAR;+Ehv8)r-4fik_Insn#{`v?w=-Oy0x&mJX4&o{h0bHPe_lU2n*kRTz7e`Dk_Mlq+R3u_ z^GMLphqQ$3(svVHv0tFKhVAkVHp7_$)dXu2XsVBeUee-H`Kq)yl#)+JP$9egx=ltY-8@A1b456Qfsf#d585*xC7#3Jawsjqg?uCIBWG>I@G>8{><& zP*ESCiBjo^m-4D4x^K*_BXO7-5ddQ1kO?2)MHoRKXKulObSP^UooMJF3v4}>c=DnL zF#)G?ItA88Ol`v5}-JAeld@ zcu|qjCX8hBVh5iOBD-KzKE?jbPGFJtmIBwFt~0;f=Y~R;P@?!&q>8%7%zYL?dW{c` z)a2_qdjiXua%DMfMa&G8QW{=%tObLAJ7i}|VnqI3KQ;kfAp+O{bsHBfCd15<)aA<| z?p!Y|0V5B7;$uCMBH#v%RR+`0-l@lXZZRq^l*$jbl1YtbU<# zjxWp<@s^K-X$6Ej0jsL>Y*KfgwOIS4Yyzj@&9*+C4w4;d{20wC7X#zMs(a%ZL#IX| z#rXW2gvZ$QToO?g=!F6bCfK4MMX0s3*rl3e(9`XrLYG zbIdtY+%^ZQZX5betpbfn8CC|q_Vo6Gy^~MHJpYV>HIHqfA5ht+(ZRaZA_KVg<2wn} z11TaySB+DRhZ=0m{E{?GC$}>q`6d5r8w7DOi=!x($jF}ibwKU9euj{O?r1Dx*bNBv z%pDl`8UaAoM=l5;*5fRX_iIpq_#13__7?@dHGLJj^O`+gEkNv-vC-6qb>=G;QKM zMgy?w$QS=7TLd&tSJ}}!&X9>I;Oxm(5tC`Ht^V`ssT5;9LGcr%2LvucG+9gL>sc3< zbcV!@aORrf&_mH4Ko|^eTu(ov?gf>)!+mOZh&Ve%v%qquaH^H~dmkVQTK2uFifae8@AN3di35V;6#aU z)MT_&qzCgv5f>N(m-J4Yg|tBr5_hO}H9Ud9-ug7JoucPO$N?{NVXp3BA)y%WS*5qO zATzj+O9jYm2$H*S=SwV^HU<9PbItG|D|O;9`+Hh&lTfZ_!8Y=EKB-p;LG_GcDFsNU zUoaV%Nu7z2E3m-)8BhQUYx1^^0!#L2Tghkos7 zXicOlI>32P`2HK%OG$MjwkRoI%mNJ$s^|u6U`WAYN*-BbF|O8+Rmwidj&6Q zmD7WdegqSPI9!GlTz?+i;ZK$iV%t@@OWDo=n*`f*N1^0gjQb%}6|OuH&^A55HFRTv zR7MvKK!x`NE(6|op~I%60L2l`6WN{NfLjs;hJ@c)UO-CH)RTbbR04^RUOCm=N9_Sj zr9yYTU5IkTJaGpMxn>2nj|DvsGy(Ox!UtpUxT=@{V1OY2G?(&KSr29qt0l<*C}0Hxt%=?w9bqUeDvFahL)tuiH(opVJ~q5I#ScAK5K$pspwEw3K2OmxQ>6qezFxFezSQ!{%B>rv&i%W37ii3Gj0 zryV$;{|Na}AhQ#As3J|0>_cb5xu0$N^q^v|kB#sJ#AWz$Wl0Me;|vJ_a9S zV8m0M1J+Nn;RI`}!T5($Nb~@2zR=0o%1$t0I;>wwD@2p{;vG2=Aq5HptZ_muh!$CT z&FSIOfj4ym3mf^~C)Y)^<<5ejAp@yg@feI|As#IwXNcXwu@(O;Bwm~~wcM0;t*{O^ z5dl=x{?10%Q-6#7g^yU^m7WP{sxeY?-@TlnG;FG(2m`39pT~*XL5v$0CnJj_o!|GX z#hpFE$Vk4QusJ3CQ^CG&c?PeEy*4Dl zS-{aN(XYO=n!>9%4Y4ap7q=dvc|39 zMTRVLWVi>^ynT?>Qca+ek#P6z(g6yV&K`U4kOvlNu^O%R7@$p4#J~z5Ur@$_C^qEpTxS!B|zo45a-LA9Vj# z4g)sUKGDm>2wITRLawTnx zKHqb5>3606ULnsZ)&tzjw^x+A{-EAYnZ{*F`U~a^$^4~|pz#%}U`nL~=m)i3-v}8X z5I?!;aO<Q{sMoQ3Tu0xRpP0@hYUrM8*x zF6fin-T-6~v;hSC*)4Ssfj8phPvu|@QAP8hMTYLhCM<>{J_Zo$S{ZiUo4V zw2HKpIf{=Xhw-Y_rm;)I-!}`1=W!6el4BTPi~!;ONJ!!e%?{c}5w8!Hnw>+?c+|ni z8EmyIAs*H{-~%XHAHBojv*LErssU- zB6X(QG$XzVEVf1mip*Kye8Ba}4CFns3I$x@Ym{dl;7$gJ43MQbUPHbhhF;ywvgir8q-wOv0){|Vx^*x8wNl~^*R_<=wgXE$aqlv zuh{oZ`08KRwUil3_m3}p=>&vJVCmt@V~XV@Y_$V|cQ=_Y`n8atCC$off^*{cX#;y~ zk1q{^ixr^w18+XmPf%So{tXBz-}jf}#<4Y6i3bSHIl=*L(bm@jcY$Wh$6n1|Skk@F z^|jR&gIkTVYXbiWV2TO9(Q$X-KnqsDZU|~lQPLv9g3TxeFwNn zcaHtH#dPqP=o%HzfrSz50Xrw97+h=z_!qBWk^{T_nFOM<%qL^Pv;%k*Ib#cu_xdE- zP7oQ0&2H}v@&m5i?-T6HI2*v%6za7pTP_I1r`Tm;VCNrzOw&rh=LVFSSgP%}?Hx02 zne<5Kxq#s{>jzS`D>6b@iSmr}1_OpIz5SYMUE=i06NJN$$I#m$N3t{Mm!vn|)9T-( zMF7<(_CuL0+IS-0GWv)7UsCR)XhLW1jDIf;C)=;k=>Xmet;k)C8FJdWp8^qM-=pvr zxp7KVwWeN~p_X`tuL26pYJ2v(NEGLo5cqhC-2HnHXZZWlB)eek8;!#IH2|9oY?@~) z^t@zOpAQ8a-B`WFki3;-RNJ#1^~gwyzasE z`y@!zcLPl~ERx{DXaKqIcj^?cDEq2-w34$WC$;2KMFKp7p#_-pp(Wb~lpuH|^x^z}=LJ4d+WRsrW8qJZ-?JjfN=tq0DV8A$V%p1tX~e>c z{sid)nk8K)Ch}kZ(>jOsR{N+b8%#zkYTWloeV^yKJ^(^6TlS*2{7hU^HrUv=3h`*H z!D=yFml%B7K4wdja0H{XJLj!@d#g?=<0F^*dOPMi-eeXtN+fHLmiJ;I5(eN}Rqu(l ztsE`6Ikl6Gc`SV59d0md%W9-Y<(K@A9tUYP9Av5ms#UMA@85Y2#jS-o8TmsDgzjX& ze^SCIj{$jNy9s!E+1@A6JZ3&{2EHb4 z%4f=-kG5I+JR=S80`3V1V+8oRxB%Ow_-OEMSG)a1nwsxOG~9>0p2f(EtTI4Jhz6c? z*Bi?Bux_p`lkX|6kHk~KkhO(6x)N{ZKX?7hhyYQsn7}S!>4;0~^Pu@+3BKCZzZXqd zNw*U6`;}O8qXRePX-7>LUH+yNivl? z$W~+uOfMs!Ro^vfN*VKT8Ne@UO98&VPUlpV$kKJ7(4l{KdL)O*vck{Ev{`a8vEtVr z>;q18*5CE_1c7HP6OzGM7Yo|BoS7Ci19+B+uh8uAQU^94hRZW?;Qq)z%HY$<#L#Gx zmQ=K71!MTj(y|=x)CZ++&~{R#T%V@>gV>5na`!FCp$w6+rlA!e;FIKiTL3)h^t4$7 zCej^2G8Z!ILJCcW(3Nxr`z?_cTBINl zT?Vk+`qNM;5$b8>r8Z5$ygKx;z;){o$H|^Zy6_F!Z3b2+%q+!s{%m!cvT5w=c)H>z z&<$+VW*$3YatS60bpZf`QwL~pp#fiJF*9$F-TK4XYK_etj%{q^mZw0SCgCed}N2fTm3#kxVY`vxIpu1FdelPDgV0V(1)dIa= zlu$9G!_eZpc10a{SW>BWp>jH>UWXh2+OZNqi3L@=Y^vpIHXtZ&X1E{&tpub=UzbK8 zyThSH4F^-iwFd;v$Vixs2-GHNI0Xc7fcue;O9}H{1#L2Qt_AIuo&&D|8Y}E*pz~yA z0`2Hs?{Tmne;V-y8+yRgtU{#XILcp zFValpFFnMYZBA)P*CYc$UQ+}b;Nvy@r>YK{y5c+J-IeEqUp;5$DwTA}dF^k(W z#YbFKB3R=mQA>X3^!=}(tW(S1YuE!=al!=L*A|oF#u}eEI9NBDPL{qM065Son-mkw z&S=vT)g=J@Hn10K*xeoG91%z$0aZ(O^5$7CY+~lqeECFzA5{b~XJi5F*)(fN+?YPQ z6vawwHK1WQW;2q=n=qIH$+QR6EZ>tZK$PY32_ppY8)8Zvx{V1xq48z!3U)dBltlpI z0DlLuy%y8(MJQY6Ie85mtRQ>I!mdbGtCu$Jn?*X-MfECvy5kMkGZYOY#HRrtSN?(+wKyYeg1OHPFf zd{Bz64G(~OKeJh$&se}WUElz@L1+g7)wn8L^}k`&JLthE_vUX1bP~T$Thk>qbeSIf({01xWhx#C5uZ(Rl>{fT%a$ zb)E|yqn9n3n!P{w>0AdJD1GMGA$gDpv8V+bV5I0)+JJ^;uSZZ+q7@~RbfgCrNd*t_ z=js)W$?!h@ca_WqPrZ#Sy!V(~rcaFIWXuGf2+e0$xf1#eiJDFU4um}*4Y=`t0#w`3;S8YTl!HEh!r%pz zukS^`$R$!zkDv<&+v10_FfDB%3Dl#N+$&1Kf~yCil4DdBWH#yM-Rj_pTbtE)X~PTu zK!Z5Dj27NER`v%W49xnpmLxtB)tcTIkE<}wF4ED{AOM&7ORH{Jin#-nx358gcqr6p zAz-@a!3k8qC}T?^6B!!5!@zC(>30Xq_mAc$XD(h2JP{wDA1UNa6?Nva2Sqtfp8K$P zc(4W=0d7JK-8*F$lpH|P!{aCYz6E`vQ+?wDO3tiht62d=T``t?et{sl1NhW-4`bWm z9G#_ga7jpDysAb~3Nr)^5#toU=Y?3TY*g=zNV;Admf}2@h z8(R%;JcZbzX~e%uB)LH2k&j7FmZ{o2k`+bTH1aj1MW5L8gSXh|H2X7Rve{m z@-U-B4KZbtY#jvg8!p(5C-Wtd`cl%mug823#>3!3w2wMob_N%rZtnu5f4-DuF>0;~ zEA)APNd5bZ_FC%QBeV2@Qe<0u4YvknWJwd2?#@uFv+^)!rUZwLdCKdAGXKU(%|lcY z5im9vSKR8ysB zU1=Fej_BCxp&66J?HdMU1b;BQ?B%Zu)77ouoaSa=qdr9W|J|8x1x?bgF1rShu4;lA z?5YG&5?wv8dO-H`4Iapd7q(3Jz9;z^CvyNtxya{a!QQ|l97&z|=L*iKlPLqLBwTdB zdvg|QfA9yADvWS*9+#KB@f9 zwp$N)RkR%T1XTo}qG3BLM|1h}%32-SzCZsBM|HEmePhxyceWGkIS2x=YpdjJAe~qX zlEwZgJhRStr`h|+-OVWSI@J=;FM9&YDnU((&>K$%0$EPj$WdmlD#wM8_hQ0-vqT1WO%`FKaUcRrgWu0onN3wB?pF{zFm$BZsMT?_!N~>% z6%2JfY`KMGF`U__ZkNC3{6H1sxR(Tr=Ju9ETpj^I;GaiiwpA+iM0eb43SRf!jt6GF znJ}wDkrRr!SsVvXA?9t=`pvg})#%TcTCsz@NRrn(_joN(B()_31>gg9>=NPN8eA0Q zD`S+^zbr1=3|@VT3KwwUex4}eC~g7|TRG+g-dWF#Xo5sZP}R&YsH~!4)_{~>X(>!k zEawFn0%;dpne;F!$OUmL$9ODC=r0bN@rw$7^7W`8Rnnl6U zn7Yfbh<{1QBu`>vTnOn*%N|JBBI07pO*~$T+qKiR8KSKqSE^8z` z^3*;butYoD&wf(ZA96WJ+NiwQ^_p>0{9gu6RVqzNJZUX^zE{C>wXd=IXVS7q<1qi< zw^_?i=pX`-j_mC%30&Qzi8;8Utl=oPmT5s(_Jn@j4};|>^LGV?FsvXydc!R~9_{@2 zwXd&hKGuUKdGC~JCg_})U=#$)jI@s`V@u0&p#a>o0jJCLu!5P?znfVnD`4WFyn_Ka zpD~MUyeaN4I2Fz!%nwxwq)7=HKRhMHnPDgV; zId+igkAFK!x${15@!Wtp_YnNbTYm>n%Mg-5Y}U4 z0ep;*SypFXe;~#HOl$)o`LMzICLdRbZB2SwZjzq{w1t!#v(L|vvJ!Y9eRlwHM-1&B zPfo|ifjHdRlaSD;h-nDWQ7leLbe#*UzvX%#WSSx3>&(H;jC>lyhEzJP=iy+DboK`m0u zXfIDhBMAv8iz@@yFox&B{3MkKF-!{d;~(W~!^F)%SxR3BuX^F=IpG0qNTFA&#QeaO zy~s^8v@(k?cjdpGr|#2Tby#+SrCcR{qrrA^k-rZBQEvT8NYu5vNR6+)Jsy}ocK?jKRk_j~rHfgmR`<+`q_qHp_z=ZAa%7Ee(RPSKfMXCn1~>t*%;htC z$zmW)56Gi!DXZ+Hizza`N}a^m@~ptK=yUo&HBKCaQf06sYC6YG+t!J8s; zCgE)x##jP)jG2|a4C+(Ce&!=k@OKIrWzku&9=9Nx&awN>JK6woq9+}TD8FyPmXaP7 zF+D^~!`zwdH5d394Aw^1)<6Q?w&iy-NgjY$0Ae(GF667g7zOUpYoH;ZnVjj~RSObTg8qOw~)sDK?cDRly2Py&@B1rjrGRB zW}X8;!>&#U97sJ?C%B}HsfxS$>?qEB54{F~X#g~gOytr}3H7>Pc zt|4jxc1Hd94=B|$cL;r!22OYRFxk29cOJuJH1Yixuki!9ypHzeUTJp*i#$lVrL#sA_}l^8y|M~k>y!!#(Y==_2)rPc48fAfG`)mSVwkm#(vTX*Z|7M&ax9%vpjY+VssKvRWhA_j8 z2apB|W$Jmxx1QKMBl6VUlnMe|>A=vav6F(fw&78pdn^DsBR7ZSqM*z7ZZ@^5@e-&U zTQsZ+#KTUm&{clIBZdH2#Qo4)CC)wD7z!!foyU*Sz?;l@K%f<5yWR3kwwwhTRVfLP zDmcCRrS!6M4y$|D3^-vciK_3s0e?mOW~dhTSDn3U5AQaKbBo_E-j9`2zM zB1B#l7G?oZ*Dd|DZ%K@yA{~#HV88~b&++Wm`=y#wn&vfbJ-GzKNLd~E54}I_MWwF! z@pZQRQwHk9#U$)~1aCXOqXY)HHWJM>1%ytw!W%eQ3!va^gP5vxeJdm9>wJd+UM&Q~ zbi0P)cOhh!TS>VK@`&oxCFnm@TmvbOR;p;og60MT@(#l7O;UWo06-ihEjLE;yR#~E zC*Ei1=On$R#k9dxL2d`(-%~(3d%#Ik{ANWo0aVnz!_gYw(^}noeGhi7~UZhCrrQ^#}5G*pjeK{x~B6_ZOw7m77u$v z1&IPdCSoky$;C%!{0jyV7c%y{kZOTXZuFGP9lU=3;+m1+m7P`-JhIcSCXNJ#u?`EZ zkvw-pLeUe3Rh_S5h2J?Ht9!|RyWY(W_?iV4yapr$cZ2fK&Js66*gD5;Y zuhwJRD7#_A$mOX-?N~}}UTy~FLxlpS!ZPpiRT|oG0fKUHuLqMw)r|3xmUB0a)^G&y z&kfDp5-tGc#y4R>c`=9cQ&;10pd^IxU0K=+w3Y*$>oeI#Ay%gKA7E5kq(bsnEea1P z;yIVd7%En5748Jz`AkWDw8rN)qYc}`t!5LqOzk+?3o6I_c8ta^%WMGK!+;;EB*?`> zT8)H#uR~@V>wnY0JS`|>wn!*V?y3a(Gq6kQzjMxU?%RS?Op6V;Avo5!LHxU1fEzq{ zdvgQk|NCz1Z&rQs0_!w#ICrhnPDcDno9pjf=;g&ueh>#rJ5<=QljL9zqsBZ&Y?IT0$`7)>&{5&K;amqMj3ZiYwa z6)8j4gZBg1>a`mJI4r_+BNJUC6AsMF!U{yZ7FjM4%IDz`(C`KwccmmwX?}&e_JMg& z-i*s)UzJSvV@6>D0|sj1%Q67maC(`-B=8CuOBCSISv$tv|4tegucwCJ349(yJmvz6 zcWr~$0!y~I+*f1r<;>7xFF#p}=fhAgW_gco>q7!9$lxXcQtK-NQv+It3ZsP@a^{;IuZLTq?}LlDa~|%AOhJRWLR4L#7+pf zruPCNz0|33qg2<54zh;T?tExDw|YhO*e8`$L{Un3Kd1$BCkKxBgAgEun_wH+2+O`r zi6o$=1}b1B3xS#xKc58v*+CZ=Q5kO0$7?dNMzO_;XY z98Cg9FSq#C4}3%bERmGI5Gcs5)qG+#XyOL&cTkn7&|Neyk&o(5LD{bmP2JBtB9Y)p zk>17|K<)yU01lR0&I4!H)lhk8435m`W^KB6OSP)d3>BzYtDprBahn5sw;hs!>dvxd z2$G=b3j2*B>ITt_ywFJws&bS88IRXS%jkTam zFq60zk@1r2dIDr<&@)Ufob1P$?uP^ZGKSQq!s?8>KnL2oA$|8+*G_jb(m&_w8B@OO z%NYYrzu+=aO*Rq^=5lrsgE9xAb233H(%5Uj2fAcD;W-Cv*s=oj>QXFuOUAGWU3Vbz zr)coAH<;U6TSzAYHO~XqJn8t`?cp)hd&=ABPa*{Q;%YY9j6tZj%Sl5bmSF+2NA`-) zgzD7f09PFYvR$8i!$2uT(7qpVqYj67xH#ozfgX&8XnaSD~Sj%M}G0tPnTxKhOgO#m7|)kzPYr588*7 z$2Ljv3X;Z81&?bkY4WVTr@seeBLeq<){bMSAj(%7obTdw$WVTQtn+_5qqLEsU9$!C zf>&ea;XwPKtUFA?DxVgXMwy-Fzfz~NH+P6Mu@p&il1DR#aA$okeK8T@z*_HrO5)t7~v`$&BmU#91 zt6jrzj9zMl(K_O>_f~R3_WJFy3JnkNJ~ zVpsae?;>lm+Wg@+5R)Ey6}Y%|$%(&2zAzIuxf23nWc`RBwpd?USOo;%T_9kr1{|(d z+QN6h@KW9ah}{FPSeR{F_GbkLpKW%UhI2C0PO@thlD`4GhmT!B1n~!J0jV|SNl3`c z;EiolIZ9W2sBy{kZ~S`)=?1alaFYN;KMlZ;6+FB+q`C8N0kAo&W{4cd0Jlr;i>*{y z+vfoq*)A@LmAdS{J4`ixdda#JeUrA?m`t^UbD$MYe{}`l*^fNge_8k_K6K`nIo({>~ zQDhZrnDGP*o{uTfJE@@3%3|kGMm7i6M+yx!>~%ztq`Yy2%%<*>aJ&!}NMMD(?y|Fw z6uk#yc%VH=GrN*}|CT`489liXPavk|Ij(_VbnS4e7v~@n2m7sP5{U zriQ>cW7LoshT4$!85jZn5d01W=mtDi-wm&y`L{rC17eRTycRIrS2b<=4@ zUu1WI1y7sR-|e60y9e(ba9qj(Nk;|}94701R7h?7`vhsTj?D^xhhyEt0b$zEJDi|O z<{ASZ#BP=q{_eT1NSHYtDhDBdJS)P!MS!{EPsSmeBX?j8W~8?n7g^-+W*%6;Bct zlZ3r2A9~NLF`@(l)qkxKf&e#CSZv2qpkCMOLP~N$;z(ma$yA!WO{oDBR%6iO4}$Q( zf%0s5g}P@b3vt!e*=(Jjx_z{b8r}gTsNS2m=<|a`Y9yH#Nta-VyGX}QSvLa+6gBz# ziQ@uTv$*jt1-7m$5C?%?f#TwoDJ~0WC+c3^52m4an*$;vBFr-W__CL9%6UqS*)^-}n zaj(W(OWVQHG&CCi1Gcd#?%|7NQ5bERgdzndHaOZjS}Y5VO*O~*tLsZPC=G8)R1vz8 zX`)8#kY@nO0(s9rdcyj$-dDwL*U~Tw&-H4KY&Kk3*O2?zA%Fy0SLp2^&c|~pIPkLk z@-mrXE=qR#(SXe~Ixk#*BGGwOD>nr?o+BCD<;Mv|RH?$Ti!~Q=Lu;G--pOG(~tjY2DDT8U_Qhy^nBMP24Ug z(l$N)ke;2pAw zEmj0v>2iAB>+i`1J)g&st(w`@ceUa0G)iS`!405guUZCN$UDVcv16sGB|*HwZxdQq zB|jo%U4Wm}irTki15pC%r56WU7+Jylq-o$L)3xvRP?n^z3igGWh17DInRf$33Ax(| z_SM%ItovZYYU>C8wiBgYWK6f?>ADZ|g|7wUJWdfdks=AL(KDO(N#31;F!3Gt&8ar~ z9C0iFmJS7*E(FOL;f<;}VwFX8+r4t+x(Ox}fl)472w3+juPFxh?3~g9UM&T<8mmW~ z57%CoB8H&uPGVS@e22NX5itfv=Rn#uiK#B+t)WT8r9odf|HxI!=}i6PwT5k}_pb)= zOQv5X{dM)gy(FJ34WZ}+o%C4k zsJaP(x}_s7b=m+B{Sn=W*RN}fYnjJl2|t1#MWO{3SNN=% zs|3}42FBe1R?-v-Kk3sa4w+5;3vDyBpN0liJN!{9SN*ULzxXNqYD*uBI^L+b*qmwOVf6>S=X3^!knJGQ-X%Jk&*egWSd-tFvlDmq+os6%Y4T@k;PmlOf^K+tO9uosQRf#5k^LCI>g(aV;E zOh`*WSaJ82$r1qr2EU|zY4wP+QR47nanM-NI$FYuUHahC!C>|8P{IdzS}9xG2;3?J z^HB}(b@n~-Qtg@YUCShXFX<(#Q^f-(E+a_GJuCRp^%KxRW*15p6z15_!D_)eRI~)V zR?`Dwfn+oak|XfxvfS296ZxvgmtLzlH%P5|=VnZ*qaOhZGR>of+0+yHYB44=>5BlA z*OrjD9heNLu&Wp1IvD^KkID|cf|a04=)?!KUtiOp&_wQG`avlhK3{|V2WSO|6-OKi zou%QX5%glkyaLjrHvs>E5uU`y4J+xsH&g~)8Ysdjxc5|Pt-WY#d@-dl--vi1r0U*f zJd?p)k&Xs}yIR{@idSeVS7ku)gX7e8Do2`k85LDG=yfw7rm(o06GZkHl(28#r* z#>F#gfSLwPbJBi{=bsvt%FUJ+is9n}-ByqY9wrCrdDNap&Ft@=nVBCWO0>foJ?*bf z7l9{IbTrzoj5q`DvaF}cC;gC%gF6PoYwN#q9sEYiT|~#kCHH*a8cw`~B z*d(=@g@8s!{3Xo~u}xI8`)!x{1r!D9hSv6EJl?0g=hx(-cH#V_^!8%SJQ;5qoTJP^ zy@>*Lo;p0G!^=W*hS~#2^gQ`6ev1&Y#v+qAV2oKRtNaGbE$`HZ#FOOB*^`Oty@>VU z%TmPRzUntpm>%b4z*z?8qmSBw@wdJR9e|Xspl#8Y!r+kXo81~e(?#vPfQJJ*y#XYZ zzN8r}-9jK;_+Dmv{*K5uom<7_dJo}(*vkN+b4e@r-9U7v(Bqa0nNVzj!ANa8mIFQSV^;vRjIp7-daMv=BxzW zl*b1PDR<%1=d5t+dP$tX^c9 z!wE8VSS;Qv!N~=U54mwO((gxrJ$$Ni8xL9Gh3R4mM~RmvQb{r4T`%8Q8#e#61GebbgF)ta+$dvC|zr z*1U~zVDptBT&f0`d^7vzM!Nt`$w~^j>*~y^8s^~3OxCi_aA@GEp5A9Vs5p*VF3|uQ zi~Bjynd=)0x8x^QW<9 zjy+Ugr?V!qBZ116xpuo2D6));eP{;`Y9c~X#?<`LP&Mt8mkC6dDuc2Z|w6MN1cmOub|T|vA%T-$_3(EX23&KwCZD9K&p$izay zM^R?8&+-K~QNzPAxv<{XxP|)gDP>#V2A-9)2va9$ie298w0S-9|P!K$5 z{TX)|RVYci5?vJhk!e}1;u!}VM5u)JPS@vHg7D_plF2nE=z>}N!mWl7>17H($*%|V zmW>&u`(344{(bl>-Mp$Cw@63m?>_Uc;!|6&aYh010u9%{4(PCE%~!|%f!~&{3WZ7H zW_A@ysov)Yj5-7n05K!@SXh%a`I3t5fd;m5?*{9Xe=^iQ^1YK>IM@W1&&UdCknv5n zZwa8B0tGQ%{!&8Sryo9l9M@$6Sfc_{?v5+9AjRT?_MpOM3Gs^#%G6JgVcpR8OBsz3 z_z(jfrK@X@zyVu)7-g!k!_9Z1qakIbct0Y74xP$A_OJk$)?YyY;?TDg=&XMkOxPH_ zzdX`fT^@FAp->-4OmPCm8#enS-3CEK`eQo9pl>z%ZOwQABDN|%7l~QS&~OCz+a0&#%Q0D7-L3Fxc)4(Cr#&2Cgu{Ly_#%HqZ zopoAm?34mRe8P$@DlyXdVDkCBRAY#*cA|ogE$7 z2#MpwTUVHZzJ3)o+jm$Ut3wAmhjHYyq54__zM`fBP&{Zzlt=;kKgyOjjLM$S9IXI~ zL9iW|lxU>bb8Br`A^014^!Q)S1jc`HA5paU|CIxNJ;XL)TCl?P{U-%e3Rz@us&7fF zhdRD2>t4_yY5&;{A9y zkU_IXt+!vx*d`Dy5u%xmi^Jh6?~4N4Uvi^nV_3Te`5SK|Y^&6y|L2)G?cR2--U`y) z(o_Oz+~*+_5rj(fL`l%@57GQNPQP~zD0}6Q4Q;n+;D`kegl-b{nJZs+BX*`WFokS< zQ8dX*4%aKL_kVd+l-U4Ls++d`VGXiWZnojPfcyA(fJIdB<1{+qq=;pp;5Y)pwuzln z#)TRC#97}Db(MDEU5zxgi)&oODbchqgu?(2Tj)JetJKR?;)qvS1=+?|q1CT;c@GXm z+UWZpu>%L8GLK!c^c*sF;N4#bqq6Ia2`32R05ooc^x8r8Za4>BgK+3jBSzqR_DO{V z!d^#~h|8&t5@BnxV#U1tha3bl5{7JhF6?vTd?v*CIm_AJ6`d>ZB<5gpW^4U@NbUf> zpY4)iC1pWyZ-kC}5Uqux&X7;u3=3+0HG)njk9Pv*o?$E#*=|*FzIOEcUSd?2bRQ-v z&?sou%_GW2qlo}CTv`1<0yYJ)6)digPEI3rJ8ZL<9X3pT3mCHJ;c5e?isdX$ESyEQ zp(BJZeOYBCyCf!)xeJ*Jy|RAt-kbp9(Bq^oBmW@Hey6U^{n^KQP3!@N|Di-vQ;^1@UrTc~3?=zlRF{)NLb}R&xku3+(rf~V z4rZB&M9%9zAtd&8vHHT%W}kq@uXDqmBtVR@dd~)PX{ude0?_I=n&m0c&a{m?A!xRZ zix|8oZQ60Zp(+QP!(q8I294&6qbbET682x`(thG+nd?m?I!U+xWMoM* z0E7pAgwfC63I7KEeZIS-a4sVAMNA&qi|SC8P{DK|;M@Xdb1cJQyOMa0?QqV)Pi4z} zIr9x<_mtL5 zhMpO}LY%pSzIG7)AHIm;jbHwDSd)`Kq7(xqBGZXQsxnY^n8-%z8y*ey@hbIgLH);+ zQD0VLnr8S?mly(Gdof+Uk-K~uh}AJR z0|F?)cpg1xqHLaNz!`7eE3W}j;yoT89ZH~L%)@ILEbLF2xMhLwI&x^)h(@S&PFDo3 zDi9-4;pP-+6|^cUu;|2}Nb$g6N z$yjrpBm<{6IXeMviREP^$xQXmS!7yoaVE#wQ>8F&jxy#CCWs(u+vETy1kVH_=gBvt zSN#milF}nf?w-D!6u_#1&7$7jpzj1W_@3ji2 z73_N{rn<;8_nue`>u&%kV~-1TAYGkr63*{PQT_7lelGOcKmAStv{Zhj)F=f^kxc(J z_%5Zp>^z}*4JosbO!N!NC5oUy-w%*(`Pc-E2mLv(q$$MylIfvS{1y=w!x4!;mGl}l zuwTB%Rzm>j8A?uwAPFa2&-X^=V)^QZbmsCe(ynNm|J=eBh}{rtW)Gvfey zrgpjWJ_J2%O`=Q!FM+%osKke;P{mZP<#cD_$QuCZE<&YPOP+r`(CMJfN1p8f>ARrT zQ-sT73r>o{p>YM{mp$$$s?MD@gTA!6(Z5?>X3Mpg9V>uxoYOtqSVaa-=6kG{8=R@q zopcRrY@wZ(#1X8&#=qstfE&BtlVbq~>>Z9Tf(T){FZ*Gr&?!GY7Bg84FG8&%oEWcL zK7Rnli4;mfAJ1g2vH9o=W**l=+Apveyu0lN21?M?I=unE;`>D}Xy9sNA*G=nyo0s7 zXo?Z#3tSVrRu$bMtuq3=Py>8-W&pWk!3a@2r>alV(O3v|Q>)|p+%c=nmtb ze3}HOpdD;UPB4Yo9{CR0ly`a}Z$lJu*kiO5tr3MWgKW|C-E9gb`kg&d`Z&wHpH4Cb@Y_ zV;hdDwdO(Y^Z1U1_%*s)Zt%vM)8bl0ji?5K!pn&Rn(kBlM-6;SYy$KBs~Jk~hyUvB zdap1i$a4a z!}ZxNi&XbZ`8cUH+7ob#H#Gpvf+4Hg0u)BLx-P>XWPT%;4SBiUL84T}VNN2jB-1h|e;yhcBZ`m=o2kI{p7;gm52W6Iek})U?^7zqSSur(hv;XG5 z^#e&=Ns4RWw)6nnO;aKsU!pJ=4AkVim%HoEY@|+mM6BxGBChq3wg?8$RFFyc#xQ-3 zt7S^wH9(lx|11_vT9Ynw@1zg^i;)Io`EE0(qPW6>m7f(3O8R->oFIv!PJVaoWhqX$ zsyqP9nCKb$*s|e;jS)ON5KWx-L%4kAMmtdUT82c4=iC9F2PPLNHF-u}4nFOM=RgV9 z{b7r=9i>_ZPdQvGoOuLpXy~9YIhCF zQjv+s;n?Of-qhB3nSNMD+(>gApH-myVt@xFQ&#iaxs~xU>R1zy&{wGixjhgYh~`<> zfI;PFlwt)12vy?HO~?#9cZ}+%we*Y;J)9SU*U1%{3~KLM&E5oF=Dg9%ekV=iH1&u( zymr$~*eoH2aw_G`=Zw5ay>5*W`M(#Fzt*ZeK-bl}SESj!Jt89WS}xeK!C(Nyr)4c{Xu5j|vCxS*^5MwCkr` zpaaJO+VyP;txi6aSOcxm^dtn9d3y)FKB0B^yI6T7x*Gmmh_VyYNP<;M9ZglgE}c2- zGSUGSVst=O-4z<$j^&0(qzoQ$Ix%GtKruwmIHcD@I3yQU(RM{G~RN1Rcu3jb#6 zmoeg_j29>|f42zGhe!aU2vaUKf{VGYWYmF0J(Q}bd!O|OP#(dsj$jwT!h#3oq^#V- z{ZfS>ylZIl5T*22PU*;GYhWhv@b=&_QE~yMpR^MwS&(x1;DNvZ_fO_93dWa966=u_ z0@_7D&v69=?6Fq)Bxs2qqH1bR-I#BR(Kk_Ix@Y-o5By+^;!`CP)#=pyv-}FGNj5!SW5uDr`T|2!Z!*%%BVLJ{+wn{{)eZO zn&x$GKAlfl>~R9`1LR=9FjL{*jsf9l$>?0U^!ZThDJxiv%`aBi)N=tsi$asn>j{NR zvZmL1JsL9^LwI2Xfr-Fn7mK4@&cz3j!`V;9c71+RnN(1|D=L*eT;O1iaeNNqj$kOy zLaGP3J(IVkvh2aiJBI9hZ@vNi)aTc;Z$YU+*7H~WZSlts<| zL6H;2gXlpD$BUol)&mDPjKe?2vY`&wvuqW3wx&*)H6L$+CLp`O1DT_d2VMsb95FVP zhq7?JZ)1%VR*|4F($Q9Sw`Xv3!lP*fgVX|YcRb*37A$(RfAFg1qAF~Qb zGENhJL{SHXJUG?)E`aNm~y2wV(H3d^14YI{VyxTV;eK8MK4zroS6K*a}FHAU#IfvLxHE{`@TA%sUo_*kk- za3e{G4P#i=Bw_{bm!|*db)82>R$WSd^Jx6rp4BDHD{(^)zIy1iCj|w}n(+0x)OgOd zdJ%|wb@A@>&(dg6^NP}rRpAWq|drv8C%{2NQX7b9e_=BTWh?50T(yhNUS81AL;f z{xy?oh-QY2{HQ0@v3LPrP2+LS&=!UG_xHPkY&X~ekXhp42TmEkhx%@Ma(4ui=wNbH znTEZjjF=>|t!IJOQR-k{`h2lq5Duq|Tv!HLzKSGFoj3;5UrOPhN>0NT&4TG8B?b}) zFv6Vb4!Z-^`YBM~0Cj#N*Ht~6?PXp7C{V2(JNv~md)6!2=fWi18; z1B2>ykD*xYKmq@^?sn}^ADU5yeFQQ*Ad|cFvfTqF!r=s5E#fQP@>UAr`OUf1R+PADX|*EyiLX(ta-#qY{nMNHxr4*o zNGwV-@D6LdL~8VyVG)cd5aEA9Xj!p%?H6I#_*u zs^|ekj9tIR(9F46B4~a-iO?e^{Xi>;-v?wWqOMv5tcrTH=+ln>p)r-wt|Q~f$B)bhm}?XVI7Guf3P zyG_0nMyOb%VVVZv`JwYA%J1-2@q-s|K-x_w=c#vo5QKe_jFNmPOMwC}V0dP{`?E$q z#1v-yfP7e5pBjc3W4&yCF!&_wnj!}?d=e$v>8SGQ5WeI~0ST&<2!h&g1A}|#Gauwm z3fKb)GZ)TxKngP>+V21c-cwefduJwt5t?Y*Iav+t1mgpV{@c%?bUtsHj@*P}I;=hS z1Lqh<4_@$7uF_F?QLX~vL`H3nLfP^uTr?=BvNIFpndCRZpQDUBF0taBZGE8?IpC-QlV}7te z40Z#IdzAw6BKV;K0CBDnYOpR&pTG*0Lk_ONY$_y2r!P|>;!y^Y<~n_{zY!mom+iB; z2NZLMbmke51pe-mmKYEe8l(cm@K{UXuA+NH8G&|FUBBPn1bnD=&7*2IX~3d8&n5#& z_gcTevU@KCnAcc6QFX|hULPZ@;*HIo6WD^!>;45p2Yr6|h?;e}Ky^?{FnqiSKqigH ze^*^^72qg$Q4IhHSfHt2C*-Ni#1m9GNuDccA^O85&ww{XXQd)vE%^nXE#c$C*q{VE zT?BIXjln6VU|cSKXslcj%RhB7h@%49Odxp9#2pXlEUERK^&+=kh&l4^6S~`#umxSC z^2`Ict%AK;6%;f`hQevyenR8lyI$e^z2Xq3S3z6#aBlJ5xMEFhs;cG=ksmRf|?tDB}hepVB^` zS(MZ`)YB@W6>)g>(uF4S=In*%Ym|$JytM|{Ld!+_)J!$PZ(lE0hHG`w$R7g4{OL;7cyS?0rNgpo zhxtpMb%*e2qDo{AsPKOXqO}1ht9#aW1;$3cGRPIqO25yZ%g$GS*is2cn zp?ZtVTCLTd*2QG5S`5V6o%ZL%7XuMN zn+OKnHruI`+UEqRD|4rX)UetGNK<3fn%Rp^fChsh{6Mq^RAsHDuUR|@maHLju(mq`rgRQcU}U-$Y#)`Bv~?E{5*oY@)*_Pk{9?IH1r_wgJ>-PC-1}pMw4w^%x^Qa0z3fOgTWcBBsJ1C zxtDozcz^E(;fiVl$KwICS6UkHa(h!{wXOFoy%<2^Q5T}ai_hW)TyXM5C1e{l@~-5P zTj`dO1~folKaJJ=jzZ5lDf%%6Eo7=Ir7|JQ>25~`#!r$S2jW=xJj@S>}(=M-XAln7KHyO zktS@tHYH()C{?`!XK^0@+)N1B7`Uxut=eUc!SIwH<|1&tpjKkJR`qNE$UxIkAY@Fp zjug@f3qnIKxnOvqf$b`ByyX-6=E`pdJ(@4baMKr)-zp75k-(Pq0J^Y(2gDVN-D)kT z17Ixy!Q3hXf(pa2pEAXWoUDjRDtqHxtDF`J)A6qckK|Sao|AvW^+bHhqvNzf($&3G zQU4(DsKuHt(ALM{2`+U8rP~UfkIqSJo-Hh7>>eZ)#;<_Kmd4x+b_y|Ogr9Zm?6BJO?hH}*;rqz} ztwl?@ESRnw_i}f1X2e`NK%CH|1Qp<%4TpyZ4zC~vLV|78-8KYi@xYd_iyMdZg*1T= zC$1`w2o!<^(+}9CabZasnPFEFFNO{ZBQckDkIYrfA1SQ{t$Ixd)w78!A04A- z3L_mm1rJRH(gJ@aPzMjGWnGK{LOv}9`?!vbL+@+wDUi@g+(Oj;@_j6y^lh^|M2`DU z!5Y&8Z^n2q#r$;^gBf?Q>~ZnEDbXJ6;U?In#<9uRi6HU-2BybhF^B22{CCu@%zim` z_ADzm^_7JY>wNYW2Bhl;VTZ=%Q48Z~#=QLwD%aoq}8r7m&Ah$7h#oQYJnz)`saM)C`KD4zav5K2i)Rj1BARYTugT%vR9 z#>t`s;X4ck9XthH*X-bmKP@EXP*5%3yslp8_>j6DUMm!LVy_3j4hS=6?!E6*LW5&>0YO!m_RBk>*6qnp~8Wu%NXM!$zn z5eqFWIxdC)DA5Aaf&T^u=Z)9C=$_d|&XqFkb zEE*mds-@5dqhnSs5{)e2DYG}6KV~WO_=+`9A-S096d<#OP(8$S90mt!> z#HqQ>=zNY(TR0x_rx5Xm36yXF1cWxYkvsV;^2LXR0?2V`kZqoI+}%d~aQSDd=zP!v zu0LPXf7Z6DW?Oa4xXmb`8BPR|CJ*xb&|A0+1KO+yfz@AGJ>HJo%dj2$IG5E*rG};| zB*0Lvsov5#a`$loD03OppVFo8@fUd|JE@wJa$*=3!@f5D0tUTmoCqTWy5OQ`bfsd(#C;+x+!L0-PLsCYq z16m?J+d-ZO1JWoj%aeWFzbkHUz&1k8M~0DIsc~X-ZbLkmQx?w$8{gR=u|Bu8>D${1+U$4)@Mt3iWxIXhul;dNR{<*4jZ`>5(Tb4qm=QJWwpSdx;WV%Y z2g{e;rtqOzizC_oF#kCF{`9tUF&OHQb;Yp}!uEp(4&c|Sc;E@t3`(IUXDEH)I=wOW z>WVr)v)%s_Q$t4qqn95qYyULGQGOOLju|{6fYaRtBNWLM?Wlc2XYe%UON}Hk`wdZ$IIEbFy!<*ncS%c)%c^1e4!20PHS1bY{8{6HpXn*UEs_k?X z#*fGz1OxFex2B#0e@z%4!OPFxu*oKe*Cmp~4|7GZCBzvCnmoi*Cuddw!7J8~A^$ST zOU7|i9TB`F5=Wr2K-ma$^PG53PhgVfUA*O&U+x{cww0>ys(20i}HhB-?Ro zr=IfCG>F!R_clRXdI>RS(85Jhqa4~?k!6J!7PTE%(=5CbXsF0;h@^r-psK~-(6z*Y)mLMfbOt7z{dtl*^ zM3owQUOwr-U|o;`JPz`6eatngDSmr@bC~aIlT8QUU6iw@5=~wZBr^~Ollnf3fhyyI zVphD3<~jTEuk46aNa?7f^TRxaa)cHJld%<|=U71z3(n;3Pe(n$~Bg?D(uDlwB zFBCWdutSJ{jviaC!^tXAc%Z@YlaD;ruXaE^`R`UHCn0nMcXN|7J;Bv=%`rJ*VhX^> zxjZCSX8j(Q;;Q2(s&)wmiIsr~Hs>Gu&K~I99`b53_*2@KxrqO2c!T%2!{J8-;r|zm zV*>JlEU%2q^a9Uj#Gm$AknbrQ`5e5LY=N!@RSl3O=M}?ueo`}sL7V&8VchtJxW%zU z-(~)r@W{CbKMq9$6-L0zNn~)vhc$WV7^iO=(e^YW0H92B9if5%y1u0pP~;E89ae*+ zd%!=ad4cf27v!CIb<%buxb%Gna$oL(^WrH6^}mu}X74fOrkP$a4Xp6d&i;)<-_lM2 z8KyIn{P=;11_?KbdJBO2;G5KByz7eRHsI#yLsNAC_}Z=5cgxk9Fa5&uO@%2}p(CgA zA21JSuDW|yY%CoCIi1ahv;}Q=w1GAzBqmM;06Ip0S)uEVSX(fI9Y6~NJwU3e&!2(^6`F&iyJk)pR5_9wg;^J% z7R@cAgY^^>ylYFb{cB?d5nu9jv#@eQI3r*JlA+O-SCvfT(7+~lEBsvfoqg{H*V&si z4v^GwN4X20nrY6m)4zCR?!a4EzNLZ(pQ73@-3+c zo$pyAva)9Y>m*3~wxXm}e}4hakg?C-ZxcBd8EcNbY=y}-eIzaeiU5t{?P9(gwJwO7 zP~_+5NgL&vkrxR=_^}~^2v!QPtJ)dU_J11yJ`Lmp5pu|iye;<4=i3nNJam&$ z26W)|v~4hO4KD-+@A4>rc;{tDVqn^enbpxwGC(z1v3YPcmglXc0RyoHP=?HS#4^_Yv5NBtf9_42T0MSxLVMX(;o_l6 z9TR73xNUOAmpq9Hg)0OFP*@HV@k%q+?cI7B)U749{N8$z403gzuN5^}bWR2WwPF7GpN0v>&C-F9gK{r!|Aiw*^c-Bc|36?$uq3r@qNuV|(~D_XBBnN%tV zawJSd?x(3g8_h*eOxYePLXSe-qlEog`HKJ-tP*1eczF z13=}^g_n8pK`rC~L4fn(7-fK1n@6S`35bhl$6ZxT-s%Wv%AOq$ z?-sGb{t@pys7?C@&|ZJH;=!AH3!aw&emBY7PGZ$w2!QXmY9<8>Qgp=XJ5*B@Td=O4 z09rf%ATMSdh@LEU25NrFA^4AKPfPwL4$+upEf*Z3q&$9Cc>vwDjWusq2i!#*5OTAeHVtnJqP3Dl!V8;^5ld$##IZqy#Nat}Ann-p= z>}UHk{=L&$z#u*PSQ|~4D)SKqBMZvHjsy3dl10fhe^k3G^LK{DK;ptW=Ju1UGu*ia zXVRpVq4ZodqPP7Y`9g(PPLuziqI^Ek>@izzrs1k%0HC2Rdj)p1 zq4wLT5QQO|Qo@uWi;g%1x;-8`wW(HBh)MB#zLETQVMtMfp8cxi?`1Crr)S{-M9O)i zmh||oSQ`>^L`}M5h#pY=`3p1wU%$jm^1R6g!9de@?d>0DaFE9XtyXt+u4<@W&xj^* zUrx)bsgThF;32*VfSLeixmD2N%#M1cd%I<}{Zc14|9`X?&v3g2pj9LpSmMB2h|~m% zaZxWVL%LeG{kmzjv2^R74pUD?8gbKN=KSL&Q|;E-Uekn|k#lY+7V=SR;J2?wlU zSxL9vi&D6i;r!J|E5)YISbCHioH!2#yGHBb;Y%k9q~n4P7jU;LF-9V5&rzD5`{ou@$gJ#CtjW;^m9R*uqgco zDwx4#T-YQ*?A0a5M1gjy2$xhnx?Un~u`0JJWTQLhw#eDe zf`1}1VJPR7`uw;9f1@PYoZ2f?ij>BKK*3@u7>ld4T^i?19f|LY) z$x&+s8_r`3xK!cS(l^q12`FM{q|U%bY3l|Ns!JU32j~L^PozO1 z%^z=(;r=FMzQ<#y*$lKu>6DYw9kPB%J<1+8{@hantLO_ z-^z5hpZ~9wMjR$|eK(;;UBnJk|0)e!o4Ilyn}0IM3=#JgE=>#6*4bv-wby z{0OcK`^saYW#%x4#ZGrvk^R(1>f`KThh(#&_$07UdYj>s9V~RNyK+QeS z#|Hnr!BGMoK~%j}Jm=&9nl7H~ol6h+D?3kPDbxN*))q!IR?`VM#h0oz71K zJ)j80rW4awb&4q(x}@`4ar!g-eSyj`Fm(}1QELGLuwXrcN+Wt|9|IC;MXk$fI=Rs} z780|Fzc+g5+}-~L`9h;R0KBg?cD!-*IRT@jX^{U?J25fD-YuzuHOf;2zjX;jx_bQd zJJ5{}Q*PQx`LBbCpq5>`?$;ibD*mSj36!H^zrm+=SZG>^3fMqx|dt&9<(hd`p=qk+UUcI1iR?(D)`5f|QV3yU`arY~Ry z#%At67*3C*M|GA2xQYn+g<1zbvJMRK^Z1+tz^~v$b;b;2N#tbZ;cB(j=T1a&gVQ8N zX^;LlnGiq)D$}QdZ*lgtq}WX3PO6{o_nm?EN2yFQU zlRxPMG@96)1|Z;<-5Xc=$~&L9>SPhH+tjir+}ucWTwuUa7}043CNvYJpvzQ0Z{Pe} z3(vJ{V&25J%*R(URdtQP$nfm|iKqMsktKI~o91<)0FQ2w{qcUAw~S-VqR%iV_Tcpf zgYK^DyL&O#H3`9}0a>ZHZ<>@dYp0^iGk)-r8=;#6nq(;UdV>s|UHLKz89{nx64zP( z(xf#44`;n{l@b;L2#$|l-rbn-f+Mu*ic292q%>!t!T{6Bw}%P92s2Cr!TNO~H}0Tu zUvRYtE|xp%-3sUwcIzP3h6%kn9glnkKw)h3Rr7+v#nX}<^V!jkloO3g#MX0L{*V0j zpgx-f51}%kiw!SpftBqpw5WVDUiP8W_2`M5QrBEXyB@;@L^+lLJoEk$THOrrR76Jw z6Ea6~q5$EbK~@DI7+|UdHyZ#KyYFoDl$HdSpMf@BiI?iglb7ABaE0SC!V*&kSNBt0 ztr%(S7Ikg(=l}NDFyzp{(Rd!)^iNek1|Et4V|eQ!6eJyO4t)tHI@iMOjLnAU*T9Sy z9?2o~12wb&`|*^BRb%Nly8{5d%BEAbBQQBbPVwi?fM+YuFsz*c66hlPyXlI7@?d77 z;-V_z@;mhkxc@_t?gpT~WG7+)$mi>>E3YYnW^=;4GVmikOWeSnQlvBU*KPL(+#SyY zIiAnlP{t#KJAZdq#_YF+s2i#yBpbtW@vyN)cm`7kH|0}v3&ULnomBNIO`g`f%83pI z#W+h~r}aHFp40;aGeR}XucL!yfPoVa?BKa8@SS1H^0y`pWxhBs52FSJ(cWu#sIo5m zBw30}tme+SJ1|F}=fkH0Wh}@#7jH!a{*#XZ-_lJNN{6Ke2C3v`8MeJMtd3AZCpl0k zK6D`j+rM3*`W!RqrlLpX&TSSMm4JjNvzkGNCM*nE3YEqHG9Ajuc2xQK27#ICoOxYRiWDn+)lBFhT^O-{#fu&By`E4h`&1ZyNTC0*n0tBhlDL zdAk{&4|u;qXsfbAJC-aSKX$Nnuvx%@?!IyZ_Ihv92#+ps_)re<51}zA*bY2atrZB3 zeUX4gh6J*2 z>1T}RUj(_oeg16~gf{0OWTWHFLkxDNb$Mq5b6pVTe^ z_sz>T^wjFQg^hl;^#y^bmcAHK`#heD@P4v5qfzMr&OblFr#=l&MvvQvE0zHxx?KjCpr%MTC48Fv2R%VA^hC|EGp$O^m*jwLjqKcx;|Jj zEgNaU>X9sAuukD{z(*PaQgwDllwWmx*oeCQCi9Gtn(E0 z?bx;i85qX}?f}~4>1UHYt2{f^MpI-IuqD88;za8;?ywok)6-@IDPxBb*hGG@SE)?H7J^sh} zG?s;YYHZpZ%kV1$D?Kv*f%uc|4d^*g%E>C_tnWBHrw04(sd?rYSWF=XAKRlr&w8Dd zb3iq|R7|>IiKioruMg|*m{fEr6&0ZXKBK9cyi%w(noIQT6jn)8i zoFoqd2x@Ut$2nBF@cvcG@F}O~In`MI0i<75?odovqtMs}L*l^2r@K5Eob+^Op)aMV z;7uVV?oO3FUxJ3eN5GH<`?mfOT0?ygtz8K_(9^O%N4?F`K88TK_D4y`Cg}YGp1Az& zXa3m+!y7dZP8F{YDeNp^LjcZlz5xW^Dn3;Q=Mmz{Xs)uSo9e>P!~|PC z?D5XP5qig>QQY_Yf$kz?2n1n&%AfNofKFMBAI57V-ExCjKjS z5ya=6<#O~9JbGiL6(e^!dyQ%ZbFEu`KdDGHfdl@8aF*eT%GV6br6*FmPOY@jA|}5A z!EBu<<-%gj1_sW~KU6`7se@Z&qqT8-BL27W2~(zgCq(LkPX`)YZZqolPP zH0`J7cg#w==DcG7C{)3l==Tow#QH{hDQr0vcRpDV&pUMnSxTIYD`<5E&Iuv62c1Fh zz@#4;-ZNW|wH&%pJ`v?xQo{??S1G;$LgVfhNeua~^y)fCb+>H1!ruFv3ic=)h=3WR z907X-*)wxQpZmANkm^rcYI<3b+-tx zm@|W~uLSe`M@WrXtaLP7g-GSgQ9U`G-MgIxczGS>5@WMM2Ki6+gF7cGGM2Ao$nCMU zoVpF;Jk@OhOzO$#oh{oZVcKDmjoL=W*cx_2>7!Y$6Ge_vnL8Z;yP)964kgZ*weI_# zFC8H`uXC!ul0;s;a}kphb)wM(c#ZPc8%vm1I2;BDKlu;_N#QfWWK!wuSm1;fo9?zPs82I&03rNZrH;lC*BW+xY00w%3`?e*lJ@0LUj?eEh$fR?hwTNzaX7#PWEL&V8`>_ z%JqMcE!cbG+}VWgF3n+YxuhOZLX5xQnuQ8~W=$RdJA1xaBpWwR(I6^P zFt!C^2)Jn>{wT^SG9bWxyP(Sk?76pD(G_l$7DtfJJ9cXQZHsumZ5`%wPg~AGdT*5i zG9$TVlh1{-U)WztOvnY8o2#=bp>s)UEF&ij_UoN&_eA|+hLuAIA9b%2 z0fD6`-Q{9Uaw#bU5tU09H}ST9R;G-;e5&4FGzq`H0)+s`w?eUHP;+hrB!IIn9)yBG zyEeI$cFkzRbkcMcHC|&2u-hyoN;~ud@BE;F`Bwrq`zh+?#dp3Gs6I?ni`ZeGB^B-I zu_5gM(vzmTR87I>Z%#e*dUFxJV&Q?OQj^EkD=|^He5Kj#(~KMc+L!J zqW8T5Svh)}Re0Ws4hi22Z;F1D<;yw>E{|FQ)YEbDYf8lV^zSev`(LtKZm@%rEqe{| zf>7zRSFqIuLFS)1&n1r56VKBLsT+{%@$yfF9qhYJCtIXRBEVGu6Z`PiUOp}S=9iEw zO?DpVA|(PqIyl>Y`mX45e+)ea{ian2AI288lCo|n!Kj;+_V3M*w&k+FN<(jt=h2D> z(1Y80K#we)-u-X}n)?y#ZTE%N(N%! zO-L-}8t)UT(|2PBegro@5T8t@lorni(o?CF5CReky`Ag!7N?sejZc;qt|<=GnwYD{ zfSxD<`i(4k&OdD*^M&AldAh9EeFyOH9{cI@p(Jam*x!2uk}*0*?&{@#k`!30Qj1;m z`hHX=%_aqEZ@xCU#f7f}JbC*NC!|9LO`Ip!vryCmH{1zqUe0czMS*yyDybF(Ki)qI z@<2nGuX}>=O|>KpPGF>*@?<-Q?vEMh-cOtXA6463J=_N;jUGyBl5xS?_%D1g6~T{& zYIlmJOj>LP+)%@!8}!a|W>4a704-e@r+$WeSw?Yn0fY$)faQ7sZxy%akB%VyI;#zZ z!yI3Xq6+ZvYg>~Q`p24s%r{B^FwO-UNIAUKtOff?%vj9tj@HC*>40$9YrTlT*>rOQ z%AOU^^y-xK#VEuRPI`XghLjXbDoc?B>_BFOrs<&oiA3wD(iH!5@T5b<`R9&D>@ysk zag3=`EN_q{-u}r15*Xja7JE7Av+{_5TXMsAs;qQpa$2&1<`ag#OoA`ZnC#yksv3Kifi2Re_}qX2p{(Ybl(&|JAy_Ti_VC1P7Ljcb}~E6rOB63;cC3A zfwBVz^Q3}$uy}Nj>Bp~$xH43?6EgU%7D$#OlcJ`d#@!tQO0#i?T6(2F1_$#fFw8Gd zE$G_^J=?PHSzF&l?X&6xco5G(>OgU3blJ1|&gMEn$xV1AUr^HrD(o+jaD6xhpaUoj zJsS|LpnERV*Omix#9Nuyb`-D%Z}ZKUT`lDY8*%hHoieG=?T-|!A<_(Y;6&P=cjy?A z4CLtd=RMj2E7r)17+Dj?!%PFA@a=O!)LwNjIO=GY(SECZyS&K(?jI&6J^NCtz8!DM zi~KxqUP})4d)0AsHJLHWRM*u2Ve#jO57}!>{eipYxQDBTEQuQ&C~s7osX zs9@5K#Ua)$rp_h;W*G*c9P$9c%i!WJXN}YzK~<~;AO9CKY`UW+i=S)GmZiDY)siK} zD4TNyyb4tng!2vuH^COGq!Krs>~`@v+QIrcz4~xGss`ty)Fo4bxgKr=h1MYba~Vyk z0U^4xeq&b3h~90#n)CyA1Og{xR~4@U6CeJV3wnpyeXwBmrYVbXH1(HlK<3WuGa9Lf z!U-1#maO^IRKL3GUW|qA4*rRQ-gyOvg2iF|&OI731eOW_flp^D5`cY5bXSdYH~ z9u~d6;BEA06<~7`VU0o8Uoqdu|p*|M@?C!D`3sq8Y6PAkZG> zE2XP{VCC*-G(FVdB>a?&F{XnX8dRKWtHM3EOU~=5JiW;ReL&jjNOF3CwYDOh zehj`?p{ZYAv;A1FdfWG&R122{?IOI1+X!S@{S}6OyLs%>L~|45)!12Z9UH6>k*3T7 ziNAMOF*ME{hdV;^7H102URkI$ou_C*Q>7=14vqo=R3K5fFLqM~^8ADwXEe<}fH@l; zDuZVjo>WV7#|kb6C2(|M&>lbovdHxIv2z}+=e%EFwuje}-EW~23frmzvdy_zuAD6- zS(13d^yNRhYJ0teWuv?Jlpxip;IBys%}p1?;Q_vL#|S^TY>=j3*?`<}NfR4GbdBb` z>@jf%`v#Q!23fpAo1D3o12;tq$9jgxJI1j!6aZ>safh)1yY8W{tqI2f7Mvr2`DT9D zkmfRD%-KtP_O{*`JhvqW$4dYiBlL1SGAspj59{H6GY#d!n%#h7#_$Ag2U3`dD!pZI{-AavSWf?06kgdt1ol>n#U77+t zZ?dqX2V(I`GC&u#x&c=yDQ{f@fgY)nbClVgh|uJkC}fMeJ$;G91h^xtUe@&CmiVXx z_SU+|Ml7fSt{1)(;swPkw;(&5m`l!I1mxlss|30SM{A)juV5t^Z4rENP_}kk`HD9!1iDGXDAFt9QxA9Wy z;LlH5!w zPomoeCwh4)x9O7hKI@BL^CprQr{~%Zsgv;r?iOF3GC^qr-83-<^%(laT+}CY3GEpD zFc!;fEULzPp0otW9NGu~hcfVYLtvZ}g2*DB-8Y>3ue4h$L?CBq7VE*2gjd1@myY-< z7Hm6j+bMrs^@*v#a!--`A6pqn;GRh`=k34)CTDM)>|WLmWI%n;Q_hsSGqa{U4i9@w zTug%yIs4xRBU}Liev|pJuRWqyjp7>j*h%ZY2=F%!0ddrVuu?+L_xjm1 zrih`Zwt@e(ZKWqJR{*qhK(W69Qoche$BCH4Q`&##Gb{$m#;QLR$uONYbqZ1G!m1)?b#w@?uM zspDh?Xca|W%XBn0dz|3R79=|v)ggaMlqJb}b`PA$os6*otd;f?G^mDPp?sXSSJwxH zGj!IBRR*~det0&U-zG8yh#@ns=}ciDoMVTp$ogbqLPzn$F)LxZtb@Oy{@{<6^jve%ub(}gxmdUt_0a1Vij*Vyk4 zMKCV@=628p&lL_jXYC}?R)34&m;v(p`F1kFnYO7{RPumRTU4_JTbdzT&3p+5xKwo0 zpdl|GcxoerbnnTQCZlr{mZo|ImF?I27QrVqqXArN6O5uqRmwO5)BB{t#z!iXfjfK$ zPw{&4vf#;(8bMWd3bK_vyKmfS|lD(EW4oU+rrgo};o48{uF;fBV z5I>aPM=vMWYwb)0^*D#}%)@6vWa5)5w%r_Wv~HZ*AzXX4pQ=x;cKgu+Xc6K8MPf5( z5KIlUS{Lor>Ky)Xg)l*r5JtDeoBUh@hqVIDcyF!wmjS1>Ky0V^KdLJ7HjxxxY_h5yK$p%3 z{$Y_$obF7+(D#daTHb+dwnmeBVf zBERgneRL=hb{o?L9r9TPS(_>}GGMd@aHeD2RYRV=B9nYIVB>gQ6=p#KDlEGgR!5tj zPh%$tGWab#K9$L%kdte_ouNo+TQ-#iO2-GmsNw!deKv3WQAKp8-Pvm`Gc%&y&*MgU zRyV%}b-Q>GwtcPepNt=aE@NE4O%x&ZfELrvO2IW=(rjM=0nbdP!!rh{B`FsaA^YE= z0M~(lAH4X<$yPAEdw`?@sU6X8D2V6O8j`J~c}|oT&tG~Lna?fs>6&Mwi;$}TTza4r zwL3b(x}j})5pmXiDbwaaBzpG89{r5^bPYUWp`fBYM7 zgzWzu+py9Ami6?}9v9#hk#$0~*S*65>Qh86D&udheTS(xe^grDBA$}gc&4IPmf~7r zy`)Y6!Q6*ZeA?gnTuUEWL6c{Zi0P)uUIg}Rpp|4yx~xkEL$#;6sX&XlX_)?g+RD~? zsxdLQk@2;c5)&bsWW%-{a06=TXO%;XjN)a;&^JzkP%3i9 z?Q!^V40S`Y0vaCHY-Rj}aMd@e*$Hp5CSj32`RwVr zLBway{Cui~IsYyOnYwjjgzf=KkZMmD(oqBMAS$rFnXJ^%6ueRwDlPT_7rNKL(#J1k zR}%NRFA1}U<}8@_ddW2hT3%pN3U3SqCRC9P_X;ng!I&z@k%FX+_G_5gl^FAKZ&Y%x z&sVbp)xG=I++yC$byg!6%;Z~dCuDrUQ+I2Vi??V?On_zpVsvQ?+LhWJD}u=ud~>Ka z_3FEezAXMKGjop+`Ozu@wWyel7dlM*KM?DN<|<=cHynG(EzlD|i73K2HoIR3`TXu+ zi$C)t9<}fXdt^dS59o>uzZOB6%qHakXX~K^ml|-j22GuKBpUP0{iDtGJ?1Bj%=FT4 zjDR3~{l$0yIfx>4hhNYlA3U}CKOWPK)-+9i#&8H*(Uki>*2Z!GPT#{3`44%psoBsI z(S>Or{uWmISZ(Ji;-kiEw`P+AW&3B=UUz3Lnt8b)lx^1$979G_xN=19Us1^MNgba6 zkP@?nd)J4=$5`|t<(+1D3gK~jh8`GNfzmNLmLvB9R-trxAe~;0D%<87>aT;902tZ| zL{(L}^p*?c#vri*TAEaVlInD=o}w4xc+2A4FlxenF#xJ>DfMa99k&()bQRz%;gp-5 zdl^3__K7?c4nQgkt60FdyrLy>8kSZB!qdwqS`pj5-um8MZf2Jy-yj|oLzo-BK%nCC zueP)T|8N)HnAzq2_B1MdDrnH_MJNblRLWRBmXgqn0B8pXYMg!xirGqi3YT0#s4fSj zSbFLVsxu~68>ths6_~>Z(XAX0)j(}=`aJU0k5g8-O5aZm4RDMFdgmKDP~>n0+R+#P z8i#!ouEt9Nv7RXMI6&V8-j^!KL(*R;I+iE{b-LG&U|DK^#(~}Zt^Eq}rj1lo0NUIp zJ+W8uJ?kU_^ag~sSj*HrUe$K<(|g;?6UmO&XlAelma%l?Gkrb<{p4o-)C)s2j?3ph z9zmx+h5N-TaHJLq^a+m@x7DHmcA)%-Ib()F*vreOxEO_CN7aAlFc*F6lN>G~ZfoKP z4CQvtM5CVU;3`kG90xsC;b!OW)%RO+s;Nc1R42FxEp^pbN`6?-K6A(#{O(+J5~ix1z;tD zf5~Ju_N0nf$v7zcDfU|fFi+CDz&d*Kdnt zn!sB5h)+oaA7{c&6gr#-;d_=dgGh{rSCoi_Y-lk{bUqP=F){ya^U0I1OUNt+7^%R; zWuletAFWcQ)3>lbFP*?Am&Fm#xxA(RlfR?@PN+YPK%{*i2mTf*sX3#+Nc%N@KTGK& zVwi6t0a_^p(b*N_0)ip48+!Zo0b6AwLEEVJ8?u`tfeZ;`K0vntl=u{|V`6{@0C=3! zq*lkXg=G?(YwbAg6lv!p0^8vM6*z?g;>dSV606+(1DzJM!PZRz?>Kp` zU+~SO76-RuTu$~$EQMmpb6hfC{t-Z&1#puG*PL`wd}(MKSHLH&A{5XUHp-j*DJJ1t z?0Jj=bqMzY;)nOu!8Z107l@b*f5fa`1Q$$03LK#x_vGa5F4ye8pW;yO2;VenY*V=KRKPjZKT;h*8H1`RW?}I^#~Yb=YzU8@R+``=hj~V3hL)B)7(nK zx}Un?OUC~)M!#&#!TSJDK(N21M$Z~tG0nn#0f?;CXbAZ``>Fcw@J!b91n?}0dAj@+ zV-GUKBnpA#1OU`J2in%*P2G&bcuEz^d3^-yhwmY1{By;6b{+i71^*$(e)-Y55^Zup zhwK9sdeR@Y3GCo4i=r!AsVJL72Xgu!OCO}4DNUU=hCqCP{=U!_uzWQ}}6 z18q@s4*6}3e_}DG1qtly#muZ;F5}C3G?<`CsJuc61T%*jid9c-v8BeYfxT;o)5qE! zrkXTkg4Xe*rXm?R1L)e6@D;)8XzFnHEwa-3BIQKQqnL(qHOI4R8W&9q^ z+%N*P1-ciO0nlQ$7NKQo1tO~aZq>;D(9uN)N1c<`~BMr!B7gf$ry$Db&xQSJ2(&P>> z%}yRY%C;j&2l(nU|K}N8l>d|e%sO5zW$$%ub3HI^+)Iykp9Y8C1ZU|9ufqlI7*vIr zc^uW~zuF|}iY^VcU4;kN_Fg&I0&x)tlI{CSW%95AGZwruG#S}aWT~>V(;s-=I#~i_ z1-r=)(l^@hPOQdntg2cDvU6^QnvEFa(OLj}N50?P0IKOSYDI-++~zw-XzXuFBU3@` zyjlv7lA2nfux+2r2e1Rb{!)3e4%A}?;*cPXoFM z!!-BuX+NGv`bcEYK6Fq0C<#>tb=oCp1+9?b4mB|6R|sFw8ULR=zYo$T%}GELd99%C z>iw;l1!x!Dw8kno`aM$*R}fY1v`fm?Lwj9uzQ^H6#<2<|;)L}37c0Nuqx z>EY}5kPy@H^^6;41Rh$@8X_ygfp6cX=h00x1fkObgpO9{)`|{TTDbn?8PMtE_1@SH zO!1oK2cooJ0Z=1FMB{DeYV=_D%D?ZwXXsKQxE&YWL@O_74lAmF2b>gCb(Ywm-wxi_ zTAW44k?Ak(2P=BYnj~@XZYhJb0uam4*P>KJZ$(Aj@RE8ZwhOS^*CihwZf|RbX8z^5 z1A?!QmNeDhuf62aGGZnt?Y0lRZ>@@hFzK}QGL<%D1_q+dZK;MisXy<#iB3fCraX=< z6vh-3-0i9pRTGPE1nEGJp(rk*=D2Vz{KUSt3#omSJqAGfza29oSoy#kunUu9y47Xsx@BcnLIR*$=9zuiVn zop~j12kRaPXVtb7{q~#R_{O;1mzyUdU&^*G0j}pzbEmCx24i;c7K6M@2WlGV&YPho zWTlyJFM*oKE8LmhE&{Tx0RX&+C~X8ZWlL5Xs{%~i`@ArlD}Qq*1>$DFuinK(12_Y# z>vl|c*3`eY0C(0Z$reELR*n$O^NYz3i_)Zn07*&n6xO)JqtAQ9?of35W5J_-;@#07lJ{T2E5O+EY7wr@nf2KY)e^P$`;m1UAm zLdhQ2fYN>*xcTxOSTgw6H=p2&11`RL^M7<@aa6@F>xJ99_RO$cuaycTLz-B%MxscW z2cb%C6hMw&zfC{|Yn_22Hvs56hGwt$oUy_}=2qqr0EKx002c?OGUvoEdS^-gf|hMN zea|hnx}(EwqL%0g0}8Unof~O#JS3io>)f{}&n>b=6AWJ9@kl=$@A&Jd0GZ= zh_bHJP}MI8q&7%-0qu_zHP$gbvdeXx15;w!bOu`{sC9_Wj*~6)JM`s2Dx4diw`1mRGVV0&xuqm#~-Jv=}LNnyf0r z9qaeX73)ul2#5CQ6IDc52HaftQ0_Fuppo5%-d?2B9R`Js+?VqC!Qd^9QTD9C0bFAU zKPFX00-ECQW3qwGRtTcm}goj(#8lm`M~?Q}s3CKh17y)EXsG#F(@G2YDR$9TYnF4+Ha5`URJ(iL47mR#RYUOoOeQ z5g|k!0ecZnn2}X%r|k~pZT@4C6mGs1%D1mI{7YnQ6j&VGeOxS z=pZv|K3fohkqN3QBj_mv0h1r5og7PwemmO7bU90>dd#G6Pna*C*Pcn9jnMRE2XZN5 z!0xG?$k8X+Y@}gngc)@zsmER$QyAbk2&wB_0!P6WZHEF{n0pWdR57rga@{Al&#`cJ+_8Zq44R==4Wk*6=?bjh80qd-TN}1s+x!tsv zu2{lGadUGkGFP5pES+83=nqKv09sez`bR{bH&%=Ck}y@Iqo#fV>p)+fh7qIrN&%={ z2LrgoOF)hcjVz=CbT=4-a=O>#4d)ZGGm9~lg&&lg1awsK6`xeYIY)Th@A%35VOivn zx#;hO-4FxAq+3s)1*s7^lEqE^6d02+0By>`^=i6I;dn^223%Ot;s^G0k1-vs+veKPt?Nq$Fl%1GIS#Rx`&|_1y?|> zpO5Wlj!S_-maN7VZ0%!S8kU!XVgWHS@DjDf1&hHTJvDS~1q7{YGx&RrzFlw$Y58%P zq^!8dAF<^!22BMXs$Ztrc2mW8@d|E3p%eDRop)Ke&r$#=N4jq-0H|4Ka_x2lf6XKO zjU4Wo5(%jcFw(!GQ-eOg?1`hKTIz z0Zp}RR|(#5UZKk4naWuxV{c>KEQ(zbmSA|IY|HfV2Rfwzp@6*OYva>CV;#&rPka8v9p-1F|N9wvDT23Wcx?rgrP?XNZi^X%#xRif)rs{mrNHBMoV ztnFLp0OaBHne@!2V85-Q_q{VuTJ%oU75r48{57Fdsz{lm08|25-1?A2A0e_Lv3VGc zd>Xfir~TPkrPh0zDOQ}51&a%&-`cU3b%q}4ZPQU%(xdF>0oG>)!7dOty<#hf2S^5! zVXr_T0;n|z1LtMBaVOs1TLxz5Q0;;uAh6M zA}@#J80TKh;V003iX*Q%wadFn?uid=_j#XAH{C8vb?^RR{r=Q3o> z1fFWV)-I|s6Sp9s>@i_(uyN;x14-Smr`}>NuL`b?2T~U%2%p}CZ2c^;ck{4q`$uOT zVL-+=Jo9L?o48hT2cViBW^fJFOgV(7O2u}<-v25|fS#Yq9t z4e8T@p*z=#WlYOL2aB?(MDT**31C1e11e12DXlpXzz-Hf(0>pEL<|8acUZ)FUS|RXFu*2D{ETjFLd{Hzzs;x5uq19zs`p_)oeQ z6*@Y$1G6akPz~)wMJgV#9&6UOhI!A*lcNP>-+7}`Uiz_N17_^6Ge~zHQ6H5wfuzv- zQAuVHz|FEMl!2^<+ucFp26r)q`?GkHgpMo*rnoJhMg!r+V67`1SJ^YWGPv4N2Ciyr zG$PCa3Y>15=zw%na)5HRFLk z1K?!skc;Z^4bY8T0ZdD~dY)PjqH*`TZKS<*-<~2L1pNlxp%z(z{>OUiw4!g1A<{K5 zKx+Pn#H(vu@hTyE1Hiic@#!_Q!(UM4vPKkyqfgmdve{@bHC?YS3BsJD z1pcX!s>L7dDaS}yPR=8m_1~HDbu+nJ!>c8!3iRIu1_H$QRhtF|AW}siU{d@%R%8G& z!5LZC-HFsau1`oa1sf(aMdzpgqb9B^7!9=wVH9Z=r7JOH?4kaL25AsP1OiR?+-dos z*IUI_oX=F%pE$AuS3Mnt!EPu#LJ*I*1a_2t);bU0yvlwt9&Pi+Id-{IB^~rOLAoxn z^J!Xr1CjWnSg1FsVCH(L5mNcrt1g|-Z^`EzXT>KV`RrX+04L&+es*I|>V{fr9OMR^ z794&fhTy-JyFIlUYEF6Z2kWze%bJ;+i%TW!!9KxVS*aqJsdV46wd?^Ga=K2p0pT#6 zv+GT~W{j4D-EGOZac=?U0xKqvc5q(;c#K|s2grcoaV%<-ZgKELH`MJOZylHL|5l_M zVItNd6)`$|2lojC-y-Qdh*&e>`&8~p@Ov8>_>~Ohpq_9svOaL=2lSlIF6sgM+u+ip z6Hj0=9^b$P&wmX6JHZ)oWxYluEa< z1#f<(ltvQUb0_mujxu+u2C?%~{;XBnVEb5C&7@De28bXP@5ydtM5NTSQA`HEYbcA; zFe=}0-4nW4PtN#k2WvfR8P?6iq1yb<_4mA%Z$9;+<=&}`IO6q7$Mmp)$U-%Re0o-V;5EfUZ0QjHax-C07 z&^@YuAz|08#6kUgWlwE^1locmtz;Rcz!9%w{|d{NOl$qw?dVF>~!!pkQ>QS!|HOB zMZ(uL^Wh#v@m-##B&y3M$6 zk;g6ZSq8jAyxNQ!0EV5iy|-)Q0GOP?E04Z%whyo#782t5`>fIoA+`VlFG&e?<}?=6 z1HnL+dMAVCZrAG*$oCj#Gecd`j{SFv$x*5ui6Osi1EfNkKR`7_oRT>Rp(Nev3@|=U z>zI`<8+9*lrM-d#1;o`8&R}teEV58V_T&OL7cuCWZ|PQo*M~YP`bDEw0`9cV!>Iig zlHIT^HG6uhgq*I3oJ7Y-?;MwV=aR1M0>JiX)Tk7e^Iv4@qlG$X2X41WudnyepbGve z_74671qw-H(7=PI?Q}@3Hw~6l8O@=sAl?xj@oX1#O@x4~1(y->YVb5luufMSL;pAB zX@nr4NMCJ=C0q(N3F7gaPn_Bbp`(}F^Ri|U#k?#K$ z^p0zFazbCj&@b~Psmw70Gz$1V>fTxW_eWm#y?$czLz z1pg8u)|X5ctAz@N%;5fa!|aD`WRJ=1bHl@itG?_41U8xn2cmj#Tgo0#`XZUu`W1YR zY=zFB68mC#g*#5T0GX%az`dVbGOPRCO-bxmu(Mk?fQr*73DO&tmE;cD25TcQiIURC zBCO!)M@qXmvmc652wR87RST%|sD8Ie1*iUVOOYHRjdG6G7V)NtGYn7hLq84V%;Tno zF}Cf;16;b)H)zkRev{7$W@K`}@JFYBkto#B>Q)#rJ$KbR1Cc9-V9k!b23XTJ{q3dY ziuI~Oc?ubv$u~iA_WHlY1^6c^W=q}xEzHLamYf4(s1H|y8=M#jx0_!qU3iF<1m?7f zp+WGN6O;-9CgNNQGmIwwyi(cBX%%3#KoX{h2A@KXlm3>4BV4|x)7+0pIHf?)suLLA z`XGK#%z7o>0{}5}Re4mjK3H~j{Y$fXZ-Uh*dKs=$;I2DfZ^B|N1+;_2SDm@p2))VIPF(|J- z;m*E!P^FM$!^4RB1W_}Tt*tb@EZnPRJVO9Wk<=#;_?e%r4eiwIbv%=L2Zo481UWP^ zG~)=Ai|r(!&4X-gdX3a>muEzd>@|Oc0F*sq@tNQjXXxDI3ZbeD*}PFT^(G3~K{|^l zPF}Y_)pCu<~JoLh-QCx{4{IfU9HU0QXvv z?w4w1QYoN2rGEQ&Npj@4oq$mUY>VGvKhr>j0HvF+)XMICfkg!i48_pqc2gQOu!E^; z+)#Dvf9#;W10zfoQoZlL8VE{SCUTw;n8)t;=4xX*Q7n}jM2+U;0l(1V9MU*y^ldK# zrF&TZlSoWFun>zC5?J1Gg)MAY15%IWMMjZrAbLe$c@&dpem$%+r_hy$nR=>5DqQ!+ z1VAJ}7&svid~Z(o{%osKhJr0K@*38Hl~6a)1$H{?2Xvsu{Kv!xYjE>H9RM#0Z=g>X z1}zMGm=6z+@b%i2POuH;fMgM1GB(_Y@w1W@2W{yUw@h4#J=;b_%-5pR4f+-H%qaC1-Jh`mo~B7 zDx9t8t#e=MCy;r=E*^VRyX}I_kSJSU0zuKNGkGmeYhPE83pyNji`CFrKN~}~lasaz zOGFGg1wHeMif2MS&xB2Sac6ZZ&bYicJI|=0X^*1dWhbQ-0OEy98hi?!$o>zVaSM2L z$GMo5=mB2igB4uv%kHG_o4c1D-w}C(Ox(5e0#hLylKzp?NpmXu6E^0H%&k#E>%k0-<5;0iUoJaw81G!inZgkn=B9oW{4g0v~vqHWsM2 z4w*~mg#@iGo@s&sQbf^=q2TTj(r;L*j$C0m=>9n*>;UY*-k-;gqzw9*7H)l8A0L_5Hg5?eu zcIeE5OgkJ(%?g+<39`2Ps?@Z2MYJ<+1qxZ)&!@b>Z)FNuR1t2g1 zV{1Mi_W$OGD*uy0JaK3Ccx7?NX=cE?Nf9>*2V-QrIW^SDh-#BKR2IQ+= z_FaozmN&nShux%{W6GeKG>2^V(8IX7MkEl70wu0}x|6q{0GZ}s-yVT*|43CBMq;pG zuXi6`Uwfx%CuwMv92K)hjce0&*wlZV* zx1&=pfXXYA62Fkq&}(Vg@2GUy1k}t-XF1-%0~)u^G9>^|ASzTlDpXXFC0spy|vxwb~yDElFf}PC17X)Xg zQ27X-OGNR>TP)+f1?_G|FA?qdN4_VGBVkG@6fiM|@F-(ye!Bm=1+N@<05u~Dyr84q z8n2tRKClFQXusA78z@FsuRUn%$&iE{1A~n>DTqf~I13oxHWZJ6N%v414?$T$fJyb9!X@F0Fz@%lXVw=!KpVH0o1*|ekif-0LUqT zlw3X7Onj$dO7qu#{kmGFQkvxDd569W#9JTf1Tl^wX7NXdqaMCUpA>TZzW>6ZD9na5 z?=oK?yYGWKwZjv#2S?O2!`d&7*jAb*W5;Nx%3NU3Qd979xHq=8hpvBS z1wmKpG74;;xRRNm6GKm{_$E4O&fe_+4aShAAan6*1amotbzXZx$Cu~8t|C5%GLYo1 z#D%cdd8wmDa@uBc0UEXZ=J~c*bFOkpYp!^!30y$)7E61`XN0_yE*QyI0*8R>>nJit zvF~}J>AH^D!zgO4d@{+YUCLz!YJ~EU2F1TQt2 zNKWm@1$F&TtdpRLB#FOrRLxRJcRrs6kF2NO0E9@xDYz((qY|`M2T&QB`A_|5dm==L&MP3kRN9F2siY@v z>>u~@ItK;)1Z`haMo*TCRGNT5ooel1mZ$O(plqk-YbemrnL?9>0_=brsTuiqST?FWs4-Yl^(j5AH258Qf72k$2XC&7_mCey)Qk5Gi zXfWR2K3oTU06fY!S?;=b>Dkn`$mFYk>jAqRG)7uc#@C!@V4J~`29vFg2#|}2 zLDzkDusqK*rWz(Yq22#gL_bLXPMee+2Q0Bf5$C)fuCN|HRp13jlPor%VWR`*m&*Bn zUHl)v1$F=KorM|8F0W;{npKJP42DZAs_U=4nFsNx(?1v-1mUpu6DqLhO<0;4yePm@ zICo%FA@c?g6~4KYtrSSk0kf2TasapwByfUKNvbNbqT&ByeB~&mWjA_r!*Epi1M)E; z)=|7y#h@8D*QY)BZF7nRV`o*N(hsOYXI1^H1vb8R5M~S4OxvwtaL9g~4EA|(*Y1L(8X+pMMIESfbcLEx{rnQNb1LrBrD-22bj+vy^RQ779JX1 z6G0d%U?0ycqFs-y^G^3<8*tha0WR*b=YZviaHi05hP*JF{TH4y(r2sJA-S4 z0SPD1`Q9upMg^|VMU#~vDOT+`P58sx@&`JGBt<2v0nvuz3De5_O%7$ETUmxl$I~1e z-8R#LOgy$@#6tP*1FgLZBz^0b=zN>aRe8Qt22z_9CwOF68Vd@mu#Dtk0+Ft$&R@kb z>9407`>+RRIzHC(dB}AH+rNxoQ0K~46rU;<~ey`60jNckeIpS||rXozc zt9Lg_GW8HJ>z z6qLQN;N?rm(^YZ=1DL6%-}Y3t0Rlpo1YM55UO9Rt;&1iqC_j|JRifM_1V5WMfObjk zbMwXUuTq?NF*?O49tT>NE9Q7AOgRyj0ztZlM~3ATm>{`qWXlUk7c)AKgQNif7D=4l z5fyhs1ozL6gYr+$7!fW|4N-j?JoyiI=hPwen%0UF24-RJ=aXQ!P{&Rd z{8p;J-4;z35i9 z1+z1w%l=!o1j-@fd)n&K zJA(8IPGQ6NF_)(Y=L*QQ?*B4^a2fsrY@i)tmk+Ig>ctN&k#8zI2R1Nxj+MY@UQqG!=1D3xNpIPGfR@!MJx-8#J zcKsy+g0v`s<+w{bK2vuy01s~{D=$Eo2Gy2q30N;5yjDq$ezY+-+4aqJ@pH}B1fLY0 z9c%KoT_ajZ_v>a<#irAd0zC5NQ+r(TpaQ4;2Efz!*-Bfpui97WjZV2EtrneT_vsZ; z&zkMwgZHZj0KtU*r$jS|JCuP9>9Xh2S;Ay%E<3GNwxdXz#ZZ*Iuu*K`?~={JURJz__gS+(*g z006I*^7J|Yi21}9bm$Tl!WnKeedlCGzwSzkUH#!x0kkwFaodzSn23|!bOdN4O=Oui zOc~qIdla$Qaz43Z1lArVSyrurHneDpwI6lh;Nr3y(Du&pyLQ%ON=vB1au{0ZX(tUHhLS^lLb9a`>T1d()F`a8f_FnD|#_-1W@35!8f%EVO^TTkk~yQ zY!;>62975IMhzqL)y#CU1C+Ud{N)l#F35EKF1$8=ISE}}1srtF4dmr`pCJNK0BsBI z>00mFjDwK6jElY-yJ8|wl2tpJ&>E`(=%K3B0{$*=3e~B^t@`gGD5_Jf@o_#8b-Qkk zvO5^D6wlNy0-^yPspJ3MBvG}ZsQEc*+3r&>Hp)leXetd!!2+e_28m%=qm0|;Ca!s3 z<2SwMz<*T*A@FlcQnk?apNdDP1Zd@=tc$BHI<-fbM)aHRuX{{fc z2Vf0bWP8eKs{1O@0aML~sP$nTqjvVWp>(cogZtNi2Gjn3d*sVvJy*1a$KD=^^O=r( zyK$(u7LW${QRrMN2gfoMgMxgJ#{g+U=_l==h}Bt!;5>-|Acj5kXNMp}1Y^?{pI(uy z#|nuGxyBR2~^A@j=!g{}Ao(CtFQ=1fA%%!twQfejFFZC*_B}$9Wa?lt4q) z((hI@*X?%P2mS6;bR|ZUk#{|&(k(&`d)7_kWNp}O6D=J5yIhSV1z-qpv`%Mo{XEU5 zkXC)uz0SM_t3vj(bo<5i1hZ;S1-=}ze`J4pRHY$wTpvKAABXd&_mHd3e3JC@%AE1Z z1n4w5bhe@VuWk;n4=LCb4UJY6aoCLd*vU3QfCCk=13C1%EK=;$IlN>u!DVXEwtom< z2KL%G(j5ddd(-ug0)lj`;wEj%Erq38Pa_NNl%|`tCYGLBknw=y`0Rz&$!6$}TS=C}FVX?L4 zooJ6aB}HLAp526Pf=9CP0*EBDCTE8F3=kCu)8g!h1n13%^J z@A-N)B^}gO;%C0#lE_Czb`jCMdvkbYH45Nt0?c1B7!rIglfVsx6Xl#7>j!l-N?j7& zc6+o*`E6v|27DF3{D)s^^CF!L+xPWW#Q6M@c2T*&&cE`5<>(+@0@$IQh8~I&1lw2E zWcM|%?0fH?~4Uu415ydD#4ML z2BuS|h0y2|;a`pFvuy-Ng7-V`1GeI4!E!QPK4+Jm@t0}*k|WW`uU z>?yyA`##(mBS(wYO`tWfA91?APpi9}1Q2G25{<6O0l%X?v?(>)zkSM<=QxJ&C zuzi3k1+c4!1 z9hu??-G$e%3xaD#H2ceC2P`BVKB(J4F_4v$pJlPxP>Jt<=+!AwD_0DTtJ`Qw2iJ}l z@hZ_{c%9uoNd>i}9uzJi`-1sI0RxwamC3T&2S}zoxJJSOKd=?h zJ70V}fb*!={qdIp%)wj4i~lX?1hTfJx~gez$aeXMQq-yt&2mY)aBSrs>$$eFY_o7Q z1!C56J%fAYSa`;CpyHHkSy%$ym$f%yvl9jX)0pub0`2Mj;v$UQA%XoJ+3Q_j#Jcd! z=|?~iVcDg=B-ST$0D_k{OU#Gen(NzgR%#Jz*UU3FFbPKP*^fRJk3n(u21#krV@=A= z`E_Rl6FnsKN&)cTZ0={H7}qp*p_;#X1%k*mtRjNH8Vk(&77k(S0e0j{Z({{lS~rZ$ z*6!;NPx&WQ+ZB?Fy>fAl6&|g2V>Sk9oS+a=#4_Agw!DLerunVqds=Z z)-dHIC3Usn1BGlvZ<*CzGi~dTEX=(mIA5&g+m?QXcNHRB5Zfji2CM#a;6T)8djk$h zWHjdHO@n)E7E+BogK)86ewB}S2L(O=MM@g~1Kpd$)G~m9?7goPpU z-+#Ub#$n)4jJoaK=dG|W2Svq!3DVe4(^$ZBwwS}Y+lE|3bnIU$M&A$;iR1?x0_g_| zUWweW_1dh+>SxxNnL6pbRvSg?UhcC#-b9)b1Q5qQZQuJfLhV{PWw-MQS)ucv$MRH( z1tgmrvn-}Y1n&UyoXqZ4z(a31IPVU30rkmKXPEJ8mgPqg`W))D2g0+t?w0JzEp*`U z!7`TmqZ41)PPTNa+tJ531Ak0~1|qwXG*n=_Amrl6*h@T(xh+$xFzIk%WO2=!7#|69 z0|r;LQo-^@3WgK|#0v#GVI1w&kBK`=Iwi=U5n~td0b}r{3Pk=mb9>J7D%BT3$U3iO zj_V-RKL|qp3s@&o11zL#fwILAy@4K8u8F%!LOXaEF)q-4YrBIyt(t&s16&O-Fuu25 z1-nFI3cpmsgCT#50JA@}_=fG;?U&_!0s>Z6cBK2HAM|YeUj}m0ky~AZVn%du;@kE7 z@(f8q11jdE=u}b^fV&@1u->(9L<9xbY0b5Wf-SFZSmQyU0w_2?jEtf|D~tNoB9^w| zW@3%5hj5C0%cLL1uMlmx0D)zGHF20|a7ZRz6vJ5;6R5y_b|kd01Ny+1E+>+buGF={b2@G<&|2eczf96++qCHfOZ%JaWu;hSiUvdrSpsoJhwwu%SR z1-+u{lvw6rH9_?e)d!e^Js+>2o&Zr8^UO#h0`=2K1kOb~k7+nEm0s_uBE>KF!dyi? zW6)`GouAx`PdNTc1?}Lp%W_NfO07{isOoApfen68VRM-t7{5PrJd-OkveWlZ*_q*VZMq2SbY^Tu)r)~21Rl6OKwza zi93vdstORoL*~eninEnz1DzgmWrij`JPn1{hJ)G9MfupSQ6Q17p)fMhZmmp+0s6;~ zx;$iE0TKmKhj2OvSjeciMR3P~eEukLsIfep1e0h!m$uZ;EQlnpQIo6z0&U^v7@$T< zU(^(UNkaG422+hoT^8}AE81W%K|Bke-9_6$Wy~MW7mgMMt@`t2QIu_Pl_`aAwKGzFv-hKv?B+m^-6aEn1*89c)A}k07-~; zp*=NB$hF7i#?*R(h%$7V9Mb;3wef<=z-k0M0#YNF?1#2%?G)m7vt_X%1JGiquAC<7 zu@Bn}ki<`H1#AjD*kr=XYYvs64;q~4YUpw$$feFOzaix=rzyL92J(}ZtBT+gTSHOB&th$8}7K@2Vw7$ z%Ye%N>h}@jOv_I=J-P#^jr!aTdm!i`h4VW!20RzQ>7Mt6igG2o#V;oN+t0Ei7Lv6+iFD}5tHlFgUxZd zdU2kn08ZeosO$9$D=E9sJAh+j5a#i~Qv8SqjLbMsZcM5y0EIvIbcQYZLTKEXd2?qX z8)SacY2To{{nClFB4a_d0se{rlu#OXiPUKI&Z{b0u)jb0%M4$yUpbO88^Diy1+ei^ znEyLe38LbJ>_<$dCej|wM5r9C4f|~%sB&$l#;jd~T!A z0fPk@iN;_a_aLl8eSD~n`+OE&WrvV@$qdYfnSo+100Dk}m#-e&*2-M0_Q0QK{hoH{ zZq=Qz=EGc;m%eB@IXWWF6puPfh4Kiqu1$K2M!W9Jx(LEy*rdWFrcy!0A4Q-WYt^hAJv{6Kh2RjGRcx&n|4q_ z%JyvlYlf^J0wph^SiND+2S$p}=aMK_igR|5f$FwQ0kwwNPhkXnm0xafQb11#@V=@{Gq!Qy<&!j4jyR{$|K zPe^A@B%)2ZOh`|81p*F`DZhLifvg3}GqDFo;>01m)dK*LE^+N4K7jvJ1i)3qybAzM zK(W8X4g4Y*^6;&r#NP~HgIKc^3y=L3#!}dc_y7|dztHs8s8o{i{dEZ>*ws)o?>xHT zup=vkJA*@6#Q<~!F(Q}cTQ@#$jb{5w*=`-piXQGegZw+vuYo`8R4Y0sF*k%799mz zeE@qU+r%bd-8#MHO0Tm12u*KvAamH`#U!*s&%fGn3a{N)=V{fPbT zTELp^0aFIp8cLRY`~}A9V6Yda$5+zRNr(5EPq+ryY{;TU&W?OM6jvl->IFHlmAG%3 zQ$lNuIYWCEjTgJO@TZo9LCgZf1Gv7`V?h#ojS698m!LLZ+G`$=RlbQYLv zm%k^pCBMUsFw}%k2uIS)A_WWDiw%1!VtTYegU0E(np)*W;2@%ctEQu;TffkLqXI~e z-><(KwDt83?NLJa;aq**lkO=|U!(Lh&}r;kq&56x!k2OQ}%F7%XC$waaxYp(m!xvAyaa;?Lx6Ej`{jrO1(>UnuoIt z=22F2aR#jxW!Va*1TU2WYGz7x48!8n5+At_m2&ie_;YOAH#QWW%hS=9jDC62zhydan z1!X)R@eONx=zYbX6Z8jyW)rqTrQG6)3BF94L^Eg4x|ZxPOpVvHF-@>{f??tiyG!KB~e9rE?g z=mNg15c1opkVi&#jt+|IQ$kihwcYVx#*2JDPecK1;{a_Me~X2r1!{!Qo?o~PSz3tO zx?hILJ<^6gqx|vufCcFBHR+cE@}YExWo9DVw4S~|XD5c=tJ!g;F7rH4OaaRea;FZS zej-4HSI-T1p(k^)djQ+B?7scJr{s& zevymgoV1`65Y&VI6Mzzc*#v*EgRo>M(}tLkuJ{vG17Mu>^DF7E7I2O)%e?hIWC5c@ zvsaY(M{i3t?8P4a3(JVg!zL|uYBq0F>2MC+w*v2Wp}Oa~U$@LbDBL$5?X#Ir)xOD>Ij(FCa6}(KS#*W*9vJBB@&gOL& zLj+9Xotu^vUcal9`6yu@SXlgB`N+RxJ3^Df+*HmyegeQdp;92+BF9zL_Y2I{!_gDF zhi^}AX-Ls5Jp8f7lLrz+bP@xzTjv9bo`aeWZ*6M4v6cj{Oxp_jlAYUnU<5ovJ!UWO zFBDg2F+0E^OaOjyZ`F0ieb?ctCy_hsA_7|Yjw66EjEB{_9ER>x8{oB!(vtbxG=e9^ z8MHFsPy|Gw=jRqGI2ddSc+!@9nA-%p4g1gBK=NJ+S{vPG_y?poOzsd^E)Yl%Zu2Nh zw2Q+oI7+GaXY+KHVGzV76$b}Bx0@*?{U4AeZv;&zz8E3@oIav&m$J{uFb5Z(jP!Bs-@&r8vfQd(I!R!3t1ns~ z()}orn%nZ(VF7ZF`#_uNCNZ;gFt(ypY-Ng zO&+MW!tLg>dx9v7Ee(w2Hs05P=mDr}Vr$RHv(gfXyAIo4TtXQxUaE6B<--NL$`7>h z%>xQ?M;um_YMp?PzTLIkJqQXDal!wATwgxErazHjegit^wjz~!qmp@YkC!yeF{%;m zwpFX#IY3=qoh&IedjXc2QkB0@-Gp2W?b}VQ0*eb@2O(;%e@K(@^*Pl=H2~3Ji@1Wx zppRN^o~MFx#igWE%;hy_nsd0<9L$CPNdmZ^qTwLL$thk@xmHSt&T`3>=hdP*x+}`L z@7HlC8wCBfdp7WPZ^>vBmuk`iH#t(O z8wEyHc#0R9uQeKxNYro^z6ESIJ;iUK7stW+XyA!m7%2D9i)+-RCQdA8`QLLn3jzZ? zj>8mv{8CCenv&f1uRsW?z(m|L?xM*Qvl*C)M{@D!-p5vM5gm9l%SrCjWs9t zQ)p&V^ue5*a0Ea6s-erd^;#JN!Rl+}ieZzt*{a{G_3B+x=O46XYX_Pzew$B#?$i=R z2*#99Z$_J6y1FvS-~%j$D-1{|UjaI9E&vJ&VeG1#8SjI~yKs!i;Guxsta789*%7;6 zivZX+-m$&6qGzrq1QDD1ZVA>Mdx#tD2=G61@Fw5JegYZe`P@+Gd2_W00oYj7)hp!G z8vcuu_s)AJwPVD;as?0&EsAj-n)#U1^*Ni|IK8V?f#_HRAIpOK90WC)BLOH`IjJwc zSuMwA8aLwAqKO6R`ct z9e`4a=oL->W&h;RK|2(NM)3AQwLbVBVtD@1-^=im#{xXCn2PMjFW2J0KyL`Y zIY)~^k-mD-9`*TwO=1JP3k1(^!22i+s!T_av7bC56_0Vs!gF94CM1tGhXaIt^ zsDwpeT;%Zh`&8*c1>uL;9uZ5+q5GG-Z_=n7fd`^yi}#=H_(Wmn0_x!X`KDPT!#$hV z=Bi1Ijya;lp9V9_{cA{b{->@8EY(y-I}{K2f_f6PjV?k&Hxp<#SOA55bT*iIz4~&K zG~)=;cN(`@sTjYHxve2)70-2w>jmoiygQK)jjC;{sOdU`iwjwdR@ic1?Mg$B|7A2jOKjga!5&y>!0iM#^FSTUhfR_j>JC5d=RmZ=lZ%vU6F!UjP^-IxIEvxJxl*Rdw@xHA_i5teZSw<1->LnFqZa{n`)CA3z33? zh%@h~zfLf)bOkr^hpFjE@w4;X;3$41;zIY=qWSQ6iEt3!?s5~jZvzkS-NsIy(74xn z#Ho%oPkqSe7Et0me3V|AqAxMonFg>Ec45`UtmHdd$KCr6;sSUN-Ux;ssMHc$J(143 zRRa5EOyJ$uk-JvPf>W3+=frz7uu4CbMs*F7K$>d|tO5VX%$19V!Gy?Cf73jO1amKL z%4SDd!dxD5!+B^XLngOgYgVoZ-b*n6q2#nk}ishKDOWQB%a0L|!zsje07p&h&A&PGccbSFo&cU#|=f2h=X-5m{wFGDIXhW$3K;={C zf2LaVjeWpW?HpTkFQq01x?f!N;Q$6g7)Wz2E(i~`#0}}(Sk@gagNoS)dGQDVo~eN! zn+E#hjq~{mbOfN*#r3SVK?a7quP)pWh@Ak$^wq-YlLbZ zFL9@xz_k7}JaM%GlX~cDnr>~QKEY5+!w2}NOKgX7xC8&$++%qdT-YaCeiIeG89XAK z&*C~PWCcD{7f~~@)5s|Z1KE{Pg)#nzl$+6i4m!jkPbSa_f&{#Z?E}xUlhDt!S6_qXG~P z4R?=AVxmQlPx%Ho$E(HBH_0=DfC4gZpc}5pNd`y8D6LO>kkW2-qsyRTB@_XujL{n) zi~81=q67eFcn31H^*1IJ&@V1YShMJp37^D}Q|L>T*k~gejolzy8V8oyfo7c)EFT1U z10wYyq_fZU)f0n1tJn<&R||lBFaYP~Q+|OOVVUOq9TE8zYj@1}XvD6yrnNj7p7?YV zSq85Cr>3^XF>7=7`+!i6HHkuDGlM@lZbR{eg#XMC8WQm2hduxxyB0c#Ar(B z4hy^GvyphpLvA4(HUcPX@-6+t>H7yoLP&t*(Y@lhh9c+ZO@j^}z} zBy$#1YV|MG{2qCml}9*YG5q11k8W#vKnC{j3$KEZmCGhoi2Ez_D2{9FJ|S8mOzka* zRt(cmLIB8?+a}apwH{H0Hcvz02+s9)-!>K|@HY@n4}bf{Gypd-J|ZOfNicl&ESxru z)@-C1IZwOo9|64cZ40ii(*tB3=Tyb^@R~z3nbdgsN~eoa`>V|Avz+KKFAtky zzR5O{-$yVo=mx%7Y^Jf{b|kIVJ5H>hD0i8g!=wx2rwSgG<4LVY_5k#10lr#jStW&O zm}%X}m;mxmXNJ2?0FUi*?Nqr$)CDPx-M&3k@WkHEH0(U(|Gk9bfx60gzU3ju*`j3DR;U_46L^#Zyas4}~B zdyO8{M7)Dq?ISLTJOI(Yn0}Fewo*8F-}u9*dYEng$Mzd7UXTQi>3r!>!3ET{IF+KU z2lSMU#kO2-zF^P5)rniX(#cu<6+Bf}3<4xxo$m*QocJ97*h^fk8|_LU-cEYPQVQOb zi<4(KKm_u2rCz?CP#>`9e}6CR^T)Uh3hs7{C2;X6TOetE0tJ8Q(DrnDV1w=a?GPJq zQ(CY#JDevxY6aH7*z%TCwg8@B2X_BXJ=V{ut+H^>ODC@9;Y&O!>x_gAtoeemSpiD; z*9L}Io#dH{qK2)pZ3;jG|4w*2FKXkMRL%PAQm z^aW&isIKqgcrH|xyz5eupUYC}+QK6VI3|Lbwe(zH_611cX`FCj=C1x{Xuy zm0Ds)Z@U)W&c8+3u>{+S(E=MaWhyq&5M(KPmk!N6eDT0rYN%ohhUlC$gayhL-bSy*A45{AF(7mGy3dQe>a$f&__!9)(F@M?<1E(f!!L zQg0?a-4G0)j$dQ7_J!yAN(3Jzb^8QAhi)_)3BfK{&oLyUgGNS%Kw*{h5ye5m-2lkV z{uICJHx7E?Ng(z4X92S)I--C|map7ZX$!zq$^qgnuoYgMyvEa9-=l3k5 zr2=;U(7w^kEkx59t~>QdAsheLX4x1srPGniw(%lORRJQzUUn+a1ikeL^zGvz*er2s z$gh9VgIA5+yMk{7^ak^tIiMV66Df>uD@Ir(Vp1E{Q#_jiCAnx#bCow}r3VgBa~}^9 z)!eov>j@6?0y#OVRPyU|%aCunC;s45^AT1bVmJgzZx)8tt>H^D=A;GsmTo7G?SOMeiQDxP(0(MsCF94+gc#<;NMzifV zFnE)eX3fVEU!(f#fFZ?|P&Hu~^8obl<`YFXj${gRiR9?E#>F)~ywjKp81$vy-TqzN zkpqI1t)+uv|Du|y=j{MmI$1_tx5anWC9-8iuIGlk#RHiCUzhHF))Xz$jR(1~qelH* zmObm125QRIO5PR|`~)sCb0e3SqL^Q058?N@yFy!VW(V9Q^vGh){g|hnegx&^gBWoE zJ?mxKNL0h9csLxnw*eSfUczFKqyJ?XTn5GC{kv{kf8IVvh1=E*Q4(Y4moOsJ%4pD8 zWyf{s83Yj@DIN-Xw}FhJ7eMjVsG?e{Gz|c_VTvAWc8ZpqJpkN$4Mip{7oN^nQ132i zDGj8i&zC{{7PJ$6>ntkxzX0_i5A!@SHhcM}q2>~4pHKzV9JCgY%XAs4kMc*}JpqR5 zL&i~$QbM3k9^uB7`nmu=YBGPOxo`1cjD7C=I0xRKWdn9c%+Bm*aPXpHamo<7LyFy~ zb4qrbH=0*BI0ce2MVPmiJX3bG@d&v@n7 zW;a~6&1+E(;Z6Nny@`lkb zP40T%L-Pw9*@qapOak&Xjn0~E6gw4@>_%DD5_ev&0B>pJ)5oNT@oy{P&I8e_Oa9qa z{@x^jueYhcSuK|E;qNcDeUI7wh5iy^F;Si2Qf3OKpEK9igotiGz z!Rl8439nmu{srwLyJ4&EY68!+9kS{6pL~3DQ{kywRVwxa6+O-igG?Y0^=UO}(NXN^#n zod5+klrv1zknCofVfl1VWh^&}7Q+67;&!r>foi3GP6JMw5+=-=s4d1dXOc^f>jHua z)X|V99QG)l9{Fp~&IM{31aXzO!AF=?dAUnQ@7DwR#eUvytSN4-y~-axodNTzd2WP- zf<`(66ct|44?j-3xsz3-;5*Mq1d!GB zg_}{oF9RV^{!#~h>{D{mv-a~JD7{rK+{3;pOHmd{GvJMUIR?&fDg-rAqjTxF^xsyO zCriN9$vnne3|2QlElyZng9n|ULNtL6If=1(az23Xm~q&QYTECFz_F7^^x0f>CjvmX z&x4jT5dJ&hu_ghMco7!5 zE1}p)AL`vO8q6{+1O!>jeMRTB84CD4BjLed@$0h# zy#+6y*qZ%bOczCt%kRYer%nU=#gX;%mI98TAr9^^;uJ7{kW>)}sCUjpgk=K!+c_w4Q?sMXq@^eu-o zdy75-29kk5J~5pDXdogpodwh7t0X69L4F5VZ!apdfJf8He2sV`=5I6E2k{x^yakMJ z+Dr*Cs*pa1mmOZlmuZ; zmImtJ7NZC?_5}Qc!Q5girS&zdmqH*J5IN6c*q|tV{et`Elw<1Jx&&jXM-4u?QPZ{& z-n;HzcZ-Zn2&YsS@-7+>BfOV>n*q1r+@tM#Gc?7u$F50huw%fBvAq_xM{67d!HGOc+nv(~wfhGz|ka+;t#?*aAKjy0tZ zabbFzJ)cA1#)!eG$Xvp36a{3#sI~2KEx7kFY%cM6Q}<1&X~KMdBd(o!2Osfe{sOzY zh`|fH9um$Rc{Q_OqkDuw{2a_{@jvtzOIfKXhy~J1(3B21TWLOqA2)!$niCj1^_b3x zksS-$;#?21s|4=HE`y7?(XNE3icmkp8Y0%7y<)ReBRB}z+m$0!WL&NGIi?x^#`IMy26!Cv@nn4 zQUE;)vg;YEcheW*HMFxTI-I$u`ly6|oi~*;D|;o9^af8hgy6p^3!!pF=^vfZvJs`? zZhQV|fyF@Yf+39@)|z3#vel#6epP(`;~kQKyPY3j~ODKs@aY#0SInu*s~) z@J=PJ!2%+hHE-R4nzlH2FiQ84%PI7dpa3s!qGxO!WG~Dp6K>KSDfWa!IDyji~yDWJWdL_z>Cn@O|3-b zzzJIv@M9w2bE{l%{n!_tq61Jm9DiMsIfEX(RehUZ&2mg|->A_J&-^Z-7K*!5fGawvvMp6Wu5sXqd|kpaDwb$!|t70y|;SK z#Xe@{=)rwgO+;hb#U2ypdKPBtwZG*Ybaw7Er8F#C;6w(mg;miu!P@GoqIAOn+gsFoGrq?tlYZR^0P z($|(F{9r(d=ucw;eK<5!W&^EUPW-k*i%&&IIg_N|jyynyJM>Y1FBY)rdsGL;pa(4A z=fd=(eXpf_ARbnrI)^B5FshvZeWGsl)QCn9F#rW2qpXDo=O(Q%HV~ca08-cwWYt?A zXr4gMmJuBw^9Kanja8vl%iCmuj|uYpCfQB#TO|k zd^#_X7^U7nInaOrQe_s*h7lcNXZ{XUC>kNkmwHpsWdz*!KlvO9xyV$P$Ig7a|{S>BM$O0WZZ7GH>EgM`tXI zAjz)z)deOAn*G^TdxD6zgRb=;%QUX9g zrUDV__@Q6@B62a4G)5YvH|_^}by-U%zEMexCIM?Bg+=Jltx%S2+Lw9|j%)_{kTW4R zTYQgayFpEYp#!GGF12Gd6=(pwK&X`8-mJb0TAh#W1Cc%ylYmmhSpwB$=ca=rOwX2C zpLG|-t=df|$a~G-mJP8AIJ7(>_W@izGRIxuu6rpDeC~mW9!gLpt{K@c?&swZz-Z`7 zat8f!T@NR9ZJ9E{zV-4#MeX1++CpXF9bnPLF?!z@&(lC#Yk=N)0@T` z`{}|=Q{Y%4_M~oKP6E#7jK`N0XV0erM}m~G4Zv6kD0FB19!#{zz=-OD;0F60Y08W= z5l~m?nnGzUrN(~+bJ6~iMzPVIEbQgevjD!#DgK}S&u9T!4M4ZHJ(x6%$0!%mXp0wKMV}* zJagmhvRNmAMVPGlBX=5k;-aYzQUKU60mv^&l5TY@X84$jzJhwI%RkGR080wwUQ=|} zbp-A$yLT~ckintzZR+`A7fNOmPv>MdM?L-s=UeXKg8*-^-`bhpz6UfLw7KZkY!ZxD z!zvBDQdpk{*W}4T+6Ep;aYWHWDCWLTKGBEqNqYd8zjYSZDYNr)K+%5)Ne1zc$=c4F zoLe$We_&n7CKOgQc|h$m6+qwIMdZ=F+yHngyp|oAelYEMo=Z)6GwIzF?jqkFBFNT#BDK(*=}v z;>F6r#4s%OqZMe3tCLWaGXcwtXk1`FMwp^g=mX~M8O(2Jul}nh?A3=$P!StHGsKTt zDq1&Ljcg9q#s`0V@%rroq*zZAQ(x%ei~!d9I*&7Jci&MTrHoGaG|+e1 z9swv>@|rKgv!A8wTL*-*u%K8G6@OX>lO=nx%CAK$IGh&t%J#K6?h+17@dwm`v(!w$ zT};f6^I!QypuPzaPd#T#o%@m=bKcot&IftaruRO?7o`3G!$8NJJ_kOzS-M#BEetSG zHtJ2pKm+%Yr~Vz~qxta79g>(9{pUNf1fu)B_MUc^S}OpTIt1KeMfkWW^Uns&4(!bu zhp9FO{AR~Fet!GTBzEF%-USaP(Pa`|l7nKpU53v>9`oGqbgK7)nRRtL1;qc@wF5b* z20*MUwov7eB? zbOw$`Xk>(^GQurz78Sq!$S75x3Hw~yaqKo_)14=a?*WRJm@^mf%dpeKp?X7WTkW^7 z1zl;C!q%X{4!+Akq64gPl>Tcwcgio(y&SW+X5TV?ZG3}&$5z@9I++_eZ3jiDiWas#AfhD%W)xu=X81JbP`()bf~KMJY8kO}!dun%y))rtwE?~;-vuse@p9~SUgvPHiORW? zbl0x?h@NHXzh?1v8%9_YBLTbTQZI5`P;SD+y~jn#Nfye(n_~4mP4S2QnPHlk00tf2 zQ65JDgO9ExpQkcoJ1e?>g7c&m5!W>rTxK<6@vt;xj~%Phz{xu@R_O;oojK=9LA z`32w9Dhk;$lm*FnMi}bJe;y9By`{uSP#8H!8kQ4dz?!$ini}nfnFacH0ZYS|ksFa> zWGg9-8dWaPNU$29#_PyPQTD#rK>+`clPRqLY!}SU%uE4a?*RV2a(jVP#V2;kQH5)T z$OZRk6IR88pkXE7m)AflzyTf|)3%~oz%&?14fb_BuLLfFkmGrsuj0G%fTh;dzLDjI z=^{iVE|&w^?}*%wdIh*Vk$9+EVd0Wnr$|J4X+Km1BqvzPF*rO-vbY1>AOW5KR^}+W z>=j7M6Y9AUDkyru#V7Dky??q}%2%@K;Q_-mjosfhYY18U+{G-3OW$C#$0SH|8$3ge zXYjGa{RF(D-p-(EE5Ia#Z{abfiM|>YH!#EL&Tg6yUDre+2myYbO-_6aJ)V>Qv`$IA ziTFHz$DmHqA$SHc=->}ip#f`JQF6b3D8A;}ZpbAS@6?KC0ExEY^SrhQOO*UolLEV} z)ScsX_2@Haz<&}ltvDnA`j@2my)4MFpf!S4dIb+(IC1i9v>8_}Mo0S$rcqZq;as+N zGmm*rPo|U7&H`Jbcx*-mk4>JxZJU`BJ!(XMNL5k2=+#12*Zob?f(J4bjMzcjVgchs zxDI_I%pzwic=Ki2EGZ?WfKEhHaRrkQ7#VU)5f>>Hmk8nmtdPmw^<)_5)L=t6TaELxdU*{qX75B zbxmC8HW^?v*gk$vWKwNONkSZYNY1^c)&*k~Fb!sDY5s`<25t|i;aW6WRqXAQ(zB?$ ziPoK*zXimkMkzC%V~H<_Y$mPeVif2xM(jc@Y6+r#5mw`4YXZjh-b*lM`mc^Ua}Ou0 zQ4I($skG7>v;+t#LR^<2d;zEI`E_4%HYVHKHI)Sm{wuJ~Ygqe`yKMlt+;2zXMF;C3 zF5Sv=(p*V9P+D>H3*Rl%4A>SIw5RyZvpiv+bpqgBh8~T#LE1|aNAA%c$k_EZ5mrA+r-vU^r3jEOQ!W(L5?KDOsKZnC;{}6wk1U@H&t*g zB94c>f>ccRXN1q&xK0Z3tY%b2sss3(v*3ceh-8A)SwHxES)nk1q27Es8BeCX_X4ep zrv$+DuwBou7z`hpb{-QEBu2sJ06Ms7Xbhd%ppWdh2?Im+AntQy(rt{idyQ?D4<9C< z@#S}b$akKydZkYEK?aHcg}XoS(=X8pvsER<(SK)z?LD7ANdp>+In5B>$p#`7C0$`d z=`cL|SKmUerQ!()X}n8bd?doG;pLMo<_6+4dc6}=Eof1J87tb8qLQAB@_>z3b9VZ6qsi&RQt5s1$yd)e*Q3u4fProBDdyFo4HSRRh zDG3aMDYTESQ39bG@D4rw-~%5n(C+ic+SZ9ma-3RMG#PKV5X=V)ONNgxd1?1<5diXk z-THseTRf}H#6DH>n zW0BKZ`LS!~Ed+S7AFPT(6Fh57odbqN_J(~oa96IasRwY%Zh&?J8pk(ca`d>+bI|VR zIsv^r_vIx6c`_3Bg_pIiKM0FwxjBhyKNAWl5Vr=4r2_k>tntIi+^-;D30O^rhv{cOk_cdkmm$1C4M@;()dPO*-kekW!4*uH;KK&H^cRFfkjv7shfSLX z7rDd$I{`(x$Xo+sJ2Ftr18OsjFAqJScIU`J_AQlAY5y7%-~sL#zn?QGgsH7pTb0Va zCBZTIAh7fv^VVk=7#PqSJp+Dxg>!(YL;|Hhk&r(S9md{KZ&n?m1~eDr_Zu;8S_H!Q zuBz^0@hqH9FoR{K$%3NMhNYWuP;NSrK+ZRADFxJL{9&=Ux(gb`VqL===?h?ybo)Fv zBu7m*6wUwP7zWupyn5zq56qmU1I)32v0N;~OqIo*G+(xLx8VBVG6SwfXT!6bslBR} z`MEm5?UId+AL@dU@}R`X_pLQpSp^c| zi>Flv@zxr)dm8E9@0{wjhFGl6+ZT>@4+BAneF6KjgL~OwMRCFmOtP{FXa7F=uC>=m ze#)2}NL%zfa|bYYj!_Q-p|?;U>g+9tP)?+i&WVr_UAi4EG`b%=Uk9(&NpP1rIC4^x zI)0e#e16~r&L2Vh*gY_Js_rU%o&d+eF+QXS<(|R&W^c{2qhJ91cqG_Jt*0L%ze3*g zZ2-LOEHq~MHq~Aj=~dH_OtW&Ev1bSrg)#3P(7lx;;RERGo3It!>OGe%O5%pUQdwPM zUU5nZTD<_ww0YfU%>ZGSSc!HiYuE^yL44st{HLqNRI{njWR3=8N7_yw9syojn?-nK z-V+2y%dbX8#`ZTn*Z>*n(Av6xWu-skl?8N#fPZKY^(%k532-kHPfxduQrDB#kz_{_ z@;>OU>;!Ww%up@0WxDe(snL#UyIvIY-)&M$V*$S*o2ZT=#{tM3w7Q-5d0y&_k2@V| zPL>{6E`4I!PX~opjwVN3nF9cA4|9*I^dW79fSFEI`I1PXEn72PKYdN;AK9d&E(H3nAJl|deTcj0Z)zd1Dm}$Dw$>hFkOGUr=1MCr_5iO6%T4l&d^JGB zSmnO{e`77sJ8Ivr3D>8%KKF|Z8wATgYc>4bZm%n?(<^}d!`GGWd=Dk81sNH z#suaO$Kx}4uw}f8dd<&#?2QFZxDWGpSXrB80byT%+TI+t>jySZPK=>X~C@KAAyrq4s?!N;lu&$#{qG3T=t>6e_9 z__y)%T?Hq|_WN0{?qcLSB9`+0&nWcapkf3@f1e_kh1zs(P6dn3a-d^a(RbXhQ7m|? zX2l9^%7QrsH~OKT+*>LRvIJ_pu46iJ$eSFlCPAJtCs{cmOZO@;#H~e$fAfcc4dtU z?L;1WDw?gKO9Jw05nSQ2Q{gZZ06?SSB1yWCf2}hfz!30{ezCdXMFG12FV`tTB+RiP z^WFMRZ!3+uX+AZ2;Gl}%yLY%~ng>^A{S=rN(u%%M9>r+5{a&x;NqVRDf4(>YY^@z1 z_5|25Sd;)!N(Z8JBBeoJq4~d+_mJLVARTz@N{%D7g#~a+cp`%60&fUIp3YTA+4;&{4t<8fX|!jxd-d~%NPYx-VNzE#2aQRp<^j5h{`{nzeQu8~1tX~tBm{a7v0r(NPPZhwjdV$q7o4)X@#?R} z1Nif*cbAj0kOA~@#n51ci9hdEm*o>>g9CW_I}_DA+>(fj#RApyJ_GLzzWUp;SH2%3 z{;(+4>jgQ2Yb_Jm@2nAfO(et;ivhp-c))d)E)WA;2t`ti5s$$_D5&RTn zoCNpSXRhjs!3&MfYN2+(R#rFCTD(+HB`Lw=8-JX(yahI6SvA$(E%bT0e&e682{dlf z>|(TcXg4RLS}{5bD*#eKa{N<9UxTz%5}r~J%fiiBoJ98_ZH^h~OBPlj#sFhBeZpyn zv#G4%KFPVo;dVIv$tjN*d==|69cU40J_N9|E6$s1A0*W+7TQg((^$<)WK;Z6q5RLY z+~O{MvjT4eIfL98rsW?{UJv-PlIL~yRob`a)>x7V%e$wQEeC|W0ds#@VEcLRv3z^v z)Jbwf?tD_#=p%A;nrKTnF9NRg{KC{!Ui=<&hkyRvx(8W*@%m$C-ffJdt!T7kRR^i9 zDV~t2bW`>G4kqn<|86&6V$(cH36a3Js{&44Y6frgU;_xgmb<*WYzfHot@>f$p9q|Z zA{Kv)ipz4Ha|B7E)?M*tKh_kmqbkB~E-9}=#Z`q9Bz%5o#Ls3qZvh2s2_3-^cR4kJ zU(eU&O@I?nDvI{wf0TJFDgU#zT>!itRXmb&KWK4J6na0ff6*6UX#4tg+vLl|fn6HE zHUSq?r`a8vAs8B=`y<-Ljuiy7ZxP4?e|7JhNd_MyPXs`R82)g#1j8hQfBtfmcYN|V z;-nFQCV^X3Gu@v$iU;P#jIjSUij3fS^*Tu&`n>68dE%z%LC&WtMP%1okpp!>NP;u^ zz(E;H7(m%{y%w~;v5K>O1RBT%)(=ggDhE<;{yW@mA_$iLWtN8X8<#&urm0XAnxTrW z57k`9MFTGzZ9wLW+KXK77A)9X@K0Sr$I^8mtV~nbdP`S7vH~T1l&$_(xX)uasZq@` z%mY}JQ0~fJSmsS!`IjWcAqK3LcZ~;G$F!9v8WisWHYIa$@5{Dz1kYTpuR@Rt1_d2( zFD1YKs8*q-dTSZ`wY#`a|Ej5ZMKWuiVUYWh$OhnDtjo`67|F!??PrdcMZwXim_L|B zUB6%Jpgkph#C#{>src)R4QE!#~E zTA&Oq!PXe1h5J!PG1SsLX4CEupayegVUf2`szbUMmo=CBl8)J@3d-f2o0k`~6qknr zdLlL2KQL@0|diE*~pa>n)J@0AYu5_6Rp2*1cW^Cq8S&;vr+_gXd3 zA|ObAU$P!ld|5TIrSt>OO50{H_M*$nAP2s^y>}OnPe5j0edjz$stO>5O?&ipoM+C^ zzx(i4^#|W5Q=j0uN|jrpE995S1;&f3Y)DvlojuH-7P*o`1pox~tW=2swMQ>tcgoSe z1->HbO0s3`pepY5AVBeeLAd7~M`a6%}4D0;lwufofeI{7GkK*>xgGY38Pppbn8e;O`gy8BwBgGj{m$TZhX z^+I~)kTZgjsRP8frOegoHeq`X@T`FGUb@1h|CmJu^;uc^(*QBhodCzFy`*A+k@)Wq zB~|hf0ECzlOp`VRS!l3qvX*2dF9Uv2G4|EwDtcELjvq9uD0z%;-TI~0v`j*q!1ttE zZU++gxgRovyVg-h)c02qNi#jnq$6Llx4}zR%}JGbNdi?TLvhPI(Vs6FFJbhDH5Y06 zVi56Zh*=i1(6^XxIkq)uwBuALb7t8vHDOp9hX80kh*_*U z8r9Uyq7ux8`D`U-TLDs4T%xQ!5<`7m1q8tBCF6mZbP-T4Z&z``l(MM)1L5E!v%neH z*BwQtXaFD275(q36EvVZ3X4&ugXfL31WUV1{1Dvvo97hGOaX-gDC8TJN?s?#H#VA; zlZ~$$A`N8{Oj_vaNZZP&yyk;y}gddyVbn<#Zo2e+A(T6-2wUJ+N!|SA8Z9n$&JabDr$b6l>j3; zpiGTXt0$?8UM1Po9cvvFL1Z7dtk%45)!gEN9U)c*PP^c2NLE-~nfYq=q^joZmC52pJDlmPg!Lqw2RbNIiM`T<2q`n5BASNLM8ka2^LQs#%O z6awDZjCO?wZ|>LuFaeK$GA{zK#S#pHs#E!Aq^Fc+=KwrbIf%lE(ky!Cgl25c6?W^| zufEU?FN~4LIOXp=#|E1DaNtt*UXf&YhN<{PylPT7dAF-R)@2`u4x!Nao(KDMAj5!Z zTb%R;E*}r_y=Y2za3HQAAy&eGQv~iH7XuZNT(y_myW4cNwv*%L&x+b2qXT@H8?|#G z_e$E(dj~`{h_PXEZYG#Br8?dJYU@gE`gt6}8CX9I7B8#c{0A7Vf)MF64>8kDoRk#m zNskOk@?ny^PXc8FQTboXDgY9SfbAR9S-^j7GKhQfqur%@8LF$8oHjp#*v-EMiUwoo z1dChMxFUI?I1peaXHrb!@CxqfgBnxuSu1TR2>=R3|HRvX{peXNzBM(j3eQka>vPFn zk)4-RIvsE#?FTJj93l^lQkQe%@^^#LBj8+4Xl~=Zb1ypCh*Nm6=Lh*4qQ<4>8p8A# zza5qSH;-{9ZvajPC%rX1fuC(-=mPVah?PST?@6|#*ecbSmNquK zT>|qa8%BlM)Clk@cn13=BtN zI)&BY?j^Z{CYP=KwgznS($QPSXatqW#<<~kpx@xcvj9gxxWC_BWr8x{lKwv^>Ma31 zBS{fbbcE<3Z-~Y!ryAZ#j)ump0f4b3PMCmuexL`flU{E&cm*XCdpwY<$qPF;C^EgS_B^-Ite!c4c+ z&byYQ50+*aGCoj$9jTN9-n~3>>k|VWj^ifSgSSojsWxQQ5Z$@Ss=Gdh^p8HDa)%Yy zjF<;QS{m#$op%HT$+D$%Uhn;TiN6QQc1u>VV1unl;%)*> zAleR2I5Pm`^%&wuc@^T3t?2*t^c;v-T7VXLzFE@+;i~D=8Q1`{H_rH;gMv}78m0L4 zdw|=cHiil=*Bsl*HzF%R@1avpt%^5`e4~Azi^AzqaTKX4>_k4y3`}ZAJ!YXKh1TpYSdXEWZpNTtfOJ zYvR)Q2v?B?C^eEM7pMnfDB0loV?iD>xcfhz=OI}^35WcY3-$}TpRE2yPR#}4+}mmw z&R#kC5>0IC_B~TN-vkw3=1kB7$cZpFNT~*7Gw4Z`cr=q`WZ3e$Z2NCfox*ShXz#K$ ziR=(B8p!!)Ko$Aq^#zgSw$2p5JjeZyv-leiscHUg*7{n=|cce zkF4kiLHK=zkywUs7jGAz?R&7oGll2{?){0TAO8dMLExUmvVc=(t9@rY-ox2_Ky!8X z9$xoeYThYl*?I@^B8>K5XojP=3fjb)sn@fI4!MJgWDc+OEd*s$kAntuc!hZ!^ie;HMF*Jz)r)&f*ij+PvolVixymWB`B152RF2H@k8oR0sBtvPr zb$tb}+mB@G!;aeu3W!kE5>oTq2}Rq}$Fx!4L^$q%nPmj&e_QHZioH-s!YUpedz+L4|X4qktHfRSai_f$Khls%NNF!3(gid)Nz!Of(oAbV9W?WgA;zkA<#5e_) zMX@-O|KK-)Q6l8bk+V9_b^7@*DHudrAdUe!w8nR1X(YF3#ud5xzpp#j*%znPKJ<&a z-~p<yMu~nXxT5~6u+NN8Yovp{<%DU@2dxL^KLSyM>~b^rn4@H zI_k9@G!cx$(zP%5P4pz!XUYf0%_ms{VXtquNA-6sED;>Qv_1`p0##Z}7B)V7{AUF* zB%OyFDKN(E?~f<5w=2Dmw%rIcp!LL|GxQ)T4zUIh0}jU9W}m*3eUUDpm)u!{nKjd@ zUkz0$!#vgRi4+EX2+y2~@=l|dzK>-p3McMDjA?N1kKnJzjtSdoD^LO}rsS~jha~$Xdzxn)5?r#Uuw}UP!+z8M=J}ENZAY4&i|G|2g<${cn4Dl0V`< zG1&k$P4*%`K0O81XDclnz22@(EAOq3Jw{PZ{@&782<+<-Fkzk z5TXoOBo6sOKm;-$h!#`yYjAV}`phZ*VZ;Ub@5BR&9f~6m9#Ow__|orE;eHIFALsj= zyF$h6QH24BE8&|&ux`MR6P#C5unn{+ zB_#>d=ScAHm=yyCUAKuJf4Rmsm}&b4rnI=8ZQ=mA6>T@2p_lf$zCc94I?K^XOgdF<#w-pd*JZFK zz)}WJ-dzF>l(8Gbl+_xt45dj_S)u2|T-O6ytuBd0pNj{9cPHGK_2AHS6AYAxV)3=V zRM9>BDWIHIUIW7S$dv?e!3Zp1j3Pc-ay;e@=LPEuyP*j0u&$xhdguO(R2l@h#mzLy zU}Wd+YmH3n<>KK(q00fnxz8|jSO#rk0zd$qvtB~99*?Y?qWSJu;DRxB+GqnUBB44i z=yPBwR(l0{Q@Z%YwA5?{@h6%nn(M}4<%I;(E0G;T+zhZOWU&MHv%Qob>;>8f(xTT) zf4nWRfc+aC`Wnrvev7^PgRKILrOLt-ke^2@Kz8v_C*)6c#BFCulrES&7}nxb5ia`15umf^*&|sksD2*+Y4lt{oBhidE+qIA<%X0zI{H!`TBaoA=K_DKW>kOz^s$i>_ zQ@Dj|2V@kfeL4j(T71l`;d1P;Vy}-k&Ja&0YCr>zJb@}0^%5&@DfkAQQ(cmbub8qg zgjXv(vQ{aY|-Vv44uesV@wsmqT>k(5ZfBMQS zD8JGT>7oQN^Q*Rs^R7n1NOD>2#)0G{*vw7ae3p&CT*JeEFaCZI4d2x%7lf9&`ppT7qfJ$YzKn=8({ zV63w)*u{!9U>3x}II0G6n9lEHH2Va|ePuz^uk2Aqbv)HV1wjZfXdQJ@h{>|&7Xk(nc(79co?HqYV$9!oEqr+#Kh`|7s11otug4T|AAkpKD5J;& zG}}D+^Rnu;n-feuop#u-bd?~QEo!rY@1O$Af>gPVmASFHLUm_85wFqfImQd#;=U1CslEKrC!} z=C-aV-`;rHT{U1{s?#2SMkxWp%J#{`l_`Ed_}Fm6laU|myI}$}2RaID(J{Mg&4mOY z%7%!A6(t=ejBiG68En`&%=Q~B1Qi;~mU8V}A-n{Z<=qrm?|He{)ZdPoXT*L=Buya` z3OD$p+B^s>S*!udvh|^kEQhA6GZoI@8V+5_?-ebM&uD)V2xgp7;;E!E9Z@#L{#NXVKCfTFf9SZ zz~lrT_%YA^lBQ0OnA}tedxQm)S6UCdVV~2(t&Fw@EPn<54}h>rvHa*CG@-8FjNCnh zh_UuY2tcSE|5%-i;XMTqT|y&aAql8@>ZQxwx^k2VJ;NdHP)6ozarPkEP7nqya#FSp zr~eIKt(*0;Ad>ZbQAwvqV|4|rZB-yfa$NmWIosoqZ~iDwk#;yv>}akoD50>+e!=BOc4H_aD{amp(h!B<0uf8CiX?Rn*c8z9wv8#>@(onFn>__nZQ+#%NxmKtBf*B^kqg&(Wn5;!wMEQ})@V z*f3t!Zdc7~`d<*Fl1~QueH@ZASy7pgqB$w^)m$OK7X)45S zW-oNRH4taV!Oz@N2z^1f?)?EYx0`|vnIs(hkZQ!8RG?OL4Eb#wFIybsxW$_ zkQe}WT|MX*p2ifWc#+rW@Wui#hyi)rbV+=9v8StHw6=sZ8QU{j>FB3OBj%|#khuoA zvYGTy6eV?zgfC!C`Qv;KBifn@0kmbx(gJB@v^fTdf~|a>HHZ3*p1lcP>FC^0&Q)QJ zuD1tA7V}g6MXdu$n~y>#kRACZ{!Tcew5%t|xhzSE%XK3EyA1VW7)6M*-C$$xD zrS2eZXwn<3EZJvN9~XsM^`+9WXo&^xDsi-p#%L@z^H}|PI%QV{zM=k$k0p;|z_dvn zUseNL$E-)!Ymrwk<;bwo465g4FA&CXfMHwTO);9QiIYnDtziZ1hPgpHuLxn0%_Ir}LvLn{WhD>e~ZS6}k&?0cWFt z%aM7l&MPGF#QrfEo;ujTag_#y&eC4go@%-WT}mOSSIcb#lY`|XBxu>k%p>|spK1bS z$qurv53pEypMH)gWTy=q6l zPkpyb#l}&7Xqc~DTMZrq8s(|hT`8xGayqghT6x}&5OoK#>>dj>hE4xqxS$pyoIi#`V$#$uqNWL|1y%e zuBRp6K6K4}p{}vr{gwv(Xk$<PdC~`%OjSyNg~m%40=fR zN=Tn*txy4Cu}N$)uRa}Y-sQ(A6ZGvGM&GOjwf()jfwB)1{%8Tr6?`o#dPMi}k{myR zuK#(I`R)hNh47Rz%glm?me>IpHwt+56|y%XJ;uu*2%$QgYVQ$3^t~{8ga{Gm2+tZB><{qYe@H-(I`e)%C?N-<>Bbsu%|Kn@ zaeZbAtU*wqH~&o^b$MI5DKFmQ*M|gY3-)36?4WUVz)i-sfJl~a7YwZeud4|KG6Jq~fHL3RdM9hfO~gK*67U=k3ADf}-( zs?o(gN!In78@ddTe)0ewa~%7w|NC%-vV+ChRG@+aT{-e~Fq9?804=GHoni+gK5CRr zMyOCfvK1r|hj^@pMpi=zeXkC0SHNhTm6ita9Q1pTkzQGx=+W7W%jW#Y4@rMIAtK#( zMU589=IR768D)q&d1keUoek06BQ4lTO~yv5$x};))@pdK!p#Il12puW1EAk*LwUJ6 zt7tK-hwLByT#O6MtnvyLWqbr2HA~oR=qjD{6#bPw;@uxLlw}A&A}#cl^b#flX%qy0 z7bb%;sO)OJCu}r|kfEuQ5GV>tGpW+6s51yq!NdaH;|0lUF$>al`BI*B#K81+CM`Lt za9Q@48y!8!olpTsiE&+<%Q=p^lVe!Z2Rb_qgg-YkW=#F@2fs*`{5=B^5M1B&lU3(K zG_GGp3*vkN7P$bu&dnvL&lA9m$i)QCM}Vj)AwBfyXBX6hGY)s2OUO8vz{AG%%x#T4 z|3(5yz%Y>N4K@W-K(igSG=q6VOUj8OU8;Jpk&V^%JL&{>zDM*^IzszkFjQCCc`~&a ze+Y*8UXo3w9%Vq}vB?KlYd}Xd&1-g}7h-L)`Gp|6Kwz0H6ixLD{Uty=!jc2pK)bKy z@0c?Pa*J>M0Vafsc`4R!>@}OkZX{na>!h?aIsszi)Q4ohnnpA2*$xaA&+pKaZ6 zh-oK>w>|~O{Y(mMT>!;|%{}s0@WYF`DRYcJzfJ`8?B##27$9W8SU+y(Po!4%(~h> zCpBhyzc+Wa-uVGo1y$V$qp9`1p6fuKxP2>-vHfRl1dEuMmQzCJ-lqoG8S7x)k4%Oj zxerKR`xHzty^$s@@iX#TRH4PR8At&}P?go7XGtBF%_Cxjl_CmP#$A&|fs1K^=bXsx z_wEKqO2$2LX7`R>v!F=^5e`9Rm>12UQFCbDta`Mi$Y0c`N5 z6+=fkkv9cQ6!QkU$-Mb)Ta2s5R1k&D_1*o!w3@8;=`=!en_C7tu?!lnye&`O5d6*H zB7)p&BEAbZMOP8`twVy-OQ!%u1!Dh8uh<-KBU5qW=kAZnTD!#U@fl@uDtY}H5hDN! zWfbE_rv{6ENWBtCkJx%!zZtUvcAJ1!^?c-Q-{}M+tWD`z7qO2flk~{IU2_D|GUolE zcI^Z)yKS+|p0foyx@!i(Uk0cY)e=^a(LYt;AH}B)iaHYb&_G?_CCCFpgR#&# ztsYsC9_OJK>3*=!6dj_VP{Ew#CQAZAbhK2O6}eWTr^a^}9QkVWa>~5G<+M6W0cC01 z7F_^M=RU$xVQvJ2`~#ooTli{4kDC$^Lmxuww2orTB>nC?ZP|VjDbqwddujQur=Y)->43hPzK(A#eFiTh^M{C8+6cc&OgLV!8qj zY~R9IJC(fpITKMSP%A8gzoo8vqU~3iTl+4>WEcU3m>E&J4$XO8L(-(#QmIHtY^X2P zD(-94qccCn;CciB?bvHZQJ2-1q>?L{0=&5W$qZ}>F~ArkWeer4>OcXB>6=qv-Jc-dC15h#hZyt9Oz@a+0!M z#MA}LW^?@R3$nS9xxdQfwj6jahg9^;um^{UJfsA*n@R%*glbMxmO4D+CK9&OXuZtv(wS>RWYh7h1`G#LCDA7IYI?X7*`8lOS2a6fW!#(1>mig35~(rpia*I zicDytRkQ}d{zm^ws3(L)cbgB#SUf+cf_BzknF2gf7}OYZl}e~C=pE?!i5yF^~rhaNxDat%cIrZ;&qb>pcDsS zoL|1>fBT{uzO&z7VWWX()inEv4l*2EuC1o3rso8H&k(Mq4RamC;hA$(J4bCyazgb` zqYnR!A(iVHFqi=kcqsFhmXWz-EOqkM%MZbJW_RCW4(<3pFFFUs&WQsZ$q;F27ZcR9 z{WBwJkyx^9tA;=BH4ze8TPIE;7R&`3qB%4u$@Y(}s7-N7?h-t|8L!XcZDx9u7A>gC zOX~yyO`Y1PrBQ@mKg}EyVw<~6SrECT&p|07o6OYUkb$z%jg)6o2cD!+sKdMV=r?vp*m?FMXb5GfAt;-_o zxB}kNoXj4&14WTbN|JTcw*dv}?hHha@6GIGDlubYi=XmwvX`;6%^zKXe>`33>c$6| z)THNlQUxj|EuDQ8)ZHio4MV=FZ~xwCIhe7=X2Jy8w&M~3*YPo#C>0@I|F|KHnq4e4 z`jDsx#-D6L^!o&=|CSO|P8;^wcx19QzuSeJ!%=^L3FOtye$FfOI33H01LUd zvLvR#DooXh*7t8$K=s$4dfJ@68L9`4&i!Mg7z=-X^MY2Wf>g&r#yG6|<3lw*YKnVw zLb(TrE@8Zmaya7^Gd7iRgj{~vH*WlIJg75Z4~W2p7mTAYaHwwcn$ z0Y!dx#R39`OnakrHj#|$EiaT3qcN`HAtIj}5)Cm#51agG$1MTeKrW=N^7B)Cs60;g zbypH=H<*fg9+rjq6}A!qmevGe@ojkrnL7t==^^6qSGHV{b89n>$A}{hCVU`t9JmT=sTlC zM~nFSra$(Tw=1|O`!wD30p&9L0@bDACSC%z{P#u0{6o@N^nd93iP|MuiY&so?y>9g z_*Lm!J|Y1;YUaqFCa)Cf=sVO5REC$bmQ-x=mJM7q>40OiXQc&Kv{5jU^%jY`wD<&7 zRFsH@J?(w|Gv`nos|n*lDdzw_%={9&82b|-*q!o*E*mLGV5<0j4(71wqzH67nrX5R$y2ANms;Aw8m z?3GJtFbyE@k1uw{JOqgf)k-qrW+n+@5 zRgeI1Hm23xns@5~aL@zjEs!{PkEW0=P`u5KoMqo6c76q@E}y0S;rc1OkaKp=4N(&Y z?x4hV-U49Q@aPH=c>&l(1WKV=iQ`9-nLP5A;Gnd z1r+JfG*1=&f29XkiB~;H0+F=}7^Q$2BV%=*d%gM@ipIlHzKG<)6ZHdH0Ju5a%#J!V zGZcgz0Dy5KaQI^uq(P%>c){_9G3*2_N!_qkaYRG^)kNX}7!vJBHiAzJ!VWor;hJA7{-t~Z$af;TdN zf{?^u_GpI^l2b}G-)00zrB7|jgWrqO7G^EF;_urhhJBssDRPqNQU4!>1DXc6$tIAF zTYN6sNMGoVi!)bO!0?W$`-eN9YM7(cgL?+XHptO8bXyp@>9L;wOQ>|qBslEhMz;_c zgX$jc-Dn0ZX{0qz{*GE0ta82y+;zDb2)iVh^H#F!IQhj(q#*)G8-XD*!#n9Jm^@;3 zLJ#Qxv$1#r4eeLtM*-raa?1lUPJz06-vFhn>ZKbG>uxBOS8~2jm@X6YF2o4L!ovm( z>6CSIo-M%s@$w+}?;u1+S~UKtOtAYWgdhe4l(_famqaO}U5=8tf{R!nVtfUhSG*p3N_strr2)xLq!6tn zx0OaL;6N!v!;%U}7TyMY*80x625-u|W;7x|XS;+`O2|ZTWM-n})85Yl*1G~tpdyg< zDFj0F0F)^I1(rjz*#rJ}6v+~20|O&-Nl^hYrZ-qKMiQ6Bd**kl*^Bj$-8j>);<<2W zwD~BsJp=~tMF)NE`=L@OT4W9L)GoThaa&YMTVWovaSaeyuqpyNo3G<ol{EdNE(YqMo>&4Fb_tnl5-r*z zcUl=_*XFjkpmF{o2%nxQ;u`<$dm#pFyrpDd`L&s6f!n7{T=F>C+-5c{y6}y!Eb|LN zhOGtq$i2s=s<$jm)`MqZ60ofN} zx9|DFbt^8SP)+8WLs*_!mN7G#HR;ALopY;ZZZ%RlyeDQ*@D-&I4@I1t#O8kIRcg*-8%v= zjVwaJ+D1-ygXi!mt>B0d`I55Kq*g~B5 z%m5oP)vbT;+j0jT#&P@UK#t1-8QKAt2H07iL;>BV{&aOMQ?AzBZV`He-qdgBF8n+C zPpt$Oa{Jkp(KX$8SCH35KIjyz+XomZQ!1O?!mOf@8^i}#qV9}gO}E3L&96Rb41v`Z zYi_DO+2)tEz27@l&?^BPxblWJoZrwM#PLHxt9<0?!uk>fzmYS0vZ%KTLRExp?W z)^?yHL5c(9jd2~a65(|#bkI?1yl+*awrC@)*EpiJR0*5P_;v*@Z!~f8i6ov{-Fn7| zx7f47-#0=x%&4O-xC^=K_JIZz%X}5ks#gfo?8D4~{HdIHDDMsFITQddItA+*FL`k93k2IleWy4}00~CXm6LFA<8$Mi*)E%I6 z!r1@{oXP`QF`E`W4C!6T+`{+_$U~ad)4wJdvszwquViD`io_xBuH)jA(XX#yUz5MB)RTO*@9-63o0vlknvY zi^i>?S0B8BpLKsYJL%iI>W&Bc$gRmZC92yd?JX!&&FuQ{#kYVEDG1UEX>Z<-ljaA% z;o@cO!HQsSn0|!yAcHgH8?mRf-92ujc8S=ID^0V&WH8cOWB%7In#KYiN9mBKPFy08 z0gb0FME^~ug`<|7^u~OH^W@7cBQ67~cnC8-Lo=uyT5V((tUgXZuc<`v-g-j>hVL0Y zSONekdhG;~fBZZ7)nvtw>59_)_7N)(_eHjQH6GDCo+Af*Z4N9y)?pf___crzBexJc zNSeaU;ke_b*k(b!*USJTdh9Eda!11gu@-i^HA)>I)hRw6a~|dC9&S(a$VUO1`JJxR zoyf}}+ZpACUekV>WXOoU9P|S>^~Cytc;f}B(P?#qZ=zKjF#_N^0m~3`drqrH+sJPF zW|`H=pp6DaSOSK8z>^d?E=XOSa)gns+!CmU!|7|nYx4KTFk zO?0_hZVu9M4o5Jm+(OUvphW?umhE+Us!!FR9KKEvm`WyljjiXQqH>sASH@E4HoXR^ z%*U!rMS7t~RM0!9A^d}V&r(jnZ5JO8&&H7ETVVxqJ?PVTT{~p}&Uy4n8ND&$Y z)i?%clXpCxb*>28G3*6iJmBEdAx;!cIV)A#v2j;8*%Jr%8=&0-Xb*BDp z{$Yjns%+Ar9J>fRC*TJ3Lvh1OBB*9Qfm|*IhC?W)&?>ifjlLky7v{#3tRw@P1FP5H zjgwd+9w&UpPX*q31JX$VocrBJ)B+@+4rFB_tP*IOpS`2279l_Q8lP|`)GMh~!vh5NAP*jP ze9OK4f_LD{^4Rh(byp{P6SW1H4!Mn}YJ&k%1g&G&OcP&>VAUU>?4?|mP^u3q?j+GM zflgUhW5xxpf!8$8o<*cGJN&|mVN%&^L623;Zr^1x{lBOYj}QXA8f4${BR179`E2lv z4Lr1)TemG6=<@*%M>TyHZ5acexkSh>pXH#AM5{6_H)OH@B|O1##@)Puh#C&h&65K! zfH&Hi?Ohn7LVM#$;8j^fU8t?kf&^a~jN{?P+JFYhSuHWOLtrUbC!7O3SMZPx?9rs$ z-a%6nr}*t8-RTFL=2^^{JFsz*!5emJUeuG^2$x3?(`eHly`I}wOCtmN`DYI~`i}6n zXfNZD!-yXX{E+d6tgTxEc`o7k4TJ#liwx>ontFK}Y=Ws6UEDwtZS`{2I}k5ust7FF z0dxZU@Xu>Ywy$FPnA>7EK?uu`aW28j6NxuO%!1@!O-=)BqmszL(cs{M#6aD+@)JBc z1ey)lMp>SpB;Hf)@ul(MCaMy!MD(bEDm?>@w|_#u zn*gPik9&JiRq-XOALgS2nqV_LmgX};NE!l0Jh4nEJF~XC2MZc`(F2kA-O$yi&9clq zO)edjaA>B~2pIUPqP(}3u zpuI0qzYCIL9IF8Da=c}(9s)MLTl&u=okjiw`k^li8S#8rQrJ6*Yn=e=YHH`K5FoAA zSYx1*UF!|J8urHH&6acGhP_{4_eBRadJzlhfJ&8jeE^huFKG%+-U+dq;)AQ)aEQ4& z8yEz>)p^?Z)ciXjrFr``PMJ^2sGO7ZvCciR?9hbvL~90{_Bzv9Tna2xTD@q(Rx8y& zq{4;1z2+H90DoO$<-rFTe$kr2L2UB>!!d^N{}-nbJYGu4@!g*fc7%lNgM$RdoOMD} zI9Xsj7$rx{f*{^~5yO`1`roz;>Ix*VJoN=%U$p+|z40Y97%tRn@~aXiky@R&bysPr z=9m&}D=Yv4qM_sX2$E)pQEezj+E`hoDTb$i`U27Ks{A*|6*2~HKelnInvH_SV5~R= zJxRQ?HA`NRbGXO8fg1f+{5i`aTFPXAlzp% zjsICZT5xi3f3gMJ4u+)5#nO30=7|V2AUD?{VpVWaN=`Tbe+1bz$0r02G6DtH2n3>a zM=&NhGj27A#HF6y* zCH@4&>=2c6k`@tLoAzc4{FDiNZg{GYw(H6XYo#}~Oh^Q_sLNfZ#-My+<8J-v|71b! zeR&ELU!JX7V{29+)FJ_$12M!~+qWI!FG6_!10sXakQduX0k}^FltgSI9_s0&IlLX1_K*cQ`I-r1J|%0Y#-y%VTzkT_p>O@hlHE{X zLg6qip&bXs`NM%YlxTWaL8K=UTQals0C?j#xF?#*eR&el<&Fe2SsQ_capFx$bot9D zSH(_3r*-_yv9#Vh=~(~**$@EM^+o1jUfUeg-kI=J>@6u^79;{WKyb80ZvonZc`pD| zHP$)};7~l`6;r!GZJTXc=o$PA3?ZU`(HhZH)13!>_B0}6A+m(cb7Xy;igjtm*ke?@ zST*K`oF*n{xOWF|OVmXd)?y#(r){@{iE@`~s%Tm~X)_HbEe6=;2J8a-4&IH5G-{sG z3>H}^0vH0kA?;P7N67>S}|B*`&PNgkMD`+p(00Wc^PS^B00(FYoAMt}5 z@_GP%cpSi?oFd%u#5ZAcbMa#~tBcRdk|=KQ{H;VADXj({U!E+KS4Y|9#$eQD}_z?sHRPVzdGhJS|BT z{?`}uMfwV(45z-ek0o@`x}*Bifg?-R34{kzPD=h&%N)vRql|DCFIltCrms7czmABo zfM}RS9UBDPNwn@U6GN5Zr^6kQM=VcCo#LZJWfw&40 zBV2D*5K8#AMre$on=fWmF^>f11@|kFDt+hD$$kmey%*qOY#+@+C4eo>Vk|obJ%ywO6I$X8@^M}ujQx|N@_`d-$Xk@lth`{UFLm5YaVv(_}thBVE z20#^J`Fu@8;wuMf?=h-EnH*e+rK_qjdwk?U7^I8xEU*NpZh*=*@M5YN4Uq+b~(PgWM|2fn-yP` z`y2vDGcsHP7uy~W0=5FdCIc7+FJrw*#p%dM!FGM;p-lvmO?nm=fAS@E1YZNAZW;?a zaXT2D3j6^9lQi>r!n55Ke{UX~5(cYAgs221KyiS#4jpu{-HQ8WRofn~)?l!I5SCKm30cMK6sAqe;FVI8Ts^fJcnZ5{zf z`@@b3Qp+?z>qmrHkr+4URo>ct@$b~hK^>)OsRsb_QU63oE5-yN>?h5td0WF=x--vo zvRKZ6X*nsD{dWKYkP>Wo)wU^yzsQ>TEYk(=J;DqHc&!FhX%2T4Wm5s=JhBOb>ZG(Z zN=_t1BPH^biBF*dqRJxzpHiVqP00n3EI50AfXi6iASXmBF$a|okI2Ti*+aeEp_uVR zucrcXuke>lZ&8LG!IuKOz!Q$vyqmSiwoTbm@nl-`n@Ix%z>Ze$JzU*O=O*w*!5PV_ zgHrH90HPu#XjxXz7Z!Wc-r6euL!_O6vuUlqobj8e zFswFs95Di_&%42+!Eb54@%ONT-3T}e>OXFCMfo+9Qj3A*?sx}TR9Q@2XIX;gKBoEn zw%D>P)@{9K@UxrL!m;@0DmDS1##cBABTSZ%6~^HeJ>7*2P7an`n%(7*n?_{-a&iW? zzUt9MmEicqr>m8R3={%`#;e66B}C#k3Ldks7dHkC$&s*P$vr+m#kp>6%jkGR-FP7N zy;IORtxj!%USj}shv2s6-ti}@=B2w*I@cXfilbYwPblaIHwiKfhSUMqdI$U1IyBJ= zAs$R`8jR4e^9WneR7<;qYXMPNK=*y8A4y<(Pb z9OVS+#Aot<84IFD`~qU==K@WP?~3>4mzXaz1d*?Ls#*qyXz5B&1oNbd24~UnYBVh1 z`zvyGhH`r*aYjj&OXUVWwWb2c(WXpl)Q>PNOU8GVsp>oiI5v}4m2M^&2HydYd-vmT z`M&mf)o7thV4QLmJqOWYGAiBvoa`dgvjqeyc;0j`n}kw{auUqBjz6VOZ<*(Jqh@+8 z7N3#?UibqP)^y|J^4K_$Kk`ZlDe$778!oxIDw^_$u;HGzd7A}l4jpf#Gm=G^lHH_@f7)0~;m+eSqzL zzBD`4Ob<19pdrKm{Y2t@UDVrmuZ9JgLM93Ww6lkqMN5sLKh%9S-g#d)w|pqg*z@KI ze%S>(t?u`LXPc>%1M;hH?3zC+-!#CHuguMB?Il@zo`VN2CXrPWvH6lR^rnK+*9rym zdk>5nj>jLN;h>9rI$Q=*vDc2laC)=OI!D>J*t4sx4ppOXk!QkxB;dn#Qnv>i3Pe9J zDZM8iOT6T^xBuDObNlD#6=b=1Pg!JiYSZ$-EbPh|u0 z!i_~UNUV)zE=9kAX$m~?0^8Bpgi-1Bh6bi?d(Q^|QC;+6|0}XX=<^Q{3_<~Svf{Gl z=IFgYKQA!)AMFGW?>p9;Mh2fdO8&a+m~VwX@m$)Xhx^*(I&(PUZ}bOwDR+KYHLBqi zJpF~a0C(mN1c%$#r0QL?(Fq*62a*FEa-f8GK<5-Zl`l!RsqS99bDLI!lI)oxY;FS( zB-#gM6R?YPqbfr6!SNjg8D19{-N#DSs?dTT;in9y1nU5qimiCWNBdO;__tv#7b>2dTiJft~Z}wb(8Z`IrUoIXs9h z%d|&ueNlN8X@j0Qp=~W=9tXJ#$sKnl_=p8zg@VJ1QzSR9-c8OPQfe*yyqEhY@U5ElZ3nqzS92#aM%XTo_$;cnYmvS`yV z7XbvYNb#hyp!Mw$0HXtVWrAx+7EJh5?lTINNMd%HrO^WJk;C$)f)iFFF0$Rw!QSn# zD|d*RZl4RaCF%nepgI6012twNT;e5ghx+>2M_^E_aA!Sh7G&5Ou;k}3-s%Enmu}8} z-nr_8zD76KcD@oD6G=p{Nh;e5lgy+!f^!1dCUly-lIwtwocpS(!kZ3B!iaXR!r!9Z z$#C;!SRMl9q&)R1bBmtE{GPFyBxWb%RRkYqF4(S5I7l{v_U8luf~eghAyA$ke8Rog z(w|EZpa!2U0U{lj%J#3hb`YUd*3rj6k5m5hz;)T_yTu+`9E%1bslgM-!ZK?&XK96}M2WKJHv{1Q zGh!$vdW>zy+a_o)-NtX$;JPiFal_J zOUUlW4#*T$&0wNXI{pI#Vpl(yt`wT_N)WZSI8C7k;@KP8vJ9{>OpWu~G} zgsm;&ft&ZoTQEv+Gfld->f)$QSDEk;PU!?vqMrnDY&8=d22YVN$Y*KRSI>x5aXf+z z#XFc4yZHuRnIreweiF!+6F-eW>@#>jMT=L1Q^F~o3SV=e-*W@IsS7zM=(H)XPp)BR0FGC8;^xg5(SU13d!_*q z&!H4W2JjA%Zb-aT|9W}N=nkyWdDmO919s1C@{|Nf@QTR&mA!H|mC)mx8OTo(#xC4o z8l${V`*~Z)%;5q9Txh#lU-Eg$Y(25HPlP{@soLgwk#-?bc71H+9f<}$osaoehg_?g zor1|b@wx@{@E}!OYB?b7Ymd2qOT+^Oro}fIkw-<{Srf}qbmsyBv>42vz&ZYIJCZmz zO=$y_p4BIch}Fq+J!`Wgy@Hmj9>G4eRLbd}?jNWVK+vpyQyRGW8J1nJ| z7i%!@GBEXrPyk6lw!iaZXKJ z+SQ|_E183KE$Z>XJmfzLM)pPq>zNlz3NA+ttkV7jF_9AX03>_CU=1bbiQ<}Cx~L>~ zBwJiV9l^7&LJ@8O_MB_=fVKr_c8ee;B+-ra8YVh>|!4B=4mG1-V zbwMS;vk2*>a^}DmO4YIjw?A3&aIf$ORW{^y#?T*MWX7_ROYO=5DGjFG2ks>Zx;{@U zNgE*paTgBufZ<#dNhQZo)X+1# zZNp<+O|ljM%brn(Q8hW=O#tmD80BMju0@Ql60A#Qau((=AF3V#FMiwkyh4!Fks`;4 zY67+V33z4>TULUB$h=O}BwhajJ~*Ar6_k1y&SykNHD^$Y!IAZ{Wse|eNWR1BIHrC9 zV=vRYUYD0h{)X2GA4Dwq!zAD%)0h`{E58U1nr)q!4E-2jZf;|{I|FmadAJ93>Z z3y7k8GU;h+Uxot*r80xuVf#D>65{*}Kxl*0<~5Nxnf1Y;jxK$<&J&3TeuhjcbmlUl zheRR;w@C|_HV}50$GZn4WG)2vY~%m~`=?bUziM9&0F0ej(n-oDMmVWG?=~{cce%_& z@BNSl9}BD-^~1 zQ=d0NYk43~%H6()wOi)_zV9q48@ILsCk*&Aw{>*t-7-db=kt!H;o3r=xr3JnGs{r+ zFidA1P9fkuLz_cTBD2!mj3hcGg|Z|i=mg3E8raw!yL^tLx7gV#m56_A&xmP*vXJ5# z7)PFKmc4cd!?1>|eM+A(Q<~QJSA8n>Ih1rAeowz$=s9 zmWjuvgI8V@(#vV{!ucsCm@(*v{CDOKQC@Ah)Wz(wBKR|I9P#PZ=9E4BTfY55JOgJgASkv z!!$6bIneT~k0{j#23~&Jr)ng(*LjL3qU;2S8k3$2>metuEDH?#G)oKwG!p$`zCDEg z9jC#ogV`Zy#p>174?1-g8EKUx^|Af}WMZ$s0k|#paU?N>22EKePSe5$0aiPx53nSuLC^3C`(}K2 z8dZDcE_RxD!&Of41*d!kq=M=(Ei;#UH$4C>G)47&TGgf%K1KEfwWpeqA?L>hN?xo2 zt3sKM4a{^sI0?J1`bPOsAdy9;CH{~RY3V!%K^ZjbbgGa%=lTA=MWn1h?f;XMU;&J5 zGl}cRmtye)D(7Luv zCW8AW7`ygX92I<&T9sMunZ*AF(*nxXJvXZelI5XeeE>A2y_x+b)SZE96R&~ue)KE^ zUhACN5}{-AAR~%1K}23#imOZKYK!s?FVzkf8DAL&=RY(m&vgZ4FbzwsWFs=m9<`mB!V`jVTrF3b`7*KupHN9^3c0xH zW?y}p)1yx?ciBP^y!#bV2|ZT;du($CycItGB>V6rG&gg8WVxcnVZj!uHl%tO&j61( zX#+I?3Lwc6-{sfq0Xv<;CyB4O;?Km&BpF*gUbJGDbkrCEr46xqIC9iU$nKY80!E!@ zgac9PxbJ?FN7?D*ieym$#n4Cz&Vd{|1koZcj1?;u3B{CvRng!v5ZNWx=nTC8C#7R) ztbtTT(WT@_ImQC~LRp)eDB`Me+%qw#273DhtJVsBk59YhpHb8?RL)H3xvR1;r2ES6 zCsXtf<0G#FlTTi!L+8DwgL&Gpc=)oQ@0XiT8Ro=C@LhRNL%!Vw;60t=^+;Ifs!ce+ zjt`3zPC45AD!GH1+L(-WxK~C4v@cc0t%`pq;XMwQfiUhRbU;t~EzA|07qPG+T zJtekkJ%W)X{Dx&&V`>T0a=TP~R381P!jEQKXfxFS05eYhC?XX@S&C3xE3^EtVoLmc zC{kfKaQxj3nKRJ<=WXLBE(enjcGKw`SV|gwwztRZ+W>O-jm+E?B=NTG7 zsIM*+9)8>OJd`gDf6u4A{8y069fkA*YW{xnZ!p%Bhl3+qzQ9x59nc5=$mQLNTUO_> zQ0kKaO^~{~U{TP}6Fw%lFZ!5*FkwF51yx`8gRWro(i=ch-M zWJn=J=Yl+uIttiG4PEsBZ1oHIi^QqhITwP1Z!maX$pX=&MTMLH>z~}}A-=m9t zN0Y?Iupd$m0hI1*41gVNo4hz!1}Xv(mq@MzT|AYXm;^m?&*NY_4(>mx3`bf(cD^`k zy0^=l*yn%+a<;9FxI%@4GtF2!vv8-TekyNzaXxMIOs`K-m>VYmE~<^XNq%@=_-W4z z!Qk1C?r~aYqfb`-%6`=oF!FQ)c~{k$OYto4D3Wv!g5@N)pVVO3v>hM$w0ZysnbE zO4Q>4OSCq%f*8kNuzIw(N9+ZJQ-@qlt<4IFUSjx6>#?i`EcgZfm*zfL^cKmutxfK2 zV0|7|=InLLhI2rf2rp>^L2&@+hIi1`Sc(36s1yicLaeuor?e9?C@B^on!d6HAKd?{ zY?fV>t2IUk;_BDaf2zv-`0Z&i^ksuK(Ta%%^)8;e5)jP91;ias6bP$8 ziEmV$g=iH7QIs+}Nj99a3V!2_emDgl57Lq~N7V3Tf_ikbmpkbMy7l_GMiK%UwKOnczf=#GjdB}q&`@P!6k#pXOM^fIcW^TPkuDRDgpllY4LqJNpe z-P-F7>ix@u0%6QkQ1+u_xwwU>qqIx}!{_NiaC^yHlVv-Gy2;iM%*TUzV$K-n%IE4w z$Ibl#Z;1VJK13IqbQRr+qVib@8BnW~zsj1|?9Q3}|$5P}X+jo=DwP;q&n9QQew-QAH98i>D z8t1V6Z<2RgNVey8T&vRWSFTpxDeL`)oDZ`F9+P110X$48J#pw*I`Ga5{0G56%{coz z4W?G~28%xeTLwu3_T?l_W{4uf8^wcU1$4VxdU%&Z|6janCJu4|${ZcJtOu9Lko>P& z+jYdU-3d2sx?vC}@&c>6&W`59uK(%O=s=dDpFX)5vArK2sR6KJ9cda zVB=iyNWI9XEGPy5$M->ORZHB?qKH#RBTh{Db z^1^-tU?#R!XyYA^f$*|#aJ>Q$clW$R+C)3nr9927GT1i*5m_xO4CT7{!HDL1Eku&- zAzb?V9KeD865%2Fd_|D~k>F*L#5r7VHos4vT9#$uq0Z8H6BKnhOodQNJzY0JX2>@#+ zL9q->_Qz=wk}h=tFw_rXfhb4i#W#I!JyP45nU7>KPG?~Vhiqisd~Q$xe0XHm6A1#j zNlueXO+=GrfwGx>o8yAvoks3-m8{4CqlyGhPM9Nn5WNOz&hw!7D0`u31bCVXZ>!p< zI(01sme=_fLI8>iV3!oCWO5m22{KIrQ{=$c{H}V4Un0Q=Vhz0XoKiha7x?bjlL1(S z{8f0T=W=pNE6Wc)1clrPZ$(iok4{+Xdp+w55J4s0hSR3QXx}kS?!C$0kw7Vj8>yKsfpvhR@#}gSHKu-$VKy zTN5J_0t&Eg$&v%tLwr5c+|R zj)Oe~(dF^R{UXmB2>V<+6wwJSCOs5{`Li^A&blPadr63(+(jU z>u{$|uK_3Y^xADLp^4`K{8N9%R5SzX!gq2L>7{y7+A7#oQL!o!7+1K!J)%Sd!_bRo zif(FumO9NSDtV=vpaqX0b#E0tQqqg4uF8Q2nEU$*4-5`S3=V?E*2wV;$1x^@IfZCU zGtiz_@9!=GYX_lV(RYMN6E@%@XV}X>dCf=Zv@<_)jOx2)M{C#y{Ef1dj=w*$E-%vn zfatIgkP(dd!WsS)nDVLl%)AQ+7@N8$5pCyb8o~Pn&&kTLA8*4&hqqm8z?tjjBu%CO zh=g?%O83fuwe>UDtKp=cGS4CoF(ct=4Eh*+M*$uJnzjH?GpRKmh~3Sf8XdlvD|`}Z za?BrFyrvPIm@dT!{#fgP{ZS-2<$dh}63~2$YPc(NETLe^PWm$=!gZU_Nu7&0}e&Sc6{6h+<(K&58=CE7s7z*R|@J{T)Eg}NGAeh z?%K!D*nVyWS~ZMNAhEZ}@%Aes0OA5y6B0j4!igx&I>9@^{n>s7AK~YVY-Khn{};2Y za4oXY@g`cVW5v%XulTw{{96wP1 zKEiJwyjhv4Andtzrb5!2E*nL_#gSxMK|BH!7)S{KV46TRel`yAf=%_ZqLTLKtPD*8 zyxo;XUULWk&ChfPx*-qKAC#1upI=RHw@1q$D1A~JoHY5{KHy|>8fyLrky0foeyy=C z%PgT^L~<~MB=-*QF)vX`@7nM?<)h#S-Tt-~_lJJQ+Z{pf)oY9(&9{Y~^(!})w#g~C z^Q^}QKNFIx*Go?R&ue;KD#i7~pbUfQtA8CRu|!n|kOWQvB^|;k!6QY&8nUtL3uoQn zL1tzX$wI~u2u6Oqs}X<&5vEeC{U;9-%5An|u>ZBU4l-e%K`Q)eOlr>UEv$A2-B_21 z$x6<5IDmlmCyOhg>Kqp`C;jH=|W=~TBj<5lnJah8}+Y5qO zf)$Z_0C^7V%lz1FRr#?vb}hySFa<~r<)*heQQD&yQM{EJwJFIRruA_ai4^84W`be` z%D_t+VWth$zH8=~wNeMC8H-6=7>(;w-G&_lzpab{D>}Mt6wCwa=)iq5J12##<<$0+ zkhl7}rxMigw&gDd_WP!~M%0+9JVs?!R6qww%w^1NNXB&hbPApUnkH%kFR8xk0jap} zrb{l8dn)~0CehW03s43FQe=pI9;;3VLEs*kk11Hz9KNGVNJiQA5eWfM_8Mx;v;m*# zmaW7Bx{$J3r^4h31KpLe#|_Pwy9{ToIbjh5DwAL#=TZPmffCGyS8r;WW+K9U4u8OavQ>ZSP+moA~lajbVJfF z>pRRZ7~bH1Fq*Cc_kPl5toxFD@6GEc(lxTQNh$DvMKv|ug#W#)(V2q-p5NKPh%f;7 z&d#PLGKw|)5=8*kYiabZt^j`)7o~s!FIG&Djs}7!{EStNuvy!SP_%;6;bL@9CY{cD zShC9k9%euks2-SO(CHHp%xfxRShAx}BmJlkHh)hS!8;`fu8rvl0sYa>Zla_eL#riU z$sJ8;aI1D&SlAEKoRydXExmprs9o-zKP!g0$wGJ{4A}oP}qq6(PIhf^7tiD zj1E0%pVfJq^iXC7Q^URyXX5bmE`Gh~ec4XbY(nZ}Sz{4mHeok~!k!=pOSx(g(sB(A zn?UFRd{l0yX(wVTWs6yCeUo+NDNeBi>aq?UfFq3KQ^w_k#n6{uYzi|+_3r$eYhW%8 z*`i7YIvgt*h+&^NeaP%5yL+xw?9vdjvJ`YToFQ`4GBTk85UWM*jxVG@@@JUuUzE- zjdYSo$_(8jO3&OX(GQNfMd<0e&EtmkaeBM2WX5p@+_Uk~fO{($))O0nNwZSEG zllo-_$=Fg(^##k1=4$-JAx2GlOjEZ8e^$(7isv%bYlNPL4eM_YD5)6kuXcv$*aP=x z(1&&dGvai`n&)5{m%x7jI5Khn{aqTK0~A8b*P4+5bY3>VYJ>&wBwS|cX)RaY_z1y7r>NJ85TZ{LD=4yzO=@+YcfB3KmWkr^GdOkWpdOt+_Wg^F`MNg*M1T?IK z7Cc`E62tKTbfcWLLi>($+tXsCL3S#a+0qGpW*YmCR5BNZq(_?pmN}oFSyFpD*`#ZW zG1;dRy=l@wTV-u6gjBH&Panbnb)B>iar*xBvE#{p9z-7w4fwgcCmu=3wX4M%f)WS> zR1;q*Fef+_Qn5-vqPv^e2*gY`XrE8L=QpkOf!q86?LdvJ&Ji$c-Hn`dY;(>M&^Num z;op6L*fkQ{B#5;FL}l#1U|f>9*LAv5!BD^3AVcloK<0!xw2X;NsxiR#D&s2TysuN7g1F%} zSxUL7doj%h3IZz34;Fk|kj74Z|J$dlOc#rLB$rapXqoCH7)$H}gpY^pCLjNm3R7F^ zf1LGyM(v1Bvy784TOqQIDoQ#BwDF}kXdBwat-hxFCt+KPe!@mG==JPq=x!P2YWLd* zwj4`Cx3d*T} zS(>K@b|>a(EzdlxgZch;1j0z~ySj%2>R7Cp$uR2~L~xraq|>71KB7Gz#TNiZ>;Yg`~{Vo0|=% zzDC$eH|vboU=(d-a8P~+NDdw8+rT+zs{0pHLS?=DISZ|qFQsLnFm{cr7*)y;KoPsH@)2TLKe?I)_iD@Jh`qZlmK8Pf z0pW!~`->GPsLvq@7)38^IqcU1zAK?&P+bPYitr;hWryS0POXZ!_~zZB_66*rWIX!> zfZ#p`Yi`V1dH5t0LlRF1-)j%kkDsSXp|T&JsAW*_N8oO{ zV@w?y*`vj?Qn2y_T4O=X%|Pk>tG%fF*^G2GvRBT%AoaU|T?rq_mvQa|sJB-NWEFdu zzLcSZW%c*+E!Z>bX0~_fmJLo+Y)`ZUqg(tzdQq+bXR9+8PNz%DcPR70uuK0gE5rcN zoIlM5)0v`qwCy1xS@_4@3EwvfO!^`TQpnx|6ST~Q>UdoR?doO4TfWA7$pPvK*A|Y> zEv^TyYC_=?a#>wkIB`h=PuYHe4P8D77m0MKUsmCyQaW@NXq`Hp)jj0iY_O34>XC2? za8al-XH|pjG*m8aj@G;knx@@MWx*CukV7!p_Vrdlu z7-(|B5FCC&5N5KQy%Q*fp9d6mxEV1a zI7L>j7V#*<7UDRLfqO%#YZm1KWgyG^Njb}t4n-qDP;2r1xqoi@cka5RF5o-)m7XgH za=ecsltfGCcJGoTvjQQ|mA?jVh(-hV3{mx!I6)f$;(bTF_D~6Q*_+Yqd{;mdG{6dm z+yLKB3Iw(kDGq}*;VcQpJZUg3BcGz7?Y!?s5SEHk? zoECipxkVX-_8H+*7dJ^qC0N%BrXwk4TH_|d+JjC}X+9VR*kZE@eBs4qVPAMMzgc=^ zZ1z&uIf9In#YKK7ezB7SRd0x(?{!Snrz&=f8$D%uQ;Gn$ODr4eVw!Ba)fICCXE$q7 zA?SPYMTCg(HpHIr5PMIs@UyJ)N^27*IX-m*Bb#Z$0@fgfv;i4|87gWe9Xk6AUhAo; z+xuhyi{=*qx&Ltz&Ar`QTbQvUZFT?5&8PD>V{8C6ms%j;f~L&@QA(;ZCYDv0!9j0a ze0|-mk~#5JvS9>6&eVz+jX5X-6scbH6m>RK`Qm0g`Y9-7i5OG;`fDKT4knZcYh&e3KhE9N!oKOiF>4`Cn6n z^YSo};wj;TloVI@LZQtNDjBZl1tX9JH2V{$NA^Zpf|Ms>!H+5HC2P6U^;Qo@KkEEo z*%|}_ltg*Tb$Xw}VhcSy1Juw4l;uF|%je8sqQ>FxOa|2u+gw`S&eM`66Hp z#b&yu#jlXAz99aO3w1 zX;E(i@q&muP|9BWy>3`z_y9?)>g;;dxA9svN6rkW8(B^Q3^d*@Dz1V;BzTR6k4bk9 zL458Y>-@h~(ja?i1vW1j+l3aVb8(>C_m*vR7T8J+m(TQ?EJ%a9Ta4 z?XO044Mb_ezJRJD2Z}X-3?p;BwZLJ{Ts+~{^%ip zO3}M;esWZCSz>YPmtOjL4vXXi=1zM=-*QV7KrQfTEw>&#?*%&4&jn~>bb0pI>x^Fq zUbIgU(tf|K*}y0Z*DM=8PXE{%E=bCztIj3<5lGqtYI!H;d^+ik;WP_*i|)ShH5{%= zL~*o!qZ~g{r^`eGB~M&POuL6r?TD#{&959=BQF<(7`X=XrCirÙ#E4Guf6mB&O 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

vN;R$7h3KPr?W%eri5X3#l`(^G*`C8 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

U%^`2* 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%0DMCznS;{B|WVag0ln-axppxLr(`D z@m3+IV*Dr!N>JMW)uai!jhB1Az#Xt4c*bt+w$cWp4p>-f99b1dfa`vX&8z8tQJNgW z+$d1j%VkdAWBUfPAI4{YHES)eEt`#-Di`Q&DRf7_yMdJAE}ia(9IyaQ$j@5@Bk-oN znm>h0hH~BKIX7{UP_`RzO_y{M{OALdetL{BkQc<2pQri?P?o&tRZ(x=X~|Nv)3&Xy zr=kG2!6ig>#0y8TmO^-4fyyRcb;KuN@73|`PdPCrA3_Ai1lW+CPMj9Z805{L;6P2U z@2K>|R-OQZqmsJH(|rIr$nBBIcI4`EgUjG^fuo<)0!z6ppr)zQUq*NDfSw1{F-hH3 zcIHeMVq#Q6r3>-mWlyZoZ5D~M5EUI#NaqB*4vwDvL}Vn*eH2>24ueVUXMGDalD`C4 z38P4ytET~s89?G{!Ec6zsEGfRv*ZDhtKQ8nnQo86sy=N=NDTrVbp;iWbR0)*LE#M2 zbEZwFzWoqm6soos+ri^WA*KK{vo82o@n&nPDhn*1c`e!$oDL@PVyNLwc^?jC*i8Y^ zd1B==-HP_^7)HPt%1DQPjQs)C5zEfcHRXfW{4qn~Run{OJg(y_MyE#HIx8k!S%h3rJWD zE4BDgaZ3_&$k8f7;U8|5+G3}FUK9d40!#|eP4et6UWj=TdDd~|5lvp=CML-|qHgkS zpeF%M#pfk(;AJ@yyz4t00t^?qq7@?X<1NmZ*JI8^#G?cG?vHyUy=#|KWph}k>Jj_} z8*lI~xi>+hCf&&oS!vJ$BCVtvlUo3M3LM<$$yk)5n2jC&&kEj~~I| zFDYzd##6Mk3q){#K; zcj@n$@gV_6P(f9GW4ZVarSJ{)B8Y^53kgc7Tt*5kDCbE6>)HWwQJ(c}E|ws#dsz>U z3rM>G4LU~EU)K`{RmJ9lc1Q-+Ik)v^SF(6VqrVpo*5vw4CF%~fo`~5D+qjLn7CZtN z`_*!7TUPcFOX~r|q8&R%=z1q^h>Us9PQZOz7ri33#G2!0}k9kaT(V*?i@$~<2||iq*^<>COZem%{<_c za#pxiq6xj_?9O!ifs%=o(;&N*T*I*(q(=px_F3FAb`tXrs}h~%_kIx1`@PkmPrEjK zxB=CLYoG`5w3}p)wl~08NP8qU9uZ2f<-yW@qpgM{j(Ooba=8YCH71}Z7gqtihdO&t z?cUX?FoI8d(qEBCLFaB?V4wmgc2CiWpS3i&<~A^23m1Sf+G(i(HKd10X4tUovy_*5zwoMf%AZiAnGH$H^Rw#!R zr-QyY@Yv0r+jIu$r%ng&p9q5Cl3U#)WgI*L2@;b;hik&gfQcQ(OCao#;@JoOJVs+H zyqZXJr*UBv;-mel>YMKwJc1KJ1I##i*d0?l0&?lCt(b^l5xKVH7K3y$+M zSXa7PUHJisiLXLDA+{pm?&{%Vkrf(v;N)dG3%m$Twp+{abWH_*zookjwT5Z3PXDzG z9s;NKm`CVfq??s zhZR?{giMpxHZ)7Z)%q5rUdhlkMYS6|4aEnF$h}vj!E5A~(k`szA&r=G8u$b@iGk4udDRhG zWRQAINSQbrVL$`_sIco#e;GdqZF_9tG>*swi(B6Kk+N&;~T2xh3XRH z<2nMj*@ve6+)r4F{UX;k_{uh&Ub&Ms5To22LRMsFCocqSp~18o!kBF-fViXZfx;n)Cw+r}MZ0@g=Ne;iJo z5STmLbbbU(LBzB;@mO1}h&tw9$2A_j&-ZAGf+xwzzcte{m}r_(}kREM~?h* z?D&cBohD}5w9FggK->Tcz0o)i3m6CK-+x_p;ZWP4k{XDmwZN{_f5~1H8Ho&kqDtLj z%tZmaT?X}WNL#pr*XUJDj~QEn5R&Ft;YpzSqJ@o{+K2_8Q3Z)8uu@;xkgoJZ71fkn z;zHqvc&!xX**5JGx3LE_@JRg=UHcK=?zf> zfYp`!Z(Gv-nfQ+*IWs`p$8UMp+7jygaD4+eM^;FJs4H>j&Q+d8$i#?%Ci4U^x8yU7 z1Xixi5hwsS27-Cl6p2ag*vr1KQ_0~7i=|C17tB)tFF?@0hU0k_?ML|lo_YSJWyHGx zJOk+Gdh)|0Vb}iL2K59&V`BqZ^4x0yn;EGkl~Ur?5p7~2G4q^Y;ee-`P5DL#i3Dw0 zZR_&{Aj#q|fHKG`)HAlb=h>Z7qg+y*&-N;qt0Hq*mN_>A#kj2;NKKGOz4;*H#-pcZ zqx=?p_VAe5*e|x+1n9#9|3Zv*1b07Y%$}P~@xjn{pPKixQop?*+%3pSf$FpYw-%|l zrBA<_;ag45ljz;;qzJdF=`P>%?G@0x8$7cB;=uS;1d`l#USf3@W(G3=Z+4D8+Cc;H zY`3ZgGQa8tniwvh?(*wxTdIRl;N*8!=t=`h=AnJ#fZM6;d8Xa~#ig0HT=7ZIxN|dE zUInWb|9FN*XUeag4b=pIlH?i(4q(Uv0Ui`rFjcbCcTNoD#?on^JE95}$veONlBEs+ z`cyZ`RvT)qR5waAdRA};24G<5#fWX=xAe_B0BLRn84L3Yf!QhJ{o+4oSA+$Ma>@*nb@z28$~#OHmf09M!=G2YhS8K&;7qdm99> zjUWDf-yL;>tx5!SI*$X$>8*JI98P|;_Lc{CN~vn5r7HN(;R-#bCN#MQ#zHbIz*P4J(ZD>_=Mro< zfTnT;;seB=z%53sE7p9(s>`{ezx(k8_UtlDWc>Cc#@=jU+LO79c;1bQ@2fGRtA7TB z(3o)mH$`vx1ZcQ9?@m>`h#m=k)5*PbvN!Yl+Y>u)_;WZ*;%qslfOh_^H%o`Pq7$a)L{E=AzB z$3rpjTn?65QlFO&>My?yp1QiJSmuukXXO(I&keyoq|PI-KUWD~$kXDcN8{0p%i~1N z=1?xu?;*SekUz@4oNNuS$S8byABDEO#GegD%jLJUUdg?AC8tpV-WUD%q?jn#6ES=m z-^2T&VNZde#a0^R-GYqwx+=*4#%-At#BUtmHGOWHMUD6fXUZIvlBB89-*LZvgAw=u zSxKt#GFT~2CCO!K{JGAFZ@`S1$DgyO#yqeyf`YaLY2lFB{^i|P3-W*k1_`Ns)@3<9 z)FpdG<-IMfy!vtg5xmaL>w6A@F6AYmvNWWb9OY+`nE5>^2;utcm>7HjZ<-bTTOj~D z`h-|1F+fgWw&kD5LZ$JQm-LWltWLB7*FxVb2ba6%5w5fByxHAP)#Y1_OJEAYU%T0( z{R@=`p+FG5&`k?2g!NJI0E{W#)?{=5o70`+Ht{qe0i*r^zy9Sjw6SuqcY=Q~gJ70O zYL}4-6x2&5$=?7cR{co@_%eD`YUj5hH_7&!r{VoH#dzh&Q#BmcTcLRri2XwaA|3n? z2(vtm7)oYJa+$5a{5Pgxx3Xrnzasi2Zmrt{w`^np9v}(qdY`etMrqK|DiA%3$r2C0 zpw5RSqWq)>wT!oS@>2=MKNuFl=*KmL9nsYcZLiujdkr6~1+}LFf&aL@(b!-kK6ARu zy6=Ee&HuLlPI88f;&$}91o*B6Al3||yr$_VdRooY*L7+jicj{f4zDB zQ5aWup2^!bBn`9$doxt1*I=-(`dAITXzA}4_&^W4uM_3WU>vwzU&|Z;hMFb%Q?-Mi zTHjl7hC-(HaUv>}{W(%)4Q)9^--2rdwmgE>EvcNmdk4b!{h`F`NRmPy71c21x$-&(^GoKKKvI1(SPq~0c;+9$t0wS$BZEV z1S|BX8C?Xy-7+A*!29_CU!Hq`fUyhTMqd}4?NFwAPR^0K)MW6r-fXIL&j8E@MuJq2 z;1>^-UNOTUFPLPuq|P}X1BmJ6m(&RocPA9s zM4yi~f7)GT$6!mcOuYdUQUtld1mQ`%UqC%7wx$hhi<4XNyI;Kpib znoa%%N7yL#d<(@)|2r~= zk5Hci$={RLd?@6(;6N;*C}ah*HVsfs^@s~N>ELE<+DpjSQz zV!WN-1m|K|?GIT`nFjdq-l+c&wJHkpBJ^?q?|qiDEt8I7QINuB-Ceo0plnJBZt@7+R&6{j{M}F(?RagGA}bJeDk*e z_|iHgD){S2mtSC4Fko8VUG%?ZyO}oJsfxxl`^o_VFx0&Yx|Oe8gBCP zfQb^9rO<3L$K|8|0zmy+y={auLl_qvOnw_K&v1cXdDzrMPT6#2bv;)FN&q}-NkJ(6 z3{33lMtTCbJHL6tW>(Xgj1eGGm}qzd81;&`JmX^PtRqY_4^I_VqSK4=?c@K*_@!{r ze;efhj8cq4e^tTWr`wN6e0t~>=1>&QL@QGC=oCM`-WxH4DmQ;Dx6jW%HEbuTFm6@tVnDVlYZ`plShWV8PV-Q6J|7infZ14F{W^h_g0GYO+vX;yKb) z*R2aR*#ZY9*6rv79#%WrHCA9nV{Tu7--p%qQ-Mn)eylmyxH~L8Fe^3%HOtte%&X{| zFA!>pKBQ(7xZ>7?tQ~MFv^TS;5nu8FarNmML|OHWH!rm7L8V&@TmUO5v=6bQPW0gdBuXvf+_EF{Udg6jhtu(C!qOIN$*_=lxr$c;W`MVL# zH3l_V;3^ZNofj6DN97I#ajWqNLM)#IO>$nT^d3^;uu?p2M4mlt#?C_Hv2GOuTp74g zM|QO0Y}n}~#O$twhlH8A-HHY<>P~ogBuIq8jSKn4Sofha}m)F^|o&|p+;Qamh{1ZFw^lu;nF?6n>T5+72nqcyXG^#=Q7 zxN$?IPQz;r>*P$@qvB`7!ot9K3rR-{2z%?j2zW>R0_#bczv zUNz4sO6sb1+z-hGXlnqlXh`(5b5(2(;TLSk$RFk(-izau5})s;fFb3NJy2s4~)|Y^=nBJK$ylqUyfo!O7eassp#vy))sn?{7OIEbCP_v@s5$ z?C&A~W-9_Y;<{BJBV^o1^u6}{)oi8d@N6W8MLL+Y)4me|SezMZr3G|po6{~5p!e6i z+V*kW0oyWtQd9u8lH;BNNjfFXh4-Pg*)wxRlcM`zdt$&GeDU4@#ncEnvnGwk9h={Th{ptbIY4qHBx8iD=; z!cLr}`#gtc$eM9?z*PYIk3heTMA~nXvOfP}B}T>srAA3X3R88Y7GI4#IrjTDB-EzC zS9l=&u4u9I9N^alaf2-WGNxl1dt@lIXaik^vFfBSm88+Yso+_^6KOyZuuxor3pNgQx;GKcoV`|1ZzlYHt(XyWd zflK+f*Ey$OY#=QtKLCgk5(7R{OvsrwXjD^eC$w|}Q$fW-`4rbkCmR5>#DX7!42qxN z#z6_J)9>ch5RMT6Yj?Lg*zc{4j?QK+#t-1`F4|Uc05G|9Zp8-uhI5+*iW2nxq79>( z-7RDI#TKZ>cu$BVu+)0ICu(drnc5TtdHf`8a}HB7*E9+4`yBhvv^?7fLEaH()&R}W zPNVn*Tqmg+vLzmpN8|?qo|a3cEiKYn3n`eqPJ#>lLT*S0rWQJ}qQq#;x*uwI|Aamc z#7-j(UXF<}e#8ycA!BtGXHS=JO?V?D|on$m+PKg(Zd`flg|5MU7@ z&Ed;p(OE!25ra<&bPv(50O@q;u!qpj?Hck2%5RIW3|z54-mX}b+&~JxopMU9X_XB(s)UPSfq7amKsNqj!BXmqFFoJU}n$h(*u$4>$++)-@K($ZjU5L`h zA4r~zVR@?BBz3~6tB=8iPKbm6YJFO*a#jm23r4hX00nn=(}g!dEsXN)3G!)+q(%+} zZHvMV1CF7Q7qlTgT4%lG(Th0Tz?)InqxrZPVB)0)14_?=fF3wsmlNWiry4Y8YJ~1Hn-M~eG_vCYIovE!^`}mrD97z z(2Ai4N`+4}as0e|s%GO)jlxyZd%!!*4!I(qm@Lw=q}H@tPOqwxfCiB zdZuow!!qh?=9&$)u!hxy6K;ZuJG8Xs_8Hi}^KAP{-*IGVhR#IGC{M6!A^ z1!aZh(y!zKJ)ukRJfBia#p4yFxGqovy0PuOCzbC;h`xd6l-=*rxLU z7?^80C;h_Y-9-r0hJrPfm1#06#T;&UbedsGkP{XGU&f}%3|MiW(0`>abpiU}ICf^= zHk_Nk!JDc(K{nF^0~g1D!spV#=cxp8AiP%ZUYex^x1Jg#lmsR0J`T!%vWl zOG;1y&2jj8PKBl~lOJ(KOmVU96C*$TJ1n;$Z;z&555Z>!D!V&rb4iyP*|qy!{;>3rU|45upl=AjrED0m}+Z5S7w@-(f?yx&``~? zVCi0sRjV;3(Qn;$uEBrB-&%&w?`Vd*^^HMODMy0>7;Y7AzML2DbyKa2-pTR z(~q{7_O<80F4xTjnDNF{2c5`Y9a}R9Fe?7f*_`iWf=ZdtmwoHrl!xI13vgSv{*Ls2 z-8Sa(MzSPX7kTz>pHH_bWdz<&(tkMu^zV)$OO<{wn4k{;zkNh+&hWob9659jW@K26 zeDd7|QOi)oD||9R-N>?T(8hHesCy0ynPu|G)3dIca0tjj;OdHPr(AIU7X= z3pF$b2{DEOaDzQ`HE3(U(I{9U!qp}tz2CYhON70jnaB16?o3q!7`5JRRtCgdEuwzt z8_%TJ>|{0#x$F1%A0u-(clpTvo~gnHh|S%mt?&Ke6k(`5uQCR;VFG_Cv$V`}jSyuJVZ(4hk#Wy1kR1ksUCBp)VPzkU)LLZ-;YCi^zs?yS95VOSHRjQKgl?bka$C&dxF~VXy_UWMuxEn`U?WG*q9*O_ zf&xkzT3bRaC=*b!3iJ187QRaYBwu4JlSEO9W-Udv%}f#0k&@8XkD73XK))CSa9L3R z;4D*H4awEXavnt3dMdZPiZk9EJX2HMm>~QfHynQfg-7Ea=kwQzu_L`m#N&3eqlzLL zvVt0CJKH^<`1XMWajAf(!GUO9;5W8KN+5mFN#k=WY=a1++>i0udght}K{c1880-7` z4CHFbzui)<7pJ)uWRN>#IB~m$ysbwCNu`fy0R6Bj#}>mF3p6Q>kcVccHQ>&qooAJj z>5n1=-0e_CsX;cBnDo$3uBhWc!EZe?Y@JZA3JmhLvh05cPU#Sq=d39^6t19RAVCf$ zzH(CGV=>yey@#%U94!(AMFT<`F`#GQ^lc$M>s;!z#jFo#Ypg6OgX zrZIOXM6x&eEP#^>S5cm7W0z@kR*#)9rjD!)B6Y<9ig5=Eg(>jHfdZR3{9Or6H%u4L zC*xo4`%UrF`%_E;lv`CX-MOqL>bVMJYJuq1>7J>BYkg|=>xUCw{3Qthz&9V_ft(A* z`YzmAw-3ygi;u1`f&uIQB`f+zDr&0>5s#mP|w$QyuJo^w>@IVY*6! z^m{`9(#a>)j(|xXjw3v7@8~JMQmHl0mxSdc#t7?F3%?QqMFd8F^b%#ueb>|LcxN>s zK<4>^CMbvJ_cQ-0r;u<4z(irK2j@M?I2yu9iR>;9-UQaLmWg&}n%k_P869i|RUXP& zfXX>fS2t~}wwAinNdg-0K`P0G(ix+IQAsEVD4mSl^wHT$_NCC;nbm&T+2uAVv4y86 zQoYg%I|tkc#bFxOA1X^7-xj?FdtlV}8A3BNzmLa7D`PZVEwJj%|*%heP&KMuKbd8-O5RmL&$eL_XIIZ#m zdF(G2=<5=)PwXz#ko=oE`2H%v`Ah;QUv%9*9%i=yv+uWR_m{8)Ig@B9p_Di1>x4yp z_Wll6LK=^8~>Ca65?KKTfq~&okI~$lu`x7eb6i1B#F?eVLO^6z~+nR(6OI5V0IoAvj z(7L6_S**MfUO>zJM=XQ{h!69n4x5QS>cu&HrDl0by&hWHK916_%JXnP0is?I*ei5f~J#EwmBj6gZh-h45@ohFs;b zeO>#WPSzfqn_-^<74Jk-CR3Sc+SF1Turh|m>d!ukmOLF?BNG3R69#_-_0Gpo59?^G zq#2qPEvLQ8@!mjUHwnq2gkCz3>Jb$L;@!h-RmlMQNOgcg?O>vN@G7~%AgE_!U9$22 zJ|wvU=|$vKoB0IY<3~nLtIE{=qa>7-_Q}`mTuwoBq!i_gsZ(s?o*Tf9lWLi)MBp{vm74>*`aUD z^tQG~|5)HO{BtcE@v-g~j^3S(xo;eUIU$n=^B!S@5Y`bBLDF81jZU}p$D2Ya@K~M!1uHkyPYum*>4za!oF8sqKslxKqgcVw?$o5KOV_28cN|( zWRbEAfh*pBJhk)#1sGCPNDCvYMd}qxS?A6Lg|yw%mL#D;p^4$MhK3miy<9PP$w|Nk zC%zeif+B==nqN`-E{)C;&6VspqI6LK6VIn@{t}&m6cl}y&y6q=q~~CN|3{egS4bbg z<_1~=K^=eL4Sj9SS3Ri1F8Y14kji7CaP6L5skcu54h7Kxo5h7aQP~TaVy!-zUf){a zKU|c0N!vDJCzI`iN`&|ZcRie-^3@tI6g0Suha(?|*RPILkLnK)RWoE%-GbQ!mLGh# zD9up1i-$c(*TNh=9|jJ*1MYbkoXq&2XpXoBCHu$4B6BNTz)R_}%fVPVik%OaiiHwk z&UP$f_S^X96Dd6lm~~gqb=*cLOF6Ow;O}__eBZ`njH{Nw#&OA7^?GpRWQ068X#5>C z=%?WZpxUI$L_7CQ^RgKO{0507R19W8t$7UOkSemQ*-n`?ubwZ7@hsckyMjUEHIMJH==R`g1{oB z+_$+C1{yK*E-ztDa!e6p;}Dm~iNo!7WVN0FH#QuGZe*EfjfL{F1k8H*ke9N6oI&&4 zd*XouC%`uZ?p3kS!*m2}d<++<6gcG(9v?IvO{-UW{}Tw4(HBVrCn6U9MA}^9V@+j=b38=2JKkL?%Islg4xd$aJnkq9i@kUj-+X@8H z#3fj_sODyuk$K`RDraQM(srBqUBQ6`2Qc(^qRVYZJCbw@iXKVgR2eN%Xyu+-H6jQ` zi24--PH6?lt9Y>Nxrh))I2PHg+@FJv31}q94uuTUxA9c~HD$~3FuO;z=-1Ty>u%#I zN;nz=<@6K-y=B;B*&NaVx6x*yG%4rX0d3CdQfF?7GTp-gQdUr1fvGYhtLx z#EC(?OWqIyWLCUj1`DzzffSQ!nP3DNV+-1O=gh}SKZNLhAFW*iWic=wSv>Dq%di7z zNKZ>>nN>iS@&3SHTFSv4E)4nt3|s;_eepsXIFEj3RkaGrOR}pw`=>I=;>22)19jd9 zVgUFWYG?@z#tt$Aex4xa*GGKn=X&l;qE*#sI#<^KGq<`e}hQyT(8xk}}bf56H(A%dMmb^oI=nqFlDe-G=q9urQF zQjr;YfsIC29A$)!ON_PuJItk~gt4;$he~Mj;Sj)6|G7P(8ezqf6ZRU08NTG+8QN%W zS5j~QyB^V{Ro0ij)ISc;sckX1C|$a0mcx_s0!0WHPg+I?b;3fSDRe-cL~$E^^>60}FZs*rsU?60V9*TnnD@OUBHyjYmBM%!bd2g85@tC{ohK7PEL*{ga-3TE3r$ z?9pWLD%7n79^+Umhzz8LSJX3ioaYY5X@k*8#C(0^n=BP+%BGqFML@rkOw^VYxTHBk zz8tfdFA41wOV^OGX+P4-o#6)s`qLhgrL#@uUohN}bU?yv+YX1sr^Y)FW?T8VE)AIg z3bfO3{R*d)TX$vn!sWdcrfeK+l*cDQBZRU3oCTdd|6X$`*NuFw_-Swe_5@SCLgFft8pef?wNrpwq^k_|j#&jP62>VOK=~O1 zhc;fAep1-#$ztb;mMYcR(J<)At?eS9hmvSfgy6sg5`PE6TIHl1d%0%30P<5wmLBzy zFq)_$pMXiEgCfQNWRJ42{84EktfeG0p>PAC$T_>{DpTG)6;UVNemDFD890o5x$>tg zhwr^T07E&(u8)`a*B436v*8FI%RAKqINkHc-rRVsrZqO~p8U44@*{MXUg7lJ&bE{0tO#cBJZ zB9oIW@#=W158yX-3Fru-E_KnXSXF~7cZ<#ezBSUB)K@)2S{dgq99!$HC8Hn&_0j-4 z&@r&KI$OyF+nJ`Li9zH#pts1$w!V1Z2HzRR2s0g+K0wbs}e5`t;iySK#oJK~2+B6NH zWS}h*AFQoodFmnreWgcXH6#n-=PJkH7?zHj5R&GZ`#8e_fRR@nO;TP2WV`H{#U>jEta8)uD#;$>kdswWvFdn)yYs%{Ys$a34taom z_61%5?NP}1CW+ot7YPe{-%W~6=6626vVo#&cO$I?tAiZ{&|mxp$a2>8_0i0|19t~$ z4~89YQ=B59osnL~Cj^!U!$MxBY3+$^*i)zMX&!rsGn3mwC9CQmZ8W(lFEf?|p~NF- zc`an~rc468nk6pk*WosX8gY{EqCZ{qBo18$4H2chP)1wVGRPDL2;eb0onmOMuLXqZ zKLy6aWCKhCnJtdcfWI^OOBJ2Ia!IcJ5B8Ol_PdoB6sJ3=abm3j4;h^L_AiFqI)J5| z-H2Fh6&Oz&bmr@c0@>HBRnWZ!n!>t4N5=4$%fN6Etpy&51vWt9=8FEXb+Q0A3{%es ze`1C(Metpj>1V6rw)>hLbj{@_!oP)D?v$Md4%+zwhfaPxDFdnATc4ZnVX+Mg_T)>3 ztPmV*=UhxV?N%uQf7NTK`1S%77D3i9GFT+hUxC;7?+r)|?SXjGHMa!-$ZqZBItpHe z>!cB0!xe<^OFzA^wo#Uw^N~|`iEj4;N@MqLDFmM&Ji~11s7dJ>S1q zX%Z&}9*)1_?ID)QQY0;L8J{ZRRq?nm&mLrE8g~)^lrTPn6Aoh#@TWGm?f0EwBD76<#K!QT zD^$c;dGb63BQW95=Dlelh3F{7app)3FK!dGM@ew>`|#{B;pun>T4?*%1VG<-}3whfYdnV4M={WcOKyXl`}5k0i`STA@P3cp?WEa!6t zpMlgr3d1xL?@W&iGmPrSzUG6DbYsGa^V;Ma5ydzHB8+iW;+5TDvp<9asT#-VoD{r> zeWf7EF{F*5Tr{HuLQ9eS+k?UAwhPp#ZGWXZt0{2%Nr37 zh5x}rdoj#qF#;#lG*z^nphC$_7-xUtOz$fPS}FB%{sr^8^`&v`C2i4|G%xUxKCieN zl`$+ut5duIy}O*fNU$PnoDrM00>3vJB3^JpcL8bwXQSwr?g-`tp8#~}Z)LCb?}*%0 zdGCp=@*gW=cl|HS9*DkYG2+<;$(widE|%hW*B2^y2OMBx=Sl8^W%qYaH}a4eZl%xz zN$_``A zNZn28Fs;H>ivqg`{VE?AD@jN^pCXUdn%v~_l`UlhffO)ZRm# z2eN3M^j=h0e4!e4hjGcOtpj{}0VS^?dc5xn930(Z6?hEY<1Q&{Utv>hr!)RDsnDDKFuMqeRJM%w9Q5P#N@ zM@42K%NJ-y7HjlUiLtmggFPDam|6B&;O27TM& z?lH#3%HliVTAq{32HKGD*jpccc8q}_4F!naT zY(fFAyClp5lF{N8+~Oia-C{f5kDKQ_1yUN~e=626pQHkL=edjmv$hv_*-gDb1i=Lz zb0o7o^gq~NM*c!$xl_c5zYTZ<5SP24Tm+zSFyzbJ!P~a-Ob&h9+`fE4INAVu!^$H8 z&)n^73g74h>!<2VxL+ZIn;DXr11Lr^o9h&YBF6s$Pgvs{XUf&b;%b&uK8Jf2ToSrM zvUppXU(V`U>vR?cxr`62`J}JiWBytUcZPT>h_#;tgs5BE7^AU{t-k&RZiatx9t;Cm zLsuHtK^Kd?d+?+6zI7e43b4HEJz)X|UYPh-lX}f)*~sLy?KmMPGm{I`Pt5oyMQQpx zdJ|m&!Efw4GF}7MY*tlXskxNlhnu%e=ouOx)oRJ^tkB{CMJIt?ySqX|?&14`u`2(H z^}iD|LGNW}F0U!HnQOHJ+BqZ%+Uvq{)ym)CMfg3xnCmj4E;b^WRs4j&(X9;uqo`?n zaA6?=8$>#^KeIUn{l%BFATzq){?7Mip%S7(uTx_(iW5&YlEGAo_y!(8f_ym9rA}~NH{KAUR*63cpb_itO7H_&@vX~W zP@8I1wVjS6^p+wIKqM7#1jns;^>Dy75*4 zkdZUo8hua!H^I9y;4eQPsq>nHfgad1C7)(c{7Os&>5dJaoK~<2p9sq~^W8XfLZHqS zUC+lFCsw7YiXBM=O}#O*`@hQHl`y?O&rj*b`ewM|q4+LGGl7KgGW zwGKCiu-2agkYBM@K?YQHt9#-$l=CG31VK{^3?-*Sx5|YHn;gT3rIvKB z8V+~{I&jzpyp^B>$*v7lM`v9KrVNgA4*e(}CTu^twS->-Se)gBwx2O^se&+k@eYAf zt*q->fzp+*_I^3|Dz--kgrjN92@>W1AwScY&=_MpuAaneFqIrq^J;Wj0TMX}t_8JK z!Ql5zr4`qW7Q@QmY#C?vP@1C3%0d!7pgnK7KPsp?kWgh&A~WPS|Kfvi=PRtep0 zm;RN54{?SA)}wq8zr4fZ)tG_-nCqFV^616h*k)=S-gYsG`hSrI%Ow1`IHyfk>+1c{ znE{EiwehIR56_ZGi_tu(5={RFsB1GOdsqYL?5JX=D?F&=0IxFs)X9T^iLUTv0Nz*y z0SixqwbsL9WPcZR+-mxqUQhWJ4vs+*qT7`}GzcRGf<0B`s__<}nreNAq z6D5XJ2f3%=Y$_6``UEJ;K+~EErP_J}-@1@lM2hqJleNhm(Cetb^HrF1uRfDuMTqHy zCm7)Z>--zCfi-*`YZ#uF=4e3mSKT(L&@S; z;dNF?%wSfZ^jOwYFwgX^ z2G|aiYwXJi41$d6&699)Y;Yxm-zs99@Oq>JmIQmv&6-c==*>pAt~9`Z+ezBMW?&uHB(y{jVeuK0gR=$2%bO>l;i``8!#ca5lfqKFRyvy;4_x5gH$i7-M z=KZ=GyNKisUP3`_^d&C7*@gH8n{b6bTV_GO0XUPZ_@!0|o#E06s`bKZq%jtH}Gq%MGR=umQjk zf0YwiDc0S=_nl+_$?@9oyeorKZ82qj*Xop6l^susC`ID=@$l`luuXiO9jqOdg!Rn0rbNfGkeI0_tFY+W) zSwk%ZpZGT`7akQ8`k(Sq(9}wwKqrlL5?ELuW*uAm1Iru&Hg3xXZk-_(TG*D!CmF&x z>J-`Sz(@D))}`M}3zeY)8Feb$(?$H^YbZqus^_mC^>OT?9(YA2t`Ez~`v2YrKTHfC ztzoRJxL@LcrzXJ8WmU+|Q~8M{JSMQJs`exUfBY$u1aBVZj&(4nYe{19ns~V*YN3Jn zL!yWS;Z7+9l@eMqrf1^pJj)8Sj`cm^#iPV<#OV#)r`)8k*Ha(`Exc2(`^X9DlM&!e zTh|gduj%tSOq>)u`sLffYrtd$_*kWhf(_5l>U%s~O(O8HVa=!i1J9>BIp6HMC+xZc zXyQ-Sgx&F^MVeo^jmT`Cb#Hr9^PhP^_V5LisiXV_40etSuW&B=EZq}*?sJhO2hXL@ zq>==c!%ANV4$}(*XJsCndcagijH2n@ENGvKM38EhaQO{sZA&^|%a7p&;Yuwfx2YOC zdFXz#ML;^KmT;nS8YXJd!?&&ARNXcPG8!=x76*ue3MOkDY1;9u=CiVFEe)(X=k2bW z8j-dJ0Zkcf?&Zoyt=7;WbD`ka_s8k1IDfSz8Yh$ROB{HM$g zHj?wc^tMz+5nnyczF`)M;zS0poUv`&+lEaAv`A<7e_CQy)0cxI<4yU@rE#OEK$pnmVhy=5jcGy5~!3>vpv>?ox6FElCu(Ql4WtFu&)_zTPl@ga z1#w|#&VpFWur6?k76s9v)kq7Z*@{kwE4RnFncYVPxS~)L5;a%r#=Fqi}LEg}ff7O00_cpex4wdL7TAUqQ?%xIk@m2%w0E}F! zxL9tmFzq{|j6K}{W5;IdXMU+f zvdDV@-1&h%k$t%Cpkk@jjy&7G$ky&ehA?4QW^yVbM=HGrkwXSt(foFYwzjj)uNiIp z&^+ANi#(-#j`a|%3!7^O);9gxUoPs3;nBO%jp6^^z4B-e{(xoq1&jsN-Im$_ABUm! z27+JY(esEn_msUtPW7^^3rd~jF#`(8R3`HQ$kM{Yc=i1CQ+RjtkF$P+DSI=p<|Dbp>duXL(>dG^i()QWfkxC0)It8m9eU^Eb4 z0UyMZHh121e6SmjR82`)L7=MzNkuppV)S(&ravSI=rlR6?|;`4=nqudr4DR(T;#F? zXbvJqbCK8pn{Hmsld^pOezSVvHyIj9KA-J3C?2*1Q|8>5Neo2g6ikuAcP_O0!zVuj zjhVI-1xbGA*0&NUU~?%02NXM}=)6@NV^EbuhgvTR zx6Lh9IDzrWe2X$Xr^fTxN`||B>KruN3#+CLc*4s+E3B3qmH0}bo+X<%F30YmLT0cgq(0*12 zCzkoo1R5m$rq&mtmKv%z5sKa-(D9493r^RfB7jB*)*tk3B&#^YB9_*O#Y%0NY&8G*|4pmPFEbP${Y+DWRR zRzcwgZ0phprZ4B{btwZ(1gt4OY}YJIBgw6bX74L5N;m`nyG*XJXwnV|(4QNdhm;A+ zAnDy%q!eaT(5X2pqHN#?e{m%1CTIpa=SwYl!}j5w5OH;JBc=*=YIiAcPACfkAs4=$ zh$~mIvY&F#Q36rMueveMaWu~(!7=_Qw%Kws3&$;|X4VAZ zV)gfGq&ss1l@sobq2Ji3#ohQ=4<2WcSPh8h)Nm5&OH9FjrJ~seg5$=RwNqC_MZ*hj z93*x$**kJ8$QetKyo1l@Q!lRvr(+qJ3q9*Ud3AlDM&uLE_#KiTfU|fTC2uVfwg%a92aZE_`W;G$s?Z0|#*}fb3drhNtT#Q3VwTgvfOF z&CvyiL!LJEgGViAjRyGL3sH!`$>KHeqdXDrJ7x~~!pA=NeZ0S01oD%!NJ1|d*hKXO@knv$3x!QK$!zhv zxQ+=8S~s`CFz!`gZ6->QOUB{`b<;0BrplpvPC2qLpoY8t?VCe%c&X_`8p6iT!}Qh$ zA{7!zQZz#(`59d&ibu}x?f;ak-S|x8i9Sh%9;7!=f5l!hstm zu`6*~C^vU-DmBam1YT5Ahm%U@XIJBS+qtVwQ;-Es7-AB<(T}a7?nDy>UTn&xd!5aN zDvj%MOP{$Yve*Huy!bbIrknNV=KvT4G|qUjMd`U=uriF$6Rzl0d|M@MIGHP)z_wiA z5Mdzz2guu(RJV+bxa4KDQ69$+E~dIuZ3nT@FYs8(BR#PPePS0Q5s?}KmYNMdH9-+; zrPCliEugTA`O~N;W@2y$0s0WT4kkdJz2cnj0;}>ET_3JvI~&{_!I*V>8Kb50vPp)-$X>p*@WE+bI zD6wA4k7^bOXQ0dFL$S^lwXM~eZ~aLFU#Ox*9tFb$aK!f;u>MhBn&&$4AM4>gTQRj7 zv{7h1_B8(0Ii8CEa4E@o`t;7a<|G2#f=4J2PG)5nhocLV)>e5QeP%p#CaEiHo?UlZJ{9nh?az#9MO{^cd-KsTC`M| z$BZS{c>*|mT70@E&3+pJ5GHiM?i*L{mHM_V-3T#zfO+aZGejr(78}+tl$4qge4(M#HlkYAd>N#EcyZpRKswr~zSg*nWRE@y08ltrTML@d0wY}`cZN_uh6sR!D zTq6Kwko>Yr(d%k{2hAmJ9sng!925fLST*IkuMx;Py}9^5Si`9!yV>?FRS20y`p$Vh7ld0-MEzuou&g{dEW>r%!~xg|p_82;{B2dZBcrtQcm)|`p;`XvyC zok187w+n^)x|+=}PDoRr0l@Is(%By)e~%zAJoYe@kmfs0@-NoMsN}dN~RvpjISKV@+uK;=@JES zMt@rbMz_XW1q74JsW?R_7X?_~&Y=zPdN3C~zlfZnQdJaM$_$Az0!e30(x4ycPWNDM zhq+6@@k@UIt|l`@tIoiK)R~RB0>_`Fnvxz+`X%V=gpANe;2NKoC4%_K_IehTBF27y530=41d+jUEf zHE=OL%572n3M?cH-d@C5SA@dV-K#@N0_SNIKDw-f3-`w4H)wfXOE;|aT~!Yo-p1O- zq7IH810aCD(0oLSz$E2LXd9)Jn4bpo#Ij+WEqi&LM8&86sQLXc_I#M^*fB$13eL# z19aL^e`ll-b)RA|h^8-{G&<<0Ayip51hBBA0cr%?sBsnpu({wmOSJx%%p)e=Q|IFM zkh4$dLGx=t0T^Bs3eI|$WQKp#lOVvW8gGR`Ae7->KD+A#HM_uYPY?bbc zqX-IR+z_Fxqi($T0xKhQOs?;~aN0=`*MM3ng99)tqE<0C`;2V$9>QH|1f4H1b79;9 zbK_iMaIvNhvIW{;ubSqc|M(1cqi7;<2T%^4-q;*-;Ck2+at+m^ZNqS@F_sox;s@lS z>xM$o2bo-`A}lIH>CrCg0k`Z@tE`w_W-5u0I{d)=uVRn{qVJK#SHpG z6*pN&PpCH5sfqEcga=Gg12{+WE(CkfG>YACK0?c60Y7&3O*T>H*0U|GE!>0}Wc8M7xmz9~Zcx?Er;r1w9>_-97r@ z1PXsfwxn~I0O&;RZsnvA^;JO8J^P1GM6JQW@qZ5{9+)9-ioSNKX6m!%pNGW*}Bdp z=0#9j5xb*E%Vn@11+w66b{=stt_>HR%T_uH&w{6+P?-xX|4^N+5kT7j1}!b{ewps% z0v%8}sQ53If9CkOtY-`FSkI6hXeEAU16vNlCL5z#38?}~T|IN3K{uKjP!QVZ&+#Ek zqQ1TK19Q!q0jC4E*DEPtC7sIy$?rE~d(xK0$OYGT!0e2STTCzp_9=*sg zXmub@ZuHu`=86Z7gpRiTfP7`=1(hXV{*+$7s~VXj5 z59P5abvdc~Ffh0l0|cZC$*XeN_eG+bC#uz}00=S}oD=W>3>ovz8?#t^2T6=1$qz`a zC4xN6;`>O*WZB^iOp*8ae@vBFU?*mX10|oCW)&VKN1z-glbkwvUhTPCp~JHTmYp`T z-J2?>0Im5PCn)4Rw3Sq6OKzG7C=8@DyJw9K7{fws`gl*d2fRLvpFKBkXm5v!AYxIe z{?;4RdW*420~&SULV~JZ2E)QhWC2-|UtywrN6wnYx_P#UZLGUq5-Ga8X2g`+0@ujB zEM+WTLlte@koc6RD|Tua3QwK@ur|b>^ zsH)K*olP2>K&mihObc9oVF752 z2ZPCYx#P@Yh|8plwL#Gj9PVM%gD5H^Es`yTv_GU11Om(s7dXPLrHssH@9}Eo0?eb@ zXOXgA%1ws3`>BQS0vq@gmgCw>F(+}YjMht=`%c!n+zb1rpUB?znV(iT2Z}DIaLmIG zBZ<)VlnEIC=$r?k=|M4$+K9#e!f&=s0<+LQrmb*J1BYJVV^5|cE~d*>oIe4h08mAZ z;r`4y2G<@c?6HNQVHNdxJIDt_*pIcgX>=-i@!;^I0ri{Y?+kVTZOkwK2H%lL zu?{ZW_pTu0JY21t7LYYTC-_JI$Is3$elq4mjP;JYeM#{@)X6p0Zg92TOpw zZ(4bKS6_*{*EcIXP>hzV=jhf&dux>Ro!&<*d=wPn7O zVqw0HHs;3)v>y01z1@Cw1n~R?#oDA zZXlY?S8JY67D5`?G(n#6<{VV@0*~UMBk4w*o-q2C0~nT%K)hT2KDeE>Q8~>1am0wp z05n{XC1vE1fl68xiAIpGFj+Mka3-DC&TjSABG+}Z1h-_Ua$SzQ?WYhs{8N^jW(9ifA`QzzCyFIT%0OWw%ae`%33C(@(m}ZZF(G_Avg8LZg9%KO_i5DwQ1+A3> z5@f(XS;asW(Vl25DYeo`-4GiWT^VTiMtl@K0a>I9Z7Mr;=Ijs^Vr{{Db_g31v$kZl ziu0??hX3O(0_h^LtXsi9rwn(9Wnz0~2PR^eD$3IX{?bK+RWBw20_M#q@J*h*YernO zks%fo`NG}3fA?W-vmYJ-D-$WINDa?Lua0?w+MHlQ>%uZg$($thC%c-8iT za9DatOuaX*=C*8V17Tt=7hspDjZwd7UPChHD_W9$ZTfsgh%e8arQaZX1Mu@JhH^JC zNF`*WR@O~m`(m;0wouvK*|M$bDiG_I1v?Y?v;$bQDgZbhotdEbAw3J+?3QgMc9}8U zDe0i11@zRsRs+U=^v-!D=RO0<{5}tm`>O6FVOBB*3q8`C1#NjYIP2a#SlZaeEsz@? z(`q}FNd^BRx1x~u(-pjj1s~H^asaV!QCxYzY$q$7;~ih}Rgno-WM5@8RP6l@0U&)z z%18^t!h*YhBI@EISNszt5l}#OBG~&f6t##!2QL7O;Cf0JKE1iv(s6Kyo2gB@a5^~a5R3;mlwxWfV6Pz#R z4L^&95Y-o5O1ByVc#mGXHxt(jfU)ZM+@AT;}JDK#fLrj~Pi@fgA1Wi9xD@3bh z_Sb16{F26nH#b9~@lV&dq62YjXK$uU;1vI7wY4bYk11LqQ6$<)>N)DqzpC$snK0OaK;eMW`ww9$|I z0t3EBVUYBmbux7!_0XbhK`|Sc03>SB0ysY6U}LnCnytLJnu=m$EVG#jA11UVdeg-Y z0Z)HE?-U{vf_wP#e0<%Kg z({0cCDcDbC(j`#|?U`JlBzLHAiq}01m|ny(sSpZNjxC%~cr{>DucKAe;g-c)r3lJ2{I-feylYXqMx0ab<(P~y7eWA@0+ zx8y!k@?wURx0WsQ7k9yEQ#Itt0k)IqDc$#!e#iXJmP=6SZ$2a{ z86b>;Q9)ygnW?%M0;1_A*Pqhtbf((7x`_f`n905*F#tP!0@w#>3^ z`w(>-r#q$Hn8DPlN&?n)H5Xoq0;MXDd^+PoD1N?jgB0B`T z0YzXEY_rmCikm<1{O5@dcSqIGFh%RC~Zl}q*Je*Nu2eLy8Ot9!& zk}`np(9SB(fA9L`o~KvIrfE}P-JL}61zXv~$}s=KFT8$>)(lywD7rcCvA_OK*}!Qg z*V0Yd0*#Q0vjfaiJ>w*w-j&OT>8sc7SQ0;2WMCKlldd=>0_TGf+ynmUf-w4MD;7cm@$tHX!mOl%#bdaIsXD3If0ug5H^%3A5#IagT}2;|AOQ#`Duo zoQJnxi;qHGtC4|10pMh(!9AXGB%2&89M4k$+*1umUj^q?DJn_cS_L`M2cQ7WpVzu^ zsYie%TD+_Vgg3<54(8W~41Y;DKQw>I1UVFN6{x`mw9}W$1`>|<-(Nr{xypr0wib8k z2qJe7x7sBRc_@I!GuUvRTQkj2)Q9i>&1nbH42JbXqa!`%aEA<1%dt}!v?mnh@4DE&Hp%b7^M;G zi$LNYZ!Lp>L%r0N2BoKl`SPV{99-Q573=_fSl|U8`JQZ>$2iCI@@3E01n~y1<5K(I z4=IK7dXvtw`1U-s%{)x9$8uLu0UK zklwi&1m|0!AA9=05A!PQis&=3dP*ixt0z9n> zvho)YkX|OX3#o&u%W2Z!n7eM)AL&qL;UI~B0%Z9CbYt>OM*dVnHXzj2ZP#+>?|MR$ z*s2p~BU)#k1Rda%g1V9j5CT>_1~+YG2+KB4M41r=u&3p%c~J7_+#;|E$Z8u?W)vekaadhJRfr z1)_slL3>li+t|+}v^c%Cfl%m{t$z2#^ey~jjzF?v1CJ@>5)7jk$I&f$0z20_dI5Bh z)|7aq7~_`Z25rQf2F)u>+O+BeB0<7dR?`j{Q`E?;vp0W;$`PS;#Z(z%1DUQk-wdkc zgE^IQtDWo%IdHmo;q)VHI^(TZa>>M(1>#OTFh)U=K(W>LUsf7AOv&_yF|G73!h5_p z(gC0<0*@`^*znA+()5-jtv-S$Yng0+=qAg8qAoL~x_9gC2fhIibqnJT^$bu}l@D7T z?5^C88A=&t1ghUBFU`zu1wQkxO0m`2G&Wyd?y17KvgELsRR) zkmx$gaOM>-*MIxg2fyW`wv}C#nkaG|jtwvWbBO5t&L#ILJV-2AIXjGP2d)eJ8Qz~F zqayewhe?aY+V(sG=g9CF)eLtz;)~A{12Obis!^X6+^ESdsR4=(8nIKH_;

g^z7Z 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 z

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 z%0D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

?U5UqkOC%ty`KCz+OfG}OTJ&3 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

=AgvmIL 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

pEi3&A6en?m*#@{JQ; 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%0AgVNNUikT__s#GFO60Jh}stbiB? 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 literal 0 HcmV?d00001 diff --git a/crates/block-hashes/src/lib.rs b/crates/block-hashes/src/lib.rs new file mode 100644 index 0000000000..fab6003455 --- /dev/null +++ b/crates/block-hashes/src/lib.rs @@ -0,0 +1,21 @@ +use pathfinder_common::{BlockHash, BlockNumber, Chain}; + +mod sepolia; + +#[derive(Clone)] +pub struct BlockHashDb { + chain: Chain, +} + +impl BlockHashDb { + pub fn new(chain: Chain) -> Self { + Self { chain } + } + + pub fn block_hash(&self, block_number: BlockNumber) -> Option { + match self.chain { + Chain::SepoliaTestnet => sepolia::block_hash(block_number), + _ => None, + } + } +} diff --git a/crates/block-hashes/src/sepolia.rs b/crates/block-hashes/src/sepolia.rs new file mode 100644 index 0000000000..a42c5635de --- /dev/null +++ b/crates/block-hashes/src/sepolia.rs @@ -0,0 +1,16 @@ +use pathfinder_common::{BlockHash, BlockNumber}; +use pathfinder_crypto::Felt; + +const FIRST_0_13_2_BLOCK: usize = 86311; +static BLOCK_HASHES: &[u8; 32 * FIRST_0_13_2_BLOCK] = + include_bytes!("../fixtures/sepolia_block_hashes.bin"); + +pub(super) fn block_hash(block_number: BlockNumber) -> Option { + if block_number.get() >= FIRST_0_13_2_BLOCK as u64 { + None + } else { + let offset = (block_number.get() as usize) * 32; + let felt = Felt::from_be_slice(&BLOCK_HASHES[offset..offset + 32]).unwrap(); + Some(BlockHash(felt)) + } +} diff --git a/crates/pathfinder/examples/compute_pre0132_hashes.rs b/crates/pathfinder/examples/compute_pre0132_hashes.rs index d11e1d3221..a5625f6d9a 100644 --- a/crates/pathfinder/examples/compute_pre0132_hashes.rs +++ b/crates/pathfinder/examples/compute_pre0132_hashes.rs @@ -18,7 +18,7 @@ use pathfinder_lib::state::block_hash::{ BlockHeaderData, }; -const VERSION_CUTOFF: StarknetVersion = StarknetVersion::new(0, 13, 2, 0); +const VERSION_CUTOFF: StarknetVersion = StarknetVersion::V_0_13_2; /// Computes block hashes for all blocks under the 0.13.2 cutoff in 0.13.2 style /// and stores them in a CSV file "block_hashes.csv" with the format: @@ -48,7 +48,8 @@ fn main() -> anyhow::Result<()> { }; // Open a file where we'll save the computed hashes - let mut file = std::fs::File::create("block_hashes.csv")?; + let mut csv_file = std::fs::File::create("block_hashes.csv")?; + let mut binary_file = std::fs::File::create("block_hashes.bin")?; // Iterate through all pre-0.13.2 blocks for block_number in 0..latest_block_number.get() { @@ -64,6 +65,12 @@ fn main() -> anyhow::Result<()> { .context("Fetching block header")? .context("Block header missing")?; + // As soon as we reach blocks in 0.13.2 we're done + if header.starknet_version == VERSION_CUTOFF { + println!("\rBlock {}. Done!", block_number); + break; + } + // Load block tx's (to compute receipt commitment) let txn_data_for_block = tx .transaction_data_for_block(block_id)? @@ -122,15 +129,16 @@ fn main() -> anyhow::Result<()> { let new_block_hash = compute_final_hash(&header_data).context("Computing block hash")?; // Write to the CSV file - writeln!(file, "{},{}", block_number, new_block_hash)?; + writeln!(csv_file, "{},{}", block_number, new_block_hash)?; - // As soon as we reach blocks in 0.13.2 we're done - if header.starknet_version == VERSION_CUTOFF { - println!("\rBlock {}. Done!", block_number); - break; - } + // Write to the binary file + binary_file + .write_all(new_block_hash.0.as_be_bytes()) + .context("Writing block hash to binary file")?; } + println!("\nResults are in `block_hashes.csv` and `block_hashes.bin`"); + Ok(()) } From cc149551d9206cf6b261979b9b688b55f26fe780 Mon Sep 17 00:00:00 2001 From: Krisztian Kovacs Date: Wed, 2 Oct 2024 15:21:39 +0200 Subject: [PATCH 127/282] feat(pathfinder/sync/p2p): check 0.13.2-style commitments for legacy blocks Now that we have a "trusted" database with all pre-0.13.2 Sepolia block hashes the verification for those legacy blocks looks like this: - look up expected block hash in database - verify expected block hash matches computation using 0.13.2-style commitments in the block header - check 0.13.2-style commitments when verifying transactions / events received from peers --- Cargo.lock | 1 + crates/pathfinder/Cargo.toml | 1 + crates/pathfinder/src/sync.rs | 2 + crates/pathfinder/src/sync/checkpoint.rs | 54 ++++++++++-- crates/pathfinder/src/sync/events.rs | 5 +- .../src/sync/fixtures/sepolia_headers.json | 26 ++++-- crates/pathfinder/src/sync/headers.rs | 83 +++++++++++-------- crates/pathfinder/src/sync/track.rs | 9 +- crates/pathfinder/src/sync/transactions.rs | 3 +- 9 files changed, 130 insertions(+), 54 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8c5bc1fd62..65ca2d8bb1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7250,6 +7250,7 @@ dependencies = [ "mockall", "p2p", "p2p_proto", + "pathfinder-block-hashes", "pathfinder-common", "pathfinder-compiler", "pathfinder-crypto", diff --git a/crates/pathfinder/Cargo.toml b/crates/pathfinder/Cargo.toml index 90ea9cb0d4..3d0ebb3c5a 100644 --- a/crates/pathfinder/Cargo.toml +++ b/crates/pathfinder/Cargo.toml @@ -34,6 +34,7 @@ metrics = { workspace = true } metrics-exporter-prometheus = { workspace = true } p2p = { path = "../p2p" } p2p_proto = { path = "../p2p_proto" } +pathfinder-block-hashes = { path = "../block-hashes" } pathfinder-common = { path = "../common" } pathfinder-compiler = { path = "../compiler" } pathfinder-crypto = { path = "../crypto" } diff --git a/crates/pathfinder/src/sync.rs b/crates/pathfinder/src/sync.rs index 4d65817a99..cefad9e49e 100644 --- a/crates/pathfinder/src/sync.rs +++ b/crates/pathfinder/src/sync.rs @@ -92,6 +92,7 @@ impl Sync { chain_id: self.chain_id, public_key: self.public_key, verify_tree_hashes: self.verify_tree_hashes, + block_hash_db: Some(pathfinder_block_hashes::BlockHashDb::new(self.chain)), } .run(checkpoint) .await; @@ -127,6 +128,7 @@ impl Sync { chain: self.chain, chain_id: self.chain_id, public_key: self.public_key, + block_hash_db: Some(pathfinder_block_hashes::BlockHashDb::new(self.chain)), } .run(next, parent_hash, self.fgw_client.clone()) .await; diff --git a/crates/pathfinder/src/sync/checkpoint.rs b/crates/pathfinder/src/sync/checkpoint.rs index 2038ddf924..b789c63e48 100644 --- a/crates/pathfinder/src/sync/checkpoint.rs +++ b/crates/pathfinder/src/sync/checkpoint.rs @@ -19,6 +19,7 @@ use p2p::client::types::{ClassDefinition, EventsForBlockByTransaction, Transacti use p2p::PeerData; use p2p_proto::common::{BlockNumberOrHash, Direction, Iteration}; use p2p_proto::transaction::{TransactionWithReceipt, TransactionsRequest, TransactionsResponse}; +use pathfinder_block_hashes::BlockHashDb; use pathfinder_common::receipt::Receipt; use pathfinder_common::state_update::StateUpdateData; use pathfinder_common::transaction::{Transaction, TransactionVariant}; @@ -63,6 +64,7 @@ pub struct Sync { pub chain_id: ChainId, pub public_key: PublicKey, pub verify_tree_hashes: bool, + pub block_hash_db: Option, } impl Sync { @@ -88,6 +90,7 @@ impl Sync { chain_id, public_key, verify_tree_hashes, + block_hash_db: Some(pathfinder_block_hashes::BlockHashDb::new(chain)), } } @@ -168,6 +171,7 @@ impl Sync { self.chain, self.chain_id, self.public_key, + self.block_hash_db.clone(), self.storage.clone(), ) .await?; @@ -299,13 +303,14 @@ async fn handle_header_stream( chain: Chain, chain_id: ChainId, public_key: PublicKey, + block_hash_db: Option, storage: Storage, ) -> Result<(), SyncError> { InfallibleSource::from_stream(stream) .spawn() .pipe(headers::BackwardContinuity::new(head.0, head.1), 10) .pipe( - headers::VerifyHashAndSignature::new(chain, chain_id, public_key), + headers::VerifyHashAndSignature::new(chain, chain_id, public_key, block_hash_db), 10, ) .try_chunks(1024, 10) @@ -699,12 +704,16 @@ mod tests { BlockHash, BlockHeader, BlockTimestamp, + ClassCommitment, EventCommitment, + GasPrice, + L1DataAvailabilityMode, ReceiptCommitment, SequencerAddress, StarknetVersion, StateCommitment, StateDiffCommitment, + StorageCommitment, TransactionCommitment, }; use pathfinder_storage::fake::{self as fake_storage, Block}; @@ -724,7 +733,7 @@ mod tests { } #[serde_as] - #[derive(Clone, Copy, Debug, Deserialize)] + #[derive(Clone, Debug, Deserialize)] pub struct Fixture { pub block_hash: BlockHash, pub block_number: BlockNumber, @@ -738,10 +747,15 @@ mod tests { pub event_count: usize, pub signature: [BlockCommitmentSignatureElem; 2], pub state_diff_commitment: StateDiffCommitment, + pub state_diff_length: u64, + pub receipt_commitment: ReceiptCommitment, + pub starknet_version: String, + pub eth_l1_gas_price: GasPrice, } impl From for SignedBlockHeader { fn from(dto: Fixture) -> Self { + let starknet_version: StarknetVersion = dto.starknet_version.parse().unwrap(); Self { header: BlockHeader { hash: dto.block_hash, @@ -755,7 +769,16 @@ mod tests { event_commitment: dto.event_commitment, event_count: dto.event_count, state_diff_commitment: dto.state_diff_commitment, - ..Default::default() + state_diff_length: dto.state_diff_length, + receipt_commitment: dto.receipt_commitment, + starknet_version, + eth_l1_gas_price: dto.eth_l1_gas_price, + eth_l1_data_gas_price: GasPrice(1), + strk_l1_gas_price: GasPrice(0), + strk_l1_data_gas_price: GasPrice(1), + l1_da_mode: L1DataAvailabilityMode::Calldata, + class_commitment: ClassCommitment::ZERO, + storage_commitment: StorageCommitment::ZERO, }, signature: BlockCommitmentSignature { r: dto.signature[0], @@ -807,6 +830,9 @@ mod tests { Chain::SepoliaTestnet, ChainId::SEPOLIA_TESTNET, public_key, + Some(pathfinder_block_hashes::BlockHashDb::new( + Chain::SepoliaTestnet, + )), storage.clone(), ) .await @@ -851,6 +877,9 @@ mod tests { Chain::SepoliaTestnet, ChainId::SEPOLIA_TESTNET, public_key, + Some(pathfinder_block_hashes::BlockHashDb::new( + Chain::SepoliaTestnet + )), storage.clone(), ) .await, @@ -876,6 +905,7 @@ mod tests { Chain::Mainnet, ChainId::MAINNET, public_key, + None, storage.clone(), ) .await, @@ -933,6 +963,9 @@ mod tests { Chain::SepoliaTestnet, ChainId::SEPOLIA_TESTNET, public_key, + Some(pathfinder_block_hashes::BlockHashDb::new( + Chain::SepoliaTestnet + )), storage.clone(), ) .await, @@ -976,7 +1009,11 @@ mod tests { .map(|(t, _, _)| t.clone()) .collect::>() .as_slice(), - block.header.header.starknet_version, + block + .header + .header + .starknet_version + .max(StarknetVersion::V_0_13_2), ) .unwrap(); block.header.header.transaction_commitment = transaction_commitment; @@ -1318,7 +1355,6 @@ mod tests { use super::super::handle_class_stream; use super::*; - use crate::state::block_hash::calculate_event_commitment; const SIERRA0_HASH: SierraHash = sierra_hash!("0x04e70b19333ae94bd958625f7b61ce9eec631653597e68645e13780061b2136c"); @@ -1547,7 +1583,7 @@ mod tests { use p2p::libp2p::PeerId; use pathfinder_common::event::Event; use pathfinder_common::transaction::TransactionVariant; - use pathfinder_common::TransactionHash; + use pathfinder_common::{StarknetVersion, TransactionHash}; use pathfinder_crypto::Felt; use pathfinder_storage::{fake as fake_storage, StorageBuilder}; @@ -1598,7 +1634,11 @@ mod tests { .iter() .map(|(tx, _, events)| (tx.hash, events.as_slice())) .collect::>(), - block.header.header.starknet_version, + block + .header + .header + .starknet_version + .max(StarknetVersion::V_0_13_2), ) .unwrap(); } diff --git a/crates/pathfinder/src/sync/events.rs b/crates/pathfinder/src/sync/events.rs index c26e7141a2..c2667db0dd 100644 --- a/crates/pathfinder/src/sync/events.rs +++ b/crates/pathfinder/src/sync/events.rs @@ -92,7 +92,7 @@ pub(super) async fn verify_commitment( .iter() .map(|(tx_hash, events)| (*tx_hash, events.as_slice())) .collect::>(), - header.starknet_version, + header.starknet_version.max(StarknetVersion::V_0_13_2), ) .context("Calculating commitment")?; if computed != header.event_commitment { @@ -169,7 +169,8 @@ impl ProcessStage for VerifyCommitment { tracing::debug!(expected=%ordered_events.len(), actual=%events.len(), "Number of events received does not match expected number of events"); return Err(SyncError2::EventsTransactionsMismatch); } - let actual = calculate_event_commitment(&ordered_events, version)?; + let actual = + calculate_event_commitment(&ordered_events, version.max(StarknetVersion::V_0_13_2))?; if actual != event_commitment { tracing::debug!(expected=%event_commitment, actual=%actual, "Event commitment mismatch"); return Err(SyncError2::EventCommitmentMismatch); diff --git a/crates/pathfinder/src/sync/fixtures/sepolia_headers.json b/crates/pathfinder/src/sync/fixtures/sepolia_headers.json index 9d8fa445a4..694e6b5f15 100644 --- a/crates/pathfinder/src/sync/fixtures/sepolia_headers.json +++ b/crates/pathfinder/src/sync/fixtures/sepolia_headers.json @@ -4,8 +4,8 @@ "parent_block_hash": "0x0", "block_number": 0, "state_root": "0xe005205a1327f3dff98074e528f7b96f30e0624a1dfcf571bdc81948d150a0", - "transaction_commitment": "0x6bc49c465936fa8b19a6773975bef3c1f9793463103ce679d5a2c10b0c3846e", - "event_commitment": "0x63e31e9955086a982d5d1b008c0a1579521d526fcf38445a8daf4799bde362e", + "transaction_commitment": "0x04FE7011D76891A7D7A061EC5611ABA08082E3B6B2EA0DAE4B856F3BEF2EC446", + "event_commitment": "0x05319A64C4C93C26390DE039698227AFE1CB31FF44C03E73A7D547F37FA5E49A", "timestamp": 1700406220, "sequencer_address": "0x1176a1bd84444c89232ec27754698e5d2e7e1a7f1539f12027f28b23ec9f3d8", "transaction_count": 7, @@ -14,14 +14,18 @@ "0x30a199364f1cec47d386da1839015413a464456ca6d63d6f45156d0a1254356", "0x784aba6f4948a6a1caa4f7247adcabdfe41a8ec21668c30621cd9d265516dc6" ], - "state_diff_commitment": "0x7c1ac561302d1f4a84ae34cdac3bb55cda085874e23ce48da6492ab2ce4211a" + "state_diff_commitment": "0x047ED57C27FE5AF431AB0D85E4008FC68F04D5E2F7748CDA496C62ABB27944CC", + "state_diff_length": 11, + "receipt_commitment": "0x0002764C596CB0899C4301BB6F6074AC779738BF352ABE015B282B67A8FC998D", + "starknet_version": "0.12.3", + "eth_l1_gas_price": 1046576581 }, { "block_hash": "0x78b67b11f8c23850041e11fb0f3b39db0bcb2c99d756d5a81321d1b483d79f6", "parent_block_hash": "0x5c627d4aeb51280058bed93c7889bce78114d63baad1be0f0aeb32496d5f19c", "block_number": 1, "state_root": "0xe005205a1327f3dff98074e528f7b96f30e0624a1dfcf571bdc81948d150a0", - "transaction_commitment": "0x301a3e7f3ae29c3463a5f753da62e63dc0b6738c36cb17e3d1696926457a40c", + "transaction_commitment": "0x02A082169C0259BFEF1ACC74CA06984770440AEDC1292BF24C6697A84987D880", "event_commitment": "0x0", "timestamp": 1700474724, "sequencer_address": "0x1176a1bd84444c89232ec27754698e5d2e7e1a7f1539f12027f28b23ec9f3d8", @@ -31,14 +35,18 @@ "0x47fe7aea2435f8be68f0dce8f859f0f2f31db43eddc9269a200ef7a530e34a0", "0x636260cd4a8a9991a9a2f7b09a4d8ebdfbfdb7d32d0a2b8e23d8ebc0ae014dc" ], - "state_diff_commitment": "0x70188b63055c343725f88de0f84a702b50cd81b5a426ecc75b0922d78fdac78" + "state_diff_commitment": "0x01990D108859C231985BB27DD2E1C3B77A554C9BECB37419E1076CF6032EFBEC", + "state_diff_length": 1, + "receipt_commitment": "0x02E701BAD57A0596FD6066F6E40E7A17561646E62543D3A7BC58178F3817AE40", + "starknet_version": "0.12.3", + "eth_l1_gas_price": 1000004111 }, { "block_hash": "0x7a906dfd1ff77a121b8048e6f750cda9e949d341c4487d4c6a449f183f0e61d", "parent_block_hash": "0x78b67b11f8c23850041e11fb0f3b39db0bcb2c99d756d5a81321d1b483d79f6", "block_number": 2, "state_root": "0xe005205a1327f3dff98074e528f7b96f30e0624a1dfcf571bdc81948d150a0", - "transaction_commitment": "0x6f777eb09c00aed5fa717ceb34038c1b70051b229aad566421858811c106e1a", + "transaction_commitment": "0x04C043B78EAFA499E794B9616C112C9E1884AA4AB219834C19436F785C32DE88", "event_commitment": "0x0", "timestamp": 1700475581, "sequencer_address": "0x1176a1bd84444c89232ec27754698e5d2e7e1a7f1539f12027f28b23ec9f3d8", @@ -48,6 +56,10 @@ "0x328251965bcf8dc8f870a027eebe73627b8dbd82d6981cfbd4231a197da53ae", "0x75bb4eb017f274f1f6e2a785c921a51baa761dad2d85fe54fd8607d816b829f" ], - "state_diff_commitment": "0x7d4abf2975cd79627ed1bf8040b7ae2d10e4db59ede80661caedb647da21720" + "state_diff_commitment": "0x07320438AE12FF18334E8C0BED2A4382DF1FEA88F0AAA9357B99797D04ECD396", + "state_diff_length": 1, + "receipt_commitment": "0x00CA7DA341DA6881458B9B418C7E72E5BA5C58173452881632D78270951ECE6C", + "starknet_version": "0.12.3", + "eth_l1_gas_price": 1000001558 } ] diff --git a/crates/pathfinder/src/sync/headers.rs b/crates/pathfinder/src/sync/headers.rs index d519e54cf7..4fe0255b8d 100644 --- a/crates/pathfinder/src/sync/headers.rs +++ b/crates/pathfinder/src/sync/headers.rs @@ -12,12 +12,13 @@ use pathfinder_common::{ ClassCommitment, PublicKey, SignedBlockHeader, + StarknetVersion, StorageCommitment, }; use pathfinder_storage::Storage; use tokio::task::spawn_blocking; -use crate::state::block_hash::{verify_block_hash, BlockHeaderData, VerifyResult}; +use crate::state::block_hash::{BlockHeaderData, VerifyResult}; use crate::sync::error::{SyncError, SyncError2}; use crate::sync::stream::{ProcessStage, SyncReceiver}; @@ -160,6 +161,7 @@ pub struct VerifyHashAndSignature { chain: Chain, chain_id: ChainId, public_key: PublicKey, + block_hash_db: Option, } impl ForwardContinuity { @@ -242,49 +244,58 @@ impl ProcessStage for VerifyHashAndSignature { } impl VerifyHashAndSignature { - pub fn new(chain: Chain, chain_id: ChainId, public_key: PublicKey) -> Self { + pub fn new( + chain: Chain, + chain_id: ChainId, + public_key: PublicKey, + block_hash_db: Option, + ) -> Self { Self { chain, chain_id, public_key, + block_hash_db, } } fn verify_hash(&self, header: &BlockHeader) -> bool { - let result = verify_block_hash( - BlockHeaderData { - hash: header.hash, - parent_hash: header.parent_hash, - number: header.number, - timestamp: header.timestamp, - sequencer_address: header.sequencer_address, - state_commitment: header.state_commitment, - transaction_commitment: header.transaction_commitment, - transaction_count: header - .transaction_count - .try_into() - .expect("ptr size is 64 bits"), - event_commitment: header.event_commitment, - event_count: header.event_count.try_into().expect("ptr size is 64 bits"), - state_diff_commitment: header.state_diff_commitment, - state_diff_length: header.state_diff_length, - starknet_version: header.starknet_version, - starknet_version_str: header.starknet_version.to_string(), - eth_l1_gas_price: header.eth_l1_gas_price, - strk_l1_gas_price: header.strk_l1_gas_price, - eth_l1_data_gas_price: header.eth_l1_data_gas_price, - strk_l1_data_gas_price: header.strk_l1_data_gas_price, - receipt_commitment: header.receipt_commitment, - l1_da_mode: header.l1_da_mode, - }, - self.chain, - self.chain_id, - ); - match result { - Ok(VerifyResult::Match) => true, - Ok(VerifyResult::Mismatch) => { - tracing::debug!(block_number=%header.number, expected_block_hash=%header.hash, "Block hash mismatch"); - false + let expected_hash = self + .block_hash_db + .as_ref() + .and_then(|db| db.block_hash(header.number)) + .unwrap_or(header.hash); + match crate::state::block_hash::compute_final_hash(&BlockHeaderData { + hash: header.hash, + parent_hash: header.parent_hash, + number: header.number, + timestamp: header.timestamp, + sequencer_address: header.sequencer_address, + state_commitment: header.state_commitment, + transaction_commitment: header.transaction_commitment, + transaction_count: header + .transaction_count + .try_into() + .expect("ptr size is 64 bits"), + event_commitment: header.event_commitment, + event_count: header.event_count.try_into().expect("ptr size is 64 bits"), + state_diff_commitment: header.state_diff_commitment, + state_diff_length: header.state_diff_length, + starknet_version: header.starknet_version, + starknet_version_str: header.starknet_version.to_string(), + eth_l1_gas_price: header.eth_l1_gas_price, + strk_l1_gas_price: header.strk_l1_gas_price, + eth_l1_data_gas_price: header.eth_l1_data_gas_price, + strk_l1_data_gas_price: header.strk_l1_data_gas_price, + receipt_commitment: header.receipt_commitment, + l1_da_mode: header.l1_da_mode, + }) { + Ok(block_hash) => { + if block_hash == expected_hash { + true + } else { + tracing::debug!(block_number=%header.number, expected_block_hash=%expected_hash, actual_block_hash=%block_hash, "Block hash mismatch"); + false + } } Err(e) => { tracing::debug!(block_number=%header.number, error = ?e, "Failed to verify block hash"); diff --git a/crates/pathfinder/src/sync/track.rs b/crates/pathfinder/src/sync/track.rs index 9ef04d6273..ea1a7165ab 100644 --- a/crates/pathfinder/src/sync/track.rs +++ b/crates/pathfinder/src/sync/track.rs @@ -57,6 +57,7 @@ pub struct Sync { pub chain: Chain, pub chain_id: ChainId, pub public_key: PublicKey, + pub block_hash_db: Option, } impl Sync { @@ -88,7 +89,12 @@ impl Sync { .spawn() .pipe(headers::ForwardContinuity::new(next, parent_hash), 100) .pipe( - headers::VerifyHashAndSignature::new(self.chain, self.chain_id, self.public_key), + headers::VerifyHashAndSignature::new( + self.chain, + self.chain_id, + self.public_key, + self.block_hash_db, + ), 100, ); @@ -886,6 +892,7 @@ mod tests { chain: Chain::SepoliaTestnet, chain_id: ChainId::SEPOLIA_TESTNET, public_key: PublicKey::default(), + block_hash_db: None, }; sync.run(BlockNumber::GENESIS, BlockHash::default(), FakeFgw) diff --git a/crates/pathfinder/src/sync/transactions.rs b/crates/pathfinder/src/sync/transactions.rs index ab9034d795..18f3a6b816 100644 --- a/crates/pathfinder/src/sync/transactions.rs +++ b/crates/pathfinder/src/sync/transactions.rs @@ -166,7 +166,8 @@ impl ProcessStage for VerifyCommitment { block_number, } = transactions; let txs: Vec<_> = transactions.iter().map(|(t, _)| t.clone()).collect(); - let actual = calculate_transaction_commitment(&txs, version)?; + let actual = + calculate_transaction_commitment(&txs, version.max(StarknetVersion::V_0_13_2))?; if actual != expected_commitment { tracing::debug!(%block_number, %expected_commitment, actual_commitment=%actual, "Transaction commitment mismatch"); return Err(SyncError2::TransactionCommitmentMismatch); From 0680b91c5248b5ccca6ff42f0861dfce8385cc13 Mon Sep 17 00:00:00 2001 From: Krzysztof Lis Date: Fri, 4 Oct 2024 13:09:47 +0200 Subject: [PATCH 128/282] feat(sync/track): update storage and class tries --- crates/pathfinder/src/sync.rs | 1 + crates/pathfinder/src/sync/track.rs | 63 ++++++++++++++++++++++++++--- 2 files changed, 59 insertions(+), 5 deletions(-) diff --git a/crates/pathfinder/src/sync.rs b/crates/pathfinder/src/sync.rs index cefad9e49e..e7efbde0db 100644 --- a/crates/pathfinder/src/sync.rs +++ b/crates/pathfinder/src/sync.rs @@ -129,6 +129,7 @@ impl Sync { chain_id: self.chain_id, public_key: self.public_key, block_hash_db: Some(pathfinder_block_hashes::BlockHashDb::new(self.chain)), + verify_tree_hashes: self.verify_tree_hashes, } .run(next, parent_hash, self.fgw_client.clone()) .await; diff --git a/crates/pathfinder/src/sync/track.rs b/crates/pathfinder/src/sync/track.rs index ea1a7165ab..c02566c7ca 100644 --- a/crates/pathfinder/src/sync/track.rs +++ b/crates/pathfinder/src/sync/track.rs @@ -33,6 +33,7 @@ use pathfinder_common::{ SierraHash, SignedBlockHeader, StarknetVersion, + StateCommitment, StateDiffCommitment, StateUpdate, StorageCommitment, @@ -45,6 +46,7 @@ use tokio_stream::wrappers::ReceiverStream; use super::class_definitions::CompiledClass; use super::{state_updates, transactions}; +use crate::state::{update_starknet_state, StarknetStateUpdate}; use crate::sync::class_definitions::{self, ClassWithLayout}; use crate::sync::error::SyncError2; use crate::sync::stream::{ProcessStage, SyncReceiver, SyncResult}; @@ -58,6 +60,7 @@ pub struct Sync { pub chain_id: ChainId, pub public_key: PublicKey, pub block_hash_db: Option, + pub verify_tree_hashes: bool, } impl Sync { @@ -167,7 +170,14 @@ impl Sync { classes, } .spawn() - .pipe(StoreBlock::new(storage_connection), 10) + .pipe( + StoreBlock::new( + storage_connection, + self.storage.clone(), + self.verify_tree_hashes, + ), + 10, + ) .into_stream() .try_fold((), |_, _| std::future::ready(Ok(()))) .await @@ -721,11 +731,24 @@ struct BlockData { struct StoreBlock { connection: pathfinder_storage::Connection, + // We need this so that we can create extra read-only transactions for parallel contract state + // updates + storage: Storage, + // Verify trie node hashes when loading tries from DB. + verify_tree_hashes: bool, } impl StoreBlock { - pub fn new(connection: pathfinder_storage::Connection) -> Self { - Self { connection } + pub fn new( + connection: pathfinder_storage::Connection, + storage: pathfinder_storage::Storage, + verify_tree_hashes: bool, + ) -> Self { + Self { + connection, + storage, + verify_tree_hashes, + } } } @@ -760,10 +783,12 @@ impl ProcessStage for StoreBlock { strk_l1_data_gas_price: header.strk_l1_data_gas_price, sequencer_address: header.sequencer_address, starknet_version: header.starknet_version, - class_commitment: ClassCommitment::ZERO, // TODO update class tries + // Class commitment is updated after the class tries are updated. + class_commitment: ClassCommitment::ZERO, event_commitment: header.event_commitment, state_commitment: header.state_commitment, - storage_commitment: StorageCommitment::ZERO, // TODO update storage tries + // Storage commitment is updated after the storage tries are updated. + storage_commitment: StorageCommitment::ZERO, transaction_commitment: header.transaction_commitment, transaction_count: header.transaction_count, event_count: header.event_count, @@ -788,6 +813,33 @@ impl ProcessStage for StoreBlock { db.insert_transaction_data(block_number, &transactions, Some(&ordered_events)) .context("Inserting transaction data")?; + let (storage_commitment, class_commitment) = update_starknet_state( + &db, + StarknetStateUpdate { + contract_updates: &state_diff.contract_updates, + system_contract_updates: &state_diff.system_contract_updates, + declared_sierra_classes: &state_diff.declared_sierra_classes, + }, + self.verify_tree_hashes, + block_number, + self.storage.clone(), + ) + .context("Updating Starknet state")?; + + // Ensure that roots match. + let state_commitment = StateCommitment::calculate(storage_commitment, class_commitment); + let expected_state_commitment = header.state_commitment; + if state_commitment != expected_state_commitment { + tracing::debug!( + actual_storage_commitment=%storage_commitment, + actual_class_commitment=%class_commitment, + actual_state_commitment=%state_commitment, + "State root mismatch"); + return Err(SyncError2::StateRootMismatch); + } + + db.update_storage_and_class_commitments(block_number, storage_commitment, class_commitment) + .context("Updating storage and class commitments")?; db.insert_state_update_data(block_number, &state_diff) .context("Inserting state update data")?; @@ -893,6 +945,7 @@ mod tests { chain_id: ChainId::SEPOLIA_TESTNET, public_key: PublicKey::default(), block_hash_db: None, + verify_tree_hashes: false, }; sync.run(BlockNumber::GENESIS, BlockHash::default(), FakeFgw) From aa96876d1616f0e6342545aabc2ef1602e5584ed Mon Sep 17 00:00:00 2001 From: whichqua Date: Fri, 4 Oct 2024 16:22:13 +0300 Subject: [PATCH 129/282] fix: correctly handle `get_proof` for contract without a root index Key changes include: - Adjusting the get_proof method to return an empty list when no contract root is found. - Adding checks for None values when retrieving contract roots. Pathfinder returns an internal error when one tries to fetch a proof for contract with a `contract_state_hash` but without a `root_index` for the current block height. Pathfinder returns none which is propagated down to an internal error. The following is an example of a request that fails without this fix: ```json { "id": "0", "jsonrpc": "2.0", "method": "pathfinder_getProof", "params": { "block_id": { "block_number": 155006 }, "contract_address": "0x40a29e36c82f868dc2b5712bc6729c6132ca75ae46d6c75718ecbd49a0c9fb7", "keys": [ "0x206f38f7e4f15e87567361213c28f235cccdaa1d7fd34c9db1dfe9489c6a091" ] } } ``` When there is no root_index, this fix will return empty `ProofNodes` ie `ProofNodes(vec![])` for each of the keys. This fix also prevents multiple calls to the database fetching the same root (same contract and block number) while looping through the keys. --- crates/merkle-tree/src/contract.rs | 9 +--- .../rpc/src/pathfinder/methods/get_proof.rs | 41 +++++++++++-------- 2 files changed, 26 insertions(+), 24 deletions(-) diff --git a/crates/merkle-tree/src/contract.rs b/crates/merkle-tree/src/contract.rs index 638cb4327c..a07a8561b8 100644 --- a/crates/merkle-tree/src/contract.rs +++ b/crates/merkle-tree/src/contract.rs @@ -83,15 +83,8 @@ impl<'tx> ContractsStorageTree<'tx> { contract: ContractAddress, block: BlockNumber, key: &BitSlice, + root: u64, ) -> anyhow::Result>> { - let root = tx - .contract_root_index(block, contract) - .context("Querying contract root index")?; - - let Some(root) = root else { - return Ok(None); - }; - let storage = ContractStorage { tx, block: Some(block), diff --git a/crates/rpc/src/pathfinder/methods/get_proof.rs b/crates/rpc/src/pathfinder/methods/get_proof.rs index 96295d3fb6..f7c1388f8b 100644 --- a/crates/rpc/src/pathfinder/methods/get_proof.rs +++ b/crates/rpc/src/pathfinder/methods/get_proof.rs @@ -280,24 +280,33 @@ pub async fn get_proof( .context("Querying contract's nonce")? .unwrap_or_default(); + let root = tx + .contract_root_index(header.number, input.contract_address) + .context("Querying contract root index")?; + let mut storage_proofs = Vec::new(); for k in &input.keys { - let proof = ContractsStorageTree::get_proof( - &tx, - input.contract_address, - header.number, - k.view_bits(), - ) - .context("Get proof from contract state tree")? - .ok_or_else(|| { - let e = anyhow!( - "Storage proof missing for key {:?}, but should be present", - k - ); - tracing::warn!("{e}"); - e - })?; - storage_proofs.push(ProofNodes(proof)); + if let Some(root) = root { + let proof = ContractsStorageTree::get_proof( + &tx, + input.contract_address, + header.number, + k.view_bits(), + root, + ) + .context("Get proof from contract state tree")? + .ok_or_else(|| { + let e = anyhow!( + "Storage proof missing for key {:?}, but should be present", + k + ); + tracing::warn!("{e}"); + e + })?; + storage_proofs.push(ProofNodes(proof)); + } else { + storage_proofs.push(ProofNodes(vec![])); + } } let contract_data = ContractData { From 2cf7f8ba4aeab45584ec7b975b112d283817b5cc Mon Sep 17 00:00:00 2001 From: sistemd Date: Sat, 5 Oct 2024 00:41:12 +0200 Subject: [PATCH 130/282] add failure_reason to starknet_getTransactionStatus --- crates/gateway-types/src/reply.rs | 11 ++- crates/rpc/src/dto/receipt.rs | 25 +++++-- crates/rpc/src/jsonrpc/websocket/logic.rs | 32 ++++++++- .../rpc/src/method/get_transaction_status.rs | 72 +++++++++++++++---- crates/rpc/src/v08.rs | 1 + 5 files changed, 117 insertions(+), 24 deletions(-) diff --git a/crates/gateway-types/src/reply.rs b/crates/gateway-types/src/reply.rs index 42c4de7e89..6f4ebe12b9 100644 --- a/crates/gateway-types/src/reply.rs +++ b/crates/gateway-types/src/reply.rs @@ -187,17 +187,18 @@ pub mod call { /// Used to deserialize replies to Starknet transaction requests. /// -/// We only care about the statuses so we ignore other fields. /// Please note that this does not have to be backwards compatible: /// since we only ever use it to deserialize replies from the Starknet /// feeder gateway. #[serde_as] -#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq)] +#[derive(Clone, Debug, Deserialize, PartialEq, Eq)] pub struct TransactionStatus { pub status: Status, pub finality_status: transaction_status::FinalityStatus, #[serde(default)] pub execution_status: transaction_status::ExecutionStatus, + pub transaction_failure_reason: Option, + pub revert_error: Option, } /// Types used when deserializing get_transaction replies. @@ -224,6 +225,12 @@ pub mod transaction_status { Reverted, Rejected, } + + #[derive(Clone, Debug, Deserialize, PartialEq, Eq)] + pub struct TransactionFailureReason { + pub code: String, + pub error_message: String, + } } /// Types used when deserializing L2 transaction related data. diff --git a/crates/rpc/src/dto/receipt.rs b/crates/rpc/src/dto/receipt.rs index 0a79fd4db4..51fba6a51b 100644 --- a/crates/rpc/src/dto/receipt.rs +++ b/crates/rpc/src/dto/receipt.rs @@ -16,10 +16,13 @@ pub enum TxnStatus { AcceptedOnL1, } -#[derive(Copy, Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq)] pub enum TxnExecutionStatus { Succeeded, - Reverted, + Reverted { + // Revert reason optional for backward compatibility with gateway. + reason: Option, + }, } impl From<&pathfinder_common::receipt::ExecutionStatus> for TxnExecutionStatus { @@ -27,7 +30,9 @@ impl From<&pathfinder_common::receipt::ExecutionStatus> for TxnExecutionStatus { use pathfinder_common::receipt::ExecutionStatus; match value { ExecutionStatus::Succeeded => Self::Succeeded, - ExecutionStatus::Reverted { .. } => Self::Reverted, + ExecutionStatus::Reverted { reason } => Self::Reverted { + reason: Some(reason.clone()), + }, } } } @@ -91,7 +96,7 @@ impl SerializeForVersion for TxnExecutionStatus { fn serialize(&self, serializer: Serializer) -> Result { match self { TxnExecutionStatus::Succeeded => "SUCCEEDED", - TxnExecutionStatus::Reverted => "REVERTED", + TxnExecutionStatus::Reverted { .. } => "REVERTED", } .serialize(serializer) } @@ -108,7 +113,12 @@ impl SerializeForVersion for TxnExecutionStatusWithRevertReason<'_> { serializer.serialize_field("execution_status", &TxnExecutionStatus::Succeeded)?; } ExecutionStatus::Reverted { reason } => { - serializer.serialize_field("execution_status", &TxnExecutionStatus::Reverted)?; + serializer.serialize_field( + "execution_status", + &TxnExecutionStatus::Reverted { + reason: Some(reason.clone()), + }, + )?; serializer.serialize_field("revert_reason", reason)?; } } @@ -449,8 +459,9 @@ mod tests { } #[rstest] - #[case::accepted_on_l2(TxnExecutionStatus::Succeeded, "SUCCEEDED")] - #[case::accepted_on_l1(TxnExecutionStatus::Reverted, "REVERTED")] + #[case::succeeded(TxnExecutionStatus::Succeeded, "SUCCEEDED")] + #[case::reverted_missing_reason(TxnExecutionStatus::Reverted { reason: None }, "REVERTED")] + #[case::reverted_with_reason(TxnExecutionStatus::Reverted { reason: Some("Reverted because".to_string()) }, "REVERTED")] fn txn_execution_status(#[case] input: TxnExecutionStatus, #[case] expected: &str) { let expected = json!(expected); let encoded = input.serialize(Serializer::default()).unwrap(); diff --git a/crates/rpc/src/jsonrpc/websocket/logic.rs b/crates/rpc/src/jsonrpc/websocket/logic.rs index 92cc17a563..854579bb38 100644 --- a/crates/rpc/src/jsonrpc/websocket/logic.rs +++ b/crates/rpc/src/jsonrpc/websocket/logic.rs @@ -505,7 +505,7 @@ async fn transaction_status_subscription( match gateway.transaction(transaction_hash).await { Ok(tx_status) => { num_consecutive_errors = 0; - let update = match (tx_status.finality_status, tx_status.execution_status) { + let update = match (tx_status.finality_status, &tx_status.execution_status) { (_, ExecutionStatus::Rejected) => Some(TransactionStatusUpdate::Rejected), (FinalityStatus::NotReceived, _) => { // "NOT_RECEIVED" status is never sent to the client. @@ -1206,26 +1206,36 @@ mod tests { status: Status::NotReceived, finality_status: FinalityStatus::NotReceived, execution_status: ExecutionStatus::Succeeded, + transaction_failure_reason: None, + revert_error: None, }, TransactionStatus { status: Status::Received, finality_status: FinalityStatus::Received, execution_status: ExecutionStatus::Succeeded, + transaction_failure_reason: None, + revert_error: None, }, TransactionStatus { status: Status::NotReceived, finality_status: FinalityStatus::NotReceived, execution_status: ExecutionStatus::Succeeded, + transaction_failure_reason: None, + revert_error: None, }, TransactionStatus { status: Status::Received, finality_status: FinalityStatus::Received, execution_status: ExecutionStatus::Succeeded, + transaction_failure_reason: None, + revert_error: None, }, TransactionStatus { status: Status::AcceptedOnL1, finality_status: FinalityStatus::AcceptedOnL1, execution_status: ExecutionStatus::Succeeded, + transaction_failure_reason: None, + revert_error: None, }, ] .into_iter() @@ -1291,26 +1301,36 @@ mod tests { status: Status::NotReceived, finality_status: FinalityStatus::NotReceived, execution_status: ExecutionStatus::Succeeded, + transaction_failure_reason: None, + revert_error: None, }, TransactionStatus { status: Status::Received, finality_status: FinalityStatus::Received, execution_status: ExecutionStatus::Succeeded, + transaction_failure_reason: None, + revert_error: None, }, TransactionStatus { status: Status::NotReceived, finality_status: FinalityStatus::NotReceived, execution_status: ExecutionStatus::Succeeded, + transaction_failure_reason: None, + revert_error: None, }, TransactionStatus { status: Status::Received, finality_status: FinalityStatus::Received, execution_status: ExecutionStatus::Succeeded, + transaction_failure_reason: None, + revert_error: None, }, TransactionStatus { status: Status::AcceptedOnL1, finality_status: FinalityStatus::AcceptedOnL1, execution_status: ExecutionStatus::Reverted, + transaction_failure_reason: None, + revert_error: None, }, ] .into_iter() @@ -1376,26 +1396,36 @@ mod tests { status: Status::NotReceived, finality_status: FinalityStatus::NotReceived, execution_status: ExecutionStatus::Succeeded, + transaction_failure_reason: None, + revert_error: None, }, TransactionStatus { status: Status::Received, finality_status: FinalityStatus::Received, execution_status: ExecutionStatus::Succeeded, + transaction_failure_reason: None, + revert_error: None, }, TransactionStatus { status: Status::NotReceived, finality_status: FinalityStatus::NotReceived, execution_status: ExecutionStatus::Succeeded, + transaction_failure_reason: None, + revert_error: None, }, TransactionStatus { status: Status::Received, finality_status: FinalityStatus::Received, execution_status: ExecutionStatus::Succeeded, + transaction_failure_reason: None, + revert_error: None, }, TransactionStatus { status: Status::Rejected, finality_status: FinalityStatus::NotReceived, execution_status: ExecutionStatus::Rejected, + transaction_failure_reason: None, + revert_error: None, }, ] .into_iter() diff --git a/crates/rpc/src/method/get_transaction_status.rs b/crates/rpc/src/method/get_transaction_status.rs index 6ee23ee410..6d82a852b3 100644 --- a/crates/rpc/src/method/get_transaction_status.rs +++ b/crates/rpc/src/method/get_transaction_status.rs @@ -3,6 +3,7 @@ use pathfinder_common::TransactionHash; use crate::context::RpcContext; use crate::dto::TxnExecutionStatus; +use crate::RpcVersion; #[derive(Debug, PartialEq, Eq)] pub struct Input { @@ -22,7 +23,10 @@ impl crate::dto::DeserializeForVersion for Input { #[derive(Debug, PartialEq)] pub enum Output { Received, - Rejected, + Rejected { + // Reject error message optional for backward compatibility with gateway. + error_message: Option, + }, AcceptedOnL1(TxnExecutionStatus), AcceptedOnL2(TxnExecutionStatus), } @@ -96,16 +100,24 @@ pub async fn get_transaction_status(context: RpcContext, input: Input) -> Result match (tx.finality_status, tx.execution_status) { (GatewayFinalityStatus::NotReceived, _) => Err(Error::TxnHashNotFound), - (_, GatewayExecutionStatus::Rejected) => Ok(Output::Rejected), + (_, GatewayExecutionStatus::Rejected) => Ok(Output::Rejected { + error_message: tx + .transaction_failure_reason + .map(|reason| reason.error_message), + }), (GatewayFinalityStatus::Received, _) => Ok(Output::Received), (GatewayFinalityStatus::AcceptedOnL1, GatewayExecutionStatus::Reverted) => { - Ok(Output::AcceptedOnL1(TxnExecutionStatus::Reverted)) + Ok(Output::AcceptedOnL1(TxnExecutionStatus::Reverted { + reason: tx.revert_error, + })) } (GatewayFinalityStatus::AcceptedOnL1, GatewayExecutionStatus::Succeeded) => { Ok(Output::AcceptedOnL1(TxnExecutionStatus::Succeeded)) } (GatewayFinalityStatus::AcceptedOnL2, GatewayExecutionStatus::Reverted) => { - Ok(Output::AcceptedOnL2(TxnExecutionStatus::Reverted)) + Ok(Output::AcceptedOnL2(TxnExecutionStatus::Reverted { + reason: tx.revert_error, + })) } (GatewayFinalityStatus::AcceptedOnL2, GatewayExecutionStatus::Succeeded) => { Ok(Output::AcceptedOnL2(TxnExecutionStatus::Succeeded)) @@ -119,7 +131,7 @@ impl Output { use crate::dto::TxnStatus; match self { Output::Received => TxnStatus::Received, - Output::Rejected => TxnStatus::Rejected, + Output::Rejected { .. } => TxnStatus::Rejected, Output::AcceptedOnL1(_) => TxnStatus::AcceptedOnL1, Output::AcceptedOnL2(_) => TxnStatus::AcceptedOnL2, } @@ -127,9 +139,18 @@ impl Output { fn execution_status(&self) -> Option { match self { - Output::Received | Output::Rejected => None, - Output::AcceptedOnL1(x) => Some(*x), - Output::AcceptedOnL2(x) => Some(*x), + Output::Received | Output::Rejected { .. } => None, + Output::AcceptedOnL1(x) => Some(x.clone()), + Output::AcceptedOnL2(x) => Some(x.clone()), + } + } + + fn failure_reason(&self) -> Option { + match self { + Output::Rejected { error_message } => error_message.clone(), + Output::AcceptedOnL1(TxnExecutionStatus::Reverted { reason }) => reason.clone(), + Output::AcceptedOnL2(TxnExecutionStatus::Reverted { reason }) => reason.clone(), + _ => None, } } } @@ -142,6 +163,10 @@ impl crate::dto::serialize::SerializeForVersion for Output { let mut serializer = serializer.serialize_struct()?; serializer.serialize_field("finality_status", &self.finality_status())?; serializer.serialize_optional("execution_status", self.execution_status())?; + // Delete check once rustc gives you a friendly reminder + if serializer.version != RpcVersion::V07 { + serializer.serialize_optional("failure_reason", self.failure_reason())?; + } serializer.end() } } @@ -156,14 +181,14 @@ mod tests { use super::*; #[rstest::rstest] - #[case::rejected(Output::Rejected, json!({"finality_status":"REJECTED"}))] + #[case::rejected(Output::Rejected { error_message: None }, json!({"finality_status":"REJECTED"}))] #[case::reverted(Output::Received, json!({"finality_status":"RECEIVED"}))] #[case::accepted_on_l1_succeeded( Output::AcceptedOnL1(TxnExecutionStatus::Succeeded), json!({"finality_status":"ACCEPTED_ON_L1","execution_status":"SUCCEEDED"}) )] #[case::accepted_on_l2_reverted( - Output::AcceptedOnL2(TxnExecutionStatus::Reverted), + Output::AcceptedOnL2(TxnExecutionStatus::Reverted{ reason: None }), json!({"finality_status":"ACCEPTED_ON_L2","execution_status":"REVERTED"}) )] fn output_serialization(#[case] output: Output, #[case] expected: serde_json::Value) { @@ -211,7 +236,7 @@ mod tests { } #[tokio::test] - async fn rejected() { + async fn rejected_with_error_message() { let input = Input { // Transaction hash known to be rejected by the testnet gateway. transaction_hash: transaction_hash!( @@ -221,7 +246,16 @@ mod tests { let context = RpcContext::for_tests(); let status = get_transaction_status(context, input).await.unwrap(); - assert_eq!(status, Output::Rejected); + assert_eq!( + status, + Output::Rejected { + error_message: Some( + "Transaction is too big to fit a batch; Its gas_weight weights 5214072 while \ + the batch upper bound is set to 5000000.0." + .to_string() + ) + } + ); } #[tokio::test] @@ -233,13 +267,23 @@ mod tests { let status = get_transaction_status(context.clone(), input) .await .unwrap(); - assert_eq!(status, Output::AcceptedOnL2(TxnExecutionStatus::Reverted)); + assert_eq!( + status, + Output::AcceptedOnL2(TxnExecutionStatus::Reverted { + reason: Some("Reverted because".to_string()) + }) + ); let input = Input { transaction_hash: transaction_hash_bytes!(b"pending reverted"), }; let status = get_transaction_status(context, input).await.unwrap(); - assert_eq!(status, Output::AcceptedOnL2(TxnExecutionStatus::Reverted)); + assert_eq!( + status, + Output::AcceptedOnL2(TxnExecutionStatus::Reverted { + reason: Some("Reverted!".to_string()) + }) + ); } #[tokio::test] diff --git a/crates/rpc/src/v08.rs b/crates/rpc/src/v08.rs index 9af633102d..e9f79e50b4 100644 --- a/crates/rpc/src/v08.rs +++ b/crates/rpc/src/v08.rs @@ -7,6 +7,7 @@ use crate::method::subscribe_pending_transactions::SubscribePendingTransactions; pub fn register_routes() -> RpcRouterBuilder { RpcRouter::builder(crate::RpcVersion::V08) .register("starknet_syncing", crate::method::syncing) + .register("starknet_getTransactionStatus", crate::method::get_transaction_status) .register("starknet_subscribeNewHeads", SubscribeNewHeads) .register("starknet_subscribePendingTransactions", SubscribePendingTransactions) .register("starknet_subscribeEvents", SubscribeEvents) From 9aec62692b98f51186a547b499d89e386046e0b4 Mon Sep 17 00:00:00 2001 From: sistemd Date: Sat, 5 Oct 2024 00:41:12 +0200 Subject: [PATCH 131/282] remove unneeded serde_as --- crates/gateway-types/src/reply.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/gateway-types/src/reply.rs b/crates/gateway-types/src/reply.rs index 6f4ebe12b9..1c3c3e7cb7 100644 --- a/crates/gateway-types/src/reply.rs +++ b/crates/gateway-types/src/reply.rs @@ -190,7 +190,6 @@ pub mod call { /// Please note that this does not have to be backwards compatible: /// since we only ever use it to deserialize replies from the Starknet /// feeder gateway. -#[serde_as] #[derive(Clone, Debug, Deserialize, PartialEq, Eq)] pub struct TransactionStatus { pub status: Status, From 9dcd41aa4f72fe824d95ae0f69cf3fcfd2454b72 Mon Sep 17 00:00:00 2001 From: Krisztian Kovacs Date: Fri, 4 Oct 2024 15:47:58 +0200 Subject: [PATCH 132/282] chore(doc/rpc): add 0.8.0-rc0 JSON-RPC specification --- crates/rpc/src/lib.rs | 37 + doc/rpc/v08/starknet_api_openrpc.json | 3797 +++++++++++++++++++ doc/rpc/v08/starknet_trace_api_openrpc.json | 480 +++ doc/rpc/v08/starknet_write_api.json | 291 ++ doc/rpc/v08/starknet_ws_api.json | 358 ++ 5 files changed, 4963 insertions(+) create mode 100644 doc/rpc/v08/starknet_api_openrpc.json create mode 100644 doc/rpc/v08/starknet_trace_api_openrpc.json create mode 100644 doc/rpc/v08/starknet_write_api.json create mode 100644 doc/rpc/v08/starknet_ws_api.json diff --git a/crates/rpc/src/lib.rs b/crates/rpc/src/lib.rs index 3e7a4b86b9..bf21bfeca4 100644 --- a/crates/rpc/src/lib.rs +++ b/crates/rpc/src/lib.rs @@ -869,6 +869,43 @@ mod tests { // get_transaction_status is now part of the official spec, so we are phasing it out. #[case::root_pathfinder("/", "pathfinder_rpc_api.json", &["pathfinder_version", "pathfinder_getTransactionStatus"])] + #[case::v0_8_api ("/rpc/v0_8", "v08/starknet_api_openrpc.json", &[ + "starknet_getBlockWithTxHashes", + "starknet_getBlockWithTxs", + "starknet_getBlockWithReceipts", + "starknet_getStateUpdate", + "starknet_getStorageAt", + "starknet_getMessagesStatus", + "starknet_getTransactionByHash", + "starknet_getTransactionByBlockIdAndIndex", + "starknet_getTransactionReceipt", + "starknet_getClass", + "starknet_getClassHashAt", + "starknet_getClassAt", + "starknet_getBlockTransactionCount", + "starknet_call", + "starknet_estimateFee", + "starknet_estimateMessageFee", + "starknet_blockNumber", + "starknet_blockHashAndNumber", + "starknet_chainId", + "starknet_getEvents", + "starknet_getNonce", + "starknet_getStorageProof", + ])] + #[case::v0_8_trace("/rpc/v0_8", "v08/starknet_trace_api_openrpc.json", &[ + "starknet_traceTransaction", + "starknet_simulateTransactions", + "starknet_traceBlockTransactions" + ])] + #[case::v0_8_write("/rpc/v0_8", "v08/starknet_write_api.json", &[ + "starknet_addInvokeTransaction", + "starknet_addDeclareTransaction", + "starknet_addDeployAccountTransaction" + ])] + // get_transaction_status is now part of the official spec, so we are phasing it out. + #[case::v0_8_pathfinder("/rpc/v0_8", "pathfinder_rpc_api.json", &["pathfinder_version", "pathfinder_getTransactionStatus"])] + #[case::v0_7_api ("/rpc/v0_7", "v07/starknet_api_openrpc.json", &[])] #[case::v0_7_trace("/rpc/v0_7", "v07/starknet_trace_api_openrpc.json", &[])] #[case::v0_7_write("/rpc/v0_7", "v07/starknet_write_api.json", &[])] diff --git a/doc/rpc/v08/starknet_api_openrpc.json b/doc/rpc/v08/starknet_api_openrpc.json new file mode 100644 index 0000000000..cc02a1eb3a --- /dev/null +++ b/doc/rpc/v08/starknet_api_openrpc.json @@ -0,0 +1,3797 @@ +{ + "openrpc": "1.0.0-rc1", + "info": { + "version": "0.7.1", + "title": "StarkNet Node API", + "license": {} + }, + "servers": [], + "methods": [ + { + "name": "starknet_specVersion", + "summary": "Returns the version of the Starknet JSON-RPC specification being used", + "params": [], + "result": { + "name": "result", + "description": "Semver of Starknet's JSON-RPC spec being used", + "required": true, + "schema": { + "title": "JSON-RPC spec version", + "type": "string" + } + } + }, + { + "name": "starknet_getBlockWithTxHashes", + "summary": "Get block information with transaction hashes given the block id", + "params": [ + { + "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" + } + } + ], + "result": { + "name": "result", + "description": "The resulting block information with transaction hashes", + "schema": { + "title": "Starknet get block hash with tx hashes result", + "oneOf": [ + { + "title": "Block with transaction hashes", + "$ref": "#/components/schemas/BLOCK_WITH_TX_HASHES" + }, + { + "title": "Pending block with transaction hashes", + "$ref": "#/components/schemas/PENDING_BLOCK_WITH_TX_HASHES" + } + ] + } + }, + "errors": [ + { + "$ref": "#/components/errors/BLOCK_NOT_FOUND" + } + ] + }, + { + "name": "starknet_getBlockWithTxs", + "summary": "Get block information with full transactions given the block id", + "params": [ + { + "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" + } + } + ], + "result": { + "name": "result", + "description": "The resulting block information with full transactions", + "schema": { + "title": "Starknet get block with txs result", + "oneOf": [ + { + "title": "Block with transactions", + "$ref": "#/components/schemas/BLOCK_WITH_TXS" + }, + { + "title": "Pending block with transactions", + "$ref": "#/components/schemas/PENDING_BLOCK_WITH_TXS" + } + ] + } + }, + "errors": [ + { + "$ref": "#/components/errors/BLOCK_NOT_FOUND" + } + ] + }, + { + "name": "starknet_getBlockWithReceipts", + "summary": "Get block information with full transactions and receipts given the block id", + "params": [ + { + "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" + } + } + ], + "result": { + "name": "result", + "description": "The resulting block information with full transactions", + "schema": { + "title": "Starknet get block with txs and receipts result", + "oneOf": [ + { + "title": "Block with transactions", + "$ref": "#/components/schemas/BLOCK_WITH_RECEIPTS" + }, + { + "title": "Pending block with transactions", + "$ref": "#/components/schemas/PENDING_BLOCK_WITH_RECEIPTS" + } + ] + } + }, + "errors": [ + { + "$ref": "#/components/errors/BLOCK_NOT_FOUND" + } + ] + }, + { + "name": "starknet_getStateUpdate", + "summary": "Get the information about the result of executing the requested block", + "params": [ + { + "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" + } + } + ], + "result": { + "name": "result", + "description": "The information about the state update of the requested block", + "schema": { + "title": "Starknet get state update result", + "oneOf": [ + { + "title": "State update", + "$ref": "#/components/schemas/STATE_UPDATE" + }, + { + "title": "Pending state update", + "$ref": "#/components/schemas/PENDING_STATE_UPDATE" + } + ] + } + }, + "errors": [ + { + "$ref": "#/components/errors/BLOCK_NOT_FOUND" + } + ] + }, + { + "name": "starknet_getStorageAt", + "summary": "Get the value of the storage at the given address and key", + "params": [ + { + "name": "contract_address", + "description": "The address of the contract to read from", + "summary": "The address of the contract to read from", + "required": true, + "schema": { + "title": "Address", + "$ref": "#/components/schemas/ADDRESS" + } + }, + { + "name": "key", + "description": "The key to the storage value for the given contract", + "summary": "The key to the storage value for the given contract", + "required": true, + "schema": { + "title": "Storage key", + "$ref": "#/components/schemas/STORAGE_KEY" + } + }, + { + "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" + } + } + ], + "result": { + "name": "result", + "description": "The value at the given key for the given contract. 0 if no value is found", + "summary": "The value at the given key for the given contract.", + "schema": { + "title": "Field element", + "$ref": "#/components/schemas/FELT" + } + }, + "errors": [ + { + "$ref": "#/components/errors/CONTRACT_NOT_FOUND" + }, + { + "$ref": "#/components/errors/BLOCK_NOT_FOUND" + } + ] + }, + { + "name": "starknet_getTransactionStatus", + "summary": "Gets the transaction status (possibly reflecting that the tx is still in the mempool, or dropped from it)", + "paramStructure": "by-name", + "params": [ + { + "name": "transaction_hash", + "summary": "The hash of the requested transaction", + "required": true, + "schema": { + "title": "Transaction hash", + "$ref": "#/components/schemas/TXN_HASH" + } + } + ], + "result": { + "name": "result", + "schema": { + "$ref": "#/components/schemas/TXN_STATUS_RESULT" + } + }, + "errors": [ + { + "$ref": "#/components/errors/TXN_HASH_NOT_FOUND" + } + ] + }, + { + "name": "starknet_getMessagesStatus", + "summary": "Given an l1 tx hash, returns the associated l1_handler tx hashes and statuses for all L1 -> L2 messages sent by the l1 ransaction, ordered by the l1 tx sending order", + "paramStructure": "by-name", + "params": [ + { + "name": "transaction_hash", + "summary": "The hash of the L1 transaction that sent L1->L2 messages", + "required": true, + "schema": { + "title": "Transaction hash", + "$ref": "#/components/schemas/L1_TXN_HASH" + } + } + ], + "result": { + "name": "result", + "schema": { + "type": "array", + "items": { + "type": "object", + "title": "status", + "properties": { + "transaction_hash": { + "$ref": "#/components/schemas/TXN_HASH" + }, + "finality_status": { + "title": "finality status", + "$ref": "#/components/schemas/TXN_STATUS" + }, + "failure_reason": { + "title": "failure reason", + "description": "the failure reason, only appears if finality_status is REJECTED", + "type": "string" + } + }, + "required": ["transaction_hash", "finality_status"] + } + } + }, + "errors": [ + { + "$ref": "#/components/errors/TXN_HASH_NOT_FOUND" + } + ] + }, + { + "name": "starknet_getTransactionByHash", + "summary": "Get the details and status of a submitted transaction", + "paramStructure": "by-name", + "params": [ + { + "name": "transaction_hash", + "summary": "The hash of the requested transaction", + "required": true, + "schema": { + "title": "Transaction hash", + "$ref": "#/components/schemas/TXN_HASH" + } + } + ], + "result": { + "name": "result", + "schema": { + "title": "Transaction", + "allOf": [ + { + "$ref": "#/components/schemas/TXN" + }, + { + "type": "object", + "properties": { + "transaction_hash": { + "title": "transaction hash", + "$ref": "#/components/schemas/TXN_HASH" + } + }, + "required": ["transaction_hash"] + } + ] + } + }, + "errors": [ + { + "$ref": "#/components/errors/TXN_HASH_NOT_FOUND" + } + ] + }, + { + "name": "starknet_getTransactionByBlockIdAndIndex", + "summary": "Get the details of a transaction by a given block id and index", + "description": "Get the details of the transaction given by the identified block and index in that block. If no transaction is found, null is returned.", + "params": [ + { + "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": "index", + "summary": "The index in the block to search for the transaction", + "required": true, + "schema": { + "title": "Index", + "type": "integer", + "minimum": 0 + } + } + ], + "result": { + "name": "transactionResult", + "schema": { + "title": "Transaction", + "allOf": [ + { + "$ref": "#/components/schemas/TXN" + }, + { + "type": "object", + "properties": { + "transaction_hash": { + "title": "transaction hash", + "$ref": "#/components/schemas/TXN_HASH" + } + }, + "required": ["transaction_hash"] + } + ] + } + }, + "errors": [ + { + "$ref": "#/components/errors/BLOCK_NOT_FOUND" + }, + { + "$ref": "#/components/errors/INVALID_TXN_INDEX" + } + ] + }, + { + "name": "starknet_getTransactionReceipt", + "summary": "Get the transaction receipt by the transaction hash", + "paramStructure": "by-name", + "params": [ + { + "name": "transaction_hash", + "summary": "The hash of the requested transaction", + "required": true, + "schema": { + "title": "Transaction hash", + "$ref": "#/components/schemas/TXN_HASH" + } + } + ], + "result": { + "name": "result", + "schema": { + "title": "Transaction receipt with block info", + "$ref": "#/components/schemas/TXN_RECEIPT_WITH_BLOCK_INFO" + } + }, + "errors": [ + { + "$ref": "#/components/errors/TXN_HASH_NOT_FOUND" + } + ] + }, + { + "name": "starknet_getClass", + "summary": "Get the contract class definition in the given block associated with the given hash", + "params": [ + { + "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_hash", + "description": "The hash of the requested contract class", + "required": true, + "schema": { + "title": "Field element", + "$ref": "#/components/schemas/FELT" + } + } + ], + "result": { + "name": "result", + "description": "The contract class, if found", + "schema": { + "title": "Starknet get class result", + "oneOf": [ + { + "title": "Deprecated contract class", + "$ref": "#/components/schemas/DEPRECATED_CONTRACT_CLASS" + }, + { + "title": "Contract class", + "$ref": "#/components/schemas/CONTRACT_CLASS" + } + ] + } + }, + "errors": [ + { + "$ref": "#/components/errors/BLOCK_NOT_FOUND" + }, + { + "$ref": "#/components/errors/CLASS_HASH_NOT_FOUND" + } + ] + }, + { + "name": "starknet_getClassHashAt", + "summary": "Get the contract class hash in the given block for the contract deployed at the given address", + "params": [ + { + "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": "contract_address", + "description": "The address of the contract whose class hash will be returned", + "required": true, + "schema": { + "title": "Address", + "$ref": "#/components/schemas/ADDRESS" + } + } + ], + "result": { + "name": "result", + "description": "The class hash of the given contract", + "schema": { + "title": "Field element", + "$ref": "#/components/schemas/FELT" + } + }, + "errors": [ + { + "$ref": "#/components/errors/BLOCK_NOT_FOUND" + }, + { + "$ref": "#/components/errors/CONTRACT_NOT_FOUND" + } + ] + }, + { + "name": "starknet_getClassAt", + "summary": "Get the contract class definition in the given block at the given address", + "params": [ + { + "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": "contract_address", + "description": "The address of the contract whose class definition will be returned", + "required": true, + "schema": { + "title": "Address", + "$ref": "#/components/schemas/ADDRESS" + } + } + ], + "result": { + "name": "result", + "description": "The contract class", + "schema": { + "title": "Starknet get class at result", + "oneOf": [ + { + "title": "Deprecated contract class", + "$ref": "#/components/schemas/DEPRECATED_CONTRACT_CLASS" + }, + { + "title": "Contract class", + "$ref": "#/components/schemas/CONTRACT_CLASS" + } + ] + } + }, + "errors": [ + { + "$ref": "#/components/errors/BLOCK_NOT_FOUND" + }, + { + "$ref": "#/components/errors/CONTRACT_NOT_FOUND" + } + ] + }, + { + "name": "starknet_getBlockTransactionCount", + "summary": "Get the number of transactions in a block given a block id", + "description": "Returns the number of transactions in the designated block.", + "params": [ + { + "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" + } + } + ], + "result": { + "name": "result", + "description": "The number of transactions in the designated block", + "summary": "The number of transactions in the designated block", + "schema": { + "title": "Block transaction count", + "type": "integer", + "minimum": 0 + } + }, + "errors": [ + { + "$ref": "#/components/errors/BLOCK_NOT_FOUND" + } + ] + }, + { + "name": "starknet_call", + "summary": "call a starknet function without creating a StarkNet transaction", + "description": "Calls a function in a contract and returns the return value. Using this call will not create a transaction; hence, will not change the state", + "params": [ + { + "name": "request", + "summary": "The details of the function call", + "schema": { + "title": "Function call", + "$ref": "#/components/schemas/FUNCTION_CALL" + }, + "required": true + }, + { + "name": "block_id", + "description": "The hash of the requested block, or number (height) of the requested block, or a block tag, for the block referencing the state or call the transaction on.", + "required": true, + "schema": { + "title": "Block id", + "$ref": "#/components/schemas/BLOCK_ID" + } + } + ], + "result": { + "name": "result", + "summary": "The function's return value", + "description": "The function's return value, as defined in the Cairo output", + "schema": { + "type": "array", + "title": "Field element", + "items": { + "$ref": "#/components/schemas/FELT" + } + } + }, + "errors": [ + { + "$ref": "#/components/errors/CONTRACT_NOT_FOUND" + }, + { + "$ref": "#/components/errors/CONTRACT_ERROR" + }, + { + "$ref": "#/components/errors/BLOCK_NOT_FOUND" + } + ] + }, + { + "name": "starknet_estimateFee", + "summary": "estimate the fee for of StarkNet transactions", + "description": "Estimates the resources required by a given sequence of transactions when applied on a given state. If one of the transactions reverts or fails due to any reason (e.g. validation failure or an internal error), a TRANSACTION_EXECUTION_ERROR is returned. For v0-2 transactions the estimate is given in wei, and for v3 transactions it is given in fri.", + "params": [ + { + "name": "request", + "summary": "The transaction to estimate", + "schema": { + "type": "array", + "description": "a sequence of transactions to estimate, running each transaction on the state resulting from applying all the previous ones", + "title": "Transaction", + "items": { + "$ref": "#/components/schemas/BROADCASTED_TXN" + } + }, + "required": true + }, + { + "name": "simulation_flags", + "description": "describes what parts of the transaction should be executed", + "required": true, + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SIMULATION_FLAG_FOR_ESTIMATE_FEE" + } + } + }, + { + "name": "block_id", + "description": "The hash of the requested block, or number (height) of the requested block, or a block tag, for the block referencing the state or call the transaction on.", + "required": true, + "schema": { + "title": "Block id", + "$ref": "#/components/schemas/BLOCK_ID" + } + } + ], + "result": { + "name": "result", + "description": "the fee estimations", + "schema": { + "title": "Estimation", + "type": "array", + "description": "a sequence of fee estimatione where the i'th estimate corresponds to the i'th transaction", + "items": { + "$ref": "#/components/schemas/FEE_ESTIMATE" + } + } + }, + "errors": [ + { + "$ref": "#/components/errors/TRANSACTION_EXECUTION_ERROR" + }, + { + "$ref": "#/components/errors/BLOCK_NOT_FOUND" + } + ] + }, + { + "name": "starknet_estimateMessageFee", + "summary": "estimate the L2 fee of a message sent on L1", + "description": "estimates the resources required by the l1_handler transaction induced by the message", + "params": [ + { + "name": "message", + "description": "the message's parameters", + "schema": { + "$ref": "#/components/schemas/MSG_FROM_L1" + }, + "required": true + }, + { + "name": "block_id", + "description": "The hash of the requested block, or number (height) of the requested block, or a block tag, for the block referencing the state or call the transaction on.", + "required": true, + "schema": { + "title": "Block id", + "$ref": "#/components/schemas/BLOCK_ID" + } + } + ], + "result": { + "name": "result", + "description": "the fee estimation", + "schema": { + "$ref": "#/components/schemas/FEE_ESTIMATE" + } + }, + "errors": [ + { + "$ref": "#/components/errors/CONTRACT_ERROR" + }, + { + "$ref": "#/components/errors/BLOCK_NOT_FOUND" + } + ] + }, + { + "name": "starknet_blockNumber", + "summary": "Get the most recent accepted block number", + "params": [], + "result": { + "name": "result", + "description": "The latest block number", + "schema": { + "title": "Block number", + "$ref": "#/components/schemas/BLOCK_NUMBER" + } + }, + "errors": [ + { + "$ref": "#/components/errors/NO_BLOCKS" + } + ] + }, + { + "name": "starknet_blockHashAndNumber", + "summary": "Get the most recent accepted block hash and number", + "params": [], + "result": { + "name": "result", + "description": "The latest block hash and number", + "schema": { + "title": "Starknet block hash and number result", + "type": "object", + "properties": { + "block_hash": { + "title": "Block hash", + "$ref": "#/components/schemas/BLOCK_HASH" + }, + "block_number": { + "title": "Block number", + "$ref": "#/components/schemas/BLOCK_NUMBER" + } + }, + "required": ["block_hash", "block_number"] + } + }, + "errors": [ + { + "$ref": "#/components/errors/NO_BLOCKS" + } + ] + }, + { + "name": "starknet_chainId", + "summary": "Return the currently configured StarkNet chain id", + "params": [], + "result": { + "name": "result", + "description": "The chain id this node is connected to", + "schema": { + "title": "Chain id", + "$ref": "#/components/schemas/CHAIN_ID" + } + } + }, + { + "name": "starknet_syncing", + "summary": "Returns an object about the sync status, or false if the node is not synching", + "params": [], + "result": { + "name": "syncing", + "summary": "The state of the synchronization, or false if the node is not synchronizing", + "description": "The status of the node, if it is currently synchronizing state. FALSE otherwise", + "schema": { + "title": "SyncingStatus", + "oneOf": [ + { + "type": "boolean", + "title": "False", + "description": "only legal value is FALSE here" + }, + { + "title": "Sync status", + "$ref": "#/components/schemas/SYNC_STATUS" + } + ] + } + } + }, + { + "name": "starknet_getEvents", + "summary": "Returns all events matching the given filter", + "description": "Returns all event objects matching the conditions in the provided filter", + "params": [ + { + "name": "filter", + "summary": "The conditions used to filter the returned events", + "required": true, + "schema": { + "title": "Events request", + "allOf": [ + { + "title": "Event filter", + "$ref": "#/components/schemas/EVENT_FILTER" + }, + { + "title": "Result page request", + "$ref": "#/components/schemas/RESULT_PAGE_REQUEST" + } + ] + } + } + ], + "result": { + "name": "events", + "description": "All the event objects matching the filter", + "schema": { + "title": "Events chunk", + "$ref": "#/components/schemas/EVENTS_CHUNK" + } + }, + "errors": [ + { + "$ref": "#/components/errors/PAGE_SIZE_TOO_BIG" + }, + { + "$ref": "#/components/errors/INVALID_CONTINUATION_TOKEN" + }, + { + "$ref": "#/components/errors/BLOCK_NOT_FOUND" + }, + { + "$ref": "#/components/errors/TOO_MANY_KEYS_IN_FILTER" + } + ] + }, + { + "name": "starknet_getNonce", + "summary": "Get the nonce associated with the given address in the given block", + "params": [ + { + "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": "contract_address", + "description": "The address of the contract whose nonce we're seeking", + "required": true, + "schema": { + "title": "Address", + "$ref": "#/components/schemas/ADDRESS" + } + } + ], + "result": { + "name": "result", + "description": "The contract's nonce at the requested state", + "schema": { + "title": "Field element", + "$ref": "#/components/schemas/FELT" + } + }, + "errors": [ + { + "$ref": "#/components/errors/BLOCK_NOT_FOUND" + }, + { + "$ref": "#/components/errors/CONTRACT_NOT_FOUND" + } + ] + }, + { + "name": "starknet_getStorageProof", + "summary": "get merkle paths in one of the state tries: global state, classes, individual contract", + "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": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FELT" + } + } + } + } + } + } + ], + "result": { + "name": "result", + "description": "The contract's nonce at the requested state", + "schema": { + "type": "object", + "properties": { + "classes_proof": { + "$ref": "#/components/schemas/NODE_HASH_TO_NODE_MAPPING" + }, + "contracts_proof": { + "$ref": "#/components/schemas/NODE_HASH_TO_NODE_MAPPING" + }, + "contracts_storage_proofs": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NODE_HASH_TO_NODE_MAPPING" + } + } + } + } + } + } + ], + "components": { + "contentDescriptors": {}, + "schemas": { + "EVENTS_CHUNK": { + "title": "Events chunk", + "type": "object", + "properties": { + "events": { + "type": "array", + "title": "Matching Events", + "items": { + "$ref": "#/components/schemas/EMITTED_EVENT" + } + }, + "continuation_token": { + "title": "Continuation token", + "description": "Use this token in a subsequent query to obtain the next page. Should not appear if there are no more pages.", + "type": "string" + } + }, + "required": ["events"] + }, + "RESULT_PAGE_REQUEST": { + "title": "Result page request", + "type": "object", + "properties": { + "continuation_token": { + "title": "Continuation token", + "description": "The token returned from the previous query. If no token is provided the first page is returned.", + "type": "string" + }, + "chunk_size": { + "title": "Chunk size", + "type": "integer", + "minimum": 1 + } + }, + "required": ["chunk_size"] + }, + "EMITTED_EVENT": { + "title": "Emitted event", + "description": "Event information decorated with metadata on where it was emitted / An event emitted as a result of transaction execution", + "allOf": [ + { + "title": "Event", + "description": "The event information", + "$ref": "#/components/schemas/EVENT" + }, + { + "title": "Event context", + "description": "The event emission information", + "type": "object", + "properties": { + "block_hash": { + "title": "Block hash", + "description": "The hash of the block in which the event was emitted", + "$ref": "#/components/schemas/BLOCK_HASH" + }, + "block_number": { + "title": "Block number", + "description": "The number of the block in which the event was emitted", + "$ref": "#/components/schemas/BLOCK_NUMBER" + }, + "transaction_hash": { + "title": "Transaction hash", + "description": "The transaction that emitted the event", + "$ref": "#/components/schemas/TXN_HASH" + } + }, + "required": ["transaction_hash"] + } + ] + }, + "EVENT": { + "title": "Event", + "description": "A StarkNet event", + "allOf": [ + { + "title": "Event emitter", + "type": "object", + "properties": { + "from_address": { + "title": "From address", + "$ref": "#/components/schemas/ADDRESS" + } + }, + "required": ["from_address"] + }, + { + "title": "Event content", + "$ref": "#/components/schemas/EVENT_CONTENT" + } + ] + }, + "EVENT_CONTENT": { + "title": "Event content", + "description": "The content of an event", + "type": "object", + "properties": { + "keys": { + "type": "array", + "title": "Keys", + "items": { + "$ref": "#/components/schemas/FELT" + } + }, + "data": { + "type": "array", + "title": "Data", + "items": { + "$ref": "#/components/schemas/FELT" + } + } + }, + "required": ["keys", "data"] + }, + "EVENT_KEYS": { + "title": "Keys", + "description": "The keys to filter over", + "type": "array", + "items": { + "title": "Keys", + "description": "Per key (by position), designate the possible values to be matched for events to be returned. Empty array designates 'any' value", + "type": "array", + "items": { + "$ref": "#/components/schemas/FELT" + } + } + }, + "EVENT_FILTER": { + "title": "Event filter", + "description": "An event filter/query", + "type": "object", + "properties": { + "from_block": { + "title": "from block", + "$ref": "#/components/schemas/BLOCK_ID" + }, + "to_block": { + "title": "to block", + "$ref": "#/components/schemas/BLOCK_ID" + }, + "address": { + "title": "from contract", + "$ref": "#/components/schemas/ADDRESS" + }, + "keys": { + "title": "event keys", + "description": "The keys to filter over", + "$ref": "#/components/schemas/EVENT_KEYS" + + } + }, + "required": [] + }, + "BLOCK_ID": { + "title": "Block id", + "description": "Block hash, number or tag", + "oneOf": [ + { + "title": "Block hash", + "type": "object", + "properties": { + "block_hash": { + "title": "Block hash", + "$ref": "#/components/schemas/BLOCK_HASH" + } + }, + "required": ["block_hash"] + }, + { + "title": "Block number", + "type": "object", + "properties": { + "block_number": { + "title": "Block number", + "$ref": "#/components/schemas/BLOCK_NUMBER" + } + }, + "required": ["block_number"] + }, + { + "title": "Block tag", + "$ref": "#/components/schemas/BLOCK_TAG" + } + ] + }, + "BLOCK_TAG": { + "title": "Block tag", + "type": "string", + "description": "A tag specifying a dynamic reference to a block", + "enum": ["latest", "pending"] + }, + "SYNC_STATUS": { + "title": "Sync status", + "type": "object", + "description": "An object describing the node synchronization status", + "properties": { + "starting_block_hash": { + "title": "Starting block hash", + "description": "The hash of the block from which the sync started", + "$ref": "#/components/schemas/BLOCK_HASH" + }, + "starting_block_num": { + "title": "Starting block number", + "description": "The number (height) of the block from which the sync started", + "$ref": "#/components/schemas/BLOCK_NUMBER" + }, + "current_block_hash": { + "title": "Current block hash", + "description": "The hash of the current block being synchronized", + "$ref": "#/components/schemas/BLOCK_HASH" + }, + "current_block_num": { + "title": "Current block number", + "description": "The number (height) of the current block being synchronized", + "$ref": "#/components/schemas/BLOCK_NUMBER" + }, + "highest_block_hash": { + "title": "Highest block hash", + "description": "The hash of the estimated highest block to be synchronized", + "$ref": "#/components/schemas/BLOCK_HASH" + }, + "highest_block_num": { + "title": "Highest block number", + "description": "The number (height) of the estimated highest block to be synchronized", + "$ref": "#/components/schemas/BLOCK_NUMBER" + } + }, + "required": [ + "starting_block_hash", + "starting_block_num", + "current_block_hash", + "current_block_num", + "highest_block_hash", + "highest_block_num" + ] + }, + "NUM_AS_HEX": { + "title": "Number as hex", + "description": "An integer number in hex format (0x...)", + "type": "string", + "pattern": "^0x[a-fA-F0-9]+$" + }, + "u64": { + "type": "string", + "title": "u64", + "description": "64 bit integers, represented by hex string of length at most 16", + "pattern": "^0x(0|[a-fA-F1-9]{1}[a-fA-F0-9]{0,15})$" + }, + "u128": { + "type": "string", + "title": "u128", + "description": "64 bit integers, represented by hex string of length at most 32", + "pattern": "^0x(0|[a-fA-F1-9]{1}[a-fA-F0-9]{0,31})$" + }, + "CHAIN_ID": { + "title": "Chain id", + "description": "StarkNet chain id, given in hex representation.", + "type": "string", + "pattern": "^0x[a-fA-F0-9]+$" + }, + "STATE_DIFF": { + "description": "The change in state applied in this block, given as a mapping of addresses to the new values and/or new contracts", + "type": "object", + "properties": { + "storage_diffs": { + "title": "Storage diffs", + "type": "array", + "items": { + "description": "The changes in the storage per contract address", + "$ref": "#/components/schemas/CONTRACT_STORAGE_DIFF_ITEM" + } + }, + "deprecated_declared_classes": { + "title": "Deprecated declared classes", + "type": "array", + "items": { + "description": "The hash of the declared class", + "$ref": "#/components/schemas/FELT" + } + }, + "declared_classes": { + "title": "Declared classes", + "type": "array", + "items": { + "title": "New classes", + "type": "object", + "description": "The declared class hash and compiled class hash", + "properties": { + "class_hash": { + "title": "Class hash", + "description": "The hash of the declared class", + "$ref": "#/components/schemas/FELT" + }, + "compiled_class_hash": { + "title": "Compiled class hash", + "description": "The Cairo assembly hash corresponding to the declared class", + "$ref": "#/components/schemas/FELT" + } + } + } + }, + "deployed_contracts": { + "title": "Deployed contracts", + "type": "array", + "items": { + "description": "A new contract deployed as part of the state update", + "$ref": "#/components/schemas/DEPLOYED_CONTRACT_ITEM" + } + }, + "replaced_classes": { + "title": "Replaced classes", + "type": "array", + "items": { + "description": "The list of contracts whose class was replaced", + "title": "Replaced class", + "type": "object", + "properties": { + "contract_address": { + "title": "Contract address", + "description": "The address of the contract whose class was replaced", + "$ref": "#/components/schemas/ADDRESS" + }, + "class_hash": { + "title": "Class hash", + "description": "The new class hash", + "$ref": "#/components/schemas/FELT" + } + } + } + }, + "nonces": { + "title": "Nonces", + "type": "array", + "items": { + "title": "Nonce update", + "description": "The updated nonce per contract address", + "type": "object", + "properties": { + "contract_address": { + "title": "Contract address", + "description": "The address of the contract", + "$ref": "#/components/schemas/ADDRESS" + }, + "nonce": { + "title": "Nonce", + "description": "The nonce for the given address at the end of the block", + "$ref": "#/components/schemas/FELT" + } + } + } + } + }, + "required": [ + "storage_diffs", + "deprecated_declared_classes", + "declared_classes", + "replaced_classes", + "deployed_contracts", + "nonces" + ] + }, + "PENDING_STATE_UPDATE": { + "title": "Pending state update", + "description": "Pending state update", + "type": "object", + "properties": { + "old_root": { + "title": "Old root", + "description": "The previous global state root", + "$ref": "#/components/schemas/FELT" + }, + "state_diff": { + "title": "State diff", + "$ref": "#/components/schemas/STATE_DIFF" + } + }, + "required": ["old_root", "state_diff"], + "additionalProperties": false + }, + "STATE_UPDATE": { + "title": "State update", + "type": "object", + "properties": { + "block_hash": { + "title": "Block hash", + "$ref": "#/components/schemas/BLOCK_HASH" + }, + "old_root": { + "title": "Old root", + "description": "The previous global state root", + "$ref": "#/components/schemas/FELT" + }, + "new_root": { + "title": "New root", + "description": "The new global state root", + "$ref": "#/components/schemas/FELT" + }, + "state_diff": { + "title": "State diff", + "$ref": "#/components/schemas/STATE_DIFF" + } + }, + "required": ["state_diff", "block_hash", "old_root", "new_root"] + }, + "ADDRESS": { + "title": "Address", + "$ref": "#/components/schemas/FELT" + }, + "STORAGE_KEY": { + "type": "string", + "title": "Storage key", + "$comment": "A storage key, represented as a string of hex digits", + "description": "A storage key. Represented as up to 62 hex digits, 3 bits, and 5 leading zeroes.", + "pattern": "^0x(0|[0-7]{1}[a-fA-F0-9]{0,62}$)" + }, + "ETH_ADDRESS": { + "title": "Ethereum address", + "type": "string", + "$comment": "An ethereum address", + "description": "an ethereum address represented as 40 hex digits", + "pattern": "^0x[a-fA-F0-9]{40}$" + }, + "TXN_HASH": { + "$ref": "#/components/schemas/FELT", + "description": "The hash of a Starknet transaction", + "title": "Transaction hash" + }, + "L1_TXN_HASH": { + "$ref": "#/components/schemas/NUM_AS_HEX", + "description": "The hash of an Ethereum transaction" + }, + "FELT": { + "type": "string", + "title": "Field element", + "description": "A field element. represented by at most 63 hex digits", + "pattern": "^0x(0|[a-fA-F1-9]{1}[a-fA-F0-9]{0,62})$" + }, + "BLOCK_NUMBER": { + "title": "Block number", + "description": "The block's number (its height)", + "type": "integer", + "minimum": 0 + }, + "BLOCK_HASH": { + "title": "Block hash", + "$ref": "#/components/schemas/FELT" + }, + "BLOCK_BODY_WITH_TX_HASHES": { + "title": "Block body with transaction hashes", + "type": "object", + "properties": { + "transactions": { + "title": "Transaction hashes", + "description": "The hashes of the transactions included in this block", + "type": "array", + "items": { + "description": "The hash of a single transaction", + "$ref": "#/components/schemas/TXN_HASH" + } + } + }, + "required": ["transactions"] + }, + "BLOCK_BODY_WITH_TXS": { + "title": "Block body with transactions", + "type": "object", + "properties": { + "transactions": { + "title": "Transactions", + "description": "The transactions in this block", + "type": "array", + "items": { + "title": "transactions in block", + "type": "object", + "allOf": [ + { + "title": "transaction", + "$ref": "#/components/schemas/TXN" + }, + { + "type": "object", + "properties": { + "transaction_hash": { + "title": "transaction hash", + "$ref": "#/components/schemas/TXN_HASH" + } + }, + "required": ["transaction_hash"] + } + ] + } + } + }, + "required": ["transactions"] + }, + "BLOCK_BODY_WITH_RECEIPTS": { + "title": "Block body with transactions and receipts", + "type": "object", + "properties": { + "transactions": { + "title": "Transactions", + "description": "The transactions in this block", + "type": "array", + "items": { + "type": "object", + "title": "transaction and receipt", + "properties": { + "transaction": { + "title": "transaction", + "$ref": "#/components/schemas/TXN" + }, + "receipt": { + "title": "receipt", + "$ref": "#/components/schemas/TXN_RECEIPT" + } + }, + "required": ["transaction", "receipt"] + } + } + }, + "required": ["transactions"] + }, + "BLOCK_HEADER": { + "title": "Block header", + "type": "object", + "properties": { + "block_hash": { + "title": "Block hash", + "$ref": "#/components/schemas/BLOCK_HASH" + }, + "parent_hash": { + "title": "Parent hash", + "description": "The hash of this block's parent", + "$ref": "#/components/schemas/BLOCK_HASH" + }, + "block_number": { + "title": "Block number", + "description": "The block number (its height)", + "$ref": "#/components/schemas/BLOCK_NUMBER" + }, + "new_root": { + "title": "New root", + "description": "The new global state root", + "$ref": "#/components/schemas/FELT" + }, + "timestamp": { + "title": "Timestamp", + "description": "The time in which the block was created, encoded in Unix time", + "type": "integer", + "minimum": 0 + }, + "sequencer_address": { + "title": "Sequencer address", + "description": "The StarkNet identity of the sequencer submitting this block", + "$ref": "#/components/schemas/FELT" + }, + "l1_gas_price": { + "title": "L1 gas price", + "description": "The price of l1 gas in the block", + "$ref": "#/components/schemas/RESOURCE_PRICE" + }, + "l2_gas_price": { + "title": "L2 gas price", + "description": "The price of l2 gas in the block", + "$ref": "#/components/schemas/RESOURCE_PRICE" + }, + "l1_data_gas_price": { + "title": "L1 data gas price", + "description": "The price of l1 data gas in the block", + "$ref": "#/components/schemas/RESOURCE_PRICE" + }, + "l1_da_mode": { + "title": "L1 da mode", + "type": "string", + "description": "specifies whether the data of this block is published via blob data or calldata", + "enum": ["BLOB", "CALLDATA"] + }, + "starknet_version": { + "title": "Starknet version", + "description": "Semver of the current Starknet protocol", + "type": "string" + } + }, + "required": [ + "block_hash", + "parent_hash", + "block_number", + "new_root", + "timestamp", + "sequencer_address", + "l1_gas_price", + "l1_data_gas_price", + "l1_da_mode", + "starknet_version" + ] + }, + "PENDING_BLOCK_HEADER": { + "title": "Pending block header", + "type": "object", + "properties": { + "parent_hash": { + "title": "Parent hash", + "description": "The hash of this block's parent", + "$ref": "#/components/schemas/BLOCK_HASH" + }, + "timestamp": { + "title": "Timestamp", + "description": "The time in which the block was created, encoded in Unix time", + "type": "integer", + "minimum": 0 + }, + "sequencer_address": { + "title": "Sequencer address", + "description": "The StarkNet identity of the sequencer submitting this block", + "$ref": "#/components/schemas/FELT" + }, + "l1_gas_price": { + "title": "L1 gas price", + "description": "The price of l1 gas in the block", + "$ref": "#/components/schemas/RESOURCE_PRICE" + }, + "l1_data_gas_price": { + "title": "L1 data gas price", + "description": "The price of l1 data gas in the block", + "$ref": "#/components/schemas/RESOURCE_PRICE" + }, + "l1_da_mode": { + "title": "L1 da mode", + "type": "string", + "description": "specifies whether the data of this block is published via blob data or calldata", + "enum": ["BLOB", "CALLDATA"] + }, + "starknet_version": { + "title": "Starknet version", + "description": "Semver of the current Starknet protocol", + "type": "string" + } + }, + "required": [ + "parent_hash", + "timestamp", + "sequencer_address", + "l1_gas_price", + "l1_data_gas_price", + "l1_da_mode", + "starknet_version" + ], + "not": { + "required": ["block_hash", "block_number", "new_root"] + } + }, + "BLOCK_WITH_TX_HASHES": { + "title": "Block with transaction hashes", + "description": "The block object", + "allOf": [ + { + "title": "Block status", + "type": "object", + "properties": { + "status": { + "title": "Status", + "$ref": "#/components/schemas/BLOCK_STATUS" + } + }, + "required": ["status"] + }, + { + "title": "Block header", + "$ref": "#/components/schemas/BLOCK_HEADER" + }, + { + "title": "Block body with transaction hashes", + "$ref": "#/components/schemas/BLOCK_BODY_WITH_TX_HASHES" + } + ] + }, + "BLOCK_WITH_TXS": { + "title": "Block with transactions", + "description": "The block object", + "allOf": [ + { + "title": "block with txs", + "type": "object", + "properties": { + "status": { + "title": "Status", + "$ref": "#/components/schemas/BLOCK_STATUS" + } + }, + "required": ["status"] + }, + { + "title": "Block header", + "$ref": "#/components/schemas/BLOCK_HEADER" + }, + { + "title": "Block body with transactions", + "$ref": "#/components/schemas/BLOCK_BODY_WITH_TXS" + } + ] + }, + "BLOCK_WITH_RECEIPTS": { + "title": "Block with transactions and receipts", + "description": "The block object", + "allOf": [ + { + "title": "block with txs", + "type": "object", + "properties": { + "status": { + "title": "Status", + "$ref": "#/components/schemas/BLOCK_STATUS" + } + }, + "required": ["status"] + }, + { + "title": "Block header", + "$ref": "#/components/schemas/BLOCK_HEADER" + }, + { + "title": "Block body with transactions and receipts", + "$ref": "#/components/schemas/BLOCK_BODY_WITH_RECEIPTS" + } + ] + }, + "PENDING_BLOCK_WITH_TX_HASHES": { + "title": "Pending block with transaction hashes", + "description": "The dynamic block being constructed by the sequencer. Note that this object will be deprecated upon decentralization.", + "allOf": [ + { + "title": "Block body with transactions hashes", + "$ref": "#/components/schemas/BLOCK_BODY_WITH_TX_HASHES" + }, + { + "title": "Pending block header", + "$ref": "#/components/schemas/PENDING_BLOCK_HEADER" + } + ] + }, + "PENDING_BLOCK_WITH_TXS": { + "title": "Pending block with transactions", + "description": "The dynamic block being constructed by the sequencer. Note that this object will be deprecated upon decentralization.", + "allOf": [ + { + "title": "Block body with transactions", + "$ref": "#/components/schemas/BLOCK_BODY_WITH_TXS" + }, + { + "title": "Pending block header", + "$ref": "#/components/schemas/PENDING_BLOCK_HEADER" + } + ] + }, + "PENDING_BLOCK_WITH_RECEIPTS": { + "title": "Pending block with transactions and receipts", + "description": "The dynamic block being constructed by the sequencer. Note that this object will be deprecated upon decentralization.", + "allOf": [ + { + "title": "Block body with transactions and receipts", + "$ref": "#/components/schemas/BLOCK_BODY_WITH_RECEIPTS" + }, + { + "title": "Pending block header", + "$ref": "#/components/schemas/PENDING_BLOCK_HEADER" + } + ] + }, + "DEPLOYED_CONTRACT_ITEM": { + "title": "Deployed contract item", + "type": "object", + "properties": { + "address": { + "title": "Address", + "description": "The address of the contract", + "$ref": "#/components/schemas/FELT" + }, + "class_hash": { + "title": "Class hash", + "description": "The hash of the contract code", + "$ref": "#/components/schemas/FELT" + } + }, + "required": ["address", "class_hash"] + }, + "CONTRACT_STORAGE_DIFF_ITEM": { + "title": "Contract storage diff item", + "type": "object", + "properties": { + "address": { + "title": "Address", + "description": "The contract address for which the storage changed", + "$ref": "#/components/schemas/FELT" + }, + "storage_entries": { + "title": "Storage entries", + "description": "The changes in the storage of the contract", + "type": "array", + "items": { + "title": "Storage diff item", + "type": "object", + "properties": { + "key": { + "title": "Key", + "description": "The key of the changed value", + "$ref": "#/components/schemas/FELT" + }, + "value": { + "title": "Value", + "description": "The new value applied to the given address", + "$ref": "#/components/schemas/FELT" + } + } + } + } + }, + "required": ["address", "storage_entries"] + }, + "TXN": { + "title": "Transaction", + "description": "The transaction schema, as it appears inside a block", + "oneOf": [ + { + "title": "Invoke transaction", + "$ref": "#/components/schemas/INVOKE_TXN" + }, + { + "title": "L1 handler transaction", + "$ref": "#/components/schemas/L1_HANDLER_TXN" + }, + { + "title": "Declare transaction", + "$ref": "#/components/schemas/DECLARE_TXN" + }, + { + "title": "Deploy transaction", + "$ref": "#/components/schemas/DEPLOY_TXN" + }, + { + "title": "Deploy account transaction", + "$ref": "#/components/schemas/DEPLOY_ACCOUNT_TXN" + } + ] + }, + "SIGNATURE": { + "title": "Signature", + "description": "A transaction signature", + "type": "array", + "items": { + "$ref": "#/components/schemas/FELT" + } + }, + "DECLARE_TXN": { + "title": "Declare transaction", + "oneOf": [ + { + "title": "Declare transaction V0", + "$ref": "#/components/schemas/DECLARE_TXN_V0" + }, + { + "title": "Declare transaction V1", + "$ref": "#/components/schemas/DECLARE_TXN_V1" + }, + { + "title": "Declare transaction V2", + "$ref": "#/components/schemas/DECLARE_TXN_V2" + }, + { + "title": "Declare transaction V3", + "$ref": "#/components/schemas/DECLARE_TXN_V3" + } + ] + }, + "DECLARE_TXN_V0": { + "title": "Declare Contract Transaction V0", + "description": "Declare Contract Transaction V0", + "allOf": [ + { + "type": "object", + "title": "Declare txn v0", + "properties": { + "type": { + "title": "Declare", + "type": "string", + "enum": ["DECLARE"] + }, + "sender_address": { + "title": "Sender address", + "description": "The address of the account contract sending the declaration transaction", + "$ref": "#/components/schemas/ADDRESS" + }, + "max_fee": { + "title": "Max fee", + "$ref": "#/components/schemas/FELT", + "description": "The maximal fee that can be charged for including the transaction" + }, + "version": { + "title": "Version", + "description": "Version of the transaction scheme", + "type": "string", + "enum": ["0x0", "0x100000000000000000000000000000000"] + }, + "signature": { + "title": "Signature", + "$ref": "#/components/schemas/SIGNATURE" + }, + "class_hash": { + "title": "Class hash", + "description": "The hash of the declared class", + "$ref": "#/components/schemas/FELT" + } + }, + "required": ["type", "sender_address", "max_fee", "version", "signature", "class_hash"] + } + ] + }, + "DECLARE_TXN_V1": { + "title": "Declare Contract Transaction V1", + "description": "Declare Contract Transaction V1", + "allOf": [ + { + "type": "object", + "title": "Declare txn v1", + "properties": { + "type": { + "title": "Declare", + "type": "string", + "enum": ["DECLARE"] + }, + "sender_address": { + "title": "Sender address", + "description": "The address of the account contract sending the declaration transaction", + "$ref": "#/components/schemas/ADDRESS" + }, + "max_fee": { + "title": "Max fee", + "$ref": "#/components/schemas/FELT", + "description": "The maximal fee that can be charged for including the transaction" + }, + "version": { + "title": "Version", + "description": "Version of the transaction scheme", + "type": "string", + "enum": ["0x1", "0x100000000000000000000000000000001"] + }, + "signature": { + "title": "Signature", + "$ref": "#/components/schemas/SIGNATURE" + }, + "nonce": { + "title": "Nonce", + "$ref": "#/components/schemas/FELT" + }, + "class_hash": { + "title": "Class hash", + "description": "The hash of the declared class", + "$ref": "#/components/schemas/FELT" + } + }, + "required": ["type", "sender_address", "max_fee", "version", "signature", "nonce", "class_hash"] + } + ] + }, + "DECLARE_TXN_V2": { + "title": "Declare Transaction V2", + "description": "Declare Contract Transaction V2", + "allOf": [ + { + "type": "object", + "title": "Declare txn v2", + "properties": { + "type": { + "title": "Declare", + "type": "string", + "enum": ["DECLARE"] + }, + "sender_address": { + "title": "Sender address", + "description": "The address of the account contract sending the declaration transaction", + "$ref": "#/components/schemas/ADDRESS" + }, + "compiled_class_hash": { + "title": "Compiled class hash", + "description": "The hash of the Cairo assembly resulting from the Sierra compilation", + "$ref": "#/components/schemas/FELT" + }, + "max_fee": { + "title": "Max fee", + "$ref": "#/components/schemas/FELT", + "description": "The maximal fee that can be charged for including the transaction" + }, + "version": { + "title": "Version", + "description": "Version of the transaction scheme", + "type": "string", + "enum": ["0x2", "0x100000000000000000000000000000002"] + }, + "signature": { + "title": "Signature", + "$ref": "#/components/schemas/SIGNATURE" + }, + "nonce": { + "title": "Nonce", + "$ref": "#/components/schemas/FELT" + }, + "class_hash": { + "title": "Class hash", + "description": "The hash of the declared class", + "$ref": "#/components/schemas/FELT" + } + }, + "required": [ + "type", + "sender_address", + "compiled_class_hash", + "max_fee", + "version", + "signature", + "nonce", + "class_hash" + ] + } + ] + }, + "DECLARE_TXN_V3": { + "title": "Declare Transaction V3", + "description": "Declare Contract Transaction V3", + "allOf": [ + { + "type": "object", + "title": "Declare txn v3", + "properties": { + "type": { + "title": "Declare", + "type": "string", + "enum": ["DECLARE"] + }, + "sender_address": { + "title": "Sender address", + "description": "The address of the account contract sending the declaration transaction", + "$ref": "#/components/schemas/ADDRESS" + }, + "compiled_class_hash": { + "title": "Compiled class hash", + "description": "The hash of the Cairo assembly resulting from the Sierra compilation", + "$ref": "#/components/schemas/FELT" + }, + "version": { + "title": "Version", + "description": "Version of the transaction scheme", + "type": "string", + "enum": ["0x3", "0x100000000000000000000000000000003"] + }, + "signature": { + "title": "Signature", + "$ref": "#/components/schemas/SIGNATURE" + }, + "nonce": { + "title": "Nonce", + "$ref": "#/components/schemas/FELT" + }, + "class_hash": { + "title": "Class hash", + "description": "The hash of the declared class", + "$ref": "#/components/schemas/FELT" + }, + "resource_bounds": { + "title": "Resource bounds", + "description": "resource bounds for the transaction execution", + "$ref": "#/components/schemas/RESOURCE_BOUNDS_MAPPING" + }, + "tip": { + "title": "Tip", + "$ref": "#/components/schemas/u64", + "description": "the tip for the transaction" + }, + "paymaster_data": { + "title": "Paymaster data", + "type": "array", + "items": { + "$ref": "#/components/schemas/FELT" + }, + "description": "data needed to allow the paymaster to pay for the transaction in native tokens" + }, + "account_deployment_data": { + "title": "Account deployment data", + "type": "array", + "items": { + "$ref": "#/components/schemas/FELT" + }, + "description": "data needed to deploy the account contract from which this tx will be initiated" + }, + "nonce_data_availability_mode": { + "title": "Nonce DA mode", + "description": "The storage domain of the account's nonce (an account has a nonce per DA mode)", + "$ref": "#/components/schemas/DA_MODE" + }, + "fee_data_availability_mode": { + "title": "Fee DA mode", + "description": "The storage domain of the account's balance from which fee will be charged", + "$ref": "#/components/schemas/DA_MODE" + } + }, + "required": [ + "type", + "sender_address", + "compiled_class_hash", + "version", + "signature", + "nonce", + "class_hash", + "resource_bounds", + "tip", + "paymaster_data", + "account_deployment_data", + "nonce_data_availability_mode", + "fee_data_availability_mode" + ] + } + ] + }, + "BROADCASTED_TXN": { + "oneOf": [ + { + "$ref": "#/components/schemas/BROADCASTED_INVOKE_TXN" + }, + { + "$ref": "#/components/schemas/BROADCASTED_DECLARE_TXN" + }, + { + "$ref": "#/components/schemas/BROADCASTED_DEPLOY_ACCOUNT_TXN" + } + ] + }, + "BROADCASTED_INVOKE_TXN": { + "title": "Broadcasted invoke transaction", + "$ref": "#/components/schemas/INVOKE_TXN" + }, + "BROADCASTED_DEPLOY_ACCOUNT_TXN": { + "title": "Broadcasted deploy account transaction", + "$ref": "#/components/schemas/DEPLOY_ACCOUNT_TXN" + }, + "BROADCASTED_DECLARE_TXN": { + "title": "Broadcasted declare transaction", + "oneOf": [ + { + "title": "Broadcasted declare transaction V1", + "$ref": "#/components/schemas/BROADCASTED_DECLARE_TXN_V1" + }, + { + "title": "Broadcasted declare transaction V2", + "$ref": "#/components/schemas/BROADCASTED_DECLARE_TXN_V2" + }, + { + "title": "Broadcasted declare transaction V3", + "$ref": "#/components/schemas/BROADCASTED_DECLARE_TXN_V3" + } + ] + }, + "BROADCASTED_DECLARE_TXN_V1": { + "title": "Broadcasted declare contract transaction V1", + "allOf": [ + { + "type": "object", + "title": "Declare txn v1", + "properties": { + "type": { + "title": "Declare", + "type": "string", + "enum": ["DECLARE"] + }, + "sender_address": { + "title": "Sender address", + "description": "The address of the account contract sending the declaration transaction", + "$ref": "#/components/schemas/ADDRESS" + }, + "max_fee": { + "title": "Max fee", + "$ref": "#/components/schemas/FELT", + "description": "The maximal fee that can be charged for including the transaction" + }, + "version": { + "title": "Version", + "description": "Version of the transaction scheme", + "type": "string", + "enum": ["0x1", "0x100000000000000000000000000000001"] + }, + "signature": { + "title": "Signature", + "$ref": "#/components/schemas/SIGNATURE" + }, + "nonce": { + "title": "Nonce", + "$ref": "#/components/schemas/FELT" + }, + "contract_class": { + "title": "Contract class", + "description": "The class to be declared", + "$ref": "#/components/schemas/DEPRECATED_CONTRACT_CLASS" + } + }, + "required": ["type", "sender_address", "max_fee", "version", "signature", "nonce", "contract_class"] + } + ] + }, + "BROADCASTED_DECLARE_TXN_V2": { + "title": "Broadcasted declare Transaction V2", + "description": "Broadcasted declare Contract Transaction V2", + "allOf": [ + { + "type": "object", + "title": "Declare txn v2", + "properties": { + "type": { + "title": "Declare", + "type": "string", + "enum": ["DECLARE"] + }, + "sender_address": { + "title": "Sender address", + "description": "The address of the account contract sending the declaration transaction", + "$ref": "#/components/schemas/ADDRESS" + }, + "compiled_class_hash": { + "title": "Compiled class hash", + "description": "The hash of the Cairo assembly resulting from the Sierra compilation", + "$ref": "#/components/schemas/FELT" + }, + "max_fee": { + "title": "Max fee", + "$ref": "#/components/schemas/FELT", + "description": "The maximal fee that can be charged for including the transaction" + }, + "version": { + "title": "Version", + "description": "Version of the transaction scheme", + "type": "string", + "enum": ["0x2", "0x100000000000000000000000000000002"] + }, + "signature": { + "title": "Signature", + "$ref": "#/components/schemas/SIGNATURE" + }, + "nonce": { + "title": "Nonce", + "$ref": "#/components/schemas/FELT" + }, + "contract_class": { + "title": "Contract class", + "description": "The class to be declared", + "$ref": "#/components/schemas/CONTRACT_CLASS" + } + }, + "required": [ + "type", + "sender_address", + "compiled_class_hash", + "max_fee", + "version", + "signature", + "nonce", + "contract_class" + ] + } + ] + }, + "BROADCASTED_DECLARE_TXN_V3": { + "title": "Broadcasted declare Transaction V3", + "description": "Broadcasted declare Contract Transaction V3", + "allOf": [ + { + "type": "object", + "title": "Declare txn v3", + "properties": { + "type": { + "title": "Declare", + "type": "string", + "enum": ["DECLARE"] + }, + "sender_address": { + "title": "Sender address", + "description": "The address of the account contract sending the declaration transaction", + "$ref": "#/components/schemas/ADDRESS" + }, + "compiled_class_hash": { + "title": "Compiled class hash", + "description": "The hash of the Cairo assembly resulting from the Sierra compilation", + "$ref": "#/components/schemas/FELT" + }, + "version": { + "title": "Version", + "description": "Version of the transaction scheme", + "type": "string", + "enum": ["0x3", "0x100000000000000000000000000000003"] + }, + "signature": { + "title": "Signature", + "$ref": "#/components/schemas/SIGNATURE" + }, + "nonce": { + "title": "Nonce", + "$ref": "#/components/schemas/FELT" + }, + "contract_class": { + "title": "Contract class", + "description": "The class to be declared", + "$ref": "#/components/schemas/CONTRACT_CLASS" + }, + "resource_bounds": { + "title": "Resource bounds", + "description": "resource bounds for the transaction execution", + "$ref": "#/components/schemas/RESOURCE_BOUNDS_MAPPING" + }, + "tip": { + "title": "Tip", + "$ref": "#/components/schemas/u64", + "description": "the tip for the transaction" + }, + "paymaster_data": { + "title": "Paymaster data", + "type": "array", + "items": { + "$ref": "#/components/schemas/FELT" + }, + "description": "data needed to allow the paymaster to pay for the transaction in native tokens" + }, + "account_deployment_data": { + "title": "Account deployment data", + "type": "array", + "items": { + "$ref": "#/components/schemas/FELT" + }, + "description": "data needed to deploy the account contract from which this tx will be initiated" + }, + "nonce_data_availability_mode": { + "title": "Nonce DA mode", + "description": "The storage domain of the account's nonce (an account has a nonce per DA mode)", + "$ref": "#/components/schemas/DA_MODE" + }, + "fee_data_availability_mode": { + "title": "Fee DA mode", + "description": "The storage domain of the account's balance from which fee will be charged", + "$ref": "#/components/schemas/DA_MODE" + } + }, + "required": [ + "type", + "sender_address", + "compiled_class_hash", + "version", + "signature", + "nonce", + "contract_class", + "resource_bounds", + "tip", + "paymaster_data", + "account_deployment_data", + "nonce_data_availability_mode", + "fee_data_availability_mode" + ] + } + ] + }, + "DEPLOY_ACCOUNT_TXN": { + "title": "Deploy account transaction", + "description": "deploys a new account contract", + "oneOf": [ + { + "title": "Deploy account V1", + "$ref": "#/components/schemas/DEPLOY_ACCOUNT_TXN_V1" + }, + { + "title": "Deploy account V3", + "$ref": "#/components/schemas/DEPLOY_ACCOUNT_TXN_V3" + } + ] + }, + "DEPLOY_ACCOUNT_TXN_V1": { + "title": "Deploy account transaction", + "description": "Deploys an account contract, charges fee from the pre-funded account addresses", + "type": "object", + "properties": { + "type": { + "title": "Deploy account", + "type": "string", + "enum": ["DEPLOY_ACCOUNT"] + }, + "max_fee": { + "title": "Max fee", + "$ref": "#/components/schemas/FELT", + "description": "The maximal fee that can be charged for including the transaction" + }, + "version": { + "title": "Version", + "description": "Version of the transaction scheme", + "type": "string", + "enum": ["0x1", "0x100000000000000000000000000000001"] + }, + "signature": { + "title": "Signature", + "$ref": "#/components/schemas/SIGNATURE" + }, + "nonce": { + "title": "Nonce", + "$ref": "#/components/schemas/FELT" + }, + "contract_address_salt": { + "title": "Contract address salt", + "description": "The salt for the address of the deployed contract", + "$ref": "#/components/schemas/FELT" + }, + "constructor_calldata": { + "type": "array", + "description": "The parameters passed to the constructor", + "title": "Constructor calldata", + "items": { + "$ref": "#/components/schemas/FELT" + } + }, + "class_hash": { + "title": "Class hash", + "description": "The hash of the deployed contract's class", + "$ref": "#/components/schemas/FELT" + } + }, + "required": [ + "max_fee", + "version", + "signature", + "nonce", + "type", + "contract_address_salt", + "constructor_calldata", + "class_hash" + ] + }, + "DEPLOY_ACCOUNT_TXN_V3": { + "title": "Deploy account transaction", + "description": "Deploys an account contract, charges fee from the pre-funded account addresses", + "type": "object", + "properties": { + "type": { + "title": "Deploy account", + "type": "string", + "enum": ["DEPLOY_ACCOUNT"] + }, + "version": { + "title": "Version", + "description": "Version of the transaction scheme", + "type": "string", + "enum": ["0x3", "0x100000000000000000000000000000003"] + }, + "signature": { + "title": "Signature", + "$ref": "#/components/schemas/SIGNATURE" + }, + "nonce": { + "title": "Nonce", + "$ref": "#/components/schemas/FELT" + }, + "contract_address_salt": { + "title": "Contract address salt", + "description": "The salt for the address of the deployed contract", + "$ref": "#/components/schemas/FELT" + }, + "constructor_calldata": { + "type": "array", + "description": "The parameters passed to the constructor", + "title": "Constructor calldata", + "items": { + "$ref": "#/components/schemas/FELT" + } + }, + "class_hash": { + "title": "Class hash", + "description": "The hash of the deployed contract's class", + "$ref": "#/components/schemas/FELT" + }, + "resource_bounds": { + "title": "Resource bounds", + "description": "resource bounds for the transaction execution", + "$ref": "#/components/schemas/RESOURCE_BOUNDS_MAPPING" + }, + "tip": { + "title": "Tip", + "$ref": "#/components/schemas/u64", + "description": "the tip for the transaction" + }, + "paymaster_data": { + "title": "Paymaster data", + "type": "array", + "items": { + "$ref": "#/components/schemas/FELT" + }, + "description": "data needed to allow the paymaster to pay for the transaction in native tokens" + }, + "nonce_data_availability_mode": { + "title": "Nonce DA mode", + "description": "The storage domain of the account's nonce (an account has a nonce per DA mode)", + "$ref": "#/components/schemas/DA_MODE" + }, + "fee_data_availability_mode": { + "title": "Fee DA mode", + "description": "The storage domain of the account's balance from which fee will be charged", + "$ref": "#/components/schemas/DA_MODE" + } + }, + "required": [ + "version", + "signature", + "nonce", + "type", + "contract_address_salt", + "constructor_calldata", + "class_hash", + "resource_bounds", + "tip", + "paymaster_data", + "nonce_data_availability_mode", + "fee_data_availability_mode" + ] + }, + "DEPLOY_TXN": { + "title": "Deploy Contract Transaction", + "description": "The structure of a deploy transaction. Note that this transaction type is deprecated and will no longer be supported in future versions", + "allOf": [ + { + "type": "object", + "title": "Deploy txn", + "properties": { + "version": { + "title": "Version", + "description": "Version of the transaction scheme", + "$ref": "#/components/schemas/FELT" + }, + "type": { + "title": "Deploy", + "type": "string", + "enum": ["DEPLOY"] + }, + "contract_address_salt": { + "description": "The salt for the address of the deployed contract", + "title": "Contract address salt", + "$ref": "#/components/schemas/FELT" + }, + "constructor_calldata": { + "type": "array", + "title": "Constructor calldata", + "description": "The parameters passed to the constructor", + "items": { + "$ref": "#/components/schemas/FELT" + } + }, + "class_hash": { + "title": "Class hash", + "description": "The hash of the deployed contract's class", + "$ref": "#/components/schemas/FELT" + } + }, + "required": ["version", "type", "constructor_calldata", "contract_address_salt", "class_hash"] + } + ] + }, + "INVOKE_TXN_V0": { + "title": "Invoke transaction V0", + "description": "invokes a specific function in the desired contract (not necessarily an account)", + "type": "object", + "properties": { + "type": { + "title": "Type", + "type": "string", + "enum": ["INVOKE"] + }, + "max_fee": { + "title": "Max fee", + "$ref": "#/components/schemas/FELT", + "description": "The maximal fee that can be charged for including the transaction" + }, + "version": { + "title": "Version", + "description": "Version of the transaction scheme", + "type": "string", + "enum": ["0x0", "0x100000000000000000000000000000000"] + }, + "signature": { + "title": "Signature", + "$ref": "#/components/schemas/SIGNATURE" + }, + "contract_address": { + "title": "Contract address", + "$ref": "#/components/schemas/ADDRESS" + }, + "entry_point_selector": { + "title": "Entry point selector", + "$ref": "#/components/schemas/FELT" + }, + "calldata": { + "title": "Calldata", + "type": "array", + "description": "The parameters passed to the function", + "items": { + "$ref": "#/components/schemas/FELT" + } + } + }, + "required": ["type", "contract_address", "entry_point_selector", "calldata", "max_fee", "version", "signature"] + }, + "INVOKE_TXN_V1": { + "title": "Invoke transaction V1", + "description": "initiates a transaction from a given account", + "allOf": [ + { + "type": "object", + "properties": { + "type": { + "title": "Type", + "type": "string", + "enum": ["INVOKE"] + }, + "sender_address": { + "title": "sender address", + "$ref": "#/components/schemas/ADDRESS" + }, + "calldata": { + "type": "array", + "title": "calldata", + "description": "The data expected by the account's `execute` function (in most usecases, this includes the called contract address and a function selector)", + "items": { + "$ref": "#/components/schemas/FELT" + } + }, + "max_fee": { + "title": "Max fee", + "$ref": "#/components/schemas/FELT", + "description": "The maximal fee that can be charged for including the transaction" + }, + "version": { + "title": "Version", + "description": "Version of the transaction scheme", + "type": "string", + "enum": ["0x1", "0x100000000000000000000000000000001"] + }, + "signature": { + "title": "Signature", + "$ref": "#/components/schemas/SIGNATURE" + }, + "nonce": { + "title": "Nonce", + "$ref": "#/components/schemas/FELT" + } + }, + "required": ["type", "sender_address", "calldata", "max_fee", "version", "signature", "nonce"] + } + ] + }, + "INVOKE_TXN_V3": { + "title": "Invoke transaction V3", + "description": "initiates a transaction from a given account", + "allOf": [ + { + "type": "object", + "properties": { + "type": { + "title": "Type", + "type": "string", + "enum": ["INVOKE"] + }, + "sender_address": { + "title": "sender address", + "$ref": "#/components/schemas/ADDRESS" + }, + "calldata": { + "type": "array", + "title": "calldata", + "description": "The data expected by the account's `execute` function (in most usecases, this includes the called contract address and a function selector)", + "items": { + "$ref": "#/components/schemas/FELT" + } + }, + "version": { + "title": "Version", + "description": "Version of the transaction scheme", + "type": "string", + "enum": ["0x3", "0x100000000000000000000000000000003"] + }, + "signature": { + "title": "Signature", + "$ref": "#/components/schemas/SIGNATURE" + }, + "nonce": { + "title": "Nonce", + "$ref": "#/components/schemas/FELT" + }, + "resource_bounds": { + "title": "Resource bounds", + "description": "resource bounds for the transaction execution", + "$ref": "#/components/schemas/RESOURCE_BOUNDS_MAPPING" + }, + "tip": { + "title": "Tip", + "$ref": "#/components/schemas/u64", + "description": "the tip for the transaction" + }, + "paymaster_data": { + "title": "Paymaster data", + "type": "array", + "items": { + "$ref": "#/components/schemas/FELT" + }, + "description": "data needed to allow the paymaster to pay for the transaction in native tokens" + }, + "account_deployment_data": { + "title": "Account deployment data", + "type": "array", + "items": { + "$ref": "#/components/schemas/FELT" + }, + "description": "data needed to deploy the account contract from which this tx will be initiated" + }, + "nonce_data_availability_mode": { + "title": "Nonce DA mode", + "description": "The storage domain of the account's nonce (an account has a nonce per DA mode)", + "$ref": "#/components/schemas/DA_MODE" + }, + "fee_data_availability_mode": { + "title": "Fee DA mode", + "description": "The storage domain of the account's balance from which fee will be charged", + "$ref": "#/components/schemas/DA_MODE" + } + }, + "required": [ + "type", + "sender_address", + "calldata", + "version", + "signature", + "nonce", + "resource_bounds", + "tip", + "paymaster_data", + "account_deployment_data", + "nonce_data_availability_mode", + "fee_data_availability_mode" + ] + } + ] + }, + "INVOKE_TXN": { + "title": "Invoke transaction", + "description": "Initiate a transaction from an account", + "oneOf": [ + { + "title": "Invoke transaction V0", + "$ref": "#/components/schemas/INVOKE_TXN_V0" + }, + { + "title": "Invoke transaction V1", + "$ref": "#/components/schemas/INVOKE_TXN_V1" + }, + { + "title": "Invoke transaction V3", + "$ref": "#/components/schemas/INVOKE_TXN_V3" + } + ] + }, + "L1_HANDLER_TXN": { + "title": "L1 Handler transaction", + "allOf": [ + { + "type": "object", + "title": "L1 handler transaction", + "description": "a call to an l1_handler on an L2 contract induced by a message from L1", + "properties": { + "version": { + "title": "Version", + "description": "Version of the transaction scheme", + "type": "string", + "enum": ["0x0"] + }, + "type": { + "title": "type", + "type": "string", + "enum": ["L1_HANDLER"] + }, + "nonce": { + "title": "Nonce", + "description": "The L1->L2 message nonce field of the SN Core L1 contract at the time the transaction was sent", + "$ref": "#/components/schemas/NUM_AS_HEX" + } + }, + "required": ["version", "type", "nonce"] + }, + { + "title": "Function call", + "$ref": "#/components/schemas/FUNCTION_CALL" + } + ] + }, + "COMMON_RECEIPT_PROPERTIES": { + "allOf": [ + { + "title": "Common receipt properties", + "description": "Common properties for a transaction receipt", + "type": "object", + "properties": { + "transaction_hash": { + "title": "Transaction hash", + "$ref": "#/components/schemas/TXN_HASH", + "description": "The hash identifying the transaction" + }, + "actual_fee": { + "title": "Actual fee", + "$ref": "#/components/schemas/FEE_PAYMENT", + "description": "The fee that was charged by the sequencer" + }, + "finality_status": { + "title": "Finality status", + "description": "finality status of the tx", + "$ref": "#/components/schemas/TXN_FINALITY_STATUS" + }, + "messages_sent": { + "type": "array", + "title": "Messages sent", + "items": { + "$ref": "#/components/schemas/MSG_TO_L1" + } + }, + "events": { + "description": "The events emitted as part of this transaction", + "title": "Events", + "type": "array", + "items": { + "$ref": "#/components/schemas/EVENT" + } + }, + "execution_resources": { + "title": "Execution resources", + "description": "The resources consumed by the transaction", + "$ref": "#/components/schemas/EXECUTION_RESOURCES" + } + }, + "required": [ + "transaction_hash", + "actual_fee", + "finality_status", + "messages_sent", + "events", + "execution_resources" + ] + }, + { + "oneOf": [ + { + "title": "Successful Common receipt properties", + "description": "Common properties for a transaction receipt that was executed successfully", + "type": "object", + "properties": { + "execution_status": { + "title": "Execution status", + "type": "string", + "enum": ["SUCCEEDED"], + "description": "The execution status of the transaction" + } + }, + "required": ["execution_status"] + }, + { + "title": "Reverted Common receipt properties", + "description": "Common properties for a transaction receipt that was reverted", + "type": "object", + "properties": { + "execution_status": { + "title": "Execution status", + "type": "string", + "enum": ["REVERTED"], + "description": "The execution status of the transaction" + }, + "revert_reason": { + "title": "Revert reason", + "name": "revert reason", + "description": "the revert reason for the failed execution", + "type": "string" + } + }, + "required": ["execution_status", "revert_reason"] + } + ] + } + ] + }, + "INVOKE_TXN_RECEIPT": { + "title": "Invoke Transaction Receipt", + "allOf": [ + { + "title": "Type", + "type": "object", + "properties": { + "type": { + "title": "Type", + "type": "string", + "enum": ["INVOKE"] + } + }, + "required": ["type"] + }, + { + "title": "Common receipt properties", + "$ref": "#/components/schemas/COMMON_RECEIPT_PROPERTIES" + } + ] + }, + "DECLARE_TXN_RECEIPT": { + "title": "Declare Transaction Receipt", + "allOf": [ + { + "title": "Declare txn receipt", + "type": "object", + "properties": { + "type": { + "title": "Declare", + "type": "string", + "enum": ["DECLARE"] + } + }, + "required": ["type"] + }, + { + "title": "Common receipt properties", + "$ref": "#/components/schemas/COMMON_RECEIPT_PROPERTIES" + } + ] + }, + "DEPLOY_ACCOUNT_TXN_RECEIPT": { + "title": "Deploy Account Transaction Receipt", + "allOf": [ + { + "title": "Common receipt properties", + "$ref": "#/components/schemas/COMMON_RECEIPT_PROPERTIES" + }, + { + "title": "DeployAccount txn receipt", + "type": "object", + "properties": { + "type": { + "title": "Deploy account", + "type": "string", + "enum": ["DEPLOY_ACCOUNT"] + }, + "contract_address": { + "title": "Contract address", + "description": "The address of the deployed contract", + "$ref": "#/components/schemas/FELT" + } + }, + "required": ["type", "contract_address"] + } + ] + }, + "DEPLOY_TXN_RECEIPT": { + "title": "Deploy Transaction Receipt", + "allOf": [ + { + "title": "Common receipt properties", + "$ref": "#/components/schemas/COMMON_RECEIPT_PROPERTIES" + }, + { + "title": "Deploy txn receipt", + "type": "object", + "properties": { + "type": { + "title": "Deploy", + "type": "string", + "enum": ["DEPLOY"] + }, + "contract_address": { + "title": "Contract address", + "description": "The address of the deployed contract", + "$ref": "#/components/schemas/FELT" + } + }, + "required": ["type", "contract_address"] + } + ] + }, + "L1_HANDLER_TXN_RECEIPT": { + "title": "L1 Handler Transaction Receipt", + "description": "receipt for l1 handler transaction", + "allOf": [ + { + "title": "Transaction type", + "type": "object", + "properties": { + "type": { + "title": "type", + "type": "string", + "enum": ["L1_HANDLER"] + }, + "message_hash": { + "title": "Message hash", + "description": "The message hash as it appears on the L1 core contract", + "$ref": "#/components/schemas/NUM_AS_HEX" + } + }, + "required": ["type", "message_hash"] + }, + { + "title": "Common receipt properties", + "$ref": "#/components/schemas/COMMON_RECEIPT_PROPERTIES" + } + ] + }, + "TXN_RECEIPT": { + "title": "Transaction Receipt", + "oneOf": [ + { + "title": "Invoke transaction receipt", + "$ref": "#/components/schemas/INVOKE_TXN_RECEIPT" + }, + { + "title": "L1 handler transaction receipt", + "$ref": "#/components/schemas/L1_HANDLER_TXN_RECEIPT" + }, + { + "title": "Declare transaction receipt", + "$ref": "#/components/schemas/DECLARE_TXN_RECEIPT" + }, + { + "title": "Deploy transaction receipt", + "$ref": "#/components/schemas/DEPLOY_TXN_RECEIPT" + }, + { + "title": "Deploy account transaction receipt", + "$ref": "#/components/schemas/DEPLOY_ACCOUNT_TXN_RECEIPT" + } + ] + }, + "TXN_RECEIPT_WITH_BLOCK_INFO": { + "title": "Transaction receipt with block info", + "allOf": [ + { + "title": "Transaction receipt", + "$ref": "#/components/schemas/TXN_RECEIPT" + }, + { + "type": "object", + "properties": { + "block_hash": { + "title": "Block hash", + "$ref": "#/components/schemas/BLOCK_HASH", + "description": "If this field is missing, it means the receipt belongs to the pending block" + }, + "block_number": { + "title": "Block number", + "$ref": "#/components/schemas/BLOCK_NUMBER", + "description": "If this field is missing, it means the receipt belongs to the pending block" + } + } + } + ] + }, + "MSG_TO_L1": { + "title": "Message to L1", + "type": "object", + "properties": { + "from_address": { + "description": "The address of the L2 contract sending the message", + "$ref": "#/components/schemas/FELT" + }, + "to_address": { + "title": "To address", + "description": "The target L1 address the message is sent to", + "$ref": "#/components/schemas/FELT" + }, + "payload": { + "description": "The payload of the message", + "title": "Payload", + "type": "array", + "items": { + "$ref": "#/components/schemas/FELT" + } + } + }, + "required": ["from_address", "to_address", "payload"] + }, + "MSG_FROM_L1": { + "title": "Message from L1", + "type": "object", + "properties": { + "from_address": { + "description": "The address of the L1 contract sending the message", + "$ref": "#/components/schemas/ETH_ADDRESS" + }, + "to_address": { + "title": "To address", + "description": "The target L2 address the message is sent to", + "$ref": "#/components/schemas/ADDRESS" + }, + "entry_point_selector": { + "title": "Selector", + "description": "The selector of the l1_handler in invoke in the target contract", + "$ref": "#/components/schemas/FELT" + }, + "payload": { + "description": "The payload of the message", + "title": "Payload", + "type": "array", + "items": { + "$ref": "#/components/schemas/FELT" + } + } + }, + "required": ["from_address", "to_address", "payload", "entry_point_selector"] + }, + "TXN_STATUS_RESULT": { + "title": "Transaction status result", + "description": "Transaction status result, including finality status and execution status", + "schema": { + "title": "Transaction status result", + "type": "object", + "properties": { + "finality_status": { + "title": "finality status", + "$ref": "#/components/schemas/TXN_STATUS" + }, + "execution_status": { + "title": "execution status", + "$ref": "#/components/schemas/TXN_EXECUTION_STATUS" + }, + "failure_reason": { + "title": "failure reason", + "description": "the failure reason, only appears if finality_status is REJECTED or execution_status is REVERTED", + "type": "string" + } + }, + "required": ["finality_status"] + } + }, + "TXN_STATUS": { + "title": "Transaction status", + "type": "string", + "enum": ["RECEIVED", "REJECTED", "ACCEPTED_ON_L2", "ACCEPTED_ON_L1"], + "description": "The finality status of the transaction, including the case the txn is still in the mempool or failed validation during the block construction phase" + }, + "TXN_FINALITY_STATUS": { + "title": "Finality status", + "type": "string", + "enum": ["ACCEPTED_ON_L2", "ACCEPTED_ON_L1"], + "description": "The finality status of the transaction" + }, + "TXN_EXECUTION_STATUS": { + "title": "Execution status", + "type": "string", + "enum": ["SUCCEEDED", "REVERTED"], + "description": "The execution status of the transaction" + }, + "TXN_TYPE": { + "title": "Transaction type", + "type": "string", + "enum": ["DECLARE", "DEPLOY", "DEPLOY_ACCOUNT", "INVOKE", "L1_HANDLER"], + "description": "The type of the transaction" + }, + "BLOCK_STATUS": { + "title": "Block status", + "type": "string", + "enum": ["PENDING", "ACCEPTED_ON_L2", "ACCEPTED_ON_L1", "REJECTED"], + "description": "The status of the block" + }, + "FUNCTION_CALL": { + "title": "Function call", + "type": "object", + "description": "Function call information", + "properties": { + "contract_address": { + "title": "Contract address", + "$ref": "#/components/schemas/ADDRESS" + }, + "entry_point_selector": { + "title": "Entry point selector", + "$ref": "#/components/schemas/FELT" + }, + "calldata": { + "title": "Calldata", + "type": "array", + "description": "The parameters passed to the function", + "items": { + "$ref": "#/components/schemas/FELT" + } + } + }, + "required": ["contract_address", "entry_point_selector", "calldata"] + }, + "CONTRACT_CLASS": { + "title": "Contract class", + "type": "object", + "properties": { + "sierra_program": { + "title": "Sierra program", + "type": "array", + "description": "The list of Sierra instructions of which the program consists", + "items": { + "$ref": "#/components/schemas/FELT" + } + }, + "contract_class_version": { + "title": "Contract class version", + "type": "string", + "description": "The version of the contract class object. Currently, the Starknet OS supports version 0.1.0" + }, + "entry_points_by_type": { + "title": "Entry points by type", + "type": "object", + "properties": { + "CONSTRUCTOR": { + "type": "array", + "title": "Constructor", + "items": { + "$ref": "#/components/schemas/SIERRA_ENTRY_POINT" + } + }, + "EXTERNAL": { + "title": "External", + "type": "array", + "items": { + "$ref": "#/components/schemas/SIERRA_ENTRY_POINT" + } + }, + "L1_HANDLER": { + "title": "L1 handler", + "type": "array", + "items": { + "$ref": "#/components/schemas/SIERRA_ENTRY_POINT" + } + } + }, + "required": ["CONSTRUCTOR", "EXTERNAL", "L1_HANDLER"] + }, + "abi": { + "title": "ABI", + "type": "string", + "description": "The class ABI, as supplied by the user declaring the class" + } + }, + "required": ["sierra_program", "contract_class_version", "entry_points_by_type"] + }, + "DEPRECATED_CONTRACT_CLASS": { + "title": "Deprecated contract class", + "description": "The definition of a StarkNet contract class", + "type": "object", + "properties": { + "program": { + "type": "string", + "title": "Program", + "description": "A base64 representation of the compressed program code", + "pattern": "^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)?$" + }, + "entry_points_by_type": { + "type": "object", + "title": "Deprecated entry points by type", + "properties": { + "CONSTRUCTOR": { + "type": "array", + "title": "Deprecated constructor", + "items": { + "$ref": "#/components/schemas/DEPRECATED_CAIRO_ENTRY_POINT" + } + }, + "EXTERNAL": { + "type": "array", + "title": "Deprecated external", + "items": { + "$ref": "#/components/schemas/DEPRECATED_CAIRO_ENTRY_POINT" + } + }, + "L1_HANDLER": { + "type": "array", + "title": "Deprecated L1 handler", + "items": { + "$ref": "#/components/schemas/DEPRECATED_CAIRO_ENTRY_POINT" + } + } + } + }, + "abi": { + "title": "Contract ABI", + "$ref": "#/components/schemas/CONTRACT_ABI" + } + }, + "required": ["program", "entry_points_by_type"] + }, + "DEPRECATED_CAIRO_ENTRY_POINT": { + "title": "Deprecated Cairo entry point", + "type": "object", + "properties": { + "offset": { + "title": "Offset", + "description": "The offset of the entry point in the program", + "$ref": "#/components/schemas/NUM_AS_HEX" + }, + "selector": { + "title": "Selector", + "description": "A unique identifier of the entry point (function) in the program", + "$ref": "#/components/schemas/FELT" + } + }, + "required": ["offset", "selector"] + }, + "SIERRA_ENTRY_POINT": { + "title": "Sierra entry point", + "type": "object", + "properties": { + "selector": { + "title": "Selector", + "description": "A unique identifier of the entry point (function) in the program", + "$ref": "#/components/schemas/FELT" + }, + "function_idx": { + "title": "Function index", + "description": "The index of the function in the program", + "type": "integer" + } + }, + "required": ["selector", "function_idx"] + }, + "CONTRACT_ABI": { + "title": "Contract ABI", + "type": "array", + "items": { + "$ref": "#/components/schemas/CONTRACT_ABI_ENTRY" + } + }, + "CONTRACT_ABI_ENTRY": { + "title": "Contract ABI entry", + "oneOf": [ + { + "title": "Function ABI entry", + "$ref": "#/components/schemas/FUNCTION_ABI_ENTRY" + }, + { + "title": "Event ABI entry", + "$ref": "#/components/schemas/EVENT_ABI_ENTRY" + }, + { + "title": "Struct ABI entry", + "$ref": "#/components/schemas/STRUCT_ABI_ENTRY" + } + ] + }, + "STRUCT_ABI_TYPE": { + "title": "Struct ABI type", + "type": "string", + "enum": ["struct"] + }, + "EVENT_ABI_TYPE": { + "title": "Event ABI type", + "type": "string", + "enum": ["event"] + }, + "FUNCTION_ABI_TYPE": { + "title": "Function ABI type", + "type": "string", + "enum": ["function", "l1_handler", "constructor"] + }, + "STRUCT_ABI_ENTRY": { + "title": "Struct ABI entry", + "type": "object", + "properties": { + "type": { + "title": "Struct ABI type", + "$ref": "#/components/schemas/STRUCT_ABI_TYPE" + }, + "name": { + "title": "Struct name", + "description": "The struct name", + "type": "string" + }, + "size": { + "title": "Size", + "type": "integer", + "minimum": 1 + }, + "members": { + "type": "array", + "title": "Members", + "items": { + "$ref": "#/components/schemas/STRUCT_MEMBER" + } + } + }, + "required": ["type", "name", "size", "members"] + }, + "STRUCT_MEMBER": { + "title": "Struct member", + "allOf": [ + { + "title": "Typed parameter", + "$ref": "#/components/schemas/TYPED_PARAMETER" + }, + { + "type": "object", + "title": "Offset", + "properties": { + "offset": { + "title": "Offset", + "description": "offset of this property within the struct", + "type": "integer" + } + } + } + ] + }, + "EVENT_ABI_ENTRY": { + "title": "Event ABI entry", + "type": "object", + "properties": { + "type": { + "title": "Event ABI type", + "$ref": "#/components/schemas/EVENT_ABI_TYPE" + }, + "name": { + "title": "Event name", + "description": "The event name", + "type": "string" + }, + "keys": { + "type": "array", + "title": "Typed parameter", + "items": { + "$ref": "#/components/schemas/TYPED_PARAMETER" + } + }, + "data": { + "type": "array", + "title": "Typed parameter", + "items": { + "$ref": "#/components/schemas/TYPED_PARAMETER" + } + } + }, + "required": ["type", "name", "keys", "data"] + }, + "FUNCTION_STATE_MUTABILITY": { + "title": "Function state mutability type", + "type": "string", + "enum": ["view"] + }, + "FUNCTION_ABI_ENTRY": { + "title": "Function ABI entry", + "type": "object", + "properties": { + "type": { + "title": "Function ABI type", + "$ref": "#/components/schemas/FUNCTION_ABI_TYPE" + }, + "name": { + "title": "Function name", + "description": "The function name", + "type": "string" + }, + "inputs": { + "type": "array", + "title": "Typed parameter", + "items": { + "$ref": "#/components/schemas/TYPED_PARAMETER" + } + }, + "outputs": { + "type": "array", + "title": "Typed parameter", + "items": { + "$ref": "#/components/schemas/TYPED_PARAMETER" + } + }, + "stateMutability": { + "title": "Function state mutability", + "$ref": "#/components/schemas/FUNCTION_STATE_MUTABILITY" + } + }, + "required": ["type", "name", "inputs", "outputs"] + }, + "TYPED_PARAMETER": { + "title": "Typed parameter", + "type": "object", + "properties": { + "name": { + "title": "Parameter name", + "description": "The parameter's name", + "type": "string" + }, + "type": { + "title": "Parameter type", + "description": "The parameter's type", + "type": "string" + } + }, + "required": ["name", "type"] + }, + "SIMULATION_FLAG_FOR_ESTIMATE_FEE": { + "type": "string", + "enum": ["SKIP_VALIDATE"], + "description": "Flags that indicate how to simulate a given transaction. By default, the sequencer behavior is replicated locally" + }, + "PRICE_UNIT": { + "title": "price unit", + "type": "string", + "enum": ["WEI", "FRI"] + }, + "FEE_ESTIMATE": { + "title": "Fee estimation", + "type": "object", + "properties": { + "l1_gas_consumed": { + "title": "L1 gas consumed", + "description": "The Ethereum gas consumption of the transaction, charged for L1->L2 messages and, depending on the block's DA_MODE, state diffs", + "$ref": "#/components/schemas/FELT" + }, + "l1_gas_price": { + "title": "L1 gas price", + "description": "The gas price (in wei or fri, depending on the tx version) that was used in the cost estimation", + "$ref": "#/components/schemas/FELT" + }, + "l2_gas_consumed": { + "title": "L2 gas consumed", + "description": "The L2 gas consumption of the transaction", + "$ref": "#/components/schemas/FELT" + }, + "l2_gas_price": { + "title": "L2 gas price", + "description": "The L2 gas price (in wei or fri, depending on the tx version) that was used in the cost estimation", + "$ref": "#/components/schemas/FELT" + }, + "l1_data_gas_consumed": { + "title": "L1 data gas consumed", + "description": "The Ethereum data gas consumption of the transaction", + "$ref": "#/components/schemas/FELT" + }, + "l1_data_gas_price": { + "title": "L1 data gas price", + "description": "The data gas price (in wei or fri, depending on the tx version) that was used in the cost estimation", + "$ref": "#/components/schemas/FELT" + }, + "overall_fee": { + "title": "Overall fee", + "description": "The estimated fee for the transaction (in wei or fri, depending on the tx version), equals to gas_consumed*gas_price + data_gas_consumed*data_gas_price", + "$ref": "#/components/schemas/FELT" + }, + "unit": { + "title": "Fee unit", + "description": "units in which the fee is given", + "$ref": "#/components/schemas/PRICE_UNIT" + } + }, + "required": ["gas_consumed", "gas_price", "data_gas_consumed", "data_gas_price", "overall_fee", "unit"] + }, + "FEE_PAYMENT": { + "title": "Fee Payment", + "description": "fee payment info as it appears in receipts", + "type": "object", + "properties": { + "amount": { + "title": "Amount", + "description": "amount paid", + "$ref": "#/components/schemas/FELT" + }, + "unit": { + "title": "Fee unit", + "description": "units in which the fee is given", + "$ref": "#/components/schemas/PRICE_UNIT" + } + }, + "required": ["amount", "unit"] + }, + "DA_MODE": { + "title": "DA mode", + "type": "string", + "description": "Specifies a storage domain in Starknet. Each domain has different gurantess regarding availability", + "enum": ["L1", "L2"] + }, + "RESOURCE_BOUNDS_MAPPING": { + "type": "object", + "properties": { + "l1_gas": { + "title": "L1 Gas", + "description": "The max amount and max price per unit of L1 gas used in this tx", + "$ref": "#/components/schemas/RESOURCE_BOUNDS" + }, + "l2_gas": { + "title": "L2 Gas", + "description": "The max amount and max price per unit of L2 gas used in this tx", + "$ref": "#/components/schemas/RESOURCE_BOUNDS" + } + }, + "required": ["l1_gas", "l2_gas"] + }, + "RESOURCE_BOUNDS": { + "type": "object", + "properties": { + "max_amount": { + "title": "max amount", + "description": "the max amount of the resource that can be used in the tx", + "$ref": "#/components/schemas/u64" + }, + "max_price_per_unit": { + "title": "max price", + "description": "the max price per unit of this resource for this tx", + "$ref": "#/components/schemas/u128" + } + }, + "required": ["max_amount", "max_price_per_unit"] + }, + "RESOURCE_PRICE": { + "type": "object", + "properties": { + "price_in_fri": { + "title": "price in fri", + "description": "the price of one unit of the given resource, denominated in fri (10^-18 strk)", + "$ref": "#/components/schemas/FELT" + }, + "price_in_wei": { + "title": "price in wei", + "description": "the price of one unit of the given resource, denominated in wei", + "$ref": "#/components/schemas/FELT" + } + }, + "required": ["price_in_wei", "price_in_fri"] + }, + "EXECUTION_RESOURCES": { + "type": "object", + "title": "Execution resources", + "description": "the resources consumed by the transaction", + "properties": { + "l1_gas": { + "title": "L1Gas", + "description": "l1 gas consumed by this transaction, used for l2-->l1 messages and state updates if blobs are not used", + "type": "integer" + }, + "l1_data_gas": { + "title": "L1DataGas", + "description": "data gas consumed by this transaction, 0 if blobs are not used", + "type": "integer" + }, + "l2_gas": { + "title": "L2Gas", + "description": "l2 gas consumed by this transaction, used for computation and calldata", + "type": "integer" + } + } + }, + "MERKLE_NODE": { + "type": "object", + "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": { + "$ref": "#/components/schemas/FELT" + }, + "right": { + "$ref": "#/components/schemas/FELT" + } + }, + "required": [ + "left", + "right" + ] + } + }, + "required": [ + "path", + "length", + "value" + ] + }, + "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)", + "type": "array", + "items": { + "type": "object", + "properties": { + "node_hash": { + "$ref": "#/components/schemas/FELT" + }, + "node": { + "$ref": "#/components/schemas/MERKLE_NODE" + } + }, + "required": [ + "node_hash", + "node" + ] + } + }, + "CONTRACT_EXECUTION_ERROR": { + "description": "structured error that can later be processed by wallets or sdks", + "titel": "contract execution error", + "oneOf": [ + { + "type": "object", + "title": "inner call", + "description": "error frame", + "properties": { + "contract_address": { + "$ref": "#/components/schemas/ADDRESS" + }, + "class_hash": { + "$ref": "#/components/schemas/FELT" + }, + "selector": { + "$ref": "#/components/schemas/FELT" + }, + "error": { + "$ref": "#/components/schemas/CONTRACT_EXECUTION_ERROR" + } + } + }, + { + "title": "error message", + "description": "the error raised during execution", + "type": "string" + } + ] + } + }, + "errors": { + "FAILED_TO_RECEIVE_TXN": { + "code": 1, + "message": "Failed to write transaction" + }, + "CONTRACT_NOT_FOUND": { + "code": 20, + "message": "Contract not found" + }, + "BLOCK_NOT_FOUND": { + "code": 24, + "message": "Block not found" + }, + "INVALID_TXN_INDEX": { + "code": 27, + "message": "Invalid transaction index in a block" + }, + "CLASS_HASH_NOT_FOUND": { + "code": 28, + "message": "Class hash not found" + }, + "TXN_HASH_NOT_FOUND": { + "code": 29, + "message": "Transaction hash not found" + }, + "PAGE_SIZE_TOO_BIG": { + "code": 31, + "message": "Requested page size is too big" + }, + "NO_BLOCKS": { + "code": 32, + "message": "There are no blocks" + }, + "INVALID_CONTINUATION_TOKEN": { + "code": 33, + "message": "The supplied continuation token is invalid or unknown" + }, + "TOO_MANY_KEYS_IN_FILTER": { + "code": 34, + "message": "Too many keys provided in a filter" + }, + "CONTRACT_ERROR": { + "code": 40, + "message": "Contract error", + "data": { + "type": "object", + "description": "More data about the execution failure", + "properties": { + "revert_error": { + "title": "revert error", + "description": "the execution trace up to the point of failure", + "$ref": "#/components/schemas/CONTRACT_EXECUTION_ERROR" + } + }, + "required": "revert_error" + } + }, + "TRANSACTION_EXECUTION_ERROR": { + "code": 41, + "message": "Transaction execution error", + "data": { + "type": "object", + "description": "More data about the execution failure", + "properties": { + "transaction_index": { + "title": "Transaction index", + "description": "The index of the first transaction failing in a sequence of given transactions", + "type": "integer" + }, + "execution_error": { + "title": "revert error", + "description": "the execution trace up to the point of failure", + "$ref": "#/components/schemas/CONTRACT_EXECUTION_ERROR" + } + }, + "required": ["transaction_index", "execution_error"] + } + } + } + } +} diff --git a/doc/rpc/v08/starknet_trace_api_openrpc.json b/doc/rpc/v08/starknet_trace_api_openrpc.json new file mode 100644 index 0000000000..19749a856d --- /dev/null +++ b/doc/rpc/v08/starknet_trace_api_openrpc.json @@ -0,0 +1,480 @@ +{ + "openrpc": "1.0.0-rc1", + "info": { + "version": "0.7.1", + "title": "StarkNet Trace API", + "license": {} + }, + "servers": [], + "methods": [ + { + "name": "starknet_traceTransaction", + "summary": "For a given executed transaction, return the trace of its execution, including internal calls", + "description": "Returns the execution trace of the transaction designated by the input hash", + "params": [ + { + "name": "transaction_hash", + "summary": "The hash of the transaction to trace", + "required": true, + "schema": { + "$ref": "./api/starknet_api_openrpc.json#/components/schemas/TXN_HASH" + } + } + ], + "result": { + "name": "trace", + "description": "The function call trace of the transaction designated by the given hash", + "schema": { + "$ref": "#/components/schemas/TRANSACTION_TRACE" + } + }, + "errors": [ + { + "$ref": "#/components/errors/TXN_HASH_NOT_FOUND" + }, + { + "$ref": "#/components/errors/NO_TRACE_AVAILABLE" + } + ] + }, + { + "name": "starknet_simulateTransactions", + "summary": "Simulate a given sequence of transactions on the requested state, and generate the execution traces. Note that some of the transactions may revert, in which case no error is thrown, but revert details can be seen on the returned trace object. . Note that some of the transactions may revert, this will be reflected by the revert_error property in the trace. Other types of failures (e.g. unexpected error or failure in the validation phase) will result in TRANSACTION_EXECUTION_ERROR.", + "params": [ + { + "name": "block_id", + "description": "The hash of the requested block, or number (height) of the requested block, or a block tag, for the block referencing the state or call the transaction on.", + "required": true, + "schema": { + "$ref": "#/components/schemas/BLOCK_ID" + } + }, + { + "name": "transactions", + "description": "The transactions to simulate", + "required": true, + "schema": { + "type": "array", + "description": "a sequence of transactions to simulate, running each transaction on the state resulting from applying all the previous ones", + "items": { + "$ref": "#/components/schemas/BROADCASTED_TXN" + } + } + }, + { + "name": "simulation_flags", + "description": "describes what parts of the transaction should be executed", + "required": true, + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SIMULATION_FLAG" + } + } + } + ], + "result": { + "name": "simulated_transactions", + "description": "The execution trace and consuemd resources of the required transactions", + "schema": { + "type": "array", + "items": { + "schema": { + "type": "object", + "properties": { + "transaction_trace": { + "title": "the transaction's trace", + "$ref": "#/components/schemas/TRANSACTION_TRACE" + }, + "fee_estimation": { + "title": "the transaction's resources and fee", + "$ref": "#/components/schemas/FEE_ESTIMATE" + } + } + } + } + } + }, + "errors": [ + { + "$ref": "#/components/errors/BLOCK_NOT_FOUND" + }, + { + "$ref": "#/components/errors/TRANSACTION_EXECUTION_ERROR" + } + ] + }, + { + "name": "starknet_traceBlockTransactions", + "summary": "Retrieve traces for all transactions in the given block", + "description": "Returns the execution traces of all transactions included in the given block", + "params": [ + { + "name": "block_id", + "description": "The hash of the requested block, or number (height) of the requested block, or a block tag", + "required": true, + "schema": { + "$ref": "#/components/schemas/BLOCK_ID" + } + } + ], + "result": { + "name": "traces", + "description": "The traces of all transactions in the block", + "schema": { + "type": "array", + "items": { + "type": "object", + "description": "A single pair of transaction hash and corresponding trace", + "properties": { + "transaction_hash": { + "$ref": "#/components/schemas/FELT" + }, + "trace_root": { + "$ref": "#/components/schemas/TRANSACTION_TRACE" + } + } + } + } + }, + "errors": [ + { + "$ref": "#/components/errors/BLOCK_NOT_FOUND" + } + ] + } + ], + "components": { + "contentDescriptors": {}, + "schemas": { + "TRANSACTION_TRACE": { + "oneOf": [ + { + "name": "INVOKE_TXN_TRACE", + "type": "object", + "description": "the execution trace of an invoke transaction", + "properties": { + "validate_invocation": { + "$ref": "#/components/schemas/FUNCTION_INVOCATION" + }, + "execute_invocation": { + "description": "the trace of the __execute__ call or constructor call, depending on the transaction type (none for declare transactions)", + "oneOf": [ + { + "$ref": "#/components/schemas/FUNCTION_INVOCATION" + }, + { + "type": "object", + "properties": { + "revert_reason": { + "name": "revert reason", + "description": "the revert reason for the failed execution", + "type": "string" + } + }, + "required": ["revert_reason"] + } + ] + }, + "fee_transfer_invocation": { + "$ref": "#/components/schemas/FUNCTION_INVOCATION" + }, + "state_diff": { + "title": "state_diff", + "description": "the state diffs induced by the transaction", + "$ref": "#/components/schemas/STATE_DIFF" + }, + "execution_resources": { + "title": "Execution resources", + "description": "the resources consumed by the transaction, includes both computation and data", + "$ref": "#/components/schemas/EXECUTION_RESOURCES" + }, + "type": { + "title": "Type", + "type": "string", + "enum": ["INVOKE"] + } + }, + "required": ["type", "execute_invocation", "execution_resources"] + }, + { + "name": "DECLARE_TXN_TRACE", + "type": "object", + "description": "the execution trace of a declare transaction", + "properties": { + "validate_invocation": { + "$ref": "#/components/schemas/FUNCTION_INVOCATION" + }, + "fee_transfer_invocation": { + "$ref": "#/components/schemas/FUNCTION_INVOCATION" + }, + "state_diff": { + "title": "state_diff", + "description": "the state diffs induced by the transaction", + "$ref": "#/components/schemas/STATE_DIFF" + }, + "execution_resources": { + "title": "Execution resources", + "description": "the resources consumed by the transaction, includes both computation and data", + "$ref": "#/components/schemas/EXECUTION_RESOURCES" + }, + "type": { + "title": "Type", + "type": "string", + "enum": ["DECLARE"] + } + }, + "required": ["type", "execution_resources"] + }, + { + "name": "DEPLOY_ACCOUNT_TXN_TRACE", + "type": "object", + "description": "the execution trace of a deploy account transaction", + "properties": { + "validate_invocation": { + "$ref": "#/components/schemas/FUNCTION_INVOCATION" + }, + "constructor_invocation": { + "description": "the trace of the __execute__ call or constructor call, depending on the transaction type (none for declare transactions)", + "$ref": "#/components/schemas/FUNCTION_INVOCATION" + }, + "fee_transfer_invocation": { + "$ref": "#/components/schemas/FUNCTION_INVOCATION" + }, + "state_diff": { + "title": "state_diff", + "description": "the state diffs induced by the transaction", + "$ref": "#/components/schemas/STATE_DIFF" + }, + "execution_resources": { + "title": "Execution resources", + "description": "the resources consumed by the transaction, includes both computation and data", + "$ref": "#/components/schemas/EXECUTION_RESOURCES" + }, + "type": { + "title": "Type", + "type": "string", + "enum": ["DEPLOY_ACCOUNT"] + } + }, + "required": ["type", "execution_resources", "constructor_invocation"] + }, + { + "name": "L1_HANDLER_TXN_TRACE", + "type": "object", + "description": "the execution trace of an L1 handler transaction", + "properties": { + "function_invocation": { + "description": "the trace of the __execute__ call or constructor call, depending on the transaction type (none for declare transactions)", + "$ref": "#/components/schemas/FUNCTION_INVOCATION" + }, + "state_diff": { + "title": "state_diff", + "description": "the state diffs induced by the transaction", + "$ref": "#/components/schemas/STATE_DIFF" + }, + "execution_resources": { + "title": "Execution resources", + "description": "the resources consumed by the transaction, includes both computation and data", + "$ref": "#/components/schemas/EXECUTION_RESOURCES" + }, + "type": { + "title": "Type", + "type": "string", + "enum": ["L1_HANDLER"] + } + }, + "required": ["type", "function_invocation", "execution_resources"] + } + ] + }, + "SIMULATION_FLAG": { + "type": "string", + "enum": ["SKIP_VALIDATE", "SKIP_FEE_CHARGE"], + "description": "Flags that indicate how to simulate a given transaction. By default, the sequencer behavior is replicated locally (enough funds are expected to be in the account, and fee will be deducted from the balance before the simulation of the next transaction). To skip the fee charge, use the SKIP_FEE_CHARGE flag." + }, + "NESTED_CALL": { + "$ref": "#/components/schemas/FUNCTION_INVOCATION" + }, + "FUNCTION_INVOCATION": { + "allOf": [ + { + "$ref": "#/components/schemas/FUNCTION_CALL" + }, + { + "type": "object", + "properties": { + "caller_address": { + "title": "Caller Address", + "description": "The address of the invoking contract. 0 for the root invocation", + "$ref": "#/components/schemas/FELT" + }, + "class_hash": { + "title": "Class hash", + "description": "The hash of the class being called", + "$ref": "#/components/schemas/FELT" + }, + "entry_point_type": { + "$ref": "#/components/schemas/ENTRY_POINT_TYPE" + }, + "call_type": { + "$ref": "#/components/schemas/CALL_TYPE" + }, + "result": { + "title": "Invocation Result", + "description": "The value returned from the function invocation", + "type": "array", + "items": { + "$ref": "#/components/schemas/FELT" + } + }, + "calls": { + "title": "Nested Calls", + "description": "The calls made by this invocation", + "type": "array", + "items": { + "$ref": "#/components/schemas/NESTED_CALL" + } + }, + "events": { + "title": "Invocation Events", + "description": "The events emitted in this invocation", + "type": "array", + "items": { + "$ref": "#/components/schemas/ORDERED_EVENT" + } + }, + "messages": { + "title": "L1 Messages", + "description": "The messages sent by this invocation to L1", + "type": "array", + "items": { + "$ref": "#/components/schemas/ORDERED_MESSAGE" + } + }, + "execution_resources": { + "title": "Computation resources", + "description": "Resources consumed by the internal call. This is named execution_resources for legacy reasons", + "$ref": "#/components/schemas/COMPUTATION_RESOURCES" + } + }, + "required": [ + "caller_address", + "class_hash", + "entry_point_type", + "call_type", + "result", + "calls", + "events", + "messages", + "execution_resources" + ] + } + ] + }, + "ENTRY_POINT_TYPE": { + "type": "string", + "enum": ["EXTERNAL", "L1_HANDLER", "CONSTRUCTOR"] + }, + "CALL_TYPE": { + "type": "string", + "enum": ["LIBRARY_CALL", "CALL", "DELEGATE"] + }, + "ORDERED_EVENT": { + "type": "object", + "title": "orderedEvent", + "description": "an event alongside its order within the transaction", + "allOf": [ + { + "type": "object", + "properties": { + "order": { + "title": "order", + "description": "the order of the event within the transaction", + "type": "integer" + } + } + }, + { + "$ref": "#/components/schemas/EVENT" + } + ] + }, + "ORDERED_MESSAGE": { + "type": "object", + "title": "orderedMessage", + "description": "a message alongside its order within the transaction", + "allOf": [ + { + "type": "object", + "properties": { + "order": { + "title": "order", + "description": "the order of the message within the transaction", + "type": "integer" + } + } + }, + { + "$ref": "#/components/schemas/MSG_TO_L1" + } + ] + }, + "FELT": { + "$ref": "./api/starknet_api_openrpc.json#/components/schemas/FELT" + }, + "FUNCTION_CALL": { + "$ref": "./api/starknet_api_openrpc.json#/components/schemas/FUNCTION_CALL" + }, + "EVENT": { + "$ref": "./api/starknet_api_openrpc.json#/components/schemas/EVENT_CONTENT" + }, + "MSG_TO_L1": { + "$ref": "./api/starknet_api_openrpc.json#/components/schemas/MSG_TO_L1" + }, + "BLOCK_ID": { + "$ref": "./api/starknet_api_openrpc.json#/components/schemas/BLOCK_ID" + }, + "FEE_ESTIMATE": { + "$ref": "./api/starknet_api_openrpc.json#/components/schemas/FEE_ESTIMATE" + }, + "BROADCASTED_TXN": { + "$ref": "./api/starknet_api_openrpc.json#/components/schemas/BROADCASTED_TXN" + }, + "STATE_DIFF": { + "$ref": "./api/starknet_api_openrpc.json#/components/schemas/STATE_DIFF" + }, + "COMPUTATION_RESOURCES": { + "$ref": "./api/starknet_api_openrpc.json#/components/schemas/COMPUTATION_RESOURCES" + }, + "EXECUTION_RESOURCES": { + "$ref": "./api/starknet_api_openrpc.json#/components/schemas/EXECUTION_RESOURCES" + } + }, + "errors": { + "NO_TRACE_AVAILABLE": { + "code": 10, + "message": "No trace available for transaction", + "data": { + "type": "object", + "description": "Extra information on why trace is not available. Either it wasn't executed yet (RECEIVED), or the transaction failed (REJECTED)", + "properties": { + "status": { + "type": "string", + "enum": ["RECEIVED", "REJECTED"] + } + } + } + }, + "TXN_HASH_NOT_FOUND": { + "$ref": "./api/starknet_api_openrpc.json#/components/errors/TXN_HASH_NOT_FOUND" + }, + "BLOCK_NOT_FOUND": { + "$ref": "./api/starknet_api_openrpc.json#/components/errors/BLOCK_NOT_FOUND" + }, + "TRANSACTION_EXECUTION_ERROR": { + "$ref": "./api/starknet_api_openrpc.json#/components/errors/TRANSACTION_EXECUTION_ERROR" + } + } + } +} diff --git a/doc/rpc/v08/starknet_write_api.json b/doc/rpc/v08/starknet_write_api.json new file mode 100644 index 0000000000..5c7341fd54 --- /dev/null +++ b/doc/rpc/v08/starknet_write_api.json @@ -0,0 +1,291 @@ +{ + "openrpc": "1.0.0-rc1", + "info": { + "version": "0.7.1", + "title": "StarkNet Node Write API", + "license": {} + }, + "servers": [], + "methods": [ + { + "name": "starknet_addInvokeTransaction", + "summary": "Submit a new transaction to be added to the chain", + "params": [ + { + "name": "invoke_transaction", + "description": "The information needed to invoke the function (or account, for version 1 transactions)", + "required": true, + "schema": { + "$ref": "#/components/schemas/BROADCASTED_INVOKE_TXN" + } + } + ], + "result": { + "name": "result", + "description": "The result of the transaction submission", + "schema": { + "type": "object", + "properties": { + "transaction_hash": { + "title": "The hash of the invoke transaction", + "$ref": "#/components/schemas/TXN_HASH" + } + }, + "required": ["transaction_hash"] + } + }, + "errors": [ + { + "$ref": "#/components/errors/INSUFFICIENT_ACCOUNT_BALANCE" + }, + { + "$ref": "#/components/errors/INSUFFICIENT_RESOURCES_FOR_VALIDATE" + }, + { + "$ref": "#/components/errors/INVALID_TRANSACTION_NONCE" + }, + { + "$ref": "#/components/errors/VALIDATION_FAILURE" + }, + { + "$ref": "#/components/errors/NON_ACCOUNT" + }, + { + "$ref": "#/components/errors/DUPLICATE_TX" + }, + { + "$ref": "#/components/errors/UNSUPPORTED_TX_VERSION" + }, + { + "$ref": "#/components/errors/UNEXPECTED_ERROR" + } + ] + }, + { + "name": "starknet_addDeclareTransaction", + "summary": "Submit a new class declaration transaction", + "params": [ + { + "name": "declare_transaction", + "description": "Declare transaction required to declare a new class on Starknet", + "required": true, + "schema": { + "title": "Declare transaction", + "$ref": "#/components/schemas/BROADCASTED_DECLARE_TXN" + } + } + ], + "result": { + "name": "result", + "description": "The result of the transaction submission", + "schema": { + "type": "object", + "properties": { + "transaction_hash": { + "title": "The hash of the declare transaction", + "$ref": "#/components/schemas/TXN_HASH" + }, + "class_hash": { + "title": "The hash of the declared class", + "$ref": "#/components/schemas/FELT" + } + }, + "required": ["transaction_hash", "class_hash"] + } + }, + "errors": [ + { + "$ref": "#/components/errors/CLASS_ALREADY_DECLARED" + }, + { + "$ref": "#/components/errors/COMPILATION_FAILED" + }, + { + "$ref": "#/components/errors/COMPILED_CLASS_HASH_MISMATCH" + }, + { + "$ref": "#/components/errors/INSUFFICIENT_ACCOUNT_BALANCE" + }, + { + "$ref": "#/components/errors/INSUFFICIENT_RESOURCES_FOR_VALIDATE" + }, + { + "$ref": "#/components/errors/INVALID_TRANSACTION_NONCE" + }, + { + "$ref": "#/components/errors/VALIDATION_FAILURE" + }, + { + "$ref": "#/components/errors/NON_ACCOUNT" + }, + { + "$ref": "#/components/errors/DUPLICATE_TX" + }, + { + "$ref": "#/components/errors/CONTRACT_CLASS_SIZE_IS_TOO_LARGE" + }, + { + "$ref": "#/components/errors/UNSUPPORTED_TX_VERSION" + }, + { + "$ref": "#/components/errors/UNSUPPORTED_CONTRACT_CLASS_VERSION" + }, + { + "$ref": "#/components/errors/UNEXPECTED_ERROR" + } + ] + }, + { + "name": "starknet_addDeployAccountTransaction", + "summary": "Submit a new deploy account transaction", + "params": [ + { + "name": "deploy_account_transaction", + "description": "The deploy account transaction", + "required": true, + "schema": { + "$ref": "#/components/schemas/BROADCASTED_DEPLOY_ACCOUNT_TXN" + } + } + ], + "result": { + "name": "result", + "description": "The result of the transaction submission", + "schema": { + "type": "object", + "properties": { + "transaction_hash": { + "title": "The hash of the deploy transaction", + "$ref": "#/components/schemas/TXN_HASH" + }, + "contract_address": { + "title": "The address of the new contract", + "$ref": "#/components/schemas/FELT" + } + }, + "required": ["transaction_hash", "contract_address"] + } + }, + "errors": [ + { + "$ref": "#/components/errors/INSUFFICIENT_ACCOUNT_BALANCE" + }, + { + "$ref": "#/components/errors/INSUFFICIENT_RESOURCES_FOR_VALIDATE" + }, + { + "$ref": "#/components/errors/INVALID_TRANSACTION_NONCE" + }, + { + "$ref": "#/components/errors/VALIDATION_FAILURE" + }, + { + "$ref": "#/components/errors/NON_ACCOUNT" + }, + { + "$ref": "#/components/errors/CLASS_HASH_NOT_FOUND" + }, + { + "$ref": "#/components/errors/DUPLICATE_TX" + }, + { + "$ref": "#/components/errors/UNSUPPORTED_TX_VERSION" + }, + { + "$ref": "#/components/errors/UNEXPECTED_ERROR" + } + ] + } + ], + "components": { + "contentDescriptors": {}, + "schemas": { + "NUM_AS_HEX": { + "title": "An integer number in hex format (0x...)", + "type": "string", + "pattern": "^0x[a-fA-F0-9]+$" + }, + "SIGNATURE": { + "$ref": "./api/starknet_api_openrpc.json#/components/schemas/SIGNATURE" + }, + "FELT": { + "$ref": "./api/starknet_api_openrpc.json#/components/schemas/FELT" + }, + "TXN_HASH": { + "$ref": "./api/starknet_api_openrpc.json#/components/schemas/TXN_HASH" + }, + "BROADCASTED_INVOKE_TXN": { + "$ref": "./api/starknet_api_openrpc.json#/components/schemas/BROADCASTED_INVOKE_TXN" + }, + "BROADCASTED_DECLARE_TXN": { + "$ref": "./api/starknet_api_openrpc.json#/components/schemas/BROADCASTED_DECLARE_TXN" + }, + "BROADCASTED_DEPLOY_ACCOUNT_TXN": { + "$ref": "./api/starknet_api_openrpc.json#/components/schemas/BROADCASTED_DEPLOY_ACCOUNT_TXN" + }, + "FUNCTION_CALL": { + "$ref": "./api/starknet_api_openrpc.json#/components/schemas/FUNCTION_CALL" + } + }, + "errors": { + "CLASS_HASH_NOT_FOUND": { + "code": 28, + "message": "Class hash not found" + }, + "CLASS_ALREADY_DECLARED": { + "code": 51, + "message": "Class already declared" + }, + "INVALID_TRANSACTION_NONCE": { + "code": 52, + "message": "Invalid transaction nonce" + }, + "INSUFFICIENT_RESOURCES_FOR_VALIDATE": { + "code": 53, + "message": "The transaction's resources don't cover validation or the minimal transaction fee" + }, + "INSUFFICIENT_ACCOUNT_BALANCE": { + "code": 54, + "message": "Account balance is smaller than the transaction's max_fee" + }, + "VALIDATION_FAILURE": { + "code": 55, + "message": "Account validation failed", + "data": "string" + }, + "COMPILATION_FAILED": { + "code": 56, + "message": "Compilation failed" + }, + "CONTRACT_CLASS_SIZE_IS_TOO_LARGE": { + "code": 57, + "message": "Contract class size it too large" + }, + "NON_ACCOUNT": { + "code": 58, + "message": "Sender address in not an account contract" + }, + "DUPLICATE_TX": { + "code": 59, + "message": "A transaction with the same hash already exists in the mempool" + }, + "COMPILED_CLASS_HASH_MISMATCH": { + "code": 60, + "message": "the compiled class hash did not match the one supplied in the transaction" + }, + "UNSUPPORTED_TX_VERSION": { + "code": 61, + "message": "the transaction version is not supported" + }, + "UNSUPPORTED_CONTRACT_CLASS_VERSION": { + "code": 62, + "message": "the contract class version is not supported" + }, + "UNEXPECTED_ERROR": { + "code": 63, + "message": "An unexpected error occurred", + "data": "string" + } + } + } +} diff --git a/doc/rpc/v08/starknet_ws_api.json b/doc/rpc/v08/starknet_ws_api.json new file mode 100644 index 0000000000..726f30a7e1 --- /dev/null +++ b/doc/rpc/v08/starknet_ws_api.json @@ -0,0 +1,358 @@ +{ + "openrpc": "1.3.2", + "info": { + "version": "0.8.0-rc0", + "title": "StarkNet WebSocket PRC API", + "license": {} + }, + "methods": [ + { + "name": "starknet_subscribeNewHeads", + "summary": "New block headers subscription", + "description": "Creates a WebSocket stream which will fire events for new block headers", + "params": [ + { + "name": "block", + "summary": "The block to get notifications from, default is latest, limited to 1024 blocks back", + "required": false, + "schema": { + "$ref": "./api/starknet_api_openrpc.json#/components/schemas/BLOCK_ID" + } + } + ], + "result": { + "name": "subscription_id", + "schema": { + "$ref": "#/components/schemas/SUBSCRIPTION_ID" + } + }, + "errors": [ + { + "$ref": "#/components/errors/TOO_MANY_BLOCKS_BACK" + } + ] + }, + { + "name": "starknet_subscriptionNewHeads", + "summary": "New block headers notification", + "description": "Notification to the client of a new block header", + "params": [ + { + "name": "subscription_id", + "schema": { + "$ref": "#/components/schemas/SUBSCRIPTION_ID" + } + }, + { + "name": "result", + "schema": { + "$ref": "./api/starknet_api_openrpc.json#/components/schemas/BLOCK_HEADER" + } + } + ], + "errors": [] + }, + { + "name": "starknet_subscribeEvents", + "summary": "Events subscription", + "description": "Creates a WebSocket stream which will fire events for new Starknet events with applied filters", + "params": [ + { + "name": "from_address", + "summary": "Filter events by from_address which emitted the event", + "required": false, + "schema": { + "$ref": "#/components/schemas/ADDRESS" + } + }, + { + "name": "keys", + "summary": "The keys to filter events by", + "required": false, + "schema": { + "title": "event keys", + "$ref": "#/components/schemas/EVENT_KEYS" + } + }, + { + "name": "block", + "summary": "The block to get notifications from, default is latest, limited to 1024 blocks back", + "required": false, + "schema": { + "$ref": "./api/starknet_api_openrpc.json#/components/schemas/BLOCK_ID" + } + } + ], + "result": { + "name": "subscription_id", + "schema": { + "$ref": "#/components/schemas/SUBSCRIPTION_ID" + } + }, + "errors": [ + { + "$ref": "./api/starknet_api_openrpc.json#/components/errors/TOO_MANY_KEYS_IN_FILTER" + }, + { + "$ref": "#/components/errors/TOO_MANY_BLOCKS_BACK" + } + ] + }, + { + "name": "starknet_subscriptionEvents", + "summary": "New events notification", + "description": "Notification to the client of a new event", + "params": [ + { + "name": "subscription_id", + "schema": { + "$ref": "#/components/schemas/SUBSCRIPTION_ID" + } + }, + { + "name": "result", + "schema": { + "$ref": "./api/starknet_api_openrpc.json#/components/schemas/EMITTED_EVENT" + } + } + ], + "errors": [] + }, + { + "name": "starknet_subscribeTransactionStatus", + "summary": "Transaction Status subscription", + "description": "Creates a WebSocket stream which will fire events when a transaction status is updated", + "params": [ + { + "name": "transaction_hash", + "summary": "The transaction hash to fetch status updates for", + "required": true, + "schema": { + "$ref": "#/components/schemas/FELT" + } + }, + { + "name": "block", + "summary": "The block to get notifications from, default is latest, limited to 1024 blocks back", + "required": false, + "schema": { + "$ref": "./api/starknet_api_openrpc.json#/components/schemas/BLOCK_ID" + } + } + ], + "result": { + "name": "subscription_id", + "schema": { + "$ref": "#/components/schemas/SUBSCRIPTION_ID" + } + }, + "errors": [ + { + "$ref": "#/components/errors/TOO_MANY_BLOCKS_BACK" + } + ] + }, + { + "name": "starknet_subscriptionTransactionsStatus", + "summary": "New transaction status notification", + "description": "Notification to the client of a new transaction status", + "params": [ + { + "name": "subscription_id", + "schema": { + "$ref": "#/components/schemas/SUBSCRIPTION_ID" + } + }, + { + "name": "result", + "schema": { + "$ref": "#/components/schemas/NEW_TXN_STATUS" + } + } + ], + "errors": [] + }, + { + "name": "starknet_subscribePendingTransactions", + "summary": "New Pending Transactions subscription", + "description": "Creates a WebSocket stream which will fire events when a new pending transaction is added. While there is no mempool, this notifies of transactions in the pending block", + "params": [ + { + "name": "transaction_details", + "summary": "Get all transaction details, and not only the hash. If not provided, only hash is returned. Default is false", + "required": false, + "schema": { + "type": "boolean" + } + }, + { + "name": "sender_address", + "summary": "Filter transactions to only receive notification from address list", + "required": false, + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ADDRESS" + } + } + } + ], + "result": { + "name": "subscription_id", + "schema": { + "$ref": "#/components/schemas/SUBSCRIPTION_ID" + } + }, + "errors": [ + { + "$ref": "#/components/errors/TOO_MANY_ADDRESSES_IN_FILTER" + } + ] + }, + { + "name": "starknet_subscriptionPendingTransactions", + "summary": "New pending transaction notification", + "description": "Notification to the client of a new pending transaction", + "params": [ + { + "name": "subscription_id", + "schema": { + "$ref": "#/components/schemas/SUBSCRIPTION_ID" + } + }, + { + "name": "result", + "description": "Either a tranasaction hash or full transaction details, based on subscription", + "schema": { + "oneOf": [ + { + "$ref": "./api/starknet_api_openrpc.json#/components/schemas/TXN_HASH" + }, + { + "$ref": "./api/starknet_api_openrpc.json#/components/schemas/TXN" + } + ] + } + } + ], + "errors": [] + }, + { + "name": "starknet_subscriptionReorg", + "description": "Notifies the subscriber of a reorganization of the chain", + "summary": "Can be received from subscribing to newHeads, Events, TransactionStatus", + "params": [ + { + "name": "subscription_id", + "schema": { + "$ref": "#/components/schemas/REORG_DATA" + } + } + ] + }, + { + "name": "starknet_unsubscribe", + "summary": "Closes a websocket subscription", + "description": "Close a previously opened ws stream, with the corresponding subscription id", + "params": [ + { + "name": "subscription_id", + "summary": "The subscription to close", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "result": { + "name": "Unsubscription result", + "description": "True if the unsubscription was successful", + "schema": { + "type": "boolean" + } + }, + "errors": [ + { + "$ref": "#/components/errors/INVALID_SUBSCRIPTION_ID" + } + ] + } + ], + + "components": { + "schemas": { + "FELT": { + "$ref": "./api/starknet_api_openrpc.json#/components/schemas/FELT" + }, + "ADDRESS": { + "$ref": "./api/starknet_api_openrpc.json#/components/schemas/ADDRESS" + }, + "NEW_TXN_STATUS": { + "title": "New transaction Status", + "type": "object", + "properties": { + "transaction_hash": { + "$ref": "./api/starknet_api_openrpc.json#/components/schemas/TXN_HASH" + }, + "status": { + "schema": { + "$ref": "./api/starknet_api_openrpc.json#/components/schemas/TXN_STATUS_RESULT" + } + } + } + }, + "SUBSCRIPTION_ID": { + "name": "subscription id", + "description": "An identifier for this subscription stream used to associate events with this subscription.", + "schema": { + "type": "integer" + } + }, + "REORG_DATA": { + "name": "Reorg Data", + "description": "Data about reorganized blocks, starting and ending block number and hash", + "properties": { + "starting_block_hash": { + "title": "Starting Block Hash", + "description": "Hash of the first known block of the orphaned chain", + "schema": { + "$ref": "./api/starknet_api_openrpc.json#/components/schemas/BLOCK_HASH" + } + }, + "starting_block_number": { + "title": "Starting Block Number", + "description": "Number of the first known block of the orphaned chain", + "$ref": "./api/starknet_api_openrpc.json#/components/schemas/BLOCK_NUMBER" + }, + "ending_block_hash": { + "title": "Ending Block", + "description": "The last known block of the orphaned chain", + "schema": { + "$ref": "./api/starknet_api_openrpc.json#/components/schemas/BLOCK_HASH" + } + }, + "ending_block_number": { + "title": "Ending Block Number", + "description": "Number of the last known block of the orphaned chain", + "$ref": "./api/starknet_api_openrpc.json#/components/schemas/BLOCK_NUMBER" + } + }, + "required": ["starting_block_hash", "starting_block_number", "ending_block_hash", "ending_block_number"] + } + }, + "errors": { + "INVALID_SUBSCRIPTION_ID": { + "code": 66, + "message": "Invalid subscription id" + }, + "TOO_MANY_ADDRESSES_IN_FILTER": { + "code": 67, + "message": "Too many addresses in filter sender_address filter" + }, + "TOO_MANY_BLOCKS_BACK": { + "code": 68, + "message": "Cannot go back more than 1024 blocks" + } + } + } +} From da0cc4b93ab96772bb967d415789db6c23051c5a Mon Sep 17 00:00:00 2001 From: Krisztian Kovacs Date: Fri, 4 Oct 2024 15:49:12 +0200 Subject: [PATCH 133/282] feat(rpc/v08): add `pathfinder_getProof` --- crates/rpc/src/v08.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/rpc/src/v08.rs b/crates/rpc/src/v08.rs index e9f79e50b4..fabab0347f 100644 --- a/crates/rpc/src/v08.rs +++ b/crates/rpc/src/v08.rs @@ -12,4 +12,6 @@ pub fn register_routes() -> RpcRouterBuilder { .register("starknet_subscribePendingTransactions", SubscribePendingTransactions) .register("starknet_subscribeEvents", SubscribeEvents) .register("starknet_specVersion", || "0.8.0-rc0") + + .register("pathfinder_getProof", crate::pathfinder::methods::get_proof) } From 35b762142d7782fa034ff07a32b349a50b129703 Mon Sep 17 00:00:00 2001 From: Krisztian Kovacs Date: Fri, 4 Oct 2024 16:21:57 +0200 Subject: [PATCH 134/282] chore(doc/rpc/v08): fix typo --- doc/rpc/v08/starknet_api_openrpc.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/rpc/v08/starknet_api_openrpc.json b/doc/rpc/v08/starknet_api_openrpc.json index cc02a1eb3a..e9c0a904f4 100644 --- a/doc/rpc/v08/starknet_api_openrpc.json +++ b/doc/rpc/v08/starknet_api_openrpc.json @@ -3685,7 +3685,7 @@ }, "CONTRACT_EXECUTION_ERROR": { "description": "structured error that can later be processed by wallets or sdks", - "titel": "contract execution error", + "title": "contract execution error", "oneOf": [ { "type": "object", From ce93fc8f5d067533c7e7177cdd3770dfe89bae62 Mon Sep 17 00:00:00 2001 From: Krzysztof Lis Date: Mon, 7 Oct 2024 18:23:54 +0200 Subject: [PATCH 135/282] chore: sort mods --- crates/pathfinder/src/lib.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/pathfinder/src/lib.rs b/crates/pathfinder/src/lib.rs index 702a10ff6f..ec0d1489b6 100644 --- a/crates/pathfinder/src/lib.rs +++ b/crates/pathfinder/src/lib.rs @@ -1,7 +1,6 @@ #![deny(rust_2018_idioms)] pub mod monitoring; +pub mod p2p_network; pub mod state; pub mod sync; - -pub mod p2p_network; From 3cd38668d68cee1c12f63d9901e1741d9416e0e8 Mon Sep 17 00:00:00 2001 From: Krzysztof Lis Date: Mon, 7 Oct 2024 18:24:35 +0200 Subject: [PATCH 136/282] chore: expose constant to other sub modules in the crate --- crates/pathfinder/src/state.rs | 1 + crates/pathfinder/src/state/sync.rs | 14 +++++++------- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/crates/pathfinder/src/state.rs b/crates/pathfinder/src/state.rs index fa4a2c26ec..5b7776102d 100644 --- a/crates/pathfinder/src/state.rs +++ b/crates/pathfinder/src/state.rs @@ -10,4 +10,5 @@ pub use sync::{ Gossiper, StarknetStateUpdate, SyncContext, + RESET_DELAY_ON_FAILURE, }; diff --git a/crates/pathfinder/src/state/sync.rs b/crates/pathfinder/src/state/sync.rs index 4a38e1d59f..616ed8f749 100644 --- a/crates/pathfinder/src/state/sync.rs +++ b/crates/pathfinder/src/state/sync.rs @@ -36,6 +36,13 @@ use tokio::sync::watch::Sender as WatchSender; use crate::state::l1::L1SyncContext; use crate::state::l2::{BlockChain, L2SyncContext}; +/// Delay before restarting L1 or L2 tasks if they fail. This delay helps +/// prevent DoS if these tasks are crashing. +#[cfg(not(test))] +pub const RESET_DELAY_ON_FAILURE: std::time::Duration = std::time::Duration::from_secs(60); +#[cfg(test)] +pub const RESET_DELAY_ON_FAILURE: std::time::Duration = std::time::Duration::ZERO; + #[derive(Debug)] pub enum SyncEvent { L1Update(EthereumStateUpdate), @@ -290,13 +297,6 @@ where fetch_casm_from_fgw, )); - /// Delay before restarting L1 or L2 tasks if they fail. This delay helps - /// prevent DoS if these tasks are crashing. - #[cfg(not(test))] - const RESET_DELAY_ON_FAILURE: std::time::Duration = std::time::Duration::from_secs(60); - #[cfg(test)] - const RESET_DELAY_ON_FAILURE: std::time::Duration = std::time::Duration::ZERO; - loop { tokio::select! { _ = &mut pending_handle => { From d5ae94514280099d102695950a8f31bbdd874a9a Mon Sep 17 00:00:00 2001 From: Krzysztof Lis Date: Mon, 7 Oct 2024 18:25:24 +0200 Subject: [PATCH 137/282] feat(sync/checkpoint): make checkpoint sync infallible Force it to retry in case of an L1 api error, similarly to how feeder gateway sync works. --- crates/pathfinder/src/sync.rs | 44 +++++++++++++++++++++++------------ 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/crates/pathfinder/src/sync.rs b/crates/pathfinder/src/sync.rs index e7efbde0db..a0333e5d8a 100644 --- a/crates/pathfinder/src/sync.rs +++ b/crates/pathfinder/src/sync.rs @@ -24,6 +24,8 @@ use stream::ProcessStage; use tokio::sync::watch::{self, Receiver}; use tokio_stream::wrappers::WatchStream; +use crate::state::RESET_DELAY_ON_FAILURE; + mod checkpoint; mod class_definitions; mod error; @@ -52,7 +54,7 @@ pub struct Sync { impl Sync { pub async fn run(self) -> anyhow::Result<()> { - let (next, parent_hash) = self.checkpoint_sync().await?; + let (next, parent_hash) = self.checkpoint_sync().await; // TODO: depending on how this is implemented, we might want to loop around it. self.track_sync(next, parent_hash).await @@ -63,25 +65,37 @@ impl Sync { tracing::debug!(?err, "Log and punish as appropriate"); } - async fn get_checkpoint(&self) -> anyhow::Result { + /// Retry forever until a valid L1 checkpoint is retrieved + /// + /// ### Important + /// + /// We assume that the L1 endpoint is configured correctly and any L1 API + /// errors are transient. We cannot proceed without a checkpoint, so we + /// retry until we get one. + async fn get_checkpoint(&self) -> pathfinder_ethereum::EthereumStateUpdate { use pathfinder_ethereum::EthereumApi; - match &self.l1_checkpoint_override { - Some(checkpoint) => Ok(*checkpoint), - None => self - .eth_client - .get_starknet_state(&self.eth_address) - .await - .context("Fetching latest L1 checkpoint"), + if let Some(forced) = &self.l1_checkpoint_override { + return *forced; + } + + loop { + match self.eth_client.get_starknet_state(&self.eth_address).await { + Ok(latest) => return latest, + Err(error) => { + tracing::warn!(%error, "Failed to get L1 checkpoint, retrying"); + tokio::time::sleep(RESET_DELAY_ON_FAILURE); + } + } } } /// Run checkpoint sync until it completes successfully, and we are within /// some margin of the latest L1 block. Returns the next block number to /// sync and its parent hash. - async fn checkpoint_sync(&self) -> anyhow::Result<(BlockNumber, BlockHash)> { - let mut checkpoint = self.get_checkpoint().await?; + async fn checkpoint_sync(&self) -> (BlockNumber, BlockHash) { + let mut checkpoint = self.get_checkpoint().await; - Ok(loop { + loop { let result = checkpoint::Sync { storage: self.storage.clone(), p2p: self.p2p.clone(), @@ -106,16 +120,16 @@ impl Sync { } }; - // Initial sync might take so long, that the latest checkpoint is actually far + // Initial sync might take so long that the latest checkpoint is actually far // ahead again. Repeat until we are within some margin of L1. - let latest_checkpoint = self.get_checkpoint().await?; + let latest_checkpoint = self.get_checkpoint().await; if checkpoint.block_number + CHECKPOINT_MARGIN < latest_checkpoint.block_number { checkpoint = latest_checkpoint; continue; } break continue_from; - }) + } } /// Run the track sync until it completes successfully, requires the From 54f4d11470e4952218f71d3af1b3041a4e4a4ce3 Mon Sep 17 00:00:00 2001 From: Krzysztof Lis Date: Mon, 7 Oct 2024 19:00:14 +0200 Subject: [PATCH 138/282] feat: restart checkpoint and track sync except for fatal internal errors --- crates/pathfinder/src/sync.rs | 86 +++++++++++++++++++++++++---------- 1 file changed, 62 insertions(+), 24 deletions(-) diff --git a/crates/pathfinder/src/sync.rs b/crates/pathfinder/src/sync.rs index a0333e5d8a..8cedd13b44 100644 --- a/crates/pathfinder/src/sync.rs +++ b/crates/pathfinder/src/sync.rs @@ -1,12 +1,14 @@ #![allow(dead_code, unused)] use core::panic; +use std::sync::Arc; use std::time::Duration; use anyhow::Context; -use error::SyncError2; +use error::{SyncError, SyncError2}; use futures::{pin_mut, Stream, StreamExt}; use p2p::client::peer_agnostic::Client as P2PClient; +use p2p::PeerData; use pathfinder_common::{ block_hash, BlockHash, @@ -54,13 +56,13 @@ pub struct Sync { impl Sync { pub async fn run(self) -> anyhow::Result<()> { - let (next, parent_hash) = self.checkpoint_sync().await; + let (next, parent_hash) = self.checkpoint_sync().await?; // TODO: depending on how this is implemented, we might want to loop around it. self.track_sync(next, parent_hash).await } - async fn handle_error(&self, err: error::SyncError) { + async fn handle_recoverable_error(&self, err: error::SyncError) { // TODO tracing::debug!(?err, "Log and punish as appropriate"); } @@ -92,7 +94,13 @@ impl Sync { /// Run checkpoint sync until it completes successfully, and we are within /// some margin of the latest L1 block. Returns the next block number to /// sync and its parent hash. - async fn checkpoint_sync(&self) -> (BlockNumber, BlockHash) { + /// + /// ### Important + /// + /// Sync is restarted on recoverable errors and only fatal errors (e.g.: + /// database failure, runtime failure, etc.) cause this function to exit + /// with an error. + async fn checkpoint_sync(&self) -> anyhow::Result<(BlockNumber, BlockHash)> { let mut checkpoint = self.get_checkpoint().await; loop { @@ -114,8 +122,9 @@ impl Sync { // Handle the error let continue_from = match result { Ok(continue_from) => continue_from, + Err(SyncError::Other(err)) => return Err(err), Err(err) => { - self.handle_error(err).await; + self.handle_recoverable_error(err).await; continue; } }; @@ -128,29 +137,58 @@ impl Sync { continue; } - break continue_from; + break Ok(continue_from); } } - /// Run the track sync until it completes successfully, requires the - /// number and parent hash of the first block to sync - async fn track_sync(&self, next: BlockNumber, parent_hash: BlockHash) -> anyhow::Result<()> { - let result = track::Sync { - latest: LatestStream::spawn(self.fgw_client.clone(), Duration::from_secs(2)), - p2p: self.p2p.clone(), - storage: self.storage.clone(), - chain: self.chain, - chain_id: self.chain_id, - public_key: self.public_key, - block_hash_db: Some(pathfinder_block_hashes::BlockHashDb::new(self.chain)), - verify_tree_hashes: self.verify_tree_hashes, - } - .run(next, parent_hash, self.fgw_client.clone()) - .await; - - tracing::info!("Track sync completed: {result:#?}"); + /// Run the track sync forever, requires the number and parent hash of the + /// first block to sync. + /// + /// ### Important + /// + /// Sync is restarted on recoverable errors and only fatal errors (e.g.: + /// database failure, runtime failure, etc.) cause this function to exit + /// with an error. + async fn track_sync( + &self, + mut next: BlockNumber, + mut parent_hash: BlockHash, + ) -> anyhow::Result<()> { + loop { + let mut result = track::Sync { + latest: LatestStream::spawn(self.fgw_client.clone(), Duration::from_secs(2)), + p2p: self.p2p.clone(), + storage: self.storage.clone(), + chain: self.chain, + chain_id: self.chain_id, + public_key: self.public_key, + block_hash_db: Some(pathfinder_block_hashes::BlockHashDb::new(self.chain)), + verify_tree_hashes: self.verify_tree_hashes, + } + .run(next, parent_hash, self.fgw_client.clone()) + .await; - Ok(()) + match &mut result { + Ok(_) => tracing::debug!("Restarting track sync: unexpected end of Block stream"), + Err(PeerData { + data: SyncError2::Other(fatal), + .. + }) => { + let error = if let Some(e) = Arc::get_mut(fatal) { + let mut taken = anyhow::Error::msg(""); + std::mem::swap(e, &mut taken); + taken + } else { + anyhow::Error::msg(fatal.root_cause().to_string()) + }; + tracing::error!(%error, "Stopping track sync"); + return Err(error); + } + Err(PeerData { data: error, .. }) => { + tracing::debug!(%error, "Restarting track sync") + } + } + } } } From 9b1d307dc1ab4702ca5abdbf335cb705f3f36627 Mon Sep 17 00:00:00 2001 From: Krisztian Kovacs Date: Tue, 15 Oct 2024 11:41:13 +0200 Subject: [PATCH 139/282] feat(rpc/v08): add methods that are the same as in v07 --- crates/rpc/src/lib.rs | 15 --------------- crates/rpc/src/v08.rs | 29 ++++++++++++++++++++++------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/crates/rpc/src/lib.rs b/crates/rpc/src/lib.rs index bf21bfeca4..b4ce3286da 100644 --- a/crates/rpc/src/lib.rs +++ b/crates/rpc/src/lib.rs @@ -870,27 +870,12 @@ mod tests { #[case::root_pathfinder("/", "pathfinder_rpc_api.json", &["pathfinder_version", "pathfinder_getTransactionStatus"])] #[case::v0_8_api ("/rpc/v0_8", "v08/starknet_api_openrpc.json", &[ - "starknet_getBlockWithTxHashes", - "starknet_getBlockWithTxs", "starknet_getBlockWithReceipts", - "starknet_getStateUpdate", - "starknet_getStorageAt", "starknet_getMessagesStatus", - "starknet_getTransactionByHash", - "starknet_getTransactionByBlockIdAndIndex", "starknet_getTransactionReceipt", - "starknet_getClass", - "starknet_getClassHashAt", - "starknet_getClassAt", - "starknet_getBlockTransactionCount", "starknet_call", "starknet_estimateFee", "starknet_estimateMessageFee", - "starknet_blockNumber", - "starknet_blockHashAndNumber", - "starknet_chainId", - "starknet_getEvents", - "starknet_getNonce", "starknet_getStorageProof", ])] #[case::v0_8_trace("/rpc/v0_8", "v08/starknet_trace_api_openrpc.json", &[ diff --git a/crates/rpc/src/v08.rs b/crates/rpc/src/v08.rs index fabab0347f..6675c26e9f 100644 --- a/crates/rpc/src/v08.rs +++ b/crates/rpc/src/v08.rs @@ -6,12 +6,27 @@ use crate::method::subscribe_pending_transactions::SubscribePendingTransactions; #[rustfmt::skip] pub fn register_routes() -> RpcRouterBuilder { RpcRouter::builder(crate::RpcVersion::V08) - .register("starknet_syncing", crate::method::syncing) - .register("starknet_getTransactionStatus", crate::method::get_transaction_status) - .register("starknet_subscribeNewHeads", SubscribeNewHeads) - .register("starknet_subscribePendingTransactions", SubscribePendingTransactions) - .register("starknet_subscribeEvents", SubscribeEvents) - .register("starknet_specVersion", || "0.8.0-rc0") + .register("starknet_blockHashAndNumber", crate::method::block_hash_and_number) + .register("starknet_blockNumber", crate::method::block_number) + .register("starknet_chainId", crate::method::chain_id) + .register("starknet_getBlockTransactionCount", crate::method::get_block_transaction_count) + .register("starknet_getBlockWithTxHashes", crate::method::get_block_with_tx_hashes) + .register("starknet_getBlockWithTxs", crate::method::get_block_with_txs) + .register("starknet_getClass", crate::method::get_class) + .register("starknet_getClassAt", crate::method::get_class_at) + .register("starknet_getClassHashAt", crate::method::get_class_hash_at) + .register("starknet_getEvents", crate::method::get_events) + .register("starknet_getNonce", crate::method::get_nonce) + .register("starknet_getStateUpdate", crate::method::get_state_update) + .register("starknet_getStorageAt", crate::method::get_storage_at) + .register("starknet_getTransactionByBlockIdAndIndex", crate::method::get_transaction_by_block_id_and_index) + .register("starknet_getTransactionByHash", crate::method::get_transaction_by_hash) + .register("starknet_getTransactionStatus", crate::method::get_transaction_status) + .register("starknet_subscribeNewHeads", SubscribeNewHeads) + .register("starknet_subscribePendingTransactions", SubscribePendingTransactions) + .register("starknet_subscribeEvents", SubscribeEvents) + .register("starknet_specVersion", || "0.8.0-rc0") + .register("starknet_syncing", crate::method::syncing) - .register("pathfinder_getProof", crate::pathfinder::methods::get_proof) + .register("pathfinder_getProof", crate::pathfinder::methods::get_proof) } From f1fd7c6713ba209f4995f00ad397a160e228ea02 Mon Sep 17 00:00:00 2001 From: sistemd Date: Tue, 15 Oct 2024 11:50:24 +0200 Subject: [PATCH 140/282] starknet_subscribeTransactionStatus --- crates/pathfinder/src/state/sync.rs | 6 +- crates/rpc/src/jsonrpc/router/subscription.rs | 37 +- crates/rpc/src/method.rs | 1 + crates/rpc/src/method/subscribe_events.rs | 3 +- crates/rpc/src/method/subscribe_new_heads.rs | 3 +- .../method/subscribe_pending_transactions.rs | 25 +- .../method/subscribe_transaction_status.rs | 413 ++++++++++++++++++ crates/storage/src/connection/transaction.rs | 18 + 8 files changed, 469 insertions(+), 37 deletions(-) create mode 100644 crates/rpc/src/method/subscribe_transaction_status.rs diff --git a/crates/pathfinder/src/state/sync.rs b/crates/pathfinder/src/state/sync.rs index 4a38e1d59f..6db78f7e5c 100644 --- a/crates/pathfinder/src/state/sync.rs +++ b/crates/pathfinder/src/state/sync.rs @@ -842,7 +842,11 @@ async fn l1_update( } } - transaction.commit().context("Commit database transaction") + transaction + .commit() + .context("Commit database transaction")?; + + Ok(()) }) } diff --git a/crates/rpc/src/jsonrpc/router/subscription.rs b/crates/rpc/src/jsonrpc/router/subscription.rs index 7bad2beb7a..88b26dee5e 100644 --- a/crates/rpc/src/jsonrpc/router/subscription.rs +++ b/crates/rpc/src/jsonrpc/router/subscription.rs @@ -75,28 +75,30 @@ pub trait RpcSubscriptionFlow: Send + Sync { } /// The block to start streaming from. If the subscription endpoint does not - /// support catching up, this method should always return - /// [`BlockId::Latest`]. - fn starting_block(params: &Self::Params) -> BlockId; + /// support catching up, leave this method unimplemented. + fn starting_block(_params: &Self::Params) -> BlockId { + BlockId::Latest + } /// Fetch historical data from the `from` block to the `to` block. The /// range is inclusive on both ends. If there is no historical data in the /// range, return an empty vec. If the subscription endpoint does not - /// support catching up, this method should always return - /// `Ok(CatchUp::default())`. + /// support catching up, leave this method unimplemented. async fn catch_up( - state: &RpcContext, - params: &Self::Params, - from: BlockNumber, - to: BlockNumber, - ) -> Result, RpcError>; + _state: &RpcContext, + _params: &Self::Params, + _from: BlockNumber, + _to: BlockNumber, + ) -> Result, RpcError> { + Ok(Default::default()) + } /// Subscribe to active updates. async fn subscribe( state: RpcContext, params: Self::Params, tx: mpsc::Sender>, - ); + ) -> Result<(), RpcError>; } pub struct CatchUp { @@ -242,10 +244,17 @@ where // Subscribe to new blocks. Receive the first subscription message. let (tx1, mut rx1) = mpsc::channel::>(1024); - { + tokio::spawn({ let params = params.clone(); - tokio::spawn(T::subscribe(router.context.clone(), params, tx1)); - } + let context = router.context.clone(); + let req_id = req_id.clone(); + let tx = tx.clone(); + async move { + if let Err(e) = T::subscribe(context, params, tx1).await { + tx.send_err(e, req_id).await.ok(); + } + } + }); let first_msg = match rx1.recv().await { Some(msg) => msg, None => { diff --git a/crates/rpc/src/method.rs b/crates/rpc/src/method.rs index 8814fe746e..c24cb71677 100644 --- a/crates/rpc/src/method.rs +++ b/crates/rpc/src/method.rs @@ -26,6 +26,7 @@ pub mod simulate_transactions; pub mod subscribe_events; pub mod subscribe_new_heads; pub mod subscribe_pending_transactions; +pub mod subscribe_transaction_status; pub mod syncing; pub mod trace_block_transactions; pub mod trace_transaction; diff --git a/crates/rpc/src/method/subscribe_events.rs b/crates/rpc/src/method/subscribe_events.rs index 54ed5f2258..eed4f8e727 100644 --- a/crates/rpc/src/method/subscribe_events.rs +++ b/crates/rpc/src/method/subscribe_events.rs @@ -131,7 +131,7 @@ impl RpcSubscriptionFlow for SubscribeEvents { state: RpcContext, params: Self::Params, tx: mpsc::Sender>, - ) { + ) -> Result<(), RpcError> { let mut blocks = state.notifications.l2_blocks.subscribe(); let mut reorgs = state.notifications.reorgs.subscribe(); let params = params.unwrap_or_default(); @@ -217,6 +217,7 @@ impl RpcSubscriptionFlow for SubscribeEvents { } } } + Ok(()) } } diff --git a/crates/rpc/src/method/subscribe_new_heads.rs b/crates/rpc/src/method/subscribe_new_heads.rs index 51e411c712..27674c1fec 100644 --- a/crates/rpc/src/method/subscribe_new_heads.rs +++ b/crates/rpc/src/method/subscribe_new_heads.rs @@ -98,7 +98,7 @@ impl RpcSubscriptionFlow for SubscribeNewHeads { state: RpcContext, _params: Self::Params, tx: mpsc::Sender>, - ) { + ) -> Result<(), RpcError> { let mut headers = state.notifications.block_headers.subscribe(); let mut reorgs = state.notifications.reorgs.subscribe(); loop { @@ -149,6 +149,7 @@ impl RpcSubscriptionFlow for SubscribeNewHeads { } } } + Ok(()) } } diff --git a/crates/rpc/src/method/subscribe_pending_transactions.rs b/crates/rpc/src/method/subscribe_pending_transactions.rs index 5939c557fe..9525cbf770 100644 --- a/crates/rpc/src/method/subscribe_pending_transactions.rs +++ b/crates/rpc/src/method/subscribe_pending_transactions.rs @@ -2,11 +2,11 @@ use std::collections::HashSet; use axum::async_trait; use pathfinder_common::transaction::Transaction; -use pathfinder_common::{BlockId, BlockNumber, ContractAddress, TransactionHash}; +use pathfinder_common::{BlockNumber, ContractAddress, TransactionHash}; use tokio::sync::mpsc; use crate::context::RpcContext; -use crate::jsonrpc::{CatchUp, RpcError, RpcSubscriptionFlow, SubscriptionMessage}; +use crate::jsonrpc::{RpcError, RpcSubscriptionFlow, SubscriptionMessage}; pub struct SubscribePendingTransactions; @@ -63,26 +63,11 @@ impl RpcSubscriptionFlow for SubscribePendingTransactions { type Params = Option; type Notification = Notification; - fn starting_block(_params: &Self::Params) -> BlockId { - // Catch-up is not supported. - BlockId::Latest - } - - async fn catch_up( - _state: &RpcContext, - _params: &Self::Params, - _from: BlockNumber, - _to: BlockNumber, - ) -> Result, RpcError> { - // Catch-up is not supported. - Ok(Default::default()) - } - async fn subscribe( state: RpcContext, params: Self::Params, tx: mpsc::Sender>, - ) { + ) -> Result<(), RpcError> { let params = params.unwrap_or_default(); let mut pending_data = state.pending_data.0.clone(); // Last block sent to the subscriber. Initial value doesn't really matter. @@ -138,12 +123,12 @@ impl RpcSubscriptionFlow for SubscribePendingTransactions { .is_err() { // Subscription has been closed. - return; + return Ok(()); } } if pending_data.changed().await.is_err() { tracing::debug!("Pending data channel closed, stopping subscription"); - break; + return Ok(()); } } } diff --git a/crates/rpc/src/method/subscribe_transaction_status.rs b/crates/rpc/src/method/subscribe_transaction_status.rs new file mode 100644 index 0000000000..34fd434194 --- /dev/null +++ b/crates/rpc/src/method/subscribe_transaction_status.rs @@ -0,0 +1,413 @@ +use std::sync::Arc; +use std::time::Duration; + +use axum::async_trait; +use pathfinder_common::{BlockId, BlockNumber, TransactionHash}; +use reply::transaction_status as status; +use starknet_gateway_client::GatewayApi; +use starknet_gateway_types::reply; +use tokio::sync::{broadcast, mpsc}; +use tokio::time::MissedTickBehavior; + +use super::REORG_SUBSCRIPTION_NAME; +use crate::context::RpcContext; +use crate::error::ApplicationError; +use crate::jsonrpc::{RpcError, RpcSubscriptionFlow, SubscriptionMessage}; +use crate::Reorg; + +pub struct SubscribeEvents; + +#[derive(Debug, Clone, Default)] +pub struct Params { + transaction_hash: TransactionHash, + block: Option, +} + +impl crate::dto::DeserializeForVersion for Params { + fn deserialize(value: crate::dto::Value) -> Result { + value.deserialize_map(|value| { + Ok(Self { + transaction_hash: value.deserialize("transaction_hash").map(TransactionHash)?, + block: value.deserialize_optional_serde("block")?, + }) + }) + } +} + +#[derive(Debug)] +pub enum Notification { + TransactionStatus(TransactionHash, FinalityStatus, Option), + Reorg(Arc), +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub enum FinalityStatus { + Received, + AcceptedOnL2, + AcceptedOnL1, + Rejected, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ExecutionStatus { + Succeeded, + Reverted, +} + +impl crate::dto::serialize::SerializeForVersion for Notification { + fn serialize( + &self, + serializer: crate::dto::serialize::Serializer, + ) -> Result { + return match self { + Notification::TransactionStatus(tx_hash, finality_status, execution_status) => { + let mut serializer = serializer.serialize_struct()?; + serializer.serialize_field("transaction_hash", &tx_hash)?; + serializer.serialize_field( + "status", + &TransactionStatus { + finality_status: *finality_status, + execution_status: *execution_status, + }, + )?; + serializer.end() + } + Notification::Reorg(reorg) => reorg.serialize(serializer), + }; + + struct TransactionStatus { + finality_status: FinalityStatus, + execution_status: Option, + } + + impl crate::dto::serialize::SerializeForVersion for TransactionStatus { + fn serialize( + &self, + serializer: crate::dto::serialize::Serializer, + ) -> Result { + let mut serializer = serializer.serialize_struct()?; + serializer.serialize_field( + "finality_status", + &match self.finality_status { + FinalityStatus::Received => "RECEIVED", + FinalityStatus::AcceptedOnL2 => "ACCEPTED_ON_L2", + FinalityStatus::AcceptedOnL1 => "ACCEPTED_ON_L1", + FinalityStatus::Rejected => "REJECTED", + }, + )?; + if let Some(execution_status) = self.execution_status { + serializer.serialize_field( + "execution_status", + &match execution_status { + ExecutionStatus::Succeeded => "SUCCEEDED", + ExecutionStatus::Reverted => "REVERTED", + }, + )?; + } + serializer.end() + } + } + } +} + +const SUBSCRIPTION_NAME: &str = "starknet_subscriptionTransactionsStatus"; + +#[async_trait] +impl RpcSubscriptionFlow for SubscribeEvents { + type Params = Params; + type Notification = Notification; + + #[allow(clippy::collapsible_if)] + async fn subscribe( + state: RpcContext, + params: Self::Params, + tx: mpsc::Sender>, + ) -> Result<(), RpcError> { + 'reorg: loop { + let tx_hash = params.transaction_hash; + let mut sender = Sender { + tx: &tx, + tx_hash, + last_finality_status: None, + last_execution_status: None, + last_block_number: BlockNumber::GENESIS, // Initial value not important. + }; + let mut pending_data = state.pending_data.0.clone(); + let mut l2_blocks = state.notifications.l2_blocks.subscribe(); + let mut reorgs = state.notifications.reorgs.subscribe(); + let storage = state.storage.clone(); + if let Some(first_block) = params.block { + // Check if we have the transaction in our database, and if so, send the + // relevant transaction status updates. + let (first_block, l1_state, tx_with_receipt) = + tokio::task::spawn_blocking(move || -> Result<_, RpcError> { + let mut conn = storage.connection().map_err(RpcError::InternalError)?; + let db = conn.transaction().map_err(RpcError::InternalError)?; + let first_block = db + .block_number(first_block.try_into().map_err(|_| { + RpcError::InvalidParams("block cannot be pending".to_string()) + })?) + .map_err(RpcError::InternalError)?; + let l1_block_number = + db.latest_l1_state().map_err(RpcError::InternalError)?; + let tx_with_receipt = db + .transaction_with_receipt(tx_hash) + .map_err(RpcError::InternalError)?; + Ok((first_block, l1_block_number, tx_with_receipt)) + }) + .await + .map_err(|e| RpcError::InternalError(e.into()))??; + let first_block = first_block + .ok_or_else(|| RpcError::ApplicationError(ApplicationError::BlockNotFound))?; + if let Some((_, receipt, _, block_number)) = tx_with_receipt { + // We already have the transaction in the database. + if let Some(parent) = block_number.parent() { + // This transaction was pending in the parent block. + if first_block <= parent { + if sender + .send(parent, FinalityStatus::Received, None) + .await + .is_err() + { + // Subscription closing. + break; + } + } + } + let execution_status = if receipt.is_reverted() { + Some(ExecutionStatus::Reverted) + } else { + Some(ExecutionStatus::Succeeded) + }; + if first_block <= block_number { + if sender + .send(block_number, FinalityStatus::AcceptedOnL2, execution_status) + .await + .is_err() + { + // Subscription closing. + break; + } + } + if let Some(l1_state) = l1_state { + if l1_state.block_number >= block_number { + if sender + .send( + l1_state.block_number, + FinalityStatus::AcceptedOnL1, + execution_status, + ) + .await + .is_err() + { + // Subscription closing. + break; + } + } + } + } + } + let pending = pending_data.borrow_and_update().clone(); + if pending + .block + .transactions + .iter() + .any(|tx| tx.hash == tx_hash) + { + if sender + .send(pending.number, FinalityStatus::Received, None) + .await + .is_err() + { + // Subscription closing. + break; + } + } + // Stream transaction status updates. + let mut interval = tokio::time::interval(if cfg!(test) { + Duration::from_secs(5) + } else { + Duration::from_secs(60) + }); + interval.set_missed_tick_behavior(MissedTickBehavior::Delay); + loop { + tokio::select! { + _ = interval.tick() => { + match state.sequencer.transaction(params.transaction_hash).await { + Ok(status) => { + if status.execution_status == status::ExecutionStatus::Rejected { + // Transaction has been rejected. + sender + .send(BlockNumber::GENESIS, FinalityStatus::Rejected, None) + .await + .ok(); + // No more updates needed. Even in case of reorg, the transaction will + // always be rejected. + break 'reorg; + } + } + Err(e) => { + tracing::warn!( + "Failed to get transaction status for subscription: {:?}", + e + ); + } + } + } + reorg = reorgs.recv() => { + match reorg { + Ok(reorg) => { + let block_number = sender.last_block_number; + if tx.send(SubscriptionMessage { + notification: Notification::Reorg(reorg), + block_number, + subscription_name: REORG_SUBSCRIPTION_NAME, + }).await.is_err() { + // Subscription closing. + break; + } + continue 'reorg; + } + Err(broadcast::error::RecvError::Closed) => { + tracing::debug!("Reorg channel closed, stopping subscription"); + break 'reorg; + } + Err(broadcast::error::RecvError::Lagged(_)) => { + tracing::warn!("Reorg channel lagged"); + } + } + } + r = pending_data.changed() => { + if r.is_err() { + tracing::debug!("Pending data channel closed, stopping subscription"); + break 'reorg; + } + let pending = pending_data.borrow_and_update().clone(); + if pending + .block + .transactions + .iter() + .any(|tx| tx.hash == tx_hash) + { + if sender + .send(pending.number, FinalityStatus::Received, None) + .await + .is_err() + { + // Subscription closing. + break; + } + } + } + l2_block = l2_blocks.recv() => { + match l2_block { + Ok(l2_block) => { + let receipt = l2_block.transaction_receipts.iter().find(|(receipt, _)| { + receipt.transaction_hash == tx_hash + }); + if let Some((receipt, _)) = receipt { + let execution_status = if receipt.is_reverted() { + Some(ExecutionStatus::Reverted) + } else { + Some(ExecutionStatus::Succeeded) + }; + // Send both received and accepted updates. + if sender + .send(l2_block.block_number, FinalityStatus::Received, None) + .await + .is_err() + { + // Subscription closing. + break; + } + if sender + .send(l2_block.block_number, FinalityStatus::AcceptedOnL2, execution_status) + .await + .is_err() + { + // Subscription closing. + break; + } + } + // Check if our transaction has been confirmed on L1. This is done + // here because it guarantees that the ACCEPTED_ON_L2 update will be + // sent before the ACCEPTED_ON_L1 update. + let storage = state.storage.clone(); + let l1_state = tokio::task::spawn_blocking(move || -> Result<_, RpcError> { + let mut conn = storage.connection().map_err(RpcError::InternalError)?; + let db = conn.transaction().map_err(RpcError::InternalError)?; + let l1_state = db.latest_l1_state().map_err(RpcError::InternalError)?; + Ok(l1_state) + }).await.map_err(|e| RpcError::InternalError(e.into()))??; + if let Some(l1_state) = l1_state { + if l1_state.block_number >= l2_block.block_number { + if sender + .send( + l1_state.block_number, + FinalityStatus::AcceptedOnL1, + sender.last_execution_status, + ) + .await + .is_err() + { + // Subscription closing. + break; + } + } + } + } + Err(broadcast::error::RecvError::Closed) => { + tracing::debug!("L2 block channel closed, stopping subscription"); + break 'reorg; + } + Err(broadcast::error::RecvError::Lagged(_)) => { + tracing::warn!("L2 block channel lagged"); + } + } + } + } + } + } + Ok(()) + } +} + +struct Sender<'a> { + tx: &'a mpsc::Sender>, + tx_hash: TransactionHash, + last_finality_status: Option, + last_execution_status: Option, + last_block_number: BlockNumber, +} + +impl Sender<'_> { + async fn send( + &mut self, + block_number: BlockNumber, + finality_status: FinalityStatus, + execution_status: Option, + ) -> Result<(), mpsc::error::SendError<()>> { + if let Some(last_finality_status) = self.last_finality_status { + if finality_status <= last_finality_status { + // Transaction status has not progressed. + return Ok(()); + } + } + self.last_finality_status = Some(finality_status); + self.last_execution_status = execution_status; + self.last_block_number = block_number; + self.tx + .send(SubscriptionMessage { + notification: Notification::TransactionStatus( + self.tx_hash, + finality_status, + execution_status, + ), + block_number, + subscription_name: SUBSCRIPTION_NAME, + }) + .await + .map_err(|_| mpsc::error::SendError(()))?; + Ok(()) + } +} diff --git a/crates/storage/src/connection/transaction.rs b/crates/storage/src/connection/transaction.rs index 7cdfbd5144..6a1fb33786 100644 --- a/crates/storage/src/connection/transaction.rs +++ b/crates/storage/src/connection/transaction.rs @@ -377,6 +377,24 @@ impl Transaction<'_> { .map_err(|e| e.into()) } + pub fn transaction_block_number( + &self, + hash: TransactionHash, + ) -> anyhow::Result> { + self.inner() + .query_row( + r" + SELECT block_headers.number FROM transaction_hashes + JOIN block_headers ON transaction_hashes.block_number = block_headers.number + WHERE transaction_hashes.hash = ? + ", + params![&hash], + |row| row.get_block_number(0), + ) + .optional() + .map_err(|e| e.into()) + } + fn query_transactions_by_block( &self, block_number: BlockNumber, From e3b925eb586827524bc797b60176f6b4afcbe431 Mon Sep 17 00:00:00 2001 From: sistemd Date: Tue, 15 Oct 2024 11:50:24 +0200 Subject: [PATCH 141/282] add failure_reason --- .../method/subscribe_transaction_status.rs | 88 +++++++++++-------- 1 file changed, 53 insertions(+), 35 deletions(-) diff --git a/crates/rpc/src/method/subscribe_transaction_status.rs b/crates/rpc/src/method/subscribe_transaction_status.rs index 34fd434194..811d8db1c1 100644 --- a/crates/rpc/src/method/subscribe_transaction_status.rs +++ b/crates/rpc/src/method/subscribe_transaction_status.rs @@ -2,6 +2,7 @@ use std::sync::Arc; use std::time::Duration; use axum::async_trait; +use pathfinder_common::receipt::ExecutionStatus; use pathfinder_common::{BlockId, BlockNumber, TransactionHash}; use reply::transaction_status as status; use starknet_gateway_client::GatewayApi; @@ -40,18 +41,12 @@ pub enum Notification { Reorg(Arc), } -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Debug, Clone, PartialEq, Eq)] pub enum FinalityStatus { Received, AcceptedOnL2, AcceptedOnL1, - Rejected, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum ExecutionStatus { - Succeeded, - Reverted, + Rejected { reason: Option }, } impl crate::dto::serialize::SerializeForVersion for Notification { @@ -66,8 +61,8 @@ impl crate::dto::serialize::SerializeForVersion for Notification { serializer.serialize_field( "status", &TransactionStatus { - finality_status: *finality_status, - execution_status: *execution_status, + finality_status, + execution_status, }, )?; serializer.end() @@ -75,12 +70,12 @@ impl crate::dto::serialize::SerializeForVersion for Notification { Notification::Reorg(reorg) => reorg.serialize(serializer), }; - struct TransactionStatus { - finality_status: FinalityStatus, - execution_status: Option, + struct TransactionStatus<'a> { + finality_status: &'a FinalityStatus, + execution_status: &'a Option, } - impl crate::dto::serialize::SerializeForVersion for TransactionStatus { + impl crate::dto::serialize::SerializeForVersion for TransactionStatus<'_> { fn serialize( &self, serializer: crate::dto::serialize::Serializer, @@ -92,7 +87,7 @@ impl crate::dto::serialize::SerializeForVersion for Notification { FinalityStatus::Received => "RECEIVED", FinalityStatus::AcceptedOnL2 => "ACCEPTED_ON_L2", FinalityStatus::AcceptedOnL1 => "ACCEPTED_ON_L1", - FinalityStatus::Rejected => "REJECTED", + FinalityStatus::Rejected { .. } => "REJECTED", }, )?; if let Some(execution_status) = self.execution_status { @@ -100,10 +95,22 @@ impl crate::dto::serialize::SerializeForVersion for Notification { "execution_status", &match execution_status { ExecutionStatus::Succeeded => "SUCCEEDED", - ExecutionStatus::Reverted => "REVERTED", + ExecutionStatus::Reverted { .. } => "REVERTED", }, )?; } + match (self.finality_status, self.execution_status) { + ( + FinalityStatus::Rejected { + reason: Some(reason), + }, + _, + ) + | (_, Some(ExecutionStatus::Reverted { reason })) => { + serializer.serialize_field("failure_reason", reason)?; + } + _ => {} + } serializer.end() } } @@ -174,14 +181,13 @@ impl RpcSubscriptionFlow for SubscribeEvents { } } } - let execution_status = if receipt.is_reverted() { - Some(ExecutionStatus::Reverted) - } else { - Some(ExecutionStatus::Succeeded) - }; if first_block <= block_number { if sender - .send(block_number, FinalityStatus::AcceptedOnL2, execution_status) + .send( + block_number, + FinalityStatus::AcceptedOnL2, + Some(receipt.execution_status.clone()), + ) .await .is_err() { @@ -195,7 +201,7 @@ impl RpcSubscriptionFlow for SubscribeEvents { .send( l1_state.block_number, FinalityStatus::AcceptedOnL1, - execution_status, + Some(receipt.execution_status.clone()), ) .await .is_err() @@ -238,7 +244,9 @@ impl RpcSubscriptionFlow for SubscribeEvents { if status.execution_status == status::ExecutionStatus::Rejected { // Transaction has been rejected. sender - .send(BlockNumber::GENESIS, FinalityStatus::Rejected, None) + .send(BlockNumber::GENESIS, FinalityStatus::Rejected { + reason: status.transaction_failure_reason.map(|reason| reason.error_message) + }, None) .await .ok(); // No more updates needed. Even in case of reorg, the transaction will @@ -306,11 +314,6 @@ impl RpcSubscriptionFlow for SubscribeEvents { receipt.transaction_hash == tx_hash }); if let Some((receipt, _)) = receipt { - let execution_status = if receipt.is_reverted() { - Some(ExecutionStatus::Reverted) - } else { - Some(ExecutionStatus::Succeeded) - }; // Send both received and accepted updates. if sender .send(l2_block.block_number, FinalityStatus::Received, None) @@ -321,7 +324,11 @@ impl RpcSubscriptionFlow for SubscribeEvents { break; } if sender - .send(l2_block.block_number, FinalityStatus::AcceptedOnL2, execution_status) + .send( + l2_block.block_number, + FinalityStatus::AcceptedOnL2, + Some(receipt.execution_status.clone()) + ) .await .is_err() { @@ -345,7 +352,7 @@ impl RpcSubscriptionFlow for SubscribeEvents { .send( l1_state.block_number, FinalityStatus::AcceptedOnL1, - sender.last_execution_status, + sender.last_execution_status.clone(), ) .await .is_err() @@ -387,14 +394,14 @@ impl Sender<'_> { finality_status: FinalityStatus, execution_status: Option, ) -> Result<(), mpsc::error::SendError<()>> { - if let Some(last_finality_status) = self.last_finality_status { - if finality_status <= last_finality_status { + if let Some(last_finality_status) = &self.last_finality_status { + if finality_status.as_num() <= last_finality_status.as_num() { // Transaction status has not progressed. return Ok(()); } } - self.last_finality_status = Some(finality_status); - self.last_execution_status = execution_status; + self.last_finality_status = Some(finality_status.clone()); + self.last_execution_status = execution_status.clone(); self.last_block_number = block_number; self.tx .send(SubscriptionMessage { @@ -411,3 +418,14 @@ impl Sender<'_> { Ok(()) } } + +impl FinalityStatus { + pub fn as_num(&self) -> u8 { + match self { + FinalityStatus::Received => 0, + FinalityStatus::AcceptedOnL2 => 1, + FinalityStatus::AcceptedOnL1 => 2, + FinalityStatus::Rejected { .. } => 3, + } + } +} From 2cd96c2636b31d108824285d40661273e9527151 Mon Sep 17 00:00:00 2001 From: sistemd Date: Tue, 15 Oct 2024 11:50:24 +0200 Subject: [PATCH 142/282] remove unused db method --- crates/storage/src/connection/transaction.rs | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/crates/storage/src/connection/transaction.rs b/crates/storage/src/connection/transaction.rs index 6a1fb33786..7cdfbd5144 100644 --- a/crates/storage/src/connection/transaction.rs +++ b/crates/storage/src/connection/transaction.rs @@ -377,24 +377,6 @@ impl Transaction<'_> { .map_err(|e| e.into()) } - pub fn transaction_block_number( - &self, - hash: TransactionHash, - ) -> anyhow::Result> { - self.inner() - .query_row( - r" - SELECT block_headers.number FROM transaction_hashes - JOIN block_headers ON transaction_hashes.block_number = block_headers.number - WHERE transaction_hashes.hash = ? - ", - params![&hash], - |row| row.get_block_number(0), - ) - .optional() - .map_err(|e| e.into()) - } - fn query_transactions_by_block( &self, block_number: BlockNumber, From e33d4a45dbfe7e68d0e9f56b645b4b43696fb4c9 Mon Sep 17 00:00:00 2001 From: sistemd Date: Tue, 15 Oct 2024 11:57:51 +0200 Subject: [PATCH 143/282] proper subscription errors --- crates/rpc/src/jsonrpc/router/subscription.rs | 278 ++++++++++++++++-- 1 file changed, 260 insertions(+), 18 deletions(-) diff --git a/crates/rpc/src/jsonrpc/router/subscription.rs b/crates/rpc/src/jsonrpc/router/subscription.rs index 88b26dee5e..0f95640787 100644 --- a/crates/rpc/src/jsonrpc/router/subscription.rs +++ b/crates/rpc/src/jsonrpc/router/subscription.rs @@ -13,7 +13,7 @@ use crate::context::RpcContext; use crate::dto::serialize::SerializeForVersion; use crate::dto::DeserializeForVersion; use crate::error::ApplicationError; -use crate::jsonrpc::{RequestId, RpcError, RpcRequest, RpcResponse}; +use crate::jsonrpc::{RpcError, RpcRequest, RpcResponse}; use crate::{RpcVersion, SubscriptionId}; pub const CATCH_UP_BATCH_SIZE: u64 = 64; @@ -30,7 +30,6 @@ pub(super) struct InvokeParams { input: serde_json::Value, subscription_id: SubscriptionId, subscriptions: Arc>>, - req_id: RequestId, ws_tx: mpsc::Sender>, lock: Arc>, } @@ -145,7 +144,6 @@ where input, subscription_id, subscriptions, - req_id, ws_tx, lock, }: InvokeParams, @@ -158,7 +156,7 @@ where let tx = SubscriptionSender { subscription_id, subscriptions, - tx: ws_tx.clone(), + tx: ws_tx, version: router.version, _phantom: Default::default(), }; @@ -209,7 +207,7 @@ where match T::catch_up(&router.context, ¶ms, *current_block, end).await { Ok(messages) => messages, Err(e) => { - tx.send_err(e, req_id.clone()) + tx.send_err(e) .await // Could error if the subscription is closing. .ok(); @@ -247,11 +245,10 @@ where tokio::spawn({ let params = params.clone(); let context = router.context.clone(); - let req_id = req_id.clone(); let tx = tx.clone(); async move { if let Err(e) = T::subscribe(context, params, tx1).await { - tx.send_err(e, req_id).await.ok(); + tx.send_err(e).await.ok(); } } }); @@ -274,7 +271,7 @@ where match T::catch_up(&router.context, ¶ms, current_block, end).await { Ok(messages) => messages, Err(e) => { - tx.send_err(e, req_id.clone()) + tx.send_err(e) .await // Could error if the subscription is closing. .ok(); @@ -609,7 +606,6 @@ async fn handle_request( input: params, subscription_id, subscriptions: subscriptions.clone(), - req_id: req_id.clone(), ws_tx: ws_tx.clone(), lock, }) @@ -692,16 +688,24 @@ impl SubscriptionSender { .map_err(|_| mpsc::error::SendError(())) } - pub async fn send_err( - &self, - err: RpcError, - req_id: RequestId, - ) -> Result<(), mpsc::error::SendError<()>> { + pub async fn send_err(&self, err: RpcError) -> Result<(), mpsc::error::SendError<()>> { + if !self.subscriptions.contains_key(&self.subscription_id) { + // Race condition due to the subscription ending. + return Ok(()); + } + let notification = RpcNotification { + jsonrpc: "2.0", + method: "pathfinder_subscriptionError", + params: SubscriptionResult { + subscription_id: self.subscription_id, + result: err, + }, + } + .serialize(crate::dto::serialize::Serializer::new(self.version)) + .unwrap(); + let data = serde_json::to_string(¬ification).unwrap(); self.tx - .send(Err(RpcResponse { - output: Err(err), - id: req_id, - })) + .send(Ok(Message::Text(data))) .await .map_err(|_| mpsc::error::SendError(())) } @@ -750,3 +754,241 @@ where serializer.end() } } + +#[cfg(test)] +mod tests { + use std::time::Duration; + + use axum::async_trait; + use axum::extract::ws::Message; + use pathfinder_common::{BlockHash, BlockHeader, BlockId, BlockNumber, ChainId}; + use pathfinder_crypto::Felt; + use pathfinder_storage::StorageBuilder; + use starknet_gateway_client::Client; + use tokio::sync::mpsc; + + use super::RpcSubscriptionEndpoint; + use crate::context::{RpcConfig, RpcContext}; + use crate::dto::DeserializeForVersion; + use crate::jsonrpc::{ + handle_json_rpc_socket, + CatchUp, + RpcRouter, + RpcSubscriptionFlow, + SubscriptionMessage, + }; + use crate::pending::PendingWatcher; + use crate::v02::types::syncing::Syncing; + use crate::{Notifications, SyncState}; + + #[tokio::test] + async fn test_error_returned_from_catch_up() { + struct ErrorFromCatchUp; + + #[async_trait] + impl RpcSubscriptionFlow for ErrorFromCatchUp { + type Params = Params; + type Notification = serde_json::Value; + + fn starting_block(_params: &Self::Params) -> BlockId { + BlockId::Number(BlockNumber::GENESIS) + } + + async fn catch_up( + _state: &RpcContext, + _params: &Self::Params, + _from: BlockNumber, + _to: BlockNumber, + ) -> Result, crate::jsonrpc::RpcError> { + Err(crate::jsonrpc::RpcError::InternalError(anyhow::anyhow!( + "error from catch_up" + ))) + } + + async fn subscribe( + _state: RpcContext, + _params: Self::Params, + _tx: tokio::sync::mpsc::Sender>, + ) -> Result<(), crate::jsonrpc::RpcError> { + Ok(()) + } + } + + let router = setup(5, ErrorFromCatchUp).await; + let (sender_tx, mut sender_rx) = mpsc::channel(1024); + let (receiver_tx, receiver_rx) = mpsc::channel(1024); + handle_json_rpc_socket(router.clone(), sender_tx, receiver_rx); + receiver_tx + .send(Ok(Message::Text( + serde_json::json!({ + "jsonrpc": "2.0", + "id": 1, + "method": "test", + "params": {} + }) + .to_string(), + ))) + .await + .unwrap(); + let res = sender_rx.recv().await.unwrap().unwrap(); + let subscription_id = match res { + Message::Text(json) => { + let json: serde_json::Value = serde_json::from_str(&json).unwrap(); + assert_eq!(json["jsonrpc"], "2.0"); + assert_eq!(json["id"], 1); + json["result"]["subscription_id"].as_u64().unwrap() + } + _ => panic!("Expected text message"), + }; + let msg = sender_rx.recv().await.unwrap().unwrap(); + let json: serde_json::Value = match msg { + Message::Text(json) => serde_json::from_str(&json).unwrap(), + _ => panic!("Expected text message"), + }; + assert_eq!( + json, + serde_json::json!({ + "jsonrpc": "2.0", + "method": "pathfinder_subscriptionError", + "params": { + "result": { "code": -32603, "message": "Internal error" }, + "subscription_id": subscription_id + } + }) + ) + } + + #[tokio::test] + async fn test_error_returned_from_subscribe() { + struct ErrorFromSubscribe; + + #[async_trait] + impl RpcSubscriptionFlow for ErrorFromSubscribe { + type Params = Params; + type Notification = serde_json::Value; + + fn starting_block(_params: &Self::Params) -> BlockId { + BlockId::Number(BlockNumber::GENESIS) + } + + async fn catch_up( + _state: &RpcContext, + _params: &Self::Params, + _from: BlockNumber, + _to: BlockNumber, + ) -> Result, crate::jsonrpc::RpcError> { + Ok(Default::default()) + } + + async fn subscribe( + _state: RpcContext, + _params: Self::Params, + _tx: tokio::sync::mpsc::Sender>, + ) -> Result<(), crate::jsonrpc::RpcError> { + Err(crate::jsonrpc::RpcError::InternalError(anyhow::anyhow!( + "error from catch_up" + ))) + } + } + + let router = setup(5, ErrorFromSubscribe).await; + let (sender_tx, mut sender_rx) = mpsc::channel(1024); + let (receiver_tx, receiver_rx) = mpsc::channel(1024); + handle_json_rpc_socket(router.clone(), sender_tx, receiver_rx); + receiver_tx + .send(Ok(Message::Text( + serde_json::json!({ + "jsonrpc": "2.0", + "id": 1, + "method": "test", + "params": {} + }) + .to_string(), + ))) + .await + .unwrap(); + let res = sender_rx.recv().await.unwrap().unwrap(); + let subscription_id = match res { + Message::Text(json) => { + let json: serde_json::Value = serde_json::from_str(&json).unwrap(); + assert_eq!(json["jsonrpc"], "2.0"); + assert_eq!(json["id"], 1); + json["result"]["subscription_id"].as_u64().unwrap() + } + _ => panic!("Expected text message"), + }; + let msg = sender_rx.recv().await.unwrap().unwrap(); + let json: serde_json::Value = match msg { + Message::Text(json) => serde_json::from_str(&json).unwrap(), + _ => panic!("Expected text message"), + }; + assert_eq!( + json, + serde_json::json!({ + "jsonrpc": "2.0", + "method": "pathfinder_subscriptionError", + "params": { + "result": { "code": -32603, "message": "Internal error" }, + "subscription_id": subscription_id + } + }) + ) + } + + #[derive(Debug, Clone)] + struct Params; + + impl DeserializeForVersion for Params { + fn deserialize(_: crate::dto::Value) -> Result { + Ok(Self) + } + } + + async fn setup(num_blocks: u64, endpoint: impl RpcSubscriptionEndpoint + 'static) -> RpcRouter { + let storage = StorageBuilder::in_memory().unwrap(); + tokio::task::spawn_blocking({ + let storage = storage.clone(); + move || { + let mut conn = storage.connection().unwrap(); + let db = conn.transaction().unwrap(); + for i in 0..num_blocks { + let header = BlockHeader { + hash: BlockHash(Felt::from_u64(i)), + number: BlockNumber::new_or_panic(i), + parent_hash: BlockHash::ZERO, + ..Default::default() + }; + db.insert_block_header(&header).unwrap(); + } + db.commit().unwrap(); + } + }) + .await + .unwrap(); + let (_, pending_data) = tokio::sync::watch::channel(Default::default()); + let notifications = Notifications::default(); + let ctx = RpcContext { + cache: Default::default(), + storage, + execution_storage: StorageBuilder::in_memory().unwrap(), + pending_data: PendingWatcher::new(pending_data), + sync_status: SyncState { + status: Syncing::False(false).into(), + } + .into(), + chain_id: ChainId::MAINNET, + sequencer: Client::mainnet(Duration::from_secs(10)), + websocket: None, + notifications, + config: RpcConfig { + batch_concurrency_limit: 1.try_into().unwrap(), + get_events_max_blocks_to_scan: 1.try_into().unwrap(), + get_events_max_uncached_bloom_filters_to_load: 1.try_into().unwrap(), + custom_versioned_constants: None, + }, + }; + RpcRouter::builder(crate::RpcVersion::V08) + .register("test", endpoint) + .build(ctx) + } +} From b342cf4c5a08f8f0642fcbb296076d785ee30457 Mon Sep 17 00:00:00 2001 From: sistemd Date: Tue, 15 Oct 2024 12:16:43 +0200 Subject: [PATCH 144/282] use get_transaction_status gateway endpoint --- crates/gateway-client/src/builder.rs | 4 +- crates/gateway-client/src/lib.rs | 14 +- crates/gateway-types/src/reply.rs | 15 +- crates/rpc/src/jsonrpc/websocket/logic.rs | 135 +++++++++--------- .../rpc/src/method/get_transaction_status.rs | 6 +- .../methods/get_transaction_status.rs | 6 +- .../src/v06/method/get_transaction_status.rs | 6 +- crates/rpc/src/v07.rs | 1 + 8 files changed, 98 insertions(+), 89 deletions(-) diff --git a/crates/gateway-client/src/builder.rs b/crates/gateway-client/src/builder.rs index 07faae44d6..e343a9619e 100644 --- a/crates/gateway-client/src/builder.rs +++ b/crates/gateway-client/src/builder.rs @@ -38,7 +38,7 @@ pub mod stage { /// - [add_transaction](super::Request::add_transaction) /// - [get_block](super::Request::get_block) /// - [get_class_by_hash](super::Request::get_class_by_hash) - /// - [get_transaction](super::Request::get_transaction) + /// - [get_transaction_status](super::Request::get_transaction_status) /// - [get_state_update](super::Request::get_state_update) /// - [get_contract_addresses](super::Request::get_contract_addresses) pub struct Method; @@ -142,7 +142,7 @@ impl<'a> Request<'a, stage::Method> { get_block, get_class_by_hash, get_compiled_class_by_class_hash, - get_transaction, + get_transaction_status, get_state_update, get_contract_addresses, get_block_traces, diff --git a/crates/gateway-client/src/lib.rs b/crates/gateway-client/src/lib.rs index 0b32b9abfa..9ff0e02af3 100644 --- a/crates/gateway-client/src/lib.rs +++ b/crates/gateway-client/src/lib.rs @@ -50,7 +50,7 @@ pub trait GatewayApi: Sync { unimplemented!(); } - async fn transaction( + async fn transaction_status( &self, transaction_hash: TransactionHash, ) -> Result { @@ -144,11 +144,11 @@ impl GatewayApi for std::sync::Arc { self.as_ref().pending_casm_by_hash(class_hash).await } - async fn transaction( + async fn transaction_status( &self, transaction_hash: TransactionHash, ) -> Result { - self.as_ref().transaction(transaction_hash).await + self.as_ref().transaction_status(transaction_hash).await } async fn state_update_with_block( @@ -387,14 +387,14 @@ impl GatewayApi for Client { .await } - /// Gets transaction by hash. + /// Gets transaction status by transaction hash. #[tracing::instrument(skip(self))] - async fn transaction( + async fn transaction_status( &self, transaction_hash: TransactionHash, ) -> Result { self.feeder_gateway_request() - .get_transaction() + .get_transaction_status() .transaction_hash(transaction_hash) .retry(self.retry) .get() @@ -615,7 +615,7 @@ mod tests { )]); let client = Client::with_base_url(url, GATEWAY_TIMEOUT).unwrap(); assert_eq!( - client.transaction(INVALID_TX_HASH).await.unwrap().status, + client.transaction_status(INVALID_TX_HASH).await.unwrap().tx_status, Status::NotReceived, ); } diff --git a/crates/gateway-types/src/reply.rs b/crates/gateway-types/src/reply.rs index 1c3c3e7cb7..8b380a3e0f 100644 --- a/crates/gateway-types/src/reply.rs +++ b/crates/gateway-types/src/reply.rs @@ -185,19 +185,20 @@ pub mod call { } } -/// Used to deserialize replies to Starknet transaction requests. +/// Used to deserialize replies to Starknet transaction status requests. /// /// Please note that this does not have to be backwards compatible: /// since we only ever use it to deserialize replies from the Starknet /// feeder gateway. #[derive(Clone, Debug, Deserialize, PartialEq, Eq)] pub struct TransactionStatus { - pub status: Status, + pub tx_status: Status, pub finality_status: transaction_status::FinalityStatus, - #[serde(default)] - pub execution_status: transaction_status::ExecutionStatus, - pub transaction_failure_reason: Option, - pub revert_error: Option, + // For transactions that were not received, `"execution_status": null` + // in the gateway response. + pub execution_status: Option, + pub tx_failure_reason: Option, + pub tx_revert_reason: Option, } /// Types used when deserializing get_transaction replies. @@ -226,7 +227,7 @@ pub mod transaction_status { } #[derive(Clone, Debug, Deserialize, PartialEq, Eq)] - pub struct TransactionFailureReason { + pub struct TxFailureReason { pub code: String, pub error_message: String, } diff --git a/crates/rpc/src/jsonrpc/websocket/logic.rs b/crates/rpc/src/jsonrpc/websocket/logic.rs index 854579bb38..df11a6a50c 100644 --- a/crates/rpc/src/jsonrpc/websocket/logic.rs +++ b/crates/rpc/src/jsonrpc/websocket/logic.rs @@ -502,10 +502,13 @@ async fn transaction_status_subscription( let mut poll_interval = tokio::time::interval(Duration::from_millis(500)); let mut num_consecutive_errors = 0; loop { - match gateway.transaction(transaction_hash).await { + match gateway.transaction_status(transaction_hash).await { Ok(tx_status) => { num_consecutive_errors = 0; - let update = match (tx_status.finality_status, &tx_status.execution_status) { + + let execution_status = tx_status.execution_status.unwrap_or_default(); + + let update = match (tx_status.finality_status, execution_status) { (_, ExecutionStatus::Rejected) => Some(TransactionStatusUpdate::Rejected), (FinalityStatus::NotReceived, _) => { // "NOT_RECEIVED" status is never sent to the client. @@ -555,7 +558,7 @@ async fn transaction_status_subscription( break; } } - if tx_status.execution_status == ExecutionStatus::Rejected + if execution_status == ExecutionStatus::Rejected || tx_status.finality_status == FinalityStatus::AcceptedOnL1 || tx_status.finality_status == FinalityStatus::AcceptedOnL2 { @@ -1186,7 +1189,7 @@ mod tests { #[async_trait::async_trait] impl GatewayApi for Mock { - async fn transaction( + async fn transaction_status( &self, transaction_hash: TransactionHash, ) -> Result { @@ -1203,39 +1206,39 @@ mod tests { Mock(Mutex::new( [ TransactionStatus { - status: Status::NotReceived, + tx_status: Status::NotReceived, finality_status: FinalityStatus::NotReceived, - execution_status: ExecutionStatus::Succeeded, - transaction_failure_reason: None, - revert_error: None, + execution_status: Some(ExecutionStatus::Succeeded), + tx_failure_reason: None, + tx_revert_reason: None, }, TransactionStatus { - status: Status::Received, + tx_status: Status::Received, finality_status: FinalityStatus::Received, - execution_status: ExecutionStatus::Succeeded, - transaction_failure_reason: None, - revert_error: None, + execution_status: Some(ExecutionStatus::Succeeded), + tx_failure_reason: None, + tx_revert_reason: None, }, TransactionStatus { - status: Status::NotReceived, + tx_status: Status::NotReceived, finality_status: FinalityStatus::NotReceived, - execution_status: ExecutionStatus::Succeeded, - transaction_failure_reason: None, - revert_error: None, + execution_status: Some(ExecutionStatus::Succeeded), + tx_failure_reason: None, + tx_revert_reason: None, }, TransactionStatus { - status: Status::Received, + tx_status: Status::Received, finality_status: FinalityStatus::Received, - execution_status: ExecutionStatus::Succeeded, - transaction_failure_reason: None, - revert_error: None, + execution_status: Some(ExecutionStatus::Succeeded), + tx_failure_reason: None, + tx_revert_reason: None, }, TransactionStatus { - status: Status::AcceptedOnL1, + tx_status: Status::AcceptedOnL1, finality_status: FinalityStatus::AcceptedOnL1, - execution_status: ExecutionStatus::Succeeded, - transaction_failure_reason: None, - revert_error: None, + execution_status: Some(ExecutionStatus::Succeeded), + tx_failure_reason: None, + tx_revert_reason: None, }, ] .into_iter() @@ -1281,7 +1284,7 @@ mod tests { #[async_trait::async_trait] impl GatewayApi for Mock { - async fn transaction( + async fn transaction_status( &self, transaction_hash: TransactionHash, ) -> Result { @@ -1298,39 +1301,39 @@ mod tests { Mock(Mutex::new( [ TransactionStatus { - status: Status::NotReceived, + tx_status: Status::NotReceived, finality_status: FinalityStatus::NotReceived, - execution_status: ExecutionStatus::Succeeded, - transaction_failure_reason: None, - revert_error: None, + execution_status: Some(ExecutionStatus::Succeeded), + tx_failure_reason: None, + tx_revert_reason: None, }, TransactionStatus { - status: Status::Received, + tx_status: Status::Received, finality_status: FinalityStatus::Received, - execution_status: ExecutionStatus::Succeeded, - transaction_failure_reason: None, - revert_error: None, + execution_status: Some(ExecutionStatus::Succeeded), + tx_failure_reason: None, + tx_revert_reason: None, }, TransactionStatus { - status: Status::NotReceived, + tx_status: Status::NotReceived, finality_status: FinalityStatus::NotReceived, - execution_status: ExecutionStatus::Succeeded, - transaction_failure_reason: None, - revert_error: None, + execution_status: Some(ExecutionStatus::Succeeded), + tx_failure_reason: None, + tx_revert_reason: None, }, TransactionStatus { - status: Status::Received, + tx_status: Status::Received, finality_status: FinalityStatus::Received, - execution_status: ExecutionStatus::Succeeded, - transaction_failure_reason: None, - revert_error: None, + execution_status: Some(ExecutionStatus::Succeeded), + tx_failure_reason: None, + tx_revert_reason: None, }, TransactionStatus { - status: Status::AcceptedOnL1, + tx_status: Status::AcceptedOnL1, finality_status: FinalityStatus::AcceptedOnL1, - execution_status: ExecutionStatus::Reverted, - transaction_failure_reason: None, - revert_error: None, + execution_status: Some(ExecutionStatus::Reverted), + tx_failure_reason: None, + tx_revert_reason: None, }, ] .into_iter() @@ -1376,7 +1379,7 @@ mod tests { #[async_trait::async_trait] impl GatewayApi for Mock { - async fn transaction( + async fn transaction_status( &self, transaction_hash: TransactionHash, ) -> Result { @@ -1393,39 +1396,39 @@ mod tests { Mock(Mutex::new( [ TransactionStatus { - status: Status::NotReceived, + tx_status: Status::NotReceived, finality_status: FinalityStatus::NotReceived, - execution_status: ExecutionStatus::Succeeded, - transaction_failure_reason: None, - revert_error: None, + execution_status: Some(ExecutionStatus::Succeeded), + tx_failure_reason: None, + tx_revert_reason: None, }, TransactionStatus { - status: Status::Received, + tx_status: Status::Received, finality_status: FinalityStatus::Received, - execution_status: ExecutionStatus::Succeeded, - transaction_failure_reason: None, - revert_error: None, + execution_status: Some(ExecutionStatus::Succeeded), + tx_failure_reason: None, + tx_revert_reason: None, }, TransactionStatus { - status: Status::NotReceived, + tx_status: Status::NotReceived, finality_status: FinalityStatus::NotReceived, - execution_status: ExecutionStatus::Succeeded, - transaction_failure_reason: None, - revert_error: None, + execution_status: Some(ExecutionStatus::Succeeded), + tx_failure_reason: None, + tx_revert_reason: None, }, TransactionStatus { - status: Status::Received, + tx_status: Status::Received, finality_status: FinalityStatus::Received, - execution_status: ExecutionStatus::Succeeded, - transaction_failure_reason: None, - revert_error: None, + execution_status: Some(ExecutionStatus::Succeeded), + tx_failure_reason: None, + tx_revert_reason: None, }, TransactionStatus { - status: Status::Rejected, + tx_status: Status::Rejected, finality_status: FinalityStatus::NotReceived, - execution_status: ExecutionStatus::Rejected, - transaction_failure_reason: None, - revert_error: None, + execution_status: Some(ExecutionStatus::Rejected), + tx_failure_reason: None, + tx_revert_reason: None, }, ] .into_iter() diff --git a/crates/rpc/src/method/get_transaction_status.rs b/crates/rpc/src/method/get_transaction_status.rs index 6d82a852b3..ba09f295b0 100644 --- a/crates/rpc/src/method/get_transaction_status.rs +++ b/crates/rpc/src/method/get_transaction_status.rs @@ -88,7 +88,7 @@ pub async fn get_transaction_status(context: RpcContext, input: Input) -> Result use starknet_gateway_client::GatewayApi; context .sequencer - .transaction(input.transaction_hash) + .transaction_status(input.transaction_hash) .await .context("Fetching transaction from gateway") .map_err(Error::Internal) @@ -98,7 +98,9 @@ pub async fn get_transaction_status(context: RpcContext, input: Input) -> Result FinalityStatus as GatewayFinalityStatus, }; - match (tx.finality_status, tx.execution_status) { + let execution_status = tx.execution_status.unwrap_or_default(); + + match (tx.finality_status, execution_status) { (GatewayFinalityStatus::NotReceived, _) => Err(Error::TxnHashNotFound), (_, GatewayExecutionStatus::Rejected) => Ok(Output::Rejected { error_message: tx diff --git a/crates/rpc/src/pathfinder/methods/get_transaction_status.rs b/crates/rpc/src/pathfinder/methods/get_transaction_status.rs index 70c974e1d9..49db882a91 100644 --- a/crates/rpc/src/pathfinder/methods/get_transaction_status.rs +++ b/crates/rpc/src/pathfinder/methods/get_transaction_status.rs @@ -77,10 +77,10 @@ pub async fn get_transaction_status( use starknet_gateway_client::GatewayApi; context .sequencer - .transaction(input.transaction_hash) + .transaction_status(input.transaction_hash) .await - .context("Fetching transaction from gateway") - .map(|tx| tx.status.into()) + .context("Fetching transaction status from gateway") + .map(|tx| tx.tx_status.into()) .map_err(GetGatewayTransactionError::Internal) } diff --git a/crates/rpc/src/v06/method/get_transaction_status.rs b/crates/rpc/src/v06/method/get_transaction_status.rs index a055c59f71..8b962fa3dd 100644 --- a/crates/rpc/src/v06/method/get_transaction_status.rs +++ b/crates/rpc/src/v06/method/get_transaction_status.rs @@ -134,7 +134,7 @@ pub async fn get_transaction_status( use starknet_gateway_client::GatewayApi; context .sequencer - .transaction(input.transaction_hash) + .transaction_status(input.transaction_hash) .await .context("Fetching transaction from gateway") .map_err(GetTransactionStatusError::Internal) @@ -144,7 +144,9 @@ pub async fn get_transaction_status( FinalityStatus as GatewayFinalityStatus, }; - match (tx.finality_status, tx.execution_status) { + let execution_status = tx.execution_status.unwrap_or_default(); + + match (tx.finality_status, execution_status) { (GatewayFinalityStatus::NotReceived, _) => { Err(GetTransactionStatusError::TxnHashNotFound) } diff --git a/crates/rpc/src/v07.rs b/crates/rpc/src/v07.rs index ba65cc1bfd..928d044da2 100644 --- a/crates/rpc/src/v07.rs +++ b/crates/rpc/src/v07.rs @@ -16,6 +16,7 @@ pub fn register_routes() -> RpcRouterBuilder { .register("starknet_getNonce", crate::method::get_nonce) .register("starknet_getStateUpdate", crate::method::get_state_update) .register("starknet_getStorageAt", crate::method::get_storage_at) + .register("starknet_getStorageProof", crate::method::get_storage_proof) .register("starknet_syncing", crate::method::syncing) .register("starknet_getTransactionReceipt", crate::method::get_transaction_receipt) .register("starknet_getTransactionStatus", crate::method::get_transaction_status) From a3684eb7c8205ac51b6a3c4d201ef8f6c040727a Mon Sep 17 00:00:00 2001 From: sistemd Date: Tue, 15 Oct 2024 12:16:43 +0200 Subject: [PATCH 145/282] get_transaction_status tests and minor fixes --- crates/gateway-client/src/lib.rs | 6 +++++- .../rpc/src/method/get_transaction_status.rs | 20 ++++++++++++++----- crates/rpc/src/v07.rs | 1 - 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/crates/gateway-client/src/lib.rs b/crates/gateway-client/src/lib.rs index 9ff0e02af3..adf6510ae3 100644 --- a/crates/gateway-client/src/lib.rs +++ b/crates/gateway-client/src/lib.rs @@ -615,7 +615,11 @@ mod tests { )]); let client = Client::with_base_url(url, GATEWAY_TIMEOUT).unwrap(); assert_eq!( - client.transaction_status(INVALID_TX_HASH).await.unwrap().tx_status, + client + .transaction_status(INVALID_TX_HASH) + .await + .unwrap() + .tx_status, Status::NotReceived, ); } diff --git a/crates/rpc/src/method/get_transaction_status.rs b/crates/rpc/src/method/get_transaction_status.rs index ba09f295b0..f6aa6c60de 100644 --- a/crates/rpc/src/method/get_transaction_status.rs +++ b/crates/rpc/src/method/get_transaction_status.rs @@ -103,14 +103,12 @@ pub async fn get_transaction_status(context: RpcContext, input: Input) -> Result match (tx.finality_status, execution_status) { (GatewayFinalityStatus::NotReceived, _) => Err(Error::TxnHashNotFound), (_, GatewayExecutionStatus::Rejected) => Ok(Output::Rejected { - error_message: tx - .transaction_failure_reason - .map(|reason| reason.error_message), + error_message: tx.tx_failure_reason.map(|reason| reason.error_message), }), (GatewayFinalityStatus::Received, _) => Ok(Output::Received), (GatewayFinalityStatus::AcceptedOnL1, GatewayExecutionStatus::Reverted) => { Ok(Output::AcceptedOnL1(TxnExecutionStatus::Reverted { - reason: tx.revert_error, + reason: tx.tx_revert_reason, })) } (GatewayFinalityStatus::AcceptedOnL1, GatewayExecutionStatus::Succeeded) => { @@ -118,7 +116,7 @@ pub async fn get_transaction_status(context: RpcContext, input: Input) -> Result } (GatewayFinalityStatus::AcceptedOnL2, GatewayExecutionStatus::Reverted) => { Ok(Output::AcceptedOnL2(TxnExecutionStatus::Reverted { - reason: tx.revert_error, + reason: tx.tx_revert_reason, })) } (GatewayFinalityStatus::AcceptedOnL2, GatewayExecutionStatus::Succeeded) => { @@ -237,6 +235,18 @@ mod tests { assert_eq!(status, Output::AcceptedOnL2(TxnExecutionStatus::Succeeded)); } + #[tokio::test] + async fn not_received() { + let input = Input { + // Transaction hash known to have `NOT_RECEIVED` status. + transaction_hash: transaction_hash!("0x6e6f6e2d6578697374656e74"), + }; + let context = RpcContext::for_tests(); + let status = get_transaction_status(context, input).await; + + assert_matches!(status, Err(Error::TxnHashNotFound)); + } + #[tokio::test] async fn rejected_with_error_message() { let input = Input { diff --git a/crates/rpc/src/v07.rs b/crates/rpc/src/v07.rs index 928d044da2..ba65cc1bfd 100644 --- a/crates/rpc/src/v07.rs +++ b/crates/rpc/src/v07.rs @@ -16,7 +16,6 @@ pub fn register_routes() -> RpcRouterBuilder { .register("starknet_getNonce", crate::method::get_nonce) .register("starknet_getStateUpdate", crate::method::get_state_update) .register("starknet_getStorageAt", crate::method::get_storage_at) - .register("starknet_getStorageProof", crate::method::get_storage_proof) .register("starknet_syncing", crate::method::syncing) .register("starknet_getTransactionReceipt", crate::method::get_transaction_receipt) .register("starknet_getTransactionStatus", crate::method::get_transaction_status) From 65d1e5e0baa923ed41c16a16fb605aa32b5cf37f Mon Sep 17 00:00:00 2001 From: sistemd Date: Tue, 15 Oct 2024 12:16:43 +0200 Subject: [PATCH 146/282] fix clippy --- crates/rpc/src/method/subscribe_transaction_status.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/rpc/src/method/subscribe_transaction_status.rs b/crates/rpc/src/method/subscribe_transaction_status.rs index 811d8db1c1..a448c46a41 100644 --- a/crates/rpc/src/method/subscribe_transaction_status.rs +++ b/crates/rpc/src/method/subscribe_transaction_status.rs @@ -239,13 +239,13 @@ impl RpcSubscriptionFlow for SubscribeEvents { loop { tokio::select! { _ = interval.tick() => { - match state.sequencer.transaction(params.transaction_hash).await { + match state.sequencer.transaction_status(params.transaction_hash).await { Ok(status) => { - if status.execution_status == status::ExecutionStatus::Rejected { + if matches!(status.execution_status, Some(status::ExecutionStatus::Rejected)) { // Transaction has been rejected. sender .send(BlockNumber::GENESIS, FinalityStatus::Rejected { - reason: status.transaction_failure_reason.map(|reason| reason.error_message) + reason: status.tx_failure_reason.map(|reason| reason.error_message) }, None) .await .ok(); From 4ddf2f8787b4a7098c743a102696e6e71d3114b5 Mon Sep 17 00:00:00 2001 From: sistemd Date: Tue, 15 Oct 2024 12:16:43 +0200 Subject: [PATCH 147/282] fix failing unit test --- crates/gateway-client/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/gateway-client/src/lib.rs b/crates/gateway-client/src/lib.rs index adf6510ae3..abf73d8f1b 100644 --- a/crates/gateway-client/src/lib.rs +++ b/crates/gateway-client/src/lib.rs @@ -605,11 +605,11 @@ mod tests { async fn invalid_hash() { let (_jh, url) = setup([( format!( - "/feeder_gateway/get_transaction?transactionHash={}", + "/feeder_gateway/get_transaction_status?transactionHash={}", INVALID_TX_HASH.0.to_hex_str() ), ( - r#"{"status": "NOT_RECEIVED", "finality_status": "NOT_RECEIVED"}"#, + r#"{"tx_status": "NOT_RECEIVED", "finality_status": "NOT_RECEIVED", "execution_status": null}"#, 200, ), )]); From 9b82f7d611c8f7d131c9c8059f738d04e115f93f Mon Sep 17 00:00:00 2001 From: t00ts Date: Wed, 16 Oct 2024 09:50:26 +0400 Subject: [PATCH 148/282] chore: default to v07 RPC version --- crates/pathfinder/src/bin/pathfinder/config.rs | 2 +- crates/rpc/src/lib.rs | 4 ++-- crates/rpc/src/middleware/cors.rs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/pathfinder/src/bin/pathfinder/config.rs b/crates/pathfinder/src/bin/pathfinder/config.rs index 59002eca2a..e8ada6789f 100644 --- a/crates/pathfinder/src/bin/pathfinder/config.rs +++ b/crates/pathfinder/src/bin/pathfinder/config.rs @@ -83,7 +83,7 @@ Examples: #[arg( long = "rpc.root-version", long_help = "Version of the JSON-RPC API to serve on the / (root) path", - default_value = "v06", + default_value = "v07", env = "PATHFINDER_RPC_ROOT_VERSION" )] rpc_root_version: RpcVersion, diff --git a/crates/rpc/src/lib.rs b/crates/rpc/src/lib.rs index b4ce3286da..c4c4dd3960 100644 --- a/crates/rpc/src/lib.rs +++ b/crates/rpc/src/lib.rs @@ -830,7 +830,7 @@ mod tests { // of health check. Test that we return success for such queries. let addr: SocketAddr = "127.0.0.1:0".parse().unwrap(); let context = RpcContext::for_tests(); - let (_jh, addr) = RpcServer::new(addr, context, RpcVersion::V06) + let (_jh, addr) = RpcServer::new(addr, context, RpcVersion::V07) .spawn() .await .unwrap(); @@ -939,7 +939,7 @@ mod tests { let addr: SocketAddr = "127.0.0.1:0".parse().unwrap(); let context = RpcContext::for_tests(); - let (_jh, addr) = RpcServer::new(addr, context, RpcVersion::V06) + let (_jh, addr) = RpcServer::new(addr, context, RpcVersion::V07) .spawn() .await .unwrap(); diff --git a/crates/rpc/src/middleware/cors.rs b/crates/rpc/src/middleware/cors.rs index d81a090288..dc66cec7b6 100644 --- a/crates/rpc/src/middleware/cors.rs +++ b/crates/rpc/src/middleware/cors.rs @@ -35,7 +35,7 @@ mod tests { (None, None, line!()), ] { let context = RpcContext::for_tests(); - let server = RpcServer::new("127.0.0.1:0".parse().unwrap(), context, RpcVersion::V06); + let server = RpcServer::new("127.0.0.1:0".parse().unwrap(), context, RpcVersion::V07); let server = match allowed { Some(allowed) => server.with_cors(allowed.into()), None => server, From e676eeb7857d904dd55f02b52599f71716d18382 Mon Sep 17 00:00:00 2001 From: apoorvsadana <95699312+apoorvsadana@users.noreply.github.com> Date: Wed, 16 Oct 2024 11:58:01 +0530 Subject: [PATCH 149/282] fix rpc fails on empty blocks --- crates/rpc/src/method/get_block_with_txs.rs | 2 +- crates/rpc/src/v06/method/get_block_with_txs.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/rpc/src/method/get_block_with_txs.rs b/crates/rpc/src/method/get_block_with_txs.rs index 8f0dc52ea1..a5fb6c29cd 100644 --- a/crates/rpc/src/method/get_block_with_txs.rs +++ b/crates/rpc/src/method/get_block_with_txs.rs @@ -85,7 +85,7 @@ pub async fn get_block_with_txs(context: RpcContext, input: Input) -> Result Date: Wed, 16 Oct 2024 13:20:19 +0530 Subject: [PATCH 150/282] fix changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5278b0c80d..517c0d1816 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Pathfinder stops syncing Sepolia testnet at block 218484 because of a block hash mismatch. +- `starknet_getBlockWithTxs` works with empty blocks` ## [0.14.3] - 2024-09-23 From fbe88520bada0eb48e885592d853a371d4b7535e Mon Sep 17 00:00:00 2001 From: t00ts Date: Wed, 16 Oct 2024 10:01:52 +0400 Subject: [PATCH 151/282] udpate CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5278b0c80d..2edf9909ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Ethereum RPC API now requires Websocket endpoints (prev. HTTP). If an HTTP url is provided instead, Pathfinder will attempt to connect vía Websocket protocol at that same url. +- JSON-RPC API version 0.7 is now served by default on the `/` path. ## [0.14.4] - 2024-10-03 From f96ec67287f88d94dd5bcc390b516fdc11e01abd Mon Sep 17 00:00:00 2001 From: Krzysztof Lis Date: Mon, 14 Oct 2024 13:26:39 +0200 Subject: [PATCH 152/282] feat: introduce take_or_deep_clone for Arc --- crates/common/src/error.rs | 94 +++++++++++++++++++++++++++++++++++ crates/common/src/lib.rs | 1 + crates/pathfinder/src/sync.rs | 13 ++--- 3 files changed, 99 insertions(+), 9 deletions(-) create mode 100644 crates/common/src/error.rs diff --git a/crates/common/src/error.rs b/crates/common/src/error.rs new file mode 100644 index 0000000000..58eb9133fe --- /dev/null +++ b/crates/common/src/error.rs @@ -0,0 +1,94 @@ +use std::sync::Arc; + +/// This trait is a workaround for [anyhow::Error] not being clonable. +/// +/// Most of the time you can use `Arc` instead of `anyhow::Error` +/// to circumvent `anyhow::Error` not being `Clone`. However, in some cases, you +/// might need to "unwrap" the Arc-ed error. This trait provides a way to do +/// that. +pub trait AnyhowExt { + /// Clones the error by doing `to_string` on each item in the chain. + fn deep_clone(&self) -> anyhow::Error; + /// Swaps the Arc-ed error for an empty instance using [`Arc::get_mut`] and + /// if unsuccessful clones the error using + /// [`AnyhowExt::deep_clone`]. + fn take_or_deep_clone(self: &mut Arc) -> anyhow::Error; +} + +impl AnyhowExt for anyhow::Error { + fn deep_clone(&self) -> anyhow::Error { + let mut chain = self.chain().rev(); + let first = chain + .next() + .expect("at least one error is always in the chain"); + + let mut cloned = anyhow::Error::msg(first.to_string()); + + for cause in chain { + cloned = cloned.context(cause.to_string()); + } + + cloned + } + + fn take_or_deep_clone(self: &mut Arc) -> anyhow::Error { + if let Some(e) = Arc::get_mut(self) { + let mut taken = anyhow::Error::msg(""); + std::mem::swap(e, &mut taken); + taken + } else { + // The strong ref count is greater than 1 + self.deep_clone() + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn fixture() -> (anyhow::Error, anyhow::Error) { + ( + anyhow::anyhow!("root") + .context("level 1") + .context("level 2") + .context("level 3"), + anyhow::anyhow!("root") + .context("level 1") + .context("level 2") + .context("level 3"), + ) + } + + #[test] + fn take_if_refcount_eq1() { + let (src0, src) = fixture(); + + let mut arced = std::sync::Arc::new(src0); + assert_eq!(Arc::strong_count(&arced), 1); + + // Strong ref count is 1, taking the error should work + let taken = arced.take_or_deep_clone(); + assert_eq!(format!("{}", taken), format!("{}", src)); + assert_eq!(format!("{:?}", taken), format!("{:?}", src)); + + assert!(arced.to_string().is_empty()); + } + + #[test] + fn clone_if_refcount_gt1() { + let (src0, src) = fixture(); + + let mut arced = std::sync::Arc::new(src0); + let arc_clone = arced.clone(); + assert_eq!(Arc::strong_count(&arc_clone), 2); + + // Strong ref count is 2, only a poor-man's clone is possible + let cloned = arced.take_or_deep_clone(); + assert_eq!(format!("{}", cloned), format!("{}", src)); + assert_eq!(format!("{:?}", cloned), format!("{:?}", src)); + + assert_eq!(format!("{}", arced), format!("{}", src)); + assert_eq!(format!("{:?}", arced), format!("{:?}", src)); + } +} diff --git a/crates/common/src/lib.rs b/crates/common/src/lib.rs index 21dd5859b2..b7b7f1ef5e 100644 --- a/crates/common/src/lib.rs +++ b/crates/common/src/lib.rs @@ -16,6 +16,7 @@ use serde::{Deserialize, Serialize}; pub mod class_definition; pub mod consts; +pub mod error; pub mod event; pub mod hash; mod header; diff --git a/crates/pathfinder/src/sync.rs b/crates/pathfinder/src/sync.rs index 8cedd13b44..3abd032de7 100644 --- a/crates/pathfinder/src/sync.rs +++ b/crates/pathfinder/src/sync.rs @@ -9,6 +9,7 @@ use error::{SyncError, SyncError2}; use futures::{pin_mut, Stream, StreamExt}; use p2p::client::peer_agnostic::Client as P2PClient; use p2p::PeerData; +use pathfinder_common::error::AnyhowExt; use pathfinder_common::{ block_hash, BlockHash, @@ -171,18 +172,12 @@ impl Sync { match &mut result { Ok(_) => tracing::debug!("Restarting track sync: unexpected end of Block stream"), Err(PeerData { - data: SyncError2::Other(fatal), + data: SyncError2::Other(error), .. }) => { - let error = if let Some(e) = Arc::get_mut(fatal) { - let mut taken = anyhow::Error::msg(""); - std::mem::swap(e, &mut taken); - taken - } else { - anyhow::Error::msg(fatal.root_cause().to_string()) - }; tracing::error!(%error, "Stopping track sync"); - return Err(error); + use pathfinder_common::error::AnyhowExt; + return Err(error.take_or_deep_clone()); } Err(PeerData { data: error, .. }) => { tracing::debug!(%error, "Restarting track sync") From 1a2db98cc932c57d7c368387a4e0f6f25bef1e05 Mon Sep 17 00:00:00 2001 From: Krzysztof Lis Date: Wed, 16 Oct 2024 14:43:39 +0200 Subject: [PATCH 153/282] feat(sync/checkpoint): call handle_recoverable_error, add basic logs --- crates/pathfinder/src/sync.rs | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/crates/pathfinder/src/sync.rs b/crates/pathfinder/src/sync.rs index 3abd032de7..0e0866179b 100644 --- a/crates/pathfinder/src/sync.rs +++ b/crates/pathfinder/src/sync.rs @@ -63,7 +63,7 @@ impl Sync { self.track_sync(next, parent_hash).await } - async fn handle_recoverable_error(&self, err: error::SyncError) { + async fn handle_recoverable_error(&self, err: &PeerData) { // TODO tracing::debug!(?err, "Log and punish as appropriate"); } @@ -122,10 +122,17 @@ impl Sync { // Handle the error let continue_from = match result { - Ok(continue_from) => continue_from, - Err(SyncError::Other(err)) => return Err(err), - Err(err) => { - self.handle_recoverable_error(err).await; + Ok(continue_from) => { + tracing::debug!(?continue_from, "Checkpoint sync complete"); + continue_from + } + Err(SyncError::Other(error)) => { + tracing::error!(%error, "Stopping checkpoint sync"); + return Err(error); + } + Err(error) => { + tracing::debug!(%error, "Restarting checkpoint sync"); + self.handle_recoverable_error(&error.into_v2()).await; continue; } }; @@ -135,6 +142,10 @@ impl Sync { let latest_checkpoint = self.get_checkpoint().await; if checkpoint.block_number + CHECKPOINT_MARGIN < latest_checkpoint.block_number { checkpoint = latest_checkpoint; + tracing::debug!( + local_checkpoint=%checkpoint.block_number, latest_checkpoint=%latest_checkpoint.block_number, + "Restarting checkpoint sync: L1 checkpoint has advanced" + ); continue; } @@ -179,8 +190,9 @@ impl Sync { use pathfinder_common::error::AnyhowExt; return Err(error.take_or_deep_clone()); } - Err(PeerData { data: error, .. }) => { - tracing::debug!(%error, "Restarting track sync") + Err(error) => { + tracing::debug!(error=%error.data, "Restarting track sync"); + self.handle_recoverable_error(error).await; } } } From 231ef01f41060b417331645fe053ab1dc8197873 Mon Sep 17 00:00:00 2001 From: Krzysztof Lis Date: Tue, 15 Oct 2024 09:24:42 +0200 Subject: [PATCH 154/282] feat: extract make-stream --- Cargo.lock | 10 ++++++ Cargo.toml | 1 + crates/make-stream/Cargo.toml | 11 ++++++ crates/make-stream/src/lib.rs | 29 +++++++++++++++ crates/p2p/Cargo.toml | 1 + crates/p2p/src/client/peer_agnostic.rs | 36 ++++++------------- crates/pathfinder/Cargo.toml | 1 + .../pathfinder/src/sync/storage_adapters.rs | 7 ++-- 8 files changed, 65 insertions(+), 31 deletions(-) create mode 100644 crates/make-stream/Cargo.toml create mode 100644 crates/make-stream/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 5383528fa0..80303ad594 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6432,6 +6432,14 @@ dependencies = [ "libc", ] +[[package]] +name = "make-stream" +version = "0.14.4" +dependencies = [ + "tokio", + "tokio-stream", +] + [[package]] name = "match_cfg" version = "0.1.0" @@ -7040,6 +7048,7 @@ dependencies = [ "hex", "ipnet", "libp2p", + "make-stream", "p2p_proto", "p2p_stream", "pathfinder-common", @@ -7245,6 +7254,7 @@ dependencies = [ "http 1.1.0", "ipnet", "jemallocator", + "make-stream", "metrics", "metrics-exporter-prometheus", "mockall", diff --git a/Cargo.toml b/Cargo.toml index fc17c01cbe..87c0d4a83a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ members = [ "crates/gateway-test-fixtures", "crates/gateway-test-utils", "crates/gateway-types", + "crates/make-stream", "crates/merkle-tree", "crates/p2p", "crates/p2p_proto", diff --git a/crates/make-stream/Cargo.toml b/crates/make-stream/Cargo.toml new file mode 100644 index 0000000000..7a4a6c98e0 --- /dev/null +++ b/crates/make-stream/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "make-stream" +version = { workspace = true } +authors = { workspace = true } +edition = { workspace = true } +license = { workspace = true } +rust-version = { workspace = true } + +[dependencies] +tokio = { workspace = true } +tokio-stream = { workspace = true } diff --git a/crates/make-stream/src/lib.rs b/crates/make-stream/src/lib.rs new file mode 100644 index 0000000000..80925b5767 --- /dev/null +++ b/crates/make-stream/src/lib.rs @@ -0,0 +1,29 @@ +use std::future::Future; + +use tokio::sync::mpsc::{self, Sender}; +use tokio_stream::wrappers::ReceiverStream; +use tokio_stream::Stream; + +/// Use the sender to yield items to the stream. +pub fn from_future(src: U) -> impl Stream +where + U: FnOnce(Sender) -> V + Send + 'static, + V: Future + Send + 'static, +{ + let (tx, rx) = mpsc::channel(1); + tokio::spawn(src(tx)); + + ReceiverStream::new(rx) +} + +/// Use the sender to yield items to the stream. +pub fn from_blocking(src: U) -> impl Stream +where + T: Send + 'static, + U: FnOnce(Sender) + Send + 'static, +{ + let (tx, rx) = mpsc::channel(1); + std::thread::spawn(move || src(tx)); + + ReceiverStream::new(rx) +} diff --git a/crates/p2p/Cargo.toml b/crates/p2p/Cargo.toml index 7d40bd4f61..39d1043e2b 100644 --- a/crates/p2p/Cargo.toml +++ b/crates/p2p/Cargo.toml @@ -34,6 +34,7 @@ libp2p = { workspace = true, features = [ "tokio", "yamux", ] } +make-stream = { path = "../make-stream" } p2p_proto = { path = "../p2p_proto" } p2p_stream = { path = "../p2p_stream" } pathfinder-common = { path = "../common" } diff --git a/crates/p2p/src/client/peer_agnostic.rs b/crates/p2p/src/client/peer_agnostic.rs index 8267e864a9..d46eb0b26d 100644 --- a/crates/p2p/src/client/peer_agnostic.rs +++ b/crates/p2p/src/client/peer_agnostic.rs @@ -37,7 +37,6 @@ use pathfinder_common::{ TransactionIndex, }; use tokio::sync::{mpsc, RwLock}; -use tokio_stream::wrappers::ReceiverStream; #[cfg(test)] mod fixtures; @@ -641,8 +640,7 @@ mod header_stream { tracing::trace!(?start, ?stop, ?dir, "Streaming headers"); - let (tx, rx) = mpsc::channel(1); - tokio::spawn(async move { + make_stream::from_future(move |tx| async move { // Loop which refreshes peer set once we exhaust it. loop { 'next_peer: for peer in get_peers().await { @@ -672,9 +670,7 @@ mod header_stream { // with i.e. don't let them drip feed us etc. } } - }); - - ReceiverStream::new(rx) + }) } async fn handle_response( @@ -775,8 +771,7 @@ mod transaction_stream { { tracing::trace!(?start, ?stop, "Streaming Transactions"); - let (tx, rx) = mpsc::channel(1); - tokio::spawn(async move { + make_stream::from_future(move |tx| async move { let mut counts_and_commitments_stream = Box::pin(counts_stream); let cnt = match try_next(&mut counts_and_commitments_stream).await { @@ -839,9 +834,7 @@ mod transaction_stream { return; } } - }); - - ReceiverStream::new(rx) + }) } /// ### Important @@ -956,8 +949,7 @@ mod state_diff_stream { { tracing::trace!(?start, ?stop, "Streaming state diffs"); - let (tx, rx) = mpsc::channel(1); - tokio::spawn(async move { + make_stream::from_future(move |tx| async move { let mut length_stream = Box::pin(length_stream); let cnt = match try_next(&mut length_stream).await { @@ -1018,9 +1010,7 @@ mod state_diff_stream { return; } } - }); - - ReceiverStream::new(rx) + }) } /// ### Important @@ -1176,8 +1166,7 @@ mod class_definition_stream { { tracing::trace!(?start, ?stop, "Streaming classes"); - let (tx, rx) = mpsc::channel(1); - tokio::spawn(async move { + make_stream::from_future(move |tx| async move { let mut declared_class_counts_stream = Box::pin(counts_stream); let cnt = match try_next(&mut declared_class_counts_stream).await { @@ -1240,9 +1229,7 @@ mod class_definition_stream { return; } } - }); - - ReceiverStream::new(rx) + }) } fn make_request(start: BlockNumber, stop: BlockNumber) -> ClassesRequest { @@ -1361,8 +1348,7 @@ mod event_stream { { tracing::trace!(?start, ?stop, "Streaming events"); - let (tx, rx) = mpsc::channel(1); - tokio::spawn(async move { + make_stream::from_future(move |tx| async move { let mut counts_stream = Box::pin(counts_stream); let Some(Ok(cnt)) = counts_stream.next().await else { @@ -1427,9 +1413,7 @@ mod event_stream { return; } } - }); - - ReceiverStream::new(rx) + }) } fn make_request(start: BlockNumber, stop: BlockNumber) -> EventsRequest { diff --git a/crates/pathfinder/Cargo.toml b/crates/pathfinder/Cargo.toml index 3d0ebb3c5a..7c609b4c80 100644 --- a/crates/pathfinder/Cargo.toml +++ b/crates/pathfinder/Cargo.toml @@ -30,6 +30,7 @@ futures = { workspace = true } http = { workspace = true } ipnet = { workspace = true } jemallocator = { workspace = true } +make-stream = { path = "../make-stream" } metrics = { workspace = true } metrics-exporter-prometheus = { workspace = true } p2p = { path = "../p2p" } diff --git a/crates/pathfinder/src/sync/storage_adapters.rs b/crates/pathfinder/src/sync/storage_adapters.rs index 77f5e682e3..c6878c45b4 100644 --- a/crates/pathfinder/src/sync/storage_adapters.rs +++ b/crates/pathfinder/src/sync/storage_adapters.rs @@ -21,8 +21,7 @@ pub fn counts_stream( + Send + 'static, ) -> impl futures::Stream> { - let (tx, rx) = mpsc::channel(1); - std::thread::spawn(move || { + make_stream::from_blocking(move |tx| { let mut batch = VecDeque::new(); while start <= stop { @@ -71,9 +70,7 @@ pub fn counts_stream( while let Some(counts) = batch.pop_front() { _ = tx.blocking_send(Ok(counts)); } - }); - - ReceiverStream::new(rx) + }) } #[cfg(test)] From a01b29c2509f4a3e5855d2d7447249b6b9286c0f Mon Sep 17 00:00:00 2001 From: Krisztian Kovacs Date: Tue, 15 Oct 2024 21:55:12 +0200 Subject: [PATCH 155/282] feat(rpc): implement SerializeForVersion for RpcResponse --- crates/rpc/src/jsonrpc/error.rs | 23 +++-- crates/rpc/src/jsonrpc/response.rs | 86 ++++++++++++++----- crates/rpc/src/jsonrpc/router.rs | 45 ++++++---- crates/rpc/src/jsonrpc/router/subscription.rs | 66 ++++++++++---- crates/rpc/src/jsonrpc/websocket/data.rs | 48 ++++++----- crates/rpc/src/jsonrpc/websocket/logic.rs | 42 ++++++--- .../rpc/src/method/add_declare_transaction.rs | 7 +- .../method/add_deploy_account_transaction.rs | 5 +- .../rpc/src/method/add_invoke_transaction.rs | 5 +- .../src/v06/method/add_declare_transaction.rs | 5 +- .../method/add_deploy_account_transaction.rs | 5 +- .../src/v06/method/add_invoke_transaction.rs | 5 +- 12 files changed, 232 insertions(+), 110 deletions(-) diff --git a/crates/rpc/src/jsonrpc/error.rs b/crates/rpc/src/jsonrpc/error.rs index 6ceefa5075..ed53dad2d0 100644 --- a/crates/rpc/src/jsonrpc/error.rs +++ b/crates/rpc/src/jsonrpc/error.rs @@ -1,8 +1,9 @@ use std::borrow::Cow; -use serde::Serialize; use serde_json::{json, Value}; +use crate::dto::serialize; + #[derive(Debug)] pub enum RpcError { ParseError(String), @@ -73,19 +74,17 @@ impl RpcError { } } -impl Serialize for RpcError { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - use serde::ser::SerializeMap; - - let mut obj = serializer.serialize_map(Some(2))?; - obj.serialize_entry("code", &self.code())?; - obj.serialize_entry("message", &self.message())?; +impl serialize::SerializeForVersion for RpcError { + fn serialize( + &self, + serializer: serialize::Serializer, + ) -> Result { + let mut obj = serializer.serialize_struct()?; + obj.serialize_field("code", &self.code())?; + obj.serialize_field("message", &self.message())?; if let Some(data) = self.data() { - obj.serialize_entry("data", &data)?; + obj.serialize_field("data", &data)?; } obj.end() diff --git a/crates/rpc/src/jsonrpc/response.rs b/crates/rpc/src/jsonrpc/response.rs index abcd629c9f..8efafef712 100644 --- a/crates/rpc/src/jsonrpc/response.rs +++ b/crates/rpc/src/jsonrpc/response.rs @@ -1,75 +1,104 @@ use axum::response::IntoResponse; -use serde::Serialize; use serde_json::Value; +use crate::dto::serialize::{self, SerializeForVersion}; use crate::error::ApplicationError; use crate::jsonrpc::error::RpcError; use crate::jsonrpc::RequestId; +use crate::RpcVersion; #[derive(Debug, PartialEq)] pub struct RpcResponse { pub output: RpcResult, pub id: RequestId, + pub version: RpcVersion, } impl RpcResponse { - pub const fn parse_error(error: String) -> RpcResponse { + pub const fn parse_error(error: String, version: RpcVersion) -> RpcResponse { Self { output: Err(RpcError::ParseError(error)), id: RequestId::Null, + version, } } - pub const fn invalid_request(error: String) -> RpcResponse { + pub const fn invalid_request(error: String, version: RpcVersion) -> RpcResponse { Self { output: Err(RpcError::InvalidRequest(error)), id: RequestId::Null, + version, } } - pub const fn method_not_found(id: RequestId) -> RpcResponse { + pub const fn method_not_found(id: RequestId, version: RpcVersion) -> RpcResponse { Self { output: Err(RpcError::MethodNotFound), id, + version, } } - pub const fn invalid_params(id: RequestId, error: String) -> RpcResponse { + pub const fn invalid_params(id: RequestId, error: String, version: RpcVersion) -> RpcResponse { Self { output: Err(RpcError::InvalidParams(error)), id, + version, } } - pub fn internal_error(id: RequestId, error: String) -> RpcResponse { + pub fn internal_error(id: RequestId, error: String, version: RpcVersion) -> RpcResponse { Self { output: Err(RpcError::InternalError(anyhow::Error::msg(error))), id, + version, } } } pub type RpcResult = Result; -impl Serialize for RpcResponse { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - use serde::ser::SerializeMap; +impl serialize::SerializeForVersion for RpcResponse { + fn serialize( + &self, + serializer: serialize::Serializer, + ) -> Result { + let mut obj = serializer.serialize_struct()?; + obj.serialize_field("jsonrpc", &"2.0")?; - let mut obj = serializer.serialize_map(Some(3))?; - obj.serialize_entry("jsonrpc", "2.0")?; + match &self.output { + Ok(x) => obj.serialize_field("result", &x)?, + Err(e) => obj.serialize_field("error", e)?, + }; + + match &self.id { + RequestId::Number(x) => obj.serialize_field("id", &x)?, + RequestId::String(x) => obj.serialize_field("id", &x)?, + RequestId::Null => obj.serialize_field("id", &Value::Null)?, + RequestId::Notification => {} + }; + + obj.end() + } +} + +impl serialize::SerializeForVersion for &RpcResponse { + fn serialize( + &self, + serializer: serialize::Serializer, + ) -> Result { + let mut obj = serializer.serialize_struct()?; + obj.serialize_field("jsonrpc", &"2.0")?; match &self.output { - Ok(x) => obj.serialize_entry("result", &x)?, - Err(e) => obj.serialize_entry("error", &e)?, + Ok(x) => obj.serialize_field("result", &x)?, + Err(e) => obj.serialize_field("error", e)?, }; match &self.id { - RequestId::Number(x) => obj.serialize_entry("id", &x)?, - RequestId::String(x) => obj.serialize_entry("id", &x)?, - RequestId::Null => obj.serialize_entry("id", &Value::Null)?, + RequestId::Number(x) => obj.serialize_field("id", &x)?, + RequestId::String(x) => obj.serialize_field("id", &x)?, + RequestId::Null => obj.serialize_field("id", &Value::Null)?, RequestId::Notification => {} }; @@ -91,7 +120,13 @@ impl IntoResponse for RpcResponse { _ => {} } - serde_json::to_vec(&self).unwrap().into_response() + serde_json::to_vec( + &self + .serialize(serialize::Serializer::new(self.version)) + .unwrap(), + ) + .unwrap() + .into_response() } } @@ -109,10 +144,13 @@ mod tests { let response = RpcResponse { output: Err(RpcError::InvalidParams(parsing_err.clone())), id: RequestId::Number(1), + version: RpcVersion::V07, }; let parsing_err = RpcError::InvalidParams(parsing_err); - let serialized = serde_json::to_value(&response).unwrap(); + let serialized = response + .serialize(serialize::Serializer::new(RpcVersion::V07)) + .unwrap(); let expected = json!({ "jsonrpc": "2.0", @@ -129,10 +167,12 @@ mod tests { #[test] fn output_is_ok() { - let serialized = serde_json::to_value(&RpcResponse { + let serialized = RpcResponse { output: Ok(Value::String("foobar".to_owned())), id: RequestId::Number(1), - }) + version: RpcVersion::V07, + } + .serialize(serialize::Serializer::new(RpcVersion::V07)) .unwrap(); let expected = json!({ diff --git a/crates/rpc/src/jsonrpc/router.rs b/crates/rpc/src/jsonrpc/router.rs index c3213c453c..c5125568a3 100644 --- a/crates/rpc/src/jsonrpc/router.rs +++ b/crates/rpc/src/jsonrpc/router.rs @@ -13,6 +13,7 @@ pub use subscription::{handle_json_rpc_socket, CatchUp, RpcSubscriptionFlow, Sub use subscription::{split_ws, RpcSubscriptionEndpoint}; use crate::context::RpcContext; +use crate::dto::serialize; use crate::jsonrpc::error::RpcError; use crate::jsonrpc::request::RpcRequest; use crate::jsonrpc::response::RpcResponse; @@ -28,7 +29,7 @@ pub struct RpcRouter { pub context: RpcContext, method_endpoints: &'static HashMap<&'static str, Box>, subscription_endpoints: &'static HashMap<&'static str, Box>, - version: RpcVersion, + pub version: RpcVersion, } pub struct RpcRouterBuilder { @@ -107,7 +108,7 @@ impl RpcRouter { let request = match serde_json::from_str::>(request) { Ok(request) => request, Err(e) => { - return Some(RpcResponse::invalid_request(e.to_string())); + return Some(RpcResponse::invalid_request(e.to_string(), self.version)); } }; @@ -121,7 +122,7 @@ impl RpcRouter { let Some((&method_name, method)) = self.method_endpoints.get_key_value(request.method.as_ref()) else { - return Some(RpcResponse::method_not_found(request.id)); + return Some(RpcResponse::method_not_found(request.id, self.version)); }; metrics::increment_counter!("rpc_method_calls_total", "method" => method_name, "version" => self.version.to_str()); @@ -146,6 +147,7 @@ impl RpcRouter { Some(RpcResponse { output, id: request.id, + version: self.version, }) } } @@ -189,7 +191,7 @@ pub async fn rpc_handler( ) -> impl axum::response::IntoResponse { match ws { Some(ws) => ws.on_upgrade(|ws| async move { - let (ws_tx, ws_rx) = split_ws(ws); + let (ws_tx, ws_rx) = split_ws(ws, state.version); handle_json_rpc_socket(state, ws_tx, ws_rx); }), None => { @@ -207,12 +209,23 @@ pub async fn rpc_handler( RpcResponses::Empty => ().into_response(), RpcResponses::Single(response) => response.into_response(), RpcResponses::Multiple(responses) => { - serde_json::to_string(&responses).unwrap().into_response() + use serialize::SerializeForVersion; + let values = responses + .into_iter() + .map(|response| { + response + .serialize(serialize::Serializer::new(state.version)) + .unwrap() + }) + .collect::>(); + serde_json::to_string(&values).unwrap().into_response() } }, - Err(RpcRequestError::ParseError(e)) => RpcResponse::parse_error(e).into_response(), + Err(RpcRequestError::ParseError(e)) => { + RpcResponse::parse_error(e, state.version).into_response() + } Err(RpcRequestError::InvalidRequest(e)) => { - RpcResponse::invalid_request(e).into_response() + RpcResponse::invalid_request(e, state.version).into_response() } }; @@ -238,15 +251,17 @@ pub(super) enum RpcResponses { Multiple(Vec), } -impl serde::ser::Serialize for RpcResponses { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { +impl serialize::SerializeForVersion for RpcResponses { + fn serialize( + &self, + serializer: serialize::Serializer, + ) -> Result { match self { - Self::Empty => serde::ser::Serialize::serialize(&(), serializer), - Self::Single(response) => serde::ser::Serialize::serialize(response, serializer), - Self::Multiple(responses) => serde::ser::Serialize::serialize(responses, serializer), + Self::Empty => serializer.serialize(&()), + Self::Single(response) => serializer.serialize(response), + Self::Multiple(responses) => { + serializer.serialize_iter(responses.len(), &mut responses.iter()) + } } } } diff --git a/crates/rpc/src/jsonrpc/router/subscription.rs b/crates/rpc/src/jsonrpc/router/subscription.rs index 0f95640787..0597dce538 100644 --- a/crates/rpc/src/jsonrpc/router/subscription.rs +++ b/crates/rpc/src/jsonrpc/router/subscription.rs @@ -10,7 +10,7 @@ use tracing::Instrument; use super::{run_concurrently, RpcRouter}; use crate::context::RpcContext; -use crate::dto::serialize::SerializeForVersion; +use crate::dto::serialize::{self, SerializeForVersion}; use crate::dto::DeserializeForVersion; use crate::error::ApplicationError; use crate::jsonrpc::{RpcError, RpcRequest, RpcResponse}; @@ -333,7 +333,7 @@ type WsReceiver = mpsc::Receiver>; /// serves to allow easier testing. The sender sends `Result<_, RpcResponse>` /// purely for convenience, and the [`RpcResponse`] will be encoded into a /// [`Message::Text`]. -pub fn split_ws(ws: WebSocket) -> (WsSender, WsReceiver) { +pub fn split_ws(ws: WebSocket, version: RpcVersion) -> (WsSender, WsReceiver) { let (mut ws_sender, mut ws_receiver) = ws.split(); // Send messages to the websocket using an MPSC channel. let (sender_tx, mut sender_rx) = mpsc::channel::>(1024); @@ -347,7 +347,12 @@ pub fn split_ws(ws: WebSocket) -> (WsSender, WsReceiver) { } Err(e) => { if ws_sender - .send(Message::Text(serde_json::to_string(&e).unwrap())) + .send(Message::Text( + serde_json::to_string( + &e.serialize(serialize::Serializer::new(version)).unwrap(), + ) + .unwrap(), + )) .await .is_err() { @@ -385,7 +390,7 @@ pub fn handle_json_rpc_socket( Ok(msg) => msg, Err(e) => { if ws_tx - .send(Err(RpcResponse::parse_error(e.to_string()))) + .send(Err(RpcResponse::parse_error(e.to_string(), state.version))) .await .is_err() { @@ -426,7 +431,7 @@ pub fn handle_json_rpc_socket( Ok(raw_value) => raw_value, Err(e) => { if ws_tx - .send(Err(RpcResponse::parse_error(e.to_string()))) + .send(Err(RpcResponse::parse_error(e.to_string(), state.version))) .await .is_err() { @@ -447,7 +452,14 @@ pub fn handle_json_rpc_socket( { Ok(Some(response)) | Err(response) => { if ws_tx - .send(Ok(Message::Text(serde_json::to_string(&response).unwrap()))) + .send(Ok(Message::Text( + serde_json::to_string( + &response + .serialize(serialize::Serializer::new(state.version)) + .unwrap(), + ) + .unwrap(), + ))) .await .is_err() { @@ -466,7 +478,7 @@ pub fn handle_json_rpc_socket( Ok(requests) => requests, Err(e) => { if ws_tx - .send(Err(RpcResponse::parse_error(e.to_string()))) + .send(Err(RpcResponse::parse_error(e.to_string(), state.version))) .await .is_err() { @@ -482,6 +494,7 @@ pub fn handle_json_rpc_socket( if ws_tx .send(Err(RpcResponse::invalid_request( "A batch request must contain at least one request".to_owned(), + state.version, ))) .await .is_err() @@ -521,10 +534,17 @@ pub fn handle_json_rpc_socket( continue; } + let values = responses + .into_iter() + .map(|response| { + response + .serialize(serialize::Serializer::new(state.version)) + .unwrap() + }) + .collect::>(); + if ws_tx - .send(Ok(Message::Text( - serde_json::to_string(&responses).unwrap(), - ))) + .send(Ok(Message::Text(serde_json::to_string(&values).unwrap()))) .await .is_err() { @@ -547,7 +567,7 @@ async fn handle_request( lock: Arc>, ) -> Result, RpcResponse> { let rpc_request = serde_json::from_str::>(raw_request.get()) - .map_err(|e| RpcResponse::invalid_request(e.to_string()))?; + .map_err(|e| RpcResponse::invalid_request(e.to_string(), state.version))?; let req_id = rpc_request.id; // Ignore notification requests. @@ -570,39 +590,47 @@ async fn handle_request( RpcResponse::invalid_params( req_id.clone(), "Missing params for starknet_unsubscribe".to_string(), + state.version, ) })?; - let params = serde_json::from_str::(params.get()) - .map_err(|e| RpcResponse::invalid_params(req_id.clone(), e.to_string()))?; + let params = + serde_json::from_str::(params.get()).map_err(|e| { + RpcResponse::invalid_params(req_id.clone(), e.to_string(), state.version) + })?; let (_, handle) = subscriptions .remove(¶ms.subscription_id) .ok_or_else(|| { - RpcResponse::invalid_params(req_id.clone(), "Subscription not found".to_string()) + RpcResponse::invalid_params( + req_id.clone(), + "Subscription not found".to_string(), + state.version, + ) })?; handle.abort(); metrics::increment_counter!("rpc_method_calls_total", "method" => "starknet_unsubscribe", "version" => state.version.to_str()); return Ok(Some(RpcResponse { output: Ok(true.into()), id: req_id, + version: state.version, })); } let (&method_name, endpoint) = state .subscription_endpoints .get_key_value(rpc_request.method.as_ref()) - .ok_or_else(|| RpcResponse::method_not_found(req_id.clone()))?; + .ok_or_else(|| RpcResponse::method_not_found(req_id.clone(), state.version))?; metrics::increment_counter!("rpc_method_calls_total", "method" => method_name, "version" => state.version.to_str()); let params = serde_json::to_value(rpc_request.params) - .map_err(|e| RpcResponse::invalid_params(req_id.clone(), e.to_string()))?; + .map_err(|e| RpcResponse::invalid_params(req_id.clone(), e.to_string(), state.version))?; // Start the subscription. - let state = state.clone(); + let router = state.clone(); let subscription_id = SubscriptionId::next(); let ws_tx = ws_tx.clone(); match endpoint .invoke(InvokeParams { - router: state, + router, input: params, subscription_id, subscriptions: subscriptions.clone(), @@ -620,11 +648,13 @@ async fn handle_request( serde_json::to_value(&SubscriptionIdResult { subscription_id }).unwrap(), ), id: req_id, + version: state.version, })) } Err(e) => Err(RpcResponse { output: Err(e), id: req_id, + version: state.version, }), } } diff --git a/crates/rpc/src/jsonrpc/websocket/data.rs b/crates/rpc/src/jsonrpc/websocket/data.rs index 81574969d1..fca8837af8 100644 --- a/crates/rpc/src/jsonrpc/websocket/data.rs +++ b/crates/rpc/src/jsonrpc/websocket/data.rs @@ -11,13 +11,14 @@ use pathfinder_common::{ TransactionHash, }; use serde::ser::Error; -use serde::{Deserialize, Serialize}; +use serde::Deserialize; use serde_json::Value; +use crate::dto::serialize; use crate::jsonrpc::router::RpcResponses; use crate::jsonrpc::{RequestId, RpcError, RpcResponse}; -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Deserialize, serde::Serialize)] #[serde(tag = "kind")] pub(super) enum Params { #[serde(rename = "newHeads")] @@ -28,7 +29,7 @@ pub(super) enum Params { TransactionStatus(TransactionStatusParams), } -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Deserialize, serde::Serialize)] pub(super) struct EventFilterParams { #[serde(default)] pub(super) address: Option, @@ -36,12 +37,12 @@ pub(super) struct EventFilterParams { pub(super) keys: Vec>, } -#[derive(Debug, serde::Deserialize, Serialize)] +#[derive(Debug, serde::Deserialize, serde::Serialize)] pub(super) struct TransactionStatusParams { pub(super) transaction_hash: TransactionHash, } -#[derive(Deserialize, Serialize)] +#[derive(Deserialize, serde::Serialize)] pub(super) struct SubscriptionId { pub(super) id: u32, } @@ -52,13 +53,13 @@ pub(super) struct SubscriptionItem { pub(super) item: T, } -impl Serialize for SubscriptionItem { +impl serde::Serialize for SubscriptionItem { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { - #[derive(Serialize)] - struct ResultHelper<'a, U: Serialize> { + #[derive(serde::Serialize)] + struct ResultHelper<'a, U: serde::Serialize> { subscription: u32, result: &'a U, } @@ -104,7 +105,7 @@ pub(super) enum ResponseEvent { /// Describes an emitted event returned by starknet_getEvents #[serde_with::skip_serializing_none] -#[derive(Clone, Debug, Serialize, PartialEq, Eq)] +#[derive(Clone, Debug, serde::Serialize, PartialEq, Eq)] pub struct EmittedEvent { pub data: Vec, pub keys: Vec, @@ -116,7 +117,7 @@ pub struct EmittedEvent { pub transaction_hash: TransactionHash, } -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, serde::Serialize)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum TransactionStatusUpdate { Received = 0, @@ -143,33 +144,35 @@ impl ResponseEvent { } } -impl Serialize for ResponseEvent { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { +impl serialize::SerializeForVersion for ResponseEvent { + fn serialize( + &self, + serializer: serialize::Serializer, + ) -> Result { match self { ResponseEvent::InvalidRequest(e) => { - RpcResponse::invalid_request(e.clone()).serialize(serializer) + RpcResponse::invalid_request(e.clone(), serializer.version).serialize(serializer) } ResponseEvent::InvalidParams(request_id, e) => { - RpcResponse::invalid_params(request_id.clone(), e.clone()).serialize(serializer) + RpcResponse::invalid_params(request_id.clone(), e.clone(), serializer.version) + .serialize(serializer) } ResponseEvent::InternalError(request_id, e) => { - RpcResponse::internal_error(request_id.clone(), e.to_string()).serialize(serializer) + RpcResponse::internal_error(request_id.clone(), e.to_string(), serializer.version) + .serialize(serializer) } ResponseEvent::Header(header) => header.serialize(serializer), ResponseEvent::Event(event) => event.serialize(serializer), ResponseEvent::Subscribed { subscription_id, request_id, - } => successful_response(&subscription_id, request_id.clone()) + } => successful_response(&subscription_id, request_id.clone(), serializer.version) .map_err(|_json_err| Error::custom("Payload serialization failed"))? .serialize(serializer), ResponseEvent::Unsubscribed { success, request_id, - } => successful_response(&success, request_id.clone()) + } => successful_response(&success, request_id.clone(), serializer.version) .map_err(|_json_err| Error::custom("Payload serialization failed"))? .serialize(serializer), ResponseEvent::SubscriptionClosed { @@ -181,6 +184,7 @@ impl Serialize for ResponseEvent { reason: reason.to_owned(), }), id: RequestId::Null, + version: serializer.version, } .serialize(serializer), ResponseEvent::Responses(responses) => responses.serialize(serializer), @@ -193,14 +197,16 @@ impl Serialize for ResponseEvent { pub(super) fn successful_response

( payload: &P, request_id: RequestId, + version: crate::RpcVersion, ) -> Result where - P: Serialize, + P: serde::Serialize, { let payload = serde_json::to_value(payload)?; Ok(RpcResponse { output: Ok(payload), id: request_id, + version, }) } diff --git a/crates/rpc/src/jsonrpc/websocket/logic.rs b/crates/rpc/src/jsonrpc/websocket/logic.rs index df11a6a50c..c2b3c63e57 100644 --- a/crates/rpc/src/jsonrpc/websocket/logic.rs +++ b/crates/rpc/src/jsonrpc/websocket/logic.rs @@ -24,6 +24,7 @@ use tokio::sync::{broadcast, mpsc, watch}; use tracing::error; use super::{EmittedEvent, Params, TransactionStatusUpdate}; +use crate::dto::serialize::{self, SerializeForVersion}; use crate::error::ApplicationError; use crate::jsonrpc::request::RawParams; use crate::jsonrpc::router::RpcRequestError; @@ -34,7 +35,7 @@ use crate::jsonrpc::websocket::data::{ SubscriptionItem, }; use crate::jsonrpc::{RequestId, RpcError, RpcRequest, RpcRouter}; -use crate::{BlockHeader, PendingData}; +use crate::{BlockHeader, PendingData, RpcVersion}; const SUBSCRIBE_METHOD: &str = "pathfinder_subscribe"; const UNSUBSCRIBE_METHOD: &str = "pathfinder_unsubscribe"; @@ -89,6 +90,7 @@ async fn handle_socket(socket: WebSocket, router: RpcRouter) { ws_sender, response_receiver, websocket_context.socket_buffer_capacity, + router.version, )); tokio::spawn(read(ws_receiver, response_sender, router)); } @@ -97,10 +99,11 @@ async fn write( sender: SplitSink, mut response_receiver: mpsc::Receiver, buffer_capacity: NonZeroUsize, + version: RpcVersion, ) { let mut sender = sender.buffer(buffer_capacity.get()); while let Some(response) = response_receiver.recv().await { - if let ControlFlow::Break(()) = send_response(&mut sender, &response).await { + if let ControlFlow::Break(()) = send_response(&mut sender, &response, version).await { break; } } @@ -109,8 +112,13 @@ async fn write( async fn send_response( sender: &mut Buffer, Message>, response: &ResponseEvent, + version: RpcVersion, ) -> ControlFlow<()> { - let message = match serde_json::to_string(&response) { + let message = match serde_json::to_string( + &response + .serialize(serialize::Serializer::new(version)) + .unwrap(), + ) { Ok(x) => x, Err(e) => { tracing::warn!(error=%e, kind=response.kind(), "Encoding websocket message failed"); @@ -711,6 +719,7 @@ mod tests { "EOF while parsing a value at line 1 column 0".to_owned(), )), id: RequestId::Null, + version: RpcVersion::V07, }) .await; @@ -734,7 +743,9 @@ mod tests { let expected_subscription_id = 0; client - .expect_response(&successful_response(&expected_subscription_id, req_id).unwrap()) + .expect_response( + &successful_response(&expected_subscription_id, req_id, RpcVersion::V07).unwrap(), + ) .await; // Do this a bunch of times to ensure the test reception timeout is long enough. @@ -764,7 +775,7 @@ mod tests { }) .await; client - .expect_response(&successful_response(&true, req_id).unwrap()) + .expect_response(&successful_response(&true, req_id, RpcVersion::V07).unwrap()) .await; // Now make sure we don't receive it. This is why testing the timeout was @@ -794,6 +805,7 @@ mod tests { .expect_response(&RpcResponse { output: Ok(json!("0x534e5f5345504f4c4941")), id: RequestId::Number(1), + version: RpcVersion::V07, }) .await; @@ -817,7 +829,7 @@ mod tests { .await; client - .expect_response(&successful_response(&0, req_id).unwrap()) + .expect_response(&successful_response(&0, req_id, RpcVersion::V07).unwrap()) .await; client.l2_blocks.send(block.clone().into()).unwrap(); @@ -882,7 +894,7 @@ mod tests { }) .await; client - .expect_response(&successful_response(&true, req_id).unwrap()) + .expect_response(&successful_response(&true, req_id, RpcVersion::V07).unwrap()) .await; let req_id = RequestId::Number(38); @@ -900,7 +912,7 @@ mod tests { .await; client - .expect_response(&successful_response(&1, req_id).unwrap()) + .expect_response(&successful_response(&1, req_id, RpcVersion::V07).unwrap()) .await; client.l2_blocks.send(block.clone().into()).unwrap(); @@ -933,7 +945,7 @@ mod tests { }) .await; client - .expect_response(&successful_response(&true, req_id).unwrap()) + .expect_response(&successful_response(&true, req_id, RpcVersion::V07).unwrap()) .await; let req_id = RequestId::Number(39); @@ -949,7 +961,7 @@ mod tests { .await; client - .expect_response(&successful_response(&2, req_id).unwrap()) + .expect_response(&successful_response(&2, req_id, RpcVersion::V07).unwrap()) .await; client.l2_blocks.send(block.clone().into()).unwrap(); @@ -1164,7 +1176,7 @@ mod tests { .await; client - .expect_response(&successful_response(&0, req_id).unwrap()) + .expect_response(&successful_response(&0, req_id, RpcVersion::V07).unwrap()) .await; client @@ -1487,7 +1499,7 @@ mod tests { .await; client - .expect_response(&successful_response(&0, req_id).unwrap()) + .expect_response(&successful_response(&0, req_id, RpcVersion::V07).unwrap()) .await; tokio::time::sleep(Duration::from_secs(15)).await; @@ -1689,7 +1701,7 @@ mod tests { async fn expect_response(&mut self, response: &R) where - R: Serialize, + R: SerializeForVersion, { let message = timeout(Duration::from_secs(2), self.receiver.next()) .await @@ -1702,7 +1714,9 @@ mod tests { // Deserialize it to a generic value to avoid field ordering issues. let received: Value = serde_json::from_str(&raw_text).unwrap(); - let expected = serde_json::to_value(response).unwrap(); + let expected = response + .serialize(serialize::Serializer::new(RpcVersion::V07)) + .unwrap(); assert_eq!(received, expected); } diff --git a/crates/rpc/src/method/add_declare_transaction.rs b/crates/rpc/src/method/add_declare_transaction.rs index 44ecf820cb..e643bd90e0 100644 --- a/crates/rpc/src/method/add_declare_transaction.rs +++ b/crates/rpc/src/method/add_declare_transaction.rs @@ -379,7 +379,8 @@ mod tests { use serde_json::json; use super::super::*; - use crate::dto::DeserializeForVersion; + use crate::dto::serialize::SerializeForVersion; + use crate::dto::{serialize, DeserializeForVersion}; use crate::v02::types::request::BroadcastedDeclareTransactionV1; use crate::RpcVersion; @@ -458,7 +459,9 @@ mod tests { let error = AddDeclareTransactionError::from(starknet_error); let error = crate::error::ApplicationError::from(error); let error = crate::jsonrpc::RpcError::from(error); - let error = serde_json::to_value(error).unwrap(); + let error = error + .serialize(serialize::Serializer::new(RpcVersion::V07)) + .unwrap(); let expected = json!({ "code": 63, diff --git a/crates/rpc/src/method/add_deploy_account_transaction.rs b/crates/rpc/src/method/add_deploy_account_transaction.rs index 92a58fc499..b1a45e2589 100644 --- a/crates/rpc/src/method/add_deploy_account_transaction.rs +++ b/crates/rpc/src/method/add_deploy_account_transaction.rs @@ -245,6 +245,7 @@ mod tests { }; use super::*; + use crate::dto::serialize::{self, SerializeForVersion}; use crate::v02::types::request::BroadcastedDeployAccountTransactionV3; use crate::v02::types::{DataAvailabilityMode, ResourceBound, ResourceBounds}; @@ -303,7 +304,9 @@ mod tests { let error = AddDeployAccountTransactionError::from(starknet_error); let error = crate::error::ApplicationError::from(error); let error = crate::jsonrpc::RpcError::from(error); - let error = serde_json::to_value(error).unwrap(); + let error = error + .serialize(serialize::Serializer::new(crate::RpcVersion::V07)) + .unwrap(); let expected = serde_json::json!({ "code": 63, diff --git a/crates/rpc/src/method/add_invoke_transaction.rs b/crates/rpc/src/method/add_invoke_transaction.rs index 4fe53b88d7..303455080b 100644 --- a/crates/rpc/src/method/add_invoke_transaction.rs +++ b/crates/rpc/src/method/add_invoke_transaction.rs @@ -252,6 +252,7 @@ mod tests { use serde_json::json; use super::*; + use crate::dto::serialize::{self, SerializeForVersion}; use crate::dto::DeserializeForVersion; #[test] @@ -340,7 +341,9 @@ mod tests { let error = AddInvokeTransactionError::from(starknet_error); let error = crate::error::ApplicationError::from(error); let error = crate::jsonrpc::RpcError::from(error); - let error = serde_json::to_value(error).unwrap(); + let error = error + .serialize(serialize::Serializer::new(crate::RpcVersion::V07)) + .unwrap(); let expected = json!({ "code": 63, diff --git a/crates/rpc/src/v06/method/add_declare_transaction.rs b/crates/rpc/src/v06/method/add_declare_transaction.rs index c89a6de8d6..1858f44b06 100644 --- a/crates/rpc/src/v06/method/add_declare_transaction.rs +++ b/crates/rpc/src/v06/method/add_declare_transaction.rs @@ -351,6 +351,7 @@ mod tests { use serde_json::json; use super::super::*; + use crate::dto::serialize::{self, SerializeForVersion}; use crate::v02::types::request::BroadcastedDeclareTransactionV1; fn test_declare_txn() -> Transaction { @@ -427,7 +428,9 @@ mod tests { let error = AddDeclareTransactionError::from(starknet_error); let error = crate::error::ApplicationError::from(error); let error = crate::jsonrpc::RpcError::from(error); - let error = serde_json::to_value(error).unwrap(); + let error = error + .serialize(serialize::Serializer::new(crate::RpcVersion::V07)) + .unwrap(); let expected = json!({ "code": 63, diff --git a/crates/rpc/src/v06/method/add_deploy_account_transaction.rs b/crates/rpc/src/v06/method/add_deploy_account_transaction.rs index 498988233c..8741f5a98c 100644 --- a/crates/rpc/src/v06/method/add_deploy_account_transaction.rs +++ b/crates/rpc/src/v06/method/add_deploy_account_transaction.rs @@ -217,6 +217,7 @@ mod tests { }; use super::*; + use crate::dto::serialize::{self, SerializeForVersion}; use crate::v02::types::request::BroadcastedDeployAccountTransactionV3; use crate::v02::types::{DataAvailabilityMode, ResourceBound, ResourceBounds}; @@ -271,7 +272,9 @@ mod tests { let error = AddDeployAccountTransactionError::from(starknet_error); let error = crate::error::ApplicationError::from(error); let error = crate::jsonrpc::RpcError::from(error); - let error = serde_json::to_value(error).unwrap(); + let error = error + .serialize(serialize::Serializer::new(crate::RpcVersion::V07)) + .unwrap(); let expected = serde_json::json!({ "code": 63, diff --git a/crates/rpc/src/v06/method/add_invoke_transaction.rs b/crates/rpc/src/v06/method/add_invoke_transaction.rs index fd76960077..63ea9efb33 100644 --- a/crates/rpc/src/v06/method/add_invoke_transaction.rs +++ b/crates/rpc/src/v06/method/add_invoke_transaction.rs @@ -227,6 +227,7 @@ mod tests { use serde_json::json; use super::*; + use crate::dto::serialize::{self, SerializeForVersion}; #[test] fn positional_args() { @@ -311,7 +312,9 @@ mod tests { let error = AddInvokeTransactionError::from(starknet_error); let error = crate::error::ApplicationError::from(error); let error = crate::jsonrpc::RpcError::from(error); - let error = serde_json::to_value(error).unwrap(); + let error = error + .serialize(serialize::Serializer::new(crate::RpcVersion::V07)) + .unwrap(); let expected = json!({ "code": 63, From fef609ed9185d1ea4346a4f279ac821a4e57fb8a Mon Sep 17 00:00:00 2001 From: Krzysztof Lis Date: Tue, 15 Oct 2024 12:36:38 +0200 Subject: [PATCH 156/282] feat(sync/checkpoint): revert to futures::stream in class sync --- crates/pathfinder/Cargo.toml | 2 +- crates/pathfinder/src/sync/checkpoint.rs | 98 +++++-- .../pathfinder/src/sync/class_definitions.rs | 277 ++++++++++++++++++ 3 files changed, 354 insertions(+), 23 deletions(-) diff --git a/crates/pathfinder/Cargo.toml b/crates/pathfinder/Cargo.toml index 7c609b4c80..a554719537 100644 --- a/crates/pathfinder/Cargo.toml +++ b/crates/pathfinder/Cargo.toml @@ -26,7 +26,7 @@ clap = { workspace = true, features = ["derive", "env", "wrap_help"] } console-subscriber = { workspace = true, optional = true } fake = { workspace = true } flate2 = { workspace = true } -futures = { workspace = true } +futures = { workspace = true, features = ["alloc"] } http = { workspace = true } ipnet = { workspace = true } jemallocator = { workspace = true } diff --git a/crates/pathfinder/src/sync/checkpoint.rs b/crates/pathfinder/src/sync/checkpoint.rs index efe0072c7a..af802de01d 100644 --- a/crates/pathfinder/src/sync/checkpoint.rs +++ b/crates/pathfinder/src/sync/checkpoint.rs @@ -259,10 +259,14 @@ impl Sync { ), ); + let expected_declarations = + class_definitions::expected_declarations_stream(self.storage.clone(), start, stop); + handle_class_stream( class_stream, self.storage.clone(), self.fgw_client.clone(), + expected_declarations, start, stop, ) @@ -380,35 +384,31 @@ async fn handle_state_diff_stream( Ok(()) } -async fn handle_class_stream( - stream: impl Stream> + Send + 'static, +async fn handle_class_stream( + class_definitions: impl Stream> + Send + 'static, storage: Storage, fgw: SequencerClient, + expected_declarations: impl Stream)>> + + Send + + 'static, start: BlockNumber, stop: BlockNumber, ) -> Result<(), SyncError> { - let expectation_source = - class_definitions::ExpectedDeclarationsSource::new(storage.connection()?, start, stop) - .spawn()?; + let a = class_definitions + .map_err(|e| e.data.into()) + .and_then(class_definitions::verify_layout) + .and_then(class_definitions::compute_hash) + .boxed(); - Source::from_stream(stream.map_err(|e| e.map(Into::into))) - .spawn() - .pipe(class_definitions::VerifyLayout, 10) - .pipe(class_definitions::ComputeHash, 10) - .pipe( - class_definitions::VerifyDeclaredAt::new(expectation_source), - 10, - ) - .pipe( - class_definitions::CompileSierraToCasm::new(fgw, tokio::runtime::Handle::current()), - 10, - ) - .pipe(class_definitions::Store(storage.connection()?), 10) - .into_stream() - // Drive stream to completion. + let b = class_definitions::verify_declared_at(expected_declarations.boxed(), a); + + b.and_then(|x| class_definitions::compile_sierra_to_casm_or_fetch(x, fgw.clone())) + .try_chunks(10) + .map_err(|e| e.1) + .and_then(|x| class_definitions::persist(storage.clone(), x)) + .inspect_ok(|x| tracing::info!(tail=%x, "Class definitions chunk synced")) .try_fold((), |_, _| std::future::ready(Ok(()))) - .await - .map_err(SyncError::from_v2)?; + .await?; Ok(()) } @@ -1380,8 +1380,40 @@ mod tests { } } + #[derive(Clone, Copy, Debug, Dummy)] + struct DeclaredClass { + pub block: BlockNumber, + pub class: ClassHash, + } + + #[derive(Clone, Debug)] + struct DeclaredClasses(Vec); + + impl DeclaredClasses { + pub fn to_stream( + &self, + ) -> impl futures::Stream)>> + { + let mut all = HashMap::<_, HashSet>::new(); + self.0 + .iter() + .copied() + .for_each(|DeclaredClass { block, class }| { + all.entry(block).or_default().insert(class); + }); + stream::iter(all.into_iter().map(Ok)) + } + } + + impl Dummy for DeclaredClasses { + fn dummy_with_rng(config: &T, rng: &mut R) -> Self { + DeclaredClasses(fake::vec![DeclaredClass; 1..10]) + } + } + struct Setup { pub streamed_classes: Vec, PeerData>>, + pub declared_classes: DeclaredClasses, pub expected_defs: HashMap>, pub storage: Storage, } @@ -1438,6 +1470,21 @@ mod tests { })), ]; + let declared_classes = DeclaredClasses(vec![ + DeclaredClass { + block: BlockNumber::GENESIS + 1, + class: cairo_hash, + }, + DeclaredClass { + block: BlockNumber::GENESIS + 1, + class: ClassHash(sierra0_hash.0), + }, + DeclaredClass { + block: BlockNumber::GENESIS + 1, + class: ClassHash(sierra2_hash.0), + }, + ]); + let expected_defs = [ (cairo_hash, CAIRO.to_vec()), (ClassHash(sierra0_hash.0), SIERRA0.to_vec()), @@ -1449,6 +1496,7 @@ mod tests { fake_storage::fill(&storage, &blocks); Setup { streamed_classes, + declared_classes, expected_defs, storage, } @@ -1461,6 +1509,7 @@ mod tests { async fn happy_path() { let Setup { streamed_classes, + declared_classes, expected_defs, storage, } = setup(true).await; @@ -1469,6 +1518,7 @@ mod tests { stream::iter(streamed_classes), storage.clone(), FakeFgw, + declared_classes.to_stream(), BlockNumber::GENESIS, BlockNumber::GENESIS + 1, ) @@ -1526,6 +1576,7 @@ mod tests { stream::once(std::future::ready(Ok(data))), storage, FakeFgw, + Faker.fake::().to_stream(), BlockNumber::GENESIS, BlockNumber::GENESIS, ) @@ -1537,6 +1588,7 @@ mod tests { async fn unexpected_class() { let Setup { mut streamed_classes, + declared_classes, storage, .. } = setup(true).await; @@ -1557,6 +1609,7 @@ mod tests { stream::iter(streamed_classes), storage, FakeFgw, + declared_classes.to_stream(), BlockNumber::GENESIS, BlockNumber::GENESIS + 1, ) @@ -1573,6 +1626,7 @@ mod tests { )))), StorageBuilder::in_memory().unwrap(), FakeFgw, + Faker.fake::().to_stream(), BlockNumber::GENESIS, BlockNumber::GENESIS ) diff --git a/crates/pathfinder/src/sync/class_definitions.rs b/crates/pathfinder/src/sync/class_definitions.rs index 6a111971a1..8fc4a23483 100644 --- a/crates/pathfinder/src/sync/class_definitions.rs +++ b/crates/pathfinder/src/sync/class_definitions.rs @@ -108,6 +108,48 @@ pub(super) fn declared_class_counts_stream( storage_adapters::counts_stream(storage, start, stop, batch_size, get_counts) } +pub(super) async fn verify_layout( + peer_data: PeerData, +) -> Result, SyncError> { + let PeerData { peer, data } = peer_data; + match data { + P2PClassDefinition::Cairo { + block_number, + definition, + } => { + let layout = GwClassDefinition::Cairo( + serde_json::from_slice::>(&definition) + .map_err(|e| SyncError::BadClassLayout(peer))?, + ); + Ok(PeerData::new( + peer, + ClassWithLayout { + block_number, + definition: ClassDefinition::Cairo(definition), + layout, + }, + )) + } + P2PClassDefinition::Sierra { + block_number, + sierra_definition, + } => { + let layout = GwClassDefinition::Sierra( + serde_json::from_slice::>(&sierra_definition) + .map_err(|e| SyncError::BadClassLayout(peer))?, + ); + Ok(PeerData::new( + peer, + ClassWithLayout { + block_number, + definition: ClassDefinition::Sierra(sierra_definition), + layout, + }, + )) + } + } +} + pub struct VerifyLayout; impl ProcessStage for VerifyLayout { @@ -193,6 +235,45 @@ impl ProcessStage for ComputeHash { } } +pub(super) async fn compute_hash( + peer_data: PeerData, +) -> Result, SyncError> { + let PeerData { peer, data } = peer_data; + let ClassWithLayout { + block_number, + definition, + layout, + } = data; + + let hash = tokio::task::spawn_blocking(move || match layout { + GwClassDefinition::Cairo(c) => compute_cairo_class_hash( + c.abi.as_ref().get().as_bytes(), + c.program.as_ref().get().as_bytes(), + c.entry_points_by_type.external, + c.entry_points_by_type.l1_handler, + c.entry_points_by_type.constructor, + ), + GwClassDefinition::Sierra(c) => compute_sierra_class_hash( + c.abi.as_ref(), + c.sierra_program, + c.contract_class_version.as_ref(), + c.entry_points_by_type, + ), + }) + .await + .context("Joining blocking task")? + .context("Computing class hash")?; + + Ok(PeerData::new( + peer, + Class { + block_number, + definition, + hash, + }, + )) +} + pub struct VerifyDeclaredAt { expectation_source: Receiver, current: ExpectedDeclarations, @@ -244,6 +325,52 @@ impl ProcessStage for VerifyDeclaredAt { } } +/// This function relies on the guarantee that the block numbers in the stream +/// are correct. +pub(super) fn verify_declared_at( + mut expected_declarations: BoxStream< + 'static, + anyhow::Result<(BlockNumber, HashSet)>, + >, + mut classes: BoxStream<'static, Result, SyncError>>, +) -> impl futures::Stream, SyncError>> { + make_stream::from_future(move |tx| async move { + while let Some(declared) = expected_declarations.next().await { + let (declared_at, mut declared) = match declared { + Ok(x) => x, + Err(e) => { + _ = tx.send(Err(e.into())); + return; + } + }; + + // Some blocks may have no declared classes. + if declared.is_empty() { + continue; + } + + while let Some(maybe_class) = classes.next().await { + let res = maybe_class.and_then(|class| { + if declared_at != class.data.block_number { + return Err(SyncError::UnexpectedClass(class.peer)); + } + + if declared.remove(&class.data.hash) { + Ok(class) + } else { + Err(SyncError::UnexpectedClass(class.peer)) + } + }); + let bail = res.is_err(); + _ = tx.send(res).await; + if bail { + return; + } + } + } + }) +} + #[derive(Debug, Default)] pub struct ExpectedDeclarations { pub block_number: BlockNumber, @@ -308,6 +435,47 @@ impl ExpectedDeclarationsSource { } } +/// Returns a stream of sets of class hashes declared at each block in the range +/// `start..=stop`. +pub(super) fn expected_declarations_stream( + storage: Storage, + mut start: BlockNumber, + stop: BlockNumber, +) -> impl futures::Stream)>> { + make_stream::from_blocking(move |tx| { + let mut db = match storage.connection().context("Creating database connection") { + Ok(x) => x, + Err(e) => { + tx.blocking_send(Err(e.into())); + return; + } + }; + let db = match db.transaction().context("Creating database transaction") { + Ok(x) => x, + Err(e) => { + tx.blocking_send(Err(e.into())); + return; + } + }; + + while start <= stop { + let res = db + .declared_classes_at(start.into()) + .context("Querying declared classes at block") + .and_then(|x| x.context("Block header not found")) + .map_err(Into::into) + .map(|x| (start, x.into_iter().collect::>())); + let bail = res.is_err(); + _ = tx.blocking_send(res); + if bail { + return; + } + + start += 1; + } + }) +} + pub struct CompileSierraToCasm { fgw: T, tokio_handle: tokio::runtime::Handle, @@ -362,6 +530,59 @@ impl ProcessStage for CompileSierraToCas } } +pub(super) async fn compile_sierra_to_casm_or_fetch( + peer_data: PeerData, + fgw: SequencerClient, +) -> Result, SyncError> { + let PeerData { + peer, + data: Class { + block_number, + hash, + definition, + }, + } = peer_data; + + let definition = match definition { + ClassDefinition::Cairo(c) => CompiledClassDefinition::Cairo(c), + ClassDefinition::Sierra(sierra_definition) => { + let (casm_definition, sierra_definition) = + tokio::task::spawn_blocking(move || -> (anyhow::Result<_>, _) { + ( + pathfinder_compiler::compile_to_casm(&sierra_definition) + .context("Compiling Sierra class"), + sierra_definition, + ) + }) + .await + .context("Joining blocking task")?; + + let casm_definition = match casm_definition { + Ok(x) => x, + Err(_) => fgw + .pending_casm_by_hash(hash) + .await + .context("Fetching casm definition from gateway")? + .to_vec(), + }; + + CompiledClassDefinition::Sierra { + sierra_definition, + casm_definition, + } + } + }; + + Ok(PeerData::new( + peer, + CompiledClass { + block_number, + hash, + definition, + }, + )) +} + pub struct Store(pub pathfinder_storage::Connection); impl ProcessStage for Store { @@ -412,6 +633,62 @@ impl ProcessStage for Store { } } +pub(super) async fn persist( + storage: Storage, + classes: Vec>, +) -> Result { + tokio::task::spawn_blocking(move || { + let mut connection = storage + .connection() + .context("Creating database connection")?; + let transaction = connection + .transaction() + .context("Creating database transaction")?; + let tail = classes + .last() + .map(|x| x.data.block_number) + .context("No class definitions to persist")?; + + for CompiledClass { + block_number: _, + definition, + hash, + } in classes.into_iter().map(|x| x.data) + { + match definition { + CompiledClassDefinition::Cairo(definition) => { + transaction + .update_cairo_class(hash, &definition) + .context("Updating cairo class definition")?; + } + CompiledClassDefinition::Sierra { + sierra_definition, + casm_definition, + } => { + let casm_hash = transaction + .casm_hash(hash) + .context("Getting casm hash for sierra class")? + .context("Casm hash not found")?; + + transaction + .update_sierra_class( + &SierraHash(hash.0), + &sierra_definition, + &casm_hash, + &casm_definition, + ) + .context("Updating sierra class definition")?; + } + } + } + transaction.commit().context("Committing db transaction")?; + + Ok(tail) + }) + .await + .context("Joining blocking task")? +} + pub struct VerifyClassHashes { pub declarations: BoxStream<'static, DeclaredClasses>, pub tokio_handle: tokio::runtime::Handle, From 507209f62752f16fb1689d22b2f6d44d4b8eba2e Mon Sep 17 00:00:00 2001 From: Krzysztof Lis Date: Tue, 15 Oct 2024 16:38:00 +0200 Subject: [PATCH 157/282] feat(sync/checkpoint): rayonize sierra compilation --- .../pathfinder/src/bin/pathfinder/config.rs | 4 +- crates/pathfinder/src/sync/checkpoint.rs | 36 +++--- .../pathfinder/src/sync/class_definitions.rs | 108 ++++++++++-------- 3 files changed, 78 insertions(+), 70 deletions(-) diff --git a/crates/pathfinder/src/bin/pathfinder/config.rs b/crates/pathfinder/src/bin/pathfinder/config.rs index 59002eca2a..fa592b852d 100644 --- a/crates/pathfinder/src/bin/pathfinder/config.rs +++ b/crates/pathfinder/src/bin/pathfinder/config.rs @@ -1,7 +1,7 @@ use std::collections::HashSet; use std::fs::File; use std::net::SocketAddr; -use std::num::NonZeroUsize; +use std::num::{NonZeroU32, NonZeroUsize}; use std::path::PathBuf; use std::time::Duration; @@ -94,7 +94,7 @@ Examples: number of CPU cores available.", env = "PATHFINDER_RPC_EXECUTION_CONCURRENCY" )] - execution_concurrency: Option, + execution_concurrency: Option, #[arg( long = "monitor-address", diff --git a/crates/pathfinder/src/sync/checkpoint.rs b/crates/pathfinder/src/sync/checkpoint.rs index af802de01d..877cd155b7 100644 --- a/crates/pathfinder/src/sync/checkpoint.rs +++ b/crates/pathfinder/src/sync/checkpoint.rs @@ -267,8 +267,6 @@ impl Sync { self.storage.clone(), self.fgw_client.clone(), expected_declarations, - start, - stop, ) .await?; @@ -384,27 +382,37 @@ async fn handle_state_diff_stream( Ok(()) } -async fn handle_class_stream( +async fn handle_class_stream( class_definitions: impl Stream> + Send + 'static, storage: Storage, fgw: SequencerClient, expected_declarations: impl Stream)>> + Send + 'static, - start: BlockNumber, - stop: BlockNumber, ) -> Result<(), SyncError> { + // TODO set concurrency to number of cores + let available_parallelism = std::thread::available_parallelism() + .context("Getting available parallelism")? + .get(); + let a = class_definitions .map_err(|e| e.data.into()) + // .co() set concurrency limit = num cores .and_then(class_definitions::verify_layout) - .and_then(class_definitions::compute_hash) + // try_chunks(num cores) + .and_then(class_definitions::compute_hash /* TODO rayonize */) .boxed(); - let b = class_definitions::verify_declared_at(expected_declarations.boxed(), a); - - b.and_then(|x| class_definitions::compile_sierra_to_casm_or_fetch(x, fgw.clone())) - .try_chunks(10) + class_definitions::verify_declared_at(expected_declarations.boxed(), a) + .try_chunks(available_parallelism) .map_err(|e| e.1) + .and_then(|x| { + class_definitions::compile_sierra_to_casm_or_fetch( + x, + fgw.clone(), + tokio::runtime::Handle::current(), + ) + }) .and_then(|x| class_definitions::persist(storage.clone(), x)) .inspect_ok(|x| tracing::info!(tail=%x, "Class definitions chunk synced")) .try_fold((), |_, _| std::future::ready(Ok(()))) @@ -1519,8 +1527,6 @@ mod tests { storage.clone(), FakeFgw, declared_classes.to_stream(), - BlockNumber::GENESIS, - BlockNumber::GENESIS + 1, ) .await .unwrap(); @@ -1577,8 +1583,6 @@ mod tests { storage, FakeFgw, Faker.fake::().to_stream(), - BlockNumber::GENESIS, - BlockNumber::GENESIS, ) .await, Err(SyncError::BadClassLayout(x)) => assert_eq!(x, expected_peer_id)); @@ -1610,8 +1614,6 @@ mod tests { storage, FakeFgw, declared_classes.to_stream(), - BlockNumber::GENESIS, - BlockNumber::GENESIS + 1, ) .await, Err(SyncError::UnexpectedClass(x)) => assert_eq!(x, expected_peer_id)); @@ -1627,8 +1629,6 @@ mod tests { StorageBuilder::in_memory().unwrap(), FakeFgw, Faker.fake::().to_stream(), - BlockNumber::GENESIS, - BlockNumber::GENESIS ) .await, Err(SyncError::Other(_)) diff --git a/crates/pathfinder/src/sync/class_definitions.rs b/crates/pathfinder/src/sync/class_definitions.rs index 8fc4a23483..c2572d36ec 100644 --- a/crates/pathfinder/src/sync/class_definitions.rs +++ b/crates/pathfinder/src/sync/class_definitions.rs @@ -12,6 +12,7 @@ use pathfinder_common::class_definition::{Cairo, ClassDefinition as GwClassDefin use pathfinder_common::state_update::DeclaredClasses; use pathfinder_common::{BlockNumber, CasmHash, ClassHash, SierraHash}; use pathfinder_storage::Storage; +use rayon::iter::{IntoParallelIterator, ParallelIterator}; use serde_json::de; use starknet_gateway_client::GatewayApi; use starknet_gateway_types::class_hash::from_parts::{ @@ -20,7 +21,7 @@ use starknet_gateway_types::class_hash::from_parts::{ }; use starknet_gateway_types::reply::call; use tokio::sync::mpsc::{self, Receiver}; -use tokio::sync::Mutex; +use tokio::sync::{oneshot, Mutex}; use tokio::task::spawn_blocking; use tokio_stream::wrappers::ReceiverStream; @@ -530,57 +531,64 @@ impl ProcessStage for CompileSierraToCas } } -pub(super) async fn compile_sierra_to_casm_or_fetch( - peer_data: PeerData, +pub(super) async fn compile_sierra_to_casm_or_fetch< + SequencerClient: GatewayApi + Clone + Send + 'static, +>( + peer_data: Vec>, fgw: SequencerClient, -) -> Result, SyncError> { - let PeerData { - peer, - data: Class { - block_number, - hash, - definition, - }, - } = peer_data; - - let definition = match definition { - ClassDefinition::Cairo(c) => CompiledClassDefinition::Cairo(c), - ClassDefinition::Sierra(sierra_definition) => { - let (casm_definition, sierra_definition) = - tokio::task::spawn_blocking(move || -> (anyhow::Result<_>, _) { - ( - pathfinder_compiler::compile_to_casm(&sierra_definition) - .context("Compiling Sierra class"), - sierra_definition, - ) - }) - .await - .context("Joining blocking task")?; - - let casm_definition = match casm_definition { - Ok(x) => x, - Err(_) => fgw - .pending_casm_by_hash(hash) - .await - .context("Fetching casm definition from gateway")? - .to_vec(), - }; - - CompiledClassDefinition::Sierra { - sierra_definition, - casm_definition, - } - } - }; + tokio_handle: tokio::runtime::Handle, +) -> Result>, SyncError> { + use rayon::prelude::*; + let (tx, rx) = oneshot::channel(); + rayon::spawn(move || { + let res = peer_data + .into_par_iter() + .map(|x| { + let PeerData { + peer, + data: + Class { + block_number, + hash, + definition, + }, + } = x; + + let definition = match definition { + ClassDefinition::Cairo(c) => CompiledClassDefinition::Cairo(c), + ClassDefinition::Sierra(sierra_definition) => { + let casm_definition = + pathfinder_compiler::compile_to_casm(&sierra_definition) + .context("Compiling Sierra class"); + + let casm_definition = match casm_definition { + Ok(x) => x, + Err(_) => tokio_handle + .block_on(fgw.pending_casm_by_hash(hash)) + .context("Fetching casm definition from gateway")? + .to_vec(), + }; + + CompiledClassDefinition::Sierra { + sierra_definition, + casm_definition, + } + } + }; - Ok(PeerData::new( - peer, - CompiledClass { - block_number, - hash, - definition, - }, - )) + Ok(PeerData::new( + peer, + CompiledClass { + block_number, + hash, + definition, + }, + )) + }) + .collect::>, SyncError>>(); + tx.send(res); + }); + rx.await.expect("Sender not to be dropped") } pub struct Store(pub pathfinder_storage::Connection); From 3b88ff9edc675b9f41895e7ce0efe92977c2ee07 Mon Sep 17 00:00:00 2001 From: Krzysztof Lis Date: Tue, 15 Oct 2024 20:38:37 +0200 Subject: [PATCH 158/282] fixup(sync/checkpoint/class_definitions): fix logic of verify_declared_at --- .../pathfinder/src/sync/class_definitions.rs | 62 +++++++++++++++---- 1 file changed, 49 insertions(+), 13 deletions(-) diff --git a/crates/pathfinder/src/sync/class_definitions.rs b/crates/pathfinder/src/sync/class_definitions.rs index c2572d36ec..88ddfc015b 100644 --- a/crates/pathfinder/src/sync/class_definitions.rs +++ b/crates/pathfinder/src/sync/class_definitions.rs @@ -326,8 +326,29 @@ impl ProcessStage for VerifyDeclaredAt { } } -/// This function relies on the guarantee that the block numbers in the stream -/// are correct. +/// This function makes sure that the classes we receive from other peers are +/// really declared at the expected blocks. +/// +/// ### Details +/// +/// This function ingests two streams: +/// - `expected_declarations` which is a stream of expected class declarations +/// at each block, +/// - `classes` which is a stream of class definitions for each block as +/// received from other peers, +/// +/// producing a stream of class definitions that we are sure are declared at the +/// expected blocks. +/// +/// Any mismatch between the expected and received class definitions will result +/// in an error and termination of the resulting stream. +/// +/// ### Important +/// +/// - The caller guarantees that the block numbers in both input streams are +/// correct. +/// - This function does not care if `expected_declarations` skips empty blocks +/// or not. pub(super) fn verify_declared_at( mut expected_declarations: BoxStream< 'static, @@ -336,8 +357,8 @@ pub(super) fn verify_declared_at( mut classes: BoxStream<'static, Result, SyncError>>, ) -> impl futures::Stream, SyncError>> { make_stream::from_future(move |tx| async move { - while let Some(declared) = expected_declarations.next().await { - let (declared_at, mut declared) = match declared { + while let Some(expected) = expected_declarations.next().await { + let (declared_at, mut declared) = match expected { Ok(x) => x, Err(e) => { _ = tx.send(Err(e.into())); @@ -345,25 +366,37 @@ pub(super) fn verify_declared_at( } }; - // Some blocks may have no declared classes. - if declared.is_empty() { - continue; - } + loop { + // `expected_declarations` skips empty blocks but the current set can still be + // empty because it has just been exhausted and we need to fetch the + // expectations for the next block. + if declared.is_empty() { + break; + } + + let Some(maybe_class) = classes.next().await else { + // `classes` stream has terminated + return; + }; - while let Some(maybe_class) = classes.next().await { let res = maybe_class.and_then(|class| { + // Check if the class is declared at the expected block if declared_at != class.data.block_number { + tracing::error!(%declared_at, %class.data.block_number, %class.data.hash, ?declared, "Unexpected class 1"); return Err(SyncError::UnexpectedClass(class.peer)); } if declared.remove(&class.data.hash) { Ok(class) } else { + tracing::error!(%declared_at, %class.data.block_number, %class.data.hash, ?declared, "Unexpected class 2"); Err(SyncError::UnexpectedClass(class.peer)) } }); let bail = res.is_err(); - _ = tx.send(res).await; + // Send the result to the next stage + tx.send(res).await.expect("Receiver not to be dropped"); + // Short-circuit on error if bail { return; } @@ -466,9 +499,12 @@ pub(super) fn expected_declarations_stream( .and_then(|x| x.context("Block header not found")) .map_err(Into::into) .map(|x| (start, x.into_iter().collect::>())); - let bail = res.is_err(); - _ = tx.blocking_send(res); - if bail { + let is_err = res.is_err(); + let is_empty = res.as_ref().map(|(_, x)| x.is_empty()).unwrap_or(false); + if !is_empty { + tx.blocking_send(res); + } + if is_err { return; } From 16fb37116a413ca140946131825de361f29c8325 Mon Sep 17 00:00:00 2001 From: Krzysztof Lis Date: Tue, 15 Oct 2024 22:02:57 +0200 Subject: [PATCH 159/282] feat(sync/checkpoint): rayonize class hash computation --- crates/pathfinder/src/sync/checkpoint.rs | 5 +- .../pathfinder/src/sync/class_definitions.rs | 82 ++++++++++++++++++- 2 files changed, 81 insertions(+), 6 deletions(-) diff --git a/crates/pathfinder/src/sync/checkpoint.rs b/crates/pathfinder/src/sync/checkpoint.rs index 877cd155b7..c046b02f5f 100644 --- a/crates/pathfinder/src/sync/checkpoint.rs +++ b/crates/pathfinder/src/sync/checkpoint.rs @@ -399,8 +399,9 @@ async fn handle_class_stream>, +) -> Result>, SyncError> { + use rayon::prelude::*; + let (tx, rx) = oneshot::channel(); + rayon::spawn(move || { + let res = peer_data + .into_par_iter() + .map(|PeerData { peer, data }| { + let ClassWithLayout { + block_number, + definition, + layout, + } = data; + + let hash = match layout { + GwClassDefinition::Cairo(c) => compute_cairo_class_hash( + c.abi.as_ref().get().as_bytes(), + c.program.as_ref().get().as_bytes(), + c.entry_points_by_type.external, + c.entry_points_by_type.l1_handler, + c.entry_points_by_type.constructor, + ), + GwClassDefinition::Sierra(c) => compute_sierra_class_hash( + c.abi.as_ref(), + c.sierra_program, + c.contract_class_version.as_ref(), + c.entry_points_by_type, + ), + } + .expect("todo fixme add error type"); + + Ok(PeerData::new( + peer, + Class { + block_number, + definition, + hash, + }, + )) + }) + .collect::>, SyncError>>(); + tx.send(res); + }); + rx.await.expect("Sender not to be dropped") +} + +pub(super) async fn compute_hash0( peer_data: PeerData, ) -> Result, SyncError> { let PeerData { peer, data } = peer_data; @@ -334,8 +381,8 @@ impl ProcessStage for VerifyDeclaredAt { /// This function ingests two streams: /// - `expected_declarations` which is a stream of expected class declarations /// at each block, -/// - `classes` which is a stream of class definitions for each block as -/// received from other peers, +/// - `classes` which is a stream of chunked class definitions as received from +/// other peers, /// /// producing a stream of class definitions that we are sure are declared at the /// expected blocks. @@ -354,9 +401,11 @@ pub(super) fn verify_declared_at( 'static, anyhow::Result<(BlockNumber, HashSet)>, >, - mut classes: BoxStream<'static, Result, SyncError>>, + mut classes: BoxStream<'static, Result>, SyncError>>, ) -> impl futures::Stream, SyncError>> { make_stream::from_future(move |tx| async move { + let mut dechunker = ClassDechunker::new(); + while let Some(expected) = expected_declarations.next().await { let (declared_at, mut declared) = match expected { Ok(x) => x, @@ -374,7 +423,7 @@ pub(super) fn verify_declared_at( break; } - let Some(maybe_class) = classes.next().await else { + let Some(maybe_class) = dechunker.next(&mut classes).await else { // `classes` stream has terminated return; }; @@ -405,6 +454,31 @@ pub(super) fn verify_declared_at( }) } +struct ClassDechunker(VecDeque>); + +impl ClassDechunker { + fn new() -> Self { + Self(Default::default()) + } + + /// Caller must guarantee: chunks in `classes` are never empty. + async fn next( + &mut self, + classes: &mut BoxStream<'static, Result>, SyncError>>, + ) -> Option, SyncError>> { + if self.0.is_empty() { + classes.next().await.map(|x| { + x.map(|chunk| { + self.0.extend(chunk); + self.0.pop_front().expect("Chunk not to be empty") + }) + }) + } else { + self.0.pop_front().map(Ok) + } + } +} + #[derive(Debug, Default)] pub struct ExpectedDeclarations { pub block_number: BlockNumber, From 71434288b096489c99b18b83312ae77b2c53549d Mon Sep 17 00:00:00 2001 From: Krzysztof Lis Date: Tue, 15 Oct 2024 22:21:15 +0200 Subject: [PATCH 160/282] doc(sync/checkpoint/class_definitions): improve comment --- crates/pathfinder/src/sync/checkpoint.rs | 4 ++-- crates/pathfinder/src/sync/class_definitions.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/pathfinder/src/sync/checkpoint.rs b/crates/pathfinder/src/sync/checkpoint.rs index c046b02f5f..48e3bf62ba 100644 --- a/crates/pathfinder/src/sync/checkpoint.rs +++ b/crates/pathfinder/src/sync/checkpoint.rs @@ -395,7 +395,7 @@ async fn handle_class_stream Date: Wed, 16 Oct 2024 14:30:25 +0200 Subject: [PATCH 161/282] feat(sync/checkpoint/class_definitions): verify many class layouts concurrently --- crates/pathfinder/src/sync/checkpoint.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/crates/pathfinder/src/sync/checkpoint.rs b/crates/pathfinder/src/sync/checkpoint.rs index 48e3bf62ba..3d47cf0f8f 100644 --- a/crates/pathfinder/src/sync/checkpoint.rs +++ b/crates/pathfinder/src/sync/checkpoint.rs @@ -390,15 +390,14 @@ async fn handle_class_stream Result<(), SyncError> { - // TODO set concurrency to number of cores let available_parallelism = std::thread::available_parallelism() .context("Getting available parallelism")? .get(); let classes_with_hashes = class_definitions .map_err(|e| e.data.into()) - // .co() set concurrency limit = num cores - .and_then(class_definitions::verify_layout) + .map_ok(class_definitions::verify_layout) + .try_buffered(available_parallelism) .try_chunks(available_parallelism) .map_err(|e| e.1) .and_then(class_definitions::compute_hash) From 57a0db5a0c9fa5ca803da318506e9f52c9b47f9c Mon Sep 17 00:00:00 2001 From: Krzysztof Lis Date: Wed, 16 Oct 2024 15:21:58 +0200 Subject: [PATCH 162/282] chore: clippy --- crates/pathfinder/src/sync/class_definitions.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/pathfinder/src/sync/class_definitions.rs b/crates/pathfinder/src/sync/class_definitions.rs index e9299f1a27..72b9a1c04e 100644 --- a/crates/pathfinder/src/sync/class_definitions.rs +++ b/crates/pathfinder/src/sync/class_definitions.rs @@ -554,14 +554,14 @@ pub(super) fn expected_declarations_stream( let mut db = match storage.connection().context("Creating database connection") { Ok(x) => x, Err(e) => { - tx.blocking_send(Err(e.into())); + tx.blocking_send(Err(e)); return; } }; let db = match db.transaction().context("Creating database transaction") { Ok(x) => x, Err(e) => { - tx.blocking_send(Err(e.into())); + tx.blocking_send(Err(e)); return; } }; From 62ada641142e1cbe73d880a504753c987e86deba Mon Sep 17 00:00:00 2001 From: Krzysztof Lis Date: Wed, 16 Oct 2024 15:34:14 +0200 Subject: [PATCH 163/282] refactor(checkpoint/class_definitions): deduplicate storage impls --- .../pathfinder/src/sync/class_definitions.rs | 92 ++++++++----------- 1 file changed, 38 insertions(+), 54 deletions(-) diff --git a/crates/pathfinder/src/sync/class_definitions.rs b/crates/pathfinder/src/sync/class_definitions.rs index 72b9a1c04e..19555a8e45 100644 --- a/crates/pathfinder/src/sync/class_definitions.rs +++ b/crates/pathfinder/src/sync/class_definitions.rs @@ -11,7 +11,7 @@ use p2p_proto::transaction; use pathfinder_common::class_definition::{Cairo, ClassDefinition as GwClassDefinition, Sierra}; use pathfinder_common::state_update::DeclaredClasses; use pathfinder_common::{BlockNumber, CasmHash, ClassHash, SierraHash}; -use pathfinder_storage::Storage; +use pathfinder_storage::{Storage, Transaction}; use rayon::iter::{IntoParallelIterator, ParallelIterator}; use serde_json::de; use starknet_gateway_client::GatewayApi; @@ -721,29 +721,7 @@ impl ProcessStage for Store { .transaction() .context("Creating database transaction")?; - match definition { - CompiledClassDefinition::Cairo(definition) => { - db.update_cairo_class(hash, &definition) - .context("Updating cairo class definition")?; - } - CompiledClassDefinition::Sierra { - sierra_definition, - casm_definition, - } => { - let casm_hash = db - .casm_hash(hash) - .context("Getting casm hash for sierra class")? - .context("Casm hash not found")?; - - db.update_sierra_class( - &SierraHash(hash.0), - &sierra_definition, - &casm_hash, - &casm_definition, - ) - .context("Updating sierra class definition")?; - } - } + persist_impl(&db, hash, definition)?; db.commit().context("Committing db transaction")?; @@ -756,12 +734,10 @@ pub(super) async fn persist( classes: Vec>, ) -> Result { tokio::task::spawn_blocking(move || { - let mut connection = storage + let mut db = storage .connection() .context("Creating database connection")?; - let transaction = connection - .transaction() - .context("Creating database transaction")?; + let db = db.transaction().context("Creating database transaction")?; let tail = classes .last() .map(|x| x.data.block_number) @@ -773,33 +749,9 @@ pub(super) async fn persist( hash, } in classes.into_iter().map(|x| x.data) { - match definition { - CompiledClassDefinition::Cairo(definition) => { - transaction - .update_cairo_class(hash, &definition) - .context("Updating cairo class definition")?; - } - CompiledClassDefinition::Sierra { - sierra_definition, - casm_definition, - } => { - let casm_hash = transaction - .casm_hash(hash) - .context("Getting casm hash for sierra class")? - .context("Casm hash not found")?; - - transaction - .update_sierra_class( - &SierraHash(hash.0), - &sierra_definition, - &casm_hash, - &casm_definition, - ) - .context("Updating sierra class definition")?; - } - } + persist_impl(&db, hash, definition)?; } - transaction.commit().context("Committing db transaction")?; + db.commit().context("Committing db transaction")?; Ok(tail) }) @@ -807,6 +759,38 @@ pub(super) async fn persist( .context("Joining blocking task")? } +fn persist_impl( + db: &Transaction<'_>, + hash: ClassHash, + definition: CompiledClassDefinition, +) -> anyhow::Result<()> { + match definition { + CompiledClassDefinition::Cairo(definition) => { + db.update_cairo_class(hash, &definition) + .context("Updating cairo class definition")?; + } + CompiledClassDefinition::Sierra { + sierra_definition, + casm_definition, + } => { + let casm_hash = db + .casm_hash(hash) + .context("Getting casm hash for sierra class")? + .context("Casm hash not found")?; + + db.update_sierra_class( + &SierraHash(hash.0), + &sierra_definition, + &casm_hash, + &casm_definition, + ) + .context("Updating sierra class definition")?; + } + } + + Ok(()) +} + pub struct VerifyClassHashes { pub declarations: BoxStream<'static, DeclaredClasses>, pub tokio_handle: tokio::runtime::Handle, From 245f135e626292196241adeac2e90e99b924a2a6 Mon Sep 17 00:00:00 2001 From: Krzysztof Lis Date: Wed, 16 Oct 2024 16:10:44 +0200 Subject: [PATCH 164/282] refactor(checkpoint/class_definitions): deduplicate compile impls --- .../pathfinder/src/sync/class_definitions.rs | 117 +++++++----------- 1 file changed, 44 insertions(+), 73 deletions(-) diff --git a/crates/pathfinder/src/sync/class_definitions.rs b/crates/pathfinder/src/sync/class_definitions.rs index 19555a8e45..9a2325ae29 100644 --- a/crates/pathfinder/src/sync/class_definitions.rs +++ b/crates/pathfinder/src/sync/class_definitions.rs @@ -605,39 +605,8 @@ impl ProcessStage for CompileSierraToCas type Output = CompiledClass; fn map(&mut self, input: Self::Input) -> Result { - let Class { - block_number, - hash, - definition, - } = input; - - let definition = match definition { - ClassDefinition::Cairo(c) => CompiledClassDefinition::Cairo(c), - ClassDefinition::Sierra(sierra_definition) => { - let casm_definition = pathfinder_compiler::compile_to_casm(&sierra_definition) - .context("Compiling Sierra class"); - - let casm_definition = match casm_definition { - Ok(x) => x, - Err(_) => self - .tokio_handle - .block_on(self.fgw.pending_casm_by_hash(hash)) - .context("Fetching casm definition from gateway")? - .to_vec(), - }; - - CompiledClassDefinition::Sierra { - sierra_definition, - casm_definition, - } - } - }; - - Ok(CompiledClass { - block_number, - hash, - definition, - }) + let compiled = compile_or_fetch_impl(input, &self.fgw, &self.tokio_handle)?; + Ok(compiled) } } @@ -654,46 +623,9 @@ pub(super) async fn compile_sierra_to_casm_or_fetch< let res = peer_data .into_par_iter() .map(|x| { - let PeerData { - peer, - data: - Class { - block_number, - hash, - definition, - }, - } = x; - - let definition = match definition { - ClassDefinition::Cairo(c) => CompiledClassDefinition::Cairo(c), - ClassDefinition::Sierra(sierra_definition) => { - let casm_definition = - pathfinder_compiler::compile_to_casm(&sierra_definition) - .context("Compiling Sierra class"); - - let casm_definition = match casm_definition { - Ok(x) => x, - Err(_) => tokio_handle - .block_on(fgw.pending_casm_by_hash(hash)) - .context("Fetching casm definition from gateway")? - .to_vec(), - }; - - CompiledClassDefinition::Sierra { - sierra_definition, - casm_definition, - } - } - }; - - Ok(PeerData::new( - peer, - CompiledClass { - block_number, - hash, - definition, - }, - )) + let PeerData { peer, data } = x; + let compiled = compile_or_fetch_impl(data, &fgw, &tokio_handle)?; + Ok(PeerData::new(peer, compiled)) }) .collect::>, SyncError>>(); tx.send(res); @@ -701,6 +633,45 @@ pub(super) async fn compile_sierra_to_casm_or_fetch< rx.await.expect("Sender not to be dropped") } +fn compile_or_fetch_impl( + class: Class, + fgw: &SequencerClient, + tokio_handle: &tokio::runtime::Handle, +) -> anyhow::Result { + let Class { + block_number, + hash, + definition, + } = class; + + let definition = match definition { + ClassDefinition::Cairo(c) => CompiledClassDefinition::Cairo(c), + ClassDefinition::Sierra(sierra_definition) => { + let casm_definition = pathfinder_compiler::compile_to_casm(&sierra_definition) + .context("Compiling Sierra class"); + + let casm_definition = match casm_definition { + Ok(x) => x, + Err(_) => tokio_handle + .block_on(fgw.pending_casm_by_hash(hash)) + .context("Fetching casm definition from gateway")? + .to_vec(), + }; + + CompiledClassDefinition::Sierra { + sierra_definition, + casm_definition, + } + } + }; + + Ok(CompiledClass { + block_number, + hash, + definition, + }) +} + pub struct Store(pub pathfinder_storage::Connection); impl ProcessStage for Store { From 9e905e8c6e38f0c31c5e6efc9b07bd8ba9cc072f Mon Sep 17 00:00:00 2001 From: Krzysztof Lis Date: Wed, 16 Oct 2024 16:13:28 +0200 Subject: [PATCH 165/282] fixup(checkpoint/class_definitions): propagate error in hash computation --- .../pathfinder/src/sync/class_definitions.rs | 42 +------------------ 1 file changed, 1 insertion(+), 41 deletions(-) diff --git a/crates/pathfinder/src/sync/class_definitions.rs b/crates/pathfinder/src/sync/class_definitions.rs index 9a2325ae29..d9d0ca649a 100644 --- a/crates/pathfinder/src/sync/class_definitions.rs +++ b/crates/pathfinder/src/sync/class_definitions.rs @@ -265,8 +265,7 @@ pub(super) async fn compute_hash( c.contract_class_version.as_ref(), c.entry_points_by_type, ), - } - .expect("todo fixme add error type"); + }?; Ok(PeerData::new( peer, @@ -283,45 +282,6 @@ pub(super) async fn compute_hash( rx.await.expect("Sender not to be dropped") } -pub(super) async fn compute_hash0( - peer_data: PeerData, -) -> Result, SyncError> { - let PeerData { peer, data } = peer_data; - let ClassWithLayout { - block_number, - definition, - layout, - } = data; - - let hash = tokio::task::spawn_blocking(move || match layout { - GwClassDefinition::Cairo(c) => compute_cairo_class_hash( - c.abi.as_ref().get().as_bytes(), - c.program.as_ref().get().as_bytes(), - c.entry_points_by_type.external, - c.entry_points_by_type.l1_handler, - c.entry_points_by_type.constructor, - ), - GwClassDefinition::Sierra(c) => compute_sierra_class_hash( - c.abi.as_ref(), - c.sierra_program, - c.contract_class_version.as_ref(), - c.entry_points_by_type, - ), - }) - .await - .context("Joining blocking task")? - .context("Computing class hash")?; - - Ok(PeerData::new( - peer, - Class { - block_number, - definition, - hash, - }, - )) -} - pub struct VerifyDeclaredAt { expectation_source: Receiver, current: ExpectedDeclarations, From ed8c0b98621509de54dea36fdb58419cfabd4ab3 Mon Sep 17 00:00:00 2001 From: Krzysztof Lis Date: Wed, 16 Oct 2024 16:24:54 +0200 Subject: [PATCH 166/282] fix(checkpoint/class_definitions): introduce class hash computation error Class hash computation error indicates that the class definition is invalid, which is a recoverable error, because we can resync class definitions from some other peer. --- crates/pathfinder/src/sync/class_definitions.rs | 6 ++++-- crates/pathfinder/src/sync/error.rs | 9 +++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/crates/pathfinder/src/sync/class_definitions.rs b/crates/pathfinder/src/sync/class_definitions.rs index d9d0ca649a..708baf1be7 100644 --- a/crates/pathfinder/src/sync/class_definitions.rs +++ b/crates/pathfinder/src/sync/class_definitions.rs @@ -226,7 +226,8 @@ impl ProcessStage for ComputeHash { c.contract_class_version.as_ref(), c.entry_points_by_type, ), - }?; + } + .map_err(|_| SyncError2::ClassHashComputationError)?; Ok(Class { block_number, @@ -265,7 +266,8 @@ pub(super) async fn compute_hash( c.contract_class_version.as_ref(), c.entry_points_by_type, ), - }?; + } + .map_err(|_| SyncError::ClassHashComputationError(peer))?; Ok(PeerData::new( peer, diff --git a/crates/pathfinder/src/sync/error.rs b/crates/pathfinder/src/sync/error.rs index 77381e5215..3f072b726a 100644 --- a/crates/pathfinder/src/sync/error.rs +++ b/crates/pathfinder/src/sync/error.rs @@ -18,6 +18,8 @@ pub(super) enum SyncError { StateDiffCommitmentMismatch(PeerId), #[error("Invalid class definition layout")] BadClassLayout(PeerId), + #[error("Class hash computation failed")] + ClassHashComputationError(PeerId), #[error("Unexpected class definition")] UnexpectedClass(PeerId), #[error("Event commitment mismatch")] @@ -44,6 +46,9 @@ impl SyncError { PeerData::new(x, SyncError2::StateDiffCommitmentMismatch) } SyncError::BadClassLayout(x) => PeerData::new(x, SyncError2::BadClassLayout), + SyncError::ClassHashComputationError(x) => { + PeerData::new(x, SyncError2::ClassHashComputationError) + } SyncError::UnexpectedClass(x) => PeerData::new(x, SyncError2::UnexpectedClass), SyncError::EventCommitmentMismatch(x) => { PeerData::new(x, SyncError2::EventCommitmentMismatch) @@ -64,6 +69,7 @@ impl SyncError { SyncError2::Discontinuity => SyncError::Discontinuity(peer), SyncError2::StateDiffCommitmentMismatch => SyncError::StateDiffCommitmentMismatch(peer), SyncError2::BadClassLayout => SyncError::BadClassLayout(peer), + SyncError2::ClassHashComputationError => SyncError::ClassHashComputationError(peer), SyncError2::UnexpectedClass => SyncError::UnexpectedClass(peer), SyncError2::EventCommitmentMismatch => SyncError::EventCommitmentMismatch(peer), SyncError2::TransactionCommitmentMismatch => { @@ -88,6 +94,8 @@ pub(super) enum SyncError2 { StateDiffCommitmentMismatch, #[error("Invalid class definition layout")] BadClassLayout, + #[error("Class hash computation failed")] + ClassHashComputationError, #[error("Unexpected class definition")] UnexpectedClass, #[error("Event commitment mismatch")] @@ -137,6 +145,7 @@ impl PartialEq for SyncError2 { true } (SyncError2::BadClassLayout, SyncError2::BadClassLayout) => true, + (SyncError2::ClassHashComputationError, SyncError2::ClassHashComputationError) => true, (SyncError2::UnexpectedClass, SyncError2::UnexpectedClass) => true, (SyncError2::EventCommitmentMismatch, SyncError2::EventCommitmentMismatch) => true, _ => false, From a129d261ee9babfd99620643113d47b391b2ffc5 Mon Sep 17 00:00:00 2001 From: Krzysztof Lis Date: Wed, 16 Oct 2024 16:36:32 +0200 Subject: [PATCH 167/282] refactor(checkpoint/class_definitions): deduplicate hash computation impls --- .../pathfinder/src/sync/class_definitions.rs | 93 +++++++------------ 1 file changed, 34 insertions(+), 59 deletions(-) diff --git a/crates/pathfinder/src/sync/class_definitions.rs b/crates/pathfinder/src/sync/class_definitions.rs index 708baf1be7..898536a70b 100644 --- a/crates/pathfinder/src/sync/class_definitions.rs +++ b/crates/pathfinder/src/sync/class_definitions.rs @@ -206,34 +206,7 @@ impl ProcessStage for ComputeHash { type Output = Class; fn map(&mut self, input: Self::Input) -> Result { - let ClassWithLayout { - block_number, - definition, - layout, - } = input; - - let hash = match layout { - GwClassDefinition::Cairo(c) => compute_cairo_class_hash( - c.abi.as_ref().get().as_bytes(), - c.program.as_ref().get().as_bytes(), - c.entry_points_by_type.external, - c.entry_points_by_type.l1_handler, - c.entry_points_by_type.constructor, - ), - GwClassDefinition::Sierra(c) => compute_sierra_class_hash( - c.abi.as_ref(), - c.sierra_program, - c.contract_class_version.as_ref(), - c.entry_points_by_type, - ), - } - .map_err(|_| SyncError2::ClassHashComputationError)?; - - Ok(Class { - block_number, - definition, - hash, - }) + compute_hash_impl(input).map_err(|_| SyncError2::ClassHashComputationError) } } @@ -246,37 +219,9 @@ pub(super) async fn compute_hash( let res = peer_data .into_par_iter() .map(|PeerData { peer, data }| { - let ClassWithLayout { - block_number, - definition, - layout, - } = data; - - let hash = match layout { - GwClassDefinition::Cairo(c) => compute_cairo_class_hash( - c.abi.as_ref().get().as_bytes(), - c.program.as_ref().get().as_bytes(), - c.entry_points_by_type.external, - c.entry_points_by_type.l1_handler, - c.entry_points_by_type.constructor, - ), - GwClassDefinition::Sierra(c) => compute_sierra_class_hash( - c.abi.as_ref(), - c.sierra_program, - c.contract_class_version.as_ref(), - c.entry_points_by_type, - ), - } - .map_err(|_| SyncError::ClassHashComputationError(peer))?; - - Ok(PeerData::new( - peer, - Class { - block_number, - definition, - hash, - }, - )) + let compiled = compute_hash_impl(data) + .map_err(|_| SyncError::ClassHashComputationError(peer))?; + Ok(PeerData::new(peer, compiled)) }) .collect::>, SyncError>>(); tx.send(res); @@ -284,6 +229,36 @@ pub(super) async fn compute_hash( rx.await.expect("Sender not to be dropped") } +fn compute_hash_impl(input: ClassWithLayout) -> anyhow::Result { + let ClassWithLayout { + block_number, + definition, + layout, + } = input; + + let hash = match layout { + GwClassDefinition::Cairo(c) => compute_cairo_class_hash( + c.abi.as_ref().get().as_bytes(), + c.program.as_ref().get().as_bytes(), + c.entry_points_by_type.external, + c.entry_points_by_type.l1_handler, + c.entry_points_by_type.constructor, + ), + GwClassDefinition::Sierra(c) => compute_sierra_class_hash( + c.abi.as_ref(), + c.sierra_program, + c.contract_class_version.as_ref(), + c.entry_points_by_type, + ), + }?; + + Ok(Class { + block_number, + definition, + hash, + }) +} + pub struct VerifyDeclaredAt { expectation_source: Receiver, current: ExpectedDeclarations, From a35c5205085c38aacd18dd34f719d12d3bcaf428 Mon Sep 17 00:00:00 2001 From: Krzysztof Lis Date: Wed, 16 Oct 2024 16:51:37 +0200 Subject: [PATCH 168/282] refactor(checkpoint/class_definitions): deduplicate layout verification impls --- .../pathfinder/src/sync/class_definitions.rs | 102 +++++++----------- 1 file changed, 36 insertions(+), 66 deletions(-) diff --git a/crates/pathfinder/src/sync/class_definitions.rs b/crates/pathfinder/src/sync/class_definitions.rs index 898536a70b..9fd53577e5 100644 --- a/crates/pathfinder/src/sync/class_definitions.rs +++ b/crates/pathfinder/src/sync/class_definitions.rs @@ -6,6 +6,7 @@ use anyhow::Context; use futures::pin_mut; use futures::stream::{BoxStream, StreamExt}; use p2p::client::types::ClassDefinition as P2PClassDefinition; +use p2p::libp2p::PeerId; use p2p::PeerData; use p2p_proto::transaction; use pathfinder_common::class_definition::{Cairo, ClassDefinition as GwClassDefinition, Sierra}; @@ -113,86 +114,55 @@ pub(super) async fn verify_layout( peer_data: PeerData, ) -> Result, SyncError> { let PeerData { peer, data } = peer_data; - match data { + verify_layout_impl(data) + .map(|x| PeerData::new(peer, x)) + .map_err(|_| SyncError::BadClassLayout(peer)) +} + +pub struct VerifyLayout; + +impl ProcessStage for VerifyLayout { + const NAME: &'static str = "Class::VerifyLayout"; + + type Input = P2PClassDefinition; + type Output = ClassWithLayout; + + fn map(&mut self, input: Self::Input) -> Result { + verify_layout_impl(input).map_err(|_| SyncError2::BadClassLayout) + } +} + +fn verify_layout_impl(def: P2PClassDefinition) -> anyhow::Result { + match def { P2PClassDefinition::Cairo { block_number, definition, } => { let layout = GwClassDefinition::Cairo( - serde_json::from_slice::>(&definition) - .map_err(|e| SyncError::BadClassLayout(peer))?, + serde_json::from_slice::>(&definition).inspect_err( + |e| tracing::debug!(%block_number, error=%e, "Bad class layout"), + )?, ); - Ok(PeerData::new( - peer, - ClassWithLayout { - block_number, - definition: ClassDefinition::Cairo(definition), - layout, - }, - )) + Ok(ClassWithLayout { + block_number, + definition: ClassDefinition::Cairo(definition), + layout, + }) } P2PClassDefinition::Sierra { block_number, sierra_definition, } => { let layout = GwClassDefinition::Sierra( - serde_json::from_slice::>(&sierra_definition) - .map_err(|e| SyncError::BadClassLayout(peer))?, + serde_json::from_slice::>(&sierra_definition).inspect_err( + |e| tracing::debug!(%block_number, error=%e, "Bad class layout"), + )?, ); - Ok(PeerData::new( - peer, - ClassWithLayout { - block_number, - definition: ClassDefinition::Sierra(sierra_definition), - layout, - }, - )) - } - } -} - -pub struct VerifyLayout; - -impl ProcessStage for VerifyLayout { - const NAME: &'static str = "Class::VerifyLayout"; - - type Input = P2PClassDefinition; - type Output = ClassWithLayout; - - fn map(&mut self, input: Self::Input) -> Result { - match input { - P2PClassDefinition::Cairo { - block_number, - definition, - } => { - let layout = GwClassDefinition::Cairo( - serde_json::from_slice::>(&definition).map_err(|e| { - tracing::debug!(%block_number, error=%e, "Bad class layout"); - SyncError2::BadClassLayout - })?, - ); - Ok(ClassWithLayout { - block_number, - definition: ClassDefinition::Cairo(definition), - layout, - }) - } - P2PClassDefinition::Sierra { + Ok(ClassWithLayout { block_number, - sierra_definition, - } => { - let layout = GwClassDefinition::Sierra( - serde_json::from_slice::>(&sierra_definition).map_err(|e| { - tracing::debug!(%block_number, error=%e, "Bad class layout"); - SyncError2::BadClassLayout - })?, - ); - Ok(ClassWithLayout { - block_number, - definition: ClassDefinition::Sierra(sierra_definition), - layout, - }) - } + definition: ClassDefinition::Sierra(sierra_definition), + layout, + }) } } } From 302388b2f40b263d929396865b6c8b12fb91e6e5 Mon Sep 17 00:00:00 2001 From: apoorvsadana <95699312+apoorvsadana@users.noreply.github.com> Date: Thu, 17 Oct 2024 13:34:56 +0530 Subject: [PATCH 169/282] move fix to query --- CHANGELOG.md | 5 +- crates/rpc/src/method/get_block_with_txs.rs | 2 +- .../rpc/src/v06/method/get_block_with_txs.rs | 2 +- crates/storage/src/connection/transaction.rs | 89 +++++++++---------- 4 files changed, 46 insertions(+), 52 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 517c0d1816..2704abf98d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,12 +21,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Ethereum RPC API now requires Websocket endpoints (prev. HTTP). If an HTTP url is provided instead, Pathfinder will attempt to connect vía Websocket protocol at that same url. +### Fixed + +- `starknet_getBlockWithTxs` works with empty blocks` + ## [0.14.4] - 2024-10-03 ### Fixed - Pathfinder stops syncing Sepolia testnet at block 218484 because of a block hash mismatch. -- `starknet_getBlockWithTxs` works with empty blocks` ## [0.14.3] - 2024-09-23 diff --git a/crates/rpc/src/method/get_block_with_txs.rs b/crates/rpc/src/method/get_block_with_txs.rs index a5fb6c29cd..dc9cf50ec4 100644 --- a/crates/rpc/src/method/get_block_with_txs.rs +++ b/crates/rpc/src/method/get_block_with_txs.rs @@ -85,7 +85,7 @@ pub async fn get_block_with_txs(context: RpcContext, input: Input) -> Result, Option>>); +type TransactionsAndEventsByBlock = (Vec<(StarknetTransaction, Receipt)>, Vec>); type TransactionAndEventsByHash = ( BlockNumber, StarknetTransaction, @@ -247,10 +247,8 @@ impl Transaction<'_> { let Some(block_number) = self.block_number(block)? else { return Ok(None); }; - let Some(transactions) = self.query_transactions_by_block(block_number)? else { - return Ok(None); - }; - Ok(transactions + Ok(self + .query_transactions_by_block(block_number)? .get(index) .map(|(transaction, ..)| transaction.clone())) } @@ -259,10 +257,7 @@ impl Transaction<'_> { let Some(block_number) = self.block_number(block)? else { return Ok(0); }; - let Some(transactions) = self.query_transactions_by_block(block_number)? else { - return Ok(0); - }; - Ok(transactions.len()) + Ok(self.query_transactions_by_block(block_number)?.len()) } pub fn transaction_data_for_block( @@ -273,12 +268,7 @@ impl Transaction<'_> { return Ok(None); }; - let Some((transactions, events)) = - self.query_transactions_and_events_by_block(block_number)? - else { - return Ok(None); - }; - let events = events.context("Events missing")?; + let (transactions, events) = self.query_transactions_and_events_by_block(block_number)?; Ok(Some( transactions @@ -297,14 +287,12 @@ impl Transaction<'_> { return Ok(None); }; - Ok(self - .query_transactions_by_block(block_number)? - .map(|transactions| { - transactions - .into_iter() - .map(|(transaction, ..)| transaction) - .collect() - })) + Ok(Some( + self.query_transactions_by_block(block_number)? + .into_iter() + .map(|(transaction, ..)| transaction) + .collect(), + )) } pub fn transactions_with_receipts_for_block( @@ -315,9 +303,12 @@ impl Transaction<'_> { return Ok(None); }; - Ok(self - .query_transactions_by_block(block_number)? - .map(|transactions| transactions.into_iter().map(|(t, r, ..)| (t, r)).collect())) + Ok(Some( + self.query_transactions_by_block(block_number)? + .into_iter() + .map(|(t, r, ..)| (t, r)) + .collect(), + )) } pub fn events_for_block(&self, block: BlockId) -> anyhow::Result>> { @@ -380,7 +371,7 @@ impl Transaction<'_> { fn query_transactions_by_block( &self, block_number: BlockNumber, - ) -> anyhow::Result>> { + ) -> anyhow::Result> { let mut stmt = self.inner().prepare_cached( r" SELECT transactions @@ -390,7 +381,7 @@ impl Transaction<'_> { )?; let mut rows = stmt.query(params![&block_number])?; let Some(row) = rows.next()? else { - return Ok(None); + return Ok(vec![]); }; let transactions = row.get_blob(0)?; let transactions = compression::decompress_transactions(transactions) @@ -401,17 +392,15 @@ impl Transaction<'_> { .0; let transactions = transactions.transactions_with_receipts(); - Ok(Some( - transactions - .into_iter() - .map( - |dto::TransactionWithReceiptV2 { - transaction, - receipt, - }| { (transaction.into(), receipt.into()) }, - ) - .collect(), - )) + Ok(transactions + .into_iter() + .map( + |dto::TransactionWithReceiptV2 { + transaction, + receipt, + }| { (transaction.into(), receipt.into()) }, + ) + .collect()) } fn query_transaction_hashes_by_block( @@ -437,7 +426,7 @@ impl Transaction<'_> { fn query_transactions_and_events_by_block( &self, block_number: BlockNumber, - ) -> anyhow::Result> { + ) -> anyhow::Result { let mut stmt = self.inner().prepare_cached( r" SELECT transactions, events @@ -447,7 +436,7 @@ impl Transaction<'_> { )?; let mut rows = stmt.query(params![&block_number])?; let Some(row) = rows.next()? else { - return Ok(None); + return Ok((vec![], vec![])); }; let transactions = row.get_blob(0)?; let transactions = compression::decompress_transactions(transactions) @@ -472,7 +461,7 @@ impl Transaction<'_> { let events = events.map(|events| match events { dto::EventsForBlock::V0 { events } => events, }); - Ok(Some(( + Ok(( transactions .into_iter() .map( @@ -482,13 +471,15 @@ impl Transaction<'_> { }| { (transaction.into(), receipt.into()) }, ) .collect(), - events.map(|events| { - events - .into_iter() - .map(|e| e.into_iter().map(Into::into).collect()) - .collect() - }), - ))) + events + .map(|events| { + events + .into_iter() + .map(|e| e.into_iter().map(Into::into).collect()) + .collect() + }) + .unwrap_or(vec![]), + )) } fn query_events_by_block( From 046913001bee82bfeb122b26354de9d8840d6c5e Mon Sep 17 00:00:00 2001 From: apoorvsadana <95699312+apoorvsadana@users.noreply.github.com> Date: Thu, 17 Oct 2024 13:37:59 +0530 Subject: [PATCH 170/282] fix log --- crates/rpc/src/method/get_block_with_txs.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/rpc/src/method/get_block_with_txs.rs b/crates/rpc/src/method/get_block_with_txs.rs index dc9cf50ec4..8f0dc52ea1 100644 --- a/crates/rpc/src/method/get_block_with_txs.rs +++ b/crates/rpc/src/method/get_block_with_txs.rs @@ -85,7 +85,7 @@ pub async fn get_block_with_txs(context: RpcContext, input: Input) -> Result Date: Thu, 17 Oct 2024 13:41:12 +0530 Subject: [PATCH 171/282] fix clippy --- crates/storage/src/connection/transaction.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/storage/src/connection/transaction.rs b/crates/storage/src/connection/transaction.rs index 12502287e3..6e75c6f378 100644 --- a/crates/storage/src/connection/transaction.rs +++ b/crates/storage/src/connection/transaction.rs @@ -478,7 +478,7 @@ impl Transaction<'_> { .map(|e| e.into_iter().map(Into::into).collect()) .collect() }) - .unwrap_or(vec![]), + .unwrap_or_default(), )) } From 289ab8346b51d3c00f0100c044d7a38a65b1924a Mon Sep 17 00:00:00 2001 From: Krzysztof Lis Date: Thu, 17 Oct 2024 15:59:23 +0200 Subject: [PATCH 172/282] revert(sync/checkpoint/class_definitions): verify many class layouts concurrently This reverts commit c9b0e4cf8c273d06ed1a29e438ec9934dac9989e. --- crates/pathfinder/src/sync/checkpoint.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/pathfinder/src/sync/checkpoint.rs b/crates/pathfinder/src/sync/checkpoint.rs index 3d47cf0f8f..6b75597d86 100644 --- a/crates/pathfinder/src/sync/checkpoint.rs +++ b/crates/pathfinder/src/sync/checkpoint.rs @@ -396,8 +396,7 @@ async fn handle_class_stream Date: Thu, 17 Oct 2024 16:02:35 +0200 Subject: [PATCH 173/282] feat(checkpoint/class_definitions): increase chunk size to num cpus * 8 This gives a ~1.5 perf boost. --- crates/pathfinder/src/sync/checkpoint.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/crates/pathfinder/src/sync/checkpoint.rs b/crates/pathfinder/src/sync/checkpoint.rs index 6b75597d86..f4a6ecc8fe 100644 --- a/crates/pathfinder/src/sync/checkpoint.rs +++ b/crates/pathfinder/src/sync/checkpoint.rs @@ -390,20 +390,22 @@ async fn handle_class_stream Result<(), SyncError> { - let available_parallelism = std::thread::available_parallelism() + // Increasing the chunk size above num cpus improves performance even more. + let chunk_size = std::thread::available_parallelism() .context("Getting available parallelism")? - .get(); + .get() + * 8; let classes_with_hashes = class_definitions .map_err(|e| e.data.into()) .and_then(class_definitions::verify_layout) - .try_chunks(available_parallelism) + .try_chunks(chunk_size) .map_err(|e| e.1) .and_then(class_definitions::compute_hash) .boxed(); class_definitions::verify_declared_at(expected_declarations.boxed(), classes_with_hashes) - .try_chunks(available_parallelism) + .try_chunks(chunk_size) .map_err(|e| e.1) .and_then(|x| { class_definitions::compile_sierra_to_casm_or_fetch( From ce452c11fe59f19f5dd9017843a7022df67b3050 Mon Sep 17 00:00:00 2001 From: t00ts Date: Fri, 18 Oct 2024 13:39:50 +0400 Subject: [PATCH 174/282] chore: fix new clippy warnings (rust 1.82.0) --- crates/executor/src/types.rs | 1 + crates/rpc/src/v06/method/simulate_transactions.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/crates/executor/src/types.rs b/crates/executor/src/types.rs index 9ce3cb5c86..2862a974fb 100644 --- a/crates/executor/src/types.rs +++ b/crates/executor/src/types.rs @@ -153,6 +153,7 @@ pub struct DeployAccountTransactionTrace { } #[derive(Debug, Clone)] +#[allow(clippy::large_enum_variant)] pub enum ExecuteInvocation { FunctionInvocation(Option), RevertedReason(String), diff --git a/crates/rpc/src/v06/method/simulate_transactions.rs b/crates/rpc/src/v06/method/simulate_transactions.rs index 9a85495d46..bf7130bce3 100644 --- a/crates/rpc/src/v06/method/simulate_transactions.rs +++ b/crates/rpc/src/v06/method/simulate_transactions.rs @@ -591,6 +591,7 @@ pub mod dto { #[derive(Clone, Debug, Default, Serialize, Eq, PartialEq)] #[serde(untagged)] + #[allow(clippy::large_enum_variant)] pub enum ExecuteInvocation { #[default] Empty, From 3b88fb4db88c2c4b84bfb48856f328ad482d2529 Mon Sep 17 00:00:00 2001 From: sistemd Date: Fri, 18 Oct 2024 12:16:34 +0200 Subject: [PATCH 175/282] add starknet_getStorageProof method --- Cargo.lock | 1 + crates/common/src/lib.rs | 4 +- crates/common/src/trie.rs | 2 +- crates/merkle-tree/src/class.rs | 35 +- crates/merkle-tree/src/contract.rs | 13 +- crates/rpc/Cargo.toml | 1 + crates/rpc/src/error.rs | 4 + crates/rpc/src/lib.rs | 37 +- crates/rpc/src/method.rs | 2 + crates/rpc/src/method/get_storage_proof.rs | 784 ++++++++++++++++++ .../rpc/src/pathfinder/methods/get_proof.rs | 29 +- crates/rpc/src/v08.rs | 1 + 12 files changed, 879 insertions(+), 34 deletions(-) create mode 100644 crates/rpc/src/method/get_storage_proof.rs diff --git a/Cargo.lock b/Cargo.lock index 80303ad594..fe5ea65f03 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7444,6 +7444,7 @@ dependencies = [ "async-trait", "axum 0.7.5", "base64 0.13.1", + "bitvec", "bytes", "dashmap", "flate2", diff --git a/crates/common/src/lib.rs b/crates/common/src/lib.rs index b7b7f1ef5e..dd055c14de 100644 --- a/crates/common/src/lib.rs +++ b/crates/common/src/lib.rs @@ -513,7 +513,6 @@ macros::felt_newtypes!( CallResultValue, ClassCommitment, ClassCommitmentLeafHash, - ClassHash, ConstructorParam, ContractAddressSalt, ContractNonce, @@ -541,9 +540,10 @@ macros::felt_newtypes!( TransactionSignatureElem, ]; [ + CasmHash, + ClassHash, ContractAddress, SierraHash, - CasmHash, StorageAddress, ] ); diff --git a/crates/common/src/trie.rs b/crates/common/src/trie.rs index 8c0a01786e..3bec4de7d5 100644 --- a/crates/common/src/trie.rs +++ b/crates/common/src/trie.rs @@ -7,7 +7,7 @@ use crate::hash::FeltHash; /// A node in a Starknet patricia-merkle trie. /// /// See pathfinders merkle-tree crate for more information. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum TrieNode { Binary { left: Felt, right: Felt }, Edge { child: Felt, path: BitVec }, diff --git a/crates/merkle-tree/src/class.rs b/crates/merkle-tree/src/class.rs index 5aaf7966f5..e7451d6e7b 100644 --- a/crates/merkle-tree/src/class.rs +++ b/crates/merkle-tree/src/class.rs @@ -1,5 +1,5 @@ use anyhow::Context; -use pathfinder_common::hash::PoseidonHash; +use pathfinder_common::hash::{PedersenHash, PoseidonHash}; use pathfinder_common::trie::TrieNode; use pathfinder_common::{ BlockNumber, @@ -18,7 +18,7 @@ use crate::tree::MerkleTree; /// /// It maps a class's [SierraHash] to its [ClassCommitmentLeafHash] /// -/// Tree data is persisted by a sqlite table 'tree_class'. +/// Tree data is persisted by a sqlite table 'trie_class'. pub struct ClassCommitmentTree<'tx> { tree: MerkleTree, storage: ClassStorage<'tx>, @@ -54,6 +54,28 @@ impl<'tx> ClassCommitmentTree<'tx> { self } + /// Generates a proof for `key`. See [`MerkleTree::get_proof`]. + pub fn get_proof( + tx: &'tx Transaction<'tx>, + block: BlockNumber, + key: &ClassHash, + ) -> anyhow::Result>> { + let root = tx + .class_root_index(block) + .context("Querying class root index")?; + + let Some(root) = root else { + return Ok(None); + }; + + let storage = ClassStorage { + tx, + block: Some(block), + }; + + MerkleTree::::get_proof(root, &storage, key.view_bits()) + } + /// Adds a leaf node for a Sierra -> CASM commitment. /// /// Note that the leaf value is _not_ the Cairo hash, but a hashed value @@ -78,15 +100,8 @@ impl<'tx> ClassCommitmentTree<'tx> { tx: &'tx Transaction<'tx>, block: BlockNumber, class_hash: ClassHash, + root: u64, ) -> anyhow::Result>> { - let root = tx - .class_root_index(block) - .context("Querying class root index")?; - - let Some(root) = root else { - return Ok(None); - }; - let storage = ClassStorage { tx, block: Some(block), diff --git a/crates/merkle-tree/src/contract.rs b/crates/merkle-tree/src/contract.rs index a07a8561b8..bb7d49c746 100644 --- a/crates/merkle-tree/src/contract.rs +++ b/crates/merkle-tree/src/contract.rs @@ -32,7 +32,7 @@ use crate::tree::{MerkleTree, Visit}; /// It maps a contract's [storage addresses](StorageAddress) to their /// [values](StorageValue). /// -/// Tree data is persisted by a sqlite table 'tree_contracts'. +/// Tree data is persisted by a sqlite table 'trie_contracts'. pub struct ContractsStorageTree<'tx> { tree: MerkleTree, storage: ContractStorage<'tx>, @@ -122,7 +122,7 @@ impl<'tx> ContractsStorageTree<'tx> { /// It maps each contract's [address](ContractAddress) to it's [state /// hash](ContractStateHash). /// -/// Tree data is persisted by a sqlite table 'tree_global'. +/// Tree data is persisted by a sqlite table 'trie_storage'. pub struct StorageCommitmentTree<'tx> { tree: MerkleTree, storage: StorageTrieStorage<'tx>, @@ -187,15 +187,8 @@ impl<'tx> StorageCommitmentTree<'tx> { tx: &'tx Transaction<'tx>, block: BlockNumber, address: &ContractAddress, + root: u64, ) -> anyhow::Result>> { - let root = tx - .storage_root_index(block) - .context("Querying storage root index")?; - - let Some(root) = root else { - return Ok(None); - }; - let storage = StorageTrieStorage { tx, block: Some(block), diff --git a/crates/rpc/Cargo.toml b/crates/rpc/Cargo.toml index fb33758168..ad7698ada5 100644 --- a/crates/rpc/Cargo.toml +++ b/crates/rpc/Cargo.toml @@ -56,6 +56,7 @@ zstd = { workspace = true } [dev-dependencies] assert_matches = { workspace = true } +bitvec = { workspace = true } bytes = { workspace = true } flate2 = { workspace = true } gateway-test-utils = { path = "../gateway-test-utils" } diff --git a/crates/rpc/src/error.rs b/crates/rpc/src/error.rs index 7168ba39ad..9935bf748c 100644 --- a/crates/rpc/src/error.rs +++ b/crates/rpc/src/error.rs @@ -91,6 +91,8 @@ pub enum ApplicationError { }, #[error("Gateway is down")] SubscriptionGatewayDown { subscription_id: u32 }, + #[error("The node doesn't support storage proofs for blocks that are too far in the past")] + StorageProofNotSupported, #[error("Proof is missing")] ProofMissing, /// Internal errors are errors whose details we don't want to show to the @@ -125,6 +127,7 @@ impl ApplicationError { ApplicationError::TooManyKeysInFilter { .. } => 34, ApplicationError::ContractError { .. } => 40, ApplicationError::TransactionExecutionError { .. } => 41, + ApplicationError::StorageProofNotSupported { .. } => 42, ApplicationError::InvalidContractClass => 50, ApplicationError::ClassAlreadyDeclared => 51, ApplicationError::InvalidTransactionNonce => 52, @@ -216,6 +219,7 @@ impl ApplicationError { "limit": limit, "requested": requested, })), + ApplicationError::StorageProofNotSupported => None, ApplicationError::ProofMissing => None, ApplicationError::SubscriptionTransactionHashNotFound { subscription_id, diff --git a/crates/rpc/src/lib.rs b/crates/rpc/src/lib.rs index c4c4dd3960..42cfc4f92e 100644 --- a/crates/rpc/src/lib.rs +++ b/crates/rpc/src/lib.rs @@ -265,7 +265,7 @@ pub mod test_utils { Receipt, }; use pathfinder_common::transaction::*; - use pathfinder_merkle_tree::StorageCommitmentTree; + use pathfinder_merkle_tree::{ClassCommitmentTree, StorageCommitmentTree}; use pathfinder_storage::{BlockId, Storage, StorageBuilder}; use starknet_gateway_types::reply::GasPrices; @@ -330,6 +330,9 @@ pub mod test_utils { let sierra_class_definition = starknet_gateway_test_fixtures::class_definitions::CAIRO_0_11_SIERRA.to_vec(); + let sierra_class = SierraHash(class2_hash.0); + let sierra_casm_hash = casm_hash_bytes!(b"non-existent"); + db_txn .insert_cairo_class(class0_hash, &class0_definition) .unwrap(); @@ -338,9 +341,9 @@ pub mod test_utils { .unwrap(); db_txn .insert_sierra_class( - &SierraHash(class2_hash.0), + &sierra_class, &sierra_class_definition, - &casm_hash_bytes!(b"non-existent"), + &sierra_casm_hash, &[], ) .unwrap(); @@ -451,6 +454,33 @@ pub mod test_utils { .set(contract1_addr, contract_state_hash) .unwrap(); + let mut class_commitment_tree = + ClassCommitmentTree::load(&db_txn, BlockNumber::GENESIS + 2).unwrap(); + let sierra_leaf_hash = + pathfinder_common::calculate_class_commitment_leaf_hash(sierra_casm_hash); + + db_txn + .insert_class_commitment_leaf( + BlockNumber::GENESIS + 2, + &sierra_leaf_hash, + &sierra_casm_hash, + ) + .unwrap(); + + class_commitment_tree + .set(sierra_class, sierra_leaf_hash) + .unwrap(); + + let (_, trie_update) = class_commitment_tree.commit().unwrap(); + + let class_root_idx = db_txn + .insert_class_trie(&trie_update, BlockNumber::GENESIS + 2) + .unwrap(); + + db_txn + .insert_class_root(BlockNumber::GENESIS + 2, class_root_idx) + .unwrap(); + let update_results = update_contract_state( contract2_addr, &HashMap::new(), @@ -876,7 +906,6 @@ mod tests { "starknet_call", "starknet_estimateFee", "starknet_estimateMessageFee", - "starknet_getStorageProof", ])] #[case::v0_8_trace("/rpc/v0_8", "v08/starknet_trace_api_openrpc.json", &[ "starknet_traceTransaction", diff --git a/crates/rpc/src/method.rs b/crates/rpc/src/method.rs index c24cb71677..c0b3320b57 100644 --- a/crates/rpc/src/method.rs +++ b/crates/rpc/src/method.rs @@ -18,6 +18,7 @@ pub mod get_events; pub mod get_nonce; pub mod get_state_update; pub mod get_storage_at; +pub mod get_storage_proof; pub mod get_transaction_by_block_id_and_index; pub mod get_transaction_by_hash; pub mod get_transaction_receipt; @@ -51,6 +52,7 @@ pub use get_events::get_events; pub use get_nonce::get_nonce; pub use get_state_update::get_state_update; pub use get_storage_at::get_storage_at; +pub use get_storage_proof::get_storage_proof; pub use get_transaction_by_block_id_and_index::get_transaction_by_block_id_and_index; pub use get_transaction_by_hash::get_transaction_by_hash; pub use get_transaction_receipt::get_transaction_receipt; diff --git a/crates/rpc/src/method/get_storage_proof.rs b/crates/rpc/src/method/get_storage_proof.rs new file mode 100644 index 0000000000..c1827c526b --- /dev/null +++ b/crates/rpc/src/method/get_storage_proof.rs @@ -0,0 +1,784 @@ +use std::collections::HashSet; + +use anyhow::{anyhow, Context}; +use pathfinder_common::hash::PedersenHash; +use pathfinder_common::trie::TrieNode; +use pathfinder_common::{ + BlockHash, + BlockId, + ClassHash, + ContractAddress, + ContractNonce, + StorageAddress, +}; +use pathfinder_crypto::Felt; +use pathfinder_merkle_tree::{ClassCommitmentTree, ContractsStorageTree, StorageCommitmentTree}; + +use crate::context::RpcContext; +use crate::dto::serialize::SerializeForVersion; +use crate::dto::DeserializeForVersion; + +#[derive(Debug, PartialEq, Eq)] +pub struct ContractStorageKeys { + contract_address: ContractAddress, + storage_keys: Vec, +} + +impl DeserializeForVersion for ContractStorageKeys { + fn deserialize(value: crate::dto::Value) -> Result { + value.deserialize_map(|value| { + Ok(Self { + contract_address: value.deserialize("contract_address").map(ContractAddress)?, + storage_keys: value.deserialize_array("storage_keys", |value| { + value.deserialize().map(StorageAddress) + })?, + }) + }) + } +} + +#[derive(Debug)] +pub enum Error { + Internal(anyhow::Error), + BlockNotFound, + ProofLimitExceeded { limit: u32, requested: u32 }, + StorageProofNotSupported, + ProofMissing, +} + +impl From for Error { + fn from(e: anyhow::Error) -> Self { + Self::Internal(e) + } +} + +// Doing this manually since `generate_rpc_error_subset!` +// does not support enum struct variants. +impl From for crate::error::ApplicationError { + fn from(e: Error) -> Self { + match e { + Error::ProofLimitExceeded { limit, requested } => { + Self::ProofLimitExceeded { limit, requested } + } + Error::BlockNotFound => Self::BlockNotFound, + Error::Internal(internal) => Self::Internal(internal), + Error::StorageProofNotSupported => Self::StorageProofNotSupported, + Error::ProofMissing => Self::ProofMissing, + } + } +} + +#[derive(Debug, PartialEq, Eq)] +pub struct Input { + pub block_id: BlockId, + pub class_hashes: Option>, + pub contract_addresses: Option>, + pub contracts_storage_keys: Option>, +} + +impl DeserializeForVersion for Input { + fn deserialize(value: crate::dto::Value) -> Result { + value.deserialize_map(|value| { + Ok(Self { + block_id: value.deserialize("block_id")?, + class_hashes: value.deserialize_optional_array("class_hashes", |value| { + value.deserialize().map(ClassHash) + })?, + contract_addresses: value + .deserialize_optional_array("contract_addresses", |value| { + value.deserialize().map(ContractAddress) + })?, + contracts_storage_keys: value + .deserialize_optional_array("contracts_storage_keys", |value| { + value.deserialize() + })?, + }) + }) + } +} + +/// Wrapper around [`TrieNode`] to implement [`Serialize`]. +#[derive(Debug, PartialEq, Eq, Hash)] +struct ProofNode(TrieNode); + +impl SerializeForVersion for ProofNode { + fn serialize( + &self, + serializer: crate::dto::serialize::Serializer, + ) -> Result { + let mut s = serializer.serialize_struct()?; + match &self.0 { + TrieNode::Binary { left, right } => { + s.serialize_field("left", &left)?; + s.serialize_field("right", &right)?; + } + TrieNode::Edge { child, path } => { + let p = Felt::from_bits(path).unwrap(); + let len = path.len(); + + s.serialize_field("path", &p)?; + s.serialize_field("length", &len)?; + s.serialize_field("child", &child)?; + } + } + s.end() + } +} + +#[derive(Debug, PartialEq, Eq, Hash)] +struct NodeHashToNodeMapping { + node_hash: Felt, + node: ProofNode, +} + +impl From for NodeHashToNodeMapping { + fn from(node: TrieNode) -> Self { + let node_hash = node.hash::(); + Self { + node_hash, + node: ProofNode(node), + } + } +} + +impl SerializeForVersion for &NodeHashToNodeMapping { + fn serialize( + &self, + serializer: crate::dto::serialize::Serializer, + ) -> Result { + let mut s = serializer.serialize_struct()?; + s.serialize_field("node_hash", &self.node_hash)?; + s.serialize_field("node", &self.node)?; + s.end() + } +} + +#[derive(Debug)] +struct NodeHashToNodeMappings(Vec); + +impl SerializeForVersion for &NodeHashToNodeMappings { + fn serialize( + &self, + serializer: crate::dto::serialize::Serializer, + ) -> Result { + serializer.serialize_iter(self.0.len(), &mut self.0.iter()) + } +} + +#[derive(Debug)] +struct ContractLeafData { + nonce: ContractNonce, + class_hash: ClassHash, +} + +impl SerializeForVersion for &ContractLeafData { + fn serialize( + &self, + serializer: crate::dto::serialize::Serializer, + ) -> Result { + let mut s = serializer.serialize_struct()?; + s.serialize_field("nonce", &self.nonce)?; + s.serialize_field("class_hash", &self.class_hash)?; + s.end() + } +} + +#[derive(Debug)] +struct ContractsProof { + nodes: NodeHashToNodeMappings, + contract_leaves_data: Vec, +} + +impl SerializeForVersion for ContractsProof { + fn serialize( + &self, + serializer: crate::dto::serialize::Serializer, + ) -> Result { + let mut s = serializer.serialize_struct()?; + s.serialize_iter("nodes", self.nodes.0.len(), &mut self.nodes.0.iter())?; + s.serialize_iter( + "contract_leaves_data", + self.contract_leaves_data.len(), + &mut self.contract_leaves_data.iter(), + )?; + s.end() + } +} + +#[derive(Debug)] +struct GlobalRoots { + contracts_tree_root: Felt, + classes_tree_root: Felt, + block_hash: BlockHash, +} + +impl SerializeForVersion for GlobalRoots { + fn serialize( + &self, + serializer: crate::dto::serialize::Serializer, + ) -> Result { + let mut s = serializer.serialize_struct()?; + s.serialize_field("contracts_tree_root", &self.contracts_tree_root)?; + s.serialize_field("classes_tree_root", &self.classes_tree_root)?; + s.serialize_field("block_hash", &self.block_hash)?; + s.end() + } +} + +#[derive(Debug)] +pub struct Output { + classes_proof: NodeHashToNodeMappings, + contracts_proof: ContractsProof, + contracts_storage_proofs: Vec, + global_roots: GlobalRoots, +} + +impl SerializeForVersion for Output { + fn serialize( + &self, + serializer: crate::dto::serialize::Serializer, + ) -> Result { + let mut s = serializer.serialize_struct()?; + s.serialize_iter( + "classes_proof", + self.classes_proof.0.len(), + &mut self.classes_proof.0.iter(), + )?; + s.serialize_field("contracts_proof", &self.contracts_proof)?; + s.serialize_iter( + "contracts_storage_proofs", + self.contracts_storage_proofs.len(), + &mut self.contracts_storage_proofs.iter(), + )?; + s.serialize_field("global_roots", &self.global_roots)?; + s.end() + } +} + +/// Returns all the necessary data to trustlessly verify: +/// 1) Membership in the class trie. +/// 2) Membership in the global state trie. +/// 3) Membership in the contract storage trie. +pub async fn get_storage_proof(context: RpcContext, input: Input) -> Result { + const MAX_KEYS: usize = 100; + let mut total_keys = 0; + + total_keys += input.class_hashes.as_ref().map_or(0, |hashes| hashes.len()); + total_keys += input + .contract_addresses + .as_ref() + .map_or(0, |addresses| addresses.len()); + total_keys += input.contracts_storage_keys.as_ref().map_or(0, |keys| { + keys.iter().map(|csk| csk.storage_keys.len()).sum::() + }); + + if total_keys > MAX_KEYS { + return Err(Error::ProofLimitExceeded { + limit: MAX_KEYS as u32, + requested: total_keys as u32, + }); + } + + let block_id = match input.block_id { + BlockId::Pending => { + // Getting proof of a pending block is not supported. + return Err(Error::ProofMissing); + } + other => other.try_into().expect("Only pending cast should fail"), + }; + + let span = tracing::Span::current(); + + let jh = tokio::task::spawn_blocking(move || { + let _g = span.enter(); + + let mut db = context + .storage + .connection() + .context("Opening database connection")?; + + let tx = db.transaction().context("Creating database transaction")?; + + // Use internal error to indicate that the process of querying for a particular + // block failed, which is not the same as being sure that the block is + // not in the db. + let header = tx + .block_header(block_id) + .context("Fetching block header")? + .ok_or(Error::BlockNotFound)?; + + let class_root_idx = tx + .class_root_index(header.number) + .context("Querying class root index")? + .ok_or(Error::StorageProofNotSupported)?; + + let class_root_hash = match tx + .class_trie_node_hash(class_root_idx) + .context("Querying class root hash")? + { + None if input.class_hashes.is_some() => return Err(Error::StorageProofNotSupported), + None => Felt::default(), + Some(hash) => hash, + }; + + let classes_proof = if let Some(class_hashes) = input.class_hashes { + let mut proofs = vec![]; + for class_hash in class_hashes { + let proof = + ClassCommitmentTree::get_proof(&tx, header.number, class_hash, class_root_idx) + .context("Get proof from class tree")? + .ok_or(Error::ProofMissing)?; + proofs.push(proof); + } + + NodeHashToNodeMappings( + proofs + .into_iter() + .flatten() + .map(|node| node.into()) + .collect::>() + .into_iter() + .collect::>(), + ) + } else { + NodeHashToNodeMappings(vec![]) + }; + + let storage_root_idx = tx + .storage_root_index(header.number) + .context("Querying storage root index")? + .ok_or(Error::StorageProofNotSupported)?; + + let storage_root_hash = match tx + .storage_trie_node_hash(storage_root_idx) + .context("Querying class root hash")? + { + None if input.contract_addresses.is_some() => { + return Err(Error::StorageProofNotSupported) + } + None => Felt::default(), + Some(hash) => hash, + }; + + let (contract_proof_nodes, contract_leaves_data) = + if let Some(contract_addresses) = input.contract_addresses { + let mut proofs = vec![]; + let mut contract_leaves_data = vec![]; + for address in contract_addresses { + let proof = StorageCommitmentTree::get_proof( + &tx, + header.number, + &address, + storage_root_idx, + ) + .context("Get proof from storage tree")? + .ok_or(Error::ProofMissing)?; + proofs.push(proof); + + let class_hash = tx + .contract_class_hash(header.number.into(), address) + .context("Querying contract's class hash")? + .unwrap_or_default(); + + let nonce = tx + .contract_nonce(address, header.number.into()) + .context("Querying contract's nonce")? + .unwrap_or_default(); + + contract_leaves_data.push(ContractLeafData { nonce, class_hash }); + } + + let nodes: Vec = proofs + .into_iter() + .flatten() + .map(|node| node.into()) + .collect::>() + .into_iter() + .collect(); + + (NodeHashToNodeMappings(nodes), contract_leaves_data) + } else { + (NodeHashToNodeMappings(vec![]), vec![]) + }; + + let contracts_storage_proofs = match input.contracts_storage_keys { + None => vec![], + Some(contracts_storage_keys) => { + let mut proofs = vec![]; + for csk in contracts_storage_keys { + let root = tx + .contract_root_index(header.number, csk.contract_address) + .context("Querying contract root index")?; + + if let Some(root) = root { + let mut contract_storage_proof = vec![]; + for key in csk.storage_keys { + let proof = ContractsStorageTree::get_proof( + &tx, + csk.contract_address, + header.number, + key.view_bits(), + root, + ) + .context("Get proof from contract storage tree")? + .ok_or_else(|| { + let e = anyhow!( + "Storage proof missing for key {:?}, but should be present", + key + ); + tracing::warn!("{e}"); + e + })?; + contract_storage_proof.push(proof); + } + + let proof: Vec = contract_storage_proof + .into_iter() + .flatten() + .map(|node| node.into()) + .collect::>() + .into_iter() + .collect(); + + proofs.push(NodeHashToNodeMappings(proof)); + } else { + proofs.push(NodeHashToNodeMappings(vec![])); + } + } + + proofs + } + }; + + let contracts_proof = ContractsProof { + nodes: contract_proof_nodes, + contract_leaves_data, + }; + + let global_roots = GlobalRoots { + contracts_tree_root: storage_root_hash, + classes_tree_root: class_root_hash, + block_hash: header.hash, + }; + + Ok(Output { + classes_proof, + contracts_proof, + contracts_storage_proofs, + global_roots, + }) + }); + + jh.await.context("Database read panic or shutting down")? +} + +#[cfg(test)] +mod tests { + use pathfinder_common::macro_prelude::*; + use pathfinder_common::*; + + use super::*; + use crate::dto::serialize::SerializeForVersion; + + mod serialization { + use bitvec::bitvec; + use bitvec::prelude::Msb0; + use serde_json::json; + + use super::*; + use crate::RpcVersion; + + #[rstest::rstest] + #[case::named_all_optionals_present( + json!({ + "block_id": { + "block_hash": "0x02ea95751155e45acac9186684306684ee328c99610a7a855a8685907a60746c" + }, + "class_hashes": ["0x12345", "0x2345", "0x345"], + "contract_addresses": ["0x12345", "0x2345", "0x345"], + "contracts_storage_keys": [{ + "contract_address": "0x111", + "storage_keys": ["0x123", "0x234", "0x345"] + }] + }), + Input { + block_id: BlockId::Hash(block_hash!( + "0x02ea95751155e45acac9186684306684ee328c99610a7a855a8685907a60746c" + )), + class_hashes: Some(vec![ + class_hash!("0x12345"), + class_hash!("0x2345"), + class_hash!("0x345"), + ]), + contract_addresses: Some(vec![ + contract_address!("0x12345"), + contract_address!("0x2345"), + contract_address!("0x345"), + ]), + contracts_storage_keys: Some(vec![ContractStorageKeys { + contract_address: contract_address!("0x111"), + storage_keys: vec![ + storage_address!("0x123"), + storage_address!("0x234"), + storage_address!("0x345"), + ], + }]), + } + )] + #[case::positional_all_optionals_present( + json!([ + { + "block_hash": "0x02ea95751155e45acac9186684306684ee328c99610a7a855a8685907a60746c" + }, + ["0x12345", "0x2345", "0x345"], + ["0x12345", "0x2345", "0x345"], + [{ + "contract_address": "0x111", + "storage_keys": ["0x123", "0x234", "0x345"] + }] + ]), + Input { + block_id: BlockId::Hash(block_hash!( + "0x02ea95751155e45acac9186684306684ee328c99610a7a855a8685907a60746c" + )), + class_hashes: Some(vec![ + class_hash!("0x12345"), + class_hash!("0x2345"), + class_hash!("0x345"), + ]), + contract_addresses: Some(vec![ + contract_address!("0x12345"), + contract_address!("0x2345"), + contract_address!("0x345"), + ]), + contracts_storage_keys: Some(vec![ContractStorageKeys { + contract_address: contract_address!("0x111"), + storage_keys: vec![ + storage_address!("0x123"), + storage_address!("0x234"), + storage_address!("0x345"), + ], + }]), + } + )] + #[case::named_all_optionals_missing( + json!({ + "block_id": { + "block_hash": "0x02ea95751155e45acac9186684306684ee328c99610a7a855a8685907a60746c" + } + }), + Input { + block_id: BlockId::Hash(block_hash!( + "0x02ea95751155e45acac9186684306684ee328c99610a7a855a8685907a60746c" + )), + class_hashes: None, + contract_addresses: None, + contracts_storage_keys: None, + } + )] + #[case::positional_all_optionals_missing( + json!({ + "block_id": { + "block_hash": "0x02ea95751155e45acac9186684306684ee328c99610a7a855a8685907a60746c" + }, + }), + Input { + block_id: BlockId::Hash(block_hash!( + "0x02ea95751155e45acac9186684306684ee328c99610a7a855a8685907a60746c" + )), + class_hashes: None, + contract_addresses: None, + contracts_storage_keys: None, + } + )] + fn parsing_input(#[case] input: serde_json::Value, #[case] expected: Input) { + let input = Input::deserialize(crate::dto::Value::new(input, RpcVersion::V07)).unwrap(); + + assert_eq!(input, expected); + } + + #[rstest::rstest] + #[case::empty_output( + Output { + classes_proof: NodeHashToNodeMappings(vec![]), + contracts_proof: ContractsProof { + nodes: NodeHashToNodeMappings(vec![]), + contract_leaves_data: vec![], + }, + contracts_storage_proofs: vec![], + global_roots: GlobalRoots { + contracts_tree_root: Felt::default(), + classes_tree_root: Felt::default(), + block_hash: BlockHash::default(), + }, + }, + json!({ + "classes_proof": [], + "contracts_proof": { + "nodes": [], + "contract_leaves_data": [] + }, + "contracts_storage_proofs": [], + "global_roots": { + "contracts_tree_root": "0x0", + "classes_tree_root": "0x0", + "block_hash": "0x0" + } + }), + )] + #[case::non_empty_output( + Output { + classes_proof: NodeHashToNodeMappings(vec![NodeHashToNodeMapping { + node_hash: Felt::from_hex_str("0x123").unwrap(), + node: ProofNode(TrieNode::Binary { + left: Felt::from_hex_str("0x123").unwrap(), + right: Felt::from_hex_str("0x123").unwrap(), + }), + }]), + contracts_proof: ContractsProof { + nodes: NodeHashToNodeMappings(vec![NodeHashToNodeMapping { + node_hash: Felt::from_hex_str("0x123").unwrap(), + node: ProofNode(TrieNode::Edge { + child: Felt::from_hex_str("0x123").unwrap(), + path: bitvec![u8, Msb0; 0; 8], + }), + }]), + contract_leaves_data: vec![ContractLeafData { + nonce: ContractNonce::ZERO, + class_hash: ClassHash(Felt::from_hex_str("0x123").unwrap()), + }], + }, + contracts_storage_proofs: vec![NodeHashToNodeMappings(vec![NodeHashToNodeMapping { + node_hash: Felt::from_hex_str("0x123").unwrap(), + node: ProofNode(TrieNode::Binary { + left: Felt::from_hex_str("0x123").unwrap(), + right: Felt::from_hex_str("0x123").unwrap(), + }), + }])], + global_roots: GlobalRoots { + contracts_tree_root: Felt::from_hex_str("0x123").unwrap(), + classes_tree_root: Felt::from_hex_str("0x123").unwrap(), + block_hash: BlockHash(Felt::from_hex_str("0x123").unwrap()), + }, + }, + json!({ + "classes_proof": [ + { + "node_hash": "0x123", + "node": { + "left": "0x123", + "right": "0x123" + } + } + ], + "contracts_proof": { + "nodes": [ + { + "node_hash": "0x123", + "node": { + "child": "0x123", + "length": 8, + "path": "0x0", + } + } + ], + "contract_leaves_data": [ + { + "nonce": "0x0", + "class_hash": "0x123" + } + ] + }, + "contracts_storage_proofs": [ + [ + { + "node_hash": "0x123", + "node": { + "left": "0x123", + "right": "0x123" + } + } + ] + ], + "global_roots": { + "contracts_tree_root": "0x123", + "classes_tree_root": "0x123", + "block_hash": "0x123" + } + }), + )] + fn serialization_output(#[case] output: Output, #[case] expected: serde_json::Value) { + let output = output + .serialize(crate::dto::serialize::Serializer::default()) + .unwrap(); + + assert_eq!(output, expected); + } + } + + #[tokio::test] + async fn proof_limit_exceeded() { + let context = RpcContext::for_tests(); + let input = Input { + block_id: BlockId::Number(pathfinder_common::BlockNumber::GENESIS), + class_hashes: Some(vec![class_hash_bytes!(b"class 2 hash (sierra)"); 100]), + contract_addresses: Some(vec![contract_address_bytes!(b"contract 2 (sierra)")]), + contracts_storage_keys: Some(vec![ContractStorageKeys { + contract_address: contract_address_bytes!(b"contract 1"), + storage_keys: vec![storage_address_bytes!(b"storage addr 0")], + }]), + }; + + let output = get_storage_proof(context, input).await; + + assert!(matches!(output, Err(Error::ProofLimitExceeded { .. }))); + } + + #[tokio::test] + async fn success() { + let context = RpcContext::for_tests(); + let input = Input { + block_id: BlockId::Number(pathfinder_common::BlockNumber::GENESIS + 2), + class_hashes: Some(vec![class_hash_bytes!(b"class 2 hash (sierra)")]), + contract_addresses: Some(vec![contract_address_bytes!(b"contract 2 (sierra)")]), + contracts_storage_keys: Some(vec![ContractStorageKeys { + contract_address: contract_address_bytes!(b"contract 1"), + storage_keys: vec![storage_address_bytes!(b"storage addr 0")], + }]), + }; + + let output = get_storage_proof(context, input).await; + + assert!(output.is_ok()); + } + + #[tokio::test] + async fn pending_block() { + let context = RpcContext::for_tests(); + let input = Input { + block_id: BlockId::Pending, + class_hashes: None, + contract_addresses: None, + contracts_storage_keys: None, + }; + + let output = get_storage_proof(context, input).await; + + assert!(matches!(output, Err(Error::ProofMissing))); + } + + #[tokio::test] + async fn block_not_found() { + let context = RpcContext::for_tests(); + let input = Input { + block_id: BlockId::Number(pathfinder_common::BlockNumber::MAX), + class_hashes: None, + contract_addresses: None, + contracts_storage_keys: None, + }; + + let output = get_storage_proof(context, input).await; + + assert!(matches!(output, Err(Error::BlockNotFound))); + } +} diff --git a/crates/rpc/src/pathfinder/methods/get_proof.rs b/crates/rpc/src/pathfinder/methods/get_proof.rs index f7c1388f8b..bf9fe3f13d 100644 --- a/crates/rpc/src/pathfinder/methods/get_proof.rs +++ b/crates/rpc/src/pathfinder/methods/get_proof.rs @@ -244,12 +244,21 @@ pub async fn get_proof( other => Some(other), }; + let storage_root_idx = tx + .storage_root_index(header.number) + .context("Querying storage root index")? + .ok_or(GetProofError::ProofMissing)?; + // Generate a proof for this contract. If the contract does not exist, this will // be a "non membership" proof. - let contract_proof = - StorageCommitmentTree::get_proof(&tx, header.number, &input.contract_address) - .context("Creating contract proof")? - .ok_or(GetProofError::ProofMissing)?; + let contract_proof = StorageCommitmentTree::get_proof( + &tx, + header.number, + &input.contract_address, + storage_root_idx, + ) + .context("Creating contract proof")? + .ok_or(GetProofError::ProofMissing)?; let contract_proof = ProofNodes(contract_proof); let contract_state_hash = tx @@ -368,11 +377,17 @@ pub async fn get_proof_class( other => Some(other), }; + let class_root_idx = tx + .class_root_index(header.number) + .context("Querying class root index")? + .ok_or(GetProofError::ProofMissing)?; + // Generate a proof for this class. If the class does not exist, this will // be a "non membership" proof. - let class_proof = ClassCommitmentTree::get_proof(&tx, header.number, input.class_hash) - .context("Creating class proof")? - .ok_or(GetProofError::ProofMissing)?; + let class_proof = + ClassCommitmentTree::get_proof(&tx, header.number, input.class_hash, class_root_idx) + .context("Creating class proof")? + .ok_or(GetProofError::ProofMissing)?; let class_proof = ProofNodes(class_proof); Ok(GetClassProofOutput { diff --git a/crates/rpc/src/v08.rs b/crates/rpc/src/v08.rs index 6675c26e9f..ac69b32be6 100644 --- a/crates/rpc/src/v08.rs +++ b/crates/rpc/src/v08.rs @@ -19,6 +19,7 @@ pub fn register_routes() -> RpcRouterBuilder { .register("starknet_getNonce", crate::method::get_nonce) .register("starknet_getStateUpdate", crate::method::get_state_update) .register("starknet_getStorageAt", crate::method::get_storage_at) + .register("starknet_getStorageProof", crate::method::get_storage_proof) .register("starknet_getTransactionByBlockIdAndIndex", crate::method::get_transaction_by_block_id_and_index) .register("starknet_getTransactionByHash", crate::method::get_transaction_by_hash) .register("starknet_getTransactionStatus", crate::method::get_transaction_status) From 1787e555507b3a12f7a8a730ab1df1f35e9b7a37 Mon Sep 17 00:00:00 2001 From: sistemd Date: Fri, 18 Oct 2024 12:16:34 +0200 Subject: [PATCH 176/282] chore: clippy --- crates/merkle-tree/src/class.rs | 24 +----------------------- 1 file changed, 1 insertion(+), 23 deletions(-) diff --git a/crates/merkle-tree/src/class.rs b/crates/merkle-tree/src/class.rs index e7451d6e7b..7a257850a9 100644 --- a/crates/merkle-tree/src/class.rs +++ b/crates/merkle-tree/src/class.rs @@ -1,5 +1,5 @@ use anyhow::Context; -use pathfinder_common::hash::{PedersenHash, PoseidonHash}; +use pathfinder_common::hash::PoseidonHash; use pathfinder_common::trie::TrieNode; use pathfinder_common::{ BlockNumber, @@ -54,28 +54,6 @@ impl<'tx> ClassCommitmentTree<'tx> { self } - /// Generates a proof for `key`. See [`MerkleTree::get_proof`]. - pub fn get_proof( - tx: &'tx Transaction<'tx>, - block: BlockNumber, - key: &ClassHash, - ) -> anyhow::Result>> { - let root = tx - .class_root_index(block) - .context("Querying class root index")?; - - let Some(root) = root else { - return Ok(None); - }; - - let storage = ClassStorage { - tx, - block: Some(block), - }; - - MerkleTree::::get_proof(root, &storage, key.view_bits()) - } - /// Adds a leaf node for a Sierra -> CASM commitment. /// /// Note that the leaf value is _not_ the Cairo hash, but a hashed value From 88b38d53c0e0cdb7241e45d2c52f463802d86838 Mon Sep 17 00:00:00 2001 From: sistemd Date: Fri, 18 Oct 2024 12:16:34 +0200 Subject: [PATCH 177/282] doc fix --- crates/rpc/src/method/get_storage_proof.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/rpc/src/method/get_storage_proof.rs b/crates/rpc/src/method/get_storage_proof.rs index c1827c526b..60c6ede890 100644 --- a/crates/rpc/src/method/get_storage_proof.rs +++ b/crates/rpc/src/method/get_storage_proof.rs @@ -97,7 +97,7 @@ impl DeserializeForVersion for Input { } } -/// Wrapper around [`TrieNode`] to implement [`Serialize`]. +/// Wrapper around [`TrieNode`] to implement [`SerializeForVersion`]. #[derive(Debug, PartialEq, Eq, Hash)] struct ProofNode(TrieNode); From 520f18c1b1c4d1788dd8fe00e0c3ebeb4d1ce586 Mon Sep 17 00:00:00 2001 From: Krisztian Kovacs Date: Mon, 14 Oct 2024 16:41:08 +0200 Subject: [PATCH 178/282] feat(executor): add structured transaction execution errors In JSON-RPC 0.8.0 we're expected to return the error in a structured format for reverted and failing transactions. Unforunately `blockifier` 0.8.0-rc.3 does not directly expose that information: attributes of the `ErrorStack` type in blockifier are private, plus the revert error only comes out of blockifier as a string containg the formatted stack trace. These are expected to become available in the blockifier version that will be used for Starknet 0.13.3 -- but until that becomes available we're forced to use our blockifier fork fixing these API issues. This change adds error stack information to our executor API so that we _do_ expose the necessary information to the JSON-RPC implementation. --- Cargo.lock | 8 +- Cargo.toml | 4 +- crates/executor/src/call.rs | 13 ++- crates/executor/src/error.rs | 63 +++++++--- crates/executor/src/error_stack.rs | 57 +++++++++ crates/executor/src/estimate.rs | 6 +- crates/executor/src/lib.rs | 2 + crates/executor/src/simulate.rs | 9 +- crates/rpc/src/error.rs | 108 +++++++++++++++++- crates/rpc/src/jsonrpc/error.rs | 7 +- crates/rpc/src/jsonrpc/response.rs | 2 +- crates/rpc/src/method/call.rs | 2 +- crates/rpc/src/method/estimate_fee.rs | 5 + .../rpc/src/method/simulate_transactions.rs | 5 + .../src/method/trace_block_transactions.rs | 1 + crates/rpc/src/method/trace_transaction.rs | 1 + crates/rpc/src/v06/method/call.rs | 2 +- crates/rpc/src/v06/method/estimate_fee.rs | 2 + .../src/v06/method/simulate_transactions.rs | 2 + .../v06/method/trace_block_transactions.rs | 1 + .../rpc/src/v06/method/trace_transaction.rs | 1 + 21 files changed, 261 insertions(+), 40 deletions(-) create mode 100644 crates/executor/src/error_stack.rs diff --git a/Cargo.lock b/Cargo.lock index fe5ea65f03..362f45812b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1549,8 +1549,7 @@ dependencies = [ [[package]] name = "blockifier" version = "0.8.0-rc.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fb99b6d20e12f5dff17a2b53e3e6cab54766357a638f90dafcb43c0ac933d4b" +source = "git+https://github.com/eqlabs/sequencer?branch=eqlabs/main-v0.13.2#2e59ae0f7c0da559cd77bd9db794309a8e3fe99a" dependencies = [ "anyhow", "ark-ec", @@ -5217,7 +5216,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2 0.5.7", + "socket2 0.4.10", "tokio", "tower-service", "tracing", @@ -9430,8 +9429,7 @@ dependencies = [ [[package]] name = "starknet_api" version = "0.13.0-rc.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b505c9c076d9fce854304bd743c93ea540ebea6b16ec96819b07343a3aa2c7c" +source = "git+https://github.com/eqlabs/sequencer?branch=eqlabs/main-v0.13.2#2e59ae0f7c0da559cd77bd9db794309a8e3fe99a" dependencies = [ "bitvec", "cairo-lang-starknet-classes", diff --git a/Cargo.toml b/Cargo.toml index 87c0d4a83a..6e919634ba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -55,7 +55,7 @@ axum = "0.7.5" base64 = "0.13.1" bincode = "2.0.0-rc.3" bitvec = "1.0.1" -blockifier = "=0.8.0-rc.3" +blockifier = { git = "https://github.com/eqlabs/sequencer", branch = "eqlabs/main-v0.13.2" } bloomfilter = "1.0.12" bytes = "1.4.0" cached = "0.44.0" @@ -123,7 +123,7 @@ serde_with = "3.7.0" sha2 = "0.10.7" sha3 = "0.10" # This one needs to match the version used by blockifier -starknet_api = "=0.13.0-rc.1" +starknet_api = { git = "https://github.com/eqlabs/sequencer", branch = "eqlabs/main-v0.13.2" } # This one needs to match the version used by blockifier starknet-types-core = "=0.1.5" syn = "1.0" diff --git a/crates/executor/src/call.rs b/crates/executor/src/call.rs index 402f8ac862..cff18bdcf3 100644 --- a/crates/executor/src/call.rs +++ b/crates/executor/src/call.rs @@ -2,6 +2,7 @@ use std::sync::Arc; use blockifier::context::TransactionContext; use blockifier::execution::entry_point::{CallEntryPoint, EntryPointExecutionContext}; +use blockifier::state::state_api::StateReader; use blockifier::transaction::objects::{DeprecatedTransactionInfo, TransactionInfo}; use blockifier::versioned_constants::VersionedConstants; use cairo_vm::vm::runners::cairo_runner::ExecutionResources; @@ -29,6 +30,7 @@ pub fn call( .into_iter() .map(|param| param.0.into_starkfelt()) .collect(); + let class_hash = state.get_class_hash_at(contract_address)?; let call_entry_point = CallEntryPoint { storage_address: contract_address, @@ -49,7 +51,16 @@ pub fn call( false, )?; - let call_info = call_entry_point.execute(&mut state, &mut resources, &mut context)?; + let call_info = call_entry_point + .execute(&mut state, &mut resources, &mut context) + .map_err(|e| { + CallError::from_entry_point_execution_error( + e, + &contract_address, + &class_hash, + &entry_point_selector, + ) + })?; let result = call_info .execution diff --git a/crates/executor/src/error.rs b/crates/executor/src/error.rs index aacbfc3e7d..b32a85f4a4 100644 --- a/crates/executor/src/error.rs +++ b/crates/executor/src/error.rs @@ -3,14 +3,17 @@ use blockifier::execution::errors::{ EntryPointExecutionError as BlockifierEntryPointExecutionError, PreExecutionError, }; +use blockifier::execution::stack_trace::gen_transaction_execution_error_trace; use blockifier::state::errors::StateError; use blockifier::transaction::errors::TransactionExecutionError as BlockifierTransactionExecutionError; +use crate::error_stack::ErrorStack; + #[derive(Debug)] pub enum CallError { ContractNotFound, InvalidMessageSelector, - ContractError(anyhow::Error), + ContractError(anyhow::Error, ErrorStack), Internal(anyhow::Error), Custom(anyhow::Error), } @@ -18,36 +21,58 @@ pub enum CallError { impl From for CallError { fn from(value: BlockifierTransactionExecutionError) -> Self { use BlockifierTransactionExecutionError::*; + + let error_stack = gen_transaction_execution_error_trace(&value); + match value { ContractConstructorExecutionFailed( ConstructorEntryPointExecutionError::ExecutionError { error, .. }, - ) - | ExecutionError { error, .. } - | ValidateTransactionError { error, .. } => match error { + ) => match error { + BlockifierEntryPointExecutionError::PreExecutionError( + PreExecutionError::EntryPointNotFound(_), + ) => Self::InvalidMessageSelector, + BlockifierEntryPointExecutionError::PreExecutionError( + PreExecutionError::UninitializedStorageAddress(_), + ) => Self::ContractNotFound, + _ => Self::ContractError(error.into(), error_stack.into()), + }, + ExecutionError { error, .. } => match error { BlockifierEntryPointExecutionError::PreExecutionError( PreExecutionError::EntryPointNotFound(_), ) => Self::InvalidMessageSelector, BlockifierEntryPointExecutionError::PreExecutionError( PreExecutionError::UninitializedStorageAddress(_), ) => Self::ContractNotFound, - _ => Self::Custom(error.into()), + _ => Self::ContractError(error.into(), error_stack.into()), }, - e => Self::Custom(e.into()), + ValidateTransactionError { error, .. } => match error { + BlockifierEntryPointExecutionError::PreExecutionError( + PreExecutionError::EntryPointNotFound(_), + ) => Self::InvalidMessageSelector, + BlockifierEntryPointExecutionError::PreExecutionError( + PreExecutionError::UninitializedStorageAddress(_), + ) => Self::ContractNotFound, + _ => Self::ContractError(error.into(), error_stack.into()), + }, + e => Self::ContractError(e.into(), error_stack.into()), } } } -impl From for CallError { - fn from(e: BlockifierEntryPointExecutionError) -> Self { - match e { - BlockifierEntryPointExecutionError::PreExecutionError( - PreExecutionError::EntryPointNotFound(_), - ) => Self::InvalidMessageSelector, - BlockifierEntryPointExecutionError::PreExecutionError( - PreExecutionError::UninitializedStorageAddress(_), - ) => Self::ContractNotFound, - _ => Self::ContractError(e.into()), - } +impl CallError { + pub fn from_entry_point_execution_error( + error: BlockifierEntryPointExecutionError, + contract_address: &starknet_api::core::ContractAddress, + class_hash: &starknet_api::core::ClassHash, + entry_point: &starknet_api::core::EntryPointSelector, + ) -> Self { + let error = BlockifierTransactionExecutionError::ExecutionError { + error, + class_hash: *class_hash, + storage_address: *contract_address, + selector: *entry_point, + }; + error.into() } } @@ -77,6 +102,7 @@ pub enum TransactionExecutionError { ExecutionError { transaction_index: usize, error: String, + error_stack: ErrorStack, }, Internal(anyhow::Error), Custom(anyhow::Error), @@ -105,9 +131,12 @@ impl From for TransactionExecutionError { impl TransactionExecutionError { pub fn new(transaction_index: usize, error: BlockifierTransactionExecutionError) -> Self { + let error_stack = gen_transaction_execution_error_trace(&error); + Self::ExecutionError { transaction_index, error: error.to_string(), + error_stack: error_stack.into(), } } } diff --git a/crates/executor/src/error_stack.rs b/crates/executor/src/error_stack.rs new file mode 100644 index 0000000000..0128cfa8a7 --- /dev/null +++ b/crates/executor/src/error_stack.rs @@ -0,0 +1,57 @@ +use blockifier::execution::stack_trace::{ + gen_transaction_execution_error_trace, + ErrorStack as BlockifierErrorStack, +}; +use blockifier::transaction::errors::TransactionExecutionError; +use pathfinder_common::{ClassHash, ContractAddress, EntryPoint}; + +use crate::IntoFelt; + +#[derive(Clone, Debug, Default)] +pub struct ErrorStack(pub Vec); + +impl From for ErrorStack { + fn from(value: BlockifierErrorStack) -> Self { + Self(value.stack.into_iter().map(Into::into).collect()) + } +} + +impl From for ErrorStack { + fn from(value: TransactionExecutionError) -> Self { + let error_stack = gen_transaction_execution_error_trace(&value); + error_stack.into() + } +} + +#[derive(Clone, Debug)] +pub enum Frame { + CallFrame(CallFrame), + StringFrame(String), +} + +impl From for Frame { + fn from(value: blockifier::execution::stack_trace::Frame) -> Self { + match value { + blockifier::execution::stack_trace::Frame::EntryPoint(entry_point) => { + Frame::CallFrame(CallFrame { + storage_address: ContractAddress(entry_point.storage_address.0.into_felt()), + class_hash: ClassHash(entry_point.class_hash.0.into_felt()), + selector: entry_point.selector.map(|s| EntryPoint(s.0.into_felt())), + }) + } + blockifier::execution::stack_trace::Frame::Vm(vm_exception) => { + Frame::StringFrame(String::from(&vm_exception)) + } + blockifier::execution::stack_trace::Frame::StringFrame(string_frame) => { + Frame::StringFrame(string_frame) + } + } + } +} + +#[derive(Clone, Debug)] +pub struct CallFrame { + pub storage_address: ContractAddress, + pub class_hash: ClassHash, + pub selector: Option, +} diff --git a/crates/executor/src/estimate.rs b/crates/executor/src/estimate.rs index fc0731dd4b..a25f9201c8 100644 --- a/crates/executor/src/estimate.rs +++ b/crates/executor/src/estimate.rs @@ -37,10 +37,12 @@ pub fn estimate( match tx_info { Ok(tx_info) => { if let Some(revert_error) = tx_info.revert_error { - tracing::debug!(%revert_error, "Transaction reverted"); + let revert_string = revert_error.to_string(); + tracing::debug!(revert_error=%revert_string, "Transaction reverted"); return Err(TransactionExecutionError::ExecutionError { transaction_index: transaction_idx, - error: revert_error, + error: revert_string, + error_stack: revert_error.into(), }); } diff --git a/crates/executor/src/lib.rs b/crates/executor/src/lib.rs index 59979ed239..a73edd1e2f 100644 --- a/crates/executor/src/lib.rs +++ b/crates/executor/src/lib.rs @@ -1,6 +1,7 @@ pub(crate) mod call; pub(crate) mod class; pub(crate) mod error; +pub(crate) mod error_stack; pub(crate) mod estimate; pub(crate) mod execution_state; pub(crate) mod felt; @@ -19,6 +20,7 @@ pub use blockifier::versioned_constants::VersionedConstants; pub use call::call; pub use class::{parse_casm_definition, parse_deprecated_class_definition}; pub use error::{CallError, TransactionExecutionError}; +pub use error_stack::{CallFrame, ErrorStack, Frame}; pub use estimate::estimate; pub use execution_state::{ ExecutionState, diff --git a/crates/executor/src/simulate.rs b/crates/executor/src/simulate.rs index 4cec1283d2..41e273d755 100644 --- a/crates/executor/src/simulate.rs +++ b/crates/executor/src/simulate.rs @@ -22,6 +22,7 @@ use pathfinder_common::{ use super::error::TransactionExecutionError; use super::execution_state::ExecutionState; use super::types::{FeeEstimate, TransactionSimulation, TransactionTrace}; +use crate::error_stack::ErrorStack; use crate::transaction::transaction_hash; use crate::types::{ DataAvailabilityResources, @@ -51,6 +52,7 @@ enum CacheItem { struct ExecutionError { transaction_index: usize, error: String, + error_stack: ErrorStack, } impl From for TransactionExecutionError { @@ -58,6 +60,7 @@ impl From for TransactionExecutionError { Self::ExecutionError { transaction_index: value.transaction_index, error: value.error, + error_stack: value.error_stack, } } } @@ -115,7 +118,8 @@ pub fn simulate( match tx_info { Ok(tx_info) => { if let Some(revert_error) = &tx_info.revert_error { - tracing::trace!(%revert_error, "Transaction reverted"); + let revert_string = revert_error.to_string(); + tracing::trace!(revert_error=%revert_string, "Transaction reverted"); } tracing::trace!(actual_fee=%tx_info.transaction_receipt.fee.0, actual_resources=?tx_info.transaction_receipt.resources, "Transaction simulation finished"); @@ -192,6 +196,7 @@ pub fn trace( let err = ExecutionError { transaction_index: transaction_idx, error: e.to_string(), + error_stack: e.into(), }; let mut cache = cache.0.lock().unwrap(); let _ = sender.send(Err(err.clone())); @@ -383,7 +388,7 @@ fn to_trace( TransactionType::Invoke => TransactionTrace::Invoke(InvokeTransactionTrace { validate_invocation, execute_invocation: if let Some(reason) = execution_info.revert_error { - ExecuteInvocation::RevertedReason(reason) + ExecuteInvocation::RevertedReason(reason.to_string()) } else { ExecuteInvocation::FunctionInvocation(maybe_function_invocation) }, diff --git a/crates/rpc/src/error.rs b/crates/rpc/src/error.rs index 9935bf748c..5e104958d8 100644 --- a/crates/rpc/src/error.rs +++ b/crates/rpc/src/error.rs @@ -83,6 +83,7 @@ pub enum ApplicationError { TransactionExecutionError { transaction_index: usize, error: String, + error_stack: pathfinder_executor::ErrorStack, }, #[error("Transaction hash not found in websocket subscription")] SubscriptionTransactionHashNotFound { @@ -154,7 +155,7 @@ impl ApplicationError { } } - pub fn data(&self) -> Option { + pub fn data(&self, version: RpcVersion) -> Option { // We purposefully don't use a catch-all branch to force us to update // here whenever a new variant is added. This will prevent adding a stateful // error variant but forgetting to forward its data. @@ -189,10 +190,20 @@ impl ApplicationError { ApplicationError::TransactionExecutionError { transaction_index, error, - } => Some(json!({ - "transaction_index": transaction_index, - "execution_error": error, - })), + error_stack, + } => match version { + RpcVersion::V08 => { + let error_stack = error_stack_frames_to_json(&error_stack.0); + Some(json!({ + "transaction_index": transaction_index, + "execution_error": error_stack, + })) + } + _ => Some(json!({ + "transaction_index": transaction_index, + "execution_error": error, + })), + }, ApplicationError::Internal(_) => None, ApplicationError::Custom(cause) => { let cause = cause.to_string(); @@ -236,6 +247,34 @@ impl ApplicationError { } } +fn error_stack_frames_to_json(frames: &[pathfinder_executor::Frame]) -> serde_json::Value { + let last_string_frame_contents = frames + .iter() + .rev() + .filter_map(|frame| match frame { + pathfinder_executor::Frame::StringFrame(string) => Some(string), + _ => None, + }) + .next() + .cloned() + .unwrap_or_else(|| "Unknown error, no string frame available.".to_string()); + + let call_frames = frames.iter().filter_map(|frame| match frame { + pathfinder_executor::Frame::CallFrame(call_frame) => Some(call_frame), + _ => None, + }); + call_frames + .rev() + .fold(json!(last_string_frame_contents), |child, frame| { + json!({ + "contract_address": frame.storage_address, + "class_hash": frame.class_hash, + "selector": frame.selector, + "error": child, + }) + }) +} + /// Generates an enum subset of [ApplicationError] along with boilerplate for /// mapping the variants back to [ApplicationError]. /// @@ -399,6 +438,8 @@ macro_rules! generate_rpc_error_subset { #[allow(dead_code, unused_imports)] pub(super) use generate_rpc_error_subset; +use crate::RpcVersion; + #[cfg(test)] mod tests { mod rpc_error_subset { @@ -432,4 +473,61 @@ mod tests { assert_matches!(no_blocks, ApplicationError::NoBlocks); } } + + mod error_stack { + use pathfinder_common::{class_hash, contract_address, entry_point}; + use pathfinder_executor::{CallFrame, Frame}; + use serde_json::json; + + use super::super::error_stack_frames_to_json; + + #[test] + fn json_representation() { + let frames = vec![ + // Top-level call + Frame::CallFrame(CallFrame { + storage_address: contract_address!("0xdeadbeef"), + class_hash: class_hash!("0xcaadd"), + selector: Some(entry_point!("0xeeeee")), + }), + // An interim string representation of the in-contract call trace + Frame::StringFrame( + "Error at pc=0:4273:\nCairo traceback (most recent call last):\nUnknown \ + location (pc=0:67)\nUnknown location (pc=0:1997)\nUnknown location \ + (pc=0:2727)\nUnknown location (pc=0:3582)\n" + .to_string(), + ), + // Another call + Frame::CallFrame(CallFrame { + storage_address: contract_address!("0x2222deadbeef"), + class_hash: class_hash!("0x2222caadd"), + selector: Some(entry_point!("0x2222eeeee")), + }), + // An interim string representation of the in-contract call trace + Frame::StringFrame( + "Error at pc=0:4273:\nCairo traceback (most recent call last):\nUnknown \ + location (pc=0:67)\nUnknown location (pc=0:1997)\nUnknown location \ + (pc=0:2727)\nUnknown location (pc=0:3582)\n" + .to_string(), + ), + // Final error + Frame::StringFrame("test".to_string()), + ]; + + assert_eq!( + error_stack_frames_to_json(&frames), + json!({ + "contract_address": "0xdeadbeef", + "class_hash": "0xcaadd", + "selector": "0xeeeee", + "error": json!({ + "contract_address": "0x2222deadbeef", + "class_hash": "0x2222caadd", + "selector": "0x2222eeeee", + "error": "test", + }), + }) + ); + } + } } diff --git a/crates/rpc/src/jsonrpc/error.rs b/crates/rpc/src/jsonrpc/error.rs index ed53dad2d0..074b40d127 100644 --- a/crates/rpc/src/jsonrpc/error.rs +++ b/crates/rpc/src/jsonrpc/error.rs @@ -3,6 +3,7 @@ use std::borrow::Cow; use serde_json::{json, Value}; use crate::dto::serialize; +use crate::RpcVersion; #[derive(Debug)] pub enum RpcError { @@ -53,7 +54,7 @@ impl RpcError { } } - pub fn data(&self) -> Option { + pub fn data(&self, version: RpcVersion) -> Option { match self { RpcError::WebsocketSubscriptionClosed { subscription_id, @@ -62,7 +63,7 @@ impl RpcError { "id": subscription_id, "reason": reason, })), - RpcError::ApplicationError(e) => e.data(), + RpcError::ApplicationError(e) => e.data(version), RpcError::InternalError(_) => None, RpcError::MethodNotFound => None, RpcError::ParseError(e) | RpcError::InvalidRequest(e) | RpcError::InvalidParams(e) => { @@ -83,7 +84,7 @@ impl serialize::SerializeForVersion for RpcError { obj.serialize_field("code", &self.code())?; obj.serialize_field("message", &self.message())?; - if let Some(data) = self.data() { + if let Some(data) = self.data(serializer.version) { obj.serialize_field("data", &data)?; } diff --git a/crates/rpc/src/jsonrpc/response.rs b/crates/rpc/src/jsonrpc/response.rs index 8efafef712..714d4475d9 100644 --- a/crates/rpc/src/jsonrpc/response.rs +++ b/crates/rpc/src/jsonrpc/response.rs @@ -157,7 +157,7 @@ mod tests { "error": { "code": parsing_err.code(), "message": parsing_err.message(), - "data": parsing_err.data(), + "data": parsing_err.data(RpcVersion::V07), }, "id": 1, }); diff --git a/crates/rpc/src/method/call.rs b/crates/rpc/src/method/call.rs index 4ac2eb9803..ccfc6524e5 100644 --- a/crates/rpc/src/method/call.rs +++ b/crates/rpc/src/method/call.rs @@ -26,7 +26,7 @@ impl From for CallError { match value { ContractNotFound => Self::ContractNotFound, InvalidMessageSelector => Self::Custom(anyhow::anyhow!("Invalid message selector")), - ContractError(error) => Self::ContractError { + ContractError(error, _) => Self::ContractError { revert_error: Some(format!("Execution error: {}", error)), }, Internal(e) => Self::Internal(e), diff --git a/crates/rpc/src/method/estimate_fee.rs b/crates/rpc/src/method/estimate_fee.rs index 02f3d53e2e..7d3b36e3ba 100644 --- a/crates/rpc/src/method/estimate_fee.rs +++ b/crates/rpc/src/method/estimate_fee.rs @@ -114,6 +114,7 @@ pub enum EstimateFeeError { TransactionExecutionError { transaction_index: usize, error: String, + error_stack: pathfinder_executor::ErrorStack, }, } @@ -130,9 +131,11 @@ impl From for EstimateFeeError { ExecutionError { transaction_index, error, + error_stack, } => Self::TransactionExecutionError { transaction_index, error, + error_stack, }, Internal(e) => Self::Internal(e), Custom(e) => Self::Custom(e), @@ -157,9 +160,11 @@ impl From for ApplicationError { EstimateFeeError::TransactionExecutionError { transaction_index, error, + error_stack, } => ApplicationError::TransactionExecutionError { transaction_index, error, + error_stack, }, EstimateFeeError::Internal(e) => ApplicationError::Internal(e), EstimateFeeError::Custom(e) => ApplicationError::Custom(e), diff --git a/crates/rpc/src/method/simulate_transactions.rs b/crates/rpc/src/method/simulate_transactions.rs index f24f4b159f..59088bfae2 100644 --- a/crates/rpc/src/method/simulate_transactions.rs +++ b/crates/rpc/src/method/simulate_transactions.rs @@ -118,6 +118,7 @@ pub enum SimulateTransactionError { TransactionExecutionError { transaction_index: usize, error: String, + error_stack: pathfinder_executor::ErrorStack, }, } @@ -136,9 +137,11 @@ impl From for crate::error::ApplicationError { SimulateTransactionError::TransactionExecutionError { transaction_index, error, + error_stack, } => Self::TransactionExecutionError { transaction_index, error, + error_stack, }, } } @@ -151,9 +154,11 @@ impl From for SimulateTransactionError { ExecutionError { transaction_index, error, + error_stack, } => Self::TransactionExecutionError { transaction_index, error, + error_stack, }, Internal(e) => Self::Internal(e), Custom(e) => Self::Custom(e), diff --git a/crates/rpc/src/method/trace_block_transactions.rs b/crates/rpc/src/method/trace_block_transactions.rs index 1faee27955..822732cafa 100644 --- a/crates/rpc/src/method/trace_block_transactions.rs +++ b/crates/rpc/src/method/trace_block_transactions.rs @@ -560,6 +560,7 @@ impl From for TraceBlockTransactionsError { ExecutionError { transaction_index, error, + error_stack: _, } => Self::Custom(anyhow::anyhow!( "Transaction execution failed at index {}: {}", transaction_index, diff --git a/crates/rpc/src/method/trace_transaction.rs b/crates/rpc/src/method/trace_transaction.rs index 3b721b2771..736fb6dc47 100644 --- a/crates/rpc/src/method/trace_transaction.rs +++ b/crates/rpc/src/method/trace_transaction.rs @@ -208,6 +208,7 @@ impl From for TraceTransactionError { ExecutionError { transaction_index, error, + error_stack: _, } => Self::Custom(anyhow::anyhow!( "Transaction execution failed at index {}: {}", transaction_index, diff --git a/crates/rpc/src/v06/method/call.rs b/crates/rpc/src/v06/method/call.rs index 1e2d0723b7..d161eaf38c 100644 --- a/crates/rpc/src/v06/method/call.rs +++ b/crates/rpc/src/v06/method/call.rs @@ -27,7 +27,7 @@ impl From for CallError { match value { ContractNotFound => Self::ContractNotFound, InvalidMessageSelector => Self::Custom(anyhow::anyhow!("Invalid message selector")), - ContractError(error) => Self::ContractError { + ContractError(error, _) => Self::ContractError { revert_error: format!("Execution error: {}", error), }, Internal(e) => Self::Internal(e), diff --git a/crates/rpc/src/v06/method/estimate_fee.rs b/crates/rpc/src/v06/method/estimate_fee.rs index fe694a9a94..d8ac41f8fb 100644 --- a/crates/rpc/src/v06/method/estimate_fee.rs +++ b/crates/rpc/src/v06/method/estimate_fee.rs @@ -55,6 +55,7 @@ impl From for EstimateFeeError { ExecutionError { transaction_index, error, + error_stack: _, } => Self::TransactionExecutionError { transaction_index, error, @@ -85,6 +86,7 @@ impl From for ApplicationError { } => ApplicationError::TransactionExecutionError { transaction_index, error, + error_stack: Default::default(), }, EstimateFeeError::Internal(e) => ApplicationError::Internal(e), EstimateFeeError::Custom(e) => ApplicationError::Custom(e), diff --git a/crates/rpc/src/v06/method/simulate_transactions.rs b/crates/rpc/src/v06/method/simulate_transactions.rs index bf7130bce3..1f01c8119b 100644 --- a/crates/rpc/src/v06/method/simulate_transactions.rs +++ b/crates/rpc/src/v06/method/simulate_transactions.rs @@ -56,6 +56,7 @@ impl From for crate::error::ApplicationError { } => Self::TransactionExecutionError { transaction_index, error, + error_stack: Default::default(), }, } } @@ -68,6 +69,7 @@ impl From for SimulateTransactionError { ExecutionError { transaction_index, error, + error_stack: _, } => Self::TransactionExecutionError { transaction_index, error, diff --git a/crates/rpc/src/v06/method/trace_block_transactions.rs b/crates/rpc/src/v06/method/trace_block_transactions.rs index 465f0a8550..37842492b8 100644 --- a/crates/rpc/src/v06/method/trace_block_transactions.rs +++ b/crates/rpc/src/v06/method/trace_block_transactions.rs @@ -83,6 +83,7 @@ impl From for TraceBlockTransactionsError { ExecutionError { transaction_index, error, + error_stack: _, } => Self::Custom(anyhow::anyhow!( "Transaction execution failed at index {}: {}", transaction_index, diff --git a/crates/rpc/src/v06/method/trace_transaction.rs b/crates/rpc/src/v06/method/trace_transaction.rs index 9c7b810ef9..8d27ad91ae 100644 --- a/crates/rpc/src/v06/method/trace_transaction.rs +++ b/crates/rpc/src/v06/method/trace_transaction.rs @@ -55,6 +55,7 @@ impl From for TraceTransactionError { ExecutionError { transaction_index, error, + error_stack: _, } => Self::Custom(anyhow::anyhow!( "Transaction execution failed at index {}: {}", transaction_index, From 6bf138de1694cfedae0fe222e07ca796532d7503 Mon Sep 17 00:00:00 2001 From: Krisztian Kovacs Date: Wed, 16 Oct 2024 15:28:36 +0200 Subject: [PATCH 179/282] feat(rpc/v08): add execution-related methods to the JSON-RPC 0.8.0 API --- crates/rpc/src/error.rs | 22 +++++++++++++++---- crates/rpc/src/lib.rs | 9 +------- crates/rpc/src/method/call.rs | 18 ++++++++++----- crates/rpc/src/method/estimate_message_fee.rs | 22 +++++++++++++------ crates/rpc/src/v06/method/call.rs | 14 +++++++++--- .../src/v06/method/estimate_message_fee.rs | 22 +++++++++++++------ crates/rpc/src/v08.rs | 6 +++++ 7 files changed, 79 insertions(+), 34 deletions(-) diff --git a/crates/rpc/src/error.rs b/crates/rpc/src/error.rs index 5e104958d8..44017e2b3c 100644 --- a/crates/rpc/src/error.rs +++ b/crates/rpc/src/error.rs @@ -44,7 +44,10 @@ pub enum ApplicationError { #[error("Too many keys provided in a filter")] TooManyKeysInFilter { limit: usize, requested: usize }, #[error("Contract error")] - ContractError { revert_error: Option }, + ContractError { + revert_error: Option, + revert_error_stack: pathfinder_executor::ErrorStack, + }, #[error("Invalid contract class")] InvalidContractClass, #[error("Class already declared")] @@ -218,9 +221,20 @@ impl ApplicationError { ApplicationError::NoTraceAvailable(error) => Some(json!({ "error": error, })), - ApplicationError::ContractError { revert_error } => Some(json!({ - "revert_error": revert_error - })), + ApplicationError::ContractError { + revert_error, + revert_error_stack, + } => match version { + RpcVersion::V08 => { + let revert_error_stack = error_stack_frames_to_json(&revert_error_stack.0); + Some(json!({ + "revert_error": revert_error_stack + })) + } + _ => Some(json!({ + "revert_error": revert_error + })), + }, ApplicationError::TooManyKeysInFilter { limit, requested } => Some(json!({ "limit": limit, "requested": requested, diff --git a/crates/rpc/src/lib.rs b/crates/rpc/src/lib.rs index 42cfc4f92e..a27cd73599 100644 --- a/crates/rpc/src/lib.rs +++ b/crates/rpc/src/lib.rs @@ -903,15 +903,8 @@ mod tests { "starknet_getBlockWithReceipts", "starknet_getMessagesStatus", "starknet_getTransactionReceipt", - "starknet_call", - "starknet_estimateFee", - "starknet_estimateMessageFee", - ])] - #[case::v0_8_trace("/rpc/v0_8", "v08/starknet_trace_api_openrpc.json", &[ - "starknet_traceTransaction", - "starknet_simulateTransactions", - "starknet_traceBlockTransactions" ])] + #[case::v0_8_trace("/rpc/v0_8", "v08/starknet_trace_api_openrpc.json", &[])] #[case::v0_8_write("/rpc/v0_8", "v08/starknet_write_api.json", &[ "starknet_addInvokeTransaction", "starknet_addDeclareTransaction", diff --git a/crates/rpc/src/method/call.rs b/crates/rpc/src/method/call.rs index ccfc6524e5..a8b9ac6da3 100644 --- a/crates/rpc/src/method/call.rs +++ b/crates/rpc/src/method/call.rs @@ -11,7 +11,10 @@ pub enum CallError { Custom(anyhow::Error), BlockNotFound, ContractNotFound, - ContractError { revert_error: Option }, + ContractError { + revert_error: Option, + revert_error_stack: pathfinder_executor::ErrorStack, + }, } impl From for CallError { @@ -26,8 +29,9 @@ impl From for CallError { match value { ContractNotFound => Self::ContractNotFound, InvalidMessageSelector => Self::Custom(anyhow::anyhow!("Invalid message selector")), - ContractError(error, _) => Self::ContractError { + ContractError(error, error_stack) => Self::ContractError { revert_error: Some(format!("Execution error: {}", error)), + revert_error_stack: error_stack, }, Internal(e) => Self::Internal(e), Custom(e) => Self::Custom(e), @@ -50,9 +54,13 @@ impl From for ApplicationError { match value { CallError::BlockNotFound => ApplicationError::BlockNotFound, CallError::ContractNotFound => ApplicationError::ContractNotFound, - CallError::ContractError { revert_error } => { - ApplicationError::ContractError { revert_error } - } + CallError::ContractError { + revert_error, + revert_error_stack, + } => ApplicationError::ContractError { + revert_error, + revert_error_stack, + }, CallError::Internal(e) => ApplicationError::Internal(e), CallError::Custom(e) => ApplicationError::Custom(e), } diff --git a/crates/rpc/src/method/estimate_message_fee.rs b/crates/rpc/src/method/estimate_message_fee.rs index 6156fb6274..1367463fe7 100644 --- a/crates/rpc/src/method/estimate_message_fee.rs +++ b/crates/rpc/src/method/estimate_message_fee.rs @@ -149,7 +149,10 @@ pub enum EstimateMessageFeeError { Internal(anyhow::Error), BlockNotFound, ContractNotFound, - ContractError { revert_error: String }, + ContractError { + revert_error: String, + revert_error_stack: pathfinder_executor::ErrorStack, + }, Custom(anyhow::Error), } @@ -163,8 +166,11 @@ impl From for EstimateMessageFee fn from(c: pathfinder_executor::TransactionExecutionError) -> Self { use pathfinder_executor::TransactionExecutionError::*; match c { - ExecutionError { error, .. } => Self::ContractError { + ExecutionError { + error, error_stack, .. + } => Self::ContractError { revert_error: format!("Execution error: {}", error), + revert_error_stack: error_stack, }, Internal(e) => Self::Internal(e), Custom(e) => Self::Custom(e), @@ -187,11 +193,13 @@ impl From for ApplicationError { match value { EstimateMessageFeeError::BlockNotFound => ApplicationError::BlockNotFound, EstimateMessageFeeError::ContractNotFound => ApplicationError::ContractNotFound, - EstimateMessageFeeError::ContractError { revert_error } => { - ApplicationError::ContractError { - revert_error: Some(revert_error), - } - } + EstimateMessageFeeError::ContractError { + revert_error, + revert_error_stack, + } => ApplicationError::ContractError { + revert_error: Some(revert_error), + revert_error_stack, + }, EstimateMessageFeeError::Internal(e) => ApplicationError::Internal(e), EstimateMessageFeeError::Custom(e) => ApplicationError::Custom(e), } diff --git a/crates/rpc/src/v06/method/call.rs b/crates/rpc/src/v06/method/call.rs index d161eaf38c..6a8c39bf1e 100644 --- a/crates/rpc/src/v06/method/call.rs +++ b/crates/rpc/src/v06/method/call.rs @@ -12,7 +12,10 @@ pub enum CallError { Custom(anyhow::Error), BlockNotFound, ContractNotFound, - ContractError { revert_error: String }, + ContractError { + revert_error: String, + revert_error_stack: pathfinder_executor::ErrorStack, + }, } impl From for CallError { @@ -27,8 +30,9 @@ impl From for CallError { match value { ContractNotFound => Self::ContractNotFound, InvalidMessageSelector => Self::Custom(anyhow::anyhow!("Invalid message selector")), - ContractError(error, _) => Self::ContractError { + ContractError(error, error_stack) => Self::ContractError { revert_error: format!("Execution error: {}", error), + revert_error_stack: error_stack, }, Internal(e) => Self::Internal(e), Custom(e) => Self::Custom(e), @@ -51,8 +55,12 @@ impl From for ApplicationError { match value { CallError::BlockNotFound => ApplicationError::BlockNotFound, CallError::ContractNotFound => ApplicationError::ContractNotFound, - CallError::ContractError { revert_error } => ApplicationError::ContractError { + CallError::ContractError { + revert_error, + revert_error_stack, + } => ApplicationError::ContractError { revert_error: Some(revert_error), + revert_error_stack, }, CallError::Internal(e) => ApplicationError::Internal(e), CallError::Custom(e) => ApplicationError::Custom(e), diff --git a/crates/rpc/src/v06/method/estimate_message_fee.rs b/crates/rpc/src/v06/method/estimate_message_fee.rs index c93d4be304..830324c26a 100644 --- a/crates/rpc/src/v06/method/estimate_message_fee.rs +++ b/crates/rpc/src/v06/method/estimate_message_fee.rs @@ -23,7 +23,10 @@ pub enum EstimateMessageFeeError { Internal(anyhow::Error), BlockNotFound, ContractNotFound, - ContractError { revert_error: String }, + ContractError { + revert_error: String, + revert_error_stack: pathfinder_executor::ErrorStack, + }, Custom(anyhow::Error), } @@ -37,8 +40,11 @@ impl From for EstimateMessageFee fn from(c: pathfinder_executor::TransactionExecutionError) -> Self { use pathfinder_executor::TransactionExecutionError::*; match c { - ExecutionError { error, .. } => Self::ContractError { + ExecutionError { + error, error_stack, .. + } => Self::ContractError { revert_error: format!("Execution error: {}", error), + revert_error_stack: error_stack, }, Internal(e) => Self::Internal(e), Custom(e) => Self::Custom(e), @@ -61,11 +67,13 @@ impl From for ApplicationError { match value { EstimateMessageFeeError::BlockNotFound => ApplicationError::BlockNotFound, EstimateMessageFeeError::ContractNotFound => ApplicationError::ContractNotFound, - EstimateMessageFeeError::ContractError { revert_error } => { - ApplicationError::ContractError { - revert_error: Some(revert_error), - } - } + EstimateMessageFeeError::ContractError { + revert_error, + revert_error_stack, + } => ApplicationError::ContractError { + revert_error: Some(revert_error), + revert_error_stack, + }, EstimateMessageFeeError::Internal(e) => ApplicationError::Internal(e), EstimateMessageFeeError::Custom(e) => ApplicationError::Custom(e), } diff --git a/crates/rpc/src/v08.rs b/crates/rpc/src/v08.rs index ac69b32be6..fc48c3986b 100644 --- a/crates/rpc/src/v08.rs +++ b/crates/rpc/src/v08.rs @@ -8,7 +8,10 @@ pub fn register_routes() -> RpcRouterBuilder { RpcRouter::builder(crate::RpcVersion::V08) .register("starknet_blockHashAndNumber", crate::method::block_hash_and_number) .register("starknet_blockNumber", crate::method::block_number) + .register("starknet_call", crate::method::call) .register("starknet_chainId", crate::method::chain_id) + .register("starknet_estimateFee", crate::method::estimate_fee) + .register("starknet_estimateMessageFee", crate::method::estimate_fee) .register("starknet_getBlockTransactionCount", crate::method::get_block_transaction_count) .register("starknet_getBlockWithTxHashes", crate::method::get_block_with_tx_hashes) .register("starknet_getBlockWithTxs", crate::method::get_block_with_txs) @@ -23,11 +26,14 @@ pub fn register_routes() -> RpcRouterBuilder { .register("starknet_getTransactionByBlockIdAndIndex", crate::method::get_transaction_by_block_id_and_index) .register("starknet_getTransactionByHash", crate::method::get_transaction_by_hash) .register("starknet_getTransactionStatus", crate::method::get_transaction_status) + .register("starknet_simulateTransactions", crate::method::simulate_transactions) .register("starknet_subscribeNewHeads", SubscribeNewHeads) .register("starknet_subscribePendingTransactions", SubscribePendingTransactions) .register("starknet_subscribeEvents", SubscribeEvents) .register("starknet_specVersion", || "0.8.0-rc0") .register("starknet_syncing", crate::method::syncing) + .register("starknet_traceBlockTransactions", crate::method::trace_block_transactions) + .register("starknet_traceTransaction", crate::method::trace_transaction) .register("pathfinder_getProof", crate::pathfinder::methods::get_proof) } From 3fcdf3185cc415c6260859b11d16c59f2f0c4a26 Mon Sep 17 00:00:00 2001 From: Krisztian Kovacs Date: Thu, 17 Oct 2024 16:38:12 +0200 Subject: [PATCH 180/282] chore(rpc): `starknet_traceTransaction` cannot actually return CONTRACT_ERROR The enum variant was unused so we can just safely remove that. --- crates/rpc/src/method/trace_transaction.rs | 6 ------ crates/rpc/src/v06/method/trace_transaction.rs | 6 ------ 2 files changed, 12 deletions(-) diff --git a/crates/rpc/src/method/trace_transaction.rs b/crates/rpc/src/method/trace_transaction.rs index 736fb6dc47..d57fd0136a 100644 --- a/crates/rpc/src/method/trace_transaction.rs +++ b/crates/rpc/src/method/trace_transaction.rs @@ -189,7 +189,6 @@ pub enum TraceTransactionError { Custom(anyhow::Error), TxnHashNotFound, NoTraceAvailable(TraceError), - ContractError { revert_error: String }, } impl From for TraceTransactionError { @@ -244,11 +243,6 @@ impl From for ApplicationError { TraceTransactionError::NoTraceAvailable(status) => { ApplicationError::NoTraceAvailable(status) } - TraceTransactionError::ContractError { revert_error } => { - ApplicationError::ContractError { - revert_error: Some(revert_error), - } - } TraceTransactionError::Internal(e) => ApplicationError::Internal(e), TraceTransactionError::Custom(e) => ApplicationError::Custom(e), } diff --git a/crates/rpc/src/v06/method/trace_transaction.rs b/crates/rpc/src/v06/method/trace_transaction.rs index 8d27ad91ae..683a777075 100644 --- a/crates/rpc/src/v06/method/trace_transaction.rs +++ b/crates/rpc/src/v06/method/trace_transaction.rs @@ -36,7 +36,6 @@ pub enum TraceTransactionError { Custom(anyhow::Error), TxnHashNotFound, NoTraceAvailable(TraceError), - ContractError { revert_error: String }, } impl From for TraceTransactionError { @@ -97,11 +96,6 @@ impl From for ApplicationError { TraceTransactionError::NoTraceAvailable(status) => { ApplicationError::NoTraceAvailable(status) } - TraceTransactionError::ContractError { revert_error } => { - ApplicationError::ContractError { - revert_error: Some(revert_error), - } - } TraceTransactionError::Internal(e) => ApplicationError::Internal(e), TraceTransactionError::Custom(e) => ApplicationError::Custom(e), } From f3a9b275abc988da45c698d08227f754e3625b1a Mon Sep 17 00:00:00 2001 From: Krisztian Kovacs Date: Fri, 18 Oct 2024 14:11:47 +0200 Subject: [PATCH 181/282] chore(ci): upgrade typos to 1.26.0 --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6f0e7c4b42..abef173062 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -121,7 +121,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: crate-ci/typos@v1.16.23 + - uses: crate-ci/typos@v1.26.0 with: files: . From 2a67de02917b6f07b9b1e8d1184fd5a4587eca1f Mon Sep 17 00:00:00 2001 From: Krisztian Kovacs Date: Fri, 18 Oct 2024 14:12:07 +0200 Subject: [PATCH 182/282] chore: fix all typos That typos 1.26.0 finds and fixes. --- crates/common/src/error.rs | 2 +- crates/common/src/header.rs | 4 ++-- doc/rpc/v03/starknet_api_openrpc.json | 2 +- doc/rpc/v03/starknet_trace_api_openrpc.json | 2 +- doc/rpc/v04/starknet_api_openrpc.json | 2 +- doc/rpc/v04/starknet_trace_api_openrpc.json | 2 +- doc/rpc/v06/starknet_api_openrpc.json | 4 ++-- doc/rpc/v06/starknet_trace_api_openrpc.json | 2 +- doc/rpc/v07/starknet_api_openrpc.json | 4 ++-- doc/rpc/v07/starknet_trace_api_openrpc.json | 2 +- doc/rpc/v08/starknet_api_openrpc.json | 4 ++-- doc/rpc/v08/starknet_trace_api_openrpc.json | 2 +- 12 files changed, 16 insertions(+), 16 deletions(-) diff --git a/crates/common/src/error.rs b/crates/common/src/error.rs index 58eb9133fe..2f22cb8549 100644 --- a/crates/common/src/error.rs +++ b/crates/common/src/error.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -/// This trait is a workaround for [anyhow::Error] not being clonable. +/// This trait is a workaround for [anyhow::Error] not being cloneable. /// /// Most of the time you can use `Arc` instead of `anyhow::Error` /// to circumvent `anyhow::Error` not being `Clone`. However, in some cases, you diff --git a/crates/common/src/header.rs b/crates/common/src/header.rs index 6c02559aa1..1030de33ce 100644 --- a/crates/common/src/header.rs +++ b/crates/common/src/header.rs @@ -81,8 +81,8 @@ impl BlockHeaderBuilder { self } - pub fn state_commitment(mut self, state_commmitment: StateCommitment) -> Self { - self.0.state_commitment = state_commmitment; + pub fn state_commitment(mut self, state_commitment: StateCommitment) -> Self { + self.0.state_commitment = state_commitment; self } diff --git a/doc/rpc/v03/starknet_api_openrpc.json b/doc/rpc/v03/starknet_api_openrpc.json index 8f94d84dc4..fec950922d 100644 --- a/doc/rpc/v03/starknet_api_openrpc.json +++ b/doc/rpc/v03/starknet_api_openrpc.json @@ -518,7 +518,7 @@ "schema": { "title": "Estimation", "type": "array", - "description": "a sequence of fee estimatione where the i'th estimate corresponds to the i'th transaction", + "description": "a sequence of fee estimation where the i'th estimate corresponds to the i'th transaction", "items": { "$ref": "#/components/schemas/FEE_ESTIMATE" } diff --git a/doc/rpc/v03/starknet_trace_api_openrpc.json b/doc/rpc/v03/starknet_trace_api_openrpc.json index a916c08a12..be68f75dc1 100644 --- a/doc/rpc/v03/starknet_trace_api_openrpc.json +++ b/doc/rpc/v03/starknet_trace_api_openrpc.json @@ -75,7 +75,7 @@ ], "result": { "name": "simulated_transactions", - "description": "The execution trace and consuemd resources of the required transactions", + "description": "The execution trace and consumed resources of the required transactions", "schema": { "type": "array", "items": { diff --git a/doc/rpc/v04/starknet_api_openrpc.json b/doc/rpc/v04/starknet_api_openrpc.json index 64684ce54b..dc9a9cbee1 100644 --- a/doc/rpc/v04/starknet_api_openrpc.json +++ b/doc/rpc/v04/starknet_api_openrpc.json @@ -563,7 +563,7 @@ "schema": { "title": "Estimation", "type": "array", - "description": "a sequence of fee estimatione where the i'th estimate corresponds to the i'th transaction", + "description": "a sequence of fee estimation where the i'th estimate corresponds to the i'th transaction", "items": { "$ref": "#/components/schemas/FEE_ESTIMATE" } diff --git a/doc/rpc/v04/starknet_trace_api_openrpc.json b/doc/rpc/v04/starknet_trace_api_openrpc.json index 6e509475ad..a4c8532ef5 100644 --- a/doc/rpc/v04/starknet_trace_api_openrpc.json +++ b/doc/rpc/v04/starknet_trace_api_openrpc.json @@ -88,7 +88,7 @@ ], "result": { "name": "simulated_transactions", - "description": "The execution trace and consuemd resources of the required transactions", + "description": "The execution trace and consumed resources of the required transactions", "schema": { "type": "array", "items": { diff --git a/doc/rpc/v06/starknet_api_openrpc.json b/doc/rpc/v06/starknet_api_openrpc.json index fbbc89e6b4..70339edc92 100644 --- a/doc/rpc/v06/starknet_api_openrpc.json +++ b/doc/rpc/v06/starknet_api_openrpc.json @@ -624,7 +624,7 @@ "schema": { "title": "Estimation", "type": "array", - "description": "a sequence of fee estimatione where the i'th estimate corresponds to the i'th transaction", + "description": "a sequence of fee estimation where the i'th estimate corresponds to the i'th transaction", "items": { "$ref": "#/components/schemas/FEE_ESTIMATE" } @@ -3705,7 +3705,7 @@ "DA_MODE": { "title": "DA mode", "type": "string", - "description": "Specifies a storage domain in Starknet. Each domain has different gurantess regarding availability", + "description": "Specifies a storage domain in Starknet. Each domain has different guarantees regarding availability", "enum": [ "L1", "L2" diff --git a/doc/rpc/v06/starknet_trace_api_openrpc.json b/doc/rpc/v06/starknet_trace_api_openrpc.json index 06ae5baea0..7a269b0ddf 100644 --- a/doc/rpc/v06/starknet_trace_api_openrpc.json +++ b/doc/rpc/v06/starknet_trace_api_openrpc.json @@ -75,7 +75,7 @@ ], "result": { "name": "simulated_transactions", - "description": "The execution trace and consuemd resources of the required transactions", + "description": "The execution trace and consumed resources of the required transactions", "schema": { "type": "array", "items": { diff --git a/doc/rpc/v07/starknet_api_openrpc.json b/doc/rpc/v07/starknet_api_openrpc.json index 794890bed4..2187cf5c04 100644 --- a/doc/rpc/v07/starknet_api_openrpc.json +++ b/doc/rpc/v07/starknet_api_openrpc.json @@ -653,7 +653,7 @@ "schema": { "title": "Estimation", "type": "array", - "description": "a sequence of fee estimatione where the i'th estimate corresponds to the i'th transaction", + "description": "a sequence of fee estimation where the i'th estimate corresponds to the i'th transaction", "items": { "$ref": "#/components/schemas/FEE_ESTIMATE" } @@ -3710,7 +3710,7 @@ "DA_MODE": { "title": "DA mode", "type": "string", - "description": "Specifies a storage domain in Starknet. Each domain has different gurantess regarding availability", + "description": "Specifies a storage domain in Starknet. Each domain has different guarantees regarding availability", "enum": [ "L1", "L2" diff --git a/doc/rpc/v07/starknet_trace_api_openrpc.json b/doc/rpc/v07/starknet_trace_api_openrpc.json index 39eba0b19d..06ba8c4cf9 100644 --- a/doc/rpc/v07/starknet_trace_api_openrpc.json +++ b/doc/rpc/v07/starknet_trace_api_openrpc.json @@ -75,7 +75,7 @@ ], "result": { "name": "simulated_transactions", - "description": "The execution trace and consuemd resources of the required transactions", + "description": "The execution trace and consumed resources of the required transactions", "schema": { "type": "array", "items": { diff --git a/doc/rpc/v08/starknet_api_openrpc.json b/doc/rpc/v08/starknet_api_openrpc.json index e9c0a904f4..7f6d17767b 100644 --- a/doc/rpc/v08/starknet_api_openrpc.json +++ b/doc/rpc/v08/starknet_api_openrpc.json @@ -681,7 +681,7 @@ "schema": { "title": "Estimation", "type": "array", - "description": "a sequence of fee estimatione where the i'th estimate corresponds to the i'th transaction", + "description": "a sequence of fee estimation where the i'th estimate corresponds to the i'th transaction", "items": { "$ref": "#/components/schemas/FEE_ESTIMATE" } @@ -3556,7 +3556,7 @@ "DA_MODE": { "title": "DA mode", "type": "string", - "description": "Specifies a storage domain in Starknet. Each domain has different gurantess regarding availability", + "description": "Specifies a storage domain in Starknet. Each domain has different guarantees regarding availability", "enum": ["L1", "L2"] }, "RESOURCE_BOUNDS_MAPPING": { diff --git a/doc/rpc/v08/starknet_trace_api_openrpc.json b/doc/rpc/v08/starknet_trace_api_openrpc.json index 19749a856d..1b4bba3a7e 100644 --- a/doc/rpc/v08/starknet_trace_api_openrpc.json +++ b/doc/rpc/v08/starknet_trace_api_openrpc.json @@ -75,7 +75,7 @@ ], "result": { "name": "simulated_transactions", - "description": "The execution trace and consuemd resources of the required transactions", + "description": "The execution trace and consumed resources of the required transactions", "schema": { "type": "array", "items": { From bf6c8b29c89862f400c86194b4ad5222c2a8992c Mon Sep 17 00:00:00 2001 From: Krisztian Kovacs Date: Fri, 18 Oct 2024 14:57:17 +0200 Subject: [PATCH 183/282] feat(executor/types): add inner call execution resources to FunctionInvocation --- crates/executor/src/types.rs | 12 ++++++++++++ crates/rpc/src/method/trace_block_transactions.rs | 6 ++++++ 2 files changed, 18 insertions(+) diff --git a/crates/executor/src/types.rs b/crates/executor/src/types.rs index 2862a974fb..13a9dc8690 100644 --- a/crates/executor/src/types.rs +++ b/crates/executor/src/types.rs @@ -202,6 +202,7 @@ pub struct FunctionInvocation { pub messages: Vec, pub result: Vec, pub computation_resources: ComputationResources, + pub execution_resources: InnerCallExecutionResources, } #[derive(Debug, Clone, Eq, PartialEq)] @@ -212,6 +213,12 @@ pub struct MsgToL1 { pub from_address: Felt, } +#[derive(Debug, Clone)] +pub struct InnerCallExecutionResources { + pub l1_gas: u128, + pub l2_gas: u128, +} + #[derive(Debug, Default, Clone, Eq, PartialEq)] pub struct StateDiff { pub storage_diffs: BTreeMap>, @@ -343,6 +350,11 @@ impl From for FunctionInvocation { messages, result, computation_resources: call_info.resources.into(), + execution_resources: InnerCallExecutionResources { + l1_gas: call_info.execution.gas_consumed.into(), + // TODO: Use proper l2_gas value for Starknet 0.13.3 + l2_gas: 0, + }, } } } diff --git a/crates/rpc/src/method/trace_block_transactions.rs b/crates/rpc/src/method/trace_block_transactions.rs index 822732cafa..6b95273865 100644 --- a/crates/rpc/src/method/trace_block_transactions.rs +++ b/crates/rpc/src/method/trace_block_transactions.rs @@ -1,5 +1,6 @@ use anyhow::Context; use pathfinder_common::BlockId; +use pathfinder_executor::types::InnerCallExecutionResources; use pathfinder_executor::TransactionExecutionError; use starknet_gateway_client::GatewayApi; @@ -426,6 +427,11 @@ fn map_gateway_function_invocation( .collect(), result: invocation.result, computation_resources: map_gateway_computation_resources(invocation.execution_resources), + execution_resources: InnerCallExecutionResources { + l1_gas: invocation.execution_resources.total_gas_consumed.map(|gas| gas.l1_gas).unwrap_or_default(), + // TODO: Use proper l1_gas value for Starknet 0.13.3 + l2_gas: 0, + } }) } From aeafce37af8b5fff65a11cdd49f52ad2ea0ab1e2 Mon Sep 17 00:00:00 2001 From: Krisztian Kovacs Date: Mon, 21 Oct 2024 15:49:13 +0200 Subject: [PATCH 184/282] fix(rpc/v08): follow-up spec change for inner call execution resources This PR implements https://github.com/starkware-libs/starknet-specs/pull/233 so that we don't expose compute resources for inner calls only L1 and L2 gas. --- crates/rpc/src/dto/simulation.rs | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/crates/rpc/src/dto/simulation.rs b/crates/rpc/src/dto/simulation.rs index 890fc42542..8f3c91740f 100644 --- a/crates/rpc/src/dto/simulation.rs +++ b/crates/rpc/src/dto/simulation.rs @@ -3,6 +3,7 @@ use pathfinder_common::{ContractAddress, ContractNonce}; use serde::ser::Error; use super::serialize::SerializeStruct; +use crate::RpcVersion; #[derive(Debug)] pub struct TransactionTrace<'a> { @@ -176,10 +177,16 @@ impl crate::dto::serialize::SerializeForVersion for FunctionInvocation<'_> { self.0.result.len(), &mut self.0.result.iter().map(crate::dto::Felt), )?; - serializer.serialize_field( - "execution_resources", - &ComputationResources(&self.0.computation_resources), - )?; + match serializer.version { + RpcVersion::V08 => serializer.serialize_field( + "execution_resources", + &InnerCallExecutionResources(&self.0.execution_resources), + )?, + _ => serializer.serialize_field( + "execution_resources", + &ComputationResources(&self.0.computation_resources), + )?, + } serializer.end() } } @@ -288,6 +295,20 @@ impl crate::dto::serialize::SerializeForVersion for ComputationResources<'_> { } } +struct InnerCallExecutionResources<'a>(&'a pathfinder_executor::types::InnerCallExecutionResources); + +impl crate::dto::serialize::SerializeForVersion for InnerCallExecutionResources<'_> { + fn serialize( + &self, + serializer: super::serialize::Serializer, + ) -> Result { + let mut serializer = serializer.serialize_struct()?; + serializer.serialize_field("l1_gas", &self.0.l1_gas)?; + serializer.serialize_field("l2_gas", &self.0.l2_gas)?; + serializer.end() + } +} + struct StateDiff<'a>(&'a pathfinder_executor::types::StateDiff); impl crate::dto::serialize::SerializeForVersion for StateDiff<'_> { From b2a9d1ef137ab7ad3d36da0bac35ba6964a95784 Mon Sep 17 00:00:00 2001 From: Krisztian Kovacs Date: Fri, 18 Oct 2024 13:51:58 +0200 Subject: [PATCH 185/282] chore(doc/rpc/v08): update OpenRPC specifications From latest specifications available in the `starknet-specs` repo. --- doc/rpc/v08/starknet_api_openrpc.json | 265 +++++++++++++------- doc/rpc/v08/starknet_trace_api_openrpc.json | 27 +- 2 files changed, 192 insertions(+), 100 deletions(-) diff --git a/doc/rpc/v08/starknet_api_openrpc.json b/doc/rpc/v08/starknet_api_openrpc.json index 7f6d17767b..0762a5e62f 100644 --- a/doc/rpc/v08/starknet_api_openrpc.json +++ b/doc/rpc/v08/starknet_api_openrpc.json @@ -908,58 +908,68 @@ }, { "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", + "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"] } - } } - } } - } ], "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": { @@ -967,16 +977,69 @@ "$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": { + "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", + "global_roots" + ] + }, + "errors": [ + { + "$ref": "#/components/errors/BLOCK_NOT_FOUND" + }, + { + "$ref": "#/components/errors/STORAGE_PROOF_NOT_SUPPORTED" } - } + ] } } ], @@ -3098,26 +3161,23 @@ "TXN_STATUS_RESULT": { "title": "Transaction status result", "description": "Transaction status result, including finality status and execution status", - "schema": { - "title": "Transaction status result", - "type": "object", - "properties": { - "finality_status": { - "title": "finality status", - "$ref": "#/components/schemas/TXN_STATUS" - }, - "execution_status": { - "title": "execution status", - "$ref": "#/components/schemas/TXN_EXECUTION_STATUS" - }, - "failure_reason": { - "title": "failure reason", - "description": "the failure reason, only appears if finality_status is REJECTED or execution_status is REVERTED", - "type": "string" - } + "type": "object", + "properties": { + "finality_status": { + "title": "finality status", + "$ref": "#/components/schemas/TXN_STATUS" }, - "required": ["finality_status"] - } + "execution_status": { + "title": "execution status", + "$ref": "#/components/schemas/TXN_EXECUTION_STATUS" + }, + "failure_reason": { + "title": "failure reason", + "description": "the failure reason, only appears if finality_status is REJECTED or execution_status is REVERTED", + "type": "string" + } + }, + "required": ["finality_status"] }, "TXN_STATUS": { "title": "Transaction status", @@ -3627,45 +3687,57 @@ "description": "l2 gas consumed by this transaction, used for computation and calldata", "type": "integer" } - } + }, + "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": { + "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", @@ -3704,7 +3776,8 @@ "error": { "$ref": "#/components/schemas/CONTRACT_EXECUTION_ERROR" } - } + }, + "required": ["contract_address", "class_hash", "selector", "error"] }, { "title": "error message", @@ -3791,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" } } } diff --git a/doc/rpc/v08/starknet_trace_api_openrpc.json b/doc/rpc/v08/starknet_trace_api_openrpc.json index 1b4bba3a7e..45ba0a3f75 100644 --- a/doc/rpc/v08/starknet_trace_api_openrpc.json +++ b/doc/rpc/v08/starknet_trace_api_openrpc.json @@ -353,9 +353,9 @@ } }, "execution_resources": { - "title": "Computation resources", - "description": "Resources consumed by the internal call. This is named execution_resources for legacy reasons", - "$ref": "#/components/schemas/COMPUTATION_RESOURCES" + "title": "Execution resources", + "description": "Resources consumed by the internal call", + "$ref": "#/components/schemas/INNER_CALL_EXECUTION_RESOURCES" } }, "required": [ @@ -420,6 +420,24 @@ } ] }, + "INNER_CALL_EXECUTION_RESOURCES": { + "type": "object", + "title": "Execution resources", + "description": "the resources consumed by an inner call (does not account for state diffs since data is squashed across the transaction)", + "properties": { + "l1_gas": { + "title": "L1Gas", + "description": "l1 gas consumed by this transaction, used for l2-->l1 messages and state updates if blobs are not used", + "type": "integer" + }, + "l2_gas": { + "title": "L2Gas", + "description": "l2 gas consumed by this transaction, used for computation and calldata", + "type": "integer" + } + }, + "required": ["l1_gas", "l2_gas"] + }, "FELT": { "$ref": "./api/starknet_api_openrpc.json#/components/schemas/FELT" }, @@ -444,9 +462,6 @@ "STATE_DIFF": { "$ref": "./api/starknet_api_openrpc.json#/components/schemas/STATE_DIFF" }, - "COMPUTATION_RESOURCES": { - "$ref": "./api/starknet_api_openrpc.json#/components/schemas/COMPUTATION_RESOURCES" - }, "EXECUTION_RESOURCES": { "$ref": "./api/starknet_api_openrpc.json#/components/schemas/EXECUTION_RESOURCES" } From c0c12ce5df42fea247ecd5ec290e32d5c878e322 Mon Sep 17 00:00:00 2001 From: Krisztian Kovacs Date: Mon, 21 Oct 2024 15:57:09 +0200 Subject: [PATCH 186/282] chore(doc/rpc/v08): fix typos --- doc/rpc/v08/starknet_api_openrpc.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/rpc/v08/starknet_api_openrpc.json b/doc/rpc/v08/starknet_api_openrpc.json index 0762a5e62f..6f1d5e49ff 100644 --- a/doc/rpc/v08/starknet_api_openrpc.json +++ b/doc/rpc/v08/starknet_api_openrpc.json @@ -969,7 +969,7 @@ ], "result": { "name": "result", - "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", + "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 effectively proving non-membership", "schema": { "type": "object", "properties": { From 0c94d1d5ed97c57c5534691daac1e1db7e288790 Mon Sep 17 00:00:00 2001 From: Krisztian Kovacs Date: Mon, 21 Oct 2024 15:59:58 +0200 Subject: [PATCH 187/282] fixup! feat(executor/types): add inner call execution resources to FunctionInvocation --- crates/rpc/src/method/trace_block_transactions.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/crates/rpc/src/method/trace_block_transactions.rs b/crates/rpc/src/method/trace_block_transactions.rs index 6b95273865..6c1b1bdb82 100644 --- a/crates/rpc/src/method/trace_block_transactions.rs +++ b/crates/rpc/src/method/trace_block_transactions.rs @@ -428,10 +428,14 @@ fn map_gateway_function_invocation( result: invocation.result, computation_resources: map_gateway_computation_resources(invocation.execution_resources), execution_resources: InnerCallExecutionResources { - l1_gas: invocation.execution_resources.total_gas_consumed.map(|gas| gas.l1_gas).unwrap_or_default(), + l1_gas: invocation + .execution_resources + .total_gas_consumed + .map(|gas| gas.l1_gas) + .unwrap_or_default(), // TODO: Use proper l1_gas value for Starknet 0.13.3 l2_gas: 0, - } + }, }) } From c45119095794767e6031463c7b56fa48903cef1e Mon Sep 17 00:00:00 2001 From: Krisztian Kovacs Date: Tue, 22 Oct 2024 10:49:08 +0200 Subject: [PATCH 188/282] fix(rpc/v08): rename INSUFFICIENT_MAX_FEE error To INSUFFICIENT_RESOURCES_FOR_VALIDATE and change the message we return to comply with the JSON-RPC 0.8.0 specification. --- crates/rpc/src/error.rs | 21 +++++++++++++++---- crates/rpc/src/jsonrpc/error.rs | 6 +++--- crates/rpc/src/jsonrpc/response.rs | 2 +- .../rpc/src/method/add_declare_transaction.rs | 8 ++++--- .../method/add_deploy_account_transaction.rs | 6 +++--- .../rpc/src/method/add_invoke_transaction.rs | 8 ++++--- .../src/v06/method/add_declare_transaction.rs | 8 ++++--- .../method/add_deploy_account_transaction.rs | 6 +++--- .../src/v06/method/add_invoke_transaction.rs | 8 ++++--- 9 files changed, 47 insertions(+), 26 deletions(-) diff --git a/crates/rpc/src/error.rs b/crates/rpc/src/error.rs index 44017e2b3c..3976d5f682 100644 --- a/crates/rpc/src/error.rs +++ b/crates/rpc/src/error.rs @@ -54,8 +54,8 @@ pub enum ApplicationError { ClassAlreadyDeclared, #[error("Invalid transaction nonce")] InvalidTransactionNonce, - #[error("Max fee is smaller than the minimal transaction cost (validation plus fee transfer)")] - InsufficientMaxFee, + #[error("The transaction's resources don't cover validation or the minimal transaction fee")] + InsufficientResourcesForValidate, #[error("Account balance is smaller than the transaction's max_fee")] InsufficientAccountBalance, #[error("Account validation failed")] @@ -135,7 +135,7 @@ impl ApplicationError { ApplicationError::InvalidContractClass => 50, ApplicationError::ClassAlreadyDeclared => 51, ApplicationError::InvalidTransactionNonce => 52, - ApplicationError::InsufficientMaxFee => 53, + ApplicationError::InsufficientResourcesForValidate => 53, ApplicationError::InsufficientAccountBalance => 54, ApplicationError::ValidationFailure | ApplicationError::ValidationFailureV06(_) => 55, ApplicationError::CompilationFailed => 56, @@ -158,6 +158,19 @@ impl ApplicationError { } } + pub fn message(&self, version: RpcVersion) -> String { + match self { + ApplicationError::InsufficientResourcesForValidate => match version { + RpcVersion::V06 | RpcVersion::V07 => "Max fee is smaller than the minimal \ + transaction cost (validation plus fee \ + transfer)" + .to_string(), + _ => self.to_string(), + }, + _ => self.to_string(), + } + } + pub fn data(&self, version: RpcVersion) -> Option { // We purposefully don't use a catch-all branch to force us to update // here whenever a new variant is added. This will prevent adding a stateful @@ -177,7 +190,7 @@ impl ApplicationError { ApplicationError::InvalidContractClass => None, ApplicationError::ClassAlreadyDeclared => None, ApplicationError::InvalidTransactionNonce => None, - ApplicationError::InsufficientMaxFee => None, + ApplicationError::InsufficientResourcesForValidate => None, ApplicationError::InsufficientAccountBalance => None, ApplicationError::ValidationFailure => None, ApplicationError::CompilationFailed => None, diff --git a/crates/rpc/src/jsonrpc/error.rs b/crates/rpc/src/jsonrpc/error.rs index 074b40d127..f6a45e6e1b 100644 --- a/crates/rpc/src/jsonrpc/error.rs +++ b/crates/rpc/src/jsonrpc/error.rs @@ -42,14 +42,14 @@ impl RpcError { } } - pub fn message(&self) -> Cow<'_, str> { + pub fn message(&self, version: RpcVersion) -> Cow<'_, str> { match self { RpcError::ParseError(..) => "Parse error".into(), RpcError::InvalidRequest(..) => "Invalid request".into(), RpcError::MethodNotFound { .. } => "Method not found".into(), RpcError::InvalidParams(..) => "Invalid params".into(), RpcError::InternalError(_) => "Internal error".into(), - RpcError::ApplicationError(e) => e.to_string().into(), + RpcError::ApplicationError(e) => e.message(version).into(), RpcError::WebsocketSubscriptionClosed { .. } => "Websocket subscription closed".into(), } } @@ -82,7 +82,7 @@ impl serialize::SerializeForVersion for RpcError { ) -> Result { let mut obj = serializer.serialize_struct()?; obj.serialize_field("code", &self.code())?; - obj.serialize_field("message", &self.message())?; + obj.serialize_field("message", &self.message(serializer.version))?; if let Some(data) = self.data(serializer.version) { obj.serialize_field("data", &data)?; diff --git a/crates/rpc/src/jsonrpc/response.rs b/crates/rpc/src/jsonrpc/response.rs index 714d4475d9..6ed692fffe 100644 --- a/crates/rpc/src/jsonrpc/response.rs +++ b/crates/rpc/src/jsonrpc/response.rs @@ -156,7 +156,7 @@ mod tests { "jsonrpc": "2.0", "error": { "code": parsing_err.code(), - "message": parsing_err.message(), + "message": parsing_err.message(RpcVersion::V07), "data": parsing_err.data(RpcVersion::V07), }, "id": 1, diff --git a/crates/rpc/src/method/add_declare_transaction.rs b/crates/rpc/src/method/add_declare_transaction.rs index e643bd90e0..dc1d66b1fe 100644 --- a/crates/rpc/src/method/add_declare_transaction.rs +++ b/crates/rpc/src/method/add_declare_transaction.rs @@ -14,7 +14,7 @@ use crate::v02::types::request::BroadcastedDeclareTransaction; pub enum AddDeclareTransactionError { ClassAlreadyDeclared, InvalidTransactionNonce, - InsufficientMaxFee, + InsufficientResourcesForValidate, InsufficientAccountBalance, ValidationFailure(String), CompilationFailed, @@ -32,7 +32,9 @@ impl From for crate::error::ApplicationError { match value { AddDeclareTransactionError::ClassAlreadyDeclared => Self::ClassAlreadyDeclared, AddDeclareTransactionError::InvalidTransactionNonce => Self::InvalidTransactionNonce, - AddDeclareTransactionError::InsufficientMaxFee => Self::InsufficientMaxFee, + AddDeclareTransactionError::InsufficientResourcesForValidate => { + Self::InsufficientResourcesForValidate + } AddDeclareTransactionError::InsufficientAccountBalance => { Self::InsufficientAccountBalance } @@ -100,7 +102,7 @@ impl From for AddDeclareTransactionError { AddDeclareTransactionError::InsufficientAccountBalance } SequencerError::StarknetError(e) if e.code == InsufficientMaxFee.into() => { - AddDeclareTransactionError::InsufficientMaxFee + AddDeclareTransactionError::InsufficientResourcesForValidate } SequencerError::StarknetError(e) if e.code == InvalidTransactionNonce.into() => { AddDeclareTransactionError::InvalidTransactionNonce diff --git a/crates/rpc/src/method/add_deploy_account_transaction.rs b/crates/rpc/src/method/add_deploy_account_transaction.rs index b1a45e2589..c796f521e8 100644 --- a/crates/rpc/src/method/add_deploy_account_transaction.rs +++ b/crates/rpc/src/method/add_deploy_account_transaction.rs @@ -51,7 +51,7 @@ pub struct Output { pub enum AddDeployAccountTransactionError { ClassHashNotFound, InvalidTransactionNonce, - InsufficientMaxFee, + InsufficientResourcesForValidate, InsufficientAccountBalance, ValidationFailure(String), DuplicateTransaction, @@ -66,7 +66,7 @@ impl From for crate::error::ApplicationError { match value { ClassHashNotFound => Self::ClassHashNotFound, InvalidTransactionNonce => Self::InvalidTransactionNonce, - InsufficientMaxFee => Self::InsufficientMaxFee, + InsufficientResourcesForValidate => Self::InsufficientResourcesForValidate, InsufficientAccountBalance => Self::InsufficientAccountBalance, ValidationFailure(message) => Self::ValidationFailureV06(message), DuplicateTransaction => Self::DuplicateTransaction, @@ -100,7 +100,7 @@ impl From for AddDeployAccountTransactionError { AddDeployAccountTransactionError::InsufficientAccountBalance } SequencerError::StarknetError(e) if e.code == InsufficientMaxFee.into() => { - AddDeployAccountTransactionError::InsufficientMaxFee + AddDeployAccountTransactionError::InsufficientResourcesForValidate } SequencerError::StarknetError(e) if e.code == InvalidTransactionNonce.into() => { AddDeployAccountTransactionError::InvalidTransactionNonce diff --git a/crates/rpc/src/method/add_invoke_transaction.rs b/crates/rpc/src/method/add_invoke_transaction.rs index 303455080b..ff88d4c216 100644 --- a/crates/rpc/src/method/add_invoke_transaction.rs +++ b/crates/rpc/src/method/add_invoke_transaction.rs @@ -46,7 +46,7 @@ pub struct Output { #[derive(Debug)] pub enum AddInvokeTransactionError { InvalidTransactionNonce, - InsufficientMaxFee, + InsufficientResourcesForValidate, InsufficientAccountBalance, ValidationFailure(String), DuplicateTransaction, @@ -59,7 +59,9 @@ impl From for crate::error::ApplicationError { fn from(value: AddInvokeTransactionError) -> Self { match value { AddInvokeTransactionError::InvalidTransactionNonce => Self::InvalidTransactionNonce, - AddInvokeTransactionError::InsufficientMaxFee => Self::InsufficientMaxFee, + AddInvokeTransactionError::InsufficientResourcesForValidate => { + Self::InsufficientResourcesForValidate + } AddInvokeTransactionError::InsufficientAccountBalance => { Self::InsufficientAccountBalance } @@ -93,7 +95,7 @@ impl From for AddInvokeTransactionError { AddInvokeTransactionError::InsufficientAccountBalance } SequencerError::StarknetError(e) if e.code == InsufficientMaxFee.into() => { - AddInvokeTransactionError::InsufficientMaxFee + AddInvokeTransactionError::InsufficientResourcesForValidate } SequencerError::StarknetError(e) if e.code == InvalidTransactionNonce.into() => { AddInvokeTransactionError::InvalidTransactionNonce diff --git a/crates/rpc/src/v06/method/add_declare_transaction.rs b/crates/rpc/src/v06/method/add_declare_transaction.rs index 1858f44b06..f0bb56e85f 100644 --- a/crates/rpc/src/v06/method/add_declare_transaction.rs +++ b/crates/rpc/src/v06/method/add_declare_transaction.rs @@ -15,7 +15,7 @@ use crate::v02::types::request::BroadcastedDeclareTransaction; pub enum AddDeclareTransactionError { ClassAlreadyDeclared, InvalidTransactionNonce, - InsufficientMaxFee, + InsufficientResourcesForValidate, InsufficientAccountBalance, ValidationFailure(String), CompilationFailed, @@ -33,7 +33,9 @@ impl From for crate::error::ApplicationError { match value { AddDeclareTransactionError::ClassAlreadyDeclared => Self::ClassAlreadyDeclared, AddDeclareTransactionError::InvalidTransactionNonce => Self::InvalidTransactionNonce, - AddDeclareTransactionError::InsufficientMaxFee => Self::InsufficientMaxFee, + AddDeclareTransactionError::InsufficientResourcesForValidate => { + Self::InsufficientResourcesForValidate + } AddDeclareTransactionError::InsufficientAccountBalance => { Self::InsufficientAccountBalance } @@ -101,7 +103,7 @@ impl From for AddDeclareTransactionError { AddDeclareTransactionError::InsufficientAccountBalance } SequencerError::StarknetError(e) if e.code == InsufficientMaxFee.into() => { - AddDeclareTransactionError::InsufficientMaxFee + AddDeclareTransactionError::InsufficientResourcesForValidate } SequencerError::StarknetError(e) if e.code == InvalidTransactionNonce.into() => { AddDeclareTransactionError::InvalidTransactionNonce diff --git a/crates/rpc/src/v06/method/add_deploy_account_transaction.rs b/crates/rpc/src/v06/method/add_deploy_account_transaction.rs index 8741f5a98c..6fddc81ff1 100644 --- a/crates/rpc/src/v06/method/add_deploy_account_transaction.rs +++ b/crates/rpc/src/v06/method/add_deploy_account_transaction.rs @@ -41,7 +41,7 @@ pub struct AddDeployAccountTransactionOutput { pub enum AddDeployAccountTransactionError { ClassHashNotFound, InvalidTransactionNonce, - InsufficientMaxFee, + InsufficientResourcesForValidate, InsufficientAccountBalance, ValidationFailure(String), DuplicateTransaction, @@ -56,7 +56,7 @@ impl From for crate::error::ApplicationError { match value { ClassHashNotFound => Self::ClassHashNotFound, InvalidTransactionNonce => Self::InvalidTransactionNonce, - InsufficientMaxFee => Self::InsufficientMaxFee, + InsufficientResourcesForValidate => Self::InsufficientResourcesForValidate, InsufficientAccountBalance => Self::InsufficientAccountBalance, ValidationFailure(message) => Self::ValidationFailureV06(message), DuplicateTransaction => Self::DuplicateTransaction, @@ -90,7 +90,7 @@ impl From for AddDeployAccountTransactionError { AddDeployAccountTransactionError::InsufficientAccountBalance } SequencerError::StarknetError(e) if e.code == InsufficientMaxFee.into() => { - AddDeployAccountTransactionError::InsufficientMaxFee + AddDeployAccountTransactionError::InsufficientResourcesForValidate } SequencerError::StarknetError(e) if e.code == InvalidTransactionNonce.into() => { AddDeployAccountTransactionError::InvalidTransactionNonce diff --git a/crates/rpc/src/v06/method/add_invoke_transaction.rs b/crates/rpc/src/v06/method/add_invoke_transaction.rs index 63ea9efb33..a6f0984c10 100644 --- a/crates/rpc/src/v06/method/add_invoke_transaction.rs +++ b/crates/rpc/src/v06/method/add_invoke_transaction.rs @@ -35,7 +35,7 @@ pub struct AddInvokeTransactionOutput { #[derive(Debug)] pub enum AddInvokeTransactionError { InvalidTransactionNonce, - InsufficientMaxFee, + InsufficientResourcesForValidate, InsufficientAccountBalance, ValidationFailure(String), DuplicateTransaction, @@ -48,7 +48,9 @@ impl From for crate::error::ApplicationError { fn from(value: AddInvokeTransactionError) -> Self { match value { AddInvokeTransactionError::InvalidTransactionNonce => Self::InvalidTransactionNonce, - AddInvokeTransactionError::InsufficientMaxFee => Self::InsufficientMaxFee, + AddInvokeTransactionError::InsufficientResourcesForValidate => { + Self::InsufficientResourcesForValidate + } AddInvokeTransactionError::InsufficientAccountBalance => { Self::InsufficientAccountBalance } @@ -82,7 +84,7 @@ impl From for AddInvokeTransactionError { AddInvokeTransactionError::InsufficientAccountBalance } SequencerError::StarknetError(e) if e.code == InsufficientMaxFee.into() => { - AddInvokeTransactionError::InsufficientMaxFee + AddInvokeTransactionError::InsufficientResourcesForValidate } SequencerError::StarknetError(e) if e.code == InvalidTransactionNonce.into() => { AddInvokeTransactionError::InvalidTransactionNonce From 79e1a88c99c94c0081137373e83434a819f786ff Mon Sep 17 00:00:00 2001 From: Krisztian Kovacs Date: Tue, 22 Oct 2024 10:52:12 +0200 Subject: [PATCH 189/282] feat(rpc/v08): add `starknet_addXXXTransaction` methods No changes here except for the INVALID_MAX_FEE error change. --- crates/rpc/src/lib.rs | 6 +----- crates/rpc/src/v08.rs | 3 +++ 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/crates/rpc/src/lib.rs b/crates/rpc/src/lib.rs index a27cd73599..125f90a641 100644 --- a/crates/rpc/src/lib.rs +++ b/crates/rpc/src/lib.rs @@ -905,11 +905,7 @@ mod tests { "starknet_getTransactionReceipt", ])] #[case::v0_8_trace("/rpc/v0_8", "v08/starknet_trace_api_openrpc.json", &[])] - #[case::v0_8_write("/rpc/v0_8", "v08/starknet_write_api.json", &[ - "starknet_addInvokeTransaction", - "starknet_addDeclareTransaction", - "starknet_addDeployAccountTransaction" - ])] + #[case::v0_8_write("/rpc/v0_8", "v08/starknet_write_api.json", &[])] // get_transaction_status is now part of the official spec, so we are phasing it out. #[case::v0_8_pathfinder("/rpc/v0_8", "pathfinder_rpc_api.json", &["pathfinder_version", "pathfinder_getTransactionStatus"])] diff --git a/crates/rpc/src/v08.rs b/crates/rpc/src/v08.rs index fc48c3986b..19e53317df 100644 --- a/crates/rpc/src/v08.rs +++ b/crates/rpc/src/v08.rs @@ -6,6 +6,9 @@ use crate::method::subscribe_pending_transactions::SubscribePendingTransactions; #[rustfmt::skip] pub fn register_routes() -> RpcRouterBuilder { RpcRouter::builder(crate::RpcVersion::V08) + .register("starknet_addDeclareTransaction", crate::method::add_declare_transaction) + .register("starknet_addDeployAccountTransaction", crate::method::add_deploy_account_transaction) + .register("starknet_addInvokeTransaction", crate::method::add_invoke_transaction) .register("starknet_blockHashAndNumber", crate::method::block_hash_and_number) .register("starknet_blockNumber", crate::method::block_number) .register("starknet_call", crate::method::call) From 54535c384ba26014765bdfe20ecc678c9fee32a6 Mon Sep 17 00:00:00 2001 From: t00ts Date: Tue, 22 Oct 2024 10:58:22 +0400 Subject: [PATCH 190/282] feat: introduce `l2_gas_price` to `BlockHeader` --- crates/common/src/header.rs | 12 +++++++++++ crates/gateway-types/src/reply.rs | 2 ++ crates/p2p/src/client/conv.rs | 2 ++ crates/p2p/src/client/types.rs | 2 ++ crates/p2p_proto/proto/header.proto | 2 ++ crates/p2p_proto/src/header.rs | 4 ++++ crates/pathfinder/src/bin/pathfinder/main.rs | 8 +++---- crates/pathfinder/src/state/sync.rs | 2 ++ crates/pathfinder/src/state/sync/pending.rs | 1 + crates/pathfinder/src/sync/checkpoint.rs | 2 ++ .../pathfinder/src/sync/checkpoint/fixture.rs | 4 ++++ crates/pathfinder/src/sync/track.rs | 2 ++ crates/rpc/fixtures/mainnet.sqlite | Bin 454656 -> 454656 bytes crates/rpc/src/dto/block.rs | 14 ++++++++++++ crates/rpc/src/jsonrpc/websocket/data.rs | 4 ++++ crates/rpc/src/jsonrpc/websocket/logic.rs | 8 +++++++ crates/rpc/src/lib.rs | 4 ++++ crates/rpc/src/method/call.rs | 4 ++++ .../rpc/src/method/get_block_with_receipts.rs | 8 +++++++ crates/rpc/src/method/subscribe_new_heads.rs | 1 + .../src/method/trace_block_transactions.rs | 4 ++++ crates/rpc/src/pending.rs | 6 ++++++ crates/rpc/src/v06/method/call.rs | 4 ++++ crates/rpc/src/v06/method/estimate_fee.rs | 4 ++++ .../v06/method/trace_block_transactions.rs | 6 ++++++ .../rpc/src/v06/method/trace_transaction.rs | 2 ++ crates/storage/src/connection/block.rs | 20 ++++++++++++++++-- crates/storage/src/schema.rs | 2 ++ crates/storage/src/schema/revision_0065.rs | 12 +++++++++++ 29 files changed, 139 insertions(+), 7 deletions(-) create mode 100644 crates/storage/src/schema/revision_0065.rs diff --git a/crates/common/src/header.rs b/crates/common/src/header.rs index 1030de33ce..14d2617d02 100644 --- a/crates/common/src/header.rs +++ b/crates/common/src/header.rs @@ -13,6 +13,8 @@ pub struct BlockHeader { pub strk_l1_gas_price: GasPrice, pub eth_l1_data_gas_price: GasPrice, pub strk_l1_data_gas_price: GasPrice, + pub eth_l2_gas_price: GasPrice, + pub strk_l2_gas_price: GasPrice, pub sequencer_address: SequencerAddress, pub starknet_version: StarknetVersion, pub class_commitment: ClassCommitment, @@ -109,6 +111,16 @@ impl BlockHeaderBuilder { self } + pub fn eth_l2_gas_price(mut self, eth_l2_gas_price: GasPrice) -> Self { + self.0.eth_l2_gas_price = eth_l2_gas_price; + self + } + + pub fn strk_l2_gas_price(mut self, strk_l2_gas_price: GasPrice) -> Self { + self.0.strk_l2_gas_price = strk_l2_gas_price; + self + } + pub fn eth_l1_data_gas_price(mut self, eth_l1_data_gas_price: GasPrice) -> Self { self.0.eth_l1_data_gas_price = eth_l1_data_gas_price; self diff --git a/crates/gateway-types/src/reply.rs b/crates/gateway-types/src/reply.rs index 8b380a3e0f..548b33329e 100644 --- a/crates/gateway-types/src/reply.rs +++ b/crates/gateway-types/src/reply.rs @@ -73,6 +73,8 @@ pub struct Block { pub struct PendingBlock { pub l1_gas_price: GasPrices, pub l1_data_gas_price: GasPrices, + #[serde(default)] // TODO: Needed until the gateway provides the l2 gas price + pub l2_gas_price: GasPrices, #[serde(rename = "parent_block_hash")] pub parent_hash: BlockHash, diff --git a/crates/p2p/src/client/conv.rs b/crates/p2p/src/client/conv.rs index 4a2a943193..5bb69700fe 100644 --- a/crates/p2p/src/client/conv.rs +++ b/crates/p2p/src/client/conv.rs @@ -132,6 +132,8 @@ impl ToDto for SignedBlockHeader { gas_price_wei: self.header.eth_l1_gas_price.0, data_gas_price_fri: self.header.strk_l1_data_gas_price.0, data_gas_price_wei: self.header.eth_l1_data_gas_price.0, + l2_gas_price_fri: self.header.strk_l2_gas_price.0, + l2_gas_price_wei: self.header.eth_l2_gas_price.0, l1_data_availability_mode: self.header.l1_da_mode.to_dto(), signatures: vec![p2p_proto::common::ConsensusSignature { r: self.signature.r.0, diff --git a/crates/p2p/src/client/types.rs b/crates/p2p/src/client/types.rs index 4ce8427e00..2dc254c5ef 100644 --- a/crates/p2p/src/client/types.rs +++ b/crates/p2p/src/client/types.rs @@ -101,6 +101,8 @@ impl TryFromDto for SignedBlockHeader { strk_l1_gas_price: GasPrice(dto.gas_price_fri), eth_l1_data_gas_price: GasPrice(dto.data_gas_price_wei), strk_l1_data_gas_price: GasPrice(dto.data_gas_price_fri), + eth_l2_gas_price: GasPrice(dto.l2_gas_price_wei), + strk_l2_gas_price: GasPrice(dto.l2_gas_price_fri), sequencer_address: SequencerAddress(dto.sequencer_address.0), starknet_version: dto.protocol_version.parse()?, event_commitment: EventCommitment(dto.events.root.0), diff --git a/crates/p2p_proto/proto/header.proto b/crates/p2p_proto/proto/header.proto index 66e57c5219..82263c8fce 100644 --- a/crates/p2p_proto/proto/header.proto +++ b/crates/p2p_proto/proto/header.proto @@ -23,6 +23,8 @@ message SignedBlockHeader { starknet.common.Uint128 gas_price_wei = 13; starknet.common.Uint128 data_gas_price_fri = 14; starknet.common.Uint128 data_gas_price_wei = 15; + starknet.common.Uint128 l2_gas_price_fri = 18; + starknet.common.Uint128 l2_gas_price_wei = 19; starknet.common.L1DataAvailabilityMode l1_data_availability_mode = 16; // for now, we assume a small consensus, so this fits in 1M. Else, these will be repeated and extracted from this message. repeated starknet.common.ConsensusSignature signatures = 17; diff --git a/crates/p2p_proto/src/header.rs b/crates/p2p_proto/src/header.rs index 6b05d6e853..61b6c685bb 100644 --- a/crates/p2p_proto/src/header.rs +++ b/crates/p2p_proto/src/header.rs @@ -34,6 +34,8 @@ pub struct SignedBlockHeader { pub gas_price_wei: u128, pub data_gas_price_fri: u128, pub data_gas_price_wei: u128, + pub l2_gas_price_fri: u128, + pub l2_gas_price_wei: u128, pub l1_data_availability_mode: L1DataAvailabilityMode, pub signatures: Vec, } @@ -78,6 +80,8 @@ impl Dummy for SignedBlockHeader { gas_price_wei: Faker.fake_with_rng(rng), data_gas_price_fri: Faker.fake_with_rng(rng), data_gas_price_wei: Faker.fake_with_rng(rng), + l2_gas_price_fri: Faker.fake_with_rng(rng), + l2_gas_price_wei: Faker.fake_with_rng(rng), l1_data_availability_mode: Faker.fake_with_rng(rng), signatures: Faker.fake_with_rng(rng), } diff --git a/crates/pathfinder/src/bin/pathfinder/main.rs b/crates/pathfinder/src/bin/pathfinder/main.rs index 6456e9acbf..6db0efe4e3 100644 --- a/crates/pathfinder/src/bin/pathfinder/main.rs +++ b/crates/pathfinder/src/bin/pathfinder/main.rs @@ -384,12 +384,10 @@ fn setup_tracing(color: config::Color, pretty_log: bool, json_log: bool) { if json_log { subscriber.json().flatten_event(true).init(); + } else if pretty_log { + subscriber.pretty().init(); } else { - if pretty_log { - subscriber.pretty().init(); - } else { - subscriber.compact().init(); - } + subscriber.compact().init(); } } diff --git a/crates/pathfinder/src/state/sync.rs b/crates/pathfinder/src/state/sync.rs index 89237a78c2..fd3015a1f7 100644 --- a/crates/pathfinder/src/state/sync.rs +++ b/crates/pathfinder/src/state/sync.rs @@ -916,6 +916,8 @@ async fn l2_update( eth_l1_data_gas_price: block.l1_data_gas_price.price_in_wei, // Default value for Starknet <0.13.1 is zero strk_l1_data_gas_price: block.l1_data_gas_price.price_in_fri, + eth_l2_gas_price: GasPrice(0), // TODO: Fix when we get l2_gas_price in the gateway + strk_l2_gas_price: GasPrice(0), // TODO: Fix when we get l2_gas_price in the gateway sequencer_address: block .sequencer_address .unwrap_or(SequencerAddress(Felt::ZERO)), diff --git a/crates/pathfinder/src/state/sync/pending.rs b/crates/pathfinder/src/state/sync/pending.rs index 4045212555..03a15b0559 100644 --- a/crates/pathfinder/src/state/sync/pending.rs +++ b/crates/pathfinder/src/state/sync/pending.rs @@ -160,6 +160,7 @@ mod tests { ..Default::default() }, l1_data_gas_price: Default::default(), + l2_gas_price: Default::default(), parent_hash: NEXT_BLOCK.parent_block_hash, sequencer_address: sequencer_address_bytes!(b"seqeunecer address"), status: Status::Pending, diff --git a/crates/pathfinder/src/sync/checkpoint.rs b/crates/pathfinder/src/sync/checkpoint.rs index f4a6ecc8fe..56a828f8ad 100644 --- a/crates/pathfinder/src/sync/checkpoint.rs +++ b/crates/pathfinder/src/sync/checkpoint.rs @@ -791,6 +791,8 @@ mod tests { eth_l1_data_gas_price: GasPrice(1), strk_l1_gas_price: GasPrice(0), strk_l1_data_gas_price: GasPrice(1), + eth_l2_gas_price: GasPrice(0), + strk_l2_gas_price: GasPrice(0), l1_da_mode: L1DataAvailabilityMode::Calldata, class_commitment: ClassCommitment::ZERO, storage_commitment: StorageCommitment::ZERO, diff --git a/crates/pathfinder/src/sync/checkpoint/fixture.rs b/crates/pathfinder/src/sync/checkpoint/fixture.rs index 847ae54327..22bb47ca07 100644 --- a/crates/pathfinder/src/sync/checkpoint/fixture.rs +++ b/crates/pathfinder/src/sync/checkpoint/fixture.rs @@ -69,6 +69,8 @@ pub fn blocks() -> [Block; 2] { strk_l1_gas_price: GasPrice(0), eth_l1_data_gas_price: GasPrice(1), strk_l1_data_gas_price: GasPrice(1), + eth_l2_gas_price: GasPrice(3), + strk_l2_gas_price: GasPrice(3), sequencer_address: Default::default(), starknet_version: StarknetVersion::new(0, 0, 0, 0), class_commitment: class_commitment!("0x0"), @@ -1189,6 +1191,8 @@ pub fn blocks() -> [Block; 2] { strk_l1_gas_price: GasPrice(0), eth_l1_data_gas_price: GasPrice(1), strk_l1_data_gas_price: GasPrice(1), + eth_l2_gas_price: GasPrice(3), + strk_l2_gas_price: GasPrice(3), sequencer_address: Default::default(), starknet_version: StarknetVersion::default(), class_commitment: class_commitment!("0x0"), diff --git a/crates/pathfinder/src/sync/track.rs b/crates/pathfinder/src/sync/track.rs index c02566c7ca..8cd4b05b2c 100644 --- a/crates/pathfinder/src/sync/track.rs +++ b/crates/pathfinder/src/sync/track.rs @@ -781,6 +781,8 @@ impl ProcessStage for StoreBlock { strk_l1_gas_price: header.strk_l1_gas_price, eth_l1_data_gas_price: header.eth_l1_data_gas_price, strk_l1_data_gas_price: header.strk_l1_data_gas_price, + eth_l2_gas_price: header.eth_l2_gas_price, + strk_l2_gas_price: header.strk_l2_gas_price, sequencer_address: header.sequencer_address, starknet_version: header.starknet_version, // Class commitment is updated after the class tries are updated. diff --git a/crates/rpc/fixtures/mainnet.sqlite b/crates/rpc/fixtures/mainnet.sqlite index b9141b83d70f978c5640fdfeac191949ca1e0fbc..f86d30b16ead2d8fd10a17413238fc3772b5e9d1 100644 GIT binary patch delta 154 zcmZp8Al>jldV;iSFarZaJ`e{0u^bS$12GE_!vG_Y;W$}PL7p+VF=1;0Z%Wnf8Zij6lo;#LPg<0>rG_AEvR%cLD%le=4c~ delta 124 zcmZp8Al>jldV;j77Xt%BJ`e{0u^bS$05J;?!vG_Y;V@ZHL7vg8F=1;0 { price_in_fri: self.0.strk_l1_data_gas_price, }, )?; + serializer.serialize_field( + "l2_gas_price", + &ResourcePrice { + price_in_wei: self.0.eth_l2_gas_price, + price_in_fri: self.0.strk_l2_gas_price, + }, + )?; serializer.serialize_field( "l1_da_mode", &match self.0.l1_da_mode { @@ -108,6 +115,13 @@ impl crate::dto::serialize::SerializeForVersion for PendingBlockHeader<'_> { price_in_fri: self.0.l1_data_gas_price.price_in_fri, }, )?; + serializer.serialize_field( + "l2_gas_price", + &ResourcePrice { + price_in_wei: self.0.l2_gas_price.price_in_wei, + price_in_fri: self.0.l2_gas_price.price_in_fri, + }, + )?; serializer.serialize_field( "l1_da_mode", &match self.0.l1_da_mode { diff --git a/crates/rpc/src/jsonrpc/websocket/data.rs b/crates/rpc/src/jsonrpc/websocket/data.rs index fca8837af8..ab650a49c0 100644 --- a/crates/rpc/src/jsonrpc/websocket/data.rs +++ b/crates/rpc/src/jsonrpc/websocket/data.rs @@ -236,6 +236,8 @@ impl serde::Serialize for BlockHeader { strk_l1_gas_price, eth_l1_data_gas_price, strk_l1_data_gas_price, + eth_l2_gas_price, + strk_l2_gas_price, sequencer_address, starknet_version, class_commitment, @@ -261,6 +263,8 @@ impl serde::Serialize for BlockHeader { map.serialize_entry("strk_l1_gas_price", &strk_l1_gas_price)?; map.serialize_entry("eth_l1_data_gas_price", ð_l1_data_gas_price)?; map.serialize_entry("strk_l1_data_gas_price", &strk_l1_data_gas_price)?; + map.serialize_entry("eth_l2_gas_price", ð_l2_gas_price)?; + map.serialize_entry("strk_l2_gas_price", &strk_l2_gas_price)?; map.serialize_entry("sequencer_address", &sequencer_address)?; map.serialize_entry("starknet_version", &starknet_version.to_string())?; map.serialize_entry("class_commitment", &class_commitment)?; diff --git a/crates/rpc/src/jsonrpc/websocket/logic.rs b/crates/rpc/src/jsonrpc/websocket/logic.rs index c2b3c63e57..471f3475c1 100644 --- a/crates/rpc/src/jsonrpc/websocket/logic.rs +++ b/crates/rpc/src/jsonrpc/websocket/logic.rs @@ -988,6 +988,8 @@ mod tests { block: PendingBlock { l1_gas_price: block.l1_gas_price, l1_data_gas_price: block.l1_data_gas_price, + l2_gas_price: Default::default(), /* TODO: Fix when we get l2_gas_price in the + * gateway */ parent_hash: block.block_hash, sequencer_address: SequencerAddress::ZERO, status: Status::Pending, @@ -1039,6 +1041,8 @@ mod tests { block: PendingBlock { l1_gas_price: block.l1_gas_price, l1_data_gas_price: block.l1_data_gas_price, + l2_gas_price: Default::default(), /* TODO: Fix when we get l2_gas_price in the + * gateway */ parent_hash: block.block_hash, sequencer_address: SequencerAddress::ZERO, status: Status::Pending, @@ -1091,6 +1095,8 @@ mod tests { block: PendingBlock { l1_gas_price: block.l1_gas_price, l1_data_gas_price: block.l1_data_gas_price, + l2_gas_price: Default::default(), /* TODO: Fix when we get l2_gas_price in the + * gateway */ parent_hash: block.block_hash, sequencer_address: SequencerAddress::ZERO, status: Status::Pending, @@ -1139,6 +1145,8 @@ mod tests { block: PendingBlock { l1_gas_price: block.l1_gas_price, l1_data_gas_price: block.l1_data_gas_price, + l2_gas_price: Default::default(), /* TODO: Fix when we get l2_gas_price in the + * gateway */ parent_hash: block.block_hash, sequencer_address: SequencerAddress::ZERO, status: Status::Pending, diff --git a/crates/rpc/src/lib.rs b/crates/rpc/src/lib.rs index a27cd73599..8f75c32e1b 100644 --- a/crates/rpc/src/lib.rs +++ b/crates/rpc/src/lib.rs @@ -780,6 +780,10 @@ pub mod test_utils { price_in_wei: GasPrice::from_be_slice(b"datgasprice").unwrap(), price_in_fri: GasPrice::from_be_slice(b"strk datgasprice").unwrap(), }, + l2_gas_price: GasPrices { + price_in_wei: GasPrice::from_be_slice(b"l2 gas price").unwrap(), + price_in_fri: GasPrice::from_be_slice(b"strk l2gas price").unwrap(), + }, parent_hash: latest.hash, sequencer_address: sequencer_address_bytes!(b"pending sequencer address"), status: starknet_gateway_types::reply::Status::Pending, diff --git a/crates/rpc/src/method/call.rs b/crates/rpc/src/method/call.rs index a8b9ac6da3..4e55915a20 100644 --- a/crates/rpc/src/method/call.rs +++ b/crates/rpc/src/method/call.rs @@ -446,6 +446,10 @@ mod tests { price_in_wei: last_block_header.eth_l1_gas_price, price_in_fri: Default::default(), }, + l2_gas_price: GasPrices { + price_in_wei: last_block_header.eth_l2_gas_price, + price_in_fri: last_block_header.strk_l2_gas_price, + }, l1_data_gas_price: Default::default(), parent_hash: last_block_header.hash, sequencer_address: last_block_header.sequencer_address, diff --git a/crates/rpc/src/method/get_block_with_receipts.rs b/crates/rpc/src/method/get_block_with_receipts.rs index 29c4df5d3e..e09e198333 100644 --- a/crates/rpc/src/method/get_block_with_receipts.rs +++ b/crates/rpc/src/method/get_block_with_receipts.rs @@ -205,6 +205,10 @@ mod tests { "price_in_fri": "0x7374726b20676173207072696365", "price_in_wei": "0x676173207072696365", }, + "l2_gas_price": { + "price_in_fri": "0x7374726b206c32676173207072696365", + "price_in_wei": "0x6c3220676173207072696365", + }, "parent_hash": "0x6c6174657374", "sequencer_address": "0x70656e64696e672073657175656e6365722061646472657373", "starknet_version": "0.11.0", @@ -359,6 +363,10 @@ mod tests { "price_in_fri": "0x0", "price_in_wei": "0x2", }, + "l2_gas_price": { + "price_in_fri": "0x0", + "price_in_wei": "0x0", + }, "new_root": "0x57b695c82af81429fdc8966088b0196105dfb5aa22b54cbc86fc95dc3b3ece1", "parent_hash": "0x626c6f636b2031", "sequencer_address": "0x2", diff --git a/crates/rpc/src/method/subscribe_new_heads.rs b/crates/rpc/src/method/subscribe_new_heads.rs index 27674c1fec..1c5e0eac59 100644 --- a/crates/rpc/src/method/subscribe_new_heads.rs +++ b/crates/rpc/src/method/subscribe_new_heads.rs @@ -598,6 +598,7 @@ mod tests { "l1_da_mode": "CALLDATA", "l1_data_gas_price": { "price_in_fri": "0x0", "price_in_wei": "0x0" }, "l1_gas_price": { "price_in_fri": "0x0", "price_in_wei": "0x0" }, + "l2_gas_price": { "price_in_fri": "0x0", "price_in_wei": "0x0" }, "new_root": "0x0", "parent_hash": "0x0", "sequencer_address": "0x0", diff --git a/crates/rpc/src/method/trace_block_transactions.rs b/crates/rpc/src/method/trace_block_transactions.rs index 822732cafa..92339a82ed 100644 --- a/crates/rpc/src/method/trace_block_transactions.rs +++ b/crates/rpc/src/method/trace_block_transactions.rs @@ -830,6 +830,10 @@ pub(crate) mod tests { price_in_wei: GasPrice(2), price_in_fri: GasPrice(2), }, + l2_gas_price: GasPrices { + price_in_wei: GasPrice(3), + price_in_fri: GasPrice(3), + }, parent_hash: last_block_header.hash, sequencer_address: last_block_header.sequencer_address, status: starknet_gateway_types::reply::Status::Pending, diff --git a/crates/rpc/src/pending.rs b/crates/rpc/src/pending.rs index 2fc503ba8e..7cf50e2969 100644 --- a/crates/rpc/src/pending.rs +++ b/crates/rpc/src/pending.rs @@ -30,6 +30,8 @@ impl PendingData { strk_l1_gas_price: self.block.l1_gas_price.price_in_fri, eth_l1_data_gas_price: self.block.l1_data_gas_price.price_in_wei, strk_l1_data_gas_price: self.block.l1_data_gas_price.price_in_fri, + eth_l2_gas_price: 0.into(), // TODO: Fix when we get l2_gas_price in the gateway + strk_l2_gas_price: 0.into(), // TODO: Fix when we get l2_gas_price in the gateway sequencer_address: self.block.sequencer_address, starknet_version: self.block.starknet_version, // Pending block does not know what these are yet. @@ -80,6 +82,10 @@ impl PendingWatcher { price_in_wei: latest.eth_l1_data_gas_price, price_in_fri: latest.strk_l1_data_gas_price, }, + l2_gas_price: GasPrices { + price_in_wei: latest.eth_l2_gas_price, + price_in_fri: latest.strk_l2_gas_price, + }, timestamp: latest.timestamp, parent_hash: latest.hash, starknet_version: latest.starknet_version, diff --git a/crates/rpc/src/v06/method/call.rs b/crates/rpc/src/v06/method/call.rs index 6a8c39bf1e..178f39f987 100644 --- a/crates/rpc/src/v06/method/call.rs +++ b/crates/rpc/src/v06/method/call.rs @@ -429,6 +429,10 @@ mod tests { price_in_fri: Default::default(), }, l1_data_gas_price: Default::default(), + l2_gas_price: GasPrices { + price_in_wei: last_block_header.eth_l2_gas_price, + price_in_fri: last_block_header.strk_l2_gas_price, + }, parent_hash: last_block_header.hash, sequencer_address: last_block_header.sequencer_address, status: starknet_gateway_types::reply::Status::Pending, diff --git a/crates/rpc/src/v06/method/estimate_fee.rs b/crates/rpc/src/v06/method/estimate_fee.rs index d8ac41f8fb..600e0e83e9 100644 --- a/crates/rpc/src/v06/method/estimate_fee.rs +++ b/crates/rpc/src/v06/method/estimate_fee.rs @@ -530,6 +530,10 @@ pub(crate) mod tests { price_in_fri: Default::default(), }, l1_data_gas_price: Default::default(), + l2_gas_price: GasPrices { + price_in_wei: last_block_header.eth_l2_gas_price, + price_in_fri: last_block_header.strk_l2_gas_price, + }, parent_hash: last_block_header.hash, sequencer_address: last_block_header.sequencer_address, status: starknet_gateway_types::reply::Status::Pending, diff --git a/crates/rpc/src/v06/method/trace_block_transactions.rs b/crates/rpc/src/v06/method/trace_block_transactions.rs index 37842492b8..83139de38c 100644 --- a/crates/rpc/src/v06/method/trace_block_transactions.rs +++ b/crates/rpc/src/v06/method/trace_block_transactions.rs @@ -570,6 +570,10 @@ pub(crate) mod tests { price_in_wei: GasPrice(2), price_in_fri: GasPrice(2), }, + l2_gas_price: GasPrices { + price_in_wei: GasPrice(3), + price_in_fri: GasPrice(3), + }, parent_hash: last_block_header.hash, sequencer_address: last_block_header.sequencer_address, status: starknet_gateway_types::reply::Status::Pending, @@ -652,6 +656,8 @@ pub(crate) mod tests { strk_l1_gas_price: block.l1_gas_price.price_in_fri, eth_l1_data_gas_price: block.l1_data_gas_price.price_in_wei, strk_l1_data_gas_price: block.l1_data_gas_price.price_in_fri, + eth_l2_gas_price: GasPrice(0), // TODO: Fix when we get l2_gas_price in the gateway + strk_l2_gas_price: GasPrice(0), // TODO: Fix when we get l2_gas_price in the gateway sequencer_address: block .sequencer_address .unwrap_or(SequencerAddress(Felt::ZERO)), diff --git a/crates/rpc/src/v06/method/trace_transaction.rs b/crates/rpc/src/v06/method/trace_transaction.rs index 683a777075..722c9783a4 100644 --- a/crates/rpc/src/v06/method/trace_transaction.rs +++ b/crates/rpc/src/v06/method/trace_transaction.rs @@ -322,6 +322,8 @@ pub mod tests { strk_l1_gas_price: block.l1_gas_price.price_in_fri, eth_l1_data_gas_price: block.l1_data_gas_price.price_in_wei, strk_l1_data_gas_price: block.l1_data_gas_price.price_in_fri, + eth_l2_gas_price: 0.into(), // TODO: Fix when we get l2_gas_price in the gateway + strk_l2_gas_price: 0.into(), // TODO: Fix when we get l2_gas_price in the gateway sequencer_address: block .sequencer_address .unwrap_or(SequencerAddress(Felt::ZERO)), diff --git a/crates/storage/src/connection/block.rs b/crates/storage/src/connection/block.rs index 06f89f6158..c424bdd6c9 100644 --- a/crates/storage/src/connection/block.rs +++ b/crates/storage/src/connection/block.rs @@ -23,8 +23,8 @@ impl Transaction<'_> { // Insert the header self.inner().execute( r"INSERT INTO block_headers - ( number, hash, parent_hash, storage_commitment, timestamp, eth_l1_gas_price, strk_l1_gas_price, eth_l1_data_gas_price, strk_l1_data_gas_price, sequencer_address, version, transaction_commitment, event_commitment, state_commitment, class_commitment, transaction_count, event_count, l1_da_mode, receipt_commitment, state_diff_commitment, state_diff_length) - VALUES (:number, :hash, :parent_hash, :storage_commitment, :timestamp, :eth_l1_gas_price, :strk_l1_gas_price, :eth_l1_data_gas_price, :strk_l1_data_gas_price, :sequencer_address, :version, :transaction_commitment, :event_commitment, :state_commitment, :class_commitment, :transaction_count, :event_count, :l1_da_mode, :receipt_commitment, :state_diff_commitment, :state_diff_length)", + ( number, hash, parent_hash, storage_commitment, timestamp, eth_l1_gas_price, strk_l1_gas_price, eth_l1_data_gas_price, strk_l1_data_gas_price, eth_l2_gas_price, strk_l2_gas_price, sequencer_address, version, transaction_commitment, event_commitment, state_commitment, class_commitment, transaction_count, event_count, l1_da_mode, receipt_commitment, state_diff_commitment, state_diff_length) + VALUES (:number, :hash, :parent_hash, :storage_commitment, :timestamp, :eth_l1_gas_price, :strk_l1_gas_price, :eth_l1_data_gas_price, :strk_l1_data_gas_price, :eth_l2_gas_price, :strk_l2_gas_price, :sequencer_address, :version, :transaction_commitment, :event_commitment, :state_commitment, :class_commitment, :transaction_count, :event_count, :l1_da_mode, :receipt_commitment, :state_diff_commitment, :state_diff_length)", named_params! { ":number": &header.number, ":hash": &header.hash, @@ -35,6 +35,8 @@ impl Transaction<'_> { ":strk_l1_gas_price": &header.strk_l1_gas_price.to_be_bytes().as_slice(), ":eth_l1_data_gas_price": &header.eth_l1_data_gas_price.to_be_bytes().as_slice(), ":strk_l1_data_gas_price": &header.strk_l1_data_gas_price.to_be_bytes().as_slice(), + ":eth_l2_gas_price": &header.eth_l2_gas_price.to_be_bytes().as_slice(), + ":strk_l2_gas_price": &header.strk_l2_gas_price.to_be_bytes().as_slice(), ":sequencer_address": &header.sequencer_address, ":version": &header.starknet_version.as_u32(), ":transaction_commitment": &header.transaction_commitment, @@ -613,6 +615,12 @@ fn parse_row_as_header(row: &rusqlite::Row<'_>) -> rusqlite::Result let strk_l1_data_gas_price = row .get_optional_gas_price("strk_l1_data_gas_price")? .unwrap_or(GasPrice::ZERO); + let eth_l2_gas_price = row + .get_optional_gas_price("eth_l2_gas_price")? + .unwrap_or(GasPrice::ZERO); + let strk_l2_gas_price = row + .get_optional_gas_price("strk_l2_gas_price")? + .unwrap_or(GasPrice::ZERO); let sequencer_address = row.get_sequencer_address("sequencer_address")?; let transaction_commitment = row.get_transaction_commitment("transaction_commitment")?; let event_commitment = row.get_event_commitment("event_commitment")?; @@ -637,6 +645,8 @@ fn parse_row_as_header(row: &rusqlite::Row<'_>) -> rusqlite::Result strk_l1_gas_price, eth_l1_data_gas_price, strk_l1_data_gas_price, + eth_l2_gas_price, + strk_l2_gas_price, sequencer_address, class_commitment, event_commitment, @@ -688,6 +698,8 @@ mod tests { strk_l1_gas_price: GasPrice(33), eth_l1_data_gas_price: GasPrice(34), strk_l1_data_gas_price: GasPrice(35), + eth_l2_gas_price: GasPrice(36), + strk_l2_gas_price: GasPrice(36), sequencer_address: sequencer_address_bytes!(b"sequencer address genesis"), starknet_version: StarknetVersion::default(), class_commitment, @@ -707,6 +719,8 @@ mod tests { .timestamp(BlockTimestamp::new_or_panic(12)) .eth_l1_gas_price(GasPrice(34)) .strk_l1_gas_price(GasPrice(35)) + .eth_l2_gas_price(GasPrice(36)) + .strk_l2_gas_price(GasPrice(37)) .sequencer_address(sequencer_address_bytes!(b"sequencer address 1")) .event_commitment(event_commitment_bytes!(b"event commitment 1")) .class_commitment(class_commitment_bytes!(b"class commitment 1")) @@ -721,6 +735,8 @@ mod tests { .child_builder() .eth_l1_gas_price(GasPrice(38)) .strk_l1_gas_price(GasPrice(39)) + .eth_l2_gas_price(GasPrice(40)) + .strk_l2_gas_price(GasPrice(41)) .timestamp(BlockTimestamp::new_or_panic(15)) .sequencer_address(sequencer_address_bytes!(b"sequencer address 2")) .event_commitment(event_commitment_bytes!(b"event commitment 2")) diff --git a/crates/storage/src/schema.rs b/crates/storage/src/schema.rs index 33ae2c9a74..88a8af88e8 100644 --- a/crates/storage/src/schema.rs +++ b/crates/storage/src/schema.rs @@ -24,6 +24,7 @@ mod revision_0061; mod revision_0062; mod revision_0063; mod revision_0064; +mod revision_0065; pub(crate) use base::base_schema; @@ -56,6 +57,7 @@ pub fn migrations() -> &'static [MigrationFn] { revision_0062::migrate, revision_0063::migrate, revision_0064::migrate, + revision_0065::migrate, ] } diff --git a/crates/storage/src/schema/revision_0065.rs b/crates/storage/src/schema/revision_0065.rs new file mode 100644 index 0000000000..69f7b65f19 --- /dev/null +++ b/crates/storage/src/schema/revision_0065.rs @@ -0,0 +1,12 @@ +use anyhow::Context; + +pub(crate) fn migrate(tx: &rusqlite::Transaction<'_>) -> anyhow::Result<()> { + tracing::info!("Adding l2_gas_price columns to block_headers"); + + tx.execute_batch("ALTER TABLE block_headers ADD COLUMN eth_l2_gas_price BLOB DEFAULT NULL;") + .context("Adding eth_l2_gas_price column to block_headers")?; + tx.execute_batch("ALTER TABLE block_headers ADD COLUMN strk_l2_gas_price BLOB DEFAULT NULL;") + .context("Adding strk_l2_gas_price column to block_headers")?; + + Ok(()) +} From 0136f48f48acddebe65c2c727ebb6990ca390167 Mon Sep 17 00:00:00 2001 From: sistemd Date: Tue, 22 Oct 2024 12:44:18 +0200 Subject: [PATCH 191/282] starknet_subscribeTransactionStatus tests --- crates/rpc/src/jsonrpc/router/subscription.rs | 21 +- .../method/subscribe_transaction_status.rs | 748 +++++++++++++++++- crates/rpc/src/v08.rs | 2 + 3 files changed, 766 insertions(+), 5 deletions(-) diff --git a/crates/rpc/src/jsonrpc/router/subscription.rs b/crates/rpc/src/jsonrpc/router/subscription.rs index 0597dce538..ff48a5843e 100644 --- a/crates/rpc/src/jsonrpc/router/subscription.rs +++ b/crates/rpc/src/jsonrpc/router/subscription.rs @@ -155,7 +155,7 @@ where let tx = SubscriptionSender { subscription_id, - subscriptions, + subscriptions: subscriptions.clone(), tx: ws_tx, version: router.version, _phantom: Default::default(), @@ -192,9 +192,13 @@ where }; Ok(tokio::spawn(async move { + let _subscription_guard = SubscriptionsGuard { + subscription_id, + subscriptions, + }; // This lock ensures that the streaming of subscriptions doesn't start before // the caller sends the success response for the subscription request. - let _guard = lock.read().await; + let _lock_guard = lock.read().await; // Catch up to the latest block in batches of BATCH_SIZE. if let Some(current_block) = current_block.as_mut() { @@ -325,6 +329,19 @@ where } } +/// A guard to ensure that the subscription handle is removed when the +/// subscription task corresponding to that handle returns. +struct SubscriptionsGuard { + subscription_id: SubscriptionId, + subscriptions: Arc>>, +} + +impl Drop for SubscriptionsGuard { + fn drop(&mut self) { + self.subscriptions.remove(&self.subscription_id); + } +} + type WsSender = mpsc::Sender>; type WsReceiver = mpsc::Receiver>; diff --git a/crates/rpc/src/method/subscribe_transaction_status.rs b/crates/rpc/src/method/subscribe_transaction_status.rs index a448c46a41..8998092e71 100644 --- a/crates/rpc/src/method/subscribe_transaction_status.rs +++ b/crates/rpc/src/method/subscribe_transaction_status.rs @@ -16,7 +16,7 @@ use crate::error::ApplicationError; use crate::jsonrpc::{RpcError, RpcSubscriptionFlow, SubscriptionMessage}; use crate::Reorg; -pub struct SubscribeEvents; +pub struct SubscribeTransactionStatus; #[derive(Debug, Clone, Default)] pub struct Params { @@ -120,7 +120,7 @@ impl crate::dto::serialize::SerializeForVersion for Notification { const SUBSCRIPTION_NAME: &str = "starknet_subscriptionTransactionsStatus"; #[async_trait] -impl RpcSubscriptionFlow for SubscribeEvents { +impl RpcSubscriptionFlow for SubscribeTransactionStatus { type Params = Params; type Notification = Notification; @@ -347,7 +347,7 @@ impl RpcSubscriptionFlow for SubscribeEvents { Ok(l1_state) }).await.map_err(|e| RpcError::InternalError(e.into()))??; if let Some(l1_state) = l1_state { - if l1_state.block_number >= l2_block.block_number { + if l1_state.block_number >= sender.last_block_number && sender.last_execution_status.is_some() { if sender .send( l1_state.block_number, @@ -429,3 +429,745 @@ impl FinalityStatus { } } } + +#[cfg(test)] +mod tests { + use std::time::Duration; + + use axum::extract::ws::Message; + use pathfinder_common::receipt::{ExecutionStatus, Receipt}; + use pathfinder_common::transaction::Transaction; + use pathfinder_common::{ + BlockHash, + BlockHeader, + BlockNumber, + ChainId, + TransactionHash, + TransactionIndex, + }; + use pathfinder_crypto::Felt; + use pathfinder_ethereum::EthereumStateUpdate; + use pathfinder_storage::StorageBuilder; + use pretty_assertions_sorted::assert_eq; + use starknet_gateway_client::Client; + use starknet_gateway_types::reply::{Block, PendingBlock}; + use tokio::sync::mpsc; + + use crate::context::{RpcConfig, RpcContext}; + use crate::jsonrpc::{handle_json_rpc_socket, RpcResponse, RpcRouter}; + use crate::pending::PendingWatcher; + use crate::v02::types::syncing::Syncing; + use crate::{v08, Notifications, PendingData, Reorg, SubscriptionId, SyncState}; + + #[tokio::test] + async fn transaction_already_exists_in_db_accepted_on_l2_succeeded() { + let (router, mut rx, pending_sender, subscription_id) = + test_transaction_already_exists_in_db( + ExecutionStatus::Succeeded, + None, + |subscription_id| { + vec![ + serde_json::json!({ + "jsonrpc": "2.0", + "method": "starknet_subscriptionTransactionsStatus", + "params": { + "result": { + "transaction_hash": "0x1", + "status": { + "finality_status": "RECEIVED", + } + }, + "subscription_id": subscription_id + } + }), + serde_json::json!({ + "jsonrpc": "2.0", + "method": "starknet_subscriptionTransactionsStatus", + "params": { + "result": { + "transaction_hash": "0x1", + "status": { + "finality_status": "ACCEPTED_ON_L2", + "execution_status": "SUCCEEDED", + } + }, + "subscription_id": subscription_id + } + }), + ] + }, + ) + .await; + + // Test streaming updates after L2 update from DB. + + // Irrelevant pending update. + pending_sender.send_modify(|pending| { + pending.number = BlockNumber::GENESIS + 1; + }); + + // No message expected. + tokio::time::sleep(Duration::from_millis(100)).await; + assert!(rx.try_recv().is_err()); + + // Irrelevant L2 update. + router + .context + .notifications + .l2_blocks + .send( + Block { + block_number: BlockNumber::GENESIS + 1, + block_hash: BlockHash(Felt::from_u64(1)), + ..Default::default() + } + .into(), + ) + .unwrap(); + + // No message expected. + tokio::time::sleep(Duration::from_millis(100)).await; + assert!(rx.try_recv().is_err()); + + // Update L1. + tokio::task::spawn_blocking({ + let storage = router.context.storage.clone(); + move || { + let mut conn = storage.connection().unwrap(); + let db = conn.transaction().unwrap(); + db.upsert_l1_state(&EthereumStateUpdate { + state_root: Default::default(), + block_number: BlockNumber::GENESIS + 2, + block_hash: Default::default(), + }) + .unwrap(); + db.commit().unwrap(); + } + }) + .await + .unwrap(); + // Streaming update. + router + .context + .notifications + .l2_blocks + .send( + Block { + block_number: BlockNumber::GENESIS + 2, + block_hash: BlockHash(Felt::from_u64(2)), + ..Default::default() + } + .into(), + ) + .unwrap(); + let status = rx.recv().await.unwrap().unwrap(); + let json: serde_json::Value = match status { + Message::Text(json) => serde_json::from_str(&json).unwrap(), + _ => panic!("Expected text message"), + }; + assert_eq!( + json, + serde_json::json!({ + "jsonrpc": "2.0", + "method": "starknet_subscriptionTransactionsStatus", + "params": { + "result": { + "transaction_hash": "0x1", + "status": { + "finality_status": "ACCEPTED_ON_L1", + "execution_status": "SUCCEEDED", + } + }, + "subscription_id": subscription_id, + } + }) + ); + + // No more messages expected. + tokio::time::sleep(Duration::from_millis(100)).await; + assert!(rx.try_recv().is_err()); + } + + #[tokio::test] + async fn transaction_already_exists_in_db_accepted_on_l2_reverted() { + test_transaction_already_exists_in_db( + ExecutionStatus::Reverted { + reason: "tx revert".to_string(), + }, + None, + |subscription_id| { + vec![ + serde_json::json!({ + "jsonrpc": "2.0", + "method": "starknet_subscriptionTransactionsStatus", + "params": { + "result": { + "transaction_hash": "0x1", + "status": { + "finality_status": "RECEIVED", + } + }, + "subscription_id": subscription_id + } + }), + serde_json::json!({ + "jsonrpc": "2.0", + "method": "starknet_subscriptionTransactionsStatus", + "params": { + "result": { + "transaction_hash": "0x1", + "status": { + "finality_status": "ACCEPTED_ON_L2", + "execution_status": "REVERTED", + "failure_reason": "tx revert" + } + }, + "subscription_id": subscription_id + } + }), + ] + }, + ) + .await; + } + + #[tokio::test] + async fn transaction_already_exists_in_db_accepted_on_l1_succeeded() { + test_transaction_already_exists_in_db( + ExecutionStatus::Succeeded, + Some(EthereumStateUpdate { + state_root: Default::default(), + block_number: BlockNumber::GENESIS + 1, + block_hash: Default::default(), + }), + |subscription_id| { + vec![ + serde_json::json!({ + "jsonrpc": "2.0", + "method": "starknet_subscriptionTransactionsStatus", + "params": { + "result": { + "transaction_hash": "0x1", + "status": { + "finality_status": "RECEIVED", + } + }, + "subscription_id": subscription_id + } + }), + serde_json::json!({ + "jsonrpc": "2.0", + "method": "starknet_subscriptionTransactionsStatus", + "params": { + "result": { + "transaction_hash": "0x1", + "status": { + "finality_status": "ACCEPTED_ON_L2", + "execution_status": "SUCCEEDED" + } + }, + "subscription_id": subscription_id + } + }), + serde_json::json!({ + "jsonrpc": "2.0", + "method": "starknet_subscriptionTransactionsStatus", + "params": { + "result": { + "transaction_hash": "0x1", + "status": { + "finality_status": "ACCEPTED_ON_L1", + "execution_status": "SUCCEEDED" + } + }, + "subscription_id": subscription_id + } + }), + ] + }, + ) + .await; + } + + #[tokio::test] + async fn transaction_already_exists_in_db_accepted_on_l1_reverted() { + test_transaction_already_exists_in_db( + ExecutionStatus::Reverted { + reason: "tx revert".to_string(), + }, + Some(EthereumStateUpdate { + state_root: Default::default(), + block_number: BlockNumber::GENESIS + 1, + block_hash: Default::default(), + }), + |subscription_id| { + vec![ + serde_json::json!({ + "jsonrpc": "2.0", + "method": "starknet_subscriptionTransactionsStatus", + "params": { + "result": { + "transaction_hash": "0x1", + "status": { + "finality_status": "RECEIVED", + } + }, + "subscription_id": subscription_id + } + }), + serde_json::json!({ + "jsonrpc": "2.0", + "method": "starknet_subscriptionTransactionsStatus", + "params": { + "result": { + "transaction_hash": "0x1", + "status": { + "finality_status": "ACCEPTED_ON_L2", + "execution_status": "REVERTED", + "failure_reason": "tx revert" + } + }, + "subscription_id": subscription_id + } + }), + serde_json::json!({ + "jsonrpc": "2.0", + "method": "starknet_subscriptionTransactionsStatus", + "params": { + "result": { + "transaction_hash": "0x1", + "status": { + "finality_status": "ACCEPTED_ON_L1", + "execution_status": "REVERTED", + "failure_reason": "tx revert" + } + }, + "subscription_id": subscription_id + } + }), + ] + }, + ) + .await; + } + + #[tokio::test] + async fn transaction_status_streaming() { + test_transaction_status_streaming(|subscription_id| { + vec![ + TestEvent::Pending(PendingData { + block: PendingBlock { + transactions: vec![Transaction { + hash: TransactionHash(Felt::from_u64(2)), + variant: Default::default(), + }], + ..Default::default() + } + .into(), + state_update: Default::default(), + number: BlockNumber::GENESIS + 1, + }), + TestEvent::L2Block( + Block { + block_number: BlockNumber::GENESIS + 1, + block_hash: BlockHash(Felt::from_u64(1)), + ..Default::default() + } + .into(), + ), + TestEvent::Pending(PendingData { + block: PendingBlock { + transactions: vec![Transaction { + hash: TransactionHash(Felt::from_u64(1)), + variant: Default::default(), + }], + ..Default::default() + } + .into(), + state_update: Default::default(), + number: BlockNumber::GENESIS + 2, + }), + TestEvent::L2Block( + Block { + block_number: BlockNumber::GENESIS + 2, + block_hash: BlockHash(Felt::from_u64(2)), + transaction_receipts: vec![( + Receipt { + transaction_hash: TransactionHash(Felt::from_u64(1)), + ..Default::default() + }, + vec![], + )], + ..Default::default() + } + .into(), + ), + TestEvent::Message(serde_json::json!({ + "jsonrpc": "2.0", + "method": "starknet_subscriptionTransactionsStatus", + "params": { + "result": { + "transaction_hash": "0x1", + "status": { + "finality_status": "RECEIVED", + } + }, + "subscription_id": subscription_id + } + })), + TestEvent::Message(serde_json::json!({ + "jsonrpc": "2.0", + "method": "starknet_subscriptionTransactionsStatus", + "params": { + "result": { + "transaction_hash": "0x1", + "status": { + "finality_status": "ACCEPTED_ON_L2", + "execution_status": "SUCCEEDED" + } + }, + "subscription_id": subscription_id + } + })), + // Irrelevant block. + TestEvent::L2Block( + Block { + block_number: BlockNumber::GENESIS + 3, + block_hash: BlockHash(Felt::from_u64(3)), + transaction_receipts: vec![( + Receipt { + transaction_hash: TransactionHash(Felt::from_u64(5)), + ..Default::default() + }, + vec![], + )], + ..Default::default() + } + .into(), + ), + TestEvent::L1State(EthereumStateUpdate { + state_root: Default::default(), + block_number: BlockNumber::GENESIS + 3, + block_hash: Default::default(), + }), + TestEvent::L2Block( + Block { + block_number: BlockNumber::GENESIS + 4, + block_hash: BlockHash(Felt::from_u64(4)), + transaction_receipts: vec![( + Receipt { + transaction_hash: TransactionHash(Felt::from_u64(5)), + ..Default::default() + }, + vec![], + )], + ..Default::default() + } + .into(), + ), + TestEvent::Message(serde_json::json!({ + "jsonrpc": "2.0", + "method": "starknet_subscriptionTransactionsStatus", + "params": { + "result": { + "transaction_hash": "0x1", + "status": { + "finality_status": "ACCEPTED_ON_L1", + "execution_status": "SUCCEEDED" + } + }, + "subscription_id": subscription_id + } + })), + TestEvent::Reorg(Reorg { + first_block_number: BlockNumber::GENESIS + 4, + first_block_hash: BlockHash(Felt::from_u64(4)), + last_block_number: BlockNumber::GENESIS + 5, + last_block_hash: BlockHash(Felt::from_u64(5)), + }), + TestEvent::Message(serde_json::json!({ + "jsonrpc": "2.0", + "method": "starknet_subscriptionReorg", + "params": { + "subscription_id": subscription_id, + "result": { + "first_block_number": 4, + "first_block_hash": "0x4", + "last_block_number": 5, + "last_block_hash": "0x5", + } + } + })), + TestEvent::Message(serde_json::json!({ + "jsonrpc": "2.0", + "method": "starknet_subscriptionTransactionsStatus", + "params": { + "result": { + "transaction_hash": "0x1", + "status": { + "finality_status": "RECEIVED", + } + }, + "subscription_id": subscription_id + } + })), + ] + }) + .await; + } + + async fn test_transaction_status_streaming( + events: impl FnOnce(serde_json::Value) -> Vec, + ) { + let (router, pending_sender) = setup().await; + tokio::task::spawn_blocking({ + let storage = router.context.storage.clone(); + move || { + let mut conn = storage.connection().unwrap(); + let db = conn.transaction().unwrap(); + db.insert_block_header(&BlockHeader { + hash: BlockHash::ZERO, + number: BlockNumber::GENESIS, + parent_hash: BlockHash::ZERO, + ..Default::default() + }) + .unwrap(); + db.commit().unwrap(); + } + }) + .await + .unwrap(); + let tx_hash = TransactionHash(Felt::from_u64(1)); + let (sender_tx, mut sender_rx) = mpsc::channel(1024); + let (receiver_tx, receiver_rx) = mpsc::channel(1024); + handle_json_rpc_socket(router.clone(), sender_tx, receiver_rx); + let params = serde_json::json!( + {"block": {"block_number": 0}, "transaction_hash": tx_hash} + ); + receiver_tx + .send(Ok(Message::Text( + serde_json::json!({ + "jsonrpc": "2.0", + "id": 1, + "method": "starknet_subscribeTransactionStatus", + "params": params + }) + .to_string(), + ))) + .await + .unwrap(); + let res = sender_rx.recv().await.unwrap().unwrap(); + let subscription_id = match res { + Message::Text(json) => { + let mut json: serde_json::Value = serde_json::from_str(&json).unwrap(); + assert_eq!(json["jsonrpc"], "2.0"); + assert_eq!(json["id"], 1); + json["result"]["subscription_id"].take() + } + _ => panic!("Expected text message"), + }; + for event in events(subscription_id) { + match event { + TestEvent::Pending(pending_data) => { + pending_sender.send_modify(|pending| { + *pending = pending_data; + }); + } + TestEvent::L2Block(block) => { + tokio::task::spawn_blocking({ + let storage = router.context.storage.clone(); + let block = block.clone(); + move || { + let mut conn = storage.connection().unwrap(); + let db = conn.transaction().unwrap(); + db.insert_block_header(&BlockHeader { + hash: block.block_hash, + number: block.block_number, + parent_hash: BlockHash(block.block_hash.0 - Felt::from_u64(1)), + ..Default::default() + }) + .unwrap(); + db.commit().unwrap(); + } + }) + .await + .unwrap(); + router + .context + .notifications + .l2_blocks + .send(block.into()) + .unwrap(); + } + TestEvent::Reorg(reorg) => { + router + .context + .notifications + .reorgs + .send(reorg.into()) + .unwrap(); + } + TestEvent::L1State(l1_state) => { + tokio::task::spawn_blocking({ + let storage = router.context.storage.clone(); + move || { + let mut conn = storage.connection().unwrap(); + let db = conn.transaction().unwrap(); + db.upsert_l1_state(&l1_state).unwrap(); + db.commit().unwrap(); + } + }) + .await + .unwrap(); + } + TestEvent::Message(msg) => { + let status = sender_rx.recv().await.unwrap().unwrap(); + let json: serde_json::Value = match status { + Message::Text(json) => serde_json::from_str(&json).unwrap(), + _ => panic!("Expected text message"), + }; + assert_eq!(json, msg); + } + } + } + // No more messages expected. + tokio::time::sleep(Duration::from_millis(100)).await; + assert!(sender_rx.try_recv().is_err()); + } + + async fn test_transaction_already_exists_in_db( + execution_status: ExecutionStatus, + l1_state: Option, + expected: impl FnOnce(SubscriptionId) -> Vec, + ) -> ( + RpcRouter, + mpsc::Receiver>, + tokio::sync::watch::Sender, + SubscriptionId, + ) { + let (router, pending_sender) = setup().await; + let tx_hash = TransactionHash(Felt::from_u64(1)); + let block_number = BlockNumber::new_or_panic(1); + tokio::task::spawn_blocking({ + let storage = router.context.storage.clone(); + move || { + let mut conn = storage.connection().unwrap(); + let db = conn.transaction().unwrap(); + db.insert_block_header(&BlockHeader { + hash: BlockHash::ZERO, + number: BlockNumber::GENESIS, + parent_hash: BlockHash::ZERO, + ..Default::default() + }) + .unwrap(); + db.insert_block_header(&BlockHeader { + hash: BlockHash(Felt::from_u64(1)), + number: block_number, + parent_hash: BlockHash(Felt::from_u64(1)), + ..Default::default() + }) + .unwrap(); + db.insert_transaction_data( + block_number, + &[( + Transaction { + hash: tx_hash, + variant: Default::default(), + }, + Receipt { + transaction_hash: tx_hash, + transaction_index: TransactionIndex::new_or_panic(0), + execution_status, + ..Default::default() + }, + )], + Some(&[vec![]]), + ) + .unwrap(); + if let Some(l1_state) = l1_state { + db.upsert_l1_state(&l1_state).unwrap(); + } + db.commit().unwrap(); + } + }) + .await + .unwrap(); + let (sender_tx, mut sender_rx) = mpsc::channel(1024); + let (receiver_tx, receiver_rx) = mpsc::channel(1024); + handle_json_rpc_socket(router.clone(), sender_tx, receiver_rx); + let params = serde_json::json!( + {"block": {"block_number": 0}, "transaction_hash": tx_hash} + ); + receiver_tx + .send(Ok(Message::Text( + serde_json::json!({ + "jsonrpc": "2.0", + "id": 1, + "method": "starknet_subscribeTransactionStatus", + "params": params + }) + .to_string(), + ))) + .await + .unwrap(); + let res = sender_rx.recv().await.unwrap().unwrap(); + let subscription_id = match res { + Message::Text(json) => { + let mut json: serde_json::Value = serde_json::from_str(&json).unwrap(); + assert_eq!(json["jsonrpc"], "2.0"); + assert_eq!(json["id"], 1); + json["result"]["subscription_id"].take() + } + _ => panic!("Expected text message"), + }; + let subscription_id: SubscriptionId = serde_json::from_value(subscription_id).unwrap(); + for msg in expected(subscription_id) { + let status = sender_rx.recv().await.unwrap().unwrap(); + let json: serde_json::Value = match status { + Message::Text(json) => serde_json::from_str(&json).unwrap(), + _ => panic!("Expected text message"), + }; + assert_eq!(json, msg); + } + // No more messages expected. + tokio::time::sleep(Duration::from_millis(100)).await; + assert!(sender_rx.try_recv().is_err()); + (router, sender_rx, pending_sender, subscription_id) + } + + #[derive(Debug)] + enum TestEvent { + Pending(PendingData), + L2Block(Box), + Reorg(Reorg), + L1State(EthereumStateUpdate), + Message(serde_json::Value), + } + + async fn setup() -> (RpcRouter, tokio::sync::watch::Sender) { + let storage = StorageBuilder::in_memory().unwrap(); + let (pending_data_sender, pending_data) = tokio::sync::watch::channel(Default::default()); + let notifications = Notifications::default(); + let ctx = RpcContext { + cache: Default::default(), + storage, + execution_storage: StorageBuilder::in_memory().unwrap(), + pending_data: PendingWatcher::new(pending_data), + sync_status: SyncState { + status: Syncing::False(false).into(), + } + .into(), + chain_id: ChainId::MAINNET, + sequencer: Client::mainnet(Duration::from_secs(10)), + websocket: None, + notifications, + config: RpcConfig { + batch_concurrency_limit: 1.try_into().unwrap(), + get_events_max_blocks_to_scan: 1.try_into().unwrap(), + get_events_max_uncached_bloom_filters_to_load: 1.try_into().unwrap(), + custom_versioned_constants: None, + }, + }; + (v08::register_routes().build(ctx), pending_data_sender) + } +} diff --git a/crates/rpc/src/v08.rs b/crates/rpc/src/v08.rs index fc48c3986b..ce16255883 100644 --- a/crates/rpc/src/v08.rs +++ b/crates/rpc/src/v08.rs @@ -2,6 +2,7 @@ use crate::jsonrpc::{RpcRouter, RpcRouterBuilder}; use crate::method::subscribe_events::SubscribeEvents; use crate::method::subscribe_new_heads::SubscribeNewHeads; use crate::method::subscribe_pending_transactions::SubscribePendingTransactions; +use crate::method::subscribe_transaction_status::SubscribeTransactionStatus; #[rustfmt::skip] pub fn register_routes() -> RpcRouterBuilder { @@ -30,6 +31,7 @@ pub fn register_routes() -> RpcRouterBuilder { .register("starknet_subscribeNewHeads", SubscribeNewHeads) .register("starknet_subscribePendingTransactions", SubscribePendingTransactions) .register("starknet_subscribeEvents", SubscribeEvents) + .register("starknet_subscribeTransactionStatus", SubscribeTransactionStatus) .register("starknet_specVersion", || "0.8.0-rc0") .register("starknet_syncing", crate::method::syncing) .register("starknet_traceBlockTransactions", crate::method::trace_block_transactions) From 615182618d957e8f812733b5a95d2b7028d84b15 Mon Sep 17 00:00:00 2001 From: Krisztian Kovacs Date: Tue, 22 Oct 2024 16:14:10 +0200 Subject: [PATCH 192/282] chore(rpc/v08): add `starknet_executables.json` OpenRPC spec --- crates/rpc/src/lib.rs | 3 + doc/rpc/v08/starknet_executables.json | 1360 +++++++++++++++++++++++++ 2 files changed, 1363 insertions(+) create mode 100644 doc/rpc/v08/starknet_executables.json diff --git a/crates/rpc/src/lib.rs b/crates/rpc/src/lib.rs index 125f90a641..a7491ffe70 100644 --- a/crates/rpc/src/lib.rs +++ b/crates/rpc/src/lib.rs @@ -906,6 +906,9 @@ mod tests { ])] #[case::v0_8_trace("/rpc/v0_8", "v08/starknet_trace_api_openrpc.json", &[])] #[case::v0_8_write("/rpc/v0_8", "v08/starknet_write_api.json", &[])] + #[case::v0_8_executables("/rpc/v0_8", "v08/starknet_executables.json", &[ + "starknet_getCompiledCasm", + ])] // get_transaction_status is now part of the official spec, so we are phasing it out. #[case::v0_8_pathfinder("/rpc/v0_8", "pathfinder_rpc_api.json", &["pathfinder_version", "pathfinder_getTransactionStatus"])] diff --git a/doc/rpc/v08/starknet_executables.json b/doc/rpc/v08/starknet_executables.json new file mode 100644 index 0000000000..b6d17958f3 --- /dev/null +++ b/doc/rpc/v08/starknet_executables.json @@ -0,0 +1,1360 @@ +{ + "openrpc": "1.0.0", + "info": { + "version": "0.8.0", + "title": "API for getting Starknet executables from nodes that store compiled artifacts", + "license": {} + }, + "servers": [], + "methods": [ + { + "name": "starknet_getCompiledCasm", + "summary": "Get the contract class definition in the given block associated with the given hash", + "params": [ + { + "name": "class_hash", + "description": "The hash of the contract class whose CASM will be returned", + "required": true, + "schema": { + "title": "Field element", + "$ref": "#/components/schemas/FELT" + } + } + ], + "result": { + "name": "result", + "description": "The compiled contract class", + "schema": { + "title": "Starknet get compiled CASM result", + "$ref": "#/components/schemas/CASM_COMPILED_CONTRACT_CLASS" + } + }, + "errors": [ + { + "$ref": "#/components/errors/COMPILATION_ERROR" + }, + { + "$ref": "#/components/errors/CLASS_HASH_NOT_FOUND" + } + ] + } + ], + "components": { + "contentDescriptors": {}, + "schemas": { + "CASM_COMPILED_CONTRACT_CLASS": { + "type": "object", + "properties": { + "entry_points_by_type": { + "title": "Entry points by type", + "type": "object", + "properties": { + "CONSTRUCTOR": { + "type": "array", + "title": "Constructor", + "items": { + "$ref": "#/components/schemas/CASM_ENTRY_POINT" + } + }, + "EXTERNAL": { + "title": "External", + "type": "array", + "items": { + "$ref": "#/components/schemas/CASM_ENTRY_POINT" + } + }, + "L1_HANDLER": { + "title": "L1 handler", + "type": "array", + "items": { + "$ref": "#/components/schemas/CASM_ENTRY_POINT" + } + } + }, + "required": [ + "CONSTRUCTOR", + "EXTERNAL", + "L1_HANDLER" + ] + }, + "bytecode": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FELT" + } + }, + "prime": { + "$ref": "#/components/schemas/NUM_AS_HEX" + }, + "compiler_version": { + "type": "string" + }, + "hints": { + "type": "array", + "items": { + "type": "array", + "description": "2-tuple of pc value and an array of hints to execute", + "items": { + "oneOf": [ + { + "type": "integer" + }, + { + "type": "array", + "items": { + "$ref": "#/components/schemas/HINT" + } + } + ] + }, + "minItems": 2, + "maxItems": 2 + } + } + }, + "bytecode_segment_lengths": { + "type": "array", + "description": "a list of sizes of segments in the bytecode, each segment is hashed individually when computing the bytecode hash", + "items": { + "type": "integer" + } + }, + "required": [ + "prime", + "compiler_version", + "entry_points_by_type", + "bytecode", + "hints" + ] + }, + "CASM_ENTRY_POINT": { + "allOf": [ + { + "$ref": "#/components/schemas/DEPRECATED_CAIRO_ENTRY_POINT" + }, + { + "type": "object", + "properties": { + "builtins": { + "type": "array", + "items": "string" + } + }, + "required": "builtins" + } + ] + }, + "CellRef": { + "title": "CellRef", + "type": "object", + "properties": { + "register": { + "type": "string", + "enum": [ + "AP", + "FP" + ] + }, + "offset": { + "type": "integer" + } + }, + "required": [ + "register", + "offset" + ] + }, + "Deref": { + "type": "object", + "properties": { + "Deref": { + "$ref": "#/components/schemas/CellRef" + } + }, + "required": [ + "Deref" + ] + }, + "DoubleDeref": { + "title": "DoubleDeref", + "type": "object", + "properties": { + "DoubleDeref": { + "title": "DoubleDeref", + "description": "A (CellRef, offset) tuple", + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/components/schemas/CellRef" + }, + { + "type": "integer" + } + ] + }, + "minItems": 2, + "maxItems": 2 + } + }, + "required": [ + "DoubleDeref" + ] + }, + "Immediate": { + "title": "Imeediate", + "type": "object", + "properties": { + "Immediate": { + "$ref": "#/components/schemas/NUM_AS_HEX" + } + }, + "required": [ + "Immediate" + ] + }, + "BinOp": { + "title": "BinOperand", + "type": "object", + "properties": { + "BinOp": { + "op": { + "type": "string", + "enum": [ + "Add", + "Mul" + ] + }, + "a": { + "$ref": "#/components/schemas/CellRef" + }, + "b": { + "oneOf": [ + { + "$ref": "#/components/schemas/Deref" + }, + { + "$ref": "#/components/schemas/Immediate" + } + ] + } + } + }, + "required": [ + "BinOp", + "a", + "b" + ] + }, + "ResOperand": { + "oneOf": [ + { + "$ref": "#/components/schemas/Deref" + }, + { + "$ref": "#/components/schemas/DoubleDeref" + }, + { + "$ref": "#/components/schemas/Immediate" + }, + { + "$ref": "#/components/schemas/BinOp" + } + ] + }, + "HINT": { + "oneOf": [ + { + "$ref": "#/components/schemas/DEPRECATED_HINT" + }, + { + "$ref": "#/components/schemas/CORE_HINT" + }, + { + "$ref": "#/components/schemas/STARKNET_HINT" + } + ] + }, + "DEPRECATED_HINT": { + "oneOf": [ + { + "type": "string", + "title": "AssertCurrentAccessIndicesIsEmpty", + "enum": [ + "AssertCurrentAccessIndicesIsEmpty" + ] + }, + { + "type": "object", + "title": "AssertAllAccessesUsed", + "properties": { + "AssertAllAccessesUsed": { + "type": "object", + "properties": { + "n_used_accesses": { + "$ref": "#/components/schemas/CellRef" + } + }, + "required": [ + "n_used_accesses" + ] + } + }, + "required": [ + "AssertAllAccessesUsed" + ] + }, + { + "type": "string", + "title": "AssertAllKeysUsed", + "enum": [ + "AssertAllKeysUsed" + ] + }, + { + "type": "string", + "title": "AssertLeAssertThirdArcExcluded", + "enum": [ + "AssertLeAssertThirdArcExcluded" + ] + }, + { + "type": "object", + "title": "AssertLtAssertValidInput", + "properties": { + "AssertLtAssertValidInput": { + "type": "object", + "properties": { + "a": { + "$ref": "#/components/schemas/ResOperand" + }, + "b": { + "$ref": "#/components/schemas/ResOperand" + } + }, + "required": [ + "a", + "b" + ] + } + }, + "required": [ + "AssertLtAssertValidInput" + ] + }, + { + "type": "object", + "title": "Felt252DictRead", + "properties": { + "Felt252DictRead": { + "type": "object", + "properties": { + "dict_ptr": { + "$ref": "#/components/schemas/ResOperand" + }, + "key": { + "$ref": "#/components/schemas/ResOperand" + }, + "value_dst": { + "$ref": "#/components/schemas/CellRef" + } + }, + "required": [ + "dict_ptr", + "key", + "value_dst" + ] + } + }, + "required": [ + "Felt252DictRead" + ] + }, + { + "type": "object", + "title": "Felt252DictWrite", + "properties": { + "Felt252DictWrite": { + "type": "object", + "properties": { + "dict_ptr": { + "$ref": "#/components/schemas/ResOperand" + }, + "key": { + "$ref": "#/components/schemas/ResOperand" + }, + "value": { + "$ref": "#/components/schemas/ResOperand" + } + }, + "required": [ + "dict_ptr", + "key", + "value" + ] + } + }, + "required": [ + "Felt252DictWrite" + ] + } + ] + }, + "CORE_HINT": { + "oneOf": [ + { + "type": "object", + "title": "AllocSegment", + "properties": { + "AllocSegment": { + "type": "object", + "properties": { + "dst": { + "$ref": "#/components/schemas/CellRef" + } + }, + "required": [ + "dst" + ] + } + }, + "required": [ + "AllocSegment" + ] + }, + { + "type": "object", + "title": "TestLessThan", + "properties": { + "TestLessThan": { + "type": "object", + "properties": { + "lhs": { + "$ref": "#/components/schemas/ResOperand" + }, + "rhs": { + "$ref": "#/components/schemas/ResOperand" + }, + "dst": { + "$ref": "#/components/schemas/CellRef" + } + }, + "required": [ + "lhs", + "rhs", + "dst" + ] + } + }, + "required": [ + "TestLessThan" + ] + }, + { + "type": "object", + "title": "TestLessThanOrEqual", + "properties": { + "TestLessThanOrEqual": { + "type": "object", + "properties": { + "lhs": { + "$ref": "#/components/schemas/ResOperand" + }, + "rhs": { + "$ref": "#/components/schemas/ResOperand" + }, + "dst": { + "$ref": "#/components/schemas/CellRef" + } + }, + "required": [ + "lhs", + "rhs", + "dst" + ] + } + }, + "required": [ + "TestLessThanOrEqual" + ] + }, + { + "type": "object", + "title": "TestLessThanOrEqualAddress", + "properties": { + "TestLessThanOrEqualAddress": { + "type": "object", + "properties": { + "lhs": { + "$ref": "#/components/schemas/ResOperand" + }, + "rhs": { + "$ref": "#/components/schemas/ResOperand" + }, + "dst": { + "$ref": "#/components/schemas/CellRef" + } + }, + "required": [ + "lhs", + "rhs", + "dst" + ] + } + }, + "required": [ + "TestLessThanOrEqualAddress" + ] + }, + { + "type": "object", + "title": "WideMul128", + "properties": { + "WideMul128": { + "type": "object", + "properties": { + "lhs": { + "$ref": "#/components/schemas/ResOperand" + }, + "rhs": { + "$ref": "#/components/schemas/ResOperand" + }, + "high": { + "$ref": "#/components/schemas/CellRef" + }, + "low": { + "$ref": "#/components/schemas/CellRef" + } + }, + "required": [ + "lhs", + "rhs", + "high", + "low" + ] + } + }, + "required": [ + "WideMul128" + ] + }, + { + "type": "object", + "title": "DivMod", + "properties": { + "DivMod": { + "type": "object", + "properties": { + "lhs": { + "$ref": "#/components/schemas/ResOperand" + }, + "rhs": { + "$ref": "#/components/schemas/ResOperand" + }, + "quotient": { + "$ref": "#/components/schemas/CellRef" + }, + "remainder": { + "$ref": "#/components/schemas/CellRef" + } + }, + "required": [ + "lhs", + "rhs", + "quotient", + "remainder" + ] + } + }, + "required": [ + "DivMod" + ] + }, + { + "type": "object", + "title": "Uint256DivMod", + "properties": { + "Uint256DivMod": { + "type": "object", + "properties": { + "dividend0": { + "$ref": "#/components/schemas/ResOperand" + }, + "dividend1": { + "$ref": "#/components/schemas/ResOperand" + }, + "divisor0": { + "$ref": "#/components/schemas/ResOperand" + }, + "divisor1": { + "$ref": "#/components/schemas/ResOperand" + }, + "quotient0": { + "$ref": "#/components/schemas/CellRef" + }, + "quotient1": { + "$ref": "#/components/schemas/CellRef" + }, + "remainder0": { + "$ref": "#/components/schemas/CellRef" + }, + "remainder1": { + "$ref": "#/components/schemas/CellRef" + } + }, + "required": [ + "dividend0", + "dividend1", + "divisor0", + "divisor1", + "quotient0", + "quotient1", + "remainder0", + "remainder1" + ] + } + }, + "required": [ + "Uint256DivMod" + ] + }, + { + "type": "object", + "title": "Uint512DivModByUint256", + "properties": { + "Uint512DivModByUint256": { + "type": "object", + "properties": { + "dividend0": { + "$ref": "#/components/schemas/ResOperand" + }, + "dividend1": { + "$ref": "#/components/schemas/ResOperand" + }, + "dividend2": { + "$ref": "#/components/schemas/ResOperand" + }, + "dividend3": { + "$ref": "#/components/schemas/ResOperand" + }, + "divisor0": { + "$ref": "#/components/schemas/ResOperand" + }, + "divisor1": { + "$ref": "#/components/schemas/ResOperand" + }, + "quotient0": { + "$ref": "#/components/schemas/CellRef" + }, + "quotient1": { + "$ref": "#/components/schemas/CellRef" + }, + "quotient2": { + "$ref": "#/components/schemas/CellRef" + }, + "quotient3": { + "$ref": "#/components/schemas/CellRef" + }, + "remainder0": { + "$ref": "#/components/schemas/CellRef" + }, + "remainder1": { + "$ref": "#/components/schemas/CellRef" + } + }, + "required": [ + "dividend0", + "dividend1", + "dividend2", + "dividend3", + "divisor0", + "divisor1", + "quotient0", + "quotient1", + "quotient2", + "quotient3", + "remainder0", + "remainder1" + ] + } + }, + "required": [ + "Uint512DivModByUint256" + ] + }, + { + "type": "object", + "title": "SquareRoot", + "properties": { + "SquareRoot": { + "type": "object", + "properties": { + "value": { + "$ref": "#/components/schemas/ResOperand" + }, + "dst": { + "$ref": "#/components/schemas/CellRef" + } + }, + "required": [ + "value", + "dst" + ] + } + }, + "required": [ + "SquareRoot" + ] + }, + { + "type": "object", + "title": "Uint256SquareRoot", + "properties": { + "Uint256SquareRoot": { + "type": "object", + "properties": { + "value_low": { + "$ref": "#/components/schemas/ResOperand" + }, + "value_high": { + "$ref": "#/components/schemas/ResOperand" + }, + "sqrt0": { + "$ref": "#/components/schemas/CellRef" + }, + "sqrt1": { + "$ref": "#/components/schemas/CellRef" + }, + "remainder_low": { + "$ref": "#/components/schemas/CellRef" + }, + "remainder_high": { + "$ref": "#/components/schemas/CellRef" + }, + "sqrt_mul_2_minus_remainder_ge_u128": { + "$ref": "#/components/schemas/CellRef" + } + }, + "required": [ + "value_low", + "value_high", + "sqrt0", + "sqrt1", + "remainder_low", + "remainder_high", + "sqrt_mul_2_minus_remainder_ge_u128" + ] + } + }, + "required": [ + "Uint256SquareRoot" + ] + }, + { + "type": "object", + "title": "LinearSplit", + "properties": { + "LinearSplit": { + "type": "object", + "properties": { + "value": { + "$ref": "#/components/schemas/ResOperand" + }, + "scalar": { + "$ref": "#/components/schemas/ResOperand" + }, + "max_x": { + "$ref": "#/components/schemas/ResOperand" + }, + "x": { + "$ref": "#/components/schemas/CellRef" + }, + "y": { + "$ref": "#/components/schemas/CellRef" + } + }, + "required": [ + "value", + "scalar", + "max_x", + "x", + "y" + ] + } + }, + "required": [ + "LinearSplit" + ] + }, + { + "type": "object", + "title": "AllocFelt252Dict", + "properties": { + "AllocFelt252Dict": { + "type": "object", + "properties": { + "segment_arena_ptr": { + "$ref": "#/components/schemas/ResOperand" + } + }, + "required": [ + "segment_arena_ptr" + ] + } + }, + "required": [ + "AllocFelt252Dict" + ] + }, + { + "type": "object", + "title": "Felt252DictEntryInit", + "properties": { + "Felt252DictEntryInit": { + "type": "object", + "properties": { + "dict_ptr": { + "$ref": "#/components/schemas/ResOperand" + }, + "key": { + "$ref": "#/components/schemas/ResOperand" + } + }, + "required": [ + "dict_ptr", + "key" + ] + } + }, + "required": [ + "Felt252DictEntryInit" + ] + }, + { + "type": "object", + "title": "Felt252DictEntryUpdate", + "properties": { + "Felt252DictEntryUpdate": { + "type": "object", + "properties": { + "dict_ptr": { + "$ref": "#/components/schemas/ResOperand" + }, + "value": { + "$ref": "#/components/schemas/ResOperand" + } + }, + "required": [ + "dict_ptr", + "value" + ] + } + }, + "required": [ + "Felt252DictEntryUpdate" + ] + }, + { + "type": "object", + "title": "GetSegmentArenaIndex", + "properties": { + "GetSegmentArenaIndex": { + "type": "object", + "properties": { + "dict_end_ptr": { + "$ref": "#/components/schemas/ResOperand" + }, + "dict_index": { + "$ref": "#/components/schemas/CellRef" + } + }, + "required": [ + "dict_end_ptr", + "dict_index" + ] + } + }, + "required": [ + "GetSegmentArenaIndex" + ] + }, + { + "type": "object", + "title": "InitSquashData", + "properties": { + "InitSquashData": { + "type": "object", + "properties": { + "dict_access": { + "$ref": "#/components/schemas/ResOperand" + }, + "ptr_diff": { + "$ref": "#/components/schemas/ResOperand" + }, + "n_accesses": { + "$ref": "#/components/schemas/ResOperand" + }, + "big_keys": { + "$ref": "#/components/schemas/CellRef" + }, + "first_key": { + "$ref": "#/components/schemas/CellRef" + } + }, + "required": [ + "dict_access", + "ptr_diff", + "n_accesses", + "big_keys", + "first_key" + ] + } + }, + "required": [ + "InitSquashData" + ] + }, + { + "type": "object", + "title": "GetCurrentAccessIndex", + "properties": { + "GetCurrentAccessIndex": { + "type": "object", + "properties": { + "range_check_ptr": { + "$ref": "#/components/schemas/ResOperand" + } + }, + "required": [ + "range_check_ptr" + ] + } + }, + "required": [ + "GetCurrentAccessIndex" + ] + }, + { + "type": "object", + "title": "ShouldSkipSquashLoop", + "properties": { + "ShouldSkipSquashLoop": { + "type": "object", + "properties": { + "should_skip_loop": { + "$ref": "#/components/schemas/CellRef" + } + }, + "required": [ + "should_skip_loop" + ] + } + }, + "required": [ + "ShouldSkipSquashLoop" + ] + }, + { + "type": "object", + "title": "GetCurrentAccessDelta", + "properties": { + "GetCurrentAccessDelta": { + "type": "object", + "properties": { + "index_delta_minus1": { + "$ref": "#/components/schemas/CellRef" + } + }, + "required": [ + "index_delta_minus1" + ] + } + }, + "required": [ + "GetCurrentAccessDelta" + ] + }, + { + "type": "object", + "title": "ShouldContinueSquashLoop", + "properties": { + "ShouldContinueSquashLoop": { + "type": "object", + "properties": { + "should_continue": { + "$ref": "#/components/schemas/CellRef" + } + }, + "required": [ + "should_continue" + ] + } + }, + "required": [ + "ShouldContinueSquashLoop" + ] + }, + { + "type": "object", + "title": "GetNextDictKey", + "properties": { + "GetNextDictKey": { + "type": "object", + "properties": { + "next_key": { + "$ref": "#/components/schemas/CellRef" + } + }, + "required": [ + "next_key" + ] + } + }, + "required": [ + "GetNextDictKey" + ] + }, + { + "type": "object", + "title": "AssertLeFindSmallArcs", + "properties": { + "AssertLeFindSmallArcs": { + "type": "object", + "properties": { + "range_check_ptr": { + "$ref": "#/components/schemas/ResOperand" + }, + "a": { + "$ref": "#/components/schemas/ResOperand" + }, + "b": { + "$ref": "#/components/schemas/ResOperand" + } + }, + "required": [ + "range_check_ptr", + "a", + "b" + ] + } + }, + "required": [ + "AssertLeFindSmallArcs" + ] + }, + { + "type": "object", + "title": "AssertLeIsFirstArcExcluded", + "properties": { + "AssertLeIsFirstArcExcluded": { + "type": "object", + "properties": { + "skip_exclude_a_flag": { + "$ref": "#/components/schemas/CellRef" + } + }, + "required": [ + "skip_exclude_a_flag" + ] + } + }, + "required": [ + "AssertLeIsFirstArcExcluded" + ] + }, + { + "type": "object", + "title": "AssertLeIsSecondArcExcluded", + "properties": { + "AssertLeIsSecondArcExcluded": { + "type": "object", + "properties": { + "skip_exclude_b_minus_a": { + "$ref": "#/components/schemas/CellRef" + } + }, + "required": [ + "skip_exclude_b_minus_a" + ] + } + }, + "required": [ + "AssertLeIsSecondArcExcluded" + ] + }, + { + "type": "object", + "title": "RandomEcPoint", + "properties": { + "RandomEcPoint": { + "type": "object", + "properties": { + "x": { + "$ref": "#/components/schemas/CellRef" + }, + "y": { + "$ref": "#/components/schemas/CellRef" + } + }, + "required": [ + "x", + "y" + ] + } + }, + "required": [ + "RandomEcPoint" + ] + }, + { + "type": "object", + "title": "FieldSqrt", + "properties": { + "FieldSqrt": { + "type": "object", + "properties": { + "val": { + "$ref": "#/components/schemas/ResOperand" + }, + "sqrt": { + "$ref": "#/components/schemas/CellRef" + } + }, + "required": [ + "val", + "sqrt" + ] + } + }, + "required": [ + "FieldSqrt" + ] + }, + { + "type": "object", + "title": "DebugPrint", + "properties": { + "DebugPrint": { + "type": "object", + "properties": { + "start": { + "$ref": "#/components/schemas/ResOperand" + }, + "end": { + "$ref": "#/components/schemas/ResOperand" + } + }, + "required": [ + "start", + "end" + ] + } + }, + "required": [ + "DebugPrint" + ] + }, + { + "type": "object", + "title": "AllocConstantSize", + "properties": { + "AllocConstantSize": { + "type": "object", + "properties": { + "size": { + "$ref": "#/components/schemas/ResOperand" + }, + "dst": { + "$ref": "#/components/schemas/CellRef" + } + }, + "required": [ + "size", + "dst" + ] + } + }, + "required": [ + "AllocConstantSize" + ] + }, + { + "type": "object", + "title": "U256InvModN", + "properties": { + "U256InvModN": { + "type": "object", + "properties": { + "b0": { + "$ref": "#/components/schemas/ResOperand" + }, + "b1": { + "$ref": "#/components/schemas/ResOperand" + }, + "n0": { + "$ref": "#/components/schemas/ResOperand" + }, + "n1": { + "$ref": "#/components/schemas/ResOperand" + }, + "g0_or_no_inv": { + "$ref": "#/components/schemas/CellRef" + }, + "g1_option": { + "$ref": "#/components/schemas/CellRef" + }, + "s_or_r0": { + "$ref": "#/components/schemas/CellRef" + }, + "s_or_r1": { + "$ref": "#/components/schemas/CellRef" + }, + "t_or_k0": { + "$ref": "#/components/schemas/CellRef" + }, + "t_or_k1": { + "$ref": "#/components/schemas/CellRef" + } + }, + "required": [ + "b0", + "b1", + "n0", + "n1", + "g0_or_no_inv", + "g1_option", + "s_or_r0", + "s_or_r1", + "t_or_k0", + "t_or_k1" + ] + } + }, + "required": [ + "U256InvModN" + ] + }, + { + "type": "object", + "title": "EvalCircuit", + "properties": { + "EvalCircuit": { + "type": "object", + "properties": { + "n_add_mods": { + "$ref": "#/components/schemas/ResOperand" + }, + "add_mod_builtin": { + "$ref": "#/components/schemas/ResOperand" + }, + "n_mul_mods": { + "$ref": "#/components/schemas/ResOperand" + }, + "mul_mod_builtin": { + "$ref": "#/components/schemas/ResOperand" + } + }, + "required": [ + "n_add_mods", + "add_mod_builtin", + "n_mul_mods", + "mul_mod_builtin" + ] + } + }, + "required": [ + "EvalCircuit" + ] + } + ] + }, + "STARKNET_HINT": { + "oneOf": [ + { + "type": "object", + "title": "SystemCall", + "properties": { + "SystemCall": { + "type": "object", + "properties": { + "system": { + "$ref": "#/components/schemas/ResOperand" + } + }, + "required": [ + "system" + ] + } + }, + "required": [ + "SystemCall" + ] + }, + { + "type": "object", + "title": "Cheatcode", + "properties": { + "Cheatcode": { + "type": "object", + "properties": { + "selector": { + "$ref": "#/components/schemas/NUM_AS_HEX" + }, + "input_start": { + "$ref": "#/components/schemas/ResOperand" + }, + "input_end": { + "$ref": "#/components/schemas/ResOperand" + }, + "output_start": { + "$ref": "#/components/schemas/CellRef" + }, + "output_end": { + "$ref": "#/components/schemas/CellRef" + } + }, + "required": [ + "selector", + "input_start", + "input_end", + "output_start", + "output_end" + ] + } + }, + "required": [ + "Cheatcode" + ] + } + ] + }, + "FELT": { + "$ref": "./starknet_api_openrpc.json#/components/schemas/FELT" + }, + "NUM_AS_HEX": { + "$ref": "./starknet_api_openrpc.json#/components/schemas/NUM_AS_HEX" + }, + "DEPRECATED_CAIRO_ENTRY_POINT": { + "$ref": "./starknet_api_openrpc.json#/components/schemas/DEPRECATED_CAIRO_ENTRY_POINT" + } + }, + "errors": { + "CLASS_HASH_NOT_FOUND": { + "$ref": "./api/starknet_api_openrpc.json#/components/errors/CLASS_HASH_NOT_FOUND" + }, + "COMPILATION_ERROR": { + "data": { + "type": "object", + "description": "More data about the compilation failure", + "properties": { + "compilation_error": { + "title": "compilation error", + "type": "string" + } + }, + "required": "compilation_error" + } + } + } + } +} From 996ccb88b7c576fcf3d71d96584ddc4bcdf92432 Mon Sep 17 00:00:00 2001 From: t00ts Date: Mon, 14 Oct 2024 18:50:21 +0400 Subject: [PATCH 193/282] feat: introduce rpc `starknet_getMessagesStatus` --- crates/common/src/l1.rs | 111 ++++++++++++ crates/common/src/lib.rs | 4 +- crates/common/src/message.rs | 8 - crates/ethereum/Cargo.toml | 1 - crates/ethereum/src/lib.rs | 171 ++++++++++++------ crates/pathfinder/src/bin/pathfinder/main.rs | 2 + crates/pathfinder/src/state/sync.rs | 11 +- crates/pathfinder/src/state/sync/l1.rs | 18 +- crates/rpc/src/context.rs | 24 ++- crates/rpc/src/dto/primitives.rs | 13 +- crates/rpc/src/jsonrpc/router/subscription.rs | 5 + crates/rpc/src/lib.rs | 1 - crates/rpc/src/method.rs | 2 + crates/rpc/src/method/get_messages_status.rs | 113 ++++++++++++ .../rpc/src/method/get_transaction_status.rs | 6 + crates/rpc/src/method/subscribe_events.rs | 5 + crates/rpc/src/method/subscribe_new_heads.rs | 5 + .../method/subscribe_pending_transactions.rs | 5 + .../method/subscribe_transaction_status.rs | 6 +- crates/rpc/src/v08.rs | 1 + crates/storage/src/params.rs | 2 + 21 files changed, 423 insertions(+), 91 deletions(-) create mode 100644 crates/common/src/l1.rs delete mode 100644 crates/common/src/message.rs create mode 100644 crates/rpc/src/method/get_messages_status.rs diff --git a/crates/common/src/l1.rs b/crates/common/src/l1.rs new file mode 100644 index 0000000000..ecfa506ffd --- /dev/null +++ b/crates/common/src/l1.rs @@ -0,0 +1,111 @@ +use pathfinder_crypto::Felt; +use primitive_types::H256; + +use crate::macros; + +/// An Ethereum block number. +#[derive(Copy, Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct L1BlockNumber(u64); + +macros::fmt::thin_display!(L1BlockNumber); + +macros::i64_backed_u64::new_get_partialeq!(L1BlockNumber); +macros::i64_backed_u64::serdes!(L1BlockNumber); + +impl From for Felt { + fn from(x: L1BlockNumber) -> Self { + Felt::from(x.0) + } +} + +impl std::iter::Iterator for L1BlockNumber { + type Item = L1BlockNumber; + + fn next(&mut self) -> Option { + Some(*self + 1) + } +} + +impl L1BlockNumber { + pub const GENESIS: L1BlockNumber = L1BlockNumber::new_or_panic(0); +} + +impl std::ops::Add for L1BlockNumber { + type Output = L1BlockNumber; + + fn add(self, rhs: u64) -> Self::Output { + Self(self.0 + rhs) + } +} + +impl std::ops::AddAssign for L1BlockNumber { + fn add_assign(&mut self, rhs: u64) { + self.0 += rhs; + } +} + +impl std::ops::Sub for L1BlockNumber { + type Output = L1BlockNumber; + + fn sub(self, rhs: u64) -> Self::Output { + Self(self.0 - rhs) + } +} + +impl std::ops::SubAssign for L1BlockNumber { + fn sub_assign(&mut self, rhs: u64) { + self.0 -= rhs; + } +} + +/// An Ethereum transaction hash. +#[derive(Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct L1TransactionHash(H256); + +macros::fmt::thin_display!(L1TransactionHash); +macros::fmt::thin_debug!(L1TransactionHash); + +impl L1TransactionHash { + /// Creates a new `L1TransactionHash` from a `H256`. + pub fn new(hash: H256) -> Self { + Self(hash) + } + + /// Returns the raw bytes of the transaction hash. + pub fn as_bytes(&self) -> &[u8] { + self.0.as_bytes() + } + + /// Creates a new `L1TransactionHash` from a slice of bytes. + /// + /// # Panics + /// + /// If the length of the byte slice is not 32. + pub fn from_slice(bytes: &[u8]) -> Self { + Self(H256::from_slice(bytes)) + } +} + +impl From for L1TransactionHash { + fn from(hash: H256) -> Self { + Self(hash) + } +} + +impl From for H256 { + fn from(tx_hash: L1TransactionHash) -> Self { + tx_hash.0 + } +} + +impl From<[u8; 32]> for L1TransactionHash { + fn from(bytes: [u8; 32]) -> Self { + Self(H256::from(bytes)) + } +} + +impl AsRef<[u8]> for L1TransactionHash { + fn as_ref(&self) -> &[u8] { + self.0.as_ref() + } +} diff --git a/crates/common/src/lib.rs b/crates/common/src/lib.rs index dd055c14de..8e5bf802d7 100644 --- a/crates/common/src/lib.rs +++ b/crates/common/src/lib.rs @@ -20,8 +20,8 @@ pub mod error; pub mod event; pub mod hash; mod header; +mod l1; mod macros; -pub mod message; pub mod prelude; pub mod receipt; pub mod signature; @@ -31,7 +31,7 @@ pub mod transaction; pub mod trie; pub use header::{BlockHeader, BlockHeaderBuilder, L1DataAvailabilityMode, SignedBlockHeader}; -pub use message::L1ToL2MessageLog; +pub use l1::{L1BlockNumber, L1TransactionHash}; pub use signature::BlockCommitmentSignature; pub use state_update::StateUpdate; diff --git a/crates/common/src/message.rs b/crates/common/src/message.rs deleted file mode 100644 index 6a3ede47ed..0000000000 --- a/crates/common/src/message.rs +++ /dev/null @@ -1,8 +0,0 @@ -use primitive_types::H256; - -/// An L1 -> L2 message hash with the L1 tx hash where it was sent -#[derive(Debug, Clone)] -pub struct L1ToL2MessageLog { - pub message_hash: H256, - pub l1_tx_hash: H256, -} diff --git a/crates/ethereum/Cargo.toml b/crates/ethereum/Cargo.toml index aedcef4e8f..484dd140fd 100644 --- a/crates/ethereum/Cargo.toml +++ b/crates/ethereum/Cargo.toml @@ -27,4 +27,3 @@ serde_json = { workspace = true } tokio = { workspace = true, features = ["macros"] } tracing = { workspace = true } -[dev-dependencies] diff --git a/crates/ethereum/src/lib.rs b/crates/ethereum/src/lib.rs index d54c67228d..f12e435c88 100644 --- a/crates/ethereum/src/lib.rs +++ b/crates/ethereum/src/lib.rs @@ -2,14 +2,27 @@ use std::collections::BTreeMap; use std::future::Future; use std::time::Duration; -use alloy::eips::{BlockId, BlockNumberOrTag, RpcBlockHash}; -use alloy::primitives::{Address, B256}; +use alloy::eips::{BlockId, BlockNumberOrTag}; +use alloy::primitives::{Address, TxHash}; use alloy::providers::{Provider, ProviderBuilder, WsConnect}; -use alloy::rpc::types::Log; +use alloy::rpc::types::{FilteredParams, Log}; use anyhow::Context; use futures::StreamExt; -use pathfinder_common::{BlockHash, BlockNumber, EthereumChain, L1ToL2MessageLog, StateCommitment}; -use primitive_types::{H160, H256, U256}; +use pathfinder_common::transaction::L1HandlerTransaction; +use pathfinder_common::{ + BlockHash, + BlockNumber, + CallParam, + ContractAddress, + EntryPoint, + EthereumChain, + L1BlockNumber, + L1TransactionHash, + StateCommitment, + TransactionNonce, +}; +use pathfinder_crypto::Felt; +use primitive_types::{H160, U256}; use reqwest::{IntoUrl, Url}; use starknet::StarknetCoreContract; use tokio::select; @@ -36,13 +49,6 @@ pub mod core_addr { Decoder::Hex.decode(b"4737c0c1B4D5b1A687B42610DdabEE781152359c"); } -/// Events that can be emitted by the Ethereum client -#[derive(Debug)] -pub enum EthereumEvent { - StateUpdate(EthereumStateUpdate), - MessageLog(L1ToL2MessageLog), -} - /// State update from Ethereum #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub struct EthereumStateUpdate { @@ -56,14 +62,19 @@ pub struct EthereumStateUpdate { pub trait EthereumApi { async fn get_starknet_state(&self, address: &H160) -> anyhow::Result; async fn get_chain(&self) -> anyhow::Result; - async fn listen( + async fn get_l1_handler_txs( + &self, + address: &H160, + tx_hash: &L1TransactionHash, + ) -> anyhow::Result>; + async fn sync_and_listen( &mut self, address: &H160, poll_interval: Duration, callback: F, ) -> anyhow::Result<()> where - F: Fn(EthereumEvent) -> Fut + Send + 'static, + F: Fn(EthereumStateUpdate) -> Fut + Send + 'static, Fut: Future + Send + 'static; } @@ -71,7 +82,7 @@ pub trait EthereumApi { #[derive(Clone, Debug)] pub struct EthereumClient { url: Url, - pending_state_updates: BTreeMap, + pending_state_updates: BTreeMap, } impl EthereumClient { @@ -91,20 +102,16 @@ impl EthereumClient { Self::new(url) } - /// Returns the hash of the last finalized block - async fn get_finalized_block_hash(&self) -> anyhow::Result { + /// Returns the block number of the last finalized block + async fn get_finalized_block_number(&self) -> anyhow::Result { // Create a WebSocket connection let ws = WsConnect::new(self.url.clone()); let provider = ProviderBuilder::new().on_ws(ws).await?; - - // Fetch the finalized block hash + // Fetch the finalized block number provider .get_block_by_number(BlockNumberOrTag::Finalized, false) .await? - .map(|block| { - let block_hash: [u8; 32] = block.header.hash.into(); - H256::from(block_hash) - }) + .map(|block| L1BlockNumber::new_or_panic(block.header.number)) .context("Failed to fetch finalized block hash") } } @@ -114,29 +121,27 @@ impl EthereumApi for EthereumClient { /// Listens for Ethereum events and notifies the caller using the provided /// callback. State updates will only be emitted once they belong to a /// finalized block. - async fn listen( + async fn sync_and_listen( &mut self, address: &H160, poll_interval: Duration, callback: F, ) -> anyhow::Result<()> where - F: Fn(EthereumEvent) -> Fut + Send + 'static, + F: Fn(EthereumStateUpdate) -> Fut + Send + 'static, Fut: Future + Send + 'static, { // Create a WebSocket connection let ws = WsConnect::new(self.url.clone()); let provider = ProviderBuilder::new().on_ws(ws).await?; - // Create the StarknetCoreContract instance - let address = Address::new((*address).into()); - let core_contract = StarknetCoreContract::new(address, provider.clone()); + // Fetch the current Starknet state from Ethereum + let state_update = self.get_starknet_state(address).await?; + let _ = callback(state_update).await; - // Listen for L1 to L2 message events - let mut logs = provider - .subscribe_logs(&core_contract.LogMessageToL2_filter().filter) - .await? - .into_stream(); + // Create the StarknetCoreContract instance + let core_address = Address::new((*address).into()); + let core_contract = StarknetCoreContract::new(core_address, provider.clone()); // Listen for state update events let mut state_updates = provider @@ -147,7 +152,7 @@ impl EthereumApi for EthereumClient { // Poll regularly for finalized block number let provider_clone = provider.clone(); let (finalized_block_tx, mut finalized_block_rx) = - tokio::sync::mpsc::channel::(1); + tokio::sync::mpsc::channel::(1); tokio::spawn(async move { let mut interval = tokio::time::interval(poll_interval); loop { @@ -156,18 +161,20 @@ impl EthereumApi for EthereumClient { .get_block_by_number(BlockNumberOrTag::Finalized, false) .await { - let block_number = BlockNumber::new_or_panic(finalized_block.header.number); + let block_number = L1BlockNumber::new_or_panic(finalized_block.header.number); let _ = finalized_block_tx.send(block_number).await.unwrap(); } } }); - // Add the finalized block stream to the select! macro + // Process incoming events loop { select! { Some(state_update) = state_updates.next() => { // Decode the state update - let eth_block = state_update.block_number.expect("missing eth block number"); + let eth_block = L1BlockNumber::new_or_panic( + state_update.block_number.expect("missing eth block number") + ); let state_update: Log = state_update.log_decode()?; let block_number = get_block_number(state_update.inner.blockNumber); // Add or remove to/from pending state updates accordingly @@ -182,34 +189,91 @@ impl EthereumApi for EthereumClient { self.pending_state_updates.remove(ð_block); } } - Some(log) = logs.next() => { - // Decode the message - let log: Log = log.log_decode()?; - // Create L1ToL2MessageHash from the log data - let msg = L1ToL2MessageLog { - message_hash: H256::from(log.inner.message_hash().to_be_bytes()), - l1_tx_hash: log.transaction_hash.map(|hash| H256::from(hash.0)).unwrap_or_default(), - }; - // Emit the message log - callback(EthereumEvent::MessageLog(msg)).await; - } Some(block_number) = finalized_block_rx.recv() => { // Collect all state updates up to (and including) the finalized block let pending_state_updates: Vec = self.pending_state_updates - .range(..=block_number.get()) + .range(..=block_number) .map(|(_, &update)| update) .collect(); // Remove emitted updates from the map - self.pending_state_updates.retain(|&k, _| k > block_number.get()); + self.pending_state_updates.retain(|&k, _| k > block_number); // Emit the state updates for state_update in pending_state_updates { - callback(EthereumEvent::StateUpdate(state_update)).await; + let _ = callback(state_update).await; } } } } } + async fn get_l1_handler_txs( + &self, + address: &H160, + tx_hash: &L1TransactionHash, + ) -> anyhow::Result> { + // Create a WebSocket connection + let ws = WsConnect::new(self.url.clone()); + let provider = ProviderBuilder::new().on_ws(ws).await?; + + let core_address = Address::new((*address).into()); + let core_contract = StarknetCoreContract::new(core_address, provider.clone()); + let filter = FilteredParams::new(Some(core_contract.LogMessageToL2_filter().filter)); + + let tx_hash = TxHash::from_slice(tx_hash.as_bytes()); + if let Some(receipt) = provider.get_transaction_receipt(tx_hash).await? { + let logs: Vec> = receipt + .inner + .logs() + .iter() + .filter(|log| { + filter.filter_address(&log.address()) && filter.filter_topics(log.topics()) + }) + .filter_map(|log| { + log.log_decode::() + .ok() + }) + .collect(); + + let mut l1_handler_txs = Vec::new(); + for log in logs { + let nonce: [u8; 32] = log.inner.nonce.to_be_bytes(); + let to_addr: [u8; 32] = log.inner.toAddress.to_be_bytes(); + let from_addr: [u8; 20] = log.inner.fromAddress.0.into(); + let selector: [u8; 32] = log.inner.selector.to_be_bytes(); + + let felt_nonce = Felt::from(nonce); + let felt_to_addr = Felt::from(to_addr); + let felt_from_addr = Felt::from_be_slice(&from_addr)?; + let felt_selector = Felt::from(selector); + + let payload: Vec = log + .inner + .payload + .iter() + .map(|p| p.to_be_bytes::<32>()) + .map(Felt::from) + .map(CallParam) + .collect(); + + let mut call_data: Vec = vec![CallParam(felt_from_addr)]; + call_data.extend(payload); + + // Create the L1HandlerTransaction + let tx = L1HandlerTransaction { + contract_address: ContractAddress(felt_to_addr), + entry_point_selector: EntryPoint(felt_selector), + nonce: TransactionNonce(felt_nonce), + calldata: call_data, + }; + l1_handler_txs.push(tx); + } + + Ok(l1_handler_txs) + } else { + Err(anyhow::anyhow!("Transaction not found")) + } + } + /// Get the Starknet state async fn get_starknet_state(&self, address: &H160) -> anyhow::Result { // Create a WebSocket connection @@ -221,9 +285,8 @@ impl EthereumApi for EthereumClient { let contract = StarknetCoreContract::new(address, provider); // Get the finalized block hash - let finalized_block_hash = self.get_finalized_block_hash().await?; - let block_hash = B256::from(finalized_block_hash.0); - let block_id = BlockId::Hash(RpcBlockHash::from_hash(block_hash, None)); + let finalized_block_number = self.get_finalized_block_number().await?; + let block_id = BlockId::Number(BlockNumberOrTag::Number(finalized_block_number.get())); // Call the contract methods let state_root = contract.stateRoot().block(block_id).call().await?; diff --git a/crates/pathfinder/src/bin/pathfinder/main.rs b/crates/pathfinder/src/bin/pathfinder/main.rs index 6db0efe4e3..f6b24d7b40 100644 --- a/crates/pathfinder/src/bin/pathfinder/main.rs +++ b/crates/pathfinder/src/bin/pathfinder/main.rs @@ -229,9 +229,11 @@ Hint: This is usually caused by exceeding the file descriptor limit of your syst execution_storage, sync_state.clone(), pathfinder_context.network_id, + pathfinder_context.l1_core_address, pathfinder_context.gateway.clone(), rx_pending.clone(), notifications.clone(), + ethereum.client.clone(), rpc_config, ); diff --git a/crates/pathfinder/src/state/sync.rs b/crates/pathfinder/src/state/sync.rs index fd3015a1f7..080d60e89d 100644 --- a/crates/pathfinder/src/state/sync.rs +++ b/crates/pathfinder/src/state/sync.rs @@ -15,7 +15,6 @@ use pathfinder_common::state_update::{ContractUpdate, SystemContractUpdate}; use pathfinder_common::{ BlockCommitmentSignature, Chain, - L1ToL2MessageLog, PublicKey, ReceiptCommitment, StateDiffCommitment, @@ -75,8 +74,6 @@ pub enum SyncEvent { }, /// A new L2 pending update was polled. Pending((Arc, Arc)), - /// A new L1 to L2 message was finalized. - L1ToL2Message(L1ToL2MessageLog), } pub struct SyncContext { @@ -219,6 +216,7 @@ where let (event_sender, event_receiver) = mpsc::channel(8); + // Get the latest block from the database let l2_head = tokio::task::block_in_place(|| -> anyhow::Result<_> { let tx = db_conn.transaction()?; let l2_head = tx @@ -229,11 +227,13 @@ where Ok(l2_head) })?; + // Get the latest block from the sequencer let gateway_latest = sequencer .head() .await .context("Fetching latest block from gateway")?; + // Keep polling the sequencer for the latest block let (tx_latest, rx_latest) = tokio::sync::watch::channel(gateway_latest); let mut latest_handle = tokio::spawn(l2::poll_latest( sequencer.clone(), @@ -260,6 +260,7 @@ where // open even if the producer task fails. let mut l1_handle = tokio::spawn(l1_sync(event_sender.clone(), l1_context.clone())); + // Fetch latest blocks from storage let latest_blocks = latest_n_blocks(&mut db_conn, block_cache_size) .await .context("Fetching latest blocks from storage")?; @@ -690,10 +691,6 @@ async fn consumer( tracing::debug!("Updated pending data"); } } - L1ToL2Message(msg) => { - tracing::trace!("Got a new L1 to L2 message log: {:?}", msg); - // todo!() - } } } diff --git a/crates/pathfinder/src/state/sync/l1.rs b/crates/pathfinder/src/state/sync/l1.rs index 104a785c95..4fee20780a 100644 --- a/crates/pathfinder/src/state/sync/l1.rs +++ b/crates/pathfinder/src/state/sync/l1.rs @@ -1,7 +1,7 @@ use std::time::Duration; use pathfinder_common::Chain; -use pathfinder_ethereum::{EthereumApi, EthereumEvent}; +use pathfinder_ethereum::EthereumApi; use primitive_types::H160; use tokio::sync::mpsc; @@ -35,24 +35,14 @@ where poll_interval, } = context; - // Fetch the current Starknet state from Ethereum - let state_update = ethereum.get_starknet_state(&core_address).await?; - let _ = tx_event.send(SyncEvent::L1Update(state_update)).await; + let tx_event = std::sync::Arc::new(tx_event); // Subscribe to subsequent state updates and message logs - let tx_event = std::sync::Arc::new(tx_event); ethereum - .listen(&core_address, poll_interval, move |event| { + .sync_and_listen(&core_address, poll_interval, move |state_update| { let tx_event = tx_event.clone(); async move { - match event { - EthereumEvent::StateUpdate(state_update) => { - let _ = tx_event.send(SyncEvent::L1Update(state_update)).await; - } - EthereumEvent::MessageLog(log) => { - let _ = tx_event.send(SyncEvent::L1ToL2Message(log)).await; - } - } + let _ = tx_event.send(SyncEvent::L1Update(state_update)).await; } }) .await?; diff --git a/crates/rpc/src/context.rs b/crates/rpc/src/context.rs index a085f29982..1c9ede45e7 100644 --- a/crates/rpc/src/context.rs +++ b/crates/rpc/src/context.rs @@ -2,8 +2,10 @@ use std::num::NonZeroUsize; use std::sync::Arc; use pathfinder_common::ChainId; +use pathfinder_ethereum::EthereumClient; use pathfinder_executor::{TraceCache, VersionedConstants}; use pathfinder_storage::Storage; +use primitive_types::H160; pub use crate::jsonrpc::websocket::WebsocketContext; use crate::jsonrpc::Notifications; @@ -29,9 +31,11 @@ pub struct RpcContext { pub pending_data: PendingWatcher, pub sync_status: Arc, pub chain_id: ChainId, + pub core_contract_address: H160, pub sequencer: SequencerClient, pub websocket: Option, pub notifications: Notifications, + pub ethereum: EthereumClient, pub config: RpcConfig, } @@ -42,9 +46,11 @@ impl RpcContext { execution_storage: Storage, sync_status: Arc, chain_id: ChainId, + core_contract_address: H160, sequencer: SequencerClient, pending_data: tokio_watch::Receiver, notifications: Notifications, + ethereum: EthereumClient, config: RpcConfig, ) -> Self { let pending_data = PendingWatcher::new(pending_data); @@ -54,10 +60,12 @@ impl RpcContext { execution_storage, sync_status, chain_id, + core_contract_address, pending_data, sequencer, websocket: None, notifications, + ethereum, config, } } @@ -84,15 +92,22 @@ impl RpcContext { ) -> Self { use gateway_test_utils::GATEWAY_TIMEOUT; use pathfinder_common::Chain; + use pathfinder_ethereum::core_addr; - let (chain_id, sequencer) = match chain { - Chain::Mainnet => (ChainId::MAINNET, SequencerClient::mainnet(GATEWAY_TIMEOUT)), + let (chain_id, core_contract_address, sequencer) = match chain { + Chain::Mainnet => ( + ChainId::MAINNET, + H160::from(core_addr::MAINNET), + SequencerClient::mainnet(GATEWAY_TIMEOUT), + ), Chain::SepoliaTestnet => ( ChainId::SEPOLIA_TESTNET, + H160::from(core_addr::SEPOLIA_TESTNET), SequencerClient::sepolia_testnet(GATEWAY_TIMEOUT), ), Chain::SepoliaIntegration => ( ChainId::SEPOLIA_INTEGRATION, + H160::from(core_addr::SEPOLIA_INTEGRATION), SequencerClient::sepolia_integration(GATEWAY_TIMEOUT), ), Chain::Custom => unreachable!("Should not be testing with custom chain"), @@ -109,14 +124,19 @@ impl RpcContext { custom_versioned_constants: None, }; + let ethereum = + EthereumClient::new("wss://eth-sepolia.g.alchemy.com/v2/just-for-tests").unwrap(); + Self::new( storage.clone(), storage, sync_state, chain_id, + core_contract_address, sequencer.disable_retry_for_tests(), rx, Notifications::default(), + ethereum, config, ) } diff --git a/crates/rpc/src/dto/primitives.rs b/crates/rpc/src/dto/primitives.rs index 4a47a4bd6d..5b29762cec 100644 --- a/crates/rpc/src/dto/primitives.rs +++ b/crates/rpc/src/dto/primitives.rs @@ -1,4 +1,5 @@ -use pathfinder_common::ContractAddress; +use pathfinder_common::{ContractAddress, L1TransactionHash}; +use primitive_types::H256; use serde::de::Error; use super::serialize::SerializeForVersion; @@ -246,6 +247,16 @@ impl SerializeForVersion for U256Hex { } } +impl DeserializeForVersion for H256 { + fn deserialize(value: Value) -> Result { + let hex_str: String = value.deserialize_serde()?; + let bytes = hex_str::bytes_from_hex_str_stripped::<32>(&hex_str).map_err(|e| { + serde_json::Error::custom(format!("failed to parse hex string as u256: {}", e)) + })?; + Ok(H256(bytes)) + } +} + impl SerializeForVersion for Address<'_> { fn serialize(&self, serializer: Serializer) -> Result { serializer.serialize(&Felt(&self.0 .0)) diff --git a/crates/rpc/src/jsonrpc/router/subscription.rs b/crates/rpc/src/jsonrpc/router/subscription.rs index ff48a5843e..da767e4164 100644 --- a/crates/rpc/src/jsonrpc/router/subscription.rs +++ b/crates/rpc/src/jsonrpc/router/subscription.rs @@ -810,7 +810,9 @@ mod tests { use axum::extract::ws::Message; use pathfinder_common::{BlockHash, BlockHeader, BlockId, BlockNumber, ChainId}; use pathfinder_crypto::Felt; + use pathfinder_ethereum::EthereumClient; use pathfinder_storage::StorageBuilder; + use primitive_types::H160; use starknet_gateway_client::Client; use tokio::sync::mpsc; @@ -1024,9 +1026,12 @@ mod tests { } .into(), chain_id: ChainId::MAINNET, + core_contract_address: H160::from(pathfinder_ethereum::core_addr::MAINNET), sequencer: Client::mainnet(Duration::from_secs(10)), websocket: None, notifications, + ethereum: EthereumClient::new("wss://eth-sepolia.g.alchemy.com/v2/just-for-tests") + .unwrap(), config: RpcConfig { batch_concurrency_limit: 1.try_into().unwrap(), get_events_max_blocks_to_scan: 1.try_into().unwrap(), diff --git a/crates/rpc/src/lib.rs b/crates/rpc/src/lib.rs index e7da466d12..17d3a8869b 100644 --- a/crates/rpc/src/lib.rs +++ b/crates/rpc/src/lib.rs @@ -905,7 +905,6 @@ mod tests { #[case::v0_8_api ("/rpc/v0_8", "v08/starknet_api_openrpc.json", &[ "starknet_getBlockWithReceipts", - "starknet_getMessagesStatus", "starknet_getTransactionReceipt", ])] #[case::v0_8_trace("/rpc/v0_8", "v08/starknet_trace_api_openrpc.json", &[])] diff --git a/crates/rpc/src/method.rs b/crates/rpc/src/method.rs index c0b3320b57..5ac36c9417 100644 --- a/crates/rpc/src/method.rs +++ b/crates/rpc/src/method.rs @@ -15,6 +15,7 @@ pub mod get_class; pub mod get_class_at; pub mod get_class_hash_at; pub mod get_events; +pub mod get_messages_status; pub mod get_nonce; pub mod get_state_update; pub mod get_storage_at; @@ -49,6 +50,7 @@ pub use get_class::get_class; pub use get_class_at::get_class_at; pub use get_class_hash_at::get_class_hash_at; pub use get_events::get_events; +pub use get_messages_status::get_messages_status; pub use get_nonce::get_nonce; pub use get_state_update::get_state_update; pub use get_storage_at::get_storage_at; diff --git a/crates/rpc/src/method/get_messages_status.rs b/crates/rpc/src/method/get_messages_status.rs new file mode 100644 index 0000000000..6c9a4de4e1 --- /dev/null +++ b/crates/rpc/src/method/get_messages_status.rs @@ -0,0 +1,113 @@ +use anyhow::Context; +use pathfinder_common::{L1TransactionHash, TransactionHash}; +use pathfinder_ethereum::EthereumApi; +use serde::{Deserialize, Serialize}; + +use crate::context::RpcContext; +use crate::method::get_transaction_status; + +#[derive(Debug, PartialEq, Eq)] +pub struct Input { + transaction_hash: L1TransactionHash, +} + +impl crate::dto::DeserializeForVersion for Input { + fn deserialize(value: crate::dto::Value) -> Result { + value.deserialize_map(|value| { + Ok(Self { + transaction_hash: value + .deserialize("transaction_hash") + .map(L1TransactionHash::new)?, + }) + }) + } +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +enum FinalityStatus { + Received, + Rejected, + AcceptedOnL2, + AcceptedOnL1, +} + +#[derive(Clone, Debug)] +pub struct L1HandlerTransactionStatus { + transaction_hash: TransactionHash, + finality_status: FinalityStatus, + failure_reason: Option, +} + +#[derive(Debug)] +pub struct Output(Vec); + +crate::error::generate_rpc_error_subset!(Error: TxnHashNotFound); + +pub async fn get_messages_status(context: RpcContext, input: Input) -> Result { + let span = tracing::Span::current(); + + let _g = span.enter(); + + // Fetch the L1 handler transactions for the given transaction hash + let ethereum = context.ethereum.clone(); + + let l1_handler_txs = ethereum + .get_l1_handler_txs(&context.core_contract_address, &input.transaction_hash) + .await + .context("Fetching L1 handler tx hashes") + .map_err(|_| Error::TxnHashNotFound)?; + + let mut res = vec![]; + for tx in l1_handler_txs { + let tx_hash = tx.calculate_hash(context.chain_id); + + let input = get_transaction_status::Input::new(tx_hash); + let status = get_transaction_status(context.clone(), input) + .await + .map_err(|_| Error::TxnHashNotFound)?; + + use get_transaction_status::Output as TxStatus; + let finality_status = match status { + TxStatus::Received => FinalityStatus::Received, + TxStatus::Rejected { .. } => FinalityStatus::Rejected, + TxStatus::AcceptedOnL1(_) => FinalityStatus::AcceptedOnL1, + TxStatus::AcceptedOnL2(_) => FinalityStatus::AcceptedOnL2, + }; + + let failure_reason = match status { + TxStatus::Rejected { error_message, .. } => error_message, + _ => None, + }; + + res.push(L1HandlerTransactionStatus { + transaction_hash: tx.calculate_hash(context.chain_id), + finality_status, + failure_reason, + }); + } + + Ok(Output(res)) +} + +impl crate::dto::serialize::SerializeForVersion for Output { + fn serialize( + &self, + serializer: crate::dto::serialize::Serializer, + ) -> Result { + serializer.serialize_iter(self.0.len(), &mut self.0.clone().into_iter()) + } +} + +impl crate::dto::serialize::SerializeForVersion for L1HandlerTransactionStatus { + fn serialize( + &self, + serializer: crate::dto::serialize::Serializer, + ) -> Result { + let mut serializer = serializer.serialize_struct()?; + serializer.serialize_field("transaction_hash", &self.transaction_hash)?; + serializer.serialize_field("finality_status", &self.finality_status)?; + serializer.serialize_optional("failure_reason", self.failure_reason.as_deref())?; + serializer.end() + } +} diff --git a/crates/rpc/src/method/get_transaction_status.rs b/crates/rpc/src/method/get_transaction_status.rs index f6aa6c60de..f48762fa2c 100644 --- a/crates/rpc/src/method/get_transaction_status.rs +++ b/crates/rpc/src/method/get_transaction_status.rs @@ -10,6 +10,12 @@ pub struct Input { transaction_hash: TransactionHash, } +impl Input { + pub fn new(transaction_hash: TransactionHash) -> Self { + Self { transaction_hash } + } +} + impl crate::dto::DeserializeForVersion for Input { fn deserialize(value: crate::dto::Value) -> Result { value.deserialize_map(|value| { diff --git a/crates/rpc/src/method/subscribe_events.rs b/crates/rpc/src/method/subscribe_events.rs index eed4f8e727..537cba978d 100644 --- a/crates/rpc/src/method/subscribe_events.rs +++ b/crates/rpc/src/method/subscribe_events.rs @@ -242,7 +242,9 @@ mod tests { TransactionIndex, }; use pathfinder_crypto::Felt; + use pathfinder_ethereum::EthereumClient; use pathfinder_storage::StorageBuilder; + use primitive_types::H160; use starknet_gateway_client::Client; use starknet_gateway_types::reply::Block; use tokio::sync::mpsc; @@ -678,9 +680,12 @@ mod tests { } .into(), chain_id: ChainId::MAINNET, + core_contract_address: H160::from(pathfinder_ethereum::core_addr::MAINNET), sequencer: Client::mainnet(Duration::from_secs(10)), websocket: None, notifications, + ethereum: EthereumClient::new("wss://eth-sepolia.g.alchemy.com/v2/just-for-tests") + .unwrap(), config: RpcConfig { batch_concurrency_limit: 64.try_into().unwrap(), get_events_max_blocks_to_scan: 1024.try_into().unwrap(), diff --git a/crates/rpc/src/method/subscribe_new_heads.rs b/crates/rpc/src/method/subscribe_new_heads.rs index 1c5e0eac59..120d731ccd 100644 --- a/crates/rpc/src/method/subscribe_new_heads.rs +++ b/crates/rpc/src/method/subscribe_new_heads.rs @@ -160,7 +160,9 @@ mod tests { use axum::extract::ws::Message; use pathfinder_common::{felt, BlockHash, BlockHeader, BlockNumber, ChainId}; use pathfinder_crypto::Felt; + use pathfinder_ethereum::EthereumClient; use pathfinder_storage::StorageBuilder; + use primitive_types::H160; use starknet_gateway_client::Client; use tokio::sync::mpsc; @@ -485,9 +487,12 @@ mod tests { } .into(), chain_id: ChainId::MAINNET, + core_contract_address: H160::from(pathfinder_ethereum::core_addr::MAINNET), sequencer: Client::mainnet(Duration::from_secs(10)), websocket: None, notifications, + ethereum: EthereumClient::new("wss://eth-sepolia.g.alchemy.com/v2/just-for-tests") + .unwrap(), config: RpcConfig { batch_concurrency_limit: 1.try_into().unwrap(), get_events_max_blocks_to_scan: 1.try_into().unwrap(), diff --git a/crates/rpc/src/method/subscribe_pending_transactions.rs b/crates/rpc/src/method/subscribe_pending_transactions.rs index 9525cbf770..142c371a5d 100644 --- a/crates/rpc/src/method/subscribe_pending_transactions.rs +++ b/crates/rpc/src/method/subscribe_pending_transactions.rs @@ -148,7 +148,9 @@ mod tests { ContractAddress, TransactionHash, }; + use pathfinder_ethereum::EthereumClient; use pathfinder_storage::StorageBuilder; + use primitive_types::H160; use starknet_gateway_client::Client; use starknet_gateway_types::reply::PendingBlock; use tokio::sync::{mpsc, watch}; @@ -481,9 +483,12 @@ mod tests { } .into(), chain_id: ChainId::MAINNET, + core_contract_address: H160::from(pathfinder_ethereum::core_addr::MAINNET), sequencer: Client::mainnet(Duration::from_secs(10)), websocket: None, notifications, + ethereum: EthereumClient::new("wss://eth-sepolia.g.alchemy.com/v2/just-for-tests") + .unwrap(), config: RpcConfig { batch_concurrency_limit: 1.try_into().unwrap(), get_events_max_blocks_to_scan: 1.try_into().unwrap(), diff --git a/crates/rpc/src/method/subscribe_transaction_status.rs b/crates/rpc/src/method/subscribe_transaction_status.rs index 8998092e71..8b54ac99f0 100644 --- a/crates/rpc/src/method/subscribe_transaction_status.rs +++ b/crates/rpc/src/method/subscribe_transaction_status.rs @@ -446,9 +446,10 @@ mod tests { TransactionIndex, }; use pathfinder_crypto::Felt; - use pathfinder_ethereum::EthereumStateUpdate; + use pathfinder_ethereum::{EthereumClient, EthereumStateUpdate}; use pathfinder_storage::StorageBuilder; use pretty_assertions_sorted::assert_eq; + use primitive_types::H160; use starknet_gateway_client::Client; use starknet_gateway_types::reply::{Block, PendingBlock}; use tokio::sync::mpsc; @@ -1158,9 +1159,12 @@ mod tests { } .into(), chain_id: ChainId::MAINNET, + core_contract_address: H160::from(pathfinder_ethereum::core_addr::MAINNET), sequencer: Client::mainnet(Duration::from_secs(10)), websocket: None, notifications, + ethereum: EthereumClient::new("wss://eth-sepolia.g.alchemy.com/v2/just-for-tests") + .unwrap(), config: RpcConfig { batch_concurrency_limit: 1.try_into().unwrap(), get_events_max_blocks_to_scan: 1.try_into().unwrap(), diff --git a/crates/rpc/src/v08.rs b/crates/rpc/src/v08.rs index d6888a646e..4d13f9422e 100644 --- a/crates/rpc/src/v08.rs +++ b/crates/rpc/src/v08.rs @@ -23,6 +23,7 @@ pub fn register_routes() -> RpcRouterBuilder { .register("starknet_getClassAt", crate::method::get_class_at) .register("starknet_getClassHashAt", crate::method::get_class_hash_at) .register("starknet_getEvents", crate::method::get_events) + .register("starknet_getMessagesStatus", crate::method::get_messages_status) .register("starknet_getNonce", crate::method::get_nonce) .register("starknet_getStateUpdate", crate::method::get_state_update) .register("starknet_getStorageAt", crate::method::get_storage_at) diff --git a/crates/storage/src/params.rs b/crates/storage/src/params.rs index 17b5e727ce..6d7d0aa596 100644 --- a/crates/storage/src/params.rs +++ b/crates/storage/src/params.rs @@ -23,6 +23,7 @@ use pathfinder_common::{ EventKey, Fee, GasPrice, + L1BlockNumber, L1DataAvailabilityMode, L1ToL2MessageNonce, L1ToL2MessagePayloadElem, @@ -124,6 +125,7 @@ to_sql_felt!( to_sql_compressed_felt!(ContractNonce, StorageValue, TransactionNonce); to_sql_int!(BlockNumber, BlockTimestamp); +to_sql_int!(L1BlockNumber); to_sql_builtin!( String, From 3a94dbba737dd530ef8d065316a014ee710924cd Mon Sep 17 00:00:00 2001 From: t00ts Date: Thu, 17 Oct 2024 12:19:21 +0400 Subject: [PATCH 194/282] feat(rpc): make `ExecutionResources` compatible with json-rpc v08 spec --- crates/common/src/receipt.rs | 6 +++++ crates/gateway-types/src/reply.rs | 2 ++ crates/p2p/src/client/conv.rs | 2 ++ .../pathfinder/src/sync/checkpoint/fixture.rs | 27 +++++++++++++++++++ crates/rpc/src/dto/receipt.rs | 16 +++++++---- crates/storage/src/connection/transaction.rs | 1 + crates/storage/src/test_utils.rs | 1 + 7 files changed, 50 insertions(+), 5 deletions(-) diff --git a/crates/common/src/receipt.rs b/crates/common/src/receipt.rs index 266a5dc4bb..1861dd5b91 100644 --- a/crates/common/src/receipt.rs +++ b/crates/common/src/receipt.rs @@ -44,6 +44,7 @@ pub struct ExecutionResources { pub n_memory_holes: u64, pub data_availability: L1Gas, pub total_gas_consumed: L1Gas, + pub l2_gas: L2Gas, } #[derive(Clone, Debug, Default, PartialEq, Eq, Dummy)] @@ -52,6 +53,10 @@ pub struct L1Gas { pub l1_data_gas: u128, } +#[derive(Clone, Debug, Default, PartialEq, Eq, Dummy, serde::Serialize)] +#[serde(transparent)] +pub struct L2Gas(pub u128); + #[derive(Clone, Debug, Default, PartialEq, Eq)] pub struct BuiltinCounters { pub output: u64, @@ -103,6 +108,7 @@ impl Dummy for ExecutionResources { data_availability: Faker.fake_with_rng(rng), // TODO fix this after total_gas_consumed is added to p2p messages total_gas_consumed: Default::default(), + l2_gas: Default::default(), } } } diff --git a/crates/gateway-types/src/reply.rs b/crates/gateway-types/src/reply.rs index 548b33329e..9fe4ca0abb 100644 --- a/crates/gateway-types/src/reply.rs +++ b/crates/gateway-types/src/reply.rs @@ -327,6 +327,8 @@ pub mod transaction { n_memory_holes: value.n_memory_holes, data_availability: value.data_availability.unwrap_or_default().into(), total_gas_consumed: value.total_gas_consumed.unwrap_or_default().into(), + // TODO: Fix this when we have a way to get L2 gas from the gateway + l2_gas: Default::default(), } } } diff --git a/crates/p2p/src/client/conv.rs b/crates/p2p/src/client/conv.rs index 5bb69700fe..0d962d7661 100644 --- a/crates/p2p/src/client/conv.rs +++ b/crates/p2p/src/client/conv.rs @@ -740,6 +740,8 @@ impl TryFrom<(p2p_proto::receipt::Receipt, TransactionIndex)> for crate::client: )? .0, }, + // TODO: Fix this when we have a way to get L2 gas from the gateway + l2_gas: Default::default(), }, l2_to_l1_messages: common .messages_sent diff --git a/crates/pathfinder/src/sync/checkpoint/fixture.rs b/crates/pathfinder/src/sync/checkpoint/fixture.rs index 22bb47ca07..6f2371478c 100644 --- a/crates/pathfinder/src/sync/checkpoint/fixture.rs +++ b/crates/pathfinder/src/sync/checkpoint/fixture.rs @@ -5,6 +5,7 @@ use pathfinder_common::receipt::{ ExecutionResources, ExecutionStatus, L1Gas, + L2Gas, L2ToL1Message, Receipt, }; @@ -139,6 +140,7 @@ pub fn blocks() -> [Block; 2] { l1_gas: 0, l1_data_gas: 0, }, + l2_gas: L2Gas(0), }, l2_to_l1_messages: vec![], execution_status: Succeeded, @@ -191,6 +193,7 @@ pub fn blocks() -> [Block; 2] { l1_gas: 0, l1_data_gas: 0, }, + l2_gas: L2Gas(0), }, l2_to_l1_messages: vec![], execution_status: Succeeded, @@ -244,6 +247,7 @@ pub fn blocks() -> [Block; 2] { l1_gas: 0, l1_data_gas: 0, }, + l2_gas: L2Gas(0), }, l2_to_l1_messages: vec![ L2ToL1Message { @@ -307,6 +311,7 @@ pub fn blocks() -> [Block; 2] { l1_gas: 0, l1_data_gas: 0, }, + l2_gas: L2Gas(0), }, l2_to_l1_messages: vec![], execution_status: Succeeded, @@ -363,6 +368,7 @@ pub fn blocks() -> [Block; 2] { l1_gas: 0, l1_data_gas: 0, }, + l2_gas: L2Gas(0), }, l2_to_l1_messages: vec![], execution_status: Succeeded, @@ -422,6 +428,7 @@ pub fn blocks() -> [Block; 2] { l1_gas: 0, l1_data_gas: 0, }, + l2_gas: L2Gas(0), }, l2_to_l1_messages: vec![], execution_status: Succeeded, @@ -476,6 +483,7 @@ pub fn blocks() -> [Block; 2] { l1_gas: 0, l1_data_gas: 0, }, + l2_gas: L2Gas(0), }, l2_to_l1_messages: vec![ L2ToL1Message { @@ -539,6 +547,7 @@ pub fn blocks() -> [Block; 2] { l1_gas: 0, l1_data_gas: 0, }, + l2_gas: L2Gas(0), }, l2_to_l1_messages: vec![], execution_status: Succeeded, @@ -591,6 +600,7 @@ pub fn blocks() -> [Block; 2] { l1_gas: 0, l1_data_gas: 0, }, + l2_gas: L2Gas(0), }, l2_to_l1_messages: vec![], execution_status: Succeeded, @@ -643,6 +653,7 @@ pub fn blocks() -> [Block; 2] { l1_gas: 0, l1_data_gas: 0, }, + l2_gas: L2Gas(0), }, l2_to_l1_messages: vec![], execution_status: Succeeded, @@ -697,6 +708,7 @@ pub fn blocks() -> [Block; 2] { l1_gas: 0, l1_data_gas: 0, }, + l2_gas: L2Gas(0), }, l2_to_l1_messages: vec![], execution_status: Succeeded, @@ -749,6 +761,7 @@ pub fn blocks() -> [Block; 2] { l1_gas: 0, l1_data_gas: 0, }, + l2_gas: L2Gas(0), }, l2_to_l1_messages: vec![], execution_status: Succeeded, @@ -803,6 +816,7 @@ pub fn blocks() -> [Block; 2] { l1_gas: 0, l1_data_gas: 0, }, + l2_gas: L2Gas(0), }, l2_to_l1_messages: vec![ L2ToL1Message { @@ -866,6 +880,7 @@ pub fn blocks() -> [Block; 2] { l1_gas: 0, l1_data_gas: 0, }, + l2_gas: L2Gas(0), }, l2_to_l1_messages: vec![], execution_status: Succeeded, @@ -922,6 +937,7 @@ pub fn blocks() -> [Block; 2] { l1_gas: 0, l1_data_gas: 0, }, + l2_gas: L2Gas(0), }, l2_to_l1_messages: vec![], execution_status: Succeeded, @@ -976,6 +992,7 @@ pub fn blocks() -> [Block; 2] { l1_gas: 0, l1_data_gas: 0, }, + l2_gas: L2Gas(0), }, l2_to_l1_messages: vec![], execution_status: Succeeded, @@ -1030,6 +1047,7 @@ pub fn blocks() -> [Block; 2] { l1_gas: 0, l1_data_gas: 0, }, + l2_gas: L2Gas(0), }, l2_to_l1_messages: vec![], execution_status: Succeeded, @@ -1084,6 +1102,7 @@ pub fn blocks() -> [Block; 2] { l1_gas: 0, l1_data_gas: 0, }, + l2_gas: L2Gas(0), }, l2_to_l1_messages: vec![], execution_status: Succeeded, @@ -1255,6 +1274,7 @@ pub fn blocks() -> [Block; 2] { l1_gas: 0, l1_data_gas: 0, }, + l2_gas: L2Gas(0), }, l2_to_l1_messages: vec![], execution_status: Succeeded, @@ -1307,6 +1327,7 @@ pub fn blocks() -> [Block; 2] { l1_gas: 0, l1_data_gas: 0, }, + l2_gas: L2Gas(0), }, l2_to_l1_messages: vec![], execution_status: Succeeded, @@ -1361,6 +1382,7 @@ pub fn blocks() -> [Block; 2] { l1_gas: 0, l1_data_gas: 0, }, + l2_gas: L2Gas(0), }, l2_to_l1_messages: vec![], execution_status: Succeeded, @@ -1420,6 +1442,7 @@ pub fn blocks() -> [Block; 2] { l1_gas: 0, l1_data_gas: 0, }, + l2_gas: L2Gas(0), }, l2_to_l1_messages: vec![], execution_status: Succeeded, @@ -1474,6 +1497,7 @@ pub fn blocks() -> [Block; 2] { l1_gas: 0, l1_data_gas: 0, }, + l2_gas: L2Gas(0), }, l2_to_l1_messages: vec![ L2ToL1Message { @@ -1536,6 +1560,7 @@ pub fn blocks() -> [Block; 2] { l1_gas: 0, l1_data_gas: 0, }, + l2_gas: L2Gas(0), }, l2_to_l1_messages: vec![ L2ToL1Message { @@ -1601,6 +1626,7 @@ pub fn blocks() -> [Block; 2] { l1_gas: 0, l1_data_gas: 0, }, + l2_gas: L2Gas(0), }, l2_to_l1_messages: vec![], execution_status: Succeeded, @@ -1655,6 +1681,7 @@ pub fn blocks() -> [Block; 2] { l1_gas: 0, l1_data_gas: 0, }, + l2_gas: L2Gas(0), }, l2_to_l1_messages: vec![], execution_status: Succeeded, diff --git a/crates/rpc/src/dto/receipt.rs b/crates/rpc/src/dto/receipt.rs index 51fba6a51b..62740103d4 100644 --- a/crates/rpc/src/dto/receipt.rs +++ b/crates/rpc/src/dto/receipt.rs @@ -372,11 +372,17 @@ impl SerializeForVersion for ExecutionResources<'_> { let mut serializer = serializer.serialize_struct()?; - serializer.flatten(&ComputationResources(self.0))?; - serializer.serialize_field( - "data_availability", - &DataAvailability(&self.0.data_availability), - )?; + if serializer.version < RpcVersion::V08 { + serializer.flatten(&ComputationResources(self.0))?; + serializer.serialize_field( + "data_availability", + &DataAvailability(&self.0.data_availability), + )?; + } else { + serializer.serialize_field("l1_gas", &self.0.total_gas_consumed.l1_gas)?; + serializer.serialize_field("l1_data_gas", &self.0.total_gas_consumed.l1_data_gas)?; + serializer.serialize_field("l2_gas", &self.0.l2_gas)?; + } serializer.end() } diff --git a/crates/storage/src/connection/transaction.rs b/crates/storage/src/connection/transaction.rs index 6e75c6f378..7187bec55e 100644 --- a/crates/storage/src/connection/transaction.rs +++ b/crates/storage/src/connection/transaction.rs @@ -846,6 +846,7 @@ pub(crate) mod dto { }, _ => Default::default(), }, + l2_gas: Default::default(), } } } diff --git a/crates/storage/src/test_utils.rs b/crates/storage/src/test_utils.rs index 3bc59b832a..9036342af4 100644 --- a/crates/storage/src/test_utils.rs +++ b/crates/storage/src/test_utils.rs @@ -125,6 +125,7 @@ pub(crate) fn create_transactions_and_receipts( l1_gas: i as u128 + 333, l1_data_gas: i as u128 + 666, }, + l2_gas: Default::default(), }, transaction_hash: tx.hash, transaction_index: TransactionIndex::new_or_panic(i as u64 + 2311), From 5be12a8bb761ddbc0e755ed904714c2f39b4a994 Mon Sep 17 00:00:00 2001 From: Krzysztof Lis Date: Fri, 25 Oct 2024 18:00:25 +0200 Subject: [PATCH 195/282] test(common/error): fix test failure when RUST_BACKTRACE enabled --- crates/common/src/error.rs | 42 +++++++++++++++++++++++--------------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/crates/common/src/error.rs b/crates/common/src/error.rs index 2f22cb8549..c648a5423b 100644 --- a/crates/common/src/error.rs +++ b/crates/common/src/error.rs @@ -47,22 +47,22 @@ impl AnyhowExt for anyhow::Error { mod tests { use super::*; - fn fixture() -> (anyhow::Error, anyhow::Error) { - ( - anyhow::anyhow!("root") - .context("level 1") - .context("level 2") - .context("level 3"), - anyhow::anyhow!("root") - .context("level 1") - .context("level 2") - .context("level 3"), - ) + fn fixture() -> anyhow::Error { + anyhow::anyhow!("root") + .context("level 1") + .context("level 2") + .context("level 3") + } + + fn strip_backtrace(s: &str) -> &str { + let i = s.find("Stack backtrace").unwrap_or(s.len()); + &s[..i] } #[test] fn take_if_refcount_eq1() { - let (src0, src) = fixture(); + let src0 = fixture(); + let src = fixture(); let mut arced = std::sync::Arc::new(src0); assert_eq!(Arc::strong_count(&arced), 1); @@ -70,14 +70,18 @@ mod tests { // Strong ref count is 1, taking the error should work let taken = arced.take_or_deep_clone(); assert_eq!(format!("{}", taken), format!("{}", src)); - assert_eq!(format!("{:?}", taken), format!("{:?}", src)); + assert_eq!( + strip_backtrace(&format!("{:?}", taken)), + strip_backtrace(&format!("{:?}", src)) + ); assert!(arced.to_string().is_empty()); } #[test] fn clone_if_refcount_gt1() { - let (src0, src) = fixture(); + let src0 = fixture(); + let src = fixture(); let mut arced = std::sync::Arc::new(src0); let arc_clone = arced.clone(); @@ -86,9 +90,15 @@ mod tests { // Strong ref count is 2, only a poor-man's clone is possible let cloned = arced.take_or_deep_clone(); assert_eq!(format!("{}", cloned), format!("{}", src)); - assert_eq!(format!("{:?}", cloned), format!("{:?}", src)); + assert_eq!( + strip_backtrace(&format!("{:?}", arced)), + strip_backtrace(&format!("{:?}", src)) + ); assert_eq!(format!("{}", arced), format!("{}", src)); - assert_eq!(format!("{:?}", arced), format!("{:?}", src)); + assert_eq!( + strip_backtrace(&format!("{:?}", arced)), + strip_backtrace(&format!("{:?}", src)) + ); } } From 419cf42262b5f92fc9fdbb75fc2ae94e94151fb5 Mon Sep 17 00:00:00 2001 From: Krzysztof Lis Date: Thu, 17 Oct 2024 13:32:39 +0200 Subject: [PATCH 196/282] feat(checkpoint/state_diff): merge trie updates --- crates/common/src/state_update.rs | 166 ++++++++++++++- crates/merkle-tree/src/contract_state.rs | 19 +- crates/pathfinder/src/state.rs | 1 - crates/pathfinder/src/state/sync.rs | 25 +-- crates/pathfinder/src/sync.rs | 8 +- crates/pathfinder/src/sync/checkpoint.rs | 20 +- crates/pathfinder/src/sync/error.rs | 4 + crates/pathfinder/src/sync/state_updates.rs | 217 +++++++++++++++++++- crates/pathfinder/src/sync/track.rs | 8 +- crates/rpc/src/lib.rs | 8 +- 10 files changed, 412 insertions(+), 64 deletions(-) diff --git a/crates/common/src/state_update.rs b/crates/common/src/state_update.rs index 3e6ac221ed..78cc5f681b 100644 --- a/crates/common/src/state_update.rs +++ b/crates/common/src/state_update.rs @@ -1,4 +1,5 @@ -use std::collections::{HashMap, HashSet}; +use std::collections::{hash_map, HashMap, HashSet}; +use std::slice; use fake::Dummy; @@ -48,12 +49,39 @@ pub struct SystemContractUpdate { pub storage: HashMap, } -#[derive(Debug, Clone, PartialEq, Dummy)] +#[derive(Debug, Copy, Clone, PartialEq, Dummy)] pub enum ContractClassUpdate { Deploy(ClassHash), Replace(ClassHash), } +pub struct StateUpdateRef<'a> { + pub contract_updates: Vec<(&'a ContractAddress, ContractUpdateRef<'a>)>, + pub system_contract_updates: Vec<(&'a ContractAddress, SystemContractUpdateRef<'a>)>, + pub declared_sierra_classes: &'a HashMap, +} + +pub struct ContractUpdateRef<'a> { + pub storage: StorageRef<'a>, + pub class: &'a Option, + pub nonce: &'a Option, +} + +pub struct SystemContractUpdateRef<'a> { + pub storage: StorageRef<'a>, +} + +#[derive(Copy, Clone)] +pub enum StorageRef<'a> { + HashMap(&'a HashMap), + Vec(&'a Vec<(StorageAddress, StorageValue)>), +} + +pub enum StorageRefIter<'a> { + HashMap(hash_map::Iter<'a, StorageAddress, StorageValue>), + Vec(slice::Iter<'a, (StorageAddress, StorageValue)>), +} + impl ContractUpdate { pub fn replaced_class(&self) -> Option<&ClassHash> { match &self.class { @@ -336,6 +364,140 @@ impl From for StateUpdateData { } } +impl<'a> From<&'a StateUpdate> for StateUpdateRef<'a> { + fn from(state_update: &'a StateUpdate) -> Self { + Self { + contract_updates: state_update + .contract_updates + .iter() + .map(|(k, v)| { + ( + k, + ContractUpdateRef { + storage: StorageRef::HashMap(&v.storage), + class: &v.class, + nonce: &v.nonce, + }, + ) + }) + .collect(), + system_contract_updates: state_update + .system_contract_updates + .iter() + .map(|(k, v)| { + ( + k, + SystemContractUpdateRef { + storage: StorageRef::HashMap(&v.storage), + }, + ) + }) + .collect(), + declared_sierra_classes: &state_update.declared_sierra_classes, + } + } +} + +impl<'a> From<&'a StateUpdateData> for StateUpdateRef<'a> { + fn from(state_update: &'a StateUpdateData) -> Self { + Self { + contract_updates: state_update + .contract_updates + .iter() + .map(|(k, v)| { + ( + k, + ContractUpdateRef { + storage: StorageRef::HashMap(&v.storage), + class: &v.class, + nonce: &v.nonce, + }, + ) + }) + .collect(), + system_contract_updates: state_update + .system_contract_updates + .iter() + .map(|(k, v)| { + ( + k, + SystemContractUpdateRef { + storage: StorageRef::HashMap(&v.storage), + }, + ) + }) + .collect(), + declared_sierra_classes: &state_update.declared_sierra_classes, + } + } +} + +impl StorageRef<'_> { + pub fn iter(&self) -> StorageRefIter<'_> { + match self { + StorageRef::HashMap(map) => StorageRefIter::HashMap(map.iter()), + StorageRef::Vec(vec) => StorageRefIter::Vec(vec.iter()), + } + } + + pub fn is_empty(&self) -> bool { + match self { + StorageRef::HashMap(map) => map.is_empty(), + StorageRef::Vec(vec) => vec.is_empty(), + } + } +} + +impl<'a> From<&'a ContractUpdate> for ContractUpdateRef<'a> { + fn from(x: &'a ContractUpdate) -> Self { + ContractUpdateRef { + storage: (&x.storage).into(), + class: &x.class, + nonce: &x.nonce, + } + } +} + +impl<'a> From<&'a SystemContractUpdate> for SystemContractUpdateRef<'a> { + fn from(x: &'a SystemContractUpdate) -> Self { + SystemContractUpdateRef { + storage: (&x.storage).into(), + } + } +} + +impl<'a> From<&'a HashMap> for StorageRef<'a> { + fn from(x: &'a HashMap) -> Self { + StorageRef::HashMap(x) + } +} + +impl<'a> From<&'a Vec<(StorageAddress, StorageValue)>> for StorageRef<'a> { + fn from(x: &'a Vec<(StorageAddress, StorageValue)>) -> Self { + StorageRef::Vec(x) + } +} + +impl<'a> IntoIterator for &'a StorageRef<'a> { + type Item = (&'a StorageAddress, &'a StorageValue); + type IntoIter = StorageRefIter<'a>; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +impl<'a> Iterator for StorageRefIter<'a> { + type Item = (&'a StorageAddress, &'a StorageValue); + + fn next(&mut self) -> Option { + match self { + StorageRefIter::HashMap(iter) => iter.next(), + StorageRefIter::Vec(iter) => iter.next().map(|(k, v)| (k, v)), + } + } +} + mod state_diff_commitment { use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; diff --git a/crates/merkle-tree/src/contract_state.rs b/crates/merkle-tree/src/contract_state.rs index 4bde30d203..d191f4c242 100644 --- a/crates/merkle-tree/src/contract_state.rs +++ b/crates/merkle-tree/src/contract_state.rs @@ -1,7 +1,5 @@ -use std::collections::HashMap; - use anyhow::Context; -use pathfinder_common::state_update::ReverseContractUpdate; +use pathfinder_common::state_update::{ReverseContractUpdate, StorageRef}; use pathfinder_common::{ BlockNumber, ClassHash, @@ -9,8 +7,6 @@ use pathfinder_common::{ ContractNonce, ContractRoot, ContractStateHash, - StorageAddress, - StorageValue, }; use pathfinder_crypto::hash::pedersen_hash; use pathfinder_crypto::Felt; @@ -51,9 +47,9 @@ impl ContractStateUpdateResult { /// Updates a contract's state with and returns the resulting /// [ContractStateHash]. -pub fn update_contract_state( +pub fn update_contract_state<'a>( contract_address: ContractAddress, - updates: &HashMap, + updates: StorageRef<'a>, new_nonce: Option, new_class_hash: Option, transaction: &Transaction<'_>, @@ -70,7 +66,7 @@ pub fn update_contract_state( } .with_verify_hashes(verify_hashes); - for (key, value) in updates { + for (key, value) in &updates { contract_tree .set(*key, *value) .context("Update contract storage tree")?; @@ -99,7 +95,12 @@ pub fn update_contract_state( transaction .contract_class_hash(block.into(), contract_address) .context("Querying contract's class hash")? - .context("Contract's class hash is missing")? + .with_context(|| { + format!( + "Contract's class hash is missing, block: {block}, contract_address: \ + {contract_address}" + ) + })? }; let nonce = if let Some(nonce) = new_nonce { diff --git a/crates/pathfinder/src/state.rs b/crates/pathfinder/src/state.rs index 5b7776102d..a827ac7f33 100644 --- a/crates/pathfinder/src/state.rs +++ b/crates/pathfinder/src/state.rs @@ -8,7 +8,6 @@ pub use sync::{ sync, update_starknet_state, Gossiper, - StarknetStateUpdate, SyncContext, RESET_DELAY_ON_FAILURE, }; diff --git a/crates/pathfinder/src/state/sync.rs b/crates/pathfinder/src/state/sync.rs index 080d60e89d..378d1cd6e4 100644 --- a/crates/pathfinder/src/state/sync.rs +++ b/crates/pathfinder/src/state/sync.rs @@ -4,14 +4,13 @@ pub mod l2; mod pending; pub mod revert; -use std::collections::HashMap; use std::future::Future; use std::sync::Arc; use std::time::{Duration, Instant}; use anyhow::Context; use pathfinder_common::prelude::*; -use pathfinder_common::state_update::{ContractUpdate, SystemContractUpdate}; +use pathfinder_common::state_update::StateUpdateRef; use pathfinder_common::{ BlockCommitmentSignature, Chain, @@ -871,11 +870,7 @@ async fn l2_update( .context("Create database transaction")?; let (storage_commitment, class_commitment) = update_starknet_state( &transaction, - StarknetStateUpdate { - contract_updates: &state_update.contract_updates, - system_contract_updates: &state_update.system_contract_updates, - declared_sierra_classes: &state_update.declared_sierra_classes, - }, + (&state_update).into(), verify_tree_hashes, block.block_number, storage, @@ -1126,15 +1121,9 @@ async fn l2_reorg( }) } -pub struct StarknetStateUpdate<'a> { - pub contract_updates: &'a HashMap, - pub system_contract_updates: &'a HashMap, - pub declared_sierra_classes: &'a HashMap, -} - pub fn update_starknet_state( transaction: &Transaction<'_>, - state_update: StarknetStateUpdate<'_>, + state_update: StateUpdateRef<'_>, verify_hashes: bool, block: BlockNumber, // we need this so that we can create extra read-only transactions for @@ -1169,9 +1158,9 @@ pub fn update_starknet_state( }; let transaction = connection.transaction()?; update_contract_state( - *contract_address, - &update.storage, - update.nonce, + **contract_address, + update.storage, + *update.nonce, update.class.as_ref().map(|x| x.class_hash()), &transaction, verify_hashes, @@ -1201,7 +1190,7 @@ pub fn update_starknet_state( for (contract, update) in state_update.system_contract_updates { let update_result = update_contract_state( *contract, - &update.storage, + update.storage, None, None, transaction, diff --git a/crates/pathfinder/src/sync.rs b/crates/pathfinder/src/sync.rs index 0e0866179b..951fc44762 100644 --- a/crates/pathfinder/src/sync.rs +++ b/crates/pathfinder/src/sync.rs @@ -127,11 +127,11 @@ impl Sync { continue_from } Err(SyncError::Other(error)) => { - tracing::error!(%error, "Stopping checkpoint sync"); + tracing::error!(?error, "Stopping checkpoint sync"); return Err(error); } Err(error) => { - tracing::debug!(%error, "Restarting checkpoint sync"); + tracing::debug!(?error, "Restarting checkpoint sync"); self.handle_recoverable_error(&error.into_v2()).await; continue; } @@ -186,12 +186,12 @@ impl Sync { data: SyncError2::Other(error), .. }) => { - tracing::error!(%error, "Stopping track sync"); + tracing::error!(?error, "Stopping track sync"); use pathfinder_common::error::AnyhowExt; return Err(error.take_or_deep_clone()); } Err(error) => { - tracing::debug!(error=%error.data, "Restarting track sync"); + tracing::debug!(error=?error.data, "Restarting track sync"); self.handle_recoverable_error(error).await; } } diff --git a/crates/pathfinder/src/sync/checkpoint.rs b/crates/pathfinder/src/sync/checkpoint.rs index 56a828f8ad..cf9a6e4517 100644 --- a/crates/pathfinder/src/sync/checkpoint.rs +++ b/crates/pathfinder/src/sync/checkpoint.rs @@ -364,21 +364,17 @@ async fn handle_state_diff_stream( state_updates::FetchCommitmentFromDb::new(storage.connection()?), 10, ) - .pipe(state_updates::VerifyCommitment, 10) - .pipe( - state_updates::UpdateStarknetState { - storage: storage.clone(), - connection: storage.connection()?, - current_block: start, - verify_tree_hashes, - }, - 10, - ) + .pipe(state_updates::VerifyCommitment2, 10) .into_stream() + .try_chunks(1000) + .map_err(|e| e.1) + .map_err(|e| SyncError::from_v2(e)) + .and_then(|x| { + state_updates::batch_update_starknet_state(storage.clone(), verify_tree_hashes, x) + }) .inspect_ok(|x| tracing::debug!(tail=%x.data, "State diff synced")) .try_fold((), |_, _| std::future::ready(Ok(()))) - .await - .map_err(SyncError::from_v2)?; + .await?; Ok(()) } diff --git a/crates/pathfinder/src/sync/error.rs b/crates/pathfinder/src/sync/error.rs index 3f072b726a..789af78a24 100644 --- a/crates/pathfinder/src/sync/error.rs +++ b/crates/pathfinder/src/sync/error.rs @@ -26,6 +26,8 @@ pub(super) enum SyncError { EventCommitmentMismatch(PeerId), #[error("Transaction commitment mismatch")] TransactionCommitmentMismatch(PeerId), + #[error("State root mismatch")] + StateRootMismatch(PeerId), } impl PartialEq for SyncError { @@ -56,6 +58,7 @@ impl SyncError { SyncError::TransactionCommitmentMismatch(x) => { PeerData::new(x, SyncError2::TransactionCommitmentMismatch) } + SyncError::StateRootMismatch(x) => PeerData::new(x, SyncError2::StateRootMismatch), } } @@ -75,6 +78,7 @@ impl SyncError { SyncError2::TransactionCommitmentMismatch => { SyncError::TransactionCommitmentMismatch(peer) } + SyncError2::StateRootMismatch => SyncError::StateRootMismatch(peer), other => SyncError::Other(other.into()), } } diff --git a/crates/pathfinder/src/sync/state_updates.rs b/crates/pathfinder/src/sync/state_updates.rs index b20406977d..93992fbd05 100644 --- a/crates/pathfinder/src/sync/state_updates.rs +++ b/crates/pathfinder/src/sync/state_updates.rs @@ -3,19 +3,30 @@ use std::num::NonZeroUsize; use anyhow::Context; use p2p::PeerData; -use pathfinder_common::state_update::{self, ContractClassUpdate, ContractUpdate, StateUpdateData}; +use pathfinder_common::state_update::{ + self, + ContractClassUpdate, + ContractUpdate, + StateUpdateData, + SystemContractUpdate, +}; use pathfinder_common::{ + class_hash, BlockHash, BlockHeader, BlockNumber, + CasmHash, ClassCommitment, + ClassHash, + ContractAddress, + SierraHash, StarknetVersion, StateCommitment, StateDiffCommitment, StateUpdate, StorageCommitment, }; -use pathfinder_merkle_tree::contract_state::{update_contract_state, ContractStateUpdateResult}; +use pathfinder_merkle_tree::contract_state::ContractStateUpdateResult; use pathfinder_merkle_tree::StorageCommitmentTree; use pathfinder_storage::{Storage, TrieUpdate}; use tokio::sync::mpsc; @@ -23,7 +34,7 @@ use tokio::task::spawn_blocking; use tokio_stream::wrappers::ReceiverStream; use super::storage_adapters; -use crate::state::{update_starknet_state, StarknetStateUpdate}; +use crate::state::update_starknet_state; use crate::sync::error::{SyncError, SyncError2}; use crate::sync::stream::ProcessStage; @@ -124,6 +135,200 @@ impl ProcessStage for VerifyCommitment { } } +pub struct VerifyCommitment2; + +impl ProcessStage for VerifyCommitment2 { + const NAME: &'static str = "StateDiff::Verify2"; + type Input = (StateUpdateData, BlockNumber, StateDiffCommitment); + type Output = (StateUpdateData, BlockNumber); + + fn map(&mut self, input: Self::Input) -> Result { + let (state_diff, block_number, expected_commitment) = input; + let actual_commitment = state_diff.compute_state_diff_commitment(); + + if actual_commitment != expected_commitment { + tracing::debug!(%block_number, %expected_commitment, %actual_commitment, "State diff commitment mismatch"); + return Err(SyncError2::StateDiffCommitmentMismatch); + } + + Ok((state_diff, block_number)) + } +} + +mod multi_block { + use std::collections::{HashMap, HashSet}; + + use pathfinder_common::state_update::{ + ContractClassUpdate, + ContractUpdateRef, + StateUpdateRef, + StorageRef, + SystemContractUpdateRef, + }; + use pathfinder_common::{ + CasmHash, + ClassHash, + ContractAddress, + ContractNonce, + SierraHash, + StorageAddress, + StorageValue, + }; + + #[derive(Default, Debug, Clone, PartialEq)] + pub struct StateUpdateData { + pub contract_updates: HashMap, + pub system_contract_updates: HashMap, + pub declared_cairo_classes: HashSet, + pub declared_sierra_classes: HashMap, + } + + #[derive(Default, Debug, Clone, PartialEq)] + pub struct ContractUpdate { + /// Duplicate storage addresses are possible because this update spans + /// many blocks + pub storage: Vec<(StorageAddress, StorageValue)>, + /// The __last__ (ie. highest) in the batch of blocks + pub class: Option, + /// The __last__ (ie. highest) in the batch of blocks + pub nonce: Option, + } + + #[derive(Default, Debug, Clone, PartialEq)] + pub struct SystemContractUpdate { + /// Duplicate storage addresses are possible because this update spans + /// many blocks + pub storage: Vec<(StorageAddress, StorageValue)>, + } + + impl<'a> From<&'a StateUpdateData> for StateUpdateRef<'a> { + fn from(update: &'a StateUpdateData) -> Self { + Self { + contract_updates: update + .contract_updates + .iter() + .map(|(k, v)| { + ( + k, + ContractUpdateRef { + storage: (&v.storage).into(), + class: &v.class, + nonce: &v.nonce, + }, + ) + }) + .collect(), + system_contract_updates: update + .system_contract_updates + .iter() + .map(|(k, v)| { + ( + k, + SystemContractUpdateRef { + storage: (&v.storage).into(), + }, + ) + }) + .collect(), + declared_sierra_classes: &update.declared_sierra_classes, + } + } + } +} + +pub fn merge_state_updates( + state_updates: Vec>, +) -> PeerData { + let mut merged = multi_block::StateUpdateData::default(); + let peer = state_updates.last().expect("Non empty").peer; + + state_updates + .into_iter() + .map(|PeerData { data: (sud, _), .. }| sud) + .for_each(|x| { + x.contract_updates.into_iter().for_each(|(k, v)| { + let e = merged.contract_updates.entry(k).or_default(); + e.storage.extend(v.storage); + e.nonce = v.nonce.or(e.nonce); + e.class = v.class.or(e.class); + }); + x.system_contract_updates.into_iter().for_each(|(k, v)| { + let e = merged.system_contract_updates.entry(k).or_default(); + e.storage.extend(v.storage); + }); + merged + .declared_sierra_classes + .extend(x.declared_sierra_classes); + merged + .declared_cairo_classes + .extend(x.declared_cairo_classes); + }); + + PeerData::new(peer, merged) +} + +pub async fn batch_update_starknet_state( + storage: pathfinder_storage::Storage, + verify_tree_hashes: bool, + state_updates: Vec>, +) -> Result, SyncError> { + tokio::task::spawn_blocking(move || { + let mut db = storage + .connection() + .context("Creating database connection")?; + let db = db.transaction().context("Creating database transaction")?; + + let tail = state_updates.last().expect("Non empty").data.1; + + for PeerData { + data: (state_update, block_number), + .. + } in &state_updates + { + db.insert_state_update_data(*block_number, state_update) + .context("Inserting state update data")?; + } + + let PeerData { peer, data: merged } = merge_state_updates(state_updates); + + let (storage_commitment, class_commitment) = update_starknet_state( + &db, + (&merged).into(), + verify_tree_hashes, + tail, + storage.clone(), + ) + .context("Updating Starknet state")?; + + // Ensure that roots match. + let state_commitment = StateCommitment::calculate(storage_commitment, class_commitment); + let expected_state_commitment = db + .state_commitment(tail.into()) + .context("Querying state commitment")? + .context("State commitment not found")?; + if state_commitment != expected_state_commitment { + tracing::debug!( + %tail, + actual_storage_commitment=%storage_commitment, + actual_class_commitment=%class_commitment, + actual_state_commitment=%state_commitment, + %expected_state_commitment, + "State root mismatch"); + // TODO Wrapping in PeerData does not seem to make much sense in this case, it's + // more the range of blocks that matters + return Err(SyncError::StateRootMismatch(peer)); + } + + db.update_storage_and_class_commitments(tail, storage_commitment, class_commitment) + .context("Updating storage and class commitments")?; + db.commit().context("Committing db transaction")?; + + Ok(PeerData::new(peer, tail)) + }) + .await + .context("Joining blocking task")? +} + pub struct UpdateStarknetState { pub storage: pathfinder_storage::Storage, pub connection: pathfinder_storage::Connection, @@ -147,11 +352,7 @@ impl ProcessStage for UpdateStarknetState { let (storage_commitment, class_commitment) = update_starknet_state( &db, - StarknetStateUpdate { - contract_updates: &state_update.contract_updates, - system_contract_updates: &state_update.system_contract_updates, - declared_sierra_classes: &state_update.declared_sierra_classes, - }, + (&state_update).into(), self.verify_tree_hashes, self.current_block, self.storage.clone(), diff --git a/crates/pathfinder/src/sync/track.rs b/crates/pathfinder/src/sync/track.rs index 8cd4b05b2c..f836c76828 100644 --- a/crates/pathfinder/src/sync/track.rs +++ b/crates/pathfinder/src/sync/track.rs @@ -46,7 +46,7 @@ use tokio_stream::wrappers::ReceiverStream; use super::class_definitions::CompiledClass; use super::{state_updates, transactions}; -use crate::state::{update_starknet_state, StarknetStateUpdate}; +use crate::state::update_starknet_state; use crate::sync::class_definitions::{self, ClassWithLayout}; use crate::sync::error::SyncError2; use crate::sync::stream::{ProcessStage, SyncReceiver, SyncResult}; @@ -817,11 +817,7 @@ impl ProcessStage for StoreBlock { let (storage_commitment, class_commitment) = update_starknet_state( &db, - StarknetStateUpdate { - contract_updates: &state_diff.contract_updates, - system_contract_updates: &state_diff.system_contract_updates, - declared_sierra_classes: &state_diff.declared_sierra_classes, - }, + (&state_diff).into(), self.verify_tree_hashes, block_number, self.storage.clone(), diff --git a/crates/rpc/src/lib.rs b/crates/rpc/src/lib.rs index f14b91a38e..f1fb1df08a 100644 --- a/crates/rpc/src/lib.rs +++ b/crates/rpc/src/lib.rs @@ -354,7 +354,7 @@ pub mod test_utils { // Update block 0 let update_results = update_contract_state( contract0_addr, - &contract0_update, + (&contract0_update).into(), Some(contract_nonce!("0x1")), Some(class0_hash), &db_txn, @@ -397,7 +397,7 @@ pub mod test_utils { .unwrap(); let update_results = update_contract_state( contract1_addr, - &contract1_update1, + (&contract1_update1).into(), None, Some(class1_hash), &db_txn, @@ -438,7 +438,7 @@ pub mod test_utils { StorageCommitmentTree::load(&db_txn, BlockNumber::GENESIS + 1).unwrap(); let update_results = update_contract_state( contract1_addr, - &contract1_update2, + (&contract1_update2).into(), Some(contract_nonce!("0x10")), None, &db_txn, @@ -483,7 +483,7 @@ pub mod test_utils { let update_results = update_contract_state( contract2_addr, - &HashMap::new(), + (&HashMap::new()).into(), Some(contract_nonce!("0xfeed")), Some(class2_hash), &db_txn, From 32f62550be083e80e6a3dc7d8d503851cf40e3c9 Mon Sep 17 00:00:00 2001 From: Krzysztof Lis Date: Wed, 23 Oct 2024 13:23:21 +0200 Subject: [PATCH 197/282] chore: clippy --- crates/merkle-tree/src/contract_state.rs | 4 ++-- crates/pathfinder/src/sync/checkpoint.rs | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/crates/merkle-tree/src/contract_state.rs b/crates/merkle-tree/src/contract_state.rs index d191f4c242..710bd82926 100644 --- a/crates/merkle-tree/src/contract_state.rs +++ b/crates/merkle-tree/src/contract_state.rs @@ -47,9 +47,9 @@ impl ContractStateUpdateResult { /// Updates a contract's state with and returns the resulting /// [ContractStateHash]. -pub fn update_contract_state<'a>( +pub fn update_contract_state( contract_address: ContractAddress, - updates: StorageRef<'a>, + updates: StorageRef<'_>, new_nonce: Option, new_class_hash: Option, transaction: &Transaction<'_>, diff --git a/crates/pathfinder/src/sync/checkpoint.rs b/crates/pathfinder/src/sync/checkpoint.rs index cf9a6e4517..97418ceeff 100644 --- a/crates/pathfinder/src/sync/checkpoint.rs +++ b/crates/pathfinder/src/sync/checkpoint.rs @@ -367,8 +367,7 @@ async fn handle_state_diff_stream( .pipe(state_updates::VerifyCommitment2, 10) .into_stream() .try_chunks(1000) - .map_err(|e| e.1) - .map_err(|e| SyncError::from_v2(e)) + .map_err(|e| SyncError::from_v2(e.1)) .and_then(|x| { state_updates::batch_update_starknet_state(storage.clone(), verify_tree_hashes, x) }) From 18a47c7309112298b396defca39862809971fd25 Mon Sep 17 00:00:00 2001 From: Krzysztof Lis Date: Wed, 23 Oct 2024 23:52:17 +0200 Subject: [PATCH 198/282] fix(p2p/client): internal stream producers should terminate when receiver dropped --- crates/make-stream/src/lib.rs | 14 +++++++++++ crates/p2p/src/builder.rs | 2 +- crates/p2p/src/client/peer_agnostic.rs | 34 +++++++++++++++++++------- 3 files changed, 40 insertions(+), 10 deletions(-) diff --git a/crates/make-stream/src/lib.rs b/crates/make-stream/src/lib.rs index 80925b5767..c504cd5672 100644 --- a/crates/make-stream/src/lib.rs +++ b/crates/make-stream/src/lib.rs @@ -5,6 +5,13 @@ use tokio_stream::wrappers::ReceiverStream; use tokio_stream::Stream; /// Use the sender to yield items to the stream. +/// +/// ### Warning +/// +/// Implementor of the future must ensure that the `src` future __exits if the +/// sender fails to send an item__ (ie. fails to yield an item to the stream). +/// Otherwise, the `src` future will never complete and will keep running, +/// because it is detached via `tokio::spawn`. pub fn from_future(src: U) -> impl Stream where U: FnOnce(Sender) -> V + Send + 'static, @@ -17,6 +24,13 @@ where } /// Use the sender to yield items to the stream. +/// +/// ### Warning +/// +/// Implementor of the closure must ensure that the `src` closure __exits if the +/// sender fails to send an item__ (ie. fails to yield an item to the stream). +/// Otherwise, the `src` closure will never complete and will keep running, +/// because it is detached via `std::thread::spawn`. pub fn from_blocking(src: U) -> impl Stream where T: Send + 'static, diff --git a/crates/p2p/src/builder.rs b/crates/p2p/src/builder.rs index 6b88354685..1901a958b9 100644 --- a/crates/p2p/src/builder.rs +++ b/crates/p2p/src/builder.rs @@ -57,7 +57,7 @@ impl Builder { behaviour, local_peer_id, swarm::Config::with_tokio_executor() - .with_idle_connection_timeout(Duration::from_secs(3600 * 365)), // A YEAR + .with_idle_connection_timeout(Duration::from_secs(60)), ); let (event_sender, event_receiver) = mpsc::channel(1); diff --git a/crates/p2p/src/client/peer_agnostic.rs b/crates/p2p/src/client/peer_agnostic.rs index d46eb0b26d..05dbefa308 100644 --- a/crates/p2p/src/client/peer_agnostic.rs +++ b/crates/p2p/src/client/peer_agnostic.rs @@ -685,11 +685,14 @@ mod header_stream { Ok(BlockHeadersResponse::Header(hdr)) => match SignedBlockHeader::try_from_dto(*hdr) { Ok(hdr) => { if done(direction, *start, stop) { - tracing::debug!(%peer, "Header stream Fin missing, got extra header instead"); + tracing::debug!(%peer, "Header stream Fin missing, got extra header instead, terminating"); return Action::TerminateStream; } - _ = tx.send(PeerData::new(peer, hdr)).await; + if let Err(_) = tx.send(PeerData::new(peer, hdr)).await { + tracing::debug!(%peer, "Failed to yield to stream, terminating"); + return Action::TerminateStream; + } *start = match direction { Direction::Forward => *start + 1, @@ -699,7 +702,7 @@ mod header_stream { Action::NextResponse } Err(error) => { - tracing::debug!(%peer, %error, "Header stream failed"); + tracing::debug!(%peer, %error, "Header stream failed, terminating"); if done(direction, *start, stop) { return Action::TerminateStream; } @@ -716,7 +719,7 @@ mod header_stream { Action::NextPeer } Err(error) => { - tracing::debug!(%peer, %error, "Header stream failed"); + tracing::debug!(%peer, %error, "Header stream failed, terminating"); if done(direction, *start, stop) { return Action::TerminateStream; } @@ -906,9 +909,13 @@ mod transaction_stream { ) -> bool { tracing::trace!(block_number=%start, "All transactions received for block"); - _ = tx + if let Err(_) = tx .send(Ok(PeerData::new(peer, (transactions, *start)))) - .await; + .await + { + tracing::debug!(%peer, "Failed to yield to stream, terminating"); + return true; + } if *start == stop { return true; @@ -1125,7 +1132,10 @@ mod state_diff_stream { ) -> bool { tracing::trace!(block_number=%start, "State diff received for block"); - _ = tx.send(Ok(PeerData::new(peer, (state_diff, *start)))).await; + if let Err(_) = tx.send(Ok(PeerData::new(peer, (state_diff, *start)))).await { + tracing::debug!(%peer, "Failed to yield to stream, terminating"); + return true; + } if *start == stop { return true; @@ -1307,7 +1317,10 @@ mod class_definition_stream { tracing::trace!(block_number=%start, "All classes received for block"); for class_definition in class_definitions { - _ = tx.send(Ok(PeerData::new(peer, class_definition))).await; + if let Err(_) = tx.send(Ok(PeerData::new(peer, class_definition))).await { + tracing::debug!(%peer, "Failed to yield to stream, terminating"); + return true; + } } if *start == stop { @@ -1489,7 +1502,10 @@ mod event_stream { ) -> bool { tracing::trace!(block_number=%start, "All events received for block"); - _ = tx.send(Ok(PeerData::new(peer, (*start, events)))).await; + if let Err(_) = tx.send(Ok(PeerData::new(peer, (*start, events)))).await { + tracing::debug!(%peer, "Failed to yield to stream, terminating"); + return true; + } if *start == stop { return true; From 936b26579d13e687eb148895dfec893748954a97 Mon Sep 17 00:00:00 2001 From: Krzysztof Lis Date: Fri, 25 Oct 2024 18:50:42 +0200 Subject: [PATCH 199/282] chore: clippy --- crates/p2p/src/client/peer_agnostic.rs | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/crates/p2p/src/client/peer_agnostic.rs b/crates/p2p/src/client/peer_agnostic.rs index 05dbefa308..5282e8b747 100644 --- a/crates/p2p/src/client/peer_agnostic.rs +++ b/crates/p2p/src/client/peer_agnostic.rs @@ -689,7 +689,7 @@ mod header_stream { return Action::TerminateStream; } - if let Err(_) = tx.send(PeerData::new(peer, hdr)).await { + if tx.send(PeerData::new(peer, hdr)).await.is_err() { tracing::debug!(%peer, "Failed to yield to stream, terminating"); return Action::TerminateStream; } @@ -909,9 +909,10 @@ mod transaction_stream { ) -> bool { tracing::trace!(block_number=%start, "All transactions received for block"); - if let Err(_) = tx + if tx .send(Ok(PeerData::new(peer, (transactions, *start)))) .await + .is_err() { tracing::debug!(%peer, "Failed to yield to stream, terminating"); return true; @@ -1132,7 +1133,11 @@ mod state_diff_stream { ) -> bool { tracing::trace!(block_number=%start, "State diff received for block"); - if let Err(_) = tx.send(Ok(PeerData::new(peer, (state_diff, *start)))).await { + if tx + .send(Ok(PeerData::new(peer, (state_diff, *start)))) + .await + .is_err() + { tracing::debug!(%peer, "Failed to yield to stream, terminating"); return true; } @@ -1317,7 +1322,11 @@ mod class_definition_stream { tracing::trace!(block_number=%start, "All classes received for block"); for class_definition in class_definitions { - if let Err(_) = tx.send(Ok(PeerData::new(peer, class_definition))).await { + if tx + .send(Ok(PeerData::new(peer, class_definition))) + .await + .is_err() + { tracing::debug!(%peer, "Failed to yield to stream, terminating"); return true; } @@ -1502,7 +1511,11 @@ mod event_stream { ) -> bool { tracing::trace!(block_number=%start, "All events received for block"); - if let Err(_) = tx.send(Ok(PeerData::new(peer, (*start, events)))).await { + if tx + .send(Ok(PeerData::new(peer, (*start, events)))) + .await + .is_err() + { tracing::debug!(%peer, "Failed to yield to stream, terminating"); return true; } From 464a0cf49019c7d6d84a811bc296c28ceb9f3d42 Mon Sep 17 00:00:00 2001 From: sistemd Date: Mon, 28 Oct 2024 12:44:04 +0100 Subject: [PATCH 200/282] optimize storage proof retrieval --- crates/merkle-tree/src/class.rs | 24 +- crates/merkle-tree/src/contract.rs | 47 +++- crates/merkle-tree/src/tree.rs | 275 ++++++++++++--------- crates/rpc/src/method/get_storage_proof.rs | 114 ++++----- 4 files changed, 279 insertions(+), 181 deletions(-) diff --git a/crates/merkle-tree/src/class.rs b/crates/merkle-tree/src/class.rs index 7a257850a9..4daa5ad4d7 100644 --- a/crates/merkle-tree/src/class.rs +++ b/crates/merkle-tree/src/class.rs @@ -73,7 +73,8 @@ impl<'tx> ClassCommitmentTree<'tx> { Ok((commitment, update)) } - /// Generates a proof for a given `key` + /// Generates a proof for a given `class_hash`. + /// See [`MerkleTree::get_proof`]. pub fn get_proof( tx: &'tx Transaction<'tx>, block: BlockNumber, @@ -87,6 +88,27 @@ impl<'tx> ClassCommitmentTree<'tx> { MerkleTree::::get_proof(root, &storage, class_hash.0.view_bits()) } + + /// Generates a proof for the given list of `class_hashes`. + /// See [`MerkleTree::get_proofs`]. + pub fn get_proofs( + tx: &'tx Transaction<'tx>, + block: BlockNumber, + class_hashes: &[ClassHash], + root: u64, + ) -> anyhow::Result>>> { + let storage = ClassStorage { + tx, + block: Some(block), + }; + + let keys = class_hashes + .iter() + .map(|hash| hash.0.view_bits()) + .collect::>(); + + MerkleTree::::get_proofs(root, &storage, &keys) + } } struct ClassStorage<'tx> { diff --git a/crates/merkle-tree/src/contract.rs b/crates/merkle-tree/src/contract.rs index bb7d49c746..9200765459 100644 --- a/crates/merkle-tree/src/contract.rs +++ b/crates/merkle-tree/src/contract.rs @@ -94,6 +94,29 @@ impl<'tx> ContractsStorageTree<'tx> { MerkleTree::::get_proof(root, &storage, key) } + /// Generates a proof for the given list of `keys`. + /// See [`MerkleTree::get_proofs`]. + pub fn get_proofs( + tx: &'tx Transaction<'tx>, + contract: ContractAddress, + block: BlockNumber, + keys: &[StorageAddress], + root: u64, + ) -> anyhow::Result>>> { + let storage = ContractStorage { + tx, + block: Some(block), + contract, + }; + + let keys = keys + .iter() + .map(|addr| addr.0.view_bits()) + .collect::>(); + + MerkleTree::::get_proofs(root, &storage, &keys) + } + pub fn set(&mut self, address: StorageAddress, value: StorageValue) -> anyhow::Result<()> { let key = address.view_bits().to_owned(); self.tree.set(&self.storage, key, value.0) @@ -182,7 +205,8 @@ impl<'tx> StorageCommitmentTree<'tx> { Ok((commitment, update)) } - /// Generates a proof for the given `key`. See [`MerkleTree::get_proof`]. + /// Generates a proof for the given `address`. + /// See [`MerkleTree::get_proof`]. pub fn get_proof( tx: &'tx Transaction<'tx>, block: BlockNumber, @@ -197,6 +221,27 @@ impl<'tx> StorageCommitmentTree<'tx> { MerkleTree::::get_proof(root, &storage, address.view_bits()) } + /// Generates a proof for the given list of `addresses`. + /// See [`MerkleTree::get_proofs`]. + pub fn get_proofs( + tx: &'tx Transaction<'tx>, + block: BlockNumber, + addresses: &[ContractAddress], + root: u64, + ) -> anyhow::Result>>> { + let storage = StorageTrieStorage { + tx, + block: Some(block), + }; + + let keys = addresses + .iter() + .map(|addr| addr.0.view_bits()) + .collect::>(); + + MerkleTree::::get_proofs(root, &storage, &keys) + } + /// See [`MerkleTree::dfs`] pub fn dfs) -> ControlFlow>( &mut self, diff --git a/crates/merkle-tree/src/tree.rs b/crates/merkle-tree/src/tree.rs index e5a6118a59..f681c63cd8 100644 --- a/crates/merkle-tree/src/tree.rs +++ b/crates/merkle-tree/src/tree.rs @@ -521,107 +521,134 @@ impl MerkleTree { } } - /// Generates a merkle-proof for a given `key`. + /// Single-key version of [`MerkleTree::get_proofs`]. + pub fn get_proof( + root: u64, + storage: &impl Storage, + key: &BitSlice, + ) -> anyhow::Result>> { + Self::get_proofs(root, storage, &[key]).map(|mut proofs| proofs.pop().flatten()) + } + + /// Generates merkle-proofs for a given list of `keys`. /// - /// Returns vector of [`TrieNode`] which form a chain from the root to the - /// key, if it exists, or down to the node which proves that the key - /// does not exist. + /// For each key, returns a vector of [`TrieNode`]s which form a chain from + /// the root to the key, if it exists, or down to the node which proves + /// that the key does not exist. If a node in the path is missing from + /// storage, `None` will be returned for that path. /// - /// The nodes are returned in order, root first. + /// The nodes are added to the proof in order, root first. /// /// Verification is performed by confirming that: /// 1. the chain follows the path of `key`, and /// 2. the hashes are correct, and /// 3. the root hash matches the known root - pub fn get_proof( + /// + /// Uses a cache to avoid repeated lookups. + pub fn get_proofs( root: u64, storage: &impl Storage, - key: &BitSlice, - ) -> anyhow::Result>> { - // Manually traverse towards the key. - let mut nodes = Vec::new(); - - let mut next = Some(root); - let mut height = 0; - while let Some(index) = next.take() { - let Some(node) = storage.get(index).context("Resolving node")? else { - return Ok(None); - }; + keys: &[&BitSlice], + ) -> anyhow::Result>>> { + let mut node_cache: HashMap = HashMap::new(); + let mut proofs = vec![]; + + 'key_loop: for key in keys { + // Manually traverse towards the key. + let mut nodes = Vec::new(); + + let mut next = Some(root); + let mut height = 0; + while let Some(index) = next.take() { + let node = match node_cache.get(&index) { + Some(node) => node.clone(), + None => { + let Some(node) = storage.get(index).context("Resolving node")? else { + proofs.push(None); + continue 'key_loop; + }; + node_cache.insert(index, node.clone()); + node + } + }; - let node = match node { - StoredNode::Binary { left, right } => { - // Choose the direction to go in. - next = match key.get(height).map(|b| Direction::from(*b)) { - Some(Direction::Left) => Some(left), - Some(Direction::Right) => Some(right), - None => anyhow::bail!("Key path too short for binary node"), - }; - height += 1; + let node = match node { + StoredNode::Binary { left, right } => { + // Choose the direction to go in. + next = match key.get(height).map(|b| Direction::from(*b)) { + Some(Direction::Left) => Some(left), + Some(Direction::Right) => Some(right), + None => anyhow::bail!("Key path too short for binary node"), + }; + height += 1; - let left = storage - .hash(left) - .context("Querying left child's hash")? - .context("Left child's hash is missing")?; + let left = storage + .hash(left) + .context("Querying left child's hash")? + .context("Left child's hash is missing")?; - let right = storage - .hash(right) - .context("Querying right child's hash")? - .context("Right child's hash is missing")?; + let right = storage + .hash(right) + .context("Querying right child's hash")? + .context("Right child's hash is missing")?; - TrieNode::Binary { left, right } - } - StoredNode::Edge { child, path } => { - let key = key - .get(height..height + path.len()) - .context("Key path is too short for edge node")?; - height += path.len(); - - // If the path matches then we continue otherwise the proof is complete. - if key == path { - next = Some(child); + TrieNode::Binary { left, right } } + StoredNode::Edge { child, path } => { + let key = key + .get(height..height + path.len()) + .context("Key path is too short for edge node")?; + height += path.len(); + + // If the path matches then we continue otherwise the proof is complete. + if key == path { + next = Some(child); + } - let child = storage - .hash(child) - .context("Querying child child's hash")? - .context("Child's hash is missing")?; + let child = storage + .hash(child) + .context("Querying child child's hash")? + .context("Child's hash is missing")?; - TrieNode::Edge { child, path } - } - StoredNode::LeafBinary => { - // End of the line, get child hashes. - let mut path = key[..height].to_bitvec(); - path.push(Direction::Left.into()); - let left = storage - .leaf(&path) - .context("Querying left leaf hash")? - .context("Left leaf is missing")?; - path.pop(); - path.push(Direction::Right.into()); - let right = storage - .leaf(&path) - .context("Querying right leaf hash")? - .context("Right leaf is missing")?; + TrieNode::Edge { child, path } + } + StoredNode::LeafBinary => { + // End of the line, get child hashes. + let mut path = key[..height].to_bitvec(); + path.push(Direction::Left.into()); + let left = storage + .leaf(&path) + .context("Querying left leaf hash")? + .context("Left leaf is missing")?; + path.pop(); + path.push(Direction::Right.into()); + let right = storage + .leaf(&path) + .context("Querying right leaf hash")? + .context("Right leaf is missing")?; + + TrieNode::Binary { left, right } + } + StoredNode::LeafEdge { path } => { + let mut current_path = key[..height].to_bitvec(); + // End of the line, get hash of the child. + current_path.extend_from_bitslice(&path); + let child = storage + .leaf(¤t_path) + .context("Querying leaf hash")? + .context("Child leaf is missing")?; + + TrieNode::Edge { child, path } + } + }; - TrieNode::Binary { left, right } - } - StoredNode::LeafEdge { path } => { - let mut current_path = key[..height].to_bitvec(); - // End of the line, get hash of the child. - current_path.extend_from_bitslice(&path); - let child = storage - .leaf(¤t_path) - .context("Querying leaf hash")? - .context("Child leaf is missing")?; - - TrieNode::Edge { child, path } - } - }; + nodes.push(node); + } - nodes.push(node); + proofs.push(Some(nodes)); } - Ok(Some(nodes)) + Ok(proofs) } /// Traverses from the current root towards destination node. @@ -1970,7 +1997,6 @@ mod tests { use pathfinder_crypto::Felt; use super::{Direction, TestStorage, TestTree}; - use crate::storage::Storage; use crate::tree::tests::commit_and_persist_with_pruning; #[derive(Debug, PartialEq, Eq)] @@ -2109,30 +2135,20 @@ mod tests { fn verify(&mut self) { let keys_bits: Vec<&BitSlice> = self.keys.iter().map(|k| k.view_bits()).collect(); - let proofs = get_proofs(&keys_bits, self.root_idx, &self.storage).unwrap(); + let proofs = + TestTree::get_proofs(self.root_idx, &self.storage, &keys_bits).unwrap(); keys_bits .iter() .zip(self.values.iter()) .enumerate() .for_each(|(i, (k, v))| { - let verified = verify_proof(self.root, k, *v, &proofs[i]).unwrap(); + let verified = + verify_proof(self.root, k, *v, proofs[i].as_ref().unwrap()).unwrap(); assert_eq!(verified, Membership::Member, "Failed to prove key"); }); } } - /// Generates a storage proof for each `key` in `keys` and returns the - /// result in the form of an array. - fn get_proofs( - keys: &'_ [&BitSlice], - root: u64, - storage: &impl Storage, - ) -> anyhow::Result>> { - keys.iter() - .map(|k| TestTree::get_proof(root, storage, k).map(Option::unwrap)) - .collect() - } - #[test] fn simple_binary() { let mut uut = TestTree::empty(); @@ -2156,9 +2172,10 @@ mod tests { uut.set(&storage, key2.clone(), value_2).unwrap(); let (root, root_idx) = commit_and_persist_with_pruning(uut, &mut storage); - let proofs = get_proofs(&keys, root_idx, &storage).unwrap(); + let proofs = TestTree::get_proofs(root_idx, &storage, &keys).unwrap(); - let verified_key1 = verify_proof(root, &key1, value_1, &proofs[0]).unwrap(); + let verified_key1 = + verify_proof(root, &key1, value_1, proofs[0].as_ref().unwrap()).unwrap(); assert_eq!(verified_key1, Membership::Member); } @@ -2195,14 +2212,17 @@ mod tests { let (root, root_idx) = commit_and_persist_with_pruning(uut, &mut storage); - let proofs = get_proofs(&keys, root_idx, &storage).unwrap(); - let verified_1 = verify_proof(root, &key1, value_1, &proofs[0]).unwrap(); + let proofs = TestTree::get_proofs(root_idx, &storage, &keys).unwrap(); + let verified_1 = + verify_proof(root, &key1, value_1, proofs[0].as_ref().unwrap()).unwrap(); assert_eq!(verified_1, Membership::Member, "Failed to prove key1"); - let verified_2 = verify_proof(root, &key2, value_2, &proofs[1]).unwrap(); + let verified_2 = + verify_proof(root, &key2, value_2, proofs[1].as_ref().unwrap()).unwrap(); assert_eq!(verified_2, Membership::Member, "Failed to prove key2"); - let verified_key3 = verify_proof(root, &key3, value_3, &proofs[2]).unwrap(); + let verified_key3 = + verify_proof(root, &key3, value_3, proofs[2].as_ref().unwrap()).unwrap(); assert_eq!(verified_key3, Membership::Member, "Failed to prove key3"); } @@ -2227,8 +2247,9 @@ mod tests { let (root, root_idx) = commit_and_persist_with_pruning(uut, &mut storage); - let proofs = get_proofs(&keys, root_idx, &storage).unwrap(); - let verified_1 = verify_proof(root, &key1, value_1, &proofs[0]).unwrap(); + let proofs = TestTree::get_proofs(root_idx, &storage, &keys).unwrap(); + let verified_1 = + verify_proof(root, &key1, value_1, proofs[0].as_ref().unwrap()).unwrap(); assert_eq!(verified_1, Membership::Member, "Failed to prove key1"); } @@ -2253,8 +2274,9 @@ mod tests { let (root, root_idx) = commit_and_persist_with_pruning(uut, &mut storage); - let proofs = get_proofs(&keys, root_idx, &storage).unwrap(); - let verified_1 = verify_proof(root, &key1, value_1, &proofs[0]).unwrap(); + let proofs = TestTree::get_proofs(root_idx, &storage, &keys).unwrap(); + let verified_1 = + verify_proof(root, &key1, value_1, proofs[0].as_ref().unwrap()).unwrap(); assert_eq!(verified_1, Membership::Member, "Failed to prove key1"); } @@ -2279,8 +2301,9 @@ mod tests { let (root, root_idx) = commit_and_persist_with_pruning(uut, &mut storage); - let proofs = get_proofs(&keys, root_idx, &storage).unwrap(); - let verified_1 = verify_proof(root, &key1, value_1, &proofs[0]).unwrap(); + let proofs = TestTree::get_proofs(root_idx, &storage, &keys).unwrap(); + let verified_1 = + verify_proof(root, &key1, value_1, proofs[0].as_ref().unwrap()).unwrap(); assert_eq!(verified_1, Membership::Member, "Failed to prove key1"); } @@ -2310,11 +2333,13 @@ mod tests { let (root, root_idx) = commit_and_persist_with_pruning(uut, &mut storage); - let proofs = get_proofs(&keys, root_idx, &storage).unwrap(); - let verified_1 = verify_proof(root, &key1, value_1, &proofs[0]).unwrap(); + let proofs = TestTree::get_proofs(root_idx, &storage, &keys).unwrap(); + let verified_1 = + verify_proof(root, &key1, value_1, proofs[0].as_ref().unwrap()).unwrap(); assert_eq!(verified_1, Membership::Member, "Failed to prove key1"); - let verified_2 = verify_proof(root, &key2, value_2, &proofs[1]).unwrap(); + let verified_2 = + verify_proof(root, &key2, value_2, proofs[1].as_ref().unwrap()).unwrap(); assert_eq!(verified_2, Membership::Member, "Failed to prove key2"); } @@ -2357,13 +2382,15 @@ mod tests { let keys_bits: Vec<&BitSlice> = inexistent_keys.iter().map(|k| k.view_bits()).collect(); let proofs = - get_proofs(&keys_bits, random_tree.root_idx, &random_tree.storage).unwrap(); + TestTree::get_proofs(random_tree.root_idx, &random_tree.storage, &keys_bits) + .unwrap(); keys_bits .iter() .zip(random_tree.values.iter()) .enumerate() .for_each(|(i, (k, v))| { - let verified = verify_proof(random_tree.root, k, *v, &proofs[i]).unwrap(); + let verified = + verify_proof(random_tree.root, k, *v, proofs[i].as_ref().unwrap()).unwrap(); assert_eq!(verified, Membership::NonMember); }); } @@ -2385,14 +2412,16 @@ mod tests { let keys_bits: Vec<&BitSlice> = random_tree.keys.iter().map(|k| k.view_bits()).collect(); let proofs = - get_proofs(&keys_bits[..], random_tree.root_idx, &random_tree.storage).unwrap(); + TestTree::get_proofs(random_tree.root_idx, &random_tree.storage, &keys_bits) + .unwrap(); keys_bits .iter() .zip(inexistent_values.iter()) .enumerate() .for_each(|(i, (k, v))| { - let verified = verify_proof(random_tree.root, k, *v, &proofs[i]); + let verified = + verify_proof(random_tree.root, k, *v, proofs[i].as_ref().unwrap()); assert!(verified.is_none()); }); } @@ -2423,19 +2452,19 @@ mod tests { let (root, root_idx) = commit_and_persist_with_pruning(uut, &mut storage); - let mut proofs = get_proofs(&keys, root_idx, &storage).unwrap(); + let mut proofs = TestTree::get_proofs(root_idx, &storage, &keys).unwrap(); // Modify the left hash - let new_node = match &proofs[0][0] { + let new_node = match &proofs[0].as_ref().unwrap()[0] { TrieNode::Binary { right, .. } => TrieNode::Binary { left: felt!("0x42"), right: *right, }, _ => unreachable!(), }; - proofs[0][0] = new_node; + proofs[0].as_mut().unwrap()[0] = new_node; - let verified = verify_proof(root, &key1, value_1, &proofs[0]); + let verified = verify_proof(root, &key1, value_1, proofs[0].as_ref().unwrap()); assert!(verified.is_none()); } @@ -2465,19 +2494,19 @@ mod tests { let (root, root_idx) = commit_and_persist_with_pruning(uut, &mut storage); - let mut proofs = get_proofs(&keys, root_idx, &storage).unwrap(); + let mut proofs = TestTree::get_proofs(root_idx, &storage, &keys).unwrap(); // Modify the child hash - let new_node = match &proofs[0][1] { + let new_node = match &proofs[0].as_ref().unwrap()[1] { TrieNode::Edge { path, .. } => TrieNode::Edge { child: felt!("0x42"), path: path.clone(), }, _ => unreachable!(), }; - proofs[0][1] = new_node; + proofs[0].as_mut().unwrap()[1] = new_node; - let verified = verify_proof(root, &key1, value_1, &proofs[0]); + let verified = verify_proof(root, &key1, value_1, proofs[0].as_ref().unwrap()); assert!(verified.is_none()); } } diff --git a/crates/rpc/src/method/get_storage_proof.rs b/crates/rpc/src/method/get_storage_proof.rs index 60c6ede890..b7e2a142f8 100644 --- a/crates/rpc/src/method/get_storage_proof.rs +++ b/crates/rpc/src/method/get_storage_proof.rs @@ -322,14 +322,12 @@ pub async fn get_storage_proof(context: RpcContext, input: Input) -> Result, _>>()?; NodeHashToNodeMappings( proofs @@ -360,21 +358,31 @@ pub async fn get_storage_proof(context: RpcContext, input: Input) -> Result hash, }; - let (contract_proof_nodes, contract_leaves_data) = - if let Some(contract_addresses) = input.contract_addresses { - let mut proofs = vec![]; - let mut contract_leaves_data = vec![]; - for address in contract_addresses { - let proof = StorageCommitmentTree::get_proof( - &tx, - header.number, - &address, - storage_root_idx, - ) - .context("Get proof from storage tree")? - .ok_or(Error::ProofMissing)?; - proofs.push(proof); - + let (contract_proof_nodes, contract_leaves_data) = if let Some(contract_addresses) = + input.contract_addresses + { + let proofs = StorageCommitmentTree::get_proofs( + &tx, + header.number, + &contract_addresses, + storage_root_idx, + ) + .context("Get proofs from class tree")? + .into_iter() + .map(|proof| proof.ok_or(Error::ProofMissing)) + .collect::, _>>()?; + + let nodes: Vec = proofs + .into_iter() + .flatten() + .map(|node| node.into()) + .collect::>() + .into_iter() + .collect(); + + let contract_leaves_data = contract_addresses + .iter() + .map(|&address| { let class_hash = tx .contract_class_hash(header.number.into(), address) .context("Querying contract's class hash")? @@ -385,21 +393,14 @@ pub async fn get_storage_proof(context: RpcContext, input: Input) -> Result = proofs - .into_iter() - .flatten() - .map(|node| node.into()) - .collect::>() - .into_iter() - .collect(); + Ok::(ContractLeafData { nonce, class_hash }) + }) + .collect::, _>>()?; - (NodeHashToNodeMappings(nodes), contract_leaves_data) - } else { - (NodeHashToNodeMappings(vec![]), vec![]) - }; + (NodeHashToNodeMappings(nodes), contract_leaves_data) + } else { + (NodeHashToNodeMappings(vec![]), vec![]) + }; let contracts_storage_proofs = match input.contracts_storage_keys { None => vec![], @@ -411,26 +412,27 @@ pub async fn get_storage_proof(context: RpcContext, input: Input) -> Result, _>>()?; let proof: Vec = contract_storage_proof .into_iter() @@ -739,11 +741,11 @@ mod tests { let context = RpcContext::for_tests(); let input = Input { block_id: BlockId::Number(pathfinder_common::BlockNumber::GENESIS + 2), - class_hashes: Some(vec![class_hash_bytes!(b"class 2 hash (sierra)")]), - contract_addresses: Some(vec![contract_address_bytes!(b"contract 2 (sierra)")]), + class_hashes: Some(vec![class_hash_bytes!(b"class 2 hash (sierra)"); 5]), + contract_addresses: Some(vec![contract_address_bytes!(b"contract 2 (sierra)"); 5]), contracts_storage_keys: Some(vec![ContractStorageKeys { contract_address: contract_address_bytes!(b"contract 1"), - storage_keys: vec![storage_address_bytes!(b"storage addr 0")], + storage_keys: vec![storage_address_bytes!(b"storage addr 0"); 5], }]), }; From 96d0b0ec6303df26019282f600d75f22398c92b5 Mon Sep 17 00:00:00 2001 From: sistemd Date: Mon, 28 Oct 2024 12:44:04 +0100 Subject: [PATCH 201/282] disallow returning `None` in `get_proofs` --- crates/merkle-tree/src/class.rs | 6 +- crates/merkle-tree/src/contract.rs | 10 +-- crates/merkle-tree/src/tree.rs | 83 +++++++++--------- crates/rpc/src/method/get_storage_proof.rs | 85 ++++++++----------- .../rpc/src/pathfinder/methods/get_proof.rs | 36 ++++---- 5 files changed, 105 insertions(+), 115 deletions(-) diff --git a/crates/merkle-tree/src/class.rs b/crates/merkle-tree/src/class.rs index 4daa5ad4d7..348da029dd 100644 --- a/crates/merkle-tree/src/class.rs +++ b/crates/merkle-tree/src/class.rs @@ -11,7 +11,7 @@ use pathfinder_common::{ use pathfinder_crypto::Felt; use pathfinder_storage::{Transaction, TrieUpdate}; -use crate::tree::MerkleTree; +use crate::tree::{GetProofError, MerkleTree}; /// A [Patricia Merkle tree](MerkleTree) used to calculate commitments to /// Starknet's Sierra classes. @@ -80,7 +80,7 @@ impl<'tx> ClassCommitmentTree<'tx> { block: BlockNumber, class_hash: ClassHash, root: u64, - ) -> anyhow::Result>> { + ) -> Result, GetProofError> { let storage = ClassStorage { tx, block: Some(block), @@ -96,7 +96,7 @@ impl<'tx> ClassCommitmentTree<'tx> { block: BlockNumber, class_hashes: &[ClassHash], root: u64, - ) -> anyhow::Result>>> { + ) -> Result>, GetProofError> { let storage = ClassStorage { tx, block: Some(block), diff --git a/crates/merkle-tree/src/contract.rs b/crates/merkle-tree/src/contract.rs index 9200765459..1b00b2a065 100644 --- a/crates/merkle-tree/src/contract.rs +++ b/crates/merkle-tree/src/contract.rs @@ -24,7 +24,7 @@ use pathfinder_crypto::Felt; use pathfinder_storage::{Transaction, TrieUpdate}; use crate::merkle_node::InternalNode; -use crate::tree::{MerkleTree, Visit}; +use crate::tree::{GetProofError, MerkleTree, Visit}; /// A [Patricia Merkle tree](MerkleTree) used to calculate commitments to a /// Starknet contract's storage. @@ -84,7 +84,7 @@ impl<'tx> ContractsStorageTree<'tx> { block: BlockNumber, key: &BitSlice, root: u64, - ) -> anyhow::Result>> { + ) -> Result, GetProofError> { let storage = ContractStorage { tx, block: Some(block), @@ -102,7 +102,7 @@ impl<'tx> ContractsStorageTree<'tx> { block: BlockNumber, keys: &[StorageAddress], root: u64, - ) -> anyhow::Result>>> { + ) -> Result>, GetProofError> { let storage = ContractStorage { tx, block: Some(block), @@ -212,7 +212,7 @@ impl<'tx> StorageCommitmentTree<'tx> { block: BlockNumber, address: &ContractAddress, root: u64, - ) -> anyhow::Result>> { + ) -> Result, GetProofError> { let storage = StorageTrieStorage { tx, block: Some(block), @@ -228,7 +228,7 @@ impl<'tx> StorageCommitmentTree<'tx> { block: BlockNumber, addresses: &[ContractAddress], root: u64, - ) -> anyhow::Result>>> { + ) -> Result>, GetProofError> { let storage = StorageTrieStorage { tx, block: Some(block), diff --git a/crates/merkle-tree/src/tree.rs b/crates/merkle-tree/src/tree.rs index f681c63cd8..b945d1f140 100644 --- a/crates/merkle-tree/src/tree.rs +++ b/crates/merkle-tree/src/tree.rs @@ -526,16 +526,16 @@ impl MerkleTree { root: u64, storage: &impl Storage, key: &BitSlice, - ) -> anyhow::Result>> { - Self::get_proofs(root, storage, &[key]).map(|mut proofs| proofs.pop().flatten()) + ) -> Result, GetProofError> { + Self::get_proofs(root, storage, &[key]) + .map(|proofs| proofs.into_iter().next().expect("Single proof is present")) } /// Generates merkle-proofs for a given list of `keys`. /// /// For each key, returns a vector of [`TrieNode`]s which form a chain from /// the root to the key, if it exists, or down to the node which proves - /// that the key does not exist. If a node in the path is missing from - /// storage, `None` will be returned for that path. + /// that the key does not exist. /// /// The nodes are added to the proof in order, root first. /// @@ -549,11 +549,11 @@ impl MerkleTree { root: u64, storage: &impl Storage, keys: &[&BitSlice], - ) -> anyhow::Result>>> { + ) -> Result>, GetProofError> { let mut node_cache: HashMap = HashMap::new(); let mut proofs = vec![]; - 'key_loop: for key in keys { + for key in keys { // Manually traverse towards the key. let mut nodes = Vec::new(); @@ -564,8 +564,7 @@ impl MerkleTree { Some(node) => node.clone(), None => { let Some(node) = storage.get(index).context("Resolving node")? else { - proofs.push(None); - continue 'key_loop; + return Err(GetProofError::StorageNodeMissing(index)); }; node_cache.insert(index, node.clone()); node @@ -578,7 +577,11 @@ impl MerkleTree { next = match key.get(height).map(|b| Direction::from(*b)) { Some(Direction::Left) => Some(left), Some(Direction::Right) => Some(right), - None => anyhow::bail!("Key path too short for binary node"), + None => { + return Err( + anyhow::anyhow!("Key path too short for binary node").into() + ) + } }; height += 1; @@ -645,7 +648,7 @@ impl MerkleTree { nodes.push(node); } - proofs.push(Some(nodes)); + proofs.push(nodes); } Ok(proofs) @@ -892,6 +895,18 @@ impl MerkleTree { } } +#[derive(Debug)] +pub enum GetProofError { + Internal(anyhow::Error), + StorageNodeMissing(u64), +} + +impl From for GetProofError { + fn from(e: anyhow::Error) -> Self { + Self::Internal(e) + } +} + /// Direction for the [`MerkleTree::dfs`] as the return value of the visitor /// function. #[derive(Default)] @@ -2142,8 +2157,7 @@ mod tests { .zip(self.values.iter()) .enumerate() .for_each(|(i, (k, v))| { - let verified = - verify_proof(self.root, k, *v, proofs[i].as_ref().unwrap()).unwrap(); + let verified = verify_proof(self.root, k, *v, &proofs[i]).unwrap(); assert_eq!(verified, Membership::Member, "Failed to prove key"); }); } @@ -2174,8 +2188,7 @@ mod tests { let proofs = TestTree::get_proofs(root_idx, &storage, &keys).unwrap(); - let verified_key1 = - verify_proof(root, &key1, value_1, proofs[0].as_ref().unwrap()).unwrap(); + let verified_key1 = verify_proof(root, &key1, value_1, &proofs[0]).unwrap(); assert_eq!(verified_key1, Membership::Member); } @@ -2213,16 +2226,13 @@ mod tests { let (root, root_idx) = commit_and_persist_with_pruning(uut, &mut storage); let proofs = TestTree::get_proofs(root_idx, &storage, &keys).unwrap(); - let verified_1 = - verify_proof(root, &key1, value_1, proofs[0].as_ref().unwrap()).unwrap(); + let verified_1 = verify_proof(root, &key1, value_1, &proofs[0]).unwrap(); assert_eq!(verified_1, Membership::Member, "Failed to prove key1"); - let verified_2 = - verify_proof(root, &key2, value_2, proofs[1].as_ref().unwrap()).unwrap(); + let verified_2 = verify_proof(root, &key2, value_2, &proofs[1]).unwrap(); assert_eq!(verified_2, Membership::Member, "Failed to prove key2"); - let verified_key3 = - verify_proof(root, &key3, value_3, proofs[2].as_ref().unwrap()).unwrap(); + let verified_key3 = verify_proof(root, &key3, value_3, &proofs[2]).unwrap(); assert_eq!(verified_key3, Membership::Member, "Failed to prove key3"); } @@ -2248,8 +2258,7 @@ mod tests { let (root, root_idx) = commit_and_persist_with_pruning(uut, &mut storage); let proofs = TestTree::get_proofs(root_idx, &storage, &keys).unwrap(); - let verified_1 = - verify_proof(root, &key1, value_1, proofs[0].as_ref().unwrap()).unwrap(); + let verified_1 = verify_proof(root, &key1, value_1, &proofs[0]).unwrap(); assert_eq!(verified_1, Membership::Member, "Failed to prove key1"); } @@ -2275,8 +2284,7 @@ mod tests { let (root, root_idx) = commit_and_persist_with_pruning(uut, &mut storage); let proofs = TestTree::get_proofs(root_idx, &storage, &keys).unwrap(); - let verified_1 = - verify_proof(root, &key1, value_1, proofs[0].as_ref().unwrap()).unwrap(); + let verified_1 = verify_proof(root, &key1, value_1, &proofs[0]).unwrap(); assert_eq!(verified_1, Membership::Member, "Failed to prove key1"); } @@ -2302,8 +2310,7 @@ mod tests { let (root, root_idx) = commit_and_persist_with_pruning(uut, &mut storage); let proofs = TestTree::get_proofs(root_idx, &storage, &keys).unwrap(); - let verified_1 = - verify_proof(root, &key1, value_1, proofs[0].as_ref().unwrap()).unwrap(); + let verified_1 = verify_proof(root, &key1, value_1, &proofs[0]).unwrap(); assert_eq!(verified_1, Membership::Member, "Failed to prove key1"); } @@ -2334,12 +2341,10 @@ mod tests { let (root, root_idx) = commit_and_persist_with_pruning(uut, &mut storage); let proofs = TestTree::get_proofs(root_idx, &storage, &keys).unwrap(); - let verified_1 = - verify_proof(root, &key1, value_1, proofs[0].as_ref().unwrap()).unwrap(); + let verified_1 = verify_proof(root, &key1, value_1, &proofs[0]).unwrap(); assert_eq!(verified_1, Membership::Member, "Failed to prove key1"); - let verified_2 = - verify_proof(root, &key2, value_2, proofs[1].as_ref().unwrap()).unwrap(); + let verified_2 = verify_proof(root, &key2, value_2, &proofs[1]).unwrap(); assert_eq!(verified_2, Membership::Member, "Failed to prove key2"); } @@ -2389,8 +2394,7 @@ mod tests { .zip(random_tree.values.iter()) .enumerate() .for_each(|(i, (k, v))| { - let verified = - verify_proof(random_tree.root, k, *v, proofs[i].as_ref().unwrap()).unwrap(); + let verified = verify_proof(random_tree.root, k, *v, &proofs[i]).unwrap(); assert_eq!(verified, Membership::NonMember); }); } @@ -2420,8 +2424,7 @@ mod tests { .zip(inexistent_values.iter()) .enumerate() .for_each(|(i, (k, v))| { - let verified = - verify_proof(random_tree.root, k, *v, proofs[i].as_ref().unwrap()); + let verified = verify_proof(random_tree.root, k, *v, &proofs[i]); assert!(verified.is_none()); }); } @@ -2455,16 +2458,16 @@ mod tests { let mut proofs = TestTree::get_proofs(root_idx, &storage, &keys).unwrap(); // Modify the left hash - let new_node = match &proofs[0].as_ref().unwrap()[0] { + let new_node = match &proofs[0][0] { TrieNode::Binary { right, .. } => TrieNode::Binary { left: felt!("0x42"), right: *right, }, _ => unreachable!(), }; - proofs[0].as_mut().unwrap()[0] = new_node; + proofs[0][0] = new_node; - let verified = verify_proof(root, &key1, value_1, proofs[0].as_ref().unwrap()); + let verified = verify_proof(root, &key1, value_1, &proofs[0]); assert!(verified.is_none()); } @@ -2497,16 +2500,16 @@ mod tests { let mut proofs = TestTree::get_proofs(root_idx, &storage, &keys).unwrap(); // Modify the child hash - let new_node = match &proofs[0].as_ref().unwrap()[1] { + let new_node = match &proofs[0][1] { TrieNode::Edge { path, .. } => TrieNode::Edge { child: felt!("0x42"), path: path.clone(), }, _ => unreachable!(), }; - proofs[0].as_mut().unwrap()[1] = new_node; + proofs[0][1] = new_node; - let verified = verify_proof(root, &key1, value_1, proofs[0].as_ref().unwrap()); + let verified = verify_proof(root, &key1, value_1, &proofs[0]); assert!(verified.is_none()); } } diff --git a/crates/rpc/src/method/get_storage_proof.rs b/crates/rpc/src/method/get_storage_proof.rs index b7e2a142f8..6962d40252 100644 --- a/crates/rpc/src/method/get_storage_proof.rs +++ b/crates/rpc/src/method/get_storage_proof.rs @@ -1,6 +1,6 @@ use std::collections::HashSet; -use anyhow::{anyhow, Context}; +use anyhow::Context; use pathfinder_common::hash::PedersenHash; use pathfinder_common::trie::TrieNode; use pathfinder_common::{ @@ -12,6 +12,7 @@ use pathfinder_common::{ StorageAddress, }; use pathfinder_crypto::Felt; +use pathfinder_merkle_tree::tree::GetProofError; use pathfinder_merkle_tree::{ClassCommitmentTree, ContractsStorageTree, StorageCommitmentTree}; use crate::context::RpcContext; @@ -52,6 +53,18 @@ impl From for Error { } } +impl From for Error { + fn from(e: GetProofError) -> Self { + match e { + GetProofError::Internal(e) => Self::Internal(e), + GetProofError::StorageNodeMissing(index) => { + tracing::warn!("Storage node missing: {}", index); + Self::ProofMissing + } + } + } +} + // Doing this manually since `generate_rpc_error_subset!` // does not support enum struct variants. impl From for crate::error::ApplicationError { @@ -322,22 +335,16 @@ pub async fn get_storage_proof(context: RpcContext, input: Input) -> Result, _>>()?; - - NodeHashToNodeMappings( - proofs + let nodes: Vec = + ClassCommitmentTree::get_proofs(&tx, header.number, &class_hashes, class_root_idx)? .into_iter() .flatten() .map(|node| node.into()) .collect::>() .into_iter() - .collect::>(), - ) + .collect(); + + NodeHashToNodeMappings(nodes) } else { NodeHashToNodeMappings(vec![]) }; @@ -361,24 +368,18 @@ pub async fn get_storage_proof(context: RpcContext, input: Input) -> Result = StorageCommitmentTree::get_proofs( &tx, header.number, &contract_addresses, storage_root_idx, - ) - .context("Get proofs from class tree")? + )? .into_iter() - .map(|proof| proof.ok_or(Error::ProofMissing)) - .collect::, _>>()?; - - let nodes: Vec = proofs - .into_iter() - .flatten() - .map(|node| node.into()) - .collect::>() - .into_iter() - .collect(); + .flatten() + .map(|node| node.into()) + .collect::>() + .into_iter() + .collect(); let contract_leaves_data = contract_addresses .iter() @@ -412,37 +413,21 @@ pub async fn get_storage_proof(context: RpcContext, input: Input) -> Result = ContractsStorageTree::get_proofs( &tx, csk.contract_address, header.number, &csk.storage_keys, root, - ) - .context("Get proofs from contract storage tree")? + )? + .into_iter() + .flatten() + .map(|node| node.into()) + .collect::>() .into_iter() - .enumerate() - .map(|(i, proof)| { - proof.ok_or_else(|| { - let e = anyhow!( - "Storage proof missing for key: {:?}, but should be present", - csk.storage_keys.get(i) - ); - tracing::warn!("{e}"); - e - }) - }) - .collect::, _>>()?; - - let proof: Vec = contract_storage_proof - .into_iter() - .flatten() - .map(|node| node.into()) - .collect::>() - .into_iter() - .collect(); - - proofs.push(NodeHashToNodeMappings(proof)); + .collect(); + + proofs.push(NodeHashToNodeMappings(nodes)); } else { proofs.push(NodeHashToNodeMappings(vec![])); } diff --git a/crates/rpc/src/pathfinder/methods/get_proof.rs b/crates/rpc/src/pathfinder/methods/get_proof.rs index bf9fe3f13d..04c4623987 100644 --- a/crates/rpc/src/pathfinder/methods/get_proof.rs +++ b/crates/rpc/src/pathfinder/methods/get_proof.rs @@ -3,7 +3,7 @@ use pathfinder_common::prelude::*; use pathfinder_common::trie::TrieNode; use pathfinder_common::BlockId; use pathfinder_crypto::Felt; -use pathfinder_merkle_tree::{ClassCommitmentTree, ContractsStorageTree, StorageCommitmentTree}; +use pathfinder_merkle_tree::{ClassCommitmentTree, ContractsStorageTree, StorageCommitmentTree, tree}; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; @@ -62,6 +62,18 @@ impl From for GetProofError { } } +impl From for GetProofError { + fn from(e: tree::GetProofError) -> Self { + match e { + tree::GetProofError::Internal(e) => Self::Internal(e), + tree::GetProofError::StorageNodeMissing(index) => { + tracing::warn!("Storage node missing: {}", index); + Self::ProofMissing + } + } + } +} + impl From for crate::error::ApplicationError { fn from(x: GetProofError) -> Self { match x { @@ -256,9 +268,8 @@ pub async fn get_proof( header.number, &input.contract_address, storage_root_idx, - ) - .context("Creating contract proof")? - .ok_or(GetProofError::ProofMissing)?; + )?; + let contract_proof = ProofNodes(contract_proof); let contract_state_hash = tx @@ -302,16 +313,8 @@ pub async fn get_proof( header.number, k.view_bits(), root, - ) - .context("Get proof from contract state tree")? - .ok_or_else(|| { - let e = anyhow!( - "Storage proof missing for key {:?}, but should be present", - k - ); - tracing::warn!("{e}"); - e - })?; + )?; + storage_proofs.push(ProofNodes(proof)); } else { storage_proofs.push(ProofNodes(vec![])); @@ -385,9 +388,8 @@ pub async fn get_proof_class( // Generate a proof for this class. If the class does not exist, this will // be a "non membership" proof. let class_proof = - ClassCommitmentTree::get_proof(&tx, header.number, input.class_hash, class_root_idx) - .context("Creating class proof")? - .ok_or(GetProofError::ProofMissing)?; + ClassCommitmentTree::get_proof(&tx, header.number, input.class_hash, class_root_idx)?; + let class_proof = ProofNodes(class_proof); Ok(GetClassProofOutput { From 0961ca3327339f5278993b4f41f3f050552d573a Mon Sep 17 00:00:00 2001 From: sistemd Date: Mon, 28 Oct 2024 12:44:04 +0100 Subject: [PATCH 202/282] fmt --- crates/rpc/src/pathfinder/methods/get_proof.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/crates/rpc/src/pathfinder/methods/get_proof.rs b/crates/rpc/src/pathfinder/methods/get_proof.rs index 04c4623987..50c0f53ff0 100644 --- a/crates/rpc/src/pathfinder/methods/get_proof.rs +++ b/crates/rpc/src/pathfinder/methods/get_proof.rs @@ -3,7 +3,12 @@ use pathfinder_common::prelude::*; use pathfinder_common::trie::TrieNode; use pathfinder_common::BlockId; use pathfinder_crypto::Felt; -use pathfinder_merkle_tree::{ClassCommitmentTree, ContractsStorageTree, StorageCommitmentTree, tree}; +use pathfinder_merkle_tree::{ + tree, + ClassCommitmentTree, + ContractsStorageTree, + StorageCommitmentTree, +}; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; From 9ae3fd20a544b06adf8d4895a67cfd4eccb3c88a Mon Sep 17 00:00:00 2001 From: sistemd Date: Mon, 28 Oct 2024 12:51:19 +0100 Subject: [PATCH 203/282] avoid node hash recalculation --- crates/merkle-tree/src/class.rs | 13 +-- crates/merkle-tree/src/contract.rs | 23 +++-- crates/merkle-tree/src/tree.rs | 99 ++++++++++++++----- crates/rpc/src/method/get_storage_proof.rs | 95 +++++++++--------- .../rpc/src/pathfinder/methods/get_proof.rs | 15 ++- 5 files changed, 155 insertions(+), 90 deletions(-) diff --git a/crates/merkle-tree/src/class.rs b/crates/merkle-tree/src/class.rs index 348da029dd..83ca0360ff 100644 --- a/crates/merkle-tree/src/class.rs +++ b/crates/merkle-tree/src/class.rs @@ -1,6 +1,5 @@ use anyhow::Context; use pathfinder_common::hash::PoseidonHash; -use pathfinder_common::trie::TrieNode; use pathfinder_common::{ BlockNumber, ClassCommitment, @@ -11,7 +10,7 @@ use pathfinder_common::{ use pathfinder_crypto::Felt; use pathfinder_storage::{Transaction, TrieUpdate}; -use crate::tree::{GetProofError, MerkleTree}; +use crate::tree::{GetProofError, MerkleTree, TrieNodeWithHash}; /// A [Patricia Merkle tree](MerkleTree) used to calculate commitments to /// Starknet's Sierra classes. @@ -80,7 +79,7 @@ impl<'tx> ClassCommitmentTree<'tx> { block: BlockNumber, class_hash: ClassHash, root: u64, - ) -> Result, GetProofError> { + ) -> Result, GetProofError> { let storage = ClassStorage { tx, block: Some(block), @@ -89,14 +88,16 @@ impl<'tx> ClassCommitmentTree<'tx> { MerkleTree::::get_proof(root, &storage, class_hash.0.view_bits()) } - /// Generates a proof for the given list of `class_hashes`. - /// See [`MerkleTree::get_proofs`]. + /// Generates proofs for the given list of `class_hashes`. See + /// [`MerkleTree::get_proofs`]. Returns [(`TrieNode`, + /// `Felt`)](TrieNodeWithHash) pairs where the second element is the + /// node hash. pub fn get_proofs( tx: &'tx Transaction<'tx>, block: BlockNumber, class_hashes: &[ClassHash], root: u64, - ) -> Result>, GetProofError> { + ) -> Result>, GetProofError> { let storage = ClassStorage { tx, block: Some(block), diff --git a/crates/merkle-tree/src/contract.rs b/crates/merkle-tree/src/contract.rs index 1b00b2a065..418827c2cb 100644 --- a/crates/merkle-tree/src/contract.rs +++ b/crates/merkle-tree/src/contract.rs @@ -10,7 +10,6 @@ use anyhow::Context; use bitvec::prelude::Msb0; use bitvec::slice::BitSlice; use pathfinder_common::hash::PedersenHash; -use pathfinder_common::trie::TrieNode; use pathfinder_common::{ BlockNumber, ContractAddress, @@ -24,7 +23,7 @@ use pathfinder_crypto::Felt; use pathfinder_storage::{Transaction, TrieUpdate}; use crate::merkle_node::InternalNode; -use crate::tree::{GetProofError, MerkleTree, Visit}; +use crate::tree::{GetProofError, MerkleTree, TrieNodeWithHash, Visit}; /// A [Patricia Merkle tree](MerkleTree) used to calculate commitments to a /// Starknet contract's storage. @@ -84,7 +83,7 @@ impl<'tx> ContractsStorageTree<'tx> { block: BlockNumber, key: &BitSlice, root: u64, - ) -> Result, GetProofError> { + ) -> Result, GetProofError> { let storage = ContractStorage { tx, block: Some(block), @@ -94,15 +93,17 @@ impl<'tx> ContractsStorageTree<'tx> { MerkleTree::::get_proof(root, &storage, key) } - /// Generates a proof for the given list of `keys`. - /// See [`MerkleTree::get_proofs`]. + /// Generates proofs for the given list of `keys`. See + /// [`MerkleTree::get_proofs`]. Returns [(`TrieNode`, + /// `Felt`)](TrieNodeWithHash) pairs where the second element is the + /// node hash. pub fn get_proofs( tx: &'tx Transaction<'tx>, contract: ContractAddress, block: BlockNumber, keys: &[StorageAddress], root: u64, - ) -> Result>, GetProofError> { + ) -> Result>, GetProofError> { let storage = ContractStorage { tx, block: Some(block), @@ -212,7 +213,7 @@ impl<'tx> StorageCommitmentTree<'tx> { block: BlockNumber, address: &ContractAddress, root: u64, - ) -> Result, GetProofError> { + ) -> Result, GetProofError> { let storage = StorageTrieStorage { tx, block: Some(block), @@ -221,14 +222,16 @@ impl<'tx> StorageCommitmentTree<'tx> { MerkleTree::::get_proof(root, &storage, address.view_bits()) } - /// Generates a proof for the given list of `addresses`. - /// See [`MerkleTree::get_proofs`]. + /// Generates proofs for the given list of `addresses`. See + /// [`MerkleTree::get_proofs`]. Returns [(`TrieNode`, + /// `Felt`)](TrieNodeWithHash) pairs where the second element is the + /// node hash. pub fn get_proofs( tx: &'tx Transaction<'tx>, block: BlockNumber, addresses: &[ContractAddress], root: u64, - ) -> Result>, GetProofError> { + ) -> Result>, GetProofError> { let storage = StorageTrieStorage { tx, block: Some(block), diff --git a/crates/merkle-tree/src/tree.rs b/crates/merkle-tree/src/tree.rs index b945d1f140..8118d23f9a 100644 --- a/crates/merkle-tree/src/tree.rs +++ b/crates/merkle-tree/src/tree.rs @@ -526,16 +526,17 @@ impl MerkleTree { root: u64, storage: &impl Storage, key: &BitSlice, - ) -> Result, GetProofError> { + ) -> Result, GetProofError> { Self::get_proofs(root, storage, &[key]) .map(|proofs| proofs.into_iter().next().expect("Single proof is present")) } /// Generates merkle-proofs for a given list of `keys`. /// - /// For each key, returns a vector of [`TrieNode`]s which form a chain from - /// the root to the key, if it exists, or down to the node which proves - /// that the key does not exist. + /// For each key, returns a vector of [`(TrieNode, Felt)`](TrieNodeWithHash) + /// pairs. The second element of each pair is the node hash. + /// The nodes form a chain from the root to the key, if it exists, or down + /// to the node which proves that the key does not exist. /// /// The nodes are added to the proof in order, root first. /// @@ -544,13 +545,15 @@ impl MerkleTree { /// 2. the hashes are correct, and /// 3. the root hash matches the known root /// - /// Uses a cache to avoid repeated lookups. + /// Uses caching to avoid repeated lookups. pub fn get_proofs( root: u64, storage: &impl Storage, keys: &[&BitSlice], - ) -> Result>, GetProofError> { + ) -> Result>, GetProofError> { let mut node_cache: HashMap = HashMap::new(); + let mut node_hash_cache: HashMap = HashMap::new(); + let mut proofs = vec![]; for key in keys { @@ -645,7 +648,18 @@ impl MerkleTree { } }; - nodes.push(node); + let node_hash = match node_hash_cache.get(&index) { + Some(&hash) => hash, + None => { + let hash = storage + .hash(index) + .context("Querying node hash")? + .context("Node hash is missing")?; + node_hash_cache.insert(index, hash); + hash + } + }; + nodes.push((node, node_hash)); } proofs.push(nodes); @@ -895,6 +909,8 @@ impl MerkleTree { } } +pub type TrieNodeWithHash = (TrieNode, Felt); + #[derive(Debug)] pub enum GetProofError { Internal(anyhow::Error), @@ -2011,7 +2027,7 @@ mod tests { use pathfinder_common::trie::TrieNode; use pathfinder_crypto::Felt; - use super::{Direction, TestStorage, TestTree}; + use super::{Direction, TestStorage, TestTree, TrieNodeWithHash}; use crate::tree::tests::commit_and_persist_with_pruning; #[derive(Debug, PartialEq, Eq)] @@ -2048,7 +2064,7 @@ mod tests { root: Felt, key: &BitSlice, value: Felt, - proofs: &[TrieNode], + proofs: &[TrieNodeWithHash], ) -> Option { // Protect from ill-formed keys if key.len() != 251 { @@ -2058,7 +2074,7 @@ mod tests { let mut expected_hash = root; let mut remaining_path: &BitSlice = key; - for proof_node in proofs.iter() { + for (proof_node, _) in proofs.iter() { // Hash mismatch? Return None. if proof_node.hash::() != expected_hash { return None; @@ -2458,14 +2474,17 @@ mod tests { let mut proofs = TestTree::get_proofs(root_idx, &storage, &keys).unwrap(); // Modify the left hash - let new_node = match &proofs[0][0] { - TrieNode::Binary { right, .. } => TrieNode::Binary { - left: felt!("0x42"), - right: *right, - }, + let new_node_with_hash = match &proofs[0][0] { + (TrieNode::Binary { right, .. }, hash) => { + let node = TrieNode::Binary { + left: felt!("0x42"), + right: *right, + }; + (node, *hash) + } _ => unreachable!(), }; - proofs[0][0] = new_node; + proofs[0][0] = new_node_with_hash; let verified = verify_proof(root, &key1, value_1, &proofs[0]); assert!(verified.is_none()); @@ -2500,17 +2519,53 @@ mod tests { let mut proofs = TestTree::get_proofs(root_idx, &storage, &keys).unwrap(); // Modify the child hash - let new_node = match &proofs[0][1] { - TrieNode::Edge { path, .. } => TrieNode::Edge { - child: felt!("0x42"), - path: path.clone(), - }, + let new_node_with_hash = match &proofs[0][1] { + (TrieNode::Edge { path, .. }, hash) => { + let node = TrieNode::Edge { + child: felt!("0x42"), + path: path.clone(), + }; + (node, *hash) + } _ => unreachable!(), }; - proofs[0][1] = new_node; + proofs[0][1] = new_node_with_hash; let verified = verify_proof(root, &key1, value_1, &proofs[0]); assert!(verified.is_none()); } + + #[test] + fn verify_simple_proof_with_correct_hashes() { + let mut uut = TestTree::empty(); + let mut storage = TestStorage::default(); + + // (251,0x00,0x99) + // / + // / + // (0x99) + + let key_1 = felt!("0x0"); // 0b00 + + let key1 = key_1.view_bits().to_owned(); + let keys = vec![key1.as_bitslice()]; + + let value_1 = felt!("0xaa"); + + uut.set(&storage, key1.clone(), value_1).unwrap(); + + let (root, root_idx) = commit_and_persist_with_pruning(uut, &mut storage); + + let proofs = TestTree::get_proofs(root_idx, &storage, &keys).unwrap(); + + for proof in proofs.iter() { + let verified_1 = verify_proof(root, &key1, value_1, proof).unwrap(); + assert_eq!(verified_1, Membership::Member, "Failed to prove key1"); + + for (node, hash) in proof.iter() { + assert_eq!(node.hash::(), *hash); + } + } + } } } diff --git a/crates/rpc/src/method/get_storage_proof.rs b/crates/rpc/src/method/get_storage_proof.rs index 6962d40252..ba18d53c5c 100644 --- a/crates/rpc/src/method/get_storage_proof.rs +++ b/crates/rpc/src/method/get_storage_proof.rs @@ -1,7 +1,6 @@ use std::collections::HashSet; use anyhow::Context; -use pathfinder_common::hash::PedersenHash; use pathfinder_common::trie::TrieNode; use pathfinder_common::{ BlockHash, @@ -144,16 +143,6 @@ struct NodeHashToNodeMapping { node: ProofNode, } -impl From for NodeHashToNodeMapping { - fn from(node: TrieNode) -> Self { - let node_hash = node.hash::(); - Self { - node_hash, - node: ProofNode(node), - } - } -} - impl SerializeForVersion for &NodeHashToNodeMapping { fn serialize( &self, @@ -339,7 +328,10 @@ pub async fn get_storage_proof(context: RpcContext, input: Input) -> Result>() .into_iter() .collect(); @@ -365,43 +357,45 @@ pub async fn get_storage_proof(context: RpcContext, input: Input) -> Result hash, }; - let (contract_proof_nodes, contract_leaves_data) = if let Some(contract_addresses) = - input.contract_addresses - { - let nodes: Vec = StorageCommitmentTree::get_proofs( - &tx, - header.number, - &contract_addresses, - storage_root_idx, - )? - .into_iter() - .flatten() - .map(|node| node.into()) - .collect::>() - .into_iter() - .collect(); - - let contract_leaves_data = contract_addresses - .iter() - .map(|&address| { - let class_hash = tx - .contract_class_hash(header.number.into(), address) - .context("Querying contract's class hash")? - .unwrap_or_default(); - - let nonce = tx - .contract_nonce(address, header.number.into()) - .context("Querying contract's nonce")? - .unwrap_or_default(); - - Ok::(ContractLeafData { nonce, class_hash }) + let (contract_proof_nodes, contract_leaves_data) = + if let Some(contract_addresses) = input.contract_addresses { + let nodes = StorageCommitmentTree::get_proofs( + &tx, + header.number, + &contract_addresses, + storage_root_idx, + )? + .into_iter() + .flatten() + .map(|(node, node_hash)| NodeHashToNodeMapping { + node_hash, + node: ProofNode(node), }) - .collect::, _>>()?; - - (NodeHashToNodeMappings(nodes), contract_leaves_data) - } else { - (NodeHashToNodeMappings(vec![]), vec![]) - }; + .collect::>() + .into_iter() + .collect(); + + let contract_leaves_data = contract_addresses + .iter() + .map(|&address| { + let class_hash = tx + .contract_class_hash(header.number.into(), address) + .context("Querying contract's class hash")? + .unwrap_or_default(); + + let nonce = tx + .contract_nonce(address, header.number.into()) + .context("Querying contract's nonce")? + .unwrap_or_default(); + + Ok(ContractLeafData { nonce, class_hash }) + }) + .collect::, Error>>()?; + + (NodeHashToNodeMappings(nodes), contract_leaves_data) + } else { + (NodeHashToNodeMappings(vec![]), vec![]) + }; let contracts_storage_proofs = match input.contracts_storage_keys { None => vec![], @@ -422,7 +416,10 @@ pub async fn get_storage_proof(context: RpcContext, input: Input) -> Result>() .into_iter() .collect(); diff --git a/crates/rpc/src/pathfinder/methods/get_proof.rs b/crates/rpc/src/pathfinder/methods/get_proof.rs index 50c0f53ff0..cf80c97fc3 100644 --- a/crates/rpc/src/pathfinder/methods/get_proof.rs +++ b/crates/rpc/src/pathfinder/methods/get_proof.rs @@ -273,7 +273,10 @@ pub async fn get_proof( header.number, &input.contract_address, storage_root_idx, - )?; + )? + .into_iter() + .map(|(node, _)| node) + .collect(); let contract_proof = ProofNodes(contract_proof); @@ -318,7 +321,10 @@ pub async fn get_proof( header.number, k.view_bits(), root, - )?; + )? + .into_iter() + .map(|(node, _)| node) + .collect(); storage_proofs.push(ProofNodes(proof)); } else { @@ -393,7 +399,10 @@ pub async fn get_proof_class( // Generate a proof for this class. If the class does not exist, this will // be a "non membership" proof. let class_proof = - ClassCommitmentTree::get_proof(&tx, header.number, input.class_hash, class_root_idx)?; + ClassCommitmentTree::get_proof(&tx, header.number, input.class_hash, class_root_idx)? + .into_iter() + .map(|(node, _)| node) + .collect(); let class_proof = ProofNodes(class_proof); From c865437e72fa03d7256f15a90b340f4dc069b7de Mon Sep 17 00:00:00 2001 From: t00ts Date: Tue, 29 Oct 2024 10:24:23 +0100 Subject: [PATCH 204/282] feat(rpc): add l2 gas data to `FeeEstimate` --- crates/executor/src/types.rs | 32 +-- crates/pathfinder/examples/re_execute.rs | 4 +- crates/rpc/src/dto/fee.rs | 29 ++- crates/rpc/src/method/estimate_fee.rs | 200 +++++++++++------- crates/rpc/src/method/estimate_message_fee.rs | 20 +- crates/rpc/src/v06/method/estimate_fee.rs | 77 ++++--- .../src/v06/method/estimate_message_fee.rs | 20 +- .../src/v06/method/simulate_transactions.rs | 8 +- 8 files changed, 237 insertions(+), 153 deletions(-) diff --git a/crates/executor/src/types.rs b/crates/executor/src/types.rs index 13a9dc8690..a3e19499a3 100644 --- a/crates/executor/src/types.rs +++ b/crates/executor/src/types.rs @@ -18,10 +18,12 @@ use super::felt::IntoFelt; #[derive(Debug, PartialEq, Eq)] pub struct FeeEstimate { - pub gas_consumed: primitive_types::U256, - pub gas_price: primitive_types::U256, - pub data_gas_consumed: primitive_types::U256, - pub data_gas_price: primitive_types::U256, + pub l1_gas_consumed: primitive_types::U256, + pub l1_gas_price: primitive_types::U256, + pub l1_data_gas_consumed: primitive_types::U256, + pub l1_data_gas_price: primitive_types::U256, + pub l2_gas_consumed: primitive_types::U256, + pub l2_gas_price: primitive_types::U256, pub overall_fee: primitive_types::U256, pub unit: PriceUnit, } @@ -35,23 +37,23 @@ impl FeeEstimate { minimal_l1_gas_amount_vector: &Option, ) -> FeeEstimate { tracing::trace!(resources=?tx_info.transaction_receipt.resources, "Transaction resources"); - let gas_price = block_info + let l1_gas_price = block_info .gas_prices .get_gas_price_by_fee_type(&fee_type) .get(); - let data_gas_price = block_info + let l1_data_gas_price = block_info .gas_prices .get_data_gas_price_by_fee_type(&fee_type) .get(); let minimal_l1_gas_amount_vector = minimal_l1_gas_amount_vector.unwrap_or_default(); - let gas_consumed = tx_info + let l1_gas_consumed = tx_info .transaction_receipt .gas .l1_gas .max(minimal_l1_gas_amount_vector.l1_gas); - let data_gas_consumed = tx_info + let l1_data_gas_consumed = tx_info .transaction_receipt .gas .l1_data_gas @@ -63,18 +65,20 @@ impl FeeEstimate { let overall_fee = blockifier::fee::fee_utils::get_fee_by_gas_vector( block_info, GasVector { - l1_gas: gas_consumed, - l1_data_gas: data_gas_consumed, + l1_gas: l1_gas_consumed, + l1_data_gas: l1_data_gas_consumed, }, &fee_type, ) .0; FeeEstimate { - gas_consumed: gas_consumed.into(), - gas_price: gas_price.into(), - data_gas_consumed: data_gas_consumed.into(), - data_gas_price: data_gas_price.into(), + l1_gas_consumed: l1_gas_consumed.into(), + l1_gas_price: l1_gas_price.into(), + l1_data_gas_consumed: l1_data_gas_consumed.into(), + l1_data_gas_price: l1_data_gas_price.into(), + l2_gas_consumed: 0.into(), // TODO: Fix when we have l2 gas price + l2_gas_price: 0.into(), // TODO: Fix when we have l2 gas price overall_fee: overall_fee.into(), unit: fee_type.into(), } diff --git a/crates/pathfinder/examples/re_execute.rs b/crates/pathfinder/examples/re_execute.rs index dab13897b1..93f2e66cec 100644 --- a/crates/pathfinder/examples/re_execute.rs +++ b/crates/pathfinder/examples/re_execute.rs @@ -193,8 +193,8 @@ fn execute(storage: &mut Storage, chain_id: ChainId, work: Work) { receipt.execution_resources.total_gas_consumed.l1_gas }; - let estimated_gas_consumed = estimate.gas_consumed.as_u128(); - let estimated_data_gas_consumed = estimate.data_gas_consumed.as_u128(); + let estimated_gas_consumed = estimate.l1_gas_consumed.as_u128(); + let estimated_data_gas_consumed = estimate.l1_data_gas_consumed.as_u128(); let gas_diff = actual_gas_consumed.abs_diff(estimated_gas_consumed); let data_gas_diff = actual_data_gas_consumed.abs_diff(estimated_data_gas_consumed); diff --git a/crates/rpc/src/dto/fee.rs b/crates/rpc/src/dto/fee.rs index de21db6bfa..f81da27e46 100644 --- a/crates/rpc/src/dto/fee.rs +++ b/crates/rpc/src/dto/fee.rs @@ -9,12 +9,29 @@ impl crate::dto::serialize::SerializeForVersion for FeeEstimate<'_> { serializer: crate::dto::serialize::Serializer, ) -> Result { let mut serializer = serializer.serialize_struct()?; - serializer.serialize_field("gas_consumed", &U256Hex(self.0.gas_consumed))?; - serializer.serialize_field("gas_price", &U256Hex(self.0.gas_price))?; - serializer.serialize_field("data_gas_consumed", &U256Hex(self.0.data_gas_consumed))?; - serializer.serialize_field("data_gas_price", &U256Hex(self.0.data_gas_price))?; - serializer.serialize_field("overall_fee", &U256Hex(self.0.overall_fee))?; - serializer.serialize_field("unit", &PriceUnit(&self.0.unit))?; + + if serializer.version >= crate::dto::RpcVersion::V08 { + serializer.serialize_field("l1_gas_consumed", &U256Hex(self.0.l1_gas_consumed))?; + serializer.serialize_field("l1_gas_price", &U256Hex(self.0.l1_gas_price))?; + serializer.serialize_field( + "l1_data_gas_consumed", + &U256Hex(self.0.l1_data_gas_consumed), + )?; + serializer.serialize_field("l1_data_gas_price", &U256Hex(self.0.l1_data_gas_price))?; + serializer.serialize_field("l2_gas_consumed", &U256Hex(self.0.l2_gas_consumed))?; + serializer.serialize_field("l2_gas_price", &U256Hex(self.0.l2_gas_price))?; + serializer.serialize_field("overall_fee", &U256Hex(self.0.overall_fee))?; + serializer.serialize_field("unit", &PriceUnit(&self.0.unit))?; + } else { + serializer.serialize_field("gas_price", &U256Hex(self.0.l1_gas_price))?; + serializer.serialize_field("gas_consumed", &U256Hex(self.0.l1_gas_consumed))?; + serializer + .serialize_field("data_gas_consumed", &U256Hex(self.0.l1_data_gas_consumed))?; + serializer.serialize_field("data_gas_price", &U256Hex(self.0.l1_data_gas_price))?; + serializer.serialize_field("overall_fee", &U256Hex(self.0.overall_fee))?; + serializer.serialize_field("unit", &PriceUnit(&self.0.unit))?; + } + serializer.end() } } diff --git a/crates/rpc/src/method/estimate_fee.rs b/crates/rpc/src/method/estimate_fee.rs index 7d3b36e3ba..5e1bce73b1 100644 --- a/crates/rpc/src/method/estimate_fee.rs +++ b/crates/rpc/src/method/estimate_fee.rs @@ -380,45 +380,55 @@ mod tests { }; let result = estimate_fee(context, input).await.unwrap(); let declare_expected = FeeEstimate { - gas_consumed: 23817.into(), - gas_price: 1.into(), + l1_gas_consumed: 23817.into(), + l1_gas_price: 1.into(), + l1_data_gas_consumed: 192.into(), + l1_data_gas_price: 2.into(), + l2_gas_consumed: 0.into(), + l2_gas_price: 0.into(), overall_fee: 24201.into(), unit: PriceUnit::Wei, - data_gas_consumed: 192.into(), - data_gas_price: 2.into(), }; let deploy_expected = FeeEstimate { - gas_consumed: 16.into(), - gas_price: 1.into(), + l1_gas_consumed: 16.into(), + l1_gas_price: 1.into(), + l1_data_gas_consumed: 224.into(), + l1_data_gas_price: 2.into(), + l2_gas_consumed: 0.into(), + l2_gas_price: 0.into(), overall_fee: 464.into(), unit: PriceUnit::Wei, - data_gas_consumed: 224.into(), - data_gas_price: 2.into(), }; let invoke_expected = FeeEstimate { - gas_consumed: 12.into(), - gas_price: 1.into(), + l1_gas_consumed: 12.into(), + l1_gas_price: 1.into(), + l1_data_gas_consumed: 128.into(), + l1_data_gas_price: 2.into(), + l2_gas_consumed: 0.into(), + l2_gas_price: 0.into(), overall_fee: 268.into(), unit: PriceUnit::Wei, - data_gas_consumed: 128.into(), - data_gas_price: 2.into(), }; let invoke_v0_expected = FeeEstimate { - gas_consumed: 10.into(), - gas_price: 1.into(), + l1_gas_consumed: 10.into(), + l1_gas_price: 1.into(), + l1_data_gas_consumed: 128.into(), + l1_data_gas_price: 2.into(), + l2_gas_consumed: 0.into(), + l2_gas_price: 0.into(), overall_fee: 266.into(), unit: PriceUnit::Wei, - data_gas_consumed: 128.into(), - data_gas_price: 2.into(), }; let invoke_v3_expected = FeeEstimate { - gas_consumed: 12.into(), + l1_gas_consumed: 12.into(), // STRK gas price is 2 - gas_price: 2.into(), + l1_gas_price: 2.into(), + l1_data_gas_consumed: 128.into(), + l1_data_gas_price: 2.into(), + l2_gas_consumed: 0.into(), + l2_gas_price: 0.into(), overall_fee: 280.into(), unit: PriceUnit::Fri, - data_gas_consumed: 128.into(), - data_gas_price: 2.into(), }; assert_eq!( result, @@ -465,45 +475,55 @@ mod tests { }; let result = estimate_fee(context, input).await.unwrap(); let declare_expected = FeeEstimate { - gas_consumed: 878.into(), - gas_price: 1.into(), + l1_gas_consumed: 878.into(), + l1_gas_price: 1.into(), + l1_data_gas_consumed: 192.into(), + l1_data_gas_price: 2.into(), + l2_gas_consumed: 0.into(), + l2_gas_price: 0.into(), overall_fee: 1262.into(), unit: PriceUnit::Wei, - data_gas_consumed: 192.into(), - data_gas_price: 2.into(), }; let deploy_expected = FeeEstimate { - gas_consumed: 16.into(), - gas_price: 1.into(), + l1_gas_consumed: 16.into(), + l1_gas_price: 1.into(), + l1_data_gas_consumed: 224.into(), + l1_data_gas_price: 2.into(), + l2_gas_consumed: 0.into(), + l2_gas_price: 0.into(), overall_fee: 464.into(), unit: PriceUnit::Wei, - data_gas_consumed: 224.into(), - data_gas_price: 2.into(), }; let invoke_expected = FeeEstimate { - gas_consumed: 12.into(), - gas_price: 1.into(), + l1_gas_consumed: 12.into(), + l1_gas_price: 1.into(), + l1_data_gas_consumed: 128.into(), + l1_data_gas_price: 2.into(), + l2_gas_consumed: 0.into(), + l2_gas_price: 0.into(), overall_fee: 268.into(), unit: PriceUnit::Wei, - data_gas_consumed: 128.into(), - data_gas_price: 2.into(), }; let invoke_v0_expected = FeeEstimate { - gas_consumed: 10.into(), - gas_price: 1.into(), + l1_gas_consumed: 10.into(), + l1_gas_price: 1.into(), + l1_data_gas_consumed: 128.into(), + l1_data_gas_price: 2.into(), + l2_gas_consumed: 0.into(), + l2_gas_price: 0.into(), overall_fee: 266.into(), unit: PriceUnit::Wei, - data_gas_consumed: 128.into(), - data_gas_price: 2.into(), }; let invoke_v3_expected = FeeEstimate { - gas_consumed: 12.into(), + l1_gas_consumed: 12.into(), // STRK gas price is 2 - gas_price: 2.into(), + l1_gas_price: 2.into(), + l1_data_gas_consumed: 128.into(), + l1_data_gas_price: 2.into(), + l2_gas_consumed: 0.into(), + l2_gas_price: 0.into(), overall_fee: 280.into(), unit: PriceUnit::Fri, - data_gas_consumed: 128.into(), - data_gas_price: 2.into(), }; assert_eq!( result, @@ -550,45 +570,55 @@ mod tests { }; let result = super::estimate_fee(context, input).await.unwrap(); let declare_expected = FeeEstimate { - gas_consumed: 23819.into(), - gas_price: 1.into(), + l1_gas_consumed: 23819.into(), + l1_gas_price: 1.into(), + l1_data_gas_consumed: 192.into(), + l1_data_gas_price: 2.into(), + l2_gas_consumed: 0.into(), + l2_gas_price: 0.into(), overall_fee: 24203.into(), unit: PriceUnit::Wei, - data_gas_consumed: 192.into(), - data_gas_price: 2.into(), }; let deploy_expected = FeeEstimate { - gas_consumed: 19.into(), - gas_price: 1.into(), + l1_gas_consumed: 19.into(), + l1_gas_price: 1.into(), + l1_data_gas_consumed: 224.into(), + l1_data_gas_price: 2.into(), + l2_gas_consumed: 0.into(), + l2_gas_price: 0.into(), overall_fee: 467.into(), unit: PriceUnit::Wei, - data_gas_consumed: 224.into(), - data_gas_price: 2.into(), }; let invoke_expected = FeeEstimate { - gas_consumed: 14.into(), - gas_price: 1.into(), + l1_gas_consumed: 14.into(), + l1_gas_price: 1.into(), + l1_data_gas_consumed: 128.into(), + l1_data_gas_price: 2.into(), + l2_gas_consumed: 0.into(), + l2_gas_price: 0.into(), overall_fee: 270.into(), unit: PriceUnit::Wei, - data_gas_consumed: 128.into(), - data_gas_price: 2.into(), }; let invoke_v0_expected = FeeEstimate { - gas_consumed: 11.into(), - gas_price: 1.into(), + l1_gas_consumed: 11.into(), + l1_gas_price: 1.into(), + l1_data_gas_consumed: 128.into(), + l1_data_gas_price: 2.into(), + l2_gas_consumed: 0.into(), + l2_gas_price: 0.into(), overall_fee: 267.into(), unit: PriceUnit::Wei, - data_gas_consumed: 128.into(), - data_gas_price: 2.into(), }; let invoke_v3_expected = FeeEstimate { - gas_consumed: 14.into(), + l1_gas_consumed: 14.into(), // STRK gas price is 2 - gas_price: 2.into(), + l1_gas_price: 2.into(), + l1_data_gas_consumed: 128.into(), + l1_data_gas_price: 2.into(), + l2_gas_consumed: 0.into(), + l2_gas_price: 0.into(), overall_fee: 284.into(), unit: PriceUnit::Fri, - data_gas_consumed: 128.into(), - data_gas_price: 2.into(), }; assert_eq!( result, @@ -635,45 +665,55 @@ mod tests { }; let result = super::estimate_fee(context, input).await.unwrap(); let declare_expected = FeeEstimate { - gas_consumed: 880.into(), - gas_price: 1.into(), + l1_gas_consumed: 880.into(), + l1_gas_price: 1.into(), + l1_data_gas_consumed: 192.into(), + l1_data_gas_price: 2.into(), + l2_gas_consumed: 0.into(), + l2_gas_price: 0.into(), overall_fee: 1264.into(), unit: PriceUnit::Wei, - data_gas_consumed: 192.into(), - data_gas_price: 2.into(), }; let deploy_expected = FeeEstimate { - gas_consumed: 19.into(), - gas_price: 1.into(), + l1_gas_consumed: 19.into(), + l1_gas_price: 1.into(), + l1_data_gas_consumed: 224.into(), + l1_data_gas_price: 2.into(), + l2_gas_consumed: 0.into(), + l2_gas_price: 0.into(), overall_fee: 467.into(), unit: PriceUnit::Wei, - data_gas_consumed: 224.into(), - data_gas_price: 2.into(), }; let invoke_expected = FeeEstimate { - gas_consumed: 14.into(), - gas_price: 1.into(), + l1_gas_consumed: 14.into(), + l1_gas_price: 1.into(), + l1_data_gas_consumed: 128.into(), + l1_data_gas_price: 2.into(), + l2_gas_consumed: 0.into(), + l2_gas_price: 0.into(), overall_fee: 270.into(), unit: PriceUnit::Wei, - data_gas_consumed: 128.into(), - data_gas_price: 2.into(), }; let invoke_v0_expected = FeeEstimate { - gas_consumed: 11.into(), - gas_price: 1.into(), + l1_gas_consumed: 11.into(), + l1_gas_price: 1.into(), + l1_data_gas_consumed: 128.into(), + l1_data_gas_price: 2.into(), + l2_gas_consumed: 0.into(), + l2_gas_price: 0.into(), overall_fee: 267.into(), unit: PriceUnit::Wei, - data_gas_consumed: 128.into(), - data_gas_price: 2.into(), }; let invoke_v3_expected = FeeEstimate { - gas_consumed: 14.into(), + l1_gas_consumed: 14.into(), // STRK gas price is 2 - gas_price: 2.into(), + l1_gas_price: 2.into(), + l1_data_gas_consumed: 128.into(), + l1_data_gas_price: 2.into(), + l2_gas_consumed: 0.into(), + l2_gas_price: 0.into(), overall_fee: 284.into(), unit: PriceUnit::Fri, - data_gas_consumed: 128.into(), - data_gas_price: 2.into(), }; assert_eq!( result, diff --git a/crates/rpc/src/method/estimate_message_fee.rs b/crates/rpc/src/method/estimate_message_fee.rs index 1367463fe7..f9cc925a73 100644 --- a/crates/rpc/src/method/estimate_message_fee.rs +++ b/crates/rpc/src/method/estimate_message_fee.rs @@ -78,12 +78,14 @@ pub async fn estimate_message_fee( let result = result.pop().unwrap(); Ok(Output(pathfinder_executor::types::FeeEstimate { - gas_consumed: result.gas_consumed, - gas_price: result.gas_price, + l1_gas_consumed: result.l1_gas_consumed, + l1_gas_price: result.l1_gas_price, + l1_data_gas_consumed: result.l1_data_gas_consumed, + l1_data_gas_price: result.l1_data_gas_price, + l2_gas_consumed: result.l2_gas_consumed, + l2_gas_price: result.l2_gas_price, overall_fee: result.overall_fee, unit: result.unit, - data_gas_consumed: result.data_gas_consumed, - data_gas_price: result.data_gas_price, })) } @@ -311,10 +313,12 @@ mod tests { #[tokio::test] async fn test_estimate_message_fee() { let expected = super::Output(pathfinder_executor::types::FeeEstimate { - gas_consumed: 14647.into(), - gas_price: 2.into(), - data_gas_consumed: 128.into(), - data_gas_price: 1.into(), + l1_gas_consumed: 14647.into(), + l1_gas_price: 2.into(), + l1_data_gas_consumed: 128.into(), + l1_data_gas_price: 1.into(), + l2_gas_consumed: 0.into(), + l2_gas_price: 0.into(), overall_fee: 29422.into(), unit: pathfinder_executor::types::PriceUnit::Wei, }); diff --git a/crates/rpc/src/v06/method/estimate_fee.rs b/crates/rpc/src/v06/method/estimate_fee.rs index 600e0e83e9..ad899f55e4 100644 --- a/crates/rpc/src/v06/method/estimate_fee.rs +++ b/crates/rpc/src/v06/method/estimate_fee.rs @@ -98,15 +98,19 @@ impl From for ApplicationError { #[derive(Clone, Debug, serde::Serialize, PartialEq, Eq)] pub struct FeeEstimate { #[serde_as(as = "pathfinder_serde::U256AsHexStr")] - pub gas_consumed: primitive_types::U256, + pub l1_gas_consumed: primitive_types::U256, #[serde_as(as = "pathfinder_serde::U256AsHexStr")] - pub gas_price: primitive_types::U256, + pub l1_gas_price: primitive_types::U256, #[serde_as(as = "Option")] #[serde(skip_serializing_if = "Option::is_none")] - pub data_gas_consumed: Option, + pub l1_data_gas_consumed: Option, #[serde_as(as = "Option")] #[serde(skip_serializing_if = "Option::is_none")] - pub data_gas_price: Option, + pub l1_data_gas_price: Option, + #[serde_as(as = "pathfinder_serde::U256AsHexStr")] + pub l2_gas_consumed: primitive_types::U256, + #[serde_as(as = "pathfinder_serde::U256AsHexStr")] + pub l2_gas_price: primitive_types::U256, #[serde_as(as = "pathfinder_serde::U256AsHexStr")] pub overall_fee: primitive_types::U256, pub unit: PriceUnit, @@ -115,20 +119,22 @@ pub struct FeeEstimate { impl From for FeeEstimate { fn from(value: pathfinder_executor::types::FeeEstimate) -> Self { Self { - gas_consumed: value.gas_consumed, - gas_price: value.gas_price, + l1_gas_consumed: value.l1_gas_consumed, + l1_gas_price: value.l1_gas_price, + l1_data_gas_consumed: Some(value.l1_data_gas_consumed), + l1_data_gas_price: Some(value.l1_data_gas_price), + l2_gas_consumed: value.l2_gas_consumed, + l2_gas_price: value.l2_gas_price, overall_fee: value.overall_fee, unit: value.unit.into(), - data_gas_consumed: Some(value.data_gas_consumed), - data_gas_price: Some(value.data_gas_price), } } } impl FeeEstimate { pub fn with_v06_format(&mut self) { - self.data_gas_consumed = None; - self.data_gas_price = None; + self.l1_data_gas_consumed = None; + self.l1_data_gas_price = None; } } @@ -467,45 +473,54 @@ pub(crate) mod tests { }; let result = estimate_fee(context, input).await.unwrap(); let declare_expected = FeeEstimate { - gas_consumed: 2768.into(), - gas_price: 1.into(), + l1_gas_consumed: 2768.into(), + l1_gas_price: 1.into(), + l1_data_gas_consumed: None, + l1_data_gas_price: None, + l2_gas_consumed: 0.into(), + l2_gas_price: 0.into(), overall_fee: 2768.into(), unit: PriceUnit::Wei, - data_gas_consumed: None, - data_gas_price: None, }; let deploy_expected = FeeEstimate { - gas_consumed: 3020.into(), - gas_price: 1.into(), + l1_gas_consumed: 3020.into(), + l1_gas_price: 1.into(), + l1_data_gas_consumed: None, + l1_data_gas_price: None, + l2_gas_consumed: 0.into(), + l2_gas_price: 0.into(), overall_fee: 3020.into(), unit: PriceUnit::Wei, - data_gas_consumed: None, - data_gas_price: None, }; let invoke_expected = FeeEstimate { - gas_consumed: 1674.into(), - gas_price: 1.into(), + l1_gas_consumed: 1674.into(), + l1_gas_price: 1.into(), + l1_data_gas_consumed: None, + l1_data_gas_price: None, + l2_gas_consumed: 0.into(), + l2_gas_price: 0.into(), overall_fee: 1674.into(), unit: PriceUnit::Wei, - data_gas_consumed: None, - data_gas_price: None, }; let invoke_v0_expected = FeeEstimate { - gas_consumed: 1669.into(), - gas_price: 1.into(), + l1_gas_consumed: 1669.into(), + l1_gas_price: 1.into(), + l1_data_gas_consumed: None, + l1_data_gas_price: None, + l2_gas_consumed: 0.into(), + l2_gas_price: 0.into(), overall_fee: 1669.into(), unit: PriceUnit::Wei, - data_gas_consumed: None, - data_gas_price: None, }; let invoke_v3_expected = FeeEstimate { - gas_consumed: 1674.into(), - // STRK gas price is 2 - gas_price: 2.into(), + l1_gas_consumed: 1674.into(), + l1_gas_price: 2.into(), + l1_data_gas_consumed: None, + l1_data_gas_price: None, + l2_gas_consumed: 0.into(), + l2_gas_price: 0.into(), overall_fee: 3348.into(), unit: PriceUnit::Fri, - data_gas_consumed: None, - data_gas_price: None, }; assert_eq!( result, diff --git a/crates/rpc/src/v06/method/estimate_message_fee.rs b/crates/rpc/src/v06/method/estimate_message_fee.rs index 830324c26a..57174b4b62 100644 --- a/crates/rpc/src/v06/method/estimate_message_fee.rs +++ b/crates/rpc/src/v06/method/estimate_message_fee.rs @@ -109,12 +109,14 @@ pub async fn estimate_message_fee( estimate_message_fee_impl(context, input, L1BlobDataAvailability::Disabled).await?; Ok(FeeEstimate { - gas_consumed: result.gas_consumed, - gas_price: result.gas_price, + l1_gas_consumed: result.l1_gas_consumed, + l1_gas_price: result.l1_gas_price, + l1_data_gas_consumed: Some(result.l1_data_gas_consumed), + l1_data_gas_price: Some(result.l1_data_gas_price), + l2_gas_consumed: result.l2_gas_consumed, + l2_gas_price: result.l2_gas_price, overall_fee: result.overall_fee, unit: result.unit.into(), - data_gas_consumed: None, - data_gas_price: None, }) } @@ -405,12 +407,14 @@ mod tests { #[tokio::test] async fn test_estimate_message_fee() { let expected = FeeEstimate { - gas_consumed: 16302.into(), - gas_price: 1.into(), + l1_gas_consumed: 16302.into(), + l1_gas_price: 1.into(), + l1_data_gas_consumed: Some(0.into()), + l1_data_gas_price: Some(1.into()), + l2_gas_consumed: 0.into(), + l2_gas_price: 0.into(), overall_fee: 16302.into(), unit: PriceUnit::Wei, - data_gas_consumed: None, - data_gas_price: None, }; let rpc = setup(Setup::Full).await.expect("RPC context"); diff --git a/crates/rpc/src/v06/method/simulate_transactions.rs b/crates/rpc/src/v06/method/simulate_transactions.rs index 1f01c8119b..75357db6bc 100644 --- a/crates/rpc/src/v06/method/simulate_transactions.rs +++ b/crates/rpc/src/v06/method/simulate_transactions.rs @@ -227,10 +227,10 @@ pub mod dto { impl From for FeeEstimate { fn from(value: pathfinder_executor::types::FeeEstimate) -> Self { Self { - gas_consumed: value.gas_consumed, - gas_price: value.gas_price, - data_gas_consumed: Some(value.data_gas_consumed), - data_gas_price: Some(value.data_gas_price), + gas_consumed: value.l1_gas_consumed, + gas_price: value.l1_gas_price, + data_gas_consumed: Some(value.l1_data_gas_consumed), + data_gas_price: Some(value.l1_data_gas_price), overall_fee: value.overall_fee, unit: value.unit.into(), } From 23b75a6c7dd9de81ff376daa94751cbd1be86310 Mon Sep 17 00:00:00 2001 From: t00ts Date: Thu, 31 Oct 2024 12:18:04 +0100 Subject: [PATCH 205/282] feat(rpc): restore `starknet_getBlockWithReceipts` in v08 routes --- crates/rpc/src/lib.rs | 1 - crates/rpc/src/v08.rs | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/rpc/src/lib.rs b/crates/rpc/src/lib.rs index f1fb1df08a..783b2cbaa3 100644 --- a/crates/rpc/src/lib.rs +++ b/crates/rpc/src/lib.rs @@ -904,7 +904,6 @@ mod tests { #[case::root_pathfinder("/", "pathfinder_rpc_api.json", &["pathfinder_version", "pathfinder_getTransactionStatus"])] #[case::v0_8_api ("/rpc/v0_8", "v08/starknet_api_openrpc.json", &[ - "starknet_getBlockWithReceipts", "starknet_getTransactionReceipt", ])] #[case::v0_8_trace("/rpc/v0_8", "v08/starknet_trace_api_openrpc.json", &[])] diff --git a/crates/rpc/src/v08.rs b/crates/rpc/src/v08.rs index 4d13f9422e..35617cc65e 100644 --- a/crates/rpc/src/v08.rs +++ b/crates/rpc/src/v08.rs @@ -31,6 +31,7 @@ pub fn register_routes() -> RpcRouterBuilder { .register("starknet_getTransactionByBlockIdAndIndex", crate::method::get_transaction_by_block_id_and_index) .register("starknet_getTransactionByHash", crate::method::get_transaction_by_hash) .register("starknet_getTransactionStatus", crate::method::get_transaction_status) + .register("starknet_getBlockWithReceipts", crate::method::get_block_with_receipts) .register("starknet_simulateTransactions", crate::method::simulate_transactions) .register("starknet_subscribeNewHeads", SubscribeNewHeads) .register("starknet_subscribePendingTransactions", SubscribePendingTransactions) From 3d01aecc1574d2561934103c6dd2495ea2876380 Mon Sep 17 00:00:00 2001 From: Vaclav Barta Date: Fri, 25 Oct 2024 15:55:40 +0200 Subject: [PATCH 206/282] added class.class_hash --- crates/p2p/src/client/peer_agnostic.rs | 6 ++++-- crates/p2p/src/client/peer_agnostic/fixtures.rs | 3 +++ crates/p2p_proto/proto/class.proto | 1 + crates/p2p_proto/src/class.rs | 15 ++++++++++----- .../pathfinder/src/p2p_network/sync_handlers.rs | 2 ++ .../src/p2p_network/sync_handlers/tests.rs | 4 ++-- 6 files changed, 22 insertions(+), 9 deletions(-) diff --git a/crates/p2p/src/client/peer_agnostic.rs b/crates/p2p/src/client/peer_agnostic.rs index 5282e8b747..e5bf2ec398 100644 --- a/crates/p2p/src/client/peer_agnostic.rs +++ b/crates/p2p/src/client/peer_agnostic.rs @@ -513,6 +513,7 @@ impl BlockClient for Client { Ok(ClassesResponse::Class(p2p_proto::class::Class::Cairo0 { class, domain: _, + class_hash: _, })) => { let definition = CairoDefinition::try_from_dto(class) .map_err(|_| ClassDefinitionsError::CairoDefinitionError(peer))?; @@ -524,6 +525,7 @@ impl BlockClient for Client { Ok(ClassesResponse::Class(p2p_proto::class::Class::Cairo1 { class, domain: _, + class_hash: _, })) => { let definition = SierraDefinition::try_from_dto(class) .map_err(|_| ClassDefinitionsError::SierraDefinitionError(peer))?; @@ -1272,7 +1274,7 @@ mod class_definition_stream { block_number: BlockNumber, ) -> Option { match response { - Ok(ClassesResponse::Class(p2p_proto::class::Class::Cairo0 { class, domain: _ })) => { + Ok(ClassesResponse::Class(p2p_proto::class::Class::Cairo0 { class, domain: _, class_hash: _ })) => { let Ok(CairoDefinition(definition)) = CairoDefinition::try_from_dto(class) else { // TODO punish the peer tracing::debug!(%peer, "Cairo definition failed to parse"); @@ -1284,7 +1286,7 @@ mod class_definition_stream { definition, }) } - Ok(ClassesResponse::Class(p2p_proto::class::Class::Cairo1 { class, domain: _ })) => { + Ok(ClassesResponse::Class(p2p_proto::class::Class::Cairo1 { class, domain: _, class_hash: _ })) => { let Ok(SierraDefinition(definition)) = SierraDefinition::try_from_dto(class) else { // TODO punish the peer tracing::debug!(%peer, "Sierra definition failed to parse"); diff --git a/crates/p2p/src/client/peer_agnostic/fixtures.rs b/crates/p2p/src/client/peer_agnostic/fixtures.rs index f2b5f1d047..7f57d5b864 100644 --- a/crates/p2p/src/client/peer_agnostic/fixtures.rs +++ b/crates/p2p/src/client/peer_agnostic/fixtures.rs @@ -25,6 +25,7 @@ use pathfinder_common::{ TransactionHash, TransactionIndex, }; +use pathfinder_crypto::Felt; use tagged::Tagged; use tagged_debug_derive::TaggedDebug; use tokio::sync::Mutex; @@ -275,10 +276,12 @@ pub fn class_resp(tag: i32) -> ClassesResponse { ClassDefinition::Sierra(s) => Class::Cairo1 { class: s.to_dto(), domain: 0, + class_hash: Hash(Felt::default()), }, ClassDefinition::Cairo(c) => Class::Cairo0 { class: c.to_dto(), domain: 0, + class_hash: Hash(Felt::default()), }, } }) diff --git a/crates/p2p_proto/proto/class.proto b/crates/p2p_proto/proto/class.proto index 3fc11d4aef..2ed53bcf87 100644 --- a/crates/p2p_proto/proto/class.proto +++ b/crates/p2p_proto/proto/class.proto @@ -41,6 +41,7 @@ message Class { Cairo1Class cairo1 = 2; } uint32 domain = 3; + starknet.common.Hash class_hash = 4; } message ClassesRequest { diff --git a/crates/p2p_proto/src/class.rs b/crates/p2p_proto/src/class.rs index 47ea38be20..ccad0fdcc3 100644 --- a/crates/p2p_proto/src/class.rs +++ b/crates/p2p_proto/src/class.rs @@ -5,7 +5,7 @@ use pathfinder_crypto::Felt; use tagged::Tagged; use tagged_debug_derive::TaggedDebug; -use crate::common::Iteration; +use crate::common::{Hash, Iteration}; use crate::{proto, proto_field, ToProtobuf, TryFromProtobuf}; #[derive(Debug, Clone, PartialEq, Eq, ToProtobuf, TryFromProtobuf, Dummy, PartialOrd, Ord)] @@ -75,8 +75,8 @@ impl Dummy for Cairo1Class { #[derive(Clone, PartialEq, Eq, Dummy, TaggedDebug)] pub enum Class { - Cairo0 { class: Cairo0Class, domain: u32 }, - Cairo1 { class: Cairo1Class, domain: u32 }, + Cairo0 { class: Cairo0Class, domain: u32, class_hash: Hash }, + Cairo1 { class: Cairo1Class, domain: u32, class_hash: Hash }, } impl ToProtobuf for Class { @@ -84,13 +84,15 @@ impl ToProtobuf for Class { use proto::class::class::Class::{Cairo0, Cairo1}; use proto::class::Class; match self { - Self::Cairo0 { class, domain } => Class { + Self::Cairo0 { class, domain, class_hash } => Class { class: Some(Cairo0(class.to_protobuf())), domain, + class_hash: Some(class_hash.to_protobuf()), }, - Self::Cairo1 { class, domain } => Class { + Self::Cairo1 { class, domain, class_hash } => Class { class: Some(Cairo1(class.to_protobuf())), domain, + class_hash: Some(class_hash.to_protobuf()), }, } } @@ -102,14 +104,17 @@ impl TryFromProtobuf for Class { field_name: &'static str, ) -> Result { use proto::class::class::Class::{Cairo0, Cairo1}; + let class_hash = Hash::try_from_protobuf(input.class_hash, field_name)?; Ok(match proto_field(input.class, field_name)? { Cairo0(c) => Self::Cairo0 { class: Cairo0Class::try_from_protobuf(c, field_name)?, domain: input.domain, + class_hash, }, Cairo1(c) => Self::Cairo1 { class: Cairo1Class::try_from_protobuf(c, field_name)?, domain: input.domain, + class_hash, }, }) } diff --git a/crates/pathfinder/src/p2p_network/sync_handlers.rs b/crates/pathfinder/src/p2p_network/sync_handlers.rs index 6a846df46d..4870b2e291 100644 --- a/crates/pathfinder/src/p2p_network/sync_handlers.rs +++ b/crates/pathfinder/src/p2p_network/sync_handlers.rs @@ -194,6 +194,7 @@ fn get_classes_for_block( Class::Cairo0 { class: cairo_class.to_dto(), domain: 0, // TODO + class_hash: Hash(class_hash.0), } } ClassDefinition::Sierra { @@ -205,6 +206,7 @@ fn get_classes_for_block( Class::Cairo1 { class: sierra_class.to_dto(), domain: 0, // TODO + class_hash: Hash(class_hash.0), } } }; diff --git a/crates/pathfinder/src/p2p_network/sync_handlers/tests.rs b/crates/pathfinder/src/p2p_network/sync_handlers/tests.rs index 3c376be5ec..45154896fc 100644 --- a/crates/pathfinder/src/p2p_network/sync_handlers/tests.rs +++ b/crates/pathfinder/src/p2p_network/sync_handlers/tests.rs @@ -361,10 +361,10 @@ mod prop { let mut actual_sierra = Vec::new(); responses.into_iter().for_each(|response| match response { - ClassesResponse::Class(Class::Cairo0 { class, domain: _ }) => { + ClassesResponse::Class(Class::Cairo0 { class, domain: _, class_hash: _ }) => { actual_cairo.push(CairoDefinition::try_from_dto(class).unwrap().0); }, - ClassesResponse::Class(Class::Cairo1 { class, domain: _ }) => { + ClassesResponse::Class(Class::Cairo1 { class, domain: _, class_hash: _ }) => { let SierraDefinition(sierra) = SierraDefinition::try_from_dto(class).unwrap(); actual_sierra.push(sierra); }, From 794d0fa9a275a10f42087067743e92b5a6b09d46 Mon Sep 17 00:00:00 2001 From: Vaclav Barta Date: Tue, 29 Oct 2024 15:23:24 +0100 Subject: [PATCH 207/282] added Transaction.transaction_hash --- crates/p2p/src/client/conv.rs | 36 +++++++-------- crates/p2p/src/client/peer_agnostic.rs | 4 +- .../p2p/src/client/peer_agnostic/fixtures.rs | 11 ++++- crates/p2p_proto/proto/transaction.proto | 1 + crates/p2p_proto/src/transaction.rs | 45 ++++++++++--------- .../src/p2p_network/sync_handlers.rs | 6 ++- .../src/p2p_network/sync_handlers/tests.rs | 2 +- 7 files changed, 61 insertions(+), 44 deletions(-) diff --git a/crates/p2p/src/client/conv.rs b/crates/p2p/src/client/conv.rs index 0d962d7661..9ec2c7b88b 100644 --- a/crates/p2p/src/client/conv.rs +++ b/crates/p2p/src/client/conv.rs @@ -19,7 +19,7 @@ use p2p_proto::receipt::{ MessageToL1, ReceiptCommon, }; -use p2p_proto::transaction::AccountSignature; +use p2p_proto::transaction::{AccountSignature, TransactionEnum}; use pathfinder_common::class_definition::{ Cairo, SelectorAndFunctionIndex, @@ -143,13 +143,13 @@ impl ToDto for SignedBlockHeader { } } -impl ToDto for TransactionVariant { - fn to_dto(self) -> p2p_proto::transaction::Transaction { +impl ToDto for TransactionVariant { + fn to_dto(self) -> TransactionEnum { use p2p_proto::transaction as proto; use pathfinder_common::transaction::TransactionVariant::*; match self { - DeclareV0(x) => proto::Transaction::DeclareV0(proto::DeclareV0 { + DeclareV0(x) => TransactionEnum::DeclareV0(proto::DeclareV0 { sender: Address(x.sender_address.0), max_fee: x.max_fee.0, signature: AccountSignature { @@ -157,7 +157,7 @@ impl ToDto for TransactionVariant { }, class_hash: Hash(x.class_hash.0), }), - DeclareV1(x) => proto::Transaction::DeclareV1(proto::DeclareV1 { + DeclareV1(x) => TransactionEnum::DeclareV1(proto::DeclareV1 { sender: Address(x.sender_address.0), max_fee: x.max_fee.0, signature: AccountSignature { @@ -166,7 +166,7 @@ impl ToDto for TransactionVariant { class_hash: Hash(x.class_hash.0), nonce: x.nonce.0, }), - DeclareV2(x) => proto::Transaction::DeclareV2(proto::DeclareV2 { + DeclareV2(x) => TransactionEnum::DeclareV2(proto::DeclareV2 { sender: Address(x.sender_address.0), max_fee: x.max_fee.0, signature: AccountSignature { @@ -176,7 +176,7 @@ impl ToDto for TransactionVariant { nonce: x.nonce.0, compiled_class_hash: Hash(x.compiled_class_hash.0), }), - DeclareV3(x) => proto::Transaction::DeclareV3(proto::DeclareV3 { + DeclareV3(x) => TransactionEnum::DeclareV3(proto::DeclareV3 { sender: Address(x.sender_address.0), signature: AccountSignature { parts: x.signature.into_iter().map(|s| s.0).collect(), @@ -198,19 +198,19 @@ impl ToDto for TransactionVariant { nonce_data_availability_mode: x.nonce_data_availability_mode.to_dto(), fee_data_availability_mode: x.fee_data_availability_mode.to_dto(), }), - DeployV0(x) => proto::Transaction::Deploy(proto::Deploy { + DeployV0(x) => TransactionEnum::Deploy(proto::Deploy { class_hash: Hash(x.class_hash.0), address_salt: x.contract_address_salt.0, calldata: x.constructor_calldata.into_iter().map(|c| c.0).collect(), version: 0, }), - DeployV1(x) => proto::Transaction::Deploy(proto::Deploy { + DeployV1(x) => TransactionEnum::Deploy(proto::Deploy { class_hash: Hash(x.class_hash.0), address_salt: x.contract_address_salt.0, calldata: x.constructor_calldata.into_iter().map(|c| c.0).collect(), version: 1, }), - DeployAccountV1(x) => proto::Transaction::DeployAccountV1(proto::DeployAccountV1 { + DeployAccountV1(x) => TransactionEnum::DeployAccountV1(proto::DeployAccountV1 { max_fee: x.max_fee.0, signature: AccountSignature { parts: x.signature.into_iter().map(|s| s.0).collect(), @@ -220,7 +220,7 @@ impl ToDto for TransactionVariant { address_salt: x.contract_address_salt.0, calldata: x.constructor_calldata.into_iter().map(|c| c.0).collect(), }), - DeployAccountV3(x) => proto::Transaction::DeployAccountV3(proto::DeployAccountV3 { + DeployAccountV3(x) => TransactionEnum::DeployAccountV3(proto::DeployAccountV3 { signature: AccountSignature { parts: x.signature.into_iter().map(|s| s.0).collect(), }, @@ -237,7 +237,7 @@ impl ToDto for TransactionVariant { nonce_data_availability_mode: x.nonce_data_availability_mode.to_dto(), fee_data_availability_mode: x.fee_data_availability_mode.to_dto(), }), - InvokeV0(x) => proto::Transaction::InvokeV0(proto::InvokeV0 { + InvokeV0(x) => TransactionEnum::InvokeV0(proto::InvokeV0 { max_fee: x.max_fee.0, signature: AccountSignature { parts: x.signature.into_iter().map(|s| s.0).collect(), @@ -246,7 +246,7 @@ impl ToDto for TransactionVariant { entry_point_selector: x.entry_point_selector.0, calldata: x.calldata.into_iter().map(|c| c.0).collect(), }), - InvokeV1(x) => proto::Transaction::InvokeV1(proto::InvokeV1 { + InvokeV1(x) => TransactionEnum::InvokeV1(proto::InvokeV1 { sender: Address(x.sender_address.0), max_fee: x.max_fee.0, signature: AccountSignature { @@ -255,7 +255,7 @@ impl ToDto for TransactionVariant { nonce: x.nonce.0, calldata: x.calldata.into_iter().map(|c| c.0).collect(), }), - InvokeV3(x) => proto::Transaction::InvokeV3(proto::InvokeV3 { + InvokeV3(x) => TransactionEnum::InvokeV3(proto::InvokeV3 { sender: Address(x.sender_address.0), signature: AccountSignature { parts: x.signature.into_iter().map(|s| s.0).collect(), @@ -276,7 +276,7 @@ impl ToDto for TransactionVariant { fee_data_availability_mode: x.fee_data_availability_mode.to_dto(), nonce: x.nonce.0, }), - L1Handler(x) => proto::Transaction::L1HandlerV0(proto::L1HandlerV0 { + L1Handler(x) => TransactionEnum::L1HandlerV0(proto::L1HandlerV0 { nonce: x.nonce.0, address: Address(x.contract_address.0), entry_point_selector: x.entry_point_selector.0, @@ -432,12 +432,12 @@ impl ToDto for L1DataAvailabilityMode } } -impl TryFromDto for TransactionVariant { - fn try_from_dto(dto: p2p_proto::transaction::Transaction) -> anyhow::Result +impl TryFromDto for TransactionVariant { + fn try_from_dto(dto: TransactionEnum) -> anyhow::Result where Self: Sized, { - use p2p_proto::transaction::Transaction::{ + use TransactionEnum::{ DeclareV0, DeclareV1, DeclareV2, diff --git a/crates/p2p/src/client/peer_agnostic.rs b/crates/p2p/src/client/peer_agnostic.rs index e5bf2ec398..3f8f8d59d1 100644 --- a/crates/p2p/src/client/peer_agnostic.rs +++ b/crates/p2p/src/client/peer_agnostic.rs @@ -329,7 +329,7 @@ impl BlockClient for Client { match x { Ok(TransactionsResponse::Fin) => unreachable!("Already handled Fin above"), Ok(TransactionsResponse::TransactionWithReceipt(tx_with_receipt)) => Ok(( - TransactionVariant::try_from_dto(tx_with_receipt.transaction)?, + TransactionVariant::try_from_dto(tx_with_receipt.transaction.txn)?, Receipt::try_from(( tx_with_receipt.receipt, TransactionIndex::new(i.try_into().unwrap()) @@ -856,7 +856,7 @@ mod transaction_stream { receipt, })) => { if let (Ok(t), Ok(r)) = ( - TransactionVariant::try_from_dto(transaction), + TransactionVariant::try_from_dto(transaction.txn), Receipt::try_from((receipt, txn_idx)), ) { Some((t, r)) diff --git a/crates/p2p/src/client/peer_agnostic/fixtures.rs b/crates/p2p/src/client/peer_agnostic/fixtures.rs index 7f57d5b864..86de0b8765 100644 --- a/crates/p2p/src/client/peer_agnostic/fixtures.rs +++ b/crates/p2p/src/client/peer_agnostic/fixtures.rs @@ -18,6 +18,7 @@ use pathfinder_common::{ BlockHeader, BlockNumber, CasmHash, + ChainId, ClassHash, ContractAddress, SierraHash, @@ -124,9 +125,15 @@ pub fn hdr(tag: i32) -> SignedBlockHeader { pub fn txn_resp(tag: i32, transaction_index: u64) -> TransactionsResponse { let TestTxn { t, r } = txn(tag, transaction_index); + let receipt = (&t, r).to_dto(); + let h = t.calculate_hash(ChainId::SEPOLIA_TESTNET, false); + let transaction = p2p_proto::transaction::Transaction { + txn: t.to_dto(), + transaction_hash: Hash(h.0), + }; let resp = TransactionsResponse::TransactionWithReceipt(TransactionWithReceipt { - receipt: (&t, r).to_dto(), - transaction: t.to_dto(), + receipt, + transaction, }); Tagged::get(format!("txn resp {tag}"), || resp) .unwrap() diff --git a/crates/p2p_proto/proto/transaction.proto b/crates/p2p_proto/proto/transaction.proto index 8f9fa2cd27..cbb43b466a 100644 --- a/crates/p2p_proto/proto/transaction.proto +++ b/crates/p2p_proto/proto/transaction.proto @@ -141,6 +141,7 @@ message Transaction InvokeV3 invoke_v3 = 10; L1HandlerV0 l1_handler = 11; } + starknet.common.Hash transaction_hash = 12; } message TransactionWithReceipt { diff --git a/crates/p2p_proto/src/transaction.rs b/crates/p2p_proto/src/transaction.rs index 5eb28ed893..01f78667a5 100644 --- a/crates/p2p_proto/src/transaction.rs +++ b/crates/p2p_proto/src/transaction.rs @@ -160,7 +160,7 @@ pub struct L1HandlerV0 { } #[derive(Debug, Clone, PartialEq, Eq, Dummy)] -pub enum Transaction { +pub enum TransactionEnum { DeclareV0(DeclareV0), DeclareV1(DeclareV1), DeclareV2(DeclareV2), @@ -174,6 +174,13 @@ pub enum Transaction { L1HandlerV0(L1HandlerV0), } +#[derive(Debug, Clone, PartialEq, Eq, ToProtobuf, TryFromProtobuf, Dummy)] +#[protobuf(name = "crate::proto::transaction::Transaction")] +pub struct Transaction { + pub txn: TransactionEnum, + pub transaction_hash: Hash, +} + #[derive(Debug, Clone, PartialEq, Eq, ToProtobuf, TryFromProtobuf, Dummy)] #[protobuf(name = "crate::proto::transaction::TransactionWithReceipt")] pub struct TransactionWithReceipt { @@ -195,8 +202,8 @@ pub enum TransactionsResponse { Fin, } -impl ToProtobuf for Transaction { - fn to_protobuf(self) -> proto::transaction::Transaction { +impl ToProtobuf for TransactionEnum { + fn to_protobuf(self) -> proto::transaction::transaction::Txn { use proto::transaction::transaction::Txn::{ DeclareV0, DeclareV1, @@ -210,27 +217,25 @@ impl ToProtobuf for Transaction { InvokeV3, L1Handler, }; - proto::transaction::Transaction { - txn: Some(match self { - Self::DeclareV0(txn) => DeclareV0(txn.to_protobuf()), - Self::DeclareV1(txn) => DeclareV1(txn.to_protobuf()), - Self::DeclareV2(txn) => DeclareV2(txn.to_protobuf()), - Self::DeclareV3(txn) => DeclareV3(txn.to_protobuf()), - Self::Deploy(txn) => Deploy(txn.to_protobuf()), - Self::DeployAccountV1(txn) => DeployAccountV1(txn.to_protobuf()), - Self::DeployAccountV3(txn) => DeployAccountV3(txn.to_protobuf()), - Self::InvokeV0(txn) => InvokeV0(txn.to_protobuf()), - Self::InvokeV1(txn) => InvokeV1(txn.to_protobuf()), - Self::InvokeV3(txn) => InvokeV3(txn.to_protobuf()), - Self::L1HandlerV0(txn) => L1Handler(txn.to_protobuf()), - }), + match self { + Self::DeclareV0(txn) => DeclareV0(txn.to_protobuf()), + Self::DeclareV1(txn) => DeclareV1(txn.to_protobuf()), + Self::DeclareV2(txn) => DeclareV2(txn.to_protobuf()), + Self::DeclareV3(txn) => DeclareV3(txn.to_protobuf()), + Self::Deploy(txn) => Deploy(txn.to_protobuf()), + Self::DeployAccountV1(txn) => DeployAccountV1(txn.to_protobuf()), + Self::DeployAccountV3(txn) => DeployAccountV3(txn.to_protobuf()), + Self::InvokeV0(txn) => InvokeV0(txn.to_protobuf()), + Self::InvokeV1(txn) => InvokeV1(txn.to_protobuf()), + Self::InvokeV3(txn) => InvokeV3(txn.to_protobuf()), + Self::L1HandlerV0(txn) => L1Handler(txn.to_protobuf()), } } } -impl TryFromProtobuf for Transaction { +impl TryFromProtobuf for TransactionEnum { fn try_from_protobuf( - input: proto::transaction::Transaction, + input: proto::transaction::transaction::Txn, field_name: &'static str, ) -> Result { use proto::transaction::transaction::Txn::{ @@ -246,7 +251,7 @@ impl TryFromProtobuf for Transaction { InvokeV3, L1Handler, }; - match proto_field(input.txn, field_name)? { + match input { DeclareV0(t) => TryFromProtobuf::try_from_protobuf(t, field_name).map(Self::DeclareV0), DeclareV1(t) => TryFromProtobuf::try_from_protobuf(t, field_name).map(Self::DeclareV1), DeclareV2(t) => TryFromProtobuf::try_from_protobuf(t, field_name).map(Self::DeclareV2), diff --git a/crates/pathfinder/src/p2p_network/sync_handlers.rs b/crates/pathfinder/src/p2p_network/sync_handlers.rs index 4870b2e291..fe59b3a680 100644 --- a/crates/pathfinder/src/p2p_network/sync_handlers.rs +++ b/crates/pathfinder/src/p2p_network/sync_handlers.rs @@ -295,9 +295,13 @@ fn get_transactions_for_block( tracing::trace!(transaction_hash=%txn.hash, "Sending transaction"); let receipt = (&txn.variant, receipt).to_dto(); + let transaction = p2p_proto::transaction::Transaction { + txn: txn.variant.to_dto(), + transaction_hash: Hash(txn.hash.0), + }; tx.blocking_send(TransactionsResponse::TransactionWithReceipt( TransactionWithReceipt { - transaction: txn.variant.to_dto(), + transaction, receipt, }, )) diff --git a/crates/pathfinder/src/p2p_network/sync_handlers/tests.rs b/crates/pathfinder/src/p2p_network/sync_handlers/tests.rs index 45154896fc..6f3e6375d7 100644 --- a/crates/pathfinder/src/p2p_network/sync_handlers/tests.rs +++ b/crates/pathfinder/src/p2p_network/sync_handlers/tests.rs @@ -449,7 +449,7 @@ mod prop { // Check the rest let mut actual = responses.into_iter().map(|response| match response { TransactionsResponse::TransactionWithReceipt(TransactionWithReceipt { transaction, receipt }) => { - (TransactionVariant::try_from_dto(transaction).unwrap(), Receipt::try_from((receipt, TransactionIndex::new_or_panic(0))).unwrap()) + (TransactionVariant::try_from_dto(transaction.txn).unwrap(), Receipt::try_from((receipt, TransactionIndex::new_or_panic(0))).unwrap()) } _ => panic!("unexpected response"), }).collect::>(); From 9f5758242d82b5e82fb5bb0db23513e726e42084 Mon Sep 17 00:00:00 2001 From: Vaclav Barta Date: Tue, 29 Oct 2024 15:41:47 +0100 Subject: [PATCH 208/282] formatted --- crates/p2p/src/client/peer_agnostic.rs | 12 ++++++++++-- crates/p2p_proto/src/class.rs | 24 ++++++++++++++++++++---- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/crates/p2p/src/client/peer_agnostic.rs b/crates/p2p/src/client/peer_agnostic.rs index 3f8f8d59d1..16012e8bbd 100644 --- a/crates/p2p/src/client/peer_agnostic.rs +++ b/crates/p2p/src/client/peer_agnostic.rs @@ -1274,7 +1274,11 @@ mod class_definition_stream { block_number: BlockNumber, ) -> Option { match response { - Ok(ClassesResponse::Class(p2p_proto::class::Class::Cairo0 { class, domain: _, class_hash: _ })) => { + Ok(ClassesResponse::Class(p2p_proto::class::Class::Cairo0 { + class, + domain: _, + class_hash: _, + })) => { let Ok(CairoDefinition(definition)) = CairoDefinition::try_from_dto(class) else { // TODO punish the peer tracing::debug!(%peer, "Cairo definition failed to parse"); @@ -1286,7 +1290,11 @@ mod class_definition_stream { definition, }) } - Ok(ClassesResponse::Class(p2p_proto::class::Class::Cairo1 { class, domain: _, class_hash: _ })) => { + Ok(ClassesResponse::Class(p2p_proto::class::Class::Cairo1 { + class, + domain: _, + class_hash: _, + })) => { let Ok(SierraDefinition(definition)) = SierraDefinition::try_from_dto(class) else { // TODO punish the peer tracing::debug!(%peer, "Sierra definition failed to parse"); diff --git a/crates/p2p_proto/src/class.rs b/crates/p2p_proto/src/class.rs index ccad0fdcc3..2ec38666e4 100644 --- a/crates/p2p_proto/src/class.rs +++ b/crates/p2p_proto/src/class.rs @@ -75,8 +75,16 @@ impl Dummy for Cairo1Class { #[derive(Clone, PartialEq, Eq, Dummy, TaggedDebug)] pub enum Class { - Cairo0 { class: Cairo0Class, domain: u32, class_hash: Hash }, - Cairo1 { class: Cairo1Class, domain: u32, class_hash: Hash }, + Cairo0 { + class: Cairo0Class, + domain: u32, + class_hash: Hash, + }, + Cairo1 { + class: Cairo1Class, + domain: u32, + class_hash: Hash, + }, } impl ToProtobuf for Class { @@ -84,12 +92,20 @@ impl ToProtobuf for Class { use proto::class::class::Class::{Cairo0, Cairo1}; use proto::class::Class; match self { - Self::Cairo0 { class, domain, class_hash } => Class { + Self::Cairo0 { + class, + domain, + class_hash, + } => Class { class: Some(Cairo0(class.to_protobuf())), domain, class_hash: Some(class_hash.to_protobuf()), }, - Self::Cairo1 { class, domain, class_hash } => Class { + Self::Cairo1 { + class, + domain, + class_hash, + } => Class { class: Some(Cairo1(class.to_protobuf())), domain, class_hash: Some(class_hash.to_protobuf()), From 74a4afc830a9a0c7152fad2a97d071cb162a2b39 Mon Sep 17 00:00:00 2001 From: Vaclav Barta Date: Thu, 31 Oct 2024 11:15:10 +0100 Subject: [PATCH 209/282] Update crates/p2p/src/client/peer_agnostic/fixtures.rs Co-authored-by: CHr15F0x --- crates/p2p/src/client/peer_agnostic/fixtures.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/p2p/src/client/peer_agnostic/fixtures.rs b/crates/p2p/src/client/peer_agnostic/fixtures.rs index 86de0b8765..1d5c2e7555 100644 --- a/crates/p2p/src/client/peer_agnostic/fixtures.rs +++ b/crates/p2p/src/client/peer_agnostic/fixtures.rs @@ -283,7 +283,7 @@ pub fn class_resp(tag: i32) -> ClassesResponse { ClassDefinition::Sierra(s) => Class::Cairo1 { class: s.to_dto(), domain: 0, - class_hash: Hash(Felt::default()), + class_hash: Faker.fake(), }, ClassDefinition::Cairo(c) => Class::Cairo0 { class: c.to_dto(), From a008702602a411b4a7060c43045cf22472f3af1e Mon Sep 17 00:00:00 2001 From: Vaclav Barta Date: Thu, 31 Oct 2024 11:18:16 +0100 Subject: [PATCH 210/282] TransactionEnum -> TransactionVariant --- crates/p2p/src/client/conv.rs | 90 +++++++++++++++-------------- crates/p2p_proto/src/transaction.rs | 8 +-- 2 files changed, 51 insertions(+), 47 deletions(-) diff --git a/crates/p2p/src/client/conv.rs b/crates/p2p/src/client/conv.rs index 9ec2c7b88b..dde63a667f 100644 --- a/crates/p2p/src/client/conv.rs +++ b/crates/p2p/src/client/conv.rs @@ -19,7 +19,7 @@ use p2p_proto::receipt::{ MessageToL1, ReceiptCommon, }; -use p2p_proto::transaction::{AccountSignature, TransactionEnum}; +use p2p_proto::transaction::AccountSignature; use pathfinder_common::class_definition::{ Cairo, SelectorAndFunctionIndex, @@ -143,13 +143,13 @@ impl ToDto for SignedBlockHeader { } } -impl ToDto for TransactionVariant { - fn to_dto(self) -> TransactionEnum { +impl ToDto for TransactionVariant { + fn to_dto(self) -> p2p_proto::transaction::TransactionVariant { use p2p_proto::transaction as proto; use pathfinder_common::transaction::TransactionVariant::*; match self { - DeclareV0(x) => TransactionEnum::DeclareV0(proto::DeclareV0 { + DeclareV0(x) => proto::TransactionVariant::DeclareV0(proto::DeclareV0 { sender: Address(x.sender_address.0), max_fee: x.max_fee.0, signature: AccountSignature { @@ -157,7 +157,7 @@ impl ToDto for TransactionVariant { }, class_hash: Hash(x.class_hash.0), }), - DeclareV1(x) => TransactionEnum::DeclareV1(proto::DeclareV1 { + DeclareV1(x) => proto::TransactionVariant::DeclareV1(proto::DeclareV1 { sender: Address(x.sender_address.0), max_fee: x.max_fee.0, signature: AccountSignature { @@ -166,7 +166,7 @@ impl ToDto for TransactionVariant { class_hash: Hash(x.class_hash.0), nonce: x.nonce.0, }), - DeclareV2(x) => TransactionEnum::DeclareV2(proto::DeclareV2 { + DeclareV2(x) => proto::TransactionVariant::DeclareV2(proto::DeclareV2 { sender: Address(x.sender_address.0), max_fee: x.max_fee.0, signature: AccountSignature { @@ -176,7 +176,7 @@ impl ToDto for TransactionVariant { nonce: x.nonce.0, compiled_class_hash: Hash(x.compiled_class_hash.0), }), - DeclareV3(x) => TransactionEnum::DeclareV3(proto::DeclareV3 { + DeclareV3(x) => proto::TransactionVariant::DeclareV3(proto::DeclareV3 { sender: Address(x.sender_address.0), signature: AccountSignature { parts: x.signature.into_iter().map(|s| s.0).collect(), @@ -198,46 +198,50 @@ impl ToDto for TransactionVariant { nonce_data_availability_mode: x.nonce_data_availability_mode.to_dto(), fee_data_availability_mode: x.fee_data_availability_mode.to_dto(), }), - DeployV0(x) => TransactionEnum::Deploy(proto::Deploy { + DeployV0(x) => proto::TransactionVariant::Deploy(proto::Deploy { class_hash: Hash(x.class_hash.0), address_salt: x.contract_address_salt.0, calldata: x.constructor_calldata.into_iter().map(|c| c.0).collect(), version: 0, }), - DeployV1(x) => TransactionEnum::Deploy(proto::Deploy { + DeployV1(x) => proto::TransactionVariant::Deploy(proto::Deploy { class_hash: Hash(x.class_hash.0), address_salt: x.contract_address_salt.0, calldata: x.constructor_calldata.into_iter().map(|c| c.0).collect(), version: 1, }), - DeployAccountV1(x) => TransactionEnum::DeployAccountV1(proto::DeployAccountV1 { - max_fee: x.max_fee.0, - signature: AccountSignature { - parts: x.signature.into_iter().map(|s| s.0).collect(), - }, - class_hash: Hash(x.class_hash.0), - nonce: x.nonce.0, - address_salt: x.contract_address_salt.0, - calldata: x.constructor_calldata.into_iter().map(|c| c.0).collect(), - }), - DeployAccountV3(x) => TransactionEnum::DeployAccountV3(proto::DeployAccountV3 { - signature: AccountSignature { - parts: x.signature.into_iter().map(|s| s.0).collect(), - }, - class_hash: Hash(x.class_hash.0), - nonce: x.nonce.0, - address_salt: x.contract_address_salt.0, - calldata: x.constructor_calldata.into_iter().map(|c| c.0).collect(), - resource_bounds: p2p_proto::transaction::ResourceBounds { - l1_gas: x.resource_bounds.l1_gas.to_dto(), - l2_gas: x.resource_bounds.l2_gas.to_dto(), - }, - tip: x.tip.0, - paymaster_data: x.paymaster_data.into_iter().map(|p| p.0).collect(), - nonce_data_availability_mode: x.nonce_data_availability_mode.to_dto(), - fee_data_availability_mode: x.fee_data_availability_mode.to_dto(), - }), - InvokeV0(x) => TransactionEnum::InvokeV0(proto::InvokeV0 { + DeployAccountV1(x) => { + proto::TransactionVariant::DeployAccountV1(proto::DeployAccountV1 { + max_fee: x.max_fee.0, + signature: AccountSignature { + parts: x.signature.into_iter().map(|s| s.0).collect(), + }, + class_hash: Hash(x.class_hash.0), + nonce: x.nonce.0, + address_salt: x.contract_address_salt.0, + calldata: x.constructor_calldata.into_iter().map(|c| c.0).collect(), + }) + } + DeployAccountV3(x) => { + proto::TransactionVariant::DeployAccountV3(proto::DeployAccountV3 { + signature: AccountSignature { + parts: x.signature.into_iter().map(|s| s.0).collect(), + }, + class_hash: Hash(x.class_hash.0), + nonce: x.nonce.0, + address_salt: x.contract_address_salt.0, + calldata: x.constructor_calldata.into_iter().map(|c| c.0).collect(), + resource_bounds: p2p_proto::transaction::ResourceBounds { + l1_gas: x.resource_bounds.l1_gas.to_dto(), + l2_gas: x.resource_bounds.l2_gas.to_dto(), + }, + tip: x.tip.0, + paymaster_data: x.paymaster_data.into_iter().map(|p| p.0).collect(), + nonce_data_availability_mode: x.nonce_data_availability_mode.to_dto(), + fee_data_availability_mode: x.fee_data_availability_mode.to_dto(), + }) + } + InvokeV0(x) => proto::TransactionVariant::InvokeV0(proto::InvokeV0 { max_fee: x.max_fee.0, signature: AccountSignature { parts: x.signature.into_iter().map(|s| s.0).collect(), @@ -246,7 +250,7 @@ impl ToDto for TransactionVariant { entry_point_selector: x.entry_point_selector.0, calldata: x.calldata.into_iter().map(|c| c.0).collect(), }), - InvokeV1(x) => TransactionEnum::InvokeV1(proto::InvokeV1 { + InvokeV1(x) => proto::TransactionVariant::InvokeV1(proto::InvokeV1 { sender: Address(x.sender_address.0), max_fee: x.max_fee.0, signature: AccountSignature { @@ -255,7 +259,7 @@ impl ToDto for TransactionVariant { nonce: x.nonce.0, calldata: x.calldata.into_iter().map(|c| c.0).collect(), }), - InvokeV3(x) => TransactionEnum::InvokeV3(proto::InvokeV3 { + InvokeV3(x) => proto::TransactionVariant::InvokeV3(proto::InvokeV3 { sender: Address(x.sender_address.0), signature: AccountSignature { parts: x.signature.into_iter().map(|s| s.0).collect(), @@ -276,7 +280,7 @@ impl ToDto for TransactionVariant { fee_data_availability_mode: x.fee_data_availability_mode.to_dto(), nonce: x.nonce.0, }), - L1Handler(x) => TransactionEnum::L1HandlerV0(proto::L1HandlerV0 { + L1Handler(x) => proto::TransactionVariant::L1HandlerV0(proto::L1HandlerV0 { nonce: x.nonce.0, address: Address(x.contract_address.0), entry_point_selector: x.entry_point_selector.0, @@ -432,12 +436,12 @@ impl ToDto for L1DataAvailabilityMode } } -impl TryFromDto for TransactionVariant { - fn try_from_dto(dto: TransactionEnum) -> anyhow::Result +impl TryFromDto for TransactionVariant { + fn try_from_dto(dto: p2p_proto::transaction::TransactionVariant) -> anyhow::Result where Self: Sized, { - use TransactionEnum::{ + use p2p_proto::transaction::TransactionVariant::{ DeclareV0, DeclareV1, DeclareV2, diff --git a/crates/p2p_proto/src/transaction.rs b/crates/p2p_proto/src/transaction.rs index 01f78667a5..90bbc9c9f2 100644 --- a/crates/p2p_proto/src/transaction.rs +++ b/crates/p2p_proto/src/transaction.rs @@ -160,7 +160,7 @@ pub struct L1HandlerV0 { } #[derive(Debug, Clone, PartialEq, Eq, Dummy)] -pub enum TransactionEnum { +pub enum TransactionVariant { DeclareV0(DeclareV0), DeclareV1(DeclareV1), DeclareV2(DeclareV2), @@ -177,7 +177,7 @@ pub enum TransactionEnum { #[derive(Debug, Clone, PartialEq, Eq, ToProtobuf, TryFromProtobuf, Dummy)] #[protobuf(name = "crate::proto::transaction::Transaction")] pub struct Transaction { - pub txn: TransactionEnum, + pub txn: TransactionVariant, pub transaction_hash: Hash, } @@ -202,7 +202,7 @@ pub enum TransactionsResponse { Fin, } -impl ToProtobuf for TransactionEnum { +impl ToProtobuf for TransactionVariant { fn to_protobuf(self) -> proto::transaction::transaction::Txn { use proto::transaction::transaction::Txn::{ DeclareV0, @@ -233,7 +233,7 @@ impl ToProtobuf for TransactionEnum { } } -impl TryFromProtobuf for TransactionEnum { +impl TryFromProtobuf for TransactionVariant { fn try_from_protobuf( input: proto::transaction::transaction::Txn, field_name: &'static str, From 11a519c54c68797c376a131d24e5d9f6ed4e1340 Mon Sep 17 00:00:00 2001 From: Krisztian Kovacs Date: Tue, 5 Nov 2024 16:56:23 +0100 Subject: [PATCH 211/282] fix(p2p): make `strk_l2_gas_price` and `eth_l2_gas_price` fields optional These fields are Pathfinder-specific extensions at this point, so we should handle them as optional. --- crates/p2p/src/client/conv.rs | 4 ++-- crates/p2p/src/client/types.rs | 4 ++-- crates/p2p_proto/src/header.rs | 6 ++++-- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/crates/p2p/src/client/conv.rs b/crates/p2p/src/client/conv.rs index dde63a667f..0fdde1d219 100644 --- a/crates/p2p/src/client/conv.rs +++ b/crates/p2p/src/client/conv.rs @@ -132,8 +132,8 @@ impl ToDto for SignedBlockHeader { gas_price_wei: self.header.eth_l1_gas_price.0, data_gas_price_fri: self.header.strk_l1_data_gas_price.0, data_gas_price_wei: self.header.eth_l1_data_gas_price.0, - l2_gas_price_fri: self.header.strk_l2_gas_price.0, - l2_gas_price_wei: self.header.eth_l2_gas_price.0, + l2_gas_price_fri: Some(self.header.strk_l2_gas_price.0), + l2_gas_price_wei: Some(self.header.eth_l2_gas_price.0), l1_data_availability_mode: self.header.l1_da_mode.to_dto(), signatures: vec![p2p_proto::common::ConsensusSignature { r: self.signature.r.0, diff --git a/crates/p2p/src/client/types.rs b/crates/p2p/src/client/types.rs index 2dc254c5ef..a3b28157f2 100644 --- a/crates/p2p/src/client/types.rs +++ b/crates/p2p/src/client/types.rs @@ -101,8 +101,8 @@ impl TryFromDto for SignedBlockHeader { strk_l1_gas_price: GasPrice(dto.gas_price_fri), eth_l1_data_gas_price: GasPrice(dto.data_gas_price_wei), strk_l1_data_gas_price: GasPrice(dto.data_gas_price_fri), - eth_l2_gas_price: GasPrice(dto.l2_gas_price_wei), - strk_l2_gas_price: GasPrice(dto.l2_gas_price_fri), + eth_l2_gas_price: GasPrice(dto.l2_gas_price_wei.unwrap_or_default()), + strk_l2_gas_price: GasPrice(dto.l2_gas_price_fri.unwrap_or_default()), sequencer_address: SequencerAddress(dto.sequencer_address.0), starknet_version: dto.protocol_version.parse()?, event_commitment: EventCommitment(dto.events.root.0), diff --git a/crates/p2p_proto/src/header.rs b/crates/p2p_proto/src/header.rs index 61b6c685bb..792953d3cf 100644 --- a/crates/p2p_proto/src/header.rs +++ b/crates/p2p_proto/src/header.rs @@ -34,8 +34,10 @@ pub struct SignedBlockHeader { pub gas_price_wei: u128, pub data_gas_price_fri: u128, pub data_gas_price_wei: u128, - pub l2_gas_price_fri: u128, - pub l2_gas_price_wei: u128, + #[optional] + pub l2_gas_price_fri: Option, + #[optional] + pub l2_gas_price_wei: Option, pub l1_data_availability_mode: L1DataAvailabilityMode, pub signatures: Vec, } From 4874fa64fe22816b9a4d2da7747ad2359e58db11 Mon Sep 17 00:00:00 2001 From: sistemd Date: Tue, 5 Nov 2024 21:31:33 +0100 Subject: [PATCH 212/282] aggregate bloom filter --- crates/storage/Cargo.toml | 5 + crates/storage/src/bloom.rs | 418 ++++++++++++++++++++++++- crates/storage/src/connection/event.rs | 111 ++++++- 3 files changed, 524 insertions(+), 10 deletions(-) diff --git a/crates/storage/Cargo.toml b/crates/storage/Cargo.toml index 45636034e2..1f770f13cd 100644 --- a/crates/storage/Cargo.toml +++ b/crates/storage/Cargo.toml @@ -7,6 +7,11 @@ license = { workspace = true } rust-version = { workspace = true } # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[features] +aggregate_bloom = [] + +default = [] + [dependencies] anyhow = { workspace = true } base64 = { workspace = true } diff --git a/crates/storage/src/bloom.rs b/crates/storage/src/bloom.rs index f1b0e22722..f8fcd01a0a 100644 --- a/crates/storage/src/bloom.rs +++ b/crates/storage/src/bloom.rs @@ -1,3 +1,65 @@ +//! A Bloom filter is a space-efficient probabilistic data structure that +//! is used to test whether an element is a member of a set. An empty Bloom +//! filter is a bit array of m bits, all set to 0. It is equipped with k +//! different hash functions, which map set elements to one of the m possible +//! array positions. To check whether an element is in the set, the element is +//! hashed k times and the bits at the resulting positions are checked. If all k +//! bits are set to 1, the element is considered to be in the set (false +//! positives are possible). In our case, each block is a set with its own +//! [`BloomFilter`] and the elements are block events' keys and contract +//! addresses. +//! +//! When considering scenarios where keys need to be checked for large ranges +//! of blocks, it can certainly take a long time if [`BloomFilter`]s for each +//! block are loaded and checked against one by one. A possible optimization is +//! to store aggregates of [`BloomFilter`]s for ranges of blocks and, once keys +//! need to be checked for that range, this [`AggregateBloom`] filter can +//! be loaded and checked against in a single shot. +//! +//! Example: A key K1 that is mapped to three (out of *eight) indices of the +//! bloom filter, needs to be added to an aggregate bloom filter for a range +//! of *ten blocks. (*these are illustratory numbers, in practice much larger +//! values are used). +//! +//! We start with an empty aggregate filter, an 8x10 bitmap full of zeroes. Rows +//! of this matrix represent bloom filter indices that keys can be mapped to +//! whereas the columns represent the blocks within the range for which the +//! aggregate bloom filter is used. +//! +//! HashFn(K1) = [0, 1, 0, 1, 1, 0, 0, 0] +//! +//! We are inserting K1 as a part of the first block. In order to insert the key +//! into the aggregate, we first rotate it clockwise by 90 degrees (turning it +//! from a row vector into a column vector). Then, we set the first bit (since +//! we are adding to the first block) of rows 1, 3 and 4 (zero based) because +//! bloom filter hash functions map K1 to these indices. After this, we are left +//! with the following bitmap: +//! +//! [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] +//! [1, 0, 0, 0, 0, 0, 0, 0, 0, 0] +//! [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] +//! [1, 0, 0, 0, 0, 0, 0, 0, 0, 0] +//! [1, 0, 0, 0, 0, 0, 0, 0, 0, 0] +//! [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] +//! [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] +//! [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] +//! +//! which we can now store. Next, to check if K1 has been added to any of the +//! blocks, we perform the following steps: +//! 1) Load an [`AggregateBloom`] filter with the previously stored bitmap. +//! 2) Obtain the indices to which K1 maps to (1, 3 and 4 in this example), +//! pluck out the corresponding rows and bitwise AND them together, leaving +//! us with a 1x10 bit vector. +//! 3) Indices of bits that are set in the bit vector obtained through step 2) +//! are block numbers to which K1 could have been added (or are false +//! positives due to how Bloom filters work). In this example, the first bit +//! will be set meaning block 0 could contain K1 (and it does since this is a +//! very simplified example). +//! +//! This way, it's possible to quickly figure out which blocks correspond to a +//! specific set of keys without having to load and check each individual bloom +//! filter. + use std::sync::{Mutex, MutexGuard}; use bloomfilter::Bloom; @@ -13,18 +75,185 @@ use crate::ReorgCounter; // filter. pub const EVENT_KEY_FILTER_LIMIT: usize = 16; +/// An aggregate of all Bloom filters for a given range of blocks. +/// Before being added to `AggregateBloom`, each [`BloomFilter`] is +/// rotated by 90 degrees (transposed). +#[derive(Debug)] +pub(crate) struct AggregateBloom { + /// A [Self::BLOCK_RANGE_LEN] by [BloomFilter::BITVEC_LEN] matrix stored in + /// a single array. + bitmap: Vec, + /// Block range for which the aggregate filter is constructed. + block_range: std::ops::Range, + next_block: BlockNumber, +} + +impl AggregateBloom { + // TODO: + // Remove #[allow(dead_code)] when follow up is done. + + /// Maximum number of blocks to aggregate in a single `AggregateBloom`. + const BLOCK_RANGE_LEN: u64 = 32_768; + const BLOCK_RANGE_BYTES: u64 = Self::BLOCK_RANGE_LEN / 8; + + /// Create a new `AggregateBloom` for the (`from_block`, `from_block + + /// [Self::BLOCK_RANGE_LEN]`) range. + #[allow(dead_code)] + pub fn new(from_block: BlockNumber) -> Self { + let bitmap = vec![0; (Self::BLOCK_RANGE_BYTES * BloomFilter::BITVEC_LEN) as usize]; + + let to_block = from_block + Self::BLOCK_RANGE_LEN; + + Self { + bitmap, + block_range: from_block..to_block, + next_block: from_block, + } + } + + #[allow(dead_code)] + pub fn from_bytes(from_block: BlockNumber, bytes: Vec) -> Self { + assert_eq!( + bytes.len() as u64, + Self::BLOCK_RANGE_BYTES * BloomFilter::BITVEC_LEN, + "Bitmap size mismatch" + ); + + let to_block = from_block + Self::BLOCK_RANGE_LEN; + + Self { + bitmap: bytes, + block_range: from_block..to_block, + next_block: from_block, + } + } + + #[allow(dead_code)] + pub fn to_bytes(&self) -> &[u8] { + &self.bitmap + } + + /// Rotate the bloom filter by 90 degrees and add it to the aggregate. + #[allow(dead_code)] + pub fn add_bloom( + &mut self, + bloom: &BloomFilter, + insert_pos: BlockNumber, + ) -> Result<(), AddBloomError> { + if !self.block_range.contains(&insert_pos) { + return Err(AddBloomError::InvalidBlockNumber); + } + assert_eq!(self.next_block, insert_pos, "Unexpected insert position"); + assert_eq!( + bloom.0.number_of_hash_functions(), + BloomFilter::K_NUM, + "Hash function count mismatch" + ); + + let bloom = bloom.0.bit_vec().to_bytes(); + assert_eq!( + bloom.len() as u64, + BloomFilter::BITVEC_BYTES, + "Bit vector length mismatch" + ); + + let byte_index = (insert_pos.get() / 8) as usize; + let bit_index = (insert_pos.get() % 8) as usize; + for (i, bloom_byte) in bloom.iter().enumerate() { + if *bloom_byte == 0 { + continue; + } + + let base = 8 * i; + for j in 0..8 { + let row_idx = base + j; + let idx = Self::bitmap_index_at(row_idx, byte_index); + self.bitmap[idx] |= ((bloom_byte >> (7 - j)) & 1) << bit_index; + } + } + + self.next_block += 1; + if self.next_block >= self.block_range.end { + tracing::info!( + "Block limit reached for [{}, {}) range", + self.block_range.start, + self.block_range.end + ); + return Err(AddBloomError::BlockLimitReached); + } + + Ok(()) + } + + #[allow(dead_code)] + pub fn blocks_for_filter(&self, filter: &crate::EventFilter) -> Vec { + let mut keys = vec![]; + + if let Some(contract_address) = filter.contract_address { + keys.push(contract_address.0); + } + filter.keys.iter().flatten().for_each(|k| keys.push(k.0)); + + self.blocks_for_keys(keys) + } + + #[allow(dead_code)] + fn blocks_for_keys(&self, keys: Vec) -> Vec { + let mut block_matches = vec![]; + + for k in keys { + let mut row_to_check = vec![u8::MAX; Self::BLOCK_RANGE_BYTES as usize]; + + let indices = BloomFilter::indices_for_key(&k); + for row_idx in indices { + for (col_idx, row_byte) in row_to_check.iter_mut().enumerate() { + let idx = Self::bitmap_index_at(row_idx, col_idx); + *row_byte &= self.bitmap[idx]; + } + } + + for (col_idx, byte) in row_to_check.iter().enumerate() { + if *byte == 0 { + continue; + } + + for i in 0..8 { + if byte & (1 << i) != 0 { + block_matches.push(BlockNumber::new_or_panic((col_idx * 8 + i) as u64)); + } + } + } + } + + block_matches + } + + #[allow(dead_code)] + fn bitmap_index_at(row: usize, col: usize) -> usize { + row * Self::BLOCK_RANGE_BYTES as usize + col + } +} + +#[derive(Debug)] +pub enum AddBloomError { + BlockLimitReached, + InvalidBlockNumber, +} + #[derive(Clone)] pub(crate) struct BloomFilter(Bloom); impl BloomFilter { + // The size of the bitmap used by the Bloom filter. + const BITVEC_LEN: u64 = 16_384; // The size of the bitmap used by the Bloom filter (in bytes). - const BITMAP_BYTES: u64 = 2048; - // The maximal number of items anticipated to be inserted into the Bloom filter. - const ITEMS_COUNT: u32 = 1024; + const BITVEC_BYTES: u64 = Self::BITVEC_LEN / 8; // The number of hash functions used by the Bloom filter. // We need this value to be able to re-create the filter with the deserialized // bitmap. const K_NUM: u32 = 12; + // The maximal number of items anticipated to be inserted into the Bloom filter. + const ITEMS_COUNT: u32 = 1024; // The seed used by the hash functions of the filter. // This is a randomly generated vector of 32 bytes. const SEED: [u8; 32] = [ @@ -35,7 +264,7 @@ impl BloomFilter { pub fn new() -> Self { let bloom = Bloom::new_with_seed( - Self::BITMAP_BYTES as usize, + Self::BITVEC_BYTES as usize, Self::ITEMS_COUNT as usize, &Self::SEED, ); @@ -45,7 +274,7 @@ impl BloomFilter { } pub fn from_compressed_bytes(bytes: &[u8]) -> Self { - let bytes = zstd::bulk::decompress(bytes, Self::BITMAP_BYTES as usize * 2) + let bytes = zstd::bulk::decompress(bytes, Self::BITVEC_BYTES as usize * 2) .expect("Decompressing Bloom filter"); Self::from_bytes(&bytes) } @@ -57,7 +286,7 @@ impl BloomFilter { let k4 = u64::from_le_bytes(Self::SEED[24..32].try_into().unwrap()); let bloom = Bloom::from_existing( bytes, - Self::BITMAP_BYTES * 8, + Self::BITVEC_BYTES * 8, Self::K_NUM, [(k1, k2), (k3, k4)], ); @@ -121,6 +350,24 @@ impl BloomFilter { self.check_keys(&filter.keys) } + + // Workaround to get the indices of the keys in the filter. + // Needed because the `bloomfilter` crate doesn't provide a + // way to get this information. + fn indices_for_key(key: &Felt) -> Vec { + // Use key on an empty Bloom filter + let mut bloom = Self::new(); + bloom.set(key); + + bloom + .0 + .bit_vec() + .iter() + .enumerate() + .filter(|(_, bit)| *bit) + .map(|(i, _)| i) + .collect() + } } type CacheKey = (crate::ReorgCounter, BlockNumber); @@ -153,11 +400,13 @@ impl Cache { #[cfg(test)] mod tests { + use assert_matches::assert_matches; use pathfinder_common::felt; use super::*; - const KEY: Felt = felt!("0x0218b538681900fad5a0b2ffe1d6781c0c3f14df5d32071ace0bdc9d46cb69eb"); + const KEY: Felt = felt!("0x0218b538681900fad5a0b2ffe1d6781c0c3f14df5d32071ace0bdc9d46cb69ea"); + const KEY1: Felt = felt!("0x0218b538681900fad5a0b2ffe1d6781c0c3f14df5d32071ace0bdc9d46cb69eb"); const KEY_NOT_IN_FILTER: Felt = felt!("0x0218b538681900fad5a0b2ffe1d6781c0c3f14df5d32071ace0bdc9d46cb69ec"); @@ -179,4 +428,159 @@ mod tests { assert!(bloom.check(&KEY)); assert!(!bloom.check(&KEY_NOT_IN_FILTER)); } + + #[test] + #[cfg_attr(not(feature = "aggregate_bloom"), ignore)] + fn add_bloom_and_check_single_block_found() { + let from_block = BlockNumber::new_or_panic(0); + let mut aggregate_bloom_filter = AggregateBloom::new(from_block); + + let mut bloom = BloomFilter::new(); + bloom.set(&KEY); + bloom.set(&KEY1); + + aggregate_bloom_filter + .add_bloom(&bloom, from_block) + .unwrap(); + + let block_matches = aggregate_bloom_filter.blocks_for_keys(vec![KEY]); + + assert_eq!(block_matches, vec![from_block]); + } + + #[test] + #[cfg_attr(not(feature = "aggregate_bloom"), ignore)] + fn add_blooms_and_check_multiple_blocks_found() { + let from_block = BlockNumber::new_or_panic(0); + let mut aggregate_bloom_filter = AggregateBloom::new(from_block); + + let mut bloom = BloomFilter::new(); + bloom.set(&KEY); + + aggregate_bloom_filter + .add_bloom(&bloom, from_block) + .unwrap(); + aggregate_bloom_filter + .add_bloom(&bloom, from_block + 1) + .unwrap(); + + let block_matches = aggregate_bloom_filter.blocks_for_keys(vec![KEY]); + + assert_eq!(block_matches, vec![from_block, from_block + 1]); + } + + #[test] + #[cfg_attr(not(feature = "aggregate_bloom"), ignore)] + fn key_not_in_filter_returns_empty_vec() { + let from_block = BlockNumber::new_or_panic(0); + let mut aggregate_bloom_filter = AggregateBloom::new(from_block); + + let mut bloom = BloomFilter::new(); + bloom.set(&KEY); + bloom.set(&KEY1); + + aggregate_bloom_filter + .add_bloom(&bloom, from_block) + .unwrap(); + aggregate_bloom_filter + .add_bloom(&bloom, from_block + 1) + .unwrap(); + + let block_matches_empty = aggregate_bloom_filter.blocks_for_keys(vec![KEY_NOT_IN_FILTER]); + + assert_eq!(block_matches_empty, Vec::::new()); + } + + #[test] + #[cfg_attr(not(feature = "aggregate_bloom"), ignore)] + fn serialize_aggregate_roundtrip() { + let from_block = BlockNumber::new_or_panic(0); + let mut aggregate_bloom_filter = AggregateBloom::new(from_block); + + let mut bloom = BloomFilter::new(); + bloom.set(&KEY); + + aggregate_bloom_filter + .add_bloom(&bloom, from_block) + .unwrap(); + aggregate_bloom_filter + .add_bloom(&bloom, from_block + 1) + .unwrap(); + + let bytes = aggregate_bloom_filter.to_bytes(); + let aggregate_bloom_filter = AggregateBloom::from_bytes(from_block, bytes.to_vec()); + + let block_matches = aggregate_bloom_filter.blocks_for_keys(vec![KEY]); + let block_matches_empty = aggregate_bloom_filter.blocks_for_keys(vec![KEY_NOT_IN_FILTER]); + + assert_eq!(block_matches, vec![from_block, from_block + 1]); + assert_eq!(block_matches_empty, Vec::::new()); + } + + #[test] + #[cfg_attr(not(feature = "aggregate_bloom"), ignore)] + fn block_limit_reached_after_full_range() { + impl AggregateBloom { + /// Real [Self::add_bloom] makes this test last way to long + fn add_bloom_mock(&mut self) { + self.next_block += 1; + } + } + + let from_block = BlockNumber::new_or_panic(0); + let mut aggregate_bloom_filter = AggregateBloom::new(from_block); + + let mut bloom = BloomFilter::new(); + bloom.set(&KEY); + + for _ in from_block.get()..(AggregateBloom::BLOCK_RANGE_LEN - 1) { + aggregate_bloom_filter.add_bloom_mock(); + } + + let last_block = from_block + AggregateBloom::BLOCK_RANGE_LEN - 1; + assert_matches!( + aggregate_bloom_filter.add_bloom(&bloom, last_block), + Err(AddBloomError::BlockLimitReached) + ); + } + + #[test] + #[cfg_attr(not(feature = "aggregate_bloom"), ignore)] + fn invalid_insert_pos() { + let from_block = BlockNumber::new_or_panic(0); + let mut aggregate_bloom_filter = AggregateBloom::new(from_block); + + let mut bloom = BloomFilter::new(); + bloom.set(&KEY); + + aggregate_bloom_filter + .add_bloom(&bloom, from_block) + .unwrap(); + + let invalid_insert_pos = from_block + AggregateBloom::BLOCK_RANGE_LEN; + assert_matches!( + aggregate_bloom_filter.add_bloom(&bloom, invalid_insert_pos), + Err(AddBloomError::InvalidBlockNumber) + ); + } + + #[test] + #[cfg_attr(not(feature = "aggregate_bloom"), ignore)] + #[should_panic] + fn skipping_a_block_panics() { + let from_block = BlockNumber::new_or_panic(0); + let mut aggregate_bloom_filter = AggregateBloom::new(from_block); + + let mut bloom = BloomFilter::new(); + bloom.set(&KEY); + + aggregate_bloom_filter + .add_bloom(&bloom, from_block) + .unwrap(); + + let skipped_block = from_block + 2; + aggregate_bloom_filter + .add_bloom(&bloom, skipped_block) + .unwrap(); + } } diff --git a/crates/storage/src/connection/event.rs b/crates/storage/src/connection/event.rs index 52fce6f21c..c779dac3c2 100644 --- a/crates/storage/src/connection/event.rs +++ b/crates/storage/src/connection/event.rs @@ -1,5 +1,6 @@ use std::num::NonZeroUsize; +use anyhow::Result; use pathfinder_common::event::Event; use pathfinder_common::{ BlockHash, @@ -10,7 +11,9 @@ use pathfinder_common::{ TransactionHash, }; -use crate::bloom::BloomFilter; +#[cfg(feature = "aggregate_bloom")] +use crate::bloom::AddBloomError; +use crate::bloom::{AggregateBloom, BloomFilter}; use crate::prelude::*; use crate::ReorgCounter; @@ -231,8 +234,8 @@ impl Transaction<'_> { } } - // Stop if we have a page of events plus an extra one to decide if we're on the - // last page. + // Stop if we have a page of events plus an extra one to decide if we're on + // the last page. if emitted_events.len() > filter.page_size { break ScanResult::PageFull; } @@ -246,6 +249,80 @@ impl Transaction<'_> { } }; + // TODO: + // The logic that constructs aggregate bloom filters is temporarily + // placed here, in order to compare with the current implementation. + // It will be moved to sync as a follow up. + #[cfg(feature = "aggregate_bloom")] + { + let mut aggregates = vec![]; + let mut running_aggregate = AggregateBloom::new(from_block); + + let mut blocks_from_individual = vec![]; + + for block_num in from_block.get()..=to_block.get() { + if block_num as usize >= max_blocks_to_scan.get() { + break; + } + + let block_num = BlockNumber::new_or_panic(block_num); + + // TODO: + // Using single block `BloomFilter` API for now since we don't have + // a table for `AggregateBloom` yet. + let bloom = self.load_bloom(reorg_counter, block_num)?; + match bloom { + Filter::Missing => {} + Filter::Cached(bloom) => { + if bloom.check_filter(filter) { + blocks_from_individual.push(block_num); + } + + match running_aggregate.add_bloom(&bloom, block_num) { + Ok(_) => {} + Err(AddBloomError::BlockLimitReached) => { + aggregates.push(running_aggregate); + running_aggregate = AggregateBloom::new(block_num + 1); + } + Err(AddBloomError::InvalidBlockNumber) => { + unreachable!() // For now. + } + } + } + Filter::Loaded(bloom) => { + if bloom.check_filter(filter) { + blocks_from_individual.push(block_num); + } + + match running_aggregate.add_bloom(&bloom, block_num) { + Ok(_) => {} + Err(AddBloomError::BlockLimitReached) => { + aggregates.push(running_aggregate); + running_aggregate = AggregateBloom::new(block_num + 1); + } + Err(AddBloomError::InvalidBlockNumber) => { + unreachable!() // For now. + } + } + } + } + } + + // Remainder of (to_block - from_block) % AggregateBloom::BLOCK_RANGE_LEN + aggregates.push(running_aggregate); + + let blocks_from_aggregate = aggregates.iter().fold(vec![], |mut acc, aggregate| { + acc.extend(aggregate.blocks_for_filter(filter)); + acc + }); + + if blocks_from_individual != blocks_from_aggregate { + tracing::error!("Blocks from individual and aggregate bloom filter do not match"); + tracing::error!("Individual: {:?}", blocks_from_individual,); + tracing::error!("Aggregate: {:?}", blocks_from_aggregate,); + } + } + match result { ScanResult::Done => { return Ok(PageOfEvents { @@ -389,6 +466,30 @@ impl Transaction<'_> { None => Filter::Missing, }) } + + // TODO: + // Implement once [`AggregateBloom`] table is added. + fn _running_bloom_aggregate(&self) -> Result, anyhow::Error> { + // Fetch running aggregate from DB + unimplemented!() + } + + fn _load_bloom_range( + &self, + _from_block: BlockNumber, + _to_block: BlockNumber, + ) -> anyhow::Result> { + // Should be something like: + // (from_block..to_block) + // .chunks(AggregateBloom::BLOCK_RANGE_LEN) + // .iter() + // .enumerate() + // .map(|(i, _)| { + // // load from DB where ID is i + // }) + // .collect() + unimplemented!() + } } fn continuation_token( @@ -1118,6 +1219,10 @@ mod tests { } #[test] + // TODO: + // This fails when "aggregate_bloom" feature is enabled because in that case all filters are + // loaded twice. We can ignore it for now. + #[cfg_attr(feature = "aggregate_bloom", ignore)] fn bloom_filter_load_limit() { let (storage, test_data) = test_utils::setup_test_storage(); let emitted_events = test_data.events; From ae7d62a4633c49ae71be54ee248cf455677dcd55 Mon Sep 17 00:00:00 2001 From: Vaclav Barta Date: Fri, 1 Nov 2024 09:11:45 +0100 Subject: [PATCH 213/282] checking incoming tx hash in CalculateHashes --- crates/p2p/src/client/conv.rs | 13 ++++ crates/p2p/src/client/peer_agnostic.rs | 12 ++-- crates/p2p/src/client/peer_agnostic/tests.rs | 6 +- crates/p2p/src/client/peer_agnostic/traits.rs | 4 +- crates/p2p/src/client/types.rs | 4 +- crates/pathfinder/src/sync/checkpoint.rs | 59 ++++++++++++++++++- crates/pathfinder/src/sync/error.rs | 6 ++ crates/pathfinder/src/sync/track.rs | 6 +- crates/pathfinder/src/sync/transactions.rs | 33 ++++++----- 9 files changed, 111 insertions(+), 32 deletions(-) diff --git a/crates/p2p/src/client/conv.rs b/crates/p2p/src/client/conv.rs index 0fdde1d219..dd19c76569 100644 --- a/crates/p2p/src/client/conv.rs +++ b/crates/p2p/src/client/conv.rs @@ -50,6 +50,7 @@ use pathfinder_common::transaction::{ L1HandlerTransaction, ResourceBound, ResourceBounds, + Transaction, TransactionVariant, }; use pathfinder_common::{ @@ -683,6 +684,18 @@ impl TryFromDto for TransactionVaria } } +impl TryFromDto for Transaction { + fn try_from_dto(dto: p2p_proto::transaction::Transaction) -> anyhow::Result + where + Self: Sized, + { + Ok(Transaction { + hash: TransactionHash(dto.transaction_hash.0), + variant: TransactionVariant::try_from_dto(dto.txn)?, + }) + } +} + impl TryFrom<(p2p_proto::receipt::Receipt, TransactionIndex)> for crate::client::types::Receipt { type Error = anyhow::Error; diff --git a/crates/p2p/src/client/peer_agnostic.rs b/crates/p2p/src/client/peer_agnostic.rs index 16012e8bbd..a2e64950ae 100644 --- a/crates/p2p/src/client/peer_agnostic.rs +++ b/crates/p2p/src/client/peer_agnostic.rs @@ -22,7 +22,7 @@ use p2p_proto::state::{ use p2p_proto::transaction::{TransactionWithReceipt, TransactionsRequest, TransactionsResponse}; use pathfinder_common::event::Event; use pathfinder_common::state_update::{ContractClassUpdate, StateUpdateData}; -use pathfinder_common::transaction::TransactionVariant; +use pathfinder_common::transaction::Transaction; use pathfinder_common::{ BlockNumber, CasmHash, @@ -297,7 +297,7 @@ impl BlockClient for Client { block: BlockNumber, ) -> Option<( PeerId, - impl Stream>, + impl Stream>, )> { let request = TransactionsRequest { iteration: Iteration { @@ -329,7 +329,7 @@ impl BlockClient for Client { match x { Ok(TransactionsResponse::Fin) => unreachable!("Already handled Fin above"), Ok(TransactionsResponse::TransactionWithReceipt(tx_with_receipt)) => Ok(( - TransactionVariant::try_from_dto(tx_with_receipt.transaction.txn)?, + Transaction::try_from_dto(tx_with_receipt.transaction)?, Receipt::try_from(( tx_with_receipt.receipt, TransactionIndex::new(i.try_into().unwrap()) @@ -849,14 +849,14 @@ mod transaction_stream { peer: PeerId, response: std::io::Result, txn_idx: TransactionIndex, - ) -> Option<(TransactionVariant, Receipt)> { + ) -> Option<(Transaction, Receipt)> { match response { Ok(TransactionsResponse::TransactionWithReceipt(TransactionWithReceipt { transaction, receipt, })) => { if let (Ok(t), Ok(r)) = ( - TransactionVariant::try_from_dto(transaction.txn), + Transaction::try_from_dto(transaction), Receipt::try_from((receipt, txn_idx)), ) { Some((t, r)) @@ -904,7 +904,7 @@ mod transaction_stream { peer: PeerId, progress: &mut BlockProgress, count_stream: &mut (impl Stream> + Unpin + Send + 'static), - transactions: Vec<(TransactionVariant, Receipt)>, + transactions: Vec<(Transaction, Receipt)>, start: &mut BlockNumber, stop: BlockNumber, tx: mpsc::Sender>, diff --git a/crates/p2p/src/client/peer_agnostic/tests.rs b/crates/p2p/src/client/peer_agnostic/tests.rs index c559d7a7fa..c3d78e038e 100644 --- a/crates/p2p/src/client/peer_agnostic/tests.rs +++ b/crates/p2p/src/client/peer_agnostic/tests.rs @@ -319,7 +319,11 @@ async fn make_transaction_stream( .map_ok(|x| { ( TestPeer(x.peer), - x.data.0.into_iter().map(TestTxn::new).collect(), + x.data + .0 + .into_iter() + .map(|(t, r)| TestTxn::new((t.variant, r))) + .collect(), ) }) .map_err(|_| ()) diff --git a/crates/p2p/src/client/peer_agnostic/traits.rs b/crates/p2p/src/client/peer_agnostic/traits.rs index c1e6fc3a13..fa4aeaa59e 100644 --- a/crates/p2p/src/client/peer_agnostic/traits.rs +++ b/crates/p2p/src/client/peer_agnostic/traits.rs @@ -2,7 +2,7 @@ use futures::{Future, Stream}; use libp2p::PeerId; use pathfinder_common::event::Event; use pathfinder_common::state_update::StateUpdateData; -use pathfinder_common::transaction::TransactionVariant; +use pathfinder_common::transaction::Transaction; use pathfinder_common::{BlockNumber, SignedBlockHeader, TransactionHash}; use crate::client::types::{ @@ -82,7 +82,7 @@ pub trait BlockClient { ) -> impl Future< Output = Option<( PeerId, - impl Stream> + Send, + impl Stream> + Send, )>, > + Send; diff --git a/crates/p2p/src/client/types.rs b/crates/p2p/src/client/types.rs index a3b28157f2..21000a0eae 100644 --- a/crates/p2p/src/client/types.rs +++ b/crates/p2p/src/client/types.rs @@ -3,7 +3,7 @@ use fake::Dummy; use libp2p::PeerId; use pathfinder_common::event::Event; use pathfinder_common::receipt::{ExecutionResources, ExecutionStatus, L2ToL1Message}; -use pathfinder_common::transaction::TransactionVariant; +use pathfinder_common::transaction::Transaction; use pathfinder_common::{ BlockCommitmentSignature, BlockCommitmentSignatureElem, @@ -75,7 +75,7 @@ impl From for Receipt { } } -pub type TransactionData = Vec<(TransactionVariant, Receipt)>; +pub type TransactionData = Vec<(Transaction, Receipt)>; pub type EventsForBlockByTransaction = (BlockNumber, Vec<(TransactionHash, Vec)>); diff --git a/crates/pathfinder/src/sync/checkpoint.rs b/crates/pathfinder/src/sync/checkpoint.rs index 97418ceeff..69c2755e85 100644 --- a/crates/pathfinder/src/sync/checkpoint.rs +++ b/crates/pathfinder/src/sync/checkpoint.rs @@ -1034,7 +1034,7 @@ mod tests { block .transaction_data .iter() - .map(|x| (x.0.variant.clone(), x.1.clone().into())) + .map(|x| (x.0.clone(), x.1.clone().into())) .collect::>(), block.header.header.number, ))) @@ -1067,6 +1067,42 @@ mod tests { .unwrap() } + async fn setup_commitment_mismatch(num_blocks: usize) -> Setup { + use fake::{Fake, Faker}; + tokio::task::spawn_blocking(move || { + let mut blocks = fake_storage::init::with_n_blocks(num_blocks); + let streamed_transactions = blocks + .iter_mut() + .map(|block| { + block.header.header.transaction_commitment = Faker.fake(); + + anyhow::Result::Ok(PeerData::for_tests(( + block + .transaction_data + .iter() + .map(|x| (x.0.clone(), x.1.clone().into())) + .collect::>(), + block.header.header.number, + ))) + }) + .collect::>(); + blocks.iter_mut().for_each(|b| { + // Purge transaction data. + b.transaction_data = Default::default(); + }); + + let storage = StorageBuilder::in_memory().unwrap(); + fake_storage::fill(&storage, &blocks); + Setup { + streamed_transactions, + expected_transactions: Vec::default(), + storage, + } + }) + .await + .unwrap() + } + #[tokio::test] async fn happy_path() { const NUM_BLOCKS: usize = 10; @@ -1110,7 +1146,7 @@ mod tests { } #[tokio::test] - async fn commitment_mismatch() { + async fn transaction_mismatch() { let Setup { streamed_transactions, storage, @@ -1126,6 +1162,25 @@ mod tests { BlockNumber::GENESIS, ) .await, + Err(SyncError::BadTransactionHash(_)) + ); + } + + #[tokio::test] + async fn commitment_mismatch() { + let Setup { + streamed_transactions, + storage, + .. + } = setup_commitment_mismatch(1).await; + assert_matches!( + handle_transaction_stream( + stream::iter(streamed_transactions), + storage.clone(), + ChainId::SEPOLIA_TESTNET, + BlockNumber::GENESIS, + ) + .await, Err(SyncError::TransactionCommitmentMismatch(_)) ); } diff --git a/crates/pathfinder/src/sync/error.rs b/crates/pathfinder/src/sync/error.rs index 789af78a24..0e1431a038 100644 --- a/crates/pathfinder/src/sync/error.rs +++ b/crates/pathfinder/src/sync/error.rs @@ -28,6 +28,8 @@ pub(super) enum SyncError { TransactionCommitmentMismatch(PeerId), #[error("State root mismatch")] StateRootMismatch(PeerId), + #[error("Transaction hash verification failed")] + BadTransactionHash(PeerId), } impl PartialEq for SyncError { @@ -59,6 +61,7 @@ impl SyncError { PeerData::new(x, SyncError2::TransactionCommitmentMismatch) } SyncError::StateRootMismatch(x) => PeerData::new(x, SyncError2::StateRootMismatch), + SyncError::BadTransactionHash(x) => PeerData::new(x, SyncError2::BadTransactionHash), } } @@ -79,6 +82,7 @@ impl SyncError { SyncError::TransactionCommitmentMismatch(peer) } SyncError2::StateRootMismatch => SyncError::StateRootMismatch(peer), + SyncError2::BadTransactionHash => SyncError::BadTransactionHash(peer), other => SyncError::Other(other.into()), } } @@ -136,6 +140,8 @@ pub(super) enum SyncError2 { TransactionCommitmentNotFound, #[error("State root mismatch")] StateRootMismatch, + #[error("Transaction hash verification failed")] + BadTransactionHash, } impl PartialEq for SyncError2 { diff --git a/crates/pathfinder/src/sync/track.rs b/crates/pathfinder/src/sync/track.rs index f836c76828..33152fa18f 100644 --- a/crates/pathfinder/src/sync/track.rs +++ b/crates/pathfinder/src/sync/track.rs @@ -1040,7 +1040,7 @@ mod tests { block: BlockNumber, ) -> Option<( PeerId, - impl Stream> + Send, + impl Stream> + Send, )> { let tr = self .blocks @@ -1049,8 +1049,8 @@ mod tests { .unwrap() .transaction_data .iter() - .map(|(t, r, e)| Ok((t.variant.clone(), P2PReceipt::from(r.clone())))) - .collect::>>(); + .map(|(t, r, e)| Ok((t.clone(), P2PReceipt::from(r.clone())))) + .collect::>>(); Some((PeerId::random(), stream::iter(tr))) } diff --git a/crates/pathfinder/src/sync/transactions.rs b/crates/pathfinder/src/sync/transactions.rs index 18f3a6b816..b6f63475ba 100644 --- a/crates/pathfinder/src/sync/transactions.rs +++ b/crates/pathfinder/src/sync/transactions.rs @@ -89,23 +89,24 @@ impl ProcessStage for CalculateHashes { let (transactions, block_number, version, expected_commitment) = input; let transactions = transactions .into_par_iter() - .map(|(tv, r)| { - let transaction_hash = tv.calculate_hash(self.0, false); - let transaction = Transaction { - hash: transaction_hash, - variant: tv, - }; - let receipt = Receipt { - actual_fee: r.actual_fee, - execution_resources: r.execution_resources, - l2_to_l1_messages: r.l2_to_l1_messages, - execution_status: r.execution_status, - transaction_hash, - transaction_index: r.transaction_index, - }; - (transaction, receipt) + .map(|(tx, r)| { + let transaction_hash = tx.variant.calculate_hash(self.0, false); + if tx.hash != transaction_hash { + tracing::debug!(input_hash=%tx.hash, actual_hash=%transaction_hash, "Transaction hash mismatch"); + Err(SyncError2::BadTransactionHash) + } else { + let receipt = Receipt { + actual_fee: r.actual_fee, + execution_resources: r.execution_resources, + l2_to_l1_messages: r.l2_to_l1_messages, + execution_status: r.execution_status, + transaction_hash, + transaction_index: r.transaction_index, + }; + Ok((tx, receipt)) + } }) - .collect::>(); + .collect::, _>>()?; Ok(UnverifiedTransactions { expected_commitment, transactions, From 3705dec3e4f16de8447154250565b3f2861098be Mon Sep 17 00:00:00 2001 From: Vaclav Barta Date: Mon, 4 Nov 2024 10:56:32 +0100 Subject: [PATCH 214/282] checking incoming class hash in compute_hash_impl --- crates/p2p/src/client/peer_agnostic.rs | 12 ++++++---- .../p2p/src/client/peer_agnostic/fixtures.rs | 17 ++++++++++---- crates/p2p/src/client/types.rs | 4 ++++ crates/pathfinder/src/sync/checkpoint.rs | 7 +++++- .../pathfinder/src/sync/class_definitions.rs | 23 ++++++++++++++----- crates/pathfinder/src/sync/error.rs | 6 +++++ crates/pathfinder/src/sync/track.rs | 7 +++--- 7 files changed, 58 insertions(+), 18 deletions(-) diff --git a/crates/p2p/src/client/peer_agnostic.rs b/crates/p2p/src/client/peer_agnostic.rs index a2e64950ae..a0311d9276 100644 --- a/crates/p2p/src/client/peer_agnostic.rs +++ b/crates/p2p/src/client/peer_agnostic.rs @@ -513,25 +513,27 @@ impl BlockClient for Client { Ok(ClassesResponse::Class(p2p_proto::class::Class::Cairo0 { class, domain: _, - class_hash: _, + class_hash, })) => { let definition = CairoDefinition::try_from_dto(class) .map_err(|_| ClassDefinitionsError::CairoDefinitionError(peer))?; class_definitions.push(ClassDefinition::Cairo { block_number: block, definition: definition.0, + hash: ClassHash(class_hash.0), }); } Ok(ClassesResponse::Class(p2p_proto::class::Class::Cairo1 { class, domain: _, - class_hash: _, + class_hash, })) => { let definition = SierraDefinition::try_from_dto(class) .map_err(|_| ClassDefinitionsError::SierraDefinitionError(peer))?; class_definitions.push(ClassDefinition::Sierra { block_number: block, sierra_definition: definition.0, + hash: SierraHash(class_hash.0), }); } Ok(ClassesResponse::Fin) => { @@ -1277,7 +1279,7 @@ mod class_definition_stream { Ok(ClassesResponse::Class(p2p_proto::class::Class::Cairo0 { class, domain: _, - class_hash: _, + class_hash, })) => { let Ok(CairoDefinition(definition)) = CairoDefinition::try_from_dto(class) else { // TODO punish the peer @@ -1288,12 +1290,13 @@ mod class_definition_stream { Some(ClassDefinition::Cairo { block_number, definition, + hash: ClassHash(class_hash.0), }) } Ok(ClassesResponse::Class(p2p_proto::class::Class::Cairo1 { class, domain: _, - class_hash: _, + class_hash, })) => { let Ok(SierraDefinition(definition)) = SierraDefinition::try_from_dto(class) else { // TODO punish the peer @@ -1304,6 +1307,7 @@ mod class_definition_stream { Some(ClassDefinition::Sierra { block_number, sierra_definition: definition, + hash: SierraHash(class_hash.0), }) } Ok(ClassesResponse::Fin) => { diff --git a/crates/p2p/src/client/peer_agnostic/fixtures.rs b/crates/p2p/src/client/peer_agnostic/fixtures.rs index 1d5c2e7555..0bdbee8cbf 100644 --- a/crates/p2p/src/client/peer_agnostic/fixtures.rs +++ b/crates/p2p/src/client/peer_agnostic/fixtures.rs @@ -26,7 +26,6 @@ use pathfinder_common::{ TransactionHash, TransactionIndex, }; -use pathfinder_crypto::Felt; use tagged::Tagged; use tagged_debug_derive::TaggedDebug; use tokio::sync::Mutex; @@ -288,7 +287,7 @@ pub fn class_resp(tag: i32) -> ClassesResponse { ClassDefinition::Cairo(c) => Class::Cairo0 { class: c.to_dto(), domain: 0, - class_hash: Hash(Felt::default()), + class_hash: Faker.fake(), }, } }) @@ -300,18 +299,28 @@ pub fn class_resp(tag: i32) -> ClassesResponse { pub fn class(tag: i32, block_number: u64) -> ClassDefinition { let block_number = BlockNumber::new_or_panic(block_number); match class_resp(tag) { - ClassesResponse::Class(Class::Cairo0 { class, .. }) => { + ClassesResponse::Class(Class::Cairo0 { + class, + domain: _, + class_hash, + }) => { Tagged::get(format!("class {tag}"), || ClassDefinition::Cairo { block_number, definition: CairoDefinition::try_from_dto(class).unwrap().0, + hash: ClassHash(class_hash.0), }) .unwrap() .data } - ClassesResponse::Class(Class::Cairo1 { class, .. }) => { + ClassesResponse::Class(Class::Cairo1 { + class, + domain: _, + class_hash, + }) => { Tagged::get(format!("class {tag}"), || ClassDefinition::Sierra { block_number, sierra_definition: SierraDefinition::try_from_dto(class).unwrap().0, + hash: SierraHash(class_hash.0), }) .unwrap() .data diff --git a/crates/p2p/src/client/types.rs b/crates/p2p/src/client/types.rs index 21000a0eae..53cfc5e621 100644 --- a/crates/p2p/src/client/types.rs +++ b/crates/p2p/src/client/types.rs @@ -12,11 +12,13 @@ use pathfinder_common::{ BlockNumber, BlockTimestamp, ClassCommitment, + ClassHash, EventCommitment, Fee, GasPrice, ReceiptCommitment, SequencerAddress, + SierraHash, SignedBlockHeader, StateCommitment, StateDiffCommitment, @@ -35,10 +37,12 @@ pub enum ClassDefinition { Cairo { block_number: BlockNumber, definition: Vec, + hash: ClassHash, }, Sierra { block_number: BlockNumber, sierra_definition: Vec, + hash: SierraHash, }, } diff --git a/crates/pathfinder/src/sync/checkpoint.rs b/crates/pathfinder/src/sync/checkpoint.rs index 69c2755e85..e774bfe9ab 100644 --- a/crates/pathfinder/src/sync/checkpoint.rs +++ b/crates/pathfinder/src/sync/checkpoint.rs @@ -1520,14 +1520,17 @@ mod tests { Ok(PeerData::for_tests(ClassDefinition::Cairo { block_number: BlockNumber::GENESIS + 1, definition: CAIRO.to_vec(), + hash: cairo_hash, })), Ok(PeerData::for_tests(ClassDefinition::Sierra { block_number: BlockNumber::GENESIS + 1, sierra_definition: SIERRA0.to_vec(), + hash: sierra0_hash, })), Ok(PeerData::for_tests(ClassDefinition::Sierra { block_number: BlockNumber::GENESIS + 1, sierra_definition: SIERRA2.to_vec(), + hash: sierra2_hash, })), ]; @@ -1618,11 +1621,13 @@ mod tests { #[rstest::rstest] #[case::cairo(ClassDefinition::Cairo { block_number: BlockNumber::GENESIS + 1, - definition: Default::default() + definition: Default::default(), + hash: Default::default(), })] #[case::sierra(ClassDefinition::Sierra { block_number: BlockNumber::GENESIS + 1, sierra_definition: Default::default(), + hash: Default::default(), })] #[tokio::test] async fn bad_layout(#[case] class: ClassDefinition) { diff --git a/crates/pathfinder/src/sync/class_definitions.rs b/crates/pathfinder/src/sync/class_definitions.rs index 9fd53577e5..403898761c 100644 --- a/crates/pathfinder/src/sync/class_definitions.rs +++ b/crates/pathfinder/src/sync/class_definitions.rs @@ -35,6 +35,7 @@ pub struct ClassWithLayout { pub block_number: BlockNumber, pub definition: ClassDefinition, pub layout: GwClassDefinition<'static>, + pub hash: ClassHash, } #[derive(Debug)] @@ -137,6 +138,7 @@ fn verify_layout_impl(def: P2PClassDefinition) -> anyhow::Result { let layout = GwClassDefinition::Cairo( serde_json::from_slice::>(&definition).inspect_err( @@ -147,11 +149,13 @@ fn verify_layout_impl(def: P2PClassDefinition) -> anyhow::Result { let layout = GwClassDefinition::Sierra( serde_json::from_slice::>(&sierra_definition).inspect_err( @@ -162,6 +166,7 @@ fn verify_layout_impl(def: P2PClassDefinition) -> anyhow::Result anyhow::Result { block_number, definition, layout, + hash, } = input; - let hash = match layout { + let computed_hash = match layout { GwClassDefinition::Cairo(c) => compute_cairo_class_hash( c.abi.as_ref().get().as_bytes(), c.program.as_ref().get().as_bytes(), @@ -222,11 +228,16 @@ fn compute_hash_impl(input: ClassWithLayout) -> anyhow::Result { ), }?; - Ok(Class { - block_number, - definition, - hash, - }) + if computed_hash != hash { + tracing::debug!(input_hash=%hash, actual_hash=%computed_hash, "Class hash mismatch"); + Err(SyncError2::BadClassHash.into()) + } else { + Ok(Class { + block_number, + definition, + hash, + }) + } } pub struct VerifyDeclaredAt { diff --git a/crates/pathfinder/src/sync/error.rs b/crates/pathfinder/src/sync/error.rs index 0e1431a038..625d368aeb 100644 --- a/crates/pathfinder/src/sync/error.rs +++ b/crates/pathfinder/src/sync/error.rs @@ -30,6 +30,8 @@ pub(super) enum SyncError { StateRootMismatch(PeerId), #[error("Transaction hash verification failed")] BadTransactionHash(PeerId), + #[error("Class hash verification failed")] + BadClassHash(PeerId), } impl PartialEq for SyncError { @@ -62,6 +64,7 @@ impl SyncError { } SyncError::StateRootMismatch(x) => PeerData::new(x, SyncError2::StateRootMismatch), SyncError::BadTransactionHash(x) => PeerData::new(x, SyncError2::BadTransactionHash), + SyncError::BadClassHash(x) => PeerData::new(x, SyncError2::BadClassHash), } } @@ -83,6 +86,7 @@ impl SyncError { } SyncError2::StateRootMismatch => SyncError::StateRootMismatch(peer), SyncError2::BadTransactionHash => SyncError::BadTransactionHash(peer), + SyncError2::BadClassHash => SyncError::BadClassHash(peer), other => SyncError::Other(other.into()), } } @@ -142,6 +146,8 @@ pub(super) enum SyncError2 { StateRootMismatch, #[error("Transaction hash verification failed")] BadTransactionHash, + #[error("Class hash verification failed")] + BadClassHash, } impl PartialEq for SyncError2 { diff --git a/crates/pathfinder/src/sync/track.rs b/crates/pathfinder/src/sync/track.rs index 33152fa18f..239a8e725e 100644 --- a/crates/pathfinder/src/sync/track.rs +++ b/crates/pathfinder/src/sync/track.rs @@ -14,7 +14,6 @@ use p2p::client::types::{ TransactionData, }; use p2p::PeerData; -use pathfinder_common::class_definition::ClassDefinition; use pathfinder_common::event::Event; use pathfinder_common::receipt::Receipt; use pathfinder_common::state_update::{DeclaredClasses, StateUpdateData}; @@ -1087,16 +1086,18 @@ mod tests { let defs = b .cairo_defs .iter() - .map(|(_, x)| ClassDefinition::Cairo { + .map(|(h, x)| ClassDefinition::Cairo { block_number: block, definition: x.clone(), + hash: *h, }) .chain( b.sierra_defs .iter() - .map(|(_, x, _)| ClassDefinition::Sierra { + .map(|(h, x, _)| ClassDefinition::Sierra { block_number: block, sierra_definition: x.clone(), + hash: *h, }), ) .collect::>(); From 613dc79e51ca0fa770694c1ab8a00f22e73a90a5 Mon Sep 17 00:00:00 2001 From: t00ts Date: Tue, 5 Nov 2024 12:22:57 +0100 Subject: [PATCH 215/282] feat(rpc): add `starknet_getCompiledCasm` --- Cargo.lock | 1 + crates/common/Cargo.toml | 1 + crates/common/src/casm_class.rs | 98 ++++++++++++++++++++++ crates/common/src/lib.rs | 1 + crates/rpc/src/lib.rs | 4 +- crates/rpc/src/method.rs | 2 + crates/rpc/src/method/get_compiled_casm.rs | 96 +++++++++++++++++++++ crates/rpc/src/v08.rs | 2 +- 8 files changed, 201 insertions(+), 4 deletions(-) create mode 100644 crates/common/src/casm_class.rs create mode 100644 crates/rpc/src/method/get_compiled_casm.rs diff --git a/Cargo.lock b/Cargo.lock index 362f45812b..618817e08a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7317,6 +7317,7 @@ dependencies = [ "fake", "metrics", "num-bigint 0.4.6", + "num-traits 0.2.19", "paste", "pathfinder-crypto", "primitive-types", diff --git a/crates/common/Cargo.toml b/crates/common/Cargo.toml index df929d24b5..05b4fcd8c0 100644 --- a/crates/common/Cargo.toml +++ b/crates/common/Cargo.toml @@ -16,6 +16,7 @@ bitvec = { workspace = true } fake = { workspace = true, features = ["derive"] } metrics = { workspace = true } num-bigint = { workspace = true } +num-traits = "0.2" paste = { workspace = true } pathfinder-crypto = { path = "../crypto" } primitive-types = { workspace = true, features = ["serde"] } diff --git a/crates/common/src/casm_class.rs b/crates/common/src/casm_class.rs new file mode 100644 index 0000000000..52d5cf2880 --- /dev/null +++ b/crates/common/src/casm_class.rs @@ -0,0 +1,98 @@ +use num_bigint::BigUint; +use num_traits::Num; +use pathfinder_crypto::Felt; +use serde::{Deserialize, Serialize}; + +use crate::EntryPoint; + +/// A contract in the Starknet network. +#[derive(Debug, Serialize, Deserialize)] +pub struct CasmContractClass { + pub bytecode: Vec, + pub bytecode_segment_lengths: Option, + pub compiler_version: String, + pub hints: serde_json::Value, + pub entry_points_by_type: CasmContractEntryPoints, + pub prime: BigUintAsHex, +} + +/// The entry points (functions) of a contract. +#[derive(Debug, Serialize, Deserialize)] +pub struct CasmContractEntryPoints { + #[serde(rename = "EXTERNAL")] + pub external: Vec, + #[serde(rename = "L1_HANDLER")] + pub l1_handler: Vec, + #[serde(rename = "CONSTRUCTOR")] + pub constructor: Vec, +} + +/// An entry point (function) of a contract. +#[derive(Debug, Serialize, Deserialize)] +pub struct CasmContractEntryPoint { + /// A field element that encodes the signature of the called function. + pub selector: EntryPoint, + /// The offset of the instruction that should be called within the contract + /// bytecode. + pub offset: usize, + // List of builtins. + pub builtins: Vec, +} + +/// A field element that encodes the signature of the called function. +#[derive(Debug, Serialize, Deserialize)] +#[serde(transparent)] +pub struct BigUintAsHex { + /// A field element that encodes the signature of the called function. + #[serde( + serialize_with = "serialize_big_uint", + deserialize_with = "deserialize_big_uint" + )] + pub value: BigUint, +} + +pub fn serialize_big_uint(num: &BigUint, serializer: S) -> Result +where + S: serde::Serializer, +{ + serializer.serialize_str(&format!("{num:#x}")) +} + +pub fn deserialize_big_uint<'a, D>(deserializer: D) -> Result +where + D: serde::Deserializer<'a>, +{ + let s = &::deserialize(deserializer)?; + match s.strip_prefix("0x") { + Some(num_no_prefix) => BigUint::from_str_radix(num_no_prefix, 16) + .map_err(|error| serde::de::Error::custom(format!("{error}"))), + None => Err(serde::de::Error::custom(format!( + "{s} does not start with `0x` is missing." + ))), + } +} + +/// NestedIntList is either a list of NestedIntList or an integer. +/// E.g., `[0, [1, 2], [3, [4]]]`. +/// +/// Used to represents the lengths of the segments in a contract, which are in a +/// form of a tree. +/// +/// For example, the contract may be segmented by functions, where each function +/// is segmented by its branches. It is also possible to have the inner +/// segmentation only for some of the functions, while others are kept as +/// non-segmented leaves in the tree. +#[derive(Debug, Serialize, Deserialize)] +#[serde(untagged)] +pub enum NestedIntList { + Leaf(usize), + Node(Vec), +} + +impl TryFrom<&str> for CasmContractClass { + type Error = serde_json::Error; + + fn try_from(value: &str) -> Result { + serde_json::from_str(value) + } +} diff --git a/crates/common/src/lib.rs b/crates/common/src/lib.rs index 8e5bf802d7..c0fab899ba 100644 --- a/crates/common/src/lib.rs +++ b/crates/common/src/lib.rs @@ -14,6 +14,7 @@ use pathfinder_crypto::Felt; use primitive_types::H160; use serde::{Deserialize, Serialize}; +pub mod casm_class; pub mod class_definition; pub mod consts; pub mod error; diff --git a/crates/rpc/src/lib.rs b/crates/rpc/src/lib.rs index 783b2cbaa3..506867823e 100644 --- a/crates/rpc/src/lib.rs +++ b/crates/rpc/src/lib.rs @@ -908,9 +908,7 @@ mod tests { ])] #[case::v0_8_trace("/rpc/v0_8", "v08/starknet_trace_api_openrpc.json", &[])] #[case::v0_8_write("/rpc/v0_8", "v08/starknet_write_api.json", &[])] - #[case::v0_8_executables("/rpc/v0_8", "v08/starknet_executables.json", &[ - "starknet_getCompiledCasm", - ])] + // get_transaction_status is now part of the official spec, so we are phasing it out. #[case::v0_8_pathfinder("/rpc/v0_8", "pathfinder_rpc_api.json", &["pathfinder_version", "pathfinder_getTransactionStatus"])] diff --git a/crates/rpc/src/method.rs b/crates/rpc/src/method.rs index 5ac36c9417..ecf15bb715 100644 --- a/crates/rpc/src/method.rs +++ b/crates/rpc/src/method.rs @@ -14,6 +14,7 @@ pub mod get_block_with_txs; pub mod get_class; pub mod get_class_at; pub mod get_class_hash_at; +pub mod get_compiled_casm; pub mod get_events; pub mod get_messages_status; pub mod get_nonce; @@ -49,6 +50,7 @@ pub use get_block_with_txs::get_block_with_txs; pub use get_class::get_class; pub use get_class_at::get_class_at; pub use get_class_hash_at::get_class_hash_at; +pub use get_compiled_casm::get_compiled_casm; pub use get_events::get_events; pub use get_messages_status::get_messages_status; pub use get_nonce::get_nonce; diff --git a/crates/rpc/src/method/get_compiled_casm.rs b/crates/rpc/src/method/get_compiled_casm.rs new file mode 100644 index 0000000000..1401b4f3e8 --- /dev/null +++ b/crates/rpc/src/method/get_compiled_casm.rs @@ -0,0 +1,96 @@ +use anyhow::Context; +use pathfinder_common::casm_class::CasmContractClass; +use pathfinder_common::ClassHash; + +use crate::context::RpcContext; +use crate::error::ApplicationError; + +#[derive(Debug)] +pub struct Input { + pub class_hash: ClassHash, +} + +impl crate::dto::DeserializeForVersion for Input { + fn deserialize(value: crate::dto::Value) -> Result { + value.deserialize_map(|value| { + Ok(Self { + class_hash: ClassHash(value.deserialize("class_hash")?), + }) + }) + } +} + +#[derive(Debug)] +pub struct Output(CasmContractClass); + +impl crate::dto::serialize::SerializeForVersion for Output { + fn serialize( + &self, + serializer: crate::dto::serialize::Serializer, + ) -> Result { + self.0.serialize(serializer) + } +} + +#[derive(Debug)] +pub enum Error { + CompilationFailed, + ClassHashNotFound(ClassHash), + Internal(anyhow::Error), +} + +impl From for Error { + fn from(error: anyhow::Error) -> Self { + Self::Internal(error) + } +} + +impl From for crate::jsonrpc::RpcError { + fn from(error: Error) -> Self { + match error { + Error::CompilationFailed => Self::ApplicationError(ApplicationError::CompilationFailed), + Error::ClassHashNotFound(_) => { + Self::ApplicationError(ApplicationError::ClassHashNotFound) + } + Error::Internal(e) => Self::InternalError(e), + } + } +} + +/// Get the compiled casm for a given class hash. +pub async fn get_compiled_casm(context: RpcContext, input: Input) -> Result { + let span = tracing::Span::current(); + let jh = tokio::task::spawn_blocking(move || -> Result { + let _g = span.enter(); + + let mut db = context + .storage + .connection() + .context("Opening database connection") + .map_err(Error::Internal)?; + + let tx = db + .transaction() + .context("Creating database transaction") + .map_err(Error::Internal)?; + + // Get the class definition + let casm_definition = tx + .casm_definition(input.class_hash) + .context("Fetching class definition") + .map_err(Error::Internal)? + .ok_or(Error::ClassHashNotFound(input.class_hash))?; + + // Convert to JSON string + let casm_definition_str = String::from_utf8_lossy(&casm_definition); + + // Parse the casm definition + let casm_contract_class = CasmContractClass::try_from(casm_definition_str.as_ref()) + .context("Parsing casm definition") + .map_err(|_| Error::CompilationFailed)?; + + Ok(Output(casm_contract_class)) + }); + + jh.await.context("Fetching compiled casm")? +} diff --git a/crates/rpc/src/v08.rs b/crates/rpc/src/v08.rs index 35617cc65e..44c0362338 100644 --- a/crates/rpc/src/v08.rs +++ b/crates/rpc/src/v08.rs @@ -41,6 +41,6 @@ pub fn register_routes() -> RpcRouterBuilder { .register("starknet_syncing", crate::method::syncing) .register("starknet_traceBlockTransactions", crate::method::trace_block_transactions) .register("starknet_traceTransaction", crate::method::trace_transaction) - + .register("starknet_getCompiledCasm", crate::method::get_compiled_casm) .register("pathfinder_getProof", crate::pathfinder::methods::get_proof) } From 051657a3313ecc5a354969d894baa5a23a159ff4 Mon Sep 17 00:00:00 2001 From: t00ts Date: Wed, 6 Nov 2024 14:46:57 +0100 Subject: [PATCH 216/282] chore: add tests for `starknet_getCompiledCasm` --- crates/common/src/casm_class.rs | 10 +- crates/rpc/src/method/get_compiled_casm.rs | 1454 ++++++++++++++++++++ 2 files changed, 1459 insertions(+), 5 deletions(-) diff --git a/crates/common/src/casm_class.rs b/crates/common/src/casm_class.rs index 52d5cf2880..cc08b9e133 100644 --- a/crates/common/src/casm_class.rs +++ b/crates/common/src/casm_class.rs @@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize}; use crate::EntryPoint; /// A contract in the Starknet network. -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)] pub struct CasmContractClass { pub bytecode: Vec, pub bytecode_segment_lengths: Option, @@ -17,7 +17,7 @@ pub struct CasmContractClass { } /// The entry points (functions) of a contract. -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)] pub struct CasmContractEntryPoints { #[serde(rename = "EXTERNAL")] pub external: Vec, @@ -28,7 +28,7 @@ pub struct CasmContractEntryPoints { } /// An entry point (function) of a contract. -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)] pub struct CasmContractEntryPoint { /// A field element that encodes the signature of the called function. pub selector: EntryPoint, @@ -40,7 +40,7 @@ pub struct CasmContractEntryPoint { } /// A field element that encodes the signature of the called function. -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)] #[serde(transparent)] pub struct BigUintAsHex { /// A field element that encodes the signature of the called function. @@ -82,7 +82,7 @@ where /// is segmented by its branches. It is also possible to have the inner /// segmentation only for some of the functions, while others are kept as /// non-segmented leaves in the tree. -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)] #[serde(untagged)] pub enum NestedIntList { Leaf(usize), diff --git a/crates/rpc/src/method/get_compiled_casm.rs b/crates/rpc/src/method/get_compiled_casm.rs index 1401b4f3e8..ce8f969258 100644 --- a/crates/rpc/src/method/get_compiled_casm.rs +++ b/crates/rpc/src/method/get_compiled_casm.rs @@ -94,3 +94,1457 @@ pub async fn get_compiled_casm(context: RpcContext, input: Input) -> Result anyhow::Result { + let dir = tempdir().expect("tempdir"); + let mut db_path = dir.path().to_path_buf(); + db_path.push("db.sqlite"); + + let storage = pathfinder_storage::StorageBuilder::in_memory().unwrap(); + + { + let mut db = storage.connection().expect("db connection"); + let tx = db.transaction().expect("tx"); + + tx.insert_sierra_class( + &sierra_hash!("0x0484c163658bcce5f9916f486171ac60143a92897533aa7ff7ac800b16c63311"), + CAIRO_1_1_0_BALANCE_SIERRA_JSON, + &casm_hash!("0x0484c163658bcce5f9916f486171ac60143a92897533aa7ff7ac800b16c63311"), + CAIRO_1_1_0_BALANCE_CASM_JSON, + ) + .expect("insert class"); + + tx.commit().unwrap(); + } + + let rpc = RpcContext::for_tests().with_storage(storage); + + Ok(rpc) + } + + fn input() -> Input { + Input { + class_hash: class_hash!( + "0x0484c163658bcce5f9916f486171ac60143a92897533aa7ff7ac800b16c63311" + ), + } + } + + fn input_not_found() -> Input { + Input { + class_hash: class_hash!( + "0x0000c163658bcce5f9916f486171ac60143000897533aa7ff7ac800b16c63000" + ), + } + } + + fn expected() -> CasmContractClass { + // Expected entry points + let entry_points_by_type = CasmContractEntryPoints { + external: vec![ + CasmContractEntryPoint { + selector: EntryPoint(felt!( + "0x0362398BEC32BC0EBB411203221A35A0301193A96F317EBE5E40BE9F60D15320" + )), + offset: 0, + builtins: vec!["range_check".to_string()], + }, + CasmContractEntryPoint { + selector: EntryPoint(felt!( + "0x039E11D48192E4333233C7EB19D10AD67C362BB28580C604D67884C85DA39695" + )), + offset: 141, + builtins: vec!["range_check".to_string()], + }, + ], + l1_handler: vec![CasmContractEntryPoint { + selector: EntryPoint(felt!( + "0x031EE153A27E249DC4BADE6B861B37EF1E1EA0A4C0BF73B7405A02E9E72F7BE3" + )), + offset: 266, + builtins: vec!["range_check".to_string()], + }], + constructor: vec![CasmContractEntryPoint { + selector: EntryPoint(felt!( + "0x028FFE4FF0F226A9107253E17A904099AA4F63A02A5621DE0576E5AA71BC5194" + )), + offset: 428, + builtins: vec!["range_check".to_string()], + }], + }; + + // Expected bytecode + let bytecode = vec![ + felt!("0x0000000000000000000000000000000000000000000000000A0680017FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000007"), + felt!("0x000000000000000000000000000000000000000000000000482680017FFA8000"), + felt!("0x00000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFA7EA"), + felt!("0x000000000000000000000000000000000000000000000000400280007FF97FFF"), + felt!("0x000000000000000000000000000000000000000000000000010780017FFF7FFF"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000079"), + felt!("0x0000000000000000000000000000000000000000000000004825800180007FFA"), + felt!("0x0000000000000000000000000000000000000000000000000000000000005816"), + felt!("0x000000000000000000000000000000000000000000000000400280007FF97FFF"), + felt!("0x000000000000000000000000000000000000000000000000480A7FFC7FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000480A7FFD7FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000001104800180018000"), + felt!("0x000000000000000000000000000000000000000000000000000000000000022D"), + felt!("0x000000000000000000000000000000000000000000000000482680017FF98000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x000000000000000000000000000000000000000000000000020680017FFF7FFD"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000060"), + felt!("0x00000000000000000000000000000000000000000000000048307FFB80007FFC"), + felt!("0x0000000000000000000000000000000000000000000000004824800180007FFF"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000000"), + felt!("0x000000000000000000000000000000000000000000000000020680017FFF7FFF"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000004"), + felt!("0x000000000000000000000000000000000000000000000000010780017FFF7FFF"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000006"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000000"), + felt!("0x000000000000000000000000000000000000000000000000010780017FFF7FFF"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000004"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x00000000000000000000000000000000000000000000000048307FFE80007FFF"), + felt!("0x000000000000000000000000000000000000000000000000020680017FFF7FFF"), + felt!("0x000000000000000000000000000000000000000000000000000000000000003D"), + felt!("0x0000000000000000000000000000000000000000000000001104800180018000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000326"), + felt!("0x000000000000000000000000000000000000000000000000482480017FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000325"), + felt!("0x000000000000000000000000000000000000000000000000480080007FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000A0680017FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000009"), + felt!("0x0000000000000000000000000000000000000000000000004824800180007FE4"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000000"), + felt!("0x000000000000000000000000000000000000000000000000482480017FFF8000"), + felt!("0x0000000000000000000000000000000100000000000000000000000000000000"), + felt!("0x000000000000000000000000000000000000000000000000400080007FF37FFF"), + felt!("0x000000000000000000000000000000000000000000000000010780017FFF7FFF"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000020"), + felt!("0x0000000000000000000000000000000000000000000000004824800180007FE4"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000000"), + felt!("0x000000000000000000000000000000000000000000000000400080007FF47FFF"), + felt!("0x00000000000000000000000000000000000000000000000048127FFF7FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000480A7FFB7FFF8000"), + felt!("0x00000000000000000000000000000000000000000000000048127FF17FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000001104800180018000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000228"), + felt!("0x000000000000000000000000000000000000000000000000482480017FBB8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x000000000000000000000000000000000000000000000000020680017FFF7FFC"), + felt!("0x000000000000000000000000000000000000000000000000000000000000000C"), + felt!("0x000000000000000000000000000000000000000000000000040780017FFF7FFF"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x00000000000000000000000000000000000000000000000048127FFE7FFF8000"), + felt!("0x00000000000000000000000000000000000000000000000048127FF87FFF8000"), + felt!("0x00000000000000000000000000000000000000000000000048127FF87FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000000"), + felt!("0x00000000000000000000000000000000000000000000000048127FFB7FFF8000"), + felt!("0x00000000000000000000000000000000000000000000000048127FFA7FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000208B7FFF7FFF7FFE"), + felt!("0x00000000000000000000000000000000000000000000000048127FFF7FFF8000"), + felt!("0x00000000000000000000000000000000000000000000000048127FF97FFF8000"), + felt!("0x00000000000000000000000000000000000000000000000048127FF97FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x00000000000000000000000000000000000000000000000048127FF97FFF8000"), + felt!("0x00000000000000000000000000000000000000000000000048127FF97FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000208B7FFF7FFF7FFE"), + felt!("0x000000000000000000000000000000000000000000000000040780017FFF7FFF"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x000000000000000000000000000000000000000000004F7574206F6620676173"), + felt!("0x000000000000000000000000000000000000000000000000400080007FFE7FFF"), + felt!("0x000000000000000000000000000000000000000000000000482480017FF18000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x00000000000000000000000000000000000000000000000048127FDF7FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000480A7FFB7FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x00000000000000000000000000000000000000000000000048127FFA7FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000482480017FF98000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x000000000000000000000000000000000000000000000000208B7FFF7FFF7FFE"), + felt!("0x000000000000000000000000000000000000000000000000480A7FFB7FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000001104800180018000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000226"), + felt!("0x000000000000000000000000000000000000000000000000040780017FFF7FFF"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x00000000496E70757420746F6F206C6F6E6720666F7220617267756D656E7473"), + felt!("0x000000000000000000000000000000000000000000000000400080007FFE7FFF"), + felt!("0x00000000000000000000000000000000000000000000000048127FF47FFF8000"), + felt!("0x00000000000000000000000000000000000000000000000048127FE27FFF8000"), + felt!("0x00000000000000000000000000000000000000000000000048127FFB7FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x00000000000000000000000000000000000000000000000048127FFA7FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000482480017FF98000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x000000000000000000000000000000000000000000000000208B7FFF7FFF7FFE"), + felt!("0x000000000000000000000000000000000000000000000000040780017FFF7FFF"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x000000496E70757420746F6F2073686F727420666F7220617267756D656E7473"), + felt!("0x000000000000000000000000000000000000000000000000400080007FFE7FFF"), + felt!("0x00000000000000000000000000000000000000000000000048127FFD7FFF8000"), + felt!("0x00000000000000000000000000000000000000000000000048127FEB7FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000480A7FFB7FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x00000000000000000000000000000000000000000000000048127FFA7FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000482480017FF98000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x000000000000000000000000000000000000000000000000208B7FFF7FFF7FFE"), + felt!("0x000000000000000000000000000000000000000000000000040780017FFF7FFF"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x000000000000000000000000000000000000000000004F7574206F6620676173"), + felt!("0x000000000000000000000000000000000000000000000000400080007FFE7FFF"), + felt!("0x000000000000000000000000000000000000000000000000482680017FF98000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x000000000000000000000000000000000000000000000000480A7FFA7FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000480A7FFB7FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x00000000000000000000000000000000000000000000000048127FFA7FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000482480017FF98000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x000000000000000000000000000000000000000000000000208B7FFF7FFF7FFE"), + felt!("0x0000000000000000000000000000000000000000000000000A0680017FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000007"), + felt!("0x000000000000000000000000000000000000000000000000482680017FFA8000"), + felt!("0x00000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFE548"), + felt!("0x000000000000000000000000000000000000000000000000400280007FF97FFF"), + felt!("0x000000000000000000000000000000000000000000000000010780017FFF7FFF"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000069"), + felt!("0x0000000000000000000000000000000000000000000000004825800180007FFA"), + felt!("0x0000000000000000000000000000000000000000000000000000000000001AB8"), + felt!("0x000000000000000000000000000000000000000000000000400280007FF97FFF"), + felt!("0x00000000000000000000000000000000000000000000000048297FFC80007FFD"), + felt!("0x000000000000000000000000000000000000000000000000482680017FF98000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x0000000000000000000000000000000000000000000000004824800180007FFE"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000000"), + felt!("0x000000000000000000000000000000000000000000000000020680017FFF7FFF"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000004"), + felt!("0x000000000000000000000000000000000000000000000000010780017FFF7FFF"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000006"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000000"), + felt!("0x000000000000000000000000000000000000000000000000010780017FFF7FFF"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000004"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x00000000000000000000000000000000000000000000000048307FFE80007FFF"), + felt!("0x000000000000000000000000000000000000000000000000020680017FFF7FFF"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000041"), + felt!("0x0000000000000000000000000000000000000000000000001104800180018000"), + felt!("0x000000000000000000000000000000000000000000000000000000000000029F"), + felt!("0x000000000000000000000000000000000000000000000000482480017FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000000000000000029E"), + felt!("0x000000000000000000000000000000000000000000000000480080007FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000A0680017FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000009"), + felt!("0x0000000000000000000000000000000000000000000000004824800180007FF4"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000000"), + felt!("0x000000000000000000000000000000000000000000000000482480017FFF8000"), + felt!("0x0000000000000000000000000000000100000000000000000000000000000000"), + felt!("0x000000000000000000000000000000000000000000000000400080007FF47FFF"), + felt!("0x000000000000000000000000000000000000000000000000010780017FFF7FFF"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000024"), + felt!("0x0000000000000000000000000000000000000000000000004824800180007FF4"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000000"), + felt!("0x000000000000000000000000000000000000000000000000400080007FF57FFF"), + felt!("0x00000000000000000000000000000000000000000000000048127FFF7FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000480A7FFB7FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000001104800180018000"), + felt!("0x00000000000000000000000000000000000000000000000000000000000001CA"), + felt!("0x000000000000000000000000000000000000000000000000482480017FDE8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x000000000000000000000000000000000000000000000000020680017FFF7FFC"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000011"), + felt!("0x000000000000000000000000000000000000000000000000040780017FFF7FFF"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x00000000000000000000000000000000000000000000000048127FFD7FFF8000"), + felt!("0x00000000000000000000000000000000000000000000000048127FFE7FFF8000"), + felt!("0x00000000000000000000000000000000000000000000000048127FFD7FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000001104800180018000"), + felt!("0x00000000000000000000000000000000000000000000000000000000000001F0"), + felt!("0x00000000000000000000000000000000000000000000000048127FF77FFF8000"), + felt!("0x00000000000000000000000000000000000000000000000048127FF17FFF8000"), + felt!("0x00000000000000000000000000000000000000000000000048127FF17FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000000"), + felt!("0x00000000000000000000000000000000000000000000000048127FFA7FFF8000"), + felt!("0x00000000000000000000000000000000000000000000000048127FFA7FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000208B7FFF7FFF7FFE"), + felt!("0x00000000000000000000000000000000000000000000000048127FFF7FFF8000"), + felt!("0x00000000000000000000000000000000000000000000000048127FF97FFF8000"), + felt!("0x00000000000000000000000000000000000000000000000048127FF97FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x00000000000000000000000000000000000000000000000048127FF97FFF8000"), + felt!("0x00000000000000000000000000000000000000000000000048127FF97FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000208B7FFF7FFF7FFE"), + felt!("0x000000000000000000000000000000000000000000000000040780017FFF7FFF"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x000000000000000000000000000000000000000000004F7574206F6620676173"), + felt!("0x000000000000000000000000000000000000000000000000400080007FFE7FFF"), + felt!("0x000000000000000000000000000000000000000000000000482480017FF28000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x00000000000000000000000000000000000000000000000048127FEF7FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000480A7FFB7FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x00000000000000000000000000000000000000000000000048127FFA7FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000482480017FF98000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x000000000000000000000000000000000000000000000000208B7FFF7FFF7FFE"), + felt!("0x000000000000000000000000000000000000000000000000480A7FFB7FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000001104800180018000"), + felt!("0x000000000000000000000000000000000000000000000000000000000000019B"), + felt!("0x000000000000000000000000000000000000000000000000040780017FFF7FFF"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x00000000496E70757420746F6F206C6F6E6720666F7220617267756D656E7473"), + felt!("0x000000000000000000000000000000000000000000000000400080007FFE7FFF"), + felt!("0x00000000000000000000000000000000000000000000000048127FF57FFF8000"), + felt!("0x00000000000000000000000000000000000000000000000048127FF27FFF8000"), + felt!("0x00000000000000000000000000000000000000000000000048127FFB7FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x00000000000000000000000000000000000000000000000048127FFA7FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000482480017FF98000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x000000000000000000000000000000000000000000000000208B7FFF7FFF7FFE"), + felt!("0x000000000000000000000000000000000000000000000000040780017FFF7FFF"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x000000000000000000000000000000000000000000004F7574206F6620676173"), + felt!("0x000000000000000000000000000000000000000000000000400080007FFE7FFF"), + felt!("0x000000000000000000000000000000000000000000000000482680017FF98000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x000000000000000000000000000000000000000000000000480A7FFA7FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000480A7FFB7FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x00000000000000000000000000000000000000000000000048127FFA7FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000482480017FF98000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x000000000000000000000000000000000000000000000000208B7FFF7FFF7FFE"), + felt!("0x0000000000000000000000000000000000000000000000000A0680017FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000007"), + felt!("0x000000000000000000000000000000000000000000000000482680017FFA8000"), + felt!("0x00000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFF9FAC"), + felt!("0x000000000000000000000000000000000000000000000000400280007FF97FFF"), + felt!("0x000000000000000000000000000000000000000000000000010780017FFF7FFF"), + felt!("0x000000000000000000000000000000000000000000000000000000000000008E"), + felt!("0x0000000000000000000000000000000000000000000000004825800180007FFA"), + felt!("0x0000000000000000000000000000000000000000000000000000000000006054"), + felt!("0x000000000000000000000000000000000000000000000000400280007FF97FFF"), + felt!("0x000000000000000000000000000000000000000000000000480A7FFC7FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000480A7FFD7FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000001104800180018000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000123"), + felt!("0x000000000000000000000000000000000000000000000000482680017FF98000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x000000000000000000000000000000000000000000000000020680017FFF7FFD"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000075"), + felt!("0x00000000000000000000000000000000000000000000000048127FFB7FFF8000"), + felt!("0x00000000000000000000000000000000000000000000000048127FFB7FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000001104800180018000"), + felt!("0x000000000000000000000000000000000000000000000000000000000000011B"), + felt!("0x000000000000000000000000000000000000000000000000020680017FFF7FFE"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000061"), + felt!("0x00000000000000000000000000000000000000000000000048307FFC80007FFD"), + felt!("0x0000000000000000000000000000000000000000000000004824800180007FFF"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000000"), + felt!("0x000000000000000000000000000000000000000000000000020680017FFF7FFF"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000004"), + felt!("0x000000000000000000000000000000000000000000000000010780017FFF7FFF"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000006"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000000"), + felt!("0x000000000000000000000000000000000000000000000000010780017FFF7FFF"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000004"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x00000000000000000000000000000000000000000000000048307FFE80007FFF"), + felt!("0x000000000000000000000000000000000000000000000000020680017FFF7FFF"), + felt!("0x000000000000000000000000000000000000000000000000000000000000003E"), + felt!("0x0000000000000000000000000000000000000000000000001104800180018000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000216"), + felt!("0x000000000000000000000000000000000000000000000000482480017FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000215"), + felt!("0x000000000000000000000000000000000000000000000000480080007FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000A0680017FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000009"), + felt!("0x0000000000000000000000000000000000000000000000004824800180007FD4"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000000"), + felt!("0x000000000000000000000000000000000000000000000000482480017FFF8000"), + felt!("0x0000000000000000000000000000000100000000000000000000000000000000"), + felt!("0x000000000000000000000000000000000000000000000000400080007FE37FFF"), + felt!("0x000000000000000000000000000000000000000000000000010780017FFF7FFF"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000021"), + felt!("0x0000000000000000000000000000000000000000000000004824800180007FD4"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000000"), + felt!("0x000000000000000000000000000000000000000000000000400080007FE47FFF"), + felt!("0x00000000000000000000000000000000000000000000000048127FFF7FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000480A7FFB7FFF8000"), + felt!("0x00000000000000000000000000000000000000000000000048127FE17FFF8000"), + felt!("0x00000000000000000000000000000000000000000000000048127FF17FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000001104800180018000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000175"), + felt!("0x000000000000000000000000000000000000000000000000482480017FAA8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x000000000000000000000000000000000000000000000000020680017FFF7FFC"), + felt!("0x000000000000000000000000000000000000000000000000000000000000000C"), + felt!("0x000000000000000000000000000000000000000000000000040780017FFF7FFF"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x00000000000000000000000000000000000000000000000048127FFE7FFF8000"), + felt!("0x00000000000000000000000000000000000000000000000048127FF87FFF8000"), + felt!("0x00000000000000000000000000000000000000000000000048127FF87FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000000"), + felt!("0x00000000000000000000000000000000000000000000000048127FFB7FFF8000"), + felt!("0x00000000000000000000000000000000000000000000000048127FFA7FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000208B7FFF7FFF7FFE"), + felt!("0x00000000000000000000000000000000000000000000000048127FFF7FFF8000"), + felt!("0x00000000000000000000000000000000000000000000000048127FF97FFF8000"), + felt!("0x00000000000000000000000000000000000000000000000048127FF97FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x00000000000000000000000000000000000000000000000048127FF97FFF8000"), + felt!("0x00000000000000000000000000000000000000000000000048127FF97FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000208B7FFF7FFF7FFE"), + felt!("0x000000000000000000000000000000000000000000000000040780017FFF7FFF"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x000000000000000000000000000000000000000000004F7574206F6620676173"), + felt!("0x000000000000000000000000000000000000000000000000400080007FFE7FFF"), + felt!("0x000000000000000000000000000000000000000000000000482480017FE18000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x00000000000000000000000000000000000000000000000048127FCF7FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000480A7FFB7FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x00000000000000000000000000000000000000000000000048127FFA7FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000482480017FF98000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x000000000000000000000000000000000000000000000000208B7FFF7FFF7FFE"), + felt!("0x000000000000000000000000000000000000000000000000480A7FFB7FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000001104800180018000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000115"), + felt!("0x000000000000000000000000000000000000000000000000040780017FFF7FFF"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x00000000496E70757420746F6F206C6F6E6720666F7220617267756D656E7473"), + felt!("0x000000000000000000000000000000000000000000000000400080007FFE7FFF"), + felt!("0x00000000000000000000000000000000000000000000000048127FE47FFF8000"), + felt!("0x00000000000000000000000000000000000000000000000048127FD27FFF8000"), + felt!("0x00000000000000000000000000000000000000000000000048127FFB7FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x00000000000000000000000000000000000000000000000048127FFA7FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000482480017FF98000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x000000000000000000000000000000000000000000000000208B7FFF7FFF7FFE"), + felt!("0x000000000000000000000000000000000000000000000000040780017FFF7FFF"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x000000496E70757420746F6F2073686F727420666F7220617267756D656E7473"), + felt!("0x000000000000000000000000000000000000000000000000400080007FFE7FFF"), + felt!("0x00000000000000000000000000000000000000000000000048127FED7FFF8000"), + felt!("0x00000000000000000000000000000000000000000000000048127FDB7FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000480A7FFB7FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x00000000000000000000000000000000000000000000000048127FFA7FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000482480017FF98000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x000000000000000000000000000000000000000000000000208B7FFF7FFF7FFE"), + felt!("0x000000000000000000000000000000000000000000000000040780017FFF7FFF"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x000000496E70757420746F6F2073686F727420666F7220617267756D656E7473"), + felt!("0x000000000000000000000000000000000000000000000000400080007FFE7FFF"), + felt!("0x00000000000000000000000000000000000000000000000048127FFD7FFF8000"), + felt!("0x00000000000000000000000000000000000000000000000048127FEB7FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000480A7FFB7FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x00000000000000000000000000000000000000000000000048127FFA7FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000482480017FF98000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x000000000000000000000000000000000000000000000000208B7FFF7FFF7FFE"), + felt!("0x000000000000000000000000000000000000000000000000040780017FFF7FFF"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x000000000000000000000000000000000000000000004F7574206F6620676173"), + felt!("0x000000000000000000000000000000000000000000000000400080007FFE7FFF"), + felt!("0x000000000000000000000000000000000000000000000000482680017FF98000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x000000000000000000000000000000000000000000000000480A7FFA7FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000480A7FFB7FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x00000000000000000000000000000000000000000000000048127FFA7FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000482480017FF98000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x000000000000000000000000000000000000000000000000208B7FFF7FFF7FFE"), + felt!("0x0000000000000000000000000000000000000000000000000A0680017FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000007"), + felt!("0x000000000000000000000000000000000000000000000000482680017FFA8000"), + felt!("0x00000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFDB7A"), + felt!("0x000000000000000000000000000000000000000000000000400280007FF97FFF"), + felt!("0x000000000000000000000000000000000000000000000000010780017FFF7FFF"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000079"), + felt!("0x0000000000000000000000000000000000000000000000004825800180007FFA"), + felt!("0x0000000000000000000000000000000000000000000000000000000000002486"), + felt!("0x000000000000000000000000000000000000000000000000400280007FF97FFF"), + felt!("0x000000000000000000000000000000000000000000000000480A7FFC7FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000480A7FFD7FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000001104800180018000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000081"), + felt!("0x000000000000000000000000000000000000000000000000482680017FF98000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x000000000000000000000000000000000000000000000000020680017FFF7FFD"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000060"), + felt!("0x00000000000000000000000000000000000000000000000048307FFB80007FFC"), + felt!("0x0000000000000000000000000000000000000000000000004824800180007FFF"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000000"), + felt!("0x000000000000000000000000000000000000000000000000020680017FFF7FFF"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000004"), + felt!("0x000000000000000000000000000000000000000000000000010780017FFF7FFF"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000006"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000000"), + felt!("0x000000000000000000000000000000000000000000000000010780017FFF7FFF"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000004"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x00000000000000000000000000000000000000000000000048307FFE80007FFF"), + felt!("0x000000000000000000000000000000000000000000000000020680017FFF7FFF"), + felt!("0x000000000000000000000000000000000000000000000000000000000000003D"), + felt!("0x0000000000000000000000000000000000000000000000001104800180018000"), + felt!("0x000000000000000000000000000000000000000000000000000000000000017A"), + felt!("0x000000000000000000000000000000000000000000000000482480017FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000179"), + felt!("0x000000000000000000000000000000000000000000000000480080007FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000A0680017FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000009"), + felt!("0x0000000000000000000000000000000000000000000000004824800180007FE4"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000000"), + felt!("0x000000000000000000000000000000000000000000000000482480017FFF8000"), + felt!("0x0000000000000000000000000000000100000000000000000000000000000000"), + felt!("0x000000000000000000000000000000000000000000000000400080007FF37FFF"), + felt!("0x000000000000000000000000000000000000000000000000010780017FFF7FFF"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000020"), + felt!("0x0000000000000000000000000000000000000000000000004824800180007FE4"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000000"), + felt!("0x000000000000000000000000000000000000000000000000400080007FF47FFF"), + felt!("0x00000000000000000000000000000000000000000000000048127FFF7FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000480A7FFB7FFF8000"), + felt!("0x00000000000000000000000000000000000000000000000048127FF17FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000001104800180018000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000100"), + felt!("0x000000000000000000000000000000000000000000000000482480017FD28000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x000000000000000000000000000000000000000000000000020680017FFF7FFC"), + felt!("0x000000000000000000000000000000000000000000000000000000000000000C"), + felt!("0x000000000000000000000000000000000000000000000000040780017FFF7FFF"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x00000000000000000000000000000000000000000000000048127FFE7FFF8000"), + felt!("0x00000000000000000000000000000000000000000000000048127FF87FFF8000"), + felt!("0x00000000000000000000000000000000000000000000000048127FF87FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000000"), + felt!("0x00000000000000000000000000000000000000000000000048127FFB7FFF8000"), + felt!("0x00000000000000000000000000000000000000000000000048127FFA7FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000208B7FFF7FFF7FFE"), + felt!("0x00000000000000000000000000000000000000000000000048127FFF7FFF8000"), + felt!("0x00000000000000000000000000000000000000000000000048127FF97FFF8000"), + felt!("0x00000000000000000000000000000000000000000000000048127FF97FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x00000000000000000000000000000000000000000000000048127FF97FFF8000"), + felt!("0x00000000000000000000000000000000000000000000000048127FF97FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000208B7FFF7FFF7FFE"), + felt!("0x000000000000000000000000000000000000000000000000040780017FFF7FFF"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x000000000000000000000000000000000000000000004F7574206F6620676173"), + felt!("0x000000000000000000000000000000000000000000000000400080007FFE7FFF"), + felt!("0x000000000000000000000000000000000000000000000000482480017FF18000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x00000000000000000000000000000000000000000000000048127FDF7FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000480A7FFB7FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x00000000000000000000000000000000000000000000000048127FFA7FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000482480017FF98000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x000000000000000000000000000000000000000000000000208B7FFF7FFF7FFE"), + felt!("0x000000000000000000000000000000000000000000000000480A7FFB7FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000001104800180018000"), + felt!("0x000000000000000000000000000000000000000000000000000000000000007A"), + felt!("0x000000000000000000000000000000000000000000000000040780017FFF7FFF"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x00000000496E70757420746F6F206C6F6E6720666F7220617267756D656E7473"), + felt!("0x000000000000000000000000000000000000000000000000400080007FFE7FFF"), + felt!("0x00000000000000000000000000000000000000000000000048127FF47FFF8000"), + felt!("0x00000000000000000000000000000000000000000000000048127FE27FFF8000"), + felt!("0x00000000000000000000000000000000000000000000000048127FFB7FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x00000000000000000000000000000000000000000000000048127FFA7FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000482480017FF98000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x000000000000000000000000000000000000000000000000208B7FFF7FFF7FFE"), + felt!("0x000000000000000000000000000000000000000000000000040780017FFF7FFF"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x000000496E70757420746F6F2073686F727420666F7220617267756D656E7473"), + felt!("0x000000000000000000000000000000000000000000000000400080007FFE7FFF"), + felt!("0x00000000000000000000000000000000000000000000000048127FFD7FFF8000"), + felt!("0x00000000000000000000000000000000000000000000000048127FEB7FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000480A7FFB7FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x00000000000000000000000000000000000000000000000048127FFA7FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000482480017FF98000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x000000000000000000000000000000000000000000000000208B7FFF7FFF7FFE"), + felt!("0x000000000000000000000000000000000000000000000000040780017FFF7FFF"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x000000000000000000000000000000000000000000004F7574206F6620676173"), + felt!("0x000000000000000000000000000000000000000000000000400080007FFE7FFF"), + felt!("0x000000000000000000000000000000000000000000000000482680017FF98000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x000000000000000000000000000000000000000000000000480A7FFA7FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000480A7FFB7FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x00000000000000000000000000000000000000000000000048127FFA7FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000482480017FF98000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x000000000000000000000000000000000000000000000000208B7FFF7FFF7FFE"), + felt!("0x00000000000000000000000000000000000000000000000048297FFC80007FFD"), + felt!("0x000000000000000000000000000000000000000000000000020680017FFF7FFF"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000004"), + felt!("0x000000000000000000000000000000000000000000000000010780017FFF7FFF"), + felt!("0x000000000000000000000000000000000000000000000000000000000000000A"), + felt!("0x000000000000000000000000000000000000000000000000482680017FFC8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x000000000000000000000000000000000000000000000000480A7FFD7FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000000"), + felt!("0x000000000000000000000000000000000000000000000000480A7FFC7FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000010780017FFF7FFF"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000008"), + felt!("0x000000000000000000000000000000000000000000000000480A7FFC7FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000480A7FFD7FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000000"), + felt!("0x00000000000000000000000000000000000000000000000048127FFC7FFF8000"), + felt!("0x00000000000000000000000000000000000000000000000048127FFC7FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000020680017FFF7FFC"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000009"), + felt!("0x000000000000000000000000000000000000000000000000480080007FFD8000"), + felt!("0x00000000000000000000000000000000000000000000000048127FFD7FFF8000"), + felt!("0x00000000000000000000000000000000000000000000000048127FFD7FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000000"), + felt!("0x00000000000000000000000000000000000000000000000048127FFC7FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000208B7FFF7FFF7FFE"), + felt!("0x000000000000000000000000000000000000000000000000040780017FFF7FFF"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x00000000000000000000000000000000000000000000000048127FFD7FFF8000"), + felt!("0x00000000000000000000000000000000000000000000000048127FFD7FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000000"), + felt!("0x000000000000000000000000000000000000000000000000208B7FFF7FFF7FFE"), + felt!("0x000000000000000000000000000000000000000000000000480A7FFB7FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000480A7FFC7FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000001104800180018000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000026"), + felt!("0x000000000000000000000000000000000000000000000000020680017FFF7FFD"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000019"), + felt!("0x00000000000000000000000000000000000000000000000048127FFB7FFF8000"), + felt!("0x00000000000000000000000000000000000000000000000048127FFB7FFF8000"), + felt!("0x00000000000000000000000000000000000000000000000048287FFD7FFD8000"), + felt!("0x0000000000000000000000000000000000000000000000001104800180018000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000092"), + felt!("0x000000000000000000000000000000000000000000000000020680017FFF7FFD"), + felt!("0x000000000000000000000000000000000000000000000000000000000000000B"), + felt!("0x00000000000000000000000000000000000000000000000048127FFB7FFF8000"), + felt!("0x00000000000000000000000000000000000000000000000048127FFB7FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000000"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000000"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000000"), + felt!("0x000000000000000000000000000000000000000000000000208B7FFF7FFF7FFE"), + felt!("0x00000000000000000000000000000000000000000000000048127FFB7FFF8000"), + felt!("0x00000000000000000000000000000000000000000000000048127FFB7FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x00000000000000000000000000000000000000000000000048127FFB7FFF8000"), + felt!("0x00000000000000000000000000000000000000000000000048127FFB7FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000208B7FFF7FFF7FFE"), + felt!("0x000000000000000000000000000000000000000000000000040780017FFF7FFF"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000018"), + felt!("0x00000000000000000000000000000000000000000000000048127FE37FFF8000"), + felt!("0x00000000000000000000000000000000000000000000000048127FE37FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x00000000000000000000000000000000000000000000000048127FE37FFF8000"), + felt!("0x00000000000000000000000000000000000000000000000048127FE37FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000208B7FFF7FFF7FFE"), + felt!("0x000000000000000000000000000000000000000000000000480A7FFD7FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000208B7FFF7FFF7FFE"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000000"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x0206F38F7E4F15E87567361213C28F235CCCDAA1D7FD34C9DB1DFE9489C6A091"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x00000000000000000000000000000000000000000053746F7261676552656164"), + felt!("0x000000000000000000000000000000000000000000000000400280007FFD7FFF"), + felt!("0x000000000000000000000000000000000000000000000000400380017FFD7FFC"), + felt!("0x000000000000000000000000000000000000000000000000400280027FFD7FFD"), + felt!("0x000000000000000000000000000000000000000000000000400280037FFD7FFE"), + felt!("0x000000000000000000000000000000000000000000000000480280057FFD8000"), + felt!("0x000000000000000000000000000000000000000000000000020680017FFF7FFF"), + felt!("0x000000000000000000000000000000000000000000000000000000000000000C"), + felt!("0x000000000000000000000000000000000000000000000000480280047FFD8000"), + felt!("0x000000000000000000000000000000000000000000000000482680017FFD8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000007"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000000"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000000"), + felt!("0x000000000000000000000000000000000000000000000000480280067FFD8000"), + felt!("0x000000000000000000000000000000000000000000000000010780017FFF7FFF"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000009"), + felt!("0x000000000000000000000000000000000000000000000000480280047FFD8000"), + felt!("0x000000000000000000000000000000000000000000000000482680017FFD8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000008"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x000000000000000000000000000000000000000000000000480280067FFD8000"), + felt!("0x000000000000000000000000000000000000000000000000480280077FFD8000"), + felt!("0x0000000000000000000000000000000000000000000000001104800180018000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000089"), + felt!("0x000000000000000000000000000000000000000000000000020680017FFF7FFD"), + felt!("0x000000000000000000000000000000000000000000000000000000000000000A"), + felt!("0x00000000000000000000000000000000000000000000000048127FF67FFF8000"), + felt!("0x00000000000000000000000000000000000000000000000048127FF67FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000000"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000000"), + felt!("0x00000000000000000000000000000000000000000000000048127FFB7FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000208B7FFF7FFF7FFE"), + felt!("0x00000000000000000000000000000000000000000000000048127FF67FFF8000"), + felt!("0x00000000000000000000000000000000000000000000000048127FF67FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x00000000000000000000000000000000000000000000000048127FFB7FFF8000"), + felt!("0x00000000000000000000000000000000000000000000000048127FFB7FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000208B7FFF7FFF7FFE"), + felt!("0x000000000000000000000000000000000000000000000000400380007FFD7FFB"), + felt!("0x000000000000000000000000000000000000000000000000480A7FFC7FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000482680017FFD8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x000000000000000000000000000000000000000000000000208B7FFF7FFF7FFE"), + felt!("0x000000000000000000000000000000000000000000000000480A7FFA7FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000480A7FFB7FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000001104800180018000"), + felt!("0x0800000000000010FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC9"), + felt!("0x000000000000000000000000000000000000000000000000020680017FFF7FFD"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000019"), + felt!("0x00000000000000000000000000000000000000000000000048127FFB7FFF8000"), + felt!("0x00000000000000000000000000000000000000000000000048127FFB7FFF8000"), + felt!("0x00000000000000000000000000000000000000000000000048287FFD7FFD8000"), + felt!("0x0000000000000000000000000000000000000000000000001104800180018000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000034"), + felt!("0x000000000000000000000000000000000000000000000000020680017FFF7FFD"), + felt!("0x000000000000000000000000000000000000000000000000000000000000000B"), + felt!("0x00000000000000000000000000000000000000000000000048127FFB7FFF8000"), + felt!("0x00000000000000000000000000000000000000000000000048127FFB7FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000000"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000000"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000000"), + felt!("0x000000000000000000000000000000000000000000000000208B7FFF7FFF7FFE"), + felt!("0x00000000000000000000000000000000000000000000000048127FFB7FFF8000"), + felt!("0x00000000000000000000000000000000000000000000000048127FFB7FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x00000000000000000000000000000000000000000000000048127FFB7FFF8000"), + felt!("0x00000000000000000000000000000000000000000000000048127FFB7FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000208B7FFF7FFF7FFE"), + felt!("0x000000000000000000000000000000000000000000000000040780017FFF7FFF"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000018"), + felt!("0x00000000000000000000000000000000000000000000000048127FE37FFF8000"), + felt!("0x00000000000000000000000000000000000000000000000048127FE37FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x00000000000000000000000000000000000000000000000048127FE37FFF8000"), + felt!("0x00000000000000000000000000000000000000000000000048127FE37FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000208B7FFF7FFF7FFE"), + felt!("0x000000000000000000000000000000000000000000000000480A7FFB7FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000480A7FFC7FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000480A7FFD7FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000001104800180018000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000014"), + felt!("0x000000000000000000000000000000000000000000000000020680017FFF7FFD"), + felt!("0x000000000000000000000000000000000000000000000000000000000000000B"), + felt!("0x00000000000000000000000000000000000000000000000048127FFB7FFF8000"), + felt!("0x00000000000000000000000000000000000000000000000048127FFB7FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000000"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000000"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000000"), + felt!("0x000000000000000000000000000000000000000000000000208B7FFF7FFF7FFE"), + felt!("0x00000000000000000000000000000000000000000000000048127FFB7FFF8000"), + felt!("0x00000000000000000000000000000000000000000000000048127FFB7FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x00000000000000000000000000000000000000000000000048127FFB7FFF8000"), + felt!("0x00000000000000000000000000000000000000000000000048127FFB7FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000208B7FFF7FFF7FFE"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000000"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x0206F38F7E4F15E87567361213C28F235CCCDAA1D7FD34C9DB1DFE9489C6A091"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x000000000000000000000000000000000000000053746F726167655772697465"), + felt!("0x000000000000000000000000000000000000000000000000400280007FFC7FFF"), + felt!("0x000000000000000000000000000000000000000000000000400380017FFC7FFB"), + felt!("0x000000000000000000000000000000000000000000000000400280027FFC7FFD"), + felt!("0x000000000000000000000000000000000000000000000000400280037FFC7FFE"), + felt!("0x000000000000000000000000000000000000000000000000400380047FFC7FFD"), + felt!("0x000000000000000000000000000000000000000000000000480280067FFC8000"), + felt!("0x000000000000000000000000000000000000000000000000020680017FFF7FFF"), + felt!("0x000000000000000000000000000000000000000000000000000000000000000D"), + felt!("0x000000000000000000000000000000000000000000000000480280057FFC8000"), + felt!("0x000000000000000000000000000000000000000000000000482680017FFC8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000007"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000000"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000000"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000000"), + felt!("0x000000000000000000000000000000000000000000000000010780017FFF7FFF"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000009"), + felt!("0x000000000000000000000000000000000000000000000000480280057FFC8000"), + felt!("0x000000000000000000000000000000000000000000000000482680017FFC8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000009"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x000000000000000000000000000000000000000000000000480280077FFC8000"), + felt!("0x000000000000000000000000000000000000000000000000480280087FFC8000"), + felt!("0x0000000000000000000000000000000000000000000000001104800180018000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000021"), + felt!("0x000000000000000000000000000000000000000000000000020680017FFF7FFD"), + felt!("0x000000000000000000000000000000000000000000000000000000000000000B"), + felt!("0x00000000000000000000000000000000000000000000000048127FF67FFF8000"), + felt!("0x00000000000000000000000000000000000000000000000048127FF67FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000000"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000000"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000000"), + felt!("0x000000000000000000000000000000000000000000000000208B7FFF7FFF7FFE"), + felt!("0x00000000000000000000000000000000000000000000000048127FF67FFF8000"), + felt!("0x00000000000000000000000000000000000000000000000048127FF67FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x00000000000000000000000000000000000000000000000048127FFB7FFF8000"), + felt!("0x00000000000000000000000000000000000000000000000048127FFB7FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000208B7FFF7FFF7FFE"), + felt!("0x000000000000000000000000000000000000000000000000020780017FFF7FFB"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000008"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000000"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000000"), + felt!("0x000000000000000000000000000000000000000000000000480A7FFD7FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000208B7FFF7FFF7FFE"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x000000000000000000000000000000000000000000000000480A7FFC7FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000480A7FFD7FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000208B7FFF7FFF7FFE"), + felt!("0x000000000000000000000000000000000000000000000000020780017FFF7FFB"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000009"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000000"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000000"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000000"), + felt!("0x000000000000000000000000000000000000000000000000208B7FFF7FFF7FFE"), + felt!("0x000000000000000000000000000000000000000000000000480680017FFF8000"), + felt!("0x0000000000000000000000000000000000000000000000000000000000000001"), + felt!("0x000000000000000000000000000000000000000000000000480A7FFC7FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000480A7FFD7FFF8000"), + felt!("0x000000000000000000000000000000000000000000000000208B7FFF7FFF7FFE"), + ]; + + // Expected compiler version + let compiler_version = "1.1.0".to_string(); + + // Expected prime + let prime: BigUintAsHex = serde_json::from_str( + "\"0x800000000000011000000000000000000000000000000000000000000000001\"", + ) + .unwrap(); + + // Expected hints + let hints = json!([ + [ + 0, + [ + { + "TestLessThanOrEqual": { + "dst": { + "offset": 0, + "register": "AP" + }, + "lhs": { + "Immediate": "0x5816" + }, + "rhs": { + "Deref": { + "offset": -6, + "register": "FP" + } + } + } + } + ] + ], + [ + 41, + [ + { + "TestLessThanOrEqual": { + "dst": { + "offset": 0, + "register": "AP" + }, + "lhs": { + "Immediate": "0x0" + }, + "rhs": { + "Deref": { + "offset": -27, + "register": "AP" + } + } + } + } + ] + ], + [ + 62, + [ + { + "AllocSegment": { + "dst": { + "offset": 0, + "register": "AP" + } + } + } + ] + ], + [ + 80, + [ + { + "AllocSegment": { + "dst": { + "offset": 0, + "register": "AP" + } + } + } + ] + ], + [ + 98, + [ + { + "AllocSegment": { + "dst": { + "offset": 0, + "register": "AP" + } + } + } + ] + ], + [ + 112, + [ + { + "AllocSegment": { + "dst": { + "offset": 0, + "register": "AP" + } + } + } + ] + ], + [ + 126, + [ + { + "AllocSegment": { + "dst": { + "offset": 0, + "register": "AP" + } + } + } + ] + ], + [ + 141, + [ + { + "TestLessThanOrEqual": { + "dst": { + "offset": 0, + "register": "AP" + }, + "lhs": { + "Immediate": "0x1ab8" + }, + "rhs": { + "Deref": { + "offset": -6, + "register": "FP" + } + } + } + } + ] + ], + [ + 176, + [ + { + "TestLessThanOrEqual": { + "dst": { + "offset": 0, + "register": "AP" + }, + "lhs": { + "Immediate": "0x0" + }, + "rhs": { + "Deref": { + "offset": -11, + "register": "AP" + } + } + } + } + ] + ], + [ + 196, + [ + { + "AllocSegment": { + "dst": { + "offset": 0, + "register": "AP" + } + } + } + ] + ], + [ + 219, + [ + { + "AllocSegment": { + "dst": { + "offset": 0, + "register": "AP" + } + } + } + ] + ], + [ + 237, + [ + { + "AllocSegment": { + "dst": { + "offset": 0, + "register": "AP" + } + } + } + ] + ], + [ + 251, + [ + { + "AllocSegment": { + "dst": { + "offset": 0, + "register": "AP" + } + } + } + ] + ], + [ + 266, + [ + { + "TestLessThanOrEqual": { + "dst": { + "offset": 0, + "register": "AP" + }, + "lhs": { + "Immediate": "0x6054" + }, + "rhs": { + "Deref": { + "offset": -6, + "register": "FP" + } + } + } + } + ] + ], + [ + 313, + [ + { + "TestLessThanOrEqual": { + "dst": { + "offset": 0, + "register": "AP" + }, + "lhs": { + "Immediate": "0x0" + }, + "rhs": { + "Deref": { + "offset": -43, + "register": "AP" + } + } + } + } + ] + ], + [ + 335, + [ + { + "AllocSegment": { + "dst": { + "offset": 0, + "register": "AP" + } + } + } + ] + ], + [ + 353, + [ + { + "AllocSegment": { + "dst": { + "offset": 0, + "register": "AP" + } + } + } + ] + ], + [ + 371, + [ + { + "AllocSegment": { + "dst": { + "offset": 0, + "register": "AP" + } + } + } + ] + ], + [ + 385, + [ + { + "AllocSegment": { + "dst": { + "offset": 0, + "register": "AP" + } + } + } + ] + ], + [ + 399, + [ + { + "AllocSegment": { + "dst": { + "offset": 0, + "register": "AP" + } + } + } + ] + ], + [ + 413, + [ + { + "AllocSegment": { + "dst": { + "offset": 0, + "register": "AP" + } + } + } + ] + ], + [ + 428, + [ + { + "TestLessThanOrEqual": { + "dst": { + "offset": 0, + "register": "AP" + }, + "lhs": { + "Immediate": "0x2486" + }, + "rhs": { + "Deref": { + "offset": -6, + "register": "FP" + } + } + } + } + ] + ], + [ + 469, + [ + { + "TestLessThanOrEqual": { + "dst": { + "offset": 0, + "register": "AP" + }, + "lhs": { + "Immediate": "0x0" + }, + "rhs": { + "Deref": { + "offset": -27, + "register": "AP" + } + } + } + } + ] + ], + [ + 490, + [ + { + "AllocSegment": { + "dst": { + "offset": 0, + "register": "AP" + } + } + } + ] + ], + [ + 508, + [ + { + "AllocSegment": { + "dst": { + "offset": 0, + "register": "AP" + } + } + } + ] + ], + [ + 526, + [ + { + "AllocSegment": { + "dst": { + "offset": 0, + "register": "AP" + } + } + } + ] + ], + [ + 540, + [ + { + "AllocSegment": { + "dst": { + "offset": 0, + "register": "AP" + } + } + } + ] + ], + [ + 554, + [ + { + "AllocSegment": { + "dst": { + "offset": 0, + "register": "AP" + } + } + } + ] + ], + [ + 658, + [ + { + "SystemCall": { + "system": { + "Deref": { + "offset": -3, + "register": "FP" + } + } + } + } + ] + ], + [ + 774, + [ + { + "SystemCall": { + "system": { + "Deref": { + "offset": -4, + "register": "FP" + } + } + } + } + ] + ] + ]); + + CasmContractClass { + entry_points_by_type, + compiler_version, + prime, + hints, + bytecode, + bytecode_segment_lengths: None, + } + } +} From 07fc537f6e9926ef2030c64efdf5bd69a6542287 Mon Sep 17 00:00:00 2001 From: t00ts Date: Thu, 7 Nov 2024 09:59:14 +0100 Subject: [PATCH 217/282] feat(rpc): add `starknet_getTransactionReceipt` --- crates/rpc/src/lib.rs | 3 --- crates/rpc/src/v08.rs | 1 + 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/rpc/src/lib.rs b/crates/rpc/src/lib.rs index 506867823e..422daf5348 100644 --- a/crates/rpc/src/lib.rs +++ b/crates/rpc/src/lib.rs @@ -903,9 +903,6 @@ mod tests { // get_transaction_status is now part of the official spec, so we are phasing it out. #[case::root_pathfinder("/", "pathfinder_rpc_api.json", &["pathfinder_version", "pathfinder_getTransactionStatus"])] - #[case::v0_8_api ("/rpc/v0_8", "v08/starknet_api_openrpc.json", &[ - "starknet_getTransactionReceipt", - ])] #[case::v0_8_trace("/rpc/v0_8", "v08/starknet_trace_api_openrpc.json", &[])] #[case::v0_8_write("/rpc/v0_8", "v08/starknet_write_api.json", &[])] diff --git a/crates/rpc/src/v08.rs b/crates/rpc/src/v08.rs index 44c0362338..308150301e 100644 --- a/crates/rpc/src/v08.rs +++ b/crates/rpc/src/v08.rs @@ -30,6 +30,7 @@ pub fn register_routes() -> RpcRouterBuilder { .register("starknet_getStorageProof", crate::method::get_storage_proof) .register("starknet_getTransactionByBlockIdAndIndex", crate::method::get_transaction_by_block_id_and_index) .register("starknet_getTransactionByHash", crate::method::get_transaction_by_hash) + .register("starknet_getTransactionReceipt", crate::method::get_transaction_receipt) .register("starknet_getTransactionStatus", crate::method::get_transaction_status) .register("starknet_getBlockWithReceipts", crate::method::get_block_with_receipts) .register("starknet_simulateTransactions", crate::method::simulate_transactions) From a75ff2e2027f4e8285c0c2fe508fdab8455feb65 Mon Sep 17 00:00:00 2001 From: Krzysztof Lis Date: Tue, 29 Oct 2024 17:56:56 +0100 Subject: [PATCH 218/282] fix(config): max_concurrent_streams should be 1 1. This means that at most 1 stream per peer (ie. per connection) for a given sync protocol is allowed. This prevents a p2p-serving pathfinder from being choked with too many request streams from the same peer, which de facto is also a kind of rate limiting mechanism. 2. Moreover: enable listening on more than 1 address (and port), which allows for easier testing with more than 1 peer without the need to use a bootstrap node. --- crates/pathfinder/src/bin/pathfinder/config.rs | 17 +++++++++-------- crates/pathfinder/src/p2p_network.rs | 12 +++++++----- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/crates/pathfinder/src/bin/pathfinder/config.rs b/crates/pathfinder/src/bin/pathfinder/config.rs index 3ce06d8b77..a0fab2ded1 100644 --- a/crates/pathfinder/src/bin/pathfinder/config.rs +++ b/crates/pathfinder/src/bin/pathfinder/config.rs @@ -418,13 +418,14 @@ struct P2PCli { identity_config_file: Option, #[arg( long = "p2p.listen-on", - long_help = "The multiaddress on which to listen for incoming p2p connections. If not \ - provided, default route on randomly assigned port will be used.", - value_name = "MULTIADDRESS", + long_help = "The list of multiaddresses on which to listen for incoming p2p connections. \ + If not provided, default route on randomly assigned port will be used.", + value_name = "MULTIADDRESS_LIST", + value_delimiter = ',', default_value = "/ip4/0.0.0.0/tcp/0", env = "PATHFINDER_P2P_LISTEN_ON" )] - listen_on: Multiaddr, + listen_on: Vec, #[arg( long = "p2p.bootstrap-addresses", long_help = r#"Comma separated list of multiaddresses to use as bootstrap nodes. Each multiaddress must contain a peer ID. @@ -520,9 +521,9 @@ Example: #[arg( long = "p2p.experimental.max-concurrent-streams", long_help = "Maximum allowed number of concurrent streams per each \ - request/response-stream protocol.", + request/response-stream protocol per connection.", value_name = "LIMIT", - default_value = "100", + default_value = "1", env = "PATHFINDER_P2P_EXPERIMENTAL_MAX_CONCURRENT_STREAMS" )] max_concurrent_streams: usize, @@ -741,7 +742,7 @@ pub enum NetworkConfig { pub struct P2PConfig { pub proxy: bool, pub identity_config_file: Option, - pub listen_on: Multiaddr, + pub listen_on: Vec, pub bootstrap_addresses: Vec, pub predefined_peers: Vec, pub max_inbound_direct_connections: usize, @@ -891,7 +892,7 @@ impl P2PConfig { max_outbound_connections: args.max_outbound_connections.try_into().unwrap(), proxy: args.proxy, identity_config_file: args.identity_config_file, - listen_on: args.listen_on, + listen_on: parse_multiaddr_vec("p2p.listen-on", args.listen_on), bootstrap_addresses: parse_multiaddr_vec( "p2p.bootstrap-addresses", args.bootstrap_addresses, diff --git a/crates/pathfinder/src/p2p_network.rs b/crates/pathfinder/src/p2p_network.rs index db88ca8a9a..436ff0719e 100644 --- a/crates/pathfinder/src/p2p_network.rs +++ b/crates/pathfinder/src/p2p_network.rs @@ -21,7 +21,7 @@ pub struct P2PContext { pub storage: Storage, pub proxy: bool, pub keypair: Keypair, - pub listen_on: Multiaddr, + pub listen_on: Vec, pub bootstrap_addresses: Vec, pub predefined_peers: Vec, } @@ -49,10 +49,12 @@ pub async fn start(context: P2PContext) -> anyhow::Result { tokio::task::spawn(p2p_main_loop.run().instrument(span)) }; - p2p_client - .start_listening(listen_on) - .await - .context("Starting P2P listener")?; + for addr in listen_on { + p2p_client + .start_listening(addr.clone()) + .await + .with_context(|| format!("Starting P2P listener: {addr}"))?; + } let ensure_peer_id_in_multiaddr = |addr: &Multiaddr, msg: &'static str| { addr.iter() From 0436091c2a3bb207f3b5baee615a57f1f560cf94 Mon Sep 17 00:00:00 2001 From: Vaclav Barta Date: Thu, 7 Nov 2024 08:22:26 +0100 Subject: [PATCH 219/282] updated block hash algo for Starknet version >= 0.13.4 --- crates/common/src/lib.rs | 4 + .../examples/compute_pre0132_hashes.rs | 2 + crates/pathfinder/src/state/block_hash.rs | 97 +++++++++++++++---- crates/pathfinder/src/sync/headers.rs | 2 + 4 files changed, 85 insertions(+), 20 deletions(-) diff --git a/crates/common/src/lib.rs b/crates/common/src/lib.rs index c0fab899ba..ed226143c1 100644 --- a/crates/common/src/lib.rs +++ b/crates/common/src/lib.rs @@ -465,6 +465,10 @@ impl StarknetVersion { } pub const V_0_13_2: Self = Self::new(0, 13, 2, 0); + + // TODO: version at which block hash definition changes taken from + // Starkware implementation but might yet change + pub const V_0_13_4: Self = Self::new(0, 13, 4, 0); } impl FromStr for StarknetVersion { diff --git a/crates/pathfinder/examples/compute_pre0132_hashes.rs b/crates/pathfinder/examples/compute_pre0132_hashes.rs index a5625f6d9a..b1c7fbd227 100644 --- a/crates/pathfinder/examples/compute_pre0132_hashes.rs +++ b/crates/pathfinder/examples/compute_pre0132_hashes.rs @@ -163,6 +163,8 @@ fn get_header_data(header: &BlockHeader) -> BlockHeaderData { strk_l1_gas_price: header.strk_l1_gas_price, eth_l1_data_gas_price: header.eth_l1_data_gas_price, strk_l1_data_gas_price: header.strk_l1_data_gas_price, + eth_l2_gas_price: header.eth_l2_gas_price, + strk_l2_gas_price: header.strk_l2_gas_price, receipt_commitment: header.receipt_commitment, l1_da_mode: header.l1_da_mode, } diff --git a/crates/pathfinder/src/state/block_hash.rs b/crates/pathfinder/src/state/block_hash.rs index 2027b840d6..a60225df55 100644 --- a/crates/pathfinder/src/state/block_hash.rs +++ b/crates/pathfinder/src/state/block_hash.rs @@ -133,6 +133,8 @@ pub struct BlockHeaderData { pub strk_l1_gas_price: GasPrice, pub eth_l1_data_gas_price: GasPrice, pub strk_l1_data_gas_price: GasPrice, + pub eth_l2_gas_price: GasPrice, + pub strk_l2_gas_price: GasPrice, pub receipt_commitment: ReceiptCommitment, pub l1_da_mode: L1DataAvailabilityMode, } @@ -160,6 +162,8 @@ impl BlockHeaderData { strk_l1_gas_price: header.strk_l1_gas_price, eth_l1_data_gas_price: header.eth_l1_data_gas_price, strk_l1_data_gas_price: header.strk_l1_data_gas_price, + eth_l2_gas_price: header.eth_l2_gas_price, + strk_l2_gas_price: header.strk_l2_gas_price, receipt_commitment: header.receipt_commitment, l1_da_mode: header.l1_da_mode, state_diff_commitment: header.state_diff_commitment, @@ -202,6 +206,8 @@ impl BlockHeaderData { strk_l1_gas_price: block.l1_gas_price.price_in_fri, eth_l1_data_gas_price: block.l1_data_gas_price.price_in_wei, strk_l1_data_gas_price: block.l1_data_gas_price.price_in_fri, + eth_l2_gas_price: GasPrice(0), // TODO: Fix when we get l2_gas_price in the gateway + strk_l2_gas_price: GasPrice(0), // TODO: Fix when we get l2_gas_price in the gateway receipt_commitment: block.receipt_commitment.unwrap_or_default(), l1_da_mode: block.l1_da_mode.into(), }) @@ -399,25 +405,7 @@ fn compute_final_hash_pre_0_13_2(header: &BlockHeaderData) -> BlockHash { BlockHash(chain.finalize()) } -pub fn compute_final_hash(header: &BlockHeaderData) -> Result { - // Concatenate the transaction count, event count, state diff length, and L1 - // data availability mode into a single felt. - let mut concat_counts = [0u8; 32]; - let mut writer = concat_counts.as_mut_slice(); - writer - .write_all(&header.transaction_count.to_be_bytes()) - .unwrap(); - writer.write_all(&header.event_count.to_be_bytes()).unwrap(); - writer - .write_all(&header.state_diff_length.to_be_bytes()) - .unwrap(); - writer - .write_all(&[match header.l1_da_mode { - L1DataAvailabilityMode::Calldata => 0, - L1DataAvailabilityMode::Blob => 0b10000000, - }]) - .unwrap(); - let concat_counts = MontFelt::from_be_bytes(concat_counts); +fn compute_final_hash_v0(header: &BlockHeaderData) -> Result { // Hash the block header. let mut hasher = PoseidonHasher::new(); hasher.write(felt_bytes!(b"STARKNET_BLOCK_HASH0").into()); @@ -425,7 +413,7 @@ pub fn compute_final_hash(header: &BlockHeaderData) -> Result { hasher.write(header.state_commitment.0.into()); hasher.write(header.sequencer_address.0.into()); hasher.write(header.timestamp.get().into()); - hasher.write(concat_counts); + hasher.write(concatenate_counts(header)); hasher.write(header.state_diff_commitment.0.into()); hasher.write(header.transaction_commitment.0.into()); hasher.write(header.event_commitment.0.into()); @@ -444,6 +432,40 @@ pub fn compute_final_hash(header: &BlockHeaderData) -> Result { Ok(BlockHash(hasher.finish().into())) } +// Bumps the initial STARKNET_BLOCK_HASH0 to STARKNET_BLOCK_HASH1, +// replaces gas price elements with gas_prices_hash. +fn compute_final_hash_v1(header: &BlockHeaderData) -> Result { + // Hash the block header. + let mut hasher = PoseidonHasher::new(); + hasher.write(felt_bytes!(b"STARKNET_BLOCK_HASH1").into()); + hasher.write(header.number.get().into()); + hasher.write(header.state_commitment.0.into()); + hasher.write(header.sequencer_address.0.into()); + hasher.write(header.timestamp.get().into()); + hasher.write(concatenate_counts(header)); + hasher.write(header.state_diff_commitment.0.into()); + hasher.write(header.transaction_commitment.0.into()); + hasher.write(header.event_commitment.0.into()); + hasher.write(header.receipt_commitment.0.into()); + hasher.write(gas_prices_to_hash(header)); + hasher.write( + Felt::from_be_slice(header.starknet_version_str.as_bytes()) + .expect("Starknet version should fit into a felt") + .into(), + ); + hasher.write(MontFelt::ZERO); + hasher.write(header.parent_hash.0.into()); + Ok(BlockHash(hasher.finish().into())) +} + +pub fn compute_final_hash(header: &BlockHeaderData) -> Result { + if header.starknet_version < StarknetVersion::V_0_13_4 { + compute_final_hash_v0(header) + } else { + compute_final_hash_v1(header) + } +} + /// Calculate transaction commitment hash value. /// /// The transaction commitment is the root of the Patricia Merkle tree with @@ -529,6 +551,39 @@ pub fn calculate_receipt_commitment(receipts: &[Receipt]) -> Result(hashes).map(ReceiptCommitment) } +// Concatenate the transaction count, event count, state diff length, +// and L1 data availability mode into a single felt. +fn concatenate_counts(header: &BlockHeaderData) -> MontFelt { + let mut concat_counts = [0u8; 32]; + let mut writer = concat_counts.as_mut_slice(); + writer + .write_all(&header.transaction_count.to_be_bytes()) + .unwrap(); + writer.write_all(&header.event_count.to_be_bytes()).unwrap(); + writer + .write_all(&header.state_diff_length.to_be_bytes()) + .unwrap(); + writer + .write_all(&[match header.l1_da_mode { + L1DataAvailabilityMode::Calldata => 0, + L1DataAvailabilityMode::Blob => 0b10000000, + }]) + .unwrap(); + MontFelt::from_be_bytes(concat_counts) +} + +fn gas_prices_to_hash(header: &BlockHeaderData) -> MontFelt { + let mut hasher = PoseidonHasher::new(); + hasher.write(felt_bytes!(b"STARKNET_GAS_PRICES0").into()); + hasher.write(header.eth_l1_gas_price.0.into()); + hasher.write(header.strk_l1_gas_price.0.into()); + hasher.write(header.eth_l1_data_gas_price.0.into()); + hasher.write(header.strk_l1_data_gas_price.0.into()); + hasher.write(header.eth_l2_gas_price.0.into()); + hasher.write(header.strk_l2_gas_price.0.into()); + hasher.finish() +} + fn calculate_commitment_root(hashes: Vec) -> Result { let mut tree: TransactionOrEventTree = Default::default(); @@ -1053,6 +1108,8 @@ mod tests { eth_l1_gas_price: GasPrice(7), strk_l1_data_gas_price: GasPrice(10), eth_l1_data_gas_price: GasPrice(9), + strk_l2_gas_price: GasPrice(0), // not used for StarknetVersion::V_0_13_2 + eth_l2_gas_price: GasPrice(0), // ditto starknet_version: StarknetVersion::V_0_13_2, starknet_version_str: "10".to_string(), parent_hash: BlockHash(11u64.into()), diff --git a/crates/pathfinder/src/sync/headers.rs b/crates/pathfinder/src/sync/headers.rs index 4fe0255b8d..3c4afa1ba8 100644 --- a/crates/pathfinder/src/sync/headers.rs +++ b/crates/pathfinder/src/sync/headers.rs @@ -286,6 +286,8 @@ impl VerifyHashAndSignature { strk_l1_gas_price: header.strk_l1_gas_price, eth_l1_data_gas_price: header.eth_l1_data_gas_price, strk_l1_data_gas_price: header.strk_l1_data_gas_price, + eth_l2_gas_price: header.eth_l2_gas_price, + strk_l2_gas_price: header.strk_l2_gas_price, receipt_commitment: header.receipt_commitment, l1_da_mode: header.l1_da_mode, }) { From 6c767a7dbc6ad437ba83ac74ec9b7c023c449ddd Mon Sep 17 00:00:00 2001 From: Vaclav Barta Date: Thu, 7 Nov 2024 08:35:39 +0100 Subject: [PATCH 220/282] updated transaction hash algo for Starknet version >= 0.13.4 --- crates/pathfinder/src/state/block_hash.rs | 32 ++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/crates/pathfinder/src/state/block_hash.rs b/crates/pathfinder/src/state/block_hash.rs index a60225df55..6ba024c9c6 100644 --- a/crates/pathfinder/src/state/block_hash.rs +++ b/crates/pathfinder/src/state/block_hash.rs @@ -485,6 +485,8 @@ pub fn calculate_transaction_commitment( calculate_transaction_hash_with_signature_pre_0_11_1(tx) } else if version < StarknetVersion::V_0_13_2 { calculate_transaction_hash_with_signature_pre_0_13_2(tx) + } else if version < StarknetVersion::V_0_13_4 { + calculate_transaction_hash_with_signature_pre_0_13_4(tx) } else { calculate_transaction_hash_with_signature(tx) } @@ -665,7 +667,7 @@ fn calculate_transaction_hash_with_signature_pre_0_13_2(tx: &Transaction) -> Fel /// Compute the combined hash of the transaction hash and the signature. /// /// [Reference code from StarkWare](https://github.com/starkware-libs/starknet-api/blob/5565e5282f5fead364a41e49c173940fd83dee00/src/block_hash/block_hash_calculator.rs#L95-L98). -fn calculate_transaction_hash_with_signature(tx: &Transaction) -> Felt { +fn calculate_transaction_hash_with_signature_pre_0_13_4(tx: &Transaction) -> Felt { let signature = match &tx.variant { TransactionVariant::InvokeV0(tx) => tx.signature.as_slice(), TransactionVariant::DeclareV0(tx) => tx.signature.as_slice(), @@ -695,6 +697,30 @@ fn calculate_transaction_hash_with_signature(tx: &Transaction) -> Felt { hasher.finish().into() } +fn calculate_transaction_hash_with_signature(tx: &Transaction) -> Felt { + let signature = match &tx.variant { + TransactionVariant::InvokeV0(tx) => tx.signature.as_slice(), + TransactionVariant::DeclareV0(tx) => tx.signature.as_slice(), + TransactionVariant::DeclareV1(tx) => tx.signature.as_slice(), + TransactionVariant::DeclareV2(tx) => tx.signature.as_slice(), + TransactionVariant::DeclareV3(tx) => tx.signature.as_slice(), + TransactionVariant::DeployAccountV1(tx) => tx.signature.as_slice(), + TransactionVariant::DeployAccountV3(tx) => tx.signature.as_slice(), + TransactionVariant::InvokeV1(tx) => tx.signature.as_slice(), + TransactionVariant::InvokeV3(tx) => tx.signature.as_slice(), + TransactionVariant::DeployV0(_) + | TransactionVariant::DeployV1(_) + | TransactionVariant::L1Handler(_) => &[], + }; + + let mut hasher = PoseidonHasher::new(); + hasher.write(tx.hash.0.into()); + for elem in signature { + hasher.write(elem.0.into()); + } + hasher.finish().into() +} + fn calculate_signature_hash(signature: &[TransactionSignatureElem]) -> Felt { let mut hash = HashChain::default(); for s in signature { @@ -969,7 +995,7 @@ mod tests { }; let expected = felt!("0x2f0d8840bcf3bc629598d8a6cc80cb7c0d9e52d93dab244bbf9cd0dca0ad082"); assert_eq!( - calculate_transaction_hash_with_signature(&transaction), + calculate_transaction_hash_with_signature_pre_0_13_4(&transaction), expected ); @@ -979,7 +1005,7 @@ mod tests { }; let expected = felt!("0x00a93bf5e58b9378d093aa86ddc2f61a3295a1d1e665bd0ef3384dd07b30e033"); assert_eq!( - calculate_transaction_hash_with_signature(&transaction), + calculate_transaction_hash_with_signature_pre_0_13_4(&transaction), expected ); } From 1caba0a2110d50928c0041326e87f5105d24e493 Mon Sep 17 00:00:00 2001 From: Vaclav Barta Date: Thu, 7 Nov 2024 10:05:26 +0100 Subject: [PATCH 221/282] added tests for new block & transaction hash --- crates/pathfinder/src/state/block_hash.rs | 81 ++++++++++++++++++++++- 1 file changed, 80 insertions(+), 1 deletion(-) diff --git a/crates/pathfinder/src/state/block_hash.rs b/crates/pathfinder/src/state/block_hash.rs index 6ba024c9c6..15a6a2c47a 100644 --- a/crates/pathfinder/src/state/block_hash.rs +++ b/crates/pathfinder/src/state/block_hash.rs @@ -880,6 +880,56 @@ mod tests { assert_eq!(expected_final_hash, calculated_final_hash); } + /// Source: + /// https://github.com/starkware-libs/sequencer/blob/main/crates/starknet_api/src/block_hash/block_hash_calculator_test.rs#L74-121 + #[rstest::rstest] + fn test_final_transaction_hash_variants( + #[values(StarknetVersion::V_0_13_2, StarknetVersion::V_0_13_4)] + starknet_version: StarknetVersion, + ) { + let block_header = BlockHeaderData { + hash: Default::default(), + number: BlockNumber::new_or_panic(1), + state_commitment: StateCommitment(2u64.into()), + sequencer_address: SequencerAddress(3u64.into()), + timestamp: BlockTimestamp::new_or_panic(4), + l1_da_mode: L1DataAvailabilityMode::Blob, + strk_l1_gas_price: GasPrice(6), + eth_l1_gas_price: GasPrice(7), + strk_l1_data_gas_price: GasPrice(10), + eth_l1_data_gas_price: GasPrice(9), + strk_l2_gas_price: GasPrice(11), + eth_l2_gas_price: GasPrice(12), + starknet_version, + starknet_version_str: format!("{}", starknet_version), + parent_hash: BlockHash(11u64.into()), + transaction_commitment: TransactionCommitment(felt!( + "0x72f432efa51e2a34f68404ac5e77514301e26eb53ec89badd8173f4e8561b95" + )), + transaction_count: 1, + event_commitment: EventCommitment(Felt::ZERO), + event_count: 0, + state_diff_commitment: StateDiffCommitment(felt!( + "0x281f5966e49ad7dad9323826d53d1d27c0c4e6ebe5525e2e2fbca549bfa0a67" + )), + state_diff_length: 10, + receipt_commitment: ReceiptCommitment(felt!( + "0x8e7dfb2772c2ac26e712fb97404355d66db0ba9555f0f64f30d61a56df9c76" + )), + }; + + let expected_hash = BlockHash(match starknet_version { + StarknetVersion::V_0_13_2 => { + felt!("0xe248d6ce583f8fa48d1d401d4beb9ceced3733e38d8eacb0d8d3669a7d901c") + } + _ => { + felt!("0x3d6174623c812f5dc03fa3faa07c42c06fd90ad425629ee5f39e149df65c3ca") + } + }); + + assert_eq!(compute_final_hash(&block_header).unwrap(), expected_hash); + } + #[test] fn test_block_hash_without_sequencer_address() { // This tests with a post-0.7, pre-0.8.0 block where zero is used as the @@ -1010,6 +1060,35 @@ mod tests { ); } + #[test] + fn test_transaction_hash_with_signature_0_13_4() { + let transaction = Transaction { + hash: TransactionHash(Felt::ONE), + variant: TransactionVariant::InvokeV3(InvokeTransactionV3 { + signature: vec![ + TransactionSignatureElem(Felt::from_u64(2)), + TransactionSignatureElem(Felt::from_u64(3)), + ], + ..Default::default() + }), + }; + let expected = felt!("0x2f0d8840bcf3bc629598d8a6cc80cb7c0d9e52d93dab244bbf9cd0dca0ad082"); + assert_eq!( + calculate_transaction_hash_with_signature(&transaction), + expected + ); + + let transaction = Transaction { + hash: TransactionHash(Felt::ONE), + variant: TransactionVariant::L1Handler(Default::default()), + }; + let expected = felt!("0x00579E8877C7755365D5EC1EC7D3A94A457EFF5D1F40482BBE9729C064CDEAD2"); + assert_eq!( + calculate_transaction_hash_with_signature(&transaction), + expected + ); + } + /// Source: /// https://github.com/starkware-libs/starknet-api/blob/5565e5282f5fead364a41e49c173940fd83dee00/src/block_hash/transaction_commitment_test.rs#L32. #[test] @@ -1135,7 +1214,7 @@ mod tests { strk_l1_data_gas_price: GasPrice(10), eth_l1_data_gas_price: GasPrice(9), strk_l2_gas_price: GasPrice(0), // not used for StarknetVersion::V_0_13_2 - eth_l2_gas_price: GasPrice(0), // ditto + eth_l2_gas_price: GasPrice(0), // ditto starknet_version: StarknetVersion::V_0_13_2, starknet_version_str: "10".to_string(), parent_hash: BlockHash(11u64.into()), From 5eda931db29e056abd74eae257abb71b7f6497f7 Mon Sep 17 00:00:00 2001 From: Krzysztof Lis Date: Thu, 31 Oct 2024 19:34:00 +0100 Subject: [PATCH 222/282] refactor(block_hash): compute_final_hash is infallible --- .../examples/compute_pre0132_hashes.rs | 2 +- crates/pathfinder/src/state/block_hash.rs | 21 ++++++++----------- crates/pathfinder/src/sync/headers.rs | 19 +++++++---------- crates/storage/src/fake.rs | 8 +++---- 4 files changed, 21 insertions(+), 29 deletions(-) diff --git a/crates/pathfinder/examples/compute_pre0132_hashes.rs b/crates/pathfinder/examples/compute_pre0132_hashes.rs index b1c7fbd227..3c49935573 100644 --- a/crates/pathfinder/examples/compute_pre0132_hashes.rs +++ b/crates/pathfinder/examples/compute_pre0132_hashes.rs @@ -126,7 +126,7 @@ fn main() -> anyhow::Result<()> { // Compute the block hash in the 0.13.2 style let header_data = get_header_data(&header); - let new_block_hash = compute_final_hash(&header_data).context("Computing block hash")?; + let new_block_hash = compute_final_hash(&header_data); // Write to the CSV file writeln!(csv_file, "{},{}", block_number, new_block_hash)?; diff --git a/crates/pathfinder/src/state/block_hash.rs b/crates/pathfinder/src/state/block_hash.rs index 15a6a2c47a..838fb59ce7 100644 --- a/crates/pathfinder/src/state/block_hash.rs +++ b/crates/pathfinder/src/state/block_hash.rs @@ -253,7 +253,7 @@ pub fn verify_block_hash( false } } else { - let computed_hash = compute_final_hash(&header)?; + let computed_hash = compute_final_hash(&header); computed_hash == header.hash }; @@ -405,7 +405,7 @@ fn compute_final_hash_pre_0_13_2(header: &BlockHeaderData) -> BlockHash { BlockHash(chain.finalize()) } -fn compute_final_hash_v0(header: &BlockHeaderData) -> Result { +fn compute_final_hash_v0(header: &BlockHeaderData) -> BlockHash { // Hash the block header. let mut hasher = PoseidonHasher::new(); hasher.write(felt_bytes!(b"STARKNET_BLOCK_HASH0").into()); @@ -429,12 +429,12 @@ fn compute_final_hash_v0(header: &BlockHeaderData) -> Result { ); hasher.write(MontFelt::ZERO); hasher.write(header.parent_hash.0.into()); - Ok(BlockHash(hasher.finish().into())) + BlockHash(hasher.finish().into()) } // Bumps the initial STARKNET_BLOCK_HASH0 to STARKNET_BLOCK_HASH1, // replaces gas price elements with gas_prices_hash. -fn compute_final_hash_v1(header: &BlockHeaderData) -> Result { +fn compute_final_hash_v1(header: &BlockHeaderData) -> BlockHash { // Hash the block header. let mut hasher = PoseidonHasher::new(); hasher.write(felt_bytes!(b"STARKNET_BLOCK_HASH1").into()); @@ -455,10 +455,10 @@ fn compute_final_hash_v1(header: &BlockHeaderData) -> Result { ); hasher.write(MontFelt::ZERO); hasher.write(header.parent_hash.0.into()); - Ok(BlockHash(hasher.finish().into())) + BlockHash(hasher.finish().into()) } -pub fn compute_final_hash(header: &BlockHeaderData) -> Result { +pub fn compute_final_hash(header: &BlockHeaderData) -> BlockHash { if header.starknet_version < StarknetVersion::V_0_13_4 { compute_final_hash_v0(header) } else { @@ -927,7 +927,7 @@ mod tests { } }); - assert_eq!(compute_final_hash(&block_header).unwrap(), expected_hash); + assert_eq!(compute_final_hash(&block_header), expected_hash); } #[test] @@ -1235,7 +1235,7 @@ mod tests { let expected_hash = BlockHash(felt!( "0x061e4998d51a248f1d0288d7e17f6287757b0e5e6c5e1e58ddf740616e312134" )); - assert_eq!(compute_final_hash(&header).unwrap(), expected_hash); + assert_eq!(compute_final_hash(&header), expected_hash); } // Source @@ -1271,9 +1271,6 @@ mod tests { ) .unwrap(); - assert_eq!( - compute_final_hash(&block_header_data).unwrap(), - expected_hash - ); + assert_eq!(compute_final_hash(&block_header_data), expected_hash); } } diff --git a/crates/pathfinder/src/sync/headers.rs b/crates/pathfinder/src/sync/headers.rs index 3c4afa1ba8..4567a94223 100644 --- a/crates/pathfinder/src/sync/headers.rs +++ b/crates/pathfinder/src/sync/headers.rs @@ -264,7 +264,7 @@ impl VerifyHashAndSignature { .as_ref() .and_then(|db| db.block_hash(header.number)) .unwrap_or(header.hash); - match crate::state::block_hash::compute_final_hash(&BlockHeaderData { + let computed_hash = crate::state::block_hash::compute_final_hash(&BlockHeaderData { hash: header.hash, parent_hash: header.parent_hash, number: header.number, @@ -290,17 +290,12 @@ impl VerifyHashAndSignature { strk_l2_gas_price: header.strk_l2_gas_price, receipt_commitment: header.receipt_commitment, l1_da_mode: header.l1_da_mode, - }) { - Ok(block_hash) => { - if block_hash == expected_hash { - true - } else { - tracing::debug!(block_number=%header.number, expected_block_hash=%expected_hash, actual_block_hash=%block_hash, "Block hash mismatch"); - false - } - } - Err(e) => { - tracing::debug!(block_number=%header.number, error = ?e, "Failed to verify block hash"); + }); + { + if computed_hash == expected_hash { + true + } else { + tracing::debug!(block_number=%header.number, expected_block_hash=%expected_hash, actual_block_hash=%computed_hash, "Block hash mismatch"); false } } diff --git a/crates/storage/src/fake.rs b/crates/storage/src/fake.rs index 39c9d2235c..e3ecf7dd69 100644 --- a/crates/storage/src/fake.rs +++ b/crates/storage/src/fake.rs @@ -133,7 +133,7 @@ pub mod init { use super::Block; - pub type BlockHashFn = Box anyhow::Result>; + pub type BlockHashFn = Box BlockHash>; pub type TransactionCommitmentFn = Box anyhow::Result>; pub type ReceiptCommitmentFn = Box anyhow::Result>; @@ -151,7 +151,7 @@ pub mod init { impl Default for Config { fn default() -> Self { Self { - calculate_block_hash: Box::new(|_| Ok(Faker.fake())), + calculate_block_hash: Box::new(|_| Faker.fake()), calculate_transaction_commitment: Box::new(|_, _| Ok(Faker.fake())), calculate_receipt_commitment: Box::new(|_| Ok(Faker.fake())), calculate_event_commitment: Box::new(|_, _| Ok(Faker.fake())), @@ -434,7 +434,7 @@ pub mod init { } = init.get_mut(0).unwrap(); header.header.parent_hash = BlockHash::ZERO; - header.header.hash = (config.calculate_block_hash)(&header.header).unwrap(); + header.header.hash = (config.calculate_block_hash)(&header.header); state_update.block_hash = header.header.hash; @@ -451,7 +451,7 @@ pub mod init { header.header.parent_hash = parent_hash; - header.header.hash = (config.calculate_block_hash)(&header.header).unwrap(); + header.header.hash = (config.calculate_block_hash)(&header.header); state_update.block_hash = header.header.hash; } From cc2e6315e4dd573ef3eddad45e5563a857a59941 Mon Sep 17 00:00:00 2001 From: Krzysztof Lis Date: Thu, 31 Oct 2024 19:36:33 +0100 Subject: [PATCH 223/282] refactor: rename variable --- crates/p2p/src/client/peer_agnostic.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/p2p/src/client/peer_agnostic.rs b/crates/p2p/src/client/peer_agnostic.rs index a0311d9276..904af0caa0 100644 --- a/crates/p2p/src/client/peer_agnostic.rs +++ b/crates/p2p/src/client/peer_agnostic.rs @@ -779,9 +779,9 @@ mod transaction_stream { tracing::trace!(?start, ?stop, "Streaming Transactions"); make_stream::from_future(move |tx| async move { - let mut counts_and_commitments_stream = Box::pin(counts_stream); + let mut expected_transaction_counts_stream = Box::pin(counts_stream); - let cnt = match try_next(&mut counts_and_commitments_stream).await { + let cnt = match try_next(&mut expected_transaction_counts_stream).await { Ok(x) => x, Err(e) => { _ = tx.send(Err(e)).await; @@ -826,7 +826,7 @@ mod transaction_stream { if yield_block( peer, &mut progress, - &mut counts_and_commitments_stream, + &mut expected_transaction_counts_stream, transactions, &mut start, stop, From 0b33f1f01eff4617fcb759d231775ff094e31613 Mon Sep 17 00:00:00 2001 From: Krzysztof Lis Date: Thu, 31 Oct 2024 19:56:02 +0100 Subject: [PATCH 224/282] feat(sync/checkpoint): report chunk tail when syncing headers --- crates/p2p/src/client/peer_agnostic.rs | 2 ++ crates/pathfinder/src/sync/checkpoint.rs | 7 ++++--- crates/pathfinder/src/sync/headers.rs | 5 +++-- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/crates/p2p/src/client/peer_agnostic.rs b/crates/p2p/src/client/peer_agnostic.rs index 904af0caa0..c075162707 100644 --- a/crates/p2p/src/client/peer_agnostic.rs +++ b/crates/p2p/src/client/peer_agnostic.rs @@ -1561,6 +1561,8 @@ async fn try_next( match count_stream.next().await { Some(Ok(cnt)) => Ok(cnt), Some(Err(e)) => Err(PeerData::new(PeerId::random(), e)), + // This is a non-recovarable error, the stream is expected to yield the correct number of + // items and then terminate. Otherwise it's a DB error. None => Err(PeerData::new( PeerId::random(), anyhow::anyhow!("Count stream terminated prematurely"), diff --git a/crates/pathfinder/src/sync/checkpoint.rs b/crates/pathfinder/src/sync/checkpoint.rs index e774bfe9ab..2543c68412 100644 --- a/crates/pathfinder/src/sync/checkpoint.rs +++ b/crates/pathfinder/src/sync/checkpoint.rs @@ -315,15 +315,16 @@ async fn handle_header_stream( headers::VerifyHashAndSignature::new(chain, chain_id, public_key, block_hash_db), 10, ) - .try_chunks(1024, 10) + .try_chunks(1000, 10) .pipe( headers::Persist { - connection: storage.connection()?, + connection: storage.connection().context("Creating db connection")?, }, 10, ) .into_stream() - .try_fold((), |_state, _x| std::future::ready(Ok(()))) + .inspect_ok(|x| tracing::debug!(tail=%x.data, "Headers chunk synced")) + .try_fold((), |_, _| std::future::ready(Ok(()))) .await .map_err(SyncError::from_v2)?; Ok(()) diff --git a/crates/pathfinder/src/sync/headers.rs b/crates/pathfinder/src/sync/headers.rs index 4567a94223..735a4b1e62 100644 --- a/crates/pathfinder/src/sync/headers.rs +++ b/crates/pathfinder/src/sync/headers.rs @@ -316,9 +316,10 @@ pub struct Persist { impl ProcessStage for Persist { const NAME: &'static str = "Headers::Persist"; type Input = Vec; - type Output = (); + type Output = BlockNumber; fn map(&mut self, input: Self::Input) -> Result { + let tail = input.last().expect("not empty").header.number; let tx = self .connection .transaction() @@ -332,6 +333,6 @@ impl ProcessStage for Persist { } tx.commit().context("Committing database transaction")?; - Ok(()) + Ok(tail) } } From c9de76c3dc0ae2ab04625a04884b77fde97e10ff Mon Sep 17 00:00:00 2001 From: Krzysztof Lis Date: Thu, 31 Oct 2024 20:06:15 +0100 Subject: [PATCH 225/282] fix(sync): fetching casm error is recoverable --- crates/pathfinder/src/sync/class_definitions.rs | 15 ++++++++++----- crates/pathfinder/src/sync/error.rs | 8 ++++++++ 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/crates/pathfinder/src/sync/class_definitions.rs b/crates/pathfinder/src/sync/class_definitions.rs index 403898761c..548b60f013 100644 --- a/crates/pathfinder/src/sync/class_definitions.rs +++ b/crates/pathfinder/src/sync/class_definitions.rs @@ -20,6 +20,7 @@ use starknet_gateway_types::class_hash::from_parts::{ compute_cairo_class_hash, compute_sierra_class_hash, }; +use starknet_gateway_types::error::SequencerError; use starknet_gateway_types::reply::call; use tokio::sync::mpsc::{self, Receiver}; use tokio::sync::{oneshot, Mutex}; @@ -523,7 +524,8 @@ impl ProcessStage for CompileSierraToCas type Output = CompiledClass; fn map(&mut self, input: Self::Input) -> Result { - let compiled = compile_or_fetch_impl(input, &self.fgw, &self.tokio_handle)?; + let compiled = compile_or_fetch_impl(input, &self.fgw, &self.tokio_handle) + .map_err(|_| SyncError2::FetchingCasmFailed)?; Ok(compiled) } } @@ -542,7 +544,8 @@ pub(super) async fn compile_sierra_to_casm_or_fetch< .into_par_iter() .map(|x| { let PeerData { peer, data } = x; - let compiled = compile_or_fetch_impl(data, &fgw, &tokio_handle)?; + let compiled = compile_or_fetch_impl(data, &fgw, &tokio_handle) + .map_err(|_| SyncError::FetchingCasmFailed)?; Ok(PeerData::new(peer, compiled)) }) .collect::>, SyncError>>(); @@ -555,7 +558,7 @@ fn compile_or_fetch_impl( class: Class, fgw: &SequencerClient, tokio_handle: &tokio::runtime::Handle, -) -> anyhow::Result { +) -> Result { let Class { block_number, hash, @@ -570,9 +573,11 @@ fn compile_or_fetch_impl( let casm_definition = match casm_definition { Ok(x) => x, + // Feeder gateway request errors are recoverable at this point because we know + // that the class is declared and exists so if the gateway responds with an + // error we should restart the sync and retry later. Err(_) => tokio_handle - .block_on(fgw.pending_casm_by_hash(hash)) - .context("Fetching casm definition from gateway")? + .block_on(fgw.pending_casm_by_hash(hash))? .to_vec(), }; diff --git a/crates/pathfinder/src/sync/error.rs b/crates/pathfinder/src/sync/error.rs index 625d368aeb..e890da8f10 100644 --- a/crates/pathfinder/src/sync/error.rs +++ b/crates/pathfinder/src/sync/error.rs @@ -32,6 +32,8 @@ pub(super) enum SyncError { BadTransactionHash(PeerId), #[error("Class hash verification failed")] BadClassHash(PeerId), + #[error("Fetching casm from feeder gateway failed")] + FetchingCasmFailed, } impl PartialEq for SyncError { @@ -65,6 +67,9 @@ impl SyncError { SyncError::StateRootMismatch(x) => PeerData::new(x, SyncError2::StateRootMismatch), SyncError::BadTransactionHash(x) => PeerData::new(x, SyncError2::BadTransactionHash), SyncError::BadClassHash(x) => PeerData::new(x, SyncError2::BadClassHash), + SyncError::FetchingCasmFailed => { + PeerData::new(PeerId::random(), SyncError2::FetchingCasmFailed) + } } } @@ -87,6 +92,7 @@ impl SyncError { SyncError2::StateRootMismatch => SyncError::StateRootMismatch(peer), SyncError2::BadTransactionHash => SyncError::BadTransactionHash(peer), SyncError2::BadClassHash => SyncError::BadClassHash(peer), + SyncError2::FetchingCasmFailed => SyncError::FetchingCasmFailed, other => SyncError::Other(other.into()), } } @@ -148,6 +154,8 @@ pub(super) enum SyncError2 { BadTransactionHash, #[error("Class hash verification failed")] BadClassHash, + #[error("Fetching casm from feeder gateway failed")] + FetchingCasmFailed, } impl PartialEq for SyncError2 { From d0e8dbeea336906069254e6f62ffc8615c1f91c9 Mon Sep 17 00:00:00 2001 From: Krzysztof Lis Date: Mon, 4 Nov 2024 08:27:16 +0100 Subject: [PATCH 226/282] doc(sync): add comment --- crates/pathfinder/src/sync/transactions.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/pathfinder/src/sync/transactions.rs b/crates/pathfinder/src/sync/transactions.rs index b6f63475ba..d1fd272265 100644 --- a/crates/pathfinder/src/sync/transactions.rs +++ b/crates/pathfinder/src/sync/transactions.rs @@ -167,8 +167,10 @@ impl ProcessStage for VerifyCommitment { block_number, } = transactions; let txs: Vec<_> = transactions.iter().map(|(t, _)| t.clone()).collect(); - let actual = - calculate_transaction_commitment(&txs, version.max(StarknetVersion::V_0_13_2))?; + // This computation can only fail in case of internal trie error which is always + // a fatal error + let actual = calculate_transaction_commitment(&txs, version.max(StarknetVersion::V_0_13_2)) + .context("Computing transaction commitment")?; if actual != expected_commitment { tracing::debug!(%block_number, %expected_commitment, actual_commitment=%actual, "Transaction commitment mismatch"); return Err(SyncError2::TransactionCommitmentMismatch); From fff3243808bf813421125569f94ac83a0cb836b1 Mon Sep 17 00:00:00 2001 From: Krzysztof Lis Date: Mon, 4 Nov 2024 08:45:17 +0100 Subject: [PATCH 227/282] refactor(sync): remove VerifyCommitment2 --- crates/pathfinder/src/sync/checkpoint.rs | 2 +- crates/pathfinder/src/sync/state_updates.rs | 22 +-------------------- crates/pathfinder/src/sync/track.rs | 13 ++++++++++-- 3 files changed, 13 insertions(+), 24 deletions(-) diff --git a/crates/pathfinder/src/sync/checkpoint.rs b/crates/pathfinder/src/sync/checkpoint.rs index 2543c68412..1f9efbbf92 100644 --- a/crates/pathfinder/src/sync/checkpoint.rs +++ b/crates/pathfinder/src/sync/checkpoint.rs @@ -365,7 +365,7 @@ async fn handle_state_diff_stream( state_updates::FetchCommitmentFromDb::new(storage.connection()?), 10, ) - .pipe(state_updates::VerifyCommitment2, 10) + .pipe(state_updates::VerifyCommitment, 10) .into_stream() .try_chunks(1000) .map_err(|e| SyncError::from_v2(e.1)) diff --git a/crates/pathfinder/src/sync/state_updates.rs b/crates/pathfinder/src/sync/state_updates.rs index 93992fbd05..3faae0ea0d 100644 --- a/crates/pathfinder/src/sync/state_updates.rs +++ b/crates/pathfinder/src/sync/state_updates.rs @@ -118,27 +118,7 @@ impl ProcessStage for FetchCommitmentFromDb { pub struct VerifyCommitment; impl ProcessStage for VerifyCommitment { - const NAME: &'static str = "StateDiff::Verify"; - type Input = (StateUpdateData, BlockNumber, StateDiffCommitment); - type Output = StateUpdateData; - - fn map(&mut self, input: Self::Input) -> Result { - let (state_diff, block_number, expected_commitment) = input; - let actual_commitment = state_diff.compute_state_diff_commitment(); - - if actual_commitment != expected_commitment { - tracing::debug!(%block_number, %expected_commitment, %actual_commitment, "State diff commitment mismatch"); - return Err(SyncError2::StateDiffCommitmentMismatch); - } - - Ok(state_diff) - } -} - -pub struct VerifyCommitment2; - -impl ProcessStage for VerifyCommitment2 { - const NAME: &'static str = "StateDiff::Verify2"; + const NAME: &'static str = "StateDiff::VerifyCommitment"; type Input = (StateUpdateData, BlockNumber, StateDiffCommitment); type Output = (StateUpdateData, BlockNumber); diff --git a/crates/pathfinder/src/sync/track.rs b/crates/pathfinder/src/sync/track.rs index 239a8e725e..ac6205d7de 100644 --- a/crates/pathfinder/src/sync/track.rs +++ b/crates/pathfinder/src/sync/track.rs @@ -232,7 +232,10 @@ struct StateDiffFanout { } impl StateDiffFanout { - fn from_source(mut source: SyncReceiver, buffer: usize) -> Self { + fn from_source( + mut source: SyncReceiver<(StateUpdateData, BlockNumber)>, + buffer: usize, + ) -> Self { let (s_tx, s_rx) = tokio::sync::mpsc::channel(buffer); let (d1_tx, d1_rx) = tokio::sync::mpsc::channel(buffer); let (d2_tx, d2_rx) = tokio::sync::mpsc::channel(buffer); @@ -241,13 +244,19 @@ impl StateDiffFanout { while let Some(state_update) = source.recv().await { let is_err = state_update.is_err(); - if s_tx.send(state_update.clone()).await.is_err() || is_err { + if s_tx + .send(state_update.clone().map(|x| x.map(|(sud, _)| sud))) + .await + .is_err() + || is_err + { return; } let class_declarations = state_update .expect("Error case already handled") .data + .0 .declared_classes(); if d1_tx.send(class_declarations.clone()).await.is_err() { From a1921c2c814f54dd24ae38921736d1de1faf7e67 Mon Sep 17 00:00:00 2001 From: Krzysztof Lis Date: Mon, 4 Nov 2024 09:04:05 +0100 Subject: [PATCH 228/282] refactor(sync): deduplicate UpdateStarknetState impl --- crates/pathfinder/src/sync/state_updates.rs | 117 +++++++++++--------- 1 file changed, 62 insertions(+), 55 deletions(-) diff --git a/crates/pathfinder/src/sync/state_updates.rs b/crates/pathfinder/src/sync/state_updates.rs index 3faae0ea0d..48a5c91dad 100644 --- a/crates/pathfinder/src/sync/state_updates.rs +++ b/crates/pathfinder/src/sync/state_updates.rs @@ -1,5 +1,6 @@ use std::collections::VecDeque; use std::num::NonZeroUsize; +use std::sync::Arc; use anyhow::Context; use p2p::PeerData; @@ -8,6 +9,7 @@ use pathfinder_common::state_update::{ ContractClassUpdate, ContractUpdate, StateUpdateData, + StateUpdateRef, SystemContractUpdate, }; use pathfinder_common::{ @@ -271,37 +273,13 @@ pub async fn batch_update_starknet_state( let PeerData { peer, data: merged } = merge_state_updates(state_updates); - let (storage_commitment, class_commitment) = update_starknet_state( - &db, - (&merged).into(), - verify_tree_hashes, - tail, - storage.clone(), - ) - .context("Updating Starknet state")?; - - // Ensure that roots match. - let state_commitment = StateCommitment::calculate(storage_commitment, class_commitment); - let expected_state_commitment = db - .state_commitment(tail.into()) - .context("Querying state commitment")? - .context("State commitment not found")?; - if state_commitment != expected_state_commitment { - tracing::debug!( - %tail, - actual_storage_commitment=%storage_commitment, - actual_class_commitment=%class_commitment, - actual_state_commitment=%state_commitment, - %expected_state_commitment, - "State root mismatch"); - // TODO Wrapping in PeerData does not seem to make much sense in this case, it's - // more the range of blocks that matters - return Err(SyncError::StateRootMismatch(peer)); - } + let state_update_ref: StateUpdateRef<'_> = (&merged).into(); - db.update_storage_and_class_commitments(tail, storage_commitment, class_commitment) - .context("Updating storage and class commitments")?; - db.commit().context("Committing db transaction")?; + update_starknet_state_impl(db, state_update_ref, verify_tree_hashes, tail, storage) + .map_err(|e| match e { + UpdateStarknetStateError::StateRootMismatch => SyncError::StateRootMismatch(peer), + UpdateStarknetStateError::DBError(error) => SyncError::Other(error), + })?; Ok(PeerData::new(peer, tail)) }) @@ -330,38 +308,67 @@ impl ProcessStage for UpdateStarknetState { let tail = self.current_block; - let (storage_commitment, class_commitment) = update_starknet_state( - &db, + db.insert_state_update_data(self.current_block, &state_update) + .context("Inserting state update data")?; + + update_starknet_state_impl( + db, (&state_update).into(), self.verify_tree_hashes, - self.current_block, + tail, self.storage.clone(), ) - .context("Updating Starknet state")?; - - // Ensure that roots match. - let state_commitment = StateCommitment::calculate(storage_commitment, class_commitment); - let expected_state_commitment = db - .state_commitment(self.current_block.into()) - .context("Querying state commitment")? - .context("State commitment not found")?; - if state_commitment != expected_state_commitment { - tracing::debug!( - actual_storage_commitment=%storage_commitment, - actual_class_commitment=%class_commitment, - actual_state_commitment=%state_commitment, - "State root mismatch"); - return Err(SyncError2::StateRootMismatch); - } - - db.update_storage_and_class_commitments(tail, storage_commitment, class_commitment) - .context("Updating storage and class commitments")?; - db.insert_state_update_data(self.current_block, &state_update) - .context("Inserting state update data")?; - db.commit().context("Committing db transaction")?; + .map_err(|e| match e { + UpdateStarknetStateError::StateRootMismatch => SyncError2::StateRootMismatch, + UpdateStarknetStateError::DBError(error) => SyncError2::Other(Arc::new(error)), + })?; self.current_block += 1; Ok(tail) } } + +#[derive(Debug, thiserror::Error)] +enum UpdateStarknetStateError { + #[error("State root mismatch")] + StateRootMismatch, + #[error(transparent)] + DBError(#[from] anyhow::Error), +} + +fn update_starknet_state_impl( + db: pathfinder_storage::Transaction<'_>, + state_update_ref: StateUpdateRef<'_>, + verify_tree_hashes: bool, + tail: BlockNumber, + storage: Storage, +) -> Result<(), UpdateStarknetStateError> { + let (storage_commitment, class_commitment) = update_starknet_state( + &db, + state_update_ref, + verify_tree_hashes, + tail, + storage.clone(), + ) + .context("Updating Starknet state")?; + let state_commitment = StateCommitment::calculate(storage_commitment, class_commitment); + let expected_state_commitment = db + .state_commitment(tail.into()) + .context("Querying state commitment")? + .context("State commitment not found")?; + if state_commitment != expected_state_commitment { + tracing::debug!( + %tail, + actual_storage_commitment=%storage_commitment, + actual_class_commitment=%class_commitment, + actual_state_commitment=%state_commitment, + %expected_state_commitment, + "State root mismatch"); + return Err(UpdateStarknetStateError::StateRootMismatch); + } + db.update_storage_and_class_commitments(tail, storage_commitment, class_commitment) + .context("Updating storage and class commitments")?; + db.commit().context("Committing db transaction")?; + Ok(()) +} From 4aedd1dc214270edfca0bf52047c6bbb50314aa5 Mon Sep 17 00:00:00 2001 From: Krzysztof Lis Date: Mon, 4 Nov 2024 09:07:31 +0100 Subject: [PATCH 229/282] fix(sync): state diff commitment, starknet version and transaction commitment missing are not fatal errors --- crates/pathfinder/src/sync/error.rs | 6 ------ crates/pathfinder/src/sync/state_updates.rs | 4 +++- crates/pathfinder/src/sync/transactions.rs | 6 ++++-- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/crates/pathfinder/src/sync/error.rs b/crates/pathfinder/src/sync/error.rs index e890da8f10..2a3a24dca4 100644 --- a/crates/pathfinder/src/sync/error.rs +++ b/crates/pathfinder/src/sync/error.rs @@ -142,12 +142,6 @@ pub(super) enum SyncError2 { SierraDefinitionError, #[error("Class definitions and declarations mismatch")] ClassDefinitionsDeclarationsMismatch, - #[error("Starknet version not found in db")] - StarknetVersionNotFound, - #[error("State diff commitment not found in db")] - StateDiffCommitmentNotFound, - #[error("Transaction commitment not found in db")] - TransactionCommitmentNotFound, #[error("State root mismatch")] StateRootMismatch, #[error("Transaction hash verification failed")] diff --git a/crates/pathfinder/src/sync/state_updates.rs b/crates/pathfinder/src/sync/state_updates.rs index 48a5c91dad..5fe16f701b 100644 --- a/crates/pathfinder/src/sync/state_updates.rs +++ b/crates/pathfinder/src/sync/state_updates.rs @@ -112,7 +112,9 @@ impl ProcessStage for FetchCommitmentFromDb { let commitment = db .state_diff_commitment(block_number) .context("Fetching state diff commitment")? - .ok_or(SyncError2::StateDiffCommitmentNotFound)?; + // This is a fatal error because the block header is already expected to be in the + // database + .context("State diff commitment not found")?; Ok((data, block_number, commitment)) } } diff --git a/crates/pathfinder/src/sync/transactions.rs b/crates/pathfinder/src/sync/transactions.rs index d1fd272265..46a9dfe017 100644 --- a/crates/pathfinder/src/sync/transactions.rs +++ b/crates/pathfinder/src/sync/transactions.rs @@ -143,11 +143,13 @@ impl ProcessStage for FetchCommitmentFromDb { let version = db .block_version(block_number) .context("Fetching starknet version")? - .ok_or(SyncError2::StarknetVersionNotFound)?; + // This block header is supposed to be in the database so this is a fatal error + .context("Starknet version not found in db")?; let commitment = db .transaction_commitment(block_number) .context("Fetching transaction commitment")? - .ok_or(SyncError2::TransactionCommitmentNotFound)?; + // This block header is supposed to be in the database so this is a fatal error + .context("Transaction commitment not found in db")?; Ok((data, block_number, version, commitment)) } } From 213d8f019fb20a5d3576b1c67178239ab1feeefd Mon Sep 17 00:00:00 2001 From: Krzysztof Lis Date: Mon, 4 Nov 2024 09:49:47 +0100 Subject: [PATCH 230/282] refactor(sync): rename SyncError::Other to Fatal --- crates/pathfinder/src/sync.rs | 2 +- crates/pathfinder/src/sync/checkpoint.rs | 16 ++++++++-------- crates/pathfinder/src/sync/error.rs | 14 +++++--------- crates/pathfinder/src/sync/state_updates.rs | 2 +- 4 files changed, 15 insertions(+), 19 deletions(-) diff --git a/crates/pathfinder/src/sync.rs b/crates/pathfinder/src/sync.rs index 951fc44762..896e4f046e 100644 --- a/crates/pathfinder/src/sync.rs +++ b/crates/pathfinder/src/sync.rs @@ -126,7 +126,7 @@ impl Sync { tracing::debug!(?continue_from, "Checkpoint sync complete"); continue_from } - Err(SyncError::Other(error)) => { + Err(SyncError::Fatal(error)) => { tracing::error!(?error, "Stopping checkpoint sync"); return Err(error); } diff --git a/crates/pathfinder/src/sync/checkpoint.rs b/crates/pathfinder/src/sync/checkpoint.rs index 1f9efbbf92..edb94edd82 100644 --- a/crates/pathfinder/src/sync/checkpoint.rs +++ b/crates/pathfinder/src/sync/checkpoint.rs @@ -982,7 +982,7 @@ mod tests { storage.clone(), ) .await, - Err(SyncError::Other(_)) + Err(SyncError::Fatal(_)) ); } } @@ -1198,7 +1198,7 @@ mod tests { BlockNumber::GENESIS, ) .await, - Err(SyncError::Other(_)) + Err(SyncError::Fatal(_)) ); } @@ -1216,7 +1216,7 @@ mod tests { BlockNumber::GENESIS, ) .await, - Err(SyncError::Other(_)) + Err(SyncError::Fatal(_)) ); } } @@ -1367,7 +1367,7 @@ mod tests { false, ) .await, - Err(SyncError::Other(_)) + Err(SyncError::Fatal(_)) ); } @@ -1385,7 +1385,7 @@ mod tests { false, ) .await, - Err(SyncError::Other(_)) + Err(SyncError::Fatal(_)) ); } } @@ -1690,7 +1690,7 @@ mod tests { Faker.fake::().to_stream(), ) .await, - Err(SyncError::Other(_)) + Err(SyncError::Fatal(_)) ); } } @@ -1839,7 +1839,7 @@ mod tests { ) .await .unwrap_err(), - SyncError::Other(_) + SyncError::Fatal(_) ); } @@ -1852,7 +1852,7 @@ mod tests { ) .await .unwrap_err(), - SyncError::Other(_) + SyncError::Fatal(_) ); } } diff --git a/crates/pathfinder/src/sync/error.rs b/crates/pathfinder/src/sync/error.rs index 2a3a24dca4..b5f5bacd4f 100644 --- a/crates/pathfinder/src/sync/error.rs +++ b/crates/pathfinder/src/sync/error.rs @@ -6,8 +6,10 @@ use pathfinder_common::{BlockNumber, ClassHash, SignedBlockHeader}; #[derive(Debug, thiserror::Error)] pub(super) enum SyncError { + /// This is the only variant that causes any sync process to halt and does + /// not result in a retry. #[error(transparent)] - Other(#[from] anyhow::Error), + Fatal(#[from] anyhow::Error), #[error("Header signature verification failed")] BadHeaderSignature(PeerId), #[error("Block hash verification failed")] @@ -36,17 +38,11 @@ pub(super) enum SyncError { FetchingCasmFailed, } -impl PartialEq for SyncError { - fn eq(&self, other: &Self) -> bool { - todo!(); - } -} - impl SyncError { /// Temporary cast to allow refactoring towards SyncError2. pub fn into_v2(self) -> PeerData { match self { - SyncError::Other(e) => PeerData::new(PeerId::random(), SyncError2::Other(Arc::new(e))), + SyncError::Fatal(e) => PeerData::new(PeerId::random(), SyncError2::Other(Arc::new(e))), SyncError::BadHeaderSignature(x) => PeerData::new(x, SyncError2::BadHeaderSignature), SyncError::BadBlockHash(x) => PeerData::new(x, SyncError2::BadBlockHash), SyncError::Discontinuity(x) => PeerData::new(x, SyncError2::Discontinuity), @@ -93,7 +89,7 @@ impl SyncError { SyncError2::BadTransactionHash => SyncError::BadTransactionHash(peer), SyncError2::BadClassHash => SyncError::BadClassHash(peer), SyncError2::FetchingCasmFailed => SyncError::FetchingCasmFailed, - other => SyncError::Other(other.into()), + other => SyncError::Fatal(other.into()), } } } diff --git a/crates/pathfinder/src/sync/state_updates.rs b/crates/pathfinder/src/sync/state_updates.rs index 5fe16f701b..e5e18724aa 100644 --- a/crates/pathfinder/src/sync/state_updates.rs +++ b/crates/pathfinder/src/sync/state_updates.rs @@ -280,7 +280,7 @@ pub async fn batch_update_starknet_state( update_starknet_state_impl(db, state_update_ref, verify_tree_hashes, tail, storage) .map_err(|e| match e { UpdateStarknetStateError::StateRootMismatch => SyncError::StateRootMismatch(peer), - UpdateStarknetStateError::DBError(error) => SyncError::Other(error), + UpdateStarknetStateError::DBError(error) => SyncError::Fatal(error), })?; Ok(PeerData::new(peer, tail)) From ad738e5a45d0041d894b6d71102a6508a8ad8ade Mon Sep 17 00:00:00 2001 From: Krzysztof Lis Date: Mon, 4 Nov 2024 10:03:44 +0100 Subject: [PATCH 231/282] refactor(sync): sort SyncError variants --- crates/pathfinder/src/sync/error.rs | 32 ++++++++++++++--------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/crates/pathfinder/src/sync/error.rs b/crates/pathfinder/src/sync/error.rs index b5f5bacd4f..f5aeea2d8c 100644 --- a/crates/pathfinder/src/sync/error.rs +++ b/crates/pathfinder/src/sync/error.rs @@ -10,32 +10,32 @@ pub(super) enum SyncError { /// not result in a retry. #[error(transparent)] Fatal(#[from] anyhow::Error), - #[error("Header signature verification failed")] - BadHeaderSignature(PeerId), #[error("Block hash verification failed")] BadBlockHash(PeerId), - #[error("Discontinuity in header chain")] - Discontinuity(PeerId), - #[error("State diff commitment mismatch")] - StateDiffCommitmentMismatch(PeerId), + #[error("Class hash verification failed")] + BadClassHash(PeerId), #[error("Invalid class definition layout")] BadClassLayout(PeerId), + #[error("Header signature verification failed")] + BadHeaderSignature(PeerId), + #[error("Transaction hash verification failed")] + BadTransactionHash(PeerId), #[error("Class hash computation failed")] ClassHashComputationError(PeerId), - #[error("Unexpected class definition")] - UnexpectedClass(PeerId), + #[error("Discontinuity in header chain")] + Discontinuity(PeerId), #[error("Event commitment mismatch")] EventCommitmentMismatch(PeerId), - #[error("Transaction commitment mismatch")] - TransactionCommitmentMismatch(PeerId), - #[error("State root mismatch")] - StateRootMismatch(PeerId), - #[error("Transaction hash verification failed")] - BadTransactionHash(PeerId), - #[error("Class hash verification failed")] - BadClassHash(PeerId), #[error("Fetching casm from feeder gateway failed")] FetchingCasmFailed, + #[error("State diff commitment mismatch")] + StateDiffCommitmentMismatch(PeerId), + #[error("State root mismatch")] + StateRootMismatch(PeerId), + #[error("Transaction commitment mismatch")] + TransactionCommitmentMismatch(PeerId), + #[error("Unexpected class definition")] + UnexpectedClass(PeerId), } impl SyncError { From b57f755aa92d84972a7157c41888977d2bb8b87f Mon Sep 17 00:00:00 2001 From: Krzysztof Lis Date: Mon, 4 Nov 2024 10:09:02 +0100 Subject: [PATCH 232/282] refactor(sync): add missing variants to SyncError --- crates/pathfinder/src/sync/error.rs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/crates/pathfinder/src/sync/error.rs b/crates/pathfinder/src/sync/error.rs index f5aeea2d8c..4246922c5d 100644 --- a/crates/pathfinder/src/sync/error.rs +++ b/crates/pathfinder/src/sync/error.rs @@ -20,18 +20,38 @@ pub(super) enum SyncError { BadHeaderSignature(PeerId), #[error("Transaction hash verification failed")] BadTransactionHash(PeerId), + #[error("Incorrect cairo definition")] + CairoDefinitionError(PeerId), + #[error("Class definitions and declarations mismatch")] + ClassDefinitionsDeclarationsMismatch(PeerId), #[error("Class hash computation failed")] ClassHashComputationError(PeerId), #[error("Discontinuity in header chain")] Discontinuity(PeerId), #[error("Event commitment mismatch")] EventCommitmentMismatch(PeerId), + #[error("Mismatch between events and transactions")] + EventsTransactionsMismatch(PeerId), #[error("Fetching casm from feeder gateway failed")] FetchingCasmFailed, + #[error("Incorrect state diff count")] + IncorrectStateDiffCount(PeerId), + #[error("Invalid data in DTO")] + InvalidDto(PeerId), + #[error("Incorrect sierra definition")] + SierraDefinitionError(PeerId), #[error("State diff commitment mismatch")] StateDiffCommitmentMismatch(PeerId), #[error("State root mismatch")] StateRootMismatch(PeerId), + #[error("Too few events")] + TooFewEvents(PeerId), + #[error("Too few transactions")] + TooFewTransactions, + #[error("Too many events")] + TooManyEvents(PeerId), + #[error("Too many transactions")] + TooManyTransactions(PeerId), #[error("Transaction commitment mismatch")] TransactionCommitmentMismatch(PeerId), #[error("Unexpected class definition")] @@ -66,6 +86,7 @@ impl SyncError { SyncError::FetchingCasmFailed => { PeerData::new(PeerId::random(), SyncError2::FetchingCasmFailed) } + _ => todo!(), } } From 1114357c83ebd1a15680fa315c14131fcd8bb2c0 Mon Sep 17 00:00:00 2001 From: Krzysztof Lis Date: Mon, 4 Nov 2024 10:19:11 +0100 Subject: [PATCH 233/282] feat(sync/error): add PartialEq impl to SyncError --- crates/pathfinder/src/sync/error.rs | 48 +++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/crates/pathfinder/src/sync/error.rs b/crates/pathfinder/src/sync/error.rs index 4246922c5d..72ed1b6675 100644 --- a/crates/pathfinder/src/sync/error.rs +++ b/crates/pathfinder/src/sync/error.rs @@ -58,6 +58,54 @@ pub(super) enum SyncError { UnexpectedClass(PeerId), } +impl PartialEq for SyncError { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (SyncError::Fatal(x), SyncError::Fatal(y)) => x.to_string() == y.to_string(), + (SyncError::BadBlockHash(x), SyncError::BadBlockHash(y)) => x == y, + (SyncError::BadClassLayout(x), SyncError::BadClassLayout(y)) => x == y, + (SyncError::BadHeaderSignature(x), SyncError::BadHeaderSignature(y)) => x == y, + (SyncError::CairoDefinitionError(x), SyncError::CairoDefinitionError(y)) => x == y, + ( + SyncError::ClassDefinitionsDeclarationsMismatch(x), + SyncError::ClassDefinitionsDeclarationsMismatch(y), + ) => x == y, + (SyncError::ClassHashComputationError(x), SyncError::ClassHashComputationError(y)) => { + x == y + } + (SyncError::Discontinuity(x), SyncError::Discontinuity(y)) => x == y, + (SyncError::EventCommitmentMismatch(x), SyncError::EventCommitmentMismatch(y)) => { + x == y + } + ( + SyncError::EventsTransactionsMismatch(x), + SyncError::EventsTransactionsMismatch(y), + ) => x == y, + (SyncError::FetchingCasmFailed, SyncError::FetchingCasmFailed) => true, + (SyncError::IncorrectStateDiffCount(x), SyncError::IncorrectStateDiffCount(y)) => { + x == y + } + (SyncError::InvalidDto(x), SyncError::InvalidDto(y)) => x == y, + (SyncError::SierraDefinitionError(x), SyncError::SierraDefinitionError(y)) => x == y, + ( + SyncError::StateDiffCommitmentMismatch(x), + SyncError::StateDiffCommitmentMismatch(y), + ) => x == y, + (SyncError::StateRootMismatch(x), SyncError::StateRootMismatch(y)) => x == y, + (SyncError::TooFewEvents(x), SyncError::TooFewEvents(y)) => x == y, + (SyncError::TooFewTransactions, SyncError::TooFewTransactions) => true, + (SyncError::TooManyEvents(x), SyncError::TooManyEvents(y)) => x == y, + (SyncError::TooManyTransactions(x), SyncError::TooManyTransactions(y)) => x == y, + ( + SyncError::TransactionCommitmentMismatch(x), + SyncError::TransactionCommitmentMismatch(y), + ) => x == y, + (SyncError::UnexpectedClass(x), SyncError::UnexpectedClass(y)) => x == y, + _ => false, + } + } +} + impl SyncError { /// Temporary cast to allow refactoring towards SyncError2. pub fn into_v2(self) -> PeerData { From c94f441547c9e1b43eec9f641afc7b34b885fa11 Mon Sep 17 00:00:00 2001 From: Krzysztof Lis Date: Mon, 4 Nov 2024 12:40:20 +0100 Subject: [PATCH 234/282] refactor(sync/error): remove SyncError2 --- crates/pathfinder/src/sync.rs | 21 ++- crates/pathfinder/src/sync/checkpoint.rs | 26 ++- .../pathfinder/src/sync/class_definitions.rs | 72 ++++++--- crates/pathfinder/src/sync/error.rs | 150 ++---------------- crates/pathfinder/src/sync/events.rs | 12 +- crates/pathfinder/src/sync/headers.rs | 29 ++-- crates/pathfinder/src/sync/state_updates.rs | 23 ++- crates/pathfinder/src/sync/stream.rs | 28 ++-- crates/pathfinder/src/sync/track.rs | 63 ++++---- crates/pathfinder/src/sync/transactions.rs | 27 ++-- 10 files changed, 179 insertions(+), 272 deletions(-) diff --git a/crates/pathfinder/src/sync.rs b/crates/pathfinder/src/sync.rs index 896e4f046e..e30e47e5e4 100644 --- a/crates/pathfinder/src/sync.rs +++ b/crates/pathfinder/src/sync.rs @@ -5,7 +5,7 @@ use std::sync::Arc; use std::time::Duration; use anyhow::Context; -use error::{SyncError, SyncError2}; +use error::SyncError; use futures::{pin_mut, Stream, StreamExt}; use p2p::client::peer_agnostic::Client as P2PClient; use p2p::PeerData; @@ -63,7 +63,7 @@ impl Sync { self.track_sync(next, parent_hash).await } - async fn handle_recoverable_error(&self, err: &PeerData) { + async fn handle_recoverable_error(&self, err: &error::SyncError) { // TODO tracing::debug!(?err, "Log and punish as appropriate"); } @@ -126,13 +126,13 @@ impl Sync { tracing::debug!(?continue_from, "Checkpoint sync complete"); continue_from } - Err(SyncError::Fatal(error)) => { + Err(SyncError::Fatal(mut error)) => { tracing::error!(?error, "Stopping checkpoint sync"); - return Err(error); + return Err(error.take_or_deep_clone()); } Err(error) => { tracing::debug!(?error, "Restarting checkpoint sync"); - self.handle_recoverable_error(&error.into_v2()).await; + self.handle_recoverable_error(&error).await; continue; } }; @@ -180,19 +180,16 @@ impl Sync { .run(next, parent_hash, self.fgw_client.clone()) .await; - match &mut result { + match result { Ok(_) => tracing::debug!("Restarting track sync: unexpected end of Block stream"), - Err(PeerData { - data: SyncError2::Other(error), - .. - }) => { + Err(SyncError::Fatal(mut error)) => { tracing::error!(?error, "Stopping track sync"); use pathfinder_common::error::AnyhowExt; return Err(error.take_or_deep_clone()); } Err(error) => { - tracing::debug!(error=?error.data, "Restarting track sync"); - self.handle_recoverable_error(error).await; + tracing::debug!(?error, "Restarting track sync"); + self.handle_recoverable_error(&error).await; } } } diff --git a/crates/pathfinder/src/sync/checkpoint.rs b/crates/pathfinder/src/sync/checkpoint.rs index edb94edd82..98e3c440b3 100644 --- a/crates/pathfinder/src/sync/checkpoint.rs +++ b/crates/pathfinder/src/sync/checkpoint.rs @@ -42,7 +42,6 @@ use tokio::sync::Mutex; use tokio::task::spawn_blocking; use tracing::Instrument; -use super::error::SyncError2; use crate::state::block_hash::calculate_transaction_commitment; use crate::sync::error::SyncError; use crate::sync::stream::{InfallibleSource, Source, SyncReceiver, SyncResult}; @@ -326,8 +325,6 @@ async fn handle_header_stream( .inspect_ok(|x| tracing::debug!(tail=%x.data, "Headers chunk synced")) .try_fold((), |_, _| std::future::ready(Ok(()))) .await - .map_err(SyncError::from_v2)?; - Ok(()) } async fn handle_transaction_stream( @@ -336,7 +333,10 @@ async fn handle_transaction_stream( chain_id: ChainId, start: BlockNumber, ) -> Result<(), SyncError> { - Source::from_stream(stream.map_err(|e| e.map(Into::into))) + // TODO + // stream errors should not carry PeerId as only fatal errors are reported by + // the stream and those stem from DB errors which are fatal. + Source::from_stream(stream.map_err(|e| e.data.into())) .spawn() .pipe( transactions::FetchCommitmentFromDb::new(storage.connection()?), @@ -349,8 +349,6 @@ async fn handle_transaction_stream( .inspect_ok(|x| tracing::debug!(tail=%x.data, "Transactions chunk synced")) .try_fold((), |_, _| std::future::ready(Ok(()))) .await - .map_err(SyncError::from_v2)?; - Ok(()) } async fn handle_state_diff_stream( @@ -359,7 +357,7 @@ async fn handle_state_diff_stream( start: BlockNumber, verify_tree_hashes: bool, ) -> Result<(), SyncError> { - Source::from_stream(stream.map_err(|e| e.map(Into::into))) + Source::from_stream(stream.map_err(|e| e.data.into())) .spawn() .pipe( state_updates::FetchCommitmentFromDb::new(storage.connection()?), @@ -368,14 +366,13 @@ async fn handle_state_diff_stream( .pipe(state_updates::VerifyCommitment, 10) .into_stream() .try_chunks(1000) - .map_err(|e| SyncError::from_v2(e.1)) + .map_err(|e| e.1) .and_then(|x| { state_updates::batch_update_starknet_state(storage.clone(), verify_tree_hashes, x) }) .inspect_ok(|x| tracing::debug!(tail=%x.data, "State diff synced")) .try_fold((), |_, _| std::future::ready(Ok(()))) - .await?; - Ok(()) + .await } async fn handle_class_stream( @@ -397,7 +394,7 @@ async fn handle_class_stream Result { - verify_layout_impl(input).map_err(|_| SyncError2::BadClassLayout) + fn map(&mut self, input: Self::Input) -> Result { + // TODO + // Use the real peer id here + verify_layout_impl(input).map_err(|_| SyncError::BadClassLayout(PeerId::random())) } } @@ -173,20 +175,22 @@ fn verify_layout_impl(def: P2PClassDefinition) -> anyhow::Result Result { - compute_hash_impl(input).map_err(|_| SyncError2::ClassHashComputationError) + fn map(&mut self, input: Self::Input) -> Result { + // TODO + // Use the real peer id here + verify_hash_impl(&PeerId::random(), input) } } -pub(super) async fn compute_hash( +pub(super) async fn verify_hash( peer_data: Vec>, ) -> Result>, SyncError> { use rayon::prelude::*; @@ -195,8 +199,7 @@ pub(super) async fn compute_hash( let res = peer_data .into_par_iter() .map(|PeerData { peer, data }| { - let compiled = compute_hash_impl(data) - .map_err(|_| SyncError::ClassHashComputationError(peer))?; + let compiled = verify_hash_impl(&peer, data)?; Ok(PeerData::new(peer, compiled)) }) .collect::>, SyncError>>(); @@ -205,7 +208,7 @@ pub(super) async fn compute_hash( rx.await.expect("Sender not to be dropped") } -fn compute_hash_impl(input: ClassWithLayout) -> anyhow::Result { +fn verify_hash_impl(peer: &PeerId, input: ClassWithLayout) -> Result { let ClassWithLayout { block_number, definition, @@ -227,11 +230,15 @@ fn compute_hash_impl(input: ClassWithLayout) -> anyhow::Result { c.contract_class_version.as_ref(), c.entry_points_by_type, ), - }?; + } + .map_err(|error| { + tracing::debug!(%peer, %block_number, expected_hash=%hash, %error, "Class hash computation failed"); + SyncError::ClassHashComputationError(*peer) + })?; if computed_hash != hash { - tracing::debug!(input_hash=%hash, actual_hash=%computed_hash, "Class hash mismatch"); - Err(SyncError2::BadClassHash.into()) + tracing::debug!(%peer, %block_number, expected_hash=%hash, %computed_hash, "Class hash mismatch"); + Err(SyncError::BadClassHash(*peer)) } else { Ok(Class { block_number, @@ -261,7 +268,7 @@ impl ProcessStage for VerifyDeclaredAt { type Input = Class; type Output = Class; - fn map(&mut self, input: Self::Input) -> Result { + fn map(&mut self, input: Self::Input) -> Result { if self.current.classes.is_empty() { self.current = loop { let expected = self @@ -280,14 +287,18 @@ impl ProcessStage for VerifyDeclaredAt { if self.current.block_number != input.block_number { tracing::debug!(expected_block_number=%self.current.block_number, block_number=%input.block_number, class_hash=%input.hash, "Unexpected class definition"); - return Err(SyncError2::UnexpectedClass); + // TODO + // Use the real peer id here + return Err(SyncError::UnexpectedClass(PeerId::random())); } if self.current.classes.remove(&input.hash) { Ok(input) } else { tracing::debug!(block_number=%input.block_number, class_hash=%input.hash, "Unexpected class definition"); - Err(SyncError2::UnexpectedClass) + // TODO + // Use the real peer id here + Err(SyncError::UnexpectedClass(PeerId::random())) } } } @@ -523,9 +534,9 @@ impl ProcessStage for CompileSierraToCas type Input = Class; type Output = CompiledClass; - fn map(&mut self, input: Self::Input) -> Result { + fn map(&mut self, input: Self::Input) -> Result { let compiled = compile_or_fetch_impl(input, &self.fgw, &self.tokio_handle) - .map_err(|_| SyncError2::FetchingCasmFailed)?; + .map_err(|_| SyncError::FetchingCasmFailed)?; Ok(compiled) } } @@ -603,7 +614,7 @@ impl ProcessStage for Store { type Input = CompiledClass; type Output = BlockNumber; - fn map(&mut self, input: Self::Input) -> Result { + fn map(&mut self, input: Self::Input) -> Result { let CompiledClass { block_number, hash, @@ -696,7 +707,7 @@ impl ProcessStage for VerifyClassHashes { type Input = Vec; type Output = Vec; - fn map(&mut self, input: Self::Input) -> Result { + fn map(&mut self, input: Self::Input) -> Result { let mut declared_classes = self .tokio_handle .block_on(self.declarations.next()) @@ -707,7 +718,11 @@ impl ProcessStage for VerifyClassHashes { CompiledClassDefinition::Cairo(_) => { if !declared_classes.cairo.remove(&class.hash) { tracing::debug!(class_hash=%class.hash, "Class hash not found in declared classes"); - return Err(SyncError2::ClassDefinitionsDeclarationsMismatch); + // TODO + // Use the real peer id here + return Err(SyncError::ClassDefinitionsDeclarationsMismatch( + PeerId::random(), + )); } } CompiledClassDefinition::Sierra { .. } => { @@ -717,7 +732,10 @@ impl ProcessStage for VerifyClassHashes { .remove(&hash) .ok_or_else(|| { tracing::debug!(class_hash=%class.hash, "Class hash not found in declared classes"); - SyncError2::ClassDefinitionsDeclarationsMismatch})?; + // TODO + // Use the real peer id here + SyncError::ClassDefinitionsDeclarationsMismatch(PeerId::random()) + })?; } } } @@ -735,7 +753,11 @@ impl ProcessStage for VerifyClassHashes { ) .collect(); tracing::trace!(?missing, "Expected class definitions are missing"); - Err(SyncError2::ClassDefinitionsDeclarationsMismatch) + // TODO + // Use the real peer id here + Err(SyncError::ClassDefinitionsDeclarationsMismatch( + PeerId::random(), + )) } } } diff --git a/crates/pathfinder/src/sync/error.rs b/crates/pathfinder/src/sync/error.rs index 72ed1b6675..ab87d11895 100644 --- a/crates/pathfinder/src/sync/error.rs +++ b/crates/pathfinder/src/sync/error.rs @@ -4,12 +4,12 @@ use p2p::libp2p::PeerId; use p2p::PeerData; use pathfinder_common::{BlockNumber, ClassHash, SignedBlockHeader}; -#[derive(Debug, thiserror::Error)] +#[derive(Debug, thiserror::Error, Clone)] pub(super) enum SyncError { /// This is the only variant that causes any sync process to halt and does /// not result in a retry. #[error(transparent)] - Fatal(#[from] anyhow::Error), + Fatal(#[from] Arc), #[error("Block hash verification failed")] BadBlockHash(PeerId), #[error("Class hash verification failed")] @@ -34,6 +34,8 @@ pub(super) enum SyncError { EventsTransactionsMismatch(PeerId), #[error("Fetching casm from feeder gateway failed")] FetchingCasmFailed, + #[error("Incorrect class definition count")] + IncorrectClassDefinitionCount(PeerId), #[error("Incorrect state diff count")] IncorrectStateDiffCount(PeerId), #[error("Invalid data in DTO")] @@ -47,7 +49,7 @@ pub(super) enum SyncError { #[error("Too few events")] TooFewEvents(PeerId), #[error("Too few transactions")] - TooFewTransactions, + TooFewTransactions(PeerId), #[error("Too many events")] TooManyEvents(PeerId), #[error("Too many transactions")] @@ -82,6 +84,10 @@ impl PartialEq for SyncError { SyncError::EventsTransactionsMismatch(y), ) => x == y, (SyncError::FetchingCasmFailed, SyncError::FetchingCasmFailed) => true, + ( + SyncError::IncorrectClassDefinitionCount(x), + SyncError::IncorrectClassDefinitionCount(y), + ) => x == y, (SyncError::IncorrectStateDiffCount(x), SyncError::IncorrectStateDiffCount(y)) => { x == y } @@ -93,7 +99,7 @@ impl PartialEq for SyncError { ) => x == y, (SyncError::StateRootMismatch(x), SyncError::StateRootMismatch(y)) => x == y, (SyncError::TooFewEvents(x), SyncError::TooFewEvents(y)) => x == y, - (SyncError::TooFewTransactions, SyncError::TooFewTransactions) => true, + (SyncError::TooFewTransactions(x), SyncError::TooFewTransactions(y)) => x == y, (SyncError::TooManyEvents(x), SyncError::TooManyEvents(y)) => x == y, (SyncError::TooManyTransactions(x), SyncError::TooManyTransactions(y)) => x == y, ( @@ -106,138 +112,8 @@ impl PartialEq for SyncError { } } -impl SyncError { - /// Temporary cast to allow refactoring towards SyncError2. - pub fn into_v2(self) -> PeerData { - match self { - SyncError::Fatal(e) => PeerData::new(PeerId::random(), SyncError2::Other(Arc::new(e))), - SyncError::BadHeaderSignature(x) => PeerData::new(x, SyncError2::BadHeaderSignature), - SyncError::BadBlockHash(x) => PeerData::new(x, SyncError2::BadBlockHash), - SyncError::Discontinuity(x) => PeerData::new(x, SyncError2::Discontinuity), - SyncError::StateDiffCommitmentMismatch(x) => { - PeerData::new(x, SyncError2::StateDiffCommitmentMismatch) - } - SyncError::BadClassLayout(x) => PeerData::new(x, SyncError2::BadClassLayout), - SyncError::ClassHashComputationError(x) => { - PeerData::new(x, SyncError2::ClassHashComputationError) - } - SyncError::UnexpectedClass(x) => PeerData::new(x, SyncError2::UnexpectedClass), - SyncError::EventCommitmentMismatch(x) => { - PeerData::new(x, SyncError2::EventCommitmentMismatch) - } - SyncError::TransactionCommitmentMismatch(x) => { - PeerData::new(x, SyncError2::TransactionCommitmentMismatch) - } - SyncError::StateRootMismatch(x) => PeerData::new(x, SyncError2::StateRootMismatch), - SyncError::BadTransactionHash(x) => PeerData::new(x, SyncError2::BadTransactionHash), - SyncError::BadClassHash(x) => PeerData::new(x, SyncError2::BadClassHash), - SyncError::FetchingCasmFailed => { - PeerData::new(PeerId::random(), SyncError2::FetchingCasmFailed) - } - _ => todo!(), - } - } - - /// Temporary cast to allow refactoring towards SyncError2. - pub fn from_v2(v2: PeerData) -> Self { - let PeerData { peer, data } = v2; - - match data { - SyncError2::BadHeaderSignature => SyncError::BadHeaderSignature(peer), - SyncError2::BadBlockHash => SyncError::BadBlockHash(peer), - SyncError2::Discontinuity => SyncError::Discontinuity(peer), - SyncError2::StateDiffCommitmentMismatch => SyncError::StateDiffCommitmentMismatch(peer), - SyncError2::BadClassLayout => SyncError::BadClassLayout(peer), - SyncError2::ClassHashComputationError => SyncError::ClassHashComputationError(peer), - SyncError2::UnexpectedClass => SyncError::UnexpectedClass(peer), - SyncError2::EventCommitmentMismatch => SyncError::EventCommitmentMismatch(peer), - SyncError2::TransactionCommitmentMismatch => { - SyncError::TransactionCommitmentMismatch(peer) - } - SyncError2::StateRootMismatch => SyncError::StateRootMismatch(peer), - SyncError2::BadTransactionHash => SyncError::BadTransactionHash(peer), - SyncError2::BadClassHash => SyncError::BadClassHash(peer), - SyncError2::FetchingCasmFailed => SyncError::FetchingCasmFailed, - other => SyncError::Fatal(other.into()), - } - } -} - -#[derive(Debug, thiserror::Error, Clone)] -pub(super) enum SyncError2 { - #[error(transparent)] - Other(#[from] Arc), - #[error("Header signature verification failed")] - BadHeaderSignature, - #[error("Block hash verification failed")] - BadBlockHash, - #[error("Discontinuity in header chain")] - Discontinuity, - #[error("State diff commitment mismatch")] - StateDiffCommitmentMismatch, - #[error("Invalid class definition layout")] - BadClassLayout, - #[error("Class hash computation failed")] - ClassHashComputationError, - #[error("Unexpected class definition")] - UnexpectedClass, - #[error("Event commitment mismatch")] - EventCommitmentMismatch, - #[error("Too many events")] - TooManyEvents, - #[error("Too few events")] - TooFewEvents, - #[error("Transaction commitment mismatch")] - TransactionCommitmentMismatch, - #[error("Too many transactions")] - TooManyTransactions, - #[error("Too few transactions")] - TooFewTransactions, - #[error("Invalid data in DTO")] - InvalidDto, - #[error("Mismatch between events and transactions")] - EventsTransactionsMismatch, - #[error("Incorrect state diff count")] - IncorrectStateDiffCount, - #[error("Incorrect class definition count")] - IncorrectClassDefinitionCount, - #[error("Incorrect cairo definition")] - CairoDefinitionError, - #[error("Incorrect sierra definition")] - SierraDefinitionError, - #[error("Class definitions and declarations mismatch")] - ClassDefinitionsDeclarationsMismatch, - #[error("State root mismatch")] - StateRootMismatch, - #[error("Transaction hash verification failed")] - BadTransactionHash, - #[error("Class hash verification failed")] - BadClassHash, - #[error("Fetching casm from feeder gateway failed")] - FetchingCasmFailed, -} - -impl PartialEq for SyncError2 { - fn eq(&self, other: &Self) -> bool { - match (self, other) { - (Self::Other(x), Self::Other(y)) => x.to_string() == y.to_string(), - (SyncError2::BadHeaderSignature, SyncError2::BadHeaderSignature) => true, - (SyncError2::BadBlockHash, SyncError2::BadBlockHash) => true, - (SyncError2::Discontinuity, SyncError2::Discontinuity) => true, - (SyncError2::StateDiffCommitmentMismatch, SyncError2::StateDiffCommitmentMismatch) => { - true - } - (SyncError2::BadClassLayout, SyncError2::BadClassLayout) => true, - (SyncError2::ClassHashComputationError, SyncError2::ClassHashComputationError) => true, - (SyncError2::UnexpectedClass, SyncError2::UnexpectedClass) => true, - (SyncError2::EventCommitmentMismatch, SyncError2::EventCommitmentMismatch) => true, - _ => false, - } - } -} - -impl From for SyncError2 { - fn from(value: anyhow::Error) -> Self { - Self::Other(Arc::new(value)) +impl From for SyncError { + fn from(e: anyhow::Error) -> Self { + Self::Fatal(Arc::new(e)) } } diff --git a/crates/pathfinder/src/sync/events.rs b/crates/pathfinder/src/sync/events.rs index c2667db0dd..1646a0736c 100644 --- a/crates/pathfinder/src/sync/events.rs +++ b/crates/pathfinder/src/sync/events.rs @@ -3,6 +3,7 @@ use std::num::NonZeroUsize; use anyhow::Context; use p2p::client::types::EventsForBlockByTransaction; +use p2p::libp2p::PeerId; use p2p::PeerData; use pathfinder_common::event::Event; use pathfinder_common::receipt::Receipt; @@ -22,7 +23,6 @@ use tokio_stream::wrappers::ReceiverStream; use super::error::SyncError; use super::storage_adapters; use crate::state::block_hash::calculate_event_commitment; -use crate::sync::error::SyncError2; use crate::sync::stream::ProcessStage; /// Returns the first block number whose events are missing in storage, counting @@ -157,7 +157,7 @@ impl ProcessStage for VerifyCommitment { fn map( &mut self, (event_commitment, transactions, mut events, version): Self::Input, - ) -> Result { + ) -> Result { let mut ordered_events = Vec::new(); for tx_hash in &transactions { // Some transactions may not have events @@ -167,13 +167,17 @@ impl ProcessStage for VerifyCommitment { } if ordered_events.len() != events.len() { tracing::debug!(expected=%ordered_events.len(), actual=%events.len(), "Number of events received does not match expected number of events"); - return Err(SyncError2::EventsTransactionsMismatch); + // TODO + // Use a real peer ID here + return Err(SyncError::EventsTransactionsMismatch(PeerId::random())); } let actual = calculate_event_commitment(&ordered_events, version.max(StarknetVersion::V_0_13_2))?; if actual != event_commitment { tracing::debug!(expected=%event_commitment, actual=%actual, "Event commitment mismatch"); - return Err(SyncError2::EventCommitmentMismatch); + // TODO + // Use a real peer ID here + return Err(SyncError::EventCommitmentMismatch(PeerId::random())); } Ok(events) } diff --git a/crates/pathfinder/src/sync/headers.rs b/crates/pathfinder/src/sync/headers.rs index 735a4b1e62..d48e79e9d4 100644 --- a/crates/pathfinder/src/sync/headers.rs +++ b/crates/pathfinder/src/sync/headers.rs @@ -1,6 +1,7 @@ #![allow(dead_code, unused_variables)] use anyhow::Context; use futures::StreamExt; +use p2p::libp2p::PeerId; use p2p::PeerData; use p2p_proto::header; use pathfinder_common::{ @@ -19,7 +20,7 @@ use pathfinder_storage::Storage; use tokio::task::spawn_blocking; use crate::state::block_hash::{BlockHeaderData, VerifyResult}; -use crate::sync::error::{SyncError, SyncError2}; +use crate::sync::error::SyncError; use crate::sync::stream::{ProcessStage, SyncReceiver}; type SignedHeaderResult = Result, SyncError>; @@ -176,12 +177,14 @@ impl ProcessStage for ForwardContinuity { type Input = SignedBlockHeader; type Output = SignedBlockHeader; - fn map(&mut self, input: Self::Input) -> Result { + fn map(&mut self, input: Self::Input) -> Result { let header = &input.header; if header.number != self.next || header.parent_hash != self.parent_hash { tracing::debug!(expected_block_number=%self.next, actual_block_number=%header.number, expected_parent_block_hash=%self.parent_hash, actual_parent_block_hash=%header.parent_hash, "Block chain discontinuity"); - return Err(SyncError2::Discontinuity); + // TODO + // Use a real peer ID here + return Err(SyncError::Discontinuity(PeerId::random())); } self.next += 1; @@ -208,12 +211,18 @@ impl ProcessStage for BackwardContinuity { type Input = SignedBlockHeader; type Output = SignedBlockHeader; - fn map(&mut self, input: Self::Input) -> Result { - let number = self.number.ok_or(SyncError2::Discontinuity)?; + fn map(&mut self, input: Self::Input) -> Result { + // TODO + // Use a real peer ID here + let number = self + .number + .ok_or(SyncError::Discontinuity(PeerId::random()))?; if input.header.number != number || input.header.hash != self.hash { tracing::debug!(expected_block_number=%number, actual_block_number=%input.header.number, expected_block_hash=%self.hash, actual_block_hash=%input.header.hash, "Block chain discontinuity"); - return Err(SyncError2::Discontinuity); + // TODO + // Use a real peer ID here + return Err(SyncError::Discontinuity(PeerId::random())); } self.number = number.parent(); @@ -228,9 +237,11 @@ impl ProcessStage for VerifyHashAndSignature { type Input = SignedBlockHeader; type Output = SignedBlockHeader; - fn map(&mut self, input: Self::Input) -> Result { + fn map(&mut self, input: Self::Input) -> Result { if !self.verify_hash(&input.header) { - return Err(SyncError2::BadBlockHash); + // TODO + // Use a real peer ID here + return Err(SyncError::BadBlockHash(PeerId::random())); } if !self.verify_signature(&input) { @@ -318,7 +329,7 @@ impl ProcessStage for Persist { type Input = Vec; type Output = BlockNumber; - fn map(&mut self, input: Self::Input) -> Result { + fn map(&mut self, input: Self::Input) -> Result { let tail = input.last().expect("not empty").header.number; let tx = self .connection diff --git a/crates/pathfinder/src/sync/state_updates.rs b/crates/pathfinder/src/sync/state_updates.rs index e5e18724aa..053561f1a5 100644 --- a/crates/pathfinder/src/sync/state_updates.rs +++ b/crates/pathfinder/src/sync/state_updates.rs @@ -3,6 +3,7 @@ use std::num::NonZeroUsize; use std::sync::Arc; use anyhow::Context; +use p2p::libp2p::PeerId; use p2p::PeerData; use pathfinder_common::state_update::{ self, @@ -37,7 +38,7 @@ use tokio_stream::wrappers::ReceiverStream; use super::storage_adapters; use crate::state::update_starknet_state; -use crate::sync::error::{SyncError, SyncError2}; +use crate::sync::error::SyncError; use crate::sync::stream::ProcessStage; /// Returns the first block number whose state update is missing, counting from @@ -104,7 +105,7 @@ impl ProcessStage for FetchCommitmentFromDb { type Input = (T, BlockNumber); type Output = (T, BlockNumber, StateDiffCommitment); - fn map(&mut self, (data, block_number): Self::Input) -> Result { + fn map(&mut self, (data, block_number): Self::Input) -> Result { let mut db = self .db .transaction() @@ -126,13 +127,15 @@ impl ProcessStage for VerifyCommitment { type Input = (StateUpdateData, BlockNumber, StateDiffCommitment); type Output = (StateUpdateData, BlockNumber); - fn map(&mut self, input: Self::Input) -> Result { + fn map(&mut self, input: Self::Input) -> Result { let (state_diff, block_number, expected_commitment) = input; let actual_commitment = state_diff.compute_state_diff_commitment(); if actual_commitment != expected_commitment { tracing::debug!(%block_number, %expected_commitment, %actual_commitment, "State diff commitment mismatch"); - return Err(SyncError2::StateDiffCommitmentMismatch); + // TODO + // add peer id here, for now just use a random one as a placeholder + return Err(SyncError::StateDiffCommitmentMismatch(PeerId::random())); } Ok((state_diff, block_number)) @@ -280,7 +283,7 @@ pub async fn batch_update_starknet_state( update_starknet_state_impl(db, state_update_ref, verify_tree_hashes, tail, storage) .map_err(|e| match e { UpdateStarknetStateError::StateRootMismatch => SyncError::StateRootMismatch(peer), - UpdateStarknetStateError::DBError(error) => SyncError::Fatal(error), + UpdateStarknetStateError::DBError(error) => SyncError::Fatal(Arc::new(error)), })?; Ok(PeerData::new(peer, tail)) @@ -302,7 +305,7 @@ impl ProcessStage for UpdateStarknetState { const NAME: &'static str = "StateDiff::UpdateStarknetState"; - fn map(&mut self, state_update: Self::Input) -> Result { + fn map(&mut self, state_update: Self::Input) -> Result { let mut db = self .connection .transaction() @@ -321,8 +324,12 @@ impl ProcessStage for UpdateStarknetState { self.storage.clone(), ) .map_err(|e| match e { - UpdateStarknetStateError::StateRootMismatch => SyncError2::StateRootMismatch, - UpdateStarknetStateError::DBError(error) => SyncError2::Other(Arc::new(error)), + UpdateStarknetStateError::StateRootMismatch => { + // TODO + // add peer id here, for now just use a random one as a placeholder + SyncError::StateRootMismatch(PeerId::random()) + } + UpdateStarknetStateError::DBError(error) => SyncError::Fatal(Arc::new(error)), })?; self.current_block += 1; diff --git a/crates/pathfinder/src/sync/stream.rs b/crates/pathfinder/src/sync/stream.rs index e81a6951a3..a37cbb81ff 100644 --- a/crates/pathfinder/src/sync/stream.rs +++ b/crates/pathfinder/src/sync/stream.rs @@ -6,7 +6,7 @@ use p2p::PeerData; use tokio::sync::mpsc::Receiver; use tokio_stream::wrappers::ReceiverStream; -use crate::sync::error::SyncError2; +use crate::sync::error::SyncError; pub struct SyncReceiver { inner: Receiver>, @@ -15,7 +15,7 @@ pub struct SyncReceiver { /// [SyncReceiver::try_chunks]. pub struct ChunkSyncReceiver(SyncReceiver>); -pub type SyncResult = Result, PeerData>; +pub type SyncResult = Result, SyncError>; pub trait ProcessStage { type Input; @@ -24,7 +24,7 @@ pub trait ProcessStage { /// Used to identify this stage in metrics and traces. const NAME: &'static str; - fn map(&mut self, input: Self::Input) -> Result; + fn map(&mut self, input: Self::Input) -> Result; } impl ChunkSyncReceiver { @@ -89,9 +89,8 @@ impl SyncReceiver { let output: Result, _> = data .into_iter() .map(|data| { - stage.map(data).map_err(|e| { - tracing::debug!(error=%e, "Processing item failed"); - PeerData::new(peer, e) + stage.map(data).inspect_err(|error| { + tracing::debug!(%error, "Processing item failed"); }) }) .collect(); @@ -149,13 +148,13 @@ impl SyncReceiver { let t = std::time::Instant::now(); // Process the data. - let output = stage - .map(data) - .map(|x| PeerData::new(peer, x)) - .map_err(|e| { - tracing::debug!(error=%e, "Processing item failed"); - PeerData::new(peer, e) - }); + let output = + stage + .map(data) + .map(|x| PeerData::new(peer, x)) + .inspect_err(|error| { + tracing::debug!(%error, "Processing item failed"); + }); // Log trace and metrics. let elements_per_sec = count as f32 / t.elapsed().as_secs_f32(); @@ -333,7 +332,8 @@ where } } -#[cfg(test)] +// TODO +#[cfg(test_FIXME)] mod tests { use super::*; diff --git a/crates/pathfinder/src/sync/track.rs b/crates/pathfinder/src/sync/track.rs index ac6205d7de..5f6a78eb0e 100644 --- a/crates/pathfinder/src/sync/track.rs +++ b/crates/pathfinder/src/sync/track.rs @@ -13,6 +13,7 @@ use p2p::client::types::{ StateDiffsError, TransactionData, }; +use p2p::libp2p::PeerId; use p2p::PeerData; use pathfinder_common::event::Event; use pathfinder_common::receipt::Receipt; @@ -47,7 +48,7 @@ use super::class_definitions::CompiledClass; use super::{state_updates, transactions}; use crate::state::update_starknet_state; use crate::sync::class_definitions::{self, ClassWithLayout}; -use crate::sync::error::SyncError2; +use crate::sync::error::SyncError; use crate::sync::stream::{ProcessStage, SyncReceiver, SyncResult}; use crate::sync::{events, headers}; @@ -68,7 +69,7 @@ impl Sync { next: BlockNumber, parent_hash: BlockHash, fgw: SequencerClient, - ) -> Result<(), PeerData> + ) -> Result<(), SyncError> where L: Stream + Clone + Send + 'static, P: BlockClient + Clone + HeaderStream + Send + 'static, @@ -76,12 +77,7 @@ impl Sync { let storage_connection = self .storage .connection() - .context("Creating database connection") - // FIXME: PeerData should allow for None peers. - .map_err(|e| PeerData { - peer: p2p::libp2p::PeerId::random(), - data: SyncError2::from(e), - })?; + .context("Creating database connection")?; let mut headers = HeaderSource { p2p: self.p2p.clone(), @@ -148,7 +144,7 @@ impl Sync { } .spawn() .pipe_each(class_definitions::VerifyLayout, 10) - .pipe_each(class_definitions::ComputeHash, 10) + .pipe_each(class_definitions::VerifyHash, 10) .pipe_each( class_definitions::CompileSierraToCasm::new(fgw, tokio::runtime::Handle::current()), 10, @@ -400,13 +396,11 @@ impl

TransactionSource

{ let (transaction, receipt) = match transactions.next().await { Some(Ok((transaction, receipt))) => (transaction, receipt), Some(Err(_)) => { - let err = PeerData::new(peer, SyncError2::InvalidDto); - let _ = tx.send(Err(err)).await; + let _ = tx.send(Err(SyncError::InvalidDto(peer))).await; return; } None => { - let err = PeerData::new(peer, SyncError2::TooFewTransactions); - let _ = tx.send(Err(err)).await; + let _ = tx.send(Err(SyncError::TooFewTransactions(peer))).await; return; } }; @@ -416,8 +410,7 @@ impl

TransactionSource

{ // Ensure that the stream is exhausted. if transactions.next().await.is_some() { - let err = PeerData::new(peer, SyncError2::TooManyTransactions); - let _ = tx.send(Err(err)).await; + let _ = tx.send(Err(SyncError::TooManyTransactions(peer))).await; return; } @@ -473,9 +466,9 @@ impl

EventSource

{ }; let Some(block_transactions) = transactions.next().await else { - let err = - PeerData::new(peer, SyncError2::Other(anyhow!("No transactions").into())); - let _ = tx.send(Err(err)).await; + // TODO is this a fatal error? + // is this an error at all? Can blocks have no transactions? + let _ = tx.send(Err(anyhow!("No transactions").into())).await; return; }; @@ -491,13 +484,11 @@ impl

EventSource

{ block_events.entry(tx_hash).or_default().push(event); } Some(Err(_)) => { - let err = PeerData::new(peer, SyncError2::InvalidDto); - let _ = tx.send(Err(err)).await; + let _ = tx.send(Err(SyncError::InvalidDto(peer))).await; return; } None => { - let err = PeerData::new(peer, SyncError2::TooFewEvents); - let _ = tx.send(Err(err)).await; + let _ = tx.send(Err(SyncError::TooFewEvents(peer))).await; return; } } @@ -505,8 +496,7 @@ impl

EventSource

{ // Ensure that the stream is exhausted. if events.next().await.is_some() { - let err = PeerData::new(peer, SyncError2::TooManyEvents); - let _ = tx.send(Err(err)).await; + let _ = tx.send(Err(SyncError::TooManyEvents(peer))).await; return; } @@ -556,13 +546,12 @@ impl

StateDiffSource

{ Ok(Some(state_diff)) => break state_diff, Ok(None) => {} Err(StateDiffsError::IncorrectStateDiffCount(peer)) => { - let err = PeerData::new(peer, SyncError2::IncorrectStateDiffCount); - let _ = tx.send(Err(err)).await; + let _ = tx.send(Err(SyncError::IncorrectStateDiffCount(peer))).await; return; } - Err(StateDiffsError::ResponseStreamFailure(peer, error)) => { - let err = PeerData::new(peer, SyncError2::InvalidDto); - let _ = tx.send(Err(err)).await; + Err(StateDiffsError::ResponseStreamFailure(peer, _error)) => { + // TODO what about the _error + let _ = tx.send(Err(SyncError::InvalidDto(peer))).await; return; } } @@ -623,16 +612,17 @@ impl

ClassSource

{ Err(err) => { let err = match err { ClassDefinitionsError::IncorrectClassDefinitionCount(peer) => { - PeerData::new(peer, SyncError2::IncorrectClassDefinitionCount) + SyncError::IncorrectClassDefinitionCount(peer) } ClassDefinitionsError::CairoDefinitionError(peer) => { - PeerData::new(peer, SyncError2::CairoDefinitionError) + SyncError::CairoDefinitionError(peer) } ClassDefinitionsError::SierraDefinitionError(peer) => { - PeerData::new(peer, SyncError2::SierraDefinitionError) + SyncError::SierraDefinitionError(peer) } - ClassDefinitionsError::ResponseStreamFailure(peer, error) => { - PeerData::new(peer, SyncError2::InvalidDto) + ClassDefinitionsError::ResponseStreamFailure(peer, _error) => { + // TODO what about the _error + SyncError::InvalidDto(peer) } }; let _ = tx.send(Err(err)).await; @@ -765,7 +755,7 @@ impl ProcessStage for StoreBlock { type Input = BlockData; type Output = (); - fn map(&mut self, input: Self::Input) -> Result { + fn map(&mut self, input: Self::Input) -> Result { let BlockData { header: SignedBlockHeader { header, signature }, mut events, @@ -841,7 +831,8 @@ impl ProcessStage for StoreBlock { actual_class_commitment=%class_commitment, actual_state_commitment=%state_commitment, "State root mismatch"); - return Err(SyncError2::StateRootMismatch); + // TODO: remove placeholder + return Err(SyncError::StateRootMismatch(PeerId::random())); } db.update_storage_and_class_commitments(block_number, storage_commitment, class_commitment) diff --git a/crates/pathfinder/src/sync/transactions.rs b/crates/pathfinder/src/sync/transactions.rs index 46a9dfe017..7684629bc3 100644 --- a/crates/pathfinder/src/sync/transactions.rs +++ b/crates/pathfinder/src/sync/transactions.rs @@ -3,6 +3,7 @@ use std::num::NonZeroUsize; use anyhow::{anyhow, Context}; use p2p::client::types::TransactionData; +use p2p::libp2p::PeerId; use p2p::PeerData; use pathfinder_common::receipt::Receipt; use pathfinder_common::transaction::{Transaction, TransactionVariant}; @@ -18,7 +19,7 @@ use pathfinder_storage::Storage; use tokio::sync::mpsc; use tokio_stream::wrappers::ReceiverStream; -use super::error::{SyncError, SyncError2}; +use super::error::SyncError; use super::storage_adapters; use super::stream::ProcessStage; use crate::state::block_hash::calculate_transaction_commitment; @@ -84,23 +85,25 @@ impl ProcessStage for CalculateHashes { ); type Output = UnverifiedTransactions; - fn map(&mut self, input: Self::Input) -> Result { + fn map(&mut self, input: Self::Input) -> Result { use rayon::prelude::*; + // TODO remove the placeholder + let peer = &PeerId::random(); let (transactions, block_number, version, expected_commitment) = input; let transactions = transactions .into_par_iter() .map(|(tx, r)| { - let transaction_hash = tx.variant.calculate_hash(self.0, false); - if tx.hash != transaction_hash { - tracing::debug!(input_hash=%tx.hash, actual_hash=%transaction_hash, "Transaction hash mismatch"); - Err(SyncError2::BadTransactionHash) + let computed_hash = tx.variant.calculate_hash(self.0, false); + if tx.hash != computed_hash { + tracing::debug!(%peer, %block_number, expected_hash=%tx.hash, %computed_hash, "Transaction hash mismatch"); + Err(SyncError::BadTransactionHash(*peer)) } else { let receipt = Receipt { actual_fee: r.actual_fee, execution_resources: r.execution_resources, l2_to_l1_messages: r.l2_to_l1_messages, execution_status: r.execution_status, - transaction_hash, + transaction_hash: computed_hash, transaction_index: r.transaction_index, }; Ok((tx, receipt)) @@ -135,7 +138,7 @@ impl ProcessStage for FetchCommitmentFromDb { type Input = (T, BlockNumber); type Output = (T, BlockNumber, StarknetVersion, TransactionCommitment); - fn map(&mut self, (data, block_number): Self::Input) -> Result { + fn map(&mut self, (data, block_number): Self::Input) -> Result { let mut db = self .db .transaction() @@ -161,7 +164,7 @@ impl ProcessStage for VerifyCommitment { type Input = UnverifiedTransactions; type Output = Vec<(Transaction, Receipt)>; - fn map(&mut self, transactions: Self::Input) -> Result { + fn map(&mut self, transactions: Self::Input) -> Result { let UnverifiedTransactions { expected_commitment, transactions, @@ -175,7 +178,9 @@ impl ProcessStage for VerifyCommitment { .context("Computing transaction commitment")?; if actual != expected_commitment { tracing::debug!(%block_number, %expected_commitment, actual_commitment=%actual, "Transaction commitment mismatch"); - return Err(SyncError2::TransactionCommitmentMismatch); + // TODO + // Use the real peer id here + return Err(SyncError::TransactionCommitmentMismatch(PeerId::random())); } Ok(transactions) } @@ -200,7 +205,7 @@ impl ProcessStage for Store { type Input = Vec<(Transaction, Receipt)>; type Output = BlockNumber; - fn map(&mut self, transactions: Self::Input) -> Result { + fn map(&mut self, transactions: Self::Input) -> Result { let mut db = self .db .transaction() From c98e3e09758d2cb7fbe840c7fb8d0e02d7a4cc36 Mon Sep 17 00:00:00 2001 From: Krzysztof Lis Date: Mon, 4 Nov 2024 16:24:36 +0100 Subject: [PATCH 235/282] refactor(p2p): p2p client stream apis only report fatal errors Which have no associated peer. --- crates/p2p/src/client/peer_agnostic.rs | 15 +++++++-------- crates/p2p/src/client/peer_agnostic/traits.rs | 2 +- crates/pathfinder/src/sync/checkpoint.rs | 11 ++++++----- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/crates/p2p/src/client/peer_agnostic.rs b/crates/p2p/src/client/peer_agnostic.rs index c075162707..e331f3775f 100644 --- a/crates/p2p/src/client/peer_agnostic.rs +++ b/crates/p2p/src/client/peer_agnostic.rs @@ -1557,16 +1557,15 @@ mod event_stream { async fn try_next( count_stream: &mut (impl Stream> + Unpin + Send + 'static), -) -> Result> { +) -> Result { match count_stream.next().await { Some(Ok(cnt)) => Ok(cnt), - Some(Err(e)) => Err(PeerData::new(PeerId::random(), e)), - // This is a non-recovarable error, the stream is expected to yield the correct number of - // items and then terminate. Otherwise it's a DB error. - None => Err(PeerData::new( - PeerId::random(), - anyhow::anyhow!("Count stream terminated prematurely"), - )), + // This is a non-recovarable error, because "Counter" streams fail only if the underlying + // database fails. + Some(Err(e)) => Err(e), + // This is a non-recovarable error, because we expect all the necessary headers that are the + // source of the stream to be in the database. + None => Err(anyhow::anyhow!("Count stream terminated prematurely")), } } diff --git a/crates/p2p/src/client/peer_agnostic/traits.rs b/crates/p2p/src/client/peer_agnostic/traits.rs index fa4aeaa59e..c96448e732 100644 --- a/crates/p2p/src/client/peer_agnostic/traits.rs +++ b/crates/p2p/src/client/peer_agnostic/traits.rs @@ -16,7 +16,7 @@ use crate::client::types::{ }; use crate::PeerData; -pub type StreamItem = Result, PeerData>; +pub type StreamItem = Result, anyhow::Error>; pub trait HeaderStream { fn header_stream( diff --git a/crates/pathfinder/src/sync/checkpoint.rs b/crates/pathfinder/src/sync/checkpoint.rs index 98e3c440b3..48a82b3089 100644 --- a/crates/pathfinder/src/sync/checkpoint.rs +++ b/crates/pathfinder/src/sync/checkpoint.rs @@ -336,7 +336,7 @@ async fn handle_transaction_stream( // TODO // stream errors should not carry PeerId as only fatal errors are reported by // the stream and those stem from DB errors which are fatal. - Source::from_stream(stream.map_err(|e| e.data.into())) + Source::from_stream(stream.map_err(Into::into)) .spawn() .pipe( transactions::FetchCommitmentFromDb::new(storage.connection()?), @@ -357,7 +357,7 @@ async fn handle_state_diff_stream( start: BlockNumber, verify_tree_hashes: bool, ) -> Result<(), SyncError> { - Source::from_stream(stream.map_err(|e| e.data.into())) + Source::from_stream(stream.map_err(Into::into)) .spawn() .pipe( state_updates::FetchCommitmentFromDb::new(storage.connection()?), @@ -390,7 +390,7 @@ async fn handle_class_stream Result<(), SyncError> { stream - .map_err(|e| e.data.into()) + .map_err(Into::into) .and_then(|x| events::verify_commitment(x, storage.clone())) .try_chunks(100) .map_err(|e| e.1) @@ -692,7 +692,8 @@ async fn persist_anchor(storage: Storage, anchor: EthereumStateUpdate) -> anyhow .context("Joining blocking task")? } -#[cfg(test)] +// FIXME P2P +#[cfg(test_FIXME)] mod tests { use super::*; From 0b802c9489794b2dffeefcd19dfcf54229100678 Mon Sep 17 00:00:00 2001 From: Krzysztof Lis Date: Mon, 4 Nov 2024 16:38:26 +0100 Subject: [PATCH 236/282] refactor: report proper peer upon error when using the pipe api --- .../pathfinder/src/sync/class_definitions.rs | 67 +++++++------------ crates/pathfinder/src/sync/events.rs | 9 +-- crates/pathfinder/src/sync/headers.rs | 28 +++----- crates/pathfinder/src/sync/state_updates.rs | 20 +++--- crates/pathfinder/src/sync/stream.rs | 28 ++++---- crates/pathfinder/src/sync/track.rs | 5 +- crates/pathfinder/src/sync/transactions.rs | 16 +++-- 7 files changed, 73 insertions(+), 100 deletions(-) diff --git a/crates/pathfinder/src/sync/class_definitions.rs b/crates/pathfinder/src/sync/class_definitions.rs index 11eb38b708..7d50745581 100644 --- a/crates/pathfinder/src/sync/class_definitions.rs +++ b/crates/pathfinder/src/sync/class_definitions.rs @@ -116,9 +116,7 @@ pub(super) async fn verify_layout( peer_data: PeerData, ) -> Result, SyncError> { let PeerData { peer, data } = peer_data; - verify_layout_impl(data) - .map(|x| PeerData::new(peer, x)) - .map_err(|_| SyncError::BadClassLayout(peer)) + verify_layout_impl(&peer, data).map(|x| PeerData::new(peer, x)) } pub struct VerifyLayout; @@ -129,14 +127,15 @@ impl ProcessStage for VerifyLayout { type Input = P2PClassDefinition; type Output = ClassWithLayout; - fn map(&mut self, input: Self::Input) -> Result { - // TODO - // Use the real peer id here - verify_layout_impl(input).map_err(|_| SyncError::BadClassLayout(PeerId::random())) + fn map(&mut self, peer: &PeerId, input: Self::Input) -> Result { + verify_layout_impl(peer, input) } } -fn verify_layout_impl(def: P2PClassDefinition) -> anyhow::Result { +fn verify_layout_impl( + peer: &PeerId, + def: P2PClassDefinition, +) -> Result { match def { P2PClassDefinition::Cairo { block_number, @@ -144,9 +143,10 @@ fn verify_layout_impl(def: P2PClassDefinition) -> anyhow::Result { let layout = GwClassDefinition::Cairo( - serde_json::from_slice::>(&definition).inspect_err( - |e| tracing::debug!(%block_number, error=%e, "Bad class layout"), - )?, + serde_json::from_slice::>(&definition).map_err(|error| { + tracing::debug!(%peer, %block_number, %error, "Bad class layout"); + SyncError::BadClassLayout(*peer) + })?, ); Ok(ClassWithLayout { block_number, @@ -161,9 +161,10 @@ fn verify_layout_impl(def: P2PClassDefinition) -> anyhow::Result { let layout = GwClassDefinition::Sierra( - serde_json::from_slice::>(&sierra_definition).inspect_err( - |e| tracing::debug!(%block_number, error=%e, "Bad class layout"), - )?, + serde_json::from_slice::>(&sierra_definition).map_err(|error| { + tracing::debug!(%peer, %block_number, %error, "Bad class layout"); + SyncError::BadClassLayout(*peer) + })?, ); Ok(ClassWithLayout { block_number, @@ -183,10 +184,8 @@ impl ProcessStage for VerifyHash { type Input = ClassWithLayout; type Output = Class; - fn map(&mut self, input: Self::Input) -> Result { - // TODO - // Use the real peer id here - verify_hash_impl(&PeerId::random(), input) + fn map(&mut self, peer: &PeerId, input: Self::Input) -> Result { + verify_hash_impl(peer, input) } } @@ -268,7 +267,7 @@ impl ProcessStage for VerifyDeclaredAt { type Input = Class; type Output = Class; - fn map(&mut self, input: Self::Input) -> Result { + fn map(&mut self, peer: &PeerId, input: Self::Input) -> Result { if self.current.classes.is_empty() { self.current = loop { let expected = self @@ -287,18 +286,14 @@ impl ProcessStage for VerifyDeclaredAt { if self.current.block_number != input.block_number { tracing::debug!(expected_block_number=%self.current.block_number, block_number=%input.block_number, class_hash=%input.hash, "Unexpected class definition"); - // TODO - // Use the real peer id here - return Err(SyncError::UnexpectedClass(PeerId::random())); + return Err(SyncError::UnexpectedClass(*peer)); } if self.current.classes.remove(&input.hash) { Ok(input) } else { tracing::debug!(block_number=%input.block_number, class_hash=%input.hash, "Unexpected class definition"); - // TODO - // Use the real peer id here - Err(SyncError::UnexpectedClass(PeerId::random())) + Err(SyncError::UnexpectedClass(*peer)) } } } @@ -534,7 +529,7 @@ impl ProcessStage for CompileSierraToCas type Input = Class; type Output = CompiledClass; - fn map(&mut self, input: Self::Input) -> Result { + fn map(&mut self, _: &PeerId, input: Self::Input) -> Result { let compiled = compile_or_fetch_impl(input, &self.fgw, &self.tokio_handle) .map_err(|_| SyncError::FetchingCasmFailed)?; Ok(compiled) @@ -614,7 +609,7 @@ impl ProcessStage for Store { type Input = CompiledClass; type Output = BlockNumber; - fn map(&mut self, input: Self::Input) -> Result { + fn map(&mut self, _: &PeerId, input: Self::Input) -> Result { let CompiledClass { block_number, hash, @@ -707,7 +702,7 @@ impl ProcessStage for VerifyClassHashes { type Input = Vec; type Output = Vec; - fn map(&mut self, input: Self::Input) -> Result { + fn map(&mut self, peer: &PeerId, input: Self::Input) -> Result { let mut declared_classes = self .tokio_handle .block_on(self.declarations.next()) @@ -718,11 +713,7 @@ impl ProcessStage for VerifyClassHashes { CompiledClassDefinition::Cairo(_) => { if !declared_classes.cairo.remove(&class.hash) { tracing::debug!(class_hash=%class.hash, "Class hash not found in declared classes"); - // TODO - // Use the real peer id here - return Err(SyncError::ClassDefinitionsDeclarationsMismatch( - PeerId::random(), - )); + return Err(SyncError::ClassDefinitionsDeclarationsMismatch(*peer)); } } CompiledClassDefinition::Sierra { .. } => { @@ -732,9 +723,7 @@ impl ProcessStage for VerifyClassHashes { .remove(&hash) .ok_or_else(|| { tracing::debug!(class_hash=%class.hash, "Class hash not found in declared classes"); - // TODO - // Use the real peer id here - SyncError::ClassDefinitionsDeclarationsMismatch(PeerId::random()) + SyncError::ClassDefinitionsDeclarationsMismatch(*peer) })?; } } @@ -753,11 +742,7 @@ impl ProcessStage for VerifyClassHashes { ) .collect(); tracing::trace!(?missing, "Expected class definitions are missing"); - // TODO - // Use the real peer id here - Err(SyncError::ClassDefinitionsDeclarationsMismatch( - PeerId::random(), - )) + Err(SyncError::ClassDefinitionsDeclarationsMismatch(*peer)) } } } diff --git a/crates/pathfinder/src/sync/events.rs b/crates/pathfinder/src/sync/events.rs index 1646a0736c..80ca0a3b15 100644 --- a/crates/pathfinder/src/sync/events.rs +++ b/crates/pathfinder/src/sync/events.rs @@ -156,6 +156,7 @@ impl ProcessStage for VerifyCommitment { fn map( &mut self, + peer: &PeerId, (event_commitment, transactions, mut events, version): Self::Input, ) -> Result { let mut ordered_events = Vec::new(); @@ -167,17 +168,13 @@ impl ProcessStage for VerifyCommitment { } if ordered_events.len() != events.len() { tracing::debug!(expected=%ordered_events.len(), actual=%events.len(), "Number of events received does not match expected number of events"); - // TODO - // Use a real peer ID here - return Err(SyncError::EventsTransactionsMismatch(PeerId::random())); + return Err(SyncError::EventsTransactionsMismatch(*peer)); } let actual = calculate_event_commitment(&ordered_events, version.max(StarknetVersion::V_0_13_2))?; if actual != event_commitment { tracing::debug!(expected=%event_commitment, actual=%actual, "Event commitment mismatch"); - // TODO - // Use a real peer ID here - return Err(SyncError::EventCommitmentMismatch(PeerId::random())); + return Err(SyncError::EventCommitmentMismatch(*peer)); } Ok(events) } diff --git a/crates/pathfinder/src/sync/headers.rs b/crates/pathfinder/src/sync/headers.rs index d48e79e9d4..2e0d0b8422 100644 --- a/crates/pathfinder/src/sync/headers.rs +++ b/crates/pathfinder/src/sync/headers.rs @@ -177,14 +177,12 @@ impl ProcessStage for ForwardContinuity { type Input = SignedBlockHeader; type Output = SignedBlockHeader; - fn map(&mut self, input: Self::Input) -> Result { + fn map(&mut self, peer: &PeerId, input: Self::Input) -> Result { let header = &input.header; if header.number != self.next || header.parent_hash != self.parent_hash { - tracing::debug!(expected_block_number=%self.next, actual_block_number=%header.number, expected_parent_block_hash=%self.parent_hash, actual_parent_block_hash=%header.parent_hash, "Block chain discontinuity"); - // TODO - // Use a real peer ID here - return Err(SyncError::Discontinuity(PeerId::random())); + tracing::debug!(%peer, expected_block_number=%self.next, actual_block_number=%header.number, expected_parent_block_hash=%self.parent_hash, actual_parent_block_hash=%header.parent_hash, "Block chain discontinuity"); + return Err(SyncError::Discontinuity(*peer)); } self.next += 1; @@ -211,18 +209,12 @@ impl ProcessStage for BackwardContinuity { type Input = SignedBlockHeader; type Output = SignedBlockHeader; - fn map(&mut self, input: Self::Input) -> Result { - // TODO - // Use a real peer ID here - let number = self - .number - .ok_or(SyncError::Discontinuity(PeerId::random()))?; + fn map(&mut self, peer: &PeerId, input: Self::Input) -> Result { + let number = self.number.ok_or(SyncError::Discontinuity(*peer))?; if input.header.number != number || input.header.hash != self.hash { tracing::debug!(expected_block_number=%number, actual_block_number=%input.header.number, expected_block_hash=%self.hash, actual_block_hash=%input.header.hash, "Block chain discontinuity"); - // TODO - // Use a real peer ID here - return Err(SyncError::Discontinuity(PeerId::random())); + return Err(SyncError::Discontinuity(*peer)); } self.number = number.parent(); @@ -237,11 +229,9 @@ impl ProcessStage for VerifyHashAndSignature { type Input = SignedBlockHeader; type Output = SignedBlockHeader; - fn map(&mut self, input: Self::Input) -> Result { + fn map(&mut self, peer: &PeerId, input: Self::Input) -> Result { if !self.verify_hash(&input.header) { - // TODO - // Use a real peer ID here - return Err(SyncError::BadBlockHash(PeerId::random())); + return Err(SyncError::BadBlockHash((*peer))); } if !self.verify_signature(&input) { @@ -329,7 +319,7 @@ impl ProcessStage for Persist { type Input = Vec; type Output = BlockNumber; - fn map(&mut self, input: Self::Input) -> Result { + fn map(&mut self, _: &PeerId, input: Self::Input) -> Result { let tail = input.last().expect("not empty").header.number; let tx = self .connection diff --git a/crates/pathfinder/src/sync/state_updates.rs b/crates/pathfinder/src/sync/state_updates.rs index 053561f1a5..e82544e6bb 100644 --- a/crates/pathfinder/src/sync/state_updates.rs +++ b/crates/pathfinder/src/sync/state_updates.rs @@ -105,7 +105,11 @@ impl ProcessStage for FetchCommitmentFromDb { type Input = (T, BlockNumber); type Output = (T, BlockNumber, StateDiffCommitment); - fn map(&mut self, (data, block_number): Self::Input) -> Result { + fn map( + &mut self, + _: &PeerId, + (data, block_number): Self::Input, + ) -> Result { let mut db = self .db .transaction() @@ -127,15 +131,13 @@ impl ProcessStage for VerifyCommitment { type Input = (StateUpdateData, BlockNumber, StateDiffCommitment); type Output = (StateUpdateData, BlockNumber); - fn map(&mut self, input: Self::Input) -> Result { + fn map(&mut self, peer: &PeerId, input: Self::Input) -> Result { let (state_diff, block_number, expected_commitment) = input; let actual_commitment = state_diff.compute_state_diff_commitment(); if actual_commitment != expected_commitment { tracing::debug!(%block_number, %expected_commitment, %actual_commitment, "State diff commitment mismatch"); - // TODO - // add peer id here, for now just use a random one as a placeholder - return Err(SyncError::StateDiffCommitmentMismatch(PeerId::random())); + return Err(SyncError::StateDiffCommitmentMismatch(*peer)); } Ok((state_diff, block_number)) @@ -305,7 +307,7 @@ impl ProcessStage for UpdateStarknetState { const NAME: &'static str = "StateDiff::UpdateStarknetState"; - fn map(&mut self, state_update: Self::Input) -> Result { + fn map(&mut self, peer: &PeerId, state_update: Self::Input) -> Result { let mut db = self .connection .transaction() @@ -324,11 +326,7 @@ impl ProcessStage for UpdateStarknetState { self.storage.clone(), ) .map_err(|e| match e { - UpdateStarknetStateError::StateRootMismatch => { - // TODO - // add peer id here, for now just use a random one as a placeholder - SyncError::StateRootMismatch(PeerId::random()) - } + UpdateStarknetStateError::StateRootMismatch => SyncError::StateRootMismatch(*peer), UpdateStarknetStateError::DBError(error) => SyncError::Fatal(Arc::new(error)), })?; diff --git a/crates/pathfinder/src/sync/stream.rs b/crates/pathfinder/src/sync/stream.rs index a37cbb81ff..e10e3dd2d4 100644 --- a/crates/pathfinder/src/sync/stream.rs +++ b/crates/pathfinder/src/sync/stream.rs @@ -24,7 +24,7 @@ pub trait ProcessStage { /// Used to identify this stage in metrics and traces. const NAME: &'static str; - fn map(&mut self, input: Self::Input) -> Result; + fn map(&mut self, peer: &PeerId, input: Self::Input) -> Result; } impl ChunkSyncReceiver { @@ -89,7 +89,7 @@ impl SyncReceiver { let output: Result, _> = data .into_iter() .map(|data| { - stage.map(data).inspect_err(|error| { + stage.map(&peer, data).inspect_err(|error| { tracing::debug!(%error, "Processing item failed"); }) }) @@ -148,13 +148,12 @@ impl SyncReceiver { let t = std::time::Instant::now(); // Process the data. - let output = - stage - .map(data) - .map(|x| PeerData::new(peer, x)) - .inspect_err(|error| { - tracing::debug!(%error, "Processing item failed"); - }); + let output = stage + .map(&peer, data) + .map(|x| PeerData::new(peer, x)) + .inspect_err(|error| { + tracing::debug!(%error, "Processing item failed"); + }); // Log trace and metrics. let elements_per_sec = count as f32 / t.elapsed().as_secs_f32(); @@ -189,7 +188,7 @@ impl SyncReceiver { std::thread::spawn(move || { let mut chunk = Vec::with_capacity(capacity); - let mut peer = PeerId::random(); + let mut peer = None; let mut err = None; while let Some(input) = self.inner.blocking_recv() { @@ -203,14 +202,17 @@ impl SyncReceiver { // 1st element, assign peer ID. if chunk.is_empty() { - peer = input.peer; + peer = Some(input.peer); } chunk.push(input.data); if chunk.len() == capacity { let data = std::mem::replace(&mut chunk, Vec::with_capacity(capacity)); - if tx.blocking_send(Ok(PeerData::new(peer, data))).is_err() { + if tx + .blocking_send(Ok(PeerData::new(peer.expect("to be set"), data))) + .is_err() + { break; }; } @@ -218,7 +220,7 @@ impl SyncReceiver { // Send any remaining elements. if !chunk.is_empty() { - _ = tx.blocking_send(Ok(PeerData::new(peer, chunk))); + _ = tx.blocking_send(Ok(PeerData::new(peer.expect("to be set"), chunk))); } if let Some(err) = err { diff --git a/crates/pathfinder/src/sync/track.rs b/crates/pathfinder/src/sync/track.rs index 5f6a78eb0e..8b1c6b2f96 100644 --- a/crates/pathfinder/src/sync/track.rs +++ b/crates/pathfinder/src/sync/track.rs @@ -755,7 +755,7 @@ impl ProcessStage for StoreBlock { type Input = BlockData; type Output = (); - fn map(&mut self, input: Self::Input) -> Result { + fn map(&mut self, peer: &PeerId, input: Self::Input) -> Result { let BlockData { header: SignedBlockHeader { header, signature }, mut events, @@ -831,8 +831,7 @@ impl ProcessStage for StoreBlock { actual_class_commitment=%class_commitment, actual_state_commitment=%state_commitment, "State root mismatch"); - // TODO: remove placeholder - return Err(SyncError::StateRootMismatch(PeerId::random())); + return Err(SyncError::StateRootMismatch(*peer)); } db.update_storage_and_class_commitments(block_number, storage_commitment, class_commitment) diff --git a/crates/pathfinder/src/sync/transactions.rs b/crates/pathfinder/src/sync/transactions.rs index 7684629bc3..75ab70ae49 100644 --- a/crates/pathfinder/src/sync/transactions.rs +++ b/crates/pathfinder/src/sync/transactions.rs @@ -85,7 +85,7 @@ impl ProcessStage for CalculateHashes { ); type Output = UnverifiedTransactions; - fn map(&mut self, input: Self::Input) -> Result { + fn map(&mut self, _: &PeerId, input: Self::Input) -> Result { use rayon::prelude::*; // TODO remove the placeholder let peer = &PeerId::random(); @@ -138,7 +138,11 @@ impl ProcessStage for FetchCommitmentFromDb { type Input = (T, BlockNumber); type Output = (T, BlockNumber, StarknetVersion, TransactionCommitment); - fn map(&mut self, (data, block_number): Self::Input) -> Result { + fn map( + &mut self, + _: &PeerId, + (data, block_number): Self::Input, + ) -> Result { let mut db = self .db .transaction() @@ -164,7 +168,7 @@ impl ProcessStage for VerifyCommitment { type Input = UnverifiedTransactions; type Output = Vec<(Transaction, Receipt)>; - fn map(&mut self, transactions: Self::Input) -> Result { + fn map(&mut self, peer: &PeerId, transactions: Self::Input) -> Result { let UnverifiedTransactions { expected_commitment, transactions, @@ -178,9 +182,7 @@ impl ProcessStage for VerifyCommitment { .context("Computing transaction commitment")?; if actual != expected_commitment { tracing::debug!(%block_number, %expected_commitment, actual_commitment=%actual, "Transaction commitment mismatch"); - // TODO - // Use the real peer id here - return Err(SyncError::TransactionCommitmentMismatch(PeerId::random())); + return Err(SyncError::TransactionCommitmentMismatch(*peer)); } Ok(transactions) } @@ -205,7 +207,7 @@ impl ProcessStage for Store { type Input = Vec<(Transaction, Receipt)>; type Output = BlockNumber; - fn map(&mut self, transactions: Self::Input) -> Result { + fn map(&mut self, _: &PeerId, transactions: Self::Input) -> Result { let mut db = self .db .transaction() From 77fdcb9610eeeeb2bf82b509397743f3ee12a234 Mon Sep 17 00:00:00 2001 From: Krzysztof Lis Date: Mon, 4 Nov 2024 18:27:51 +0100 Subject: [PATCH 237/282] refactor(sync): use SyncError directly where possible --- crates/pathfinder/src/sync.rs | 2 +- .../pathfinder/src/sync/class_definitions.rs | 34 ++++++++++--------- crates/pathfinder/src/sync/events.rs | 4 +-- crates/pathfinder/src/sync/state_updates.rs | 33 +++++++----------- 4 files changed, 34 insertions(+), 39 deletions(-) diff --git a/crates/pathfinder/src/sync.rs b/crates/pathfinder/src/sync.rs index e30e47e5e4..ad927c7e07 100644 --- a/crates/pathfinder/src/sync.rs +++ b/crates/pathfinder/src/sync.rs @@ -65,7 +65,7 @@ impl Sync { async fn handle_recoverable_error(&self, err: &error::SyncError) { // TODO - tracing::debug!(?err, "Log and punish as appropriate"); + tracing::debug!(%err, "Log and punish as appropriate"); } /// Retry forever until a valid L1 checkpoint is retrieved diff --git a/crates/pathfinder/src/sync/class_definitions.rs b/crates/pathfinder/src/sync/class_definitions.rs index 7d50745581..2a8119d062 100644 --- a/crates/pathfinder/src/sync/class_definitions.rs +++ b/crates/pathfinder/src/sync/class_definitions.rs @@ -285,14 +285,14 @@ impl ProcessStage for VerifyDeclaredAt { } if self.current.block_number != input.block_number { - tracing::debug!(expected_block_number=%self.current.block_number, block_number=%input.block_number, class_hash=%input.hash, "Unexpected class definition"); + tracing::debug!(%peer, expected_block_number=%self.current.block_number, block_number=%input.block_number, class_hash=%input.hash, "Unexpected class definition"); return Err(SyncError::UnexpectedClass(*peer)); } if self.current.classes.remove(&input.hash) { Ok(input) } else { - tracing::debug!(block_number=%input.block_number, class_hash=%input.hash, "Unexpected class definition"); + tracing::debug!(%peer, block_number=%input.block_number, class_hash=%input.hash, "Unexpected class definition"); Err(SyncError::UnexpectedClass(*peer)) } } @@ -353,18 +353,18 @@ pub(super) fn verify_declared_at( return; }; - let res = maybe_class.and_then(|class| { + let res = maybe_class.and_then(|PeerData { peer, data: class }| { // Check if the class is declared at the expected block - if declared_at != class.data.block_number { - tracing::error!(%declared_at, %class.data.block_number, %class.data.hash, ?declared, "Unexpected class 1"); - return Err(SyncError::UnexpectedClass(class.peer)); + if declared_at != class.block_number { + tracing::debug!(%peer, expected_block_number=%declared_at, block_number=%class.block_number, %class.hash, "Unexpected class definition"); + return Err(SyncError::UnexpectedClass(peer)); } - if declared.remove(&class.data.hash) { - Ok(class) + if declared.remove(&class.hash) { + Ok(PeerData::new(peer, class)) } else { - tracing::error!(%declared_at, %class.data.block_number, %class.data.hash, ?declared, "Unexpected class 2"); - Err(SyncError::UnexpectedClass(class.peer)) + tracing::debug!(%peer, block_number=%class.block_number, %class.hash, "Unexpected class definition"); + Err(SyncError::UnexpectedClass(peer)) } }); let bail = res.is_err(); @@ -530,8 +530,7 @@ impl ProcessStage for CompileSierraToCas type Output = CompiledClass; fn map(&mut self, _: &PeerId, input: Self::Input) -> Result { - let compiled = compile_or_fetch_impl(input, &self.fgw, &self.tokio_handle) - .map_err(|_| SyncError::FetchingCasmFailed)?; + let compiled = compile_or_fetch_impl(input, &self.fgw, &self.tokio_handle)?; Ok(compiled) } } @@ -550,8 +549,7 @@ pub(super) async fn compile_sierra_to_casm_or_fetch< .into_par_iter() .map(|x| { let PeerData { peer, data } = x; - let compiled = compile_or_fetch_impl(data, &fgw, &tokio_handle) - .map_err(|_| SyncError::FetchingCasmFailed)?; + let compiled = compile_or_fetch_impl(data, &fgw, &tokio_handle)?; Ok(PeerData::new(peer, compiled)) }) .collect::>, SyncError>>(); @@ -564,7 +562,7 @@ fn compile_or_fetch_impl( class: Class, fgw: &SequencerClient, tokio_handle: &tokio::runtime::Handle, -) -> Result { +) -> Result { let Class { block_number, hash, @@ -583,7 +581,11 @@ fn compile_or_fetch_impl( // that the class is declared and exists so if the gateway responds with an // error we should restart the sync and retry later. Err(_) => tokio_handle - .block_on(fgw.pending_casm_by_hash(hash))? + .block_on(fgw.pending_casm_by_hash(hash)) + .map_err(|error| { + tracing::debug!(%block_number, class_hash=%hash, %error, "Fetching casm from feeder gateway failed"); + SyncError::FetchingCasmFailed + })? .to_vec(), }; diff --git a/crates/pathfinder/src/sync/events.rs b/crates/pathfinder/src/sync/events.rs index 80ca0a3b15..2c2885a760 100644 --- a/crates/pathfinder/src/sync/events.rs +++ b/crates/pathfinder/src/sync/events.rs @@ -167,13 +167,13 @@ impl ProcessStage for VerifyCommitment { } } if ordered_events.len() != events.len() { - tracing::debug!(expected=%ordered_events.len(), actual=%events.len(), "Number of events received does not match expected number of events"); + tracing::debug!(%peer, expected=%ordered_events.len(), actual=%events.len(), "Number of events received does not match expected number of events"); return Err(SyncError::EventsTransactionsMismatch(*peer)); } let actual = calculate_event_commitment(&ordered_events, version.max(StarknetVersion::V_0_13_2))?; if actual != event_commitment { - tracing::debug!(expected=%event_commitment, actual=%actual, "Event commitment mismatch"); + tracing::debug!(%peer, expected=%event_commitment, actual=%actual, "Event commitment mismatch"); return Err(SyncError::EventCommitmentMismatch(*peer)); } Ok(events) diff --git a/crates/pathfinder/src/sync/state_updates.rs b/crates/pathfinder/src/sync/state_updates.rs index e82544e6bb..d722e50d3f 100644 --- a/crates/pathfinder/src/sync/state_updates.rs +++ b/crates/pathfinder/src/sync/state_updates.rs @@ -282,11 +282,14 @@ pub async fn batch_update_starknet_state( let state_update_ref: StateUpdateRef<'_> = (&merged).into(); - update_starknet_state_impl(db, state_update_ref, verify_tree_hashes, tail, storage) - .map_err(|e| match e { - UpdateStarknetStateError::StateRootMismatch => SyncError::StateRootMismatch(peer), - UpdateStarknetStateError::DBError(error) => SyncError::Fatal(Arc::new(error)), - })?; + update_starknet_state_impl( + &peer, + db, + state_update_ref, + verify_tree_hashes, + tail, + storage, + )?; Ok(PeerData::new(peer, tail)) }) @@ -319,16 +322,13 @@ impl ProcessStage for UpdateStarknetState { .context("Inserting state update data")?; update_starknet_state_impl( + peer, db, (&state_update).into(), self.verify_tree_hashes, tail, self.storage.clone(), - ) - .map_err(|e| match e { - UpdateStarknetStateError::StateRootMismatch => SyncError::StateRootMismatch(*peer), - UpdateStarknetStateError::DBError(error) => SyncError::Fatal(Arc::new(error)), - })?; + )?; self.current_block += 1; @@ -336,21 +336,14 @@ impl ProcessStage for UpdateStarknetState { } } -#[derive(Debug, thiserror::Error)] -enum UpdateStarknetStateError { - #[error("State root mismatch")] - StateRootMismatch, - #[error(transparent)] - DBError(#[from] anyhow::Error), -} - fn update_starknet_state_impl( + peer: &PeerId, db: pathfinder_storage::Transaction<'_>, state_update_ref: StateUpdateRef<'_>, verify_tree_hashes: bool, tail: BlockNumber, storage: Storage, -) -> Result<(), UpdateStarknetStateError> { +) -> Result<(), SyncError> { let (storage_commitment, class_commitment) = update_starknet_state( &db, state_update_ref, @@ -372,7 +365,7 @@ fn update_starknet_state_impl( actual_state_commitment=%state_commitment, %expected_state_commitment, "State root mismatch"); - return Err(UpdateStarknetStateError::StateRootMismatch); + return Err(SyncError::StateRootMismatch(*peer)); } db.update_storage_and_class_commitments(tail, storage_commitment, class_commitment) .context("Updating storage and class commitments")?; From 6811fb3cbea873b50bb6bbf67af70a79fb48b92e Mon Sep 17 00:00:00 2001 From: Krzysztof Lis Date: Tue, 5 Nov 2024 18:16:44 +0100 Subject: [PATCH 238/282] feat(sync): add missing logs --- crates/pathfinder/src/sync.rs | 8 ++++---- crates/pathfinder/src/sync/checkpoint.rs | 2 +- crates/pathfinder/src/sync/class_definitions.rs | 6 +++--- crates/pathfinder/src/sync/events.rs | 1 + crates/pathfinder/src/sync/headers.rs | 14 ++++++++++---- crates/pathfinder/src/sync/state_updates.rs | 3 ++- crates/pathfinder/src/sync/transactions.rs | 2 +- 7 files changed, 22 insertions(+), 14 deletions(-) diff --git a/crates/pathfinder/src/sync.rs b/crates/pathfinder/src/sync.rs index ad927c7e07..8a5c696094 100644 --- a/crates/pathfinder/src/sync.rs +++ b/crates/pathfinder/src/sync.rs @@ -127,11 +127,11 @@ impl Sync { continue_from } Err(SyncError::Fatal(mut error)) => { - tracing::error!(?error, "Stopping checkpoint sync"); + tracing::error!(%error, "Stopping checkpoint sync"); return Err(error.take_or_deep_clone()); } Err(error) => { - tracing::debug!(?error, "Restarting checkpoint sync"); + tracing::debug!(%error, "Restarting checkpoint sync"); self.handle_recoverable_error(&error).await; continue; } @@ -183,12 +183,12 @@ impl Sync { match result { Ok(_) => tracing::debug!("Restarting track sync: unexpected end of Block stream"), Err(SyncError::Fatal(mut error)) => { - tracing::error!(?error, "Stopping track sync"); + tracing::error!(%error, "Stopping track sync"); use pathfinder_common::error::AnyhowExt; return Err(error.take_or_deep_clone()); } Err(error) => { - tracing::debug!(?error, "Restarting track sync"); + tracing::debug!(%error, "Restarting track sync"); self.handle_recoverable_error(&error).await; } } diff --git a/crates/pathfinder/src/sync/checkpoint.rs b/crates/pathfinder/src/sync/checkpoint.rs index 48a82b3089..3859cc33c8 100644 --- a/crates/pathfinder/src/sync/checkpoint.rs +++ b/crates/pathfinder/src/sync/checkpoint.rs @@ -370,7 +370,7 @@ async fn handle_state_diff_stream( .and_then(|x| { state_updates::batch_update_starknet_state(storage.clone(), verify_tree_hashes, x) }) - .inspect_ok(|x| tracing::debug!(tail=%x.data, "State diff synced")) + .inspect_ok(|x| tracing::debug!(tail=%x.data, "State diffs chunk synced")) .try_fold((), |_, _| std::future::ready(Ok(()))) .await } diff --git a/crates/pathfinder/src/sync/class_definitions.rs b/crates/pathfinder/src/sync/class_definitions.rs index 2a8119d062..47df093fd9 100644 --- a/crates/pathfinder/src/sync/class_definitions.rs +++ b/crates/pathfinder/src/sync/class_definitions.rs @@ -714,7 +714,7 @@ impl ProcessStage for VerifyClassHashes { match class.definition { CompiledClassDefinition::Cairo(_) => { if !declared_classes.cairo.remove(&class.hash) { - tracing::debug!(class_hash=%class.hash, "Class hash not found in declared classes"); + tracing::debug!(%peer, block_number=%class.block_number, class_hash=%class.hash, "Class hash not found in declared classes"); return Err(SyncError::ClassDefinitionsDeclarationsMismatch(*peer)); } } @@ -724,7 +724,7 @@ impl ProcessStage for VerifyClassHashes { .sierra .remove(&hash) .ok_or_else(|| { - tracing::debug!(class_hash=%class.hash, "Class hash not found in declared classes"); + tracing::debug!(%peer, block_number=%class.block_number, class_hash=%class.hash, "Class hash not found in declared classes"); SyncError::ClassDefinitionsDeclarationsMismatch(*peer) })?; } @@ -743,7 +743,7 @@ impl ProcessStage for VerifyClassHashes { .map(|casm_hash| ClassHash(casm_hash.0)), ) .collect(); - tracing::trace!(?missing, "Expected class definitions are missing"); + tracing::trace!(%peer, ?missing, "Expected class definitions are missing"); Err(SyncError::ClassDefinitionsDeclarationsMismatch(*peer)) } } diff --git a/crates/pathfinder/src/sync/events.rs b/crates/pathfinder/src/sync/events.rs index 2c2885a760..6e8c52a04a 100644 --- a/crates/pathfinder/src/sync/events.rs +++ b/crates/pathfinder/src/sync/events.rs @@ -96,6 +96,7 @@ pub(super) async fn verify_commitment( ) .context("Calculating commitment")?; if computed != header.event_commitment { + tracing::debug!(%peer, %block_number, expected_commitment=%header.event_commitment, actual_commitment=%computed, "Event commitment mismatch"); return Err(SyncError::EventCommitmentMismatch(peer)); } Ok(events) diff --git a/crates/pathfinder/src/sync/headers.rs b/crates/pathfinder/src/sync/headers.rs index 2e0d0b8422..5fbab9cdb5 100644 --- a/crates/pathfinder/src/sync/headers.rs +++ b/crates/pathfinder/src/sync/headers.rs @@ -210,7 +210,10 @@ impl ProcessStage for BackwardContinuity { type Output = SignedBlockHeader; fn map(&mut self, peer: &PeerId, input: Self::Input) -> Result { - let number = self.number.ok_or(SyncError::Discontinuity(*peer))?; + let number = self.number.ok_or_else(|| { + tracing::debug!(actual_block_number=%input.header.number, actual_block_hash=%input.header.hash, "Block chain discontinuity, no block expected before genesis"); + SyncError::Discontinuity(*peer) + })?; if input.header.number != number || input.header.hash != self.hash { tracing::debug!(expected_block_number=%number, actual_block_number=%input.header.number, expected_block_hash=%self.hash, actual_block_hash=%input.header.hash, "Block chain discontinuity"); @@ -235,9 +238,9 @@ impl ProcessStage for VerifyHashAndSignature { } if !self.verify_signature(&input) { - // TODO: make this an error once state diff commitments and signatures are fixed - // on the feeder gateway return Err(SyncError2::BadHeaderSignature); - tracing::debug!(header=?input.header, "Header signature verification failed"); + // TODO: make this an error once state diff commitments and + // signatures are fixed on the feeder gateway return + // Err(SyncError2::BadHeaderSignature); } Ok(input) @@ -306,6 +309,9 @@ impl VerifyHashAndSignature { header .signature .verify(self.public_key, header.header.hash) + .inspect_err( + |error| tracing::debug!(%error, ?header, "Header signature verification failed"), + ) .is_ok() } } diff --git a/crates/pathfinder/src/sync/state_updates.rs b/crates/pathfinder/src/sync/state_updates.rs index d722e50d3f..194fd01e20 100644 --- a/crates/pathfinder/src/sync/state_updates.rs +++ b/crates/pathfinder/src/sync/state_updates.rs @@ -136,7 +136,7 @@ impl ProcessStage for VerifyCommitment { let actual_commitment = state_diff.compute_state_diff_commitment(); if actual_commitment != expected_commitment { - tracing::debug!(%block_number, %expected_commitment, %actual_commitment, "State diff commitment mismatch"); + tracing::debug!(%peer, %block_number, %expected_commitment, %actual_commitment, "State diff commitment mismatch"); return Err(SyncError::StateDiffCommitmentMismatch(*peer)); } @@ -359,6 +359,7 @@ fn update_starknet_state_impl( .context("State commitment not found")?; if state_commitment != expected_state_commitment { tracing::debug!( + %peer, %tail, actual_storage_commitment=%storage_commitment, actual_class_commitment=%class_commitment, diff --git a/crates/pathfinder/src/sync/transactions.rs b/crates/pathfinder/src/sync/transactions.rs index 75ab70ae49..1fb17159c7 100644 --- a/crates/pathfinder/src/sync/transactions.rs +++ b/crates/pathfinder/src/sync/transactions.rs @@ -181,7 +181,7 @@ impl ProcessStage for VerifyCommitment { let actual = calculate_transaction_commitment(&txs, version.max(StarknetVersion::V_0_13_2)) .context("Computing transaction commitment")?; if actual != expected_commitment { - tracing::debug!(%block_number, %expected_commitment, actual_commitment=%actual, "Transaction commitment mismatch"); + tracing::debug!(%peer, %block_number, %expected_commitment, actual_commitment=%actual, "Transaction commitment mismatch"); return Err(SyncError::TransactionCommitmentMismatch(*peer)); } Ok(transactions) From 41a238c6f1fc50d9b4ddd8c7f4c7466930ba5f21 Mon Sep 17 00:00:00 2001 From: Krzysztof Lis Date: Tue, 5 Nov 2024 19:07:56 +0100 Subject: [PATCH 239/282] test(pathfinder/sync/stream): update tests --- crates/p2p/src/peer_data.rs | 6 ----- crates/pathfinder/src/sync/stream.rs | 40 +++++++++++++++++----------- 2 files changed, 25 insertions(+), 21 deletions(-) diff --git a/crates/p2p/src/peer_data.rs b/crates/p2p/src/peer_data.rs index e543d1c71e..fd2eb58ae9 100644 --- a/crates/p2p/src/peer_data.rs +++ b/crates/p2p/src/peer_data.rs @@ -13,12 +13,6 @@ impl PeerData { Self { peer, data } } - pub fn from_result(peer: PeerId, result: Result) -> Result, PeerData> { - result - .map(|x| Self::new(peer, x)) - .map_err(|e| PeerData::::new(peer, e)) - } - pub fn for_tests(data: T) -> Self { Self { peer: PeerId::random(), diff --git a/crates/pathfinder/src/sync/stream.rs b/crates/pathfinder/src/sync/stream.rs index e10e3dd2d4..1c4bfd339a 100644 --- a/crates/pathfinder/src/sync/stream.rs +++ b/crates/pathfinder/src/sync/stream.rs @@ -334,24 +334,30 @@ where } } -// TODO -#[cfg(test_FIXME)] +#[cfg(test)] mod tests { + use std::sync::LazyLock; + use super::*; + pub fn peer_id() -> PeerId { + static PEER_ID: LazyLock = LazyLock::new(|| PeerId::random()); + *PEER_ID + } + #[rstest::rstest] #[case::all_ok( vec![Ok(0), Ok(1), Ok(2)], vec![Ok(0), Ok(1), Ok(2)] )] #[case::short_circuit_on_error( - vec![Ok(0), Ok(1), Err(SyncError2::BadBlockHash), Ok(2)], - vec![Ok(0), Ok(1), Err(SyncError2::BadBlockHash)], + vec![Ok(0), Ok(1), Err(SyncError::BadBlockHash(peer_id())), Ok(2)], + vec![Ok(0), Ok(1), Err(SyncError::BadBlockHash(peer_id()))], )] #[tokio::test] async fn input_stream( - #[case] input: Vec>, - #[case] expected: Vec>, + #[case] input: Vec>, + #[case] expected: Vec>, ) { struct NoOp; impl ProcessStage for NoOp { @@ -359,7 +365,7 @@ mod tests { type Input = u8; type Output = u8; - fn map(&mut self, input: Self::Input) -> Result { + fn map(&mut self, _: &PeerId, input: Self::Input) -> Result { Ok(input) } } @@ -369,11 +375,11 @@ mod tests { let input = SyncReceiver::iter( input .into_iter() - .map(move |x| PeerData::from_result(peer, x)), + .map(move |x| x.map(|y| PeerData::new(peer, y))), ); let expected = expected .into_iter() - .map(|x| PeerData::from_result(peer, x)) + .map(|x| x.map(|y| PeerData::new(peer, y))) .collect::>(); let stage = NoOp {}; @@ -392,21 +398,25 @@ mod tests { type Input = u8; type Output = u8; - fn map(&mut self, input: Self::Input) -> Result { + fn map( + &mut self, + peer: &PeerId, + input: Self::Input, + ) -> Result { if self.0 == 0 { self.0 = 1; Ok(input) } else { - Err(SyncError2::BadBlockHash) + Err(SyncError::BadBlockHash(*peer)) } } } let peer = PeerId::random(); - let input = (0..10).map(move |x| PeerData::from_result(peer, Ok(x))); + let input = (0..10).map(move |x| Ok(PeerData::new(peer, x))); let expected = vec![ - Ok(PeerData::new(peer, 0)), - Err(PeerData::new(peer, SyncError2::BadBlockHash)), + Ok(PeerData::new(peer, 0u8)), + Err(SyncError::BadBlockHash(peer)), ]; let stage = OnlyOnce(0); @@ -422,7 +432,7 @@ mod tests { #[tokio::test] async fn short_circuit_on_source_error() { let ok = Ok(PeerData::for_tests(0)); - let err = Err(PeerData::for_tests(SyncError2::BadBlockHash)); + let err = Err(SyncError::BadBlockHash(PeerId::random())); let ok_unprocessed = Ok(PeerData::for_tests(1)); let input = vec![ok.clone(), err.clone(), ok_unprocessed]; From 24b1f1e81b31ec453392612522392613344d270a Mon Sep 17 00:00:00 2001 From: Krzysztof Lis Date: Tue, 5 Nov 2024 19:30:20 +0100 Subject: [PATCH 240/282] test(pathfinder/sync/checkpoint): update tests --- crates/pathfinder/src/sync/checkpoint.rs | 61 ++++++++++-------------- 1 file changed, 25 insertions(+), 36 deletions(-) diff --git a/crates/pathfinder/src/sync/checkpoint.rs b/crates/pathfinder/src/sync/checkpoint.rs index 3859cc33c8..523360efdb 100644 --- a/crates/pathfinder/src/sync/checkpoint.rs +++ b/crates/pathfinder/src/sync/checkpoint.rs @@ -692,8 +692,7 @@ async fn persist_anchor(storage: Storage, anchor: EthereumStateUpdate) -> anyhow .context("Joining blocking task")? } -// FIXME P2P -#[cfg(test_FIXME)] +#[cfg(test)] mod tests { use super::*; @@ -865,7 +864,6 @@ mod tests { pretty_assertions_sorted::assert_eq!(expected_headers, actual_headers); } - #[tokio::test] async fn discontinuity() { let Setup { @@ -1185,9 +1183,7 @@ mod tests { async fn stream_failure() { assert_matches!( handle_transaction_stream( - stream::once(std::future::ready(Err(PeerData::for_tests( - anyhow::anyhow!("") - )))), + stream::once(std::future::ready(Err(anyhow::anyhow!("")))), StorageBuilder::in_memory().unwrap(), ChainId::SEPOLIA_TESTNET, BlockNumber::GENESIS, @@ -1243,7 +1239,7 @@ mod tests { let streamed_state_diffs = blocks .iter() .map(|block| { - Result::, PeerData<_>>::Ok(PeerData::for_tests(( + Result::, _>::Ok(PeerData::for_tests(( block.state_update.clone().into(), block.header.header.number, ))) @@ -1354,9 +1350,7 @@ mod tests { async fn stream_failure() { assert_matches!( handle_state_diff_stream( - stream::once(std::future::ready(Err(PeerData::for_tests( - anyhow::anyhow!("") - )))), + stream::once(std::future::ready(Err(anyhow::anyhow!("")))), StorageBuilder::in_memory().unwrap(), BlockNumber::GENESIS, false, @@ -1469,7 +1463,7 @@ mod tests { } struct Setup { - pub streamed_classes: Vec, PeerData>>, + pub streamed_classes: Vec, anyhow::Error>>, pub declared_classes: DeclaredClasses, pub expected_defs: HashMap>, pub storage: Storage, @@ -1632,14 +1626,14 @@ mod tests { let expected_peer_id = data.peer; assert_matches!( - handle_class_stream( - stream::once(std::future::ready(Ok(data))), - storage, - FakeFgw, - Faker.fake::().to_stream(), - ) - .await, - Err(SyncError::BadClassLayout(x)) => assert_eq!(x, expected_peer_id)); + handle_class_stream( + stream::once(std::future::ready(Ok(data))), + storage, + FakeFgw, + Faker.fake::().to_stream(), + ) + .await, + Err(SyncError::BadClassLayout(x)) => assert_eq!(x, expected_peer_id)); } #[tokio::test] @@ -1663,23 +1657,21 @@ mod tests { let expected_peer_id = streamed_classes.last().unwrap().as_ref().unwrap().peer; assert_matches!( - handle_class_stream( - stream::iter(streamed_classes), - storage, - FakeFgw, - declared_classes.to_stream(), - ) - .await, - Err(SyncError::UnexpectedClass(x)) => assert_eq!(x, expected_peer_id)); + handle_class_stream( + stream::iter(streamed_classes), + storage, + FakeFgw, + declared_classes.to_stream(), + ) + .await, + Err(SyncError::UnexpectedClass(x)) => assert_eq!(x, expected_peer_id)); } #[tokio::test] async fn stream_failure() { assert_matches!( handle_class_stream( - stream::once(std::future::ready(Err(PeerData::for_tests( - anyhow::anyhow!("") - )))), + stream::once(std::future::ready(Err(anyhow::anyhow!("")))), StorageBuilder::in_memory().unwrap(), FakeFgw, Faker.fake::().to_stream(), @@ -1706,8 +1698,7 @@ mod tests { use crate::state::block_hash::calculate_event_commitment; struct Setup { - pub streamed_events: - Vec, PeerData>>, + pub streamed_events: Vec, anyhow::Error>>, pub expected_events: Vec)>>, pub storage: Storage, } @@ -1827,9 +1818,7 @@ mod tests { async fn stream_failure() { assert_matches::assert_matches!( handle_event_stream( - stream::once(std::future::ready(Err(PeerData::for_tests( - anyhow::anyhow!("") - )))), + stream::once(std::future::ready(Err(anyhow::anyhow!("")))), StorageBuilder::in_memory().unwrap() ) .await @@ -1849,6 +1838,6 @@ mod tests { .unwrap_err(), SyncError::Fatal(_) ); - } + } /* */ } } From b5b4158f759555fc351e8e3a83772c3e875c161c Mon Sep 17 00:00:00 2001 From: Krzysztof Lis Date: Tue, 5 Nov 2024 19:36:22 +0100 Subject: [PATCH 241/282] chore: clippy --- crates/pathfinder/src/sync/headers.rs | 2 +- crates/pathfinder/src/sync/stream.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/pathfinder/src/sync/headers.rs b/crates/pathfinder/src/sync/headers.rs index 5fbab9cdb5..5e78486b48 100644 --- a/crates/pathfinder/src/sync/headers.rs +++ b/crates/pathfinder/src/sync/headers.rs @@ -234,7 +234,7 @@ impl ProcessStage for VerifyHashAndSignature { fn map(&mut self, peer: &PeerId, input: Self::Input) -> Result { if !self.verify_hash(&input.header) { - return Err(SyncError::BadBlockHash((*peer))); + return Err(SyncError::BadBlockHash(*peer)); } if !self.verify_signature(&input) { diff --git a/crates/pathfinder/src/sync/stream.rs b/crates/pathfinder/src/sync/stream.rs index 1c4bfd339a..ad68ec1e1c 100644 --- a/crates/pathfinder/src/sync/stream.rs +++ b/crates/pathfinder/src/sync/stream.rs @@ -341,7 +341,7 @@ mod tests { use super::*; pub fn peer_id() -> PeerId { - static PEER_ID: LazyLock = LazyLock::new(|| PeerId::random()); + static PEER_ID: LazyLock = LazyLock::new(PeerId::random); *PEER_ID } From 307117e9ae7310624cf002a8189eef526f2bf1fa Mon Sep 17 00:00:00 2001 From: Krzysztof Lis Date: Tue, 5 Nov 2024 20:12:19 +0100 Subject: [PATCH 242/282] chore: remove outdated todos and a stray empty comment --- crates/pathfinder/src/sync/checkpoint.rs | 5 +---- crates/pathfinder/src/sync/track.rs | 6 ++---- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/crates/pathfinder/src/sync/checkpoint.rs b/crates/pathfinder/src/sync/checkpoint.rs index 523360efdb..14179b7e7c 100644 --- a/crates/pathfinder/src/sync/checkpoint.rs +++ b/crates/pathfinder/src/sync/checkpoint.rs @@ -333,9 +333,6 @@ async fn handle_transaction_stream( chain_id: ChainId, start: BlockNumber, ) -> Result<(), SyncError> { - // TODO - // stream errors should not carry PeerId as only fatal errors are reported by - // the stream and those stem from DB errors which are fatal. Source::from_stream(stream.map_err(Into::into)) .spawn() .pipe( @@ -1838,6 +1835,6 @@ mod tests { .unwrap_err(), SyncError::Fatal(_) ); - } /* */ + } } } diff --git a/crates/pathfinder/src/sync/track.rs b/crates/pathfinder/src/sync/track.rs index 8b1c6b2f96..8d4e739dd7 100644 --- a/crates/pathfinder/src/sync/track.rs +++ b/crates/pathfinder/src/sync/track.rs @@ -549,8 +549,7 @@ impl

StateDiffSource

{ let _ = tx.send(Err(SyncError::IncorrectStateDiffCount(peer))).await; return; } - Err(StateDiffsError::ResponseStreamFailure(peer, _error)) => { - // TODO what about the _error + Err(StateDiffsError::ResponseStreamFailure(peer, _)) => { let _ = tx.send(Err(SyncError::InvalidDto(peer))).await; return; } @@ -620,8 +619,7 @@ impl

ClassSource

{ ClassDefinitionsError::SierraDefinitionError(peer) => { SyncError::SierraDefinitionError(peer) } - ClassDefinitionsError::ResponseStreamFailure(peer, _error) => { - // TODO what about the _error + ClassDefinitionsError::ResponseStreamFailure(peer, _) => { SyncError::InvalidDto(peer) } }; From b8e417c9f9387b98c6994eeb2722e2bcd971f96b Mon Sep 17 00:00:00 2001 From: Krzysztof Lis Date: Thu, 7 Nov 2024 15:31:24 +0100 Subject: [PATCH 243/282] doc: fix typos --- crates/p2p/src/client/peer_agnostic.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/p2p/src/client/peer_agnostic.rs b/crates/p2p/src/client/peer_agnostic.rs index e331f3775f..56cc0d3c39 100644 --- a/crates/p2p/src/client/peer_agnostic.rs +++ b/crates/p2p/src/client/peer_agnostic.rs @@ -1560,10 +1560,10 @@ async fn try_next( ) -> Result { match count_stream.next().await { Some(Ok(cnt)) => Ok(cnt), - // This is a non-recovarable error, because "Counter" streams fail only if the underlying + // This is a non-recoverable error, because "Counter" streams fail only if the underlying // database fails. Some(Err(e)) => Err(e), - // This is a non-recovarable error, because we expect all the necessary headers that are the + // This is a non-recoverable error, because we expect all the necessary headers that are the // source of the stream to be in the database. None => Err(anyhow::anyhow!("Count stream terminated prematurely")), } From 98340ece8dddeda751ffa9cd6cf700d23c4c9818 Mon Sep 17 00:00:00 2001 From: Krzysztof Lis Date: Thu, 7 Nov 2024 15:51:06 +0100 Subject: [PATCH 244/282] chore: remove unused import --- crates/pathfinder/src/sync/state_updates.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/pathfinder/src/sync/state_updates.rs b/crates/pathfinder/src/sync/state_updates.rs index 194fd01e20..152c754a67 100644 --- a/crates/pathfinder/src/sync/state_updates.rs +++ b/crates/pathfinder/src/sync/state_updates.rs @@ -14,7 +14,6 @@ use pathfinder_common::state_update::{ SystemContractUpdate, }; use pathfinder_common::{ - class_hash, BlockHash, BlockHeader, BlockNumber, From 939f9d094ccfcdc99b93c457e9eb05b94022d634 Mon Sep 17 00:00:00 2001 From: Krisztian Kovacs Date: Fri, 8 Nov 2024 11:28:15 +0100 Subject: [PATCH 245/282] fix(rpc/v08): fix EXECUTION_RESOURCES serialization in transaction traces EXECUTION_RESOURCES should contain gas info exclusively now. --- crates/executor/src/simulate.rs | 3 +++ crates/executor/src/types.rs | 3 +++ crates/rpc/src/dto/simulation.rs | 25 ++++++++++++----- .../src/method/trace_block_transactions.rs | 27 +++++++++++++++++++ 4 files changed, 51 insertions(+), 7 deletions(-) diff --git a/crates/executor/src/simulate.rs b/crates/executor/src/simulate.rs index 41e273d755..3656002bd8 100644 --- a/crates/executor/src/simulate.rs +++ b/crates/executor/src/simulate.rs @@ -367,6 +367,9 @@ fn to_trace( let execution_resources = ExecutionResources { computation_resources, data_availability, + l1_gas: execution_info.transaction_receipt.gas.l1_gas, + l1_data_gas: execution_info.transaction_receipt.da_gas.l1_data_gas, + l2_gas: 0, }; match transaction_type { diff --git a/crates/executor/src/types.rs b/crates/executor/src/types.rs index a3e19499a3..fb5edb846d 100644 --- a/crates/executor/src/types.rs +++ b/crates/executor/src/types.rs @@ -261,6 +261,9 @@ pub struct ReplacedClass { pub struct ExecutionResources { pub computation_resources: ComputationResources, pub data_availability: DataAvailabilityResources, + pub l1_gas: u128, + pub l1_data_gas: u128, + pub l2_gas: u128, } #[derive(Debug, Default, Clone, Eq, PartialEq)] diff --git a/crates/rpc/src/dto/simulation.rs b/crates/rpc/src/dto/simulation.rs index 8f3c91740f..786dff05af 100644 --- a/crates/rpc/src/dto/simulation.rs +++ b/crates/rpc/src/dto/simulation.rs @@ -461,13 +461,24 @@ impl crate::dto::serialize::SerializeForVersion for ExecutionResources<'_> { &self, serializer: super::serialize::Serializer, ) -> Result { - let mut serializer = serializer.serialize_struct()?; - serializer.flatten(&ComputationResources(&self.0.computation_resources))?; - serializer.serialize_field( - "data_availability", - &DataAvailabilityResources(&self.0.data_availability), - )?; - serializer.end() + match serializer.version { + RpcVersion::V08 => { + let mut serializer = serializer.serialize_struct()?; + serializer.serialize_field("l1_gas", &self.0.l1_gas)?; + serializer.serialize_field("l1_data_gas", &self.0.l1_data_gas)?; + serializer.serialize_field("l2_gas", &self.0.l2_gas)?; + serializer.end() + } + _ => { + let mut serializer = serializer.serialize_struct()?; + serializer.flatten(&ComputationResources(&self.0.computation_resources))?; + serializer.serialize_field( + "data_availability", + &DataAvailabilityResources(&self.0.data_availability), + )?; + serializer.end() + } + } } } diff --git a/crates/rpc/src/method/trace_block_transactions.rs b/crates/rpc/src/method/trace_block_transactions.rs index 5dbea615f0..e21e5c0870 100644 --- a/crates/rpc/src/method/trace_block_transactions.rs +++ b/crates/rpc/src/method/trace_block_transactions.rs @@ -273,10 +273,37 @@ pub(crate) fn map_gateway_trace( .try_into() .unwrap(), }; + let l1_gas = validate_invocation_resources + .total_gas_consumed + .unwrap_or_default() + .l1_gas + + function_invocation_resources + .total_gas_consumed + .unwrap_or_default() + .l1_gas + + fee_transfer_invocation_resources + .total_gas_consumed + .unwrap_or_default() + .l1_gas; + let l1_data_gas = validate_invocation_resources + .total_gas_consumed + .unwrap_or_default() + .l1_data_gas + + function_invocation_resources + .total_gas_consumed + .unwrap_or_default() + .l1_data_gas + + fee_transfer_invocation_resources + .total_gas_consumed + .unwrap_or_default() + .l1_data_gas; let execution_resources = pathfinder_executor::types::ExecutionResources { computation_resources, // These values are not available in the gateway trace. data_availability: Default::default(), + l1_gas, + l1_data_gas, + l2_gas: 0, }; use pathfinder_common::transaction::TransactionVariant; From 38dae82222024ee7f6969e55cc756e8c8644031f Mon Sep 17 00:00:00 2001 From: Krisztian Kovacs Date: Fri, 8 Nov 2024 11:55:22 +0100 Subject: [PATCH 246/282] chore(doc/rpc/v08): update OpenRPC specs --- doc/rpc/v08/starknet_api_openrpc.json | 333 ++++++++++---------- doc/rpc/v08/starknet_executables.json | 40 ++- doc/rpc/v08/starknet_trace_api_openrpc.json | 23 +- doc/rpc/v08/starknet_write_api.json | 2 +- doc/rpc/v08/starknet_ws_api.json | 15 +- 5 files changed, 210 insertions(+), 203 deletions(-) diff --git a/doc/rpc/v08/starknet_api_openrpc.json b/doc/rpc/v08/starknet_api_openrpc.json index 6f1d5e49ff..731ebe9705 100644 --- a/doc/rpc/v08/starknet_api_openrpc.json +++ b/doc/rpc/v08/starknet_api_openrpc.json @@ -1,7 +1,7 @@ { "openrpc": "1.0.0-rc1", "info": { - "version": "0.7.1", + "version": "0.8.0-rc.0", "title": "StarkNet Node API", "license": {} }, @@ -250,7 +250,7 @@ }, { "name": "starknet_getMessagesStatus", - "summary": "Given an l1 tx hash, returns the associated l1_handler tx hashes and statuses for all L1 -> L2 messages sent by the l1 ransaction, ordered by the l1 tx sending order", + "summary": "Given an l1 tx hash, returns the associated l1_handler tx hashes and statuses for all L1 -> L2 messages sent by the l1 transaction, ordered by the l1 tx sending order", "paramStructure": "by-name", "params": [ { @@ -681,7 +681,7 @@ "schema": { "title": "Estimation", "type": "array", - "description": "a sequence of fee estimation where the i'th estimate corresponds to the i'th transaction", + "description": "a sequence of fee estimatione where the i'th estimate corresponds to the i'th transaction", "items": { "$ref": "#/components/schemas/FEE_ESTIMATE" } @@ -907,141 +907,136 @@ ] }, { - "name": "starknet_getStorageProof", - "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": "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", - "required": false, - "schema": { - "type": "array", - "items": { - "type": "object", - "properties": { - "contract_address": { - "$ref": "#/components/schemas/ADDRESS" - }, - "storage_keys": { - "type": "array", - "items": { - "$ref": "#/components/schemas/FELT" - } - } - }, - "required": ["contract_address", "storage_keys"] - } - } + "name": "starknet_getStorageProof", + "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": "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" } - ], - "result": { - "name": "result", - "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 effectively proving non-membership", + } + }, + { + "name": "contract_addresses", + "description": "a list of contracts for which we want to prove membership in the global state trie", + "required": false, "schema": { - "type": "object", - "properties": { - "classes_proof": { - "$ref": "#/components/schemas/NODE_HASH_TO_NODE_MAPPING" - }, - "contracts_proof": { - "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": { - "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"] - } - } + "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" }, - "required": ["nodes", "contract_leaves_data"] - }, - "contracts_storage_proofs": { - "type": "array", - "items": { - "$ref": "#/components/schemas/NODE_HASH_TO_NODE_MAPPING" + "storage_keys": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FELT" + } } }, - "global_roots": { - "type": "object", - "properties": { - "contracts_tree_root": { + "required": ["contract_address", "storage_keys"] + } + } + } + ], + "result": { + "name": "result", + "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": { + "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": { + "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" - }, - "classes_tree_root": { + }, + "class_hash": { "$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": ["nonce", "class_hash"] + } + } + }, + "required": ["nodes", "contract_leaves_data"] }, - "required": [ - "classes_proof", - "contracts_proof", - "contracts_storage_proofs", - "global_roots" - ] - }, - "errors": [ - { - "$ref": "#/components/errors/BLOCK_NOT_FOUND" + "contracts_storage_proofs": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NODE_HASH_TO_NODE_MAPPING" + } }, - { - "$ref": "#/components/errors/STORAGE_PROOF_NOT_SUPPORTED" + "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", "global_roots"] } - } + }, + "errors": [ + { + "$ref": "#/components/errors/BLOCK_NOT_FOUND" + }, + { + "$ref": "#/components/errors/STORAGE_PROOF_NOT_SUPPORTED" + } + ] + } ], "components": { "contentDescriptors": {}, @@ -1193,7 +1188,6 @@ "title": "event keys", "description": "The keys to filter over", "$ref": "#/components/schemas/EVENT_KEYS" - } }, "required": [] @@ -1636,6 +1630,7 @@ "timestamp", "sequencer_address", "l1_gas_price", + "l2_gas_price", "l1_data_gas_price", "l1_da_mode", "starknet_version" @@ -1666,6 +1661,11 @@ "description": "The price of l1 gas in the block", "$ref": "#/components/schemas/RESOURCE_PRICE" }, + "l2_gas_price": { + "title": "L2 gas price", + "description": "The price of l2 gas in the block", + "$ref": "#/components/schemas/RESOURCE_PRICE" + }, "l1_data_gas_price": { "title": "L1 data gas price", "description": "The price of l1 data gas in the block", @@ -1688,6 +1688,7 @@ "timestamp", "sequencer_address", "l1_gas_price", + "l2_gas_price", "l1_data_gas_price", "l1_da_mode", "starknet_version" @@ -3172,9 +3173,9 @@ "$ref": "#/components/schemas/TXN_EXECUTION_STATUS" }, "failure_reason": { - "title": "failure reason", - "description": "the failure reason, only appears if finality_status is REJECTED or execution_status is REVERTED", - "type": "string" + "title": "failure reason", + "description": "the failure reason, only appears if finality_status is REJECTED or execution_status is REVERTED", + "type": "string" } }, "required": ["finality_status"] @@ -3616,7 +3617,7 @@ "DA_MODE": { "title": "DA mode", "type": "string", - "description": "Specifies a storage domain in Starknet. Each domain has different guarantees regarding availability", + "description": "Specifies a storage domain in Starknet. Each domain has different gurantess regarding availability", "enum": ["L1", "L2"] }, "RESOURCE_BOUNDS_MAPPING": { @@ -3694,26 +3695,26 @@ "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" - } + { + "$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": { - "left": { - "description": "the hash of the left child", - "$ref": "#/components/schemas/FELT" - }, - "right": { - "description": "the hash of the right child", - "$ref": "#/components/schemas/FELT" - } + "left": { + "description": "the hash of the left child", + "$ref": "#/components/schemas/FELT" + }, + "right": { + "description": "the hash of the right child", + "$ref": "#/components/schemas/FELT" + } }, "required": ["left", "right"] }, @@ -3721,18 +3722,18 @@ "type": "object", "description": "represents a path to the highest non-zero descendant node", "properties": { - "path": { - "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" - }, - "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" - } + "path": { + "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" + }, + "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", "child"] }, @@ -3749,20 +3750,20 @@ "$ref": "#/components/schemas/MERKLE_NODE" } }, - "required": [ - "node_hash", - "node" - ] + "required": ["node_hash", "node"] } - }, - "CONTRACT_EXECUTION_ERROR": { + }, + "CONTRACT_EXECUTION_ERROR": { + "description": "structured error that can later be processed by wallets or sdks", + "title": "contract execution error", + "$ref": "#/components/schemas/CONTRACT_EXECUTION_ERROR_INNER" + }, + "CONTRACT_EXECUTION_ERROR_INNER": { "description": "structured error that can later be processed by wallets or sdks", "title": "contract execution error", "oneOf": [ { "type": "object", - "title": "inner call", - "description": "error frame", "properties": { "contract_address": { "$ref": "#/components/schemas/ADDRESS" @@ -3871,4 +3872,4 @@ } } } -} +} \ No newline at end of file diff --git a/doc/rpc/v08/starknet_executables.json b/doc/rpc/v08/starknet_executables.json index b6d17958f3..595a1786ce 100644 --- a/doc/rpc/v08/starknet_executables.json +++ b/doc/rpc/v08/starknet_executables.json @@ -1,7 +1,7 @@ { "openrpc": "1.0.0", "info": { - "version": "0.8.0", + "version": "0.8.0-rc.0", "title": "API for getting Starknet executables from nodes that store compiled artifacts", "license": {} }, @@ -110,13 +110,13 @@ "minItems": 2, "maxItems": 2 } - } - }, - "bytecode_segment_lengths": { - "type": "array", - "description": "a list of sizes of segments in the bytecode, each segment is hashed individually when computing the bytecode hash", - "items": { - "type": "integer" + }, + "bytecode_segment_lengths": { + "type": "array", + "description": "a list of sizes of segments in the bytecode, each segment is hashed invidually when computing the bytecode hash", + "items": { + "type": "integer" + } } }, "required": [ @@ -181,7 +181,7 @@ "properties": { "DoubleDeref": { "title": "DoubleDeref", - "description": "A (CellRef, offset) tuple", + "description": "A (CellRef, offsest) tuple", "type": "array", "items": { "oneOf": [ @@ -202,7 +202,7 @@ ] }, "Immediate": { - "title": "Imeediate", + "title": "Immediate", "type": "object", "properties": { "Immediate": { @@ -213,11 +213,13 @@ "Immediate" ] }, - "BinOp": { + "BinOp":{ "title": "BinOperand", "type": "object", - "properties": { + "properties":{ "BinOp": { + "type": "object", + "properties": { "op": { "type": "string", "enum": [ @@ -238,12 +240,16 @@ } ] } + }, + "required":[ + "op", + "a", + "b" + ] } }, - "required": [ - "BinOp", - "a", - "b" + "required":[ + "BinOp" ] }, "ResOperand": { @@ -1357,4 +1363,4 @@ } } } -} +} \ No newline at end of file diff --git a/doc/rpc/v08/starknet_trace_api_openrpc.json b/doc/rpc/v08/starknet_trace_api_openrpc.json index 45ba0a3f75..7e312aa438 100644 --- a/doc/rpc/v08/starknet_trace_api_openrpc.json +++ b/doc/rpc/v08/starknet_trace_api_openrpc.json @@ -1,7 +1,7 @@ { "openrpc": "1.0.0-rc1", "info": { - "version": "0.7.1", + "version": "0.8.0-rc.0", "title": "StarkNet Trace API", "license": {} }, @@ -30,7 +30,7 @@ }, "errors": [ { - "$ref": "#/components/errors/TXN_HASH_NOT_FOUND" + "$ref": "./api/starknet_api_openrpc.json#/components/errors/TXN_HASH_NOT_FOUND" }, { "$ref": "#/components/errors/NO_TRACE_AVAILABLE" @@ -39,7 +39,7 @@ }, { "name": "starknet_simulateTransactions", - "summary": "Simulate a given sequence of transactions on the requested state, and generate the execution traces. Note that some of the transactions may revert, in which case no error is thrown, but revert details can be seen on the returned trace object. . Note that some of the transactions may revert, this will be reflected by the revert_error property in the trace. Other types of failures (e.g. unexpected error or failure in the validation phase) will result in TRANSACTION_EXECUTION_ERROR.", + "summary": "Simulate a given sequence of transactions on the requested state, and generate the execution traces. Note that some of the transactions may revert, in which case no error is thrown, but revert details can be seen on the returned trace object. Note that some of the transactions may revert, this will be reflected by the revert_error property in the trace. Other types of failures (e.g. unexpected error or failure in the validation phase) will result in TRANSACTION_EXECUTION_ERROR.", "params": [ { "name": "block_id", @@ -75,7 +75,7 @@ ], "result": { "name": "simulated_transactions", - "description": "The execution trace and consumed resources of the required transactions", + "description": "The execution trace and consuemd resources of the required transactions", "schema": { "type": "array", "items": { @@ -97,10 +97,10 @@ }, "errors": [ { - "$ref": "#/components/errors/BLOCK_NOT_FOUND" + "$ref": "./api/starknet_api_openrpc.json#/components/errors/BLOCK_NOT_FOUND" }, { - "$ref": "#/components/errors/TRANSACTION_EXECUTION_ERROR" + "$ref": "./api/starknet_api_openrpc.json#/components/errors/TRANSACTION_EXECUTION_ERROR" } ] }, @@ -139,7 +139,7 @@ }, "errors": [ { - "$ref": "#/components/errors/BLOCK_NOT_FOUND" + "$ref": "./api/starknet_api_openrpc.json#/components/errors/BLOCK_NOT_FOUND" } ] } @@ -480,15 +480,6 @@ } } } - }, - "TXN_HASH_NOT_FOUND": { - "$ref": "./api/starknet_api_openrpc.json#/components/errors/TXN_HASH_NOT_FOUND" - }, - "BLOCK_NOT_FOUND": { - "$ref": "./api/starknet_api_openrpc.json#/components/errors/BLOCK_NOT_FOUND" - }, - "TRANSACTION_EXECUTION_ERROR": { - "$ref": "./api/starknet_api_openrpc.json#/components/errors/TRANSACTION_EXECUTION_ERROR" } } } diff --git a/doc/rpc/v08/starknet_write_api.json b/doc/rpc/v08/starknet_write_api.json index 5c7341fd54..d41cf3251d 100644 --- a/doc/rpc/v08/starknet_write_api.json +++ b/doc/rpc/v08/starknet_write_api.json @@ -1,7 +1,7 @@ { "openrpc": "1.0.0-rc1", "info": { - "version": "0.7.1", + "version": "0.8.0-rc.0", "title": "StarkNet Node Write API", "license": {} }, diff --git a/doc/rpc/v08/starknet_ws_api.json b/doc/rpc/v08/starknet_ws_api.json index 726f30a7e1..9e7ef37495 100644 --- a/doc/rpc/v08/starknet_ws_api.json +++ b/doc/rpc/v08/starknet_ws_api.json @@ -2,7 +2,7 @@ "openrpc": "1.3.2", "info": { "version": "0.8.0-rc0", - "title": "StarkNet WebSocket PRC API", + "title": "StarkNet WebSocket RPC API", "license": {} }, "methods": [ @@ -29,6 +29,9 @@ "errors": [ { "$ref": "#/components/errors/TOO_MANY_BLOCKS_BACK" + }, + { + "$ref": "./api/starknet_api_openrpc.json#/components/errors/BLOCK_NOT_FOUND" } ] }, @@ -95,6 +98,9 @@ }, { "$ref": "#/components/errors/TOO_MANY_BLOCKS_BACK" + }, + { + "$ref": "./api/starknet_api_openrpc.json#/components/errors/BLOCK_NOT_FOUND" } ] }, @@ -149,11 +155,14 @@ "errors": [ { "$ref": "#/components/errors/TOO_MANY_BLOCKS_BACK" + }, + { + "$ref": "./api/starknet_api_openrpc.json#/components/errors/BLOCK_NOT_FOUND" } ] }, { - "name": "starknet_subscriptionTransactionsStatus", + "name": "starknet_subscriptionTransactionStatus", "summary": "New transaction status notification", "description": "Notification to the client of a new transaction status", "params": [ @@ -355,4 +364,4 @@ } } } -} +} \ No newline at end of file From bfead1fbaa13d9010630c1e810f327cb6ab6cb9d Mon Sep 17 00:00:00 2001 From: Krisztian Kovacs Date: Fri, 8 Nov 2024 12:00:34 +0100 Subject: [PATCH 247/282] chore(doc/rpc/v08): fix typos in specs --- doc/rpc/v08/starknet_api_openrpc.json | 6 +++--- doc/rpc/v08/starknet_executables.json | 6 +++--- doc/rpc/v08/starknet_trace_api_openrpc.json | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/doc/rpc/v08/starknet_api_openrpc.json b/doc/rpc/v08/starknet_api_openrpc.json index 731ebe9705..6bf3a4d4dd 100644 --- a/doc/rpc/v08/starknet_api_openrpc.json +++ b/doc/rpc/v08/starknet_api_openrpc.json @@ -681,7 +681,7 @@ "schema": { "title": "Estimation", "type": "array", - "description": "a sequence of fee estimatione where the i'th estimate corresponds to the i'th transaction", + "description": "a sequence of fee estimation where the i'th estimate corresponds to the i'th transaction", "items": { "$ref": "#/components/schemas/FEE_ESTIMATE" } @@ -969,7 +969,7 @@ ], "result": { "name": "result", - "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", + "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 effectively proving non-membership", "schema": { "type": "object", "properties": { @@ -3617,7 +3617,7 @@ "DA_MODE": { "title": "DA mode", "type": "string", - "description": "Specifies a storage domain in Starknet. Each domain has different gurantess regarding availability", + "description": "Specifies a storage domain in Starknet. Each domain has different guarantees regarding availability", "enum": ["L1", "L2"] }, "RESOURCE_BOUNDS_MAPPING": { diff --git a/doc/rpc/v08/starknet_executables.json b/doc/rpc/v08/starknet_executables.json index 595a1786ce..4a22ef2c11 100644 --- a/doc/rpc/v08/starknet_executables.json +++ b/doc/rpc/v08/starknet_executables.json @@ -113,7 +113,7 @@ }, "bytecode_segment_lengths": { "type": "array", - "description": "a list of sizes of segments in the bytecode, each segment is hashed invidually when computing the bytecode hash", + "description": "a list of sizes of segments in the bytecode, each segment is hashed individually when computing the bytecode hash", "items": { "type": "integer" } @@ -181,7 +181,7 @@ "properties": { "DoubleDeref": { "title": "DoubleDeref", - "description": "A (CellRef, offsest) tuple", + "description": "A (CellRef, offset) tuple", "type": "array", "items": { "oneOf": [ @@ -1363,4 +1363,4 @@ } } } -} \ No newline at end of file +} diff --git a/doc/rpc/v08/starknet_trace_api_openrpc.json b/doc/rpc/v08/starknet_trace_api_openrpc.json index 7e312aa438..6df705edb2 100644 --- a/doc/rpc/v08/starknet_trace_api_openrpc.json +++ b/doc/rpc/v08/starknet_trace_api_openrpc.json @@ -75,7 +75,7 @@ ], "result": { "name": "simulated_transactions", - "description": "The execution trace and consuemd resources of the required transactions", + "description": "The execution trace and consumed resources of the required transactions", "schema": { "type": "array", "items": { From c9789435ed69114719d4062e975d7f96883d51e4 Mon Sep 17 00:00:00 2001 From: Krisztian Kovacs Date: Fri, 8 Nov 2024 12:17:48 +0100 Subject: [PATCH 248/282] fix(rpc/method): rename transaction status notification There was a change in the OpenRPC specification: `starknet_subscriptionTransactionsStatus` has been renamed to `starknet_subscriptionTransactionStatus`. --- .../method/subscribe_transaction_status.rs | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/crates/rpc/src/method/subscribe_transaction_status.rs b/crates/rpc/src/method/subscribe_transaction_status.rs index 8b54ac99f0..8f835810b3 100644 --- a/crates/rpc/src/method/subscribe_transaction_status.rs +++ b/crates/rpc/src/method/subscribe_transaction_status.rs @@ -117,7 +117,7 @@ impl crate::dto::serialize::SerializeForVersion for Notification { } } -const SUBSCRIPTION_NAME: &str = "starknet_subscriptionTransactionsStatus"; +const SUBSCRIPTION_NAME: &str = "starknet_subscriptionTransactionStatus"; #[async_trait] impl RpcSubscriptionFlow for SubscribeTransactionStatus { @@ -470,7 +470,7 @@ mod tests { vec![ serde_json::json!({ "jsonrpc": "2.0", - "method": "starknet_subscriptionTransactionsStatus", + "method": "starknet_subscriptionTransactionStatus", "params": { "result": { "transaction_hash": "0x1", @@ -483,7 +483,7 @@ mod tests { }), serde_json::json!({ "jsonrpc": "2.0", - "method": "starknet_subscriptionTransactionsStatus", + "method": "starknet_subscriptionTransactionStatus", "params": { "result": { "transaction_hash": "0x1", @@ -570,7 +570,7 @@ mod tests { json, serde_json::json!({ "jsonrpc": "2.0", - "method": "starknet_subscriptionTransactionsStatus", + "method": "starknet_subscriptionTransactionStatus", "params": { "result": { "transaction_hash": "0x1", @@ -600,7 +600,7 @@ mod tests { vec![ serde_json::json!({ "jsonrpc": "2.0", - "method": "starknet_subscriptionTransactionsStatus", + "method": "starknet_subscriptionTransactionStatus", "params": { "result": { "transaction_hash": "0x1", @@ -613,7 +613,7 @@ mod tests { }), serde_json::json!({ "jsonrpc": "2.0", - "method": "starknet_subscriptionTransactionsStatus", + "method": "starknet_subscriptionTransactionStatus", "params": { "result": { "transaction_hash": "0x1", @@ -645,7 +645,7 @@ mod tests { vec![ serde_json::json!({ "jsonrpc": "2.0", - "method": "starknet_subscriptionTransactionsStatus", + "method": "starknet_subscriptionTransactionStatus", "params": { "result": { "transaction_hash": "0x1", @@ -658,7 +658,7 @@ mod tests { }), serde_json::json!({ "jsonrpc": "2.0", - "method": "starknet_subscriptionTransactionsStatus", + "method": "starknet_subscriptionTransactionStatus", "params": { "result": { "transaction_hash": "0x1", @@ -672,7 +672,7 @@ mod tests { }), serde_json::json!({ "jsonrpc": "2.0", - "method": "starknet_subscriptionTransactionsStatus", + "method": "starknet_subscriptionTransactionStatus", "params": { "result": { "transaction_hash": "0x1", @@ -705,7 +705,7 @@ mod tests { vec![ serde_json::json!({ "jsonrpc": "2.0", - "method": "starknet_subscriptionTransactionsStatus", + "method": "starknet_subscriptionTransactionStatus", "params": { "result": { "transaction_hash": "0x1", @@ -718,7 +718,7 @@ mod tests { }), serde_json::json!({ "jsonrpc": "2.0", - "method": "starknet_subscriptionTransactionsStatus", + "method": "starknet_subscriptionTransactionStatus", "params": { "result": { "transaction_hash": "0x1", @@ -733,7 +733,7 @@ mod tests { }), serde_json::json!({ "jsonrpc": "2.0", - "method": "starknet_subscriptionTransactionsStatus", + "method": "starknet_subscriptionTransactionStatus", "params": { "result": { "transaction_hash": "0x1", @@ -805,7 +805,7 @@ mod tests { ), TestEvent::Message(serde_json::json!({ "jsonrpc": "2.0", - "method": "starknet_subscriptionTransactionsStatus", + "method": "starknet_subscriptionTransactionStatus", "params": { "result": { "transaction_hash": "0x1", @@ -818,7 +818,7 @@ mod tests { })), TestEvent::Message(serde_json::json!({ "jsonrpc": "2.0", - "method": "starknet_subscriptionTransactionsStatus", + "method": "starknet_subscriptionTransactionStatus", "params": { "result": { "transaction_hash": "0x1", @@ -868,7 +868,7 @@ mod tests { ), TestEvent::Message(serde_json::json!({ "jsonrpc": "2.0", - "method": "starknet_subscriptionTransactionsStatus", + "method": "starknet_subscriptionTransactionStatus", "params": { "result": { "transaction_hash": "0x1", @@ -901,7 +901,7 @@ mod tests { })), TestEvent::Message(serde_json::json!({ "jsonrpc": "2.0", - "method": "starknet_subscriptionTransactionsStatus", + "method": "starknet_subscriptionTransactionStatus", "params": { "result": { "transaction_hash": "0x1", From 61de7b293ebfec9c7e43997d97d9f3a39c204950 Mon Sep 17 00:00:00 2001 From: sistemd Date: Fri, 8 Nov 2024 14:26:56 +0100 Subject: [PATCH 249/282] aggregate filter creation and storage --- crates/rpc/Cargo.toml | 5 + crates/rpc/fixtures/mainnet.sqlite | Bin 454656 -> 454656 bytes crates/rpc/src/method/get_events.rs | 45 ++ crates/rpc/src/method/subscribe_events.rs | 76 +-- crates/storage/src/bloom.rs | 318 ++++++------ crates/storage/src/connection/event.rs | 510 +++++++++++++++---- crates/storage/src/connection/transaction.rs | 12 + crates/storage/src/schema.rs | 4 + crates/storage/src/schema/revision_0066.rs | 77 +++ 9 files changed, 761 insertions(+), 286 deletions(-) create mode 100644 crates/storage/src/schema/revision_0066.rs diff --git a/crates/rpc/Cargo.toml b/crates/rpc/Cargo.toml index ad7698ada5..c52d643acf 100644 --- a/crates/rpc/Cargo.toml +++ b/crates/rpc/Cargo.toml @@ -7,6 +7,11 @@ license = { workspace = true } rust-version = { workspace = true } # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[features] +aggregate_bloom = [] + +default = [] + [dependencies] anyhow = { workspace = true } async-trait = { workspace = true } diff --git a/crates/rpc/fixtures/mainnet.sqlite b/crates/rpc/fixtures/mainnet.sqlite index f86d30b16ead2d8fd10a17413238fc3772b5e9d1..88b5ca98c3427f34d954d0e5d8cd88fee940f60c 100644 GIT binary patch delta 459 zcmZp8Al>jldV;iS1Oo#@J`e{0u?!G*05J;?!vG_Y;WSxLL7p+9F`+equ{D9IHGz3+ z0?U&8#Vi2|+gT>CFefldPv`i+qRyHPRGiap@_}W$$p_Z6`UXI*D5E$7^8}_(eA9Vn z@rv@qazEvI%)Njsh~qLxIFkjtFxw?I6V@dxhgb|5#W|-7CNMn6%~rbYz~E(Iiz zR+OI`pOllIoIO1unNfWD1syg{_I#*FTr#61NQ8qGs3Jdi`bRZ33AIo^&%jVu4TMG= zg%YR+O}NpT+aD{i{bby3tju+WanJ2RT5Ri%0szj4 Bkh1^) delta 7189 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 z diff --git a/crates/rpc/src/method/get_events.rs b/crates/rpc/src/method/get_events.rs index ab59924e00..c52c0c8130 100644 --- a/crates/rpc/src/method/get_events.rs +++ b/crates/rpc/src/method/get_events.rs @@ -217,6 +217,17 @@ pub async fn get_events( offset: requested_offset, }; + // TODO: + // Instrumentation and `AggregateBloom` version of fetching events + // for the given `EventFilter` are under a feature flag for now and + // we do not execute them during testing because they would only + // slow the tests down and would not have any impact on their outcome. + // Follow-up PR will use the `AggregateBloom` logic to create the output, + // then the conditions will be removed. + + #[cfg(all(feature = "aggregate_bloom", not(test)))] + let start = std::time::Instant::now(); + let page = transaction .events( &filter, @@ -228,6 +239,40 @@ pub async fn get_events( EventFilterError::PageSizeTooSmall => GetEventsError::Custom(e.into()), })?; + #[cfg(all(feature = "aggregate_bloom", not(test)))] + { + let elapsed = start.elapsed(); + + tracing::info!( + "Getting events (individual Bloom filters) took {:?}", + elapsed + ); + + let start = std::time::Instant::now(); + let page_from_aggregate = transaction + .events_from_aggregate(&filter, context.config.get_events_max_blocks_to_scan) + .map_err(|e| match e { + EventFilterError::Internal(e) => GetEventsError::Internal(e), + EventFilterError::PageSizeTooSmall => GetEventsError::Custom(e.into()), + })?; + let elapsed = start.elapsed(); + + tracing::info!( + "Getting events (aggregate Bloom filters) took {:?}", + elapsed + ); + + if page != page_from_aggregate { + tracing::error!( + "Page of events from individual and aggregate bloom filters does not match!" + ); + tracing::error!("Individual: {:?}", page); + tracing::error!("Aggregate: {:?}", page_from_aggregate); + } else { + tracing::info!("Page of events from individual and aggregate bloom filters match!"); + } + } + let mut events = GetEventsResult { events: page.events.into_iter().map(|e| e.into()).collect(), continuation_token: page.continuation_token.map(|token| { diff --git a/crates/rpc/src/method/subscribe_events.rs b/crates/rpc/src/method/subscribe_events.rs index 537cba978d..ab3683d482 100644 --- a/crates/rpc/src/method/subscribe_events.rs +++ b/crates/rpc/src/method/subscribe_events.rs @@ -250,14 +250,14 @@ mod tests { use tokio::sync::mpsc; use crate::context::{RpcConfig, RpcContext}; - use crate::jsonrpc::{handle_json_rpc_socket, RpcRouter}; + use crate::jsonrpc::{handle_json_rpc_socket, RpcRouter, CATCH_UP_BATCH_SIZE}; use crate::pending::PendingWatcher; use crate::v02::types::syncing::Syncing; use crate::{v08, Notifications, Reorg, SyncState}; #[tokio::test] async fn no_filtering() { - let num_blocks = 2000; + let num_blocks = 80; let router = setup(num_blocks).await; let (sender_tx, mut sender_rx) = mpsc::channel(1024); let (receiver_tx, receiver_rx) = mpsc::channel(1024); @@ -319,14 +319,14 @@ mod tests { #[tokio::test] async fn filter_from_address() { - let router = setup(2000).await; + let router = setup(80).await; let (sender_tx, mut sender_rx) = mpsc::channel(1024); let (receiver_tx, receiver_rx) = mpsc::channel(1024); handle_json_rpc_socket(router.clone(), sender_tx, receiver_rx); let params = serde_json::json!( { "block": {"block_number": 0}, - "from_address": "0x90", + "from_address": "0x46", } ); receiver_tx @@ -351,7 +351,7 @@ mod tests { } _ => panic!("Expected text message"), }; - let expected = sample_event_message(0x90, subscription_id); + let expected = sample_event_message(0x46, subscription_id); let event = sender_rx.recv().await.unwrap().unwrap(); let json: serde_json::Value = match event { Message::Text(json) => serde_json::from_str(&json).unwrap(), @@ -371,9 +371,9 @@ mod tests { .context .notifications .l2_blocks - .send(sample_block(0x90).into()) + .send(sample_block(0x46).into()) .unwrap(); - let expected = sample_event_message(0x90, subscription_id); + let expected = sample_event_message(0x46, subscription_id); let event = sender_rx.recv().await.unwrap().unwrap(); let json: serde_json::Value = match event { Message::Text(json) => serde_json::from_str(&json).unwrap(), @@ -385,14 +385,14 @@ mod tests { #[tokio::test] async fn filter_keys() { - let router = setup(2000).await; + let router = setup(80).await; let (sender_tx, mut sender_rx) = mpsc::channel(1024); let (receiver_tx, receiver_rx) = mpsc::channel(1024); handle_json_rpc_socket(router.clone(), sender_tx, receiver_rx); let params = serde_json::json!( { "block": {"block_number": 0}, - "keys": [["0x90"], [], ["0x92", "0x93"]], + "keys": [["0x46"], [], ["0x47", "0x48"]], } ); receiver_tx @@ -417,7 +417,7 @@ mod tests { } _ => panic!("Expected text message"), }; - let expected = sample_event_message(0x90, subscription_id); + let expected = sample_event_message(0x46, subscription_id); let event = sender_rx.recv().await.unwrap().unwrap(); let json: serde_json::Value = match event { Message::Text(json) => serde_json::from_str(&json).unwrap(), @@ -437,9 +437,9 @@ mod tests { .context .notifications .l2_blocks - .send(sample_block(0x90).into()) + .send(sample_block(0x46).into()) .unwrap(); - let expected = sample_event_message(0x90, subscription_id); + let expected = sample_event_message(0x46, subscription_id); let event = sender_rx.recv().await.unwrap().unwrap(); let json: serde_json::Value = match event { Message::Text(json) => serde_json::from_str(&json).unwrap(), @@ -451,15 +451,15 @@ mod tests { #[tokio::test] async fn filter_from_address_and_keys() { - let router = setup(2000).await; + let router = setup(80).await; let (sender_tx, mut sender_rx) = mpsc::channel(1024); let (receiver_tx, receiver_rx) = mpsc::channel(1024); handle_json_rpc_socket(router.clone(), sender_tx, receiver_rx); let params = serde_json::json!( { "block": {"block_number": 0}, - "from_address": "0x90", - "keys": [["0x90"], [], ["0x92", "0x93"]], + "from_address": "0x46", + "keys": [["0x46"], [], ["0x47", "0x48"]], } ); receiver_tx @@ -484,7 +484,7 @@ mod tests { } _ => panic!("Expected text message"), }; - let expected = sample_event_message(0x90, subscription_id); + let expected = sample_event_message(0x46, subscription_id); let event = sender_rx.recv().await.unwrap().unwrap(); let json: serde_json::Value = match event { Message::Text(json) => serde_json::from_str(&json).unwrap(), @@ -504,9 +504,9 @@ mod tests { .context .notifications .l2_blocks - .send(sample_block(0x90).into()) + .send(sample_block(0x46).into()) .unwrap(); - let expected = sample_event_message(0x90, subscription_id); + let expected = sample_event_message(0x46, subscription_id); let event = sender_rx.recv().await.unwrap().unwrap(); let json: serde_json::Value = match event { Message::Text(json) => serde_json::from_str(&json).unwrap(), @@ -518,32 +518,32 @@ mod tests { #[tokio::test] async fn too_many_keys_filter() { - let router = setup(2000).await; + let router = setup(80).await; let (sender_tx, mut sender_rx) = mpsc::channel(1024); let (receiver_tx, receiver_rx) = mpsc::channel(1024); handle_json_rpc_socket(router.clone(), sender_tx, receiver_rx); let params = serde_json::json!( { "block": {"block_number": 0}, - "from_address": "0x90", + "from_address": "0x46", "keys": [ - ["0x91"], - ["0x92"], - ["0x93"], - ["0x94"], - ["0x95"], - ["0x96"], - ["0x97"], - ["0x98"], - ["0x99"], - ["0x9a"], - ["0x9b"], - ["0x9c"], - ["0x9d"], - ["0x9e"], - ["0x9f"], - ["0xa0"], - ["0xa1"], + ["0x46"], + ["0x47"], + ["0x48"], + ["0x49"], + ["0x4a"], + ["0x4b"], + ["0x4c"], + ["0x4d"], + ["0x4e"], + ["0x4f"], + ["0x50"], + ["0x51"], + ["0x52"], + ["0x53"], + ["0x54"], + ["0x55"], + ["0x56"], ], } ); @@ -644,6 +644,8 @@ mod tests { } async fn setup(num_blocks: u64) -> RpcRouter { + assert!(num_blocks == 0 || num_blocks > CATCH_UP_BATCH_SIZE); + let storage = StorageBuilder::in_memory().unwrap(); tokio::task::spawn_blocking({ let storage = storage.clone(); diff --git a/crates/storage/src/bloom.rs b/crates/storage/src/bloom.rs index f8fcd01a0a..b0ce2adcf8 100644 --- a/crates/storage/src/bloom.rs +++ b/crates/storage/src/bloom.rs @@ -60,6 +60,7 @@ //! specific set of keys without having to load and check each individual bloom //! filter. +use std::collections::BTreeSet; use std::sync::{Mutex, MutexGuard}; use bloomfilter::Bloom; @@ -79,71 +80,80 @@ pub const EVENT_KEY_FILTER_LIMIT: usize = 16; /// Before being added to `AggregateBloom`, each [`BloomFilter`] is /// rotated by 90 degrees (transposed). #[derive(Debug)] -pub(crate) struct AggregateBloom { +pub struct AggregateBloom { /// A [Self::BLOCK_RANGE_LEN] by [BloomFilter::BITVEC_LEN] matrix stored in /// a single array. bitmap: Vec, - /// Block range for which the aggregate filter is constructed. - block_range: std::ops::Range, - next_block: BlockNumber, + /// Starting (inclusive) block number for the range of blocks that this + /// aggregate covers. + pub from_block: BlockNumber, + /// Ending (inclusive) block number for the range of blocks that this + /// aggregate covers. + pub to_block: BlockNumber, } +// TODO: +// Delete after cfg flag is removed +#[allow(dead_code)] impl AggregateBloom { - // TODO: - // Remove #[allow(dead_code)] when follow up is done. - /// Maximum number of blocks to aggregate in a single `AggregateBloom`. - const BLOCK_RANGE_LEN: u64 = 32_768; + pub const BLOCK_RANGE_LEN: u64 = 32_768; const BLOCK_RANGE_BYTES: u64 = Self::BLOCK_RANGE_LEN / 8; - /// Create a new `AggregateBloom` for the (`from_block`, `from_block + - /// [Self::BLOCK_RANGE_LEN]`) range. - #[allow(dead_code)] + /// Create a new `AggregateBloom` for the (`from_block`, `from_block` + + /// [`block_range_length`](Self::BLOCK_RANGE_LEN) - 1) range. pub fn new(from_block: BlockNumber) -> Self { - let bitmap = vec![0; (Self::BLOCK_RANGE_BYTES * BloomFilter::BITVEC_LEN) as usize]; + let to_block = from_block + Self::BLOCK_RANGE_LEN - 1; + let bitmap = vec![0; Self::BLOCK_RANGE_BYTES as usize * BloomFilter::BITVEC_LEN as usize]; + Self::from_parts(from_block, to_block, bitmap) + } - let to_block = from_block + Self::BLOCK_RANGE_LEN; + pub fn from_existing_compressed( + from_block: u64, + to_block: u64, + compressed_bitmap: Vec, + ) -> Self { + let from_block = BlockNumber::new_or_panic(from_block); + let to_block = BlockNumber::new_or_panic(to_block); - Self { - bitmap, - block_range: from_block..to_block, - next_block: from_block, - } + let bitmap = zstd::bulk::decompress( + &compressed_bitmap, + AggregateBloom::BLOCK_RANGE_BYTES as usize * BloomFilter::BITVEC_LEN as usize, + ) + .expect("Decompressing aggregate Bloom filter"); + + Self::from_parts(from_block, to_block, bitmap) } - #[allow(dead_code)] - pub fn from_bytes(from_block: BlockNumber, bytes: Vec) -> Self { + fn from_parts(from_block: BlockNumber, to_block: BlockNumber, bitmap: Vec) -> Self { assert_eq!( - bytes.len() as u64, + from_block + Self::BLOCK_RANGE_LEN - 1, + to_block, + "Block range mismatch" + ); + assert_eq!( + bitmap.len() as u64, Self::BLOCK_RANGE_BYTES * BloomFilter::BITVEC_LEN, - "Bitmap size mismatch" + "Bitmap length mismatch" ); - let to_block = from_block + Self::BLOCK_RANGE_LEN; - Self { - bitmap: bytes, - block_range: from_block..to_block, - next_block: from_block, + bitmap, + from_block, + to_block, } } - #[allow(dead_code)] - pub fn to_bytes(&self) -> &[u8] { - &self.bitmap + pub fn compress_bitmap(&self) -> Vec { + zstd::bulk::compress(&self.bitmap, 10).expect("Compressing aggregate Bloom filter") } /// Rotate the bloom filter by 90 degrees and add it to the aggregate. - #[allow(dead_code)] - pub fn add_bloom( - &mut self, - bloom: &BloomFilter, - insert_pos: BlockNumber, - ) -> Result<(), AddBloomError> { - if !self.block_range.contains(&insert_pos) { - return Err(AddBloomError::InvalidBlockNumber); - } - assert_eq!(self.next_block, insert_pos, "Unexpected insert position"); + pub fn add_bloom(&mut self, bloom: &BloomFilter, block_number: BlockNumber) { + assert!( + (self.from_block..=self.to_block).contains(&block_number), + "Invalid block number", + ); assert_eq!( bloom.0.number_of_hash_functions(), BloomFilter::K_NUM, @@ -157,8 +167,9 @@ impl AggregateBloom { "Bit vector length mismatch" ); - let byte_index = (insert_pos.get() / 8) as usize; - let bit_index = (insert_pos.get() % 8) as usize; + let relative_block_number = block_number.get() - self.from_block.get(); + let byte_idx = (relative_block_number / 8) as usize; + let bit_idx = (relative_block_number % 8) as usize; for (i, bloom_byte) in bloom.iter().enumerate() { if *bloom_byte == 0 { continue; @@ -167,48 +178,54 @@ impl AggregateBloom { let base = 8 * i; for j in 0..8 { let row_idx = base + j; - let idx = Self::bitmap_index_at(row_idx, byte_index); - self.bitmap[idx] |= ((bloom_byte >> (7 - j)) & 1) << bit_index; + *self.bitmap_at_mut(row_idx, byte_idx) |= ((bloom_byte >> (7 - j)) & 1) << bit_idx; } } + } - self.next_block += 1; - if self.next_block >= self.block_range.end { - tracing::info!( - "Block limit reached for [{}, {}) range", - self.block_range.start, - self.block_range.end - ); - return Err(AddBloomError::BlockLimitReached); + pub fn blocks_for_filter(&self, filter: &crate::EventFilter) -> BTreeSet { + // Empty filters are considered present in all blocks. + if filter.contract_address.is_none() && (filter.keys.iter().flatten().count() == 0) { + return (self.from_block.get()..=self.to_block.get()) + .map(BlockNumber::new_or_panic) + .collect(); } - Ok(()) - } - - #[allow(dead_code)] - pub fn blocks_for_filter(&self, filter: &crate::EventFilter) -> Vec { - let mut keys = vec![]; + let mut blocks: BTreeSet<_> = filter + .keys + .iter() + .enumerate() + .flat_map(|(idx, keys)| { + let keys: Vec<_> = keys + .iter() + .map(|key| { + let mut key_with_idx = key.0; + key_with_idx.as_mut_be_bytes()[0] |= (idx as u8) << 4; + key_with_idx + }) + .collect(); + + self.blocks_for_keys(&keys) + }) + .collect(); if let Some(contract_address) = filter.contract_address { - keys.push(contract_address.0); + blocks.extend(self.blocks_for_keys(&[contract_address.0])); } - filter.keys.iter().flatten().for_each(|k| keys.push(k.0)); - self.blocks_for_keys(keys) + blocks } - #[allow(dead_code)] - fn blocks_for_keys(&self, keys: Vec) -> Vec { + fn blocks_for_keys(&self, keys: &[Felt]) -> Vec { let mut block_matches = vec![]; for k in keys { let mut row_to_check = vec![u8::MAX; Self::BLOCK_RANGE_BYTES as usize]; - let indices = BloomFilter::indices_for_key(&k); + let indices = BloomFilter::indices_for_key(k); for row_idx in indices { for (col_idx, row_byte) in row_to_check.iter_mut().enumerate() { - let idx = Self::bitmap_index_at(row_idx, col_idx); - *row_byte &= self.bitmap[idx]; + *row_byte &= self.bitmap_at(row_idx, col_idx); } } @@ -219,7 +236,8 @@ impl AggregateBloom { for i in 0..8 { if byte & (1 << i) != 0 { - block_matches.push(BlockNumber::new_or_panic((col_idx * 8 + i) as u64)); + let match_number = self.from_block + col_idx as u64 * 8 + i as u64; + block_matches.push(match_number); } } } @@ -228,16 +246,15 @@ impl AggregateBloom { block_matches } - #[allow(dead_code)] - fn bitmap_index_at(row: usize, col: usize) -> usize { - row * Self::BLOCK_RANGE_BYTES as usize + col + fn bitmap_at(&self, row: usize, col: usize) -> u8 { + let idx = row * Self::BLOCK_RANGE_BYTES as usize + col; + self.bitmap[idx] } -} -#[derive(Debug)] -pub enum AddBloomError { - BlockLimitReached, - InvalidBlockNumber, + fn bitmap_at_mut(&mut self, row: usize, col: usize) -> &mut u8 { + let idx = row * Self::BLOCK_RANGE_BYTES as usize + col; + &mut self.bitmap[idx] + } } #[derive(Clone)] @@ -354,6 +371,9 @@ impl BloomFilter { // Workaround to get the indices of the keys in the filter. // Needed because the `bloomfilter` crate doesn't provide a // way to get this information. + // TODO: + // Delete after cfg flag is removed + #[allow(dead_code)] fn indices_for_key(key: &Felt) -> Vec { // Use key on an empty Bloom filter let mut bloom = Self::new(); @@ -400,7 +420,6 @@ impl Cache { #[cfg(test)] mod tests { - use assert_matches::assert_matches; use pathfinder_common::felt; use super::*; @@ -439,148 +458,141 @@ mod tests { bloom.set(&KEY); bloom.set(&KEY1); - aggregate_bloom_filter - .add_bloom(&bloom, from_block) - .unwrap(); + aggregate_bloom_filter.add_bloom(&bloom, from_block); - let block_matches = aggregate_bloom_filter.blocks_for_keys(vec![KEY]); + let filter = crate::EventFilter { + keys: vec![vec![EventKey(KEY)]], + contract_address: None, + ..Default::default() + }; + let block_matches = aggregate_bloom_filter.blocks_for_keys(&[KEY]); + assert_eq!(block_matches, vec![from_block]); + + let block_matches: Vec<_> = aggregate_bloom_filter + .blocks_for_filter(&filter) + .into_iter() + .collect(); assert_eq!(block_matches, vec![from_block]); } #[test] #[cfg_attr(not(feature = "aggregate_bloom"), ignore)] - fn add_blooms_and_check_multiple_blocks_found() { - let from_block = BlockNumber::new_or_panic(0); + fn aggregate_bloom_past_first_range() { + let from_block = BlockNumber::new_or_panic(AggregateBloom::BLOCK_RANGE_LEN); let mut aggregate_bloom_filter = AggregateBloom::new(from_block); let mut bloom = BloomFilter::new(); bloom.set(&KEY); + bloom.set(&KEY1); - aggregate_bloom_filter - .add_bloom(&bloom, from_block) - .unwrap(); - aggregate_bloom_filter - .add_bloom(&bloom, from_block + 1) - .unwrap(); + let filter = crate::EventFilter { + keys: vec![vec![EventKey(KEY)]], + contract_address: None, + ..Default::default() + }; - let block_matches = aggregate_bloom_filter.blocks_for_keys(vec![KEY]); + aggregate_bloom_filter.add_bloom(&bloom, from_block); - assert_eq!(block_matches, vec![from_block, from_block + 1]); + let block_matches = aggregate_bloom_filter.blocks_for_keys(&[KEY]); + assert_eq!(block_matches, vec![from_block]); + + let block_matches: Vec<_> = aggregate_bloom_filter + .blocks_for_filter(&filter) + .into_iter() + .collect(); + assert_eq!(block_matches, vec![from_block]); } #[test] #[cfg_attr(not(feature = "aggregate_bloom"), ignore)] - fn key_not_in_filter_returns_empty_vec() { + fn add_blooms_and_check_multiple_blocks_found() { let from_block = BlockNumber::new_or_panic(0); let mut aggregate_bloom_filter = AggregateBloom::new(from_block); let mut bloom = BloomFilter::new(); bloom.set(&KEY); - bloom.set(&KEY1); - aggregate_bloom_filter - .add_bloom(&bloom, from_block) - .unwrap(); - aggregate_bloom_filter - .add_bloom(&bloom, from_block + 1) - .unwrap(); + aggregate_bloom_filter.add_bloom(&bloom, from_block); + aggregate_bloom_filter.add_bloom(&bloom, from_block + 1); - let block_matches_empty = aggregate_bloom_filter.blocks_for_keys(vec![KEY_NOT_IN_FILTER]); + let filter = crate::EventFilter { + keys: vec![vec![EventKey(KEY)]], + contract_address: None, + ..Default::default() + }; - assert_eq!(block_matches_empty, Vec::::new()); + let block_matches = aggregate_bloom_filter.blocks_for_keys(&[KEY]); + assert_eq!(block_matches, vec![from_block, from_block + 1]); + + let block_matches: Vec<_> = aggregate_bloom_filter + .blocks_for_filter(&filter) + .into_iter() + .collect(); + assert_eq!(block_matches, vec![from_block, from_block + 1]); } #[test] #[cfg_attr(not(feature = "aggregate_bloom"), ignore)] - fn serialize_aggregate_roundtrip() { + fn key_not_in_filter_returns_empty_vec() { let from_block = BlockNumber::new_or_panic(0); let mut aggregate_bloom_filter = AggregateBloom::new(from_block); let mut bloom = BloomFilter::new(); bloom.set(&KEY); + bloom.set(&KEY1); - aggregate_bloom_filter - .add_bloom(&bloom, from_block) - .unwrap(); - aggregate_bloom_filter - .add_bloom(&bloom, from_block + 1) - .unwrap(); - - let bytes = aggregate_bloom_filter.to_bytes(); - let aggregate_bloom_filter = AggregateBloom::from_bytes(from_block, bytes.to_vec()); + aggregate_bloom_filter.add_bloom(&bloom, from_block); + aggregate_bloom_filter.add_bloom(&bloom, from_block + 1); - let block_matches = aggregate_bloom_filter.blocks_for_keys(vec![KEY]); - let block_matches_empty = aggregate_bloom_filter.blocks_for_keys(vec![KEY_NOT_IN_FILTER]); + let block_matches_empty = aggregate_bloom_filter.blocks_for_keys(&[KEY_NOT_IN_FILTER]); - assert_eq!(block_matches, vec![from_block, from_block + 1]); assert_eq!(block_matches_empty, Vec::::new()); } #[test] #[cfg_attr(not(feature = "aggregate_bloom"), ignore)] - fn block_limit_reached_after_full_range() { - impl AggregateBloom { - /// Real [Self::add_bloom] makes this test last way to long - fn add_bloom_mock(&mut self) { - self.next_block += 1; - } - } - + fn serialize_aggregate_roundtrip() { let from_block = BlockNumber::new_or_panic(0); let mut aggregate_bloom_filter = AggregateBloom::new(from_block); let mut bloom = BloomFilter::new(); bloom.set(&KEY); - for _ in from_block.get()..(AggregateBloom::BLOCK_RANGE_LEN - 1) { - aggregate_bloom_filter.add_bloom_mock(); - } + aggregate_bloom_filter.add_bloom(&bloom, from_block); + aggregate_bloom_filter.add_bloom(&bloom, from_block + 1); - let last_block = from_block + AggregateBloom::BLOCK_RANGE_LEN - 1; - assert_matches!( - aggregate_bloom_filter.add_bloom(&bloom, last_block), - Err(AddBloomError::BlockLimitReached) + let compressed_bitmap = aggregate_bloom_filter.compress_bitmap(); + let mut decompressed = AggregateBloom::from_existing_compressed( + aggregate_bloom_filter.from_block.get(), + aggregate_bloom_filter.to_block.get(), + compressed_bitmap, ); - } - - #[test] - #[cfg_attr(not(feature = "aggregate_bloom"), ignore)] - fn invalid_insert_pos() { - let from_block = BlockNumber::new_or_panic(0); - let mut aggregate_bloom_filter = AggregateBloom::new(from_block); + decompressed.add_bloom(&bloom, from_block + 2); - let mut bloom = BloomFilter::new(); - bloom.set(&KEY); + let block_matches = decompressed.blocks_for_keys(&[KEY]); + let block_matches_empty = decompressed.blocks_for_keys(&[KEY_NOT_IN_FILTER]); - aggregate_bloom_filter - .add_bloom(&bloom, from_block) - .unwrap(); - - let invalid_insert_pos = from_block + AggregateBloom::BLOCK_RANGE_LEN; - assert_matches!( - aggregate_bloom_filter.add_bloom(&bloom, invalid_insert_pos), - Err(AddBloomError::InvalidBlockNumber) + assert_eq!( + block_matches, + vec![from_block, from_block + 1, from_block + 2] ); + assert_eq!(block_matches_empty, Vec::::new()); } #[test] #[cfg_attr(not(feature = "aggregate_bloom"), ignore)] #[should_panic] - fn skipping_a_block_panics() { + fn invalid_insert_pos() { let from_block = BlockNumber::new_or_panic(0); let mut aggregate_bloom_filter = AggregateBloom::new(from_block); let mut bloom = BloomFilter::new(); bloom.set(&KEY); - aggregate_bloom_filter - .add_bloom(&bloom, from_block) - .unwrap(); + aggregate_bloom_filter.add_bloom(&bloom, from_block); - let skipped_block = from_block + 2; - aggregate_bloom_filter - .add_bloom(&bloom, skipped_block) - .unwrap(); + let invalid_insert_pos = from_block + AggregateBloom::BLOCK_RANGE_LEN; + aggregate_bloom_filter.add_bloom(&bloom, invalid_insert_pos); } } diff --git a/crates/storage/src/connection/event.rs b/crates/storage/src/connection/event.rs index c779dac3c2..726efd1305 100644 --- a/crates/storage/src/connection/event.rs +++ b/crates/storage/src/connection/event.rs @@ -1,5 +1,7 @@ use std::num::NonZeroUsize; +#[cfg(feature = "aggregate_bloom")] +use anyhow::Context; use anyhow::Result; use pathfinder_common::event::Event; use pathfinder_common::{ @@ -12,8 +14,8 @@ use pathfinder_common::{ }; #[cfg(feature = "aggregate_bloom")] -use crate::bloom::AddBloomError; -use crate::bloom::{AggregateBloom, BloomFilter}; +use crate::bloom::AggregateBloom; +use crate::bloom::BloomFilter; use crate::prelude::*; use crate::ReorgCounter; @@ -66,14 +68,51 @@ pub struct PageOfEvents { } impl Transaction<'_> { + #[cfg(feature = "aggregate_bloom")] + pub(super) fn upsert_block_events_aggregate( + &self, + block_number: BlockNumber, + events: &[Event], + ) -> anyhow::Result<()> { + #[rustfmt::skip] + let mut stmt = self.inner().prepare_cached( + "INSERT INTO starknet_event_filters_aggregate (from_block, to_block, bloom) \ + VALUES (?, ?, ?) ON CONFLICT DO UPDATE SET bloom=excluded.bloom", + )?; + + let mut running_aggregate = match self.load_aggregate_bloom(block_number)? { + // Loading existing block range + Some(aggregate) => aggregate, + // New block range reached + None => AggregateBloom::new(block_number), + }; + + let mut bloom = BloomFilter::new(); + for event in events { + bloom.set_keys(&event.keys); + bloom.set_address(&event.from_address); + } + + running_aggregate.add_bloom(&bloom, block_number); + + stmt.execute(params![ + &running_aggregate.from_block, + &running_aggregate.to_block, + &running_aggregate.compress_bitmap() + ])?; + + Ok(()) + } + pub(super) fn upsert_block_events<'a>( &self, block_number: BlockNumber, events: impl Iterator, ) -> anyhow::Result<()> { + #[rustfmt::skip] let mut stmt = self.inner().prepare_cached( - "INSERT INTO starknet_events_filters (block_number, bloom) VALUES (?, ?) ON CONFLICT \ - DO UPDATE SET bloom=excluded.bloom", + "INSERT INTO starknet_events_filters (block_number, bloom) VALUES (?, ?) \ + ON CONFLICT DO UPDATE SET bloom=excluded.bloom", )?; let mut bloom = BloomFilter::new(); @@ -249,87 +288,131 @@ impl Transaction<'_> { } }; - // TODO: - // The logic that constructs aggregate bloom filters is temporarily - // placed here, in order to compare with the current implementation. - // It will be moved to sync as a follow up. - #[cfg(feature = "aggregate_bloom")] - { - let mut aggregates = vec![]; - let mut running_aggregate = AggregateBloom::new(from_block); + match result { + ScanResult::Done => { + return Ok(PageOfEvents { + events: emitted_events, + continuation_token: None, + }) + } + ScanResult::PageFull => { + assert!(emitted_events.len() > filter.page_size); + let continuation_token = continuation_token( + &emitted_events, + ContinuationToken { + block_number: from_block, + offset: filter.offset, + }, + ) + .unwrap(); + emitted_events.truncate(filter.page_size); - let mut blocks_from_individual = vec![]; + return Ok(PageOfEvents { + events: emitted_events, + continuation_token: Some(ContinuationToken { + block_number: continuation_token.block_number, + // account for the extra event + offset: continuation_token.offset - 1, + }), + }); + } + ScanResult::ContinueFrom(block_number) => { + // We've reached a search limit without filling the page. + // We'll need to continue from the next block. + return Ok(PageOfEvents { + events: emitted_events, + continuation_token: Some(ContinuationToken { + block_number, + offset: 0, + }), + }); + } + } + } - for block_num in from_block.get()..=to_block.get() { - if block_num as usize >= max_blocks_to_scan.get() { - break; - } + // TODO: + // This function is temporarily here to compare the performance of the new + // aggregate bloom filter. + #[cfg(feature = "aggregate_bloom")] + pub fn events_from_aggregate( + &self, + filter: &EventFilter, + max_blocks_to_scan: NonZeroUsize, + ) -> Result { + use std::collections::BTreeSet; - let block_num = BlockNumber::new_or_panic(block_num); + if filter.page_size < 1 { + return Err(EventFilterError::PageSizeTooSmall); + } - // TODO: - // Using single block `BloomFilter` API for now since we don't have - // a table for `AggregateBloom` yet. - let bloom = self.load_bloom(reorg_counter, block_num)?; - match bloom { - Filter::Missing => {} - Filter::Cached(bloom) => { - if bloom.check_filter(filter) { - blocks_from_individual.push(block_num); - } + let from_block = filter.from_block.unwrap_or(BlockNumber::GENESIS); + let to_block = filter.to_block.unwrap_or(BlockNumber::MAX); + let key_filter_is_empty = filter.keys.iter().flatten().count() == 0; - match running_aggregate.add_bloom(&bloom, block_num) { - Ok(_) => {} - Err(AddBloomError::BlockLimitReached) => { - aggregates.push(running_aggregate); - running_aggregate = AggregateBloom::new(block_num + 1); - } - Err(AddBloomError::InvalidBlockNumber) => { - unreachable!() // For now. - } - } - } - Filter::Loaded(bloom) => { - if bloom.check_filter(filter) { - blocks_from_individual.push(block_num); - } + let mut emitted_events = Vec::new(); - match running_aggregate.add_bloom(&bloom, block_num) { - Ok(_) => {} - Err(AddBloomError::BlockLimitReached) => { - aggregates.push(running_aggregate); - running_aggregate = AggregateBloom::new(block_num + 1); - } - Err(AddBloomError::InvalidBlockNumber) => { - unreachable!() // For now. - } - } - } - } - } + let mut blocks_scanned: usize = 0; + let mut offset = filter.offset; - // Remainder of (to_block - from_block) % AggregateBloom::BLOCK_RANGE_LEN - aggregates.push(running_aggregate); + enum ScanResult { + Done, + PageFull, + ContinueFrom(BlockNumber), + } - let blocks_from_aggregate = aggregates.iter().fold(vec![], |mut acc, aggregate| { + let aggregates = self.load_aggregate_bloom_range(from_block, to_block)?; + let mut filtered_blocks = aggregates + .iter() + .fold(BTreeSet::new(), |mut acc, aggregate| { acc.extend(aggregate.blocks_for_filter(filter)); acc }); - if blocks_from_individual != blocks_from_aggregate { - tracing::error!("Blocks from individual and aggregate bloom filter do not match"); - tracing::error!("Individual: {:?}", blocks_from_individual,); - tracing::error!("Aggregate: {:?}", blocks_from_aggregate,); + filtered_blocks.retain(|&block| block >= from_block && block <= to_block); + + let mut blocks_iter = filtered_blocks.iter(); + let result = loop { + let Some(&block) = blocks_iter.next() else { + break ScanResult::Done; + }; + + // Stop if we're past the last block. + if block > to_block { + break ScanResult::Done; } - } - match result { - ScanResult::Done => { - return Ok(PageOfEvents { - events: emitted_events, - continuation_token: None, - }) + // Check if we've reached our block scan limit + blocks_scanned += 1; + if blocks_scanned > max_blocks_to_scan.get() { + tracing::trace!("Block scan limit reached"); + break ScanResult::ContinueFrom(block); + } + + match self.scan_block_into( + block, + filter, + key_filter_is_empty, + offset, + &mut emitted_events, + )? { + BlockScanResult::NoSuchBlock => break ScanResult::Done, + BlockScanResult::Done { new_offset } => { + offset = new_offset; + } } + + // Stop if we have a page of events plus an extra one to decide if we're on + // the last page. + if emitted_events.len() > filter.page_size { + break ScanResult::PageFull; + } + }; + + match result { + ScanResult::Done => Ok(PageOfEvents { + events: emitted_events, + continuation_token: None, + }), ScanResult::PageFull => { assert!(emitted_events.len() > filter.page_size); let continuation_token = continuation_token( @@ -342,25 +425,25 @@ impl Transaction<'_> { .unwrap(); emitted_events.truncate(filter.page_size); - return Ok(PageOfEvents { + Ok(PageOfEvents { events: emitted_events, continuation_token: Some(ContinuationToken { block_number: continuation_token.block_number, // account for the extra event offset: continuation_token.offset - 1, }), - }); + }) } ScanResult::ContinueFrom(block_number) => { // We've reached a search limit without filling the page. // We'll need to continue from the next block. - return Ok(PageOfEvents { + Ok(PageOfEvents { events: emitted_events, continuation_token: Some(ContinuationToken { block_number, offset: 0, }), - }); + }) } } } @@ -467,28 +550,63 @@ impl Transaction<'_> { }) } - // TODO: - // Implement once [`AggregateBloom`] table is added. - fn _running_bloom_aggregate(&self) -> Result, anyhow::Error> { - // Fetch running aggregate from DB - unimplemented!() + #[cfg(feature = "aggregate_bloom")] + fn load_aggregate_bloom( + &self, + block_number: BlockNumber, + ) -> anyhow::Result> { + #[rustfmt::skip] + let mut select_stmt = self.inner().prepare_cached( + "SELECT from_block, to_block, bloom FROM starknet_event_filters_aggregate \ + WHERE from_block <= ? AND to_block >= ?", + )?; + + let aggregate = select_stmt + .query_row(params![&block_number, &block_number], |row| { + let from_block: u64 = row.get(0)?; + let to_block: u64 = row.get(1)?; + let compressed_bitmap: Vec = row.get(2)?; + + Ok((from_block, to_block, compressed_bitmap)) + }) + .optional() + .context("Querying running bloom aggregate")? + .map(|(from_block, to_block, compressed_bitmap)| { + AggregateBloom::from_existing_compressed(from_block, to_block, compressed_bitmap) + }); + + Ok(aggregate) } - fn _load_bloom_range( + #[cfg(feature = "aggregate_bloom")] + fn load_aggregate_bloom_range( &self, - _from_block: BlockNumber, - _to_block: BlockNumber, + start_block: BlockNumber, + end_block: BlockNumber, ) -> anyhow::Result> { - // Should be something like: - // (from_block..to_block) - // .chunks(AggregateBloom::BLOCK_RANGE_LEN) - // .iter() - // .enumerate() - // .map(|(i, _)| { - // // load from DB where ID is i - // }) - // .collect() - unimplemented!() + #[rustfmt::skip] + let mut stmt = self.inner().prepare_cached( + "SELECT from_block, to_block, bloom FROM starknet_event_filters_aggregate \ + WHERE from_block <= ? AND to_block >= ? \ + ORDER BY from_block", + )?; + + let aggregates = stmt + .query_map(params![&end_block, &start_block], |row| { + let from_block: u64 = row.get(0)?; + let to_block: u64 = row.get(1)?; + let compressed_bitmap: Vec = row.get(2)?; + + Ok(AggregateBloom::from_existing_compressed( + from_block, + to_block, + compressed_bitmap, + )) + }) + .context("Querying bloom filter range")? + .collect::, _>>()?; + + Ok(aggregates) } } @@ -584,6 +702,14 @@ mod tests { continuation_token: None, } ); + + #[cfg(feature = "aggregate_bloom")] + { + let events_from_aggregate = tx + .events_from_aggregate(&filter, *MAX_BLOCKS_TO_SCAN) + .unwrap(); + assert_eq!(events_from_aggregate, events); + } } #[test] @@ -724,6 +850,14 @@ mod tests { continuation_token: None, } ); + + #[cfg(feature = "aggregate_bloom")] + { + let events_from_aggregate = tx + .events_from_aggregate(&filter, *MAX_BLOCKS_TO_SCAN) + .unwrap(); + assert_eq!(events_from_aggregate, events); + } } #[test] @@ -755,6 +889,14 @@ mod tests { continuation_token: None, } ); + + #[cfg(feature = "aggregate_bloom")] + { + let events_from_aggregate = tx + .events_from_aggregate(&filter, *MAX_BLOCKS_TO_SCAN) + .unwrap(); + assert_eq!(events_from_aggregate, events); + } } #[test] @@ -788,6 +930,14 @@ mod tests { } ); + #[cfg(feature = "aggregate_bloom")] + { + let events_from_aggregate = tx + .events_from_aggregate(&filter, *MAX_BLOCKS_TO_SCAN) + .unwrap(); + assert_eq!(events_from_aggregate, events); + } + // test continuation token let filter = EventFilter { from_block: Some(events.continuation_token.unwrap().block_number), @@ -810,6 +960,14 @@ mod tests { continuation_token: None, } ); + + #[cfg(feature = "aggregate_bloom")] + { + let events_from_aggregate = tx + .events_from_aggregate(&filter, *MAX_BLOCKS_TO_SCAN) + .unwrap(); + assert_eq!(events_from_aggregate, events); + } } #[test] @@ -840,6 +998,14 @@ mod tests { continuation_token: None, } ); + + #[cfg(feature = "aggregate_bloom")] + { + let events_from_aggregate = tx + .events_from_aggregate(&filter, *MAX_BLOCKS_TO_SCAN) + .unwrap(); + assert_eq!(events_from_aggregate, events); + } } #[test] @@ -870,6 +1036,14 @@ mod tests { continuation_token: None, } ); + + #[cfg(feature = "aggregate_bloom")] + { + let events_from_aggregate = tx + .events_from_aggregate(&filter, *MAX_BLOCKS_TO_SCAN) + .unwrap(); + assert_eq!(events_from_aggregate, events); + } } #[test] @@ -900,11 +1074,20 @@ mod tests { } ); + #[cfg(feature = "aggregate_bloom")] + { + let events_from_aggregate = tx + .events_from_aggregate(&filter, *MAX_BLOCKS_TO_SCAN) + .unwrap(); + assert_eq!(events_from_aggregate, events); + } + // try event keys in the wrong order, should not match let filter = EventFilter { keys: vec![vec![expected_event.keys[1]], vec![expected_event.keys[0]]], ..filter }; + let events = tx .events(&filter, *MAX_BLOCKS_TO_SCAN, *MAX_BLOOM_FILTERS_TO_LOAD) .unwrap(); @@ -915,6 +1098,14 @@ mod tests { continuation_token: None, } ); + + #[cfg(feature = "aggregate_bloom")] + { + let events_from_aggregate = tx + .events_from_aggregate(&filter, *MAX_BLOCKS_TO_SCAN) + .unwrap(); + assert_eq!(events_from_aggregate, events); + } } #[test] @@ -943,6 +1134,14 @@ mod tests { continuation_token: None, } ); + + #[cfg(feature = "aggregate_bloom")] + { + let events_from_aggregate = tx + .events_from_aggregate(&filter, *MAX_BLOCKS_TO_SCAN) + .unwrap(); + assert_eq!(events_from_aggregate, events); + } } #[test] @@ -960,6 +1159,7 @@ mod tests { page_size: 10, offset: 0, }; + let events = tx .events(&filter, *MAX_BLOCKS_TO_SCAN, *MAX_BLOOM_FILTERS_TO_LOAD) .unwrap(); @@ -974,6 +1174,14 @@ mod tests { } ); + #[cfg(feature = "aggregate_bloom")] + { + let events_from_aggregate = tx + .events_from_aggregate(&filter, *MAX_BLOCKS_TO_SCAN) + .unwrap(); + assert_eq!(events_from_aggregate, events); + } + let filter = EventFilter { from_block: None, to_block: None, @@ -982,6 +1190,7 @@ mod tests { page_size: 10, offset: 10, }; + let events = tx .events(&filter, *MAX_BLOCKS_TO_SCAN, *MAX_BLOOM_FILTERS_TO_LOAD) .unwrap(); @@ -996,6 +1205,14 @@ mod tests { } ); + #[cfg(feature = "aggregate_bloom")] + { + let events_from_aggregate = tx + .events_from_aggregate(&filter, *MAX_BLOCKS_TO_SCAN) + .unwrap(); + assert_eq!(events_from_aggregate, events); + } + let filter = EventFilter { from_block: None, to_block: None, @@ -1004,6 +1221,7 @@ mod tests { page_size: 10, offset: 30, }; + let events = tx .events(&filter, *MAX_BLOCKS_TO_SCAN, *MAX_BLOOM_FILTERS_TO_LOAD) .unwrap(); @@ -1014,6 +1232,14 @@ mod tests { continuation_token: None } ); + + #[cfg(feature = "aggregate_bloom")] + { + let events_from_aggregate = tx + .events_from_aggregate(&filter, *MAX_BLOCKS_TO_SCAN) + .unwrap(); + assert_eq!(events_from_aggregate, events); + } } #[test] @@ -1032,6 +1258,7 @@ mod tests { // _after_ the last one offset: test_utils::NUM_BLOCKS * test_utils::EVENTS_PER_BLOCK, }; + let events = tx .events(&filter, *MAX_BLOCKS_TO_SCAN, *MAX_BLOOM_FILTERS_TO_LOAD) .unwrap(); @@ -1042,6 +1269,14 @@ mod tests { continuation_token: None, } ); + + #[cfg(feature = "aggregate_bloom")] + { + let events_from_aggregate = tx + .events_from_aggregate(&filter, *MAX_BLOCKS_TO_SCAN) + .unwrap(); + assert_eq!(events_from_aggregate, events); + } } #[test] @@ -1065,6 +1300,7 @@ mod tests { page_size: 2, offset: 0, }; + let events = tx .events(&filter, *MAX_BLOCKS_TO_SCAN, *MAX_BLOOM_FILTERS_TO_LOAD) .unwrap(); @@ -1079,6 +1315,14 @@ mod tests { } ); + #[cfg(feature = "aggregate_bloom")] + { + let events_from_aggregate = tx + .events_from_aggregate(&filter, *MAX_BLOCKS_TO_SCAN) + .unwrap(); + assert_eq!(events_from_aggregate, events); + } + // increase offset let filter: EventFilter = EventFilter { from_block: None, @@ -1088,6 +1332,7 @@ mod tests { page_size: 2, offset: 2, }; + let events = tx .events(&filter, *MAX_BLOCKS_TO_SCAN, *MAX_BLOOM_FILTERS_TO_LOAD) .unwrap(); @@ -1102,6 +1347,14 @@ mod tests { } ); + #[cfg(feature = "aggregate_bloom")] + { + let events_from_aggregate = tx + .events_from_aggregate(&filter, *MAX_BLOCKS_TO_SCAN) + .unwrap(); + assert_eq!(events_from_aggregate, events); + } + // using the continuation token should be equivalent to the previous query let filter: EventFilter = EventFilter { from_block: Some(BlockNumber::new_or_panic(0)), @@ -1111,6 +1364,7 @@ mod tests { page_size: 2, offset: 2, }; + let events = tx .events(&filter, *MAX_BLOCKS_TO_SCAN, *MAX_BLOOM_FILTERS_TO_LOAD) .unwrap(); @@ -1125,6 +1379,14 @@ mod tests { } ); + #[cfg(feature = "aggregate_bloom")] + { + let events_from_aggregate = tx + .events_from_aggregate(&filter, *MAX_BLOCKS_TO_SCAN) + .unwrap(); + assert_eq!(events_from_aggregate, events); + } + // increase offset by two let filter = EventFilter { from_block: None, @@ -1134,6 +1396,7 @@ mod tests { page_size: 2, offset: 4, }; + let events = tx .events(&filter, *MAX_BLOCKS_TO_SCAN, *MAX_BLOOM_FILTERS_TO_LOAD) .unwrap(); @@ -1145,6 +1408,14 @@ mod tests { } ); + #[cfg(feature = "aggregate_bloom")] + { + let events_from_aggregate = tx + .events_from_aggregate(&filter, *MAX_BLOCKS_TO_SCAN) + .unwrap(); + assert_eq!(events_from_aggregate, events); + } + // using the continuation token should be equivalent to the previous query let filter = EventFilter { from_block: Some(BlockNumber::new_or_panic(3)), @@ -1154,6 +1425,7 @@ mod tests { page_size: 2, offset: 1, }; + let events = tx .events(&filter, *MAX_BLOCKS_TO_SCAN, *MAX_BLOOM_FILTERS_TO_LOAD) .unwrap(); @@ -1164,6 +1436,14 @@ mod tests { continuation_token: None, } ); + + #[cfg(feature = "aggregate_bloom")] + { + let events_from_aggregate = tx + .events_from_aggregate(&filter, *MAX_BLOCKS_TO_SCAN) + .unwrap(); + assert_eq!(events_from_aggregate, events); + } } #[test] @@ -1181,6 +1461,7 @@ mod tests { page_size: 20, offset: 0, }; + let events = tx .events(&filter, 1.try_into().unwrap(), *MAX_BLOOM_FILTERS_TO_LOAD) .unwrap(); @@ -1195,6 +1476,14 @@ mod tests { } ); + #[cfg(feature = "aggregate_bloom")] + { + let events_from_aggregate = tx + .events_from_aggregate(&filter, 1.try_into().unwrap()) + .unwrap(); + assert_eq!(events_from_aggregate, events); + } + let filter = EventFilter { from_block: Some(BlockNumber::new_or_panic(1)), to_block: None, @@ -1203,6 +1492,7 @@ mod tests { page_size: 20, offset: 0, }; + let events = tx .events(&filter, 1.try_into().unwrap(), *MAX_BLOOM_FILTERS_TO_LOAD) .unwrap(); @@ -1216,13 +1506,17 @@ mod tests { }), } ); + + #[cfg(feature = "aggregate_bloom")] + { + let events_from_aggregate = tx + .events_from_aggregate(&filter, 1.try_into().unwrap()) + .unwrap(); + assert_eq!(events_from_aggregate, events); + } } #[test] - // TODO: - // This fails when "aggregate_bloom" feature is enabled because in that case all filters are - // loaded twice. We can ignore it for now. - #[cfg_attr(feature = "aggregate_bloom", ignore)] fn bloom_filter_load_limit() { let (storage, test_data) = test_utils::setup_test_storage(); let emitted_events = test_data.events; @@ -1237,6 +1531,7 @@ mod tests { page_size: emitted_events.len(), offset: 0, }; + let events = tx .events(&filter, *MAX_BLOCKS_TO_SCAN, 1.try_into().unwrap()) .unwrap(); @@ -1251,6 +1546,17 @@ mod tests { } ); + // TODO: + // This does not match at the moment because aggregate bloom implementation + // does not have a limit on the number of bloom filters to load. + #[cfg(all(feature = "aggregate_bloom", any()))] + { + let events_from_aggregate = tx + .events_from_aggregate(&filter, *MAX_BLOCKS_TO_SCAN) + .unwrap(); + assert_eq!(events_from_aggregate, events); + } + let filter = EventFilter { from_block: Some(BlockNumber::new_or_panic(1)), to_block: None, @@ -1259,6 +1565,7 @@ mod tests { page_size: emitted_events.len(), offset: 0, }; + let events = tx .events(&filter, *MAX_BLOCKS_TO_SCAN, 1.try_into().unwrap()) .unwrap(); @@ -1272,5 +1579,16 @@ mod tests { }), } ); + + // TODO: + // This does not match at the moment because aggregate bloom implementation + // does not have a limit on the number of bloom filters to load. + #[cfg(all(feature = "aggregate_bloom", any()))] + { + let events_from_aggregate = tx + .events_from_aggregate(&filter, *MAX_BLOCKS_TO_SCAN) + .unwrap(); + assert_eq!(events_from_aggregate, events); + } } } diff --git a/crates/storage/src/connection/transaction.rs b/crates/storage/src/connection/transaction.rs index 7187bec55e..91894e86dc 100644 --- a/crates/storage/src/connection/transaction.rs +++ b/crates/storage/src/connection/transaction.rs @@ -167,6 +167,12 @@ impl Transaction<'_> { .context("Inserting transaction data")?; if let Some(events) = events { + #[cfg(feature = "aggregate_bloom")] + { + let events: Vec = events.iter().flatten().cloned().collect(); + self.upsert_block_events_aggregate(block_number, &events) + .context("Inserting events into Bloom filter aggregate")?; + } let events = events.iter().flatten(); self.upsert_block_events(block_number, events) .context("Inserting events into Bloom filter")?; @@ -210,6 +216,12 @@ impl Transaction<'_> { ]) .context("Updating events")?; + #[cfg(feature = "aggregate_bloom")] + { + let events: Vec = events.iter().flatten().cloned().collect(); + self.upsert_block_events_aggregate(block_number, &events) + .context("Inserting events into Bloom filter aggregate")?; + } self.upsert_block_events(block_number, events.iter().flatten()) .context("Inserting events into Bloom filter")?; diff --git a/crates/storage/src/schema.rs b/crates/storage/src/schema.rs index 88a8af88e8..ffe92147ec 100644 --- a/crates/storage/src/schema.rs +++ b/crates/storage/src/schema.rs @@ -25,6 +25,8 @@ mod revision_0062; mod revision_0063; mod revision_0064; mod revision_0065; +#[cfg(feature = "aggregate_bloom")] +mod revision_0066; pub(crate) use base::base_schema; @@ -58,6 +60,8 @@ pub fn migrations() -> &'static [MigrationFn] { revision_0063::migrate, revision_0064::migrate, revision_0065::migrate, + #[cfg(feature = "aggregate_bloom")] + revision_0066::migrate, ] } diff --git a/crates/storage/src/schema/revision_0066.rs b/crates/storage/src/schema/revision_0066.rs new file mode 100644 index 0000000000..52159524a0 --- /dev/null +++ b/crates/storage/src/schema/revision_0066.rs @@ -0,0 +1,77 @@ +use anyhow::Context; +use pathfinder_common::BlockNumber; +use rusqlite::params; + +use crate::bloom::{AggregateBloom, BloomFilter}; + +#[allow(dead_code)] +pub(crate) fn migrate(tx: &rusqlite::Transaction<'_>) -> anyhow::Result<()> { + tracing::warn!("Creating starknet_event_filters table with aggregate bloom filters"); + + let mut select_old_filters_query = + tx.prepare("SELECT bloom FROM starknet_events_filters ORDER BY block_number")?; + + let mut bloom_filters_bytes = select_old_filters_query + .query_map(params![], |row| { + let bytes = row.get::<_, Vec>(0)?; + + Ok(bytes) + }) + .context("Selecting old filters")?; + + let mut bloom_filters = vec![]; + loop { + let Some(bloom) = bloom_filters_bytes.next().transpose()? else { + break; + }; + + bloom_filters.push(BloomFilter::from_compressed_bytes(&bloom)); + } + + tx.execute( + "CREATE TABLE starknet_event_filters_aggregate ( + from_block INTEGER NOT NULL, + to_block INTEGER NOT NULL, + bloom BLOB, + UNIQUE(from_block, to_block) + )", + params![], + ) + .context("Creating starknet_event_filters_aggregate table")?; + + bloom_filters + .chunks(AggregateBloom::BLOCK_RANGE_LEN as usize) + .enumerate() + .try_for_each(|(i, bloom_filter_chunk)| -> anyhow::Result<()> { + let from_block = i as u64 * AggregateBloom::BLOCK_RANGE_LEN; + let to_block = from_block + AggregateBloom::BLOCK_RANGE_LEN - 1; + let from_block = BlockNumber::new_or_panic(from_block); + let to_block = BlockNumber::new_or_panic(to_block); + + let mut aggregate = AggregateBloom::new(from_block); + + for (j, bloom_filter) in bloom_filter_chunk.iter().enumerate() { + let block_number = from_block + j as u64; + + aggregate.add_bloom(bloom_filter, block_number); + } + + tx.execute( + "INSERT INTO starknet_event_filters_aggregate (from_block, to_block, bloom) + VALUES (?, ?, ?)", + params![ + &from_block.get(), + &to_block.get(), + &aggregate.compress_bitmap() + ], + ) + .context("Inserting aggregate bloom filter")?; + + Ok(()) + })?; + + // TODO: + // Delete old filters table + + Ok(()) +} From 7edff5c0819bdb24de3fb0f8c84e9af8b4a86838 Mon Sep 17 00:00:00 2001 From: sistemd Date: Fri, 8 Nov 2024 14:26:56 +0100 Subject: [PATCH 250/282] use named params --- crates/storage/src/connection/event.rs | 30 +++++++++++++++----------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/crates/storage/src/connection/event.rs b/crates/storage/src/connection/event.rs index 726efd1305..2b4a60de3a 100644 --- a/crates/storage/src/connection/event.rs +++ b/crates/storage/src/connection/event.rs @@ -587,22 +587,28 @@ impl Transaction<'_> { #[rustfmt::skip] let mut stmt = self.inner().prepare_cached( "SELECT from_block, to_block, bloom FROM starknet_event_filters_aggregate \ - WHERE from_block <= ? AND to_block >= ? \ + WHERE from_block <= :end_block AND to_block >= :start_block \ ORDER BY from_block", )?; let aggregates = stmt - .query_map(params![&end_block, &start_block], |row| { - let from_block: u64 = row.get(0)?; - let to_block: u64 = row.get(1)?; - let compressed_bitmap: Vec = row.get(2)?; - - Ok(AggregateBloom::from_existing_compressed( - from_block, - to_block, - compressed_bitmap, - )) - }) + .query_map( + named_params![ + ":end_block": &end_block, + ":start_block": &start_block + ], + |row| { + let from_block: u64 = row.get(0)?; + let to_block: u64 = row.get(1)?; + let compressed_bitmap: Vec = row.get(2)?; + + Ok(AggregateBloom::from_existing_compressed( + from_block, + to_block, + compressed_bitmap, + )) + }, + ) .context("Querying bloom filter range")? .collect::, _>>()?; From cd55d08470a7354a21807a91b9c834d32890e841 Mon Sep 17 00:00:00 2001 From: sistemd Date: Fri, 8 Nov 2024 14:26:56 +0100 Subject: [PATCH 251/282] fmt --- crates/storage/src/connection/event.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/storage/src/connection/event.rs b/crates/storage/src/connection/event.rs index 2b4a60de3a..e6fe68711a 100644 --- a/crates/storage/src/connection/event.rs +++ b/crates/storage/src/connection/event.rs @@ -594,7 +594,7 @@ impl Transaction<'_> { let aggregates = stmt .query_map( named_params![ - ":end_block": &end_block, + ":end_block": &end_block, ":start_block": &start_block ], |row| { From 3146b5504029e9c65e3a1ced8ae87c9eef09d3d0 Mon Sep 17 00:00:00 2001 From: Krisztian Kovacs Date: Fri, 8 Nov 2024 15:26:12 +0100 Subject: [PATCH 252/282] fix(rpc/v08/get_compiled_casm): omit `bytecode_segment_lengths` from response if None That isn't a required property in the response so it's actually conformant to just skip serializing it. However, `null` is not a valid value according to the spec. --- crates/common/src/casm_class.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/common/src/casm_class.rs b/crates/common/src/casm_class.rs index cc08b9e133..2385410edf 100644 --- a/crates/common/src/casm_class.rs +++ b/crates/common/src/casm_class.rs @@ -9,6 +9,7 @@ use crate::EntryPoint; #[derive(Debug, Serialize, Deserialize, Eq, PartialEq)] pub struct CasmContractClass { pub bytecode: Vec, + #[serde(default, skip_serializing_if = "Option::is_none")] pub bytecode_segment_lengths: Option, pub compiler_version: String, pub hints: serde_json::Value, From fc103d3e0915721b5d73df7a7d6788ad41c363db Mon Sep 17 00:00:00 2001 From: t00ts Date: Wed, 13 Nov 2024 09:29:02 +0100 Subject: [PATCH 253/282] fix(rpc): returning wrong error on rpc `getCompiledCasm` --- crates/rpc/src/method/get_compiled_casm.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/rpc/src/method/get_compiled_casm.rs b/crates/rpc/src/method/get_compiled_casm.rs index ce8f969258..393113f2e0 100644 --- a/crates/rpc/src/method/get_compiled_casm.rs +++ b/crates/rpc/src/method/get_compiled_casm.rs @@ -87,7 +87,7 @@ pub async fn get_compiled_casm(context: RpcContext, input: Input) -> Result Date: Wed, 13 Nov 2024 11:03:17 +0100 Subject: [PATCH 254/282] fix(rpc): flatten `subscription_id` on new subscription result --- crates/rpc/src/jsonrpc/router/subscription.rs | 13 +++---------- crates/rpc/src/method/subscribe_events.rs | 10 +++++----- crates/rpc/src/method/subscribe_new_heads.rs | 8 ++++---- .../src/method/subscribe_pending_transactions.rs | 8 ++++---- .../rpc/src/method/subscribe_transaction_status.rs | 4 ++-- 5 files changed, 18 insertions(+), 25 deletions(-) diff --git a/crates/rpc/src/jsonrpc/router/subscription.rs b/crates/rpc/src/jsonrpc/router/subscription.rs index da767e4164..c37740b26e 100644 --- a/crates/rpc/src/jsonrpc/router/subscription.rs +++ b/crates/rpc/src/jsonrpc/router/subscription.rs @@ -661,9 +661,7 @@ async fn handle_request( panic!("subscription id overflow"); } Ok(Some(RpcResponse { - output: Ok( - serde_json::to_value(&SubscriptionIdResult { subscription_id }).unwrap(), - ), + output: Ok(serde_json::to_value(subscription_id).unwrap()), id: req_id, version: state.version, })) @@ -682,11 +680,6 @@ struct StarknetUnsubscribeParams { subscription_id: SubscriptionId, } -#[derive(Debug, serde::Serialize)] -struct SubscriptionIdResult { - subscription_id: SubscriptionId, -} - #[derive(Debug)] pub struct SubscriptionSender { pub subscription_id: SubscriptionId, @@ -885,7 +878,7 @@ mod tests { let json: serde_json::Value = serde_json::from_str(&json).unwrap(); assert_eq!(json["jsonrpc"], "2.0"); assert_eq!(json["id"], 1); - json["result"]["subscription_id"].as_u64().unwrap() + json["result"].as_u64().unwrap() } _ => panic!("Expected text message"), }; @@ -962,7 +955,7 @@ mod tests { let json: serde_json::Value = serde_json::from_str(&json).unwrap(); assert_eq!(json["jsonrpc"], "2.0"); assert_eq!(json["id"], 1); - json["result"]["subscription_id"].as_u64().unwrap() + json["result"].as_u64().unwrap() } _ => panic!("Expected text message"), }; diff --git a/crates/rpc/src/method/subscribe_events.rs b/crates/rpc/src/method/subscribe_events.rs index ab3683d482..534ab44339 100644 --- a/crates/rpc/src/method/subscribe_events.rs +++ b/crates/rpc/src/method/subscribe_events.rs @@ -283,7 +283,7 @@ mod tests { let json: serde_json::Value = serde_json::from_str(&json).unwrap(); assert_eq!(json["jsonrpc"], "2.0"); assert_eq!(json["id"], 1); - json["result"]["subscription_id"].as_u64().unwrap() + json["result"].as_u64().unwrap() } _ => panic!("Expected text message"), }; @@ -347,7 +347,7 @@ mod tests { let json: serde_json::Value = serde_json::from_str(&json).unwrap(); assert_eq!(json["jsonrpc"], "2.0"); assert_eq!(json["id"], 1); - json["result"]["subscription_id"].as_u64().unwrap() + json["result"].as_u64().unwrap() } _ => panic!("Expected text message"), }; @@ -413,7 +413,7 @@ mod tests { let json: serde_json::Value = serde_json::from_str(&json).unwrap(); assert_eq!(json["jsonrpc"], "2.0"); assert_eq!(json["id"], 1); - json["result"]["subscription_id"].as_u64().unwrap() + json["result"].as_u64().unwrap() } _ => panic!("Expected text message"), }; @@ -480,7 +480,7 @@ mod tests { let json: serde_json::Value = serde_json::from_str(&json).unwrap(); assert_eq!(json["jsonrpc"], "2.0"); assert_eq!(json["id"], 1); - json["result"]["subscription_id"].as_u64().unwrap() + json["result"].as_u64().unwrap() } _ => panic!("Expected text message"), }; @@ -603,7 +603,7 @@ mod tests { let json: serde_json::Value = serde_json::from_str(&json).unwrap(); assert_eq!(json["jsonrpc"], "2.0"); assert_eq!(json["id"], 1); - json["result"]["subscription_id"].as_u64().unwrap() + json["result"].as_u64().unwrap() } _ => panic!("Expected text message"), }; diff --git a/crates/rpc/src/method/subscribe_new_heads.rs b/crates/rpc/src/method/subscribe_new_heads.rs index 120d731ccd..db8a082147 100644 --- a/crates/rpc/src/method/subscribe_new_heads.rs +++ b/crates/rpc/src/method/subscribe_new_heads.rs @@ -259,7 +259,7 @@ mod tests { let json: serde_json::Value = serde_json::from_str(&json).unwrap(); assert_eq!(json["jsonrpc"], "2.0"); assert_eq!(json["id"], 1); - json["result"]["subscription_id"].as_u64().unwrap() + json["result"].as_u64().unwrap() } _ => panic!("Expected text message"), }; @@ -347,7 +347,7 @@ mod tests { let json: serde_json::Value = serde_json::from_str(&json).unwrap(); assert_eq!(json["jsonrpc"], "2.0"); assert_eq!(json["id"], 1); - json["result"]["subscription_id"].as_u64().unwrap() + json["result"].as_u64().unwrap() } _ => panic!("Expected text message"), }; @@ -396,7 +396,7 @@ mod tests { let json: serde_json::Value = serde_json::from_str(&json).unwrap(); assert_eq!(json["jsonrpc"], "2.0"); assert_eq!(json["id"], 1); - json["result"]["subscription_id"].as_u64().unwrap() + json["result"].as_u64().unwrap() } _ => panic!("Expected text message"), }; @@ -542,7 +542,7 @@ mod tests { let json: serde_json::Value = serde_json::from_str(&json).unwrap(); assert_eq!(json["jsonrpc"], "2.0"); assert_eq!(json["id"], 1); - json["result"]["subscription_id"].as_u64().unwrap() + json["result"].as_u64().unwrap() } _ => panic!("Expected text message"), }; diff --git a/crates/rpc/src/method/subscribe_pending_transactions.rs b/crates/rpc/src/method/subscribe_pending_transactions.rs index 142c371a5d..824af9e901 100644 --- a/crates/rpc/src/method/subscribe_pending_transactions.rs +++ b/crates/rpc/src/method/subscribe_pending_transactions.rs @@ -184,7 +184,7 @@ mod tests { let json: serde_json::Value = serde_json::from_str(&json).unwrap(); assert_eq!(json["jsonrpc"], "2.0"); assert_eq!(json["id"], 1); - json["result"]["subscription_id"].as_u64().unwrap() + json["result"].as_u64().unwrap() } _ => { panic!("Expected text message"); @@ -273,7 +273,7 @@ mod tests { let json: serde_json::Value = serde_json::from_str(&json).unwrap(); assert_eq!(json["jsonrpc"], "2.0"); assert_eq!(json["id"], 1); - json["result"]["subscription_id"].as_u64().unwrap() + json["result"].as_u64().unwrap() } _ => { panic!("Expected text message"); @@ -326,7 +326,7 @@ mod tests { let json: serde_json::Value = serde_json::from_str(&json).unwrap(); assert_eq!(json["jsonrpc"], "2.0"); assert_eq!(json["id"], 1); - json["result"]["subscription_id"].as_u64().unwrap() + json["result"].as_u64().unwrap() } _ => { panic!("Expected text message"); @@ -375,7 +375,7 @@ mod tests { let json: serde_json::Value = serde_json::from_str(&json).unwrap(); assert_eq!(json["jsonrpc"], "2.0"); assert_eq!(json["id"], 1); - json["result"]["subscription_id"].as_u64().unwrap() + json["result"].as_u64().unwrap() } _ => { panic!("Expected text message"); diff --git a/crates/rpc/src/method/subscribe_transaction_status.rs b/crates/rpc/src/method/subscribe_transaction_status.rs index 8f835810b3..4642099c4f 100644 --- a/crates/rpc/src/method/subscribe_transaction_status.rs +++ b/crates/rpc/src/method/subscribe_transaction_status.rs @@ -963,7 +963,7 @@ mod tests { let mut json: serde_json::Value = serde_json::from_str(&json).unwrap(); assert_eq!(json["jsonrpc"], "2.0"); assert_eq!(json["id"], 1); - json["result"]["subscription_id"].take() + json["result"].take() } _ => panic!("Expected text message"), }; @@ -1117,7 +1117,7 @@ mod tests { let mut json: serde_json::Value = serde_json::from_str(&json).unwrap(); assert_eq!(json["jsonrpc"], "2.0"); assert_eq!(json["id"], 1); - json["result"]["subscription_id"].take() + json["result"].take() } _ => panic!("Expected text message"), }; From 289aed4216c16634d3755b7236a46e8abf963c6f Mon Sep 17 00:00:00 2001 From: Krzysztof Lis Date: Tue, 12 Nov 2024 11:39:26 +0100 Subject: [PATCH 255/282] fix(sync/track): missing transactions is a fatal error --- crates/pathfinder/src/sync.rs | 5 +++++ crates/pathfinder/src/sync/track.rs | 17 +++++++++-------- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/crates/pathfinder/src/sync.rs b/crates/pathfinder/src/sync.rs index 8a5c696094..2f38ef589a 100644 --- a/crates/pathfinder/src/sync.rs +++ b/crates/pathfinder/src/sync.rs @@ -103,6 +103,9 @@ impl Sync { /// with an error. async fn checkpoint_sync(&self) -> anyhow::Result<(BlockNumber, BlockHash)> { let mut checkpoint = self.get_checkpoint().await; + let from = (checkpoint.block_number, checkpoint.block_hash); + + tracing::info!(?from, "Checkpoint sync started"); loop { let result = checkpoint::Sync { @@ -166,6 +169,8 @@ impl Sync { mut next: BlockNumber, mut parent_hash: BlockHash, ) -> anyhow::Result<()> { + tracing::info!(next_block=%next, "Track sync started"); + loop { let mut result = track::Sync { latest: LatestStream::spawn(self.fgw_client.clone(), Duration::from_secs(2)), diff --git a/crates/pathfinder/src/sync/track.rs b/crates/pathfinder/src/sync/track.rs index 8d4e739dd7..be779465b9 100644 --- a/crates/pathfinder/src/sync/track.rs +++ b/crates/pathfinder/src/sync/track.rs @@ -1,7 +1,7 @@ use std::collections::{HashMap, HashSet}; use std::pin; -use anyhow::{anyhow, Context}; +use anyhow::Context; use futures::stream::BoxStream; use futures::{pin_mut, Stream, StreamExt, TryStreamExt}; use p2p::client::peer_agnostic::traits::{BlockClient, HeaderStream}; @@ -459,19 +459,20 @@ impl

EventSource

{ } = self; while let Some(header) = headers.next().await { + let Some(block_transactions) = transactions.next().await else { + // Expected transactions stream ended prematurely which means there was an error + // at the source and track sync should be restarted. We should not signal an + // error here as the error has already been indicated at the + // transactions source. + return; + }; + let (peer, mut events) = loop { if let Some(stream) = p2p.clone().events_for_block(header.number).await { break stream; } }; - let Some(block_transactions) = transactions.next().await else { - // TODO is this a fatal error? - // is this an error at all? Can blocks have no transactions? - let _ = tx.send(Err(anyhow!("No transactions").into())).await; - return; - }; - let mut block_events: HashMap<_, Vec> = HashMap::new(); let event_count = header.event_count; From ee37ce6f39c0c27c9290382e674052dbdbada8ed Mon Sep 17 00:00:00 2001 From: Krisztian Kovacs Date: Wed, 13 Nov 2024 10:25:18 +0100 Subject: [PATCH 256/282] fix(sync/class_definitions): don't keep a read transaction open during classes sync Keeping a transaction open causes the SQLite WAL file to grow over 2GiB because changes cannot be merged back into the database as long as there are open read transactions. This change also removes some unused duplicate code. Closes #2242 --- .../pathfinder/src/sync/class_definitions.rs | 128 ++---------------- 1 file changed, 10 insertions(+), 118 deletions(-) diff --git a/crates/pathfinder/src/sync/class_definitions.rs b/crates/pathfinder/src/sync/class_definitions.rs index 47df093fd9..9b68528639 100644 --- a/crates/pathfinder/src/sync/class_definitions.rs +++ b/crates/pathfinder/src/sync/class_definitions.rs @@ -247,57 +247,6 @@ fn verify_hash_impl(peer: &PeerId, input: ClassWithLayout) -> Result, - current: ExpectedDeclarations, -} - -impl VerifyDeclaredAt { - pub fn new(expectation_source: Receiver) -> Self { - Self { - expectation_source, - current: Default::default(), - } - } -} - -impl ProcessStage for VerifyDeclaredAt { - const NAME: &'static str = "Class::VerifyDeclarationBlock"; - - type Input = Class; - type Output = Class; - - fn map(&mut self, peer: &PeerId, input: Self::Input) -> Result { - if self.current.classes.is_empty() { - self.current = loop { - let expected = self - .expectation_source - .blocking_recv() - .context("Receiving expected declarations")?; - - // Some blocks may have no declared classes. Try the next one. - if expected.classes.is_empty() { - continue; - } - - break expected; - }; - } - - if self.current.block_number != input.block_number { - tracing::debug!(%peer, expected_block_number=%self.current.block_number, block_number=%input.block_number, class_hash=%input.hash, "Unexpected class definition"); - return Err(SyncError::UnexpectedClass(*peer)); - } - - if self.current.classes.remove(&input.hash) { - Ok(input) - } else { - tracing::debug!(%peer, block_number=%input.block_number, class_hash=%input.hash, "Unexpected class definition"); - Err(SyncError::UnexpectedClass(*peer)) - } - } -} - /// This function makes sure that the classes we receive from other peers are /// really declared at the expected blocks. /// @@ -410,64 +359,6 @@ pub struct ExpectedDeclarations { pub classes: HashSet, } -pub struct ExpectedDeclarationsSource { - db_connection: pathfinder_storage::Connection, - start: BlockNumber, - stop: BlockNumber, -} - -impl ExpectedDeclarationsSource { - pub fn new( - db_connection: pathfinder_storage::Connection, - start: BlockNumber, - stop: BlockNumber, - ) -> Self { - Self { - db_connection, - start, - stop, - } - } - - pub fn spawn(self) -> anyhow::Result> { - let (tx, rx) = mpsc::channel(1); - let Self { - mut db_connection, - mut start, - stop, - } = self; - - tokio::task::spawn_blocking(move || { - let db = db_connection - .transaction() - .context("Creating database transaction")?; - - while start <= stop { - let declared = db - .declared_classes_at(start.into()) - .context("Querying declared classes at block")? - .context("Block header not found")? - .into_iter() - .collect::>(); - - if !declared.is_empty() { - tx.blocking_send(ExpectedDeclarations { - block_number: start, - classes: declared, - }) - .context("Sending expected declarations")?; - } - - start += 1; - } - - anyhow::Ok(()) - }); - - Ok(rx) - } -} - /// Returns a stream of sets of class hashes declared at each block in the range /// `start..=stop`. pub(super) fn expected_declarations_stream( @@ -483,21 +374,22 @@ pub(super) fn expected_declarations_stream( return; } }; - let db = match db.transaction().context("Creating database transaction") { - Ok(x) => x, - Err(e) => { - tx.blocking_send(Err(e)); - return; - } - }; while start <= stop { + let db = match db.transaction().context("Creating database transaction") { + Ok(x) => x, + Err(e) => { + tx.blocking_send(Err(e)); + return; + } + }; let res = db .declared_classes_at(start.into()) .context("Querying declared classes at block") .and_then(|x| x.context("Block header not found")) .map_err(Into::into) .map(|x| (start, x.into_iter().collect::>())); + drop(db); let is_err = res.is_err(); let is_empty = res.as_ref().map(|(_, x)| x.is_empty()).unwrap_or(false); if !is_empty { @@ -639,7 +531,6 @@ pub(super) async fn persist( let mut db = storage .connection() .context("Creating database connection")?; - let db = db.transaction().context("Creating database transaction")?; let tail = classes .last() .map(|x| x.data.block_number) @@ -651,9 +542,10 @@ pub(super) async fn persist( hash, } in classes.into_iter().map(|x| x.data) { + let db = db.transaction().context("Creating database transaction")?; persist_impl(&db, hash, definition)?; + db.commit().context("Committing db transaction")?; } - db.commit().context("Committing db transaction")?; Ok(tail) }) From 55024ad0bceef1f890028bbdc9d4c8af8c2cea9d Mon Sep 17 00:00:00 2001 From: Krzysztof Lis Date: Wed, 13 Nov 2024 14:52:41 +0100 Subject: [PATCH 257/282] fix(sync/track): LatestStream yields duplicate items of the same value Instead of waiting. --- crates/pathfinder/src/sync.rs | 18 +++++++++++++----- crates/pathfinder/src/sync/track.rs | 4 +--- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/crates/pathfinder/src/sync.rs b/crates/pathfinder/src/sync.rs index 2f38ef589a..ca554cbfd2 100644 --- a/crates/pathfinder/src/sync.rs +++ b/crates/pathfinder/src/sync.rs @@ -208,8 +208,6 @@ struct LatestStream { impl Clone for LatestStream { fn clone(&self) -> Self { - tracing::info!("LatestStream: clone()"); - Self { // Keep the rx for the next clone rx: self.rx.clone(), @@ -234,7 +232,6 @@ impl Stream for LatestStream { impl LatestStream { fn spawn(fgw: GatewayClient, head_poll_interval: Duration) -> Self { - tracing::info!("LatestStream: spawn()"); // No buffer, for backpressure let (tx, rx) = watch::channel((BlockNumber::GENESIS, BlockHash::ZERO)); @@ -253,12 +250,23 @@ impl LatestStream { continue; }; - tracing::info!(?latest, "LatestStream: block_header()"); + tracing::trace!(?latest, "LatestStream"); - if tx.send(latest).is_err() { + if tx.is_closed() { tracing::debug!("Channel closed, exiting"); break; } + + tx.send_if_modified(|current| { + // TODO: handle reorgs correctly + if *current != latest { + tracing::info!(?latest, "LatestStream"); + *current = latest; + true + } else { + false + } + }); } }); diff --git a/crates/pathfinder/src/sync/track.rs b/crates/pathfinder/src/sync/track.rs index be779465b9..440caaf4ff 100644 --- a/crates/pathfinder/src/sync/track.rs +++ b/crates/pathfinder/src/sync/track.rs @@ -201,9 +201,7 @@ impl HeaderSource { tokio::spawn(async move { let mut latest_onchain = Box::pin(latest_onchain); while let Some(latest_onchain) = latest_onchain.next().await { - // Ignore reorgs for now. Unsure how to handle this properly. - - // TODO: Probably need a loop here if we don't get enough headers? + // TODO: handle reorgs correctly let mut headers = Box::pin(p2p.clone().header_stream(start, latest_onchain.0, false)); From a9003b9ec1ecf36432fe2206e574789f805fe3de Mon Sep 17 00:00:00 2001 From: Krzysztof Lis Date: Wed, 13 Nov 2024 16:44:20 +0100 Subject: [PATCH 258/282] refactor(sync/track): rayonize class hash verification and class compilation --- .../pathfinder/src/sync/class_definitions.rs | 25 +++++++++++++------ crates/pathfinder/src/sync/track.rs | 4 +-- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/crates/pathfinder/src/sync/class_definitions.rs b/crates/pathfinder/src/sync/class_definitions.rs index 47df093fd9..4a40b64c18 100644 --- a/crates/pathfinder/src/sync/class_definitions.rs +++ b/crates/pathfinder/src/sync/class_definitions.rs @@ -181,11 +181,17 @@ pub struct VerifyHash; impl ProcessStage for VerifyHash { const NAME: &'static str = "Class::VerifyHash"; - type Input = ClassWithLayout; - type Output = Class; + type Input = Vec; + type Output = Vec; fn map(&mut self, peer: &PeerId, input: Self::Input) -> Result { - verify_hash_impl(peer, input) + input + .into_par_iter() + .map(|class| { + let compiled = verify_hash_impl(peer, class)?; + Ok(compiled) + }) + .collect::, SyncError>>() } } @@ -526,12 +532,17 @@ impl CompileSierraToCasm { impl ProcessStage for CompileSierraToCasm { const NAME: &'static str = "Class::CompileSierraToCasm"; - type Input = Class; - type Output = CompiledClass; + type Input = Vec; + type Output = Vec; fn map(&mut self, _: &PeerId, input: Self::Input) -> Result { - let compiled = compile_or_fetch_impl(input, &self.fgw, &self.tokio_handle)?; - Ok(compiled) + input + .into_par_iter() + .map(|class| { + let compiled = compile_or_fetch_impl(class, &self.fgw, &self.tokio_handle)?; + Ok(compiled) + }) + .collect::, SyncError>>() } } diff --git a/crates/pathfinder/src/sync/track.rs b/crates/pathfinder/src/sync/track.rs index 440caaf4ff..9238f69a70 100644 --- a/crates/pathfinder/src/sync/track.rs +++ b/crates/pathfinder/src/sync/track.rs @@ -144,8 +144,8 @@ impl Sync { } .spawn() .pipe_each(class_definitions::VerifyLayout, 10) - .pipe_each(class_definitions::VerifyHash, 10) - .pipe_each( + .pipe(class_definitions::VerifyHash, 10) + .pipe( class_definitions::CompileSierraToCasm::new(fgw, tokio::runtime::Handle::current()), 10, ) From 0993d5b5643fecf0d7c8fcea4a09c60302d23eae Mon Sep 17 00:00:00 2001 From: Krzysztof Lis Date: Wed, 13 Nov 2024 16:44:43 +0100 Subject: [PATCH 259/282] refactor(sync/track): remove pipe_each, rayonize layout verification --- .../pathfinder/src/sync/class_definitions.rs | 9 ++- crates/pathfinder/src/sync/stream.rs | 55 ------------------- crates/pathfinder/src/sync/track.rs | 2 +- 3 files changed, 7 insertions(+), 59 deletions(-) diff --git a/crates/pathfinder/src/sync/class_definitions.rs b/crates/pathfinder/src/sync/class_definitions.rs index 4a40b64c18..131f72e958 100644 --- a/crates/pathfinder/src/sync/class_definitions.rs +++ b/crates/pathfinder/src/sync/class_definitions.rs @@ -124,11 +124,14 @@ pub struct VerifyLayout; impl ProcessStage for VerifyLayout { const NAME: &'static str = "Class::VerifyLayout"; - type Input = P2PClassDefinition; - type Output = ClassWithLayout; + type Input = Vec; + type Output = Vec; fn map(&mut self, peer: &PeerId, input: Self::Input) -> Result { - verify_layout_impl(peer, input) + input + .into_par_iter() + .map(|class| verify_layout_impl(peer, class)) + .collect() } } diff --git a/crates/pathfinder/src/sync/stream.rs b/crates/pathfinder/src/sync/stream.rs index ad68ec1e1c..6d6404340c 100644 --- a/crates/pathfinder/src/sync/stream.rs +++ b/crates/pathfinder/src/sync/stream.rs @@ -66,61 +66,6 @@ impl SyncReceiver { self.pipe_impl(stage, buffer, |_| 1) } - /// Similar to [SyncReceiver::pipe], but processes each element in the - /// input individually. - pub fn pipe_each(mut self, mut stage: S, buffer: usize) -> SyncReceiver> - where - T: IntoIterator + Send + 'static, - S: ProcessStage + Send + 'static, - S::Output: Send, - { - let (tx, rx) = tokio::sync::mpsc::channel(buffer); - - std::thread::spawn(move || { - let queue_capacity = self.inner.max_capacity(); - - while let Some(input) = self.inner.blocking_recv() { - let result = match input { - Ok(PeerData { peer, data }) => { - // Stats for tracing and metrics. - let t = std::time::Instant::now(); - - // Process the data. - let output: Result, _> = data - .into_iter() - .map(|data| { - stage.map(&peer, data).inspect_err(|error| { - tracing::debug!(%error, "Processing item failed"); - }) - }) - .collect(); - let output = output.map(|x| PeerData::new(peer, x)); - - // Log trace and metrics. - let elements_per_sec = 1.0 / t.elapsed().as_secs_f32(); - let queue_fullness = queue_capacity - self.inner.capacity(); - let input_queue = Fullness(queue_fullness, queue_capacity); - tracing::debug!( - "Stage: {}, queue: {}, {elements_per_sec:.0} items/s", - S::NAME, - input_queue - ); - - output - } - Err(e) => Err(e), - }; - - let is_err = result.is_err(); - if tx.blocking_send(result).is_err() || is_err { - return; - } - } - }); - - SyncReceiver::from_receiver(rx) - } - /// A private impl which hides the ugly `count_fn` used to differentiate /// between processing a single element from [SyncReceiver] and multiple /// elements from [ChunkSyncReceiver]. diff --git a/crates/pathfinder/src/sync/track.rs b/crates/pathfinder/src/sync/track.rs index 9238f69a70..070110629a 100644 --- a/crates/pathfinder/src/sync/track.rs +++ b/crates/pathfinder/src/sync/track.rs @@ -143,7 +143,7 @@ impl Sync { start: next, } .spawn() - .pipe_each(class_definitions::VerifyLayout, 10) + .pipe(class_definitions::VerifyLayout, 10) .pipe(class_definitions::VerifyHash, 10) .pipe( class_definitions::CompileSierraToCasm::new(fgw, tokio::runtime::Handle::current()), From 7ed785bc6e2b467bed9bd5b9d6738639d1e1a343 Mon Sep 17 00:00:00 2001 From: Krisztian Kovacs Date: Thu, 14 Nov 2024 08:48:11 +0100 Subject: [PATCH 260/282] feat(doc/rpc/v08): update OpenRPC specs --- doc/rpc/v08/starknet_api_openrpc.json | 7 ++++- doc/rpc/v08/starknet_executables.json | 43 ++++++++++++++------------- doc/rpc/v08/starknet_ws_api.json | 24 ++++++++++++--- 3 files changed, 48 insertions(+), 26 deletions(-) diff --git a/doc/rpc/v08/starknet_api_openrpc.json b/doc/rpc/v08/starknet_api_openrpc.json index 6bf3a4d4dd..4c46d7ba5b 100644 --- a/doc/rpc/v08/starknet_api_openrpc.json +++ b/doc/rpc/v08/starknet_api_openrpc.json @@ -3628,13 +3628,18 @@ "description": "The max amount and max price per unit of L1 gas used in this tx", "$ref": "#/components/schemas/RESOURCE_BOUNDS" }, + "l1_data_gas": { + "title": "L1 Data Gas", + "description": "The max amount and max price per unit of L1 blob gas used in this tx", + "$ref": "#/components/schemas/RESOURCE_BOUNDS" + }, "l2_gas": { "title": "L2 Gas", "description": "The max amount and max price per unit of L2 gas used in this tx", "$ref": "#/components/schemas/RESOURCE_BOUNDS" } }, - "required": ["l1_gas", "l2_gas"] + "required": ["l1_gas", "l1_data_gas", "l2_gas"] }, "RESOURCE_BOUNDS": { "type": "object", diff --git a/doc/rpc/v08/starknet_executables.json b/doc/rpc/v08/starknet_executables.json index 4a22ef2c11..d8acb2b738 100644 --- a/doc/rpc/v08/starknet_executables.json +++ b/doc/rpc/v08/starknet_executables.json @@ -34,7 +34,7 @@ "$ref": "#/components/errors/COMPILATION_ERROR" }, { - "$ref": "#/components/errors/CLASS_HASH_NOT_FOUND" + "$ref": "./api/starknet_api_openrpc.json#/components/errors/CLASS_HASH_NOT_FOUND" } ] } @@ -128,21 +128,23 @@ ] }, "CASM_ENTRY_POINT": { - "allOf": [ - { - "$ref": "#/components/schemas/DEPRECATED_CAIRO_ENTRY_POINT" - }, - { - "type": "object", - "properties": { - "builtins": { - "type": "array", - "items": "string" - } + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/DEPRECATED_CAIRO_ENTRY_POINT" }, - "required": "builtins" - } - ] + { + "type": "object", + "properties": { + "builtins": { + "type": "array", + "items": "string" + } + }, + "required": "builtins" + } + ] + } }, "CellRef": { "title": "CellRef", @@ -1335,20 +1337,19 @@ ] }, "FELT": { - "$ref": "./starknet_api_openrpc.json#/components/schemas/FELT" + "$ref": "./api/starknet_api_openrpc.json#/components/schemas/FELT" }, "NUM_AS_HEX": { - "$ref": "./starknet_api_openrpc.json#/components/schemas/NUM_AS_HEX" + "$ref": "./api/starknet_api_openrpc.json#/components/schemas/NUM_AS_HEX" }, "DEPRECATED_CAIRO_ENTRY_POINT": { - "$ref": "./starknet_api_openrpc.json#/components/schemas/DEPRECATED_CAIRO_ENTRY_POINT" + "$ref": "./api/starknet_api_openrpc.json#/components/schemas/DEPRECATED_CAIRO_ENTRY_POINT" } }, "errors": { - "CLASS_HASH_NOT_FOUND": { - "$ref": "./api/starknet_api_openrpc.json#/components/errors/CLASS_HASH_NOT_FOUND" - }, "COMPILATION_ERROR": { + "code": 100, + "message": "Failed to compile the contract", "data": { "type": "object", "description": "More data about the compilation failure", diff --git a/doc/rpc/v08/starknet_ws_api.json b/doc/rpc/v08/starknet_ws_api.json index 9e7ef37495..90f9996ac6 100644 --- a/doc/rpc/v08/starknet_ws_api.json +++ b/doc/rpc/v08/starknet_ws_api.json @@ -12,7 +12,7 @@ "description": "Creates a WebSocket stream which will fire events for new block headers", "params": [ { - "name": "block", + "name": "block_id", "summary": "The block to get notifications from, default is latest, limited to 1024 blocks back", "required": false, "schema": { @@ -32,6 +32,9 @@ }, { "$ref": "./api/starknet_api_openrpc.json#/components/errors/BLOCK_NOT_FOUND" + }, + { + "$ref": "#/components/errors/CALL_ON_PENDING" } ] }, @@ -74,11 +77,11 @@ "required": false, "schema": { "title": "event keys", - "$ref": "#/components/schemas/EVENT_KEYS" + "$ref": "./api/starknet_api_openrpc.json#/components/schemas/EVENT_KEYS" } }, { - "name": "block", + "name": "block_id", "summary": "The block to get notifications from, default is latest, limited to 1024 blocks back", "required": false, "schema": { @@ -101,6 +104,9 @@ }, { "$ref": "./api/starknet_api_openrpc.json#/components/errors/BLOCK_NOT_FOUND" + }, + { + "$ref": "#/components/errors/CALL_ON_PENDING" } ] }, @@ -138,7 +144,7 @@ } }, { - "name": "block", + "name": "block_id", "summary": "The block to get notifications from, default is latest, limited to 1024 blocks back", "required": false, "schema": { @@ -253,6 +259,12 @@ "params": [ { "name": "subscription_id", + "schema": { + "$ref": "#/components/schemas/SUBSCRIPTION_ID" + } + }, + { + "name": "result", "schema": { "$ref": "#/components/schemas/REORG_DATA" } @@ -361,6 +373,10 @@ "TOO_MANY_BLOCKS_BACK": { "code": 68, "message": "Cannot go back more than 1024 blocks" + }, + "CALL_ON_PENDING": { + "code": 69, + "message": "This method does not support being called on the pending block" } } } From 4e9c5bde1068d7ebc977df3e5d658e6d71649a1a Mon Sep 17 00:00:00 2001 From: Krisztian Kovacs Date: Thu, 14 Nov 2024 09:03:59 +0100 Subject: [PATCH 261/282] fix(rpc): add all v08 OpenRPC API JSON files to routing test --- crates/rpc/src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/rpc/src/lib.rs b/crates/rpc/src/lib.rs index 422daf5348..6a4a5a4cc1 100644 --- a/crates/rpc/src/lib.rs +++ b/crates/rpc/src/lib.rs @@ -903,6 +903,8 @@ mod tests { // get_transaction_status is now part of the official spec, so we are phasing it out. #[case::root_pathfinder("/", "pathfinder_rpc_api.json", &["pathfinder_version", "pathfinder_getTransactionStatus"])] + #[case::v0_8_api("/rpc/v0_8", "v08/starknet_api_openrpc.json", &[])] + #[case::v0_8_executables("/rpc/v0_8", "v08/starknet_executables.json", &[])] #[case::v0_8_trace("/rpc/v0_8", "v08/starknet_trace_api_openrpc.json", &[])] #[case::v0_8_write("/rpc/v0_8", "v08/starknet_write_api.json", &[])] From 69cbecaafc66b94163288df08257262149d741ee Mon Sep 17 00:00:00 2001 From: t00ts Date: Thu, 14 Nov 2024 12:42:46 +0100 Subject: [PATCH 262/282] feat(rpc): rename `block` to `block_id` as per updated starknet spec --- crates/rpc/src/method/subscribe_events.rs | 16 ++++++++-------- crates/rpc/src/method/subscribe_new_heads.rs | 12 ++++++------ .../src/method/subscribe_transaction_status.rs | 10 +++++----- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/crates/rpc/src/method/subscribe_events.rs b/crates/rpc/src/method/subscribe_events.rs index 534ab44339..52d3cf9ab6 100644 --- a/crates/rpc/src/method/subscribe_events.rs +++ b/crates/rpc/src/method/subscribe_events.rs @@ -18,7 +18,7 @@ pub struct SubscribeEvents; pub struct Params { from_address: Option, keys: Option>>, - block: Option, + block_id: Option, } impl crate::dto::DeserializeForVersion for Option { @@ -35,7 +35,7 @@ impl crate::dto::DeserializeForVersion for Option { keys: value.deserialize_optional_array("keys", |value| { value.deserialize_array(|value| Ok(EventKey(value.deserialize()?))) })?, - block: value.deserialize_optional_serde("block")?, + block_id: value.deserialize_optional_serde("block_id")?, })) }) } @@ -85,7 +85,7 @@ impl RpcSubscriptionFlow for SubscribeEvents { fn starting_block(params: &Self::Params) -> BlockId { params .as_ref() - .and_then(|req| req.block) + .and_then(|req| req.block_id) .unwrap_or(BlockId::Latest) } @@ -263,7 +263,7 @@ mod tests { let (receiver_tx, receiver_rx) = mpsc::channel(1024); handle_json_rpc_socket(router.clone(), sender_tx, receiver_rx); let params = serde_json::json!( - {"block": {"block_number": 0}} + {"block_id": {"block_number": 0}} ); receiver_tx .send(Ok(Message::Text( @@ -325,7 +325,7 @@ mod tests { handle_json_rpc_socket(router.clone(), sender_tx, receiver_rx); let params = serde_json::json!( { - "block": {"block_number": 0}, + "block_id": {"block_number": 0}, "from_address": "0x46", } ); @@ -391,7 +391,7 @@ mod tests { handle_json_rpc_socket(router.clone(), sender_tx, receiver_rx); let params = serde_json::json!( { - "block": {"block_number": 0}, + "block_id": {"block_number": 0}, "keys": [["0x46"], [], ["0x47", "0x48"]], } ); @@ -457,7 +457,7 @@ mod tests { handle_json_rpc_socket(router.clone(), sender_tx, receiver_rx); let params = serde_json::json!( { - "block": {"block_number": 0}, + "block_id": {"block_number": 0}, "from_address": "0x46", "keys": [["0x46"], [], ["0x47", "0x48"]], } @@ -524,7 +524,7 @@ mod tests { handle_json_rpc_socket(router.clone(), sender_tx, receiver_rx); let params = serde_json::json!( { - "block": {"block_number": 0}, + "block_id": {"block_number": 0}, "from_address": "0x46", "keys": [ ["0x46"], diff --git a/crates/rpc/src/method/subscribe_new_heads.rs b/crates/rpc/src/method/subscribe_new_heads.rs index db8a082147..bba317b87c 100644 --- a/crates/rpc/src/method/subscribe_new_heads.rs +++ b/crates/rpc/src/method/subscribe_new_heads.rs @@ -13,7 +13,7 @@ pub struct SubscribeNewHeads; #[derive(Debug, Clone)] pub struct Params { - block: Option, + block_id: Option, } impl crate::dto::DeserializeForVersion for Option { @@ -24,7 +24,7 @@ impl crate::dto::DeserializeForVersion for Option { } value.deserialize_map(|value| { Ok(Some(Params { - block: value.deserialize_optional_serde("block")?, + block_id: value.deserialize_optional_serde("block_id")?, })) }) } @@ -58,7 +58,7 @@ impl RpcSubscriptionFlow for SubscribeNewHeads { fn starting_block(params: &Self::Params) -> BlockId { params .as_ref() - .and_then(|req| req.block) + .and_then(|req| req.block_id) .unwrap_or(BlockId::Latest) } @@ -247,7 +247,7 @@ mod tests { "jsonrpc": "2.0", "id": 1, "method": "starknet_subscribeNewHeads", - "params": {"block": {"block_number": 0}} + "params": {"block_id": {"block_number": 0}} }) .to_string(), ))) @@ -517,11 +517,11 @@ mod tests { handle_json_rpc_socket(router.clone(), sender_tx, receiver_rx); let params = if num_blocks == 0 { serde_json::json!( - {"block": "latest"} + {"block_id": "latest"} ) } else { serde_json::json!( - {"block": {"block_number": 0}} + {"block_id": {"block_number": 0}} ) }; receiver_tx diff --git a/crates/rpc/src/method/subscribe_transaction_status.rs b/crates/rpc/src/method/subscribe_transaction_status.rs index 4642099c4f..90152323c2 100644 --- a/crates/rpc/src/method/subscribe_transaction_status.rs +++ b/crates/rpc/src/method/subscribe_transaction_status.rs @@ -21,7 +21,7 @@ pub struct SubscribeTransactionStatus; #[derive(Debug, Clone, Default)] pub struct Params { transaction_hash: TransactionHash, - block: Option, + block_id: Option, } impl crate::dto::DeserializeForVersion for Params { @@ -29,7 +29,7 @@ impl crate::dto::DeserializeForVersion for Params { value.deserialize_map(|value| { Ok(Self { transaction_hash: value.deserialize("transaction_hash").map(TransactionHash)?, - block: value.deserialize_optional_serde("block")?, + block_id: value.deserialize_optional_serde("block_id")?, }) }) } @@ -143,7 +143,7 @@ impl RpcSubscriptionFlow for SubscribeTransactionStatus { let mut l2_blocks = state.notifications.l2_blocks.subscribe(); let mut reorgs = state.notifications.reorgs.subscribe(); let storage = state.storage.clone(); - if let Some(first_block) = params.block { + if let Some(first_block) = params.block_id { // Check if we have the transaction in our database, and if so, send the // relevant transaction status updates. let (first_block, l1_state, tx_with_receipt) = @@ -943,7 +943,7 @@ mod tests { let (receiver_tx, receiver_rx) = mpsc::channel(1024); handle_json_rpc_socket(router.clone(), sender_tx, receiver_rx); let params = serde_json::json!( - {"block": {"block_number": 0}, "transaction_hash": tx_hash} + {"block_id": {"block_number": 0}, "transaction_hash": tx_hash} ); receiver_tx .send(Ok(Message::Text( @@ -1097,7 +1097,7 @@ mod tests { let (receiver_tx, receiver_rx) = mpsc::channel(1024); handle_json_rpc_socket(router.clone(), sender_tx, receiver_rx); let params = serde_json::json!( - {"block": {"block_number": 0}, "transaction_hash": tx_hash} + {"block_id": {"block_number": 0}, "transaction_hash": tx_hash} ); receiver_tx .send(Ok(Message::Text( From 6f74d533548368839b9327feb704b69fe27991da Mon Sep 17 00:00:00 2001 From: sistemd Date: Thu, 14 Nov 2024 17:24:30 +0100 Subject: [PATCH 263/282] running aggregate stored in memory --- crates/rpc/fixtures/mainnet.sqlite | Bin 454656 -> 417792 bytes crates/rpc/src/method/subscribe_events.rs | 10 +- .../v06/method/trace_block_transactions.rs | 14 + .../rpc/src/v06/method/trace_transaction.rs | 22 +- crates/storage/src/bloom.rs | 103 +++----- crates/storage/src/connection.rs | 16 ++ crates/storage/src/connection/event.rs | 155 +++++++---- crates/storage/src/connection/transaction.rs | 21 +- crates/storage/src/lib.rs | 241 ++++++++++++++++++ crates/storage/src/schema/revision_0066.rs | 114 +++++---- crates/storage/src/test_utils.rs | 48 ++-- 11 files changed, 535 insertions(+), 209 deletions(-) diff --git a/crates/rpc/fixtures/mainnet.sqlite b/crates/rpc/fixtures/mainnet.sqlite index 88b5ca98c3427f34d954d0e5d8cd88fee940f60c..0a5316c3fbb3d6ae1168d27f454e80fa1c0e83f4 100644 GIT binary patch delta 70875 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

K71jZ6ua)49V5?w?K#hNypUHQCg|Z62Q^0o5 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 + .chunks(pathfinder_storage::BLOCK_RANGE_LEN as usize) + .map(|range| *range.last().unwrap() as u64) + .for_each(|block| { + let block = BlockNumber::new_or_panic(block); + transaction + .insert_transaction_data(block, &[], None) + .unwrap(); + }); + let block: starknet_gateway_types::reply::Block = serde_json::from_str(include_str!("../../../fixtures/mainnet-619596.json")).unwrap(); let transaction_count = block.transactions.len(); diff --git a/crates/rpc/src/v06/method/trace_transaction.rs b/crates/rpc/src/v06/method/trace_transaction.rs index 722c9783a4..cbc9fbdc9f 100644 --- a/crates/rpc/src/v06/method/trace_transaction.rs +++ b/crates/rpc/src/v06/method/trace_transaction.rs @@ -257,7 +257,14 @@ pub async fn trace_transaction_impl( #[cfg(test)] pub mod tests { - use pathfinder_common::{block_hash, transaction_hash, BlockHeader, Chain, SequencerAddress}; + use pathfinder_common::{ + block_hash, + transaction_hash, + BlockHeader, + BlockNumber, + Chain, + SequencerAddress, + }; use pathfinder_crypto::Felt; use super::super::trace_block_transactions::tests::{ @@ -305,6 +312,19 @@ pub mod tests { let context = RpcContext::for_tests_on(Chain::Mainnet); let mut connection = context.storage.connection().unwrap(); let transaction = connection.transaction().unwrap(); + + // Need to avoid skipping blocks for `insert_transaction_data`. + (0..619596) + .collect::>() + .chunks(pathfinder_storage::BLOCK_RANGE_LEN as usize) + .map(|range| *range.last().unwrap() as u64) + .for_each(|block| { + let block = BlockNumber::new_or_panic(block); + transaction + .insert_transaction_data(block, &[], None) + .unwrap(); + }); + let block: starknet_gateway_types::reply::Block = serde_json::from_str(include_str!("../../../fixtures/mainnet-619596.json")).unwrap(); let transaction_count = block.transactions.len(); diff --git a/crates/storage/src/bloom.rs b/crates/storage/src/bloom.rs index b0ce2adcf8..6b238f5852 100644 --- a/crates/storage/src/bloom.rs +++ b/crates/storage/src/bloom.rs @@ -68,7 +68,7 @@ use cached::{Cached, SizedCache}; use pathfinder_common::{BlockNumber, ContractAddress, EventKey}; use pathfinder_crypto::Felt; -use crate::ReorgCounter; +use crate::{EventFilter, ReorgCounter}; // We're using the upper 4 bits of the 32 byte representation of a felt // to store the index of the key in the values set in the Bloom filter. @@ -76,10 +76,12 @@ use crate::ReorgCounter; // filter. pub const EVENT_KEY_FILTER_LIMIT: usize = 16; +pub const BLOCK_RANGE_LEN: u64 = AggregateBloom::BLOCK_RANGE_LEN; + /// An aggregate of all Bloom filters for a given range of blocks. /// Before being added to `AggregateBloom`, each [`BloomFilter`] is /// rotated by 90 degrees (transposed). -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct AggregateBloom { /// A [Self::BLOCK_RANGE_LEN] by [BloomFilter::BITVEC_LEN] matrix stored in /// a single array. @@ -109,13 +111,10 @@ impl AggregateBloom { } pub fn from_existing_compressed( - from_block: u64, - to_block: u64, + from_block: BlockNumber, + to_block: BlockNumber, compressed_bitmap: Vec, ) -> Self { - let from_block = BlockNumber::new_or_panic(from_block); - let to_block = BlockNumber::new_or_panic(to_block); - let bitmap = zstd::bulk::decompress( &compressed_bitmap, AggregateBloom::BLOCK_RANGE_BYTES as usize * BloomFilter::BITVEC_LEN as usize, @@ -126,15 +125,10 @@ impl AggregateBloom { } fn from_parts(from_block: BlockNumber, to_block: BlockNumber, bitmap: Vec) -> Self { - assert_eq!( - from_block + Self::BLOCK_RANGE_LEN - 1, - to_block, - "Block range mismatch" - ); + assert_eq!(from_block + Self::BLOCK_RANGE_LEN - 1, to_block); assert_eq!( bitmap.len() as u64, - Self::BLOCK_RANGE_BYTES * BloomFilter::BITVEC_LEN, - "Bitmap length mismatch" + Self::BLOCK_RANGE_BYTES * BloomFilter::BITVEC_LEN ); Self { @@ -148,24 +142,21 @@ impl AggregateBloom { zstd::bulk::compress(&self.bitmap, 10).expect("Compressing aggregate Bloom filter") } - /// Rotate the bloom filter by 90 degrees and add it to the aggregate. + /// Rotate the [`BloomFilter`] by 90 degrees (transpose) and add it to the + /// aggregate. It is up to the user to keep track of when the aggregate + /// filter's block range has been exhausted and respond accordingly. pub fn add_bloom(&mut self, bloom: &BloomFilter, block_number: BlockNumber) { assert!( (self.from_block..=self.to_block).contains(&block_number), - "Invalid block number", - ); - assert_eq!( - bloom.0.number_of_hash_functions(), - BloomFilter::K_NUM, - "Hash function count mismatch" + "Block number {} is not in the range {}..={}", + block_number, + self.from_block, + self.to_block ); + assert_eq!(bloom.0.number_of_hash_functions(), BloomFilter::K_NUM); let bloom = bloom.0.bit_vec().to_bytes(); - assert_eq!( - bloom.len() as u64, - BloomFilter::BITVEC_BYTES, - "Bit vector length mismatch" - ); + assert_eq!(bloom.len() as u64, BloomFilter::BITVEC_BYTES); let relative_block_number = block_number.get() - self.from_block.get(); let byte_idx = (relative_block_number / 8) as usize; @@ -183,7 +174,9 @@ impl AggregateBloom { } } - pub fn blocks_for_filter(&self, filter: &crate::EventFilter) -> BTreeSet { + /// Returns a set of [block numbers](BlockNumber) for which the given keys + /// are present in the aggregate. + pub fn blocks_for_filter(&self, filter: &EventFilter) -> BTreeSet { // Empty filters are considered present in all blocks. if filter.contract_address.is_none() && (filter.keys.iter().flatten().count() == 0) { return (self.from_block.get()..=self.to_block.get()) @@ -449,7 +442,7 @@ mod tests { } #[test] - #[cfg_attr(not(feature = "aggregate_bloom"), ignore)] + #[cfg(feature = "aggregate_bloom")] fn add_bloom_and_check_single_block_found() { let from_block = BlockNumber::new_or_panic(0); let mut aggregate_bloom_filter = AggregateBloom::new(from_block); @@ -460,43 +453,14 @@ mod tests { aggregate_bloom_filter.add_bloom(&bloom, from_block); - let filter = crate::EventFilter { - keys: vec![vec![EventKey(KEY)]], - contract_address: None, - ..Default::default() - }; - let block_matches = aggregate_bloom_filter.blocks_for_keys(&[KEY]); assert_eq!(block_matches, vec![from_block]); - let block_matches: Vec<_> = aggregate_bloom_filter - .blocks_for_filter(&filter) - .into_iter() - .collect(); - assert_eq!(block_matches, vec![from_block]); - } - - #[test] - #[cfg_attr(not(feature = "aggregate_bloom"), ignore)] - fn aggregate_bloom_past_first_range() { - let from_block = BlockNumber::new_or_panic(AggregateBloom::BLOCK_RANGE_LEN); - let mut aggregate_bloom_filter = AggregateBloom::new(from_block); - - let mut bloom = BloomFilter::new(); - bloom.set(&KEY); - bloom.set(&KEY1); - - let filter = crate::EventFilter { + let filter = EventFilter { keys: vec![vec![EventKey(KEY)]], contract_address: None, ..Default::default() }; - - aggregate_bloom_filter.add_bloom(&bloom, from_block); - - let block_matches = aggregate_bloom_filter.blocks_for_keys(&[KEY]); - assert_eq!(block_matches, vec![from_block]); - let block_matches: Vec<_> = aggregate_bloom_filter .blocks_for_filter(&filter) .into_iter() @@ -505,7 +469,7 @@ mod tests { } #[test] - #[cfg_attr(not(feature = "aggregate_bloom"), ignore)] + #[cfg(feature = "aggregate_bloom")] fn add_blooms_and_check_multiple_blocks_found() { let from_block = BlockNumber::new_or_panic(0); let mut aggregate_bloom_filter = AggregateBloom::new(from_block); @@ -516,15 +480,14 @@ mod tests { aggregate_bloom_filter.add_bloom(&bloom, from_block); aggregate_bloom_filter.add_bloom(&bloom, from_block + 1); - let filter = crate::EventFilter { + let block_matches = aggregate_bloom_filter.blocks_for_keys(&[KEY]); + assert_eq!(block_matches, vec![from_block, from_block + 1]); + + let filter = EventFilter { keys: vec![vec![EventKey(KEY)]], contract_address: None, ..Default::default() }; - - let block_matches = aggregate_bloom_filter.blocks_for_keys(&[KEY]); - assert_eq!(block_matches, vec![from_block, from_block + 1]); - let block_matches: Vec<_> = aggregate_bloom_filter .blocks_for_filter(&filter) .into_iter() @@ -533,7 +496,7 @@ mod tests { } #[test] - #[cfg_attr(not(feature = "aggregate_bloom"), ignore)] + #[cfg(feature = "aggregate_bloom")] fn key_not_in_filter_returns_empty_vec() { let from_block = BlockNumber::new_or_panic(0); let mut aggregate_bloom_filter = AggregateBloom::new(from_block); @@ -546,12 +509,11 @@ mod tests { aggregate_bloom_filter.add_bloom(&bloom, from_block + 1); let block_matches_empty = aggregate_bloom_filter.blocks_for_keys(&[KEY_NOT_IN_FILTER]); - assert_eq!(block_matches_empty, Vec::::new()); } #[test] - #[cfg_attr(not(feature = "aggregate_bloom"), ignore)] + #[cfg(feature = "aggregate_bloom")] fn serialize_aggregate_roundtrip() { let from_block = BlockNumber::new_or_panic(0); let mut aggregate_bloom_filter = AggregateBloom::new(from_block); @@ -564,15 +526,14 @@ mod tests { let compressed_bitmap = aggregate_bloom_filter.compress_bitmap(); let mut decompressed = AggregateBloom::from_existing_compressed( - aggregate_bloom_filter.from_block.get(), - aggregate_bloom_filter.to_block.get(), + aggregate_bloom_filter.from_block, + aggregate_bloom_filter.to_block, compressed_bitmap, ); decompressed.add_bloom(&bloom, from_block + 2); let block_matches = decompressed.blocks_for_keys(&[KEY]); let block_matches_empty = decompressed.blocks_for_keys(&[KEY_NOT_IN_FILTER]); - assert_eq!( block_matches, vec![from_block, from_block + 1, from_block + 2] @@ -581,7 +542,7 @@ mod tests { } #[test] - #[cfg_attr(not(feature = "aggregate_bloom"), ignore)] + #[cfg(feature = "aggregate_bloom")] #[should_panic] fn invalid_insert_pos() { let from_block = BlockNumber::new_or_panic(0); diff --git a/crates/storage/src/connection.rs b/crates/storage/src/connection.rs index 60159f265e..55defedb46 100644 --- a/crates/storage/src/connection.rs +++ b/crates/storage/src/connection.rs @@ -1,4 +1,6 @@ use std::sync::Arc; +#[cfg(feature = "aggregate_bloom")] +use std::sync::Mutex; mod block; mod class; @@ -27,11 +29,16 @@ pub(crate) use reorg_counter::ReorgCounter; pub use rusqlite::TransactionBehavior; pub use trie::{Node, NodeRef, RootIndexUpdate, StoredNode, TrieUpdate}; +#[cfg(feature = "aggregate_bloom")] +use crate::bloom::AggregateBloom; + type PooledConnection = r2d2::PooledConnection; pub struct Connection { connection: PooledConnection, bloom_filter_cache: Arc, + #[cfg(feature = "aggregate_bloom")] + running_aggregate: Arc>, trie_prune_mode: TriePruneMode, } @@ -39,11 +46,14 @@ impl Connection { pub(crate) fn new( connection: PooledConnection, bloom_filter_cache: Arc, + #[cfg(feature = "aggregate_bloom")] running_aggregate: Arc>, trie_prune_mode: TriePruneMode, ) -> Self { Self { connection, bloom_filter_cache, + #[cfg(feature = "aggregate_bloom")] + running_aggregate, trie_prune_mode, } } @@ -53,6 +63,8 @@ impl Connection { Ok(Transaction { transaction: tx, bloom_filter_cache: self.bloom_filter_cache.clone(), + #[cfg(feature = "aggregate_bloom")] + running_aggregate: self.running_aggregate.clone(), trie_prune_mode: self.trie_prune_mode, }) } @@ -65,6 +77,8 @@ impl Connection { Ok(Transaction { transaction: tx, bloom_filter_cache: self.bloom_filter_cache.clone(), + #[cfg(feature = "aggregate_bloom")] + running_aggregate: self.running_aggregate.clone(), trie_prune_mode: self.trie_prune_mode, }) } @@ -73,6 +87,8 @@ impl Connection { pub struct Transaction<'inner> { transaction: rusqlite::Transaction<'inner>, bloom_filter_cache: Arc, + #[cfg(feature = "aggregate_bloom")] + running_aggregate: Arc>, trie_prune_mode: TriePruneMode, } diff --git a/crates/storage/src/connection/event.rs b/crates/storage/src/connection/event.rs index e6fe68711a..f28a84e813 100644 --- a/crates/storage/src/connection/event.rs +++ b/crates/storage/src/connection/event.rs @@ -69,22 +69,32 @@ pub struct PageOfEvents { impl Transaction<'_> { #[cfg(feature = "aggregate_bloom")] - pub(super) fn upsert_block_events_aggregate( + /// Upsert the [aggregate event bloom filter](AggregateBloom) for the given + /// block number. This function operates under the assumption that + /// blocks are _never_ skipped so even if there are no events for a + /// block, this function should still be called with an empty iterator. + /// When testing it is fine to skip blocks, as long as the block at the end + /// of the current range is not skipped. + pub(super) fn upsert_block_events_aggregate<'a>( &self, block_number: BlockNumber, - events: &[Event], + events: impl Iterator, ) -> anyhow::Result<()> { - #[rustfmt::skip] let mut stmt = self.inner().prepare_cached( - "INSERT INTO starknet_event_filters_aggregate (from_block, to_block, bloom) \ - VALUES (?, ?, ?) ON CONFLICT DO UPDATE SET bloom=excluded.bloom", + r" + INSERT INTO starknet_events_filters_aggregate + (from_block, to_block, bitmap) + VALUES (?, ?, ?) + ON CONFLICT DO UPDATE SET bitmap=excluded.bitmap + ", )?; - let mut running_aggregate = match self.load_aggregate_bloom(block_number)? { - // Loading existing block range - Some(aggregate) => aggregate, - // New block range reached - None => AggregateBloom::new(block_number), + let mut running_aggregate = match self.running_aggregate.lock() { + Ok(guard) => guard, + Err(poisoned) => { + tracing::error!("Poisoned lock in upsert_block_events_aggregate"); + poisoned.into_inner() + } }; let mut bloom = BloomFilter::new(); @@ -94,12 +104,17 @@ impl Transaction<'_> { } running_aggregate.add_bloom(&bloom, block_number); - - stmt.execute(params![ - &running_aggregate.from_block, - &running_aggregate.to_block, - &running_aggregate.compress_bitmap() - ])?; + // This check is the reason that blocks cannot be skipped, if they were we would + // risk missing the last block of the current aggregate's range. + if block_number == running_aggregate.to_block { + stmt.execute(params![ + &running_aggregate.from_block, + &running_aggregate.to_block, + &running_aggregate.compress_bitmap() + ])?; + + *running_aggregate = AggregateBloom::new(running_aggregate.to_block + 1); + } Ok(()) } @@ -331,8 +346,7 @@ impl Transaction<'_> { } // TODO: - // This function is temporarily here to compare the performance of the new - // aggregate bloom filter. + // Add a limit to how many aggregate_bloom ranges can be loaded #[cfg(feature = "aggregate_bloom")] pub fn events_from_aggregate( &self, @@ -550,56 +564,30 @@ impl Transaction<'_> { }) } - #[cfg(feature = "aggregate_bloom")] - fn load_aggregate_bloom( - &self, - block_number: BlockNumber, - ) -> anyhow::Result> { - #[rustfmt::skip] - let mut select_stmt = self.inner().prepare_cached( - "SELECT from_block, to_block, bloom FROM starknet_event_filters_aggregate \ - WHERE from_block <= ? AND to_block >= ?", - )?; - - let aggregate = select_stmt - .query_row(params![&block_number, &block_number], |row| { - let from_block: u64 = row.get(0)?; - let to_block: u64 = row.get(1)?; - let compressed_bitmap: Vec = row.get(2)?; - - Ok((from_block, to_block, compressed_bitmap)) - }) - .optional() - .context("Querying running bloom aggregate")? - .map(|(from_block, to_block, compressed_bitmap)| { - AggregateBloom::from_existing_compressed(from_block, to_block, compressed_bitmap) - }); - - Ok(aggregate) - } - #[cfg(feature = "aggregate_bloom")] fn load_aggregate_bloom_range( &self, start_block: BlockNumber, end_block: BlockNumber, ) -> anyhow::Result> { - #[rustfmt::skip] let mut stmt = self.inner().prepare_cached( - "SELECT from_block, to_block, bloom FROM starknet_event_filters_aggregate \ - WHERE from_block <= :end_block AND to_block >= :start_block \ - ORDER BY from_block", + r" + SELECT from_block, to_block, bitmap + FROM starknet_events_filters_aggregate + WHERE from_block <= :end_block AND to_block >= :start_block + ORDER BY from_block + ", )?; - let aggregates = stmt + let mut aggregates = stmt .query_map( named_params![ ":end_block": &end_block, ":start_block": &start_block ], |row| { - let from_block: u64 = row.get(0)?; - let to_block: u64 = row.get(1)?; + let from_block = row.get_block_number(0)?; + let to_block = row.get_block_number(1)?; let compressed_bitmap: Vec = row.get(2)?; Ok(AggregateBloom::from_existing_compressed( @@ -612,6 +600,21 @@ impl Transaction<'_> { .context("Querying bloom filter range")? .collect::, _>>()?; + // There are no aggregates in the database yet or the loaded aggregates + // don't cover the requested range. + let should_include_running = aggregates.last().map_or(true, |a| end_block > a.to_block); + + if should_include_running { + let running_aggregate = match self.running_aggregate.lock() { + Ok(guard) => guard, + Err(poisoned) => { + tracing::error!("Poisoned lock in load_aggregate_bloom_range"); + poisoned.into_inner() + } + }; + aggregates.push(running_aggregate.clone()); + } + Ok(aggregates) } } @@ -1522,6 +1525,52 @@ mod tests { } } + #[test] + #[cfg(feature = "aggregate_bloom")] + fn crossing_aggregate_filter_range_stores_and_updates_running() { + let blocks: Vec = [ + // First aggregate filter start. + BlockNumber::GENESIS, + BlockNumber::GENESIS + 1, + BlockNumber::GENESIS + 2, + BlockNumber::GENESIS + 3, + // End. + BlockNumber::GENESIS + AggregateBloom::BLOCK_RANGE_LEN - 1, + // Second aggregate filter start. + BlockNumber::GENESIS + AggregateBloom::BLOCK_RANGE_LEN, + BlockNumber::GENESIS + AggregateBloom::BLOCK_RANGE_LEN + 1, + BlockNumber::GENESIS + AggregateBloom::BLOCK_RANGE_LEN + 2, + BlockNumber::GENESIS + AggregateBloom::BLOCK_RANGE_LEN + 3, + // End. + BlockNumber::GENESIS + 2 * AggregateBloom::BLOCK_RANGE_LEN - 1, + // Third aggregate filter start. + BlockNumber::GENESIS + 2 * AggregateBloom::BLOCK_RANGE_LEN, + BlockNumber::GENESIS + 2 * AggregateBloom::BLOCK_RANGE_LEN + 1, + ] + .iter() + .map(|&n| n.get() as usize) + .collect(); + + let (storage, _) = test_utils::setup_custom_test_storage(&blocks, 2); + let mut connection = storage.connection().unwrap(); + let tx = connection.transaction().unwrap(); + + let inserted_aggregate_filter_count = tx + .inner() + .prepare("SELECT COUNT(*) FROM starknet_events_filters_aggregate") + .unwrap() + .query_row([], |row| row.get::<_, u64>(0)) + .unwrap(); + assert_eq!(inserted_aggregate_filter_count, 2); + + let running_aggregate = tx.running_aggregate.lock().unwrap(); + // Running aggregate starts from next block range. + assert_eq!( + running_aggregate.from_block, + 2 * AggregateBloom::BLOCK_RANGE_LEN + ); + } + #[test] fn bloom_filter_load_limit() { let (storage, test_data) = test_utils::setup_test_storage(); diff --git a/crates/storage/src/connection/transaction.rs b/crates/storage/src/connection/transaction.rs index 91894e86dc..018f830f18 100644 --- a/crates/storage/src/connection/transaction.rs +++ b/crates/storage/src/connection/transaction.rs @@ -100,6 +100,10 @@ impl Transaction<'_> { events: Option<&[Vec]>, ) -> anyhow::Result<()> { if transactions.is_empty() && events.map_or(true, |x| x.is_empty()) { + // Advance the running event bloom filter even if there's nothing to add since + // it requires that no blocks are skipped. + #[cfg(feature = "aggregate_bloom")] + self.upsert_block_events_aggregate(block_number, std::iter::empty())?; return Ok(()); } @@ -166,13 +170,14 @@ impl Transaction<'_> { ]) .context("Inserting transaction data")?; + #[cfg(feature = "aggregate_bloom")] + { + let events = events.unwrap_or_default().iter().flatten(); + self.upsert_block_events_aggregate(block_number, events) + .context("Inserting events into Bloom filter aggregate")?; + } + if let Some(events) = events { - #[cfg(feature = "aggregate_bloom")] - { - let events: Vec = events.iter().flatten().cloned().collect(); - self.upsert_block_events_aggregate(block_number, &events) - .context("Inserting events into Bloom filter aggregate")?; - } let events = events.iter().flatten(); self.upsert_block_events(block_number, events) .context("Inserting events into Bloom filter")?; @@ -218,8 +223,8 @@ impl Transaction<'_> { #[cfg(feature = "aggregate_bloom")] { - let events: Vec = events.iter().flatten().cloned().collect(); - self.upsert_block_events_aggregate(block_number, &events) + let events = events.iter().flatten(); + self.upsert_block_events_aggregate(block_number, events) .context("Inserting events into Bloom filter aggregate")?; } self.upsert_block_events(block_number, events.iter().flatten()) diff --git a/crates/storage/src/lib.rs b/crates/storage/src/lib.rs index 0dde97aca1..47bd04ac77 100644 --- a/crates/storage/src/lib.rs +++ b/crates/storage/src/lib.rs @@ -6,6 +6,7 @@ mod prelude; mod bloom; +pub use bloom::BLOCK_RANGE_LEN; mod connection; pub mod fake; mod params; @@ -15,8 +16,12 @@ pub mod test_utils; use std::num::NonZeroU32; use std::path::{Path, PathBuf}; use std::sync::Arc; +#[cfg(feature = "aggregate_bloom")] +use std::sync::Mutex; use anyhow::Context; +#[cfg(feature = "aggregate_bloom")] +use bloom::AggregateBloom; pub use bloom::EVENT_KEY_FILTER_LIMIT; pub use connection::*; use pathfinder_common::{BlockHash, BlockNumber}; @@ -90,6 +95,8 @@ struct Inner { database_path: Arc, pool: Pool, bloom_filter_cache: Arc, + #[cfg(feature = "aggregate_bloom")] + running_aggregate: Arc>, trie_prune_mode: TriePruneMode, } @@ -97,6 +104,8 @@ pub struct StorageManager { database_path: PathBuf, journal_mode: JournalMode, bloom_filter_cache: Arc, + #[cfg(feature = "aggregate_bloom")] + running_aggregate: Arc>, trie_prune_mode: TriePruneMode, } @@ -128,6 +137,8 @@ impl StorageManager { database_path: Arc::new(self.database_path.clone()), pool, bloom_filter_cache: self.bloom_filter_cache.clone(), + #[cfg(feature = "aggregate_bloom")] + running_aggregate: self.running_aggregate.clone(), trie_prune_mode: self.trie_prune_mode, })) } @@ -260,6 +271,10 @@ impl StorageBuilder { tracing::info!("Merkle trie pruning disabled"); } + #[cfg(feature = "aggregate_bloom")] + let running_aggregate = reconstruct_running_aggregate(&mut connection) + .context("Reconstructing running aggregate bloom filter")?; + connection .close() .map_err(|(_connection, error)| error) @@ -269,6 +284,8 @@ impl StorageBuilder { database_path: self.database_path, journal_mode: self.journal_mode, bloom_filter_cache: Arc::new(bloom::Cache::with_size(self.bloom_filter_cache_size)), + #[cfg(feature = "aggregate_bloom")] + running_aggregate: Arc::new(Mutex::new(running_aggregate)), trie_prune_mode, }) } @@ -340,6 +357,8 @@ impl Storage { Ok(Connection::new( conn, self.0.bloom_filter_cache.clone(), + #[cfg(feature = "aggregate_bloom")] + self.0.running_aggregate.clone(), self.0.trie_prune_mode, )) } @@ -495,6 +514,100 @@ fn schema_version(connection: &rusqlite::Connection) -> anyhow::Result { Ok(version) } +/// Reconstruct the [aggregate](bloom::AggregateBloom) for the range of blocks +/// between the last stored to_block in the aggregate Bloom filter table and the +/// last overall block in the database. This is needed because the aggregate +/// Bloom filter for each [block range](bloom::AggregateBloom::BLOCK_RANGE_LEN) +/// is stored once the range is complete, before that it is kept in memory and +/// can be lost upon shutdown. +#[cfg(feature = "aggregate_bloom")] +fn reconstruct_running_aggregate( + connection: &mut rusqlite::Connection, +) -> anyhow::Result { + use bloom::BloomFilter; + use params::{named_params, RowExt}; + use pathfinder_common::event::Event; + use transaction; + + let tx = connection + .transaction() + .context("Creating database transaction")?; + let mut select_last_to_block_stmt = tx.prepare( + r" + SELECT to_block + FROM starknet_events_filters_aggregate + ORDER BY from_block DESC LIMIT 1 + ", + )?; + let mut events_to_reconstruct_stmt = tx.prepare( + r" + SELECT events + FROM transactions + WHERE block_number >= :first_running_aggregate_block + ", + )?; + + let last_to_block = select_last_to_block_stmt + .query_row([], |row| row.get::<_, u64>(0)) + .optional() + .context("Querying last stored aggregate to_block")?; + + let first_running_aggregate_block = match last_to_block { + Some(last_to_block) => BlockNumber::new_or_panic(last_to_block + 1), + // Aggregate Bloom filter table is empty -> reconstruct running aggregate + // from the genesis block. + None => BlockNumber::GENESIS, + }; + + let events_to_reconstruct: Vec>>> = events_to_reconstruct_stmt + .query_and_then( + named_params![":first_running_aggregate_block": &first_running_aggregate_block], + |row| { + let events: Option = row + .get_optional_blob(0)? + .map(|events_blob| -> anyhow::Result<_> { + let events = transaction::compression::decompress_events(events_blob) + .context("Decompressing events")?; + let events: transaction::dto::EventsForBlock = + bincode::serde::decode_from_slice(&events, bincode::config::standard()) + .context("Deserializing events")? + .0; + + Ok(events) + }) + .transpose()?; + + Ok(events.map(|events| { + events + .events() + .into_iter() + .map(|e| e.into_iter().map(Into::into).collect()) + .collect() + })) + }, + ) + .context("Querying events to reconstruct")? + .collect::>()?; + + let mut running_aggregate = AggregateBloom::new(first_running_aggregate_block); + + for (block, events_for_block) in events_to_reconstruct.iter().enumerate() { + if let Some(events) = events_for_block { + let block_number = first_running_aggregate_block + block as u64; + + let mut bloom = BloomFilter::new(); + for event in events.iter().flatten() { + bloom.set_keys(&event.keys); + bloom.set_address(&event.from_address); + } + + running_aggregate.add_bloom(&bloom, block_number); + } + } + + Ok(running_aggregate) +} + #[cfg(test)] mod tests { use super::*; @@ -611,4 +724,132 @@ mod tests { "Cannot enable Merkle trie pruning on a database that was not created with it enabled." ); } + + #[test] + #[cfg(feature = "aggregate_bloom")] + fn running_aggregate_reconstructed_after_shutdown() { + use std::num::NonZeroUsize; + use std::sync::LazyLock; + + use test_utils::*; + + static MAX_BLOCKS_TO_SCAN: LazyLock = + LazyLock::new(|| NonZeroUsize::new(10).unwrap()); + static MAX_BLOOM_FILTERS_TO_LOAD: LazyLock = + LazyLock::new(|| NonZeroUsize::new(1000).unwrap()); + + let blocks = [0, 1, 2, 3, 4, 5]; + let transactions_per_block = 2; + let headers = create_blocks(&blocks); + let transactions_and_receipts = + create_transactions_and_receipts(blocks.len() * transactions_per_block); + let emitted_events = extract_events(&headers, &transactions_and_receipts); + let insert_block_data = |tx: &Transaction<'_>, idx: usize| { + let header = &headers[idx]; + + tx.insert_block_header(header).unwrap(); + tx.insert_transaction_data( + header.number, + &transactions_and_receipts + [idx * transactions_per_block..(idx + 1) * transactions_per_block] + .iter() + .cloned() + .map(|(tx, receipt, ..)| (tx, receipt)) + .collect::>(), + Some( + &transactions_and_receipts + [idx * transactions_per_block..(idx + 1) * transactions_per_block] + .iter() + .cloned() + .map(|(_, _, events)| events) + .collect::>(), + ), + ) + .unwrap(); + }; + + // First run starts here... + let db = crate::StorageBuilder::in_memory().unwrap(); + let db_path = Arc::clone(&db.0.database_path).to_path_buf(); + + // Keep this around so that the in-memory database doesn't get dropped. + let mut rsqlite_conn = rusqlite::Connection::open(&db_path).unwrap(); + + let mut conn = db.connection().unwrap(); + let tx = conn.transaction().unwrap(); + + // ...we add two blocks. + for i in 0..2 { + insert_block_data(&tx, i); + } + + let filter = EventFilter { + keys: vec![ + vec![], + // Key present in all events as the 2nd key. + vec![pathfinder_common::macro_prelude::event_key!("0xdeadbeef")], + ], + page_size: emitted_events.len(), + ..Default::default() + }; + + let events_from_aggregate_before = tx + .events_from_aggregate(&filter, *MAX_BLOCKS_TO_SCAN) + .unwrap() + .events; + let events_before = tx + .events(&filter, *MAX_BLOCKS_TO_SCAN, *MAX_BLOOM_FILTERS_TO_LOAD) + .unwrap() + .events; + + assert_eq!(events_before, events_from_aggregate_before); + + // Pretend like we shut down by dropping these. + tx.commit().unwrap(); + drop(conn); + drop(db); + + // Second run starts here (same database)... + let db = crate::StorageBuilder::file(db_path) + .journal_mode(JournalMode::Rollback) + .migrate() + .unwrap() + .create_pool(NonZeroU32::new(5).unwrap()) + .unwrap(); + + let mut conn = db.connection().unwrap(); + let tx = conn.transaction().unwrap(); + + // ...we add the rest of the blocks. + for i in 2..headers.len() { + insert_block_data(&tx, i); + } + + let events_from_aggregate_after = tx + .events_from_aggregate(&filter, *MAX_BLOCKS_TO_SCAN) + .unwrap() + .events; + let events_after = tx + .events(&filter, *MAX_BLOCKS_TO_SCAN, *MAX_BLOOM_FILTERS_TO_LOAD) + .unwrap() + .events; + + assert_eq!(events_after, events_from_aggregate_after); + + let inserted_aggregate_filter_count = rsqlite_conn + .transaction() + .unwrap() + .prepare("SELECT COUNT(*) FROM starknet_events_filters_aggregate") + .unwrap() + .query_row([], |row| row.get::<_, u64>(0)) + .unwrap(); + + // We are using only the running aggregate. + assert!(inserted_aggregate_filter_count == 0); + assert!(events_from_aggregate_after.len() > events_from_aggregate_before.len()); + // Events added in the first run are present in the running aggregate. + for e in events_from_aggregate_before { + assert!(events_from_aggregate_after.contains(&e)); + } + } } diff --git a/crates/storage/src/schema/revision_0066.rs b/crates/storage/src/schema/revision_0066.rs index 52159524a0..99440ca0ab 100644 --- a/crates/storage/src/schema/revision_0066.rs +++ b/crates/storage/src/schema/revision_0066.rs @@ -1,77 +1,83 @@ use anyhow::Context; use pathfinder_common::BlockNumber; -use rusqlite::params; use crate::bloom::{AggregateBloom, BloomFilter}; +use crate::params::params; #[allow(dead_code)] pub(crate) fn migrate(tx: &rusqlite::Transaction<'_>) -> anyhow::Result<()> { - tracing::warn!("Creating starknet_event_filters table with aggregate bloom filters"); - - let mut select_old_filters_query = - tx.prepare("SELECT bloom FROM starknet_events_filters ORDER BY block_number")?; - - let mut bloom_filters_bytes = select_old_filters_query - .query_map(params![], |row| { - let bytes = row.get::<_, Vec>(0)?; - - Ok(bytes) - }) - .context("Selecting old filters")?; - - let mut bloom_filters = vec![]; - loop { - let Some(bloom) = bloom_filters_bytes.next().transpose()? else { - break; - }; - - bloom_filters.push(BloomFilter::from_compressed_bytes(&bloom)); - } + tracing::info!("Creating starknet_events_filters_aggregate table and migrating filters"); tx.execute( - "CREATE TABLE starknet_event_filters_aggregate ( - from_block INTEGER NOT NULL, - to_block INTEGER NOT NULL, - bloom BLOB, + r" + CREATE TABLE starknet_events_filters_aggregate ( + from_block INTEGER NOT NULL, + to_block INTEGER NOT NULL, + bitmap BLOB NOT NULL, UNIQUE(from_block, to_block) - )", + ) + ", params![], ) - .context("Creating starknet_event_filters_aggregate table")?; + .context("Creating starknet_events_filters_aggregate table")?; - bloom_filters - .chunks(AggregateBloom::BLOCK_RANGE_LEN as usize) - .enumerate() - .try_for_each(|(i, bloom_filter_chunk)| -> anyhow::Result<()> { - let from_block = i as u64 * AggregateBloom::BLOCK_RANGE_LEN; - let to_block = from_block + AggregateBloom::BLOCK_RANGE_LEN - 1; - let from_block = BlockNumber::new_or_panic(from_block); - let to_block = BlockNumber::new_or_panic(to_block); + migrate_individual_filters(tx)?; - let mut aggregate = AggregateBloom::new(from_block); + // TODO: + // Delete old filters table - for (j, bloom_filter) in bloom_filter_chunk.iter().enumerate() { - let block_number = from_block + j as u64; + Ok(()) +} - aggregate.add_bloom(bloom_filter, block_number); - } +/// Migrate individual bloom filters to the new aggregate table. We only need to +/// migrate all of the [BLOCK_RANGE_LEN](AggregateBloom::BLOCK_RANGE_LEN) sized +/// chunks. The remainder will be reconstructed by the +/// [StorageManager](crate::StorageManager) as the running aggregate. +fn migrate_individual_filters(tx: &rusqlite::Transaction<'_>) -> anyhow::Result<()> { + let mut select_old_bloom_stmt = + tx.prepare("SELECT bloom FROM starknet_events_filters ORDER BY block_number")?; + let bloom_filters: Vec = select_old_bloom_stmt + .query_and_then(params![], |row| { + let bytes: Vec = row.get(0)?; + Ok(BloomFilter::from_compressed_bytes(&bytes)) + }) + .context("Querying old Bloom filters")? + .collect::>()?; + + if bloom_filters.is_empty() { + // There are no bloom filters to migrate. + return Ok(()); + } - tx.execute( - "INSERT INTO starknet_event_filters_aggregate (from_block, to_block, bloom) - VALUES (?, ?, ?)", - params![ - &from_block.get(), - &to_block.get(), - &aggregate.compress_bitmap() - ], - ) - .context("Inserting aggregate bloom filter")?; + let mut insert_aggregate_stmt = tx.prepare( + r" + INSERT INTO starknet_events_filters_aggregate + (from_block, to_block, bitmap) + VALUES (?, ?, ?) + ", + )?; + let mut aggregate = AggregateBloom::new(BlockNumber::GENESIS); + bloom_filters + .iter() + .enumerate() + .try_for_each(|(i, bloom_filter)| -> anyhow::Result<()> { + let block_number = BlockNumber::new_or_panic(i as u64); + + aggregate.add_bloom(bloom_filter, block_number); + if block_number == aggregate.to_block { + insert_aggregate_stmt + .execute(params![ + &aggregate.from_block, + &aggregate.to_block, + &aggregate.compress_bitmap() + ]) + .context("Inserting aggregate bloom filter")?; + + aggregate = AggregateBloom::new(block_number + 1); + } Ok(()) })?; - // TODO: - // Delete old filters table - Ok(()) } diff --git a/crates/storage/src/test_utils.rs b/crates/storage/src/test_utils.rs index 9036342af4..4cd3e1860e 100644 --- a/crates/storage/src/test_utils.rs +++ b/crates/storage/src/test_utils.rs @@ -24,18 +24,19 @@ pub const EVENTS_PER_BLOCK: usize = INVOKE_TRANSACTIONS_PER_BLOCK + DECLARE_TRAN pub const NUM_TRANSACTIONS: usize = NUM_BLOCKS * TRANSACTIONS_PER_BLOCK; pub const NUM_EVENTS: usize = NUM_BLOCKS * EVENTS_PER_BLOCK; -/// Creates a set of consecutive [BlockHeader]s starting from L2 genesis, -/// with arbitrary other values. -pub(crate) fn create_blocks() -> [BlockHeader; NUM_BLOCKS] { - (0..NUM_BLOCKS) - .map(|i| { +/// Creates a custom set of [BlockHeader]s with arbitrary values. +pub(crate) fn create_blocks(block_numbers: &[usize]) -> Vec { + block_numbers + .iter() + .enumerate() + .map(|(i, &block_number)| { let storage_commitment = StorageCommitment(Felt::from_hex_str(&"b".repeat(i + 3)).unwrap()); let class_commitment = ClassCommitment(Felt::from_hex_str(&"c".repeat(i + 3)).unwrap()); let index_as_felt = Felt::from_be_slice(&[i as u8]).unwrap(); BlockHeader::builder() - .number(BlockNumber::GENESIS + i as u64) + .number(BlockNumber::GENESIS + block_number as u64) .timestamp(BlockTimestamp::new_or_panic(i as u64 + 500)) .class_commitment(class_commitment) .storage_commitment(storage_commitment) @@ -47,14 +48,13 @@ pub(crate) fn create_blocks() -> [BlockHeader; NUM_BLOCKS] { .finalize_with_hash(BlockHash(Felt::from_hex_str(&"a".repeat(i + 3)).unwrap())) }) .collect::>() - .try_into() - .unwrap() } -/// Creates a set of test transactions and receipts. +/// Creates a custom test set of N transactions and receipts. pub(crate) fn create_transactions_and_receipts( -) -> [(Transaction, Receipt, Vec); NUM_TRANSACTIONS] { - let transactions = (0..NUM_TRANSACTIONS).map(|i| match i % TRANSACTIONS_PER_BLOCK { + n: usize, +) -> Vec<(Transaction, Receipt, Vec)> { + let transactions = (0..n).map(|i| match i % TRANSACTIONS_PER_BLOCK { x if x < INVOKE_TRANSACTIONS_PER_BLOCK => Transaction { hash: TransactionHash(Felt::from_hex_str(&"4".repeat(i + 3)).unwrap()), variant: TransactionVariant::InvokeV0(InvokeTransactionV0 { @@ -149,7 +149,7 @@ pub(crate) fn create_transactions_and_receipts( (tx, receipt, events) }); - tx_receipt.collect::>().try_into().unwrap() + tx_receipt.collect::>() } /// Creates a set of emitted events from given blocks and transactions. @@ -187,28 +187,42 @@ pub struct TestData { pub events: Vec, } -// Creates a storage instance in memory with a set of expected emitted event +/// Creates an in-memory storage instance that contains a set of consecutive +/// [BlockHeader]s starting from L2 genesis, with arbitrary other values and a +/// set of expected emitted events. pub fn setup_test_storage() -> (crate::Storage, TestData) { + let block_numbers: Vec = (0..NUM_BLOCKS).collect(); + setup_custom_test_storage(&block_numbers, TRANSACTIONS_PER_BLOCK) +} + +// Creates an in-memory storage instance with custom block numbers (not +// necessarily consecutive) and a custom number of transactions per block, with +// a set of expected emitted events. +pub fn setup_custom_test_storage( + block_numbers: &[usize], + transactions_per_block: usize, +) -> (crate::Storage, TestData) { let storage = crate::StorageBuilder::in_memory().unwrap(); let mut connection = storage.connection().unwrap(); let tx = connection.transaction().unwrap(); - let headers = create_blocks(); - let transactions_and_receipts = create_transactions_and_receipts(); + let headers = create_blocks(block_numbers); + let transactions_and_receipts = + create_transactions_and_receipts(block_numbers.len() * transactions_per_block); for (i, header) in headers.iter().enumerate() { tx.insert_block_header(header).unwrap(); tx.insert_transaction_data( header.number, &transactions_and_receipts - [i * TRANSACTIONS_PER_BLOCK..(i + 1) * TRANSACTIONS_PER_BLOCK] + [i * transactions_per_block..(i + 1) * transactions_per_block] .iter() .cloned() .map(|(tx, receipt, ..)| (tx, receipt)) .collect::>(), Some( &transactions_and_receipts - [i * TRANSACTIONS_PER_BLOCK..(i + 1) * TRANSACTIONS_PER_BLOCK] + [i * transactions_per_block..(i + 1) * transactions_per_block] .iter() .cloned() .map(|(_, _, events)| events) From f92dfa899971b63b4b79bae5bc9f0bdbd73c91d4 Mon Sep 17 00:00:00 2001 From: Krisztian Kovacs Date: Thu, 14 Nov 2024 17:24:52 +0100 Subject: [PATCH 264/282] feat(rpc): add routing tests for methods available over Websocket This change adds routing tests for methods that should be available over Websocket. --- crates/rpc/src/lib.rs | 249 ++++++++++++++++++++++++++++++------------ 1 file changed, 177 insertions(+), 72 deletions(-) diff --git a/crates/rpc/src/lib.rs b/crates/rpc/src/lib.rs index 6a4a5a4cc1..b1da1705c6 100644 --- a/crates/rpc/src/lib.rs +++ b/crates/rpc/src/lib.rs @@ -895,42 +895,82 @@ mod tests { assert!(!status.is_success()); } + enum Api { + HttpOnly, + WebsocketOnly, + Both, + } + + impl Api { + fn has_websocket(&self) -> bool { + matches!(self, Self::WebsocketOnly | Self::Both) + } + + fn has_http(&self) -> bool { + matches!(self, Self::HttpOnly | Self::Both) + } + } + #[rustfmt::skip] #[rstest::rstest] - #[case::root_api ("/", "v06/starknet_api_openrpc.json", &[])] - #[case::root_trace("/", "v06/starknet_trace_api_openrpc.json", &[])] - #[case::root_write("/", "v06/starknet_write_api.json", &[])] + #[case::root_api("/", "v06/starknet_api_openrpc.json", &[], Api::HttpOnly)] + #[case::root_api_websocket("/ws", "v06/starknet_api_openrpc.json", &[], Api::WebsocketOnly)] + #[case::root_trace("/", "v06/starknet_trace_api_openrpc.json", &[], Api::HttpOnly)] + #[case::root_trace_websocket("/ws", "v06/starknet_trace_api_openrpc.json", &[], Api::WebsocketOnly)] + #[case::root_write("/", "v06/starknet_write_api.json", &[], Api::HttpOnly)] + #[case::root_write_websocket("/ws", "v06/starknet_write_api.json", &[], Api::WebsocketOnly)] // get_transaction_status is now part of the official spec, so we are phasing it out. - #[case::root_pathfinder("/", "pathfinder_rpc_api.json", &["pathfinder_version", "pathfinder_getTransactionStatus"])] - - #[case::v0_8_api("/rpc/v0_8", "v08/starknet_api_openrpc.json", &[])] - #[case::v0_8_executables("/rpc/v0_8", "v08/starknet_executables.json", &[])] - #[case::v0_8_trace("/rpc/v0_8", "v08/starknet_trace_api_openrpc.json", &[])] - #[case::v0_8_write("/rpc/v0_8", "v08/starknet_write_api.json", &[])] - + #[case::root_pathfinder("/", "pathfinder_rpc_api.json", &["pathfinder_version", "pathfinder_getTransactionStatus"], Api::HttpOnly)] + #[case::root_pathfinder_websocket("/ws", "pathfinder_rpc_api.json", &["pathfinder_version", "pathfinder_getTransactionStatus"], Api::WebsocketOnly)] + + #[case::v0_8_api("/rpc/v0_8", "v08/starknet_api_openrpc.json", &[], Api::Both)] + #[case::v0_8_executables("/rpc/v0_8", "v08/starknet_executables.json", &[], Api::Both)] + #[case::v0_8_trace("/rpc/v0_8", "v08/starknet_trace_api_openrpc.json", &[], Api::Both)] + #[case::v0_8_write("/rpc/v0_8", "v08/starknet_write_api.json", &[], Api::Both)] + #[case::v0_8_websocket( + "/rpc/v0_8", + "v08/starknet_ws_api.json", + // "starknet_subscription*" methods are in fact notifications + &[ + "starknet_subscriptionNewHeads", + "starknet_subscriptionPendingTransactions", + "starknet_subscriptionTransactionStatus", + "starknet_subscriptionEvents", + "starknet_subscriptionReorg" + ], + Api::WebsocketOnly)] // get_transaction_status is now part of the official spec, so we are phasing it out. - #[case::v0_8_pathfinder("/rpc/v0_8", "pathfinder_rpc_api.json", &["pathfinder_version", "pathfinder_getTransactionStatus"])] - - #[case::v0_7_api ("/rpc/v0_7", "v07/starknet_api_openrpc.json", &[])] - #[case::v0_7_trace("/rpc/v0_7", "v07/starknet_trace_api_openrpc.json", &[])] - #[case::v0_7_write("/rpc/v0_7", "v07/starknet_write_api.json", &[])] + #[case::v0_8_pathfinder("/rpc/v0_8", "pathfinder_rpc_api.json", &["pathfinder_version", "pathfinder_getTransactionStatus"], Api::Both)] + + #[case::v0_7_api("/rpc/v0_7", "v07/starknet_api_openrpc.json", &[], Api::HttpOnly)] + #[case::v0_7_api_websocket("/ws/rpc/v0_7", "v07/starknet_api_openrpc.json", &[], Api::WebsocketOnly)] + #[case::v0_7_trace("/rpc/v0_7", "v07/starknet_trace_api_openrpc.json", &[], Api::HttpOnly)] + #[case::v0_7_trace_websocket("/ws/rpc/v0_7", "v07/starknet_trace_api_openrpc.json", &[], Api::WebsocketOnly)] + #[case::v0_7_write("/rpc/v0_7", "v07/starknet_write_api.json", &[], Api::HttpOnly)] + #[case::v0_7_write_websocket("/ws/rpc/v0_7", "v07/starknet_write_api.json", &[], Api::WebsocketOnly)] // get_transaction_status is now part of the official spec, so we are phasing it out. - #[case::v0_7_pathfinder("/rpc/v0_7", "pathfinder_rpc_api.json", &["pathfinder_version", "pathfinder_getTransactionStatus"])] - - #[case::v0_6_api ("/rpc/v0_6", "v06/starknet_api_openrpc.json", &[])] - #[case::v0_6_trace("/rpc/v0_6", "v06/starknet_trace_api_openrpc.json", &[])] - #[case::v0_6_write("/rpc/v0_6", "v06/starknet_write_api.json", &[])] + #[case::v0_7_pathfinder("/rpc/v0_7", "pathfinder_rpc_api.json", &["pathfinder_version", "pathfinder_getTransactionStatus"], Api::HttpOnly)] + #[case::v0_7_pathfinder_websocket("/ws/rpc/v0_7", "pathfinder_rpc_api.json", &["pathfinder_version", "pathfinder_getTransactionStatus"], Api::WebsocketOnly)] + + #[case::v0_6_api("/rpc/v0_6", "v06/starknet_api_openrpc.json", &[], Api::HttpOnly)] + #[case::v0_6_api_websocket("/ws/rpc/v0_6", "v06/starknet_api_openrpc.json", &[], Api::WebsocketOnly)] + #[case::v0_6_trace("/rpc/v0_6", "v06/starknet_trace_api_openrpc.json", &[], Api::HttpOnly)] + #[case::v0_6_trace_websocket("/ws/rpc/v0_6", "v06/starknet_trace_api_openrpc.json", &[], Api::WebsocketOnly)] + #[case::v0_6_write("/rpc/v0_6", "v06/starknet_write_api.json", &[], Api::HttpOnly)] + #[case::v0_6_write_websocket("/ws/rpc/v0_6", "v06/starknet_write_api.json", &[], Api::WebsocketOnly)] // get_transaction_status is now part of the official spec, so we are phasing it out. - #[case::v0_6_pathfinder("/rpc/v0_6", "pathfinder_rpc_api.json", &["pathfinder_version", "pathfinder_getTransactionStatus"])] + #[case::v0_6_pathfinder("/rpc/v0_6", "pathfinder_rpc_api.json", &["pathfinder_version", "pathfinder_getTransactionStatus"], Api::HttpOnly)] + #[case::v0_6_pathfinder_websocket("/ws/rpc/v0_6", "pathfinder_rpc_api.json", &["pathfinder_version", "pathfinder_getTransactionStatus"], Api::WebsocketOnly)] - #[case::pathfinder("/rpc/pathfinder/v0.1", "pathfinder_rpc_api.json", &[])] - #[case::pathfinder("/rpc/pathfinder/v0_1", "pathfinder_rpc_api.json", &[])] + #[case::pathfinder("/rpc/pathfinder/v0.1", "pathfinder_rpc_api.json", &[], Api::HttpOnly)] + #[case::pathfinder("/ws/rpc/pathfinder/v0_1", "pathfinder_rpc_api.json", &[], Api::WebsocketOnly)] #[tokio::test] async fn rpc_routing( #[case] route: &'static str, #[case] specification: std::path::PathBuf, #[case] exclude: &[&'static str], + #[case] api: Api, ) { let specification = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")) .join("..") @@ -958,70 +998,135 @@ mod tests { methods.retain(|x| !exclude.contains(x)); let addr: SocketAddr = "127.0.0.1:0".parse().unwrap(); - let context = RpcContext::for_tests(); + let mut context = RpcContext::for_tests(); + if api.has_websocket() { + let (_, rx_pending) = tokio::sync::watch::channel(Default::default()); + + context = context.with_websockets(context::WebsocketContext::new( + std::num::NonZeroUsize::new(10).unwrap(), + std::num::NonZeroUsize::new(10).unwrap(), + rx_pending, + )); + } let (_jh, addr) = RpcServer::new(addr, context, RpcVersion::V07) .spawn() .await .unwrap(); - let url = format!("http://{addr}{route}"); - let client = reqwest::Client::new(); - let method_not_found = json!(-32601); - let mut failures = Vec::new(); - for method in methods { - let request = json!({ - "jsonrpc": "2.0", - "method": method, - "id": 0, - }); - - let res: serde_json::Value = client - .post(url.clone()) - .json(&request) - .send() - .await - .unwrap() - .json() - .await - .unwrap(); + if api.has_http() { + let url = format!("http://{addr}{route}"); + let client = reqwest::Client::new(); + let mut failures: Vec<&&str> = Vec::new(); + + for method in &methods { + let request = json!({ + "jsonrpc": "2.0", + "method": method, + "id": 0, + }); + + let res: serde_json::Value = client + .post(url.clone()) + .json(&request) + .send() + .await + .unwrap() + .json() + .await + .unwrap(); - if res["error"]["code"] == method_not_found { - failures.push(method); + if res["error"]["code"] == method_not_found { + failures.push(method); + } } - } - if !failures.is_empty() { - panic!("{failures:#?} were not found"); - } + if !failures.is_empty() { + panic!("{failures:#?} were not found"); + } - // Check that excluded methods are indeed not present. - failures.clear(); - for excluded in exclude { - let request = json!({ - "jsonrpc": "2.0", - "method": excluded, - "id": 0, - }); - - let res: serde_json::Value = client - .post(url.clone()) - .json(&request) - .send() - .await - .unwrap() - .json() - .await - .unwrap(); + // Check that excluded methods are indeed not present. + failures.clear(); + for excluded in exclude { + let request = json!({ + "jsonrpc": "2.0", + "method": excluded, + "id": 0, + }); + + let res: serde_json::Value = client + .post(url.clone()) + .json(&request) + .send() + .await + .unwrap() + .json() + .await + .unwrap(); + + if res["error"]["code"] != method_not_found { + failures.push(excluded); + } + } - if res["error"]["code"] != method_not_found { - failures.push(excluded); + if !failures.is_empty() { + panic!("{failures:#?} were marked as excluded but are actually present"); } } - if !failures.is_empty() { - panic!("{failures:#?} were marked as excluded but are actually present"); + if api.has_websocket() { + use tokio_tungstenite::tungstenite::Message; + use tokio_tungstenite::tungstenite::client::IntoClientRequest; + use futures::{SinkExt, StreamExt}; + + let request = format!("ws://{addr}{route}").into_client_request().unwrap(); + let (mut stream, _) = tokio_tungstenite::connect_async(request).await.unwrap(); + + let mut failures: Vec<&&str> = Vec::new(); + for method in &methods { + let request = json!({ + "jsonrpc": "2.0", + "method": method, + "id": 0, + }); + + stream.send(Message::Text(request.to_string())).await.unwrap(); + let res = stream.next().await.unwrap().unwrap(); + let res: serde_json::Value = serde_json::from_str(&res.to_string()).unwrap(); + + if res["error"]["code"] == method_not_found { + failures.push(method); + } + } + + if !failures.is_empty() { + panic!("{failures:#?} were not found"); + } + + // Check that excluded methods are indeed not present. + failures.clear(); + for excluded in exclude { + let request = json!({ + "jsonrpc": "2.0", + "method": excluded, + "id": 0, + }); + + stream.send(Message::Text(request.to_string())).await.unwrap(); + let res = stream.next().await.unwrap().unwrap(); + let res: serde_json::Value = serde_json::from_str(&res.to_string()).unwrap(); + + if res["error"]["code"] != method_not_found { + failures.push(excluded); + } + } + + if !failures.is_empty() { + panic!("{failures:#?} were marked as excluded but are actually present"); + } } + + } } From 80e24dcd903e510aae347ec6d85d0d7be8b13f68 Mon Sep 17 00:00:00 2001 From: t00ts Date: Fri, 15 Nov 2024 11:19:44 +0400 Subject: [PATCH 265/282] feat(rpc): add starknet subs errors to ws endpoints --- crates/rpc/src/error.rs | 13 +++++ crates/rpc/src/jsonrpc/router/subscription.rs | 16 +++--- crates/rpc/src/method/subscribe_events.rs | 44 ++++++++++++++++ crates/rpc/src/method/subscribe_new_heads.rs | 51 +++++++++++++++++++ 4 files changed, 115 insertions(+), 9 deletions(-) diff --git a/crates/rpc/src/error.rs b/crates/rpc/src/error.rs index 3976d5f682..90f34948d2 100644 --- a/crates/rpc/src/error.rs +++ b/crates/rpc/src/error.rs @@ -99,6 +99,12 @@ pub enum ApplicationError { StorageProofNotSupported, #[error("Proof is missing")] ProofMissing, + #[error("Invalid subscription id")] + InvalidSubscriptionID, + #[error("Too many addresses in filter sender_address filter")] + TooManyAddressesInFilter, + #[error("This method does not support being called on the pending block")] + CallOnPending, /// Internal errors are errors whose details we don't want to show to the /// end user. These are logged, and a simple "internal error" message is /// shown to the end user. @@ -151,6 +157,10 @@ impl ApplicationError { ApplicationError::ProofMissing => 10001, ApplicationError::SubscriptionTransactionHashNotFound { .. } => 10029, ApplicationError::SubscriptionGatewayDown { .. } => 10030, + // doc/rpc/starknet_ws_api.json + ApplicationError::InvalidSubscriptionID => 66, + ApplicationError::TooManyAddressesInFilter => 67, + ApplicationError::CallOnPending => 69, // https://www.jsonrpc.org/specification#error_object ApplicationError::GatewayError(_) | ApplicationError::Internal(_) @@ -200,6 +210,9 @@ impl ApplicationError { ApplicationError::CompiledClassHashMismatch => None, ApplicationError::UnsupportedTxVersion => None, ApplicationError::UnsupportedContractClassVersion => None, + ApplicationError::InvalidSubscriptionID => None, + ApplicationError::TooManyAddressesInFilter => None, + ApplicationError::CallOnPending => None, ApplicationError::GatewayError(error) => Some(json!({ "error": error, })), diff --git a/crates/rpc/src/jsonrpc/router/subscription.rs b/crates/rpc/src/jsonrpc/router/subscription.rs index c37740b26e..7b4703c99c 100644 --- a/crates/rpc/src/jsonrpc/router/subscription.rs +++ b/crates/rpc/src/jsonrpc/router/subscription.rs @@ -165,9 +165,7 @@ where let mut current_block = match first_block { BlockId::Pending => { - return Err(RpcError::InvalidParams( - "Pending block not supported".to_string(), - )); + return Err(RpcError::ApplicationError(ApplicationError::CallOnPending)); } BlockId::Latest => { // No need to catch up. The code below will subscribe to new blocks. @@ -616,12 +614,12 @@ async fn handle_request( })?; let (_, handle) = subscriptions .remove(¶ms.subscription_id) - .ok_or_else(|| { - RpcResponse::invalid_params( - req_id.clone(), - "Subscription not found".to_string(), - state.version, - ) + .ok_or_else(|| RpcResponse { + output: Err(RpcError::ApplicationError( + ApplicationError::InvalidSubscriptionID, + )), + id: req_id.clone(), + version: state.version, })?; handle.abort(); metrics::increment_counter!("rpc_method_calls_total", "method" => "starknet_unsubscribe", "version" => state.version.to_str()); diff --git a/crates/rpc/src/method/subscribe_events.rs b/crates/rpc/src/method/subscribe_events.rs index 522a3b5395..fbebafcbe6 100644 --- a/crates/rpc/src/method/subscribe_events.rs +++ b/crates/rpc/src/method/subscribe_events.rs @@ -68,6 +68,9 @@ impl RpcSubscriptionFlow for SubscribeEvents { fn validate_params(params: &Self::Params) -> Result<(), RpcError> { if let Some(params) = params { + if let Some(BlockId::Pending) = params.block_id { + return Err(RpcError::ApplicationError(ApplicationError::CallOnPending)); + } if let Some(keys) = ¶ms.keys { if keys.len() > EVENT_KEY_FILTER_LIMIT { return Err(RpcError::ApplicationError( @@ -643,6 +646,47 @@ mod tests { ); } + #[tokio::test] + async fn subscribe_with_pending_block() { + let router = setup(0).await; + let (sender_tx, mut sender_rx) = mpsc::channel(1024); + let (receiver_tx, receiver_rx) = mpsc::channel(1024); + handle_json_rpc_socket(router.clone(), sender_tx, receiver_rx); + + // Send subscription request with pending block + receiver_tx + .send(Ok(Message::Text( + serde_json::json!({ + "jsonrpc": "2.0", + "id": 1, + "method": "starknet_subscribeEvents", + "params": {"block_id": "pending"} + }) + .to_string(), + ))) + .await + .unwrap(); + + // Expect error response + let res = sender_rx.recv().await.unwrap().unwrap(); + let json: serde_json::Value = match res { + Message::Text(json) => serde_json::from_str(&json).unwrap(), + _ => panic!("Expected text message"), + }; + + assert_eq!( + json, + serde_json::json!({ + "jsonrpc": "2.0", + "id": 1, + "error": { + "code": 69, + "message": "This method does not support being called on the pending block" + } + }) + ); + } + async fn setup(num_blocks: u64) -> RpcRouter { assert!(num_blocks == 0 || num_blocks > CATCH_UP_BATCH_SIZE); diff --git a/crates/rpc/src/method/subscribe_new_heads.rs b/crates/rpc/src/method/subscribe_new_heads.rs index bba317b87c..e7492f83d7 100644 --- a/crates/rpc/src/method/subscribe_new_heads.rs +++ b/crates/rpc/src/method/subscribe_new_heads.rs @@ -6,6 +6,7 @@ use tokio::sync::mpsc; use super::REORG_SUBSCRIPTION_NAME; use crate::context::RpcContext; +use crate::error::ApplicationError; use crate::jsonrpc::{CatchUp, RpcError, RpcSubscriptionFlow, SubscriptionMessage}; use crate::Reorg; @@ -55,6 +56,15 @@ impl RpcSubscriptionFlow for SubscribeNewHeads { type Params = Option; type Notification = Notification; + fn validate_params(params: &Self::Params) -> Result<(), RpcError> { + if let Some(params) = params { + if let Some(BlockId::Pending) = params.block_id { + return Err(RpcError::ApplicationError(ApplicationError::CallOnPending)); + } + } + Ok(()) + } + fn starting_block(params: &Self::Params) -> BlockId { params .as_ref() @@ -459,6 +469,47 @@ mod tests { assert!(rx.is_empty()); } + #[tokio::test] + async fn subscribe_with_pending_block() { + let router = setup(0).await; + let (sender_tx, mut sender_rx) = mpsc::channel(1024); + let (receiver_tx, receiver_rx) = mpsc::channel(1024); + handle_json_rpc_socket(router.clone(), sender_tx, receiver_rx); + + // Send subscription request with pending block + receiver_tx + .send(Ok(Message::Text( + serde_json::json!({ + "jsonrpc": "2.0", + "id": 1, + "method": "starknet_subscribeNewHeads", + "params": {"block_id": "pending"} + }) + .to_string(), + ))) + .await + .unwrap(); + + // Expect error response + let res = sender_rx.recv().await.unwrap().unwrap(); + let json: serde_json::Value = match res { + Message::Text(json) => serde_json::from_str(&json).unwrap(), + _ => panic!("Expected text message"), + }; + + assert_eq!( + json, + serde_json::json!({ + "jsonrpc": "2.0", + "id": 1, + "error": { + "code": 69, + "message": "This method does not support being called on the pending block" + } + }) + ); + } + async fn setup(num_blocks: u64) -> RpcRouter { let storage = StorageBuilder::in_memory().unwrap(); tokio::task::spawn_blocking({ From c89bd8af34a3fe7648b15ccfaab725aa5b6dd637 Mon Sep 17 00:00:00 2001 From: Krisztian Kovacs Date: Fri, 15 Nov 2024 15:14:29 +0100 Subject: [PATCH 266/282] feat(rpc/v08): add `l1_data_gas` to v3 transaction resource bounds This is a partial implementation: since we don't _really_ use these new values other than passing those on to the Starknet gateway when submitting new transactions the new field is not added to the `ResourceBounds` type in the common crate. We'll have to do that once these values need to be stored in storage and used in the P2P protocol when adding proper support for Starknet 0.13.4. Closes: #2379 --- crates/gateway-types/src/reply.rs | 10 ++++++++++ .../rpc/fixtures/0.6.0/broadcasted_transactions.json | 4 ++++ crates/rpc/src/method/add_declare_transaction.rs | 1 + .../rpc/src/method/add_deploy_account_transaction.rs | 1 + crates/rpc/src/method/add_invoke_transaction.rs | 1 + crates/rpc/src/v02/types.rs | 9 +++++++++ crates/rpc/src/v06/method/add_declare_transaction.rs | 1 + .../src/v06/method/add_deploy_account_transaction.rs | 1 + crates/rpc/src/v06/method/add_invoke_transaction.rs | 1 + crates/rpc/src/v06/method/simulate_transactions.rs | 1 + 10 files changed, 30 insertions(+) diff --git a/crates/gateway-types/src/reply.rs b/crates/gateway-types/src/reply.rs index 9fe4ca0abb..c860224cae 100644 --- a/crates/gateway-types/src/reply.rs +++ b/crates/gateway-types/src/reply.rs @@ -800,6 +800,14 @@ pub mod transaction { pub l1_gas: ResourceBound, #[serde(rename = "L2_GAS")] pub l2_gas: ResourceBound, + // Introduced in Starknet v0.13.4. This has to be optional because not sending it to the + // gateway is not equivalent to sending an explicit zero bound. + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "L1_DATA_GAS" + )] + pub l1_data_gas: Option, } impl From for pathfinder_common::transaction::ResourceBounds { @@ -816,6 +824,8 @@ pub mod transaction { Self { l1_gas: value.l1_gas.into(), l2_gas: value.l2_gas.into(), + // TODO: add this when adding support for Starknet 0.13.4 + l1_data_gas: None, } } } diff --git a/crates/rpc/fixtures/0.6.0/broadcasted_transactions.json b/crates/rpc/fixtures/0.6.0/broadcasted_transactions.json index 5d2fb9c6c5..05cc637856 100644 --- a/crates/rpc/fixtures/0.6.0/broadcasted_transactions.json +++ b/crates/rpc/fixtures/0.6.0/broadcasted_transactions.json @@ -183,6 +183,10 @@ ], "nonce": "0x8", "resource_bounds": { + "l1_data_gas": { + "max_amount": "0x3333", + "max_price_per_unit": "0x4444" + }, "l1_gas": { "max_amount": "0x1111", "max_price_per_unit": "0x2222" diff --git a/crates/rpc/src/method/add_declare_transaction.rs b/crates/rpc/src/method/add_declare_transaction.rs index dc1d66b1fe..5bdd0a765c 100644 --- a/crates/rpc/src/method/add_declare_transaction.rs +++ b/crates/rpc/src/method/add_declare_transaction.rs @@ -743,6 +743,7 @@ mod tests { max_amount: ResourceAmount(0), max_price_per_unit: ResourcePricePerUnit(0), }, + l1_data_gas: None, }, tip: Tip(0), paymaster_data: vec![], diff --git a/crates/rpc/src/method/add_deploy_account_transaction.rs b/crates/rpc/src/method/add_deploy_account_transaction.rs index c796f521e8..a0a7786aca 100644 --- a/crates/rpc/src/method/add_deploy_account_transaction.rs +++ b/crates/rpc/src/method/add_deploy_account_transaction.rs @@ -389,6 +389,7 @@ mod tests { max_amount: ResourceAmount(0), max_price_per_unit: ResourcePricePerUnit(0), }, + l1_data_gas: None, }, tip: Tip(0), paymaster_data: vec![], diff --git a/crates/rpc/src/method/add_invoke_transaction.rs b/crates/rpc/src/method/add_invoke_transaction.rs index ff88d4c216..cb6e8c234a 100644 --- a/crates/rpc/src/method/add_invoke_transaction.rs +++ b/crates/rpc/src/method/add_invoke_transaction.rs @@ -424,6 +424,7 @@ mod tests { max_amount: ResourceAmount(0), max_price_per_unit: ResourcePricePerUnit(0), }, + l1_data_gas: None, }, tip: Tip(0), paymaster_data: vec![], diff --git a/crates/rpc/src/v02/types.rs b/crates/rpc/src/v02/types.rs index ef3eecab3d..0a47fba535 100644 --- a/crates/rpc/src/v02/types.rs +++ b/crates/rpc/src/v02/types.rs @@ -13,6 +13,8 @@ pub mod syncing; pub struct ResourceBounds { pub l1_gas: ResourceBound, pub l2_gas: ResourceBound, + #[serde(skip_serializing_if = "Option::is_none")] + pub l1_data_gas: Option, } impl crate::dto::DeserializeForVersion for ResourceBounds { @@ -21,6 +23,7 @@ impl crate::dto::DeserializeForVersion for ResourceBounds { Ok(Self { l1_gas: value.deserialize("l1_gas")?, l2_gas: value.deserialize("l2_gas")?, + l1_data_gas: value.deserialize_optional("l1_data_gas")?, }) }) } @@ -907,6 +910,7 @@ pub mod request { max_amount: ResourceAmount(0), max_price_per_unit: ResourcePricePerUnit(0), }, + l1_data_gas: None, }, tip: Tip(0x1234), paymaster_data: vec![ @@ -976,6 +980,7 @@ pub mod request { max_amount: ResourceAmount(0), max_price_per_unit: ResourcePricePerUnit(0), }, + l1_data_gas: None, }, tip: Tip(0x1234), paymaster_data: vec![ @@ -1006,6 +1011,10 @@ pub mod request { max_amount: ResourceAmount(0), max_price_per_unit: ResourcePricePerUnit(0), }, + l1_data_gas: Some(ResourceBound { + max_amount: ResourceAmount(0x3333), + max_price_per_unit: ResourcePricePerUnit(0x4444), + }), }, tip: Tip(0x1234), paymaster_data: vec![ diff --git a/crates/rpc/src/v06/method/add_declare_transaction.rs b/crates/rpc/src/v06/method/add_declare_transaction.rs index f0bb56e85f..7874ee9e66 100644 --- a/crates/rpc/src/v06/method/add_declare_transaction.rs +++ b/crates/rpc/src/v06/method/add_declare_transaction.rs @@ -709,6 +709,7 @@ mod tests { max_amount: ResourceAmount(0), max_price_per_unit: ResourcePricePerUnit(0), }, + l1_data_gas: None, }, tip: Tip(0), paymaster_data: vec![], diff --git a/crates/rpc/src/v06/method/add_deploy_account_transaction.rs b/crates/rpc/src/v06/method/add_deploy_account_transaction.rs index 6fddc81ff1..206f51baa4 100644 --- a/crates/rpc/src/v06/method/add_deploy_account_transaction.rs +++ b/crates/rpc/src/v06/method/add_deploy_account_transaction.rs @@ -357,6 +357,7 @@ mod tests { max_amount: ResourceAmount(0), max_price_per_unit: ResourcePricePerUnit(0), }, + l1_data_gas: None, }, tip: Tip(0), paymaster_data: vec![], diff --git a/crates/rpc/src/v06/method/add_invoke_transaction.rs b/crates/rpc/src/v06/method/add_invoke_transaction.rs index a6f0984c10..ae0b6e7def 100644 --- a/crates/rpc/src/v06/method/add_invoke_transaction.rs +++ b/crates/rpc/src/v06/method/add_invoke_transaction.rs @@ -395,6 +395,7 @@ mod tests { max_amount: ResourceAmount(0), max_price_per_unit: ResourcePricePerUnit(0), }, + l1_data_gas: None, }, tip: Tip(0), paymaster_data: vec![], diff --git a/crates/rpc/src/v06/method/simulate_transactions.rs b/crates/rpc/src/v06/method/simulate_transactions.rs index 75357db6bc..8c5963f53b 100644 --- a/crates/rpc/src/v06/method/simulate_transactions.rs +++ b/crates/rpc/src/v06/method/simulate_transactions.rs @@ -1225,6 +1225,7 @@ pub(crate) mod tests { max_amount: ResourceAmount(10000), max_price_per_unit: ResourcePricePerUnit(100000000), }, + l1_data_gas: None, }, tip: Tip(0), paymaster_data: vec![], From 9238626994948d9505201ca848272eb95e37e208 Mon Sep 17 00:00:00 2001 From: Vaclav Barta Date: Tue, 19 Nov 2024 08:37:39 +0100 Subject: [PATCH 267/282] removed lock error handling from LruContractCache --- crates/executor/src/lru_cache.rs | 20 ++++++-------------- crates/executor/src/state_reader.rs | 4 ++-- 2 files changed, 8 insertions(+), 16 deletions(-) diff --git a/crates/executor/src/lru_cache.rs b/crates/executor/src/lru_cache.rs index 2e88702999..1f5eed4242 100644 --- a/crates/executor/src/lru_cache.rs +++ b/crates/executor/src/lru_cache.rs @@ -1,12 +1,9 @@ use std::sync::{LazyLock, Mutex, MutexGuard}; use blockifier::execution::contract_class::ContractClass; -use blockifier::state::errors::StateError; -use blockifier::state::state_api::StateResult; use cached::{Cached, SizedCache}; use pathfinder_common::BlockNumber; use starknet_api::core::ClassHash as StarknetClassHash; -use tracing::warn; pub static GLOBAL_CACHE: LazyLock = LazyLock::new(LruContractCache::new); @@ -25,15 +22,12 @@ impl LruContractCache { Self(Mutex::new(SizedCache::with_size(128))) } - fn locked_cache(&self) -> StateResult>> { - self.0.lock().map_err(|err| { - warn!("Contract class cache lock is poisoned. Cause: {}.", err); - StateError::StateReadError("Poisoned lock".to_string()) - }) + fn locked_cache(&self) -> MutexGuard<'_, SizedCache> { + self.0.lock().unwrap() } - pub fn get(&self, class_hash: &StarknetClassHash) -> StateResult> { - Ok(self.locked_cache()?.cache_get(class_hash).cloned()) + pub fn get(&self, class_hash: &StarknetClassHash) -> Option { + self.locked_cache().cache_get(class_hash).cloned() } pub fn set( @@ -41,15 +35,13 @@ impl LruContractCache { class_hash: StarknetClassHash, contract_class: ContractClass, block_number: BlockNumber, - ) -> StateResult<()> { - self.locked_cache()?.cache_set( + ) { + self.locked_cache().cache_set( class_hash, Entry { definition: contract_class.clone(), height: block_number, }, ); - - Ok(()) } } diff --git a/crates/executor/src/state_reader.rs b/crates/executor/src/state_reader.rs index b5c395376f..574d7a1971 100644 --- a/crates/executor/src/state_reader.rs +++ b/crates/executor/src/state_reader.rs @@ -230,7 +230,7 @@ impl StateReader for PathfinderStateReader<'_> { tracing::trace_span!("get_compiled_contract_class", class_hash=%pathfinder_class_hash) .entered(); - if let Some(entry) = GLOBAL_CACHE.get(&class_hash)? { + if let Some(entry) = GLOBAL_CACHE.get(&class_hash) { if let Some(reader_block_number) = self.block_number { if entry.height <= reader_block_number { tracing::trace!("Global class cache hit"); @@ -243,7 +243,7 @@ impl StateReader for PathfinderStateReader<'_> { self.non_cached_compiled_contract_class(pathfinder_class_hash, &class_hash)?; if let Some(block_number) = definition_block_number { - GLOBAL_CACHE.set(class_hash, contract_class.clone(), block_number)?; + GLOBAL_CACHE.set(class_hash, contract_class.clone(), block_number); } Ok(contract_class) From 5d2d330bfd8b64772a6773045942a17f11fdef80 Mon Sep 17 00:00:00 2001 From: Krisztian Kovacs Date: Tue, 19 Nov 2024 10:29:06 +0100 Subject: [PATCH 268/282] fix(storage): work around in-memory DB locking issues We're using shared cache mode with our in-memory DB to allow multiple connections from within the same process. This means that in contrast to a file-based DB we immediately get locking errors in case of concurrent writes -- a pool size of one avoids this. There are certain tests that _do_ need multiple connections to the in-memory DB though so we add a constructor that allows specifying the size of the pool we require. --- crates/pathfinder/src/state/sync.rs | 42 +++++++++++++++---- crates/pathfinder/src/state/sync/l2.rs | 19 +++++++-- crates/pathfinder/src/sync/checkpoint.rs | 24 +++++++++-- crates/pathfinder/src/sync/track.rs | 6 ++- crates/rpc/src/method/call.rs | 2 + .../rpc/src/pathfinder/methods/get_proof.rs | 1 + crates/rpc/src/test_setup.rs | 6 ++- crates/rpc/src/v06/method/call.rs | 2 + .../v06/method/trace_block_transactions.rs | 1 + .../rpc/src/v06/method/trace_transaction.rs | 1 + crates/storage/src/lib.rs | 20 ++++++++- 11 files changed, 107 insertions(+), 17 deletions(-) diff --git a/crates/pathfinder/src/state/sync.rs b/crates/pathfinder/src/state/sync.rs index 378d1cd6e4..bc9dbf80b3 100644 --- a/crates/pathfinder/src/state/sync.rs +++ b/crates/pathfinder/src/state/sync.rs @@ -1390,7 +1390,11 @@ mod tests { #[tokio::test(flavor = "multi_thread")] async fn block_updates() { - let storage = StorageBuilder::in_memory().unwrap(); + let storage = StorageBuilder::in_memory_with_trie_pruning_and_pool_size( + pathfinder_storage::TriePruneMode::Archive, + std::num::NonZeroU32::new(5).unwrap(), + ) + .unwrap(); let mut connection = storage.connection().unwrap(); let (event_tx, event_rx) = tokio::sync::mpsc::channel(100); @@ -1439,7 +1443,11 @@ mod tests { #[tokio::test(flavor = "multi_thread")] async fn reorg() { - let storage = StorageBuilder::in_memory().unwrap(); + let storage = StorageBuilder::in_memory_with_trie_pruning_and_pool_size( + pathfinder_storage::TriePruneMode::Archive, + std::num::NonZeroU32::new(5).unwrap(), + ) + .unwrap(); let mut connection = storage.connection().unwrap(); let (event_tx, event_rx) = tokio::sync::mpsc::channel(100); @@ -1491,7 +1499,11 @@ mod tests { // A bug caused reorg'd block numbers to be skipped. This // was due to the expected block number not being updated // when handling the reorg. - let storage = StorageBuilder::in_memory().unwrap(); + let storage = StorageBuilder::in_memory_with_trie_pruning_and_pool_size( + pathfinder_storage::TriePruneMode::Archive, + std::num::NonZeroU32::new(5).unwrap(), + ) + .unwrap(); let mut connection = storage.connection().unwrap(); let (event_tx, event_rx) = tokio::sync::mpsc::channel(100); @@ -1552,7 +1564,11 @@ mod tests { #[tokio::test(flavor = "multi_thread")] async fn reorg_to_genesis() { - let storage = StorageBuilder::in_memory().unwrap(); + let storage = StorageBuilder::in_memory_with_trie_pruning_and_pool_size( + pathfinder_storage::TriePruneMode::Archive, + std::num::NonZeroU32::new(5).unwrap(), + ) + .unwrap(); let mut connection = storage.connection().unwrap(); let (event_tx, event_rx) = tokio::sync::mpsc::channel(100); @@ -1591,7 +1607,11 @@ mod tests { #[tokio::test(flavor = "multi_thread")] async fn new_cairo_contract() { - let storage = StorageBuilder::in_memory().unwrap(); + let storage = StorageBuilder::in_memory_with_trie_pruning_and_pool_size( + pathfinder_storage::TriePruneMode::Archive, + std::num::NonZeroU32::new(5).unwrap(), + ) + .unwrap(); let mut connection = storage.connection().unwrap(); let (event_tx, event_rx) = tokio::sync::mpsc::channel(1); @@ -1630,7 +1650,11 @@ mod tests { #[tokio::test(flavor = "multi_thread")] async fn new_sierra_contract() { - let storage = StorageBuilder::in_memory().unwrap(); + let storage = StorageBuilder::in_memory_with_trie_pruning_and_pool_size( + pathfinder_storage::TriePruneMode::Archive, + std::num::NonZeroU32::new(5).unwrap(), + ) + .unwrap(); let mut connection = storage.connection().unwrap(); let (event_tx, event_rx) = tokio::sync::mpsc::channel(1); @@ -1671,7 +1695,11 @@ mod tests { #[tokio::test(flavor = "multi_thread")] async fn consumer_should_ignore_duplicate_blocks() { - let storage = StorageBuilder::in_memory().unwrap(); + let storage = StorageBuilder::in_memory_with_trie_pruning_and_pool_size( + pathfinder_storage::TriePruneMode::Archive, + std::num::NonZeroU32::new(5).unwrap(), + ) + .unwrap(); let (event_tx, event_rx) = tokio::sync::mpsc::channel(5); diff --git a/crates/pathfinder/src/state/sync/l2.rs b/crates/pathfinder/src/state/sync/l2.rs index 804efabd12..8d1908e791 100644 --- a/crates/pathfinder/src/state/sync/l2.rs +++ b/crates/pathfinder/src/state/sync/l2.rs @@ -1171,6 +1171,7 @@ async fn reorg( #[cfg(test)] mod tests { mod sync { + use std::num::NonZeroU32; use std::sync::LazyLock; use assert_matches::assert_matches; @@ -1330,7 +1331,11 @@ mod tests { tx_event: mpsc::Sender, sequencer: MockGatewayApi, ) -> JoinHandle> { - let storage = StorageBuilder::in_memory().unwrap(); + let storage = StorageBuilder::in_memory_with_trie_pruning_and_pool_size( + pathfinder_storage::TriePruneMode::Archive, + NonZeroU32::new(5).unwrap(), + ) + .unwrap(); let sequencer = std::sync::Arc::new(sequencer); let context = L2SyncContext { sequencer, @@ -1358,7 +1363,11 @@ mod tests { tx_event: mpsc::Sender, sequencer: MockGatewayApi, ) -> JoinHandle>> { - let storage = StorageBuilder::in_memory().unwrap(); + let storage = StorageBuilder::in_memory_with_trie_pruning_and_pool_size( + pathfinder_storage::TriePruneMode::Archive, + NonZeroU32::new(5).unwrap(), + ) + .unwrap(); let sequencer = std::sync::Arc::new(sequencer); let context = L2SyncContext { sequencer, @@ -1844,7 +1853,11 @@ mod tests { chain: Chain::SepoliaTestnet, chain_id: ChainId::SEPOLIA_TESTNET, block_validation_mode: MODE, - storage: StorageBuilder::in_memory().unwrap(), + storage: StorageBuilder::in_memory_with_trie_pruning_and_pool_size( + pathfinder_storage::TriePruneMode::Archive, + NonZeroU32::new(5).unwrap(), + ) + .unwrap(), sequencer_public_key: PublicKey::ZERO, fetch_concurrency: std::num::NonZeroUsize::new(1).unwrap(), fetch_casm_from_fgw: false, diff --git a/crates/pathfinder/src/sync/checkpoint.rs b/crates/pathfinder/src/sync/checkpoint.rs index 14179b7e7c..debf6c6558 100644 --- a/crates/pathfinder/src/sync/checkpoint.rs +++ b/crates/pathfinder/src/sync/checkpoint.rs @@ -810,7 +810,11 @@ mod tests { .map(PeerData::for_tests) .collect::>(), expected_headers, - storage: StorageBuilder::in_memory().unwrap(), + storage: StorageBuilder::in_memory_with_trie_pruning_and_pool_size( + pathfinder_storage::TriePruneMode::Archive, + std::num::NonZeroU32::new(5).unwrap(), + ) + .unwrap(), // https://alpha-sepolia.starknet.io/feeder_gateway/get_public_key public_key: public_key!( "0x1252b6bce1351844c677869c6327e80eae1535755b611c66b8f46e595b40eea" @@ -1046,7 +1050,11 @@ mod tests { b.transaction_data = Default::default(); }); - let storage = StorageBuilder::in_memory().unwrap(); + let storage = StorageBuilder::in_memory_with_trie_pruning_and_pool_size( + pathfinder_storage::TriePruneMode::Archive, + std::num::NonZeroU32::new(5).unwrap(), + ) + .unwrap(); fake_storage::fill(&storage, &blocks); Setup { streamed_transactions, @@ -1082,7 +1090,11 @@ mod tests { b.transaction_data = Default::default(); }); - let storage = StorageBuilder::in_memory().unwrap(); + let storage = StorageBuilder::in_memory_with_trie_pruning_and_pool_size( + pathfinder_storage::TriePruneMode::Archive, + std::num::NonZeroU32::new(5).unwrap(), + ) + .unwrap(); fake_storage::fill(&storage, &blocks); Setup { streamed_transactions, @@ -1267,7 +1279,11 @@ mod tests { }) .collect::>(); - let storage = StorageBuilder::in_memory().unwrap(); + let storage = StorageBuilder::in_memory_with_trie_pruning_and_pool_size( + pathfinder_storage::TriePruneMode::Archive, + std::num::NonZeroU32::new(5).unwrap(), + ) + .unwrap(); fake_storage::fill(&storage, &blocks); Setup { streamed_state_diffs, diff --git a/crates/pathfinder/src/sync/track.rs b/crates/pathfinder/src/sync/track.rs index 070110629a..1bc84949f8 100644 --- a/crates/pathfinder/src/sync/track.rs +++ b/crates/pathfinder/src/sync/track.rs @@ -928,7 +928,11 @@ mod tests { blocks: blocks.clone(), }; - let storage = StorageBuilder::in_memory().unwrap(); + let storage = StorageBuilder::in_memory_with_trie_pruning_and_pool_size( + pathfinder_storage::TriePruneMode::Archive, + std::num::NonZeroU32::new(5).unwrap(), + ) + .unwrap(); let sync = Sync { latest: futures::stream::iter(vec![latest]), diff --git a/crates/rpc/src/method/call.rs b/crates/rpc/src/method/call.rs index 4e55915a20..31353c188c 100644 --- a/crates/rpc/src/method/call.rs +++ b/crates/rpc/src/method/call.rs @@ -294,6 +294,7 @@ mod tests { .unwrap(); tx.commit().unwrap(); + drop(db); let context = RpcContext::for_tests_on(pathfinder_common::Chain::Mainnet).with_storage(storage); @@ -501,6 +502,7 @@ mod tests { tx.insert_state_update(block_number, &state_update).unwrap(); tx.commit().unwrap(); + drop(connection); let input = Input { request: FunctionCall { diff --git a/crates/rpc/src/pathfinder/methods/get_proof.rs b/crates/rpc/src/pathfinder/methods/get_proof.rs index cf80c97fc3..7d4103be00 100644 --- a/crates/rpc/src/pathfinder/methods/get_proof.rs +++ b/crates/rpc/src/pathfinder/methods/get_proof.rs @@ -468,6 +468,7 @@ mod tests { ) .unwrap(); tx.commit().unwrap(); + drop(conn); let input = GetProofInput { block_id: BlockId::Latest, diff --git a/crates/rpc/src/test_setup.rs b/crates/rpc/src/test_setup.rs index 3278e0a527..ee73a2ac76 100644 --- a/crates/rpc/src/test_setup.rs +++ b/crates/rpc/src/test_setup.rs @@ -20,7 +20,11 @@ pub async fn test_storage StateUpdate>( version: StarknetVersion, customize_state_update: F, ) -> (Storage, BlockHeader, ContractAddress, ContractAddress) { - let storage = pathfinder_storage::StorageBuilder::in_memory().unwrap(); + let storage = pathfinder_storage::StorageBuilder::in_memory_with_trie_pruning_and_pool_size( + pathfinder_storage::TriePruneMode::Archive, + std::num::NonZeroU32::new(2).unwrap(), + ) + .unwrap(); let mut db = storage.connection().unwrap(); let tx = db.transaction().unwrap(); diff --git a/crates/rpc/src/v06/method/call.rs b/crates/rpc/src/v06/method/call.rs index 178f39f987..8403d410f6 100644 --- a/crates/rpc/src/v06/method/call.rs +++ b/crates/rpc/src/v06/method/call.rs @@ -275,6 +275,7 @@ mod tests { .unwrap(); tx.commit().unwrap(); + drop(db); let context = RpcContext::for_tests_on(pathfinder_common::Chain::Mainnet).with_storage(storage); @@ -484,6 +485,7 @@ mod tests { tx.insert_state_update(block_number, &state_update).unwrap(); tx.commit().unwrap(); + drop(connection); let input = CallInput { request: FunctionCall { diff --git a/crates/rpc/src/v06/method/trace_block_transactions.rs b/crates/rpc/src/v06/method/trace_block_transactions.rs index 4a7eaea6c3..fcfd508316 100644 --- a/crates/rpc/src/v06/method/trace_block_transactions.rs +++ b/crates/rpc/src/v06/method/trace_block_transactions.rs @@ -713,6 +713,7 @@ pub(crate) mod tests { .insert_transaction_data(header.number, &transactions_data, Some(&events_data)) .unwrap(); transaction.commit().unwrap(); + drop(connection); // The tracing succeeds. trace_block_transactions( diff --git a/crates/rpc/src/v06/method/trace_transaction.rs b/crates/rpc/src/v06/method/trace_transaction.rs index cbc9fbdc9f..3b8c7b565a 100644 --- a/crates/rpc/src/v06/method/trace_transaction.rs +++ b/crates/rpc/src/v06/method/trace_transaction.rs @@ -385,6 +385,7 @@ pub mod tests { .insert_transaction_data(header.number, &transactions_data, Some(&events_data)) .unwrap(); transaction.commit().unwrap(); + drop(connection); // The tracing succeeds. trace_transaction( diff --git a/crates/storage/src/lib.rs b/crates/storage/src/lib.rs index 47bd04ac77..27929eae90 100644 --- a/crates/storage/src/lib.rs +++ b/crates/storage/src/lib.rs @@ -194,7 +194,25 @@ impl StorageBuilder { /// Convenience function for tests to create an in-memory database with a /// specific trie prune mode. + /// + /// Note that most of the time we _do_ want to use a pool size of 1. We're + /// using shared cache mode with our in-memory DB to allow multiple + /// connections from within the same process. This means that in + /// contrast to a file-based DB we immediately get locking errors in + /// case of concurrent writes -- a pool size of one avoids this. pub fn in_memory_with_trie_pruning(trie_prune_mode: TriePruneMode) -> anyhow::Result { + Self::in_memory_with_trie_pruning_and_pool_size( + trie_prune_mode, + NonZeroU32::new(1).unwrap(), + ) + } + + /// Convenience function for tests to create an in-memory database with a + /// specific trie prune mode. + pub fn in_memory_with_trie_pruning_and_pool_size( + trie_prune_mode: TriePruneMode, + pool_size: NonZeroU32, + ) -> anyhow::Result { // Create a unique database name so that they are not shared between // concurrent tests. i.e. Make every in-mem Storage unique. static COUNT: std::sync::Mutex = std::sync::Mutex::new(0); @@ -224,7 +242,7 @@ impl StorageBuilder { } storage.trie_prune_mode = trie_prune_mode; - storage.create_pool(NonZeroU32::new(5).unwrap()) + storage.create_pool(pool_size) } /// Performs the database schema migration and returns a [storage From ae7e882a56fbeff99db7a580f4a63c2d941c357b Mon Sep 17 00:00:00 2001 From: Vaclav Barta Date: Tue, 19 Nov 2024 10:52:57 +0100 Subject: [PATCH 269/282] removed lock error handling from Cache --- crates/storage/src/bloom.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/storage/src/bloom.rs b/crates/storage/src/bloom.rs index 6b238f5852..d272d64a41 100644 --- a/crates/storage/src/bloom.rs +++ b/crates/storage/src/bloom.rs @@ -392,7 +392,7 @@ impl Cache { } fn locked_cache(&self) -> MutexGuard<'_, SizedCache> { - self.0.lock().unwrap_or_else(|e| e.into_inner()) + self.0.lock().unwrap() } pub fn get( From 039fbd2b059c7dd1e584e4164d7b143dfe71e169 Mon Sep 17 00:00:00 2001 From: Vaclav Barta Date: Tue, 19 Nov 2024 10:53:38 +0100 Subject: [PATCH 270/282] removed lock error handling from Transaction --- crates/storage/src/connection/event.rs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/crates/storage/src/connection/event.rs b/crates/storage/src/connection/event.rs index f28a84e813..ccdaa848a1 100644 --- a/crates/storage/src/connection/event.rs +++ b/crates/storage/src/connection/event.rs @@ -89,13 +89,7 @@ impl Transaction<'_> { ", )?; - let mut running_aggregate = match self.running_aggregate.lock() { - Ok(guard) => guard, - Err(poisoned) => { - tracing::error!("Poisoned lock in upsert_block_events_aggregate"); - poisoned.into_inner() - } - }; + let mut running_aggregate = self.running_aggregate.lock().unwrap(); let mut bloom = BloomFilter::new(); for event in events { From f8c892a4c0e0c4bf8550f52b5092482f414d07b7 Mon Sep 17 00:00:00 2001 From: sistemd Date: Tue, 19 Nov 2024 11:33:55 +0100 Subject: [PATCH 271/282] purge aggregate bloom filter --- crates/pathfinder/Cargo.toml | 1 + crates/pathfinder/src/state/sync.rs | 5 ++ crates/pathfinder/src/sync/checkpoint.rs | 5 ++ crates/rpc/Cargo.toml | 2 - crates/storage/Cargo.toml | 2 - crates/storage/src/connection.rs | 2 +- crates/storage/src/connection/block.rs | 9 ++ crates/storage/src/connection/event.rs | 105 +++++++++++++++++++++++ crates/storage/src/lib.rs | 96 +-------------------- 9 files changed, 127 insertions(+), 100 deletions(-) diff --git a/crates/pathfinder/Cargo.toml b/crates/pathfinder/Cargo.toml index a554719537..86ee8836a8 100644 --- a/crates/pathfinder/Cargo.toml +++ b/crates/pathfinder/Cargo.toml @@ -14,6 +14,7 @@ path = "src/lib.rs" [features] tokio-console = ["console-subscriber", "tokio/tracing"] p2p = [] +aggregate_bloom = [] [dependencies] anyhow = { workspace = true } diff --git a/crates/pathfinder/src/state/sync.rs b/crates/pathfinder/src/state/sync.rs index 378d1cd6e4..c93b25e278 100644 --- a/crates/pathfinder/src/state/sync.rs +++ b/crates/pathfinder/src/state/sync.rs @@ -1082,6 +1082,11 @@ async fn l2_reorg( head -= 1; } + #[cfg(feature = "aggregate_bloom")] + transaction + .reconstruct_running_aggregate() + .context("Reconstructing running aggregate bloom")?; + // Track combined L1 and L2 state. let l1_l2_head = transaction.l1_l2_pointer().context("Query L1-L2 head")?; if let Some(l1_l2_head) = l1_l2_head { diff --git a/crates/pathfinder/src/sync/checkpoint.rs b/crates/pathfinder/src/sync/checkpoint.rs index 14179b7e7c..a437b9fb27 100644 --- a/crates/pathfinder/src/sync/checkpoint.rs +++ b/crates/pathfinder/src/sync/checkpoint.rs @@ -665,6 +665,11 @@ async fn rollback_to_anchor( head -= 1; } + #[cfg(feature = "aggregate_bloom")] + transaction + .reconstruct_running_aggregate() + .context("Reconstructing running aggregate bloom")?; + Ok(()) }) .await diff --git a/crates/rpc/Cargo.toml b/crates/rpc/Cargo.toml index c52d643acf..f7c88f0902 100644 --- a/crates/rpc/Cargo.toml +++ b/crates/rpc/Cargo.toml @@ -10,8 +10,6 @@ rust-version = { workspace = true } [features] aggregate_bloom = [] -default = [] - [dependencies] anyhow = { workspace = true } async-trait = { workspace = true } diff --git a/crates/storage/Cargo.toml b/crates/storage/Cargo.toml index 1f770f13cd..fcd2b3190e 100644 --- a/crates/storage/Cargo.toml +++ b/crates/storage/Cargo.toml @@ -10,8 +10,6 @@ rust-version = { workspace = true } [features] aggregate_bloom = [] -default = [] - [dependencies] anyhow = { workspace = true } base64 = { workspace = true } diff --git a/crates/storage/src/connection.rs b/crates/storage/src/connection.rs index 55defedb46..46ead118f0 100644 --- a/crates/storage/src/connection.rs +++ b/crates/storage/src/connection.rs @@ -5,7 +5,7 @@ use std::sync::Mutex; mod block; mod class; mod ethereum; -mod event; +pub(crate) mod event; mod reference; mod reorg_counter; mod signature; diff --git a/crates/storage/src/connection/block.rs b/crates/storage/src/connection/block.rs index c424bdd6c9..5a4f64763d 100644 --- a/crates/storage/src/connection/block.rs +++ b/crates/storage/src/connection/block.rs @@ -116,6 +116,15 @@ impl Transaction<'_> { /// /// This includes block header, block body and state update information. pub fn purge_block(&self, block: BlockNumber) -> anyhow::Result<()> { + self.inner() + .execute( + r" + DELETE FROM starknet_events_filters_aggregate + WHERE from_block <= :block AND to_block >= :block + ", + named_params![":block": &block], + ) + .context("Deleting aggregate bloom filter")?; self.inner() .execute( "DELETE FROM starknet_events_filters WHERE block_number = ?", diff --git a/crates/storage/src/connection/event.rs b/crates/storage/src/connection/event.rs index f28a84e813..66ebf636ac 100644 --- a/crates/storage/src/connection/event.rs +++ b/crates/storage/src/connection/event.rs @@ -69,12 +69,28 @@ pub struct PageOfEvents { impl Transaction<'_> { #[cfg(feature = "aggregate_bloom")] + pub fn reconstruct_running_aggregate(&self) -> anyhow::Result<()> { + let aggregate = reconstruct_running_aggregate(self.inner())?; + let mut running_aggregate = match self.running_aggregate.lock() { + Ok(guard) => guard, + Err(poisoned) => { + tracing::error!("Poisoned lock in reconstruct_running_aggregate"); + poisoned.into_inner() + } + }; + + *running_aggregate = aggregate; + + Ok(()) + } + /// Upsert the [aggregate event bloom filter](AggregateBloom) for the given /// block number. This function operates under the assumption that /// blocks are _never_ skipped so even if there are no events for a /// block, this function should still be called with an empty iterator. /// When testing it is fine to skip blocks, as long as the block at the end /// of the current range is not skipped. + #[cfg(feature = "aggregate_bloom")] pub(super) fn upsert_block_events_aggregate<'a>( &self, block_number: BlockNumber, @@ -619,6 +635,95 @@ impl Transaction<'_> { } } +/// Reconstruct the [aggregate](crate::bloom::AggregateBloom) for the range of +/// blocks between the last stored `to_block` in the aggregate Bloom filter +/// table and the last overall block in the database. This is needed because the +/// aggregate Bloom filter for each [block +/// range](crate::bloom::AggregateBloom::BLOCK_RANGE_LEN) is stored once the +/// range is complete, before that it is kept in memory and can be lost upon +/// shutdown. +#[cfg(feature = "aggregate_bloom")] +pub fn reconstruct_running_aggregate( + tx: &rusqlite::Transaction<'_>, +) -> anyhow::Result { + use super::transaction; + + let mut last_to_block_stmt = tx.prepare( + r" + SELECT to_block + FROM starknet_events_filters_aggregate + ORDER BY from_block DESC LIMIT 1 + ", + )?; + let mut events_to_reconstruct_stmt = tx.prepare( + r" + SELECT events + FROM transactions + WHERE block_number >= :first_running_aggregate_block + ", + )?; + + let last_to_block = last_to_block_stmt + .query_row([], |row| row.get::<_, u64>(0)) + .optional() + .context("Querying last stored aggregate to_block")?; + + let first_running_aggregate_block = match last_to_block { + Some(last_to_block) => BlockNumber::new_or_panic(last_to_block + 1), + // Aggregate Bloom filter table is empty -> reconstruct running aggregate + // from the genesis block. + None => BlockNumber::GENESIS, + }; + + let events_to_reconstruct: Vec>>> = events_to_reconstruct_stmt + .query_and_then( + named_params![":first_running_aggregate_block": &first_running_aggregate_block], + |row| { + let events: Option = row + .get_optional_blob(0)? + .map(|events_blob| -> anyhow::Result<_> { + let events = transaction::compression::decompress_events(events_blob) + .context("Decompressing events")?; + let events: transaction::dto::EventsForBlock = + bincode::serde::decode_from_slice(&events, bincode::config::standard()) + .context("Deserializing events")? + .0; + + Ok(events) + }) + .transpose()?; + + Ok(events.map(|events| { + events + .events() + .into_iter() + .map(|e| e.into_iter().map(Into::into).collect()) + .collect() + })) + }, + ) + .context("Querying events to reconstruct")? + .collect::>()?; + + let mut running_aggregate = AggregateBloom::new(first_running_aggregate_block); + + for (block, events_for_block) in events_to_reconstruct.iter().enumerate() { + if let Some(events) = events_for_block { + let block_number = first_running_aggregate_block + block as u64; + + let mut bloom = BloomFilter::new(); + for event in events.iter().flatten() { + bloom.set_keys(&event.keys); + bloom.set_address(&event.from_address); + } + + running_aggregate.add_bloom(&bloom, block_number); + } + } + + Ok(running_aggregate) +} + fn continuation_token( events: &[EmittedEvent], previous_token: ContinuationToken, diff --git a/crates/storage/src/lib.rs b/crates/storage/src/lib.rs index 47bd04ac77..c0929622e5 100644 --- a/crates/storage/src/lib.rs +++ b/crates/storage/src/lib.rs @@ -272,7 +272,7 @@ impl StorageBuilder { } #[cfg(feature = "aggregate_bloom")] - let running_aggregate = reconstruct_running_aggregate(&mut connection) + let running_aggregate = event::reconstruct_running_aggregate(&connection.transaction()?) .context("Reconstructing running aggregate bloom filter")?; connection @@ -514,100 +514,6 @@ fn schema_version(connection: &rusqlite::Connection) -> anyhow::Result { Ok(version) } -/// Reconstruct the [aggregate](bloom::AggregateBloom) for the range of blocks -/// between the last stored to_block in the aggregate Bloom filter table and the -/// last overall block in the database. This is needed because the aggregate -/// Bloom filter for each [block range](bloom::AggregateBloom::BLOCK_RANGE_LEN) -/// is stored once the range is complete, before that it is kept in memory and -/// can be lost upon shutdown. -#[cfg(feature = "aggregate_bloom")] -fn reconstruct_running_aggregate( - connection: &mut rusqlite::Connection, -) -> anyhow::Result { - use bloom::BloomFilter; - use params::{named_params, RowExt}; - use pathfinder_common::event::Event; - use transaction; - - let tx = connection - .transaction() - .context("Creating database transaction")?; - let mut select_last_to_block_stmt = tx.prepare( - r" - SELECT to_block - FROM starknet_events_filters_aggregate - ORDER BY from_block DESC LIMIT 1 - ", - )?; - let mut events_to_reconstruct_stmt = tx.prepare( - r" - SELECT events - FROM transactions - WHERE block_number >= :first_running_aggregate_block - ", - )?; - - let last_to_block = select_last_to_block_stmt - .query_row([], |row| row.get::<_, u64>(0)) - .optional() - .context("Querying last stored aggregate to_block")?; - - let first_running_aggregate_block = match last_to_block { - Some(last_to_block) => BlockNumber::new_or_panic(last_to_block + 1), - // Aggregate Bloom filter table is empty -> reconstruct running aggregate - // from the genesis block. - None => BlockNumber::GENESIS, - }; - - let events_to_reconstruct: Vec>>> = events_to_reconstruct_stmt - .query_and_then( - named_params![":first_running_aggregate_block": &first_running_aggregate_block], - |row| { - let events: Option = row - .get_optional_blob(0)? - .map(|events_blob| -> anyhow::Result<_> { - let events = transaction::compression::decompress_events(events_blob) - .context("Decompressing events")?; - let events: transaction::dto::EventsForBlock = - bincode::serde::decode_from_slice(&events, bincode::config::standard()) - .context("Deserializing events")? - .0; - - Ok(events) - }) - .transpose()?; - - Ok(events.map(|events| { - events - .events() - .into_iter() - .map(|e| e.into_iter().map(Into::into).collect()) - .collect() - })) - }, - ) - .context("Querying events to reconstruct")? - .collect::>()?; - - let mut running_aggregate = AggregateBloom::new(first_running_aggregate_block); - - for (block, events_for_block) in events_to_reconstruct.iter().enumerate() { - if let Some(events) = events_for_block { - let block_number = first_running_aggregate_block + block as u64; - - let mut bloom = BloomFilter::new(); - for event in events.iter().flatten() { - bloom.set_keys(&event.keys); - bloom.set_address(&event.from_address); - } - - running_aggregate.add_bloom(&bloom, block_number); - } - } - - Ok(running_aggregate) -} - #[cfg(test)] mod tests { use super::*; From 8a4afce1c5625332c7e12fcf9bf0173b2a83b1a5 Mon Sep 17 00:00:00 2001 From: t00ts Date: Mon, 18 Nov 2024 09:01:54 +0100 Subject: [PATCH 272/282] feat(rpc): decouple `v06` from `v02` methods --- .../rpc/src/method/block_hash_and_number.rs | 1 + crates/rpc/src/method/block_number.rs | 1 + crates/rpc/src/method/chain_id.rs | 21 ++++++++++++++++ .../src/method/get_block_transaction_count.rs | 3 ++- crates/rpc/src/method/get_class.rs | 5 ++-- crates/rpc/src/method/get_class_at.rs | 5 ++-- crates/rpc/src/v02/method.rs | 1 - crates/rpc/src/v06.rs | 25 ++++++++----------- 8 files changed, 42 insertions(+), 20 deletions(-) diff --git a/crates/rpc/src/method/block_hash_and_number.rs b/crates/rpc/src/method/block_hash_and_number.rs index 3183d868a6..837f524c2f 100644 --- a/crates/rpc/src/method/block_hash_and_number.rs +++ b/crates/rpc/src/method/block_hash_and_number.rs @@ -10,6 +10,7 @@ pub struct Output { crate::error::generate_rpc_error_subset!(Error: NoBlocks); +/// Get the latest block hash and number. pub async fn block_hash_and_number(context: RpcContext) -> Result { let span = tracing::Span::current(); diff --git a/crates/rpc/src/method/block_number.rs b/crates/rpc/src/method/block_number.rs index e2a41eaa82..4fa84dc992 100644 --- a/crates/rpc/src/method/block_number.rs +++ b/crates/rpc/src/method/block_number.rs @@ -7,6 +7,7 @@ pub struct Output(pathfinder_common::BlockNumber); crate::error::generate_rpc_error_subset!(Error: NoBlocks); +/// Get the latest block number. pub async fn block_number(context: RpcContext) -> Result { let span = tracing::Span::current(); diff --git a/crates/rpc/src/method/chain_id.rs b/crates/rpc/src/method/chain_id.rs index c4643b2094..97a53d432e 100644 --- a/crates/rpc/src/method/chain_id.rs +++ b/crates/rpc/src/method/chain_id.rs @@ -4,6 +4,7 @@ crate::error::generate_rpc_error_subset!(Error); pub struct Output(pathfinder_common::ChainId); +/// Get the chain ID. pub async fn chain_id(context: RpcContext) -> Result { Ok(Output(context.chain_id)) } @@ -16,3 +17,23 @@ impl crate::dto::serialize::SerializeForVersion for Output { serializer.serialize(&crate::dto::ChainId(&self.0)) } } + +#[cfg(test)] +mod tests { + use pathfinder_common::ChainId; + use pathfinder_crypto::Felt; + + #[tokio::test] + async fn encoding() { + let value = "some_chain_id"; + let chain_id = Felt::from_be_slice(value.as_bytes()).unwrap(); + let chain_id = ChainId(chain_id); + + let encoded = serde_json::to_string(&chain_id).unwrap(); + + let expected = hex::encode(value); + let expected = format!(r#""0x{expected}""#); + + assert_eq!(encoded, expected); + } +} diff --git a/crates/rpc/src/method/get_block_transaction_count.rs b/crates/rpc/src/method/get_block_transaction_count.rs index 5cb5bcee7d..e1d5f7831b 100644 --- a/crates/rpc/src/method/get_block_transaction_count.rs +++ b/crates/rpc/src/method/get_block_transaction_count.rs @@ -5,7 +5,7 @@ use crate::context::RpcContext; #[derive(Debug, PartialEq, Eq)] pub struct Input { - block_id: BlockId, + block_id: pathfinder_common::BlockId, } impl crate::dto::DeserializeForVersion for Input { @@ -23,6 +23,7 @@ crate::error::generate_rpc_error_subset!(Error: BlockNotFound); #[derive(Debug)] pub struct Output(u64); +/// Get the number of transactions in a block. pub async fn get_block_transaction_count( context: RpcContext, input: Input, diff --git a/crates/rpc/src/method/get_class.rs b/crates/rpc/src/method/get_class.rs index b944936251..145c21a41c 100644 --- a/crates/rpc/src/method/get_class.rs +++ b/crates/rpc/src/method/get_class.rs @@ -10,8 +10,8 @@ crate::error::generate_rpc_error_subset!(Error: BlockNotFound, ClassHashNotFound #[derive(Debug, PartialEq, Eq)] pub struct Input { - block_id: BlockId, - class_hash: ClassHash, + block_id: pathfinder_common::BlockId, + class_hash: pathfinder_common::ClassHash, } impl crate::dto::DeserializeForVersion for Input { @@ -40,6 +40,7 @@ impl From for Output { } } +/// Get a contract class. pub async fn get_class(context: RpcContext, input: Input) -> Result { let span = tracing::Span::current(); let jh = tokio::task::spawn_blocking(move || -> Result { diff --git a/crates/rpc/src/method/get_class_at.rs b/crates/rpc/src/method/get_class_at.rs index b082806933..9dbe05b340 100644 --- a/crates/rpc/src/method/get_class_at.rs +++ b/crates/rpc/src/method/get_class_at.rs @@ -10,8 +10,8 @@ crate::error::generate_rpc_error_subset!(Error: BlockNotFound, ContractNotFound) #[derive(Debug, PartialEq, Eq)] pub struct Input { - block_id: BlockId, - contract_address: ContractAddress, + block_id: pathfinder_common::BlockId, + contract_address: pathfinder_common::ContractAddress, } impl crate::dto::DeserializeForVersion for Input { @@ -54,6 +54,7 @@ impl SerializeForVersion for Output { } } +/// Get a contract class. pub async fn get_class_at(context: RpcContext, input: Input) -> Result { let span = tracing::Span::current(); diff --git a/crates/rpc/src/v02/method.rs b/crates/rpc/src/v02/method.rs index b0a87941d1..109134a897 100644 --- a/crates/rpc/src/v02/method.rs +++ b/crates/rpc/src/v02/method.rs @@ -9,7 +9,6 @@ mod get_storage_at; pub(crate) mod get_transaction_by_block_id_and_index; pub(crate) mod get_transaction_by_hash; -pub(crate) use block_hash_and_number::{block_hash_and_number, block_number}; pub(crate) use chain_id::chain_id; pub(crate) use get_block_transaction_count::get_block_transaction_count; pub(crate) use get_class::get_class; diff --git a/crates/rpc/src/v06.rs b/crates/rpc/src/v06.rs index 14c77bcd92..f043acde32 100644 --- a/crates/rpc/src/v06.rs +++ b/crates/rpc/src/v06.rs @@ -3,24 +3,21 @@ use crate::jsonrpc::{RpcRouter, RpcRouterBuilder}; pub(crate) mod method; pub(crate) mod types; -use crate::v02::method as v02_method; -use crate::v03::method as v03_method; - #[rustfmt::skip] pub fn register_routes() -> RpcRouterBuilder { RpcRouter::builder(crate::RpcVersion::V06) - .register("starknet_blockHashAndNumber" , v02_method::block_hash_and_number) - .register("starknet_blockNumber" , v02_method::block_number) - .register("starknet_chainId" , v02_method::chain_id) - .register("starknet_getBlockTransactionCount" , v02_method::get_block_transaction_count) - .register("starknet_getClass" , v02_method::get_class) - .register("starknet_getClassAt" , v02_method::get_class_at) - .register("starknet_getClassHashAt" , v02_method::get_class_hash_at) - .register("starknet_getNonce" , v02_method::get_nonce) - .register("starknet_getStorageAt" , v02_method::get_storage_at) + .register("starknet_blockHashAndNumber" , crate::method::block_hash_and_number) + .register("starknet_blockNumber" , crate::method::block_number) + .register("starknet_chainId" , crate::method::chain_id) + .register("starknet_getBlockTransactionCount" , crate::method::get_block_transaction_count) + .register("starknet_getClass" , crate::method::get_class) + .register("starknet_getClassAt" , crate::method::get_class_at) + .register("starknet_getClassHashAt" , crate::method::get_class_hash_at) + .register("starknet_getNonce" , crate::method::get_nonce) + .register("starknet_getStorageAt" , crate::method::get_storage_at) - .register("starknet_getEvents" , v03_method::get_events) - .register("starknet_getStateUpdate" , v03_method::get_state_update) + .register("starknet_getEvents" , crate::method::get_events) + .register("starknet_getStateUpdate" , crate::method::get_state_update) .register("starknet_syncing" , method::syncing) .register("starknet_getTransactionStatus" , method::get_transaction_status) From c7e21df8ec0e5daf7bd1873a507ae2b945c87cae Mon Sep 17 00:00:00 2001 From: t00ts Date: Mon, 18 Nov 2024 12:35:18 +0100 Subject: [PATCH 273/282] feat(rpc): remove `v02` and `v03` --- crates/pathfinder/src/monitoring.rs | 4 +- crates/pathfinder/src/state/sync.rs | 2 +- crates/rpc/src/dto/class.rs | 2 +- crates/rpc/src/dto/primitives.rs | 6 +- crates/rpc/src/executor.rs | 6 +- crates/rpc/src/jsonrpc/router/subscription.rs | 2 +- crates/rpc/src/lib.rs | 7 +- .../rpc/src/method/add_declare_transaction.rs | 10 +- .../method/add_deploy_account_transaction.rs | 6 +- .../rpc/src/method/add_invoke_transaction.rs | 10 +- crates/rpc/src/method/estimate_fee.rs | 11 +- crates/rpc/src/method/get_class.rs | 2 +- crates/rpc/src/method/get_class_at.rs | 2 +- crates/rpc/src/method/get_state_update.rs | 356 ++++++ crates/rpc/src/method/get_storage_at.rs | 2 +- .../rpc/src/method/simulate_transactions.rs | 10 +- crates/rpc/src/method/subscribe_events.rs | 2 +- crates/rpc/src/method/subscribe_new_heads.rs | 2 +- .../method/subscribe_pending_transactions.rs | 2 +- .../method/subscribe_transaction_status.rs | 2 +- crates/rpc/src/method/syncing.rs | 4 +- crates/rpc/src/{v02 => }/types.rs | 7 +- crates/rpc/src/{v02 => }/types/class.rs | 6 +- crates/rpc/src/{v02 => }/types/syncing.rs | 0 crates/rpc/src/v02/method.rs | 18 - .../src/v02/method/block_hash_and_number.rs | 63 - crates/rpc/src/v02/method/chain_id.rs | 34 - .../v02/method/get_block_transaction_count.rs | 166 --- crates/rpc/src/v02/method/get_class.rs | 387 ------ crates/rpc/src/v02/method/get_class_at.rs | 343 ------ .../rpc/src/v02/method/get_class_hash_at.rs | 241 ---- crates/rpc/src/v02/method/get_nonce.rs | 248 ---- crates/rpc/src/v02/method/get_storage_at.rs | 341 ------ .../get_transaction_by_block_id_and_index.rs | 135 --- .../src/v02/method/get_transaction_by_hash.rs | 96 -- crates/rpc/src/v03.rs | 1 - crates/rpc/src/v03/method.rs | 5 - crates/rpc/src/v03/method/get_events.rs | 1060 ----------------- crates/rpc/src/v03/method/get_state_update.rs | 598 ---------- crates/rpc/src/v06.rs | 2 +- crates/rpc/src/v06/method.rs | 2 - .../src/v06/method/add_declare_transaction.rs | 10 +- .../method/add_deploy_account_transaction.rs | 6 +- .../src/v06/method/add_invoke_transaction.rs | 10 +- crates/rpc/src/v06/method/estimate_fee.rs | 10 +- .../v06/method/get_block_with_tx_hashes.rs | 4 +- .../rpc/src/v06/method/get_block_with_txs.rs | 4 +- .../get_transaction_by_block_id_and_index.rs | 8 +- .../src/v06/method/get_transaction_by_hash.rs | 15 - .../src/v06/method/get_transaction_receipt.rs | 2 +- .../src/v06/method/simulate_transactions.rs | 25 +- crates/rpc/src/v06/method/syncing.rs | 6 +- crates/rpc/src/v07/dto/receipt.rs | 2 +- 53 files changed, 451 insertions(+), 3854 deletions(-) rename crates/rpc/src/{v02 => }/types.rs (99%) rename crates/rpc/src/{v02 => }/types/class.rs (99%) rename crates/rpc/src/{v02 => }/types/syncing.rs (100%) delete mode 100644 crates/rpc/src/v02/method.rs delete mode 100644 crates/rpc/src/v02/method/block_hash_and_number.rs delete mode 100644 crates/rpc/src/v02/method/chain_id.rs delete mode 100644 crates/rpc/src/v02/method/get_block_transaction_count.rs delete mode 100644 crates/rpc/src/v02/method/get_class.rs delete mode 100644 crates/rpc/src/v02/method/get_class_at.rs delete mode 100644 crates/rpc/src/v02/method/get_class_hash_at.rs delete mode 100644 crates/rpc/src/v02/method/get_nonce.rs delete mode 100644 crates/rpc/src/v02/method/get_storage_at.rs delete mode 100644 crates/rpc/src/v02/method/get_transaction_by_block_id_and_index.rs delete mode 100644 crates/rpc/src/v02/method/get_transaction_by_hash.rs delete mode 100644 crates/rpc/src/v03.rs delete mode 100644 crates/rpc/src/v03/method.rs delete mode 100644 crates/rpc/src/v03/method/get_events.rs delete mode 100644 crates/rpc/src/v03/method/get_state_update.rs delete mode 100644 crates/rpc/src/v06/method/get_transaction_by_hash.rs diff --git a/crates/pathfinder/src/monitoring.rs b/crates/pathfinder/src/monitoring.rs index 364e278b0d..d3ec17c742 100644 --- a/crates/pathfinder/src/monitoring.rs +++ b/crates/pathfinder/src/monitoring.rs @@ -3,7 +3,7 @@ use std::sync::atomic::AtomicBool; use std::sync::Arc; use metrics_exporter_prometheus::PrometheusHandle; -use pathfinder_rpc::v02::types::syncing::Syncing; +use pathfinder_rpc::types::syncing::Syncing; use pathfinder_rpc::SyncState; #[derive(Clone)] @@ -86,7 +86,7 @@ mod tests { use metrics_exporter_prometheus::PrometheusBuilder; use pathfinder_common::BlockNumber; - use pathfinder_rpc::v02::types::syncing::{NumberedBlock, Status, Syncing}; + use pathfinder_rpc::types::syncing::{NumberedBlock, Status, Syncing}; use pathfinder_rpc::SyncState; use tokio::sync::RwLock; diff --git a/crates/pathfinder/src/state/sync.rs b/crates/pathfinder/src/state/sync.rs index c93b25e278..4c959886f2 100644 --- a/crates/pathfinder/src/state/sync.rs +++ b/crates/pathfinder/src/state/sync.rs @@ -22,7 +22,7 @@ use pathfinder_crypto::Felt; use pathfinder_ethereum::{EthereumApi, EthereumStateUpdate}; use pathfinder_merkle_tree::contract_state::update_contract_state; use pathfinder_merkle_tree::{ClassCommitmentTree, StorageCommitmentTree}; -use pathfinder_rpc::v02::types::syncing::{self, NumberedBlock, Syncing}; +use pathfinder_rpc::types::syncing::{self, NumberedBlock, Syncing}; use pathfinder_rpc::{Notifications, PendingData, Reorg, SyncState, TopicBroadcasters}; use pathfinder_storage::{Connection, Storage, Transaction, TransactionBehavior}; use primitive_types::H160; diff --git a/crates/rpc/src/dto/class.rs b/crates/rpc/src/dto/class.rs index 048a2ac73c..ccf8d7a1ff 100644 --- a/crates/rpc/src/dto/class.rs +++ b/crates/rpc/src/dto/class.rs @@ -3,7 +3,7 @@ use serde_with::ser::SerializeAsWrap; use super::U64Hex; use crate::dto::serialize::SerializeForVersion; use crate::dto::{serialize, Felt}; -use crate::v02::types; +use crate::types; pub struct DeprecatedContractClass<'a>(pub &'a types::CairoContractClass); pub struct ContractClass<'a>(pub &'a types::SierraContractClass); diff --git a/crates/rpc/src/dto/primitives.rs b/crates/rpc/src/dto/primitives.rs index 5b29762cec..ad1daf59de 100644 --- a/crates/rpc/src/dto/primitives.rs +++ b/crates/rpc/src/dto/primitives.rs @@ -6,7 +6,7 @@ use super::serialize::SerializeForVersion; use super::{DeserializeForVersion, Value}; use crate::dto::serialize::{self, Serializer}; -pub struct SyncStatus<'a>(pub &'a crate::v02::types::syncing::Status); +pub struct SyncStatus<'a>(pub &'a crate::types::syncing::Status); pub struct Felt<'a>(pub &'a pathfinder_crypto::Felt); pub struct BlockHash<'a>(pub &'a pathfinder_common::BlockHash); @@ -309,8 +309,8 @@ mod tests { #[test] fn sync_status() { - use crate::v02::types::syncing::NumberedBlock; - let status = crate::v02::types::syncing::Status { + use crate::types::syncing::NumberedBlock; + let status = crate::types::syncing::Status { starting: NumberedBlock { number: pathfinder_common::BlockNumber::GENESIS, hash: block_hash!("0x123"), diff --git a/crates/rpc/src/executor.rs b/crates/rpc/src/executor.rs index fb0d31e443..4c9699738b 100644 --- a/crates/rpc/src/executor.rs +++ b/crates/rpc/src/executor.rs @@ -4,12 +4,12 @@ use pathfinder_common::{ChainId, StarknetVersion}; use pathfinder_executor::{ClassInfo, IntoStarkFelt}; use starknet_api::core::PatriciaKey; -use crate::v02::types::request::{ +use crate::types::request::{ BroadcastedDeployAccountTransaction, BroadcastedInvokeTransaction, BroadcastedTransaction, }; -use crate::v02::types::SierraContractClass; +use crate::types::SierraContractClass; pub enum ExecutionStateError { BlockNotFound, @@ -29,7 +29,7 @@ pub(crate) fn map_broadcasted_transaction( transaction: &BroadcastedTransaction, chain_id: ChainId, ) -> anyhow::Result { - use crate::v02::types::request::BroadcastedDeclareTransaction; + use crate::types::request::BroadcastedDeclareTransaction; let class_info = match &transaction { BroadcastedTransaction::Declare(BroadcastedDeclareTransaction::V0(tx)) => { diff --git a/crates/rpc/src/jsonrpc/router/subscription.rs b/crates/rpc/src/jsonrpc/router/subscription.rs index 7b4703c99c..4334778a45 100644 --- a/crates/rpc/src/jsonrpc/router/subscription.rs +++ b/crates/rpc/src/jsonrpc/router/subscription.rs @@ -818,7 +818,7 @@ mod tests { SubscriptionMessage, }; use crate::pending::PendingWatcher; - use crate::v02::types::syncing::Syncing; + use crate::types::syncing::Syncing; use crate::{Notifications, SyncState}; #[tokio::test] diff --git a/crates/rpc/src/lib.rs b/crates/rpc/src/lib.rs index b1da1705c6..5df688323a 100644 --- a/crates/rpc/src/lib.rs +++ b/crates/rpc/src/lib.rs @@ -11,8 +11,7 @@ mod pathfinder; mod pending; #[cfg(test)] mod test_setup; -pub mod v02; -pub mod v03; +pub mod types; pub mod v06; pub mod v07; pub mod v08; @@ -39,7 +38,7 @@ use tower_http::ServiceBuilderExt; use crate::jsonrpc::rpc_handler; use crate::jsonrpc::websocket::websocket_handler; pub use crate::jsonrpc::websocket::{BlockHeader, TopicBroadcasters}; -use crate::v02::types::syncing::Syncing; +use crate::types::syncing::Syncing; const DEFAULT_MAX_CONNECTIONS: usize = 1024; @@ -832,7 +831,7 @@ mod tests { #[test] fn roundtrip_syncing() { - use crate::v02::types::syncing::{NumberedBlock, Status, Syncing}; + use crate::types::syncing::{NumberedBlock, Status, Syncing}; let examples = [ (line!(), "false", Syncing::False(false)), diff --git a/crates/rpc/src/method/add_declare_transaction.rs b/crates/rpc/src/method/add_declare_transaction.rs index 5bdd0a765c..449814a77a 100644 --- a/crates/rpc/src/method/add_declare_transaction.rs +++ b/crates/rpc/src/method/add_declare_transaction.rs @@ -8,7 +8,7 @@ use starknet_gateway_types::request::add_transaction::{ }; use crate::context::RpcContext; -use crate::v02::types::request::BroadcastedDeclareTransaction; +use crate::types::request::BroadcastedDeclareTransaction; #[derive(Debug)] pub enum AddDeclareTransactionError { @@ -319,13 +319,13 @@ mod tests { }; use super::*; - use crate::v02::types::request::{ + use crate::types::request::{ BroadcastedDeclareTransaction, BroadcastedDeclareTransactionV1, BroadcastedDeclareTransactionV2, BroadcastedDeclareTransactionV3, }; - use crate::v02::types::{ + use crate::types::{ CairoContractClass, ContractClass, DataAvailabilityMode, @@ -383,7 +383,7 @@ mod tests { use super::super::*; use crate::dto::serialize::SerializeForVersion; use crate::dto::{serialize, DeserializeForVersion}; - use crate::v02::types::request::BroadcastedDeclareTransactionV1; + use crate::types::request::BroadcastedDeclareTransactionV1; use crate::RpcVersion; fn test_declare_txn() -> Transaction { @@ -480,7 +480,7 @@ mod tests { use super::super::*; use crate::dto::DeserializeForVersion; - use crate::v02::types::request::BroadcastedDeclareTransactionV2; + use crate::types::request::BroadcastedDeclareTransactionV2; use crate::RpcVersion; fn test_declare_txn() -> Transaction { diff --git a/crates/rpc/src/method/add_deploy_account_transaction.rs b/crates/rpc/src/method/add_deploy_account_transaction.rs index a0a7786aca..d7e794b40a 100644 --- a/crates/rpc/src/method/add_deploy_account_transaction.rs +++ b/crates/rpc/src/method/add_deploy_account_transaction.rs @@ -4,7 +4,7 @@ use starknet_gateway_client::GatewayApi; use starknet_gateway_types::error::{KnownStarknetErrorCode, SequencerError}; use crate::context::RpcContext; -use crate::v02::types::request::{ +use crate::types::request::{ BroadcastedDeployAccountTransaction, BroadcastedDeployAccountTransactionV1, }; @@ -246,8 +246,8 @@ mod tests { use super::*; use crate::dto::serialize::{self, SerializeForVersion}; - use crate::v02::types::request::BroadcastedDeployAccountTransactionV3; - use crate::v02::types::{DataAvailabilityMode, ResourceBound, ResourceBounds}; + use crate::types::request::BroadcastedDeployAccountTransactionV3; + use crate::types::{DataAvailabilityMode, ResourceBound, ResourceBounds}; const INPUT_JSON: &str = r#"{ "max_fee": "0xbf391377813", diff --git a/crates/rpc/src/method/add_invoke_transaction.rs b/crates/rpc/src/method/add_invoke_transaction.rs index cb6e8c234a..562a9ccbcf 100644 --- a/crates/rpc/src/method/add_invoke_transaction.rs +++ b/crates/rpc/src/method/add_invoke_transaction.rs @@ -4,7 +4,7 @@ use starknet_gateway_client::GatewayApi; use starknet_gateway_types::error::SequencerError; use crate::context::RpcContext; -use crate::v02::types::request::BroadcastedInvokeTransaction; +use crate::types::request::BroadcastedInvokeTransaction; #[derive(Debug, PartialEq, Eq)] pub enum Transaction { @@ -216,8 +216,8 @@ mod tests { use pathfinder_common::{ResourceAmount, ResourcePricePerUnit, Tip, TransactionVersion}; use super::*; - use crate::v02::types::request::BroadcastedInvokeTransactionV1; - use crate::v02::types::{DataAvailabilityMode, ResourceBound, ResourceBounds}; + use crate::types::request::BroadcastedInvokeTransactionV1; + use crate::types::{DataAvailabilityMode, ResourceBound, ResourceBounds}; fn test_invoke_txn() -> Transaction { Transaction::Invoke(BroadcastedInvokeTransaction::V1( @@ -360,7 +360,7 @@ mod tests { #[tokio::test] #[ignore = "gateway 429"] async fn duplicate_transaction() { - use crate::v02::types::request::BroadcastedInvokeTransactionV1; + use crate::types::request::BroadcastedInvokeTransactionV1; let context = RpcContext::for_tests(); let input = BroadcastedInvokeTransactionV1 { @@ -401,7 +401,7 @@ mod tests { #[ignore = "gateway 429"] // https://external.integration.starknet.io/feeder_gateway/get_transaction?transactionHash=0x41906f1c314cca5f43170ea75d3b1904196a10101190d2b12a41cc61cfd17c async fn duplicate_v3_transaction() { - use crate::v02::types::request::BroadcastedInvokeTransactionV3; + use crate::types::request::BroadcastedInvokeTransactionV3; let context = RpcContext::for_tests_on(pathfinder_common::Chain::SepoliaIntegration); let input = BroadcastedInvokeTransactionV3 { diff --git a/crates/rpc/src/method/estimate_fee.rs b/crates/rpc/src/method/estimate_fee.rs index 5e1bce73b1..f94f90c862 100644 --- a/crates/rpc/src/method/estimate_fee.rs +++ b/crates/rpc/src/method/estimate_fee.rs @@ -5,7 +5,7 @@ use serde::de::Error; use crate::context::RpcContext; use crate::error::ApplicationError; -use crate::v02::types::request::BroadcastedTransaction; +use crate::types::request::BroadcastedTransaction; #[derive(Debug, PartialEq, Eq)] pub struct Input { @@ -193,7 +193,7 @@ mod tests { use pretty_assertions_sorted::assert_eq; use super::*; - use crate::v02::types::request::{ + use crate::types::request::{ BroadcastedDeclareTransaction, BroadcastedDeclareTransactionV2, BroadcastedInvokeTransaction, @@ -202,12 +202,7 @@ mod tests { BroadcastedInvokeTransactionV3, BroadcastedTransaction, }; - use crate::v02::types::{ - ContractClass, - DataAvailabilityMode, - ResourceBounds, - SierraContractClass, - }; + use crate::types::{ContractClass, DataAvailabilityMode, ResourceBounds, SierraContractClass}; fn declare_transaction(account_contract_address: ContractAddress) -> BroadcastedTransaction { let sierra_definition = include_bytes!("../../fixtures/contracts/storage_access.json"); diff --git a/crates/rpc/src/method/get_class.rs b/crates/rpc/src/method/get_class.rs index 145c21a41c..c1e5214ce5 100644 --- a/crates/rpc/src/method/get_class.rs +++ b/crates/rpc/src/method/get_class.rs @@ -4,7 +4,7 @@ use pathfinder_common::{BlockId, ClassHash}; use crate::context::RpcContext; use crate::dto; use crate::dto::serialize::SerializeForVersion; -use crate::v02::types::{CairoContractClass, ContractClass, SierraContractClass}; +use crate::types::{CairoContractClass, ContractClass, SierraContractClass}; crate::error::generate_rpc_error_subset!(Error: BlockNotFound, ClassHashNotFound); diff --git a/crates/rpc/src/method/get_class_at.rs b/crates/rpc/src/method/get_class_at.rs index 9dbe05b340..a259833675 100644 --- a/crates/rpc/src/method/get_class_at.rs +++ b/crates/rpc/src/method/get_class_at.rs @@ -4,7 +4,7 @@ use pathfinder_common::{BlockId, ContractAddress}; use crate::context::RpcContext; use crate::dto; use crate::dto::serialize::SerializeForVersion; -use crate::v02::types::{CairoContractClass, ContractClass, SierraContractClass}; +use crate::types::{CairoContractClass, ContractClass, SierraContractClass}; crate::error::generate_rpc_error_subset!(Error: BlockNotFound, ContractNotFound); diff --git a/crates/rpc/src/method/get_state_update.rs b/crates/rpc/src/method/get_state_update.rs index be5e9d307d..6a660df49d 100644 --- a/crates/rpc/src/method/get_state_update.rs +++ b/crates/rpc/src/method/get_state_update.rs @@ -78,6 +78,362 @@ pub async fn get_state_update(context: RpcContext, input: Input) -> Result, + /// None for `pending` + #[serde(default)] + #[serde_as(as = "Option")] + pub new_root: Option, + #[serde_as(as = "RpcFelt")] + pub old_root: StateCommitment, + pub state_diff: StateDiff, + } + + impl From for StateUpdate { + fn from(value: pathfinder_common::StateUpdate) -> Self { + let mut storage_diffs = Vec::new(); + let mut deployed_contracts = Vec::new(); + let mut replaced_classes = Vec::new(); + let mut nonces = Vec::new(); + + for (contract_address, update) in value.contract_updates { + if let Some(nonce) = update.nonce { + nonces.push(Nonce { + contract_address, + nonce, + }); + } + + match update.class { + Some(ContractClassUpdate::Deploy(class_hash)) => { + deployed_contracts.push(DeployedContract { + address: contract_address, + class_hash, + }) + } + Some(ContractClassUpdate::Replace(class_hash)) => { + replaced_classes.push(ReplacedClass { + contract_address, + class_hash, + }) + } + None => {} + } + + let storage_entries = update + .storage + .into_iter() + .map(|(key, value)| StorageEntry { key, value }) + .collect(); + + storage_diffs.push(StorageDiff { + address: contract_address, + storage_entries, + }); + } + + for (address, update) in value.system_contract_updates { + let storage_entries = update + .storage + .into_iter() + .map(|(key, value)| StorageEntry { key, value }) + .collect(); + + storage_diffs.push(StorageDiff { + address, + storage_entries, + }); + } + + let declared_classes = value + .declared_sierra_classes + .into_iter() + .map(|(class_hash, compiled_class_hash)| DeclaredSierraClass { + class_hash, + compiled_class_hash, + }) + .collect(); + + let deprecated_declared_classes = value.declared_cairo_classes.into_iter().collect(); + + let state_diff = StateDiff { + storage_diffs, + deprecated_declared_classes, + declared_classes, + deployed_contracts, + replaced_classes, + nonces, + }; + + let block_hash = match value.block_hash { + BlockHash::ZERO => None, + other => Some(other), + }; + + let new_root = match value.state_commitment { + StateCommitment::ZERO => None, + other => Some(other), + }; + + StateUpdate { + block_hash, + new_root, + old_root: value.parent_state_commitment, + state_diff, + } + } + } + + /// L2 state diff. + #[serde_with::serde_as] + #[derive(Clone, Debug, Serialize, PartialEq, Eq, Default)] + #[cfg_attr(test, derive(serde::Deserialize))] + #[serde(deny_unknown_fields)] + pub struct StateDiff { + pub storage_diffs: Vec, + #[serde_as(as = "Vec")] + pub deprecated_declared_classes: Vec, + pub declared_classes: Vec, + pub deployed_contracts: Vec, + pub replaced_classes: Vec, + pub nonces: Vec, + } + + impl From for StateDiff { + fn from(value: pathfinder_executor::types::StateDiff) -> Self { + Self { + storage_diffs: value + .storage_diffs + .into_iter() + .map(|(address, diff)| StorageDiff { + address, + storage_entries: diff.into_iter().map(Into::into).collect(), + }) + .collect(), + deprecated_declared_classes: value + .deprecated_declared_classes + .into_iter() + .collect(), + declared_classes: value.declared_classes.into_iter().map(Into::into).collect(), + deployed_contracts: value + .deployed_contracts + .into_iter() + .map(Into::into) + .collect(), + replaced_classes: value.replaced_classes.into_iter().map(Into::into).collect(), + nonces: value + .nonces + .into_iter() + .map(|(contract_address, nonce)| Nonce { + contract_address, + nonce, + }) + .collect(), + } + } + } + + /// L2 storage diff of a contract. + #[serde_with::serde_as] + #[derive(Clone, Debug, Serialize, PartialEq, Eq, PartialOrd, Ord)] + #[cfg_attr(test, derive(serde::Deserialize))] + #[serde(deny_unknown_fields)] + pub struct StorageDiff { + #[serde_as(as = "RpcFelt251")] + pub address: ContractAddress, + pub storage_entries: Vec, + } + + /// A key-value entry of a storage diff. + #[serde_with::serde_as] + #[derive(Clone, Debug, Serialize, PartialEq, Eq, PartialOrd, Ord)] + #[cfg_attr(test, derive(serde::Deserialize))] + #[serde(deny_unknown_fields)] + pub struct StorageEntry { + #[serde_as(as = "RpcFelt251")] + pub key: StorageAddress, + #[serde_as(as = "RpcFelt")] + pub value: StorageValue, + } + + impl From for StorageEntry { + fn from(d: starknet_gateway_types::reply::state_update::StorageDiff) -> Self { + Self { + key: d.key, + value: d.value, + } + } + } + + impl From for StorageEntry { + fn from(d: pathfinder_executor::types::StorageDiff) -> Self { + Self { + key: d.key, + value: d.value, + } + } + } + + /// L2 state diff declared Sierra class item. + #[serde_with::serde_as] + #[derive(Clone, Debug, Serialize, PartialEq, Eq)] + #[cfg_attr(test, derive(serde::Deserialize))] + #[serde(deny_unknown_fields)] + pub struct DeclaredSierraClass { + #[serde_as(as = "RpcFelt")] + pub class_hash: SierraHash, + #[serde_as(as = "RpcFelt")] + pub compiled_class_hash: CasmHash, + } + + impl From for DeclaredSierraClass { + fn from(d: pathfinder_executor::types::DeclaredSierraClass) -> Self { + Self { + class_hash: d.class_hash, + compiled_class_hash: d.compiled_class_hash, + } + } + } + + /// L2 state diff deployed contract item. + #[serde_with::serde_as] + #[derive(Clone, Debug, Serialize, PartialEq, Eq)] + #[cfg_attr(test, derive(serde::Deserialize))] + #[serde(deny_unknown_fields)] + pub struct DeployedContract { + #[serde_as(as = "RpcFelt251")] + pub address: ContractAddress, + #[serde_as(as = "RpcFelt")] + pub class_hash: ClassHash, + } + + impl From for DeployedContract { + fn from(d: pathfinder_executor::types::DeployedContract) -> Self { + Self { + address: d.address, + class_hash: d.class_hash, + } + } + } + + /// L2 state diff replaced class item. + #[serde_with::serde_as] + #[derive(Clone, Debug, Serialize, PartialEq, Eq)] + #[cfg_attr(test, derive(serde::Deserialize))] + #[serde(deny_unknown_fields)] + pub struct ReplacedClass { + #[serde_as(as = "RpcFelt251")] + pub contract_address: ContractAddress, + #[serde_as(as = "RpcFelt")] + pub class_hash: ClassHash, + } + + impl From for ReplacedClass { + fn from(d: pathfinder_executor::types::ReplacedClass) -> Self { + Self { + contract_address: d.contract_address, + class_hash: d.class_hash, + } + } + } + + /// L2 state diff nonce item. + #[serde_with::serde_as] + #[derive(Clone, Debug, Serialize, PartialEq, Eq)] + #[cfg_attr(test, derive(serde::Deserialize))] + #[serde(deny_unknown_fields)] + pub struct Nonce { + #[serde_as(as = "RpcFelt251")] + pub contract_address: ContractAddress, + #[serde_as(as = "RpcFelt")] + pub nonce: ContractNonce, + } + + #[cfg(test)] + mod tests { + use pathfinder_common::macro_prelude::*; + + use super::*; + + #[test] + fn receipt() { + let state_update = StateUpdate { + block_hash: Some(block_hash!("0xdeadbeef")), + new_root: Some(state_commitment!("0x1")), + old_root: state_commitment!("0x2"), + state_diff: StateDiff { + storage_diffs: vec![StorageDiff { + address: contract_address!("0xadc"), + storage_entries: vec![StorageEntry { + key: storage_address!("0xf0"), + value: storage_value!("0x55"), + }], + }], + deprecated_declared_classes: vec![class_hash!("0xcdef"), class_hash!("0xcdee")], + declared_classes: vec![DeclaredSierraClass { + class_hash: sierra_hash!("0xabcd"), + compiled_class_hash: casm_hash!("0xdcba"), + }], + deployed_contracts: vec![DeployedContract { + address: contract_address!("0xadd"), + class_hash: class_hash!("0xcdef"), + }], + replaced_classes: vec![ReplacedClass { + contract_address: contract_address!("0xcad"), + class_hash: class_hash!("0xdac"), + }], + nonces: vec![Nonce { + contract_address: contract_address!("0xca"), + nonce: contract_nonce!("0x404ce"), + }], + }, + }; + let data = vec![ + state_update.clone(), + StateUpdate { + block_hash: None, + ..state_update + }, + ]; + + let fixture = + include_str!("../../fixtures/0.50.0/state_update.json").replace([' ', '\n'], ""); + + pretty_assertions_sorted::assert_eq!(serde_json::to_string(&data).unwrap(), fixture); + pretty_assertions_sorted::assert_eq!( + serde_json::from_str::>(&fixture).unwrap(), + data + ); + } + } +} + #[cfg(test)] mod tests { use dto::DeserializeForVersion; diff --git a/crates/rpc/src/method/get_storage_at.rs b/crates/rpc/src/method/get_storage_at.rs index 3e6d364516..e8c214c43f 100644 --- a/crates/rpc/src/method/get_storage_at.rs +++ b/crates/rpc/src/method/get_storage_at.rs @@ -104,7 +104,7 @@ mod tests { /// # Important /// /// `BlockId` parsing is tested in - /// [`get_block`][crate::rpc::v02::method::get_block::tests::parsing] + /// [`get_block`][crate::rpc::method::get_block::tests::parsing] /// and is not repeated here. #[rstest::rstest] #[case::positional(json!(["1", "2", "latest"]))] diff --git a/crates/rpc/src/method/simulate_transactions.rs b/crates/rpc/src/method/simulate_transactions.rs index 59088bfae2..3ccee4645c 100644 --- a/crates/rpc/src/method/simulate_transactions.rs +++ b/crates/rpc/src/method/simulate_transactions.rs @@ -198,13 +198,13 @@ pub(crate) mod tests { use super::simulate_transactions; use crate::context::RpcContext; use crate::dto::serialize::{SerializeForVersion, Serializer}; - use crate::v02::types::request::{ + use crate::method::get_state_update::types::{DeployedContract, Nonce, StateDiff}; + use crate::types::request::{ BroadcastedDeclareTransaction, BroadcastedDeclareTransactionV1, BroadcastedTransaction, }; - use crate::v02::types::ContractClass; - use crate::v03::method::get_state_update::types::{DeployedContract, Nonce, StateDiff}; + use crate::types::ContractClass; use crate::v06::method::call::FunctionCall; use crate::v06::method::simulate_transactions::tests::setup_storage_with_starknet_version; use crate::v06::method::simulate_transactions::{dto, SimulateTransactionInput}; @@ -373,7 +373,7 @@ pub(crate) mod tests { const OVERALL_FEE: u64 = 15720; use dto::*; - use crate::v03::method::get_state_update::types::{StorageDiff, StorageEntry}; + use crate::method::get_state_update::types::{StorageDiff, StorageEntry}; pretty_assertions_sorted::assert_eq!( result.serialize(Serializer { @@ -515,7 +515,7 @@ pub(crate) mod tests { use super::dto::*; use super::*; - use crate::v03::method::get_state_update::types::{ + use crate::method::get_state_update::types::{ DeclaredSierraClass, StorageDiff, StorageEntry, diff --git a/crates/rpc/src/method/subscribe_events.rs b/crates/rpc/src/method/subscribe_events.rs index fbebafcbe6..ca31de9cc5 100644 --- a/crates/rpc/src/method/subscribe_events.rs +++ b/crates/rpc/src/method/subscribe_events.rs @@ -255,7 +255,7 @@ mod tests { use crate::context::{RpcConfig, RpcContext}; use crate::jsonrpc::{handle_json_rpc_socket, RpcRouter, CATCH_UP_BATCH_SIZE}; use crate::pending::PendingWatcher; - use crate::v02::types::syncing::Syncing; + use crate::types::syncing::Syncing; use crate::{v08, Notifications, Reorg, SyncState}; #[tokio::test] diff --git a/crates/rpc/src/method/subscribe_new_heads.rs b/crates/rpc/src/method/subscribe_new_heads.rs index e7492f83d7..f949b2f0d5 100644 --- a/crates/rpc/src/method/subscribe_new_heads.rs +++ b/crates/rpc/src/method/subscribe_new_heads.rs @@ -179,7 +179,7 @@ mod tests { use crate::context::{RpcConfig, RpcContext}; use crate::jsonrpc::{handle_json_rpc_socket, RpcResponse, RpcRouter, CATCH_UP_BATCH_SIZE}; use crate::pending::PendingWatcher; - use crate::v02::types::syncing::Syncing; + use crate::types::syncing::Syncing; use crate::{v08, Notifications, Reorg, SubscriptionId, SyncState}; #[tokio::test] diff --git a/crates/rpc/src/method/subscribe_pending_transactions.rs b/crates/rpc/src/method/subscribe_pending_transactions.rs index 824af9e901..c10784e19d 100644 --- a/crates/rpc/src/method/subscribe_pending_transactions.rs +++ b/crates/rpc/src/method/subscribe_pending_transactions.rs @@ -158,7 +158,7 @@ mod tests { use crate::context::{RpcConfig, RpcContext}; use crate::jsonrpc::{handle_json_rpc_socket, RpcResponse}; use crate::pending::PendingWatcher; - use crate::v02::types::syncing::Syncing; + use crate::types::syncing::Syncing; use crate::{v08, Notifications, PendingData, SyncState}; #[tokio::test] diff --git a/crates/rpc/src/method/subscribe_transaction_status.rs b/crates/rpc/src/method/subscribe_transaction_status.rs index 90152323c2..184bf8d6e2 100644 --- a/crates/rpc/src/method/subscribe_transaction_status.rs +++ b/crates/rpc/src/method/subscribe_transaction_status.rs @@ -457,7 +457,7 @@ mod tests { use crate::context::{RpcConfig, RpcContext}; use crate::jsonrpc::{handle_json_rpc_socket, RpcResponse, RpcRouter}; use crate::pending::PendingWatcher; - use crate::v02::types::syncing::Syncing; + use crate::types::syncing::Syncing; use crate::{v08, Notifications, PendingData, Reorg, SubscriptionId, SyncState}; #[tokio::test] diff --git a/crates/rpc/src/method/syncing.rs b/crates/rpc/src/method/syncing.rs index 58be864434..23255dfc55 100644 --- a/crates/rpc/src/method/syncing.rs +++ b/crates/rpc/src/method/syncing.rs @@ -1,5 +1,5 @@ use crate::context::RpcContext; -use crate::v02::types::syncing::Syncing; +use crate::types::syncing::Syncing; crate::error::generate_rpc_error_subset!(Error); @@ -39,7 +39,7 @@ mod tests { use pathfinder_common::{block_hash, BlockNumber}; use super::*; - use crate::v02::types::syncing::{NumberedBlock, Status}; + use crate::types::syncing::{NumberedBlock, Status}; #[tokio::test] async fn not_started_yet() { diff --git a/crates/rpc/src/v02/types.rs b/crates/rpc/src/types.rs similarity index 99% rename from crates/rpc/src/v02/types.rs rename to crates/rpc/src/types.rs index 0a47fba535..5fe91f299e 100644 --- a/crates/rpc/src/v02/types.rs +++ b/crates/rpc/src/types.rs @@ -1,13 +1,14 @@ //! Common data structures used by the JSON-RPC API methods. pub(crate) mod class; +pub mod syncing; + pub use class::*; use pathfinder_common::{ResourceAmount, ResourcePricePerUnit}; use serde::de::Error; use serde_with::serde_as; use crate::dto::{U128Hex, U64Hex}; -pub mod syncing; #[derive(Copy, Clone, Debug, Default, serde::Deserialize, serde::Serialize, PartialEq, Eq)] pub struct ResourceBounds { @@ -809,7 +810,7 @@ pub mod request { mod tests { macro_rules! fixture { ($file_name:literal) => { - include_str!(concat!("../../fixtures/0.6.0/", $file_name)).replace(&[' ', '\n'], "") + include_str!(concat!("../fixtures/0.6.0/", $file_name)).replace(&[' ', '\n'], "") }; } @@ -834,7 +835,7 @@ pub mod request { use super::super::*; use crate::dto::DeserializeForVersion; - use crate::v02::types::{ + use crate::types::{ CairoContractClass, ContractEntryPoints, DataAvailabilityMode, diff --git a/crates/rpc/src/v02/types/class.rs b/crates/rpc/src/types/class.rs similarity index 99% rename from crates/rpc/src/v02/types/class.rs rename to crates/rpc/src/types/class.rs index 82d74e655e..ef2efa3849 100644 --- a/crates/rpc/src/v02/types/class.rs +++ b/crates/rpc/src/types/class.rs @@ -488,10 +488,12 @@ impl From for pathfinder_common::class_definition::SelectorAnd #[cfg(test)] mod tests { + use super::ContractEntryPoint; + mod contract_entry_point { use pathfinder_common::felt; - use crate::v02::types::ContractEntryPoint; + use super::ContractEntryPoint; #[test] fn with_hex_offset() { @@ -559,7 +561,7 @@ mod tests { use pathfinder_executor::parse_deprecated_class_definition; use crate::dto::DeserializeForVersion; - use crate::v02::types::CairoContractClass; + use crate::types::CairoContractClass; #[test] fn convert_deprecated_class_definition_without_debug_info_into_starknet_api_type() { diff --git a/crates/rpc/src/v02/types/syncing.rs b/crates/rpc/src/types/syncing.rs similarity index 100% rename from crates/rpc/src/v02/types/syncing.rs rename to crates/rpc/src/types/syncing.rs diff --git a/crates/rpc/src/v02/method.rs b/crates/rpc/src/v02/method.rs deleted file mode 100644 index 109134a897..0000000000 --- a/crates/rpc/src/v02/method.rs +++ /dev/null @@ -1,18 +0,0 @@ -mod block_hash_and_number; -mod chain_id; -mod get_block_transaction_count; -mod get_class; -mod get_class_at; -mod get_class_hash_at; -mod get_nonce; -mod get_storage_at; -pub(crate) mod get_transaction_by_block_id_and_index; -pub(crate) mod get_transaction_by_hash; - -pub(crate) use chain_id::chain_id; -pub(crate) use get_block_transaction_count::get_block_transaction_count; -pub(crate) use get_class::get_class; -pub(crate) use get_class_at::get_class_at; -pub(crate) use get_class_hash_at::get_class_hash_at; -pub(crate) use get_nonce::get_nonce; -pub(crate) use get_storage_at::get_storage_at; diff --git a/crates/rpc/src/v02/method/block_hash_and_number.rs b/crates/rpc/src/v02/method/block_hash_and_number.rs deleted file mode 100644 index 911f4040fe..0000000000 --- a/crates/rpc/src/v02/method/block_hash_and_number.rs +++ /dev/null @@ -1,63 +0,0 @@ -use anyhow::Context; -use pathfinder_common::{BlockHash, BlockNumber}; -use pathfinder_storage::BlockId; - -use crate::context::RpcContext; -use crate::felt::RpcFelt; - -#[serde_with::serde_as] -#[derive(serde::Serialize)] -pub struct BlockHashAndNumber { - #[serde_as(as = "RpcFelt")] - pub block_hash: BlockHash, - pub block_number: BlockNumber, -} - -crate::error::generate_rpc_error_subset!(BlockNumberError: NoBlocks); - -pub async fn block_hash_and_number( - context: RpcContext, -) -> Result { - let storage = context.storage.clone(); - let span = tracing::Span::current(); - - let jh = tokio::task::spawn_blocking(move || { - let _g = span.enter(); - let mut db = storage - .connection() - .context("Opening database connection")?; - let tx = db.transaction().context("Creating database transaction")?; - - tx.block_id(BlockId::Latest) - .context("Reading latest block hash and number from database")? - .map(|(block_number, block_hash)| BlockHashAndNumber { - block_hash, - block_number, - }) - .ok_or(BlockNumberError::NoBlocks) - }); - - jh.await.context("Database read panic or shutting down")? -} - -pub async fn block_number(context: RpcContext) -> Result { - block_hash_and_number(context) - .await - .map(|result| result.block_number) -} - -#[cfg(test)] -mod tests { - use pathfinder_common::macro_prelude::*; - - use super::*; - - #[tokio::test] - async fn test_block_hash_and_number() { - let context = RpcContext::for_tests(); - let result = block_hash_and_number(context).await.unwrap(); - - assert_eq!(result.block_number, BlockNumber::new_or_panic(2)); - assert_eq!(result.block_hash, block_hash_bytes!(b"latest")); - } -} diff --git a/crates/rpc/src/v02/method/chain_id.rs b/crates/rpc/src/v02/method/chain_id.rs deleted file mode 100644 index 5a8fcd8ed9..0000000000 --- a/crates/rpc/src/v02/method/chain_id.rs +++ /dev/null @@ -1,34 +0,0 @@ -use pathfinder_common::ChainId; - -use crate::context::RpcContext; -use crate::felt::RpcFelt; - -crate::error::generate_rpc_error_subset!(ChainIdError); - -#[serde_with::serde_as] -#[derive(serde::Serialize)] -pub struct ChainIdOutput(#[serde_as(as = "RpcFelt")] ChainId); - -pub async fn chain_id(context: RpcContext) -> Result { - Ok(ChainIdOutput(context.chain_id)) -} - -#[cfg(test)] -mod tests { - use pathfinder_common::ChainId; - use pathfinder_crypto::Felt; - - #[tokio::test] - async fn encoding() { - let value = "example ID"; - let chain_id = Felt::from_be_slice(value.as_bytes()).unwrap(); - let chain_id = ChainId(chain_id); - - let encoded = serde_json::to_string(&chain_id).unwrap(); - - let expected = hex::encode(value); - let expected = format!(r#""0x{expected}""#); - - assert_eq!(encoded, expected); - } -} diff --git a/crates/rpc/src/v02/method/get_block_transaction_count.rs b/crates/rpc/src/v02/method/get_block_transaction_count.rs deleted file mode 100644 index febc644c86..0000000000 --- a/crates/rpc/src/v02/method/get_block_transaction_count.rs +++ /dev/null @@ -1,166 +0,0 @@ -use anyhow::Context; -use pathfinder_common::BlockId; - -use crate::context::RpcContext; - -#[derive(serde::Deserialize, Debug, PartialEq, Eq)] -#[serde(deny_unknown_fields)] -pub struct GetBlockTransactionCountInput { - block_id: BlockId, -} - -impl crate::dto::DeserializeForVersion for GetBlockTransactionCountInput { - fn deserialize(value: crate::dto::Value) -> Result { - value.deserialize_serde() - } -} - -type BlockTransactionCount = u64; - -crate::error::generate_rpc_error_subset!(GetBlockTransactionCountError: BlockNotFound); - -pub async fn get_block_transaction_count( - context: RpcContext, - input: GetBlockTransactionCountInput, -) -> Result { - let storage = context.storage.clone(); - let span = tracing::Span::current(); - - let jh = tokio::task::spawn_blocking(move || { - let _g = span.enter(); - let mut db = storage - .connection() - .context("Opening database connection")?; - let tx = db.transaction().context("Creating database transaction")?; - - let block_id = match input.block_id { - BlockId::Pending => { - let count = context - .pending_data - .get(&tx) - .context("Querying pending data")? - .block - .transactions - .len() as u64; - return Ok(count); - } - other => other.try_into().expect("Only pending cast should fail"), - }; - - let block_transaction_count = tx - .transaction_count(block_id) - .context("Reading transaction count from database")?; - - // Check if the value was 0 because there were no transactions, or because the - // block hash is invalid. - if block_transaction_count == 0 { - let header = tx - .block_header(block_id) - .context("Querying block existence")?; - - return if header.is_some() { - Ok(0) - } else { - Err(GetBlockTransactionCountError::BlockNotFound) - }; - } - Ok(block_transaction_count as BlockTransactionCount) - }); - - jh.await.context("Database read panic or shutting down")? -} - -#[cfg(test)] -mod tests { - use pathfinder_common::macro_prelude::*; - use pathfinder_common::{BlockHash, BlockNumber}; - use pathfinder_crypto::Felt; - - use super::*; - - mod json { - use super::*; - - fn check(chunk: &str, block_id: BlockId) { - let json = format!("{{ \"block_id\": {chunk} }}"); - let input = - serde_json::from_str::(&json).expect("JSON parsing"); - assert_eq!(input.block_id, block_id, "JSON: '{json}'"); - } - - #[test] - fn test_latest() { - check("\"latest\"", BlockId::Latest); - } - - #[test] - fn test_pending() { - check("\"pending\"", BlockId::Pending); - } - - #[test] - fn test_block_number() { - check( - "{ \"block_number\": 42 }", - BlockId::Number(BlockNumber::new_or_panic(42)), - ); - } - - #[test] - fn test_block_hash() { - check( - "{ \"block_hash\": \"0xFACE\" }", - BlockId::Hash(block_hash!("0xface")), - ); - } - } - - async fn check_count(context: RpcContext, block_id: BlockId, count: u64) { - let input = GetBlockTransactionCountInput { block_id }; - let result = get_block_transaction_count(context, input) - .await - .expect("block transaction count"); - assert_eq!(result, count); - } - - async fn check_error(context: RpcContext, block_id: BlockId) { - let input = GetBlockTransactionCountInput { block_id }; - let result = get_block_transaction_count(context, input).await; - assert!(result.is_err()); - } - - #[tokio::test] - async fn test_genesis() { - let context = RpcContext::for_tests(); - let block_id = BlockId::Hash(block_hash_bytes!(b"genesis")); - check_count(context, block_id, 1).await; - } - - #[tokio::test] - async fn test_latest() { - let context = RpcContext::for_tests(); - let block_id = BlockId::Latest; - check_count(context, block_id, 5).await; - } - - #[tokio::test] - async fn test_pending() { - let context = RpcContext::for_tests_with_pending().await; - let block_id = BlockId::Pending; - check_count(context, block_id, 3).await; - } - - #[tokio::test] - async fn test_invalid_hash() { - let context = RpcContext::for_tests(); - let block_id = BlockId::Hash(BlockHash(Felt::ZERO)); - check_error(context, block_id).await; - } - - #[tokio::test] - async fn test_invalid_number() { - let context = RpcContext::for_tests(); - let block_id = BlockId::Number(BlockNumber::new_or_panic(123)); - check_error(context, block_id).await; - } -} diff --git a/crates/rpc/src/v02/method/get_class.rs b/crates/rpc/src/v02/method/get_class.rs deleted file mode 100644 index 5c30e28484..0000000000 --- a/crates/rpc/src/v02/method/get_class.rs +++ /dev/null @@ -1,387 +0,0 @@ -use anyhow::Context; -use pathfinder_common::{BlockId, ClassHash}; - -use crate::context::RpcContext; -use crate::v02::types::ContractClass; - -crate::error::generate_rpc_error_subset!(GetClassError: BlockNotFound, ClassHashNotFound); - -#[derive(serde::Deserialize, Debug, PartialEq, Eq)] -#[serde(deny_unknown_fields)] -pub struct GetClassInput { - block_id: BlockId, - class_hash: ClassHash, -} - -impl crate::dto::DeserializeForVersion for GetClassInput { - fn deserialize(value: crate::dto::Value) -> Result { - value.deserialize_serde() - } -} - -pub async fn get_class( - context: RpcContext, - input: GetClassInput, -) -> Result { - let span = tracing::Span::current(); - let jh = tokio::task::spawn_blocking(move || -> Result { - let _g = span.enter(); - let mut db = context - .storage - .connection() - .context("Opening database connection")?; - let tx = db.transaction().context("Creating database transaction")?; - - let is_pending = if input.block_id.is_pending() { - context - .pending_data - .get(&tx) - .context("Querying pending data")? - .state_update - .class_is_declared(input.class_hash) - } else { - false - }; - - // Map block id to the storage variant. - let block_id = match input.block_id { - BlockId::Pending => pathfinder_storage::BlockId::Latest, - other => other.try_into().expect("Only pending cast should fail"), - }; - - // Check that block exists - let block_exists = tx.block_exists(block_id)?; - if !block_exists { - return Err(GetClassError::BlockNotFound); - } - - // If the class is declared in the pending block, then we shouldn't check the - // class's declaration point. - let definition = if is_pending { - tx.class_definition(input.class_hash) - } else { - tx.class_definition_at(block_id, input.class_hash) - } - .context("Fetching class definition")?; - - let Some(definition) = definition else { - return Err(GetClassError::ClassHashNotFound); - }; - - let class = ContractClass::from_definition_bytes(&definition) - .context("Parsing class definition")?; - - Ok(class) - }); - - jh.await.context("Reading class from database")? -} - -#[cfg(test)] -mod tests { - use assert_matches::assert_matches; - use pathfinder_common::macro_prelude::*; - - use super::*; - - mod parsing { - use serde_json::json; - - use super::*; - - #[test] - fn positional_args() { - let positional = json!([ - { "block_hash": "0xabcde" }, - "0x12345" - ]); - - let input = serde_json::from_value::(positional).unwrap(); - let expected = GetClassInput { - block_id: block_hash!("0xabcde").into(), - class_hash: class_hash!("0x12345"), - }; - assert_eq!(input, expected); - } - - #[test] - fn named_args() { - let named = json!({ - "block_id": { "block_hash": "0xabcde" }, - "class_hash": "0x12345" - }); - - let input = serde_json::from_value::(named).unwrap(); - let expected = GetClassInput { - block_id: block_hash!("0xabcde").into(), - class_hash: class_hash!("0x12345"), - }; - assert_eq!(input, expected); - } - } - - #[tokio::test] - async fn pending() { - let context = RpcContext::for_tests(); - - // Cairo v0.x class - let valid_v0 = class_hash_bytes!(b"class 0 hash"); - super::get_class( - context.clone(), - GetClassInput { - block_id: BlockId::Pending, - class_hash: valid_v0, - }, - ) - .await - .unwrap(); - // Cairo v1.x class (Sierra) - let valid_v1 = class_hash_bytes!(b"class 2 hash (sierra)"); - super::get_class( - context.clone(), - GetClassInput { - block_id: BlockId::Pending, - class_hash: valid_v1, - }, - ) - .await - .unwrap(); - - let invalid = class_hash_bytes!(b"invalid"); - let error = super::get_class( - context, - GetClassInput { - block_id: BlockId::Pending, - class_hash: invalid, - }, - ) - .await - .unwrap_err(); - - assert_matches!(error, GetClassError::ClassHashNotFound); - } - - #[tokio::test] - async fn latest() { - let context = RpcContext::for_tests(); - - // Cairo v0.x class - let valid_v0 = class_hash_bytes!(b"class 0 hash"); - super::get_class( - context.clone(), - GetClassInput { - block_id: BlockId::Latest, - class_hash: valid_v0, - }, - ) - .await - .unwrap(); - - // Cairo v1.x class (Sierra) - let valid_v1 = class_hash_bytes!(b"class 2 hash (sierra)"); - super::get_class( - context.clone(), - GetClassInput { - block_id: BlockId::Latest, - class_hash: valid_v1, - }, - ) - .await - .unwrap(); - - let invalid = class_hash_bytes!(b"invalid"); - let error = super::get_class( - context.clone(), - GetClassInput { - block_id: BlockId::Latest, - class_hash: invalid, - }, - ) - .await - .unwrap_err(); - assert_matches!(error, GetClassError::ClassHashNotFound); - - // This class is defined, but is not declared in any canonical block, such - // as what may occur for a pending class declaration. - let undeclared = class_hash_bytes!(b"class pending hash"); - let error = super::get_class( - context.clone(), - GetClassInput { - block_id: BlockId::Latest, - class_hash: undeclared, - }, - ) - .await - .unwrap_err(); - assert_matches!(error, GetClassError::ClassHashNotFound); - } - - #[tokio::test] - async fn at_number() { - use pathfinder_common::BlockNumber; - - let context = RpcContext::for_tests(); - - // Cairo v0.x class - // This class is declared in block 0. - let valid_v0 = class_hash_bytes!(b"class 0 hash"); - super::get_class( - context.clone(), - GetClassInput { - block_id: BlockId::Number(BlockNumber::new_or_panic(1)), - class_hash: valid_v0, - }, - ) - .await - .unwrap(); - - // Cairo v1.x class (Sierra) - // This class is declared in block 2. - let valid_v1 = class_hash_bytes!(b"class 2 hash (sierra)"); - super::get_class( - context.clone(), - GetClassInput { - block_id: BlockId::Number(BlockNumber::new_or_panic(2)), - class_hash: valid_v1, - }, - ) - .await - .unwrap(); - - let error = super::get_class( - context.clone(), - GetClassInput { - block_id: BlockId::Number(BlockNumber::GENESIS), - class_hash: valid_v1, - }, - ) - .await - .unwrap_err(); - assert_matches!(error, GetClassError::ClassHashNotFound); - - let invalid = class_hash_bytes!(b"invalid"); - let error = super::get_class( - context.clone(), - GetClassInput { - block_id: BlockId::Number(BlockNumber::new_or_panic(2)), - class_hash: invalid, - }, - ) - .await - .unwrap_err(); - assert_matches!(error, GetClassError::ClassHashNotFound); - - // This class is defined, but is not declared in any canonical block, such - // as what may occur for a pending class declaration. - let undeclared = class_hash_bytes!(b"class pending hash"); - let error = super::get_class( - context.clone(), - GetClassInput { - block_id: BlockId::Number(BlockNumber::new_or_panic(2)), - class_hash: undeclared, - }, - ) - .await - .unwrap_err(); - assert_matches!(error, GetClassError::ClassHashNotFound); - - // Class exists, but block number does not. - let valid = class_hash_bytes!(b"class 0 hash"); - let error = super::get_class( - context.clone(), - GetClassInput { - block_id: BlockId::Number(BlockNumber::MAX), - class_hash: valid, - }, - ) - .await - .unwrap_err(); - assert_matches!(error, GetClassError::BlockNotFound); - } - - #[tokio::test] - async fn read_at_hash() { - let context = RpcContext::for_tests(); - - // Cairo v0.x class - // This class is declared in block 1. - let valid_v0 = class_hash_bytes!(b"class 0 hash"); - let block1_hash = block_hash_bytes!(b"block 1"); - super::get_class( - context.clone(), - GetClassInput { - block_id: BlockId::Hash(block1_hash), - class_hash: valid_v0, - }, - ) - .await - .unwrap(); - - // Cairo v1.x class - // This class is declared in block 2. - let valid_v1 = class_hash_bytes!(b"class 2 hash (sierra)"); - let block2_hash = block_hash_bytes!(b"latest"); - super::get_class( - context.clone(), - GetClassInput { - block_id: BlockId::Hash(block2_hash), - class_hash: valid_v1, - }, - ) - .await - .unwrap(); - - let block0_hash = block_hash_bytes!(b"genesis"); - let error = super::get_class( - context.clone(), - GetClassInput { - block_id: BlockId::Hash(block0_hash), - class_hash: valid_v1, - }, - ) - .await - .unwrap_err(); - assert_matches!(error, GetClassError::ClassHashNotFound); - - let invalid = class_hash_bytes!(b"invalid"); - let latest_hash = block_hash_bytes!(b"latest"); - let error = super::get_class( - context.clone(), - GetClassInput { - block_id: BlockId::Hash(latest_hash), - class_hash: invalid, - }, - ) - .await - .unwrap_err(); - assert_matches!(error, GetClassError::ClassHashNotFound); - - // This class is defined, but is not declared in any canonical block. - let undeclared = class_hash_bytes!(b"class pending hash"); - let latest_hash = block_hash_bytes!(b"latest"); - let error = super::get_class( - context.clone(), - GetClassInput { - block_id: BlockId::Hash(latest_hash), - class_hash: undeclared, - }, - ) - .await - .unwrap_err(); - assert_matches!(error, GetClassError::ClassHashNotFound); - - // Class exists, but block hash does not. - let valid = class_hash_bytes!(b"class 0 hash"); - let invalid_block = block_hash_bytes!(b"invalid"); - let error = super::get_class( - context.clone(), - GetClassInput { - block_id: BlockId::Hash(invalid_block), - class_hash: valid, - }, - ) - .await - .unwrap_err(); - assert_matches!(error, GetClassError::BlockNotFound); - } -} diff --git a/crates/rpc/src/v02/method/get_class_at.rs b/crates/rpc/src/v02/method/get_class_at.rs deleted file mode 100644 index e008dbbbe7..0000000000 --- a/crates/rpc/src/v02/method/get_class_at.rs +++ /dev/null @@ -1,343 +0,0 @@ -use anyhow::Context; -use pathfinder_common::{BlockId, ContractAddress}; - -use crate::context::RpcContext; -use crate::v02::types::ContractClass; - -crate::error::generate_rpc_error_subset!(GetClassAtError: BlockNotFound, ContractNotFound); - -#[derive(serde::Deserialize, Debug, PartialEq, Eq)] -#[serde(deny_unknown_fields)] -pub struct GetClassAtInput { - block_id: BlockId, - contract_address: ContractAddress, -} - -impl crate::dto::DeserializeForVersion for GetClassAtInput { - fn deserialize(value: crate::dto::Value) -> Result { - value.deserialize_serde() - } -} - -pub async fn get_class_at( - context: RpcContext, - input: GetClassAtInput, -) -> Result { - let span = tracing::Span::current(); - - let jh = tokio::task::spawn_blocking(move || { - let _g = span.enter(); - let mut db = context - .storage - .connection() - .context("Opening database connection")?; - - let tx = db.transaction().context("Creating database transaction")?; - - let pending_class_hash = if input.block_id == BlockId::Pending { - context - .pending_data - .get(&tx) - .context("Querying pending data")? - .state_update - .contract_class(input.contract_address) - } else { - None - }; - - // Map block id to the storage variant. - let block_id = match input.block_id { - BlockId::Pending => pathfinder_storage::BlockId::Latest, - other => other.try_into().expect("Only pending cast should fail"), - }; - - if !tx.block_exists(block_id)? { - return Err(GetClassAtError::BlockNotFound); - } - - let class_hash = match pending_class_hash { - Some(class_hash) => class_hash, - None => tx - .contract_class_hash(block_id, input.contract_address) - .context("Querying contract's class hash")? - .ok_or(GetClassAtError::ContractNotFound)?, - }; - - let definition = tx - .class_definition(class_hash) - .context("Fetching class definition")? - .context("Class definition missing from database")?; - - let class = ContractClass::from_definition_bytes(&definition) - .context("Parsing class definition")?; - - Ok(class) - }); - - jh.await.context("Reading class from database")? -} - -#[cfg(test)] -mod tests { - use assert_matches::assert_matches; - use pathfinder_common::macro_prelude::*; - - use super::*; - - mod parsing { - use serde_json::json; - - use super::*; - - #[test] - fn positional_args() { - let positional = json!([ - { "block_hash": "0xabcde" }, - "0x12345" - ]); - - let input = serde_json::from_value::(positional).unwrap(); - let expected = GetClassAtInput { - block_id: block_hash!("0xabcde").into(), - contract_address: contract_address!("0x12345"), - }; - assert_eq!(input, expected); - } - - #[test] - fn named_args() { - let named = json!({ - "block_id": { "block_hash": "0xabcde" }, - "contract_address": "0x12345" - }); - - let input = serde_json::from_value::(named).unwrap(); - let expected = GetClassAtInput { - block_id: block_hash!("0xabcde").into(), - contract_address: contract_address!("0x12345"), - }; - assert_eq!(input, expected); - } - } - - #[tokio::test] - async fn pending() { - let context = RpcContext::for_tests(); - - // Cairo class v0.x - let valid_v0 = contract_address_bytes!(b"contract 0"); - super::get_class_at( - context.clone(), - GetClassAtInput { - block_id: BlockId::Pending, - contract_address: valid_v0, - }, - ) - .await - .unwrap(); - - // Cairo class v1.x - let valid_v1 = contract_address_bytes!(b"contract 2 (sierra)"); - super::get_class_at( - context.clone(), - GetClassAtInput { - block_id: BlockId::Pending, - contract_address: valid_v1, - }, - ) - .await - .unwrap(); - - let invalid = contract_address_bytes!(b"invalid"); - let error = super::get_class_at( - context.clone(), - GetClassAtInput { - block_id: BlockId::Pending, - contract_address: invalid, - }, - ) - .await - .unwrap_err(); - assert_matches!(error, GetClassAtError::ContractNotFound); - } - - #[tokio::test] - async fn latest() { - let context = RpcContext::for_tests(); - - // Cairo class v0.x - let valid_v0 = contract_address_bytes!(b"contract 0"); - super::get_class_at( - context.clone(), - GetClassAtInput { - block_id: BlockId::Latest, - contract_address: valid_v0, - }, - ) - .await - .unwrap(); - - // Cairo class v1.x - let valid_v1 = contract_address_bytes!(b"contract 2 (sierra)"); - super::get_class_at( - context.clone(), - GetClassAtInput { - block_id: BlockId::Latest, - contract_address: valid_v1, - }, - ) - .await - .unwrap(); - - let invalid = contract_address_bytes!(b"invalid"); - let error = super::get_class_at( - context.clone(), - GetClassAtInput { - block_id: BlockId::Latest, - contract_address: invalid, - }, - ) - .await - .unwrap_err(); - assert_matches!(error, GetClassAtError::ContractNotFound); - } - - #[tokio::test] - async fn number() { - use pathfinder_common::BlockNumber; - - let context = RpcContext::for_tests(); - - // Cairo v0.x class - // This contract is declared in block 1. - let valid_v0 = contract_address_bytes!(b"contract 1"); - super::get_class_at( - context.clone(), - GetClassAtInput { - block_id: BlockId::Number(BlockNumber::new_or_panic(1)), - contract_address: valid_v0, - }, - ) - .await - .unwrap(); - - // Cairo v1.x class (sierra) - // This contract is declared in block 2. - let valid_v1 = contract_address_bytes!(b"contract 2 (sierra)"); - super::get_class_at( - context.clone(), - GetClassAtInput { - block_id: BlockId::Number(BlockNumber::new_or_panic(2)), - contract_address: valid_v1, - }, - ) - .await - .unwrap(); - - let error = super::get_class_at( - context.clone(), - GetClassAtInput { - block_id: BlockId::Number(BlockNumber::GENESIS), - contract_address: valid_v0, - }, - ) - .await - .unwrap_err(); - assert_matches!(error, GetClassAtError::ContractNotFound); - - let invalid = contract_address_bytes!(b"invalid"); - let error = super::get_class_at( - context.clone(), - GetClassAtInput { - block_id: BlockId::Number(BlockNumber::new_or_panic(2)), - contract_address: invalid, - }, - ) - .await - .unwrap_err(); - assert_matches!(error, GetClassAtError::ContractNotFound); - - // Class exists, but block number does not. - let error = super::get_class_at( - context.clone(), - GetClassAtInput { - block_id: BlockId::Number(BlockNumber::MAX), - contract_address: valid_v0, - }, - ) - .await - .unwrap_err(); - assert_matches!(error, GetClassAtError::BlockNotFound); - } - - #[tokio::test] - async fn hash() { - let context = RpcContext::for_tests(); - - // Cairo v0.x class - // This class is declared in block 1. - let valid_v0 = contract_address_bytes!(b"contract 1"); - let block1_hash = block_hash_bytes!(b"block 1"); - super::get_class_at( - context.clone(), - GetClassAtInput { - block_id: BlockId::Hash(block1_hash), - contract_address: valid_v0, - }, - ) - .await - .unwrap(); - - // Cairo v1.x class (sierra) - // This class is declared in block 2. - let valid_v1 = contract_address_bytes!(b"contract 2 (sierra)"); - let block2_hash = block_hash_bytes!(b"latest"); - super::get_class_at( - context.clone(), - GetClassAtInput { - block_id: BlockId::Hash(block2_hash), - contract_address: valid_v1, - }, - ) - .await - .unwrap(); - - let block0_hash = block_hash_bytes!(b"genesis"); - let error = super::get_class_at( - context.clone(), - GetClassAtInput { - block_id: BlockId::Hash(block0_hash), - contract_address: valid_v0, - }, - ) - .await - .unwrap_err(); - assert_matches!(error, GetClassAtError::ContractNotFound); - - let invalid = contract_address_bytes!(b"invalid"); - let latest_hash = block_hash_bytes!(b"latest"); - let error = super::get_class_at( - context.clone(), - GetClassAtInput { - block_id: BlockId::Hash(latest_hash), - contract_address: invalid, - }, - ) - .await - .unwrap_err(); - assert_matches!(error, GetClassAtError::ContractNotFound); - - // Class exists, but block hash does not. - let invalid_block = block_hash_bytes!(b"invalid"); - let error = super::get_class_at( - context.clone(), - GetClassAtInput { - block_id: BlockId::Hash(invalid_block), - contract_address: valid_v0, - }, - ) - .await - .unwrap_err(); - assert_matches!(error, GetClassAtError::BlockNotFound); - } -} diff --git a/crates/rpc/src/v02/method/get_class_hash_at.rs b/crates/rpc/src/v02/method/get_class_hash_at.rs deleted file mode 100644 index 602c6ab4c9..0000000000 --- a/crates/rpc/src/v02/method/get_class_hash_at.rs +++ /dev/null @@ -1,241 +0,0 @@ -use anyhow::Context; -use pathfinder_common::{BlockId, ClassHash, ContractAddress}; - -use crate::context::RpcContext; -use crate::felt::RpcFelt; - -crate::error::generate_rpc_error_subset!(GetClassHashAtError: BlockNotFound, ContractNotFound); - -#[derive(serde::Deserialize, Debug, PartialEq, Eq)] -#[serde(deny_unknown_fields)] -pub struct GetClassHashAtInput { - block_id: BlockId, - contract_address: ContractAddress, -} - -impl crate::dto::DeserializeForVersion for GetClassHashAtInput { - fn deserialize(value: crate::dto::Value) -> Result { - value.deserialize_serde() - } -} - -#[serde_with::serde_as] -#[derive(serde::Serialize, Debug)] -pub struct GetClassHashOutput(#[serde_as(as = "RpcFelt")] ClassHash); - -pub async fn get_class_hash_at( - context: RpcContext, - input: GetClassHashAtInput, -) -> Result { - let span = tracing::Span::current(); - let jh = tokio::task::spawn_blocking(move || { - let _g = span.enter(); - let mut db = context - .storage - .connection() - .context("Opening database connection")?; - - let tx = db.transaction().context("Creating database transaction")?; - - if input.block_id == BlockId::Pending { - let pending = context - .pending_data - .get(&tx) - .context("Querying pending data")? - .state_update - .contract_class(input.contract_address); - - if let Some(pending) = pending { - return Ok(GetClassHashOutput(pending)); - } - } - - // Map block id to the storage variant. - let block_id = match input.block_id { - BlockId::Pending => pathfinder_storage::BlockId::Latest, - other => other.try_into().expect("Only pending cast should fail"), - }; - - // Check for block existence. - if !tx.block_exists(block_id)? { - return Err(GetClassHashAtError::BlockNotFound); - } - - tx.contract_class_hash(block_id, input.contract_address) - .context("Fetching class hash from database")? - .ok_or(GetClassHashAtError::ContractNotFound) - .map(GetClassHashOutput) - }); - - jh.await.context("Database read panic or shutting down")? -} - -#[cfg(test)] -mod tests { - use assert_matches::assert_matches; - use pathfinder_common::macro_prelude::*; - - use super::*; - - mod parsing { - use serde_json::json; - - use super::*; - - #[test] - fn positional_args() { - let positional = json!([ - { "block_hash": "0xabcde" }, - "0x12345" - ]); - - let input = serde_json::from_value::(positional).unwrap(); - let expected = GetClassHashAtInput { - block_id: block_hash!("0xabcde").into(), - contract_address: contract_address!("0x12345"), - }; - assert_eq!(input, expected); - } - - #[test] - fn named_args() { - let named = json!({ - "block_id": { "block_hash": "0xabcde" }, - "contract_address": "0x12345" - }); - - let input = serde_json::from_value::(named).unwrap(); - let expected = GetClassHashAtInput { - block_id: block_hash!("0xabcde").into(), - contract_address: contract_address!("0x12345"), - }; - assert_eq!(input, expected); - } - } - - mod errors { - use super::*; - - #[tokio::test] - async fn contract_not_found() { - let context = RpcContext::for_tests(); - - let input = GetClassHashAtInput { - block_id: BlockId::Latest, - contract_address: contract_address_bytes!(b"invalid"), - }; - let result = get_class_hash_at(context, input).await; - assert_matches!(result, Err(GetClassHashAtError::ContractNotFound)); - } - - #[tokio::test] - async fn block_not_found() { - let context = RpcContext::for_tests(); - - let input = GetClassHashAtInput { - block_id: BlockId::Hash(block_hash_bytes!(b"invalid")), - // This contract does exist and is added in block 0. - contract_address: contract_address_bytes!(b"contract 0"), - }; - let result = get_class_hash_at(context, input).await; - assert_matches!(result, Err(GetClassHashAtError::BlockNotFound)); - } - } - - #[tokio::test] - async fn latest() { - let context = RpcContext::for_tests(); - let expected = class_hash_bytes!(b"class 0 hash"); - - let input = GetClassHashAtInput { - block_id: BlockId::Latest, - contract_address: contract_address_bytes!(b"contract 0"), - }; - let result = get_class_hash_at(context, input).await.unwrap(); - assert_eq!(result.0, expected); - } - - #[tokio::test] - async fn at_block() { - use pathfinder_common::BlockNumber; - let context = RpcContext::for_tests(); - - // This contract is deployed in block 1. - let address = contract_address_bytes!(b"contract 1"); - - let input = GetClassHashAtInput { - block_id: BlockNumber::new_or_panic(0).into(), - contract_address: address, - }; - let result = get_class_hash_at(context.clone(), input).await; - assert_matches!(result, Err(GetClassHashAtError::ContractNotFound)); - - let expected = class_hash_bytes!(b"class 1 hash"); - let input = GetClassHashAtInput { - block_id: BlockNumber::new_or_panic(1).into(), - contract_address: address, - }; - let result = get_class_hash_at(context.clone(), input).await.unwrap(); - assert_eq!(result.0, expected); - - let input = GetClassHashAtInput { - block_id: BlockNumber::new_or_panic(2).into(), - contract_address: address, - }; - let result = get_class_hash_at(context, input).await.unwrap(); - assert_eq!(result.0, expected); - } - - #[tokio::test] - async fn pending_defaults_to_latest() { - let context = RpcContext::for_tests(); - let expected = class_hash_bytes!(b"class 0 hash"); - - let input = GetClassHashAtInput { - block_id: BlockId::Pending, - contract_address: contract_address_bytes!(b"contract 0"), - }; - let result = get_class_hash_at(context, input).await.unwrap(); - assert_eq!(result.0, expected); - } - - #[tokio::test] - async fn pending() { - let context = RpcContext::for_tests_with_pending().await; - - // This should still work even though it was deployed in an actual block. - let expected = class_hash_bytes!(b"class 0 hash"); - let input = GetClassHashAtInput { - block_id: BlockId::Pending, - contract_address: contract_address_bytes!(b"contract 0"), - }; - let result = get_class_hash_at(context.clone(), input).await.unwrap(); - assert_eq!(result.0, expected); - - // This is an actual pending deployed contract. - let expected = class_hash_bytes!(b"pending class 0 hash"); - let input = GetClassHashAtInput { - block_id: BlockId::Pending, - contract_address: contract_address_bytes!(b"pending contract 0 address"), - }; - let result = get_class_hash_at(context.clone(), input).await.unwrap(); - assert_eq!(result.0, expected); - - // Replaced class in pending should also work. - let expected = class_hash_bytes!(b"pending class 2 hash (replaced)"); - let input = GetClassHashAtInput { - block_id: BlockId::Pending, - contract_address: contract_address_bytes!(b"pending contract 2 (replaced)"), - }; - let result = get_class_hash_at(context.clone(), input).await.unwrap(); - assert_eq!(result.0, expected); - - // This one remains missing. - let input = GetClassHashAtInput { - block_id: BlockId::Latest, - contract_address: contract_address_bytes!(b"invalid"), - }; - let result = get_class_hash_at(context, input).await; - assert_matches!(result, Err(GetClassHashAtError::ContractNotFound)); - } -} diff --git a/crates/rpc/src/v02/method/get_nonce.rs b/crates/rpc/src/v02/method/get_nonce.rs deleted file mode 100644 index 779488ca6c..0000000000 --- a/crates/rpc/src/v02/method/get_nonce.rs +++ /dev/null @@ -1,248 +0,0 @@ -use anyhow::Context; -use pathfinder_common::{BlockId, ContractAddress, ContractNonce}; - -use crate::context::RpcContext; -use crate::felt::RpcFelt; - -#[derive(serde::Deserialize, Debug, PartialEq, Eq)] -#[serde(deny_unknown_fields)] -pub struct GetNonceInput { - block_id: BlockId, - contract_address: ContractAddress, -} - -impl crate::dto::DeserializeForVersion for GetNonceInput { - fn deserialize(value: crate::dto::Value) -> Result { - value.deserialize_serde() - } -} - -#[serde_with::serde_as] -#[derive(serde::Serialize, Debug, PartialEq)] -pub struct GetNonceOutput(#[serde_as(as = "RpcFelt")] ContractNonce); - -crate::error::generate_rpc_error_subset!(GetNonceError: BlockNotFound, ContractNotFound); - -pub async fn get_nonce( - context: RpcContext, - input: GetNonceInput, -) -> Result { - let contract_address = input.contract_address; - - let storage = context.storage.clone(); - let span = tracing::Span::current(); - let jh = tokio::task::spawn_blocking(move || -> Result<_, GetNonceError> { - let _g = span.enter(); - let mut db = storage - .connection() - .context("Opening database connection")?; - let tx = db.transaction().context("Creating database transaction")?; - - if input.block_id.is_pending() { - if let Some(nonce) = context - .pending_data - .get(&tx) - .context("Querying pending data")? - .state_update - .contract_nonce(contract_address) - { - return Ok(GetNonceOutput(nonce)); - } - } - - let block_id = match input.block_id { - BlockId::Pending => pathfinder_storage::BlockId::Latest, - other => other.try_into().expect("Only pending cast should fail"), - }; - - // Check that block exists. This should occur first as the block number - // isn't checked explicitly (i.e. nonce fetch just uses <= number). - let block_exists = tx.block_exists(block_id).context("Checking block exists")?; - if !block_exists { - return Err(GetNonceError::BlockNotFound); - } - - let nonce = tx - .contract_nonce(contract_address, block_id) - .context("Querying contract nonce from database")?; - - if let Some(nonce) = nonce { - return Ok(GetNonceOutput(nonce)); - }; - - // Check whether contract exists or not. - let contract_exists = tx - .contract_exists(contract_address, block_id) - .context("Checking contract exists")?; - - if contract_exists { - Ok(GetNonceOutput(ContractNonce::ZERO)) - } else { - Err(GetNonceError::ContractNotFound) - } - }); - jh.await.context("Database read panic or shutting down")? -} - -#[cfg(test)] -mod tests { - use pathfinder_common::macro_prelude::*; - use pathfinder_common::{BlockId, BlockNumber, ContractNonce}; - - use super::{get_nonce, GetNonceError, GetNonceInput}; - use crate::context::RpcContext; - - mod parsing { - use serde_json::json; - - use super::*; - - #[test] - fn positional_args() { - let positional = json!([ - { "block_hash": "0xabcde" }, - "0x12345" - ]); - - let input = serde_json::from_value::(positional).unwrap(); - let expected = GetNonceInput { - block_id: block_hash!("0xabcde").into(), - contract_address: contract_address!("0x12345"), - }; - assert_eq!(input, expected); - } - - #[test] - fn named_args() { - let named = json!({ - "block_id": { "block_hash": "0xabcde" }, - "contract_address": "0x12345" - }); - - let input = serde_json::from_value::(named).unwrap(); - let expected = GetNonceInput { - block_id: block_hash!("0xabcde").into(), - contract_address: contract_address!("0x12345"), - }; - assert_eq!(input, expected); - } - } - - mod errors { - use super::*; - - #[tokio::test] - async fn contract_not_found() { - let context = RpcContext::for_tests(); - - let input = GetNonceInput { - block_id: BlockId::Latest, - contract_address: contract_address_bytes!(b"invalid"), - }; - - let result = get_nonce(context, input).await; - - assert_matches::assert_matches!(result, Err(GetNonceError::ContractNotFound)); - } - - #[tokio::test] - async fn block_not_found() { - let context = RpcContext::for_tests(); - - let input = GetNonceInput { - block_id: BlockId::Hash(block_hash_bytes!(b"invalid")), - // This contract does exist and is added in block 0. - contract_address: contract_address_bytes!(b"contract 0"), - }; - - let result = get_nonce(context, input).await; - - assert_matches::assert_matches!(result, Err(GetNonceError::BlockNotFound)); - } - } - - #[tokio::test] - async fn latest() { - let context = RpcContext::for_tests(); - - // This contract is created in `setup_storage` and has a nonce set to 0x1. - let input = GetNonceInput { - block_id: BlockId::Latest, - contract_address: contract_address_bytes!(b"contract 0"), - }; - let nonce = get_nonce(context, input).await.unwrap(); - assert_eq!(nonce.0, contract_nonce!("0x1")); - } - - #[tokio::test] - async fn at_block() { - let context = RpcContext::for_tests(); - - // This contract is created in `setup_storage` at block 1, - // but only gets nonce=0x10 explicitly set in block 2. - let input = GetNonceInput { - block_id: BlockNumber::new_or_panic(2).into(), - contract_address: contract_address_bytes!(b"contract 1"), - }; - let nonce = get_nonce(context, input).await.unwrap(); - assert_eq!(nonce.0, contract_nonce!("0x10")); - } - - #[tokio::test] - async fn pending() { - let context = RpcContext::for_tests_with_pending().await; - - // This contract is created in `setup_storage` and has a nonce set in the - // pending block. - let input = GetNonceInput { - block_id: BlockId::Pending, - contract_address: contract_address_bytes!(b"contract 1"), - }; - let nonce = get_nonce(context, input).await.unwrap(); - assert_eq!(nonce.0, contract_nonce_bytes!(b"pending nonce")); - } - - #[tokio::test] - async fn pending_defaults_to_latest() { - let context = RpcContext::for_tests(); - - // This contract is created in `setup_storage` and has a nonce set to 0x1, and - // is not overwritten in pending (since this test does not specify any - // pending data). - let input = GetNonceInput { - block_id: BlockId::Pending, - contract_address: contract_address_bytes!(b"contract 0"), - }; - let nonce = get_nonce(context, input).await.unwrap(); - assert_eq!(nonce.0, contract_nonce!("0x1")); - } - - #[tokio::test] - async fn defaults_to_zero() { - let context = RpcContext::for_tests(); - - // This contract is created in `setup_storage` at block 1, - // but only gets a nonce explicitly set in block 2. - let input = GetNonceInput { - block_id: BlockNumber::new_or_panic(1).into(), - contract_address: contract_address_bytes!(b"contract 1"), - }; - let nonce = get_nonce(context, input).await.unwrap(); - - assert_eq!(nonce.0, ContractNonce::ZERO); - } - - #[tokio::test] - async fn contract_deployed_in_pending_defaults_to_zero() { - let context = RpcContext::for_tests_with_pending().await; - - // This contract is deployed in the pending block but does not have a nonce - // update. - let input = GetNonceInput { - block_id: BlockId::Pending, - contract_address: contract_address_bytes!(b"pending contract 0 address"), - }; - let nonce = get_nonce(context, input).await.unwrap(); - assert_eq!(nonce.0, ContractNonce::ZERO); - } -} diff --git a/crates/rpc/src/v02/method/get_storage_at.rs b/crates/rpc/src/v02/method/get_storage_at.rs deleted file mode 100644 index 7a9535b9bc..0000000000 --- a/crates/rpc/src/v02/method/get_storage_at.rs +++ /dev/null @@ -1,341 +0,0 @@ -use anyhow::Context; -use pathfinder_common::{BlockId, ContractAddress, StorageAddress, StorageValue}; -use serde::Deserialize; - -use crate::context::RpcContext; -use crate::felt::RpcFelt; - -#[derive(Deserialize, Debug, PartialEq, Eq)] -#[serde(deny_unknown_fields)] -pub struct GetStorageAtInput { - pub contract_address: ContractAddress, - pub key: StorageAddress, - pub block_id: BlockId, -} - -impl crate::dto::DeserializeForVersion for GetStorageAtInput { - fn deserialize(value: crate::dto::Value) -> Result { - value.deserialize_serde() - } -} - -#[serde_with::serde_as] -#[derive(serde::Serialize, Debug)] -pub struct GetStorageOutput(#[serde_as(as = "RpcFelt")] StorageValue); - -crate::error::generate_rpc_error_subset!(GetStorageAtError: ContractNotFound, BlockNotFound); - -/// Get the value of the storage at the given address and key. -pub async fn get_storage_at( - context: RpcContext, - input: GetStorageAtInput, -) -> Result { - let storage = context.storage.clone(); - let span = tracing::Span::current(); - - let jh = tokio::task::spawn_blocking(move || { - let _g = span.enter(); - let mut db = storage - .connection() - .context("Opening database connection")?; - - let tx = db.transaction().context("Creating database transaction")?; - - if input.block_id.is_pending() { - if let Some(value) = context - .pending_data - .get(&tx) - .context("Querying pending data")? - .state_update - .storage_value(input.contract_address, input.key) - { - return Ok(GetStorageOutput(value)); - } - } - - let block_id = match input.block_id { - BlockId::Pending => pathfinder_storage::BlockId::Latest, - other => other.try_into().expect("Only pending cast should fail"), - }; - - // Check for block existence. - if !tx.block_exists(block_id)? { - return Err(GetStorageAtError::BlockNotFound); - } - - let value = tx - .storage_value(block_id, input.contract_address, input.key) - .context("Querying storage value")?; - - match value { - Some(value) => Ok(GetStorageOutput(value)), - None => { - if tx.contract_exists(input.contract_address, block_id)? { - Ok(GetStorageOutput(StorageValue::ZERO)) - } else { - Err(GetStorageAtError::ContractNotFound) - } - } - } - }); - - jh.await.context("Database read panic or shutting down")? -} - -#[cfg(test)] -mod tests { - use assert_matches::assert_matches; - use pathfinder_common::macro_prelude::*; - use pathfinder_common::BlockNumber; - use serde_json::json; - - use super::*; - - /// # Important - /// - /// `BlockId` parsing is tested in - /// [`get_block`][crate::rpc::v02::method::get_block::tests::parsing] - /// and is not repeated here. - #[rstest::rstest] - #[case::positional(json!(["1", "2", "latest"]))] - #[case::named(json!({"contract_address": "0x1", "key": "0x2", "block_id": "latest"}))] - fn parsing(#[case] input: serde_json::Value) { - let expected = GetStorageAtInput { - contract_address: contract_address!("0x1"), - key: storage_address!("0x2"), - block_id: BlockId::Latest, - }; - - let input = serde_json::from_value::(input).unwrap(); - - assert_eq!(input, expected); - } - - #[tokio::test] - async fn pending() { - let ctx = RpcContext::for_tests_with_pending().await; - let contract_address = contract_address_bytes!(b"pending contract 1 address"); - let key = storage_address_bytes!(b"pending storage key 0"); - let block_id = BlockId::Pending; - - let result = get_storage_at( - ctx, - GetStorageAtInput { - contract_address, - key, - block_id, - }, - ) - .await - .unwrap(); - - assert_eq!(result.0, storage_value_bytes!(b"pending storage value 0")); - } - - #[tokio::test] - async fn pending_falls_back_to_latest() { - let ctx = RpcContext::for_tests_with_pending().await; - let contract_address = contract_address_bytes!(b"contract 1"); - let key = storage_address_bytes!(b"storage addr 0"); - let block_id = BlockId::Pending; - - let result = get_storage_at( - ctx, - GetStorageAtInput { - contract_address, - key, - block_id, - }, - ) - .await - .unwrap(); - - assert_eq!(result.0, storage_value_bytes!(b"storage value 2")); - } - - #[tokio::test] - async fn pending_deployed_defaults_to_zero() { - let ctx = RpcContext::for_tests_with_pending().await; - // Contract is deployed in pending block, but has no storage values set. - let contract_address = contract_address_bytes!(b"pending contract 0 address"); - let key = storage_address_bytes!(b"non-existent"); - let block_id = BlockId::Pending; - - let result = get_storage_at( - ctx, - GetStorageAtInput { - contract_address, - key, - block_id, - }, - ) - .await - .unwrap(); - - assert_eq!(result.0, StorageValue::ZERO); - } - - #[tokio::test] - async fn latest() { - let ctx = RpcContext::for_tests_with_pending().await; - let contract_address = contract_address_bytes!(b"contract 1"); - let key = storage_address_bytes!(b"storage addr 0"); - let block_id = BlockId::Latest; - - let result = get_storage_at( - ctx, - GetStorageAtInput { - contract_address, - key, - block_id, - }, - ) - .await - .unwrap(); - - assert_eq!(result.0, storage_value_bytes!(b"storage value 2")); - } - - #[tokio::test] - async fn defaults_to_zero() { - let ctx = RpcContext::for_tests_with_pending().await; - let contract_address = contract_address_bytes!(b"contract 1"); - let key = storage_address_bytes!(b"non-existent"); - let block_id = BlockId::Latest; - - let result = get_storage_at( - ctx, - GetStorageAtInput { - contract_address, - key, - block_id, - }, - ) - .await - .unwrap(); - - assert_eq!(result.0, StorageValue::ZERO); - } - - #[tokio::test] - async fn by_hash() { - let ctx = RpcContext::for_tests_with_pending().await; - let contract_address = contract_address_bytes!(b"contract 1"); - let key = storage_address_bytes!(b"storage addr 0"); - let block_id = BlockId::Hash(block_hash_bytes!(b"block 1")); - - let result = get_storage_at( - ctx, - GetStorageAtInput { - contract_address, - key, - block_id, - }, - ) - .await - .unwrap(); - - assert_eq!(result.0, storage_value_bytes!(b"storage value 1")); - } - - #[tokio::test] - async fn by_number() { - let ctx = RpcContext::for_tests_with_pending().await; - let contract_address = contract_address_bytes!(b"contract 1"); - let key = storage_address_bytes!(b"storage addr 0"); - let block_id = BlockId::Number(BlockNumber::GENESIS + 1); - - let result = get_storage_at( - ctx, - GetStorageAtInput { - contract_address, - key, - block_id, - }, - ) - .await - .unwrap(); - - assert_eq!(result.0, storage_value_bytes!(b"storage value 1")); - } - - #[tokio::test] - async fn unknown_contract() { - let ctx = RpcContext::for_tests_with_pending().await; - let contract_address = contract_address_bytes!(b"non-existent"); - let key = storage_address_bytes!(b"storage addr 0"); - let block_id = BlockId::Latest; - - let result = get_storage_at( - ctx, - GetStorageAtInput { - contract_address, - key, - block_id, - }, - ) - .await; - - assert_matches!(result, Err(GetStorageAtError::ContractNotFound)); - } - - #[tokio::test] - async fn contract_is_unknown_before_deployment() { - let ctx = RpcContext::for_tests_with_pending().await; - let contract_address = contract_address_bytes!(b"contract 1"); - let key = storage_address_bytes!(b"storage addr 0"); - let block_id = BlockId::Hash(block_hash_bytes!(b"genesis")); - - let result = get_storage_at( - ctx, - GetStorageAtInput { - contract_address, - key, - block_id, - }, - ) - .await; - - assert_matches!(result, Err(GetStorageAtError::ContractNotFound)); - } - - #[tokio::test] - async fn block_not_found_by_number() { - let ctx = RpcContext::for_tests_with_pending().await; - let contract_address = contract_address_bytes!(b"contract 1"); - let key = storage_address_bytes!(b"storage addr 0"); - let block_id = BlockId::Number(BlockNumber::MAX); - - let result = get_storage_at( - ctx, - GetStorageAtInput { - contract_address, - key, - block_id, - }, - ) - .await; - - assert_matches!(result, Err(GetStorageAtError::BlockNotFound)); - } - - #[tokio::test] - async fn block_not_found_by_hash() { - let ctx = RpcContext::for_tests_with_pending().await; - let contract_address = contract_address_bytes!(b"contract 1"); - let key = storage_address_bytes!(b"storage addr 0"); - let block_id = BlockId::Hash(block_hash_bytes!(b"unknown")); - - let result = get_storage_at( - ctx, - GetStorageAtInput { - contract_address, - key, - block_id, - }, - ) - .await; - - assert_matches!(result, Err(GetStorageAtError::BlockNotFound)); - } -} diff --git a/crates/rpc/src/v02/method/get_transaction_by_block_id_and_index.rs b/crates/rpc/src/v02/method/get_transaction_by_block_id_and_index.rs deleted file mode 100644 index 0f65843548..0000000000 --- a/crates/rpc/src/v02/method/get_transaction_by_block_id_and_index.rs +++ /dev/null @@ -1,135 +0,0 @@ -use anyhow::Context; -use pathfinder_common::transaction::Transaction; -use pathfinder_common::{BlockId, TransactionIndex}; - -use crate::context::RpcContext; - -#[derive(serde::Deserialize, Debug, PartialEq, Eq)] -#[serde(deny_unknown_fields)] -pub struct GetTransactionByBlockIdAndIndexInput { - block_id: BlockId, - index: TransactionIndex, -} - -impl crate::dto::DeserializeForVersion for GetTransactionByBlockIdAndIndexInput { - fn deserialize(value: crate::dto::Value) -> Result { - value.deserialize_serde() - } -} - -crate::error::generate_rpc_error_subset!( - GetTransactionByBlockIdAndIndexError: BlockNotFound, - InvalidTxnIndex -); - -pub async fn get_transaction_by_block_id_and_index_impl( - context: RpcContext, - input: GetTransactionByBlockIdAndIndexInput, -) -> Result { - let index: usize = input - .index - .get() - .try_into() - .map_err(|_| GetTransactionByBlockIdAndIndexError::InvalidTxnIndex)?; - - let storage = context.storage.clone(); - let span = tracing::Span::current(); - - let jh = tokio::task::spawn_blocking(move || { - let _g = span.enter(); - let mut db = storage - .connection() - .context("Opening database connection")?; - - let db_tx = db.transaction().context("Creating database transaction")?; - - let block_id = match input.block_id { - BlockId::Pending => { - let result = context - .pending_data - .get(&db_tx) - .context("Querying pending dat")? - .block - .transactions - .get(index) - .cloned() - .ok_or(GetTransactionByBlockIdAndIndexError::InvalidTxnIndex); - return result; - } - other => other.try_into().expect("Only pending cast should fail"), - }; - - // Get the transaction from storage. - match db_tx - .transaction_at_block(block_id, index) - .context("Reading transaction from database")? - { - Some(transaction) => Ok(transaction), - None => { - // We now need to check whether it was the block hash or transaction index which - // were invalid. We do this by checking if the block exists - // at all. If no, then the block hash is invalid. If yes, then the index is - // invalid. - let block_exists = db_tx - .block_exists(block_id) - .context("Querying block existence")?; - if block_exists { - Err(GetTransactionByBlockIdAndIndexError::InvalidTxnIndex) - } else { - Err(GetTransactionByBlockIdAndIndexError::BlockNotFound) - } - } - } - }); - - jh.await.context("Database read panic or shutting down")? -} - -#[cfg(test)] -mod tests { - use pathfinder_common::macro_prelude::*; - - use super::*; - - mod parsing { - use serde_json::json; - - use super::*; - - #[test] - fn positional_args() { - let positional = json!([ - {"block_hash": "0xdeadbeef"}, - 1 - ]); - - let input = - serde_json::from_value::(positional).unwrap(); - assert_eq!( - input, - GetTransactionByBlockIdAndIndexInput { - block_id: BlockId::Hash(block_hash!("0xdeadbeef")), - index: TransactionIndex::new_or_panic(1), - } - ) - } - - #[test] - fn named_args() { - let named_args = json!({ - "block_id": {"block_hash": "0xdeadbeef"}, - "index": 1 - }); - - let input = - serde_json::from_value::(named_args).unwrap(); - assert_eq!( - input, - GetTransactionByBlockIdAndIndexInput { - block_id: BlockId::Hash(block_hash!("0xdeadbeef")), - index: TransactionIndex::new_or_panic(1), - } - ) - } - } -} diff --git a/crates/rpc/src/v02/method/get_transaction_by_hash.rs b/crates/rpc/src/v02/method/get_transaction_by_hash.rs deleted file mode 100644 index 8d14bd096c..0000000000 --- a/crates/rpc/src/v02/method/get_transaction_by_hash.rs +++ /dev/null @@ -1,96 +0,0 @@ -use anyhow::Context; -use pathfinder_common::transaction::Transaction; -use pathfinder_common::TransactionHash; - -use crate::context::RpcContext; - -#[derive(serde::Deserialize, Debug, PartialEq, Eq)] -#[serde(deny_unknown_fields)] -pub struct GetTransactionByHashInput { - transaction_hash: TransactionHash, -} - -impl crate::dto::DeserializeForVersion for GetTransactionByHashInput { - fn deserialize(value: crate::dto::Value) -> Result { - value.deserialize_serde() - } -} - -pub async fn get_transaction_by_hash_impl( - context: RpcContext, - input: GetTransactionByHashInput, -) -> anyhow::Result> { - let storage = context.storage.clone(); - let span = tracing::Span::current(); - - let jh = tokio::task::spawn_blocking(move || { - let _g = span.enter(); - let mut db = storage - .connection() - .context("Opening database connection")?; - - let db_tx = db.transaction().context("Creating database transaction")?; - - // Check pending transactions. - if let Some(tx) = context - .pending_data - .get(&db_tx) - .context("Querying pending data")? - .block - .transactions - .iter() - .find(|tx| tx.hash == input.transaction_hash) - .cloned() - { - return Ok(Some(tx)); - } - - // Get the transaction from storage. - db_tx - .transaction(input.transaction_hash) - .context("Reading transaction from database") - .map(|x| x.map(Into::into)) - }); - - jh.await.context("Database read panic or shutting down")? -} - -#[cfg(test)] -mod tests { - use pathfinder_common::macro_prelude::*; - - use super::*; - - mod parsing { - use serde_json::json; - - use super::*; - - #[test] - fn positional_args() { - let positional = json!(["0xdeadbeef"]); - - let input = serde_json::from_value::(positional).unwrap(); - assert_eq!( - input, - GetTransactionByHashInput { - transaction_hash: transaction_hash!("0xdeadbeef") - } - ) - } - - #[test] - fn named_args() { - let named_args = json!({ - "transaction_hash": "0xdeadbeef" - }); - let input = serde_json::from_value::(named_args).unwrap(); - assert_eq!( - input, - GetTransactionByHashInput { - transaction_hash: transaction_hash!("0xdeadbeef") - } - ) - } - } -} diff --git a/crates/rpc/src/v03.rs b/crates/rpc/src/v03.rs deleted file mode 100644 index 168c3043b5..0000000000 --- a/crates/rpc/src/v03.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod method; diff --git a/crates/rpc/src/v03/method.rs b/crates/rpc/src/v03/method.rs deleted file mode 100644 index c23775ac20..0000000000 --- a/crates/rpc/src/v03/method.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod get_events; -pub(crate) mod get_state_update; - -pub(crate) use get_events::get_events; -pub(crate) use get_state_update::get_state_update; diff --git a/crates/rpc/src/v03/method/get_events.rs b/crates/rpc/src/v03/method/get_events.rs deleted file mode 100644 index 8eb1853028..0000000000 --- a/crates/rpc/src/v03/method/get_events.rs +++ /dev/null @@ -1,1060 +0,0 @@ -use std::str::FromStr; - -use anyhow::Context; -use pathfinder_common::{BlockId, BlockNumber, ContractAddress, EventKey}; -use pathfinder_storage::{EventFilterError, EVENT_KEY_FILTER_LIMIT}; -use serde::Deserialize; -use starknet_gateway_types::reply::PendingBlock; -use tokio::task::JoinHandle; - -use crate::context::RpcContext; -use crate::method::get_events::EVENT_PAGE_SIZE_LIMIT; -use crate::pending::PendingData; - -#[derive(Debug)] -pub enum GetEventsError { - Internal(anyhow::Error), - Custom(anyhow::Error), - BlockNotFound, - PageSizeTooBig, - InvalidContinuationToken, - TooManyKeysInFilter { limit: usize, requested: usize }, -} - -impl From for GetEventsError { - fn from(e: anyhow::Error) -> Self { - Self::Internal(e) - } -} - -impl From for crate::error::ApplicationError { - fn from(e: GetEventsError) -> Self { - match e { - GetEventsError::Internal(internal) => Self::Internal(internal), - GetEventsError::Custom(internal) => Self::Custom(internal), - GetEventsError::BlockNotFound => Self::BlockNotFound, - GetEventsError::PageSizeTooBig => Self::PageSizeTooBig, - GetEventsError::InvalidContinuationToken => Self::InvalidContinuationToken, - GetEventsError::TooManyKeysInFilter { limit, requested } => { - Self::TooManyKeysInFilter { limit, requested } - } - } - } -} - -#[derive(serde::Deserialize, Debug, PartialEq, Eq)] -#[cfg_attr(test, derive(Clone))] -#[serde(deny_unknown_fields)] -pub struct GetEventsInput { - filter: EventFilter, -} - -impl crate::dto::DeserializeForVersion for GetEventsInput { - fn deserialize(value: crate::dto::Value) -> Result { - value.deserialize_serde() - } -} - -/// Contains event filter parameters passed to `starknet_getEvents`. -#[serde_with::skip_serializing_none] -#[derive(Default, Clone, Debug, Deserialize, PartialEq, Eq)] -#[serde(deny_unknown_fields)] -pub struct EventFilter { - #[serde(default)] - pub from_block: Option, - #[serde(default)] - pub to_block: Option, - #[serde(default)] - pub address: Option, - #[serde(default)] - pub keys: Vec>, - - // These are inlined here because serde flatten and deny_unknown_fields - // don't work together. - pub chunk_size: usize, - /// Offset, measured in events, which points to the requested chunk - #[serde(default)] - pub continuation_token: Option, -} - -/// Returns events matching the specified filter -pub async fn get_events( - context: RpcContext, - input: GetEventsInput, -) -> Result { - // The [Block::Pending] in ranges makes things quite complicated. This - // implementation splits the ranges into the following buckets: - // - // 1. pending : pending -> query pending only - // 2. pending : non-pending -> return empty result - // 3. non-pending : non-pending -> query db only - // 4. non-pending : pending -> query db and potentially append pending - // events - // - // The database query for 3 and 4 is combined into one step. - // - // 4 requires some additional logic to handle some edge cases: - // a) Query database - // b) if full page -> return page - // check if there are matching events in the pending block - // and return a continuation token for the pending block - // c) else if empty / partially full -> append events from start of pending - // if there are more pending events return a continuation token - // with the appropriate offset within the pending block - - use BlockId::*; - - let request = input.filter; - - let continuation_token = match &request.continuation_token { - Some(s) => Some( - s.parse::() - .map_err(|_| GetEventsError::InvalidContinuationToken)?, - ), - None => None, - }; - - if request.keys.len() > EVENT_KEY_FILTER_LIMIT { - return Err(GetEventsError::TooManyKeysInFilter { - limit: EVENT_KEY_FILTER_LIMIT, - requested: request.keys.len(), - }); - } - if request.chunk_size > EVENT_PAGE_SIZE_LIMIT { - return Err(GetEventsError::PageSizeTooBig); - } - - let storage = context.storage.clone(); - - // truncate empty key lists from the end of the key filter - let mut keys = request.keys.clone(); - if let Some(last_non_empty) = keys.iter().rposition(|keys| !keys.is_empty()) { - keys.truncate(last_non_empty + 1); - } - - // blocking task to perform database event query - let span = tracing::Span::current(); - let db_events: JoinHandle> = tokio::task::spawn_blocking(move || { - let _g = span.enter(); - let mut connection = storage - .connection() - .context("Opening database connection")?; - - let transaction = connection - .transaction() - .context("Creating database transaction")?; - - // Handle the trivial (1) and (2) cases. - match (&request.from_block, &request.to_block) { - (Some(Pending), non_pending) if *non_pending != Some(Pending) => { - return Ok(types::GetEventsResult { - events: Vec::new(), - continuation_token: None, - }); - } - (Some(Pending), Some(Pending)) => { - let pending = context - .pending_data - .get(&transaction) - .context("Querying pending data")?; - return get_pending_events(&request, &pending, continuation_token); - } - _ => {} - } - - let from_block = map_from_block_to_number(&transaction, request.from_block)?; - let to_block = map_to_block_to_number(&transaction, request.to_block)?; - - let (from_block, requested_offset) = match continuation_token { - Some(token) => token.start_block_and_offset(from_block)?, - None => (from_block, 0), - }; - - let filter = pathfinder_storage::EventFilter { - from_block, - to_block, - contract_address: request.address, - keys: keys.clone(), - page_size: request.chunk_size, - offset: requested_offset, - }; - - let page = transaction - .events( - &filter, - context.config.get_events_max_blocks_to_scan, - context.config.get_events_max_uncached_bloom_filters_to_load, - ) - .map_err(|e| match e { - EventFilterError::Internal(e) => GetEventsError::Internal(e), - EventFilterError::PageSizeTooSmall => GetEventsError::Custom(e.into()), - })?; - - let mut events = types::GetEventsResult { - events: page.events.into_iter().map(|e| e.into()).collect(), - continuation_token: page.continuation_token.map(|token| { - ContinuationToken { - block_number: token.block_number, - offset: token.offset, - } - .to_string() - }), - }; - - // Append pending data if required. - if events.continuation_token.is_none() && matches!(request.to_block, Some(Pending)) { - let pending = context - .pending_data - .get(&transaction) - .context("Querying pending data")?; - - if events.events.len() < request.chunk_size { - let amount = request.chunk_size - events.events.len(); - - let current_offset = match continuation_token { - Some(continuation_token) => { - continuation_token.offset_in_block(pending.number)? - } - None => 0, - }; - - let keys: Vec> = request - .keys - .into_iter() - .map(|keys| keys.into_iter().collect()) - .collect(); - - let is_last_page = append_pending_events( - &pending.block, - &mut events.events, - current_offset, - amount, - request.address, - keys, - ); - - events.continuation_token = if is_last_page { - None - } else { - let continuation_token = ContinuationToken { - block_number: pending.number, - offset: current_offset + amount, - }; - Some(continuation_token.to_string()) - }; - } else { - // We have a full page from the database, but there might be more pending - // events. Return a continuation token for the pending block. - events.continuation_token = Some( - ContinuationToken { - block_number: pending.number, - offset: 0, - } - .to_string(), - ); - } - } - - Ok(events) - }); - - db_events - .await - .context("Database read panic or shutting down")? -} - -// Handle the case when we're querying events exclusively from the pending -// block. -fn get_pending_events( - request: &EventFilter, - pending: &PendingData, - continuation_token: Option, -) -> Result { - let current_offset = match continuation_token { - Some(continuation_token) => continuation_token.offset_in_block(pending.number)?, - None => 0, - }; - - let keys: Vec> = request - .keys - .iter() - .map(|keys| keys.iter().copied().collect()) - .collect(); - - let mut events = Vec::new(); - - let is_last_page = append_pending_events( - &pending.block, - &mut events, - current_offset, - request.chunk_size, - request.address, - keys, - ); - - let continuation_token = if is_last_page { - None - } else { - Some( - ContinuationToken { - block_number: pending.number, - offset: current_offset + request.chunk_size, - } - .to_string(), - ) - }; - - Ok(types::GetEventsResult { - events, - continuation_token, - }) -} - -// Maps `to_block` BlockId to a block number which can be used by the events -// query. -// -// This block id specifies the upper end of the range, so pending/latest/None -// means there's no upper limit. -fn map_to_block_to_number( - tx: &pathfinder_storage::Transaction<'_>, - block: Option, -) -> Result, GetEventsError> { - use BlockId::*; - - match block { - Some(Hash(hash)) => { - let number = tx - .block_id(hash.into()) - .context("Querying block number")? - .ok_or(GetEventsError::BlockNotFound)? - .0; - - Ok(Some(number)) - } - Some(Number(number)) => Ok(Some(number)), - Some(Pending) | Some(Latest) | None => Ok(None), - } -} - -// Maps `from_block` BlockId to a block number which can be used by the events -// query. -// -// This block id specifies the lower end of the range, so pending/latest means -// a lower limit here. -fn map_from_block_to_number( - tx: &pathfinder_storage::Transaction<'_>, - block: Option, -) -> Result, GetEventsError> { - use BlockId::*; - - match block { - Some(Hash(hash)) => { - let number = tx - .block_id(hash.into()) - .context("Querying block number")? - .ok_or(GetEventsError::BlockNotFound)? - .0; - - Ok(Some(number)) - } - Some(Number(number)) => Ok(Some(number)), - Some(Pending) | Some(Latest) => { - let number = tx - .block_id(pathfinder_storage::BlockId::Latest) - .context("Querying latest block number")? - .ok_or(GetEventsError::BlockNotFound)? - .0; - Ok(Some(number)) - } - None => Ok(None), - } -} - -/// Append's pending events to `dst` based on the filter requirements and -/// returns true if this was the last pending data i.e. `is_last_page`. -fn append_pending_events( - pending_block: &PendingBlock, - dst: &mut Vec, - skip: usize, - amount: usize, - address: Option, - keys: Vec>, -) -> bool { - let original_len = dst.len(); - - let key_filter_is_empty = keys.iter().flatten().count() == 0; - - let pending_events = pending_block - .transaction_receipts - .iter() - .flat_map(|(receipt, events)| { - events - .iter() - .zip(std::iter::repeat(receipt.transaction_hash)) - }) - .filter(|(event, _)| match address { - Some(address) => event.from_address == address, - None => true, - }) - .filter(|(event, _)| { - if key_filter_is_empty { - return true; - } - - if event.keys.len() < keys.len() { - return false; - } - - event - .keys - .iter() - .zip(keys.iter()) - .all(|(key, filter)| filter.is_empty() || filter.contains(key)) - }) - .skip(skip) - // We need to take an extra event to determine is_last_page. - .take(amount + 1) - .map(|(event, tx_hash)| types::EmittedEvent { - data: event.data.clone(), - keys: event.keys.clone(), - from_address: event.from_address, - block_hash: None, - block_number: None, - transaction_hash: tx_hash, - }); - - dst.extend(pending_events); - let is_last_page = dst.len() <= (original_len + amount); - if !is_last_page { - dst.pop(); - } - - is_last_page -} - -#[derive(Clone, Copy, Debug, PartialEq)] -struct ContinuationToken { - block_number: BlockNumber, - offset: usize, -} - -impl FromStr for ContinuationToken { - type Err = ParseContinuationTokenError; - - fn from_str(s: &str) -> Result { - if let Some((block_number, offset)) = s.split_once('-') { - let block_number = block_number - .parse::() - .map_err(|_| ParseContinuationTokenError)?; - let offset = offset.parse().map_err(|_| ParseContinuationTokenError)?; - - let block_number = BlockNumber::new(block_number).ok_or(ParseContinuationTokenError)?; - - Ok(ContinuationToken { - block_number, - offset, - }) - } else { - Err(ParseContinuationTokenError) - } - } -} - -impl std::fmt::Display for ContinuationToken { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}-{}", self.block_number.get(), self.offset) - } -} - -impl ContinuationToken { - fn offset_in_block(&self, block_number: BlockNumber) -> Result { - use std::cmp::Ordering; - match Ord::cmp(&self.block_number, &block_number) { - Ordering::Equal => Ok(self.offset), - Ordering::Less => Ok(0), - Ordering::Greater => Err(GetEventsError::InvalidContinuationToken), - } - } - - fn start_block_and_offset( - &self, - from_block: Option, - ) -> Result<(Option, usize), GetEventsError> { - match from_block { - Some(from_block) => { - if from_block > self.block_number { - Err(GetEventsError::InvalidContinuationToken) - } else { - Ok((Some(self.block_number), self.offset)) - } - } - None => { - // from block was unspecified in filter, just use the value from the token - Ok((Some(self.block_number), self.offset)) - } - } - } -} - -#[derive(Debug, Eq, PartialEq)] -struct ParseContinuationTokenError; - -mod types { - use pathfinder_common::{ - BlockHash, - BlockNumber, - ContractAddress, - EventData, - EventKey, - TransactionHash, - }; - use serde::Serialize; - - /// Describes an emitted event returned by starknet_getEvents - #[derive(Clone, Debug, Serialize, PartialEq, Eq)] - #[serde(deny_unknown_fields)] - pub struct EmittedEvent { - pub data: Vec, - pub keys: Vec, - pub from_address: ContractAddress, - /// [`None`] for pending events. - pub block_hash: Option, - /// [`None`] for pending events. - pub block_number: Option, - pub transaction_hash: TransactionHash, - } - - impl From for EmittedEvent { - fn from(event: pathfinder_storage::EmittedEvent) -> Self { - Self { - data: event.data, - keys: event.keys, - from_address: event.from_address, - block_hash: Some(event.block_hash), - block_number: Some(event.block_number), - transaction_hash: event.transaction_hash, - } - } - } - - // Result type for starknet_getEvents - #[serde_with::skip_serializing_none] - #[derive(Clone, Debug, Serialize, PartialEq, Eq)] - #[serde(deny_unknown_fields)] - pub struct GetEventsResult { - pub events: Vec, - /// Offset, measured in events, which points to the chunk that follows - /// currently requested chunk (`events`) - pub continuation_token: Option, - } -} - -#[cfg(test)] -mod tests { - use pathfinder_common::macro_prelude::*; - use pathfinder_storage::test_utils; - use pretty_assertions_sorted::assert_eq; - use serde_json::json; - - use super::types::{EmittedEvent, GetEventsResult}; - use super::*; - - #[rstest::rstest] - #[case::positional_with_optionals(json!([{ - "from_block":{"block_number":0}, - "to_block":"latest", - "address":"0x1", - "keys":[["0x2"],[]], - "chunk_size":3, - "continuation_token":"4"}]), true - )] - #[case::named_with_optionals(json!({"filter":{ - "from_block":{"block_number":0}, - "to_block":"latest", - "address":"0x1","keys":[["0x2"],[]], - "chunk_size":3, - "continuation_token":"4"}}), true - )] - #[case::positional_without_optionals(json!([{"chunk_size":5}]), false)] - #[case::named_without_optionals(json!({"filter":{"chunk_size":5}}), false)] - fn parsing(#[case] input: serde_json::Value, #[case] with_optionals: bool) { - let filter = if with_optionals { - EventFilter { - from_block: Some(BlockId::Number(BlockNumber::new_or_panic(0))), - to_block: Some(BlockId::Latest), - address: Some(contract_address!("0x1")), - keys: vec![vec![event_key!("0x2")], vec![]], - chunk_size: 3, - continuation_token: Some("4".to_string()), - } - } else { - EventFilter { - chunk_size: 5, - ..Default::default() - } - }; - let expected = GetEventsInput { filter }; - - let input = serde_json::from_value::(input).unwrap(); - assert_eq!(input, expected); - } - - #[test] - fn continuation_token() { - use assert_matches::assert_matches; - - assert_matches!( - "1234".parse::(), - Err(ParseContinuationTokenError) - ); - assert_matches!( - "invalid".parse::(), - Err(ParseContinuationTokenError) - ); - assert_matches!( - "1234-5678-9012".parse::(), - Err(ParseContinuationTokenError) - ); - assert_matches!( - "-1234-5678".parse::(), - Err(ParseContinuationTokenError) - ); - - assert_eq!( - "1234-4567".parse::().unwrap(), - ContinuationToken { - block_number: BlockNumber::new_or_panic(1234), - offset: 4567 - } - ); - } - - fn setup() -> (RpcContext, Vec) { - let (storage, test_data) = test_utils::setup_test_storage(); - let events = test_data - .events - .into_iter() - .map(EmittedEvent::from) - .collect(); - let context = RpcContext::for_tests().with_storage(storage); - - (context, events) - } - - impl PartialEq for GetEventsError { - fn eq(&self, other: &Self) -> bool { - match (self, other) { - (Self::Internal(l), Self::Internal(r)) => l.to_string() == r.to_string(), - _ => core::mem::discriminant(self) == core::mem::discriminant(other), - } - } - } - - #[tokio::test] - async fn get_events_with_empty_filter() { - let (context, events) = setup(); - - let input = GetEventsInput { - filter: EventFilter { - chunk_size: test_utils::NUM_EVENTS, - ..Default::default() - }, - }; - let result = get_events(context, input).await.unwrap(); - - assert_eq!( - result, - GetEventsResult { - events, - continuation_token: None, - } - ); - } - - #[tokio::test] - async fn get_events_with_fully_specified_filter() { - let (context, events) = setup(); - - let expected_event = &events[1]; - let expected_result = GetEventsResult { - events: vec![expected_event.clone()], - continuation_token: None, - }; - let input = GetEventsInput { - filter: EventFilter { - from_block: Some(expected_event.block_number.unwrap().into()), - to_block: Some(expected_event.block_number.unwrap().into()), - address: Some(expected_event.from_address), - // we're using a key which is present in _all_ events - keys: vec![vec![], vec![event_key!("0xdeadbeef")]], - chunk_size: test_utils::NUM_EVENTS, - continuation_token: None, - }, - }; - let result = get_events(context.clone(), input.clone()).await.unwrap(); - assert_eq!(result, expected_result); - } - - #[tokio::test] - async fn get_events_by_block() { - let (context, events) = setup(); - - const BLOCK_NUMBER: usize = 2; - let input = GetEventsInput { - filter: EventFilter { - from_block: Some(BlockNumber::new_or_panic(BLOCK_NUMBER as u64).into()), - to_block: Some(BlockNumber::new_or_panic(BLOCK_NUMBER as u64).into()), - chunk_size: test_utils::NUM_EVENTS, - ..Default::default() - }, - }; - - let result = get_events(context, input).await.unwrap(); - - let expected_events = &events[test_utils::EVENTS_PER_BLOCK * BLOCK_NUMBER - ..test_utils::EVENTS_PER_BLOCK * (BLOCK_NUMBER + 1)]; - assert_eq!( - result, - GetEventsResult { - events: expected_events.to_vec(), - continuation_token: None, - } - ); - } - - #[tokio::test] - async fn get_events_from_latest_block() { - let (context, events) = setup(); - - const LATEST_BLOCK_NUMBER: usize = 3; - let input = GetEventsInput { - filter: EventFilter { - from_block: Some(BlockId::Latest), - to_block: Some(BlockId::Latest), - chunk_size: test_utils::NUM_EVENTS, - ..Default::default() - }, - }; - - let result = get_events(context, input).await.unwrap(); - - let expected_events = &events[test_utils::EVENTS_PER_BLOCK * LATEST_BLOCK_NUMBER - ..test_utils::EVENTS_PER_BLOCK * (LATEST_BLOCK_NUMBER + 1)]; - assert_eq!( - result, - GetEventsResult { - events: expected_events.to_vec(), - continuation_token: None, - } - ); - } - - #[tokio::test] - async fn get_events_with_invalid_page_size() { - let (context, _) = setup(); - - let input = GetEventsInput { - filter: EventFilter { - chunk_size: pathfinder_storage::EVENT_PAGE_SIZE_LIMIT + 1, - ..Default::default() - }, - }; - let error = get_events(context, input).await.unwrap_err(); - - assert_eq!(GetEventsError::PageSizeTooBig, error); - } - - #[tokio::test] - async fn get_events_with_too_many_keys_in_filter() { - let (context, _) = setup(); - - let limit = EVENT_KEY_FILTER_LIMIT; - - let keys = [vec![event_key!("01")]] - .iter() - .cloned() - .cycle() - .take(limit + 1) - .collect::>(); - - let input = GetEventsInput { - filter: EventFilter { - keys, - chunk_size: 10, - ..Default::default() - }, - }; - let error = get_events(context, input).await.unwrap_err(); - - assert_eq!( - GetEventsError::TooManyKeysInFilter { - limit, - requested: limit + 1 - }, - error - ); - } - - #[tokio::test] - async fn get_events_by_key_with_paging() { - let (context, events) = setup(); - - let expected_events = &events[27..33]; - let keys_for_expected_events: Vec> = - vec![expected_events.iter().map(|e| e.keys[0]).collect()]; - - let input = GetEventsInput { - filter: EventFilter { - keys: keys_for_expected_events.clone(), - chunk_size: 1, - ..Default::default() - }, - }; - let result = get_events(context.clone(), input).await.unwrap(); - assert_eq!( - result, - GetEventsResult { - events: expected_events[..1].to_vec(), - continuation_token: Some("0-1".to_string()), - } - ); - - let input = GetEventsInput { - filter: EventFilter { - keys: keys_for_expected_events.clone(), - chunk_size: 2, - continuation_token: Some("0-1".to_string()), - ..Default::default() - }, - }; - let result = get_events(context.clone(), input).await.unwrap(); - assert_eq!( - result, - GetEventsResult { - events: expected_events[1..3].to_vec(), - continuation_token: Some("3-0".to_string()), - } - ); - - let input = GetEventsInput { - filter: EventFilter { - keys: keys_for_expected_events.clone(), - chunk_size: 3, - continuation_token: Some("3-0".to_string()), - ..Default::default() - }, - }; - let result = get_events(context.clone(), input).await.unwrap(); - assert_eq!( - result, - GetEventsResult { - events: expected_events[3..].to_vec(), - continuation_token: None, - } - ); - - // nonexistent page - let input = GetEventsInput { - filter: EventFilter { - keys: keys_for_expected_events.clone(), - chunk_size: 1, - // Offset pointing to after the last event - continuation_token: Some("2-6".to_string()), - ..Default::default() - }, - }; - let result = get_events(context, input).await.unwrap(); - assert_eq!(result.events, &[]); - assert_eq!(result.continuation_token, None); - } - - mod pending { - use pretty_assertions_sorted::assert_eq; - - use super::*; - - #[tokio::test] - async fn backward_range() { - let context = RpcContext::for_tests_with_pending().await; - - let input = GetEventsInput { - filter: EventFilter { - from_block: Some(BlockId::Pending), - to_block: Some(BlockId::Latest), - chunk_size: 100, - ..Default::default() - }, - }; - let result = get_events(context, input).await.unwrap(); - assert!(result.events.is_empty()); - } - - #[tokio::test] - async fn all_events() { - let context = RpcContext::for_tests_with_pending().await; - - let mut input = GetEventsInput { - filter: EventFilter { - to_block: Some(BlockId::Latest), - chunk_size: 1024, - ..Default::default() - }, - }; - - let events = get_events(context.clone(), input.clone()).await.unwrap(); - assert_eq!(events.events.len(), 1); - - input.filter.from_block = Some(BlockId::Pending); - input.filter.to_block = Some(BlockId::Pending); - let pending_events = get_events(context.clone(), input.clone()).await.unwrap(); - assert_eq!(pending_events.events.len(), 3); - - input.filter.from_block = None; - let all_events = get_events(context.clone(), input.clone()).await.unwrap(); - - let expected = events - .events - .into_iter() - .chain(pending_events.events.into_iter()) - .collect::>(); - - assert_eq!(all_events.events, expected); - assert!(all_events.continuation_token.is_none()); - } - - #[tokio::test] - async fn paging() { - let context = RpcContext::for_tests_with_pending().await; - - let mut input = GetEventsInput { - filter: EventFilter { - to_block: Some(BlockId::Pending), - chunk_size: 1024, - ..Default::default() - }, - }; - - let all = get_events(context.clone(), input.clone()) - .await - .unwrap() - .events; - - // Check edge case where the page is full with events from the DB but this was - // the last page from the DB -- should continue from offset 0 of the - // pending block next time - input.filter.chunk_size = 1; - input.filter.continuation_token = None; - let result = get_events(context.clone(), input.clone()).await.unwrap(); - assert_eq!(result.events, &all[0..1]); - assert_eq!(result.continuation_token, Some("3-0".to_string())); - - // Page includes a DB event and an event from the pending block, but there are - // more pending events for the next page - input.filter.chunk_size = 2; - input.filter.continuation_token = None; - let result = get_events(context.clone(), input.clone()).await.unwrap(); - assert_eq!(result.events, &all[0..2]); - assert_eq!(result.continuation_token, Some("3-1".to_string())); - - input.filter.chunk_size = 1; - input.filter.continuation_token = result.continuation_token; - let result = get_events(context.clone(), input.clone()).await.unwrap(); - assert_eq!(result.events, &all[2..3]); - assert_eq!(result.continuation_token, Some("3-2".to_string())); - - input.filter.chunk_size = 100; // Only a single event remains though - input.filter.continuation_token = result.continuation_token; - let result = get_events(context.clone(), input.clone()).await.unwrap(); - assert_eq!(result.events, &all[3..4]); - assert_eq!(result.continuation_token, None); - - // continuing from a page that does exist, should return all events (even from - // pending) - input.filter.chunk_size = 123; - input.filter.continuation_token = Some("0-0".to_string()); - let result = get_events(context.clone(), input.clone()).await.unwrap(); - assert_eq!(result.events, all); - assert_eq!(result.continuation_token, None); - - // nonexistent page: offset too large - input.filter.chunk_size = 123; // Does not matter - input.filter.continuation_token = Some("3-3".to_string()); // Points to after the last event - let result = get_events(context.clone(), input.clone()).await.unwrap(); - assert_eq!(result.events, &[]); - assert_eq!(result.continuation_token, None); - - // nonexistent page: block number - input.filter.chunk_size = 123; // Does not matter - input.filter.continuation_token = Some("4-1".to_string()); // Points to after the last event - let error = get_events(context.clone(), input).await.unwrap_err(); - assert_eq!(error, GetEventsError::InvalidContinuationToken); - } - - #[tokio::test] - async fn paging_with_no_more_matching_events_in_pending() { - let context = RpcContext::for_tests_with_pending().await; - - let mut input = GetEventsInput { - filter: EventFilter { - from_block: None, - to_block: Some(BlockId::Pending), - address: None, - keys: vec![vec![ - event_key_bytes!(b"event 0 key"), - event_key_bytes!(b"pending key 2"), - ]], - chunk_size: 1024, - continuation_token: None, - }, - }; - - let all = get_events(context.clone(), input.clone()) - .await - .unwrap() - .events; - assert_eq!(all.len(), 2); - - // returns a continuation token if there are more matches in pending - input.filter.chunk_size = 1; - let result = get_events(context.clone(), input.clone()).await.unwrap(); - assert_eq!(result.events, &all[0..1]); - assert_eq!(result.continuation_token, Some("3-0".to_string())); - } - - #[tokio::test] - async fn key_matching() { - let context = RpcContext::for_tests_with_pending().await; - - let mut input = GetEventsInput { - filter: EventFilter { - from_block: Some(BlockId::Pending), - to_block: Some(BlockId::Pending), - address: None, - keys: vec![], - chunk_size: 1024, - continuation_token: None, - }, - }; - - let all = get_events(context.clone(), input.clone()) - .await - .unwrap() - .events; - assert_eq!(all.len(), 3); - - input.filter.keys = vec![vec![event_key_bytes!(b"pending key 2")]]; - let events = get_events(context.clone(), input.clone()) - .await - .unwrap() - .events; - assert_eq!(events, &all[2..3]); - - input.filter.keys = vec![vec![], vec![event_key_bytes!(b"second pending key")]]; - let events = get_events(context.clone(), input.clone()) - .await - .unwrap() - .events; - assert_eq!(events, &all[1..2]); - } - } -} diff --git a/crates/rpc/src/v03/method/get_state_update.rs b/crates/rpc/src/v03/method/get_state_update.rs deleted file mode 100644 index d0df8eb8f0..0000000000 --- a/crates/rpc/src/v03/method/get_state_update.rs +++ /dev/null @@ -1,598 +0,0 @@ -use anyhow::Context; -use pathfinder_common::BlockId; - -use crate::RpcContext; - -#[derive(serde::Deserialize, Debug, PartialEq, Eq)] -#[serde(deny_unknown_fields)] -pub struct GetStateUpdateInput { - block_id: BlockId, -} - -impl crate::dto::DeserializeForVersion for GetStateUpdateInput { - fn deserialize(value: crate::dto::Value) -> Result { - value.deserialize_serde() - } -} - -crate::error::generate_rpc_error_subset!(GetStateUpdateError: BlockNotFound); - -pub async fn get_state_update( - context: RpcContext, - input: GetStateUpdateInput, -) -> Result { - let storage = context.storage.clone(); - let span = tracing::Span::current(); - - let jh = tokio::task::spawn_blocking(move || { - let _g = span.enter(); - let mut db = storage - .connection() - .context("Opening database connection")?; - - let tx = db.transaction().context("Creating database transaction")?; - - if input.block_id.is_pending() { - let state_update = context - .pending_data - .get(&tx) - .context("Query pending data")? - .state_update; - - let state_update = (*state_update).clone(); - - return Ok(state_update.into()); - } - - let block_id = input - .block_id - .try_into() - .expect("Only pending cast should fail"); - - get_state_update_from_storage(&tx, block_id) - }); - - jh.await.context("Database read panic or shutting down")? -} - -fn get_state_update_from_storage( - tx: &pathfinder_storage::Transaction<'_>, - block: pathfinder_storage::BlockId, -) -> Result { - let state_update = tx - .state_update(block) - .context("Fetching state diff")? - .ok_or(GetStateUpdateError::BlockNotFound)?; - - Ok(state_update.into()) -} - -pub(crate) mod types { - use pathfinder_common::state_update::ContractClassUpdate; - use pathfinder_common::{ - BlockHash, - CasmHash, - ClassHash, - ContractAddress, - ContractNonce, - SierraHash, - StateCommitment, - StorageAddress, - StorageValue, - }; - use serde::Serialize; - use serde_with::skip_serializing_none; - - use crate::felt::{RpcFelt, RpcFelt251}; - - #[serde_with::serde_as] - #[skip_serializing_none] - #[derive(Clone, Debug, Serialize, PartialEq, Eq)] - #[cfg_attr(test, derive(serde::Deserialize))] - #[serde(deny_unknown_fields)] - pub struct StateUpdate { - /// None for `pending` - #[serde(default)] - #[serde_as(as = "Option")] - pub block_hash: Option, - /// None for `pending` - #[serde(default)] - #[serde_as(as = "Option")] - pub new_root: Option, - #[serde_as(as = "RpcFelt")] - pub old_root: StateCommitment, - pub state_diff: StateDiff, - } - - #[cfg(test)] - impl StateUpdate { - // Sorts its vectors so that they can be equated. - pub fn sort(&mut self) { - self.state_diff - .deployed_contracts - .sort_by_key(|x| x.address); - self.state_diff - .declared_classes - .sort_by_key(|x| x.class_hash); - self.state_diff - .replaced_classes - .sort_by_key(|x| x.contract_address); - self.state_diff.deprecated_declared_classes.sort(); - self.state_diff.nonces.sort_by_key(|x| x.contract_address); - self.state_diff.storage_diffs.sort_by_key(|x| x.address); - self.state_diff.storage_diffs.iter_mut().for_each(|x| { - x.storage_entries.sort_by_key(|x| x.key); - }); - } - } - - impl From for StateUpdate { - fn from(value: pathfinder_common::StateUpdate) -> Self { - let mut storage_diffs = Vec::new(); - let mut deployed_contracts = Vec::new(); - let mut replaced_classes = Vec::new(); - let mut nonces = Vec::new(); - - for (contract_address, update) in value.contract_updates { - if let Some(nonce) = update.nonce { - nonces.push(Nonce { - contract_address, - nonce, - }); - } - - match update.class { - Some(ContractClassUpdate::Deploy(class_hash)) => { - deployed_contracts.push(DeployedContract { - address: contract_address, - class_hash, - }) - } - Some(ContractClassUpdate::Replace(class_hash)) => { - replaced_classes.push(ReplacedClass { - contract_address, - class_hash, - }) - } - None => {} - } - - let storage_entries = update - .storage - .into_iter() - .map(|(key, value)| StorageEntry { key, value }) - .collect(); - - storage_diffs.push(StorageDiff { - address: contract_address, - storage_entries, - }); - } - - for (address, update) in value.system_contract_updates { - let storage_entries = update - .storage - .into_iter() - .map(|(key, value)| StorageEntry { key, value }) - .collect(); - - storage_diffs.push(StorageDiff { - address, - storage_entries, - }); - } - - let declared_classes = value - .declared_sierra_classes - .into_iter() - .map(|(class_hash, compiled_class_hash)| DeclaredSierraClass { - class_hash, - compiled_class_hash, - }) - .collect(); - - let deprecated_declared_classes = value.declared_cairo_classes.into_iter().collect(); - - let state_diff = StateDiff { - storage_diffs, - deprecated_declared_classes, - declared_classes, - deployed_contracts, - replaced_classes, - nonces, - }; - - let block_hash = match value.block_hash { - BlockHash::ZERO => None, - other => Some(other), - }; - - let new_root = match value.state_commitment { - StateCommitment::ZERO => None, - other => Some(other), - }; - - StateUpdate { - block_hash, - new_root, - old_root: value.parent_state_commitment, - state_diff, - } - } - } - - /// L2 state diff. - #[serde_with::serde_as] - #[derive(Clone, Debug, Serialize, PartialEq, Eq, Default)] - #[cfg_attr(test, derive(serde::Deserialize))] - #[serde(deny_unknown_fields)] - pub struct StateDiff { - pub storage_diffs: Vec, - #[serde_as(as = "Vec")] - pub deprecated_declared_classes: Vec, - pub declared_classes: Vec, - pub deployed_contracts: Vec, - pub replaced_classes: Vec, - pub nonces: Vec, - } - - impl From for StateDiff { - fn from(value: pathfinder_executor::types::StateDiff) -> Self { - Self { - storage_diffs: value - .storage_diffs - .into_iter() - .map(|(address, diff)| StorageDiff { - address, - storage_entries: diff.into_iter().map(Into::into).collect(), - }) - .collect(), - deprecated_declared_classes: value - .deprecated_declared_classes - .into_iter() - .collect(), - declared_classes: value.declared_classes.into_iter().map(Into::into).collect(), - deployed_contracts: value - .deployed_contracts - .into_iter() - .map(Into::into) - .collect(), - replaced_classes: value.replaced_classes.into_iter().map(Into::into).collect(), - nonces: value - .nonces - .into_iter() - .map(|(contract_address, nonce)| Nonce { - contract_address, - nonce, - }) - .collect(), - } - } - } - - /// L2 storage diff of a contract. - #[serde_with::serde_as] - #[derive(Clone, Debug, Serialize, PartialEq, Eq, PartialOrd, Ord)] - #[cfg_attr(test, derive(serde::Deserialize))] - #[serde(deny_unknown_fields)] - pub struct StorageDiff { - #[serde_as(as = "RpcFelt251")] - pub address: ContractAddress, - pub storage_entries: Vec, - } - - /// A key-value entry of a storage diff. - #[serde_with::serde_as] - #[derive(Clone, Debug, Serialize, PartialEq, Eq, PartialOrd, Ord)] - #[cfg_attr(test, derive(serde::Deserialize))] - #[serde(deny_unknown_fields)] - pub struct StorageEntry { - #[serde_as(as = "RpcFelt251")] - pub key: StorageAddress, - #[serde_as(as = "RpcFelt")] - pub value: StorageValue, - } - - impl From for StorageEntry { - fn from(d: starknet_gateway_types::reply::state_update::StorageDiff) -> Self { - Self { - key: d.key, - value: d.value, - } - } - } - - impl From for StorageEntry { - fn from(d: pathfinder_executor::types::StorageDiff) -> Self { - Self { - key: d.key, - value: d.value, - } - } - } - - /// L2 state diff declared Sierra class item. - #[serde_with::serde_as] - #[derive(Clone, Debug, Serialize, PartialEq, Eq)] - #[cfg_attr(test, derive(serde::Deserialize))] - #[serde(deny_unknown_fields)] - pub struct DeclaredSierraClass { - #[serde_as(as = "RpcFelt")] - pub class_hash: SierraHash, - #[serde_as(as = "RpcFelt")] - pub compiled_class_hash: CasmHash, - } - - impl From for DeclaredSierraClass { - fn from(d: pathfinder_executor::types::DeclaredSierraClass) -> Self { - Self { - class_hash: d.class_hash, - compiled_class_hash: d.compiled_class_hash, - } - } - } - - /// L2 state diff deployed contract item. - #[serde_with::serde_as] - #[derive(Clone, Debug, Serialize, PartialEq, Eq)] - #[cfg_attr(test, derive(serde::Deserialize))] - #[serde(deny_unknown_fields)] - pub struct DeployedContract { - #[serde_as(as = "RpcFelt251")] - pub address: ContractAddress, - #[serde_as(as = "RpcFelt")] - pub class_hash: ClassHash, - } - - impl From for DeployedContract { - fn from(d: pathfinder_executor::types::DeployedContract) -> Self { - Self { - address: d.address, - class_hash: d.class_hash, - } - } - } - - /// L2 state diff replaced class item. - #[serde_with::serde_as] - #[derive(Clone, Debug, Serialize, PartialEq, Eq)] - #[cfg_attr(test, derive(serde::Deserialize))] - #[serde(deny_unknown_fields)] - pub struct ReplacedClass { - #[serde_as(as = "RpcFelt251")] - pub contract_address: ContractAddress, - #[serde_as(as = "RpcFelt")] - pub class_hash: ClassHash, - } - - impl From for ReplacedClass { - fn from(d: pathfinder_executor::types::ReplacedClass) -> Self { - Self { - contract_address: d.contract_address, - class_hash: d.class_hash, - } - } - } - - /// L2 state diff nonce item. - #[serde_with::serde_as] - #[derive(Clone, Debug, Serialize, PartialEq, Eq)] - #[cfg_attr(test, derive(serde::Deserialize))] - #[serde(deny_unknown_fields)] - pub struct Nonce { - #[serde_as(as = "RpcFelt251")] - pub contract_address: ContractAddress, - #[serde_as(as = "RpcFelt")] - pub nonce: ContractNonce, - } - - #[cfg(test)] - mod tests { - use pathfinder_common::macro_prelude::*; - - use super::*; - - #[test] - fn receipt() { - let state_update = StateUpdate { - block_hash: Some(block_hash!("0xdeadbeef")), - new_root: Some(state_commitment!("0x1")), - old_root: state_commitment!("0x2"), - state_diff: StateDiff { - storage_diffs: vec![StorageDiff { - address: contract_address!("0xadc"), - storage_entries: vec![StorageEntry { - key: storage_address!("0xf0"), - value: storage_value!("0x55"), - }], - }], - deprecated_declared_classes: vec![class_hash!("0xcdef"), class_hash!("0xcdee")], - declared_classes: vec![DeclaredSierraClass { - class_hash: sierra_hash!("0xabcd"), - compiled_class_hash: casm_hash!("0xdcba"), - }], - deployed_contracts: vec![DeployedContract { - address: contract_address!("0xadd"), - class_hash: class_hash!("0xcdef"), - }], - replaced_classes: vec![ReplacedClass { - contract_address: contract_address!("0xcad"), - class_hash: class_hash!("0xdac"), - }], - nonces: vec![Nonce { - contract_address: contract_address!("0xca"), - nonce: contract_nonce!("0x404ce"), - }], - }, - }; - let data = vec![ - state_update.clone(), - StateUpdate { - block_hash: None, - ..state_update - }, - ]; - - let fixture = - include_str!("../../../fixtures/0.50.0/state_update.json").replace([' ', '\n'], ""); - - pretty_assertions_sorted::assert_eq!(serde_json::to_string(&data).unwrap(), fixture); - pretty_assertions_sorted::assert_eq!( - serde_json::from_str::>(&fixture).unwrap(), - data - ); - } - } -} - -#[cfg(test)] -mod tests { - use pathfinder_common::macro_prelude::*; - use pathfinder_common::BlockNumber; - use pathfinder_storage::fake::Block; - use serde_json::json; - - use super::types::StateUpdate; - use super::*; - - #[rstest::rstest] - #[case::pending_by_position(json!(["pending"]), BlockId::Pending)] - #[case::pending_by_name(json!({"block_id": "pending"}), BlockId::Pending)] - #[case::latest_by_position(json!(["latest"]), BlockId::Latest)] - #[case::latest_by_name(json!({"block_id": "latest"}), BlockId::Latest)] - #[case::number_by_position(json!([{"block_number":123}]), BlockNumber::new_or_panic(123).into())] - #[case::number_by_name(json!({"block_id": {"block_number":123}}), BlockNumber::new_or_panic(123).into())] - #[case::hash_by_position(json!([{"block_hash": "0xbeef"}]), block_hash!("0xbeef").into())] - #[case::hash_by_name(json!({"block_id": {"block_hash": "0xbeef"}}), block_hash!("0xbeef").into())] - fn input_parsing(#[case] input: serde_json::Value, #[case] block_id: BlockId) { - let input = serde_json::from_value::(input).unwrap(); - - let expected = GetStateUpdateInput { block_id }; - - assert_eq!(input, expected); - } - - /// Add some dummy state updates to the context for testing - fn context_with_state_updates() -> (Vec, RpcContext) { - let storage = pathfinder_storage::StorageBuilder::in_memory().unwrap(); - - let state_updates = pathfinder_storage::fake::with_n_blocks(&storage, 3) - .into_iter() - .map(|Block { state_update, .. }| state_update.into()) - .collect(); - - let context = RpcContext::for_tests().with_storage(storage); - - (state_updates, context) - } - - impl PartialEq for GetStateUpdateError { - fn eq(&self, other: &Self) -> bool { - match (self, other) { - (Self::Internal(l), Self::Internal(r)) => l.to_string() == r.to_string(), - _ => core::mem::discriminant(self) == core::mem::discriminant(other), - } - } - } - - /// Compares the sorted state updates. - fn sort_assert_eq(mut left: StateUpdate, mut right: StateUpdate) { - left.sort(); - right.sort(); - - pretty_assertions_sorted::assert_eq!(left, right); - } - - #[tokio::test] - async fn latest() { - let (mut in_storage, ctx) = context_with_state_updates(); - - let result = get_state_update( - ctx, - GetStateUpdateInput { - block_id: BlockId::Latest, - }, - ) - .await - .unwrap(); - - sort_assert_eq(result, in_storage.pop().unwrap()); - } - - #[tokio::test] - async fn by_number() { - let (in_storage, ctx) = context_with_state_updates(); - - let result = get_state_update( - ctx, - GetStateUpdateInput { - block_id: BlockId::Number(BlockNumber::GENESIS), - }, - ) - .await - .unwrap(); - - sort_assert_eq(result, in_storage[0].clone()); - } - - #[tokio::test] - async fn by_hash() { - let (in_storage, ctx) = context_with_state_updates(); - - let result = get_state_update( - ctx, - GetStateUpdateInput { - block_id: BlockId::Hash(in_storage[1].block_hash.unwrap()), - }, - ) - .await - .unwrap(); - - sort_assert_eq(result, in_storage[1].clone()); - } - - #[tokio::test] - async fn not_found_by_number() { - let (_in_storage, ctx) = context_with_state_updates(); - - let result = get_state_update( - ctx, - GetStateUpdateInput { - block_id: BlockId::Number(BlockNumber::MAX), - }, - ) - .await; - - assert_eq!(result, Err(GetStateUpdateError::BlockNotFound)); - } - - #[tokio::test] - async fn not_found_by_hash() { - let (_in_storage, ctx) = context_with_state_updates(); - - let result = get_state_update( - ctx, - GetStateUpdateInput { - block_id: BlockId::Hash(block_hash_bytes!(b"non-existent")), - }, - ) - .await; - - assert_eq!(result, Err(GetStateUpdateError::BlockNotFound)); - } - - #[tokio::test] - async fn pending() { - let context = RpcContext::for_tests_with_pending().await; - let input = GetStateUpdateInput { - block_id: BlockId::Pending, - }; - - let expected = context.pending_data.get_unchecked().state_update; - let expected = (*expected).clone().into(); - - let result = get_state_update(context, input).await.unwrap(); - - sort_assert_eq(result, expected); - } -} diff --git a/crates/rpc/src/v06.rs b/crates/rpc/src/v06.rs index f043acde32..7ec1a861f7 100644 --- a/crates/rpc/src/v06.rs +++ b/crates/rpc/src/v06.rs @@ -30,7 +30,7 @@ pub fn register_routes() -> RpcRouterBuilder { .register("starknet_getBlockWithTxHashes" , method::get_block_with_tx_hashes) .register("starknet_getBlockWithTxs" , method::get_block_with_txs) .register("starknet_getTransactionByBlockIdAndIndex" , method::get_transaction_by_block_id_and_index) - .register("starknet_getTransactionByHash" , method::get_transaction_by_hash) + .register("starknet_getTransactionByHash" , crate::method::get_transaction_by_hash) .register("starknet_getTransactionReceipt" , method::get_transaction_receipt) .register("starknet_simulateTransactions" , method::simulate_transactions) .register("starknet_specVersion" , || "0.6.0") diff --git a/crates/rpc/src/v06/method.rs b/crates/rpc/src/v06/method.rs index b4022e47f1..1887d76c1c 100644 --- a/crates/rpc/src/v06/method.rs +++ b/crates/rpc/src/v06/method.rs @@ -7,7 +7,6 @@ pub(crate) mod estimate_message_fee; mod get_block_with_tx_hashes; mod get_block_with_txs; mod get_transaction_by_block_id_and_index; -mod get_transaction_by_hash; pub(crate) mod get_transaction_receipt; mod get_transaction_status; pub(crate) mod simulate_transactions; @@ -24,7 +23,6 @@ pub(crate) use estimate_message_fee::estimate_message_fee; pub(crate) use get_block_with_tx_hashes::get_block_with_tx_hashes; pub(crate) use get_block_with_txs::get_block_with_txs; pub(crate) use get_transaction_by_block_id_and_index::get_transaction_by_block_id_and_index; -pub(crate) use get_transaction_by_hash::get_transaction_by_hash; pub(super) use get_transaction_receipt::get_transaction_receipt; pub(crate) use get_transaction_status::get_transaction_status; pub(crate) use simulate_transactions::simulate_transactions; diff --git a/crates/rpc/src/v06/method/add_declare_transaction.rs b/crates/rpc/src/v06/method/add_declare_transaction.rs index 7874ee9e66..8c08323b4b 100644 --- a/crates/rpc/src/v06/method/add_declare_transaction.rs +++ b/crates/rpc/src/v06/method/add_declare_transaction.rs @@ -9,7 +9,7 @@ use starknet_gateway_types::request::add_transaction::{ use crate::context::RpcContext; use crate::felt::RpcFelt; -use crate::v02::types::request::BroadcastedDeclareTransaction; +use crate::types::request::BroadcastedDeclareTransaction; #[derive(Debug)] pub enum AddDeclareTransactionError { @@ -291,13 +291,13 @@ mod tests { }; use super::*; - use crate::v02::types::request::{ + use crate::types::request::{ BroadcastedDeclareTransaction, BroadcastedDeclareTransactionV1, BroadcastedDeclareTransactionV2, BroadcastedDeclareTransactionV3, }; - use crate::v02::types::{ + use crate::types::{ CairoContractClass, ContractClass, DataAvailabilityMode, @@ -354,7 +354,7 @@ mod tests { use super::super::*; use crate::dto::serialize::{self, SerializeForVersion}; - use crate::v02::types::request::BroadcastedDeclareTransactionV1; + use crate::types::request::BroadcastedDeclareTransactionV1; fn test_declare_txn() -> Transaction { Transaction::Declare(BroadcastedDeclareTransaction::V1( @@ -448,7 +448,7 @@ mod tests { use serde_json::json; use super::super::*; - use crate::v02::types::request::BroadcastedDeclareTransactionV2; + use crate::types::request::BroadcastedDeclareTransactionV2; fn test_declare_txn() -> Transaction { Transaction::Declare(BroadcastedDeclareTransaction::V2( diff --git a/crates/rpc/src/v06/method/add_deploy_account_transaction.rs b/crates/rpc/src/v06/method/add_deploy_account_transaction.rs index 206f51baa4..4d4839b3e9 100644 --- a/crates/rpc/src/v06/method/add_deploy_account_transaction.rs +++ b/crates/rpc/src/v06/method/add_deploy_account_transaction.rs @@ -4,7 +4,7 @@ use starknet_gateway_types::error::{KnownStarknetErrorCode, SequencerError}; use crate::context::RpcContext; use crate::felt::{RpcFelt, RpcFelt251}; -use crate::v02::types::request::{ +use crate::types::request::{ BroadcastedDeployAccountTransaction, BroadcastedDeployAccountTransactionV1, }; @@ -218,8 +218,8 @@ mod tests { use super::*; use crate::dto::serialize::{self, SerializeForVersion}; - use crate::v02::types::request::BroadcastedDeployAccountTransactionV3; - use crate::v02::types::{DataAvailabilityMode, ResourceBound, ResourceBounds}; + use crate::types::request::BroadcastedDeployAccountTransactionV3; + use crate::types::{DataAvailabilityMode, ResourceBound, ResourceBounds}; const INPUT_JSON: &str = r#"{ "max_fee": "0xbf391377813", diff --git a/crates/rpc/src/v06/method/add_invoke_transaction.rs b/crates/rpc/src/v06/method/add_invoke_transaction.rs index ae0b6e7def..a1c480ff5f 100644 --- a/crates/rpc/src/v06/method/add_invoke_transaction.rs +++ b/crates/rpc/src/v06/method/add_invoke_transaction.rs @@ -4,7 +4,7 @@ use starknet_gateway_types::error::SequencerError; use crate::context::RpcContext; use crate::felt::RpcFelt; -use crate::v02::types::request::BroadcastedInvokeTransaction; +use crate::types::request::BroadcastedInvokeTransaction; #[derive(serde::Deserialize, Debug, PartialEq, Eq)] #[serde(tag = "type")] @@ -191,8 +191,8 @@ mod tests { use pathfinder_common::{ResourceAmount, ResourcePricePerUnit, Tip, TransactionVersion}; use super::*; - use crate::v02::types::request::BroadcastedInvokeTransactionV1; - use crate::v02::types::{DataAvailabilityMode, ResourceBound, ResourceBounds}; + use crate::types::request::BroadcastedInvokeTransactionV1; + use crate::types::{DataAvailabilityMode, ResourceBound, ResourceBounds}; fn test_invoke_txn() -> Transaction { Transaction::Invoke(BroadcastedInvokeTransaction::V1( @@ -331,7 +331,7 @@ mod tests { #[tokio::test] #[ignore = "gateway 429"] async fn duplicate_transaction() { - use crate::v02::types::request::BroadcastedInvokeTransactionV1; + use crate::types::request::BroadcastedInvokeTransactionV1; let context = RpcContext::for_tests(); let input = BroadcastedInvokeTransactionV1 { @@ -372,7 +372,7 @@ mod tests { #[ignore = "gateway 429"] // https://external.integration.starknet.io/feeder_gateway/get_transaction?transactionHash=0x41906f1c314cca5f43170ea75d3b1904196a10101190d2b12a41cc61cfd17c async fn duplicate_v3_transaction() { - use crate::v02::types::request::BroadcastedInvokeTransactionV3; + use crate::types::request::BroadcastedInvokeTransactionV3; let context = RpcContext::for_tests_on(pathfinder_common::Chain::SepoliaIntegration); let input = BroadcastedInvokeTransactionV3 { diff --git a/crates/rpc/src/v06/method/estimate_fee.rs b/crates/rpc/src/v06/method/estimate_fee.rs index ad899f55e4..fc24bcec86 100644 --- a/crates/rpc/src/v06/method/estimate_fee.rs +++ b/crates/rpc/src/v06/method/estimate_fee.rs @@ -5,7 +5,7 @@ use serde_with::serde_as; use crate::context::RpcContext; use crate::error::ApplicationError; -use crate::v02::types::request::BroadcastedTransaction; +use crate::types::request::BroadcastedTransaction; use crate::v06::types::PriceUnit; #[derive(serde::Deserialize, Debug, PartialEq, Eq)] @@ -230,7 +230,7 @@ pub(crate) mod tests { }; use super::*; - use crate::v02::types::request::BroadcastedInvokeTransaction; + use crate::types::request::BroadcastedInvokeTransaction; mod parsing { use serde_json::json; @@ -239,7 +239,7 @@ pub(crate) mod tests { fn test_invoke_txn() -> BroadcastedTransaction { BroadcastedTransaction::Invoke(BroadcastedInvokeTransaction::V1( - crate::v02::types::request::BroadcastedInvokeTransactionV1 { + crate::types::request::BroadcastedInvokeTransactionV1 { version: TransactionVersion::ONE_WITH_QUERY_VERSION, max_fee: Fee(felt!("0x6")), signature: vec![TransactionSignatureElem(felt!("0x7"))], @@ -319,14 +319,14 @@ pub(crate) mod tests { use starknet_gateway_types::reply::{GasPrices, L1DataAvailabilityMode, PendingBlock}; use super::*; - use crate::v02::types::request::{ + use crate::types::request::{ BroadcastedDeclareTransaction, BroadcastedDeclareTransactionV2, BroadcastedInvokeTransactionV0, BroadcastedInvokeTransactionV1, BroadcastedInvokeTransactionV3, }; - use crate::v02::types::{ + use crate::types::{ ContractClass, DataAvailabilityMode, ResourceBounds, diff --git a/crates/rpc/src/v06/method/get_block_with_tx_hashes.rs b/crates/rpc/src/v06/method/get_block_with_tx_hashes.rs index 22f393c9a1..b40f5f9150 100644 --- a/crates/rpc/src/v06/method/get_block_with_tx_hashes.rs +++ b/crates/rpc/src/v06/method/get_block_with_tx_hashes.rs @@ -3,7 +3,7 @@ use pathfinder_common::BlockId; use serde::Deserialize; use crate::context::RpcContext; -use crate::v02::types::reply::BlockStatus; +use crate::types::reply::BlockStatus; #[derive(Deserialize, Debug, PartialEq, Eq)] #[cfg_attr(test, derive(Copy, Clone))] @@ -79,7 +79,7 @@ mod types { use pathfinder_common::{BlockHeader, TransactionHash}; use serde::Serialize; - use crate::v02::types::reply::BlockStatus; + use crate::types::reply::BlockStatus; /// L2 Block as returned by the RPC API. #[derive(Clone, Debug, Serialize, PartialEq, Eq)] diff --git a/crates/rpc/src/v06/method/get_block_with_txs.rs b/crates/rpc/src/v06/method/get_block_with_txs.rs index 714c61988a..634b155025 100644 --- a/crates/rpc/src/v06/method/get_block_with_txs.rs +++ b/crates/rpc/src/v06/method/get_block_with_txs.rs @@ -3,7 +3,7 @@ use pathfinder_common::{BlockId, BlockNumber}; use serde::Deserialize; use crate::context::RpcContext; -use crate::v02::types::reply::BlockStatus; +use crate::types::reply::BlockStatus; use crate::v06::types::TransactionWithHash; #[derive(Deserialize, Debug, PartialEq, Eq)] @@ -95,7 +95,7 @@ mod types { use serde::Serialize; use serde_with::{serde_as, skip_serializing_none}; - use crate::v02::types::reply::BlockStatus; + use crate::types::reply::BlockStatus; use crate::v06::types::TransactionWithHash; /// L2 Block as returned by the RPC API. diff --git a/crates/rpc/src/v06/method/get_transaction_by_block_id_and_index.rs b/crates/rpc/src/v06/method/get_transaction_by_block_id_and_index.rs index bc4a7d7a1e..f9bc501f2f 100644 --- a/crates/rpc/src/v06/method/get_transaction_by_block_id_and_index.rs +++ b/crates/rpc/src/v06/method/get_transaction_by_block_id_and_index.rs @@ -1,14 +1,14 @@ use crate::context::RpcContext; -use crate::v02::method::get_transaction_by_block_id_and_index::{ - get_transaction_by_block_id_and_index_impl, +use crate::method::get_transaction_by_block_id_and_index::{ + get_transaction_by_block_id_and_index as get_transaction_by_block_id_and_index_impl, GetTransactionByBlockIdAndIndexError, - GetTransactionByBlockIdAndIndexInput, + Input, }; use crate::v06::types::TransactionWithHash; pub async fn get_transaction_by_block_id_and_index( context: RpcContext, - input: GetTransactionByBlockIdAndIndexInput, + input: Input, ) -> Result { get_transaction_by_block_id_and_index_impl(context, input) .await diff --git a/crates/rpc/src/v06/method/get_transaction_by_hash.rs b/crates/rpc/src/v06/method/get_transaction_by_hash.rs deleted file mode 100644 index 346d6eeb27..0000000000 --- a/crates/rpc/src/v06/method/get_transaction_by_hash.rs +++ /dev/null @@ -1,15 +0,0 @@ -use crate::context::RpcContext; -use crate::v02::method::get_transaction_by_hash as v02_get_transaction_by_hash; -use crate::v06::types::TransactionWithHash; - -crate::error::generate_rpc_error_subset!(GetTransactionByHashError: TxnHashNotFound); - -pub async fn get_transaction_by_hash( - context: RpcContext, - input: v02_get_transaction_by_hash::GetTransactionByHashInput, -) -> Result { - v02_get_transaction_by_hash::get_transaction_by_hash_impl(context, input) - .await? - .map(Into::into) - .ok_or(GetTransactionByHashError::TxnHashNotFound) -} diff --git a/crates/rpc/src/v06/method/get_transaction_receipt.rs b/crates/rpc/src/v06/method/get_transaction_receipt.rs index 926f7733ae..eb5f2f6788 100644 --- a/crates/rpc/src/v06/method/get_transaction_receipt.rs +++ b/crates/rpc/src/v06/method/get_transaction_receipt.rs @@ -111,7 +111,7 @@ pub mod types { use serde_with::serde_as; use crate::felt::{RpcFelt, RpcFelt251}; - use crate::v02::types::reply::BlockStatus; + use crate::types::reply::BlockStatus; use crate::v06::types::PriceUnit; /// L2 transaction receipt as returned by the RPC API. diff --git a/crates/rpc/src/v06/method/simulate_transactions.rs b/crates/rpc/src/v06/method/simulate_transactions.rs index 8c5963f53b..f464e0f3e2 100644 --- a/crates/rpc/src/v06/method/simulate_transactions.rs +++ b/crates/rpc/src/v06/method/simulate_transactions.rs @@ -8,7 +8,7 @@ use serde::{Deserialize, Serialize}; use self::dto::SimulatedTransaction; use crate::context::RpcContext; use crate::executor::ExecutionStateError; -use crate::v02::types::request::BroadcastedTransaction; +use crate::types::request::BroadcastedTransaction; #[derive(Deserialize, Debug)] #[serde(deny_unknown_fields)] @@ -183,7 +183,7 @@ pub mod dto { use super::*; use crate::felt::RpcFelt; - use crate::v03::method::get_state_update::types::StateDiff; + use crate::method::get_state_update::types::StateDiff; use crate::v06::method::call::FunctionCall; use crate::v06::types::PriceUnit; @@ -803,12 +803,9 @@ pub(crate) mod tests { }; use super::*; - use crate::v02::types::request::{ - BroadcastedDeclareTransaction, - BroadcastedDeclareTransactionV1, - }; - use crate::v02::types::ContractClass; - use crate::v03::method::get_state_update::types::{DeployedContract, Nonce, StateDiff}; + use crate::method::get_state_update::types::{DeployedContract, Nonce, StateDiff}; + use crate::types::request::{BroadcastedDeclareTransaction, BroadcastedDeclareTransactionV1}; + use crate::types::ContractClass; use crate::v06::method::call::FunctionCall; use crate::v06::types::PriceUnit; @@ -992,7 +989,7 @@ pub(crate) mod tests { const DECLARE_GAS_CONSUMED: u64 = 2225; use super::dto::*; - use crate::v03::method::get_state_update::types::{StorageDiff, StorageEntry}; + use crate::method::get_state_update::types::{StorageDiff, StorageEntry}; pretty_assertions_sorted::assert_eq!( result, @@ -1127,14 +1124,14 @@ pub(crate) mod tests { }; use super::*; - use crate::v02::types::request::{ + use crate::types::request::{ BroadcastedDeclareTransactionV2, BroadcastedInvokeTransaction, BroadcastedInvokeTransactionV1, BroadcastedInvokeTransactionV3, BroadcastedTransaction, }; - use crate::v02::types::{ResourceBound, ResourceBounds}; + use crate::types::{ResourceBound, ResourceBounds}; pub fn declare(account_contract_address: ContractAddress) -> BroadcastedTransaction { let contract_class = ContractClass::from_definition_bytes(SIERRA_DEFINITION) @@ -1230,8 +1227,8 @@ pub(crate) mod tests { tip: Tip(0), paymaster_data: vec![], account_deployment_data: vec![], - nonce_data_availability_mode: crate::v02::types::DataAvailabilityMode::L1, - fee_data_availability_mode: crate::v02::types::DataAvailabilityMode::L1, + nonce_data_availability_mode: crate::types::DataAvailabilityMode::L1, + fee_data_availability_mode: crate::types::DataAvailabilityMode::L1, sender_address: account_contract_address, calldata: vec![ CallParam(*DEPLOYED_CONTRACT_ADDRESS.get()), @@ -1252,7 +1249,7 @@ pub(crate) mod tests { use super::dto::*; use super::*; - use crate::v03::method::get_state_update::types::{ + use crate::method::get_state_update::types::{ DeclaredSierraClass, StorageDiff, StorageEntry, diff --git a/crates/rpc/src/v06/method/syncing.rs b/crates/rpc/src/v06/method/syncing.rs index 459f51be1c..3475fbf084 100644 --- a/crates/rpc/src/v06/method/syncing.rs +++ b/crates/rpc/src/v06/method/syncing.rs @@ -9,7 +9,7 @@ pub async fn syncing(context: RpcContext) -> Result // Scoped so I don't have to think too hard about mutex guard drop semantics. let value = { context.sync_status.status.read().await.clone() }; - use crate::v02::types::syncing::Syncing; + use crate::types::syncing::Syncing; let value = match value { Syncing::False(_) => SyncingOutput::False, Syncing::Status(status) => { @@ -110,7 +110,7 @@ mod tests { async fn syncing() { use pathfinder_common::BlockNumber; - use crate::v02::types::syncing::{NumberedBlock, Status as V2Status, Syncing as V2Syncing}; + use crate::types::syncing::{NumberedBlock, Status as V2Status, Syncing as V2Syncing}; let status = V2Syncing::Status(V2Status { starting: NumberedBlock::from(("aabb", 1)), @@ -138,7 +138,7 @@ mod tests { #[tokio::test] async fn not_syncing() { - let status = crate::v02::types::syncing::Syncing::False(false); + let status = crate::types::syncing::Syncing::False(false); let context = RpcContext::for_tests(); *context.sync_status.status.write().await = status; diff --git a/crates/rpc/src/v07/dto/receipt.rs b/crates/rpc/src/v07/dto/receipt.rs index d24a2fd008..b123469e0c 100644 --- a/crates/rpc/src/v07/dto/receipt.rs +++ b/crates/rpc/src/v07/dto/receipt.rs @@ -2,7 +2,7 @@ use pathfinder_common::prelude::*; use pathfinder_serde::H256AsNoLeadingZerosHexStr; use serde::Serialize; -use crate::v02::types::reply::BlockStatus; +use crate::types::reply::BlockStatus; use crate::v06::method::get_transaction_receipt::types as v06; use crate::PendingData; From f838582827130b08bca76aca552cff60d991f6b4 Mon Sep 17 00:00:00 2001 From: Krisztian Kovacs Date: Thu, 21 Nov 2024 09:14:15 +0100 Subject: [PATCH 274/282] fix(rpc/subscribe_transaction_status): return CALL_ON_PENDING error for pending block_id --- crates/rpc/src/method/subscribe_transaction_status.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/rpc/src/method/subscribe_transaction_status.rs b/crates/rpc/src/method/subscribe_transaction_status.rs index 184bf8d6e2..372396ca08 100644 --- a/crates/rpc/src/method/subscribe_transaction_status.rs +++ b/crates/rpc/src/method/subscribe_transaction_status.rs @@ -152,7 +152,7 @@ impl RpcSubscriptionFlow for SubscribeTransactionStatus { let db = conn.transaction().map_err(RpcError::InternalError)?; let first_block = db .block_number(first_block.try_into().map_err(|_| { - RpcError::InvalidParams("block cannot be pending".to_string()) + RpcError::ApplicationError(ApplicationError::CallOnPending) })?) .map_err(RpcError::InternalError)?; let l1_block_number = From de449920af74d3d4db374008b55cebe3052e5f43 Mon Sep 17 00:00:00 2001 From: Krisztian Kovacs Date: Thu, 21 Nov 2024 13:00:12 +0100 Subject: [PATCH 275/282] feat(rpc/v08): bump supported version to 0.8.0-rc1 --- crates/rpc/src/v08.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/rpc/src/v08.rs b/crates/rpc/src/v08.rs index 308150301e..9106c9134d 100644 --- a/crates/rpc/src/v08.rs +++ b/crates/rpc/src/v08.rs @@ -38,7 +38,7 @@ pub fn register_routes() -> RpcRouterBuilder { .register("starknet_subscribePendingTransactions", SubscribePendingTransactions) .register("starknet_subscribeEvents", SubscribeEvents) .register("starknet_subscribeTransactionStatus", SubscribeTransactionStatus) - .register("starknet_specVersion", || "0.8.0-rc0") + .register("starknet_specVersion", || "0.8.0-rc1") .register("starknet_syncing", crate::method::syncing) .register("starknet_traceBlockTransactions", crate::method::trace_block_transactions) .register("starknet_traceTransaction", crate::method::trace_transaction) From acbbab852d5f35c5283ed680c95292e07b9fdee8 Mon Sep 17 00:00:00 2001 From: Krisztian Kovacs Date: Thu, 21 Nov 2024 13:01:23 +0100 Subject: [PATCH 276/282] chore: update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 347fea1fc3..f90cf0af53 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add `pathfinder_getClassProof` endpoint to retrieve the Merkle proof of any class hash in the class trie. - add `process_start_time_seconds` metric showing the unix timestamp when the process started. - `--log-output-json` CLI option has been added to output the Pathfinder log in line-delimited JSON. +- Preliminary support has been added for the new JSON-RPC 0.8.0-rc1 specification. ### Changed From 39bc855e08eae6eb1879688b7095cc4f95e36429 Mon Sep 17 00:00:00 2001 From: Krisztian Kovacs Date: Thu, 21 Nov 2024 15:32:49 +0100 Subject: [PATCH 277/282] chore(README): add JSON-RPC 0.8 API --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 45c983589b..10b08410e0 100644 --- a/README.md +++ b/README.md @@ -275,11 +275,12 @@ This can be used to interact with a custom Starknet gateway, or to use a gateway You can interact with Starknet using the JSON-RPC API. Pathfinder supports the official Starknet RPC API and in addition supplements this with its own pathfinder specific extensions such as `pathfinder_getProof`. -Currently, pathfinder supports `v0.6` and `v0.7` versions of the Starknet JSON-RPC specification. +Currently, pathfinder supports `v0.6`, `v0.7` and `v0.8` versions of the Starknet JSON-RPC specification. The `path` of the URL used to access the JSON-RPC server determines which version of the API is served: - the `v0.6.0` API is exposed on the `/rpc/v0_6` path via HTTP and on `/ws/rpc/v0_6` via Websocket - the `v0.7.0` API is exposed on the `/rpc/v0_7` path via HTTP and on `/ws/rpc/v0_7` via Websocket +- the `v0.8.0-rc1` API is exposed on the `/rpc/v0_8` path via both HTTP and Websocket - the pathfinder extension API is exposed on `/rpc/pathfinder/v0.1` and `/rpc/pathfinder/v0_1` via HTTP and `/ws/rpc/pathfinder/v0_1` via Websocket. Version of the API, which is served on the root (`/`) path via HTTP and on `/ws` via Websocket, can be configured via the pathfinder parameter `--rpc.root-version` (or the `RPC_ROOT_VERSION` environment variable). From cc8bed743e7bfbabfe6845b2a0ad118f7b4df46b Mon Sep 17 00:00:00 2001 From: Krisztian Kovacs Date: Thu, 21 Nov 2024 15:33:01 +0100 Subject: [PATCH 278/282] chore(CHANGELOG): add 0.15.0 release --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f90cf0af53..2ce8a0092d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ More expansive patch notes and explanations may be found in the specific [pathfi The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## Unreleased +## [0.15.0] - 2024-11-21 ### Added From b047d48eb6aed8847949a45a5995037edeca0fa8 Mon Sep 17 00:00:00 2001 From: Krisztian Kovacs Date: Thu, 21 Nov 2024 15:32:37 +0100 Subject: [PATCH 279/282] chore: bump version to 0.15.0 --- Cargo.lock | 46 ++++++++++++++++++------------------- Cargo.toml | 2 +- crates/load-test/Cargo.lock | 2 +- 3 files changed, 25 insertions(+), 25 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 618817e08a..b71f607243 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4704,7 +4704,7 @@ checksum = "42012b0f064e01aa58b545fe3727f90f7dd4020f4a3ea735b50344965f5a57e9" [[package]] name = "gateway-test-utils" -version = "0.14.4" +version = "0.15.0" dependencies = [ "reqwest", "serde_json", @@ -6433,7 +6433,7 @@ dependencies = [ [[package]] name = "make-stream" -version = "0.14.4" +version = "0.15.0" dependencies = [ "tokio", "tokio-stream", @@ -7034,7 +7034,7 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] name = "p2p" -version = "0.14.4" +version = "0.15.0" dependencies = [ "anyhow", "async-trait", @@ -7076,7 +7076,7 @@ dependencies = [ [[package]] name = "p2p_proto" -version = "0.14.4" +version = "0.15.0" dependencies = [ "anyhow", "fake", @@ -7097,7 +7097,7 @@ dependencies = [ [[package]] name = "p2p_proto_derive" -version = "0.14.4" +version = "0.15.0" dependencies = [ "proc-macro2", "quote", @@ -7106,7 +7106,7 @@ dependencies = [ [[package]] name = "p2p_stream" -version = "0.14.4" +version = "0.15.0" dependencies = [ "anyhow", "async-trait", @@ -7235,7 +7235,7 @@ checksum = "17359afc20d7ab31fdb42bb844c8b3bb1dabd7dcf7e68428492da7f16966fcef" [[package]] name = "pathfinder" -version = "0.14.4" +version = "0.15.0" dependencies = [ "anyhow", "assert_matches", @@ -7302,7 +7302,7 @@ dependencies = [ [[package]] name = "pathfinder-block-hashes" -version = "0.14.4" +version = "0.15.0" dependencies = [ "pathfinder-common", "pathfinder-crypto", @@ -7310,7 +7310,7 @@ dependencies = [ [[package]] name = "pathfinder-common" -version = "0.14.4" +version = "0.15.0" dependencies = [ "anyhow", "bitvec", @@ -7335,7 +7335,7 @@ dependencies = [ [[package]] name = "pathfinder-compiler" -version = "0.14.4" +version = "0.15.0" dependencies = [ "anyhow", "cairo-lang-starknet 1.0.0-alpha.6", @@ -7356,7 +7356,7 @@ dependencies = [ [[package]] name = "pathfinder-crypto" -version = "0.14.4" +version = "0.15.0" dependencies = [ "ark-ff 0.4.2", "assert_matches", @@ -7373,7 +7373,7 @@ dependencies = [ [[package]] name = "pathfinder-ethereum" -version = "0.14.4" +version = "0.15.0" dependencies = [ "alloy", "anyhow", @@ -7393,7 +7393,7 @@ dependencies = [ [[package]] name = "pathfinder-executor" -version = "0.14.4" +version = "0.15.0" dependencies = [ "anyhow", "blockifier", @@ -7413,7 +7413,7 @@ dependencies = [ [[package]] name = "pathfinder-merkle-tree" -version = "0.14.4" +version = "0.15.0" dependencies = [ "anyhow", "bitvec", @@ -7429,7 +7429,7 @@ dependencies = [ [[package]] name = "pathfinder-retry" -version = "0.14.4" +version = "0.15.0" dependencies = [ "tokio", "tokio-retry", @@ -7437,7 +7437,7 @@ dependencies = [ [[package]] name = "pathfinder-rpc" -version = "0.14.4" +version = "0.15.0" dependencies = [ "anyhow", "assert_matches", @@ -7490,7 +7490,7 @@ dependencies = [ [[package]] name = "pathfinder-serde" -version = "0.14.4" +version = "0.15.0" dependencies = [ "anyhow", "num-bigint 0.4.6", @@ -7505,7 +7505,7 @@ dependencies = [ [[package]] name = "pathfinder-storage" -version = "0.14.4" +version = "0.15.0" dependencies = [ "anyhow", "assert_matches", @@ -9350,7 +9350,7 @@ dependencies = [ [[package]] name = "starknet-gateway-client" -version = "0.14.4" +version = "0.15.0" dependencies = [ "anyhow", "assert_matches", @@ -9383,7 +9383,7 @@ dependencies = [ [[package]] name = "starknet-gateway-test-fixtures" -version = "0.14.4" +version = "0.15.0" dependencies = [ "pathfinder-common", "pathfinder-crypto", @@ -9391,7 +9391,7 @@ dependencies = [ [[package]] name = "starknet-gateway-types" -version = "0.14.4" +version = "0.15.0" dependencies = [ "anyhow", "assert_matches", @@ -9630,7 +9630,7 @@ dependencies = [ [[package]] name = "tagged" -version = "0.14.4" +version = "0.15.0" dependencies = [ "fake", "pretty_assertions_sorted", @@ -9639,7 +9639,7 @@ dependencies = [ [[package]] name = "tagged-debug-derive" -version = "0.14.4" +version = "0.15.0" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 6e919634ba..65d3db3c3b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,7 +40,7 @@ lto = true opt-level = 3 [workspace.package] -version = "0.14.4" +version = "0.15.0" edition = "2021" license = "MIT OR Apache-2.0" rust-version = "1.80" diff --git a/crates/load-test/Cargo.lock b/crates/load-test/Cargo.lock index 3eb7fd205d..27c5a35932 100644 --- a/crates/load-test/Cargo.lock +++ b/crates/load-test/Cargo.lock @@ -976,7 +976,7 @@ dependencies = [ [[package]] name = "pathfinder-crypto" -version = "0.14.4" +version = "0.15.0" dependencies = [ "bitvec", "fake", From ce3b225e277136ab1d06d1aec417e60769bac93e Mon Sep 17 00:00:00 2001 From: Ammar Arif Date: Wed, 4 Dec 2024 11:38:01 -0500 Subject: [PATCH 280/282] Bump `blockifier` and `starknet_api` --- Cargo.lock | 4 ++-- Cargo.toml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 14aefc2a5d..363d1a11fc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1549,7 +1549,7 @@ dependencies = [ [[package]] name = "blockifier" version = "0.8.0-rc.3" -source = "git+https://github.com/cartridge-gg/sequencer?branch=pathfinder-patch#f323bcc34e3d381515f0f59c632112f6d3050d6e" +source = "git+https://github.com/cartridge-gg/sequencer?branch=cartridge/pathfinder-patch-fork#8dd82dc4eaf1a6a7faefd818dcc6d026d5453fd0" dependencies = [ "anyhow", "ark-ec", @@ -9430,7 +9430,7 @@ dependencies = [ [[package]] name = "starknet_api" version = "0.13.0-rc.1" -source = "git+https://github.com/cartridge-gg/sequencer?branch=pathfinder-patch#f323bcc34e3d381515f0f59c632112f6d3050d6e" +source = "git+https://github.com/cartridge-gg/sequencer?branch=cartridge/pathfinder-patch-fork#8dd82dc4eaf1a6a7faefd818dcc6d026d5453fd0" dependencies = [ "bitvec", "cairo-lang-starknet-classes", diff --git a/Cargo.toml b/Cargo.toml index 5370fc0acb..aa91da0dca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -55,7 +55,7 @@ axum = "0.7.5" base64 = "0.13.1" bincode = "2.0.0-rc.3" bitvec = "1.0.1" -blockifier = { git = "https://github.com/cartridge-gg/sequencer", branch = "pathfinder-patch" } +blockifier = { git = "https://github.com/cartridge-gg/sequencer", branch = "cartridge/pathfinder-patch-fork" } bloomfilter = "1.0.12" bytes = "1.4.0" cached = "0.44.0" @@ -120,7 +120,7 @@ sha2 = "0.10.7" sha3 = "0.10" # This one needs to match the version used by blockifier # starknet_api = "=0.13.0-rc.1" -starknet_api = { git = "https://github.com/cartridge-gg/sequencer", branch = "pathfinder-patch" } +starknet_api = { git = "https://github.com/cartridge-gg/sequencer", branch = "cartridge/pathfinder-patch-fork" } # This one needs to match the version used by blockifier starknet-types-core = "=0.1.5" syn = "1.0" From ef73f2314843d40b418ee75510ceed3359a0503e Mon Sep 17 00:00:00 2001 From: Ammar Arif Date: Wed, 4 Dec 2024 11:41:20 -0500 Subject: [PATCH 281/282] fix merge conflicts --- crates/rpc/src/method/estimate_fee.rs | 62 +++++++++++++++------------ crates/storage/src/bloom.rs | 1 + 2 files changed, 35 insertions(+), 28 deletions(-) diff --git a/crates/rpc/src/method/estimate_fee.rs b/crates/rpc/src/method/estimate_fee.rs index 5486d9860a..9989659c7a 100644 --- a/crates/rpc/src/method/estimate_fee.rs +++ b/crates/rpc/src/method/estimate_fee.rs @@ -200,13 +200,9 @@ mod tests { use super::*; use crate::types::request::{ - BroadcastedDeclareTransaction, - BroadcastedDeclareTransactionV2, - BroadcastedInvokeTransaction, - BroadcastedInvokeTransactionV0, - BroadcastedInvokeTransactionV1, - BroadcastedInvokeTransactionV3, - BroadcastedTransaction, + BroadcastedDeclareTransaction, BroadcastedDeclareTransactionV2, + BroadcastedInvokeTransaction, BroadcastedInvokeTransactionV0, + BroadcastedInvokeTransactionV1, BroadcastedInvokeTransactionV3, BroadcastedTransaction, }; use crate::types::{ContractClass, DataAvailabilityMode, ResourceBounds, SierraContractClass}; @@ -493,50 +489,60 @@ mod tests { invoke_v0_transaction, invoke_v3_transaction, ], - simulation_flags: SimulationFlags(vec![]), + simulation_flags: vec![], block_id: BlockId::Number(last_block_header.number), }; let result = estimate_fee(context, input).await.unwrap(); let declare_expected = FeeEstimate { - gas_consumed: 878.into(), - gas_price: 1.into(), + l1_gas_consumed: 878.into(), + l1_gas_price: 1.into(), + l1_data_gas_consumed: 192.into(), + l1_data_gas_price: 2.into(), + l2_gas_consumed: 0.into(), + l2_gas_price: 0.into(), overall_fee: 1262.into(), unit: PriceUnit::Wei, - data_gas_consumed: 192.into(), - data_gas_price: 2.into(), }; let deploy_expected = FeeEstimate { - gas_consumed: 16.into(), - gas_price: 1.into(), + l1_gas_consumed: 16.into(), + l1_gas_price: 1.into(), + l1_data_gas_consumed: 224.into(), + l1_data_gas_price: 2.into(), + l2_gas_consumed: 0.into(), + l2_gas_price: 0.into(), overall_fee: 464.into(), unit: PriceUnit::Wei, - data_gas_consumed: 224.into(), - data_gas_price: 2.into(), }; let invoke_expected = FeeEstimate { - gas_consumed: 12.into(), - gas_price: 1.into(), + l1_gas_consumed: 12.into(), + l1_gas_price: 1.into(), + l1_data_gas_consumed: 128.into(), + l1_data_gas_price: 2.into(), + l2_gas_consumed: 0.into(), + l2_gas_price: 0.into(), overall_fee: 268.into(), unit: PriceUnit::Wei, - data_gas_consumed: 128.into(), - data_gas_price: 2.into(), }; let invoke_v0_expected = FeeEstimate { - gas_consumed: 10.into(), - gas_price: 1.into(), + l1_gas_consumed: 10.into(), + l1_gas_price: 1.into(), + l1_data_gas_consumed: 128.into(), + l1_data_gas_price: 2.into(), + l2_gas_consumed: 0.into(), + l2_gas_price: 0.into(), overall_fee: 266.into(), unit: PriceUnit::Wei, - data_gas_consumed: 128.into(), - data_gas_price: 2.into(), }; let invoke_v3_expected = FeeEstimate { - gas_consumed: 12.into(), + l1_gas_consumed: 12.into(), // STRK gas price is 2 - gas_price: 2.into(), + l1_gas_price: 2.into(), + l1_data_gas_consumed: 128.into(), + l1_data_gas_price: 2.into(), + l2_gas_consumed: 0.into(), + l2_gas_price: 0.into(), overall_fee: 280.into(), unit: PriceUnit::Fri, - data_gas_consumed: 128.into(), - data_gas_price: 2.into(), }; assert_eq!( result, diff --git a/crates/storage/src/bloom.rs b/crates/storage/src/bloom.rs index d272d64a41..8d64410023 100644 --- a/crates/storage/src/bloom.rs +++ b/crates/storage/src/bloom.rs @@ -418,6 +418,7 @@ mod tests { use super::*; const KEY: Felt = felt!("0x0218b538681900fad5a0b2ffe1d6781c0c3f14df5d32071ace0bdc9d46cb69ea"); + #[allow(dead_code)] const KEY1: Felt = felt!("0x0218b538681900fad5a0b2ffe1d6781c0c3f14df5d32071ace0bdc9d46cb69eb"); const KEY_NOT_IN_FILTER: Felt = felt!("0x0218b538681900fad5a0b2ffe1d6781c0c3f14df5d32071ace0bdc9d46cb69ec"); From e959f6212419bb2cb01f0d21da93516ce3128a1f Mon Sep 17 00:00:00 2001 From: Ammar Arif Date: Wed, 4 Dec 2024 11:42:48 -0500 Subject: [PATCH 282/282] fmt --- crates/rpc/src/method/estimate_fee.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/crates/rpc/src/method/estimate_fee.rs b/crates/rpc/src/method/estimate_fee.rs index 9989659c7a..1e80d1c1f5 100644 --- a/crates/rpc/src/method/estimate_fee.rs +++ b/crates/rpc/src/method/estimate_fee.rs @@ -200,9 +200,13 @@ mod tests { use super::*; use crate::types::request::{ - BroadcastedDeclareTransaction, BroadcastedDeclareTransactionV2, - BroadcastedInvokeTransaction, BroadcastedInvokeTransactionV0, - BroadcastedInvokeTransactionV1, BroadcastedInvokeTransactionV3, BroadcastedTransaction, + BroadcastedDeclareTransaction, + BroadcastedDeclareTransactionV2, + BroadcastedInvokeTransaction, + BroadcastedInvokeTransactionV0, + BroadcastedInvokeTransactionV1, + BroadcastedInvokeTransactionV3, + BroadcastedTransaction, }; use crate::types::{ContractClass, DataAvailabilityMode, ResourceBounds, SierraContractClass};

vMkTGmrVIn;r=Nde8GP1Dq>?8%UQe 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öq`*9cNj<83uNe1acQrcKKnwndH;U_F><%o>Dl2k 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�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 z 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�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