Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(katana): fix feeder gateway types #2760

Merged
merged 2 commits into from
Dec 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 45 additions & 13 deletions crates/katana/feeder-gateway/src/client.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
use katana_primitives::block::{BlockIdOrTag, BlockTag};
use katana_primitives::class::CasmContractClass;
use katana_primitives::Felt;
use reqwest::header::{HeaderMap, HeaderValue};
use reqwest::{Client, StatusCode};
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use url::Url;

use crate::types::{ContractClass, StateUpdate, StateUpdateWithBlock};
use crate::types::{Block, ContractClass, StateUpdate, StateUpdateWithBlock};

/// HTTP request header for the feeder gateway API key. This allow bypassing the rate limiting.
const X_THROTTLING_BYPASS: &str = "X-Throttling-Bypass";

#[derive(Debug, thiserror::Error)]
pub enum Error {
Expand All @@ -16,15 +20,22 @@
#[error(transparent)]
Sequencer(SequencerError),

#[error("Request rate limited")]
#[error("failed to parse header value '{value}'")]
InvalidHeaderValue { value: String },

Comment on lines +23 to +25
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Avoid leaking API keys in error messages

Ohayo sensei, including the API key in error messages may expose sensitive information. Please modify the error handling to prevent leaking the API key value.

Apply this diff to address the issue:

-    #[error("failed to parse header value '{value}'")]
-    InvalidHeaderValue { value: String },
+    #[error("failed to parse API key header value")]
+    InvalidHeaderValue,
-            .map_err(|_| Error::InvalidHeaderValue { value: key.to_string() })?;
+            .map_err(|_| Error::InvalidHeaderValue)?;

Also applies to: 152-155

#[error("request rate limited")]
RateLimited,
}

/// Client for interacting with the Starknet's feeder gateway.
#[derive(Debug, Clone)]
pub struct SequencerGateway {
/// The feeder gateway base URL.
base_url: Url,
client: Client,
/// The HTTP client used to send the requests.
http_client: Client,
/// The API key used to bypass the rate limiting of the feeder gateway.
api_key: Option<String>,
}

impl SequencerGateway {
Expand All @@ -44,8 +55,19 @@

/// Creates a new gateway client at the given base URL.
pub fn new(base_url: Url) -> Self {
let api_key = None;
let client = Client::new();
Self { client, base_url }
Self { http_client: client, base_url, api_key }
}

/// Sets the API key.
pub fn with_api_key(mut self, api_key: String) -> Self {
self.api_key = Some(api_key);
self
}

Check warning on line 67 in crates/katana/feeder-gateway/src/client.rs

View check run for this annotation

Codecov / codecov/patch

crates/katana/feeder-gateway/src/client.rs#L64-L67

Added lines #L64 - L67 were not covered by tests

pub async fn get_block(&self, block_id: BlockIdOrTag) -> Result<Block, Error> {
self.feeder_gateway("get_block").with_block_id(block_id).send().await

Check warning on line 70 in crates/katana/feeder-gateway/src/client.rs

View check run for this annotation

Codecov / codecov/patch

crates/katana/feeder-gateway/src/client.rs#L69-L70

Added lines #L69 - L70 were not covered by tests
}

pub async fn get_state_update(&self, block_id: BlockIdOrTag) -> Result<StateUpdate, Error> {
Expand Down Expand Up @@ -90,7 +112,7 @@
fn feeder_gateway(&self, method: &str) -> RequestBuilder<'_> {
let mut url = self.base_url.clone();
url.path_segments_mut().expect("invalid base url").extend(["feeder_gateway", method]);
RequestBuilder { client: &self.client, url }
RequestBuilder { gateway_client: self, url }
}
}

Expand All @@ -103,7 +125,7 @@

#[derive(Debug, Clone)]
struct RequestBuilder<'a> {
client: &'a Client,
gateway_client: &'a SequencerGateway,
url: Url,
}

Expand All @@ -124,7 +146,17 @@
}

