diff --git a/Cargo.lock b/Cargo.lock index 05aaebf..caecff8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -678,6 +678,61 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "axum" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" +dependencies = [ + "async-trait", + "axum-core", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-core" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "sync_wrapper", + "tower-layer", + "tower-service", + "tracing", +] + [[package]] name = "backtrace" version = "0.3.75" @@ -1740,6 +1795,7 @@ dependencies = [ "http", "http-body", "httparse", + "httpdate", "itoa", "pin-project-lite", "smallvec", @@ -2139,6 +2195,12 @@ dependencies = [ "regex-automata", ] +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" + [[package]] name = "memchr" version = "2.7.5" @@ -2171,6 +2233,19 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "mock-collector" +version = "0.1.0" +dependencies = [ + "axum", + "gas-agent", + "serde", + "serde_json", + "tokio", + "tracing", + "tracing-subscriber", +] + [[package]] name = "nanorand" version = "0.7.0" @@ -3456,6 +3531,16 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_path_to_error" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59fab13f937fa393d08645bf3a84bdfe86e296747b506ada67bb15f10f218b2a" +dependencies = [ + "itoa", + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -3988,6 +4073,7 @@ dependencies = [ "tokio", "tower-layer", "tower-service", + "tracing", ] [[package]] @@ -4026,6 +4112,7 @@ version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ + "log", "pin-project-lite", "tracing-attributes", "tracing-core", diff --git a/Cargo.toml b/Cargo.toml index f8dc0e2..986410b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,9 @@ +[workspace] +members = [ + ".", + "crates/mock-collector" +] + [package] name = "gas-agent" version = "0.1.1" diff --git a/crates/mock-collector/Cargo.toml b/crates/mock-collector/Cargo.toml new file mode 100644 index 0000000..6b7ab07 --- /dev/null +++ b/crates/mock-collector/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "mock-collector" +version = "0.1.0" +edition = "2021" + +[dependencies] +gas-agent = { path = "../.." } +tokio = { version = "1.44", features = ["full"] } +axum = "0.7" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +tracing = "0.1" +tracing-subscriber = { version = "0.3", features = ["env-filter"] } diff --git a/crates/mock-collector/src/main.rs b/crates/mock-collector/src/main.rs new file mode 100644 index 0000000..7145999 --- /dev/null +++ b/crates/mock-collector/src/main.rs @@ -0,0 +1,90 @@ +use axum::{ + extract::rejection::JsonRejection, http::StatusCode, response::IntoResponse, routing::post, + Router, +}; +use gas_agent::AgentPayload; +use serde::{Deserialize, Serialize}; +use std::net::SocketAddr; +use tracing::{info, warn}; + +/// The payload structure sent by the gas agent +#[derive(Debug, Deserialize, Serialize)] +struct AgentSubmission { + payload: AgentPayload, + signature: String, + network_signature: String, +} + +async fn handle_agent_publish( + payload: Result, JsonRejection>, +) -> impl IntoResponse { + match payload { + Ok(axum::Json(submission)) => { + info!("═══════════════════════════════════════════════════════════════"); + info!("RECEIVED AGENT SUBMISSION"); + info!("═══════════════════════════════════════════════════════════════"); + info!("System: {:?}", submission.payload.system); + info!("Network: {:?}", submission.payload.network); + info!("From Block: {}", submission.payload.from_block); + info!("Settlement: {:?}", submission.payload.settlement); + info!( + "Price: {} {:?}", + submission.payload.price, submission.payload.unit + ); + info!("Timestamp: {}", submission.payload.timestamp); + info!("Schema Version: {}", submission.payload.schema_version); + info!("───────────────────────────────────────────────────────────────"); + info!( + "Signature: {}...", + &submission.signature[..20.min(submission.signature.len())] + ); + info!( + "Network Signature: {}...", + &submission.network_signature[..20.min(submission.network_signature.len())] + ); + info!("═══════════════════════════════════════════════════════════════"); + (StatusCode::OK, "OK".to_string()) + } + Err(rejection) => { + warn!("═══════════════════════════════════════════════════════════════"); + warn!("INVALID PAYLOAD RECEIVED"); + warn!("═══════════════════════════════════════════════════════════════"); + warn!("Error: {}", rejection); + if let JsonRejection::JsonDataError(ref err) = rejection { + warn!("Details: {}", err.body_text()); + } else if let JsonRejection::JsonSyntaxError(ref err) = rejection { + warn!("Details: {}", err.body_text()); + } + warn!("═══════════════════════════════════════════════════════════════"); + ( + StatusCode::BAD_REQUEST, + format!("Invalid payload: {}", rejection), + ) + } + } +} + +#[tokio::main] +async fn main() { + // Initialize tracing + tracing_subscriber::fmt() + .with_env_filter( + tracing_subscriber::EnvFilter::from_default_env() + .add_directive(tracing::Level::INFO.into()), + ) + .init(); + + let app = Router::new() + .route("/v0/agents", post(handle_agent_publish)) + .fallback(|req: axum::http::Request| async move { + warn!("Unhandled request: {} {}", req.method(), req.uri()); + (StatusCode::NOT_FOUND, "Not Found") + }); + + let addr = SocketAddr::from(([0, 0, 0, 0], 3000)); + info!("Mock Collector listening on http://{}", addr); + info!("Expecting POST requests at http://{}/v0/agents", addr); + + let listener = tokio::net::TcpListener::bind(addr).await.unwrap(); + axum::serve(listener, app).await.unwrap(); +} diff --git a/src/chain/encode.rs b/src/chain/encode.rs index 42f2ba4..e320d8c 100644 --- a/src/chain/encode.rs +++ b/src/chain/encode.rs @@ -83,15 +83,12 @@ mod tests { use alloy::primitives::aliases::{U240, U48}; use super::*; - use crate::logs::init_logs; - - // This ensures metrics are initialized exactly once + // This ensures tracing is initialized exactly once for tests static INIT: Once = Once::new(); fn setup() { INIT.call_once(|| { - // Initialize metrics for testing - init_logs(); + let _ = tracing_subscriber::fmt().with_test_writer().try_init(); }); } diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..7bdbcce --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,6 @@ +//! Gas Agent library - shared types for the gas agent ecosystem. + +mod chain; +mod types; + +pub use types::AgentPayload; diff --git a/src/types.rs b/src/types.rs index 890f430..04aa37d 100644 --- a/src/types.rs +++ b/src/types.rs @@ -12,6 +12,7 @@ use serde::{Deserialize, Serialize}; use std::{fmt, str::FromStr}; use strum_macros::{Display, EnumString}; +#[allow(dead_code)] // Used by binary #[derive(Debug, Clone, EnumString, Display, Deserialize, Serialize)] #[strum(serialize_all = "snake_case")] #[serde(rename_all = "snake_case")] @@ -25,6 +26,7 @@ pub enum ModelKind { PendingFloor, } +#[allow(dead_code)] // Used by binary #[derive(Debug, Clone, Deserialize, Serialize)] #[serde(rename_all = "snake_case")] #[serde(from = "String")] @@ -265,6 +267,7 @@ impl SystemNetworkKey { } } + #[allow(dead_code)] // Used by agent binary only pub fn to_block_time(&self) -> u64 { match self { SystemNetworkKey {