Skip to content

Commit

Permalink
chore(starknet_http_server): parse region using header map
Browse files Browse the repository at this point in the history
  • Loading branch information
ArniStarkware committed Jan 15, 2025
1 parent e940132 commit a1a2a7f
Show file tree
Hide file tree
Showing 6 changed files with 128 additions and 24 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion crates/starknet_http_server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ starknet_gateway_types.workspace = true
starknet_infra_utils.workspace = true
starknet_sequencer_infra.workspace = true
thiserror.workspace = true
tokio = { workspace = true, features = ["rt"] }
tracing.workspace = true
validator.workspace = true

Expand All @@ -33,6 +34,7 @@ blockifier = { workspace = true, features = ["testing"] }
mempool_test_utils.workspace = true
metrics-exporter-prometheus.workspace = true
serde_json.workspace = true
starknet-types-core.workspace = true
starknet_gateway_types = { workspace = true, features = ["testing"] }
starknet_http_server = { workspace = true, features = ["testing"] }
tokio = { workspace = true, features = ["rt"] }
tracing-test.workspace = true
16 changes: 15 additions & 1 deletion crates/starknet_http_server/src/http_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use std::clone::Clone;
use std::net::SocketAddr;

use axum::extract::State;
use axum::http::HeaderMap;
use axum::routing::post;
use axum::{async_trait, Json, Router};
use starknet_api::rpc_transaction::RpcTransaction;
Expand All @@ -23,6 +24,8 @@ pub mod http_server_test;

pub type HttpServerResult<T> = Result<T, HttpServerError>;

const CLIENT_REGION_HEADER: &str = "X-Client-Region";