async fn send<T: DeserializeOwned>(self) -> Result<T, Error> {
let response = self.client.get(self.url).send().await?;
let mut headers = HeaderMap::new();

Check warning on line 149 in crates/katana/feeder-gateway/src/client.rs

View check run for this annotation

Codecov / codecov/patch

crates/katana/feeder-gateway/src/client.rs#L149

Added line #L149 was not covered by tests

if let Some(key) = self.gateway_client.api_key.as_ref() {
let value = HeaderValue::from_str(key)
.map_err(|_| Error::InvalidHeaderValue { value: key.to_string() })?;
headers.insert(X_THROTTLING_BYPASS, value);
}

Check warning on line 155 in crates/katana/feeder-gateway/src/client.rs

View check run for this annotation

Codecov / codecov/patch

crates/katana/feeder-gateway/src/client.rs#L151-L155

Added lines #L151 - L155 were not covered by tests

let response =
self.gateway_client.http_client.get(self.url).headers(headers).send().await?;

Check warning on line 158 in crates/katana/feeder-gateway/src/client.rs

View check run for this annotation

Codecov / codecov/patch

crates/katana/feeder-gateway/src/client.rs#L157-L158

Added lines #L157 - L158 were not covered by tests

if response.status() == StatusCode::TOO_MANY_REQUESTS {
Err(Error::RateLimited)
} else {
Expand Down Expand Up @@ -186,9 +218,9 @@

#[test]
fn request_block_id() {
let client = Client::new();
let base_url = Url::parse("https://example.com/").unwrap();
let req = RequestBuilder { client: &client, url: base_url };
let client = SequencerGateway::new(base_url);
let req = client.feeder_gateway("test");

// Test pending block
let pending_url = req.clone().with_block_id(BlockIdOrTag::Tag(BlockTag::Pending)).url;
Expand All @@ -210,9 +242,9 @@

#[test]
fn multiple_query_params() {
let client = Client::new();
let base_url = Url::parse("https://example.com/").unwrap();
let req = RequestBuilder { client: &client, url: base_url };
let client = SequencerGateway::new(base_url);
let req = client.feeder_gateway("test");

let url = req
.add_query_param("param1", "value1")
Expand All @@ -229,9 +261,9 @@
#[test]
#[ignore]
fn request_block_id_overwrite() {
let client = Client::new();
let base_url = Url::parse("https://example.com/").unwrap();
let req = RequestBuilder { client: &client, url: base_url };
let client = SequencerGateway::new(base_url);
let req = client.feeder_gateway("test");

Check warning on line 266 in crates/katana/feeder-gateway/src/client.rs

View check run for this annotation

Codecov / codecov/patch

crates/katana/feeder-gateway/src/client.rs#L265-L266

Added lines #L265 - L266 were not covered by tests

let url = req
.clone()
Expand Down
1 change: 1 addition & 0 deletions crates/katana/feeder-gateway/src/types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use starknet::core::types::ResourcePrice;
use starknet::providers::sequencer::models::BlockStatus;

mod receipt;
mod serde_utils;
mod transaction;

pub use receipt::*;
Expand Down
106 changes: 106 additions & 0 deletions crates/katana/feeder-gateway/src/types/serde_utils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
use serde::de::Visitor;
use serde::Deserialize;

pub fn deserialize_u64<'de, D>(deserializer: D) -> Result<u64, D::Error>
where
D: serde::Deserializer<'de>,
{
struct U64HexVisitor;

impl<'de> Visitor<'de> for U64HexVisitor {
type Value = u64;

fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(formatter, "0x-prefix hex string or decimal number")
}

fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
if let Some(hex) = v.strip_prefix("0x") {
u64::from_str_radix(hex, 16).map_err(serde::de::Error::custom)
} else {
v.parse::<u64>().map_err(serde::de::Error::custom)
}
}
}

deserializer.deserialize_any(U64HexVisitor)
}

Check warning on line 30 in crates/katana/feeder-gateway/src/types/serde_utils.rs

View check run for this annotation

Codecov / codecov/patch

crates/katana/feeder-gateway/src/types/serde_utils.rs#L4-L30

Added lines #L4 - L30 were not covered by tests

pub fn deserialize_u128<'de, D>(deserializer: D) -> Result<u128, D::Error>
where
D: serde::Deserializer<'de>,
{
struct U128HexVisitor;

impl<'de> Visitor<'de> for U128HexVisitor {
type Value = u128;

fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(formatter, "0x-prefix hex string or decimal number")
}

fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
if let Some(hex) = v.strip_prefix("0x") {
u128::from_str_radix(hex, 16).map_err(serde::de::Error::custom)
} else {
v.parse::<u128>().map_err(serde::de::Error::custom)
}
}
}

deserializer.deserialize_any(U128HexVisitor)
}

Check warning on line 58 in crates/katana/feeder-gateway/src/types/serde_utils.rs

View check run for this annotation

Codecov / codecov/patch

crates/katana/feeder-gateway/src/types/serde_utils.rs#L32-L58

Added lines #L32 - L58 were not covered by tests

pub fn deserialize_optional_u64<'de, D>(deserializer: D) -> Result<Option<u64>, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(untagged)]
enum StringOrNum {
String(String),
Number(u64),
}

match Option::<StringOrNum>::deserialize(deserializer)? {
None => Ok(None),
Some(StringOrNum::Number(n)) => Ok(Some(n)),
Some(StringOrNum::String(s)) => {
if let Some(hex) = s.strip_prefix("0x") {
u64::from_str_radix(hex, 16).map(Some).map_err(serde::de::Error::custom)

Check warning on line 76 in crates/katana/feeder-gateway/src/types/serde_utils.rs

View check run for this annotation

Codecov / codecov/patch

crates/katana/feeder-gateway/src/types/serde_utils.rs#L60-L76

Added lines #L60 - L76 were not covered by tests
} else {
s.parse().map(Some).map_err(serde::de::Error::custom)

Check warning on line 78 in crates/katana/feeder-gateway/src/types/serde_utils.rs

View check run for this annotation

Codecov / codecov/patch

crates/katana/feeder-gateway/src/types/serde_utils.rs#L78

Added line #L78 was not covered by tests
}
}
}
}

