From d657c7e00db163492cf8eb00690122a40273918e Mon Sep 17 00:00:00 2001 From: Tomas Date: Fri, 28 Feb 2025 17:06:13 -0300 Subject: [PATCH 1/2] feat: test faucet website (#702) * feat: add faucet website test * review: refactor test setup and comment * review: update js script to check failed requests * review: remove unreadable literals and comment genesis block * review: improve socket addr error handling * review: document test * review: remove sleep and move code * fix: add stdout to wait for chromedriver * chore: format * review: generate faucet account in the test * review: remove unnecessary PathBuf --- Cargo.lock | 252 ++++++++++++++++++++++++++++++--- bin/faucet/Cargo.toml | 6 + bin/faucet/src/main.rs | 135 +++++++++++++++++- bin/faucet/src/stub_rpc_api.rs | 166 ++++++++++++++++++++++ 4 files changed, 535 insertions(+), 24 deletions(-) create mode 100644 bin/faucet/src/stub_rpc_api.rs diff --git a/Cargo.lock b/Cargo.lock index fe8458343..07858d22f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -200,7 +200,7 @@ dependencies = [ "axum-core 0.4.5", "bytes", "futures-util", - "http", + "http 1.2.0", "http-body", "http-body-util", "itoa", @@ -227,7 +227,7 @@ dependencies = [ "bytes", "form_urlencoded", "futures-util", - "http", + "http 1.2.0", "http-body", "http-body-util", "hyper", @@ -260,7 +260,7 @@ dependencies = [ "async-trait", "bytes", "futures-util", - "http", + "http 1.2.0", "http-body", "http-body-util", "mime", @@ -279,7 +279,7 @@ checksum = "df1362f362fd16024ae199c1970ce98f9661bf5ef94b9808fee734bc3698b733" dependencies = [ "bytes", "futures-util", - "http", + "http 1.2.0", "http-body", "http-body-util", "mime", @@ -315,6 +315,12 @@ dependencies = [ "backtrace", ] +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + [[package]] name = "base64" version = "0.22.1" @@ -574,6 +580,37 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" +[[package]] +name = "cookie" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" +dependencies = [ + "time", + "version_check", +] + +[[package]] +name = "cookie" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" +dependencies = [ + "percent-encoding", + "time", + "version_check", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation" version = "0.10.0" @@ -852,6 +889,31 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" +[[package]] +name = "fantoccini" +version = "0.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7722aeee9c2be6fa131166990295089d73d973012b758a2208b9ba51af5dd024" +dependencies = [ + "base64 0.22.1", + "cookie 0.18.1", + "futures-core", + "futures-util", + "http 1.2.0", + "http-body-util", + "hyper", + "hyper-tls", + "hyper-util", + "mime", + "openssl", + "serde", + "serde_json", + "time", + "tokio", + "url", + "webdriver", +] + [[package]] name = "fastrand" version = "2.3.0" @@ -892,6 +954,21 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "form_urlencoded" version = "1.2.1" @@ -1061,7 +1138,7 @@ dependencies = [ "fnv", "futures-core", "futures-sink", - "http", + "http 1.2.0", "indexmap 2.7.1", "slab", "tokio", @@ -1117,6 +1194,17 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + [[package]] name = "http" version = "1.2.0" @@ -1135,7 +1223,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http", + "http 1.2.0", ] [[package]] @@ -1146,7 +1234,7 @@ checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" dependencies = [ "bytes", "futures-util", - "http", + "http 1.2.0", "http-body", "pin-project-lite", ] @@ -1173,7 +1261,7 @@ dependencies = [ "futures-channel", "futures-util", "h2", - "http", + "http 1.2.0", "http-body", "httparse", "httpdate", @@ -1197,6 +1285,22 @@ dependencies = [ "tower-service", ] +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + [[package]] name = "hyper-util" version = "0.1.10" @@ -1206,7 +1310,7 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "http", + "http 1.2.0", "http-body", "hyper", "pin-project-lite", @@ -1761,7 +1865,8 @@ dependencies = [ "anyhow", "axum 0.8.1", "clap", - "http", + "fantoccini", + "http 1.2.0", "http-body-util", "miden-lib", "miden-node-proto", @@ -1772,11 +1877,14 @@ dependencies = [ "rand", "rand_chacha", "serde", + "serde_json", "static-files", "thiserror 2.0.11", "tokio", + "tokio-stream", "toml", "tonic", + "tonic-web", "tower 0.5.2", "tower-http 0.6.2", "tracing", @@ -1970,7 +2078,7 @@ version = "0.8.0" dependencies = [ "anyhow", "figment", - "http", + "http 1.2.0", "itertools 0.14.0", "miden-objects", "opentelemetry", @@ -2161,6 +2269,23 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03" +[[package]] +name = "native-tls" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dab59f8e050d5df8e4dd87d9206fb6f65a483e20ac9fda365ade4fab353196c" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework 2.11.1", + "security-framework-sys", + "tempfile", +] + [[package]] name = "new_debug_unreachable" version = "1.0.6" @@ -2312,12 +2437,50 @@ version = "1.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" +[[package]] +name = "openssl" +version = "0.10.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e14130c6a98cd258fdcb0fb6d744152343ff729cbfcb28c656a9d12b999fbcd" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "openssl-probe" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" +[[package]] +name = "openssl-sys" +version = "0.9.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bb61ea9811cc39e3c2069f40b8b8e2e70d8569b361f879786cc7ed48b777cdd" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "opentelemetry" version = "0.28.0" @@ -2340,7 +2503,7 @@ checksum = "5bef114c6d41bea83d6dc60eb41720eedd0261a67af57b66dd2b84ac46c01d91" dependencies = [ "async-trait", "futures-core", - "http", + "http 1.2.0", "opentelemetry", "opentelemetry-proto", "opentelemetry_sdk", @@ -2956,7 +3119,7 @@ dependencies = [ "openssl-probe", "rustls-pki-types", "schannel", - "security-framework", + "security-framework 3.2.0", ] [[package]] @@ -3039,6 +3202,19 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags", + "core-foundation 0.9.4", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + [[package]] name = "security-framework" version = "3.2.0" @@ -3046,7 +3222,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271720403f46ca04f7ba6f55d438f8bd878d6b8ca0a1046e8228c4145bcbb316" dependencies = [ "bitflags", - "core-foundation", + "core-foundation 0.10.0", "core-foundation-sys", "libc", "security-framework-sys", @@ -3506,6 +3682,16 @@ dependencies = [ "syn", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + [[package]] name = "tokio-rustls" version = "0.26.1" @@ -3583,10 +3769,10 @@ dependencies = [ "async-stream", "async-trait", "axum 0.7.9", - "base64", + "base64 0.22.1", "bytes", "h2", - "http", + "http 1.2.0", "http-body", "http-body-util", "hyper", @@ -3627,9 +3813,9 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5299dd20801ad736dccb4a5ea0da7376e59cd98f213bf1c3d478cf53f4834b58" dependencies = [ - "base64", + "base64 0.22.1", "bytes", - "http", + "http 1.2.0", "http-body", "http-body-util", "pin-project", @@ -3685,7 +3871,7 @@ checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5" dependencies = [ "bitflags", "bytes", - "http", + "http 1.2.0", "http-body", "http-body-util", "pin-project-lite", @@ -3701,7 +3887,7 @@ checksum = "403fa3b783d4b626a8ad51d766ab03cb6d2dbfc46b1c5d4448395e6628dc9697" dependencies = [ "bitflags", "bytes", - "http", + "http 1.2.0", "http-body", "pin-project-lite", "tower-layer", @@ -3888,6 +4074,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + [[package]] name = "unicode-width" version = "0.1.14" @@ -4120,6 +4312,26 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webdriver" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "144ab979b12d36d65065635e646549925de229954de2eb3b47459b432a42db71" +dependencies = [ + "base64 0.21.7", + "bytes", + "cookie 0.16.2", + "http 0.2.12", + "log", + "serde", + "serde_derive", + "serde_json", + "thiserror 1.0.69", + "time", + "unicode-segmentation", + "url", +] + [[package]] name = "winapi" version = "0.3.9" diff --git a/bin/faucet/Cargo.toml b/bin/faucet/Cargo.toml index 83194cc0f..a1b66b565 100644 --- a/bin/faucet/Cargo.toml +++ b/bin/faucet/Cargo.toml @@ -43,3 +43,9 @@ url = { workspace = true } # Required to inject build metadata. miden-node-utils = { workspace = true, features = ["vergen"] } static-files = "0.2" + +[dev-dependencies] +fantoccini = { version = "0.21" } +serde_json = { version = "1.0" } +tokio-stream = { workspace = true, features = ["net"] } +tonic-web = { version = "0.12" } diff --git a/bin/faucet/src/main.rs b/bin/faucet/src/main.rs index 64e223d0b..c6dce5eed 100644 --- a/bin/faucet/src/main.rs +++ b/bin/faucet/src/main.rs @@ -5,6 +5,9 @@ mod handlers; mod state; mod store; +#[cfg(test)] +mod stub_rpc_api; + use std::path::PathBuf; use anyhow::Context; @@ -96,11 +99,14 @@ async fn main() -> anyhow::Result<()> { let cli = Cli::parse(); + run_faucet_command(cli).await +} + +async fn run_faucet_command(cli: Cli) -> anyhow::Result<()> { match &cli.command { Command::Start { config } => { let config: FaucetConfig = load_config(config).context("failed to load configuration file")?; - let faucet_state = FaucetState::new(config.clone()).await?; info!(target: COMPONENT, %config, "Initializing server"); @@ -128,9 +134,12 @@ async fn main() -> anyhow::Result<()> { ) .with_state(faucet_state); - let socket_addr = config.endpoint.socket_addrs(|| None)?.into_iter().next().ok_or( - anyhow::anyhow!("Couldn't get any socket addrs for endpoint: {}", config.endpoint), - )?; + let socket_addr = config + .endpoint + .socket_addrs(|| None)? + .into_iter() + .next() + .with_context(|| format!("no sockets available on {}", config.endpoint))?; let listener = TcpListener::bind(socket_addr).await.context("failed to bind TCP listener")?; @@ -229,3 +238,121 @@ fn long_version() -> LongVersion { debug: option_env!("VERGEN_CARGO_DEBUG").unwrap_or_default(), } } + +#[cfg(test)] +mod test { + use std::{ + env::temp_dir, + io::{BufRead, BufReader}, + process::{Command, Stdio}, + str::FromStr, + }; + + use fantoccini::ClientBuilder; + use serde_json::{json, Map}; + use url::Url; + + use crate::{config::FaucetConfig, run_faucet_command, stub_rpc_api::serve_stub, Cli}; + + /// This test starts a stub node, a faucet connected to the stub node, and a chromedriver + /// to test the faucet website. It then loads the website and checks that all the requests + /// made return status 200. + #[tokio::test] + async fn test_website() { + let stub_node_url = Url::from_str("http://localhost:50051").unwrap(); + + // Start the stub node + tokio::spawn({ + let stub_node_url = stub_node_url.clone(); + async move { serve_stub(&stub_node_url).await.unwrap() } + }); + + let config_path = temp_dir().join("faucet.toml"); + let faucet_account_path = temp_dir().join("account.mac"); + + // Create config + let config = FaucetConfig { + node_url: stub_node_url, + faucet_account_path: faucet_account_path.clone(), + ..FaucetConfig::default() + }; + let config_as_toml_string = toml::to_string(&config).unwrap(); + std::fs::write(&config_path, config_as_toml_string).unwrap(); + + // Create faucet account + run_faucet_command(Cli { + command: crate::Command::CreateFaucetAccount { + config_path: config_path.clone(), + output_path: faucet_account_path.clone(), + token_symbol: "TEST".to_string(), + decimals: 2, + max_supply: 1000, + }, + }) + .await + .unwrap(); + + // Start the faucet connected to the stub + let website_url = config.endpoint.clone(); + tokio::spawn(async move { + run_faucet_command(Cli { + command: crate::Command::Start { config: config_path }, + }) + .await + .unwrap(); + }); + + // Start chromedriver. This requires having chromedriver and chrome installed + let chromedriver_port = "57709"; + #[expect(clippy::zombie_processes)] + let mut chromedriver = Command::new("chromedriver") + .arg(format!("--port={chromedriver_port}")) + .stdout(Stdio::piped()) + .spawn() + .expect("failed to start chromedriver"); + // Wait for chromedriver to be running + let stdout = chromedriver.stdout.take().unwrap(); + for line in BufReader::new(stdout).lines() { + if line.unwrap().contains("ChromeDriver was started successfully") { + break; + } + } + + // Start fantoccini client + let client = ClientBuilder::native() + .capabilities( + [( + "goog:chromeOptions".to_string(), + json!({"args": ["--headless", "--disable-gpu", "--no-sandbox"]}), + )] + .into_iter() + .collect::>(), + ) + .connect(&format!("http://localhost:{chromedriver_port}")) + .await + .expect("failed to connect to WebDriver"); + + // Open the website + client.goto(website_url.as_str()).await.unwrap(); + + let title = client.title().await.unwrap(); + assert_eq!(title, "Miden Faucet"); + + // Execute a script to get all the failed requests + let script = r" + let errors = []; + performance.getEntriesByType('resource').forEach(entry => { + if (entry.responseStatus && entry.responseStatus >= 400) { + errors.push({url: entry.name, status: entry.responseStatus}); + } + }); + return errors; + "; + let failed_requests = client.execute(script, vec![]).await.unwrap(); + assert!(failed_requests.as_array().unwrap().is_empty()); + + // Close the client and kill chromedriver + client.close().await.unwrap(); + chromedriver.kill().unwrap(); + } +} diff --git a/bin/faucet/src/stub_rpc_api.rs b/bin/faucet/src/stub_rpc_api.rs new file mode 100644 index 000000000..0a89336ff --- /dev/null +++ b/bin/faucet/src/stub_rpc_api.rs @@ -0,0 +1,166 @@ +use miden_node_proto::generated::{ + block::BlockHeader, + digest::Digest, + requests::{ + CheckNullifiersByPrefixRequest, CheckNullifiersRequest, GetAccountDetailsRequest, + GetAccountProofsRequest, GetAccountStateDeltaRequest, GetBlockByNumberRequest, + GetBlockHeaderByNumberRequest, GetNotesByIdRequest, SubmitProvenTransactionRequest, + SyncNoteRequest, SyncStateRequest, + }, + responses::{ + CheckNullifiersByPrefixResponse, CheckNullifiersResponse, GetAccountDetailsResponse, + GetAccountProofsResponse, GetAccountStateDeltaResponse, GetBlockByNumberResponse, + GetBlockHeaderByNumberResponse, GetNotesByIdResponse, SubmitProvenTransactionResponse, + SyncNoteResponse, SyncStateResponse, + }, + rpc::api_server, +}; +use miden_node_utils::errors::ApiError; +use tokio::net::TcpListener; +use tokio_stream::wrappers::TcpListenerStream; +use tonic::{Request, Response, Status}; +use url::Url; + +#[derive(Clone)] +pub struct StubRpcApi; + +#[tonic::async_trait] +impl api_server::Api for StubRpcApi { + async fn check_nullifiers( + &self, + _request: Request, + ) -> Result, Status> { + unimplemented!(); + } + + async fn check_nullifiers_by_prefix( + &self, + _request: Request, + ) -> Result, Status> { + unimplemented!(); + } + + async fn get_block_header_by_number( + &self, + _request: Request, + ) -> Result, Status> { + // Values are taken from the default genesis block as at v0.7 + Ok(Response::new(GetBlockHeaderByNumberResponse { + block_header: Some(BlockHeader { + version: 1, + prev_hash: Some(Digest { d0: 0, d1: 0, d2: 0, d3: 0 }), + block_num: 0, + chain_root: Some(Digest { + d0: 0x9729_9D39_2DA8_DC69, + d1: 0x674_44AF_6294_0719, + d2: 0x7B97_0BC7_07A0_F7D6, + d3: 0xE423_8D7C_78F3_9D8B, + }), + account_root: Some(Digest { + d0: 0x9666_5D75_8487_401A, + d1: 0xB7BF_DF8B_379F_ED71, + d2: 0xFCA7_82CB_2406_2222, + d3: 0x8D0C_B80F_6377_4E9A, + }), + nullifier_root: Some(Digest { + d0: 0xD4A0_CFF6_578C_123E, + d1: 0xF11A_1794_8930_B14A, + d2: 0xD128_DD2A_4213_B53C, + d3: 0x2DF8_FE54_F23F_6B91, + }), + note_root: Some(Digest { + d0: 0x93CE_DDC8_A187_24FE, + d1: 0x4E32_9917_2E91_30ED, + d2: 0x8022_9E0E_1808_C860, + d3: 0x13F4_7934_7EB7_FD78, + }), + tx_hash: Some(Digest { d0: 0, d1: 0, d2: 0, d3: 0 }), + kernel_root: Some(Digest { + d0: 0x7B6F_43E5_2910_C8C3, + d1: 0x99B3_2868_577E_5779, + d2: 0xAF9E_6424_57CD_B8C1, + d3: 0xB1DD_E61B_F983_2DBD, + }), + proof_hash: Some(Digest { d0: 0, d1: 0, d2: 0, d3: 0 }), + timestamp: 0x63B0_CD00, + }), + mmr_path: None, + chain_length: None, + })) + } + + async fn sync_state( + &self, + _request: Request, + ) -> Result, Status> { + unimplemented!(); + } + + async fn sync_notes( + &self, + _request: Request, + ) -> Result, Status> { + unimplemented!(); + } + + async fn get_notes_by_id( + &self, + _request: Request, + ) -> Result, Status> { + unimplemented!(); + } + + async fn submit_proven_transaction( + &self, + _request: Request, + ) -> Result, Status> { + Ok(Response::new(SubmitProvenTransactionResponse { block_height: 0 })) + } + + async fn get_account_details( + &self, + _request: Request, + ) -> Result, Status> { + Err(Status::not_found("account not found")) + } + + async fn get_block_by_number( + &self, + _request: Request, + ) -> Result, Status> { + unimplemented!() + } + + async fn get_account_state_delta( + &self, + _request: Request, + ) -> Result, Status> { + unimplemented!() + } + + async fn get_account_proofs( + &self, + _request: Request, + ) -> Result, Status> { + unimplemented!() + } +} + +pub async fn serve_stub(endpoint: &Url) -> Result<(), ApiError> { + let addr = endpoint + .socket_addrs(|| None) + .map_err(ApiError::EndpointToSocketFailed)? + .into_iter() + .next() + .unwrap(); + + let listener = TcpListener::bind(addr).await?; + let api_service = api_server::ApiServer::new(StubRpcApi); + + tonic::transport::Server::builder() + .accept_http1(true) + .add_service(tonic_web::enable(api_service)) + .serve_with_incoming(TcpListenerStream::new(listener)) + .await + .map_err(ApiError::ApiServeFailed) +} From 3c5f411738ca1fc6c77cc238b1eb028504ad75fe Mon Sep 17 00:00:00 2001 From: Serge Radinovich <47865535+sergerad@users.noreply.github.com> Date: Mon, 3 Mar 2025 22:00:45 +1300 Subject: [PATCH 2/2] chore: add Open Telemetry attributes to grpc spans (#698) --- Cargo.lock | 319 ++++++++++++++++++++++++++- Cargo.toml | 3 + bin/faucet/Cargo.toml | 4 +- crates/block-producer/Cargo.toml | 3 +- crates/block-producer/src/server.rs | 6 +- crates/store/Cargo.toml | 1 + crates/store/src/server/mod.rs | 6 +- crates/utils/Cargo.toml | 5 +- crates/utils/src/logging.rs | 24 +- crates/utils/src/tracing/grpc.rs | 81 +++++-- crates/utils/src/tracing/span_ext.rs | 53 +++-- docs/operator.md | 3 +- 12 files changed, 451 insertions(+), 57 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 07858d22f..198bf8e17 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -136,6 +136,151 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" +[[package]] +name = "async-channel" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" +dependencies = [ + "concurrent-queue", + "event-listener 2.5.3", + "futures-core", +] + +[[package]] +name = "async-channel" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-executor" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30ca9a001c1e8ba5149f91a74362376cc6bc5b919d92d988668657bd570bdcec" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "slab", +] + +[[package]] +name = "async-global-executor" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" +dependencies = [ + "async-channel 2.3.1", + "async-executor", + "async-io", + "async-lock", + "blocking", + "futures-lite", + "once_cell", +] + +[[package]] +name = "async-io" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a2b323ccce0a1d90b449fd71f2a06ca7faa7c54c2751f06c9bd851fc061059" +dependencies = [ + "async-lock", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite", + "parking", + "polling", + "rustix", + "slab", + "tracing", + "windows-sys 0.59.0", +] + +[[package]] +name = "async-lock" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" +dependencies = [ + "event-listener 5.4.0", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-process" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63255f1dc2381611000436537bbedfe83183faa303a5a0edaf191edef06526bb" +dependencies = [ + "async-channel 2.3.1", + "async-io", + "async-lock", + "async-signal", + "async-task", + "blocking", + "cfg-if", + "event-listener 5.4.0", + "futures-lite", + "rustix", + "tracing", +] + +[[package]] +name = "async-signal" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "637e00349800c0bdf8bfc21ebbc0b6524abea702b0da4168ac00d070d0c0b9f3" +dependencies = [ + "async-io", + "async-lock", + "atomic-waker", + "cfg-if", + "futures-core", + "futures-io", + "rustix", + "signal-hook-registry", + "slab", + "windows-sys 0.59.0", +] + +[[package]] +name = "async-std" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c634475f29802fde2b8f0b505b1bd00dfe4df7d4a000f0b36f7671197d5c3615" +dependencies = [ + "async-channel 1.9.0", + "async-global-executor", + "async-io", + "async-lock", + "async-process", + "crossbeam-utils", + "futures-channel", + "futures-core", + "futures-io", + "futures-lite", + "gloo-timers", + "kv-log-macro", + "log", + "memchr", + "once_cell", + "pin-project-lite", + "pin-utils", + "slab", + "wasm-bindgen-futures", +] + [[package]] name = "async-stream" version = "0.3.6" @@ -158,6 +303,12 @@ dependencies = [ "syn", ] +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + [[package]] name = "async-trait" version = "0.1.86" @@ -411,6 +562,19 @@ dependencies = [ "generic-array", ] +[[package]] +name = "blocking" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" +dependencies = [ + "async-channel 2.3.1", + "async-task", + "futures-io", + "futures-lite", + "piper", +] + [[package]] name = "bumpalo" version = "3.17.0" @@ -574,6 +738,15 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "constant_time_eq" version = "0.3.1" @@ -877,6 +1050,33 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "event-listener" +version = "5.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c3e4e0dd3673c1139bf041f3008816d9cf2946bbfac2945c09e523b8d7b05b2" +dependencies = [ + "event-listener 5.4.0", + "pin-project-lite", +] + [[package]] name = "fallible-iterator" version = "0.3.0" @@ -1026,6 +1226,19 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +[[package]] +name = "futures-lite" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cef40d21ae2c515b51041df9ed313ed21e572df340ea58a922a0aefe7e8891a1" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + [[package]] name = "futures-macro" version = "0.3.31" @@ -1127,6 +1340,18 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" +[[package]] +name = "gloo-timers" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + [[package]] name = "h2" version = "0.4.8" @@ -1188,6 +1413,12 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +[[package]] +name = "hermit-abi" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" + [[package]] name = "hex" version = "0.4.3" @@ -1593,6 +1824,15 @@ dependencies = [ "cpufeatures", ] +[[package]] +name = "kv-log-macro" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" +dependencies = [ + "log", +] + [[package]] name = "lalrpop" version = "0.20.2" @@ -1703,6 +1943,9 @@ name = "log" version = "0.4.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" +dependencies = [ + "value-bag", +] [[package]] name = "logos" @@ -2003,6 +2246,7 @@ dependencies = [ "tokio", "tokio-stream", "tonic", + "tower-http 0.6.2", "tracing", "url", "winterfell", @@ -2060,6 +2304,7 @@ dependencies = [ "tokio", "tokio-stream", "tonic", + "tower-http 0.6.2", "tracing", "url", ] @@ -2409,7 +2654,7 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "hermit-abi", + "hermit-abi 0.3.9", "libc", ] @@ -2531,6 +2776,7 @@ version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84dfad6042089c7fc1f6118b7040dc2eb4ab520abbf410b79dc481032af39570" dependencies = [ + "async-std", "async-trait", "futures-channel", "futures-executor", @@ -2558,6 +2804,12 @@ version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1036865bb9422d3300cf723f657c2851d0e9ab12567854b1f4eba3d77decf564" +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + [[package]] name = "parking_lot" version = "0.12.3" @@ -2686,12 +2938,38 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "piper" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] + [[package]] name = "pkg-config" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" +[[package]] +name = "polling" +version = "3.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a604568c3202727d1507653cb121dbd627a58684eb09a820fd746bee38b4442f" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi 0.4.0", + "pin-project-lite", + "rustix", + "tracing", + "windows-sys 0.59.0", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -3350,6 +3628,15 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + [[package]] name = "siphasher" version = "1.0.1" @@ -3890,6 +4177,7 @@ dependencies = [ "http 1.2.0", "http-body", "pin-project-lite", + "tower 0.5.2", "tower-layer", "tower-service", "tracing", @@ -4140,6 +4428,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" +[[package]] +name = "value-bag" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ef4c4aa54d5d05a279399bfa921ec387b7aba77caf7a682ae8d86785b8fdad2" + [[package]] name = "vcpkg" version = "0.2.15" @@ -4270,6 +4564,19 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.100" @@ -4302,6 +4609,16 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "web-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "web-time" version = "1.1.0" diff --git a/Cargo.toml b/Cargo.toml index 7d8563b0e..cf817fdb8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,7 @@ version = "0.8.0" [workspace.dependencies] assert_matches = { version = "1.5" } +http = { version = "1.2" } itertools = { version = "0.14" } miden-air = { version = "0.12" } miden-lib = { git = "https://github.com/0xPolygonMiden/miden-base", branch = "next" } @@ -44,6 +45,8 @@ thiserror = { version = "2.0", default-features = false } tokio = { version = "1.40", features = ["rt-multi-thread"] } tokio-stream = { version = "0.1" } tonic = { version = "0.12" } +tower = { version = "0.5" } +tower-http = { version = "0.6", features = ["trace"] } tracing = { version = "0.1" } tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt", "json"] } url = { version = "2.5", features = ["serde"] } diff --git a/bin/faucet/Cargo.toml b/bin/faucet/Cargo.toml index a1b66b565..28ef9d438 100644 --- a/bin/faucet/Cargo.toml +++ b/bin/faucet/Cargo.toml @@ -34,8 +34,8 @@ thiserror = { workspace = true } tokio = { workspace = true, features = ["fs"] } toml = { version = "0.8" } tonic = { workspace = true } -tower = "0.5" -tower-http = { version = "0.6", features = ["cors", "set-header", "trace"] } +tower = { workspace = true } +tower-http = { workspace = true, features = ["cors", "set-header", "trace"] } tracing = { workspace = true } url = { workspace = true } diff --git a/crates/block-producer/Cargo.toml b/crates/block-producer/Cargo.toml index 6c354019c..f6d0789b6 100644 --- a/crates/block-producer/Cargo.toml +++ b/crates/block-producer/Cargo.toml @@ -34,7 +34,8 @@ serde = { version = "1.0", features = ["derive"] } thiserror = { workspace = true } tokio = { workspace = true, features = ["macros", "net", "rt-multi-thread", "sync", "time"] } tokio-stream = { workspace = true, features = ["net"] } -tonic = { workspace = true } +tonic = { workspace = true, features = ["transport"] } +tower-http = { workspace = true, features = ["util"] } tracing = { workspace = true } url = { workspace = true } diff --git a/crates/block-producer/src/server.rs b/crates/block-producer/src/server.rs index 251d0b339..e3dc923e4 100644 --- a/crates/block-producer/src/server.rs +++ b/crates/block-producer/src/server.rs @@ -7,7 +7,7 @@ use miden_node_proto::generated::{ use miden_node_utils::{ errors::ApiError, formatting::{format_input_notes, format_output_notes}, - tracing::grpc::OtelInterceptor, + tracing::grpc::{block_producer_trace_fn, OtelInterceptor}, }; use miden_objects::{ block::BlockNumber, transaction::ProvenTransaction, utils::serde::Deserializable, @@ -15,6 +15,7 @@ use miden_objects::{ use tokio::{net::TcpListener, sync::Mutex}; use tokio_stream::wrappers::TcpListenerStream; use tonic::Status; +use tower_http::trace::TraceLayer; use tracing::{debug, info, instrument}; use crate::{ @@ -211,8 +212,9 @@ impl BlockProducerRpcServer { } async fn serve(self, listener: TcpListener) -> Result<(), tonic::transport::Error> { + // Build the gRPC server with the API service and trace layer. tonic::transport::Server::builder() - .trace_fn(miden_node_utils::tracing::grpc::block_producer_trace_fn) + .layer(TraceLayer::new_for_grpc().make_span_with(block_producer_trace_fn)) .add_service(api_server::ApiServer::new(self)) .serve_with_incoming(TcpListenerStream::new(listener)) .await diff --git a/crates/store/Cargo.toml b/crates/store/Cargo.toml index adc902a73..3ce5c5de0 100644 --- a/crates/store/Cargo.toml +++ b/crates/store/Cargo.toml @@ -28,6 +28,7 @@ thiserror = { workspace = true } tokio = { workspace = true, features = ["fs", "macros", "net", "rt-multi-thread"] } tokio-stream = { workspace = true, features = ["net"] } tonic = { workspace = true } +tower-http = { workspace = true, features = ["util"] } tracing = { workspace = true } url = { workspace = true } diff --git a/crates/store/src/server/mod.rs b/crates/store/src/server/mod.rs index 2b65a1dc2..a3c1f009d 100644 --- a/crates/store/src/server/mod.rs +++ b/crates/store/src/server/mod.rs @@ -1,9 +1,10 @@ use std::sync::Arc; use miden_node_proto::generated::store::api_server; -use miden_node_utils::errors::ApiError; +use miden_node_utils::{errors::ApiError, tracing::grpc::store_trace_fn}; use tokio::net::TcpListener; use tokio_stream::wrappers::TcpListenerStream; +use tower_http::trace::TraceLayer; use tracing::info; use crate::{blocks::BlockStore, config::StoreConfig, db::Db, state::State, COMPONENT}; @@ -61,8 +62,9 @@ impl Store { /// /// Note: this blocks until the server dies. pub async fn serve(self) -> Result<(), ApiError> { + // Build the gRPC server with the API service and trace layer. tonic::transport::Server::builder() - .trace_fn(miden_node_utils::tracing::grpc::store_trace_fn) + .layer(TraceLayer::new_for_grpc().make_span_with(store_trace_fn)) .add_service(self.api_service) .serve_with_incoming(TcpListenerStream::new(self.listener)) .await diff --git a/crates/utils/Cargo.toml b/crates/utils/Cargo.toml index 73995dca5..c35bfd41a 100644 --- a/crates/utils/Cargo.toml +++ b/crates/utils/Cargo.toml @@ -21,12 +21,12 @@ vergen = ["dep:vergen", "dep:vergen-gitcl"] [dependencies] anyhow = { version = "1.0" } figment = { version = "0.10", features = ["env", "toml"] } -http = { version = "1.2" } +http = { workspace = true } itertools = { workspace = true } miden-objects = { workspace = true } opentelemetry = { version = "0.28" } opentelemetry-otlp = { version = "0.28", default-features = false, features = ["grpc-tonic", "tls-roots", "trace"] } -opentelemetry_sdk = { version = "0.28", features = ["rt-tokio"] } +opentelemetry_sdk = { version = "0.28", features = ["rt-tokio", "testing"] } rand = { workspace = true } serde = { version = "1.0", features = ["derive"] } thiserror = { workspace = true } @@ -35,6 +35,7 @@ tracing = { workspace = true } tracing-forest = { version = "0.1", optional = true, features = ["chrono"] } tracing-opentelemetry = { version = "0.29" } tracing-subscriber = { workspace = true } + # Optional dependencies enabled by `vergen` feature. # This must match the version expected by `vergen-gitcl`. vergen = { "version" = "9.0", optional = true } diff --git a/crates/utils/src/logging.rs b/crates/utils/src/logging.rs index a8a220ccd..cec4f6160 100644 --- a/crates/utils/src/logging.rs +++ b/crates/utils/src/logging.rs @@ -3,7 +3,7 @@ use std::str::FromStr; use anyhow::Result; use opentelemetry::trace::TracerProvider as _; use opentelemetry_otlp::WithTonicConfig; -use opentelemetry_sdk::propagation::TraceContextPropagator; +use opentelemetry_sdk::{propagation::TraceContextPropagator, trace::SpanExporter}; use tracing::subscriber::Subscriber; use tracing_opentelemetry::OpenTelemetryLayer; use tracing_subscriber::{ @@ -39,7 +39,17 @@ pub fn setup_tracing(otel: OpenTelemetry) -> Result<()> { // Note: open-telemetry requires a tokio-runtime, so this _must_ be lazily evaluated (aka not // `then_some`) to avoid crashing sync callers (with OpenTelemetry::Disabled set). Examples of // such callers are tests with logging enabled. - let otel_layer = otel.is_enabled().then(open_telemetry_layer); + let otel_layer = { + if otel.is_enabled() { + let exporter = opentelemetry_otlp::SpanExporter::builder() + .with_tonic() + .with_tls_config(tonic::transport::ClientTlsConfig::new().with_native_roots()) + .build()?; + Some(open_telemetry_layer(exporter)) + } else { + None + } + }; let subscriber = Registry::default() .with(stdout_layer().with_filter(env_or_default_filter())) @@ -47,17 +57,13 @@ pub fn setup_tracing(otel: OpenTelemetry) -> Result<()> { tracing::subscriber::set_global_default(subscriber).map_err(Into::into) } -fn open_telemetry_layer() -> Box + Send + Sync + 'static> +fn open_telemetry_layer( + exporter: impl SpanExporter + 'static, +) -> Box + Send + Sync + 'static> where S: Subscriber + Sync + Send, for<'a> S: tracing_subscriber::registry::LookupSpan<'a>, { - let exporter = opentelemetry_otlp::SpanExporter::builder() - .with_tonic() - .with_tls_config(tonic::transport::ClientTlsConfig::new().with_native_roots()) - .build() - .unwrap(); - let tracer = opentelemetry_sdk::trace::SdkTracerProvider::builder() .with_batch_exporter(exporter) .build(); diff --git a/crates/utils/src/tracing/grpc.rs b/crates/utils/src/tracing/grpc.rs index 1e951af12..6a5bb9508 100644 --- a/crates/utils/src/tracing/grpc.rs +++ b/crates/utils/src/tracing/grpc.rs @@ -1,4 +1,13 @@ -use tracing_opentelemetry::OpenTelemetrySpanExt; +/// Creates a [`tracing::Span`] based on RPC service and method name. +macro_rules! rpc_span { + ($service:expr, $method:expr) => { + tracing::info_span!( + concat!($service, "/", $method), + rpc.service = $service, + rpc.method = $method + ) + }; +} /// A [`trace_fn`](tonic::transport::server::Server) implementation for the block producer which /// adds open-telemetry information to the span. @@ -6,11 +15,11 @@ use tracing_opentelemetry::OpenTelemetrySpanExt; /// Creates an `info` span following the open-telemetry standard: `block-producer.rpc/{method}`. /// Additionally also pulls in remote tracing context which allows the server trace to be connected /// to the client's origin trace. -pub fn block_producer_trace_fn(request: &http::Request<()>) -> tracing::Span { +pub fn block_producer_trace_fn(request: &http::Request) -> tracing::Span { let span = if let Some("SubmitProvenTransaction") = request.uri().path().rsplit('/').next() { - tracing::info_span!("block-producer.rpc/SubmitProvenTransaction") + rpc_span!("block-producer.rpc", "SubmitProvenTransaction") } else { - tracing::info_span!("block-producer.rpc/Unknown") + rpc_span!("block-producer.rpc", "Unknown") }; add_otel_span_attributes(span, request) @@ -22,23 +31,23 @@ pub fn block_producer_trace_fn(request: &http::Request<()>) -> tracing::Span { /// Creates an `info` span following the open-telemetry standard: `store.rpc/{method}`. Additionally /// also pulls in remote tracing context which allows the server trace to be connected to the /// client's origin trace. -pub fn store_trace_fn(request: &http::Request<()>) -> tracing::Span { +pub fn store_trace_fn(request: &http::Request) -> tracing::Span { let span = match request.uri().path().rsplit('/').next() { - Some("ApplyBlock") => tracing::info_span!("store.rpc/ApplyBlock"), - Some("CheckNullifiers") => tracing::info_span!("store.rpc/CheckNullifiers"), - Some("CheckNullifiersByPrefix") => tracing::info_span!("store.rpc/CheckNullifiersByPrefix"), - Some("GetAccountDetails") => tracing::info_span!("store.rpc/GetAccountDetails"), - Some("GetAccountProofs") => tracing::info_span!("store.rpc/GetAccountProofs"), - Some("GetAccountStateDelta") => tracing::info_span!("store.rpc/GetAccountStateDelta"), - Some("GetBlockByNumber") => tracing::info_span!("store.rpc/GetBlockByNumber"), - Some("GetBlockHeaderByNumber") => tracing::info_span!("store.rpc/GetBlockHeaderByNumber"), - Some("GetBlockInputs") => tracing::info_span!("store.rpc/GetBlockInputs"), - Some("GetBatchInputs") => tracing::info_span!("store.rpc/GetBatchInputs"), - Some("GetNotesById") => tracing::info_span!("store.rpc/GetNotesById"), - Some("GetTransactionInputs") => tracing::info_span!("store.rpc/GetTransactionInputs"), - Some("SyncNotes") => tracing::info_span!("store.rpc/SyncNotes"), - Some("SyncState") => tracing::info_span!("store.rpc/SyncState"), - _ => tracing::info_span!("store.rpc/Unknown"), + Some("ApplyBlock") => rpc_span!("store.rpc", "ApplyBlock"), + Some("CheckNullifiers") => rpc_span!("store.rpc", "CheckNullifiers"), + Some("CheckNullifiersByPrefix") => rpc_span!("store.rpc", "CheckNullifiersByPrefix"), + Some("GetAccountDetails") => rpc_span!("store.rpc", "GetAccountDetails"), + Some("GetAccountProofs") => rpc_span!("store.rpc", "GetAccountProofs"), + Some("GetAccountStateDelta") => rpc_span!("store.rpc", "GetAccountStateDelta"), + Some("GetBlockByNumber") => rpc_span!("store.rpc", "GetBlockByNumber"), + Some("GetBlockHeaderByNumber") => rpc_span!("store.rpc", "GetBlockHeaderByNumber"), + Some("GetBlockInputs") => rpc_span!("store.rpc", "GetBlockInputs"), + Some("GetBatchInputs") => rpc_span!("store.rpc", "GetBatchInputs"), + Some("GetNotesById") => rpc_span!("store.rpc", "GetNotesById"), + Some("GetTransactionInputs") => rpc_span!("store.rpc", "GetTransactionInputs"), + Some("SyncNotes") => rpc_span!("store.rpc", "SyncNotes"), + Some("SyncState") => rpc_span!("store.rpc", "SyncState"), + _ => rpc_span!("store.rpc", "Unknown"), }; add_otel_span_attributes(span, request) @@ -47,19 +56,44 @@ pub fn store_trace_fn(request: &http::Request<()>) -> tracing::Span { /// Adds remote tracing context to the span. /// /// Could be expanded in the future by adding in more open-telemetry properties. -fn add_otel_span_attributes(span: tracing::Span, request: &http::Request<()>) -> tracing::Span { +fn add_otel_span_attributes(span: tracing::Span, request: &http::Request) -> tracing::Span { + use super::OpenTelemetrySpanExt; // Pull the open-telemetry parent context using the HTTP extractor. We could make a more // generic gRPC extractor by utilising the gRPC metadata. However that // (a) requires cloning headers, // (b) we would have to write this ourselves, and // (c) gRPC metadata is transferred using HTTP headers in any case. - use tracing_opentelemetry::OpenTelemetrySpanExt; let otel_ctx = opentelemetry::global::get_text_map_propagator(|propagator| { propagator.extract(&MetadataExtractor(&tonic::metadata::MetadataMap::from_headers( request.headers().clone(), ))) }); - span.set_parent(otel_ctx); + tracing_opentelemetry::OpenTelemetrySpanExt::set_parent(&span, otel_ctx); + + // Set HTTP attributes. + // See https://opentelemetry.io/docs/specs/semconv/rpc/rpc-spans/#server-attributes. + span.set_attribute("rpc.system", "grpc"); + if let Some(host) = request.uri().host() { + span.set_attribute("server.address", host); + } + if let Some(host_port) = request.uri().port() { + span.set_attribute("server.port", host_port.as_u16()); + } + let remote_addr = request + .extensions() + .get::() + .and_then(tonic::transport::server::TcpConnectInfo::remote_addr); + if let Some(addr) = remote_addr { + span.set_attribute("client.address", addr.ip()); + span.set_attribute("client.port", addr.port()); + span.set_attribute("network.peer.address", addr.ip()); + span.set_attribute("network.peer.port", addr.port()); + span.set_attribute("network.transport", "tcp"); + match addr.ip() { + std::net::IpAddr::V4(_) => span.set_attribute("network.type", "ipv4"), + std::net::IpAddr::V6(_) => span.set_attribute("network.type", "ipv6"), + } + } span } @@ -73,6 +107,7 @@ impl tonic::service::Interceptor for OtelInterceptor { &mut self, mut request: tonic::Request<()>, ) -> Result, tonic::Status> { + use tracing_opentelemetry::OpenTelemetrySpanExt; let ctx = tracing::Span::current().context(); opentelemetry::global::get_text_map_propagator(|propagator| { propagator.inject_context(&ctx, &mut MetadataInjector(request.metadata_mut())); diff --git a/crates/utils/src/tracing/span_ext.rs b/crates/utils/src/tracing/span_ext.rs index a21150ac0..4aed6c7a8 100644 --- a/crates/utils/src/tracing/span_ext.rs +++ b/crates/utils/src/tracing/span_ext.rs @@ -1,4 +1,5 @@ use core::time::Duration; +use std::net::IpAddr; use miden_objects::{block::BlockNumber, Digest}; use opentelemetry::{trace::Status, Key, Value}; @@ -20,29 +21,53 @@ impl ToValue for Digest { } } -impl ToValue for f64 { - fn to_value(&self) -> Value { - (*self).into() - } -} - impl ToValue for BlockNumber { fn to_value(&self) -> Value { i64::from(self.as_u32()).into() } } -impl ToValue for u32 { - fn to_value(&self) -> Value { - i64::from(*self).into() - } +/// Generates `impl ToValue` blocks for types that are `ToString`. +macro_rules! impl_to_string_to_value { + ($($t:ty),*) => { + $( + impl ToValue for $t { + fn to_value(&self) -> Value { + self.to_string().into() + } + } + )* + }; } +impl_to_string_to_value!(IpAddr, &str); -impl ToValue for i64 { - fn to_value(&self) -> Value { - (*self).into() - } +/// Generates `impl ToValue` blocks for integer types. +macro_rules! impl_int_to_value { + ($($t:ty),*) => { + $( + impl ToValue for $t { + fn to_value(&self) -> Value { + i64::from(*self).into() + } + } + )* + }; +} +impl_int_to_value!(u16, u32); + +/// Generates `impl ToValue` blocks for types that are `Into`. +macro_rules! impl_to_value { + ($($t:ty),*) => { + $( + impl ToValue for $t { + fn to_value(&self) -> Value { + (*self).into() + } + } + )* + }; } +impl_to_value!(f64, i64); /// Utility functions based on [`tracing_opentelemetry::OpenTelemetrySpanExt`]. /// diff --git a/docs/operator.md b/docs/operator.md index 286aa64eb..9be133afd 100644 --- a/docs/operator.md +++ b/docs/operator.md @@ -90,6 +90,7 @@ block_builder.build_block ┕━ mempool.revert_expired_transactions ┕━ mempool.revert_transactions ``` + #### Batch building @@ -143,7 +144,7 @@ The exporter can be configured using environment variables as specified in the o > [setup guide](https://docs.honeycomb.io/send-data/opentelemetry/#using-the-honeycomb-opentelemetry-endpoint). ```sh -OTEL_EXPORTER_OTLP_ENDPOINT=api.honeycomb.io:443 \ +OTEL_EXPORTER_OTLP_ENDPOINT=https://api.honeycomb.io:443 \ OTEL_EXPORTER_OTLP_HEADERS="x-honeycomb-team=your-api-key" \ miden-node start --open-telemetry node ```