pub struct HttpServer {
pub config: HttpServerConfig,
app_state: AppState,
Expand Down Expand Up @@ -61,18 +64,29 @@ impl HttpServer {
#[instrument(skip(app_state))]
async fn add_tx(
State(app_state): State<AppState>,
headers: HeaderMap,
Json(tx): Json<RpcTransaction>,
) -> HttpServerResult<Json<TransactionHash>> {
record_added_transaction();
let region =
headers.get(CLIENT_REGION_HEADER).and_then(|region| region.to_str().ok()).unwrap_or("N/A");
// Consider adding the region to the GatewayInput.
let gateway_input: GatewayInput = GatewayInput { rpc_tx: tx, message_metadata: None };
let add_tx_result = app_state.gateway_client.add_tx(gateway_input).await.map_err(|e| {
debug!("Error while adding transaction: {}", e);
HttpServerError::from(e)
});
record_added_transaction_status(add_tx_result.is_ok());
record_added_transactions(&add_tx_result, region);
add_tx_result_as_json(add_tx_result)
}

fn record_added_transactions(add_tx_result: &HttpServerResult<TransactionHash>, region: &str) {
if let Ok(tx_hash) = add_tx_result {
info!("Recorded transaction with hash: {} from region: {}", tx_hash, region);
}
record_added_transaction_status(add_tx_result.is_ok());
}

pub(crate) fn add_tx_result_as_json(
result: HttpServerResult<TransactionHash>,
) -> HttpServerResult<Json<TransactionHash>> {
Expand Down
62 changes: 61 additions & 1 deletion crates/starknet_http_server/src/http_server_test.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
use axum::body::{Bytes, HttpBody};
use axum::http::StatusCode;
use axum::response::{IntoResponse, Response};
use blockifier::test_utils::CairoVersion;
use mempool_test_utils::starknet_api_test_utils::invoke_tx;
use starknet_api::transaction::TransactionHash;
use starknet_gateway_types::communication::{GatewayClientError, MockGatewayClient};
use starknet_sequencer_infra::component_client::ClientError;
use starknet_types_core::felt::Felt;
use tracing_test::traced_test;

use crate::http_server::add_tx_result_as_json;
use crate::http_server::{add_tx_result_as_json, CLIENT_REGION_HEADER};
use crate::test_utils::http_client_server_setup;

#[tokio::test]
async fn test_tx_hash_json_conversion() {
Expand All @@ -20,3 +27,56 @@ async fn test_tx_hash_json_conversion() {
async fn to_bytes(res: Response) -> Bytes {
res.into_body().collect().await.unwrap().to_bytes()
}

#[traced_test]
#[tokio::test]
/// Test that when an "add_tx" HTTP request is sent to the server, the region of the http request is
/// recorded to the info log.
async fn record_region_test() {
let mut mock_gateway_client = MockGatewayClient::new();
// Set the successful response.
let tx_hash_1 = TransactionHash(Felt::ONE);
let tx_hash_2 = TransactionHash(Felt::TWO);
mock_gateway_client.expect_add_tx().times(1).return_const(Ok(tx_hash_1));
mock_gateway_client.expect_add_tx().times(1).return_const(Ok(tx_hash_2));

let ip = "127.0.0.2".parse().unwrap();
let add_tx_http_client = http_client_server_setup(mock_gateway_client, ip).await;

// Send a transaction to the server, without a region.
let rpc_tx = invoke_tx(CairoVersion::default());
add_tx_http_client.add_tx(rpc_tx).await;
assert!(logs_contain(
format!("Recorded transaction with hash: {} from region: {}", tx_hash_1, "N/A").as_str()
));

// Send transaction to the server, with a region.
let rpc_tx = invoke_tx(CairoVersion::default());
let region = "test";
add_tx_http_client.add_tx_with_headers(rpc_tx, [(CLIENT_REGION_HEADER, region)]).await;
assert!(logs_contain(
format!("Recorded transaction with hash: {} from region: {}", tx_hash_2, region).as_str()
));
}

#[traced_test]
#[tokio::test]
/// Test that when an "add_tx" HTTP request is sent to the server, and it fails in the Gateway, no
/// record of the region is logged.
async fn record_region_negative_flow_test() {
let mut mock_gateway_client = MockGatewayClient::new();
// Set the failed response.
mock_gateway_client.expect_add_tx().times(1).return_const(Err(
GatewayClientError::ClientError(ClientError::UnexpectedResponse(
"mock response".to_string(),
)),
));

let ip = "127.0.0.3".parse().unwrap();
let add_tx_http_client = http_client_server_setup(mock_gateway_client, ip).await;

// Send a transaction to the server.
let rpc_tx = invoke_tx(CairoVersion::default());
add_tx_http_client.add_tx(rpc_tx).await;
assert!(!logs_contain("Recorded transaction with hash: "));
}
22 changes: 3 additions & 19 deletions crates/starknet_http_server/src/metrics_test.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,17 @@
use std::net::SocketAddr;
use std::sync::Arc;

use blockifier::test_utils::CairoVersion;
use mempool_test_utils::starknet_api_test_utils::invoke_tx;
use metrics_exporter_prometheus::PrometheusBuilder;
use starknet_api::transaction::TransactionHash;
use starknet_gateway_types::communication::{GatewayClientError, MockGatewayClient};
use starknet_infra_utils::metrics::parse_numeric_metric;
use starknet_sequencer_infra::component_client::ClientError;
use tokio::task;

use crate::config::HttpServerConfig;
use crate::http_server::HttpServer;
use crate::metrics::{
ADDED_TRANSACTIONS_FAILURE,
ADDED_TRANSACTIONS_SUCCESS,
ADDED_TRANSACTIONS_TOTAL,
};
use crate::test_utils::HttpTestClient;
use crate::test_utils::http_client_server_setup;

#[tokio::test]
async fn get_metrics_test() {
Expand All @@ -42,19 +36,9 @@ async fn get_metrics_test() {
let prometheus_handle = PrometheusBuilder::new()
.install_recorder()
.expect("should be able to build the recorder and install it globally");
let ip = "127.0.0.1".parse().unwrap();

// TODO(Tsabary): replace the const port with something that is not hardcoded.
// Create and run the server.
let http_server_config = HttpServerConfig { ip: "127.0.0.1".parse().unwrap(), port: 15123 };
let mut http_server =
HttpServer::new(http_server_config.clone(), Arc::new(mock_gateway_client));
tokio::spawn(async move { http_server.run().await });

let HttpServerConfig { ip, port } = http_server_config;
let add_tx_http_client = HttpTestClient::new(SocketAddr::from((ip, port)));

// Ensure the server starts running.
task::yield_now().await;
let add_tx_http_client = http_client_server_setup(mock_gateway_client, ip).await;

// Send transactions to the server.
for _ in std::iter::repeat(()).take(SUCCESS_TXS_TO_SEND + FAILURE_TXS_TO_SEND) {
Expand Down
46 changes: 44 additions & 2 deletions crates/starknet_http_server/src/test_utils.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
use std::net::SocketAddr;
#[cfg(test)]
use std::sync::Arc;

use axum::body::Body;
use reqwest::{Client, Response};
use starknet_api::rpc_transaction::RpcTransaction;
use starknet_api::test_utils::rpc_tx_to_json;
use starknet_api::transaction::TransactionHash;
#[cfg(test)]
use starknet_gateway_types::communication::MockGatewayClient;
use starknet_gateway_types::errors::GatewaySpecError;

use crate::config::HttpServerConfig;
#[cfg(test)]
use crate::http_server::HttpServer;

/// A test utility client for interacting with an http server.
pub struct HttpTestClient {
Expand Down Expand Up @@ -36,9 +42,23 @@ impl HttpTestClient {
// Prefer using assert_add_tx_success or other higher level methods of this client, to ensure
// tests are boilerplate and implementation-detail free.
pub async fn add_tx(&self, rpc_tx: RpcTransaction) -> Response {
self.add_tx_with_headers(rpc_tx, []).await
}

pub async fn add_tx_with_headers<I>(
&self,
rpc_tx: RpcTransaction,
header_members: I,
) -> Response
where
I: IntoIterator<Item = (&'static str, &'static str)>,
{
let tx_json = rpc_tx_to_json(&rpc_tx);
self.client
.post(format!("http://{}/add_tx", self.socket))
let mut request = self.client.post(format!("http://{}/add_tx", self.socket));
for (key, value) in header_members {
request = request.header(key, value);
}
request
.header("content-type", "application/json")
.body(Body::from(tx_json))
.send()
Expand All @@ -50,3 +70,25 @@ impl HttpTestClient {
pub fn create_http_server_config(socket: SocketAddr) -> HttpServerConfig {
HttpServerConfig { ip: socket.ip(), port: socket.port() }
}

#[cfg(test)]
/// Creates an HTTP server and an HttpTestClient that can interact with it.
pub async fn http_client_server_setup(
mock_gateway_client: MockGatewayClient,
ip: std::net::IpAddr,
) -> HttpTestClient {
// TODO(Tsabary): replace the const port with something that is not hardcoded.
// Create and run the server.
let http_server_config = HttpServerConfig { ip, port: 15123 };
let mut http_server =
HttpServer::new(http_server_config.clone(), Arc::new(mock_gateway_client));
tokio::spawn(async move { http_server.run().await });

let HttpServerConfig { ip, port } = http_server_config;
let add_tx_http_client = HttpTestClient::new(SocketAddr::from((ip, port)));

// Ensure the server starts running.
tokio::task::yield_now().await;

add_tx_http_client
}

0 comments on commit a1a2a7f

Please sign in to comment.