From 24dfc49b7663563cb817734a603c74aec4a021f8 Mon Sep 17 00:00:00 2001 From: nils-mathieu Date: Thu, 19 Oct 2023 17:43:24 +0200 Subject: [PATCH 01/16] intial JSON-RPC implementation --- .vscode/settings.json | 10 +- Cargo.lock | 2 + crates/client/deoxys/Cargo.toml | 5 +- .../deoxys/src/client/json_rpc/error.rs | 0 .../client/deoxys/src/client/json_rpc/mod.rs | 198 ++++++++++++++++++ .../deoxys/src/client/json_rpc/request.rs | 42 ++++ .../deoxys/src/client/json_rpc/transport.rs | 42 ++++ crates/client/deoxys/src/client/mod.rs | 61 ++++++ crates/client/deoxys/src/lib.rs | 1 + 9 files changed, 358 insertions(+), 3 deletions(-) create mode 100644 crates/client/deoxys/src/client/json_rpc/error.rs create mode 100644 crates/client/deoxys/src/client/json_rpc/mod.rs create mode 100644 crates/client/deoxys/src/client/json_rpc/request.rs create mode 100644 crates/client/deoxys/src/client/json_rpc/transport.rs create mode 100644 crates/client/deoxys/src/client/mod.rs diff --git a/.vscode/settings.json b/.vscode/settings.json index 2fc1a1b5fe..6ca6aa725c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,9 @@ { - "eslint.workingDirectories": ["tests"] -} + "eslint.workingDirectories": [ + "tests" + ], + "editor.formatOnSave": false, + "[rust]": { + "editor.formatOnSave": false + } +} \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index d28d0ca882..cf2cab9190 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6801,12 +6801,14 @@ dependencies = [ "mp-transactions", "pallet-starknet", "reqwest", + "serde", "serde_json", "sp-core 7.0.0", "starknet-core", "starknet-ff", "starknet_api 0.4.1 (git+https://github.com/keep-starknet-strange/starknet-api?branch=no_std-support-dc83f05)", "starknet_client", + "thiserror", "tokio", "validator", ] diff --git a/crates/client/deoxys/Cargo.toml b/crates/client/deoxys/Cargo.toml index 78473ed754..d274fbc678 100644 --- a/crates/client/deoxys/Cargo.toml +++ b/crates/client/deoxys/Cargo.toml @@ -16,6 +16,10 @@ repository = "https://github.com/KasarLabs/deoxys" targets = ["x86_64-unknown-linux-gnu"] [dependencies] +serde = { version = "1", features = ["derive"] } +serde_json = "1" +thiserror = "1" + blockifier = { workspace = true, default-features = false, features = [ "testing", ] } @@ -24,7 +28,6 @@ log = { version = "0.4.14" } mockito = { workspace = true } pallet-starknet = { workspace = true } reqwest = { workspace = true } -serde_json = { workspace = true } sp-core = { workspace = true, features = ["std"] } starknet-core = { workspace = true } starknet-ff = { workspace = true, default-features = false, features = [ diff --git a/crates/client/deoxys/src/client/json_rpc/error.rs b/crates/client/deoxys/src/client/json_rpc/error.rs new file mode 100644 index 0000000000..e69de29bb2 diff --git a/crates/client/deoxys/src/client/json_rpc/mod.rs b/crates/client/deoxys/src/client/json_rpc/mod.rs new file mode 100644 index 0000000000..def2ea460c --- /dev/null +++ b/crates/client/deoxys/src/client/json_rpc/mod.rs @@ -0,0 +1,198 @@ +//! Provides a JSON-RPC client. + +use std::sync::atomic::AtomicU64; +use std::fmt; + +mod request; +mod transport; + +pub use self::request::*; +pub use self::transport::*; + +/// An error that might be returned by the JSON-RPC protocol. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct JsonRpcErrorCode(pub i64); + +impl JsonRpcErrorCode { + /// The error code for an parsing error. + pub const PARSE_ERROR: Self = Self(-32700); + /// The error code for an invalid request error. + pub const INVALID_REQUEST: Self = Self(-32600); + /// The error code for an invalid method error. + pub const METHOD_NOT_FOUND: Self = Self(-32601); + /// The error code for an invalid params error. + pub const INVALID_PARAMS: Self = Self(-32602); + /// The error code for an internal error. + pub const INTERNAL_ERROR: Self = Self(-32603); +} + +impl fmt::Display for JsonRpcErrorCode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(&self.0, f) + } +} + +/// An error that might be returned by the JSON-RPC protocol. +#[derive(Debug, Clone, thiserror::Error)] +#[error("{message} (code {code})")] +pub struct JsonRpcError { + /// The code of the error. + pub code: JsonRpcErrorCode, + /// The message associated with the error. + pub message: Box, + /// An optional data field associated with the error. + pub data: Data, +} + +/// A JSON-RPC client that uses a [`Transport`] to communicate over the network. +pub struct JsonRpcClient { + /// The transport layer used to communicate over the network. + transport: T, + /// The next identifier to use for a request. + next_id: AtomicU64, +} + +/// An error that might occur when interacting with the [`JsonRpcClient`]. +#[derive(Debug, thiserror::Error)] +pub enum JsonRpcClientError { + /// The transport layer returned an error. + #[error("{0}")] + Transport(T), + /// The JSON-RPC protocol returned an error. + #[error("{0}")] + JsonRpc(JsonRpcError), + /// The received response was not a valid JSON-RPC response. + #[error("invalid JSON-RPC response")] + Protocol, +} + +impl JsonRpcClient { + /// Creates a new [`JsonRpcClient`] with the given transport layer. + pub fn new(transport: T) -> Self { + JsonRpcClient { transport, next_id: AtomicU64::new(0) } + } +} + +impl JsonRpcClient { + /// Sends a JSON-RPC request and returns the response. + pub async fn request(&self, request: R) -> Result> { + let id = self.next_id.fetch_add(1, std::sync::atomic::Ordering::Relaxed); + let body = create_json_rpc_request(R::METHOD, id, request.into_params()); + let response = self.transport.request(&body).await.map_err(JsonRpcClientError::Transport)?; + parse_json_rpc_repsonse(&response, id) + } +} + +/// Creates a JSON-RPC request from the given parameters. +fn create_json_rpc_request( + method: &'static str, + id: u64, + params: P, +) -> Vec { + #[derive(serde::Serialize)] + struct RequestType

{ + pub jsonrpc: &'static str, + pub method: &'static str, + pub params: P, + #[serde(serialize_with = "serialize_number_as_string")] + pub id: u64, + } + + let req = RequestType { jsonrpc: "2.0", method, params, id }; + + // If this panics because the serialized failed, it's a bug in the user code + // that comes from before this function is called. + serde_json::to_vec(&req).unwrap() +} + +/// Parses a JSON-RPC response. +fn parse_json_rpc_repsonse(data: &[u8], expected_id: u64) -> Result> +where + B: for<'a> serde::Deserialize<'a>, + E: for<'a> serde::Deserialize<'a>, +{ + #[derive(serde::Deserialize)] + struct ErrorType { + #[serde(deserialize_with = "i64_or_string")] + pub code: i64, + pub message: Box, + pub data: E, + } + + #[derive(serde::Deserialize)] + struct ResponseType { + pub jsonrpc: Box, + pub result: Option, + pub error: Option>, + #[serde(deserialize_with = "u64_or_string")] + pub id: u64, + } + + let response: ResponseType = serde_json::from_slice(data).map_err(|_| JsonRpcClientError::Protocol)?; + + if response.id != expected_id || &*response.jsonrpc != "2.0" { + return Err(JsonRpcClientError::Protocol); + } + + match (response.result, response.error) { + (Some(result), None) => Ok(result), + (None, Some(error)) => Err(JsonRpcClientError::JsonRpc(JsonRpcError { + code: JsonRpcErrorCode(error.code), + message: error.message, + data: error.data, + })), + _ => Err(JsonRpcClientError::Protocol), + } +} + +/// A deserializer function that accepts either a number or a string that represents a number. +fn i64_or_string<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result { + struct I64OrString; + + impl<'de> serde::de::Visitor<'de> for I64OrString { + type Value = i64; + + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str("a number or a string that represents a number") + } + + fn visit_i64(self, value: i64) -> Result { + Ok(value) + } + + fn visit_str(self, value: &str) -> Result { + value.parse().map_err(E::custom) + } + } + + deserializer.deserialize_any(I64OrString) +} + + +/// A deserializer function that accepts either a number or a string that represents a number. +fn u64_or_string<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result { + struct I64OrString; + + impl<'de> serde::de::Visitor<'de> for I64OrString { + type Value = u64; + + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str("a number or a string that represents a number") + } + + fn visit_u64(self, value: u64) -> Result { + Ok(value) + } + + fn visit_str(self, value: &str) -> Result { + value.parse().map_err(E::custom) + } + } + + deserializer.deserialize_any(I64OrString) +} + +/// Serializes number as strings of digits. +fn serialize_number_as_string(number: &u64, serializer: S) -> Result { + serializer.serialize_str(&number.to_string()) +} \ No newline at end of file diff --git a/crates/client/deoxys/src/client/json_rpc/request.rs b/crates/client/deoxys/src/client/json_rpc/request.rs new file mode 100644 index 0000000000..1c5cc5e471 --- /dev/null +++ b/crates/client/deoxys/src/client/json_rpc/request.rs @@ -0,0 +1,42 @@ +//! Defines the generic JSON-RPC [`Request`]. + +use starknet_core::types::requests::GetBlockWithTxHashesRequest; +use starknet_core::types::MaybePendingBlockWithTxHashes; + +/// Represents a JSON-RPC request. +pub trait Request { + /// The JSON-RPC method name. + const METHOD: &'static str; + + /// The response type expected for the request. + type Response: for<'de> serde::Deserialize<'de>; + + /// A type that, when serialized, represents the parameters of the request. + /// + /// This type must be directly serialized into an array. + type Params: serde::Serialize; + + /// Converts this [`Request`] into its parameters. + fn into_params(self) -> Self::Params; +} + +// ======================================================= +// IMPLEMENTATIONS OF `Request` FOR COMMON STARKNET TYPES +// ======================================================= + +macro_rules! impl_Request { + ($method:literal, $req:ty, $res:ty) => { + impl Request for $req { + const METHOD: &'static str = $method; + type Response = $res; + type Params = Self; + + #[inline(always)] + fn into_params(self) -> Self::Params { + self + } + } + }; +} + +impl_Request!("starknet_getBlockWithTxHashes", GetBlockWithTxHashesRequest, MaybePendingBlockWithTxHashes); \ No newline at end of file diff --git a/crates/client/deoxys/src/client/json_rpc/transport.rs b/crates/client/deoxys/src/client/json_rpc/transport.rs new file mode 100644 index 0000000000..9e5904400d --- /dev/null +++ b/crates/client/deoxys/src/client/json_rpc/transport.rs @@ -0,0 +1,42 @@ +//! Defines the [`Transport`] trait. + +use std::future::Future; + +/// Represents the transport layer used to communicate over the network. +pub trait Transport { + /// An error that might occur whilst communicating over the network. + type Error; + + /// The future type returned by [`Transport::request`]. + type Future: Future, Self::Error>>; + + /// Sends a request over the network and returns the response. + fn request(&self, body: &[u8]) -> Self::Future; +} + +impl Transport for &'_ T { + type Error = T::Error; + type Future = T::Future; + + fn request(&self, body: &[u8]) -> Self::Future { + (**self).request(body) + } +} + +impl Transport for Box { + type Error = T::Error; + type Future = T::Future; + + fn request(&self, body: &[u8]) -> Self::Future { + (**self).request(body) + } +} + +impl Transport for std::sync::Arc { + type Error = T::Error; + type Future = T::Future; + + fn request(&self, body: &[u8]) -> Self::Future { + (**self).request(body) + } +} diff --git a/crates/client/deoxys/src/client/mod.rs b/crates/client/deoxys/src/client/mod.rs new file mode 100644 index 0000000000..2627375e27 --- /dev/null +++ b/crates/client/deoxys/src/client/mod.rs @@ -0,0 +1,61 @@ +//! Defines a generic implementation of a Starknet JSON-RPC server client. + +use starknet_core::types::BlockId; +use starknet_core::types::requests::GetBlockWithTxHashesRequest; + +use self::json_rpc::{JsonRpcClient, JsonRpcClientError}; + +pub mod json_rpc; + +// For some reason, `starknet-core` does not define a type that requests the spec version. +// Let's do it ourselves. +struct SpecVersionRequest; + +impl self::json_rpc::Request for SpecVersionRequest { + const METHOD: &'static str = "starknet_specVersion"; + type Response = Box; + type Params = [(); 0]; + #[inline(always)] + fn into_params(self) -> Self::Params { + [(); 0] + } +} + +/// A generic implementation of a Starknet JSON-RPC client. +pub struct StarknetClient { + inner: JsonRpcClient, +} + +impl StarknetClient { + /// Creates a new [`StarknetClient`] with the given transport layer. + pub fn new(transport: T) -> Self { + Self { + inner: JsonRpcClient::new(transport), + } + } +} + +impl StarknetClient { + /// Requets the version of the Starknet JSON-RPC specification being used by the server. + pub async fn spec_version(&self) -> Result, JsonRpcClientError> { + self.inner.request(SpecVersionRequest).await + } + + /// Returns information about the block with the given ID. + /// + /// # Arguments + /// + /// * `block_id` - An identifier for the queried block. + /// + /// # Returns + /// + /// Information about the block. + /// + /// Transactions are not fully sent in the response. Instead, only their hashes are communicated. + pub async fn get_block_with_tx_hashes( + &self, + block_id: BlockId, + ) -> Result> { + self.inner.request(GetBlockWithTxHashesRequest { block_id }).await + } +} \ No newline at end of file diff --git a/crates/client/deoxys/src/lib.rs b/crates/client/deoxys/src/lib.rs index 84fb4017cd..d2d65b971c 100644 --- a/crates/client/deoxys/src/lib.rs +++ b/crates/client/deoxys/src/lib.rs @@ -24,6 +24,7 @@ use mp_hashers::pedersen::PedersenHasher; const NODE_VERSION: &str = "NODE VERSION"; mod transactions; +mod client; pub type BlockQueue = Arc>>; From f865d684f3e3af28650d1e7737393cee657b0fd0 Mon Sep 17 00:00:00 2001 From: nils-mathieu Date: Thu, 19 Oct 2023 18:44:59 +0200 Subject: [PATCH 02/16] feat(starknet_client): write an HTTPs transport layer for JSON-RPC --- Cargo.lock | 2 + crates/client/deoxys/Cargo.toml | 2 + .../client/deoxys/src/client/json_rpc/mod.rs | 3 + .../deoxys/src/client/json_rpc/transport.rs | 24 ++++-- .../src/client/json_rpc/transports/http.rs | 82 +++++++++++++++++++ 5 files changed, 105 insertions(+), 8 deletions(-) create mode 100644 crates/client/deoxys/src/client/json_rpc/transports/http.rs diff --git a/Cargo.lock b/Cargo.lock index cf2cab9190..965ef7df73 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6792,6 +6792,8 @@ dependencies = [ "blockifier", "env_logger 0.9.3", "hex", + "hyper", + "hyper-rustls 0.24.1", "log", "mockito", "mp-block", diff --git a/crates/client/deoxys/Cargo.toml b/crates/client/deoxys/Cargo.toml index d274fbc678..1c3e7ae308 100644 --- a/crates/client/deoxys/Cargo.toml +++ b/crates/client/deoxys/Cargo.toml @@ -19,6 +19,8 @@ targets = ["x86_64-unknown-linux-gnu"] serde = { version = "1", features = ["derive"] } serde_json = "1" thiserror = "1" +hyper = { version = "0.14", features = ["client", "http1"] } +hyper-rustls = "0.24" blockifier = { workspace = true, default-features = false, features = [ "testing", diff --git a/crates/client/deoxys/src/client/json_rpc/mod.rs b/crates/client/deoxys/src/client/json_rpc/mod.rs index def2ea460c..6fdd9e8112 100644 --- a/crates/client/deoxys/src/client/json_rpc/mod.rs +++ b/crates/client/deoxys/src/client/json_rpc/mod.rs @@ -6,6 +6,9 @@ use std::fmt; mod request; mod transport; +#[path = "transports/http.rs"] +mod http_transport; + pub use self::request::*; pub use self::transport::*; diff --git a/crates/client/deoxys/src/client/json_rpc/transport.rs b/crates/client/deoxys/src/client/json_rpc/transport.rs index 9e5904400d..3364d2d113 100644 --- a/crates/client/deoxys/src/client/json_rpc/transport.rs +++ b/crates/client/deoxys/src/client/json_rpc/transport.rs @@ -8,35 +8,43 @@ pub trait Transport { type Error; /// The future type returned by [`Transport::request`]. - type Future: Future, Self::Error>>; + type Future<'a>: Future, Self::Error>> + where + Self: 'a; /// Sends a request over the network and returns the response. - fn request(&self, body: &[u8]) -> Self::Future; + fn request(&self, body: &[u8]) -> Self::Future<'_>; } impl Transport for &'_ T { type Error = T::Error; - type Future = T::Future; + type Future<'a> = T::Future<'a> + where + Self: 'a; - fn request(&self, body: &[u8]) -> Self::Future { + fn request(&self, body: &[u8]) -> Self::Future<'_> { (**self).request(body) } } impl Transport for Box { type Error = T::Error; - type Future = T::Future; + type Future<'a> = T::Future<'a> + where + Self: 'a; - fn request(&self, body: &[u8]) -> Self::Future { + fn request(&self, body: &[u8]) -> Self::Future<'_> { (**self).request(body) } } impl Transport for std::sync::Arc { type Error = T::Error; - type Future = T::Future; + type Future<'a> = T::Future<'a> + where + Self: 'a; - fn request(&self, body: &[u8]) -> Self::Future { + fn request(&self, body: &[u8]) -> Self::Future<'_> { (**self).request(body) } } diff --git a/crates/client/deoxys/src/client/json_rpc/transports/http.rs b/crates/client/deoxys/src/client/json_rpc/transports/http.rs new file mode 100644 index 0000000000..81ebc4b54b --- /dev/null +++ b/crates/client/deoxys/src/client/json_rpc/transports/http.rs @@ -0,0 +1,82 @@ +//! An implementation of [`Transport`] that uses the [`hyper`] HTTP client. + +use std::future::Future; +use std::pin::Pin; + +use hyper::body::HttpBody; +use hyper::client::connect::Connect; +use hyper::Uri; + +/// The configuration for an [`HttpTransport`]. +#[derive(Debug, Clone)] +pub struct Config { + /// The URI to send requests to. + pub uri: Uri, +} + +/// An implementation of [`Transport`] that uses the [`hyper`] HTTP client. +pub struct Transport> { + client: hyper::Client, + uri: hyper::Uri, +} + +impl Transport { + /// Creates a new [`HttpTransport`] with the given configuration. + pub fn new(config: Config) -> Self { + let connector = hyper_rustls::HttpsConnectorBuilder::new() + .with_native_roots() + .https_or_http() + .enable_http1() + .wrap_connector(hyper::client::HttpConnector::new()); + + Self::with_connector(config.uri, connector) + } +} + +impl Transport { + /// Creates a new [`HttpTransport`] with the given connector. + pub fn with_connector(uri: Uri, connector: C) -> Self + where + C: Connect + Clone, + { + Self { uri, client: hyper::Client::builder().build(connector) } + } +} + +impl super::Transport for Transport +where + C: Connect + Clone + Send + Sync + 'static, +{ + type Error = hyper::Error; + + type Future<'a> = Pin, Self::Error>> + Send + Sync>> + where + Self: 'a; + + fn request(&self, body: &[u8]) -> Self::Future<'_> { + let body = hyper::Body::from(body.to_vec()); + + let fut = async move { + let mut response = self + .client + .request( + hyper::Request::builder() + .method(hyper::Method::POST) + .uri(self.uri.clone()) + .header("content-type", "application/json") + .body(body) + .unwrap(), + ) + .await?; + + let mut v = Vec::new(); + if let Some(chunk) = response.body_mut().data().await { + v.extend_from_slice(&chunk?); + } + + Ok(v) + }; + + Box::pin(fut) + } +} From 372656378e16db4877b410a371dfbfdb7b6380ff Mon Sep 17 00:00:00 2001 From: nils-mathieu Date: Thu, 19 Oct 2023 19:30:39 +0200 Subject: [PATCH 03/16] feat(starknet_client): implement all Starknet Methods --- .../deoxys/src/client/json_rpc/request.rs | 37 ++- .../src/client/json_rpc/transports/http.rs | 3 + crates/client/deoxys/src/client/mod.rs | 295 +++++++++++++++++- 3 files changed, 315 insertions(+), 20 deletions(-) diff --git a/crates/client/deoxys/src/client/json_rpc/request.rs b/crates/client/deoxys/src/client/json_rpc/request.rs index 1c5cc5e471..f77d6ffbab 100644 --- a/crates/client/deoxys/src/client/json_rpc/request.rs +++ b/crates/client/deoxys/src/client/json_rpc/request.rs @@ -1,7 +1,7 @@ //! Defines the generic JSON-RPC [`Request`]. -use starknet_core::types::requests::GetBlockWithTxHashesRequest; -use starknet_core::types::MaybePendingBlockWithTxHashes; +use starknet_core::types::requests::*; +use starknet_core::types::*; /// Represents a JSON-RPC request. pub trait Request { @@ -36,7 +36,38 @@ macro_rules! impl_Request { self } } + + impl<'a> Request for &'a $req { + const METHOD: &'static str = $method; + type Response = $res; + type Params = Self; + + #[inline(always)] + fn into_params(self) -> Self::Params { + self + } + } }; } -impl_Request!("starknet_getBlockWithTxHashes", GetBlockWithTxHashesRequest, MaybePendingBlockWithTxHashes); \ No newline at end of file +// https://github.com/starkware-libs/starknet-specs/blob/master/api/starknet_api_openrpc.json + +impl_Request!("starknet_getBlockWithTxHashes", GetBlockWithTxHashesRequest, MaybePendingBlockWithTxHashes); +impl_Request!("starknet_getBlockWithTxs", GetBlockWithTxsRequest, MaybePendingBlockWithTxs); +impl_Request!("starknet_getStateUpdate", GetStateUpdateRequest, MaybePendingStateUpdate); +impl_Request!("starknet_getStorageAt", GetStorageAtRequest, FieldElement); +impl_Request!("starknet_getTransactionByHash", GetTransactionByHashRequest, Transaction); +impl_Request!("starknet_getTransactionByBlockIdAndIndex", GetTransactionByBlockIdAndIndexRequest, Transaction); +impl_Request!("starknet_getTransactionReceipt", GetTransactionReceiptRequest, TransactionReceipt); +impl_Request!("starknet_getClass", GetClassRequest, ContractClass); +impl_Request!("starknet_getClassHashAt", GetClassHashAtRequest, FieldElement); +impl_Request!("starknet_getClassAt", GetClassAtRequest, ContractClass); +impl_Request!("starknet_getBlockTransactionCount", GetBlockTransactionCountRequest, u64); +impl_Request!("starknet_call", CallRequest, FieldElement); +impl_Request!("starknet_estimateFee", EstimateFeeRequest, FeeEstimate); +impl_Request!("starknet_estimateMessageFee", EstimateMessageFeeRequest, FeeEstimate); +impl_Request!("starknet_blockNumber", BlockNumberRequest, u64); +impl_Request!("starknet_blockHashAndNumber", BlockHashAndNumberRequest, BlockHashAndNumber); +impl_Request!("starknet_syncing", SyncingRequest, SyncStatusType); +impl_Request!("starknet_getEvents", GetEventsRequest, EventsPage); +impl_Request!("starknet_getNonce", GetNonceRequest, FieldElement); \ No newline at end of file diff --git a/crates/client/deoxys/src/client/json_rpc/transports/http.rs b/crates/client/deoxys/src/client/json_rpc/transports/http.rs index 81ebc4b54b..d86ff616c0 100644 --- a/crates/client/deoxys/src/client/json_rpc/transports/http.rs +++ b/crates/client/deoxys/src/client/json_rpc/transports/http.rs @@ -56,6 +56,9 @@ where fn request(&self, body: &[u8]) -> Self::Future<'_> { let body = hyper::Body::from(body.to_vec()); + // IMPROVE(nils-mathieu): + // Cookie to anyone capable of turning this into an actual + // non-boxed future. let fut = async move { let mut response = self .client diff --git a/crates/client/deoxys/src/client/mod.rs b/crates/client/deoxys/src/client/mod.rs index 2627375e27..9e52cffd3b 100644 --- a/crates/client/deoxys/src/client/mod.rs +++ b/crates/client/deoxys/src/client/mod.rs @@ -1,7 +1,7 @@ //! Defines a generic implementation of a Starknet JSON-RPC server client. -use starknet_core::types::BlockId; -use starknet_core::types::requests::GetBlockWithTxHashesRequest; +use starknet_core::types::requests::*; +use starknet_core::types::*; use self::json_rpc::{JsonRpcClient, JsonRpcClientError}; @@ -29,9 +29,7 @@ pub struct StarknetClient { impl StarknetClient { /// Creates a new [`StarknetClient`] with the given transport layer. pub fn new(transport: T) -> Self { - Self { - inner: JsonRpcClient::new(transport), - } + Self { inner: JsonRpcClient::new(transport) } } } @@ -41,21 +39,284 @@ impl StarknetClient { self.inner.request(SpecVersionRequest).await } - /// Returns information about the block with the given ID. - /// - /// # Arguments - /// - /// * `block_id` - An identifier for the queried block. - /// - /// # Returns - /// - /// Information about the block. - /// - /// Transactions are not fully sent in the response. Instead, only their hashes are communicated. + /// Returns information about the specified block. + /// + /// Transactions are not fully sent in the response. Instead, only their hashes are + /// communicated. pub async fn get_block_with_tx_hashes( &self, block_id: BlockId, ) -> Result> { self.inner.request(GetBlockWithTxHashesRequest { block_id }).await } -} \ No newline at end of file + + /// Returns information about the specified block. + /// + /// Transactions are fully sent in the response. If you do not need the full transactions, + /// consider using [`get_block_with_tx_hashes`](Self::get_block_with_tx_hashes) instead. + pub async fn get_block_with_txs( + &self, + block_id: BlockId, + ) -> Result> { + self.inner.request(GetBlockWithTxsRequest { block_id }).await + } + + /// Returns information about the result of executing the specified block. + pub async fn get_state_update( + &self, + block_id: BlockId, + ) -> Result> { + self.inner.request(GetStateUpdateRequest { block_id }).await + } + + /// Returns the value of the storage cell at the specified address. + /// + /// # Arguments + /// + /// - `contract_address`: The address of the contract to read from. + /// + /// - `key`: The key of the storage cell to read within the given contract. + /// + /// - `block_id`: The block to read from. + pub async fn get_storage_at( + &self, + contract_address: FieldElement, + key: FieldElement, + block_id: BlockId, + ) -> Result> { + self.inner.request(GetStorageAtRequest { contract_address, key, block_id }).await + } + + // TODO: + // Include the `starknet_getTransactionStatus` method here. For some reason it's not + // defined by `starknet-core`. + + /// Returns information about the specified transaction. + pub async fn get_transaction_by_hash( + &self, + transaction_hash: FieldElement, + ) -> Result> { + self.inner.request(GetTransactionByHashRequest { transaction_hash }).await + } + + /// Returns information about the transaction at the specified index in the specified block. + pub async fn get_transaction_by_block_id_and_index( + &self, + block_id: BlockId, + index: u64, + ) -> Result> { + self.inner.request(GetTransactionByBlockIdAndIndexRequest { block_id, index }).await + } + + /// Returns the transaction receipt for the specified transaction. + pub async fn get_transaction_receipt( + &self, + transaction_hash: FieldElement, + ) -> Result> { + self.inner.request(GetTransactionReceiptRequest { transaction_hash }).await + } + + /// Returns the contract definition for the specified class. + /// + /// # Arguments + /// + /// - `block_id`: The block to read from. + /// + /// - `class_hash`: The hash of the requested contract class. + pub async fn get_class( + &self, + block_id: BlockId, + class_hash: FieldElement, + ) -> Result> { + self.inner.request(GetClassRequest { block_id, class_hash }).await + } + + /// Returns the class hash of the specified contract address. + /// + /// # Arguments + /// + /// - `block_id`: The block to read from. + /// + /// - `contract_address`: The subject contract address. + pub async fn get_class_hash_at( + &self, + block_id: BlockId, + contract_address: FieldElement, + ) -> Result> { + self.inner.request(GetClassHashAtRequest { block_id, contract_address }).await + } + + /// Returns the contract definition for the specified class. + /// + /// # Arguments + /// + /// - `block_id`: The block to read from. + /// + /// - `contract_address`: The subject contract address. + pub async fn get_class_at( + &self, + block_id: BlockId, + contract_address: FieldElement, + ) -> Result> { + self.inner.request(GetClassAtRequest { block_id, contract_address }).await + } + + /// Returns the number of transactions in the specified block. + pub async fn get_block_transaction_count(&self, block_id: BlockId) -> Result> { + self.inner.request(GetBlockTransactionCountRequest { block_id }).await + } + + /// Calls the specified function. + /// + /// # Arguments + /// + /// - `contract_address`: The address of the contract to call. + /// + /// - `selector`: The selector of the function to call. + /// + /// - `calldata`: The calldata to pass to the function. + /// + /// - `block_id`: The block referencing the state to use for the call. + /// + /// # Returns + /// + /// The return value of the function. + pub async fn call( + &self, + contract_address: FieldElement, + selector: FieldElement, + calldata: Vec, + block_id: BlockId, + ) -> Result> { + self.inner + .request(CallRequest { + request: FunctionCall { contract_address, entry_point_selector: selector, calldata }, + block_id, + }) + .await + } + + /// Estimates the cost of the specified StarkNet transactions. + /// + /// # Arguments + /// + /// - `request`: The transactions to estimate the cost of. + /// + /// - `block_id`: The block referencing the state to use for the estimation. + pub async fn estimate_fee( + &self, + request: Vec, + block_id: BlockId, + ) -> Result> { + self.inner.request(EstimateFeeRequest { request, block_id }).await + } + + /// Estimates the resources required by the l1_handler transaction induced by the provided + /// message. + /// + /// # Arguments + /// + /// - `message`: The message to estimate the resources of. + /// + /// - `block_id`: The block referencing the state to use for the estimation. + pub async fn estimate_message_fee( + &self, + message: MsgFromL1, + block_id: BlockId, + ) -> Result> { + self.inner.request(EstimateMessageFeeRequest { message, block_id }).await + } + + /// Returns the number (height) of the latest block. + pub async fn block_number(&self) -> Result> { + self.inner.request(BlockNumberRequest).await + } + + /// Returns the hash and number of the latest block. + pub async fn block_hash_and_number(&self) -> Result<(FieldElement, u64), JsonRpcClientError> { + match self.inner.request(BlockHashAndNumberRequest).await { + Ok(BlockHashAndNumber { block_hash, block_number }) => Ok((block_hash, block_number)), + Err(err) => Err(err), + } + } + + /// Returns the current syncing status of the node. + /// + /// # Returns + /// + /// - `None` if the node is not syncing. + /// + /// - `Some(status)` if the node is syncing. In that case, `status` is the state of the + /// syncronization operation. + pub async fn syncing(&self) -> Result, JsonRpcClientError> { + match self.inner.request(SyncingRequest).await { + Ok(SyncStatusType::NotSyncing) => Ok(None), + Ok(SyncStatusType::Syncing(status)) => Ok(Some(status)), + Err(err) => Err(err), + } + } + + /// Returns the events matching the provided filter. + pub async fn get_events_manually( + &self, + filter: EventFilterWithPage, + ) -> Result> { + self.inner.request(GetEventsRequest { filter }).await + } + + /// Returns an "iterator" that can be used to gets the events matching the provided filter. + pub fn get_events(&self, filter: EventFilter, chunk_size: u64) -> Events<'_, T> { + Events { inner: self, continuation_token: None, filter, chunk_size } + } + + /// Returns the nonce associated with the specified contract. + /// + /// # Arguments + /// + /// - `contract_address`: The address of the contract to get the nonce of. + /// + /// - `block_id`: The block to read from. + pub async fn get_nonce(&self, contract_address: FieldElement, block_id: BlockId) -> Result> { + self.inner.request(GetNonceRequest { + contract_address, + block_id + }).await + } +} + +/// An "iterator" that can be used to gets the events matching the provided filter. +/// +/// This structure automatically handles pagination. +pub struct Events<'a, T> { + inner: &'a StarknetClient, + continuation_token: Option, + + // IMPROVE(nils-mathieu): + // Cloning this value is not very efficient. We can avoid it by creating a custom "EventFilter" + // struct that contains references rather than owned values. That would be make the API a bit + // harder to use though. + filter: EventFilter, + + /// The size of the requested pages. + chunk_size: u64, +} + +impl<'a, T: json_rpc::Transport> Events<'a, T> { + /// Returns the next batch of events. + pub async fn next_page(&mut self) -> Result, JsonRpcClientError> { + let events = self + .inner + .get_events_manually(EventFilterWithPage { + event_filter: self.filter.clone(), + result_page_request: ResultPageRequest { + continuation_token: self.continuation_token.clone(), + chunk_size: self.chunk_size, + }, + }) + .await?; + + self.continuation_token = events.continuation_token; + + Ok(events.events) + } +} From f308ce8fcd74d32d0a1260093fe6dd85d27fd7de Mon Sep 17 00:00:00 2001 From: nils-mathieu Date: Thu, 19 Oct 2023 19:59:53 +0200 Subject: [PATCH 04/16] feat(networks-option): add the '--network' option --- crates/node/src/commands/run.rs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/crates/node/src/commands/run.rs b/crates/node/src/commands/run.rs index afaa619908..dbf59bc448 100644 --- a/crates/node/src/commands/run.rs +++ b/crates/node/src/commands/run.rs @@ -17,6 +17,17 @@ pub enum Sealing { Instant, } +/// A possible network type. +#[derive(Debug, Clone, Copy, clap::ValueEnum)] +pub enum NetworkType { + /// The main network (mainnet). + Main, + /// The test network (testnet). + Test, + /// The integration network. + Integration, +} + #[derive(Clone, Debug, clap::Args)] pub struct ExtendedRunCmd { #[clap(flatten)] @@ -29,6 +40,10 @@ pub struct ExtendedRunCmd { /// Choose a supported DA Layer #[clap(long)] pub da_layer: Option, + + /// The network type to connect to. + #[clap(long, short, default_value = "integration")] + pub network: NetworkType, } impl ExtendedRunCmd { @@ -66,7 +81,9 @@ pub fn run_node(mut cli: Cli) -> Result<()> { let sealing = cli.run.sealing; runner.run_node_until_exit(|config| async move { - service::new_full(config, sealing, da_config, cli.run.base.rpc_port.unwrap()).await.map_err(sc_cli::Error::Service) + service::new_full(config, sealing, da_config, cli.run.base.rpc_port.unwrap()) + .await + .map_err(sc_cli::Error::Service) }) } From a1e1f3bc5d757cad43df971e0403e93db8b92a60 Mon Sep 17 00:00:00 2001 From: nils-mathieu Date: Thu, 19 Oct 2023 20:14:49 +0200 Subject: [PATCH 05/16] feat(networks): change the network used based on the requested option --- crates/client/deoxys/src/lib.rs | 8 ++++---- crates/node/src/commands/run.rs | 12 +++++++++++- crates/node/src/service.rs | 4 +++- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/crates/client/deoxys/src/lib.rs b/crates/client/deoxys/src/lib.rs index d2d65b971c..b330cdf4ec 100644 --- a/crates/client/deoxys/src/lib.rs +++ b/crates/client/deoxys/src/lib.rs @@ -186,6 +186,7 @@ impl Default for ExecutionConfig { } } +/* pub struct RpcConfig { // #[validate(custom = "validate_ascii")] pub chain_id: ChainId, @@ -216,10 +217,9 @@ impl Default for RpcConfig { } } } +*/ -pub async fn fetch_block(queue: BlockQueue, rpc_port: u16) { - let rpc_config = RpcConfig::default(); - +pub async fn fetch_block(queue: BlockQueue, uri: &str, rpc_port: u16) { let retry_config = RetryConfig { retry_base_millis: 30, retry_max_delay_millis: 30000, @@ -227,7 +227,7 @@ pub async fn fetch_block(queue: BlockQueue, rpc_port: u16) { }; let starknet_client = StarknetFeederGatewayClient::new( - &rpc_config.starknet_url, + uri, None, NODE_VERSION, retry_config diff --git a/crates/node/src/commands/run.rs b/crates/node/src/commands/run.rs index dbf59bc448..473cc88e48 100644 --- a/crates/node/src/commands/run.rs +++ b/crates/node/src/commands/run.rs @@ -28,6 +28,16 @@ pub enum NetworkType { Integration, } +impl NetworkType { + pub fn uri(&self) -> &'static str { + match self { + NetworkType::Main => "https://alpha-mainnet.starknet.io/gateway/", + NetworkType::Test => "https://alpha4.starknet.io/gateway/", + NetworkType::Integration => "https://external.integration.starknet.io/", + } + } +} + #[derive(Clone, Debug, clap::Args)] pub struct ExtendedRunCmd { #[clap(flatten)] @@ -81,7 +91,7 @@ pub fn run_node(mut cli: Cli) -> Result<()> { let sealing = cli.run.sealing; runner.run_node_until_exit(|config| async move { - service::new_full(config, sealing, da_config, cli.run.base.rpc_port.unwrap()) + service::new_full(config, sealing, da_config, cli.run.base.rpc_port.unwrap(), cli.run.network.uri()) .await .map_err(sc_cli::Error::Service) }) diff --git a/crates/node/src/service.rs b/crates/node/src/service.rs index a059fdad7b..b7a59f6283 100644 --- a/crates/node/src/service.rs +++ b/crates/node/src/service.rs @@ -272,6 +272,7 @@ pub async fn new_full( sealing: Option, da_layer: Option<(DaLayer, PathBuf)>, rpc_port: u16, + network_uri: &str, ) -> Result { let build_import_queue = if sealing.is_some() { build_manual_seal_import_queue } else { build_aura_grandpa_import_queue }; @@ -439,8 +440,9 @@ pub async fn new_full( network_starter.start_network(); + let network_uri = Box::from(network_uri); tokio::spawn(async move { - fetch_block(QUEUE.clone(), rpc_port).await; + fetch_block(QUEUE.clone(), &network_uri, rpc_port).await; }); log::info!("Manual Seal Ready"); From e54b9c9ca0a5be164d6493855725c2da4b29e6c9 Mon Sep 17 00:00:00 2001 From: nils-mathieu Date: Fri, 20 Oct 2023 18:45:36 +0200 Subject: [PATCH 06/16] remove the old client --- .../deoxys/src/client/json_rpc/error.rs | 0 .../client/deoxys/src/client/json_rpc/mod.rs | 201 ----------- .../deoxys/src/client/json_rpc/request.rs | 73 ---- .../deoxys/src/client/json_rpc/transport.rs | 50 --- .../src/client/json_rpc/transports/http.rs | 85 ----- crates/client/deoxys/src/client/mod.rs | 322 ------------------ crates/client/deoxys/src/lib.rs | 1 - 7 files changed, 732 deletions(-) delete mode 100644 crates/client/deoxys/src/client/json_rpc/error.rs delete mode 100644 crates/client/deoxys/src/client/json_rpc/mod.rs delete mode 100644 crates/client/deoxys/src/client/json_rpc/request.rs delete mode 100644 crates/client/deoxys/src/client/json_rpc/transport.rs delete mode 100644 crates/client/deoxys/src/client/json_rpc/transports/http.rs delete mode 100644 crates/client/deoxys/src/client/mod.rs diff --git a/crates/client/deoxys/src/client/json_rpc/error.rs b/crates/client/deoxys/src/client/json_rpc/error.rs deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/crates/client/deoxys/src/client/json_rpc/mod.rs b/crates/client/deoxys/src/client/json_rpc/mod.rs deleted file mode 100644 index 6fdd9e8112..0000000000 --- a/crates/client/deoxys/src/client/json_rpc/mod.rs +++ /dev/null @@ -1,201 +0,0 @@ -//! Provides a JSON-RPC client. - -use std::sync::atomic::AtomicU64; -use std::fmt; - -mod request; -mod transport; - -#[path = "transports/http.rs"] -mod http_transport; - -pub use self::request::*; -pub use self::transport::*; - -/// An error that might be returned by the JSON-RPC protocol. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct JsonRpcErrorCode(pub i64); - -impl JsonRpcErrorCode { - /// The error code for an parsing error. - pub const PARSE_ERROR: Self = Self(-32700); - /// The error code for an invalid request error. - pub const INVALID_REQUEST: Self = Self(-32600); - /// The error code for an invalid method error. - pub const METHOD_NOT_FOUND: Self = Self(-32601); - /// The error code for an invalid params error. - pub const INVALID_PARAMS: Self = Self(-32602); - /// The error code for an internal error. - pub const INTERNAL_ERROR: Self = Self(-32603); -} - -impl fmt::Display for JsonRpcErrorCode { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Display::fmt(&self.0, f) - } -} - -/// An error that might be returned by the JSON-RPC protocol. -#[derive(Debug, Clone, thiserror::Error)] -#[error("{message} (code {code})")] -pub struct JsonRpcError { - /// The code of the error. - pub code: JsonRpcErrorCode, - /// The message associated with the error. - pub message: Box, - /// An optional data field associated with the error. - pub data: Data, -} - -/// A JSON-RPC client that uses a [`Transport`] to communicate over the network. -pub struct JsonRpcClient { - /// The transport layer used to communicate over the network. - transport: T, - /// The next identifier to use for a request. - next_id: AtomicU64, -} - -/// An error that might occur when interacting with the [`JsonRpcClient`]. -#[derive(Debug, thiserror::Error)] -pub enum JsonRpcClientError { - /// The transport layer returned an error. - #[error("{0}")] - Transport(T), - /// The JSON-RPC protocol returned an error. - #[error("{0}")] - JsonRpc(JsonRpcError), - /// The received response was not a valid JSON-RPC response. - #[error("invalid JSON-RPC response")] - Protocol, -} - -impl JsonRpcClient { - /// Creates a new [`JsonRpcClient`] with the given transport layer. - pub fn new(transport: T) -> Self { - JsonRpcClient { transport, next_id: AtomicU64::new(0) } - } -} - -impl JsonRpcClient { - /// Sends a JSON-RPC request and returns the response. - pub async fn request(&self, request: R) -> Result> { - let id = self.next_id.fetch_add(1, std::sync::atomic::Ordering::Relaxed); - let body = create_json_rpc_request(R::METHOD, id, request.into_params()); - let response = self.transport.request(&body).await.map_err(JsonRpcClientError::Transport)?; - parse_json_rpc_repsonse(&response, id) - } -} - -/// Creates a JSON-RPC request from the given parameters. -fn create_json_rpc_request( - method: &'static str, - id: u64, - params: P, -) -> Vec { - #[derive(serde::Serialize)] - struct RequestType

{ - pub jsonrpc: &'static str, - pub method: &'static str, - pub params: P, - #[serde(serialize_with = "serialize_number_as_string")] - pub id: u64, - } - - let req = RequestType { jsonrpc: "2.0", method, params, id }; - - // If this panics because the serialized failed, it's a bug in the user code - // that comes from before this function is called. - serde_json::to_vec(&req).unwrap() -} - -/// Parses a JSON-RPC response. -fn parse_json_rpc_repsonse(data: &[u8], expected_id: u64) -> Result> -where - B: for<'a> serde::Deserialize<'a>, - E: for<'a> serde::Deserialize<'a>, -{ - #[derive(serde::Deserialize)] - struct ErrorType { - #[serde(deserialize_with = "i64_or_string")] - pub code: i64, - pub message: Box, - pub data: E, - } - - #[derive(serde::Deserialize)] - struct ResponseType { - pub jsonrpc: Box, - pub result: Option, - pub error: Option>, - #[serde(deserialize_with = "u64_or_string")] - pub id: u64, - } - - let response: ResponseType = serde_json::from_slice(data).map_err(|_| JsonRpcClientError::Protocol)?; - - if response.id != expected_id || &*response.jsonrpc != "2.0" { - return Err(JsonRpcClientError::Protocol); - } - - match (response.result, response.error) { - (Some(result), None) => Ok(result), - (None, Some(error)) => Err(JsonRpcClientError::JsonRpc(JsonRpcError { - code: JsonRpcErrorCode(error.code), - message: error.message, - data: error.data, - })), - _ => Err(JsonRpcClientError::Protocol), - } -} - -/// A deserializer function that accepts either a number or a string that represents a number. -fn i64_or_string<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result { - struct I64OrString; - - impl<'de> serde::de::Visitor<'de> for I64OrString { - type Value = i64; - - fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { - formatter.write_str("a number or a string that represents a number") - } - - fn visit_i64(self, value: i64) -> Result { - Ok(value) - } - - fn visit_str(self, value: &str) -> Result { - value.parse().map_err(E::custom) - } - } - - deserializer.deserialize_any(I64OrString) -} - - -/// A deserializer function that accepts either a number or a string that represents a number. -fn u64_or_string<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result { - struct I64OrString; - - impl<'de> serde::de::Visitor<'de> for I64OrString { - type Value = u64; - - fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { - formatter.write_str("a number or a string that represents a number") - } - - fn visit_u64(self, value: u64) -> Result { - Ok(value) - } - - fn visit_str(self, value: &str) -> Result { - value.parse().map_err(E::custom) - } - } - - deserializer.deserialize_any(I64OrString) -} - -/// Serializes number as strings of digits. -fn serialize_number_as_string(number: &u64, serializer: S) -> Result { - serializer.serialize_str(&number.to_string()) -} \ No newline at end of file diff --git a/crates/client/deoxys/src/client/json_rpc/request.rs b/crates/client/deoxys/src/client/json_rpc/request.rs deleted file mode 100644 index f77d6ffbab..0000000000 --- a/crates/client/deoxys/src/client/json_rpc/request.rs +++ /dev/null @@ -1,73 +0,0 @@ -//! Defines the generic JSON-RPC [`Request`]. - -use starknet_core::types::requests::*; -use starknet_core::types::*; - -/// Represents a JSON-RPC request. -pub trait Request { - /// The JSON-RPC method name. - const METHOD: &'static str; - - /// The response type expected for the request. - type Response: for<'de> serde::Deserialize<'de>; - - /// A type that, when serialized, represents the parameters of the request. - /// - /// This type must be directly serialized into an array. - type Params: serde::Serialize; - - /// Converts this [`Request`] into its parameters. - fn into_params(self) -> Self::Params; -} - -// ======================================================= -// IMPLEMENTATIONS OF `Request` FOR COMMON STARKNET TYPES -// ======================================================= - -macro_rules! impl_Request { - ($method:literal, $req:ty, $res:ty) => { - impl Request for $req { - const METHOD: &'static str = $method; - type Response = $res; - type Params = Self; - - #[inline(always)] - fn into_params(self) -> Self::Params { - self - } - } - - impl<'a> Request for &'a $req { - const METHOD: &'static str = $method; - type Response = $res; - type Params = Self; - - #[inline(always)] - fn into_params(self) -> Self::Params { - self - } - } - }; -} - -// https://github.com/starkware-libs/starknet-specs/blob/master/api/starknet_api_openrpc.json - -impl_Request!("starknet_getBlockWithTxHashes", GetBlockWithTxHashesRequest, MaybePendingBlockWithTxHashes); -impl_Request!("starknet_getBlockWithTxs", GetBlockWithTxsRequest, MaybePendingBlockWithTxs); -impl_Request!("starknet_getStateUpdate", GetStateUpdateRequest, MaybePendingStateUpdate); -impl_Request!("starknet_getStorageAt", GetStorageAtRequest, FieldElement); -impl_Request!("starknet_getTransactionByHash", GetTransactionByHashRequest, Transaction); -impl_Request!("starknet_getTransactionByBlockIdAndIndex", GetTransactionByBlockIdAndIndexRequest, Transaction); -impl_Request!("starknet_getTransactionReceipt", GetTransactionReceiptRequest, TransactionReceipt); -impl_Request!("starknet_getClass", GetClassRequest, ContractClass); -impl_Request!("starknet_getClassHashAt", GetClassHashAtRequest, FieldElement); -impl_Request!("starknet_getClassAt", GetClassAtRequest, ContractClass); -impl_Request!("starknet_getBlockTransactionCount", GetBlockTransactionCountRequest, u64); -impl_Request!("starknet_call", CallRequest, FieldElement); -impl_Request!("starknet_estimateFee", EstimateFeeRequest, FeeEstimate); -impl_Request!("starknet_estimateMessageFee", EstimateMessageFeeRequest, FeeEstimate); -impl_Request!("starknet_blockNumber", BlockNumberRequest, u64); -impl_Request!("starknet_blockHashAndNumber", BlockHashAndNumberRequest, BlockHashAndNumber); -impl_Request!("starknet_syncing", SyncingRequest, SyncStatusType); -impl_Request!("starknet_getEvents", GetEventsRequest, EventsPage); -impl_Request!("starknet_getNonce", GetNonceRequest, FieldElement); \ No newline at end of file diff --git a/crates/client/deoxys/src/client/json_rpc/transport.rs b/crates/client/deoxys/src/client/json_rpc/transport.rs deleted file mode 100644 index 3364d2d113..0000000000 --- a/crates/client/deoxys/src/client/json_rpc/transport.rs +++ /dev/null @@ -1,50 +0,0 @@ -//! Defines the [`Transport`] trait. - -use std::future::Future; - -/// Represents the transport layer used to communicate over the network. -pub trait Transport { - /// An error that might occur whilst communicating over the network. - type Error; - - /// The future type returned by [`Transport::request`]. - type Future<'a>: Future, Self::Error>> - where - Self: 'a; - - /// Sends a request over the network and returns the response. - fn request(&self, body: &[u8]) -> Self::Future<'_>; -} - -impl Transport for &'_ T { - type Error = T::Error; - type Future<'a> = T::Future<'a> - where - Self: 'a; - - fn request(&self, body: &[u8]) -> Self::Future<'_> { - (**self).request(body) - } -} - -impl Transport for Box { - type Error = T::Error; - type Future<'a> = T::Future<'a> - where - Self: 'a; - - fn request(&self, body: &[u8]) -> Self::Future<'_> { - (**self).request(body) - } -} - -impl Transport for std::sync::Arc { - type Error = T::Error; - type Future<'a> = T::Future<'a> - where - Self: 'a; - - fn request(&self, body: &[u8]) -> Self::Future<'_> { - (**self).request(body) - } -} diff --git a/crates/client/deoxys/src/client/json_rpc/transports/http.rs b/crates/client/deoxys/src/client/json_rpc/transports/http.rs deleted file mode 100644 index d86ff616c0..0000000000 --- a/crates/client/deoxys/src/client/json_rpc/transports/http.rs +++ /dev/null @@ -1,85 +0,0 @@ -//! An implementation of [`Transport`] that uses the [`hyper`] HTTP client. - -use std::future::Future; -use std::pin::Pin; - -use hyper::body::HttpBody; -use hyper::client::connect::Connect; -use hyper::Uri; - -/// The configuration for an [`HttpTransport`]. -#[derive(Debug, Clone)] -pub struct Config { - /// The URI to send requests to. - pub uri: Uri, -} - -/// An implementation of [`Transport`] that uses the [`hyper`] HTTP client. -pub struct Transport> { - client: hyper::Client, - uri: hyper::Uri, -} - -impl Transport { - /// Creates a new [`HttpTransport`] with the given configuration. - pub fn new(config: Config) -> Self { - let connector = hyper_rustls::HttpsConnectorBuilder::new() - .with_native_roots() - .https_or_http() - .enable_http1() - .wrap_connector(hyper::client::HttpConnector::new()); - - Self::with_connector(config.uri, connector) - } -} - -impl Transport { - /// Creates a new [`HttpTransport`] with the given connector. - pub fn with_connector(uri: Uri, connector: C) -> Self - where - C: Connect + Clone, - { - Self { uri, client: hyper::Client::builder().build(connector) } - } -} - -impl super::Transport for Transport -where - C: Connect + Clone + Send + Sync + 'static, -{ - type Error = hyper::Error; - - type Future<'a> = Pin, Self::Error>> + Send + Sync>> - where - Self: 'a; - - fn request(&self, body: &[u8]) -> Self::Future<'_> { - let body = hyper::Body::from(body.to_vec()); - - // IMPROVE(nils-mathieu): - // Cookie to anyone capable of turning this into an actual - // non-boxed future. - let fut = async move { - let mut response = self - .client - .request( - hyper::Request::builder() - .method(hyper::Method::POST) - .uri(self.uri.clone()) - .header("content-type", "application/json") - .body(body) - .unwrap(), - ) - .await?; - - let mut v = Vec::new(); - if let Some(chunk) = response.body_mut().data().await { - v.extend_from_slice(&chunk?); - } - - Ok(v) - }; - - Box::pin(fut) - } -} diff --git a/crates/client/deoxys/src/client/mod.rs b/crates/client/deoxys/src/client/mod.rs deleted file mode 100644 index 9e52cffd3b..0000000000 --- a/crates/client/deoxys/src/client/mod.rs +++ /dev/null @@ -1,322 +0,0 @@ -//! Defines a generic implementation of a Starknet JSON-RPC server client. - -use starknet_core::types::requests::*; -use starknet_core::types::*; - -use self::json_rpc::{JsonRpcClient, JsonRpcClientError}; - -pub mod json_rpc; - -// For some reason, `starknet-core` does not define a type that requests the spec version. -// Let's do it ourselves. -struct SpecVersionRequest; - -impl self::json_rpc::Request for SpecVersionRequest { - const METHOD: &'static str = "starknet_specVersion"; - type Response = Box; - type Params = [(); 0]; - #[inline(always)] - fn into_params(self) -> Self::Params { - [(); 0] - } -} - -/// A generic implementation of a Starknet JSON-RPC client. -pub struct StarknetClient { - inner: JsonRpcClient, -} - -impl StarknetClient { - /// Creates a new [`StarknetClient`] with the given transport layer. - pub fn new(transport: T) -> Self { - Self { inner: JsonRpcClient::new(transport) } - } -} - -impl StarknetClient { - /// Requets the version of the Starknet JSON-RPC specification being used by the server. - pub async fn spec_version(&self) -> Result, JsonRpcClientError> { - self.inner.request(SpecVersionRequest).await - } - - /// Returns information about the specified block. - /// - /// Transactions are not fully sent in the response. Instead, only their hashes are - /// communicated. - pub async fn get_block_with_tx_hashes( - &self, - block_id: BlockId, - ) -> Result> { - self.inner.request(GetBlockWithTxHashesRequest { block_id }).await - } - - /// Returns information about the specified block. - /// - /// Transactions are fully sent in the response. If you do not need the full transactions, - /// consider using [`get_block_with_tx_hashes`](Self::get_block_with_tx_hashes) instead. - pub async fn get_block_with_txs( - &self, - block_id: BlockId, - ) -> Result> { - self.inner.request(GetBlockWithTxsRequest { block_id }).await - } - - /// Returns information about the result of executing the specified block. - pub async fn get_state_update( - &self, - block_id: BlockId, - ) -> Result> { - self.inner.request(GetStateUpdateRequest { block_id }).await - } - - /// Returns the value of the storage cell at the specified address. - /// - /// # Arguments - /// - /// - `contract_address`: The address of the contract to read from. - /// - /// - `key`: The key of the storage cell to read within the given contract. - /// - /// - `block_id`: The block to read from. - pub async fn get_storage_at( - &self, - contract_address: FieldElement, - key: FieldElement, - block_id: BlockId, - ) -> Result> { - self.inner.request(GetStorageAtRequest { contract_address, key, block_id }).await - } - - // TODO: - // Include the `starknet_getTransactionStatus` method here. For some reason it's not - // defined by `starknet-core`. - - /// Returns information about the specified transaction. - pub async fn get_transaction_by_hash( - &self, - transaction_hash: FieldElement, - ) -> Result> { - self.inner.request(GetTransactionByHashRequest { transaction_hash }).await - } - - /// Returns information about the transaction at the specified index in the specified block. - pub async fn get_transaction_by_block_id_and_index( - &self, - block_id: BlockId, - index: u64, - ) -> Result> { - self.inner.request(GetTransactionByBlockIdAndIndexRequest { block_id, index }).await - } - - /// Returns the transaction receipt for the specified transaction. - pub async fn get_transaction_receipt( - &self, - transaction_hash: FieldElement, - ) -> Result> { - self.inner.request(GetTransactionReceiptRequest { transaction_hash }).await - } - - /// Returns the contract definition for the specified class. - /// - /// # Arguments - /// - /// - `block_id`: The block to read from. - /// - /// - `class_hash`: The hash of the requested contract class. - pub async fn get_class( - &self, - block_id: BlockId, - class_hash: FieldElement, - ) -> Result> { - self.inner.request(GetClassRequest { block_id, class_hash }).await - } - - /// Returns the class hash of the specified contract address. - /// - /// # Arguments - /// - /// - `block_id`: The block to read from. - /// - /// - `contract_address`: The subject contract address. - pub async fn get_class_hash_at( - &self, - block_id: BlockId, - contract_address: FieldElement, - ) -> Result> { - self.inner.request(GetClassHashAtRequest { block_id, contract_address }).await - } - - /// Returns the contract definition for the specified class. - /// - /// # Arguments - /// - /// - `block_id`: The block to read from. - /// - /// - `contract_address`: The subject contract address. - pub async fn get_class_at( - &self, - block_id: BlockId, - contract_address: FieldElement, - ) -> Result> { - self.inner.request(GetClassAtRequest { block_id, contract_address }).await - } - - /// Returns the number of transactions in the specified block. - pub async fn get_block_transaction_count(&self, block_id: BlockId) -> Result> { - self.inner.request(GetBlockTransactionCountRequest { block_id }).await - } - - /// Calls the specified function. - /// - /// # Arguments - /// - /// - `contract_address`: The address of the contract to call. - /// - /// - `selector`: The selector of the function to call. - /// - /// - `calldata`: The calldata to pass to the function. - /// - /// - `block_id`: The block referencing the state to use for the call. - /// - /// # Returns - /// - /// The return value of the function. - pub async fn call( - &self, - contract_address: FieldElement, - selector: FieldElement, - calldata: Vec, - block_id: BlockId, - ) -> Result> { - self.inner - .request(CallRequest { - request: FunctionCall { contract_address, entry_point_selector: selector, calldata }, - block_id, - }) - .await - } - - /// Estimates the cost of the specified StarkNet transactions. - /// - /// # Arguments - /// - /// - `request`: The transactions to estimate the cost of. - /// - /// - `block_id`: The block referencing the state to use for the estimation. - pub async fn estimate_fee( - &self, - request: Vec, - block_id: BlockId, - ) -> Result> { - self.inner.request(EstimateFeeRequest { request, block_id }).await - } - - /// Estimates the resources required by the l1_handler transaction induced by the provided - /// message. - /// - /// # Arguments - /// - /// - `message`: The message to estimate the resources of. - /// - /// - `block_id`: The block referencing the state to use for the estimation. - pub async fn estimate_message_fee( - &self, - message: MsgFromL1, - block_id: BlockId, - ) -> Result> { - self.inner.request(EstimateMessageFeeRequest { message, block_id }).await - } - - /// Returns the number (height) of the latest block. - pub async fn block_number(&self) -> Result> { - self.inner.request(BlockNumberRequest).await - } - - /// Returns the hash and number of the latest block. - pub async fn block_hash_and_number(&self) -> Result<(FieldElement, u64), JsonRpcClientError> { - match self.inner.request(BlockHashAndNumberRequest).await { - Ok(BlockHashAndNumber { block_hash, block_number }) => Ok((block_hash, block_number)), - Err(err) => Err(err), - } - } - - /// Returns the current syncing status of the node. - /// - /// # Returns - /// - /// - `None` if the node is not syncing. - /// - /// - `Some(status)` if the node is syncing. In that case, `status` is the state of the - /// syncronization operation. - pub async fn syncing(&self) -> Result, JsonRpcClientError> { - match self.inner.request(SyncingRequest).await { - Ok(SyncStatusType::NotSyncing) => Ok(None), - Ok(SyncStatusType::Syncing(status)) => Ok(Some(status)), - Err(err) => Err(err), - } - } - - /// Returns the events matching the provided filter. - pub async fn get_events_manually( - &self, - filter: EventFilterWithPage, - ) -> Result> { - self.inner.request(GetEventsRequest { filter }).await - } - - /// Returns an "iterator" that can be used to gets the events matching the provided filter. - pub fn get_events(&self, filter: EventFilter, chunk_size: u64) -> Events<'_, T> { - Events { inner: self, continuation_token: None, filter, chunk_size } - } - - /// Returns the nonce associated with the specified contract. - /// - /// # Arguments - /// - /// - `contract_address`: The address of the contract to get the nonce of. - /// - /// - `block_id`: The block to read from. - pub async fn get_nonce(&self, contract_address: FieldElement, block_id: BlockId) -> Result> { - self.inner.request(GetNonceRequest { - contract_address, - block_id - }).await - } -} - -/// An "iterator" that can be used to gets the events matching the provided filter. -/// -/// This structure automatically handles pagination. -pub struct Events<'a, T> { - inner: &'a StarknetClient, - continuation_token: Option, - - // IMPROVE(nils-mathieu): - // Cloning this value is not very efficient. We can avoid it by creating a custom "EventFilter" - // struct that contains references rather than owned values. That would be make the API a bit - // harder to use though. - filter: EventFilter, - - /// The size of the requested pages. - chunk_size: u64, -} - -impl<'a, T: json_rpc::Transport> Events<'a, T> { - /// Returns the next batch of events. - pub async fn next_page(&mut self) -> Result, JsonRpcClientError> { - let events = self - .inner - .get_events_manually(EventFilterWithPage { - event_filter: self.filter.clone(), - result_page_request: ResultPageRequest { - continuation_token: self.continuation_token.clone(), - chunk_size: self.chunk_size, - }, - }) - .await?; - - self.continuation_token = events.continuation_token; - - Ok(events.events) - } -} diff --git a/crates/client/deoxys/src/lib.rs b/crates/client/deoxys/src/lib.rs index d2d65b971c..84fb4017cd 100644 --- a/crates/client/deoxys/src/lib.rs +++ b/crates/client/deoxys/src/lib.rs @@ -24,7 +24,6 @@ use mp_hashers::pedersen::PedersenHasher; const NODE_VERSION: &str = "NODE VERSION"; mod transactions; -mod client; pub type BlockQueue = Arc>>; From 3dd6e0bcdc84ffead069215fc4eb9b488dabb24e Mon Sep 17 00:00:00 2001 From: nils-mathieu Date: Fri, 20 Oct 2023 19:13:01 +0200 Subject: [PATCH 07/16] basic feeder gateway implementation --- Cargo.lock | 2 - crates/client/deoxys/Cargo.toml | 4 +- crates/client/deoxys/src/feeder_gateway.rs | 63 ++++++++++++++++++++++ crates/client/deoxys/src/lib.rs | 1 + 4 files changed, 65 insertions(+), 5 deletions(-) create mode 100644 crates/client/deoxys/src/feeder_gateway.rs diff --git a/Cargo.lock b/Cargo.lock index 965ef7df73..cf2cab9190 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6792,8 +6792,6 @@ dependencies = [ "blockifier", "env_logger 0.9.3", "hex", - "hyper", - "hyper-rustls 0.24.1", "log", "mockito", "mp-block", diff --git a/crates/client/deoxys/Cargo.toml b/crates/client/deoxys/Cargo.toml index 1c3e7ae308..dafc35728e 100644 --- a/crates/client/deoxys/Cargo.toml +++ b/crates/client/deoxys/Cargo.toml @@ -19,8 +19,7 @@ targets = ["x86_64-unknown-linux-gnu"] serde = { version = "1", features = ["derive"] } serde_json = "1" thiserror = "1" -hyper = { version = "0.14", features = ["client", "http1"] } -hyper-rustls = "0.24" +reqwest = "0.11" blockifier = { workspace = true, default-features = false, features = [ "testing", @@ -29,7 +28,6 @@ env_logger = "0.9.0" log = { version = "0.4.14" } mockito = { workspace = true } pallet-starknet = { workspace = true } -reqwest = { workspace = true } sp-core = { workspace = true, features = ["std"] } starknet-core = { workspace = true } starknet-ff = { workspace = true, default-features = false, features = [ diff --git a/crates/client/deoxys/src/feeder_gateway.rs b/crates/client/deoxys/src/feeder_gateway.rs new file mode 100644 index 0000000000..5f587d8301 --- /dev/null +++ b/crates/client/deoxys/src/feeder_gateway.rs @@ -0,0 +1,63 @@ +//! A simple HTTP client that simplifies the process of interacting with a Starknet Feeder Gateway. + +use core::fmt; + +use starknet_core::types::{BlockId, BlockTag}; + +/// The configuration passed to a [`FeederGatewayClient`]. +pub struct FeederGatewayClientConfig { + /// The base URL of the Feeder gateway. + pub base_url: Box, +} + +/// An error that can occur when interacting with a [`FeederGatewayClient`]. +pub enum FeederGatewayError { + /// An error occured while transporting the request or the response over HTTP. + Http(reqwest::Error), + /// The gateway returned an error. + Gateway, + /// The gateway behaved in an unexpected way. + UnexpectedBehavior, +} + +/// A simple HTTP client that simplifies the process of interacting with a Starknet Feeder Gateway. +pub struct FeederGatewayClient { + /// The raw HTTP client we're using to create our requests. + client: reqwest::Client, + + /// The base URL of the Feeder Gateway. + base_url: Box, +} + +impl FeederGatewayClient { + /// Creates a new [`FeederGatewayClient`] with the given configuration. + pub fn new(config: FeederGatewayClientConfig) -> Self { + Self { client: reqwest::Client::new(), base_url: config.base_url } + } + + /// TODO: doc + pub async fn get_block(&self, id: BlockId) -> Result { + let url = format!("{}/getBlock?{}", self.base_url, QueryBlockId(id)); + let response = self.client.request(reqwest::Method::GET, url).send().await.map_err(FeederGatewayError::Http)?; + + if response.status() != reqwest::StatusCode::OK { + return Err(FeederGatewayError::Gateway); + } + + response.json().await.map_err(|_| FeederGatewayError::UnexpectedBehavior) + } +} + +/// An implementation of [`fmt::Display`] that displays a [`BlockId`] as an URL query parameter. +struct QueryBlockId(BlockId); + +impl fmt::Display for QueryBlockId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.0 { + BlockId::Number(number) => write!(f, "blockNumber={number}"), + BlockId::Hash(hash) => write!(f, "blockHash={hash}"), + BlockId::Tag(BlockTag::Latest) => write!(f, "latest"), + BlockId::Tag(BlockTag::Pending) => write!(f, "pending"), + } + } +} diff --git a/crates/client/deoxys/src/lib.rs b/crates/client/deoxys/src/lib.rs index 84fb4017cd..ada43b4e91 100644 --- a/crates/client/deoxys/src/lib.rs +++ b/crates/client/deoxys/src/lib.rs @@ -24,6 +24,7 @@ use mp_hashers::pedersen::PedersenHasher; const NODE_VERSION: &str = "NODE VERSION"; mod transactions; +mod feeder_gateway; pub type BlockQueue = Arc>>; From c99c16f8c36401ad1a36872e3950abc63e75980d Mon Sep 17 00:00:00 2001 From: nils-mathieu Date: Fri, 20 Oct 2023 21:09:02 +0200 Subject: [PATCH 08/16] use the starknet-providers client, clean the convertion system --- Cargo.lock | 3 +- crates/client/deoxys/Cargo.toml | 5 +- crates/client/deoxys/src/convert.rs | 154 +++++++++++++++ crates/client/deoxys/src/feeder_gateway.rs | 63 ------ crates/client/deoxys/src/lib.rs | 215 +++------------------ crates/client/deoxys/src/transactions.rs | 67 ------- 6 files changed, 187 insertions(+), 320 deletions(-) create mode 100644 crates/client/deoxys/src/convert.rs delete mode 100644 crates/client/deoxys/src/feeder_gateway.rs delete mode 100644 crates/client/deoxys/src/transactions.rs diff --git a/Cargo.lock b/Cargo.lock index cf2cab9190..3b21c3679c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6801,14 +6801,13 @@ dependencies = [ "mp-transactions", "pallet-starknet", "reqwest", - "serde", "serde_json", "sp-core 7.0.0", "starknet-core", "starknet-ff", + "starknet-providers", "starknet_api 0.4.1 (git+https://github.com/keep-starknet-strange/starknet-api?branch=no_std-support-dc83f05)", "starknet_client", - "thiserror", "tokio", "validator", ] diff --git a/crates/client/deoxys/Cargo.toml b/crates/client/deoxys/Cargo.toml index dafc35728e..23b083aa5f 100644 --- a/crates/client/deoxys/Cargo.toml +++ b/crates/client/deoxys/Cargo.toml @@ -16,10 +16,9 @@ repository = "https://github.com/KasarLabs/deoxys" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -serde = { version = "1", features = ["derive"] } -serde_json = "1" -thiserror = "1" +starknet-providers = "0.6" reqwest = "0.11" +serde_json = "1" blockifier = { workspace = true, default-features = false, features = [ "testing", diff --git a/crates/client/deoxys/src/convert.rs b/crates/client/deoxys/src/convert.rs new file mode 100644 index 0000000000..7eac880684 --- /dev/null +++ b/crates/client/deoxys/src/convert.rs @@ -0,0 +1,154 @@ +//! Converts types from [`starknet_providers`] to madara's expected types. + +use starknet_api::hash::StarkFelt; +use starknet_providers::sequencer::models as p; + +pub fn block(block: &p::Block) -> mp_block::Block { + let transactions = transactions(&block.transactions); + let events = events(&block.transaction_receipts); + let block_number = block.block_number.expect("no block number provided"); + let (transaction_commitment, event_commitment) = commitments(&transactions, &events, block_number); + + let header = mp_block::Header { + parent_block_hash: felt(block.parent_block_hash), + block_number, + status: block_status(&block.status), + block_timestamp: block.timestamp, + global_state_root: felt(block.state_root.expect("no state root provided")), + sequencer_address: contract_address(block.sequencer_address.expect("no sequencer address provided")), + transaction_count: block.transactions.len() as u128, + transaction_commitment, + event_count: events.len() as u128, + event_commitment, + protocol_version: 0, + extra_data: block.block_hash.map(|h| sp_core::U256::from_big_endian(&h.to_bytes_be())), + }; + + mp_block::Block::new(header, transactions) +} + +fn block_status(status: &p::BlockStatus) -> mp_block::BlockStatus { + match status { + p::BlockStatus::Aborted => mp_block::BlockStatus::Rejected, + p::BlockStatus::AcceptedOnL1 => mp_block::BlockStatus::AcceptedOnL1, + p::BlockStatus::AcceptedOnL2 => mp_block::BlockStatus::AcceptedOnL2, + p::BlockStatus::Pending => mp_block::BlockStatus::Pending, + p::BlockStatus::Reverted => panic!("reverted block found"), + } +} + +fn transactions(txs: &[p::TransactionType]) -> Vec { + txs.iter().map(transaction).collect() +} + +fn transaction(transaction: &p::TransactionType) -> mp_transactions::Transaction { + match transaction { + p::TransactionType::InvokeFunction(tx) => mp_transactions::Transaction::Invoke(invoke_transaction(tx)), + p::TransactionType::Declare(tx) => mp_transactions::Transaction::Declare(declare_transaction(tx)), + p::TransactionType::Deploy(tx) => mp_transactions::Transaction::Deploy(deploy_transaction(tx)), + p::TransactionType::DeployAccount(tx) => { + mp_transactions::Transaction::DeployAccount(deploy_account_transaction(tx)) + } + p::TransactionType::L1Handler(tx) => mp_transactions::Transaction::L1Handler(l1_handler_transaction(tx)), + } +} + +fn invoke_transaction(tx: &p::InvokeFunctionTransaction) -> mp_transactions::InvokeTransaction { + mp_transactions::InvokeTransaction::V1(mp_transactions::InvokeTransactionV1 { + max_fee: fee(tx.max_fee), + signature: tx.signature.iter().copied().map(felt).map(Into::into).collect(), + nonce: felt(tx.nonce.expect("no nonce provided")).into(), + sender_address: felt(tx.sender_address).into(), + calldata: tx.calldata.iter().copied().map(felt).map(Into::into).collect(), + }) +} + +fn declare_transaction(tx: &p::DeclareTransaction) -> mp_transactions::DeclareTransaction { + mp_transactions::DeclareTransaction::V2(mp_transactions::DeclareTransactionV2 { + max_fee: fee(tx.max_fee), + signature: tx.signature.iter().copied().map(felt).map(Into::into).collect(), + nonce: felt(tx.nonce).into(), + class_hash: felt(tx.class_hash).into(), + sender_address: felt(tx.sender_address).into(), + compiled_class_hash: felt(tx.compiled_class_hash.expect("no class hash available")).into(), + }) +} + +fn deploy_transaction(tx: &p::DeployTransaction) -> mp_transactions::DeployTransaction { + mp_transactions::DeployTransaction { + version: starknet_api::transaction::TransactionVersion(felt(tx.version)), + class_hash: felt(tx.class_hash).into(), + contract_address_salt: felt(tx.contract_address_salt).into(), + constructor_calldata: tx.constructor_calldata.iter().copied().map(felt).map(Into::into).collect(), + } +} + +fn deploy_account_transaction(tx: &p::DeployAccountTransaction) -> mp_transactions::DeployAccountTransaction { + mp_transactions::DeployAccountTransaction { + max_fee: fee(tx.max_fee), + signature: tx.signature.iter().copied().map(felt).map(Into::into).collect(), + nonce: felt(tx.nonce).into(), + contract_address_salt: felt(tx.contract_address_salt).into(), + constructor_calldata: tx.constructor_calldata.iter().copied().map(felt).map(Into::into).collect(), + class_hash: felt(tx.class_hash).into(), + } +} + +fn l1_handler_transaction(tx: &p::L1HandlerTransaction) -> mp_transactions::HandleL1MessageTransaction { + mp_transactions::HandleL1MessageTransaction { + // TODO: + // Convert the nonce from field element to u64?? + nonce: 0, + contract_address: felt(tx.contract_address).into(), + entry_point_selector: felt(tx.entry_point_selector).into(), + calldata: tx.calldata.iter().copied().map(felt).map(Into::into).collect(), + } +} + +fn fee(felt: starknet_ff::FieldElement) -> u128 { + // FIXME: WHY IS THIS CONVERTION EVEN A THING?? + let _ = felt; + 0 +} + +fn events(receipts: &[p::ConfirmedTransactionReceipt]) -> Vec { + receipts.iter().flat_map(|r| &r.events).map(event).collect() +} + +fn event(event: &p::Event) -> starknet_api::transaction::Event { + use starknet_api::transaction::{Event, EventContent, EventData, EventKey}; + + Event { + from_address: contract_address(event.from_address), + content: EventContent { + keys: event.keys.iter().copied().map(felt).map(EventKey).collect(), + data: EventData(event.data.iter().copied().map(felt).collect()), + }, + } +} + +fn commitments( + transactions: &[mp_transactions::Transaction], + events: &[starknet_api::transaction::Event], + block_number: u64, +) -> (StarkFelt, StarkFelt) { + use mp_hashers::pedersen::PedersenHasher; + + let chain_id = chain_id(); + + let (a, b) = mp_commitments::calculate_commitments::(transactions, events, chain_id, block_number); + + (a.into(), b.into()) +} + +fn chain_id() -> mp_felt::Felt252Wrapper { + starknet_ff::FieldElement::from_byte_slice_be(b"SN_MAIN").unwrap().into() +} + +fn felt(field_element: starknet_ff::FieldElement) -> starknet_api::hash::StarkFelt { + starknet_api::hash::StarkFelt::new(field_element.to_bytes_be()).unwrap() +} + +fn contract_address(field_element: starknet_ff::FieldElement) -> starknet_api::api_core::ContractAddress { + starknet_api::api_core::ContractAddress(starknet_api::api_core::PatriciaKey(felt(field_element))) +} diff --git a/crates/client/deoxys/src/feeder_gateway.rs b/crates/client/deoxys/src/feeder_gateway.rs deleted file mode 100644 index 5f587d8301..0000000000 --- a/crates/client/deoxys/src/feeder_gateway.rs +++ /dev/null @@ -1,63 +0,0 @@ -//! A simple HTTP client that simplifies the process of interacting with a Starknet Feeder Gateway. - -use core::fmt; - -use starknet_core::types::{BlockId, BlockTag}; - -/// The configuration passed to a [`FeederGatewayClient`]. -pub struct FeederGatewayClientConfig { - /// The base URL of the Feeder gateway. - pub base_url: Box, -} - -/// An error that can occur when interacting with a [`FeederGatewayClient`]. -pub enum FeederGatewayError { - /// An error occured while transporting the request or the response over HTTP. - Http(reqwest::Error), - /// The gateway returned an error. - Gateway, - /// The gateway behaved in an unexpected way. - UnexpectedBehavior, -} - -/// A simple HTTP client that simplifies the process of interacting with a Starknet Feeder Gateway. -pub struct FeederGatewayClient { - /// The raw HTTP client we're using to create our requests. - client: reqwest::Client, - - /// The base URL of the Feeder Gateway. - base_url: Box, -} - -impl FeederGatewayClient { - /// Creates a new [`FeederGatewayClient`] with the given configuration. - pub fn new(config: FeederGatewayClientConfig) -> Self { - Self { client: reqwest::Client::new(), base_url: config.base_url } - } - - /// TODO: doc - pub async fn get_block(&self, id: BlockId) -> Result { - let url = format!("{}/getBlock?{}", self.base_url, QueryBlockId(id)); - let response = self.client.request(reqwest::Method::GET, url).send().await.map_err(FeederGatewayError::Http)?; - - if response.status() != reqwest::StatusCode::OK { - return Err(FeederGatewayError::Gateway); - } - - response.json().await.map_err(|_| FeederGatewayError::UnexpectedBehavior) - } -} - -/// An implementation of [`fmt::Display`] that displays a [`BlockId`] as an URL query parameter. -struct QueryBlockId(BlockId); - -impl fmt::Display for QueryBlockId { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self.0 { - BlockId::Number(number) => write!(f, "blockNumber={number}"), - BlockId::Hash(hash) => write!(f, "blockHash={hash}"), - BlockId::Tag(BlockTag::Latest) => write!(f, "latest"), - BlockId::Tag(BlockTag::Pending) => write!(f, "pending"), - } - } -} diff --git a/crates/client/deoxys/src/lib.rs b/crates/client/deoxys/src/lib.rs index ada43b4e91..5ae47a9e40 100644 --- a/crates/client/deoxys/src/lib.rs +++ b/crates/client/deoxys/src/lib.rs @@ -1,30 +1,19 @@ -use mp_felt::Felt252Wrapper; -use reqwest::StatusCode; -use sp_core::U256; -use mp_block::Block; -use reqwest::header::{HeaderMap, CONTENT_TYPE, HeaderValue}; -use serde_json::{json, Value}; -use starknet_api::block::BlockNumber; -use starknet_api::api_core::{ChainId, PatriciaKey}; -use starknet_api::hash::StarkFelt; -use starknet_api::transaction::Event; -use starknet_client::RetryConfig; -use starknet_client::reader::{StarknetFeederGatewayClient, StarknetReader}; -use starknet_ff::FieldElement; -use std::sync::{ Arc, Mutex}; +#![allow(deprecated)] + use std::collections::VecDeque; -use log::info; -use tokio::time; -use std::string::String; -use starknet_client; use std::path::PathBuf; -use crate::transactions::{declare_tx_to_starknet_tx, deploy_account_tx_to_starknet_tx, invoke_tx_to_starknet_tx, l1handler_tx_to_starknet_tx, deploy_tx_to_starknet_tx}; -use mp_hashers::pedersen::PedersenHasher; +use std::sync::{Arc, Mutex}; -const NODE_VERSION: &str = "NODE VERSION"; +use log::info; +use mp_block::Block; +use reqwest::header::{HeaderMap, HeaderValue, CONTENT_TYPE}; +use reqwest::StatusCode; +use serde_json::{json, Value}; +use starknet_providers::sequencer::models::BlockId; +use starknet_providers::SequencerGatewayProvider; +use tokio::time; -mod transactions; -mod feeder_gateway; +mod convert; pub type BlockQueue = Arc>>; @@ -33,97 +22,6 @@ pub fn create_block_queue() -> BlockQueue { Arc::new(Mutex::new(VecDeque::new())) } - -// This function converts a block received from the gateway into a StarkNet block -pub fn get_header(block: starknet_client::reader::Block, transactions: mp_block::BlockTransactions, events: &[Event]) -> mp_block::Header { - let parent_block_hash = Felt252Wrapper::try_from(block.parent_block_hash.0.bytes()); - let block_number = block.block_number.0; - let global_state_root = Felt252Wrapper::try_from(block.state_root.0.bytes()); - let status = match block.status { - starknet_client::reader::objects::block::BlockStatus::Pending => mp_block::BlockStatus::Pending, - starknet_client::reader::objects::block::BlockStatus::AcceptedOnL2 => mp_block::BlockStatus::AcceptedOnL2, - starknet_client::reader::objects::block::BlockStatus::AcceptedOnL1 => mp_block::BlockStatus::AcceptedOnL1, - starknet_client::reader::objects::block::BlockStatus::Reverted => mp_block::BlockStatus::Rejected, - starknet_client::reader::objects::block::BlockStatus::Aborted => mp_block::BlockStatus::Rejected, - }; - let chain_id = Felt252Wrapper(FieldElement::from_byte_slice_be(b"SN_MAIN").unwrap()); - let sequencer_address = Felt252Wrapper(FieldElement::from(*PatriciaKey::key(&block.sequencer_address.0))); - let block_timestamp = block.timestamp.0; - let transaction_count = block.transactions.len() as u128; - let (transaction_commitment, event_commitment) = - mp_commitments::calculate_commitments::(&transactions, &events, chain_id, block_number); - let event_count: u128 = block.transaction_receipts - .iter() - .map(|receipt| receipt.events.len() as u128) - .sum(); - let protocol_version = Some(0u8); - let extra_data: U256 = Felt252Wrapper::try_from(block.block_hash.0.bytes()).unwrap().into(); - let starknet_header = mp_block::Header::new( - StarkFelt::from(parent_block_hash.unwrap()), - block_number.into(), - status.into(), - StarkFelt::from(global_state_root.unwrap()), - sequencer_address.into(), - block_timestamp.into(), - transaction_count.into(), - StarkFelt::from(transaction_commitment), - event_count.into(), - StarkFelt::from(event_commitment), - protocol_version.unwrap(), - Some(extra_data), - ); - starknet_header -} - -pub async fn get_txs(block: starknet_client::reader::Block) -> mp_block::BlockTransactions { - let mut transactions_vec: mp_block::BlockTransactions = Vec::new(); - for transaction in &block.transactions { - match transaction { - starknet_client::reader::objects::transaction::Transaction::Declare(declare_transaction) => { - // convert declare_transaction to starknet transaction - let tx = declare_tx_to_starknet_tx(declare_transaction.clone()); - transactions_vec.push(tx.unwrap()); - }, - starknet_client::reader::objects::transaction::Transaction::DeployAccount(deploy_account_transaction) => { - // convert declare_transaction to starknet transaction - let tx = deploy_account_tx_to_starknet_tx(deploy_account_transaction.clone().into()); - transactions_vec.push(tx.unwrap()); - }, - starknet_client::reader::objects::transaction::Transaction::Deploy(deploy_transaction) => { - // convert declare_transaction to starknet transaction - let tx = deploy_tx_to_starknet_tx(deploy_transaction.clone().into()).await; - transactions_vec.push(tx.unwrap()); - }, - starknet_client::reader::objects::transaction::Transaction::Invoke(invoke_transaction) => { - // convert invoke_transaction to starknet transaction - let tx = invoke_tx_to_starknet_tx(invoke_transaction.clone()); - transactions_vec.push(tx.unwrap()); - }, - starknet_client::reader::objects::transaction::Transaction::L1Handler(l1handler_transaction) => { - // convert declare_transaction to starknet transaction - let tx = l1handler_tx_to_starknet_tx(l1handler_transaction.clone().into()); - transactions_vec.push(tx.unwrap()); - }, - } - } - - transactions_vec -} - -// This function converts a block received from the gateway into a StarkNet block -pub async fn from_gateway_to_starknet_block(block: starknet_client::reader::Block) -> mp_block::Block { - let transactions_vec: mp_block::BlockTransactions = get_txs(block.clone()).await; - let all_events: Vec = block.transaction_receipts.iter() - .flat_map(|receipt| &receipt.events) - .cloned() - .collect(); - let header = get_header(block.clone(), transactions_vec.clone(), &all_events); - mp_block::Block::new( - header, - transactions_vec, - ) -} - async fn create_block(rpc_port: u16) -> Result { let client = reqwest::Client::new(); let mut headers = HeaderMap::new(); @@ -137,10 +35,7 @@ async fn create_block(rpc_port: u16) -> Result { "params": [true, true, null] }); - let response = client.post(url) - .headers(headers.clone()) - .json(&payload) - .send().await?; + let response = client.post(url).headers(headers.clone()).json(&payload).send().await?; Ok(response.status()) } @@ -161,13 +56,10 @@ async fn get_last_synced_block(rpc_port: u16) -> Result, reqwest::Er "params": [] }); - let response = client.post(&url) - .headers(headers) - .json(&payload) - .send().await?; + let response = client.post(&url).headers(headers).json(&payload).send().await?; let body: Value = response.json().await?; - + body["result"]["block"]["header"]["number"] .as_str() .and_then(|number_hex| u64::from_str_radix(&number_hex[2..], 16).ok()) @@ -186,59 +78,14 @@ impl Default for ExecutionConfig { } } -pub struct RpcConfig { - // #[validate(custom = "validate_ascii")] - pub chain_id: ChainId, - pub server_address: String, - pub max_events_chunk_size: usize, - pub max_events_keys: usize, - pub collect_metrics: bool, - pub starknet_url: String, - pub starknet_gateway_retry_config: RetryConfig, - pub execution_config: ExecutionConfig, -} - -impl Default for RpcConfig { - fn default() -> Self { - RpcConfig { - chain_id: ChainId("SN_MAIN".to_string()), - server_address: String::from("0.0.0.0:9944"), - max_events_chunk_size: 1000, - max_events_keys: 100, - collect_metrics: false, - starknet_url: String::from("https://alpha-mainnet.starknet.io/"), - starknet_gateway_retry_config: RetryConfig { - retry_base_millis: 50, - retry_max_delay_millis: 1000, - max_retries: 5, - }, - execution_config: ExecutionConfig::default(), - } - } -} - pub async fn fetch_block(queue: BlockQueue, rpc_port: u16) { - let rpc_config = RpcConfig::default(); - - let retry_config = RetryConfig { - retry_base_millis: 30, - retry_max_delay_millis: 30000, - max_retries: 10, - }; - - let starknet_client = StarknetFeederGatewayClient::new( - &rpc_config.starknet_url, - None, - NODE_VERSION, - retry_config - ).unwrap(); + let client = SequencerGatewayProvider::starknet_alpha_mainnet(); let mut i = get_last_synced_block(rpc_port).await.unwrap().unwrap() + 1; loop { - let block = starknet_client.block(BlockNumber(i)).await; - match block { + match client.get_block(BlockId::Number(i)).await { Ok(block) => { - let starknet_block = from_gateway_to_starknet_block(block.unwrap()).await; + let starknet_block = convert::block(&block); { let mut queue_guard: std::sync::MutexGuard<'_, VecDeque> = queue.lock().unwrap(); queue_guard.push_back(starknet_block); @@ -249,12 +96,12 @@ pub async fn fetch_block(queue: BlockQueue, rpc_port: u16) { info!("[👽] Block #{} synced correctly", i); i += 1; } - }, + } Err(e) => { eprintln!("Error processing RPC call: {:?}", e); } } - }, + } Err(error) => { eprintln!("Error retrieving block: {:?}", error); time::sleep(time::Duration::from_secs(2)).await; @@ -263,19 +110,14 @@ pub async fn fetch_block(queue: BlockQueue, rpc_port: u16) { } } - #[cfg(test)] mod tests { - use super::*; - use std::sync::Mutex; - use std::collections::VecDeque; - use mockall::mock; - use mockito::mock; - use starknet_ff::FieldElement; use std::convert::TryInto; + use starknet_ff::FieldElement; + // Mocking StarknetFeederGatewayClient for testing - mock! { + mockito::mock! { StarknetFeederGatewayClient { fn new(url: &str, option: Option<&str>, version: &str, retry_config: RetryConfig) -> Self; async fn block(&self, block_number: BlockNumber) -> Result, starknet_client::Error>; @@ -284,14 +126,16 @@ mod tests { #[test] fn test_get_header() { - // Provide a mock starknet_client::reader::Block and BoundedVec - // Then, call get_header with the mock data and check if the resulting Header is as expected. + // Provide a mock starknet_client::reader::Block and BoundedVec Then, call get_header with the mock data and check if the + // resulting Header is as expected. } #[test] fn test_get_txs() { // Provide a mock starknet_client::reader::Block - // Call get_txs with the mock data and verify if the resulting BoundedVec is correct. + // Call get_txs with the mock data and verify if the resulting BoundedVec is correct. } #[tokio::test] @@ -309,7 +153,8 @@ mod tests { #[tokio::test] async fn test_fetch_block_success() { // Mock the StarknetFeederGatewayClient to return a successful block. - // Run fetch_block and ensure the block is correctly added to the queue and RPC call is made. + // Run fetch_block and ensure the block is correctly added to the queue and RPC call is + // made. } #[tokio::test] diff --git a/crates/client/deoxys/src/transactions.rs b/crates/client/deoxys/src/transactions.rs deleted file mode 100644 index 9a725649db..0000000000 --- a/crates/client/deoxys/src/transactions.rs +++ /dev/null @@ -1,67 +0,0 @@ -use mp_transactions::{DeclareTransactionV0, DeclareTransactionV1, DeclareTransactionV2}; -use starknet_client::reader::{objects::transaction::{IntermediateInvokeTransaction, IntermediateDeclareTransaction}, ReaderClientError}; - -pub fn declare_tx_to_starknet_tx( - declare_transaction: IntermediateDeclareTransaction, -) -> Result { - - // Convert `IntermediateDeclareTransaction` to `starknet_api::transaction::DeclareTransaction` - let starknet_declare_tx = starknet_api::transaction::DeclareTransaction::try_from(declare_transaction)?; - - // Convert `starknet_api::transaction::DeclareTransaction` to `mp_transactions::DeclareTransaction` - let mp_declare_tx = match starknet_declare_tx { - starknet_api::transaction::DeclareTransaction::V0(inner) => { - mp_transactions::DeclareTransaction::V0(DeclareTransactionV0::from_starknet(inner)) - }, - starknet_api::transaction::DeclareTransaction::V1(inner) => { - mp_transactions::DeclareTransaction::V1(DeclareTransactionV1::from_starknet(inner)) - }, - starknet_api::transaction::DeclareTransaction::V2(inner) => { - mp_transactions::DeclareTransaction::V2(DeclareTransactionV2::from_starknet(inner)) - }, - }; - - Ok(mp_transactions::Transaction::Declare(mp_declare_tx)) -} - -pub fn invoke_tx_to_starknet_tx( - invoke_transaction: IntermediateInvokeTransaction -) -> Result { - - // Try to convert the intermediate representation to the starknet_api representation - let starknet_invoke_tx = starknet_api::transaction::InvokeTransaction::try_from(invoke_transaction)?; - - // Convert `starknet_api::transaction::InvokeTransaction` to `mp_transactions::InvokeTransaction` - let mp_invoke_tx = match starknet_invoke_tx { - starknet_api::transaction::InvokeTransaction::V0(inner) => { - mp_transactions::InvokeTransaction::V0(mp_transactions::InvokeTransactionV0::from_starknet(inner)) - }, - starknet_api::transaction::InvokeTransaction::V1(inner) => { - mp_transactions::InvokeTransaction::V1(mp_transactions::InvokeTransactionV1::from_starknet(inner)) - } - }; - - Ok(mp_transactions::Transaction::Invoke(mp_invoke_tx)) -} - -pub async fn deploy_tx_to_starknet_tx( - deploy_transaction : starknet_api::transaction::DeployTransaction -) -> Result { - let mp_deploy_tx = mp_transactions::DeployTransaction::from_starknet(deploy_transaction); - Ok(mp_transactions::Transaction::Deploy(mp_deploy_tx)) -} - -pub fn deploy_account_tx_to_starknet_tx( - deploy_account_transaction: starknet_api::transaction::DeployAccountTransaction -) -> Result { - let mp_deploy_account_tx = mp_transactions::DeployAccountTransaction::from_starknet(deploy_account_transaction); - Ok(mp_transactions::Transaction::DeployAccount(mp_deploy_account_tx)) -} - - -pub fn l1handler_tx_to_starknet_tx( - l1handler_transaction: starknet_api::transaction::L1HandlerTransaction -) -> Result { - let mp_l1handler_tx = mp_transactions::HandleL1MessageTransaction::from_starknet(l1handler_transaction); - Ok(mp_transactions::Transaction::L1Handler(mp_l1handler_tx)) -} From 8ba0544beb048273341c856d73cc40e8887c2210 Mon Sep 17 00:00:00 2001 From: nils-mathieu Date: Fri, 20 Oct 2023 21:38:35 +0200 Subject: [PATCH 09/16] remove 'starknet_client' crate --- Cargo.lock | 88 -- Cargo.toml | 2 - crates/client/deoxys/Cargo.toml | 1 - crates/client/starknet_client/Cargo.toml | 53 -- .../starknet_client/papyrus_config/Cargo.toml | 26 - .../starknet_client/papyrus_config/README.md | 38 - .../resources/custom_config_example.json | 3 - .../papyrus_config/src/command.rs | 79 -- .../papyrus_config/src/config_test.rs | 313 ------- .../papyrus_config/src/converters.rs | 80 -- .../papyrus_config/src/dumping.rs | 203 ----- .../starknet_client/papyrus_config/src/lib.rs | 132 --- .../papyrus_config/src/loading.rs | 200 ----- .../papyrus_config/src/validators.rs | 11 - .../starknet_client/resources/reader/abi.json | 177 ---- .../resources/reader/block.json | 171 ---- .../resources/reader/block_state_update.json | 41 - .../resources/reader/casm_contract_class.json | 51 -- .../resources/reader/contract_class.json | 15 - .../resources/reader/declare_transaction.json | 10 - .../resources/reader/deploy_transaction.json | 14 - .../reader/deprecated_contract_class.json | 41 - .../resources/reader/invoke_transaction.json | 29 - .../reader/invoke_transaction_l1_handler.json | 13 - .../resources/reader/transaction_receipt.json | 53 -- .../transaction_receipt_without_l1_to_l2.json | 64 -- ...action_receipt_without_l1_to_l2_nonce.json | 52 -- .../resources/writer/declare_response.json | 5 - .../resources/writer/declare_v1.json | 828 ------------------ .../resources/writer/declare_v2.json | 80 -- .../resources/writer/deploy_account.json | 15 - .../writer/deploy_account_response.json | 5 - .../resources/writer/invoke.json | 26 - .../resources/writer/invoke_response.json | 4 - crates/client/starknet_client/src/lib.rs | 212 ----- .../client/starknet_client/src/reader/mod.rs | 331 ------- .../src/reader/objects/block.rs | 249 ------ .../src/reader/objects/block_test.rs | 179 ---- .../starknet_client/src/reader/objects/mod.rs | 3 - .../src/reader/objects/state.rs | 94 -- .../src/reader/objects/transaction.rs | 464 ---------- .../src/reader/objects/transaction_test.rs | 108 --- .../starknet_feeder_gateway_client_test.rs | 465 ---------- crates/client/starknet_client/src/retry.rs | 102 --- .../client/starknet_client/src/retry_test.rs | 49 -- .../src/starknet_client_test.rs | 186 ---- .../starknet_client/src/starknet_error.rs | 96 -- .../src/starknet_error_test.rs | 89 -- .../starknet_client/src/test_utils/mod.rs | 4 - .../src/test_utils/read_resource.rs | 11 - .../starknet_client/src/test_utils/retry.rs | 9 - .../client/starknet_client/src/writer/mod.rs | 136 --- .../starknet_client/src/writer/objects/mod.rs | 4 - .../src/writer/objects/response.rs | 45 - .../src/writer/objects/response_test.rs | 18 - .../src/writer/objects/test_utils.rs | 43 - .../src/writer/objects/transaction.rs | 187 ---- .../src/writer/objects/transaction_test.rs | 36 - .../writer/starknet_gateway_client_test.rs | 173 ---- .../tests/feeder_gateway_integration_test.rs | 185 ---- crates/node/Cargo.toml | 1 - 61 files changed, 6402 deletions(-) delete mode 100644 crates/client/starknet_client/Cargo.toml delete mode 100644 crates/client/starknet_client/papyrus_config/Cargo.toml delete mode 100644 crates/client/starknet_client/papyrus_config/README.md delete mode 100644 crates/client/starknet_client/papyrus_config/resources/custom_config_example.json delete mode 100644 crates/client/starknet_client/papyrus_config/src/command.rs delete mode 100644 crates/client/starknet_client/papyrus_config/src/config_test.rs delete mode 100644 crates/client/starknet_client/papyrus_config/src/converters.rs delete mode 100644 crates/client/starknet_client/papyrus_config/src/dumping.rs delete mode 100644 crates/client/starknet_client/papyrus_config/src/lib.rs delete mode 100644 crates/client/starknet_client/papyrus_config/src/loading.rs delete mode 100644 crates/client/starknet_client/papyrus_config/src/validators.rs delete mode 100644 crates/client/starknet_client/resources/reader/abi.json delete mode 100644 crates/client/starknet_client/resources/reader/block.json delete mode 100644 crates/client/starknet_client/resources/reader/block_state_update.json delete mode 100644 crates/client/starknet_client/resources/reader/casm_contract_class.json delete mode 100644 crates/client/starknet_client/resources/reader/contract_class.json delete mode 100644 crates/client/starknet_client/resources/reader/declare_transaction.json delete mode 100644 crates/client/starknet_client/resources/reader/deploy_transaction.json delete mode 100644 crates/client/starknet_client/resources/reader/deprecated_contract_class.json delete mode 100644 crates/client/starknet_client/resources/reader/invoke_transaction.json delete mode 100644 crates/client/starknet_client/resources/reader/invoke_transaction_l1_handler.json delete mode 100644 crates/client/starknet_client/resources/reader/transaction_receipt.json delete mode 100644 crates/client/starknet_client/resources/reader/transaction_receipt_without_l1_to_l2.json delete mode 100644 crates/client/starknet_client/resources/reader/transaction_receipt_without_l1_to_l2_nonce.json delete mode 100644 crates/client/starknet_client/resources/writer/declare_response.json delete mode 100644 crates/client/starknet_client/resources/writer/declare_v1.json delete mode 100644 crates/client/starknet_client/resources/writer/declare_v2.json delete mode 100644 crates/client/starknet_client/resources/writer/deploy_account.json delete mode 100644 crates/client/starknet_client/resources/writer/deploy_account_response.json delete mode 100644 crates/client/starknet_client/resources/writer/invoke.json delete mode 100644 crates/client/starknet_client/resources/writer/invoke_response.json delete mode 100644 crates/client/starknet_client/src/lib.rs delete mode 100644 crates/client/starknet_client/src/reader/mod.rs delete mode 100644 crates/client/starknet_client/src/reader/objects/block.rs delete mode 100644 crates/client/starknet_client/src/reader/objects/block_test.rs delete mode 100644 crates/client/starknet_client/src/reader/objects/mod.rs delete mode 100644 crates/client/starknet_client/src/reader/objects/state.rs delete mode 100644 crates/client/starknet_client/src/reader/objects/transaction.rs delete mode 100644 crates/client/starknet_client/src/reader/objects/transaction_test.rs delete mode 100644 crates/client/starknet_client/src/reader/starknet_feeder_gateway_client_test.rs delete mode 100644 crates/client/starknet_client/src/retry.rs delete mode 100644 crates/client/starknet_client/src/retry_test.rs delete mode 100644 crates/client/starknet_client/src/starknet_client_test.rs delete mode 100644 crates/client/starknet_client/src/starknet_error.rs delete mode 100644 crates/client/starknet_client/src/starknet_error_test.rs delete mode 100644 crates/client/starknet_client/src/test_utils/mod.rs delete mode 100644 crates/client/starknet_client/src/test_utils/read_resource.rs delete mode 100644 crates/client/starknet_client/src/test_utils/retry.rs delete mode 100644 crates/client/starknet_client/src/writer/mod.rs delete mode 100644 crates/client/starknet_client/src/writer/objects/mod.rs delete mode 100644 crates/client/starknet_client/src/writer/objects/response.rs delete mode 100644 crates/client/starknet_client/src/writer/objects/response_test.rs delete mode 100644 crates/client/starknet_client/src/writer/objects/test_utils.rs delete mode 100644 crates/client/starknet_client/src/writer/objects/transaction.rs delete mode 100644 crates/client/starknet_client/src/writer/objects/transaction_test.rs delete mode 100644 crates/client/starknet_client/src/writer/starknet_gateway_client_test.rs delete mode 100644 crates/client/starknet_client/tests/feeder_gateway_integration_test.rs diff --git a/Cargo.lock b/Cargo.lock index 3b21c3679c..4b2a742a52 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -581,12 +581,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "assert" -version = "0.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6ad2cfc66932f8ceee708631eaaf7131d189a2a68610269a6f65b433dd8c844" - [[package]] name = "assert-json-diff" version = "2.0.2" @@ -3570,26 +3564,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "enum-iterator" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7add3873b5dd076766ee79c8e406ad1a472c385476b9e38849f8eec24f1be689" -dependencies = [ - "enum-iterator-derive", -] - -[[package]] -name = "enum-iterator-derive" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eecf8589574ce9b895052fa12d69af7a233f99e6107f5cb8dd1044f2a17bfdcb" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.38", -] - [[package]] name = "enum_dispatch" version = "0.3.12" @@ -6609,7 +6583,6 @@ dependencies = [ "sp-trie 7.0.0", "starknet-core", "starknet_api 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", - "starknet_client", "substrate-build-script-utils", "substrate-frame-rpc-system", "substrate-prometheus-endpoint", @@ -6807,7 +6780,6 @@ dependencies = [ "starknet-ff", "starknet-providers", "starknet_api 0.4.1 (git+https://github.com/keep-starknet-strange/starknet-api?branch=no_std-support-dc83f05)", - "starknet_client", "tokio", "validator", ] @@ -7918,17 +7890,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" -[[package]] -name = "os_info" -version = "3.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "006e42d5b888366f1880eda20371fedde764ed2213dc8496f49622fa0c99cd5e" -dependencies = [ - "log", - "serde", - "winapi", -] - [[package]] name = "p256" version = "0.11.1" @@ -8222,22 +8183,6 @@ dependencies = [ "sp-timestamp", ] -[[package]] -name = "papyrus_config" -version = "0.4.0" -dependencies = [ - "assert_matches", - "clap 4.4.6", - "itertools 0.10.5", - "lazy_static", - "serde", - "serde_json", - "strum_macros 0.25.2", - "tempfile", - "thiserror", - "validator", -] - [[package]] name = "parity-db" version = "0.4.12" @@ -12940,39 +12885,6 @@ dependencies = [ "thiserror-no-std", ] -[[package]] -name = "starknet_client" -version = "0.4.0" -dependencies = [ - "assert", - "assert_matches", - "async-trait", - "cairo-lang-casm 2.2.0", - "cairo-lang-starknet 2.2.0", - "cairo-lang-utils 2.2.0", - "enum-iterator", - "http", - "indexmap 2.0.0-pre", - "lazy_static", - "mockall", - "mockito", - "os_info", - "papyrus_config", - "pretty_assertions", - "rand 0.8.5", - "rand_chacha 0.3.1", - "reqwest", - "serde", - "serde_json", - "starknet-core", - "starknet_api 0.4.1 (git+https://github.com/keep-starknet-strange/starknet-api?branch=no_std-support-dc83f05)", - "thiserror", - "tokio", - "tokio-retry", - "tracing", - "url", -] - [[package]] name = "static_assertions" version = "1.1.0" diff --git a/Cargo.toml b/Cargo.toml index 86ede9fb7a..e55638ebbb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,6 @@ members = [ "crates/client/storage", "crates/client/transaction-pool", "crates/client/deoxys", - "crates/client/starknet_client", "starknet-rpc-test", ] # All previous except for `starknet-rpc-test` @@ -236,7 +235,6 @@ phf = { version = "0.11", default-features = false } # Deoxys mc-deoxys = { path = "crates/client/deoxys" } -starknet_client = { path = "crates/client/starknet_client" } tokio = "1.24.2" assert_matches = "1.5.0" mockito = "0.31.0" diff --git a/crates/client/deoxys/Cargo.toml b/crates/client/deoxys/Cargo.toml index 23b083aa5f..3eaccf3488 100644 --- a/crates/client/deoxys/Cargo.toml +++ b/crates/client/deoxys/Cargo.toml @@ -34,7 +34,6 @@ starknet-ff = { workspace = true, default-features = false, features = [ "serde", ] } starknet_api = { workspace = true, default-features = false } -starknet_client = { workspace = true } tokio = { workspace = true, features = ["macros", "test-util"] } # test_utils = { path = "./test_utils", optional = true } hex = "0.4" diff --git a/crates/client/starknet_client/Cargo.toml b/crates/client/starknet_client/Cargo.toml deleted file mode 100644 index 2a39176aac..0000000000 --- a/crates/client/starknet_client/Cargo.toml +++ /dev/null @@ -1,53 +0,0 @@ -[package] -name = "starknet_client" -version.workspace = true -edition.workspace = true -repository.workspace = true -# license-file.workspace = true -description = "A client implementation that can communicate with Starknet." - -[features] -testing = [ - "enum-iterator", - "mockall", - "rand", - "rand_chacha", - # "test_utils", -] - -[dependencies] -async-trait.workspace = true -cairo-lang-casm = "2.2.0" -cairo-lang-starknet = "2.2.0" -cairo-lang-utils = "2.2.0" -enum-iterator = { workspace = true, optional = true } -http.workspace = true -indexmap = { workspace = true, features = ["serde"] } -lazy_static.workspace = true -mockall = { workspace = true, optional = true } -os_info.workspace = true -papyrus_config = { path = "papyrus_config", version = "0.4.0" } -rand = { workspace = true, optional = true } -rand_chacha = { workspace = true, optional = true } -reqwest = { workspace = true, features = ["json", "blocking"] } -serde = { workspace = true, features = ["derive"] } -serde_json = { workspace = true, features = ["arbitrary_precision"] } -starknet_api = { workspace = true, default-features = false } -# test_utils = { path = "test_utils", optional = true } -starknet-core = { workspace = true } -thiserror.workspace = true -tokio = { workspace = true, features = ["full", "sync"] } -tokio-retry.workspace = true -tracing.workspace = true -url.workspace = true - -[dev-dependencies] -assert.workspace = true -assert_matches.workspace = true -enum-iterator.workspace = true -mockall.workspace = true -mockito.workspace = true -rand.workspace = true -rand_chacha.workspace = true -pretty_assertions.workspace = true -# test_utils = { path = "test_utils" } diff --git a/crates/client/starknet_client/papyrus_config/Cargo.toml b/crates/client/starknet_client/papyrus_config/Cargo.toml deleted file mode 100644 index cabec49025..0000000000 --- a/crates/client/starknet_client/papyrus_config/Cargo.toml +++ /dev/null @@ -1,26 +0,0 @@ -[package] -name = "papyrus_config" -version.workspace = true -edition.workspace = true -repository.workspace = true -# license-file.workspace = true -description = "A library for handling node configuration." - -[package.metadata.cargo-udeps.ignore] -development = ["tempfile"] # Dependency of a doc-test - -[dependencies] -clap = { workspace = true, features = ["env", "string"] } -itertools.workspace = true -serde = { workspace = true, features = ["derive"] } -serde_json = { workspace = true, features = ["arbitrary_precision"] } -strum_macros.workspace = true -thiserror.workspace = true -validator = { workspace = true, features = ["derive"] } - -[dev-dependencies] -assert_matches.workspace = true -itertools.workspace = true -lazy_static.workspace = true -tempfile.workspace = true -# test_utils = { path = "../test_utils" } diff --git a/crates/client/starknet_client/papyrus_config/README.md b/crates/client/starknet_client/papyrus_config/README.md deleted file mode 100644 index 8475bdac00..0000000000 --- a/crates/client/starknet_client/papyrus_config/README.md +++ /dev/null @@ -1,38 +0,0 @@ -# papyrus-config - -## Description - -papyrus-config is a flexible and powerful layered configuration system designed -specifically for Papyrus, a Starknet node. This system allows you to easily -manage configurations for your Papyrus node by leveraging various sources and -providing additional helpful features. - -## Configuration sources - -Supports multiple configuration sources in ascending order of overriding -priority: - -- Default values -- Configuration files (from first to last) -- Environment variables -- Command-line arguments - -## Additional features - -- **Support for Nested Configuration Components:** Organize your configurations - into nested components, making it easy to manage complex settings for - different aspects of the application. - -- **Usage of Pointers:** Use pointers to merge parameters that are common to - multiple components. This capability helps in streamlining configurations and - avoiding duplication of settings. - -- **Automatically-Generated Command Line Parser:** To simplify the process of - handling command-line arguments, the system automatically generates a - command-line parser. This means you don't have to write complex argument - parsing code; it's ready to use out-of-the-box. - -- **Automatically-Generated Reference Configuration File:** Makes it easier for - users by generating a reference configuration file. This file serves as a - template that highlights all available configuration options and their default - values, enabling users to customize their configurations efficiently. diff --git a/crates/client/starknet_client/papyrus_config/resources/custom_config_example.json b/crates/client/starknet_client/papyrus_config/resources/custom_config_example.json deleted file mode 100644 index a9576538f1..0000000000 --- a/crates/client/starknet_client/papyrus_config/resources/custom_config_example.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "param_path": "custom value" -} diff --git a/crates/client/starknet_client/papyrus_config/src/command.rs b/crates/client/starknet_client/papyrus_config/src/command.rs deleted file mode 100644 index 2018ecf616..0000000000 --- a/crates/client/starknet_client/papyrus_config/src/command.rs +++ /dev/null @@ -1,79 +0,0 @@ -use std::collections::BTreeMap; -use std::path::PathBuf; - -use clap::{value_parser, Arg, ArgMatches, Command}; -use serde_json::{json, Value}; - -use crate::loading::update_config_map; -use crate::{ConfigError, ParamPath, SerializationType, SerializedParam}; - -pub(crate) fn get_command_matches( - config_map: &BTreeMap, - command: Command, - command_input: Vec, -) -> Result { - Ok(command.args(build_args_parser(config_map)).try_get_matches_from(command_input)?) -} - -// Takes matched arguments from the command line interface and env variables and updates the config -// map. -// Supports usize, bool and String. -pub(crate) fn update_config_map_by_command_args( - config_map: &mut BTreeMap, - types_map: &BTreeMap, - arg_match: &ArgMatches, -) -> Result<(), ConfigError> { - for param_path_id in arg_match.ids() { - let param_path = param_path_id.as_str(); - let new_value = get_arg_by_type(types_map, arg_match, param_path)?; - update_config_map(config_map, types_map, param_path, new_value)?; - } - Ok(()) -} - -// Builds the parser for the command line flags and env variables according to the types of the -// values in the config map. -fn build_args_parser(config_map: &BTreeMap) -> Vec { - let mut args_parser = vec![ - // Custom_config_file_path. - Arg::new("config_file") - .long("config_file") - .short('f') - .value_delimiter(',') - .help("Optionally sets a config file to use") - .value_parser(value_parser!(PathBuf)), - ]; - - for (param_path, serialized_param) in config_map.iter() { - let Some(serialization_type) = serialized_param.content.get_serialization_type() else { - continue; // Pointer target - }; - let clap_parser = match serialization_type { - SerializationType::Number => clap::value_parser!(usize).into(), - SerializationType::Boolean => clap::value_parser!(bool), - SerializationType::String => clap::value_parser!(String), - }; - - let arg = Arg::new(param_path) - .long(param_path) - .env(param_path.to_uppercase()) - .help(&serialized_param.description) - .value_parser(clap_parser); - args_parser.push(arg); - } - args_parser -} - -// Converts clap arg_matches into json values. -fn get_arg_by_type( - types_map: &BTreeMap, - arg_match: &ArgMatches, - param_path: &str, -) -> Result { - let serialization_type = types_map.get(param_path).expect("missing type"); - match serialization_type { - SerializationType::Number => Ok(json!(arg_match.try_get_one::(param_path)?)), - SerializationType::Boolean => Ok(json!(arg_match.try_get_one::(param_path)?)), - SerializationType::String => Ok(json!(arg_match.try_get_one::(param_path)?)), - } -} diff --git a/crates/client/starknet_client/papyrus_config/src/config_test.rs b/crates/client/starknet_client/papyrus_config/src/config_test.rs deleted file mode 100644 index 5dd75cc451..0000000000 --- a/crates/client/starknet_client/papyrus_config/src/config_test.rs +++ /dev/null @@ -1,313 +0,0 @@ -use std::collections::BTreeMap; -use std::env; -use std::fs::File; -use std::path::PathBuf; -use std::time::Duration; - -use assert_matches::assert_matches; -use clap::Command; -use itertools::chain; -use lazy_static::lazy_static; -use serde::{Deserialize, Serialize}; -use serde_json::json; -use tempfile::TempDir; -use test_utils::get_absolute_path; -use validator::Validate; - -use crate::command::{get_command_matches, update_config_map_by_command_args}; -use crate::converters::deserialize_milliseconds_to_duration; -use crate::dumping::{ - append_sub_config_name, - combine_config_map_and_pointers, - ser_optional_param, - ser_optional_sub_config, - ser_param, - ser_required_param, - SerializeConfig, -}; -use crate::loading::{ - load, - load_and_process_config, - split_pointers_map, - split_values_and_types, - update_config_map_by_pointers, - update_optional_values, -}; -use crate::{ConfigError, ParamPath, SerializationType, SerializedContent, SerializedParam}; - -lazy_static! { - static ref CUSTOM_CONFIG_PATH: PathBuf = - get_absolute_path("crates/papyrus_config/resources/custom_config_example.json"); -} - -#[derive(Clone, Copy, Default, Serialize, Deserialize, Debug, PartialEq, Validate)] -struct InnerConfig { - #[validate(range(min = 0, max = 10))] - o: usize, -} - -impl SerializeConfig for InnerConfig { - fn dump(&self) -> BTreeMap { - BTreeMap::from([ser_param("o", &self.o, "This is o.")]) - } -} - -#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Validate)] -struct OuterConfig { - opt_elem: Option, - opt_config: Option, - #[validate] - inner_config: InnerConfig, -} - -impl SerializeConfig for OuterConfig { - fn dump(&self) -> BTreeMap { - chain!( - ser_optional_param(&self.opt_elem, 1, "opt_elem", "This is elem."), - ser_optional_sub_config(&self.opt_config, "opt_config"), - append_sub_config_name(self.inner_config.dump(), "inner_config"), - ) - .collect() - } -} - -#[test] -fn dump_and_load_config() { - let some_outer_config = OuterConfig { - opt_elem: Some(2), - opt_config: Some(InnerConfig { o: 3 }), - inner_config: InnerConfig { o: 4 }, - }; - let none_outer_config = - OuterConfig { opt_elem: None, opt_config: None, inner_config: InnerConfig { o: 5 } }; - - for outer_config in [some_outer_config, none_outer_config] { - let (mut dumped, _) = split_values_and_types(outer_config.dump()); - update_optional_values(&mut dumped); - let loaded_config = load::(&dumped).unwrap(); - assert_eq!(loaded_config, outer_config); - } -} - -#[test] -fn test_validation() { - let outer_config = - OuterConfig { opt_elem: None, opt_config: None, inner_config: InnerConfig { o: 20 } }; - assert!(outer_config.validate().is_err()); -} - -#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)] -struct TypicalConfig { - #[serde(deserialize_with = "deserialize_milliseconds_to_duration")] - a: Duration, - b: String, - c: bool, -} - -impl SerializeConfig for TypicalConfig { - fn dump(&self) -> BTreeMap { - BTreeMap::from([ - ser_param("a", &self.a.as_millis(), "This is a as milliseconds."), - ser_param("b", &self.b, "This is b."), - ser_param("c", &self.c, "This is c."), - ]) - } -} - -#[test] -fn test_update_dumped_config() { - let command = Command::new("Testing"); - let dumped_config = - TypicalConfig { a: Duration::from_secs(1), b: "bbb".to_owned(), c: false }.dump(); - let args = vec!["Testing", "--a", "1234", "--b", "15"]; - env::set_var("C", "true"); - let args: Vec = args.into_iter().map(|s| s.to_owned()).collect(); - - let arg_matches = get_command_matches(&dumped_config, command, args).unwrap(); - let (mut config_map, required_map) = split_values_and_types(dumped_config); - update_config_map_by_command_args(&mut config_map, &required_map, &arg_matches).unwrap(); - - assert_eq!(json!(1234), config_map["a"]); - assert_eq!(json!("15"), config_map["b"]); - assert_eq!(json!(true), config_map["c"]); - - let loaded_config: TypicalConfig = load(&config_map).unwrap(); - assert_eq!(Duration::from_millis(1234), loaded_config.a); -} - -#[test] -fn test_pointers_flow() { - let config_map = BTreeMap::from([ - ser_param("a1", &json!(5), "This is a."), - ser_param("a2", &json!(5), "This is a."), - ]); - let pointers = vec![( - ser_param("common_a", &json!(10), "This is common a"), - vec!["a1".to_owned(), "a2".to_owned()], - )]; - let stored_map = combine_config_map_and_pointers(config_map, &pointers).unwrap(); - assert_eq!( - stored_map["a1"], - json!(SerializedParam { - description: "This is a.".to_owned(), - content: SerializedContent::PointerTarget("common_a".to_owned()), - }) - ); - assert_eq!(stored_map["a2"], stored_map["a1"]); - assert_eq!( - stored_map["common_a"], - json!(SerializedParam { - description: "This is common a".to_owned(), - content: SerializedContent::DefaultValue(json!(10)) - }) - ); - - let serialized = serde_json::to_string(&stored_map).unwrap(); - let loaded = serde_json::from_str(&serialized).unwrap(); - let (loaded_config_map, loaded_pointers_map) = split_pointers_map(loaded); - let (mut config_map, _) = split_values_and_types(loaded_config_map); - update_config_map_by_pointers(&mut config_map, &loaded_pointers_map).unwrap(); - assert_eq!(config_map["a1"], json!(10)); - assert_eq!(config_map["a1"], config_map["a2"]); -} - -#[test] -fn test_replace_pointers() { - let (mut config_map, _) = - split_values_and_types(BTreeMap::from([ser_param("a", &json!(5), "This is a.")])); - let pointers_map = - BTreeMap::from([("b".to_owned(), "a".to_owned()), ("c".to_owned(), "a".to_owned())]); - update_config_map_by_pointers(&mut config_map, &pointers_map).unwrap(); - assert_eq!(config_map["a"], config_map["b"]); - assert_eq!(config_map["a"], config_map["c"]); - - let err = update_config_map_by_pointers(&mut BTreeMap::default(), &pointers_map).unwrap_err(); - assert_matches!(err, ConfigError::PointerTargetNotFound { .. }); -} - -#[derive(Clone, Default, Serialize, Deserialize, Debug, PartialEq)] -struct CustomConfig { - param_path: String, -} - -impl SerializeConfig for CustomConfig { - fn dump(&self) -> BTreeMap { - BTreeMap::from([ser_param("param_path", &self.param_path, "This is param_path.")]) - } -} - -// Loads param_path of CustomConfig from args. -fn load_param_path(args: Vec<&str>) -> String { - let dir = TempDir::new().unwrap(); - let file_path = dir.path().join("config.json"); - CustomConfig { param_path: "default value".to_owned() } - .dump_to_file(&vec![], file_path.to_str().unwrap()) - .unwrap(); - - let loaded_config = load_and_process_config::( - File::open(file_path).unwrap(), - Command::new("Program"), - args.into_iter().map(|s| s.to_owned()).collect(), - ) - .unwrap(); - loaded_config.param_path -} - -#[test] -fn test_load_default_config() { - let args = vec!["Testing"]; - let param_path = load_param_path(args); - assert_eq!(param_path, "default value"); -} - -#[test] -fn test_load_custom_config_file() { - let args = vec!["Testing", "-f", CUSTOM_CONFIG_PATH.to_str().unwrap()]; - let param_path = load_param_path(args); - assert_eq!(param_path, "custom value"); -} - -#[test] -fn test_load_custom_config_file_and_args() { - let args = vec![ - "Testing", - "--config_file", - CUSTOM_CONFIG_PATH.to_str().unwrap(), - "--param_path", - "command value", - ]; - let param_path = load_param_path(args); - assert_eq!(param_path, "command value"); -} - -#[test] -fn test_load_many_custom_config_files() { - let custom_config_path = CUSTOM_CONFIG_PATH.to_str().unwrap(); - let cli_config_param = format!("{custom_config_path},{custom_config_path}"); - let args = vec!["Testing", "-f", cli_config_param.as_str()]; - let param_path = load_param_path(args); - assert_eq!(param_path, "custom value"); -} - -#[test] -fn serialization_precision() { - let input = - "{\"value\":244116128358498188146337218061232635775543270890529169229936851982759783745}"; - let serialized = serde_json::from_str::(input).unwrap(); - let deserialized = serde_json::to_string(&serialized).unwrap(); - assert_eq!(input, deserialized); -} - -#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)] -struct RequiredConfig { - param_path: String, - num: usize, -} - -impl SerializeConfig for RequiredConfig { - fn dump(&self) -> BTreeMap { - BTreeMap::from([ - ser_required_param("param_path", SerializationType::String, "This is param_path."), - ser_param("num", &self.num, "This is num."), - ]) - } -} - -// Loads param_path of CustomConfig from args. -fn load_required_param_path(args: Vec<&str>) -> String { - let dir = TempDir::new().unwrap(); - let file_path = dir.path().join("config.json"); - RequiredConfig { param_path: "default value".to_owned(), num: 3 } - .dump_to_file(&vec![], file_path.to_str().unwrap()) - .unwrap(); - - let loaded_config = load_and_process_config::( - File::open(file_path).unwrap(), - Command::new("Program"), - args.into_iter().map(|s| s.to_owned()).collect(), - ) - .unwrap(); - loaded_config.param_path -} - -#[test] -fn test_negative_required_param() { - let dumped_config = RequiredConfig { param_path: "0".to_owned(), num: 3 }.dump(); - let (config_map, _) = split_values_and_types(dumped_config); - let err = load::(&config_map).unwrap_err(); - assert_matches!(err, ConfigError::MissingParam { .. }); -} - -#[test] -fn test_required_param_from_command() { - let args = vec!["Testing", "--param_path", "1234"]; - let param_path = load_required_param_path(args); - assert_eq!(param_path, "1234"); -} - -#[test] -fn test_required_param_from_file() { - let args = vec!["Testing", "--config_file", CUSTOM_CONFIG_PATH.to_str().unwrap()]; - let param_path = load_required_param_path(args); - assert_eq!(param_path, "custom value"); -} diff --git a/crates/client/starknet_client/papyrus_config/src/converters.rs b/crates/client/starknet_client/papyrus_config/src/converters.rs deleted file mode 100644 index c70ed1596f..0000000000 --- a/crates/client/starknet_client/papyrus_config/src/converters.rs +++ /dev/null @@ -1,80 +0,0 @@ -//! Utils for serialization and deserialization of nested config fields into simple types. -//! These conversions let the command line updater (which supports only numbers strings and -//! booleans) handle these fields. -//! -//! # example -//! -//! ``` -//! use std::collections::BTreeMap; -//! use std::time::Duration; -//! -//! use papyrus_config::converters::deserialize_milliseconds_to_duration; -//! use papyrus_config::loading::load; -//! use serde::Deserialize; -//! use serde_json::json; -//! -//! #[derive(Clone, Deserialize, Debug, PartialEq)] -//! struct DurationConfig { -//! #[serde(deserialize_with = "deserialize_milliseconds_to_duration")] -//! dur: Duration, -//! } -//! -//! let dumped_config = BTreeMap::from([("dur".to_owned(), json!(1000))]); -//! let loaded_config = load::(&dumped_config).unwrap(); -//! assert_eq!(loaded_config.dur.as_secs(), 1); -//! ``` - -use std::collections::HashMap; -use std::time::Duration; - -use serde::de::Error; -use serde::{Deserialize, Deserializer}; - -/// Deserializes milliseconds to duration object. -pub fn deserialize_milliseconds_to_duration<'de, D>(de: D) -> Result -where - D: Deserializer<'de>, -{ - let millis: u64 = Deserialize::deserialize(de)?; - Ok(Duration::from_millis(millis)) -} - -/// Deserializes seconds to duration object. -pub fn deserialize_seconds_to_duration<'de, D>(de: D) -> Result -where - D: Deserializer<'de>, -{ - let secs: u64 = Deserialize::deserialize(de)?; - Ok(Duration::from_secs(secs)) -} - -/// Serializes a map to "k1:v1 k2:v2" string structure. -pub fn serialize_optional_map(optional_map: &Option>) -> String { - match optional_map { - None => "".to_owned(), - Some(map) => map.iter().map(|(k, v)| format!("{k}:{v}")).collect::>().join(" "), - } -} - -/// Deserializes a map from "k1:v1 k2:v2" string structure. -pub fn deserialize_optional_map<'de, D>(de: D) -> Result>, D::Error> -where - D: Deserializer<'de>, -{ - let raw_str: String = Deserialize::deserialize(de)?; - if raw_str.is_empty() { - return Ok(None); - } - - let mut map = HashMap::new(); - for raw_pair in raw_str.split(' ') { - let split: Vec<&str> = raw_pair.split(':').collect(); - if split.len() != 2 { - return Err(D::Error::custom(format!( - "pair \"{raw_pair}\" is not valid. The Expected format is name:value" - ))); - } - map.insert(split[0].to_string(), split[1].to_string()); - } - Ok(Some(map)) -} diff --git a/crates/client/starknet_client/papyrus_config/src/dumping.rs b/crates/client/starknet_client/papyrus_config/src/dumping.rs deleted file mode 100644 index 6125c674d8..0000000000 --- a/crates/client/starknet_client/papyrus_config/src/dumping.rs +++ /dev/null @@ -1,203 +0,0 @@ -//! Utils for serializing config objects into flatten map and json file. -//! The elements structure is: -//! -//! ```json -//! "conf1.conf2.conf3.param_name": { -//! "description": "Param description.", -//! "value": json_value -//! } -//! ``` -//! In addition, supports pointers in the map, with the structure: -//! -//! ```json -//! "conf1.conf2.conf3.param_name": { -//! "description": "Param description.", -//! "pointer_target": "target_param_path" -//! } -//! ``` -//! -//! Supports required params. A required param has no default value, but the type of value that the -//! user must set: -//! ```json -//! "conf1.conf2.conf3.param_name: { -//! "description": "Param description.", -//! "required_type": Number -//! } -//! ``` -//! -//! Supports flags for optional params and sub-configs. An optional param / sub-config has an -//! "#is_none" indicator that determines whether to take its value or to deserialize it to None: -//! ```json -//! "conf1.conf2.#is_none": { -//! "description": "Flag for an optional field.", -//! "value": true -//! } -//! ``` - -use std::collections::BTreeMap; -use std::fs::File; -use std::io::{BufWriter, Write}; - -use itertools::chain; -use serde::Serialize; -use serde_json::{json, Value}; - -use crate::{ - ConfigError, - ParamPath, - SerializationType, - SerializedContent, - SerializedParam, - IS_NONE_MARK, -}; - -/// Serialization for configs. -pub trait SerializeConfig { - /// Conversion of a configuration to a mapping of flattened parameters to their descriptions and - /// values. - /// Note, in the case of a None sub configs, its elements will not included in the flatten map. - fn dump(&self) -> BTreeMap; - - /// Serialization of a configuration into a JSON file. - /// Takes a vector of {target pointer params, SerializedParam, and vector of pointing params}, - /// adds the target pointer params with the description and a value, and replaces the value of - /// the pointing params to contain only the name of the target they point to. - /// - /// Note, in the case of a None sub configs, its elements will not included in the file. - fn dump_to_file( - &self, - config_pointers: &Vec<((ParamPath, SerializedParam), Vec)>, - file_path: &str, - ) -> Result<(), ConfigError> { - let combined_map = combine_config_map_and_pointers(self.dump(), config_pointers)?; - let file = File::create(file_path)?; - let mut writer = BufWriter::new(file); - serde_json::to_writer_pretty(&mut writer, &combined_map)?; - writer.flush()?; - Ok(()) - } -} - -/// Appends `sub_config_name` to the ParamPath for each entry in `sub_config_dump`. -/// In order to load from a dump properly, `sub_config_name` must match the field's name for the -/// struct this function is called from. -pub fn append_sub_config_name( - sub_config_dump: BTreeMap, - sub_config_name: &str, -) -> BTreeMap { - BTreeMap::from_iter( - sub_config_dump - .into_iter() - .map(|(field_name, val)| (format!("{sub_config_name}.{field_name}"), val)), - ) -} - -/// Serializes a single param of a config. -/// The returned pair is designed to be an input to a dumped config map. -pub fn ser_param( - name: &str, - value: &T, - description: &str, -) -> (String, SerializedParam) { - ( - name.to_owned(), - SerializedParam { - description: description.to_owned(), - content: SerializedContent::DefaultValue(json!(value)), - }, - ) -} - -/// Serializes expected type for a single required param of a config. -/// The returned pair is designed to be an input to a dumped config map. -pub fn ser_required_param( - name: &str, - serialization_type: SerializationType, - description: &str, -) -> (String, SerializedParam) { - ( - name.to_owned(), - SerializedParam { - description: description.to_owned(), - content: SerializedContent::RequiredType(serialization_type), - }, - ) -} - -/// Serializes optional sub-config fields (or default fields for None sub-config) and adds an -/// "#is_none" flag. -pub fn ser_optional_sub_config( - optional_config: &Option, - name: &str, -) -> BTreeMap { - chain!( - BTreeMap::from_iter([ser_is_param_none(name, optional_config.is_none())]), - append_sub_config_name( - match optional_config { - None => T::default().dump(), - Some(config) => config.dump(), - }, - name, - ), - ) - .collect() -} - -/// Serializes optional param value (or default value for None param) and adds an "#is_none" flag. -pub fn ser_optional_param( - optional_param: &Option, - default_value: T, - name: &str, - description: &str, -) -> BTreeMap { - BTreeMap::from([ - ser_is_param_none(name, optional_param.is_none()), - ser_param( - name, - match optional_param { - Some(param) => param, - None => &default_value, - }, - description, - ), - ]) -} - -/// Serializes is_none flag for a param. -pub fn ser_is_param_none(name: &str, is_none: bool) -> (String, SerializedParam) { - ( - format!("{name}.{IS_NONE_MARK}"), - SerializedParam { - description: "Flag for an optional field".to_owned(), - content: SerializedContent::DefaultValue(json!(is_none)), - }, - ) -} - -// Takes a config map and a vector of {target param, serialized pointer, and vector of params that -// will point to it}. -// Adds to the map the target params. -// Replaces the value of the pointers to contain only the name of the target they point to. -pub(crate) fn combine_config_map_and_pointers( - mut config_map: BTreeMap, - pointers: &Vec<((ParamPath, SerializedParam), Vec)>, -) -> Result { - for ((target_param, serialized_pointer), pointing_params_vec) in pointers { - config_map.insert(target_param.clone(), serialized_pointer.clone()); - - for pointing_param in pointing_params_vec { - let pointing_serialized_param = - config_map.get(pointing_param).ok_or(ConfigError::PointerSourceNotFound { - pointing_param: pointing_param.to_owned(), - })?; - config_map.insert( - pointing_param.to_owned(), - SerializedParam { - description: pointing_serialized_param.description.clone(), - content: SerializedContent::PointerTarget(target_param.to_owned()), - }, - ); - } - } - Ok(json!(config_map)) -} diff --git a/crates/client/starknet_client/papyrus_config/src/lib.rs b/crates/client/starknet_client/papyrus_config/src/lib.rs deleted file mode 100644 index 3885d08931..0000000000 --- a/crates/client/starknet_client/papyrus_config/src/lib.rs +++ /dev/null @@ -1,132 +0,0 @@ -// config compiler to support no_coverage feature when running coverage in nightly mode within this -// crate -#![cfg_attr(coverage_nightly, feature(no_coverage))] -#![warn(missing_docs)] -//! Configuration utilities for a Starknet node. -//! -//! # Example -//! -//! ``` -//! use std::collections::BTreeMap; -//! use std::fs::File; -//! use std::path::Path; -//! -//! use clap::Command; -//! use papyrus_config::dumping::{ser_param, SerializeConfig}; -//! use papyrus_config::loading::load_and_process_config; -//! use papyrus_config::{ParamPath, SerializedParam}; -//! use serde::{Deserialize, Serialize}; -//! use tempfile::TempDir; -//! -//! #[derive(Clone, Serialize, Deserialize, Debug, PartialEq)] -//! struct ConfigExample { -//! key: usize, -//! } -//! -//! impl SerializeConfig for ConfigExample { -//! fn dump(&self) -> BTreeMap { -//! BTreeMap::from([ser_param("key", &self.key, "This is key description.")]) -//! } -//! } -//! -//! let dir = TempDir::new().unwrap(); -//! let file_path = dir.path().join("config.json"); -//! ConfigExample { key: 42 }.dump_to_file(&vec![], file_path.to_str().unwrap()); -//! let file = File::open(file_path).unwrap(); -//! let loaded_config = load_and_process_config::( -//! file, -//! Command::new("Program"), -//! vec!["Program".to_owned(), "--key".to_owned(), "770".to_owned()], -//! ) -//! .unwrap(); -//! assert_eq!(loaded_config.key, 770); -//! ``` - -use clap::parser::MatchesError; -use serde::{Deserialize, Serialize}; -use serde_json::Value; - -pub(crate) const IS_NONE_MARK: &str = "#is_none"; - -/// A nested path of a configuration parameter. -pub type ParamPath = String; -/// A description of a configuration parameter. -pub type Description = String; - -#[cfg(test)] -mod config_test; - -mod command; -pub mod converters; -pub mod dumping; -pub mod loading; -pub mod validators; - -/// A serialized content of a configuration parameter. -#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)] -#[serde(rename_all = "snake_case")] -pub enum SerializedContent { - /// Serialized JSON default value. - #[serde(rename = "value")] - DefaultValue(Value), - /// The target from which to take the JSON value of a configuration parameter. - PointerTarget(ParamPath), - /// Type of a required configuration parameter. - RequiredType(SerializationType), -} - -impl SerializedContent { - fn get_serialization_type(&self) -> Option { - match self { - SerializedContent::DefaultValue(value) => match value { - Value::Number(_) => Some(SerializationType::Number), - Value::Bool(_) => Some(SerializationType::Boolean), - Value::String(_) => Some(SerializationType::String), - _ => None, - }, - SerializedContent::PointerTarget(_) => None, - SerializedContent::RequiredType(ser_type) => Some(ser_type.clone()), - } - } -} - -/// A description and serialized content of a configuration parameter. -#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)] -pub struct SerializedParam { - /// The description of the parameter. - pub description: Description, - /// The content of the parameter. - #[serde(flatten)] - pub content: SerializedContent, -} - -/// A serialized type of a configuration parameter. -#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, strum_macros::Display)] -#[allow(missing_docs)] -pub enum SerializationType { - Number, - Boolean, - String, -} - -/// Errors at the configuration dumping and loading process. -#[allow(missing_docs)] -#[derive(thiserror::Error, Debug)] -pub enum ConfigError { - #[error(transparent)] - CommandInput(#[from] clap::error::Error), - #[error(transparent)] - MissingParam(#[from] serde_json::Error), - #[error(transparent)] - CommandMatches(#[from] MatchesError), - #[error(transparent)] - WriteDumpedConfig(#[from] std::io::Error), - #[error("Insert a new param is not allowed.")] - ParamNotFound { param_path: String }, - #[error("{target_param} is not found.")] - PointerTargetNotFound { target_param: String }, - #[error("{pointing_param} is not found.")] - PointerSourceNotFound { pointing_param: String }, - #[error("Changing {param_path} from required type {required} to {given} is not allowed.")] - ChangeRequiredParamType { param_path: String, required: SerializationType, given: Value }, -} diff --git a/crates/client/starknet_client/papyrus_config/src/loading.rs b/crates/client/starknet_client/papyrus_config/src/loading.rs deleted file mode 100644 index 846db2583f..0000000000 --- a/crates/client/starknet_client/papyrus_config/src/loading.rs +++ /dev/null @@ -1,200 +0,0 @@ -//! Loads a configuration object, and set values for the fields in the following order of priority: -//! * Command line arguments. -//! * Environment variables (capital letters). -//! * Custom config files, separated by ',' (comma), from last to first. -//! * Default config file. - -use std::collections::BTreeMap; -use std::fs::File; -use std::ops::IndexMut; -use std::path::PathBuf; - -use clap::parser::Values; -use clap::Command; -use command::{get_command_matches, update_config_map_by_command_args}; -use itertools::any; -use serde::Deserialize; -use serde_json::{json, Map, Value}; - -use crate::{ - command, - ConfigError, - ParamPath, - SerializationType, - SerializedContent, - SerializedParam, - IS_NONE_MARK, -}; - -/// Deserializes config from flatten JSON. -/// For an explanation of `for<'a> Deserialize<'a>` see -/// ``. -pub fn load Deserialize<'a>>( - config_map: &BTreeMap, -) -> Result { - let mut nested_map = json!({}); - for (param_path, value) in config_map { - let mut entry = &mut nested_map; - for config_name in param_path.split('.') { - entry = entry.index_mut(config_name); - } - *entry = value.clone(); - } - Ok(serde_json::from_value(nested_map)?) -} - -/// Deserializes a json config file, updates the values by the given arguments for the command, and -/// set values for the pointers. -pub fn load_and_process_config Deserialize<'a>>( - default_config_file: File, - command: Command, - args: Vec, -) -> Result { - let deserialized_default_config: Map = - serde_json::from_reader(default_config_file)?; - - // Store the pointers separately from the default values. The pointers will receive a value - // only at the end of the process. - let (default_config_map, pointers_map) = split_pointers_map(deserialized_default_config); - // Take param paths with corresponding descriptions, and get the matching arguments. - let mut arg_matches = get_command_matches(&default_config_map, command, args)?; - let (mut values_map, types_map) = split_values_and_types(default_config_map); - // If the config_file arg is given, updates the values map according to this files. - if let Some(custom_config_paths) = arg_matches.remove_many::("config_file") { - update_config_map_by_custom_configs(&mut values_map, &types_map, custom_config_paths)?; - }; - // Updates the values map according to the args. - update_config_map_by_command_args(&mut values_map, &types_map, &arg_matches)?; - // Set values to the pointers. - update_config_map_by_pointers(&mut values_map, &pointers_map)?; - // Set values according to the is-none marks. - update_optional_values(&mut values_map); - // Build and return a Config object. - load(&values_map) -} - -// Separates a json map into config map of the raw values and pointers map. -pub(crate) fn split_pointers_map( - json_map: Map, -) -> (BTreeMap, BTreeMap) { - let mut config_map: BTreeMap = BTreeMap::new(); - let mut pointers_map: BTreeMap = BTreeMap::new(); - for (param_path, stored_param) in json_map { - let Ok(ser_param) = serde_json::from_value::(stored_param.clone()) else { - unreachable!("Invalid type in the json config map") - }; - match ser_param.content { - SerializedContent::PointerTarget(pointer_target) => { - pointers_map.insert(param_path, pointer_target); - } - _ => { - config_map.insert(param_path, ser_param); - } - }; - } - (config_map, pointers_map) -} - -// Removes the description from the config map, and splits the config map into default values and -// types of the default and required values. -// The types map includes required params, that do not have a value yet. -pub(crate) fn split_values_and_types( - config_map: BTreeMap, -) -> (BTreeMap, BTreeMap) { - let mut values_map: BTreeMap = BTreeMap::new(); - let mut types_map: BTreeMap = BTreeMap::new(); - for (param_path, serialized_param) in config_map { - let Some(serialization_type) = serialized_param.content.get_serialization_type() else { - continue; - }; - types_map.insert(param_path.clone(), serialization_type); - - if let SerializedContent::DefaultValue(value) = serialized_param.content { - values_map.insert(param_path, value); - }; - } - (values_map, types_map) -} - -// Updates the config map by param path to value custom json files. -pub(crate) fn update_config_map_by_custom_configs( - config_map: &mut BTreeMap, - types_map: &BTreeMap, - custom_config_paths: Values, -) -> Result<(), ConfigError> { - for config_path in custom_config_paths { - let file = std::fs::File::open(config_path)?; - let custom_config: Map = serde_json::from_reader(file)?; - for (param_path, json_value) in custom_config { - update_config_map(config_map, types_map, param_path.as_str(), json_value)?; - } - } - Ok(()) -} - -// Sets values in the config map to the params in the pointers map. -pub(crate) fn update_config_map_by_pointers( - config_map: &mut BTreeMap, - pointers_map: &BTreeMap, -) -> Result<(), ConfigError> { - for (param_path, target_param_path) in pointers_map { - let Some(target_value) = config_map.get(target_param_path) else { - return Err(ConfigError::PointerTargetNotFound { - target_param: target_param_path.to_owned(), - }); - }; - config_map.insert(param_path.to_owned(), target_value.clone()); - } - Ok(()) -} - -// Removes the none marks, and sets null for the params marked as None instead of the inner params. -pub(crate) fn update_optional_values(config_map: &mut BTreeMap) { - let optional_params: Vec<_> = config_map - .keys() - .filter_map(|param_path| param_path.strip_suffix(&format!(".{IS_NONE_MARK}"))) - .map(|param_path| param_path.to_owned()) - .collect(); - let mut none_params = vec![]; - for optional_param in optional_params { - let value = config_map - .remove(&format!("{optional_param}.{IS_NONE_MARK}")) - .expect("Not found optional param"); - if value == json!(true) { - none_params.push(optional_param); - } - } - // Remove param paths that start with any None param. - config_map.retain(|param_path, _| { - !any(&none_params, |none_param| param_path.starts_with(none_param)) - }); - for none_param in none_params { - config_map.insert(none_param, Value::Null); - } -} - -pub(crate) fn update_config_map( - config_map: &mut BTreeMap, - types_map: &BTreeMap, - param_path: &str, - new_value: Value, -) -> Result<(), ConfigError> { - let Some(serialization_type) = types_map.get(param_path) else { - return Err(ConfigError::ParamNotFound { param_path: param_path.to_string() }); - }; - let is_type_matched = match serialization_type { - SerializationType::Number => new_value.is_number(), - SerializationType::Boolean => new_value.is_boolean(), - SerializationType::String => new_value.is_string(), - }; - if !is_type_matched { - return Err(ConfigError::ChangeRequiredParamType { - param_path: param_path.to_string(), - required: serialization_type.to_owned(), - given: new_value, - }); - } - - config_map.insert(param_path.to_owned(), new_value); - Ok(()) -} diff --git a/crates/client/starknet_client/papyrus_config/src/validators.rs b/crates/client/starknet_client/papyrus_config/src/validators.rs deleted file mode 100644 index c2153285ed..0000000000 --- a/crates/client/starknet_client/papyrus_config/src/validators.rs +++ /dev/null @@ -1,11 +0,0 @@ -//! Utils for config validations. - -use validator::ValidationError; - -/// Custom validation for ASCII string. -pub fn validate_ascii(name: &impl ToString) -> Result<(), ValidationError> { - if !name.to_string().is_ascii() { - return Err(ValidationError::new("ASCII Validation")); - } - Ok(()) -} diff --git a/crates/client/starknet_client/resources/reader/abi.json b/crates/client/starknet_client/resources/reader/abi.json deleted file mode 100644 index 38e1813bcc..0000000000 --- a/crates/client/starknet_client/resources/reader/abi.json +++ /dev/null @@ -1,177 +0,0 @@ -[ - { - "members": [ - { "name": "index", "offset": 0, "type": "felt" }, - { "name": "values", "offset": 1, "type": "(felt, felt)" } - ], - "name": "IndexAndValues", - "size": 3, - "type": "struct" - }, - { - "inputs": [ - { "name": "index", "type": "felt" }, - { "name": "diffs_len", "type": "felt" }, - { "name": "diffs", "type": "felt*" } - ], - "name": "advance_counter", - "outputs": [], - "type": "function" - }, - { - "inputs": [ - { "name": "address", "type": "felt" }, - { "name": "value", "type": "felt" } - ], - "name": "constructor", - "outputs": [], - "type": "constructor" - }, - { - "inputs": [{ "name": "index_and_x", "type": "IndexAndValues" }], - "name": "xor_counters", - "outputs": [], - "type": "function" - }, - { - "inputs": [ - { "name": "address", "type": "felt" }, - { "name": "index_and_x", "type": "IndexAndValues" } - ], - "name": "call_xor_counters", - "outputs": [], - "type": "function" - }, - { - "inputs": [{ "name": "index", "type": "felt" }], - "name": "add_signature_to_counters", - "outputs": [], - "type": "function" - }, - { - "inputs": [ - { "name": "address", "type": "felt" }, - { "name": "value", "type": "felt" } - ], - "name": "set_value", - "outputs": [], - "type": "function" - }, - { - "inputs": [{ "name": "address", "type": "felt" }], - "name": "get_value", - "outputs": [{ "name": "res", "type": "felt" }], - "type": "function" - }, - { "inputs": [], "name": "entry_point", "outputs": [], "type": "function" }, - { - "inputs": [], - "name": "test_builtins", - "outputs": [{ "name": "result", "type": "felt" }], - "type": "function" - }, - { - "inputs": [{ "name": "to_address", "type": "felt" }], - "name": "send_message", - "outputs": [], - "type": "function" - }, - { - "inputs": [ - { "name": "contract_address", "type": "felt" }, - { "name": "function_selector", "type": "felt" }, - { "name": "calldata_len", "type": "felt" }, - { "name": "calldata", "type": "felt*" } - ], - "name": "test_call_contract", - "outputs": [], - "type": "function" - }, - { - "inputs": [ - { "name": "contract_address", "type": "felt" }, - { "name": "function_selector", "type": "felt" }, - { "name": "calldata_len", "type": "felt" }, - { "name": "calldata", "type": "felt*" } - ], - "name": "test_delegate_call", - "outputs": [], - "type": "function" - }, - { - "inputs": [ - { "name": "from_address", "type": "felt" }, - { "name": "amount", "type": "felt" } - ], - "name": "deposit", - "outputs": [], - "type": "l1_handler" - }, - { - "inputs": [{ "name": "expected_address", "type": "felt" }], - "name": "test_get_caller_address", - "outputs": [], - "type": "function" - }, - { - "inputs": [{ "name": "expected_address", "type": "felt" }], - "name": "test_get_sequencer_address", - "outputs": [], - "type": "function" - }, - { - "inputs": [{ "name": "expected_address", "type": "felt" }], - "name": "test_get_contract_address", - "outputs": [], - "type": "function" - }, - { - "inputs": [ - { "name": "other_contract_address", "type": "felt" }, - { "name": "address", "type": "felt" } - ], - "name": "test_call_storage_consistency", - "outputs": [], - "type": "function" - }, - { - "inputs": [ - { "name": "other_contract_address", "type": "felt" }, - { "name": "depth", "type": "felt" } - ], - "name": "test_re_entrance", - "outputs": [], - "type": "function" - }, - { - "inputs": [{ "name": "value", "type": "felt" }], - "name": "add_value", - "outputs": [], - "type": "function" - }, - { - "inputs": [ - { "name": "self_address", "type": "felt" }, - { "name": "value", "type": "felt" } - ], - "name": "recursive_add_value", - "outputs": [], - "type": "function" - }, - { - "inputs": [{ "name": "address", "type": "felt" }], - "name": "increase_value", - "outputs": [], - "type": "function" - }, - { - "inputs": [ - { "name": "self_address", "type": "felt" }, - { "name": "arr_len", "type": "felt" }, - { "name": "arr", "type": "felt*" } - ], - "name": "test_call_with_array", - "outputs": [], - "type": "function" - } -] diff --git a/crates/client/starknet_client/resources/reader/block.json b/crates/client/starknet_client/resources/reader/block.json deleted file mode 100644 index 0d950f89fe..0000000000 --- a/crates/client/starknet_client/resources/reader/block.json +++ /dev/null @@ -1,171 +0,0 @@ -{ - "block_hash": "0x13dba522b9185b699399263c9a2afb409c03038f4bb26a46fb5fb9be3b041f2", - "parent_block_hash": "0x76fc47eb559b3a167888021394d83d707162ad5d92c15996c3aa7ac98369645", - "block_number": 273466, - "state_root": "0x070325bab27893352a2e2e3964c2898c5ac2b1c8eb16f4cd2d8e179c600e4fa7", - "status": "ACCEPTED_ON_L1", - "gas_price": "0x59682f03", - "transactions": [ - { - "contract_address": "0x3b3ca08150f47c715bcd3493e5b7fec3732ded1b884f8513bcab111f8949e5b", - "contract_address_salt": "0x1b551a2d45a5413d0b9fa8314b0fa12766cac44e4707ac30dd14677c41b2a3b", - "class_hash": "0x6ed527800ce2621c354e50d57cc1d6c0b6e3255a0eee04470254823417fecfa", - "constructor_calldata": [], - "transaction_hash": "0x1c60d1088f403f3ca990e12131e71fed086920dae52ccee3e5e80e1bf19dc0f", - "type": "DEPLOY" - }, - { - "contract_address": "0x6d0a7c29de4ea81d1b9982c04f691320a6b65eef9d6ea847b4b077a0305a24e", - "entry_point_selector": "0x15d40a3d6ca2ac30f4031e42be28da9b056fef9bb7357ac5e85627ee876e5ad", - "calldata": [ - "0x1", - "0x7394cbe418daa16e42b87ba67372d4ab4a5df0b05c6e554d158458ce245bc10", - "0x2f0b3c5710379609eb5495f1ecd348cb28167711b73609fe565a72734550354", - "0x0", - "0x3", - "0x3", - "0x6d0a7c29de4ea81d1b9982c04f691320a6b65eef9d6ea847b4b077a0305a24e", - "0x3635c9adc5dea00000", - "0x0", - "0x34" - ], - "signature": [ - "0x628c9d4398de3686311ad2d7cb90792a30070155e26b3cf98559fb0a387393b", - "0x7762c61f244f76a2ddf57ecc8c117d6906af827db8796e16ae3e291c31b75e2" - ], - "transaction_hash": "0x6e81d0030bfae36fc55bf682f96dc2d103ee02f439b10c8e9af6742e7d7e2ea", - "max_fee": "0x148b1ed190ca", - "type": "INVOKE_FUNCTION", - "version": "0x0" - }, - { - "class_hash": "0x5abf9436be774a4d4af00528296700d0181b8cf3cf85ccc556b441ef5876ffe", - "sender_address": "0x1", - "nonce": "0x0", - "max_fee": "0x0", - "version": "0x1", - "transaction_hash": "0x3ff2070e6723bb9b6414977324f916eb53b51f9691e5d9a4fb67160d048958b", - "signature": [], - "type": "DECLARE" - }, - { - "class_hash": "0x5abf9436be774a4d4af00528296700d0181b8cf3cf85ccc556b441ef5876ffe", - "compiled_class_hash": "0x5abf9436be774a4d4af00528296700d0181b8cf3cf85ccc556b441ef5876ffe", - "sender_address": "0x1", - "nonce": "0x0", - "max_fee": "0x0", - "version": "0x2", - "transaction_hash": "0x3ff2070e346", - "signature": [], - "type": "DECLARE" - }, - { - "version": "0x0", - "contract_address": "0x55a46448decca3b138edf0104b7a47d41365b8293bdfd59b03b806c102b12b7", - "entry_point_selector": "0xc73f681176fc7b3f9693986fd7b14581e8d540519e27400e88b8713932be01", - "nonce": "0x0", - "calldata": [ - "0x2db8c2615db39a5ed8750b87ac8f217485be11ec", - "0xbc614e", - "0x258" - ], - "transaction_hash": "0xfb118dc1d4a4141b7718da4b7fa98980b11caf5aa5d6e1e35e9b050aae788b", - "type": "L1_HANDLER" - } - ], - "timestamp": 1658396103, - "sequencer_address": "0x46a89ae102987331d369645031b49c27738ed096f2789c24449966da4c6de6b", - "transaction_receipts": [ - { - "transaction_index": 0, - "transaction_hash": "0x1c60d1088f403f3ca990e12131e71fed086920dae52ccee3e5e80e1bf19dc0f", - "l2_to_l1_messages": [], - "events": [], - "execution_resources": { - "n_steps": 0, - "builtin_instance_counter": {}, - "n_memory_holes": 0 - }, - "actual_fee": "0x0" - }, - { - "transaction_index": 1, - "transaction_hash": "0x6e81d0030bfae36fc55bf682f96dc2d103ee02f439b10c8e9af6742e7d7e2ea", - "l2_to_l1_messages": [], - "events": [ - { - "from_address": "0x6d0a7c29de4ea81d1b9982c04f691320a6b65eef9d6ea847b4b077a0305a24e", - "keys": [ - "0x5ad857f66a5b55f1301ff1ed7e098ac6d4433148f0b72ebc4a2945ab85ad53" - ], - "data": [ - "0x6e81d0030bfae36fc55bf682f96dc2d103ee02f439b10c8e9af6742e7d7e2ea", - "0x0" - ] - } - ], - "execution_resources": { - "n_steps": 754, - "builtin_instance_counter": { - "pedersen_builtin": 2, - "range_check_builtin": 16, - "ecdsa_builtin": 1, - "output_builtin": 0, - "bitwise_builtin": 0 - }, - "n_memory_holes": 25 - }, - "actual_fee": "0xdb2148b8ea5" - }, - { - "transaction_index": 2, - "transaction_hash": "0x3ff2070e6723bb9b6414977324f916eb53b51f9691e5d9a4fb67160d048958b", - "l2_to_l1_messages": [], - "events": [], - "execution_resources": { - "n_steps": 0, - "builtin_instance_counter": {}, - "n_memory_holes": 0 - }, - "actual_fee": "0x0" - }, - { - "transaction_index": 3, - "transaction_hash": "0x3ff2070e346", - "l2_to_l1_messages": [], - "events": [], - "execution_resources": { - "n_steps": 0, - "builtin_instance_counter": {}, - "n_memory_holes": 0 - }, - "actual_fee": "0x0" - }, - { - "transaction_index": 4, - "transaction_hash": "0xfb118dc1d4a4141b7718da4b7fa98980b11caf5aa5d6e1e35e9b050aae788b", - "l1_to_l2_consumed_message": { - "from_address": "0x2Db8c2615db39a5eD8750B87aC8F217485BE11EC", - "to_address": "0x55a46448decca3b138edf0104b7a47d41365b8293bdfd59b03b806c102b12b7", - "selector": "0xc73f681176fc7b3f9693986fd7b14581e8d540519e27400e88b8713932be01", - "payload": ["0xbc614e", "0x258"] - }, - "l2_to_l1_messages": [], - "events": [], - "execution_resources": { - "n_steps": 137, - "builtin_instance_counter": { - "pedersen_builtin": 2, - "range_check_builtin": 6, - "bitwise_builtin": 0, - "output_builtin": 0, - "ecdsa_builtin": 0, - "ec_op_builtin": 0 - }, - "n_memory_holes": 22 - }, - "actual_fee": "0x0" - } - ], - "starknet_version": "0.9.1" -} diff --git a/crates/client/starknet_client/resources/reader/block_state_update.json b/crates/client/starknet_client/resources/reader/block_state_update.json deleted file mode 100644 index 9c75817dd7..0000000000 --- a/crates/client/starknet_client/resources/reader/block_state_update.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "block_hash": "0x3f65ef25e87a83d92f32f5e4869a33580f9db47ec980c1ff27bdb5151914de5", - "new_root": "0x02ade8eea6eb6523d22a408a1f035bd351a9a5dce28926ca92d7abb490c0e74a", - "old_root": "0x0465b219d93bcb2776aa3abb009423be3e2d04dba6453d7e027830740cd699a4", - "state_diff": { - "nonces": { - "0x51c62af8919b31499b36bd1f1f702c8ef5a6309554427186c7bd456b862c115": "0x12" - }, - "storage_diffs": { - "0x13386f165f065115c1da38d755be261023c32f0134a03a8e66b6bb1e0016014": [ - { - "key": "0x3b3a699bb6ef37ff4b9c4e14319c7d8e9c9bdd10ff402d1ebde18c62ae58381", - "value": "0x61454dd6e5c83621e41b74c" - }, - { - "key": "0x1557182e4359a1f0c6301278e8f5b35a776ab58d39892581e357578fb287836", - "value": "0x79dd8085e3e5a96ea43e7d" - } - ] - }, - "deployed_contracts": [ - { - "address": "0x3e10411edafd29dfe6d427d03e35cb261b7a5efeee61bf73909ada048c029b9", - "class_hash": "0x071c3c99f5cf76fc19945d4b8b7d34c7c5528f22730d56192b50c6bbfd338a64" - } - ], - "declared_classes": [ - { - "class_hash": "0x10", - "compiled_class_hash": "0x1000" - } - ], - "old_declared_contracts": ["0x100"], - "replaced_classes": [ - { - "address": "0x56b0efe9d91fcda0f341af928404056c5220ee0ccc66be15d20611a172dbd52", - "class_hash": "0x2248aff260e5837317641ff4f861495dd71e78b9dae98a31113e569b336bd26" - } - ] - } -} diff --git a/crates/client/starknet_client/resources/reader/casm_contract_class.json b/crates/client/starknet_client/resources/reader/casm_contract_class.json deleted file mode 100644 index 942e50bde7..0000000000 --- a/crates/client/starknet_client/resources/reader/casm_contract_class.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "entry_points_by_type": { - "EXTERNAL": [ - { - "offset": 787, - "builtins": ["pedersen", "range_check"], - "selector": "0x11dd528db174d6312644720bceeb9307ba53f6e2937246ac73d5fb30603016" - } - ], - "L1_HANDLER": [], - "CONSTRUCTOR": [ - { - "offset": 4305, - "builtins": ["range_check"], - "selector": "0x28ffe4ff0f226a9107253e17a904099aa4f63a02a5621de0576e5aa71bc5194" - } - ] - }, - "bytecode": [ - "0x40780017fff7fff", - "0x2", - "0x496e70757420746f6f2073686f727420666f7220617267756d656e7473" - ], - "prime": "0x800000000000011000000000000000000000000000000000000000000000001", - "pythonic_hints": [[2, ["memory[ap + 0] = 0 <= memory[fp + -6]"]]], - "hints": [ - [ - 2, - [ - { - "TestLessThanOrEqual": { - "lhs": { - "Immediate": "0x0" - }, - "rhs": { - "Deref": { - "register": "FP", - "offset": -6 - } - }, - "dst": { - "register": "AP", - "offset": 0 - } - } - } - ] - ] - ], - "compiler_version": "1.0.0" -} diff --git a/crates/client/starknet_client/resources/reader/contract_class.json b/crates/client/starknet_client/resources/reader/contract_class.json deleted file mode 100644 index 77aff5b3d0..0000000000 --- a/crates/client/starknet_client/resources/reader/contract_class.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "sierra_program": ["0x302e312e30", "0x1c", "0x52616e6765436865636b"], - "entry_points_by_type": { - "EXTERNAL": [ - { - "function_idx": 0, - "selector": "0x22ff5f21f0b81b113e63f7db6da94fedef11b2119b4088b89664fb9a3cb658" - } - ], - "L1_HANDLER": [], - "CONSTRUCTOR": [] - }, - "contract_class_version": "0.1.0", - "abi": "[\n {\n \"type\": \"function\",\n \"name\": \"test\",\n \"inputs\": [\n {\n \"name\": \"arg\",\n \"ty\": \"core::felt\"\n },\n {\n \"name\": \"arg1\",\n \"ty\": \"core::felt\"\n },\n {\n \"name\": \"arg2\",\n \"ty\": \"core::felt\"\n }\n ],\n \"output_ty\": \"core::felt\",\n \"state_mutability\": \"external\"\n },\n {\n \"type\": \"function\",\n \"name\": \"empty\",\n \"inputs\": [],\n \"output_ty\": \"()\",\n \"state_mutability\": \"external\"\n },\n {\n \"type\": \"function\",\n \"name\": \"call_foo\",\n \"inputs\": [\n {\n \"name\": \"a\",\n \"ty\": \"core::integer::u128\"\n }\n ],\n \"output_ty\": \"core::integer::u128\",\n \"state_mutability\": \"external\"\n }\n]" -} diff --git a/crates/client/starknet_client/resources/reader/declare_transaction.json b/crates/client/starknet_client/resources/reader/declare_transaction.json deleted file mode 100644 index 61c052bc59..0000000000 --- a/crates/client/starknet_client/resources/reader/declare_transaction.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "class_hash": "0x5abf9436be774a4d4af00528296700d0181b8cf3cf85ccc556b441ef5876ffe", - "sender_address": "0x1", - "nonce": "0x0", - "max_fee": "0x0", - "version": "0x0", - "transaction_hash": "0x3ff2070e6723bb9b6414977324f916eb53b51f9691e5d9a4fb67160d048958b", - "signature": [], - "type": "DECLARE" -} diff --git a/crates/client/starknet_client/resources/reader/deploy_transaction.json b/crates/client/starknet_client/resources/reader/deploy_transaction.json deleted file mode 100644 index 84a7630b8a..0000000000 --- a/crates/client/starknet_client/resources/reader/deploy_transaction.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "contract_address": "0x264266d63d373b5287aaa0f62eb1a31a297024bf9572339c15cb84b7fb51939", - "contract_address_salt": "0x4c40ae2941a804d6941b1794c00d14bed375d19aab9c477500abbacfa58e7bf", - "class_hash": "0x25ec026985a3bf9d0cc1fe17326b245dfdc3ff89b8fde106542a3ea56c5a918", - "constructor_calldata": [ - "0x3e327de1c40540b98d05cbcb13552008e36f0ec8d61d46956d2f9752c294328", - "0x79dc0da7c54b95f10aa182ad0a46400db63156920adb65eca2654c0945a463", - "0x2", - "0x4c40ae2941a804d6941b1794c00d14bed375d19aab9c477500abbacfa58e7bf", - "0x0" - ], - "transaction_hash": "0x166ccbbb1356303eba0081511df1a1ed6c59c3a36c4ed842773ab277586c372", - "type": "DEPLOY" -} diff --git a/crates/client/starknet_client/resources/reader/deprecated_contract_class.json b/crates/client/starknet_client/resources/reader/deprecated_contract_class.json deleted file mode 100644 index 368e99dccc..0000000000 --- a/crates/client/starknet_client/resources/reader/deprecated_contract_class.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "abi": [ - { - "inputs": [ - { - "name": "implementation", - "type": "felt" - } - ], - "name": "constructor", - "outputs": [], - "type": "constructor" - } - ], - "entry_points_by_type": { - "CONSTRUCTOR": [ - { - "offset": "0x3e", - "selector": "0x28ffe4ff0f226a9107253e17a904099aa4f63a02a5621de0576e5aa71bc5194" - } - ], - "EXTERNAL": [ - { - "offset": "0x56", - "selector": "0x0" - } - ], - "L1_HANDLER": [] - }, - "program": { - "builtins": [], - "data": ["0x20780017fff7ffd", "0x4", "0x400780017fff7ffd"], - "prime": "0x800000000000011000000000000000000000000000000000000000000000001", - "main_scope": "__main__", - "identifiers": {}, - "attributes": [1234], - "debug_info": null, - "reference_manager": {}, - "hints": {} - } -} diff --git a/crates/client/starknet_client/resources/reader/invoke_transaction.json b/crates/client/starknet_client/resources/reader/invoke_transaction.json deleted file mode 100644 index a5b064592f..0000000000 --- a/crates/client/starknet_client/resources/reader/invoke_transaction.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "sender_address": "0xef934e6c63a9acba42dd644d1a0c1af5df01eeac1b9f82320b53cfacd40890", - "entry_point_selector": "0x15d40a3d6ca2ac30f4031e42be28da9b056fef9bb7357ac5e85627ee876e5ad", - "calldata": [ - "0x2", - "0x13c56add3ee9699228614221602165185b49f712cae90fc20c608d7ecc1521b", - "0x2f0b3c5710379609eb5495f1ecd348cb28167711b73609fe565a72734550354", - "0x0", - "0x2", - "0x69202aeae73af1c685003f68de5edd3eafcc702e3b1125c5e24fe6b6ccbc0e6", - "0x2f0b3c5710379609eb5495f1ecd348cb28167711b73609fe565a72734550354", - "0x2", - "0x2", - "0x4", - "0x56bc75e2d63100000", - "0x0", - "0x56bc75e2d63100000", - "0x0", - "0x1" - ], - "signature": [ - "0x5d543c91737a46b9baa7c342e8b58f62ad438037dd63ac56cf0c64de7e6e21e", - "0xde8ce74586c27f66b89dc000f973a8efdf044d9b5587666a162e6db4111646" - ], - "transaction_hash": "0x706deaf73bc99c4a2026a7252f21e3c8db0946ac2221e2abe12a9f3b78192e0", - "max_fee": "0x21a3e1b5676f", - "type": "INVOKE_FUNCTION", - "version": "0x0" -} diff --git a/crates/client/starknet_client/resources/reader/invoke_transaction_l1_handler.json b/crates/client/starknet_client/resources/reader/invoke_transaction_l1_handler.json deleted file mode 100644 index a1d39da115..0000000000 --- a/crates/client/starknet_client/resources/reader/invoke_transaction_l1_handler.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "transaction_hash": "0x5d50b7020f7cf8033fd7d913e489f47edf74fbf3c8ada85be512c7baa6a2eab", - "version": "0x0", - "contract_address": "0x58b43819bb12aba8ab3fb2e997523e507399a3f48a1e2aa20a5fb7734a0449f", - "entry_point_selector": "0xe3f5e9e1456ffa52a3fbc7e8c296631d4cc2120c0be1e2829301c0d8fa026b", - "calldata": [ - "0x5474c49483aa09993090979ade8101ebb4cdce4a", - "0xabf8dd8438d1c21e83a8b5e9c1f9b58aaf3ed360", - "0x2", - "0x4c04fac82913f01a8f01f6e15ff7e834ff2d9a9a1d8e9adffc7bd45692f4f9a" - ], - "type": "L1_HANDLER" -} diff --git a/crates/client/starknet_client/resources/reader/transaction_receipt.json b/crates/client/starknet_client/resources/reader/transaction_receipt.json deleted file mode 100644 index c76ec7e8fe..0000000000 --- a/crates/client/starknet_client/resources/reader/transaction_receipt.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "transaction_index": 1, - "transaction_hash": "0x4586cb82c15ec15a123ba42279aae105f2304cbc1f992f8360ab1b1eb0f718", - "l1_to_l2_consumed_message": { - "from_address": "0xc3511006C04EF1d78af4C8E0e74Ec18A6E64Ff9e", - "to_address": "0x73314940630fd6dcda0d772d4c972c4e0a9946bef9dabf4ef84eda8ef542b82", - "selector": "0x2d757788a8d8d6f21d1cd40bce38a8222d70654214e96ff95d8086e684fbee5", - "payload": [ - "0x26ee727ad466d4255a76f4676eaec72adb3e30e14461952a2062b3e87cbdc7f", - "0xde0b6b3a7640000", - "0x0" - ], - "nonce": "0x26fc3" - }, - "l2_to_l1_messages": [], - "events": [ - { - "from_address": "0x49d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7", - "keys": [ - "0x99cd8bde557814842a3121e8ddfd433a539b8c9f14bf31ebf108d12e6196e9" - ], - "data": [ - "0x0", - "0x26ee727ad466d4255a76f4676eaec72adb3e30e14461952a2062b3e87cbdc7f", - "0xde0b6b3a7640000", - "0x0" - ] - }, - { - "from_address": "0x73314940630fd6dcda0d772d4c972c4e0a9946bef9dabf4ef84eda8ef542b82", - "keys": [ - "0x221e5a5008f7a28564f0eaa32cdeb0848d10657c449aed3e15d12150a7c2db3" - ], - "data": [ - "0x26ee727ad466d4255a76f4676eaec72adb3e30e14461952a2062b3e87cbdc7f", - "0xde0b6b3a7640000", - "0x0" - ] - } - ], - "execution_resources": { - "n_steps": 673, - "builtin_instance_counter": { - "pedersen_builtin": 2, - "range_check_builtin": 12, - "output_builtin": 0, - "ecdsa_builtin": 0, - "bitwise_builtin": 0 - }, - "n_memory_holes": 22 - }, - "actual_fee": "0x0" -} diff --git a/crates/client/starknet_client/resources/reader/transaction_receipt_without_l1_to_l2.json b/crates/client/starknet_client/resources/reader/transaction_receipt_without_l1_to_l2.json deleted file mode 100644 index 709ebbdc97..0000000000 --- a/crates/client/starknet_client/resources/reader/transaction_receipt_without_l1_to_l2.json +++ /dev/null @@ -1,64 +0,0 @@ -{ - "transaction_index": 1, - "transaction_hash": "0x570ef4df9300824aee9c2bc1f3f1280b96a43dd67a5b4bb19ca2d2450cbad99", - "l2_to_l1_messages": [ - { - "from_address": "0x73314940630fd6dcda0d772d4c972c4e0a9946bef9dabf4ef84eda8ef542b82", - "to_address": "0xc3511006C04EF1d78af4C8E0e74Ec18A6E64Ff9e", - "payload": [ - "0x0", - "0x8821d9ce3b90b4d2f3578b8ac909e2fa9eb6530e", - "0x5543df729c0000", - "0x0" - ] - } - ], - "events": [ - { - "from_address": "0x49d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7", - "keys": [ - "0x99cd8bde557814842a3121e8ddfd433a539b8c9f14bf31ebf108d12e6196e9" - ], - "data": [ - "0x3031a6941b698fd835f6126b8a7b1089cc641d19af2cdfd6fa0167a8a4bf32c", - "0x0", - "0x5543df729c0000", - "0x0" - ] - }, - { - "from_address": "0x73314940630fd6dcda0d772d4c972c4e0a9946bef9dabf4ef84eda8ef542b82", - "keys": [ - "0x194fc63c49b0f07c8e7a78476844837255213824bd6cb81e0ccfb949921aad1" - ], - "data": [ - "0x8821d9ce3b90b4d2f3578b8ac909e2fa9eb6530e", - "0x5543df729c0000", - "0x0", - "0x3031a6941b698fd835f6126b8a7b1089cc641d19af2cdfd6fa0167a8a4bf32c" - ] - }, - { - "from_address": "0x3031a6941b698fd835f6126b8a7b1089cc641d19af2cdfd6fa0167a8a4bf32c", - "keys": [ - "0x5ad857f66a5b55f1301ff1ed7e098ac6d4433148f0b72ebc4a2945ab85ad53" - ], - "data": [ - "0x570ef4df9300824aee9c2bc1f3f1280b96a43dd67a5b4bb19ca2d2450cbad99", - "0x0" - ] - } - ], - "execution_resources": { - "n_steps": 1446, - "builtin_instance_counter": { - "pedersen_builtin": 2, - "range_check_builtin": 35, - "bitwise_builtin": 0, - "output_builtin": 0, - "ecdsa_builtin": 1 - }, - "n_memory_holes": 23 - }, - "actual_fee": "0x380dd17c8400" -} diff --git a/crates/client/starknet_client/resources/reader/transaction_receipt_without_l1_to_l2_nonce.json b/crates/client/starknet_client/resources/reader/transaction_receipt_without_l1_to_l2_nonce.json deleted file mode 100644 index d99bfdf08d..0000000000 --- a/crates/client/starknet_client/resources/reader/transaction_receipt_without_l1_to_l2_nonce.json +++ /dev/null @@ -1,52 +0,0 @@ -{ - "transaction_index": 1, - "transaction_hash": "0x4586cb82c15ec15a123ba42279aae105f2304cbc1f992f8360ab1b1eb0f718", - "l1_to_l2_consumed_message": { - "from_address": "0xc3511006C04EF1d78af4C8E0e74Ec18A6E64Ff9e", - "to_address": "0x73314940630fd6dcda0d772d4c972c4e0a9946bef9dabf4ef84eda8ef542b82", - "selector": "0x2d757788a8d8d6f21d1cd40bce38a8222d70654214e96ff95d8086e684fbee5", - "payload": [ - "0x26ee727ad466d4255a76f4676eaec72adb3e30e14461952a2062b3e87cbdc7f", - "0xde0b6b3a7640000", - "0x0" - ] - }, - "l2_to_l1_messages": [], - "events": [ - { - "from_address": "0x49d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7", - "keys": [ - "0x99cd8bde557814842a3121e8ddfd433a539b8c9f14bf31ebf108d12e6196e9" - ], - "data": [ - "0x0", - "0x26ee727ad466d4255a76f4676eaec72adb3e30e14461952a2062b3e87cbdc7f", - "0xde0b6b3a7640000", - "0x0" - ] - }, - { - "from_address": "0x73314940630fd6dcda0d772d4c972c4e0a9946bef9dabf4ef84eda8ef542b82", - "keys": [ - "0x221e5a5008f7a28564f0eaa32cdeb0848d10657c449aed3e15d12150a7c2db3" - ], - "data": [ - "0x26ee727ad466d4255a76f4676eaec72adb3e30e14461952a2062b3e87cbdc7f", - "0xde0b6b3a7640000", - "0x0" - ] - } - ], - "execution_resources": { - "n_steps": 673, - "builtin_instance_counter": { - "pedersen_builtin": 2, - "range_check_builtin": 12, - "output_builtin": 0, - "ecdsa_builtin": 0, - "bitwise_builtin": 0 - }, - "n_memory_holes": 22 - }, - "actual_fee": "0x0" -} diff --git a/crates/client/starknet_client/resources/writer/declare_response.json b/crates/client/starknet_client/resources/writer/declare_response.json deleted file mode 100644 index 60403d4fdc..0000000000 --- a/crates/client/starknet_client/resources/writer/declare_response.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "code": "TRANSACTION_RECEIVED", - "transaction_hash": "0x205ea2b8f86259db2d191895e8af9b186bf2aea05a5dbe28721b7840113f217", - "class_hash": "0x32ba0c2c5aa132c795a7ffa58057c36283dedf7bc0bfbf5687ba52fd317f56d" -} diff --git a/crates/client/starknet_client/resources/writer/declare_v1.json b/crates/client/starknet_client/resources/writer/declare_v1.json deleted file mode 100644 index 2b322c2de6..0000000000 --- a/crates/client/starknet_client/resources/writer/declare_v1.json +++ /dev/null @@ -1,828 +0,0 @@ -{ - "version": "0x1", - "max_fee": "0xde0b6b3a7640000", - "signature": [ - "0x240b2aba04732c2ecefb28141122549891f2ae62573c416b690658baebbe9e7", - "0x199fdb72392e77d78848c72949d130ffa78d1aeabe49be9c8147919e0f7a2e7" - ], - "nonce": "0x3", - "contract_class": { - "entry_points_by_type": { - "CONSTRUCTOR": [ - { - "selector": "0x28ffe4ff0f226a9107253e17a904099aa4f63a02a5621de0576e5aa71bc5194", - "offset": "0x270" - } - ], - "EXTERNAL": [ - { - "selector": "0x5fbd85570830519219bb4ad6951316f96fce363f86909d1f8adb1fdc836471", - "offset": "0x6bf" - }, - { - "selector": "0x679c22735055a10db4f275395763a3752a1e3a3043c192299ab6b574fba8d6", - "offset": "0x2fc" - }, - { - "selector": "0x7772be8b80a8a33dc6c1f9a6ab820c02e537c73e859de67f288c70f92571bb", - "offset": "0x29f" - }, - { - "selector": "0x8692275a885fee8890c5eaa075cc627d4755e3a1c8a2f1d557f7f97743761a", - "offset": "0x516" - }, - { - "selector": "0x8a2a3272a92492ded6c04f7c85df9c53134cef398564465f12af3c9c986d41", - "offset": "0x655" - }, - { - "selector": "0xbd7daa40535813d892224da817610f4c7e6fe8983abe588a4227586262d9d3", - "offset": "0x5af" - }, - { - "selector": "0xc3aec03fe455b8a64bf01ebad1b32252b107e07bc075631c513bb581ea3ee4", - "offset": "0x6f0" - }, - { - "selector": "0xd47144c49bce05b6de6bce9d5ff0cc8da9420f8945453e20ef779cbea13ad4", - "offset": "0x81f" - }, - { - "selector": "0xd5e8843577a4b0aa2c4408c543dd466ece9a2611a140c26c004169cb123e43", - "offset": "0x72f" - }, - { - "selector": "0xe7510edcf6e9f1b70f7bd1f488767b50f0363422f3c563160ab77adf62467b", - "offset": "0x84b" - }, - { - "selector": "0xe8f69bd941db5b0bff2e416c63d46f067fcdfad558c528f9fd102ba368cb5f", - "offset": "0x567" - }, - { - "selector": "0x127a04cfe41aceb22fc022bce0c5c70f2d860a7c7c054681bd821cdc18e6dbc", - "offset": "0x782" - }, - { - "selector": "0x12ead94ae9d3f9d2bdb6b847cf255f1f398193a1f88884a0ae8e18f24a037b6", - "offset": "0x41c" - }, - { - "selector": "0x167ac610845cc0ab1501b38169a7e50f1bf60602d3c2a961b30987454f97812", - "offset": "0x597" - }, - { - "selector": "0x169f135eddda5ab51886052d777a57f2ea9c162d713691b5e04a6d4ed71d47f", - "offset": "0x4de" - }, - { - "selector": "0x19a35a6e95cb7a3318dbb244f20975a1cd8587cc6b5259f15f61d7beb7ee43b", - "offset": "0x5f7" - }, - { - "selector": "0x1ae1a515cf2d214b29bdf63a79ee2d490efd4dd1acc99d383a8e549c3cecb5d", - "offset": "0x36d" - }, - { - "selector": "0x1b1343fe0f4a16bed5e5133b5ca9f03ab15976bb2df2b6d263ac3170b8b6a13", - "offset": "0x67f" - }, - { - "selector": "0x1b1a0649752af1b28b3dc29a1556eee781e4a4c3a1f7f53f90fa834de098c4d", - "offset": "0x2c2" - }, - { - "selector": "0x1cad42b55a5b2c7366b371db59448730766dfef74c0156c9c6f332c8c5e34d9", - "offset": "0x3cf" - }, - { - "selector": "0x1de4779362d5ca708d55fe1d4d499501b7f692730d2e01656e9180708985e07", - "offset": "0x769" - }, - { - "selector": "0x1eaab699414d786ce9dbfd4e86815f66680647efd13f9334ac97148e4e30e82", - "offset": "0x3fc" - }, - { - "selector": "0x218f305395474a84a39307fa5297be118fe17bf65e27ac5e2de6617baa44c64", - "offset": "0x62b" - }, - { - "selector": "0x26813d396fdb198e9ead934e4f7a592a8b88a059e45ab0eb6ee53494e8d45b0", - "offset": "0x3a8" - }, - { - "selector": "0x27c3334165536f239cfd400ed956eabff55fc60de4fb56728b6a4f6b87db01c", - "offset": "0x4ae" - }, - { - "selector": "0x2913ee03e5e3308c41e308bd391ea4faac9b9cb5062c76a6b3ab4f65397e106", - "offset": "0x436" - }, - { - "selector": "0x29cef374bfc7ad2628f04d9a18ac3c3a259c1eb3ce3d3c77bbab281c42649fc", - "offset": "0x7a1" - }, - { - "selector": "0x30f842021fbf02caf80d09a113997c1e00a32870eee0c6136bed27acb348bea", - "offset": "0x340" - }, - { - "selector": "0x317eb442b72a9fae758d4fb26830ed0d9f31c8e7da4dbff4e8c59ea6a158e7f", - "offset": "0x24a" - }, - { - "selector": "0x330a77821de972bb6bd8a5eeb87efdf645a609a3057cfc0b3de7bdfaf887c85", - "offset": "0x487" - }, - { - "selector": "0x33ce93a3eececa5c9fc70da05f4aff3b00e1820b79587924d514bc76788991a", - "offset": "0x57f" - }, - { - "selector": "0x34c4c150632e67baf44fc50e9a685184d72a822510a26a66f72058b5e7b2892", - "offset": "0x69e" - }, - { - "selector": "0x3604cea1cdb094a73a31144f14a3e5861613c008e1e879939ebc4827d10cd50", - "offset": "0x7ca" - }, - { - "selector": "0x3639fffddc860618a5a5cb4e4c1562999b4b0ee1c648dcdecd1a905482d6ac2", - "offset": "0x7f1" - }, - { - "selector": "0x398cb6c6170250c323a37586d08047d637f53b06360fa0268de8ee3ae3e52a2", - "offset": "0x864" - }, - { - "selector": "0x3d7905601c217734671143d457f0db37f7f8883112abd34b92c4abfeafde0c3", - "offset": "0x387" - }, - { - "selector": "0x3dc5da2d6d1275aeed57f43461d31967b0fed58bfe739b4ffad4091e89c4b03", - "offset": "0x5c7" - } - ], - "L1_HANDLER": [ - { - "selector": "0xc73f681176fc7b3f9693986fd7b14581e8d540519e27400e88b8713932be01", - "offset": "0x54c" - } - ] - }, - "program": "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", - "abi": [ - { - "members": [ - { - "name": "index", - "offset": 0, - "type": "felt" - }, - { - "name": "values", - "offset": 1, - "type": "(x: felt, y: felt)" - } - ], - "name": "IndexAndValues", - "size": 3, - "type": "struct" - }, - { - "members": [ - { - "name": "key", - "offset": 0, - "type": "felt" - }, - { - "name": "value", - "offset": 1, - "type": "felt" - } - ], - "name": "StorageCell", - "size": 2, - "type": "struct" - }, - { - "inputs": [ - { - "name": "index", - "type": "felt" - }, - { - "name": "diffs_len", - "type": "felt" - }, - { - "name": "diffs", - "type": "felt*" - } - ], - "name": "advance_counter", - "outputs": [], - "type": "function" - }, - { - "inputs": [ - { - "name": "address", - "type": "felt" - }, - { - "name": "value", - "type": "felt" - } - ], - "name": "constructor", - "outputs": [], - "type": "constructor" - }, - { - "inputs": [ - { - "name": "index_and_x", - "type": "IndexAndValues" - } - ], - "name": "xor_counters", - "outputs": [], - "type": "function" - }, - { - "inputs": [], - "name": "foo", - "outputs": [ - { - "name": "res", - "type": "felt" - } - ], - "type": "function" - }, - { - "inputs": [], - "name": "test_ec_op", - "outputs": [], - "type": "function" - }, - { - "inputs": [ - { - "name": "address", - "type": "felt" - }, - { - "name": "index_and_x", - "type": "IndexAndValues" - } - ], - "name": "call_xor_counters", - "outputs": [], - "type": "function" - }, - { - "inputs": [ - { - "name": "index", - "type": "felt" - } - ], - "name": "add_signature_to_counters", - "outputs": [], - "type": "function" - }, - { - "inputs": [ - { - "name": "address", - "type": "felt" - }, - { - "name": "value", - "type": "felt" - } - ], - "name": "set_value", - "outputs": [], - "type": "function" - }, - { - "inputs": [ - { - "name": "address", - "type": "felt" - } - ], - "name": "get_value", - "outputs": [ - { - "name": "res", - "type": "felt" - } - ], - "type": "function" - }, - { - "inputs": [], - "name": "entry_point", - "outputs": [], - "type": "function" - }, - { - "inputs": [], - "name": "test_builtins", - "outputs": [ - { - "name": "result", - "type": "felt" - } - ], - "type": "function" - }, - { - "inputs": [ - { - "name": "to_address", - "type": "felt" - } - ], - "name": "send_message", - "outputs": [], - "type": "function" - }, - { - "inputs": [ - { - "name": "keys_len", - "type": "felt" - }, - { - "name": "keys", - "type": "felt*" - }, - { - "name": "data_len", - "type": "felt" - }, - { - "name": "data", - "type": "felt*" - } - ], - "name": "test_emit_event", - "outputs": [], - "type": "function" - }, - { - "data": [ - { - "name": "storage_cells_len", - "type": "felt" - }, - { - "name": "storage_cells", - "type": "StorageCell*" - } - ], - "keys": [], - "name": "log_storage_cells", - "type": "event" - }, - { - "inputs": [ - { - "name": "storage_cells_len", - "type": "felt" - }, - { - "name": "storage_cells", - "type": "StorageCell*" - } - ], - "name": "test_high_level_event", - "outputs": [], - "type": "function" - }, - { - "inputs": [ - { - "name": "contract_address", - "type": "felt" - }, - { - "name": "function_selector", - "type": "felt" - }, - { - "name": "calldata_len", - "type": "felt" - }, - { - "name": "calldata", - "type": "felt*" - } - ], - "name": "test_call_contract", - "outputs": [], - "type": "function" - }, - { - "inputs": [ - { - "name": "class_hash", - "type": "felt" - }, - { - "name": "contract_address_salt", - "type": "felt" - }, - { - "name": "constructor_calldata_len", - "type": "felt" - }, - { - "name": "constructor_calldata", - "type": "felt*" - } - ], - "name": "test_deploy", - "outputs": [ - { - "name": "contract_address", - "type": "felt" - } - ], - "type": "function" - }, - { - "inputs": [ - { - "name": "class_hash", - "type": "felt" - }, - { - "name": "contract_address_salt", - "type": "felt" - }, - { - "name": "deploy_from_zero", - "type": "felt" - }, - { - "name": "constructor_calldata_len", - "type": "felt" - }, - { - "name": "constructor_calldata", - "type": "felt*" - }, - { - "name": "key", - "type": "felt" - }, - { - "name": "value", - "type": "felt" - } - ], - "name": "test_deploy_and_call", - "outputs": [ - { - "name": "contract_address", - "type": "felt" - } - ], - "type": "function" - }, - { - "inputs": [ - { - "name": "from_address", - "type": "felt" - }, - { - "name": "amount", - "type": "felt" - } - ], - "name": "deposit", - "outputs": [], - "type": "l1_handler" - }, - { - "inputs": [ - { - "name": "expected_address", - "type": "felt" - } - ], - "name": "test_get_caller_address", - "outputs": [], - "type": "function" - }, - { - "inputs": [ - { - "name": "expected_address", - "type": "felt" - } - ], - "name": "test_get_sequencer_address", - "outputs": [], - "type": "function" - }, - { - "inputs": [ - { - "name": "expected_timestamp", - "type": "felt" - } - ], - "name": "test_get_block_timestamp", - "outputs": [], - "type": "function" - }, - { - "inputs": [ - { - "name": "expected_address", - "type": "felt" - } - ], - "name": "test_get_contract_address", - "outputs": [], - "type": "function" - }, - { - "inputs": [ - { - "name": "expected_block_number", - "type": "felt" - } - ], - "name": "test_get_block_number", - "outputs": [], - "type": "function" - }, - { - "inputs": [ - { - "name": "other_contract_address", - "type": "felt" - }, - { - "name": "address", - "type": "felt" - } - ], - "name": "test_call_storage_consistency", - "outputs": [], - "type": "function" - }, - { - "inputs": [ - { - "name": "other_contract_address", - "type": "felt" - }, - { - "name": "depth", - "type": "felt" - } - ], - "name": "test_re_entrance", - "outputs": [], - "type": "function" - }, - { - "inputs": [ - { - "name": "value", - "type": "felt" - } - ], - "name": "add_value", - "outputs": [], - "type": "function" - }, - { - "inputs": [ - { - "name": "self_address", - "type": "felt" - }, - { - "name": "value", - "type": "felt" - } - ], - "name": "recursive_add_value", - "outputs": [], - "type": "function" - }, - { - "inputs": [ - { - "name": "address", - "type": "felt" - } - ], - "name": "increase_value", - "outputs": [], - "type": "function" - }, - { - "inputs": [ - { - "name": "self_address", - "type": "felt" - }, - { - "name": "arr_len", - "type": "felt" - }, - { - "name": "arr", - "type": "felt*" - } - ], - "name": "test_call_with_array", - "outputs": [], - "type": "function" - }, - { - "inputs": [ - { - "name": "self_address", - "type": "felt" - }, - { - "name": "arr_len", - "type": "felt" - }, - { - "name": "arr", - "type": "StorageCell*" - } - ], - "name": "test_call_with_struct_array", - "outputs": [], - "type": "function" - }, - { - "inputs": [ - { - "name": "class_hash", - "type": "felt" - } - ], - "name": "test_library_call_syntactic_sugar", - "outputs": [], - "type": "function" - }, - { - "inputs": [ - { - "name": "expected_account_contract_address", - "type": "felt" - } - ], - "name": "test_get_tx_info", - "outputs": [], - "type": "function" - }, - { - "inputs": [ - { - "name": "expected_version", - "type": "felt" - } - ], - "name": "test_tx_version", - "outputs": [], - "type": "function" - }, - { - "inputs": [ - { - "name": "code_address", - "type": "felt" - }, - { - "name": "selector", - "type": "felt" - }, - { - "name": "calldata_len", - "type": "felt" - }, - { - "name": "calldata", - "type": "felt*" - } - ], - "name": "test_delegate_call", - "outputs": [ - { - "name": "retdata_size", - "type": "felt" - }, - { - "name": "retdata", - "type": "felt*" - } - ], - "type": "function" - }, - { - "inputs": [ - { - "name": "class_hash", - "type": "felt" - }, - { - "name": "selector", - "type": "felt" - }, - { - "name": "calldata_len", - "type": "felt" - }, - { - "name": "calldata", - "type": "felt*" - } - ], - "name": "test_library_call", - "outputs": [ - { - "name": "retdata_size", - "type": "felt" - }, - { - "name": "retdata", - "type": "felt*" - } - ], - "type": "function" - }, - { - "inputs": [ - { - "name": "class_hash", - "type": "felt" - }, - { - "name": "selector", - "type": "felt" - }, - { - "name": "calldata_len", - "type": "felt" - }, - { - "name": "calldata", - "type": "felt*" - } - ], - "name": "test_library_call_l1_handler", - "outputs": [], - "type": "function" - }, - { - "inputs": [], - "name": "test_count_actual_storage_changes", - "outputs": [], - "type": "function" - }, - { - "inputs": [ - { - "name": "class_hash", - "type": "felt" - } - ], - "name": "test_replace_class", - "outputs": [], - "type": "function" - }, - { - "inputs": [ - { - "name": "class_hash", - "type": "felt" - } - ], - "name": "execute_replace_class", - "outputs": [], - "type": "function" - } - ] - }, - "sender_address": "0x43eef75848203b37363edd1e44e4121b49c8d4adff592c21b59566f5b76562f", - "type": "DEPRECATED_DECLARE" -} diff --git a/crates/client/starknet_client/resources/writer/declare_v2.json b/crates/client/starknet_client/resources/writer/declare_v2.json deleted file mode 100644 index 4a30c6a046..0000000000 --- a/crates/client/starknet_client/resources/writer/declare_v2.json +++ /dev/null @@ -1,80 +0,0 @@ -{ - "version": "0x2", - "max_fee": "0xde0b6b3a7640000", - "signature": [ - "0x130f50422046d324c0a44170402f0bff6854d384cfc3d9be9ce4179d37ee7c4", - "0x9f5c9cfc060ee34f99bb295a613bdb00e0ea091b43837867777f5c854d3dc3" - ], - "nonce": "0x2", - "contract_class": { - "contract_class_version": "0.1.0", - "sierra_program": "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", - "entry_points_by_type": { - "EXTERNAL": [ - { - "selector": "0x22ff5f21f0b81b113e63f7db6da94fedef11b2119b4088b89664fb9a3cb658", - "function_idx": 0 - }, - { - "selector": "0x3c118a68e16e12e97ed25cb4901c12f4d3162818669cc44c391d8049924c14", - "function_idx": 3 - }, - { - "selector": "0x5562b3e932b4d139366854d5a2e578382e6a3b6572ac9943d55e7efbe43d00", - "function_idx": 9 - }, - { - "selector": "0x5df99ae77df976b4f0e5cf28c7dcfe09bd6e81aab787b19ac0c08e03d928cf", - "function_idx": 1 - }, - { - "selector": "0xb17d8a2731ba7ca1816631e6be14f0fc1b8390422d649fa27f0fbb0c91eea8", - "function_idx": 10 - }, - { - "selector": "0xe7510edcf6e9f1b70f7bd1f488767b50f0363422f3c563160ab77adf62467b", - "function_idx": 12 - }, - { - "selector": "0x169f135eddda5ab51886052d777a57f2ea9c162d713691b5e04a6d4ed71d47f", - "function_idx": 11 - }, - { - "selector": "0x27a4a7332e590dd789019a6d125ff2aacd358e453090978cbf81f0d85e4c045", - "function_idx": 2 - }, - { - "selector": "0x27c3334165536f239cfd400ed956eabff55fc60de4fb56728b6a4f6b87db01c", - "function_idx": 7 - }, - { - "selector": "0x2913ee03e5e3308c41e308bd391ea4faac9b9cb5062c76a6b3ab4f65397e106", - "function_idx": 4 - }, - { - "selector": "0x2d7cf5d5a324a320f9f37804b1615a533fde487400b41af80f13f7ac5581325", - "function_idx": 5 - }, - { - "selector": "0x31aafc75f498fdfa7528880ad27246b4c15af4954f96228c9a132b328de1c92", - "function_idx": 6 - }, - { - "selector": "0x3604cea1cdb094a73a31144f14a3e5861613c008e1e879939ebc4827d10cd50", - "function_idx": 8 - } - ], - "L1_HANDLER": [], - "CONSTRUCTOR": [ - { - "selector": "0x28ffe4ff0f226a9107253e17a904099aa4f63a02a5621de0576e5aa71bc5194", - "function_idx": 13 - } - ] - }, - "abi": "[{\"type\": \"constructor\", \"name\": \"constructor\", \"inputs\": []}, {\"type\": \"function\", \"name\": \"test\", \"inputs\": [{\"name\": \"arg\", \"type\": \"core::felt252\"}, {\"name\": \"arg1\", \"type\": \"core::felt252\"}, {\"name\": \"arg2\", \"type\": \"core::felt252\"}], \"outputs\": [{\"type\": \"core::felt252\"}], \"state_mutability\": \"external\"}, {\"type\": \"function\", \"name\": \"test_storage_read\", \"inputs\": [{\"name\": \"address\", \"type\": \"core::felt252\"}], \"outputs\": [{\"type\": \"core::felt252\"}], \"state_mutability\": \"external\"}, {\"type\": \"function\", \"name\": \"test_storage_write\", \"inputs\": [{\"name\": \"address\", \"type\": \"core::felt252\"}, {\"name\": \"value\", \"type\": \"core::felt252\"}], \"outputs\": [], \"state_mutability\": \"external\"}, {\"type\": \"function\", \"name\": \"test_get_execution_info\", \"inputs\": [{\"name\": \"block_number\", \"type\": \"core::felt252\"}, {\"name\": \"block_timestamp\", \"type\": \"core::felt252\"}, {\"name\": \"sequencer_address\", \"type\": \"core::felt252\"}, {\"name\": \"version\", \"type\": \"core::felt252\"}, {\"name\": \"account_address\", \"type\": \"core::felt252\"}, {\"name\": \"max_fee\", \"type\": \"core::felt252\"}, {\"name\": \"chain_id\", \"type\": \"core::felt252\"}, {\"name\": \"nonce\", \"type\": \"core::felt252\"}, {\"name\": \"caller_address\", \"type\": \"core::felt252\"}, {\"name\": \"contract_address\", \"type\": \"core::felt252\"}, {\"name\": \"entry_point_selector\", \"type\": \"core::felt252\"}], \"outputs\": [], \"state_mutability\": \"external\"}, {\"type\": \"function\", \"name\": \"test_emit_event\", \"inputs\": [{\"name\": \"keys\", \"type\": \"core::array::Array::\"}, {\"name\": \"data\", \"type\": \"core::array::Array::\"}], \"outputs\": [], \"state_mutability\": \"external\"}, {\"type\": \"function\", \"name\": \"test_send_message_to_l1\", \"inputs\": [{\"name\": \"to_address\", \"type\": \"core::felt252\"}, {\"name\": \"payload\", \"type\": \"core::array::Array::\"}], \"outputs\": [], \"state_mutability\": \"external\"}, {\"type\": \"function\", \"name\": \"test_emit_simple_event\", \"inputs\": [{\"name\": \"argument\", \"type\": \"core::felt252\"}, {\"name\": \"my_array\", \"type\": \"core::array::Array::\"}, {\"name\": \"another_argument\", \"type\": \"core::felt252\"}], \"outputs\": [], \"state_mutability\": \"external\"}, {\"type\": \"function\", \"name\": \"test_call_contract\", \"inputs\": [{\"name\": \"contract_address\", \"type\": \"core::starknet::contract_address::ContractAddress\"}, {\"name\": \"entry_point_selector\", \"type\": \"core::felt252\"}, {\"name\": \"calldata\", \"type\": \"core::array::Array::\"}], \"outputs\": [], \"state_mutability\": \"external\"}, {\"type\": \"function\", \"name\": \"test_library_call\", \"inputs\": [{\"name\": \"class_hash\", \"type\": \"core::starknet::class_hash::ClassHash\"}, {\"name\": \"entry_point_selector\", \"type\": \"core::felt252\"}, {\"name\": \"calldata\", \"type\": \"core::array::Array::\"}], \"outputs\": [], \"state_mutability\": \"external\"}, {\"type\": \"function\", \"name\": \"assert_eq\", \"inputs\": [{\"name\": \"x\", \"type\": \"core::felt252\"}, {\"name\": \"y\", \"type\": \"core::felt252\"}], \"outputs\": [{\"type\": \"core::felt252\"}], \"state_mutability\": \"external\"}, {\"type\": \"function\", \"name\": \"test_segment_arena\", \"inputs\": [], \"outputs\": [{\"type\": \"core::felt252\"}], \"state_mutability\": \"external\"}, {\"type\": \"enum\", \"name\": \"core::bool\", \"variants\": [{\"name\": \"False\", \"type\": \"()\"}, {\"name\": \"True\", \"type\": \"()\"}]}, {\"type\": \"function\", \"name\": \"test_deploy\", \"inputs\": [{\"name\": \"class_hash\", \"type\": \"core::starknet::class_hash::ClassHash\"}, {\"name\": \"contract_address_salt\", \"type\": \"core::felt252\"}, {\"name\": \"calldata\", \"type\": \"core::array::Array::\"}, {\"name\": \"deploy_from_zero\", \"type\": \"core::bool\"}], \"outputs\": [], \"state_mutability\": \"external\"}, {\"type\": \"function\", \"name\": \"test_replace_class\", \"inputs\": [{\"name\": \"class_hash\", \"type\": \"core::starknet::class_hash::ClassHash\"}], \"outputs\": [], \"state_mutability\": \"external\"}, {\"type\": \"event\", \"name\": \"test_contract::test_contract_cairo1::TestContract::simple_event\", \"kind\": \"struct\", \"members\": [{\"name\": \"argument\", \"type\": \"core::felt252\", \"kind\": \"data\"}, {\"name\": \"my_array\", \"type\": \"core::array::Array::\", \"kind\": \"data\"}]}, {\"type\": \"event\", \"name\": \"test_contract::test_contract_cairo1::TestContract::Event\", \"kind\": \"enum\", \"variants\": [{\"name\": \"simple_event\", \"type\": \"test_contract::test_contract_cairo1::TestContract::simple_event\", \"kind\": \"nested\"}]}]" - }, - "compiled_class_hash": "0x2ed2ccb5433d1d50d84d3e9dd8000d4cc618492c95169e30da0f9f93bcf1d01", - "sender_address": "0x43eef75848203b37363edd1e44e4121b49c8d4adff592c21b59566f5b76562f", - "type": "DECLARE" -} diff --git a/crates/client/starknet_client/resources/writer/deploy_account.json b/crates/client/starknet_client/resources/writer/deploy_account.json deleted file mode 100644 index 17ff0066a0..0000000000 --- a/crates/client/starknet_client/resources/writer/deploy_account.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "version": "0x1", - "max_fee": "0xde0b6b3a7640000", - "signature": [ - "0x146e9a5aa075be1c025b9dcd4e728203b37b7b285fe61ba2fdd4e97eb8d731e", - "0x1bc2c4e3492e7d25ce33c60fa96127db534e45039d2840c6c2a38f88bd9d841" - ], - "nonce": "0x0", - "class_hash": "0x4189defe07cb5c3ae9fd57d88a339bd99785d44690dc83484998b0fd769d3c4", - "contract_address_salt": "0x229dbd8708a6d894da30582ef2b46675ebcfee684fde9446548a1d4219d8b14", - "constructor_calldata": [ - "0x406a640b3b70dad390d661c088df1fbaeb5162a07d57cf29ba794e2b0e3c804" - ], - "type": "DEPLOY_ACCOUNT" -} diff --git a/crates/client/starknet_client/resources/writer/deploy_account_response.json b/crates/client/starknet_client/resources/writer/deploy_account_response.json deleted file mode 100644 index b11580536d..0000000000 --- a/crates/client/starknet_client/resources/writer/deploy_account_response.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "code": "TRANSACTION_RECEIVED", - "transaction_hash": "0x6d0ea68e3e8e257ff7c6633277fc3ea6a42a57d0d15ada480cbaeec9c6be5d0", - "address": "0x219937256cd88844f9fdc9c33a2d6d492e253ae13814c2dc0ecab7f26919d46" -} diff --git a/crates/client/starknet_client/resources/writer/invoke.json b/crates/client/starknet_client/resources/writer/invoke.json deleted file mode 100644 index f33e6e0f4f..0000000000 --- a/crates/client/starknet_client/resources/writer/invoke.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "version": "0x1", - "max_fee": "0xde0b6b3a7640000", - "signature": [ - "0xfdb1626b6acc48f92185e6018b577a51d540982883d53036977df9a08fb564", - "0x37dabc3ef10428306a7ac9003d721bafdcda2b933f053c92ab6288d8408b1d9" - ], - "nonce": "0x9", - "sender_address": "0x219937256cd88844f9fdc9c33a2d6d492e253ae13814c2dc0ecab7f26919d46", - "calldata": [ - "0x1", - "0x12c3a0b15ef9bf39e03af3653ad1cff528cd32bd75167cdeb7615d8da93fd17", - "0x27c3334165536f239cfd400ed956eabff55fc60de4fb56728b6a4f6b87db01c", - "0x0", - "0x7", - "0x7", - "0x12c3a0b15ef9bf39e03af3653ad1cff528cd32bd75167cdeb7615d8da93fd17", - "0x2d7cf5d5a324a320f9f37804b1615a533fde487400b41af80f13f7ac5581325", - "0x4", - "0xe88ed417dee71cd1ebad48637002ebaa09b2fa00", - "0x2", - "0x1e8d90c08bb80f786f13eb48376bbb462d0bc09b9ccc69e9cda78192263dc4a", - "0x1c190b4c7ce26ff28464062dd13641fb42cf9a075229d87e25674c6f2b57693" - ], - "type": "INVOKE_FUNCTION" -} diff --git a/crates/client/starknet_client/resources/writer/invoke_response.json b/crates/client/starknet_client/resources/writer/invoke_response.json deleted file mode 100644 index b9aade6c5d..0000000000 --- a/crates/client/starknet_client/resources/writer/invoke_response.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "code": "TRANSACTION_RECEIVED", - "transaction_hash": "0xd5c953f3345bb4135b7d879dd7fbb41cdb8024ba41ef8a9f8b834ac8f4483" -} diff --git a/crates/client/starknet_client/src/lib.rs b/crates/client/starknet_client/src/lib.rs deleted file mode 100644 index 82681600d8..0000000000 --- a/crates/client/starknet_client/src/lib.rs +++ /dev/null @@ -1,212 +0,0 @@ -// config compiler to support no_coverage feature when running coverage in nightly mode within this -// crate -#![cfg_attr(coverage_nightly, feature(no_coverage))] - -//! This crate contains clients that can communicate with [`Starknet`] through the various -//! endpoints [`Starknet`] has. -//! -//! -//! [`Starknet`]: https://starknet.io/ - -pub mod reader; -pub mod retry; -#[cfg(test)] -mod starknet_client_test; -pub mod starknet_error; -#[cfg(test)] -mod test_utils; -pub mod writer; - -use std::collections::HashMap; - -use reqwest::header::HeaderMap; -use reqwest::{Client, RequestBuilder, StatusCode}; -use tracing::warn; - -use self::retry::Retry; -pub use self::retry::RetryConfig; -pub use self::starknet_error::{KnownStarknetErrorCode, StarknetError, StarknetErrorCode}; - -/// A [`Result`] in which the error is a [`ClientError`]. -type ClientResult = Result; - -/// A starknet client. -struct StarknetClient { - http_headers: HeaderMap, - pub internal_client: Client, - retry_config: RetryConfig, -} - -/// Errors that might be encountered while creating the client. -#[derive(thiserror::Error, Debug)] -pub enum ClientCreationError { - #[error(transparent)] - BadUrl(#[from] url::ParseError), - #[error(transparent)] - BuildError(#[from] reqwest::Error), - #[error(transparent)] - HttpHeaderError(#[from] http::Error), -} - -/// Errors that might be solved by retrying mechanism. -#[derive(Debug, Eq, PartialEq)] -pub enum RetryErrorCode { - Redirect, - Timeout, - TooManyRequests, - ServiceUnavailable, - Disconnect, -} - -/// Errors that may be returned by a reader or writer client. -#[derive(thiserror::Error, Debug)] -pub enum ClientError { - /// A client error representing bad status http responses. - #[error("Bad response status code: {:?} message: {:?}.", code, message)] - BadResponseStatus { code: StatusCode, message: String }, - /// A client error representing http request errors. - #[error(transparent)] - RequestError(#[from] reqwest::Error), - /// A client error representing errors that might be solved by retrying mechanism. - #[error("Retry error code: {:?}, message: {:?}.", code, message)] - RetryError { code: RetryErrorCode, message: String }, - /// A client error representing deserialization errors. - #[error(transparent)] - SerdeError(#[from] serde_json::Error), - /// A client error representing errors returned by the starknet client. - #[error(transparent)] - StarknetError(#[from] StarknetError), -} - -// A wrapper error for request_with_retry to handle the case that clone failed. -#[derive(thiserror::Error, Debug)] -enum RequestWithRetryError { - #[error("Request is unclonable.")] - CloneError, - #[error(transparent)] - ClientError(#[from] ClientError), -} - -impl StarknetClient { - /// Creates a new client for a starknet gateway at `url_str` with retry_config [`RetryConfig`]. - pub fn new( - http_headers: Option>, - node_version: &'static str, - retry_config: RetryConfig, - ) -> Result { - let header_map = match http_headers { - Some(inner) => (&inner).try_into()?, - None => HeaderMap::new(), - }; - let info = os_info::get(); - let system_information = - format!("{}; {}; {}", info.os_type(), info.version(), info.bitness()); - let app_user_agent = format!( - "{product_name}/{product_version} ({system_information})", - product_name = "papyrus", - product_version = node_version, - system_information = system_information - ); - Ok(StarknetClient { - http_headers: header_map, - internal_client: Client::builder().user_agent(app_user_agent).build()?, - retry_config, - }) - } - - fn get_retry_error_code(err: &ClientError) -> Option { - match err { - ClientError::BadResponseStatus { code, message: _ } => match *code { - StatusCode::TEMPORARY_REDIRECT => Some(RetryErrorCode::Redirect), - StatusCode::REQUEST_TIMEOUT | StatusCode::GATEWAY_TIMEOUT => { - Some(RetryErrorCode::Timeout) - } - StatusCode::TOO_MANY_REQUESTS => Some(RetryErrorCode::TooManyRequests), - StatusCode::SERVICE_UNAVAILABLE => Some(RetryErrorCode::ServiceUnavailable), - _ => None, - }, - - ClientError::RequestError(internal_err) => { - if internal_err.is_timeout() { - Some(RetryErrorCode::Timeout) - } else if internal_err.is_request() { - None - } else if internal_err.is_connect() { - Some(RetryErrorCode::Disconnect) - } else if internal_err.is_redirect() { - Some(RetryErrorCode::Redirect) - } else { - None - } - } - - ClientError::StarknetError(StarknetError { - code: - StarknetErrorCode::KnownErrorCode(KnownStarknetErrorCode::TransactionLimitExceeded), - message: _, - }) => Some(RetryErrorCode::TooManyRequests), - _ => None, - } - } - - fn should_retry(err: &RequestWithRetryError) -> bool { - match err { - RequestWithRetryError::ClientError(err) => Self::get_retry_error_code(err).is_some(), - RequestWithRetryError::CloneError => false, - } - } - - // If the request_builder is unclonable, the function will not retry the request upon failure. - pub async fn request_with_retry( - &self, - request_builder: RequestBuilder, - ) -> ClientResult { - let res = Retry::new(&self.retry_config) - .start_with_condition( - || async { - match request_builder.try_clone() { - Some(request_builder) => self - .request(request_builder) - .await - .map_err(RequestWithRetryError::ClientError), - None => Err(RequestWithRetryError::CloneError), - } - }, - Self::should_retry, - ) - .await; - - match res { - Ok(string) => Ok(string), - Err(RequestWithRetryError::ClientError(err)) => Err(Self::get_retry_error_code(&err) - .map(|code| ClientError::RetryError { code, message: err.to_string() }) - .unwrap_or(err)), - Err(RequestWithRetryError::CloneError) => { - warn!("Starknet client got an unclonable request. Can't retry upon failure."); - self.request(request_builder).await - } - } - } - - async fn request(&self, request_builder: RequestBuilder) -> ClientResult { - let res = request_builder.headers(self.http_headers.clone()).send().await; - let (code, message) = match res { - Ok(response) => (response.status(), response.text().await?), - Err(err) => { - let msg = err.to_string(); - (err.status().ok_or(err)?, msg) - } - }; - match code { - StatusCode::OK => Ok(message), - // TODO(Omri): The error code returned from SN changed from error 500 to error 400. For - // now, keeping both options. In the future, remove the '500' (INTERNAL_SERVER_ERROR) - // option. - StatusCode::INTERNAL_SERVER_ERROR | StatusCode::BAD_REQUEST => { - let starknet_error: StarknetError = serde_json::from_str(&message)?; - Err(ClientError::StarknetError(starknet_error)) - } - _ => Err(ClientError::BadResponseStatus { code, message }), - } - } -} diff --git a/crates/client/starknet_client/src/reader/mod.rs b/crates/client/starknet_client/src/reader/mod.rs deleted file mode 100644 index 91b794ce29..0000000000 --- a/crates/client/starknet_client/src/reader/mod.rs +++ /dev/null @@ -1,331 +0,0 @@ -//! This module contains client that can read data from [`Starknet`]. -//! -//! [`Starknet`]: https://starknet.io/ - -pub mod objects; -#[cfg(test)] -mod starknet_feeder_gateway_client_test; - -use std::collections::HashMap; - -use async_trait::async_trait; -use cairo_lang_starknet::casm_contract_class::CasmContractClass; -#[cfg(any(feature = "testing", test))] -use mockall::automock; -use serde::{Deserialize, Serialize}; -use starknet_api::block::BlockNumber; -use starknet_api::api_core::ClassHash; -use starknet_api::deprecated_contract_class::ContractClass as DeprecatedContractClass; -use starknet_api::transaction::TransactionHash; -use starknet_api::StarknetApiError; -use tracing::{debug, instrument}; -use url::Url; - -pub use crate::reader::objects::block::{Block, TransactionReceiptsError}; -pub use crate::reader::objects::state::{ - ContractClass, - DeclaredClassHashEntry, - DeployedContract, - ReplacedClass, - StateDiff, - StateUpdate, - StorageEntry, -}; -#[cfg(doc)] -pub use crate::reader::objects::transaction::TransactionReceipt; -use crate::retry::RetryConfig; -use crate::starknet_error::{KnownStarknetErrorCode, StarknetError, StarknetErrorCode}; -use crate::{ClientCreationError, ClientError, StarknetClient}; - -/// Errors that may be returned from a reader client. -#[derive(thiserror::Error, Debug)] -pub enum ReaderClientError { - /// A client error representing errors from the base StarknetClient. - #[error(transparent)] - ClientError(#[from] ClientError), - /// A client error representing deserialization errors. - /// Note: [`ClientError`] contains SerdeError as well. The difference is that this variant is - /// responsible for serde errors coming from [`StarknetReader`] and ClientError::SerdeError - /// is responsible for serde errors coming from StarknetClient. - #[error(transparent)] - SerdeError(#[from] serde_json::Error), - /// A client error representing errors from [`starknet_api`]. - #[error(transparent)] - StarknetApiError(#[from] StarknetApiError), - /// A client error representing transaction receipts errors. - #[error(transparent)] - TransactionReceiptsError(#[from] TransactionReceiptsError), - #[error("Invalid transaction: {:?}, error: {:?}.", tx_hash, msg)] - BadTransaction { tx_hash: TransactionHash, msg: String }, -} - -pub type ReaderClientResult = Result; - -/// A trait describing an object that can communicate with [`Starknet`] and read data from it. -/// -/// [`Starknet`]: https://starknet.io/ -#[cfg_attr(any(test, feature = "testing"), automock)] -#[async_trait] -pub trait StarknetReader { - /// Returns the last block in the system, returning [`None`] in case there are no blocks in the - /// system. - async fn latest_block(&self) -> ReaderClientResult>; - /// Returns a [`Block`] corresponding to `block_number`, returning [`None`] in case no such - /// block exists in the system. - async fn block(&self, block_number: BlockNumber) -> ReaderClientResult>; - /// Returns a [`GenericContractClass`] corresponding to `class_hash`. - async fn class_by_hash( - &self, - class_hash: ClassHash, - ) -> ReaderClientResult>; - async fn raw_class_by_hash( - &self, - class_hash: ClassHash, - ) -> Result; - /// Returns a [`CasmContractClass`] corresponding to `class_hash`. - async fn compiled_class_by_hash( - &self, - class_hash: ClassHash, - ) -> ReaderClientResult>; - /// Returns a [`starknet_client`][`StateUpdate`] corresponding to `block_number`. - async fn state_update( - &self, - block_number: BlockNumber, - ) -> ReaderClientResult>; -} - -/// A client for the [`Starknet`] feeder gateway. -/// -/// [`Starknet`]: https://starknet.io/ -pub struct StarknetFeederGatewayClient { - urls: StarknetUrls, - client: StarknetClient, -} - -#[derive(Clone, Debug)] -struct StarknetUrls { - get_block: Url, - get_contract_by_hash: Url, - get_compiled_class_by_class_hash: Url, - get_state_update: Url, -} - -const GET_BLOCK_URL: &str = "feeder_gateway/get_block"; -const GET_CONTRACT_BY_HASH_URL: &str = "feeder_gateway/get_class_by_hash"; -const GET_COMPILED_CLASS_BY_CLASS_HASH_URL: &str = - "feeder_gateway/get_compiled_class_by_class_hash"; -const GET_STATE_UPDATE_URL: &str = "feeder_gateway/get_state_update"; -const BLOCK_NUMBER_QUERY: &str = "blockNumber"; -const LATEST_BLOCK_NUMBER: &str = "latest"; -const CLASS_HASH_QUERY: &str = "classHash"; - -impl StarknetUrls { - fn new(url_str: &str) -> Result { - let base_url = Url::parse(url_str)?; - Ok(StarknetUrls { - get_block: base_url.join(GET_BLOCK_URL)?, - get_contract_by_hash: base_url.join(GET_CONTRACT_BY_HASH_URL)?, - get_compiled_class_by_class_hash: base_url - .join(GET_COMPILED_CLASS_BY_CLASS_HASH_URL)?, - get_state_update: base_url.join(GET_STATE_UPDATE_URL)?, - }) - } -} - -impl StarknetFeederGatewayClient { - pub fn new( - url_str: &str, - http_headers: Option>, - node_version: &'static str, - retry_config: RetryConfig, - ) -> Result { - Ok(StarknetFeederGatewayClient { - urls: StarknetUrls::new(url_str)?, - client: StarknetClient::new(http_headers, node_version, retry_config)?, - }) - } - - async fn request_with_retry_url(&self, url: Url) -> ReaderClientResult { - self.client - .request_with_retry(self.client.internal_client.get(url)) - .await - .map_err(Into::::into) - } - - async fn request_block( - &self, - block_number: Option, - ) -> ReaderClientResult> { - let mut url = self.urls.get_block.clone(); - let block_number = - block_number.map(|bn| bn.to_string()).unwrap_or(String::from(LATEST_BLOCK_NUMBER)); - url.query_pairs_mut().append_pair(BLOCK_NUMBER_QUERY, block_number.as_str()); - - let response = self.request_with_retry_url(url).await; - load_object_from_response( - response, - KnownStarknetErrorCode::BlockNotFound, - format!("Failed to get block number {block_number:?} from starknet server."), - ) - } -} - -#[async_trait] -impl StarknetReader for StarknetFeederGatewayClient { - #[instrument(skip(self), level = "debug")] - async fn latest_block(&self) -> ReaderClientResult> { - Ok(self.request_block(None).await?) - } - - #[instrument(skip(self), level = "debug")] - async fn block(&self, block_number: BlockNumber) -> ReaderClientResult> { - self.request_block(Some(block_number)).await - } - - #[instrument(skip(self), level = "debug")] - async fn class_by_hash( - &self, - class_hash: ClassHash, - ) -> ReaderClientResult> { - let mut url = self.urls.get_contract_by_hash.clone(); - let class_hash = serde_json::to_string(&class_hash)?; - url.query_pairs_mut() - .append_pair(CLASS_HASH_QUERY, &class_hash.as_str()[1..class_hash.len() - 1]); - let response = self.request_with_retry_url(url).await; - load_object_from_response( - response, - KnownStarknetErrorCode::UndeclaredClass, - format!("Failed to get class with hash {class_hash:?} from starknet server."), - ) - } - - #[instrument(skip(self), level = "debug")] - async fn raw_class_by_hash( - &self, - class_hash: ClassHash, - ) -> Result { - let mut url = self.urls.get_contract_by_hash.clone(); - let class_hash = serde_json::to_string(&class_hash)?; - url.query_pairs_mut() - .append_pair(CLASS_HASH_QUERY, &class_hash.as_str()[1..class_hash.len() - 1]); - let response = self.request_with_retry_url(url).await; - response - } - - #[instrument(skip(self), level = "debug")] - async fn state_update( - &self, - block_number: BlockNumber, - ) -> ReaderClientResult> { - let mut url = self.urls.get_state_update.clone(); - url.query_pairs_mut().append_pair(BLOCK_NUMBER_QUERY, &block_number.to_string()); - let response = self.request_with_retry_url(url).await; - load_object_from_response( - response, - KnownStarknetErrorCode::BlockNotFound, - format!( - "Failed to get state update for block number {block_number} from starknet server." - ), - ) - .map(|option| { - option.map(|mut state_update: StateUpdate| { - // Remove empty storage diffs. The feeder gateway sometimes returns an empty - // storage diff. - state_update.state_diff.storage_diffs.retain(|_k, v| !v.is_empty()); - state_update - }) - }) - } - - #[instrument(skip(self), level = "debug")] - async fn compiled_class_by_hash( - &self, - class_hash: ClassHash, - ) -> ReaderClientResult> { - debug!("Got compiled_class_by_hash {} from starknet server.", class_hash); - // FIXME: Remove the following default CasmContractClass once integration environment gets - // regenesissed. - // Use default value for CasmConractClass that are malformed in the integration environment. - // TODO: Make this array a const. - if [ - #[allow(clippy::unwrap_used)] - ClassHash( - starknet_api::hash::StarkFelt::try_from( - "0x4e70b19333ae94bd958625f7b61ce9eec631653597e68645e13780061b2136c", - ) - .unwrap(), - ), - #[allow(clippy::unwrap_used)] - ClassHash( - starknet_api::hash::StarkFelt::try_from( - "0x6208b3f9f94e6220f3d6a3562fe06a35a66181a202d946c3522fd28eda9ea1b", - ) - .unwrap(), - ), - #[allow(clippy::unwrap_used)] - ClassHash( - starknet_api::hash::StarkFelt::try_from( - "0xd6916ff38c93f834e7223a95b41d4542152d8288ff388b5d3dcdf8126a784a", - ) - .unwrap(), - ), - #[allow(clippy::unwrap_used)] - ClassHash( - starknet_api::hash::StarkFelt::try_from( - "0x161354521d46ca89a5b64aa41fa4e77ffeadc0f9796272d9b94227dbbb3840e", - ) - .unwrap(), - ), - #[allow(clippy::unwrap_used)] - ClassHash( - starknet_api::hash::StarkFelt::try_from( - "0x6a9eb910b3f83989900c8d65f9d67d67016f2528cc1b834019cf489f4f7d716", - ) - .unwrap(), - ), - ] - .contains(&class_hash) - { - debug!("Using default compiled class for class hash {}.", class_hash); - return Ok(Some(CasmContractClass::default())); - } - - let mut url = self.urls.get_compiled_class_by_class_hash.clone(); - let class_hash = serde_json::to_string(&class_hash)?; - url.query_pairs_mut() - .append_pair(CLASS_HASH_QUERY, &class_hash.as_str()[1..class_hash.len() - 1]); - let response = self.request_with_retry_url(url).await; - load_object_from_response( - response, - KnownStarknetErrorCode::UndeclaredClass, - format!("Failed to get compiled class with hash {class_hash:?} from starknet server."), - ) - } -} - -/// Load an object from a json string response. If there was a StarknetError with -/// `none_error_code`, return None. If there was a different error, log `error_message`. -fn load_object_from_response Deserialize<'a>>( - response: ReaderClientResult, - none_error_code: KnownStarknetErrorCode, - error_message: String, -) -> ReaderClientResult> { - match response { - Ok(raw_object) => Ok(Some(serde_json::from_str(&raw_object)?)), - Err(ReaderClientError::ClientError(ClientError::StarknetError(StarknetError { - code: StarknetErrorCode::KnownErrorCode(error_code), - message: _, - }))) if error_code == none_error_code => Ok(None), - Err(err) => { - debug!(error_message); - Err(err) - } - } -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(untagged)] -pub enum GenericContractClass { - Cairo0ContractClass(DeprecatedContractClass), - Cairo1ContractClass(ContractClass), -} diff --git a/crates/client/starknet_client/src/reader/objects/block.rs b/crates/client/starknet_client/src/reader/objects/block.rs deleted file mode 100644 index 2f39b5c6aa..0000000000 --- a/crates/client/starknet_client/src/reader/objects/block.rs +++ /dev/null @@ -1,249 +0,0 @@ -#[cfg(test)] -#[path = "block_test.rs"] -mod block_test; - -use std::ops::Index; - -use serde::{Deserialize, Serialize}; -use starknet_api::block::{ - Block as starknet_api_block, - BlockHash, - BlockNumber, - BlockTimestamp, - GasPrice, -}; -use starknet_api::api_core::{ContractAddress, GlobalRoot}; -#[cfg(doc)] -use starknet_api::transaction::TransactionOutput as starknet_api_transaction_output; -use starknet_api::transaction::{TransactionHash, TransactionOffsetInBlock}; - -use crate::reader::objects::transaction::{ - L1ToL2Message, - Transaction, - TransactionReceipt, - TransactionType, -}; -use crate::reader::{ReaderClientError, ReaderClientResult}; -use starknet_core; - -/// A block as returned by the starknet gateway. -#[derive(Debug, Default, Deserialize, Serialize, Clone, Eq, PartialEq)] -#[serde(deny_unknown_fields)] -pub struct Block { - pub block_hash: BlockHash, - pub block_number: BlockNumber, - pub gas_price: GasPrice, - pub parent_block_hash: BlockHash, - #[serde(default)] - pub sequencer_address: ContractAddress, - pub state_root: GlobalRoot, - pub status: BlockStatus, - #[serde(default)] - pub timestamp: BlockTimestamp, - pub transactions: Vec, - pub transaction_receipts: Vec, - // Default since old blocks don't include this field. - #[serde(default)] - pub starknet_version: String, -} - -/// Errors that might be encountered while converting the client representation of a [`Block`] to a -/// starknet_api [Block](`starknet_api_block`), specifically when converting a list of -/// [`TransactionReceipt`] to a list of starknet_api -/// [TransactionOutput](`starknet_api_transaction_output`). -#[derive(thiserror::Error, Debug)] -pub enum TransactionReceiptsError { - #[error( - "In block number {} there are {} transactions and {} transaction receipts.", - block_number, - num_of_txs, - num_of_receipts - )] - WrongNumberOfReceipts { block_number: BlockNumber, num_of_txs: usize, num_of_receipts: usize }, - #[error( - "In block number {}, transaction in index {:?} with hash {:?} and type {:?} has a receipt \ - with mismatched fields.", - block_number, - tx_index, - tx_hash, - tx_type - )] - MismatchFields { - block_number: BlockNumber, - tx_index: TransactionOffsetInBlock, - tx_hash: TransactionHash, - tx_type: TransactionType, - }, - #[error( - "In block number {}, transaction in index {:?} with hash {:?} has a receipt with \ - transaction hash {:?}.", - block_number, - tx_index, - tx_hash, - receipt_tx_hash - )] - MismatchTransactionHash { - block_number: BlockNumber, - tx_index: TransactionOffsetInBlock, - tx_hash: TransactionHash, - receipt_tx_hash: TransactionHash, - }, - #[error( - "In block number {}, transaction in index {:?} with hash {:?} has a receipt with \ - transaction index {:?}.", - block_number, - tx_index, - tx_hash, - receipt_tx_index - )] - MismatchTransactionIndex { - block_number: BlockNumber, - tx_index: TransactionOffsetInBlock, - tx_hash: TransactionHash, - receipt_tx_index: TransactionOffsetInBlock, - }, -} - -/// Converts the client representation of [`Block`] to a tuple of a starknet_api -/// [Block](`starknet_api_block`) and String representing the Starknet version corresponding to -/// that block. -impl Block { - pub fn to_starknet_api_block_and_version( - self, - ) -> ReaderClientResult<(starknet_api_block, String)> { - // Check that the number of receipts is the same as the number of transactions. - let num_of_txs = self.transactions.len(); - let num_of_receipts = self.transaction_receipts.len(); - if num_of_txs != num_of_receipts { - return Err(ReaderClientError::TransactionReceiptsError( - TransactionReceiptsError::WrongNumberOfReceipts { - block_number: self.block_number, - num_of_txs, - num_of_receipts, - }, - )); - } - - // Get the transaction outputs and execution statuses. - let mut transaction_outputs = vec![]; - let mut transaction_hashes = vec![]; - for (i, receipt) in self.transaction_receipts.into_iter().enumerate() { - let transaction = self.transactions.index(i); - - // Check that the transaction index that appears in the receipt is the same as the - // index of the transaction. - if i != receipt.transaction_index.0 { - return Err(ReaderClientError::TransactionReceiptsError( - TransactionReceiptsError::MismatchTransactionIndex { - block_number: self.block_number, - tx_index: TransactionOffsetInBlock(i), - tx_hash: transaction.transaction_hash(), - receipt_tx_index: receipt.transaction_index, - }, - )); - } - - // Check that the transaction hash that appears in the receipt is the same as in the - // transaction. - if transaction.transaction_hash() != receipt.transaction_hash { - return Err(ReaderClientError::TransactionReceiptsError( - TransactionReceiptsError::MismatchTransactionHash { - block_number: self.block_number, - tx_index: TransactionOffsetInBlock(i), - tx_hash: transaction.transaction_hash(), - receipt_tx_hash: receipt.transaction_hash, - }, - )); - } - - // Check that the receipt has the correct fields according to the transaction type. - if transaction.transaction_type() != TransactionType::L1Handler - && receipt.l1_to_l2_consumed_message != L1ToL2Message::default() - { - return Err(ReaderClientError::TransactionReceiptsError( - TransactionReceiptsError::MismatchFields { - block_number: self.block_number, - tx_index: TransactionOffsetInBlock(i), - tx_hash: transaction.transaction_hash(), - tx_type: transaction.transaction_type(), - }, - )); - } - - transaction_hashes.push(receipt.transaction_hash); - let tx_output = receipt.into_starknet_api_transaction_output(transaction); - transaction_outputs.push(tx_output); - } - - // Get the transactions. - // Note: This cannot happen before getting the transaction outputs since we need to borrow - // the block transactions inside the for loop for the transaction type (TransactionType is - // defined in starknet_client therefore starknet_api::Transaction cannot return it). - let transactions: Vec<_> = self - .transactions - .into_iter() - .map(starknet_api::transaction::Transaction::try_from) - .collect::>()?; - - // Get the header. - let header = starknet_api::block::BlockHeader { - block_hash: self.block_hash, - parent_hash: self.parent_block_hash, - block_number: self.block_number, - gas_price: self.gas_price, - state_root: self.state_root, - sequencer: self.sequencer_address, - timestamp: self.timestamp, - }; - - let body = starknet_api::block::BlockBody { - transactions, - transaction_outputs, - transaction_hashes, - }; - - Ok((starknet_api_block { header, body }, self.starknet_version)) - } -} - -#[derive( - Debug, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize, PartialOrd, Ord, Default, -)] -pub enum BlockStatus { - #[serde(rename(deserialize = "ABORTED", serialize = "ABORTED"))] - Aborted, - #[serde(rename(deserialize = "ACCEPTED_ON_L1", serialize = "ACCEPTED_ON_L1"))] - AcceptedOnL1, - #[serde(rename(deserialize = "ACCEPTED_ON_L2", serialize = "ACCEPTED_ON_L2"))] - #[default] - AcceptedOnL2, - #[serde(rename(deserialize = "PENDING", serialize = "PENDING"))] - Pending, - #[serde(rename(deserialize = "REVERTED", serialize = "REVERTED"))] - Reverted, -} - -impl From for starknet_api::block::BlockStatus { - fn from(status: BlockStatus) -> Self { - match status { - BlockStatus::Aborted => starknet_api::block::BlockStatus::Rejected, - BlockStatus::AcceptedOnL1 => starknet_api::block::BlockStatus::AcceptedOnL1, - BlockStatus::AcceptedOnL2 => starknet_api::block::BlockStatus::AcceptedOnL2, - BlockStatus::Pending => starknet_api::block::BlockStatus::Pending, - BlockStatus::Reverted => starknet_api::block::BlockStatus::Rejected, - } - } -} - -impl From for starknet_core::types::BlockStatus { - fn from(status: BlockStatus) -> Self { - match status { - BlockStatus::Pending => starknet_core::types::BlockStatus::Pending, - BlockStatus::AcceptedOnL2 => starknet_core::types::BlockStatus::AcceptedOnL2, - BlockStatus::AcceptedOnL1 => starknet_core::types::BlockStatus::AcceptedOnL1, - BlockStatus::Reverted => starknet_core::types::BlockStatus::Rejected, // Assuming Reverted maps to Rejected - _ => panic!("Unsupported status conversion"), // Handle any additional statuses or provide a default conversion - } - } -} - diff --git a/crates/client/starknet_client/src/reader/objects/block_test.rs b/crates/client/starknet_client/src/reader/objects/block_test.rs deleted file mode 100644 index 0ff6cd499a..0000000000 --- a/crates/client/starknet_client/src/reader/objects/block_test.rs +++ /dev/null @@ -1,179 +0,0 @@ -use assert::assert_ok; -use assert_matches::assert_matches; -use indexmap::IndexMap; -use pretty_assertions::assert_eq; -use starknet_api::block::BlockHash; -use starknet_api::core::{ClassHash, CompiledClassHash, ContractAddress, Nonce, PatriciaKey}; -use starknet_api::hash::{StarkFelt, StarkHash}; -use starknet_api::serde_utils::bytes_from_hex_str; -use starknet_api::state::StorageKey; -use starknet_api::transaction::{TransactionHash, TransactionOffsetInBlock}; -use starknet_api::{patricia_key, stark_felt}; - -use super::{Block, GlobalRoot, TransactionReceiptsError}; -use crate::reader::objects::state::{ - DeclaredClassHashEntry, - DeployedContract, - ReplacedClass, - StateDiff, - StateUpdate, - StorageEntry, -}; -use crate::reader::objects::transaction::TransactionReceipt; -use crate::reader::ReaderClientError; -use crate::test_utils::read_resource::read_resource_file; - -#[test] -fn load_block_succeeds() { - assert_ok!(serde_json::from_str::(&read_resource_file("reader/block.json"))); -} - -#[test] -fn load_block_state_update_succeeds() { - let expected_state_update = StateUpdate { - block_hash: BlockHash(stark_felt!( - "0x3f65ef25e87a83d92f32f5e4869a33580f9db47ec980c1ff27bdb5151914de5" - )), - new_root: GlobalRoot( - StarkHash::new( - bytes_from_hex_str::<32, false>( - "02ade8eea6eb6523d22a408a1f035bd351a9a5dce28926ca92d7abb490c0e74a", - ) - .unwrap(), - ) - .unwrap(), - ), - old_root: GlobalRoot( - StarkHash::new( - bytes_from_hex_str::<32, false>( - "0465b219d93bcb2776aa3abb009423be3e2d04dba6453d7e027830740cd699a4", - ) - .unwrap(), - ) - .unwrap(), - ), - state_diff: StateDiff { - storage_diffs: IndexMap::from([( - ContractAddress(patricia_key!( - "0x13386f165f065115c1da38d755be261023c32f0134a03a8e66b6bb1e0016014" - )), - vec![ - StorageEntry { - key: StorageKey(patricia_key!( - "0x3b3a699bb6ef37ff4b9c4e14319c7d8e9c9bdd10ff402d1ebde18c62ae58381" - )), - value: stark_felt!("0x61454dd6e5c83621e41b74c"), - }, - StorageEntry { - key: StorageKey(patricia_key!( - "0x1557182e4359a1f0c6301278e8f5b35a776ab58d39892581e357578fb287836" - )), - value: stark_felt!("0x79dd8085e3e5a96ea43e7d"), - }, - ], - )]), - deployed_contracts: vec![DeployedContract { - address: ContractAddress(patricia_key!( - "0x3e10411edafd29dfe6d427d03e35cb261b7a5efeee61bf73909ada048c029b9" - )), - class_hash: ClassHash(stark_felt!( - "0x071c3c99f5cf76fc19945d4b8b7d34c7c5528f22730d56192b50c6bbfd338a64" - )), - }], - declared_classes: vec![DeclaredClassHashEntry { - class_hash: ClassHash(stark_felt!("0x10")), - compiled_class_hash: CompiledClassHash(stark_felt!("0x1000")), - }], - old_declared_contracts: vec![ClassHash(stark_felt!("0x100"))], - nonces: IndexMap::from([( - ContractAddress(patricia_key!( - "0x51c62af8919b31499b36bd1f1f702c8ef5a6309554427186c7bd456b862c115" - )), - Nonce(stark_felt!("0x12")), - )]), - replaced_classes: vec![ReplacedClass { - address: ContractAddress(patricia_key!( - "0x56b0efe9d91fcda0f341af928404056c5220ee0ccc66be15d20611a172dbd52" - )), - class_hash: ClassHash(stark_felt!( - "0x2248aff260e5837317641ff4f861495dd71e78b9dae98a31113e569b336bd26" - )), - }], - }, - }; - assert_eq!( - expected_state_update, - serde_json::from_str::(&read_resource_file("reader/block_state_update.json")) - .unwrap() - ) -} - -#[tokio::test] -async fn to_starknet_api_block_and_version() { - let raw_block = read_resource_file("reader/block.json"); - let block: Block = serde_json::from_str(&raw_block).unwrap(); - let expected_num_of_tx_outputs = block.transactions.len(); - let (starknet_api_block, _version) = block.to_starknet_api_block_and_version().unwrap(); - assert_eq!(expected_num_of_tx_outputs, starknet_api_block.body.transaction_outputs.len()); - - let mut err_block: Block = serde_json::from_str(&raw_block).unwrap(); - err_block.transaction_receipts.pop(); - let err = err_block.to_starknet_api_block_and_version().unwrap_err(); - assert_matches!( - err, - ReaderClientError::TransactionReceiptsError( - TransactionReceiptsError::WrongNumberOfReceipts { - block_number: _, - num_of_txs: _, - num_of_receipts: _, - } - ) - ); - - let mut err_block: Block = serde_json::from_str(&raw_block).unwrap(); - err_block.transaction_receipts[0].transaction_index = TransactionOffsetInBlock(1); - let err = err_block.to_starknet_api_block_and_version().unwrap_err(); - assert_matches!( - err, - ReaderClientError::TransactionReceiptsError( - TransactionReceiptsError::MismatchTransactionIndex { - block_number: _, - tx_index: _, - tx_hash: _, - receipt_tx_index: _, - } - ) - ); - - let mut err_block: Block = serde_json::from_str(&raw_block).unwrap(); - err_block.transaction_receipts[0].transaction_hash = TransactionHash(stark_felt!("0x4")); - let err = err_block.to_starknet_api_block_and_version().unwrap_err(); - assert_matches!( - err, - ReaderClientError::TransactionReceiptsError( - TransactionReceiptsError::MismatchTransactionHash { - block_number: _, - tx_index: _, - tx_hash: _, - receipt_tx_hash: _, - } - ) - ); - - let mut err_block: Block = serde_json::from_str(&raw_block).unwrap(); - err_block.transaction_receipts[0] = TransactionReceipt { - transaction_index: TransactionOffsetInBlock(0), - transaction_hash: err_block.transactions[0].transaction_hash(), - ..err_block.transaction_receipts[4].clone() - }; - let err = err_block.to_starknet_api_block_and_version().unwrap_err(); - assert_matches!( - err, - ReaderClientError::TransactionReceiptsError(TransactionReceiptsError::MismatchFields { - block_number: _, - tx_index: _, - tx_hash: _, - tx_type: _, - }) - ); -} diff --git a/crates/client/starknet_client/src/reader/objects/mod.rs b/crates/client/starknet_client/src/reader/objects/mod.rs deleted file mode 100644 index ab93ac2c92..0000000000 --- a/crates/client/starknet_client/src/reader/objects/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod block; -pub mod state; -pub mod transaction; diff --git a/crates/client/starknet_client/src/reader/objects/state.rs b/crates/client/starknet_client/src/reader/objects/state.rs deleted file mode 100644 index c09fc230b3..0000000000 --- a/crates/client/starknet_client/src/reader/objects/state.rs +++ /dev/null @@ -1,94 +0,0 @@ -use std::collections::HashMap; - -use indexmap::IndexMap; -use serde::{Deserialize, Serialize}; -use starknet_api::block::BlockHash; -use starknet_api::api_core::{ClassHash, CompiledClassHash, ContractAddress, GlobalRoot, Nonce}; -use starknet_api::hash::StarkFelt; -use starknet_api::state::{EntryPoint, EntryPointType, StorageKey}; - -/// A state update derived from a single block as returned by the starknet gateway. -#[derive(Debug, Default, Deserialize, Serialize, Clone, Eq, PartialEq)] -pub struct StateUpdate { - pub block_hash: BlockHash, - pub new_root: GlobalRoot, - pub old_root: GlobalRoot, - pub state_diff: StateDiff, -} - -#[derive(Debug, Default, Deserialize, Serialize, Clone, Eq, PartialEq)] -pub struct StateDiff { - // IndexMap is serialized as a mapping in json, keeps ordering and is efficiently iterable. - pub storage_diffs: IndexMap>, - pub deployed_contracts: Vec, - pub declared_classes: Vec, - pub old_declared_contracts: Vec, - pub nonces: IndexMap, - pub replaced_classes: Vec, -} - -impl StateDiff { - // Returns the declared class hashes in the following order: - // [declared classes, deprecated declared class, class hashes of deployed contracts]. - pub fn class_hashes(&self) -> Vec { - let mut declared_class_hashes: Vec = self - .declared_classes - .iter() - .map(|DeclaredClassHashEntry { class_hash, compiled_class_hash: _ }| *class_hash) - .collect(); - declared_class_hashes.append(&mut self.old_declared_contracts.clone()); - let mut deployed_class_hashes = self - .deployed_contracts - .iter() - .map(|contract| contract.class_hash) - .filter(|hash| !declared_class_hashes.contains(hash)) - .collect(); - declared_class_hashes.append(&mut deployed_class_hashes); - declared_class_hashes - } -} - -/// A deployed contract in StarkNet. -#[derive(Debug, Default, Clone, Eq, PartialEq, Hash, Deserialize, Serialize, PartialOrd, Ord)] -pub struct DeployedContract { - pub address: ContractAddress, - pub class_hash: ClassHash, -} - -/// A storage entry in a contract. -#[derive(Debug, Default, Clone, Eq, PartialEq, Hash, Deserialize, Serialize, PartialOrd, Ord)] -pub struct StorageEntry { - pub key: StorageKey, - pub value: StarkFelt, -} - -#[derive(Debug, Clone, Default, Eq, PartialEq, Deserialize, Serialize)] -pub struct ContractClass { - pub sierra_program: Vec, - pub entry_points_by_type: HashMap>, - pub contract_class_version: String, - pub abi: String, -} - -impl From for starknet_api::state::ContractClass { - fn from(class: ContractClass) -> Self { - Self { - sierra_program: class.sierra_program, - entry_point_by_type: class.entry_points_by_type, - abi: class.abi, - } - } -} - -/// A mapping from class hash to the compiled class hash. -#[derive(Debug, Clone, Default, Eq, PartialEq, Deserialize, Serialize)] -pub struct DeclaredClassHashEntry { - pub class_hash: ClassHash, - pub compiled_class_hash: CompiledClassHash, -} - -#[derive(Debug, Clone, Default, Eq, PartialEq, Deserialize, Serialize)] -pub struct ReplacedClass { - pub address: ContractAddress, - pub class_hash: ClassHash, -} diff --git a/crates/client/starknet_client/src/reader/objects/transaction.rs b/crates/client/starknet_client/src/reader/objects/transaction.rs deleted file mode 100644 index b66c426a41..0000000000 --- a/crates/client/starknet_client/src/reader/objects/transaction.rs +++ /dev/null @@ -1,464 +0,0 @@ -#[cfg(test)] -#[path = "transaction_test.rs"] -mod transaction_test; - -use std::collections::HashMap; - -use lazy_static::lazy_static; -use serde::{Deserialize, Serialize}; -use starknet_api::api_core::{ - ClassHash, - CompiledClassHash, - ContractAddress, - EntryPointSelector, - EthAddress, - Nonce, -}; -use starknet_api::hash::{StarkFelt, StarkHash}; -use starknet_api::transaction::{ - Calldata, - ContractAddressSalt, - DeclareTransactionOutput, - DeployAccountTransactionOutput, - DeployTransactionOutput, - Event, - Fee, - InvokeTransactionOutput, - L1HandlerTransactionOutput, - L1ToL2Payload, - L2ToL1Payload, - MessageToL1, - TransactionExecutionStatus, - TransactionHash, - TransactionOffsetInBlock, - TransactionOutput, - TransactionSignature, - TransactionVersion, -}; - -use crate::reader::ReaderClientError; - -lazy_static! { - static ref TX_V0: TransactionVersion = TransactionVersion(StarkFelt::from(0u128)); - static ref TX_V1: TransactionVersion = TransactionVersion(StarkFelt::from(1u128)); - static ref TX_V2: TransactionVersion = TransactionVersion(StarkFelt::from(2u128)); -} - -// TODO(dan): consider extracting common fields out (version, hash, type). -#[derive(Debug, Deserialize, Serialize, Clone, Eq, PartialEq)] -#[serde(tag = "type")] -pub enum Transaction { - #[serde(rename = "DECLARE")] - Declare(IntermediateDeclareTransaction), - #[serde(rename = "DEPLOY_ACCOUNT")] - DeployAccount(DeployAccountTransaction), - #[serde(rename = "DEPLOY")] - Deploy(DeployTransaction), - #[serde(rename = "INVOKE_FUNCTION")] - Invoke(IntermediateInvokeTransaction), - #[serde(rename = "L1_HANDLER")] - L1Handler(L1HandlerTransaction), -} - -impl TryFrom for starknet_api::transaction::Transaction { - type Error = ReaderClientError; - fn try_from(tx: Transaction) -> Result { - match tx { - Transaction::Declare(declare_tx) => { - Ok(starknet_api::transaction::Transaction::Declare(declare_tx.try_into()?)) - } - Transaction::Deploy(deploy_tx) => { - Ok(starknet_api::transaction::Transaction::Deploy(deploy_tx.into())) - } - Transaction::DeployAccount(deploy_acc_tx) => { - Ok(starknet_api::transaction::Transaction::DeployAccount(deploy_acc_tx.into())) - } - Transaction::Invoke(invoke_tx) => { - Ok(starknet_api::transaction::Transaction::Invoke(invoke_tx.try_into()?)) - } - Transaction::L1Handler(l1_handler_tx) => { - Ok(starknet_api::transaction::Transaction::L1Handler(l1_handler_tx.into())) - } - } - } -} - -impl Transaction { - pub fn transaction_hash(&self) -> TransactionHash { - match self { - Transaction::Declare(tx) => tx.transaction_hash, - Transaction::Deploy(tx) => tx.transaction_hash, - Transaction::DeployAccount(tx) => tx.transaction_hash, - Transaction::Invoke(tx) => tx.transaction_hash, - Transaction::L1Handler(tx) => tx.transaction_hash, - } - } - - pub fn transaction_type(&self) -> TransactionType { - match self { - Transaction::Declare(_) => TransactionType::Declare, - Transaction::Deploy(_) => TransactionType::Deploy, - Transaction::DeployAccount(_) => TransactionType::DeployAccount, - Transaction::Invoke(_) => TransactionType::InvokeFunction, - Transaction::L1Handler(_) => TransactionType::L1Handler, - } - } - - pub fn contract_address(&self) -> Option { - match self { - Transaction::Deploy(tx) => Some(tx.contract_address), - Transaction::DeployAccount(tx) => Some(tx.contract_address), - _ => None, - } - } -} - -#[derive(Debug, Clone, Default, Eq, PartialEq, Hash, Deserialize, Serialize, PartialOrd, Ord)] -#[serde(deny_unknown_fields)] -pub struct L1HandlerTransaction { - pub transaction_hash: TransactionHash, - pub version: TransactionVersion, - #[serde(default)] - pub nonce: Nonce, - pub contract_address: ContractAddress, - pub entry_point_selector: EntryPointSelector, - pub calldata: Calldata, -} - -impl From for starknet_api::transaction::L1HandlerTransaction { - fn from(l1_handler_tx: L1HandlerTransaction) -> Self { - starknet_api::transaction::L1HandlerTransaction { - version: l1_handler_tx.version, - nonce: l1_handler_tx.nonce, - contract_address: l1_handler_tx.contract_address, - entry_point_selector: l1_handler_tx.entry_point_selector, - calldata: l1_handler_tx.calldata, - } - } -} - -#[derive(Debug, Deserialize, Serialize, Clone, Eq, PartialEq)] -#[serde(deny_unknown_fields)] -pub struct IntermediateDeclareTransaction { - pub class_hash: ClassHash, - pub compiled_class_hash: Option, - pub sender_address: ContractAddress, - pub nonce: Nonce, - pub max_fee: Fee, - pub version: TransactionVersion, - pub transaction_hash: TransactionHash, - pub signature: TransactionSignature, -} - -impl TryFrom for starknet_api::transaction::DeclareTransaction { - type Error = ReaderClientError; - - fn try_from(declare_tx: IntermediateDeclareTransaction) -> Result { - match declare_tx.version { - v if v == *TX_V0 => Ok(Self::V0(declare_tx.into())), - v if v == *TX_V1 => Ok(Self::V1(declare_tx.into())), - v if v == *TX_V2 => Ok(Self::V2(declare_tx.try_into()?)), - _ => Err(ReaderClientError::BadTransaction { - tx_hash: declare_tx.transaction_hash, - msg: format!("Declare version {:?} is not supported.", declare_tx.version), - }), - } - } -} - -impl From for starknet_api::transaction::DeclareTransactionV0V1 { - fn from(declare_tx: IntermediateDeclareTransaction) -> Self { - Self { - max_fee: declare_tx.max_fee, - signature: declare_tx.signature, - nonce: declare_tx.nonce, - class_hash: declare_tx.class_hash, - sender_address: declare_tx.sender_address, - } - } -} - -impl TryFrom for starknet_api::transaction::DeclareTransactionV2 { - type Error = ReaderClientError; - - fn try_from(declare_tx: IntermediateDeclareTransaction) -> Result { - Ok(Self { - max_fee: declare_tx.max_fee, - signature: declare_tx.signature, - nonce: declare_tx.nonce, - class_hash: declare_tx.class_hash, - compiled_class_hash: declare_tx.compiled_class_hash.ok_or( - ReaderClientError::BadTransaction { - tx_hash: declare_tx.transaction_hash, - msg: "Declare V2 must contain compiled_class_hash field.".to_string(), - }, - )?, - sender_address: declare_tx.sender_address, - }) - } -} - -#[derive(Debug, Default, Deserialize, Serialize, Clone, Eq, PartialEq)] -#[serde(deny_unknown_fields)] -pub struct DeployTransaction { - pub contract_address: ContractAddress, - pub contract_address_salt: ContractAddressSalt, - pub class_hash: ClassHash, - pub constructor_calldata: Calldata, - pub transaction_hash: TransactionHash, - #[serde(default)] - pub version: TransactionVersion, -} - -impl From for starknet_api::transaction::DeployTransaction { - fn from(deploy_tx: DeployTransaction) -> Self { - starknet_api::transaction::DeployTransaction { - version: deploy_tx.version, - constructor_calldata: deploy_tx.constructor_calldata, - class_hash: deploy_tx.class_hash, - contract_address_salt: deploy_tx.contract_address_salt, - } - } -} - -#[derive(Debug, Default, Deserialize, Serialize, Clone, Eq, PartialEq)] -#[serde(deny_unknown_fields)] -pub struct DeployAccountTransaction { - pub contract_address: ContractAddress, - pub contract_address_salt: ContractAddressSalt, - pub class_hash: ClassHash, - pub constructor_calldata: Calldata, - pub nonce: Nonce, - pub max_fee: Fee, - pub signature: TransactionSignature, - pub transaction_hash: TransactionHash, - #[serde(default)] - pub version: TransactionVersion, -} - -impl From for starknet_api::transaction::DeployAccountTransaction { - fn from(deploy_tx: DeployAccountTransaction) -> Self { - starknet_api::transaction::DeployAccountTransaction { - version: deploy_tx.version, - constructor_calldata: deploy_tx.constructor_calldata, - class_hash: deploy_tx.class_hash, - contract_address_salt: deploy_tx.contract_address_salt, - max_fee: deploy_tx.max_fee, - signature: deploy_tx.signature, - nonce: deploy_tx.nonce, - } - } -} - -#[derive(Debug, Default, Deserialize, Serialize, Clone, Eq, PartialEq)] -#[serde(deny_unknown_fields)] -pub struct IntermediateInvokeTransaction { - pub calldata: Calldata, - // In early versions of starknet, the `sender_address` field was originally named - // `contract_address`. - #[serde(alias = "contract_address")] - pub sender_address: ContractAddress, - pub entry_point_selector: Option, - #[serde(default)] - pub nonce: Option, - pub max_fee: Fee, - pub signature: TransactionSignature, - pub transaction_hash: TransactionHash, - pub version: TransactionVersion, -} - -impl TryFrom for starknet_api::transaction::InvokeTransaction { - type Error = ReaderClientError; - - fn try_from(invoke_tx: IntermediateInvokeTransaction) -> Result { - match invoke_tx.version { - v if v == *TX_V0 => Ok(Self::V0(invoke_tx.try_into()?)), - v if v == *TX_V1 => Ok(Self::V1(invoke_tx.try_into()?)), - _ => Err(ReaderClientError::BadTransaction { - tx_hash: invoke_tx.transaction_hash, - msg: format!("Invoke version {:?} is not supported.", invoke_tx.version), - }), - } - } -} - -impl TryFrom for starknet_api::transaction::InvokeTransactionV0 { - type Error = ReaderClientError; - - fn try_from(invoke_tx: IntermediateInvokeTransaction) -> Result { - Ok(Self { - max_fee: invoke_tx.max_fee, - signature: invoke_tx.signature, - contract_address: invoke_tx.sender_address, - entry_point_selector: invoke_tx.entry_point_selector.ok_or( - ReaderClientError::BadTransaction { - tx_hash: invoke_tx.transaction_hash, - msg: "Invoke V0 must contain entry_point_selector field.".to_string(), - }, - )?, - calldata: invoke_tx.calldata, - }) - } -} - -impl TryFrom for starknet_api::transaction::InvokeTransactionV1 { - type Error = ReaderClientError; - - fn try_from(invoke_tx: IntermediateInvokeTransaction) -> Result { - // TODO(yair): Consider asserting that entry_point_selector is None. - Ok(Self { - max_fee: invoke_tx.max_fee, - signature: invoke_tx.signature, - nonce: invoke_tx.nonce.ok_or(ReaderClientError::BadTransaction { - tx_hash: invoke_tx.transaction_hash, - msg: "Invoke V1 must contain nonce field.".to_string(), - })?, - sender_address: invoke_tx.sender_address, - calldata: invoke_tx.calldata, - }) - } -} - -#[derive(Debug, Default, Deserialize, Serialize, Clone, Eq, PartialEq)] -pub struct TransactionReceipt { - pub transaction_index: TransactionOffsetInBlock, - pub transaction_hash: TransactionHash, - #[serde(default)] - pub l1_to_l2_consumed_message: L1ToL2Message, - pub l2_to_l1_messages: Vec, - pub events: Vec, - #[serde(default)] - pub execution_resources: ExecutionResources, - pub actual_fee: Fee, - #[serde(default)] - pub execution_status: TransactionExecutionStatus, -} - -impl TransactionReceipt { - pub fn into_starknet_api_transaction_output( - self, - transaction: &Transaction, - ) -> TransactionOutput { - let messages_sent = self.l2_to_l1_messages.into_iter().map(MessageToL1::from).collect(); - let contract_address = transaction.contract_address(); - match transaction.transaction_type() { - TransactionType::Declare => TransactionOutput::Declare(DeclareTransactionOutput { - actual_fee: self.actual_fee, - messages_sent, - events: self.events, - execution_status: self.execution_status, - }), - TransactionType::Deploy => TransactionOutput::Deploy(DeployTransactionOutput { - actual_fee: self.actual_fee, - messages_sent, - events: self.events, - contract_address: contract_address - .expect("Deploy transaction must have a contract address."), - execution_status: self.execution_status, - }), - TransactionType::DeployAccount => { - TransactionOutput::DeployAccount(DeployAccountTransactionOutput { - actual_fee: self.actual_fee, - messages_sent, - events: self.events, - contract_address: contract_address - .expect("Deploy account transaction must have a contract address."), - execution_status: self.execution_status, - }) - } - TransactionType::InvokeFunction => TransactionOutput::Invoke(InvokeTransactionOutput { - actual_fee: self.actual_fee, - messages_sent, - events: self.events, - execution_status: self.execution_status, - }), - TransactionType::L1Handler => { - TransactionOutput::L1Handler(L1HandlerTransactionOutput { - actual_fee: self.actual_fee, - messages_sent, - events: self.events, - execution_status: self.execution_status, - }) - } - } - } -} - -#[derive(Debug, Default, Deserialize, Serialize, Clone, Eq, PartialEq)] -pub struct ExecutionResources { - pub n_steps: u64, - pub builtin_instance_counter: BuiltinInstanceCounter, - pub n_memory_holes: u64, -} - -#[derive(Debug, Deserialize, Serialize, Clone, Eq, PartialEq)] -#[serde(untagged)] -pub enum BuiltinInstanceCounter { - NonEmpty(HashMap), - Empty(EmptyBuiltinInstanceCounter), -} - -impl Default for BuiltinInstanceCounter { - fn default() -> Self { - BuiltinInstanceCounter::Empty(EmptyBuiltinInstanceCounter {}) - } -} - -#[derive(Debug, Default, Deserialize, Serialize, Clone, Eq, PartialEq)] -pub struct EmptyBuiltinInstanceCounter {} - -#[derive(Debug, Clone, Default, Eq, PartialEq, Hash, Deserialize, Serialize, PartialOrd, Ord)] -pub struct L1ToL2Nonce(pub StarkHash); - -#[derive(Debug, Default, Deserialize, Serialize, Clone, Eq, PartialEq)] -pub struct L1ToL2Message { - pub from_address: EthAddress, - pub to_address: ContractAddress, - pub selector: EntryPointSelector, - pub payload: L1ToL2Payload, - #[serde(default)] - pub nonce: L1ToL2Nonce, -} - -impl From for starknet_api::transaction::MessageToL2 { - fn from(message: L1ToL2Message) -> Self { - starknet_api::transaction::MessageToL2 { - from_address: message.from_address, - payload: message.payload, - } - } -} - -#[derive(Debug, Default, Deserialize, Serialize, Clone, Eq, PartialEq)] -pub struct L2ToL1Message { - pub from_address: ContractAddress, - pub to_address: EthAddress, - pub payload: L2ToL1Payload, -} - -impl From for starknet_api::transaction::MessageToL1 { - fn from(message: L2ToL1Message) -> Self { - starknet_api::transaction::MessageToL1 { - to_address: message.to_address, - payload: message.payload, - from_address: message.from_address, - } - } -} - -#[derive( - Debug, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize, PartialOrd, Ord, Default, -)] -pub enum TransactionType { - #[serde(rename(deserialize = "DECLARE", serialize = "DECLARE"))] - Declare, - #[serde(rename(deserialize = "DEPLOY", serialize = "DEPLOY"))] - Deploy, - #[serde(rename(deserialize = "DEPLOY_ACCOUNT", serialize = "DEPLOY_ACCOUNT"))] - DeployAccount, - #[serde(rename(deserialize = "INVOKE_FUNCTION", serialize = "INVOKE_FUNCTION"))] - #[default] - InvokeFunction, - #[serde(rename(deserialize = "L1_HANDLER", serialize = "L1_HANDLER"))] - L1Handler, -} diff --git a/crates/client/starknet_client/src/reader/objects/transaction_test.rs b/crates/client/starknet_client/src/reader/objects/transaction_test.rs deleted file mode 100644 index e53a6d24f6..0000000000 --- a/crates/client/starknet_client/src/reader/objects/transaction_test.rs +++ /dev/null @@ -1,108 +0,0 @@ -use assert::{assert_err, assert_ok}; -use assert_matches::assert_matches; - -use super::{Transaction, TransactionReceipt}; -use crate::test_utils::read_resource::read_resource_file; - -#[test] -fn load_deploy_transaction_succeeds() { - assert_matches!( - serde_json::from_str::(&read_resource_file("reader/deploy_transaction.json")), - Ok(Transaction::Deploy(_)) - ); -} - -#[test] -fn load_invoke_transaction_succeeds() { - assert_matches!( - serde_json::from_str::(&read_resource_file("reader/invoke_transaction.json")), - Ok(Transaction::Invoke(_)) - ); -} - -#[test] -fn load_invoke_with_contract_address_transaction_succeeds() { - let mut json_val: serde_json::Value = - serde_json::from_str(&read_resource_file("reader/invoke_transaction.json")).unwrap(); - let object = json_val.as_object_mut().unwrap(); - let sender_address_value = object.remove("sender_address").unwrap(); - object.insert("contract_address".to_string(), sender_address_value); - assert_matches!(serde_json::from_value::(json_val), Ok(Transaction::Invoke(_))); -} - -#[test] -fn load_l1_handler_transaction_succeeds() { - assert_matches!( - serde_json::from_str::(&read_resource_file( - "reader/invoke_transaction_l1_handler.json" - )), - Ok(Transaction::L1Handler(_)) - ); -} - -#[test] -fn load_declare_transaction_succeeds() { - assert_matches!( - serde_json::from_str::(&read_resource_file("reader/declare_transaction.json")), - Ok(Transaction::Declare(_)) - ); -} - -#[test] -fn load_transaction_succeeds() { - for file_name in [ - "reader/deploy_transaction.json", - "reader/invoke_transaction.json", - "reader/declare_transaction.json", - ] { - assert_ok!(serde_json::from_str::(&read_resource_file(file_name))); - } -} - -#[test] -fn load_transaction_unknown_field_fails() { - for file_name in [ - "reader/deploy_transaction.json", - "reader/invoke_transaction.json", - "reader/declare_transaction.json", - ] { - let mut json_value: serde_json::Value = - serde_json::from_str(&read_resource_file(file_name)).unwrap(); - json_value - .as_object_mut() - .unwrap() - .insert("unknown_field".to_string(), serde_json::Value::Null); - let json_str = serde_json::to_string(&json_value).unwrap(); - assert_err!(serde_json::from_str::(&json_str)); - } -} - -#[test] -fn load_transaction_wrong_type_fails() { - for (file_name, new_wrong_type) in [ - // The transaction has a type that doesn't match the type it is paired with. - ("reader/deploy_transaction.json", "INVOKE_FUNCTION"), - ("reader/invoke_transaction.json", "DECLARE"), - ("reader/declare_transaction.json", "DEPLOY"), - ] { - let mut json_value: serde_json::Value = - serde_json::from_str(&read_resource_file(file_name)).unwrap(); - json_value - .as_object_mut() - .unwrap() - .insert("type".to_string(), serde_json::Value::String(new_wrong_type.to_string())); - let json_str = serde_json::to_string(&json_value).unwrap(); - assert_err!(serde_json::from_str::(&json_str)); - } -} - -#[test] -fn load_transaction_receipt_succeeds() { - for file_name in [ - "reader/transaction_receipt.json", - "reader/transaction_receipt_without_l1_to_l2.json", - "reader/transaction_receipt_without_l1_to_l2_nonce.json", - ] { - assert_ok!(serde_json::from_str::(&read_resource_file(file_name))); - } -} diff --git a/crates/client/starknet_client/src/reader/starknet_feeder_gateway_client_test.rs b/crates/client/starknet_client/src/reader/starknet_feeder_gateway_client_test.rs deleted file mode 100644 index ec5c21ae7d..0000000000 --- a/crates/client/starknet_client/src/reader/starknet_feeder_gateway_client_test.rs +++ /dev/null @@ -1,465 +0,0 @@ -use std::collections::HashMap; -use std::fmt::Debug; -use std::future::Future; - -use assert_matches::assert_matches; -use cairo_lang_starknet::casm_contract_class::CasmContractClass; -use indexmap::indexmap; -use mockito::mock; -use pretty_assertions::assert_eq; -use starknet_api::block::BlockNumber; -use starknet_api::core::{ClassHash, ContractAddress, EntryPointSelector, Nonce, PatriciaKey}; -use starknet_api::deprecated_contract_class::{ - ContractClass as DeprecatedContractClass, - ContractClassAbiEntry, - EntryPoint as DeprecatedEntryPoint, - EntryPointOffset, - EntryPointType as DeprecatedEntryPointType, - FunctionAbiEntry, - Program, - TypedParameter, -}; -use starknet_api::hash::{StarkFelt, StarkHash}; -use starknet_api::state::{EntryPoint, EntryPointType, FunctionIndex}; -use starknet_api::transaction::{Fee, TransactionHash, TransactionSignature, TransactionVersion}; -use starknet_api::{patricia_key, stark_felt}; - -use super::objects::state::StateUpdate; -use super::objects::transaction::IntermediateDeclareTransaction; -use super::{ - Block, - ContractClass, - GenericContractClass, - ReaderClientError, - ReaderClientResult, - StarknetFeederGatewayClient, - StarknetReader, - BLOCK_NUMBER_QUERY, - CLASS_HASH_QUERY, - GET_BLOCK_URL, - GET_STATE_UPDATE_URL, -}; -use crate::test_utils::read_resource::read_resource_file; -use crate::test_utils::retry::get_test_config; - -const NODE_VERSION: &str = "NODE VERSION"; - -#[test] -fn new_urls() { - let url_base_str = "https://url"; - let starknet_client = - StarknetFeederGatewayClient::new(url_base_str, None, NODE_VERSION, get_test_config()) - .unwrap(); - assert_eq!( - starknet_client.urls.get_block.as_str(), - url_base_str.to_string() + "/" + GET_BLOCK_URL - ); - assert_eq!( - starknet_client.urls.get_state_update.as_str(), - url_base_str.to_string() + "/" + GET_STATE_UPDATE_URL - ); -} - -#[tokio::test] -async fn get_block_number() { - let starknet_client = StarknetFeederGatewayClient::new( - &mockito::server_url(), - None, - NODE_VERSION, - get_test_config(), - ) - .unwrap(); - // There are blocks in Starknet. - let mock_block = mock("GET", "/feeder_gateway/get_block?blockNumber=latest") - .with_status(200) - .with_body(read_resource_file("reader/block.json")) - .create(); - let latest_block = starknet_client.latest_block().await.unwrap(); - mock_block.assert(); - assert_eq!(latest_block.unwrap().block_number, BlockNumber(273466)); - - // There are no blocks in Starknet. - let body = r#"{"code": "StarknetErrorCode.BLOCK_NOT_FOUND", "message": "Block number -1 was not found."}"#; - let mock_no_block = mock("GET", "/feeder_gateway/get_block?blockNumber=latest") - .with_status(400) - .with_body(body) - .create(); - let latest_block = starknet_client.latest_block().await.unwrap(); - mock_no_block.assert(); - assert!(latest_block.is_none()); -} - -#[tokio::test] -async fn declare_tx_serde() { - let declare_tx = IntermediateDeclareTransaction { - class_hash: ClassHash(stark_felt!( - "0x7319e2f01b0947afd86c0bb0e95029551b32f6dc192c47b2e8b08415eebbc25" - )), - compiled_class_hash: None, - sender_address: ContractAddress(patricia_key!("0x1")), - nonce: Nonce(stark_felt!("0x0")), - max_fee: Fee(0), - version: TransactionVersion(stark_felt!("0x1")), - transaction_hash: TransactionHash(stark_felt!( - "0x2f2ef64daffdc72bf33b34ad024891691b8eb1d0ab70cc7f8fb71f6fd5e1f22" - )), - signature: TransactionSignature(vec![]), - }; - let raw_declare_tx = serde_json::to_string(&declare_tx).unwrap(); - assert_eq!(declare_tx, serde_json::from_str(&raw_declare_tx).unwrap()); -} - -#[tokio::test] -async fn state_update() { - let starknet_client = StarknetFeederGatewayClient::new( - &mockito::server_url(), - None, - NODE_VERSION, - get_test_config(), - ) - .unwrap(); - let raw_state_update = read_resource_file("reader/block_state_update.json"); - let mock_state_update = - mock("GET", &format!("/feeder_gateway/get_state_update?{BLOCK_NUMBER_QUERY}=123456")[..]) - .with_status(200) - .with_body(&raw_state_update) - .create(); - let state_update = starknet_client.state_update(BlockNumber(123456)).await.unwrap(); - mock_state_update.assert(); - let expected_state_update: StateUpdate = serde_json::from_str(&raw_state_update).unwrap(); - assert_eq!(state_update.unwrap(), expected_state_update); - - let body = r#"{"code": "StarknetErrorCode.BLOCK_NOT_FOUND", "message": "Block number -1 was not found."}"#; - let mock_no_block = - mock("GET", &format!("/feeder_gateway/get_state_update?{BLOCK_NUMBER_QUERY}=999999")[..]) - .with_status(400) - .with_body(body) - .create(); - let state_update = starknet_client.state_update(BlockNumber(999999)).await.unwrap(); - assert!(state_update.is_none()); - mock_no_block.assert(); -} - -#[tokio::test] -async fn serialization_precision() { - let input = - "{\"value\":244116128358498188146337218061232635775543270890529169229936851982759783745}"; - let serialized = serde_json::from_str::(input).unwrap(); - let deserialized = serde_json::to_string(&serialized).unwrap(); - assert_eq!(input, deserialized); -} - -#[tokio::test] -async fn contract_class() { - let starknet_client = StarknetFeederGatewayClient::new( - &mockito::server_url(), - None, - NODE_VERSION, - get_test_config(), - ) - .unwrap(); - let expected_contract_class = ContractClass { - sierra_program: vec![ - stark_felt!("0x302e312e30"), - stark_felt!("0x1c"), - stark_felt!("0x52616e6765436865636b"), - ], - entry_points_by_type: HashMap::from([( - EntryPointType::External, - vec! [EntryPoint { - function_idx: FunctionIndex(0), - selector: EntryPointSelector(stark_felt!( - "0x22ff5f21f0b81b113e63f7db6da94fedef11b2119b4088b89664fb9a3cb658" - )), - }], - ), - (EntryPointType::Constructor, vec![]), - (EntryPointType::L1Handler, vec![]), - ]), - contract_class_version: String::from("0.1.0"), - abi: String::from("[\n {\n \"type\": \"function\",\n \"name\": \"test\",\n \"inputs\": [\n {\n \"name\": \"arg\",\n \"ty\": \"core::felt\"\n },\n {\n \"name\": \"arg1\",\n \"ty\": \"core::felt\"\n },\n {\n \"name\": \"arg2\",\n \"ty\": \"core::felt\"\n }\n ],\n \"output_ty\": \"core::felt\",\n \"state_mutability\": \"external\"\n },\n {\n \"type\": \"function\",\n \"name\": \"empty\",\n \"inputs\": [],\n \"output_ty\": \"()\",\n \"state_mutability\": \"external\"\n },\n {\n \"type\": \"function\",\n \"name\": \"call_foo\",\n \"inputs\": [\n {\n \"name\": \"a\",\n \"ty\": \"core::integer::u128\"\n }\n ],\n \"output_ty\": \"core::integer::u128\",\n \"state_mutability\": \"external\"\n }\n]"), - }; - - let mock_by_hash = - mock( - "GET", - &format!("/feeder_gateway/get_class_by_hash?\ - {CLASS_HASH_QUERY}=0x4e70b19333ae94bd958625f7b61ce9eec631653597e68645e13780061b2136c")[..], - ) - .with_status(200) - .with_body(read_resource_file("reader/contract_class.json")) - .create(); - let contract_class = starknet_client - .class_by_hash(ClassHash(stark_felt!( - "0x4e70b19333ae94bd958625f7b61ce9eec631653597e68645e13780061b2136c" - ))) - .await - .unwrap() - .unwrap(); - - let contract_class = match contract_class { - GenericContractClass::Cairo1ContractClass(class) => class, - _ => unreachable!("Expecting Cairo0ContractClass."), - }; - mock_by_hash.assert(); - assert_eq!(contract_class, expected_contract_class); -} - -#[tokio::test] -async fn deprecated_contract_class() { - let starknet_client = StarknetFeederGatewayClient::new( - &mockito::server_url(), - None, - NODE_VERSION, - get_test_config(), - ) - .unwrap(); - let expected_contract_class = DeprecatedContractClass { - abi: Some(vec![ContractClassAbiEntry::Constructor(FunctionAbiEntry { - name: "constructor".to_string(), - inputs: vec![TypedParameter { - name: "implementation".to_string(), - r#type: "felt".to_string(), - }], - outputs: vec![], - state_mutability: None, - })]), - program: Program { - attributes: serde_json::Value::Array(vec![serde_json::json!(1234)]), - builtins: serde_json::Value::Array(Vec::new()), - compiler_version: serde_json::Value::Null, - data: serde_json::Value::Array(vec![ - serde_json::Value::String("0x20780017fff7ffd".to_string()), - serde_json::Value::String("0x4".to_string()), - serde_json::Value::String("0x400780017fff7ffd".to_string()), - ]), - debug_info: serde_json::Value::Null, - hints: serde_json::Value::Object(serde_json::Map::new()), - identifiers: serde_json::Value::Object(serde_json::Map::new()), - main_scope: serde_json::Value::String("__main__".to_string()), - prime: serde_json::Value::String( - "0x800000000000011000000000000000000000000000000000000000000000001".to_string(), - ), - reference_manager: serde_json::Value::Object(serde_json::Map::new()), - }, - entry_points_by_type: HashMap::from([ - (DeprecatedEntryPointType::L1Handler, vec![]), - ( - DeprecatedEntryPointType::Constructor, - vec![DeprecatedEntryPoint { - selector: EntryPointSelector(stark_felt!( - "0x028ffe4ff0f226a9107253e17a904099aa4f63a02a5621de0576e5aa71bc5194" - )), - offset: EntryPointOffset(62), - }], - ), - ( - DeprecatedEntryPointType::External, - vec![DeprecatedEntryPoint { - selector: EntryPointSelector(stark_felt!( - "0x0000000000000000000000000000000000000000000000000000000000000000" - )), - offset: EntryPointOffset(86), - }], - ), - ]), - }; - let mock_by_hash = - mock( - "GET", - &format!("/feeder_gateway/get_class_by_hash?\ - {CLASS_HASH_QUERY}=0x7af612493193c771c1b12f511a8b4d3b0c6d0648242af4680c7cd0d06186f17")[..], - ) - .with_status(200) - .with_body(read_resource_file("reader/deprecated_contract_class.json")) - .create(); - let contract_class = starknet_client - .class_by_hash(ClassHash(stark_felt!( - "0x7af612493193c771c1b12f511a8b4d3b0c6d0648242af4680c7cd0d06186f17" - ))) - .await - .unwrap() - .unwrap(); - let contract_class = match contract_class { - GenericContractClass::Cairo0ContractClass(class) => class, - _ => unreachable!("Expecting deprecated contract class."), - }; - mock_by_hash.assert(); - assert_eq!(contract_class, expected_contract_class); - - // Undeclared class. - let body = r#"{"code": "StarknetErrorCode.UNDECLARED_CLASS", "message": "Class with hash 0x7 is not declared."}"#; - let mock_by_hash = - mock("GET", &format!("/feeder_gateway/get_class_by_hash?{CLASS_HASH_QUERY}=0x7")[..]) - .with_status(400) - .with_body(body) - .create(); - let class = starknet_client.class_by_hash(ClassHash(stark_felt!("0x7"))).await.unwrap(); - mock_by_hash.assert(); - assert!(class.is_none()); -} - -#[tokio::test] -async fn get_block() { - let starknet_client = StarknetFeederGatewayClient::new( - &mockito::server_url(), - None, - NODE_VERSION, - get_test_config(), - ) - .unwrap(); - let raw_block = read_resource_file("reader/block.json"); - let mock_block = mock("GET", &format!("/feeder_gateway/get_block?{BLOCK_NUMBER_QUERY}=20")[..]) - .with_status(200) - .with_body(&raw_block) - .create(); - let block = starknet_client.block(BlockNumber(20)).await.unwrap().unwrap(); - println!("{:?}", block); - mock_block.assert(); - let expected_block: Block = serde_json::from_str(&raw_block).unwrap(); - assert_eq!(block, expected_block); - - // Non-existing block. - let body = r#"{"code": "StarknetErrorCode.BLOCK_NOT_FOUND", "message": "Block 9999999999 was not found."}"#; - let mock_no_block = - mock("GET", &format!("/feeder_gateway/get_block?{BLOCK_NUMBER_QUERY}=9999999999")[..]) - .with_status(400) - .with_body(body) - .create(); - let block = starknet_client.block(BlockNumber(9999999999)).await.unwrap(); - mock_no_block.assert(); - assert!(block.is_none()); -} - -#[tokio::test] -async fn compiled_class_by_hash() { - let starknet_client = StarknetFeederGatewayClient::new( - &mockito::server_url(), - None, - NODE_VERSION, - get_test_config(), - ) - .unwrap(); - let raw_casm_contract_class = read_resource_file("reader/casm_contract_class.json"); - let mock_casm_contract_class = mock( - "GET", - &format!("/feeder_gateway/get_compiled_class_by_class_hash?{CLASS_HASH_QUERY}=0x7")[..], - ) - .with_status(200) - .with_body(&raw_casm_contract_class) - .create(); - let casm_contract_class = starknet_client - .compiled_class_by_hash(ClassHash(stark_felt!("0x7"))) - .await - .unwrap() - .unwrap(); - mock_casm_contract_class.assert(); - let expected_casm_contract_class: CasmContractClass = - serde_json::from_str(&raw_casm_contract_class).unwrap(); - assert_eq!(casm_contract_class, expected_casm_contract_class); - - let body = r#"{"code": "StarknetErrorCode.UNDECLARED_CLASS", "message": "Class with hash 0x7 is not declared."}"#; - let mock_undeclared = mock( - "GET", - &format!("/feeder_gateway/get_compiled_class_by_class_hash?{CLASS_HASH_QUERY}=0x0")[..], - ) - .with_status(400) - .with_body(body) - .create(); - let class = - starknet_client.compiled_class_by_hash(ClassHash(stark_felt!("0x0"))).await.unwrap(); - mock_undeclared.assert(); - assert!(class.is_none()); -} - -#[tokio::test] -async fn state_update_with_empty_storage_diff() { - let starknet_client = StarknetFeederGatewayClient::new( - &mockito::server_url(), - None, - NODE_VERSION, - get_test_config(), - ) - .unwrap(); - let mut state_update = StateUpdate::default(); - state_update.state_diff.storage_diffs = indexmap!(ContractAddress::default() => vec![]); - - let mock = - mock("GET", &format!("/feeder_gateway/get_state_update?{BLOCK_NUMBER_QUERY}=123456")[..]) - .with_status(200) - .with_body(serde_json::to_string(&state_update).unwrap()) - .create(); - let state_update = starknet_client.state_update(BlockNumber(123456)).await.unwrap().unwrap(); - mock.assert(); - assert!(state_update.state_diff.storage_diffs.is_empty()); -} - -async fn test_unserializable< - Output: Send + Debug, - Fut: Future>, - F: FnOnce(StarknetFeederGatewayClient) -> Fut, ->( - url_suffix: &str, - call_method: F, -) { - let starknet_client = StarknetFeederGatewayClient::new( - &mockito::server_url(), - None, - NODE_VERSION, - get_test_config(), - ) - .unwrap(); - let body = "body"; - let mock = mock("GET", url_suffix).with_status(200).with_body(body).create(); - let error = call_method(starknet_client).await.unwrap_err(); - mock.assert(); - assert_matches!(error, ReaderClientError::SerdeError(_)); -} - -#[tokio::test] -async fn latest_block_unserializable() { - test_unserializable( - "/feeder_gateway/get_block?blockNumber=latest", - |starknet_client| async move { starknet_client.latest_block().await }, - ) - .await -} - -#[tokio::test] -async fn block_unserializable() { - test_unserializable("/feeder_gateway/get_block?blockNumber=20", |starknet_client| async move { - starknet_client.block(BlockNumber(20)).await - }) - .await -} - -#[tokio::test] -async fn class_by_hash_unserializable() { - test_unserializable( - &format!("/feeder_gateway/get_class_by_hash?{CLASS_HASH_QUERY}=0x1")[..], - |starknet_client| async move { - starknet_client.class_by_hash(ClassHash(stark_felt!("0x1"))).await - }, - ) - .await -} - -#[tokio::test] -async fn state_update_unserializable() { - test_unserializable( - &format!("/feeder_gateway/get_state_update?{BLOCK_NUMBER_QUERY}=123456")[..], - |starknet_client| async move { starknet_client.state_update(BlockNumber(123456)).await }, - ) - .await -} - -#[tokio::test] -async fn compiled_class_by_hash_unserializable() { - test_unserializable( - &format!("/feeder_gateway/get_compiled_class_by_class_hash?{CLASS_HASH_QUERY}=0x7")[..], - |starknet_client| async move { - starknet_client.compiled_class_by_hash(ClassHash(stark_felt!("0x7"))).await - }, - ) - .await -} diff --git a/crates/client/starknet_client/src/retry.rs b/crates/client/starknet_client/src/retry.rs deleted file mode 100644 index af4e1cfeaf..0000000000 --- a/crates/client/starknet_client/src/retry.rs +++ /dev/null @@ -1,102 +0,0 @@ -#[cfg(test)] -#[path = "retry_test.rs"] -mod retry_test; - -use std::collections::BTreeMap; -use std::fmt::Debug; -use std::iter::Take; -use std::time::Duration; - -use papyrus_config::dumping::{ser_param, SerializeConfig}; -use papyrus_config::{ParamPath, SerializedParam}; -use serde::{Deserialize, Serialize}; -use tokio_retry::strategy::ExponentialBackoff; -use tokio_retry::{Action, Condition, RetryIf}; -use tracing::debug; - -/// A configuration for the retry mechanism. -#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq)] -pub struct RetryConfig { - /// The initial waiting time in milliseconds. - pub retry_base_millis: u64, - /// The maximum waiting time in milliseconds. - pub retry_max_delay_millis: u64, - /// The maximum number of retries. - pub max_retries: usize, -} - -impl SerializeConfig for RetryConfig { - fn dump(&self) -> BTreeMap { - BTreeMap::from_iter([ - ser_param( - "retry_base_millis", - &self.retry_base_millis, - "Base waiting time after a failed request. After that, the time increases \ - exponentially.", - ), - ser_param( - "retry_max_delay_millis", - &self.retry_max_delay_millis, - "Max waiting time after a failed request.", - ), - ser_param( - "max_retries", - &self.max_retries, - "Maximum number of retries before the node stops retrying.", - ), - ]) - } -} - -/// A utility for retrying actions with a configurable backoff and error filter. Uses an -/// [`ExponentialBackoff`] strategy. -pub struct Retry { - strategy: Take, -} - -impl Retry { - pub fn new(config: &RetryConfig) -> Self { - Retry { - strategy: ExponentialBackoff::from_millis(config.retry_base_millis) - .max_delay(Duration::from_millis(config.retry_max_delay_millis)) - .take(config.max_retries), - } - } - - fn log_condition(err: &E, condition: &mut C) -> bool - where - E: Debug, - C: Condition, - { - if condition.should_retry(err) { - debug!("Received error {:?}, retrying.", err); - true - } else { - debug!("Received error {:?}, not retrying.", err); - false - } - } - - pub async fn start(&self, action: A) -> Result - where - E: Debug, - A: Action, - { - self.start_with_condition(action, |_: &_| true).await - } - - pub async fn start_with_condition( - &self, - action: A, - mut condition: C, - ) -> Result - where - E: Debug, - A: Action, - C: Condition + Send, - { - let condition: Box bool> = - Box::new(|err| Self::log_condition(err, &mut condition)); - RetryIf::spawn(self.strategy.clone(), action, condition).await - } -} diff --git a/crates/client/starknet_client/src/retry_test.rs b/crates/client/starknet_client/src/retry_test.rs deleted file mode 100644 index 7686bd32f6..0000000000 --- a/crates/client/starknet_client/src/retry_test.rs +++ /dev/null @@ -1,49 +0,0 @@ -use std::sync::{Arc, Mutex}; - -use pretty_assertions::assert_eq; - -use super::Retry; -use crate::test_utils::retry::get_test_config; - -struct Worker { - // Number of times the worker was called. Updated in every call to work. - number_of_calls: Arc>, - // Number of times the worker returns errors before it returns ok. - number_of_errors: Arc, -} - -impl Worker { - fn new(number_of_errors: usize) -> Self { - Worker { - number_of_calls: Arc::new(Mutex::new(0)), - number_of_errors: Arc::new(number_of_errors), - } - } - - fn get_last_attempt(&self) -> usize { - *self.number_of_calls.lock().unwrap() - } - - async fn work(&self) -> Result<(), &str> { - let mut number_of_calls = self.number_of_calls.lock().unwrap(); - *number_of_calls += 1; - - if *number_of_calls <= *self.number_of_errors { Err("Some error.") } else { Ok(()) } - } -} - -#[tokio::test] -async fn fail_on_all_attempts() { - let config = get_test_config(); - let worker = Worker::new(10); - Retry::new(&config).start(|| worker.work()).await.unwrap_err(); - assert_eq!(worker.get_last_attempt(), 5); -} - -#[tokio::test] -async fn success_on_third_attempt() { - let config = get_test_config(); - let worker = Worker::new(2); - Retry::new(&config).start(|| worker.work()).await.unwrap(); - assert_eq!(worker.get_last_attempt(), 3); -} diff --git a/crates/client/starknet_client/src/starknet_client_test.rs b/crates/client/starknet_client/src/starknet_client_test.rs deleted file mode 100644 index c1248b6d0c..0000000000 --- a/crates/client/starknet_client/src/starknet_client_test.rs +++ /dev/null @@ -1,186 +0,0 @@ -use assert_matches::assert_matches; -use mockito::mock; -use reqwest::StatusCode; - -use crate::starknet_error::{KnownStarknetErrorCode, StarknetError, StarknetErrorCode}; -use crate::test_utils::retry::{get_test_config, MAX_RETRIES}; -use crate::{ClientError, RetryErrorCode, StarknetClient}; - -const NODE_VERSION: &str = "NODE VERSION"; -const URL_SUFFIX: &str = "/query"; - -#[tokio::test] -async fn request_with_retry_positive_flow() { - const BODY: &str = "body"; - let starknet_client = StarknetClient::new(None, NODE_VERSION, get_test_config()).unwrap(); - let mock = mock("GET", URL_SUFFIX).with_status(200).with_body(BODY).create(); - let mut url = mockito::server_url(); - url.push_str(URL_SUFFIX); - let result = - starknet_client.request_with_retry(starknet_client.internal_client.get(&url)).await; - assert_eq!(result.unwrap(), BODY); - mock.assert(); -} - -#[tokio::test] -async fn request_with_retry_bad_response_status() { - let error_code = StatusCode::NOT_FOUND; - let starknet_client = StarknetClient::new(None, NODE_VERSION, get_test_config()).unwrap(); - let mock = mock("GET", URL_SUFFIX).with_status(error_code.as_u16().into()).create(); - let mut url = mockito::server_url(); - url.push_str(URL_SUFFIX); - let result = - starknet_client.request_with_retry(starknet_client.internal_client.get(&url)).await; - assert_matches!( - result, - Err(ClientError::BadResponseStatus { code, message: _ }) if code == error_code - ); - mock.assert(); -} - -#[tokio::test] -async fn request_with_retry_starknet_error_no_retry() { - let starknet_client = StarknetClient::new(None, NODE_VERSION, get_test_config()).unwrap(); - let expected_starknet_error = StarknetError { - code: StarknetErrorCode::KnownErrorCode(KnownStarknetErrorCode::UndeclaredClass), - message: "message".to_string(), - }; - let mock = mock("GET", URL_SUFFIX) - .with_status(StatusCode::BAD_REQUEST.as_u16().into()) - .with_body(serde_json::to_string(&expected_starknet_error).unwrap()) - .create(); - let mut url = mockito::server_url(); - url.push_str(URL_SUFFIX); - let result = - starknet_client.request_with_retry(starknet_client.internal_client.get(&url)).await; - let Err(ClientError::StarknetError(starknet_error)) = result else { - panic!("Did not get a StarknetError."); - }; - assert_eq!(starknet_error, expected_starknet_error); - mock.assert(); -} - -#[tokio::test] -async fn request_with_retry_serde_error_in_starknet_error() { - let starknet_client = StarknetClient::new(None, NODE_VERSION, get_test_config()).unwrap(); - let mock = mock("GET", URL_SUFFIX) - .with_status(StatusCode::BAD_REQUEST.as_u16().into()) - .with_body("body") - .create(); - let mut url = mockito::server_url(); - url.push_str(URL_SUFFIX); - let result = - starknet_client.request_with_retry(starknet_client.internal_client.get(&url)).await; - assert_matches!(result, Err(ClientError::SerdeError(_))); - mock.assert(); -} - -#[tokio::test] -async fn request_with_retry_max_retries_reached() { - let starknet_client = StarknetClient::new(None, NODE_VERSION, get_test_config()).unwrap(); - for (status_code, error_code) in [ - (StatusCode::TEMPORARY_REDIRECT, RetryErrorCode::Redirect), - (StatusCode::REQUEST_TIMEOUT, RetryErrorCode::Timeout), - (StatusCode::TOO_MANY_REQUESTS, RetryErrorCode::TooManyRequests), - (StatusCode::SERVICE_UNAVAILABLE, RetryErrorCode::ServiceUnavailable), - (StatusCode::GATEWAY_TIMEOUT, RetryErrorCode::Timeout), - ] { - let mock = mock("GET", URL_SUFFIX) - .with_status(status_code.as_u16().into()) - .expect(MAX_RETRIES + 1) - .create(); - let mut url = mockito::server_url(); - url.push_str(URL_SUFFIX); - let result = - starknet_client.request_with_retry(starknet_client.internal_client.get(&url)).await; - assert_matches!( - result, Err(ClientError::RetryError { code, message: _ }) if code == error_code - ); - mock.assert(); - } -} - -#[tokio::test] -async fn request_with_retry_success_on_retry() { - const BODY: &str = "body"; - assert_ne!(0, MAX_RETRIES); - let starknet_client = StarknetClient::new(None, NODE_VERSION, get_test_config()).unwrap(); - for status_code in [ - StatusCode::TEMPORARY_REDIRECT, - StatusCode::REQUEST_TIMEOUT, - StatusCode::TOO_MANY_REQUESTS, - StatusCode::SERVICE_UNAVAILABLE, - StatusCode::GATEWAY_TIMEOUT, - ] { - let mock_failure = mock("GET", URL_SUFFIX) - .with_status(status_code.as_u16().into()) - .expect(MAX_RETRIES) - .create(); - let mock_success = mock("GET", URL_SUFFIX).with_status(200).with_body(BODY).create(); - let mut url = mockito::server_url(); - url.push_str(URL_SUFFIX); - let result = - starknet_client.request_with_retry(starknet_client.internal_client.get(&url)).await; - assert_eq!(result.unwrap(), BODY); - mock_failure.assert(); - mock_success.assert(); - } -} - -#[tokio::test] -async fn request_with_retry_starknet_error_max_retries_reached() { - let starknet_client = StarknetClient::new(None, NODE_VERSION, get_test_config()).unwrap(); - let starknet_error = StarknetError { - code: StarknetErrorCode::KnownErrorCode(KnownStarknetErrorCode::TransactionLimitExceeded), - message: "message".to_string(), - }; - let starknet_error_str = serde_json::to_string(&starknet_error).unwrap(); - let mock = mock("GET", URL_SUFFIX) - .with_status(StatusCode::BAD_REQUEST.as_u16().into()) - .with_body(starknet_error_str) - .expect(MAX_RETRIES + 1) - .create(); - let mut url = mockito::server_url(); - url.push_str(URL_SUFFIX); - let result = - starknet_client.request_with_retry(starknet_client.internal_client.get(&url)).await; - assert_matches!( - result, - Err(ClientError::RetryError { code, message: _ }) if code == RetryErrorCode::TooManyRequests - ); - mock.assert(); -} - -#[tokio::test] -async fn request_with_retry_starknet_error_success_on_retry() { - const BODY: &str = "body"; - assert_ne!(0, MAX_RETRIES); - let starknet_client = StarknetClient::new(None, NODE_VERSION, get_test_config()).unwrap(); - let starknet_error = StarknetError { - code: StarknetErrorCode::KnownErrorCode(KnownStarknetErrorCode::TransactionLimitExceeded), - message: "message".to_string(), - }; - let starknet_error_str = serde_json::to_string(&starknet_error).unwrap(); - let mock_failure = mock("GET", URL_SUFFIX) - .with_status(StatusCode::BAD_REQUEST.as_u16().into()) - .with_body(starknet_error_str) - .expect(MAX_RETRIES) - .create(); - let mock_success = mock("GET", URL_SUFFIX).with_status(200).with_body(BODY).create(); - let mut url = mockito::server_url(); - url.push_str(URL_SUFFIX); - let result = - starknet_client.request_with_retry(starknet_client.internal_client.get(&url)).await; - assert_eq!(result.unwrap(), BODY); - mock_failure.assert(); - mock_success.assert(); -} - -#[test] -fn serialization_precision() { - let input = - "{\"value\":244116128358498188146337218061232635775543270890529169229936851982759783745}"; - let serialized = serde_json::from_str::(input).unwrap(); - let deserialized = serde_json::to_string(&serialized).unwrap(); - assert_eq!(input, deserialized); -} \ No newline at end of file diff --git a/crates/client/starknet_client/src/starknet_error.rs b/crates/client/starknet_client/src/starknet_error.rs deleted file mode 100644 index 5a59d7b9e8..0000000000 --- a/crates/client/starknet_client/src/starknet_error.rs +++ /dev/null @@ -1,96 +0,0 @@ -#[cfg(test)] -#[path = "starknet_error_test.rs"] -mod starknet_error_test; - -use std::fmt::{self, Display, Formatter}; - -#[cfg(any(feature = "testing", test))] -use enum_iterator::Sequence; -use serde::de::Error; -use serde::{Deserialize, Deserializer, Serialize}; - -/// Error codes returned by the starknet gateway. -#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)] -#[serde(untagged)] -pub enum StarknetErrorCode { - #[serde(deserialize_with = "deserialize_unknown_error_code")] - UnknownErrorCode(String), - KnownErrorCode(KnownStarknetErrorCode), -} - -// This struct is needed because #[serde(other)] supports only unit variants and because -// #[serde(field_identifier)] doesn't work with serializable types. -// The issue requesting that #[serde(other)] will deserialize the variant with the unknown tag's -// content is: https://github.com/serde-rs/serde/issues/1701 -#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)] -#[cfg_attr(any(test, feature = "testing"), derive(Sequence))] -pub enum KnownStarknetErrorCode { - #[serde(rename = "StarknetErrorCode.UNDECLARED_CLASS")] - UndeclaredClass, - #[serde(rename = "StarknetErrorCode.BLOCK_NOT_FOUND")] - BlockNotFound, - #[serde(rename = "StarkErrorCode.MALFORMED_REQUEST")] - MalformedRequest, - #[serde(rename = "StarknetErrorCode.OUT_OF_RANGE_CLASS_HASH")] - OutOfRangeClassHash, - #[serde(rename = "StarknetErrorCode.CLASS_ALREADY_DECLARED")] - ClassAlreadyDeclared, - #[serde(rename = "StarknetErrorCode.COMPILATION_FAILED")] - CompilationFailed, - #[serde(rename = "StarknetErrorCode.CONTRACT_BYTECODE_SIZE_TOO_LARGE")] - ContractBytecodeSizeTooLarge, - #[serde(rename = "StarknetErrorCode.CONTRACT_CLASS_OBJECT_SIZE_TOO_LARGE")] - ContractClassObjectSizeTooLarge, - #[serde(rename = "StarknetErrorCode.DUPLICATED_TRANSACTION")] - DuplicatedTransaction, - #[serde(rename = "StarknetErrorCode.ENTRY_POINT_NOT_FOUND_IN_CONTRACT")] - EntryPointNotFoundInContract, - #[serde(rename = "StarknetErrorCode.INSUFFICIENT_ACCOUNT_BALANCE")] - InsufficientAccountBalance, - #[serde(rename = "StarknetErrorCode.INSUFFICIENT_MAX_FEE")] - InsufficientMaxFee, - #[serde(rename = "StarknetErrorCode.INVALID_COMPILED_CLASS_HASH")] - InvalidCompiledClassHash, - #[serde(rename = "StarknetErrorCode.INVALID_CONTRACT_CLASS_VERSION")] - InvalidContractClassVersion, - #[serde(rename = "StarknetErrorCode.INVALID_TRANSACTION_NONCE")] - InvalidTransactionNonce, - #[serde(rename = "StarknetErrorCode.INVALID_TRANSACTION_VERSION")] - InvalidTransactionVersion, - #[serde(rename = "StarknetErrorCode.VALIDATE_FAILURE")] - ValidateFailure, - #[serde(rename = "StarknetErrorCode.TRANSACTION_LIMIT_EXCEEDED")] - TransactionLimitExceeded, -} - -/// A client error wrapping error codes returned by the starknet gateway. -#[derive(thiserror::Error, Clone, Debug, Deserialize, Serialize, Eq, PartialEq)] -pub struct StarknetError { - pub code: StarknetErrorCode, - pub message: String, -} - -impl Display for StarknetError { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!(f, "{self:?}") - } -} - -pub fn deserialize_unknown_error_code<'de, D>(de: D) -> Result -where - D: Deserializer<'de>, -{ - let string: String = Deserialize::deserialize(de)?; - let string_as_json = format!("\"{string}\""); - match serde_json::from_str::(&string_as_json) { - Ok(_) => Err(D::Error::custom( - "Trying to serialize a known Starknet error code into UnknownErrorCode", - )), - Err(json_err) => { - if json_err.is_data() { - return Ok(string); - } - Err(D::Error::custom(json_err)) - } - } -} diff --git a/crates/client/starknet_client/src/starknet_error_test.rs b/crates/client/starknet_client/src/starknet_error_test.rs deleted file mode 100644 index 329bc16b8b..0000000000 --- a/crates/client/starknet_client/src/starknet_error_test.rs +++ /dev/null @@ -1,89 +0,0 @@ -use assert::assert_err; -use serde_json::{Map, Value as JsonValue}; - -use super::{KnownStarknetErrorCode, StarknetError, StarknetErrorCode}; - -fn deserialize_starknet_error(code: &str, message: &str) -> StarknetError { - serde_json::from_value::(JsonValue::Object(Map::from_iter([ - ("code".to_string(), JsonValue::String(code.to_string())), - ("message".to_string(), JsonValue::String(message.to_string())), - ]))) - .unwrap() -} - -#[test] -fn known_error_code_deserialization() { - const MESSAGE: &str = "message"; - for (code_str, known_code) in [ - ("StarknetErrorCode.UNDECLARED_CLASS", KnownStarknetErrorCode::UndeclaredClass), - ("StarknetErrorCode.BLOCK_NOT_FOUND", KnownStarknetErrorCode::BlockNotFound), - ("StarkErrorCode.MALFORMED_REQUEST", KnownStarknetErrorCode::MalformedRequest), - ("StarknetErrorCode.OUT_OF_RANGE_CLASS_HASH", KnownStarknetErrorCode::OutOfRangeClassHash), - ("StarknetErrorCode.CLASS_ALREADY_DECLARED", KnownStarknetErrorCode::ClassAlreadyDeclared), - ("StarknetErrorCode.COMPILATION_FAILED", KnownStarknetErrorCode::CompilationFailed), - ( - "StarknetErrorCode.CONTRACT_BYTECODE_SIZE_TOO_LARGE", - KnownStarknetErrorCode::ContractBytecodeSizeTooLarge, - ), - ( - "StarknetErrorCode.CONTRACT_CLASS_OBJECT_SIZE_TOO_LARGE", - KnownStarknetErrorCode::ContractClassObjectSizeTooLarge, - ), - ("StarknetErrorCode.DUPLICATED_TRANSACTION", KnownStarknetErrorCode::DuplicatedTransaction), - ( - "StarknetErrorCode.ENTRY_POINT_NOT_FOUND_IN_CONTRACT", - KnownStarknetErrorCode::EntryPointNotFoundInContract, - ), - ( - "StarknetErrorCode.INSUFFICIENT_ACCOUNT_BALANCE", - KnownStarknetErrorCode::InsufficientAccountBalance, - ), - ("StarknetErrorCode.INSUFFICIENT_MAX_FEE", KnownStarknetErrorCode::InsufficientMaxFee), - ( - "StarknetErrorCode.INVALID_COMPILED_CLASS_HASH", - KnownStarknetErrorCode::InvalidCompiledClassHash, - ), - ( - "StarknetErrorCode.INVALID_CONTRACT_CLASS_VERSION", - KnownStarknetErrorCode::InvalidContractClassVersion, - ), - ( - "StarknetErrorCode.INVALID_TRANSACTION_NONCE", - KnownStarknetErrorCode::InvalidTransactionNonce, - ), - ( - "StarknetErrorCode.INVALID_TRANSACTION_VERSION", - KnownStarknetErrorCode::InvalidTransactionVersion, - ), - ("StarknetErrorCode.VALIDATE_FAILURE", KnownStarknetErrorCode::ValidateFailure), - ( - "StarknetErrorCode.TRANSACTION_LIMIT_EXCEEDED", - KnownStarknetErrorCode::TransactionLimitExceeded, - ), - ] { - let starknet_error = deserialize_starknet_error(code_str, MESSAGE); - let expected_starknet_error = StarknetError { - code: StarknetErrorCode::KnownErrorCode(known_code), - message: MESSAGE.to_string(), - }; - assert_eq!(expected_starknet_error, starknet_error); - } -} - -#[test] -fn unknown_error_code_deserialization() { - const MESSAGE: &str = "message"; - const CODE_STR: &str = "StarknetErrorCode.MADE_UP_CODE_FOR_TEST"; - let starknet_error = deserialize_starknet_error(CODE_STR, MESSAGE); - let expected_starknet_error = StarknetError { - code: StarknetErrorCode::UnknownErrorCode(CODE_STR.to_string()), - message: MESSAGE.to_string(), - }; - assert_eq!(expected_starknet_error, starknet_error); -} - -// This test is needed because bugs can happen in the custom deserialization of UnknownErrorCode -#[test] -fn starknet_error_code_invalid_json_format_fails() { - assert_err!(serde_json::from_str::("A string not surrounded with quotes")); -} diff --git a/crates/client/starknet_client/src/test_utils/mod.rs b/crates/client/starknet_client/src/test_utils/mod.rs deleted file mode 100644 index 06211610d4..0000000000 --- a/crates/client/starknet_client/src/test_utils/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -#[cfg(test)] -pub mod read_resource; -#[cfg(test)] -pub mod retry; diff --git a/crates/client/starknet_client/src/test_utils/read_resource.rs b/crates/client/starknet_client/src/test_utils/read_resource.rs deleted file mode 100644 index 6627703e29..0000000000 --- a/crates/client/starknet_client/src/test_utils/read_resource.rs +++ /dev/null @@ -1,11 +0,0 @@ -use std::env; -use std::fs::read_to_string; -use std::path::Path; -use std::string::String; - -pub fn read_resource_file(path_in_resource_dir: &str) -> String { - let path = Path::new(&env::var("CARGO_MANIFEST_DIR").unwrap()) - .join("resources") - .join(path_in_resource_dir); - return read_to_string(path.to_str().unwrap()).unwrap(); -} diff --git a/crates/client/starknet_client/src/test_utils/retry.rs b/crates/client/starknet_client/src/test_utils/retry.rs deleted file mode 100644 index 1fa18976e6..0000000000 --- a/crates/client/starknet_client/src/test_utils/retry.rs +++ /dev/null @@ -1,9 +0,0 @@ -use crate::retry::RetryConfig; - -pub const MAX_RETRIES: usize = 4; - -pub fn get_test_config() -> RetryConfig { - // Taking the fastest config possible (except for MAX_RETRIES which we want to be a bit bigger - // to test the functionality). - RetryConfig { retry_base_millis: 0, retry_max_delay_millis: 0, max_retries: MAX_RETRIES } -} diff --git a/crates/client/starknet_client/src/writer/mod.rs b/crates/client/starknet_client/src/writer/mod.rs deleted file mode 100644 index c7bd5fbe6f..0000000000 --- a/crates/client/starknet_client/src/writer/mod.rs +++ /dev/null @@ -1,136 +0,0 @@ -//! This module contains client that can request changes to [`Starknet`]. -//! -//! [`Starknet`]: https://starknet.io/ - -pub mod objects; - -#[cfg(test)] -mod starknet_gateway_client_test; - -use async_trait::async_trait; -#[cfg(any(feature = "testing", test))] -use mockall::automock; -use serde::{Deserialize, Serialize}; -use tracing::instrument; -use url::Url; - -use crate::writer::objects::response::{DeclareResponse, DeployAccountResponse, InvokeResponse}; -use crate::writer::objects::transaction::{ - DeclareTransaction, - DeployAccountTransaction, - InvokeTransaction, -}; -use crate::{ClientCreationError, ClientError, RetryConfig, StarknetClient}; - -/// Errors that may be returned from a writer client. -#[derive(thiserror::Error, Debug)] -pub enum WriterClientError { - /// A client error representing errors from the base StarknetClient. - #[error(transparent)] - ClientError(#[from] ClientError), - /// A client error representing deserialization errors. - /// Note: [`ClientError`] contains SerdeError as well. The difference is that this variant is - /// responsible for serde errors coming from [`StarknetWriter`] and ClientError::SerdeError - /// is responsible for serde errors coming from StarknetClient. - #[error(transparent)] - SerdeError(#[from] serde_json::Error), -} - -pub type WriterClientResult = Result; - -/// A trait describing an object that can communicate with [`Starknet`] and make changes to it. -/// -/// [`Starknet`]: https://starknet.io/ -#[cfg_attr(any(test, feature = "testing"), automock)] -#[async_trait] -pub trait StarknetWriter: Sync + Send + 'static { - /// Add an invoke transaction to [`Starknet`]. - /// - /// [`Starknet`]: https://starknet.io/ - async fn add_invoke_transaction( - &self, - tx: &InvokeTransaction, - ) -> WriterClientResult; - - /// Add a declare transaction to [`Starknet`]. - /// - /// [`Starknet`]: https://starknet.io/ - async fn add_declare_transaction( - &self, - tx: &DeclareTransaction, - ) -> WriterClientResult; - - /// Add a deploy account transaction to [`Starknet`]. - /// - /// [`Starknet`]: https://starknet.io/ - async fn add_deploy_account_transaction( - &self, - tx: &DeployAccountTransaction, - ) -> WriterClientResult; -} - -const ADD_TRANSACTION_URL_SUFFIX: &str = "gateway/add_transaction"; - -/// A client for the [`Starknet`] gateway. -/// -/// [`Starknet`]: https://starknet.io/ -pub struct StarknetGatewayClient { - add_transaction_url: Url, - client: StarknetClient, -} - -#[async_trait] -impl StarknetWriter for StarknetGatewayClient { - #[instrument(skip(self), level = "debug")] - async fn add_invoke_transaction( - &self, - tx: &InvokeTransaction, - ) -> WriterClientResult { - self.add_transaction(&tx).await - } - - #[instrument(skip(self), level = "debug")] - async fn add_deploy_account_transaction( - &self, - tx: &DeployAccountTransaction, - ) -> WriterClientResult { - self.add_transaction(&tx).await - } - - #[instrument(skip(self), level = "debug")] - async fn add_declare_transaction( - &self, - tx: &DeclareTransaction, - ) -> WriterClientResult { - self.add_transaction(&tx).await - } -} - -impl StarknetGatewayClient { - pub fn new( - starknet_url: &str, - node_version: &'static str, - retry_config: RetryConfig, - ) -> Result { - Ok(StarknetGatewayClient { - add_transaction_url: Url::parse(starknet_url)?.join(ADD_TRANSACTION_URL_SUFFIX)?, - client: StarknetClient::new(None, node_version, retry_config)?, - }) - } - - async fn add_transaction Deserialize<'a>>( - &self, - tx: &Transaction, - ) -> WriterClientResult { - let response: String = self - .client - .request_with_retry( - self.client - .internal_client - .post(self.add_transaction_url.clone()) - .body(serde_json::to_string(&tx)?), - ) - .await?; - Ok(serde_json::from_str::(&response)?) - } -} diff --git a/crates/client/starknet_client/src/writer/objects/mod.rs b/crates/client/starknet_client/src/writer/objects/mod.rs deleted file mode 100644 index 62fc7eae98..0000000000 --- a/crates/client/starknet_client/src/writer/objects/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod response; -#[cfg(any(feature = "testing", test))] -pub mod test_utils; -pub mod transaction; diff --git a/crates/client/starknet_client/src/writer/objects/response.rs b/crates/client/starknet_client/src/writer/objects/response.rs deleted file mode 100644 index d3eca4df4a..0000000000 --- a/crates/client/starknet_client/src/writer/objects/response.rs +++ /dev/null @@ -1,45 +0,0 @@ -//! This module contains the response returned by the [`Starknet`] gateway on the successful flow. -//! -//! [`Starknet`]: https://starknet.io/ - -#[cfg(test)] -#[path = "response_test.rs"] -mod response_test; - -use serde::{Deserialize, Serialize}; -use starknet_api::api_core::{ClassHash, ContractAddress}; -use starknet_api::transaction::TransactionHash; - -/// A Starknet error code that reports success. -#[derive(Debug, Default, Deserialize, Serialize, Clone, Eq, PartialEq)] -pub enum SuccessfulStarknetErrorCode { - #[serde(rename = "TRANSACTION_RECEIVED")] - #[default] - TransactionReceived, -} - -/// The response of adding a declare transaction through the Starknet gateway successfully. -#[derive(Debug, Deserialize, Serialize, Clone, Eq, PartialEq)] -#[serde(deny_unknown_fields)] -pub struct DeclareResponse { - pub code: SuccessfulStarknetErrorCode, - pub transaction_hash: TransactionHash, - pub class_hash: ClassHash, -} - -/// The response of adding a deploy account transaction through the Starknet gateway successfully. -#[derive(Debug, Deserialize, Serialize, Clone, Eq, PartialEq)] -#[serde(deny_unknown_fields)] -pub struct DeployAccountResponse { - pub code: SuccessfulStarknetErrorCode, - pub transaction_hash: TransactionHash, - pub address: ContractAddress, -} - -/// The response of adding an invoke transaction through the Starknet gateway successfully. -#[derive(Debug, Deserialize, Serialize, Clone, Eq, PartialEq)] -#[serde(deny_unknown_fields)] -pub struct InvokeResponse { - pub code: SuccessfulStarknetErrorCode, - pub transaction_hash: TransactionHash, -} diff --git a/crates/client/starknet_client/src/writer/objects/response_test.rs b/crates/client/starknet_client/src/writer/objects/response_test.rs deleted file mode 100644 index 15d2231f2c..0000000000 --- a/crates/client/starknet_client/src/writer/objects/response_test.rs +++ /dev/null @@ -1,18 +0,0 @@ -use test_utils::validate_load_and_dump; - -use super::{DeclareResponse, DeployAccountResponse, InvokeResponse}; - -#[test] -fn load_and_dump_deploy_account_same_string() { - validate_load_and_dump::("writer/deploy_account_response.json"); -} - -#[test] -fn load_and_dump_invoke_same_string() { - validate_load_and_dump::("writer/invoke_response.json"); -} - -#[test] -fn load_and_dump_declare_same_string() { - validate_load_and_dump::("writer/declare_response.json"); -} diff --git a/crates/client/starknet_client/src/writer/objects/test_utils.rs b/crates/client/starknet_client/src/writer/objects/test_utils.rs deleted file mode 100644 index ddc3dd8589..0000000000 --- a/crates/client/starknet_client/src/writer/objects/test_utils.rs +++ /dev/null @@ -1,43 +0,0 @@ -use std::collections::HashMap; - -use starknet_api::core::{ClassHash, ContractAddress}; -use starknet_api::deprecated_contract_class::{ - ContractClassAbiEntry as DeprecatedContractClassAbiEntry, - EntryPoint as DeprecatedEntryPoint, - EntryPointType as DeprecatedEntryPointType, -}; -use starknet_api::transaction::TransactionHash; -use test_utils::{auto_impl_get_test_instance, get_number_of_variants, GetTestInstance}; - -use crate::writer::objects::response::{ - DeclareResponse, - DeployAccountResponse, - InvokeResponse, - SuccessfulStarknetErrorCode, -}; -use crate::writer::objects::transaction::DeprecatedContractClass; - -auto_impl_get_test_instance! { - pub struct DeprecatedContractClass { - pub abi: Option>, - pub compressed_program: String, - pub entry_points_by_type: HashMap>, - } - pub struct InvokeResponse { - pub code: SuccessfulStarknetErrorCode, - pub transaction_hash: TransactionHash, - } - pub struct DeployAccountResponse { - pub code: SuccessfulStarknetErrorCode, - pub transaction_hash: TransactionHash, - pub address: ContractAddress, - } - pub struct DeclareResponse { - pub code: SuccessfulStarknetErrorCode, - pub transaction_hash: TransactionHash, - pub class_hash: ClassHash, - } - pub enum SuccessfulStarknetErrorCode { - TransactionReceived = 0, - } -} diff --git a/crates/client/starknet_client/src/writer/objects/transaction.rs b/crates/client/starknet_client/src/writer/objects/transaction.rs deleted file mode 100644 index 0ad12e6b34..0000000000 --- a/crates/client/starknet_client/src/writer/objects/transaction.rs +++ /dev/null @@ -1,187 +0,0 @@ -//! This module contains all the different transactions that can be added to [`Starknet`] via the -//! gateway. -//! -//! Each transaction can be serialized into a JSON object that the gateway can receive through the -//! `add_transaction` HTTP method. -//! -//! [`Starknet`]: https://starknet.io/ - -#[cfg(test)] -#[path = "transaction_test.rs"] -mod transaction_test; - -use std::collections::HashMap; - -use serde::{Deserialize, Serialize}; -use starknet_api::api_core::{ClassHash, Nonce, ContractAddress, CompiledClassHash}; -use starknet_api::deprecated_contract_class::{ - ContractClassAbiEntry as DeprecatedContractClassAbiEntry, - EntryPoint as DeprecatedEntryPoint, - EntryPointType as DeprecatedEntryPointType, -}; -use starknet_api::state::{EntryPoint, EntryPointType}; -use starknet_api::transaction::{ - Calldata, - ContractAddressSalt, - Fee, - TransactionSignature, - TransactionVersion, -}; - -// Each transaction type has a field called `type`. This field needs to be of a type that -// serializes to/deserializes from a constant string. -// -// The reason we don't solve this by having an enum of a generic transaction and let serde generate -// the `type` field through #[serde(tag)] is because we want to serialize/deserialize from the -// structs of the specific transaction types. - -/// The type field of a deploy account transaction. This enum serializes/deserializes into a -/// constant string. -#[derive(Debug, Deserialize, Serialize, Default, Clone, Copy, Eq, PartialEq)] -pub enum DeployAccountType { - #[serde(rename = "DEPLOY_ACCOUNT")] - #[default] - DeployAccount, -} - -/// The type field of an invoke transaction. This enum serializes/deserializes into a constant -/// string. -#[derive(Debug, Deserialize, Serialize, Default, Clone, Copy, Eq, PartialEq)] -pub enum InvokeType { - #[serde(rename = "INVOKE_FUNCTION")] - #[default] - Invoke, -} - -/// The type field of a declare V1 transaction. This enum serializes/deserializes into a constant -/// string. -#[derive(Debug, Deserialize, Serialize, Default, Clone, Copy, Eq, PartialEq)] -pub enum DeclareV1Type { - #[serde(rename = "DEPRECATED_DECLARE")] - #[default] - DeclareV1, -} - -/// The type field of a declare V2 transaction. This enum serializes/deserializes into a constant -/// string. -#[derive(Debug, Deserialize, Serialize, Default, Clone, Copy, Eq, PartialEq)] -pub enum DeclareV2Type { - #[serde(rename = "DECLARE")] - #[default] - DeclareV2, -} - -/// A deploy account transaction that can be added to Starknet through the Starknet gateway. -/// It has a serialization format that the Starknet gateway accepts in the `add_transaction` -/// HTTP method. -#[derive(Debug, Default, Deserialize, Serialize, Clone, Eq, PartialEq)] -#[serde(deny_unknown_fields)] -pub struct DeployAccountTransaction { - pub contract_address_salt: ContractAddressSalt, - pub class_hash: ClassHash, - pub constructor_calldata: Calldata, - pub nonce: Nonce, - pub max_fee: Fee, - pub signature: TransactionSignature, - pub version: TransactionVersion, - pub r#type: DeployAccountType, -} - -/// An invoke account transaction that can be added to Starknet through the Starknet gateway. -/// The invoke is a V1 transaction. -/// It has a serialization format that the Starknet gateway accepts in the `add_transaction` -/// HTTP method. -#[derive(Debug, Default, Deserialize, Serialize, Clone, Eq, PartialEq)] -#[serde(deny_unknown_fields)] -pub struct InvokeTransaction { - pub calldata: Calldata, - pub sender_address: ContractAddress, - pub nonce: Nonce, - pub max_fee: Fee, - pub signature: TransactionSignature, - pub version: TransactionVersion, - pub r#type: InvokeType, -} - -/// A declare transaction of a Cairo-v0 (deprecated) contract class that can be added to Starknet -/// through the Starknet gateway. -/// It has a serialization format that the Starknet gateway accepts in the `add_transaction` -/// HTTP method. -#[derive(Debug, Default, Deserialize, Serialize, Clone, Eq, PartialEq)] -#[serde(deny_unknown_fields)] -pub struct DeclareV1Transaction { - pub contract_class: DeprecatedContractClass, - pub sender_address: ContractAddress, - pub nonce: Nonce, - pub max_fee: Fee, - pub version: TransactionVersion, - pub signature: TransactionSignature, - pub r#type: DeclareV1Type, -} - -/// A declare transaction of a Cairo-v1 contract class that can be added to Starknet through the -/// Starknet gateway. -/// It has a serialization format that the Starknet gateway accepts in the `add_transaction` -/// HTTP method. -#[derive(Debug, Default, Deserialize, Serialize, Clone, Eq, PartialEq)] -#[serde(deny_unknown_fields)] -pub struct DeclareV2Transaction { - pub contract_class: ContractClass, - pub compiled_class_hash: CompiledClassHash, - pub sender_address: ContractAddress, - pub nonce: Nonce, - pub max_fee: Fee, - pub version: TransactionVersion, - pub signature: TransactionSignature, - pub r#type: DeclareV2Type, -} - -/// A declare transaction that can be added to Starknet through the Starknet gateway. -/// It has a serialization format that the Starknet gateway accepts in the `add_transaction` -/// HTTP method. -#[derive(Debug, Deserialize, Serialize, Clone, Eq, PartialEq)] -#[serde(untagged)] -pub enum DeclareTransaction { - DeclareV1(DeclareV1Transaction), - DeclareV2(DeclareV2Transaction), -} - -// The structs that are implemented here are the structs that have deviations from starknet_api. - -#[derive(Debug, Clone, Default, Eq, PartialEq, Deserialize, Serialize)] -pub struct DeprecatedContractClass { - #[serde(skip_serializing_if = "Option::is_none")] - #[serde(default)] - pub abi: Option>, - #[serde(rename = "program")] - // TODO(shahak): Create a struct for a compressed base64 value. - pub compressed_program: String, - pub entry_points_by_type: HashMap>, -} - -#[derive(Debug, Clone, Default, Eq, PartialEq, Deserialize, Serialize)] -pub struct ContractClass { - // TODO(shahak): Create a struct for a compressed base64 value. - #[serde(rename = "sierra_program")] - pub compressed_sierra_program: String, - pub contract_class_version: String, - pub entry_points_by_type: HashMap>, - pub abi: String, -} - -// The conversion is done here and not in papyrus_rpc because the gateway uses starknet_api for -// DeployAccountTransaction. -impl From for DeployAccountTransaction { - fn from(tx: starknet_api::transaction::DeployAccountTransaction) -> Self { - Self { - contract_address_salt: tx.contract_address_salt, - class_hash: tx.class_hash, - constructor_calldata: tx.constructor_calldata, - nonce: tx.nonce, - max_fee: tx.max_fee, - signature: tx.signature, - version: tx.version, - r#type: DeployAccountType::default(), - } - } -} diff --git a/crates/client/starknet_client/src/writer/objects/transaction_test.rs b/crates/client/starknet_client/src/writer/objects/transaction_test.rs deleted file mode 100644 index b2e0cb7e86..0000000000 --- a/crates/client/starknet_client/src/writer/objects/transaction_test.rs +++ /dev/null @@ -1,36 +0,0 @@ -use test_utils::{get_rng, validate_load_and_dump, GetTestInstance}; - -use super::{ - DeclareV1Transaction, - DeclareV2Transaction, - DeployAccountTransaction, - InvokeTransaction, -}; - -#[test] -fn load_and_dump_deploy_account_same_string() { - validate_load_and_dump::("writer/deploy_account.json"); -} - -#[test] -fn load_and_dump_invoke_same_string() { - validate_load_and_dump::("writer/invoke.json"); -} - -#[test] -fn load_and_dump_declare_v1_same_string() { - validate_load_and_dump::("writer/declare_v1.json"); -} - -#[test] -fn load_and_dump_declare_v2_same_string() { - validate_load_and_dump::("writer/declare_v2.json"); -} - -#[test] -fn test_deploy_account_transaction_from_starknet_api() { - let _deploy_account_transaction: DeployAccountTransaction = - starknet_api::transaction::DeployAccountTransaction::get_test_instance(&mut get_rng()) - .try_into() - .unwrap(); -} diff --git a/crates/client/starknet_client/src/writer/starknet_gateway_client_test.rs b/crates/client/starknet_client/src/writer/starknet_gateway_client_test.rs deleted file mode 100644 index 5cc128f934..0000000000 --- a/crates/client/starknet_client/src/writer/starknet_gateway_client_test.rs +++ /dev/null @@ -1,173 +0,0 @@ -use std::fmt::Debug; -use std::future::Future; - -use mockito::{mock, Matcher}; -use serde::{Deserialize, Serialize}; -use test_utils::read_json_file; - -use crate::test_utils::retry::get_test_config; -use crate::writer::{StarknetGatewayClient, StarknetWriter, WriterClientError, WriterClientResult}; - -const NODE_VERSION: &str = "NODE VERSION"; - -async fn run_add_transaction< - Transaction: Serialize + for<'a> Deserialize<'a>, - Response: for<'a> Deserialize<'a> + Debug + Eq, - F: FnOnce(StarknetGatewayClient, Transaction) -> Fut, - Fut: Future>, ->( - resource_file_transaction_path: &str, - resource_file_response_path: &str, - add_transaction_function: F, -) -> WriterClientResult { - let client = - StarknetGatewayClient::new(&mockito::server_url(), NODE_VERSION, get_test_config()) - .unwrap(); - let tx_json_value = read_json_file(resource_file_transaction_path); - let tx = serde_json::from_value::(tx_json_value.clone()).unwrap(); - let response_json_value = read_json_file(resource_file_response_path); - let mock_add_transaction = mock("POST", "/gateway/add_transaction") - .match_body(Matcher::Json(tx_json_value)) - .with_status(200) - .with_body(serde_json::to_string(&response_json_value).unwrap()) - .create(); - let result = add_transaction_function(client, tx).await; - mock_add_transaction.assert(); - result -} - -async fn test_add_transaction_succeeds< - Transaction: Serialize + for<'a> Deserialize<'a>, - Response: for<'a> Deserialize<'a> + Debug + Eq, - F: FnOnce(StarknetGatewayClient, Transaction) -> Fut, - Fut: Future>, ->( - resource_file_transaction_path: &str, - resource_file_response_path: &str, - add_transaction_function: F, -) { - let response_json_value = read_json_file(resource_file_response_path); - let expected_response = serde_json::from_value::(response_json_value).unwrap(); - assert_eq!( - expected_response, - run_add_transaction( - resource_file_transaction_path, - resource_file_response_path, - add_transaction_function - ) - .await - .unwrap() - ); -} - -async fn test_add_transaction_fails_serde< - Transaction: Serialize + for<'a> Deserialize<'a>, - Response: for<'a> Deserialize<'a> + Debug + Eq, - F: FnOnce(StarknetGatewayClient, Transaction) -> Fut, - Fut: Future>, ->( - resource_file_transaction_path: &str, - resource_file_response_path: &str, - add_transaction_function: F, -) { - let Err(WriterClientError::SerdeError(_)) = run_add_transaction( - resource_file_transaction_path, - resource_file_response_path, - add_transaction_function, - ) - .await - else { - panic!("Adding a transaction with bad response did not cause a SerdeError"); - }; -} - -#[tokio::test] -async fn add_invoke_transaction() { - test_add_transaction_succeeds( - "writer/invoke.json", - "writer/invoke_response.json", - |client, tx| async move { client.add_invoke_transaction(&tx).await }, - ) - .await; -} - -#[tokio::test] -async fn add_declare_v1_transaction() { - test_add_transaction_succeeds( - "writer/declare_v1.json", - "writer/declare_response.json", - |client, tx| async move { client.add_declare_transaction(&tx).await }, - ) - .await; -} - -#[tokio::test] -async fn add_declare_v2_transaction() { - test_add_transaction_succeeds( - "writer/declare_v2.json", - "writer/declare_response.json", - |client, tx| async move { client.add_declare_transaction(&tx).await }, - ) - .await; -} - -#[tokio::test] -async fn add_deploy_account_transaction() { - test_add_transaction_succeeds( - "writer/deploy_account.json", - "writer/deploy_account_response.json", - |client, tx| async move { client.add_deploy_account_transaction(&tx).await }, - ) - .await; -} - -#[tokio::test] -async fn add_invoke_transaction_wrong_type_response() { - for bad_response_path in ["writer/declare_response.json", "writer/deploy_account_response.json"] - { - test_add_transaction_fails_serde( - "writer/invoke.json", - bad_response_path, - |client, tx| async move { client.add_invoke_transaction(&tx).await }, - ) - .await; - } -} - -#[tokio::test] -async fn add_declare_v1_transaction_wrong_type_response() { - for bad_response_path in ["writer/invoke_response.json", "writer/deploy_account_response.json"] - { - test_add_transaction_fails_serde( - "writer/declare_v1.json", - bad_response_path, - |client, tx| async move { client.add_declare_transaction(&tx).await }, - ) - .await; - } -} - -#[tokio::test] -async fn add_declare_v2_transaction_wrong_type_response() { - for bad_response_path in ["writer/invoke_response.json", "writer/deploy_account_response.json"] - { - test_add_transaction_fails_serde( - "writer/declare_v2.json", - bad_response_path, - |client, tx| async move { client.add_declare_transaction(&tx).await }, - ) - .await; - } -} - -#[tokio::test] -async fn add_deploy_account_transaction_wrong_type_response() { - for bad_response_path in ["writer/invoke_response.json", "writer/declare_response.json"] { - test_add_transaction_fails_serde( - "writer/deploy_account.json", - bad_response_path, - |client, tx| async move { client.add_deploy_account_transaction(&tx).await }, - ) - .await; - } -} diff --git a/crates/client/starknet_client/tests/feeder_gateway_integration_test.rs b/crates/client/starknet_client/tests/feeder_gateway_integration_test.rs deleted file mode 100644 index 3df2804cc4..0000000000 --- a/crates/client/starknet_client/tests/feeder_gateway_integration_test.rs +++ /dev/null @@ -1,185 +0,0 @@ -use serde::Serialize; -use starknet_api::block::BlockNumber; -use starknet_api::core::ClassHash; -use starknet_api::hash::StarkHash; -use starknet_client::reader::{StarknetFeederGatewayClient, StarknetReader}; -use starknet_client::retry::RetryConfig; -use tokio::join; - -const NODE_VERSION: &str = "PAPYRUS-INTEGRATION-TEST-STARKNET-FEEDER-GATEWAY-CLIENT"; - -#[derive(Serialize)] -// Blocks with API changes to be tested with the get_block function. -struct BlocksForGetBlock { - // First block, the original definitions. - first_block: u32, - // A block with declare transaction. (added in v0.9.0). - declare_tx: u32, - // A block with starknet version. (added in v0.9.1). - starknet_version: u32, - // A block with declare transaction version 1. (added in v0.10.0). - // A block with nonce field in transaction. (added in v0.10.0). - declare_version_1: u32, - // A block with invoke_function transaction version 1 (added in v0.10.0). - invoke_version_1: u32, - // A block with deploy_account transaction. (added in v0.10.1). - deploy_account: u32, - // A block with declare transaction version 2. (added in v0.11.0). - declare_version_2: u32, -} - -#[derive(Serialize)] -// Blocks with API changes to be tested with the get_state_update function. -struct BlocksForGetStateUpdate { - // First block, the original definitions. - first_block: u32, - // A state update with 'old_declared_contracts'. (added in v0.9.1). - old_declared_contracts: u32, - // A state update with 'nonces'. (added in v0.10.0). - nonces: u32, - // A state update with 'declared_classes'. (added in v0.11.0). - declared_classes: u32, - // A state update with 'replaced_classes'. (added in v0.11.0). - replaced_classes: u32, -} - -#[derive(Serialize)] -// Class hashes of different versions. -struct ClassHashes { - // A class definition of Cairo 0 contract. - cairo_0_class_hash: String, - // A class definition of Cairo 1 contract. (added in v0.11.0). - cairo_1_class_hash: String, -} - -// Test data for a specific testnet. -struct TestEnvData { - url: String, - get_blocks: BlocksForGetBlock, - get_state_updates: BlocksForGetStateUpdate, - class_hashes: ClassHashes, -} - -fn into_block_number_vec(obj: T) -> Vec { - serde_json::to_value(obj) - .unwrap() - .as_object() - .unwrap() - .values() - .map(|block_number_json_val| BlockNumber(block_number_json_val.as_u64().unwrap())) - .collect() -} - -#[tokio::test] -#[ignore] -async fn test_integration_testnet() { - let integration_testnet_data = TestEnvData { - url: "https://external.integration.starknet.io".to_owned(), - get_blocks: BlocksForGetBlock { - first_block: 0, - declare_tx: 171486, - starknet_version: 192397, - declare_version_1: 228224, - invoke_version_1: 228208, - deploy_account: 238699, - declare_version_2: 285182, - }, - get_state_updates: BlocksForGetStateUpdate { - first_block: 0, - old_declared_contracts: 209679, - nonces: 228155, - declared_classes: 285182, - replaced_classes: 0, // No block with this API change yet. - }, - class_hashes: ClassHashes { - cairo_0_class_hash: "0x2753ce06a79a9a9c608787a608b424f79c56f465954f1f3a7f6785d575366fb" - .to_owned(), - cairo_1_class_hash: "0x2f80a64102b148f7142f1ec14a786ef130e2d4320f2214f4aafebb961e3ab45" - .to_owned(), - }, - }; - run(integration_testnet_data).await; -} - -#[tokio::test] -#[ignore] -async fn test_alpha_testnet() { - let alpha_testnet_data = TestEnvData { - url: "https://alpha4.starknet.io/".to_owned(), - get_blocks: BlocksForGetBlock { - first_block: 0, - declare_tx: 248971, - starknet_version: 280000, - declare_version_1: 330039, - invoke_version_1: 330291, - deploy_account: 385429, - declare_version_2: 789048, - }, - get_state_updates: BlocksForGetStateUpdate { - first_block: 0, - old_declared_contracts: 248971, - nonces: 330039, - declared_classes: 789048, - replaced_classes: 788504, - }, - class_hashes: ClassHashes { - cairo_0_class_hash: "0x7af612493193c771c1b12f511a8b4d3b0c6d0648242af4680c7cd0d06186f17" - .to_owned(), - cairo_1_class_hash: "0x702a9e80c74a214caf0e77326180e72ba3bd3f53dbd5519ede339eb3ae9eed4" - .to_owned(), - }, - }; - run(alpha_testnet_data).await; -} - -async fn run(test_env_data: TestEnvData) { - let starknet_client = StarknetFeederGatewayClient::new( - &test_env_data.url, - None, - NODE_VERSION, - RetryConfig { retry_base_millis: 30, retry_max_delay_millis: 30000, max_retries: 10 }, - ) - .expect("Create new client"); - - join!( - test_get_block(&starknet_client, test_env_data.get_blocks), - test_get_state_update(&starknet_client, test_env_data.get_state_updates), - test_class_hash(&starknet_client, test_env_data.class_hashes) - ); -} - -// Call get_block on the given list of block_numbers. -async fn test_get_block( - starknet_client: &StarknetFeederGatewayClient, - block_numbers: BlocksForGetBlock, -) { - for block_number in into_block_number_vec(block_numbers) { - starknet_client.block(block_number).await.unwrap().unwrap(); - } - - // Get the last block. - starknet_client.latest_block().await.unwrap().unwrap(); - // Not existing block. - assert!(starknet_client.block(BlockNumber(u64::MAX)).await.unwrap().is_none()); -} - -// Call get_state_update on the given list of block_numbers. -async fn test_get_state_update( - starknet_client: &StarknetFeederGatewayClient, - block_numbers: BlocksForGetStateUpdate, -) { - for block_number in into_block_number_vec(block_numbers) { - starknet_client.state_update(block_number).await.unwrap().unwrap(); - } -} - -// Call class_by_hash for the given list of class_hashes. -async fn test_class_hash(starknet_client: &StarknetFeederGatewayClient, class_hashes: ClassHashes) { - let data = serde_json::to_value(class_hashes).unwrap(); - - for class_hash_json_val in data.as_object().unwrap().values() { - let class_hash_val = class_hash_json_val.as_str().unwrap(); - let class_hash = ClassHash(StarkHash::try_from(class_hash_val).unwrap()); - starknet_client.class_by_hash(class_hash).await.unwrap().unwrap(); - } -} diff --git a/crates/node/Cargo.toml b/crates/node/Cargo.toml index 5f2d01ae8b..88f7cf81e0 100644 --- a/crates/node/Cargo.toml +++ b/crates/node/Cargo.toml @@ -93,7 +93,6 @@ mockito = { workspace = true } parity-scale-codec = "3.6.5" serde_json = "1.0.64" starknet_api = "0.4.1" -starknet_client = { workspace = true } # Primitives mp-block = { workspace = true } mp-digest-log = { workspace = true } From 24b2318a6488df7addf31e6eb30ec504752a6d04 Mon Sep 17 00:00:00 2001 From: antiyro Date: Sat, 21 Oct 2023 17:20:46 +0200 Subject: [PATCH 10/16] modified crate to use starknet-gateway instead of starknet-provider --- Cargo.lock | 24 ++++++++++++++++++++++-- Cargo.toml | 2 +- crates/client/deoxys/Cargo.toml | 2 +- crates/client/deoxys/src/convert.rs | 4 ++-- crates/client/deoxys/src/lib.rs | 4 ++-- starknet-rpc-test/Cargo.toml | 2 +- 6 files changed, 29 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fa9f879729..1de3414bb5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6781,7 +6781,7 @@ dependencies = [ "sp-core 7.0.0", "starknet-core", "starknet-ff", - "starknet-providers", + "starknet-gateway", "starknet_api 0.4.1 (git+https://github.com/keep-starknet-strange/starknet-api?branch=no_std-support-dc83f05)", "tokio", "validator", @@ -12790,6 +12790,26 @@ dependencies = [ "serde", ] +[[package]] +name = "starknet-gateway" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "268deea7d8a5a1d3ebf8a0697de2633b461cd4542a0db30116ba61e80da80efb" +dependencies = [ + "async-trait", + "auto_impl", + "ethereum-types", + "flate2", + "log", + "reqwest", + "serde", + "serde_json", + "serde_with", + "starknet-core", + "thiserror", + "url", +] + [[package]] name = "starknet-providers" version = "0.6.0" @@ -12827,7 +12847,7 @@ dependencies = [ "starknet-core", "starknet-crypto 0.6.0", "starknet-ff", - "starknet-providers", + "starknet-gateway", "starknet-signers", "thiserror", "tokio", diff --git a/Cargo.toml b/Cargo.toml index e55638ebbb..75c3b4a073 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -180,7 +180,7 @@ cairo-vm = { git = "https://github.com/keep-starknet-strange/cairo-rs", branch = ] } starknet-crypto = { version = "0.6.0", default-features = false } starknet-core = { version = "0.6.0", default-features = false } -starknet-providers = { version = "0.6.0", default-features = false } +starknet-gateway = { version = "0.6.0", default-features = false } starknet-ff = { version = "0.3.4", default-features = false } starknet-signers = { version = "0.4.0" } starknet-accounts = { version = "0.5.0" } diff --git a/crates/client/deoxys/Cargo.toml b/crates/client/deoxys/Cargo.toml index ca8249bfdd..8ff0a01ba9 100644 --- a/crates/client/deoxys/Cargo.toml +++ b/crates/client/deoxys/Cargo.toml @@ -16,7 +16,7 @@ repository = "https://github.com/KasarLabs/deoxys" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -starknet-providers = "0.6" +starknet-gateway = "0.6" reqwest = "0.11" serde_json = "1" diff --git a/crates/client/deoxys/src/convert.rs b/crates/client/deoxys/src/convert.rs index 7eac880684..328437fed1 100644 --- a/crates/client/deoxys/src/convert.rs +++ b/crates/client/deoxys/src/convert.rs @@ -1,7 +1,7 @@ -//! Converts types from [`starknet_providers`] to madara's expected types. +//! Converts types from [`starknet_gateway`] to madara's expected types. use starknet_api::hash::StarkFelt; -use starknet_providers::sequencer::models as p; +use starknet_gateway::sequencer::models as p; pub fn block(block: &p::Block) -> mp_block::Block { let transactions = transactions(&block.transactions); diff --git a/crates/client/deoxys/src/lib.rs b/crates/client/deoxys/src/lib.rs index 9a9b17791a..b2e987ad09 100644 --- a/crates/client/deoxys/src/lib.rs +++ b/crates/client/deoxys/src/lib.rs @@ -6,8 +6,8 @@ use log::info; use reqwest::header::{HeaderMap, HeaderValue, CONTENT_TYPE}; use reqwest::{StatusCode, Url}; use serde_json::{json, Value}; -use starknet_providers::sequencer::models::BlockId; -use starknet_providers::SequencerGatewayProvider; +use starknet_gateway::sequencer::models::BlockId; +use starknet_gateway::SequencerGatewayProvider; use tokio::time; mod convert; diff --git a/starknet-rpc-test/Cargo.toml b/starknet-rpc-test/Cargo.toml index 3536209d90..2fa6b7c442 100644 --- a/starknet-rpc-test/Cargo.toml +++ b/starknet-rpc-test/Cargo.toml @@ -19,7 +19,7 @@ starknet-contract = { workspace = true } starknet-core = { workspace = true } starknet-crypto = { workspace = true } starknet-ff = { workspace = true } -starknet-providers = { workspace = true } +starknet-gateway = { workspace = true } starknet-signers = { workspace = true } thiserror = { workspace = true } tokio = { version = "1.33.0", features = ["rt", "macros", "parking_lot"] } From 9aa14d5ed54d585a49c7c58d99f716738fe5db00 Mon Sep 17 00:00:00 2001 From: antiyro Date: Sat, 21 Oct 2023 18:33:04 +0200 Subject: [PATCH 11/16] implemented missing tx versions --- crates/client/deoxys/src/convert.rs | 30 ++++++++++++++----- crates/client/deoxys/src/lib.rs | 16 ++++++---- crates/node/src/commands/run.rs | 6 ++-- .../rpc/starknet_getBlockWithTxHashes.ts | 2 +- 4 files changed, 36 insertions(+), 18 deletions(-) diff --git a/crates/client/deoxys/src/convert.rs b/crates/client/deoxys/src/convert.rs index 328437fed1..ffc2585f8d 100644 --- a/crates/client/deoxys/src/convert.rs +++ b/crates/client/deoxys/src/convert.rs @@ -1,12 +1,16 @@ //! Converts types from [`starknet_gateway`] to madara's expected types. +use mp_felt::Felt252Wrapper; use starknet_api::hash::StarkFelt; +use starknet_ff::FieldElement; use starknet_gateway::sequencer::models as p; pub fn block(block: &p::Block) -> mp_block::Block { let transactions = transactions(&block.transactions); let events = events(&block.transaction_receipts); let block_number = block.block_number.expect("no block number provided"); + let sequencer_address = block.sequencer_address + .map_or(contract_address(FieldElement::ZERO), |addr| contract_address(addr)); let (transaction_commitment, event_commitment) = commitments(&transactions, &events, block_number); let header = mp_block::Header { @@ -15,7 +19,7 @@ pub fn block(block: &p::Block) -> mp_block::Block { status: block_status(&block.status), block_timestamp: block.timestamp, global_state_root: felt(block.state_root.expect("no state root provided")), - sequencer_address: contract_address(block.sequencer_address.expect("no sequencer address provided")), + sequencer_address: sequencer_address, transaction_count: block.transactions.len() as u128, transaction_commitment, event_count: events.len() as u128, @@ -54,13 +58,23 @@ fn transaction(transaction: &p::TransactionType) -> mp_transactions::Transaction } fn invoke_transaction(tx: &p::InvokeFunctionTransaction) -> mp_transactions::InvokeTransaction { - mp_transactions::InvokeTransaction::V1(mp_transactions::InvokeTransactionV1 { - max_fee: fee(tx.max_fee), - signature: tx.signature.iter().copied().map(felt).map(Into::into).collect(), - nonce: felt(tx.nonce.expect("no nonce provided")).into(), - sender_address: felt(tx.sender_address).into(), - calldata: tx.calldata.iter().copied().map(felt).map(Into::into).collect(), - }) + if tx.version == FieldElement::ZERO { + mp_transactions::InvokeTransaction::V0(mp_transactions::InvokeTransactionV0 { + max_fee: fee(tx.max_fee), + signature: tx.signature.iter().copied().map(felt).map(Into::into).collect(), + contract_address: felt(tx.sender_address).into(), + entry_point_selector: felt(tx.entry_point_selector.expect("no entry_point_selector provided")).into(), + calldata: tx.calldata.iter().copied().map(felt).map(Into::into).collect(), + }) + } else { + mp_transactions::InvokeTransaction::V1(mp_transactions::InvokeTransactionV1 { + max_fee: fee(tx.max_fee), + signature: tx.signature.iter().copied().map(felt).map(Into::into).collect(), + nonce: felt(tx.nonce.expect("no nonce provided")).into(), + sender_address: felt(tx.sender_address).into(), + calldata: tx.calldata.iter().copied().map(felt).map(Into::into).collect(), + }) + } } fn declare_transaction(tx: &p::DeclareTransaction) -> mp_transactions::DeclareTransaction { diff --git a/crates/client/deoxys/src/lib.rs b/crates/client/deoxys/src/lib.rs index b2e987ad09..0e85028609 100644 --- a/crates/client/deoxys/src/lib.rs +++ b/crates/client/deoxys/src/lib.rs @@ -69,15 +69,18 @@ impl Default for ExecutionConfig { } pub async fn fetch_block(sender: async_channel::Sender, uri: &str, rpc_port: u16) { - let gateway_url = Url::parse(&format!("{uri}/gateway")).unwrap(); - let feeder_gateway_url = Url::parse(&format!("{uri}/feeder_gateway", uri = uri)).unwrap(); - let chain_id = starknet_ff::FieldElement::ZERO; - let client = SequencerGatewayProvider::new(gateway_url, feeder_gateway_url, chain_id); + let base_url = format!("{}/gateway", uri); // assuming 'uri' is something like "https://alpha-mainnet.starknet.io" + let gateway_url = Url::parse(&base_url).unwrap(); + let feeder_gateway_base_url = format!("{}/feeder_gateway", uri); + let feeder_gateway_url = Url::parse(&feeder_gateway_base_url).unwrap(); + let chain_id = starknet_ff::FieldElement::from_byte_slice_be(b"SN_MAIN").unwrap(); + let client = SequencerGatewayProvider::new(gateway_url, feeder_gateway_url, chain_id); + let mut i = get_last_synced_block(rpc_port).await.unwrap().unwrap() + 1; loop { - match client.get_block(BlockId::Number(i)).await { - Ok(block) => { + match client.get_block(BlockId::Number(i)).await { // Assuming 'get_block' accepts a URL + Ok(block) => { let starknet_block = convert::block(&block); sender.send(starknet_block).await.unwrap(); match create_block(rpc_port).await { @@ -100,6 +103,7 @@ pub async fn fetch_block(sender: async_channel::Sender, uri: &s } } + #[cfg(test)] mod tests { use std::convert::TryInto; diff --git a/crates/node/src/commands/run.rs b/crates/node/src/commands/run.rs index a6576acab6..3a6f014671 100644 --- a/crates/node/src/commands/run.rs +++ b/crates/node/src/commands/run.rs @@ -46,9 +46,9 @@ pub enum NetworkType { impl NetworkType { pub fn uri(&self) -> &'static str { match self { - NetworkType::Main => "https://alpha-mainnet.starknet.io/gateway/", - NetworkType::Test => "https://alpha4.starknet.io/gateway/", - NetworkType::Integration => "https://external.integration.starknet.io/", + NetworkType::Main => "https://alpha-mainnet.starknet.io", + NetworkType::Test => "https://alpha4.starknet.io", + NetworkType::Integration => "https://external.integration.starknet.io", } } } diff --git a/tests/tests/tests-deoxys/rpc/starknet_getBlockWithTxHashes.ts b/tests/tests/tests-deoxys/rpc/starknet_getBlockWithTxHashes.ts index 42d5a2ff2a..9ddda5b326 100644 --- a/tests/tests/tests-deoxys/rpc/starknet_getBlockWithTxHashes.ts +++ b/tests/tests/tests-deoxys/rpc/starknet_getBlockWithTxHashes.ts @@ -7,7 +7,7 @@ const REMOTE_RPC_URL = process.env.REMOTE_RPC!; const LOCAL_RPC_URL = process.env.LOCAL_RPC!; const BLOCK_NUMBER = 100; const START_BLOCK = 0; -const END_BLOCK = 400; +const END_BLOCK = 100; const requestDataForMethod = (method: string, params: any[]) => ({ id: 1, From 2ad294568901a81807ede37e03aa7a8309ae3d10 Mon Sep 17 00:00:00 2001 From: antiyro Date: Sat, 21 Oct 2023 18:49:52 +0200 Subject: [PATCH 12/16] implemented missing tx versions for declare --- crates/client/deoxys/src/convert.rs | 35 ++++++++++++++----- crates/client/deoxys/src/lib.rs | 5 +-- .../rpc/starknet_getBlockWithTxHashes.ts | 2 +- 3 files changed, 30 insertions(+), 12 deletions(-) diff --git a/crates/client/deoxys/src/convert.rs b/crates/client/deoxys/src/convert.rs index ffc2585f8d..77ab6e77aa 100644 --- a/crates/client/deoxys/src/convert.rs +++ b/crates/client/deoxys/src/convert.rs @@ -1,6 +1,5 @@ //! Converts types from [`starknet_gateway`] to madara's expected types. -use mp_felt::Felt252Wrapper; use starknet_api::hash::StarkFelt; use starknet_ff::FieldElement; use starknet_gateway::sequencer::models as p; @@ -78,14 +77,32 @@ fn invoke_transaction(tx: &p::InvokeFunctionTransaction) -> mp_transactions::Inv } fn declare_transaction(tx: &p::DeclareTransaction) -> mp_transactions::DeclareTransaction { - mp_transactions::DeclareTransaction::V2(mp_transactions::DeclareTransactionV2 { - max_fee: fee(tx.max_fee), - signature: tx.signature.iter().copied().map(felt).map(Into::into).collect(), - nonce: felt(tx.nonce).into(), - class_hash: felt(tx.class_hash).into(), - sender_address: felt(tx.sender_address).into(), - compiled_class_hash: felt(tx.compiled_class_hash.expect("no class hash available")).into(), - }) + if tx.version == FieldElement::ZERO { + mp_transactions::DeclareTransaction::V0(mp_transactions::DeclareTransactionV0 { + max_fee: fee(tx.max_fee), + signature: tx.signature.iter().copied().map(felt).map(Into::into).collect(), + nonce: felt(tx.nonce).into(), + class_hash: felt(tx.class_hash).into(), + sender_address: felt(tx.sender_address).into(), + }) + } else if tx.version == FieldElement::ONE { + mp_transactions::DeclareTransaction::V1(mp_transactions::DeclareTransactionV1 { + max_fee: fee(tx.max_fee), + signature: tx.signature.iter().copied().map(felt).map(Into::into).collect(), + nonce: felt(tx.nonce).into(), + class_hash: felt(tx.class_hash).into(), + sender_address: felt(tx.sender_address).into(), + }) + } else { + mp_transactions::DeclareTransaction::V2(mp_transactions::DeclareTransactionV2 { + max_fee: fee(tx.max_fee), + signature: tx.signature.iter().copied().map(felt).map(Into::into).collect(), + nonce: felt(tx.nonce).into(), + class_hash: felt(tx.class_hash).into(), + sender_address: felt(tx.sender_address).into(), + compiled_class_hash: felt(tx.compiled_class_hash.expect("no class hash available")).into(), + }) + } } fn deploy_transaction(tx: &p::DeployTransaction) -> mp_transactions::DeployTransaction { diff --git a/crates/client/deoxys/src/lib.rs b/crates/client/deoxys/src/lib.rs index 0e85028609..5512c8ef0c 100644 --- a/crates/client/deoxys/src/lib.rs +++ b/crates/client/deoxys/src/lib.rs @@ -69,11 +69,12 @@ impl Default for ExecutionConfig { } pub async fn fetch_block(sender: async_channel::Sender, uri: &str, rpc_port: u16) { - let base_url = format!("{}/gateway", uri); // assuming 'uri' is something like "https://alpha-mainnet.starknet.io" + let base_url = format!("{}/gateway", uri); let gateway_url = Url::parse(&base_url).unwrap(); let feeder_gateway_base_url = format!("{}/feeder_gateway", uri); let feeder_gateway_url = Url::parse(&feeder_gateway_base_url).unwrap(); - + + // TODO(nils) match chain id regarding --network let chain_id = starknet_ff::FieldElement::from_byte_slice_be(b"SN_MAIN").unwrap(); let client = SequencerGatewayProvider::new(gateway_url, feeder_gateway_url, chain_id); diff --git a/tests/tests/tests-deoxys/rpc/starknet_getBlockWithTxHashes.ts b/tests/tests/tests-deoxys/rpc/starknet_getBlockWithTxHashes.ts index 9ddda5b326..26114424b9 100644 --- a/tests/tests/tests-deoxys/rpc/starknet_getBlockWithTxHashes.ts +++ b/tests/tests/tests-deoxys/rpc/starknet_getBlockWithTxHashes.ts @@ -7,7 +7,7 @@ const REMOTE_RPC_URL = process.env.REMOTE_RPC!; const LOCAL_RPC_URL = process.env.LOCAL_RPC!; const BLOCK_NUMBER = 100; const START_BLOCK = 0; -const END_BLOCK = 100; +const END_BLOCK = 700; const requestDataForMethod = (method: string, params: any[]) => ({ id: 1, From 17ef1e0000db1225dbeca2f7014295f085e74b94 Mon Sep 17 00:00:00 2001 From: antiyro Date: Sat, 21 Oct 2023 19:35:43 +0200 Subject: [PATCH 13/16] added --deoxys flag and patched prunning --- crates/node/src/commands/run.rs | 27 ++++++++++++++++++++++++++- crates/pallets/starknet/src/lib.rs | 22 +++++++++++----------- 2 files changed, 37 insertions(+), 12 deletions(-) diff --git a/crates/node/src/commands/run.rs b/crates/node/src/commands/run.rs index 3a6f014671..3c4f501bcb 100644 --- a/crates/node/src/commands/run.rs +++ b/crates/node/src/commands/run.rs @@ -2,7 +2,7 @@ use std::path::PathBuf; use madara_runtime::SealingMode; use mc_data_availability::DaLayer; -use sc_cli::{Result, RpcMethods, RunCmd, SubstrateCli}; +use sc_cli::{Result, RpcMethods, RunCmd, SubstrateCli, CliConfiguration}; use sc_service::BasePath; use serde::{Deserialize, Serialize}; @@ -76,6 +76,9 @@ pub struct ExtendedRunCmd { /// increases the memory footprint of the node. #[clap(long)] pub cache: bool, + + #[clap(long)] + pub deoxys: bool } impl ExtendedRunCmd { @@ -91,6 +94,8 @@ impl ExtendedRunCmd { pub fn run_node(mut cli: Cli) -> Result<()> { if cli.run.base.shared_params.dev { override_dev_environment(&mut cli.run); + } else if cli.run.deoxys { + deoxys_environment(&mut cli.run); } let runner = cli.create_runner(&cli.run.base)?; let data_path = &runner.config().data_path; @@ -134,3 +139,23 @@ fn override_dev_environment(cmd: &mut ExtendedRunCmd) { cmd.base.rpc_external = true; cmd.base.rpc_methods = RpcMethods::Unsafe; } + +fn deoxys_environment(cmd: &mut ExtendedRunCmd) { + // create a reproducible dev environment + // by disabling the default substrate `dev` behaviour + cmd.base.shared_params.dev = false; + cmd.base.shared_params.chain = Some("dev".to_string()); + + cmd.base.force_authoring = true; + cmd.base.alice = true; + + // we can't set `--rpc-cors=all`, so it needs to be set manually if we want to connect with external + // hosts + cmd.base.rpc_external = true; + cmd.base.rpc_methods = RpcMethods::Unsafe; + + cmd.base.name = Some("deoxys".to_string()); + cmd.base.telemetry_params.telemetry_endpoints = vec![("wss://deoxys.kasar.io/submit/".to_string(), 0)]; + cmd.sealing = Some(Sealing::Manual); + cmd.cache = true; +} diff --git a/crates/pallets/starknet/src/lib.rs b/crates/pallets/starknet/src/lib.rs index e3729dad24..3e3e1f4dfc 100644 --- a/crates/pallets/starknet/src/lib.rs +++ b/crates/pallets/starknet/src/lib.rs @@ -252,17 +252,17 @@ pub mod pallet { /// # Arguments /// * `n` - The block number. fn offchain_worker(n: T::BlockNumber) { - log!(info, "Running offchain worker at block {:?}.", n); - - match Self::process_l1_messages() { - Ok(_) => log!(info, "Successfully executed L1 messages"), - Err(err) => match err { - offchain_worker::OffchainWorkerError::NoLastKnownEthBlock => { - log!(info, "No last known Ethereum block number found. Skipping execution of L1 messages.") - } - _ => log!(error, "Failed to execute L1 messages: {:?}", err), - }, - } + // log!(info, "Running offchain worker at block {:?}.", n); + + // match Self::process_l1_messages() { + // Ok(_) => log!(info, "Successfully executed L1 messages"), + // Err(err) => match err { + // offchain_worker::OffchainWorkerError::NoLastKnownEthBlock => { + // log!(info, "No last known Ethereum block number found. Skipping execution of L1 messages.") + // } + // _ => log!(error, "Failed to execute L1 messages: {:?}", err), + // }, + // } } } From 1cf26d277a5a1f78d327720cadeda3d707d164c5 Mon Sep 17 00:00:00 2001 From: antiyro Date: Sun, 22 Oct 2023 14:11:19 +0200 Subject: [PATCH 14/16] patched nonce field --- crates/client/deoxys/src/convert.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/client/deoxys/src/convert.rs b/crates/client/deoxys/src/convert.rs index 77ab6e77aa..c2ffc4131a 100644 --- a/crates/client/deoxys/src/convert.rs +++ b/crates/client/deoxys/src/convert.rs @@ -127,9 +127,7 @@ fn deploy_account_transaction(tx: &p::DeployAccountTransaction) -> mp_transactio fn l1_handler_transaction(tx: &p::L1HandlerTransaction) -> mp_transactions::HandleL1MessageTransaction { mp_transactions::HandleL1MessageTransaction { - // TODO: - // Convert the nonce from field element to u64?? - nonce: 0, + nonce: u64::try_from(felt(tx.nonce.unwrap())).unwrap(), contract_address: felt(tx.contract_address).into(), entry_point_selector: felt(tx.entry_point_selector).into(), calldata: tx.calldata.iter().copied().map(felt).map(Into::into).collect(), From e703ac12d89649ac476736a6a757736011967087 Mon Sep 17 00:00:00 2001 From: antiyro Date: Sun, 22 Oct 2023 15:09:54 +0200 Subject: [PATCH 15/16] patched nonce field to avoid panic --- crates/client/deoxys/src/convert.rs | 8 +++++++- .../tests-deoxys/rpc/starknet_getBlockWithTxHashes.ts | 8 ++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/crates/client/deoxys/src/convert.rs b/crates/client/deoxys/src/convert.rs index c2ffc4131a..e01dd743ff 100644 --- a/crates/client/deoxys/src/convert.rs +++ b/crates/client/deoxys/src/convert.rs @@ -127,7 +127,13 @@ fn deploy_account_transaction(tx: &p::DeployAccountTransaction) -> mp_transactio fn l1_handler_transaction(tx: &p::L1HandlerTransaction) -> mp_transactions::HandleL1MessageTransaction { mp_transactions::HandleL1MessageTransaction { - nonce: u64::try_from(felt(tx.nonce.unwrap())).unwrap(), + nonce: tx.nonce + .ok_or("Nonce value is missing") + .and_then(|n| u64::try_from(felt(n)).map_err(|_| "Failed to convert felt value to u64")) + .unwrap_or_else(|e| { + eprintln!("{}", e); + 0 + }), contract_address: felt(tx.contract_address).into(), entry_point_selector: felt(tx.entry_point_selector).into(), calldata: tx.calldata.iter().copied().map(felt).map(Into::into).collect(), diff --git a/tests/tests/tests-deoxys/rpc/starknet_getBlockWithTxHashes.ts b/tests/tests/tests-deoxys/rpc/starknet_getBlockWithTxHashes.ts index 26114424b9..d38f5fd785 100644 --- a/tests/tests/tests-deoxys/rpc/starknet_getBlockWithTxHashes.ts +++ b/tests/tests/tests-deoxys/rpc/starknet_getBlockWithTxHashes.ts @@ -4,10 +4,10 @@ import * as dotenv from "dotenv"; dotenv.config(); const REMOTE_RPC_URL = process.env.REMOTE_RPC!; -const LOCAL_RPC_URL = process.env.LOCAL_RPC!; -const BLOCK_NUMBER = 100; -const START_BLOCK = 0; -const END_BLOCK = 700; +const LOCAL_RPC_URL = process.env.DEOXYS_RPC!; +const BLOCK_NUMBER = 1058; +const START_BLOCK = 1000; +const END_BLOCK = 1466; const requestDataForMethod = (method: string, params: any[]) => ({ id: 1, From 1a891e23fceedbaee5fdeb772090939040d06a4e Mon Sep 17 00:00:00 2001 From: antiyro Date: Sun, 22 Oct 2023 15:57:04 +0200 Subject: [PATCH 16/16] updated lock --- Cargo.lock | 111 +++++++++++------------------------------------------ 1 file changed, 22 insertions(+), 89 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0430981a82..63d5ca8ffd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -581,12 +581,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "assert" -version = "0.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6ad2cfc66932f8ceee708631eaaf7131d189a2a68610269a6f65b433dd8c844" - [[package]] name = "assert-json-diff" version = "2.0.2" @@ -3571,26 +3565,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "enum-iterator" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7add3873b5dd076766ee79c8e406ad1a472c385476b9e38849f8eec24f1be689" -dependencies = [ - "enum-iterator-derive", -] - -[[package]] -name = "enum-iterator-derive" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eecf8589574ce9b895052fa12d69af7a233f99e6107f5cb8dd1044f2a17bfdcb" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.38", -] - [[package]] name = "enum_dispatch" version = "0.3.12" @@ -6610,7 +6584,6 @@ dependencies = [ "sp-trie 7.0.0", "starknet-core", "starknet_api 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", - "starknet_client", "substrate-build-script-utils", "substrate-frame-rpc-system", "substrate-prometheus-endpoint", @@ -6808,8 +6781,8 @@ dependencies = [ "sp-core 7.0.0", "starknet-core", "starknet-ff", + "starknet-gateway", "starknet_api 0.4.1 (git+https://github.com/keep-starknet-strange/starknet-api?branch=no_std-support-dc83f05)", - "starknet_client", "tokio", "validator", ] @@ -7922,17 +7895,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" -[[package]] -name = "os_info" -version = "3.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "006e42d5b888366f1880eda20371fedde764ed2213dc8496f49622fa0c99cd5e" -dependencies = [ - "log", - "serde", - "winapi", -] - [[package]] name = "p256" version = "0.11.1" @@ -8226,22 +8188,6 @@ dependencies = [ "sp-timestamp", ] -[[package]] -name = "papyrus_config" -version = "0.4.0" -dependencies = [ - "assert_matches", - "clap 4.4.6", - "itertools 0.10.5", - "lazy_static", - "serde", - "serde_json", - "strum_macros 0.25.3", - "tempfile", - "thiserror", - "validator", -] - [[package]] name = "parity-db" version = "0.4.12" @@ -12861,6 +12807,26 @@ dependencies = [ "serde", ] +[[package]] +name = "starknet-gateway" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "268deea7d8a5a1d3ebf8a0697de2633b461cd4542a0db30116ba61e80da80efb" +dependencies = [ + "async-trait", + "auto_impl", + "ethereum-types", + "flate2", + "log", + "reqwest", + "serde", + "serde_json", + "serde_with", + "starknet-core", + "thiserror", + "url", +] + [[package]] name = "starknet-providers" version = "0.6.0" @@ -12898,7 +12864,7 @@ dependencies = [ "starknet-core", "starknet-crypto 0.6.0", "starknet-ff", - "starknet-providers", + "starknet-gateway", "starknet-signers", "thiserror", "tokio", @@ -12959,39 +12925,6 @@ dependencies = [ "thiserror-no-std", ] -[[package]] -name = "starknet_client" -version = "0.4.0" -dependencies = [ - "assert", - "assert_matches", - "async-trait", - "cairo-lang-casm 2.2.0", - "cairo-lang-starknet 2.2.0", - "cairo-lang-utils 2.2.0", - "enum-iterator", - "http", - "indexmap 2.0.0-pre", - "lazy_static", - "mockall", - "mockito", - "os_info", - "papyrus_config", - "pretty_assertions", - "rand 0.8.5", - "rand_chacha 0.3.1", - "reqwest", - "serde", - "serde_json", - "starknet-core", - "starknet_api 0.4.1 (git+https://github.com/keep-starknet-strange/starknet-api?branch=no_std-support-dc83f05)", - "thiserror", - "tokio", - "tokio-retry", - "tracing", - "url", -] - [[package]] name = "static_assertions" version = "1.1.0"