Check warning on line 82 in crates/katana/feeder-gateway/src/types/serde_utils.rs

View check run for this annotation

Codecov / codecov/patch

crates/katana/feeder-gateway/src/types/serde_utils.rs#L82

Added line #L82 was not covered by tests

pub fn deserialize_optional_u128<'de, D>(deserializer: D) -> Result<Option<u128>, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(untagged)]
enum StringOrNum {
String(String),
Number(u128),
}

match Option::<StringOrNum>::deserialize(deserializer)? {
None => Ok(None),
Some(StringOrNum::Number(n)) => Ok(Some(n)),
Some(StringOrNum::String(s)) => {
if let Some(hex) = s.strip_prefix("0x") {
u128::from_str_radix(hex, 16).map(Some).map_err(serde::de::Error::custom)

Check warning on line 100 in crates/katana/feeder-gateway/src/types/serde_utils.rs

View check run for this annotation

Codecov / codecov/patch

crates/katana/feeder-gateway/src/types/serde_utils.rs#L84-L100

Added lines #L84 - L100 were not covered by tests
} else {
s.parse().map(Some).map_err(serde::de::Error::custom)

Check warning on line 102 in crates/katana/feeder-gateway/src/types/serde_utils.rs

View check run for this annotation

Codecov / codecov/patch

crates/katana/feeder-gateway/src/types/serde_utils.rs#L102

Added line #L102 was not covered by tests
}
}
}
}

Check warning on line 106 in crates/katana/feeder-gateway/src/types/serde_utils.rs

View check run for this annotation

Codecov / codecov/patch

crates/katana/feeder-gateway/src/types/serde_utils.rs#L106

Added line #L106 was not covered by tests
Loading
Loading