From b1cf1e0a2a1161cc79996f16b88630f94f5dda53 Mon Sep 17 00:00:00 2001 From: James Date: Mon, 30 Sep 2024 09:49:08 -0400 Subject: [PATCH 01/12] tests: fix more ci only --- crates/provider/Cargo.toml | 1 + crates/provider/src/blocks.rs | 8 - crates/provider/src/ext/admin.rs | 52 +++--- crates/provider/src/ext/debug.rs | 243 ++++++++++++++------------ crates/provider/src/ext/mod.rs | 14 ++ crates/provider/src/ext/net.rs | 46 +++-- crates/provider/src/ext/trace.rs | 96 +++++----- crates/provider/src/ext/txpool.rs | 55 +++--- crates/provider/src/fillers/gas.rs | 12 -- crates/provider/src/provider/trait.rs | 89 +++------- 10 files changed, 308 insertions(+), 308 deletions(-) diff --git a/crates/provider/Cargo.toml b/crates/provider/Cargo.toml index f2fbe59e0d7..fd9c656e1a7 100644 --- a/crates/provider/Cargo.toml +++ b/crates/provider/Cargo.toml @@ -91,6 +91,7 @@ tower-http = { workspace = true, features = [ http-body-util.workspace = true http.workspace = true alloy-rpc-types-engine = { workspace = true, features = ["jwt"] } +ci_info.workspace = true [features] default = ["reqwest", "reqwest-default-tls"] diff --git a/crates/provider/src/blocks.rs b/crates/provider/src/blocks.rs index a6ddede0e7d..05d095fa7d3 100644 --- a/crates/provider/src/blocks.rs +++ b/crates/provider/src/blocks.rs @@ -185,10 +185,6 @@ mod tests { use alloy_primitives::U256; use std::{future::Future, time::Duration}; - fn init_tracing() { - let _ = tracing_subscriber::fmt::try_init(); - } - async fn timeout(future: T) -> T::Output { try_timeout(future).await.expect("Timeout") } @@ -207,8 +203,6 @@ mod tests { yield_block(true).await; } async fn yield_block(ws: bool) { - init_tracing(); - let anvil = Anvil::new().spawn(); let url = if ws { anvil.ws_endpoint() } else { anvil.endpoint() }; @@ -240,8 +234,6 @@ mod tests { // Make sure that we can process more blocks than fits in the cache. const BLOCKS_TO_MINE: usize = BLOCK_CACHE_SIZE.get() + 1; - init_tracing(); - let anvil = Anvil::new().spawn(); let url = if ws { anvil.ws_endpoint() } else { anvil.endpoint() }; diff --git a/crates/provider/src/ext/admin.rs b/crates/provider/src/ext/admin.rs index 87471c8064d..6f2c1a73c8e 100644 --- a/crates/provider/src/ext/admin.rs +++ b/crates/provider/src/ext/admin.rs @@ -86,39 +86,45 @@ where #[cfg(test)] mod test { use super::*; - use crate::ProviderBuilder; + use crate::{ext::test::async_ci_only, ProviderBuilder}; use alloy_node_bindings::{utils::run_with_tempdir, Geth}; #[tokio::test] async fn node_info() { - run_with_tempdir("geth-test-", |temp_dir| async move { - let geth = Geth::new().disable_discovery().data_dir(temp_dir).spawn(); - let provider = ProviderBuilder::new().on_http(geth.endpoint_url()); - let node_info = provider.node_info().await.unwrap(); - assert!(node_info.enode.starts_with("enode://")); + async_ci_only(|| async move { + run_with_tempdir("geth-test-", |temp_dir| async move { + let geth = Geth::new().disable_discovery().data_dir(temp_dir).spawn(); + let provider = ProviderBuilder::new().on_http(geth.endpoint_url()); + let node_info = provider.node_info().await.unwrap(); + assert!(node_info.enode.starts_with("enode://")); + }) + .await; }) .await; } #[tokio::test] async fn admin_peers() { - run_with_tempdir("geth-test-1", |temp_dir_1| async move { - run_with_tempdir("geth-test-2", |temp_dir_2| async move { - let geth1 = Geth::new().disable_discovery().data_dir(&temp_dir_1).spawn(); - let mut geth2 = - Geth::new().disable_discovery().port(0u16).data_dir(&temp_dir_2).spawn(); - - let provider1 = ProviderBuilder::new().on_http(geth1.endpoint_url()); - let provider2 = ProviderBuilder::new().on_http(geth2.endpoint_url()); - let node1_info = provider1.node_info().await.unwrap(); - let node1_id = node1_info.id; - let node1_enode = node1_info.enode; - - let added = provider2.add_peer(&node1_enode).await.unwrap(); - assert!(added); - geth2.wait_to_add_peer(&node1_id).unwrap(); - let peers = provider2.peers().await.unwrap(); - assert_eq!(peers[0].enode, node1_enode); + async_ci_only(|| async move { + run_with_tempdir("geth-test-1", |temp_dir_1| async move { + run_with_tempdir("geth-test-2", |temp_dir_2| async move { + let geth1 = Geth::new().disable_discovery().data_dir(&temp_dir_1).spawn(); + let mut geth2 = + Geth::new().disable_discovery().port(0u16).data_dir(&temp_dir_2).spawn(); + + let provider1 = ProviderBuilder::new().on_http(geth1.endpoint_url()); + let provider2 = ProviderBuilder::new().on_http(geth2.endpoint_url()); + let node1_info = provider1.node_info().await.unwrap(); + let node1_id = node1_info.id; + let node1_enode = node1_info.enode; + + let added = provider2.add_peer(&node1_enode).await.unwrap(); + assert!(added); + geth2.wait_to_add_peer(&node1_id).unwrap(); + let peers = provider2.peers().await.unwrap(); + assert_eq!(peers[0].enode, node1_enode); + }) + .await; }) .await; }) diff --git a/crates/provider/src/ext/debug.rs b/crates/provider/src/ext/debug.rs index 8ad57b7228a..0e726d68991 100644 --- a/crates/provider/src/ext/debug.rs +++ b/crates/provider/src/ext/debug.rs @@ -220,153 +220,166 @@ where #[cfg(test)] mod test { - use crate::{ProviderBuilder, WalletProvider}; - use super::*; + use crate::{ext::test::async_ci_only, ProviderBuilder, WalletProvider}; use alloy_network::TransactionBuilder; use alloy_node_bindings::{utils::run_with_tempdir, Geth, Reth}; use alloy_primitives::{address, U256}; - fn init_tracing() { - let _ = tracing_subscriber::fmt::try_init(); - } - #[tokio::test] async fn test_debug_trace_transaction() { - init_tracing(); - let provider = ProviderBuilder::new().with_recommended_fillers().on_anvil_with_wallet(); - let from = provider.default_signer_address(); - - let gas_price = provider.get_gas_price().await.unwrap(); - let tx = TransactionRequest::default() - .from(from) - .to(address!("deadbeef00000000deadbeef00000000deadbeef")) - .value(U256::from(100)) - .max_fee_per_gas(gas_price + 1) - .max_priority_fee_per_gas(gas_price + 1); - let pending = provider.send_transaction(tx).await.unwrap(); - let receipt = pending.get_receipt().await.unwrap(); - - let hash = receipt.transaction_hash; - let trace_options = GethDebugTracingOptions::default(); - - let trace = provider.debug_trace_transaction(hash, trace_options).await.unwrap(); - - if let GethTrace::Default(trace) = trace { - assert_eq!(trace.gas, 21000) - } + async_ci_only(|| async move { + let provider = ProviderBuilder::new().with_recommended_fillers().on_anvil_with_wallet(); + let from = provider.default_signer_address(); + + let gas_price = provider.get_gas_price().await.unwrap(); + let tx = TransactionRequest::default() + .from(from) + .to(address!("deadbeef00000000deadbeef00000000deadbeef")) + .value(U256::from(100)) + .max_fee_per_gas(gas_price + 1) + .max_priority_fee_per_gas(gas_price + 1); + let pending = provider.send_transaction(tx).await.unwrap(); + let receipt = pending.get_receipt().await.unwrap(); + + let hash = receipt.transaction_hash; + let trace_options = GethDebugTracingOptions::default(); + + let trace = provider.debug_trace_transaction(hash, trace_options).await.unwrap(); + + if let GethTrace::Default(trace) = trace { + assert_eq!(trace.gas, 21000) + } + }) + .await; } #[tokio::test] async fn test_debug_trace_call() { - init_tracing(); - let provider = ProviderBuilder::new().on_anvil_with_wallet(); - let from = provider.default_signer_address(); - let gas_price = provider.get_gas_price().await.unwrap(); - let tx = TransactionRequest::default() - .from(from) - .with_input("0xdeadbeef") - .max_fee_per_gas(gas_price + 1) - .max_priority_fee_per_gas(gas_price + 1); - - let trace = provider - .debug_trace_call( - tx, - BlockNumberOrTag::Latest.into(), - GethDebugTracingCallOptions::default(), - ) - .await - .unwrap(); - - if let GethTrace::Default(trace) = trace { - assert!(!trace.struct_logs.is_empty()); - } + async_ci_only(|| async move { + let provider = ProviderBuilder::new().on_anvil_with_wallet(); + let from = provider.default_signer_address(); + let gas_price = provider.get_gas_price().await.unwrap(); + let tx = TransactionRequest::default() + .from(from) + .with_input("0xdeadbeef") + .max_fee_per_gas(gas_price + 1) + .max_priority_fee_per_gas(gas_price + 1); + + let trace = provider + .debug_trace_call( + tx, + BlockNumberOrTag::Latest.into(), + GethDebugTracingCallOptions::default(), + ) + .await + .unwrap(); + + if let GethTrace::Default(trace) = trace { + assert!(!trace.struct_logs.is_empty()); + } + }) + .await; } #[tokio::test] async fn call_debug_get_raw_header() { - run_with_tempdir("geth-test-", |temp_dir| async move { - let geth = Geth::new().disable_discovery().data_dir(temp_dir).spawn(); - let provider = ProviderBuilder::new().on_http(geth.endpoint_url()); - - let rlp_header = provider - .debug_get_raw_header(BlockId::Number(BlockNumberOrTag::Latest)) - .await - .expect("debug_getRawHeader call should succeed"); - - assert!(!rlp_header.is_empty()); + async_ci_only(|| async move { + run_with_tempdir("geth-test-", |temp_dir| async move { + let geth = Geth::new().disable_discovery().data_dir(temp_dir).spawn(); + let provider = ProviderBuilder::new().on_http(geth.endpoint_url()); + + let rlp_header = provider + .debug_get_raw_header(BlockId::Number(BlockNumberOrTag::Latest)) + .await + .expect("debug_getRawHeader call should succeed"); + + assert!(!rlp_header.is_empty()); + }) + .await; }) - .await + .await; } #[tokio::test] async fn call_debug_get_raw_block() { - run_with_tempdir("geth-test-", |temp_dir| async move { - let geth = Geth::new().disable_discovery().data_dir(temp_dir).spawn(); - let provider = ProviderBuilder::new().on_http(geth.endpoint_url()); - - let rlp_block = provider - .debug_get_raw_block(BlockId::Number(BlockNumberOrTag::Latest)) - .await - .expect("debug_getRawBlock call should succeed"); - - assert!(!rlp_block.is_empty()); + async_ci_only(|| async move { + run_with_tempdir("geth-test-", |temp_dir| async move { + let geth = Geth::new().disable_discovery().data_dir(temp_dir).spawn(); + let provider = ProviderBuilder::new().on_http(geth.endpoint_url()); + + let rlp_block = provider + .debug_get_raw_block(BlockId::Number(BlockNumberOrTag::Latest)) + .await + .expect("debug_getRawBlock call should succeed"); + + assert!(!rlp_block.is_empty()); + }) + .await; }) - .await + .await; } #[tokio::test] async fn call_debug_get_raw_receipts() { - run_with_tempdir("geth-test-", |temp_dir| async move { - let geth = Geth::new().disable_discovery().data_dir(temp_dir).spawn(); - let provider = ProviderBuilder::new().on_http(geth.endpoint_url()); - - let result = - provider.debug_get_raw_receipts(BlockId::Number(BlockNumberOrTag::Latest)).await; - assert!(result.is_ok()); + async_ci_only(|| async move { + run_with_tempdir("geth-test-", |temp_dir| async move { + let geth = Geth::new().disable_discovery().data_dir(temp_dir).spawn(); + let provider = ProviderBuilder::new().on_http(geth.endpoint_url()); + + let result = provider + .debug_get_raw_receipts(BlockId::Number(BlockNumberOrTag::Latest)) + .await; + assert!(result.is_ok()); + }) + .await; }) - .await + .await; } #[tokio::test] async fn call_debug_get_bad_blocks() { - run_with_tempdir("geth-test-", |temp_dir| async move { - let geth = Geth::new().disable_discovery().data_dir(temp_dir).spawn(); - let provider = ProviderBuilder::new().on_http(geth.endpoint_url()); - - let result = provider.debug_get_bad_blocks().await; - assert!(result.is_ok()); + async_ci_only(|| async move { + run_with_tempdir("geth-test-", |temp_dir| async move { + let geth = Geth::new().disable_discovery().data_dir(temp_dir).spawn(); + let provider = ProviderBuilder::new().on_http(geth.endpoint_url()); + + let result = provider.debug_get_bad_blocks().await; + assert!(result.is_ok()); + }) + .await; }) - .await + .await; } #[tokio::test] #[cfg(not(windows))] async fn debug_trace_call_many() { - run_with_tempdir("reth-test-", |temp_dir| async move { - let reth = Reth::new().dev().disable_discovery().data_dir(temp_dir).spawn(); - let provider = - ProviderBuilder::new().with_recommended_fillers().on_http(reth.endpoint_url()); - - let tx1 = TransactionRequest::default() - .with_from(address!("0000000000000000000000000000000000000123")) - .with_to(address!("0000000000000000000000000000000000000456")); - - let tx2 = TransactionRequest::default() - .with_from(address!("0000000000000000000000000000000000000456")) - .with_to(address!("0000000000000000000000000000000000000789")); - - let bundles = vec![Bundle { transactions: vec![tx1, tx2], block_override: None }]; - let state_context = StateContext::default(); - let trace_options = GethDebugTracingCallOptions::default(); - let result = - provider.debug_trace_call_many(bundles, state_context, trace_options).await; - assert!(result.is_ok()); - - let traces = result.unwrap(); - assert_eq!( - serde_json::to_string_pretty(&traces).unwrap().trim(), - r#" + async_ci_only(|| async move { + run_with_tempdir("reth-test-", |temp_dir| async move { + let reth = Reth::new().dev().disable_discovery().data_dir(temp_dir).spawn(); + let provider = + ProviderBuilder::new().with_recommended_fillers().on_http(reth.endpoint_url()); + + let tx1 = TransactionRequest::default() + .with_from(address!("0000000000000000000000000000000000000123")) + .with_to(address!("0000000000000000000000000000000000000456")); + + let tx2 = TransactionRequest::default() + .with_from(address!("0000000000000000000000000000000000000456")) + .with_to(address!("0000000000000000000000000000000000000789")); + + let bundles = vec![Bundle { transactions: vec![tx1, tx2], block_override: None }]; + let state_context = StateContext::default(); + let trace_options = GethDebugTracingCallOptions::default(); + let result = + provider.debug_trace_call_many(bundles, state_context, trace_options).await; + assert!(result.is_ok()); + + let traces = result.unwrap(); + assert_eq!( + serde_json::to_string_pretty(&traces).unwrap().trim(), + r#" [ [ { @@ -384,9 +397,11 @@ mod test { ] ] "# - .trim(), - ); + .trim(), + ); + }) + .await; }) - .await + .await; } } diff --git a/crates/provider/src/ext/mod.rs b/crates/provider/src/ext/mod.rs index a5f5c185a20..7358fe0010e 100644 --- a/crates/provider/src/ext/mod.rs +++ b/crates/provider/src/ext/mod.rs @@ -44,3 +44,17 @@ pub use txpool::TxPoolApi; mod erc4337; #[cfg(feature = "erc4337-api")] pub use erc4337::Erc4337Api; + +#[cfg(test)] +pub(crate) mod test { + /// Run the given function only if we are in a CI environment. + pub(crate) async fn async_ci_only(f: F) + where + F: FnOnce() -> Fut, + Fut: std::future::Future, + { + if ci_info::is_ci() { + f().await; + } + } +} diff --git a/crates/provider/src/ext/net.rs b/crates/provider/src/ext/net.rs index 633d58fd59e..449f1772ed4 100644 --- a/crates/provider/src/ext/net.rs +++ b/crates/provider/src/ext/net.rs @@ -38,44 +38,54 @@ where #[cfg(test)] mod test { - use crate::ProviderBuilder; - use super::*; + use crate::{ext::test::async_ci_only, ProviderBuilder}; use alloy_node_bindings::{utils::run_with_tempdir, Geth}; #[tokio::test] async fn call_net_version() { - run_with_tempdir("geth-test-", |temp_dir| async move { - let geth = Geth::new().disable_discovery().data_dir(temp_dir).spawn(); - let provider = ProviderBuilder::new().on_http(geth.endpoint_url()); + async_ci_only(|| async move { + run_with_tempdir("geth-test-", |temp_dir| async move { + let geth = Geth::new().disable_discovery().data_dir(temp_dir).spawn(); + let provider = ProviderBuilder::new().on_http(geth.endpoint_url()); - let version = provider.net_version().await.expect("net_version call should succeed"); - assert_eq!(version, 1); + let version = + provider.net_version().await.expect("net_version call should succeed"); + assert_eq!(version, 1); + }) + .await; }) .await; } #[tokio::test] async fn call_net_peer_count() { - run_with_tempdir("geth-test-", |temp_dir| async move { - let geth = Geth::new().disable_discovery().data_dir(temp_dir).spawn(); - let provider = ProviderBuilder::new().on_http(geth.endpoint_url()); + async_ci_only(|| async move { + run_with_tempdir("geth-test-", |temp_dir| async move { + let geth = Geth::new().disable_discovery().data_dir(temp_dir).spawn(); + let provider = ProviderBuilder::new().on_http(geth.endpoint_url()); - let count = provider.net_peer_count().await.expect("net_peerCount call should succeed"); - assert_eq!(count, 0); + let count = + provider.net_peer_count().await.expect("net_peerCount call should succeed"); + assert_eq!(count, 0); + }) + .await; }) .await; } #[tokio::test] async fn call_net_listening() { - run_with_tempdir("geth-test-", |temp_dir| async move { - let geth = Geth::new().disable_discovery().data_dir(temp_dir).spawn(); - let provider = ProviderBuilder::new().on_http(geth.endpoint_url()); + async_ci_only(|| async move { + run_with_tempdir("geth-test-", |temp_dir| async move { + let geth = Geth::new().disable_discovery().data_dir(temp_dir).spawn(); + let provider = ProviderBuilder::new().on_http(geth.endpoint_url()); - let listening = - provider.net_listening().await.expect("net_listening call should succeed"); - assert!(listening); + let listening = + provider.net_listening().await.expect("net_listening call should succeed"); + assert!(listening); + }) + .await; }) .await; } diff --git a/crates/provider/src/ext/trace.rs b/crates/provider/src/ext/trace.rs index 58944e8963d..35e1caa825d 100644 --- a/crates/provider/src/ext/trace.rs +++ b/crates/provider/src/ext/trace.rs @@ -176,22 +176,16 @@ where #[cfg(test)] mod test { - use crate::ProviderBuilder; + use super::*; + use crate::{ext::test::async_ci_only, ProviderBuilder}; use alloy_eips::BlockNumberOrTag; use alloy_network::TransactionBuilder; use alloy_node_bindings::{utils::run_with_tempdir, Reth}; use alloy_primitives::address; use alloy_rpc_types_eth::TransactionRequest; - use super::*; - - fn init_tracing() { - let _ = tracing_subscriber::fmt::try_init(); - } - #[tokio::test] async fn trace_block() { - init_tracing(); let provider = ProviderBuilder::new().on_anvil(); let traces = provider.trace_block(BlockId::Number(BlockNumberOrTag::Latest)).await.unwrap(); assert_eq!(traces.len(), 0); @@ -200,21 +194,22 @@ mod test { #[tokio::test] #[cfg(not(windows))] async fn trace_call() { - run_with_tempdir("reth-test-", |temp_dir| async move { - let reth = Reth::new().dev().disable_discovery().data_dir(temp_dir).spawn(); - let provider = ProviderBuilder::new().on_http(reth.endpoint_url()); - - let tx = TransactionRequest::default() - .with_from(address!("0000000000000000000000000000000000000123")) - .with_to(address!("0000000000000000000000000000000000000456")); - - let result = provider.trace_call(&tx, &[TraceType::Trace]).await; - assert!(result.is_ok()); - - let traces = result.unwrap(); - assert_eq!( - serde_json::to_string_pretty(&traces).unwrap().trim(), - r#" + async_ci_only(|| async move { + run_with_tempdir("reth-test-", |temp_dir| async move { + let reth = Reth::new().dev().disable_discovery().data_dir(temp_dir).spawn(); + let provider = ProviderBuilder::new().on_http(reth.endpoint_url()); + + let tx = TransactionRequest::default() + .with_from(address!("0000000000000000000000000000000000000123")) + .with_to(address!("0000000000000000000000000000000000000456")); + + let result = provider.trace_call(&tx, &[TraceType::Trace]).await; + assert!(result.is_ok()); + + let traces = result.unwrap(); + assert_eq!( + serde_json::to_string_pretty(&traces).unwrap().trim(), + r#" { "output": "0x", "stateDiff": null, @@ -240,8 +235,10 @@ mod test { "vmTrace": null } "# - .trim(), - ); + .trim(), + ); + }) + .await; }) .await; } @@ -249,27 +246,28 @@ mod test { #[tokio::test] #[cfg(not(windows))] async fn trace_call_many() { - run_with_tempdir("reth-test-", |temp_dir| async move { - let reth = Reth::new().dev().disable_discovery().data_dir(temp_dir).spawn(); - let provider = ProviderBuilder::new().on_http(reth.endpoint_url()); - - let tx1 = TransactionRequest::default() - .with_from(address!("0000000000000000000000000000000000000123")) - .with_to(address!("0000000000000000000000000000000000000456")); - - let tx2 = TransactionRequest::default() - .with_from(address!("0000000000000000000000000000000000000456")) - .with_to(address!("0000000000000000000000000000000000000789")); - - let result = provider - .trace_call_many(&[(tx1, &[TraceType::Trace]), (tx2, &[TraceType::Trace])]) - .await; - assert!(result.is_ok()); - - let traces = result.unwrap(); - assert_eq!( - serde_json::to_string_pretty(&traces).unwrap().trim(), - r#" + async_ci_only(|| async move { + run_with_tempdir("reth-test-", |temp_dir| async move { + let reth = Reth::new().dev().disable_discovery().data_dir(temp_dir).spawn(); + let provider = ProviderBuilder::new().on_http(reth.endpoint_url()); + + let tx1 = TransactionRequest::default() + .with_from(address!("0000000000000000000000000000000000000123")) + .with_to(address!("0000000000000000000000000000000000000456")); + + let tx2 = TransactionRequest::default() + .with_from(address!("0000000000000000000000000000000000000456")) + .with_to(address!("0000000000000000000000000000000000000789")); + + let result = provider + .trace_call_many(&[(tx1, &[TraceType::Trace]), (tx2, &[TraceType::Trace])]) + .await; + assert!(result.is_ok()); + + let traces = result.unwrap(); + assert_eq!( + serde_json::to_string_pretty(&traces).unwrap().trim(), + r#" [ { "output": "0x", @@ -321,8 +319,10 @@ mod test { } ] "# - .trim() - ); + .trim() + ); + }) + .await; }) .await; } diff --git a/crates/provider/src/ext/txpool.rs b/crates/provider/src/ext/txpool.rs index 5326ae5922c..0250c2f468b 100644 --- a/crates/provider/src/ext/txpool.rs +++ b/crates/provider/src/ext/txpool.rs @@ -75,51 +75,62 @@ where #[cfg(test)] mod tests { - use crate::ProviderBuilder; - use super::*; + use crate::{ext::test::async_ci_only, ProviderBuilder}; use alloy_node_bindings::{utils::run_with_tempdir, Geth}; #[tokio::test] async fn test_txpool_content() { - run_with_tempdir("geth-test-", |temp_dir| async move { - let geth = Geth::new().disable_discovery().data_dir(temp_dir).spawn(); - let provider = ProviderBuilder::new().on_http(geth.endpoint_url()); - let content = provider.txpool_content().await.unwrap(); - assert_eq!(content, TxpoolContent::default()); + async_ci_only(|| async move { + run_with_tempdir("geth-test-", |temp_dir| async move { + let geth = Geth::new().disable_discovery().data_dir(temp_dir).spawn(); + let provider = ProviderBuilder::new().on_http(geth.endpoint_url()); + let content = provider.txpool_content().await.unwrap(); + assert_eq!(content, TxpoolContent::default()); + }) + .await; }) .await; } #[tokio::test] async fn test_txpool_content_from() { - run_with_tempdir("geth-test-", |temp_dir| async move { - let geth = Geth::new().disable_discovery().data_dir(temp_dir).spawn(); - let provider = ProviderBuilder::new().on_http(geth.endpoint_url()); - let content = provider.txpool_content_from(Address::default()).await.unwrap(); - assert_eq!(content, TxpoolContentFrom::default()); + async_ci_only(|| async move { + run_with_tempdir("geth-test-", |temp_dir| async move { + let geth = Geth::new().disable_discovery().data_dir(temp_dir).spawn(); + let provider = ProviderBuilder::new().on_http(geth.endpoint_url()); + let content = provider.txpool_content_from(Address::default()).await.unwrap(); + assert_eq!(content, TxpoolContentFrom::default()); + }) + .await; }) .await; } #[tokio::test] async fn test_txpool_inspect() { - run_with_tempdir("geth-test-", |temp_dir| async move { - let geth = Geth::new().disable_discovery().data_dir(temp_dir).spawn(); - let provider = ProviderBuilder::new().on_http(geth.endpoint_url()); - let content = provider.txpool_inspect().await.unwrap(); - assert_eq!(content, TxpoolInspect::default()); + async_ci_only(|| async move { + run_with_tempdir("geth-test-", |temp_dir| async move { + let geth = Geth::new().disable_discovery().data_dir(temp_dir).spawn(); + let provider = ProviderBuilder::new().on_http(geth.endpoint_url()); + let content = provider.txpool_inspect().await.unwrap(); + assert_eq!(content, TxpoolInspect::default()); + }) + .await; }) .await; } #[tokio::test] async fn test_txpool_status() { - run_with_tempdir("geth-test-", |temp_dir| async move { - let geth = Geth::new().disable_discovery().data_dir(temp_dir).spawn(); - let provider = ProviderBuilder::new().on_http(geth.endpoint_url()); - let content = provider.txpool_status().await.unwrap(); - assert_eq!(content, TxpoolStatus::default()); + async_ci_only(|| async move { + run_with_tempdir("geth-test-", |temp_dir| async move { + let geth = Geth::new().disable_discovery().data_dir(temp_dir).spawn(); + let provider = ProviderBuilder::new().on_http(geth.endpoint_url()); + let content = provider.txpool_status().await.unwrap(); + assert_eq!(content, TxpoolStatus::default()); + }) + .await; }) .await; } diff --git a/crates/provider/src/fillers/gas.rs b/crates/provider/src/fillers/gas.rs index ab92b4c5a21..ec79d56a820 100644 --- a/crates/provider/src/fillers/gas.rs +++ b/crates/provider/src/fillers/gas.rs @@ -257,14 +257,8 @@ mod tests { use alloy_primitives::{address, U256}; use alloy_rpc_types_eth::TransactionRequest; - fn init_tracing() { - let _ = tracing_subscriber::fmt::try_init(); - } - #[tokio::test] async fn no_gas_price_or_limit() { - init_tracing(); - let provider = ProviderBuilder::new().with_recommended_fillers().on_anvil_with_wallet(); // GasEstimationLayer requires chain_id to be set to handle EIP-1559 tx @@ -285,8 +279,6 @@ mod tests { #[tokio::test] async fn no_gas_limit() { - init_tracing(); - let provider = ProviderBuilder::new().with_recommended_fillers().on_anvil_with_wallet(); let gas_price = provider.get_gas_price().await.unwrap(); @@ -306,8 +298,6 @@ mod tests { #[tokio::test] async fn no_max_fee_per_blob_gas() { - init_tracing(); - let provider = ProviderBuilder::new().with_recommended_fillers().on_anvil_with_wallet(); let sidecar: SidecarBuilder = SidecarBuilder::from_slice(b"Hello World"); @@ -335,8 +325,6 @@ mod tests { #[tokio::test] async fn zero_max_fee_per_blob_gas() { - init_tracing(); - let provider = ProviderBuilder::new().with_recommended_fillers().on_anvil_with_wallet(); let sidecar: SidecarBuilder = SidecarBuilder::from_slice(b"Hello World"); diff --git a/crates/provider/src/provider/trait.rs b/crates/provider/src/provider/trait.rs index 6563997c81a..06a8c7ad4b6 100644 --- a/crates/provider/src/provider/trait.rs +++ b/crates/provider/src/provider/trait.rs @@ -1054,13 +1054,8 @@ mod tests { #[cfg(feature = "hyper")] use tower::{Layer, Service}; - fn init_tracing() { - let _ = tracing_subscriber::fmt::try_init(); - } - #[tokio::test] async fn test_provider_builder() { - init_tracing(); let provider = RootProvider::::builder().with_recommended_fillers().on_anvil(); let num = provider.get_block_number().await.unwrap(); @@ -1069,7 +1064,6 @@ mod tests { #[tokio::test] async fn test_builder_helper_fn() { - init_tracing(); let provider = builder().with_recommended_fillers().on_anvil(); let num = provider.get_block_number().await.unwrap(); assert_eq!(0, num); @@ -1078,7 +1072,6 @@ mod tests { #[cfg(feature = "hyper")] #[tokio::test] async fn test_default_hyper_transport() { - init_tracing(); let anvil = Anvil::new().spawn(); let hyper_t = alloy_transport_http::HyperTransport::new_hyper(anvil.endpoint_url()); @@ -1141,7 +1134,6 @@ mod tests { use tower_http::{ sensitive_headers::SetSensitiveRequestHeadersLayer, set_header::SetRequestHeaderLayer, }; - init_tracing(); let anvil = Anvil::new().spawn(); let hyper_client = Client::builder(TokioExecutor::new()).build_http::>(); @@ -1187,43 +1179,46 @@ mod tests { #[cfg(all(feature = "hyper", not(windows)))] #[tokio::test] async fn test_auth_layer_transport() { - use alloy_node_bindings::Reth; - use alloy_rpc_types_engine::JwtSecret; - use alloy_transport_http::{AuthLayer, AuthService, Http, HyperClient}; + crate::ext::test::async_ci_only(|| async move { + use alloy_node_bindings::Reth; + use alloy_rpc_types_engine::JwtSecret; + use alloy_transport_http::{AuthLayer, AuthService, Http, HyperClient}; - init_tracing(); - let secret = JwtSecret::random(); + let secret = JwtSecret::random(); - let reth = Reth::new().arg("--rpc.jwtsecret").arg(hex::encode(secret.as_bytes())).spawn(); + let reth = + Reth::new().arg("--rpc.jwtsecret").arg(hex::encode(secret.as_bytes())).spawn(); - let hyper_client = Client::builder(TokioExecutor::new()).build_http::>(); + let hyper_client = + Client::builder(TokioExecutor::new()).build_http::>(); - let service = - tower::ServiceBuilder::new().layer(AuthLayer::new(secret)).service(hyper_client); + let service = + tower::ServiceBuilder::new().layer(AuthLayer::new(secret)).service(hyper_client); - let layer_transport: HyperClient< - Full, - AuthService< - Client< - alloy_transport_http::hyper_util::client::legacy::connect::HttpConnector, - Full, + let layer_transport: HyperClient< + Full, + AuthService< + Client< + alloy_transport_http::hyper_util::client::legacy::connect::HttpConnector, + Full, + >, >, - >, - > = HyperClient::with_service(service); + > = HyperClient::with_service(service); - let http_hyper = Http::with_client(layer_transport, reth.endpoint_url()); + let http_hyper = Http::with_client(layer_transport, reth.endpoint_url()); - let rpc_client = alloy_rpc_client::RpcClient::new(http_hyper, true); + let rpc_client = alloy_rpc_client::RpcClient::new(http_hyper, true); - let provider = RootProvider::<_, Ethereum>::new(rpc_client); + let provider = RootProvider::<_, Ethereum>::new(rpc_client); - let num = provider.get_block_number().await.unwrap(); - assert_eq!(0, num); + let num = provider.get_block_number().await.unwrap(); + assert_eq!(0, num); + }) + .await; } #[tokio::test] async fn test_builder_helper_fn_any_network() { - init_tracing(); let anvil = Anvil::new().spawn(); let provider = builder::().with_recommended_fillers().on_http(anvil.endpoint_url()); @@ -1234,7 +1229,6 @@ mod tests { #[cfg(feature = "reqwest")] #[tokio::test] async fn object_safety() { - init_tracing(); let provider = ProviderBuilder::new().on_anvil(); // These blocks are not necessary. @@ -1270,8 +1264,6 @@ mod tests { #[cfg(feature = "ws")] #[tokio::test] async fn subscribe_blocks_http() { - init_tracing(); - let provider = ProviderBuilder::new().on_anvil_with_config(|a| a.block_time(1)); let err = provider.subscribe_blocks().await.unwrap_err(); @@ -1300,7 +1292,6 @@ mod tests { async fn subscribe_blocks_ws() { use futures::stream::StreamExt; - init_tracing(); let anvil = Anvil::new().block_time(1).spawn(); let ws = alloy_rpc_client::WsConnect::new(anvil.ws_endpoint()); let client = alloy_rpc_client::RpcClient::connect_pubsub(ws).await.unwrap(); @@ -1321,7 +1312,6 @@ mod tests { async fn subscribe_blocks_ws_boxed() { use futures::stream::StreamExt; - init_tracing(); let anvil = Anvil::new().block_time(1).spawn(); let ws = alloy_rpc_client::WsConnect::new(anvil.ws_endpoint()); let client = alloy_rpc_client::RpcClient::connect_pubsub(ws).await.unwrap(); @@ -1343,7 +1333,6 @@ mod tests { async fn subscribe_blocks_ws_remote() { use futures::stream::StreamExt; - init_tracing(); let url = "wss://eth-mainnet.g.alchemy.com/v2/viFmeVzhg6bWKVMIWWS8MhmzREB-D4f7"; let ws = alloy_rpc_client::WsConnect::new(url); let Ok(client) = alloy_rpc_client::RpcClient::connect_pubsub(ws).await else { return }; @@ -1358,7 +1347,6 @@ mod tests { #[tokio::test] async fn test_send_tx() { - init_tracing(); let provider = ProviderBuilder::new().on_anvil(); let tx = TransactionRequest { value: Some(U256::from(100)), @@ -1382,7 +1370,6 @@ mod tests { #[tokio::test] async fn test_watch_confirmed_tx() { - init_tracing(); let provider = ProviderBuilder::new().on_anvil(); let tx = TransactionRequest { value: Some(U256::from(100)), @@ -1430,7 +1417,6 @@ mod tests { #[tokio::test] async fn gets_block_number() { - init_tracing(); let provider = ProviderBuilder::new().on_anvil(); let num = provider.get_block_number().await.unwrap(); assert_eq!(0, num) @@ -1438,7 +1424,6 @@ mod tests { #[tokio::test] async fn gets_block_number_with_raw_req() { - init_tracing(); let provider = ProviderBuilder::new().on_anvil(); let num: U64 = provider.raw_request("eth_blockNumber".into(), NoParams::default()).await.unwrap(); @@ -1448,7 +1433,6 @@ mod tests { #[cfg(feature = "anvil-api")] #[tokio::test] async fn gets_transaction_count() { - init_tracing(); let provider = ProviderBuilder::new().on_anvil(); let accounts = provider.get_accounts().await.unwrap(); let sender = accounts[0]; @@ -1479,7 +1463,6 @@ mod tests { #[tokio::test] async fn gets_block_by_hash() { - init_tracing(); let provider = ProviderBuilder::new().on_anvil(); let num = 0; let tag: BlockNumberOrTag = num.into(); @@ -1492,7 +1475,6 @@ mod tests { #[tokio::test] async fn gets_block_by_hash_with_raw_req() { - init_tracing(); let provider = ProviderBuilder::new().on_anvil(); let num = 0; let tag: BlockNumberOrTag = num.into(); @@ -1507,7 +1489,6 @@ mod tests { #[tokio::test] async fn gets_block_by_number_full() { - init_tracing(); let provider = ProviderBuilder::new().on_anvil(); let num = 0; let tag: BlockNumberOrTag = num.into(); @@ -1517,7 +1498,6 @@ mod tests { #[tokio::test] async fn gets_block_by_number() { - init_tracing(); let provider = ProviderBuilder::new().on_anvil(); let num = 0; let tag: BlockNumberOrTag = num.into(); @@ -1527,7 +1507,6 @@ mod tests { #[tokio::test] async fn gets_client_version() { - init_tracing(); let provider = ProviderBuilder::new().on_anvil(); let version = provider.get_client_version().await.unwrap(); assert!(version.contains("anvil"), "{version}"); @@ -1535,7 +1514,6 @@ mod tests { #[tokio::test] async fn gets_sha3() { - init_tracing(); let provider = ProviderBuilder::new().on_anvil(); let data = b"alloy"; let hash = provider.get_sha3(data).await.unwrap(); @@ -1563,7 +1541,6 @@ mod tests { #[tokio::test] async fn gets_storage_at() { - init_tracing(); let provider = ProviderBuilder::new().on_anvil(); let addr = Address::with_last_byte(16); let storage = provider.get_storage_at(addr, U256::ZERO).await.unwrap(); @@ -1572,8 +1549,6 @@ mod tests { #[tokio::test] async fn gets_transaction_by_hash_not_found() { - init_tracing(); - let provider = ProviderBuilder::new().on_anvil(); let tx_hash = b256!("5c03fab9114ceb98994b43892ade87ddfd9ae7e8f293935c3bd29d435dc9fd95"); let tx = provider.get_transaction_by_hash(tx_hash).await.expect("failed to fetch tx"); @@ -1583,7 +1558,6 @@ mod tests { #[tokio::test] async fn gets_transaction_by_hash() { - init_tracing(); let provider = ProviderBuilder::new().with_recommended_fillers().on_anvil_with_wallet(); let req = TransactionRequest::default() @@ -1605,7 +1579,6 @@ mod tests { #[tokio::test] #[ignore] async fn gets_logs() { - init_tracing(); let provider = ProviderBuilder::new().on_anvil(); let filter = Filter::new() .at_block_hash(b256!( @@ -1621,7 +1594,6 @@ mod tests { #[tokio::test] #[ignore] async fn gets_tx_receipt() { - init_tracing(); let provider = ProviderBuilder::new().on_anvil(); let receipt = provider .get_transaction_receipt(b256!( @@ -1639,14 +1611,12 @@ mod tests { #[tokio::test] async fn gets_max_priority_fee_per_gas() { - init_tracing(); let provider = ProviderBuilder::new().on_anvil(); let _fee = provider.get_max_priority_fee_per_gas().await.unwrap(); } #[tokio::test] async fn gets_fee_history() { - init_tracing(); let provider = ProviderBuilder::new().on_anvil(); let block_number = provider.get_block_number().await.unwrap(); let fee_history = provider @@ -1662,7 +1632,6 @@ mod tests { #[tokio::test] async fn gets_block_receipts() { - init_tracing(); let provider = ProviderBuilder::new().on_anvil(); let receipts = provider.get_block_receipts(BlockId::Number(BlockNumberOrTag::Latest)).await.unwrap(); @@ -1671,7 +1640,6 @@ mod tests { #[tokio::test] async fn sends_raw_transaction() { - init_tracing(); let provider = ProviderBuilder::new().on_anvil(); let pending = provider .send_raw_transaction( @@ -1687,7 +1655,6 @@ mod tests { #[tokio::test] async fn connect_boxed() { - init_tracing(); let anvil = Anvil::new().spawn(); let provider = @@ -1710,7 +1677,6 @@ mod tests { #[tokio::test] async fn builtin_connect_boxed() { - init_tracing(); let anvil = Anvil::new().spawn(); let conn: BuiltInConnectionString = anvil.endpoint().parse().unwrap(); @@ -1727,7 +1693,6 @@ mod tests { #[tokio::test] async fn test_uncle_count() { - init_tracing(); let provider = ProviderBuilder::new().on_anvil(); let count = provider.get_uncle_count(0.into()).await.unwrap(); @@ -1744,7 +1709,6 @@ mod tests { use alloy_network::TransactionBuilder; use alloy_sol_types::SolValue; - init_tracing(); let url = "https://eth-mainnet.alchemyapi.io/v2/jGiK5vwDfC3F4r0bqukm-W2GqgdrxdSr"; let provider = ProviderBuilder::new().on_http(url.parse().unwrap()); let req = TransactionRequest::default() @@ -1759,7 +1723,6 @@ mod tests { #[tokio::test] async fn test_empty_transactions() { - init_tracing(); let provider = ProviderBuilder::new().on_anvil(); let block = provider.get_block_by_number(0.into(), false).await.unwrap().unwrap(); From 1bba5f99ed49e93ca5dfcd9ff6244483b7e90577 Mon Sep 17 00:00:00 2001 From: James Date: Mon, 30 Sep 2024 10:00:40 -0400 Subject: [PATCH 02/12] lint: allow dead --- crates/provider/src/ext/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/provider/src/ext/mod.rs b/crates/provider/src/ext/mod.rs index 7358fe0010e..6d7a3637e3a 100644 --- a/crates/provider/src/ext/mod.rs +++ b/crates/provider/src/ext/mod.rs @@ -47,6 +47,7 @@ pub use erc4337::Erc4337Api; #[cfg(test)] pub(crate) mod test { + #[allow(dead_code)] // dead only when all features off /// Run the given function only if we are in a CI environment. pub(crate) async fn async_ci_only(f: F) where From 0504c7b3ccdb9a37d0aaf0ef5d1fb8abe163b9c6 Mon Sep 17 00:00:00 2001 From: James Date: Wed, 25 Sep 2024 19:21:45 -0400 Subject: [PATCH 03/12] feat: errors for responses --- crates/json-rpc/src/response/error.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/json-rpc/src/response/error.rs b/crates/json-rpc/src/response/error.rs index 851e694913b..69a081d0591 100644 --- a/crates/json-rpc/src/response/error.rs +++ b/crates/json-rpc/src/response/error.rs @@ -132,7 +132,7 @@ where T: std::error::Error + RpcObject, { fn from(value: T) -> Self { - Self { code: -32603, message: INTERNAL_ERROR, data: Some(value) } + Self { code: -32603, message: INTERNAL_ERROR, data: Some(value.into()) } } } From cd23a9eefc6c72b7ef289e903c8861cd55429041 Mon Sep 17 00:00:00 2001 From: James Date: Wed, 25 Sep 2024 19:28:25 -0400 Subject: [PATCH 04/12] lint: clippy --- crates/json-rpc/src/response/error.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/json-rpc/src/response/error.rs b/crates/json-rpc/src/response/error.rs index 69a081d0591..851e694913b 100644 --- a/crates/json-rpc/src/response/error.rs +++ b/crates/json-rpc/src/response/error.rs @@ -132,7 +132,7 @@ where T: std::error::Error + RpcObject, { fn from(value: T) -> Self { - Self { code: -32603, message: INTERNAL_ERROR, data: Some(value.into()) } + Self { code: -32603, message: INTERNAL_ERROR, data: Some(value) } } } From a078988ea1fa38a9397fe8801520e0c7a1f963ad Mon Sep 17 00:00:00 2001 From: James Date: Tue, 24 Sep 2024 12:51:18 -0400 Subject: [PATCH 05/12] feat: axum extractor for request --- crates/json-rpc/Cargo.toml | 6 +++++ crates/json-rpc/src/lib.rs | 2 ++ crates/json-rpc/src/support/axum.rs | 41 +++++++++++++++++++++++++++++ crates/json-rpc/src/support/mod.rs | 2 ++ 4 files changed, 51 insertions(+) create mode 100644 crates/json-rpc/src/support/axum.rs create mode 100644 crates/json-rpc/src/support/mod.rs diff --git a/crates/json-rpc/Cargo.toml b/crates/json-rpc/Cargo.toml index dfc18b79d22..4e26fe4eb4d 100644 --- a/crates/json-rpc/Cargo.toml +++ b/crates/json-rpc/Cargo.toml @@ -25,3 +25,9 @@ serde_json = { workspace = true, features = ["std", "raw_value"] } thiserror.workspace = true tracing.workspace = true alloy-sol-types.workspace = true + +async-trait = { workspace = true, optional = true } +axum = { version = "0.7.6", features = ["json"], optional = true } + +[features] +axum = ["dep:axum"] \ No newline at end of file diff --git a/crates/json-rpc/src/lib.rs b/crates/json-rpc/src/lib.rs index c705b72e7ba..47c33ad8711 100644 --- a/crates/json-rpc/src/lib.rs +++ b/crates/json-rpc/src/lib.rs @@ -94,6 +94,8 @@ pub use packet::{BorrowedResponsePacket, RequestPacket, ResponsePacket}; mod request; pub use request::{PartiallySerializedRequest, Request, RequestMeta, SerializedRequest}; +mod support; + mod response; pub use response::{ BorrowedErrorPayload, BorrowedResponse, BorrowedResponsePayload, ErrorPayload, Response, diff --git a/crates/json-rpc/src/support/axum.rs b/crates/json-rpc/src/support/axum.rs new file mode 100644 index 00000000000..386ffff4bd4 --- /dev/null +++ b/crates/json-rpc/src/support/axum.rs @@ -0,0 +1,41 @@ +use crate::{ErrorPayload, Id, Request, Response, ResponsePayload, RpcObject}; +use axum::extract; + +impl From for Response<(), ()> { + fn from(value: extract::rejection::JsonRejection) -> Self { + Response { + id: Id::None, + payload: ResponsePayload::Failure(ErrorPayload { + code: -32600, + message: value.to_string(), + data: None, + }), + } + } +} + +impl axum::response::IntoResponse for Response +where + Payload: RpcObject, + ErrData: RpcObject, +{ + fn into_response(self) -> axum::response::Response { + axum::response::IntoResponse::into_response(axum::response::Json(self)) + } +} + +#[async_trait::async_trait] +impl extract::FromRequest for Request +where + axum::body::Bytes: extract::FromRequest, + Params: RpcObject, + S: Send + Sync, +{ + type Rejection = Response<(), ()>; + + async fn from_request(req: extract::Request, state: &S) -> Result { + let json = extract::Json::>::from_request(req, state).await?; + + Ok(json.0) + } +} diff --git a/crates/json-rpc/src/support/mod.rs b/crates/json-rpc/src/support/mod.rs new file mode 100644 index 00000000000..627b1ec10f9 --- /dev/null +++ b/crates/json-rpc/src/support/mod.rs @@ -0,0 +1,2 @@ +#[cfg(feature = "axum")] +mod axum; From 0ced16b98ac63b30d1fce051e65269dac97da62b Mon Sep 17 00:00:00 2001 From: James Date: Tue, 24 Sep 2024 12:51:52 -0400 Subject: [PATCH 06/12] nit: newline at eof --- crates/json-rpc/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/json-rpc/Cargo.toml b/crates/json-rpc/Cargo.toml index 4e26fe4eb4d..552992835b0 100644 --- a/crates/json-rpc/Cargo.toml +++ b/crates/json-rpc/Cargo.toml @@ -30,4 +30,4 @@ async-trait = { workspace = true, optional = true } axum = { version = "0.7.6", features = ["json"], optional = true } [features] -axum = ["dep:axum"] \ No newline at end of file +axum = ["dep:axum"] From 8f69d7a6df2447ff756b3941063693715f408105 Mon Sep 17 00:00:00 2001 From: James Date: Wed, 25 Sep 2024 19:22:19 -0400 Subject: [PATCH 07/12] feat: router --- Cargo.toml | 1 + crates/json-rpc-router/Cargo.toml | 29 +++ crates/json-rpc-router/src/lib.rs | 417 ++++++++++++++++++++++++++++++ 3 files changed, 447 insertions(+) create mode 100644 crates/json-rpc-router/Cargo.toml create mode 100644 crates/json-rpc-router/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index c040927dbf2..eeef8a0009f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,6 +41,7 @@ alloy-eips = { version = "0.4", path = "crates/eips", default-features = false } alloy-eip7547 = { version = "0.4", path = "crates/eip7547", default-features = false } alloy-genesis = { version = "0.4", path = "crates/genesis", default-features = false } alloy-json-rpc = { version = "0.4", path = "crates/json-rpc", default-features = false } +alloy-json-rpc-router = { version = "0.3", path = "crates/json-rpc-router", default-features = false } alloy-network = { version = "0.4", path = "crates/network", default-features = false } alloy-network-primitives = { version = "0.4", path = "crates/network-primitives", default-features = false } alloy-node-bindings = { version = "0.4", path = "crates/node-bindings", default-features = false } diff --git a/crates/json-rpc-router/Cargo.toml b/crates/json-rpc-router/Cargo.toml new file mode 100644 index 00000000000..c957d88614a --- /dev/null +++ b/crates/json-rpc-router/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "alloy-json-rpc-router" +description = "Basic JSON-RPC 2.0 clients" + +version.workspace = true +edition.workspace = true +rust-version.workspace = true +authors.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +exclude.workspace = true + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] + +[lints] +workspace = true + +[dependencies] +alloy-json-rpc.workspace = true + +async-trait = { workspace = true, optional = true } +serde_json = { version = "1.0.128" } +tower.workspace = true + +[dev-dependencies] +tokio = { workspace = true, features = ["macros", "test-util"] } diff --git a/crates/json-rpc-router/src/lib.rs b/crates/json-rpc-router/src/lib.rs new file mode 100644 index 00000000000..0f91a2870be --- /dev/null +++ b/crates/json-rpc-router/src/lib.rs @@ -0,0 +1,417 @@ +//! JSON-RPC router inspired by axum's `Router`. + +#![doc( + html_logo_url = "https://raw.githubusercontent.com/alloy-rs/core/main/assets/alloy.jpg", + html_favicon_url = "https://raw.githubusercontent.com/alloy-rs/core/main/assets/favicon.ico" +)] +#![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] + +use alloy_json_rpc::{ + PartiallySerializedRequest, Request, RequestMeta, Response, ResponsePayload, RpcObject, +}; +use core::fmt; +use serde_json::value::RawValue; +use std::{ + borrow::Cow, + collections::BTreeMap, + convert::Infallible, + future::Future, + marker::PhantomData, + ops::{Add, AddAssign}, + pin::Pin, + sync::Arc, + task, +}; +use tower::{util::BoxCloneService, Service}; + +/// A JSON-RPC handler for a specific method. +pub type Route = BoxCloneService, ResponsePayload, E>; + +/// A JSON-RPC router. +#[derive(Clone)] +pub struct Router { + inner: Arc>, +} + +impl Router { + /// Call a method on the router. + pub async fn call_with_state(&self, req: PartiallySerializedRequest, state: S) -> Response { + let Request { meta: RequestMeta { method, id, .. }, params } = req; + + let payload = self.inner.call_with_state(method, params, state).await; + Response { id, payload } + } +} + +impl Router<()> { + /// Call a method on the router. + pub async fn call(&self, req: PartiallySerializedRequest) -> Response { + self.call_with_state(req, ()).await + } +} + +impl fmt::Debug for Router { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Router").finish_non_exhaustive() + } +} + +/// A unique internal identifier for a method. +#[derive(Debug, Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord)] +pub struct MethodId(usize); + +impl From for MethodId { + fn from(id: usize) -> Self { + MethodId(id) + } +} + +impl Add for MethodId { + type Output = Self; + + fn add(self, rhs: Self) -> Self::Output { + MethodId(self.0 + rhs.0) + } +} + +impl Add for MethodId { + type Output = Self; + + fn add(self, rhs: usize) -> Self::Output { + MethodId(self.0 + rhs) + } +} + +impl AddAssign for MethodId { + fn add_assign(&mut self, rhs: Self) { + self.0 += rhs.0; + } +} + +impl AddAssign for MethodId { + fn add_assign(&mut self, rhs: usize) { + self.0 += rhs; + } +} + +/// A boxed, erased type that can be converted into a Handler. Similar to +/// axum's `ErasedIntoRoute` +/// +/// Currently this is a placeholder to enable future convenience functions +pub trait ErasedIntoRoute: Send { + /// Take a reference to this type, clone it, box it, and type erase it. + /// + /// This allows it to be stored in a collection of `dyn + /// ErasedIntoRoute`. + fn clone_box(&self) -> Box>; + + /// Convert this type into a handler. + fn into_route(self: Box, state: S) -> Route; + + /// Call this handler with the given state. + #[allow(dead_code)] + fn call_with_state( + self: Box, + params: Box, + state: S, + ) -> >>::Future; +} + +/// A boxed, erased type that can be converted into a handler. +/// +/// Similar to axum's `BoxedIntoRoute` +struct BoxedIntoHandler(Box>); + +impl Clone for BoxedIntoHandler { + fn clone(&self) -> Self { + Self(self.0.clone_box()) + } +} + +/// A method, which may be ready to handle requests or may need to be +/// initialized with some state. +/// +/// Analagous to axum's `MethodEndpoint` +enum Method { + /// A method that needs to be initialized with some state. + Needs(BoxedIntoHandler), + /// A method that is ready to handle requests. + Ready(Route), +} + +/// The inner state of a [`Router`]. Maps methods to their handlers. +pub struct RouterInner { + routes: BTreeMap>, + + last_id: MethodId, + name_to_id: BTreeMap, MethodId>, + id_to_name: BTreeMap>, +} + +impl fmt::Debug for RouterInner { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("RouterInner").finish_non_exhaustive() + } +} + +impl RouterInner { + /// Create a new, empty router. + pub fn new() -> Self { + RouterInner { + routes: BTreeMap::new(), + last_id: Default::default(), + name_to_id: BTreeMap::new(), + id_to_name: BTreeMap::new(), + } + } + + /// Get the next available ID. + fn get_id(&mut self) -> MethodId { + self.last_id += 1; + self.last_id + } + + /// Get a method by its name. + fn method_by_name(&self, name: &str) -> Option<&Method> { + self.name_to_id.get(name).and_then(|id| self.routes.get(id)) + } + + fn enroll_method_name(&mut self, method: Cow<'static, str>) -> MethodId { + if let Some(_) = self.name_to_id.get(&method) { + panic!("Method name already exists in the router."); + } + + let id = self.get_id(); + self.name_to_id.insert(method.clone(), id); + self.id_to_name.insert(id, method.clone()); + id + } + + /// Add a method to the router. This method may be missing state `S`. + pub fn add_into_route(mut self, method: impl Into>, handler: H) -> Self + where + H: ErasedIntoRoute, + { + let method = method.into(); + let handler = handler.clone_box(); + + add_method_inner(&mut self, method, handler); + + fn add_method_inner( + this: &mut RouterInner, + method: Cow<'static, str>, + handler: Box>, + ) { + let id = this.enroll_method_name(method); + + this.routes.insert(id, Method::Needs(BoxedIntoHandler(handler))); + } + + self + } + + /// Add a handler to the router. This method is complete and ready to call. + pub fn add_route(mut self, method: impl Into>, handler: Route) -> Self { + let method = method.into(); + let id = self.get_id(); + + self.name_to_id.insert(method.clone(), id); + self.id_to_name.insert(id, method.clone()); + self.routes.insert(id, Method::Ready(handler)); + + self + } + + /// Add a service to the router. + pub fn route_service(self, method: impl Into>, service: T) -> Self + where + T: Service< + Box, + Response = ResponsePayload, + Error = Infallible, + Future: Send + 'static, + > + Clone + + Send + + 'static, + { + self.add_route(method, BoxCloneService::new(service)) + } + + /// Call a method on the router, with the provided state. + fn call_with_state<'a>( + &'a self, + method: impl Into>, + params: Box, + state: S, + ) -> impl Future + Captures<'a> { + let method = method.into(); + let method = + self.method_by_name(method.as_ref()).ok_or_else(ResponsePayload::method_not_found); + + async move { + match method { + Err(err) => return err, + Ok(method) => match method { + Method::Needs(handler) => { + let h = handler.clone(); + h.0.into_route(state).call(params) + } + Method::Ready(handler) => handler.clone().call(params), + }, + } + .await + .unwrap() + } + } +} + +trait Captures<'a> {} +impl<'a, T: ?Sized> Captures<'a> for T {} + +/// A handler for a JSON-RPC method. +pub trait Handler: Clone + Send + Sized + 'static { + /// The future returned by the handler. + type Future: Future + Send + 'static; + + /// Call the handler with the given request and state. + fn call(self, req: Box, state: S) -> Self::Future; + + /// Create a new handler that wraps this handler and has some state. + fn with_state(self, state: S) -> HandlerService { + HandlerService::new(self, state) + } +} + +/// A handler with some state. +#[derive(Debug)] +pub struct HandlerService { + handler: H, + state: S, + _marker: std::marker::PhantomData, +} + +impl Clone for HandlerService +where + H: Clone, + S: Clone, +{ + fn clone(&self) -> Self { + Self { handler: self.handler.clone(), state: self.state.clone(), _marker: PhantomData } + } +} + +impl HandlerService { + /// Create a new handler service. + pub fn new(handler: H, state: S) -> Self { + Self { handler, state, _marker: PhantomData } + } +} + +impl tower::Service> for HandlerService +where + Self: Clone, + H: Handler, + T: Send + 'static, + S: Clone + Send + 'static, +{ + type Response = ResponsePayload; + type Error = Infallible; + type Future = Pin> + Send>>; + + fn poll_ready(&mut self, _cx: &mut task::Context<'_>) -> task::Poll> { + task::Poll::Ready(Ok(())) + } + + fn call(&mut self, request: Box) -> Self::Future { + let this = self.clone(); + Box::pin(async move { Ok(this.handler.call(request, this.state.clone()).await) }) + } +} + +impl Handler<(Params,), S> for F +where + F: FnOnce(Params) -> Fut + Clone + Send + 'static, + Fut: Future> + Send + 'static, + Params: RpcObject, + Payload: RpcObject, + ErrData: RpcObject, +{ + type Future = Pin + Send>>; + + fn call(self, req: Box, _state: S) -> Self::Future { + Box::pin(async move { + let Ok(params) = serde_json::from_str(req.get()) else { + return ResponsePayload::invalid_params(); + }; + + self(params) + .await + .serialize_payload() + .ok() + .unwrap_or_else(ResponsePayload::internal_error) + }) + } +} + +impl Handler<(Params, S), S> for F +where + F: FnOnce(Params, S) -> Fut + Clone + Send + 'static, + Fut: Future> + Send + 'static, + Params: RpcObject, + Payload: RpcObject, + ErrData: RpcObject, + S: Send + Sync + 'static, +{ + type Future = Pin + Send>>; + + fn call(self, req: Box, state: S) -> Self::Future { + Box::pin(async move { + let Ok(params) = serde_json::from_str(req.get()) else { + return ResponsePayload::invalid_params(); + }; + + self(params, state).await.serialize_payload().ok().unwrap_or_else(|| { + ResponsePayload::internal_error_message("Failed to serialize response".into()) + }) + }) + } +} + +#[cfg(test)] +mod test { + use alloy_json_rpc::ErrorPayload; + + // more of an example really + use super::*; + + #[tokio::test] + async fn example() { + let router: RouterInner<()> = RouterInner::new().route_service( + "hello_world", + tower::service_fn(|_: Box| async { + Ok(ResponsePayload::<(), u8>::internal_error_with_message_and_obj( + Cow::Borrowed("Hello, world!"), + 30u8, + ) + .serialize_payload() + .unwrap()) + }), + ); + + let res = router + .call_with_state("hello_world", Default::default(), ()) + .await + .deserialize_error() + .unwrap(); + + assert!(matches!( + res, + ResponsePayload::Failure(ErrorPayload { + code: -32603, + message: Cow::Borrowed("Hello, world!"), + data: Some(30u8) + }) + ),); + } +} From 0153819cd566a95ac2eaa06306ceb7cb33ee8109 Mon Sep 17 00:00:00 2001 From: James Date: Wed, 25 Sep 2024 19:28:56 -0400 Subject: [PATCH 08/12] lint: clippy --- crates/json-rpc-router/src/lib.rs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/crates/json-rpc-router/src/lib.rs b/crates/json-rpc-router/src/lib.rs index 0f91a2870be..21b80cd4fd0 100644 --- a/crates/json-rpc-router/src/lib.rs +++ b/crates/json-rpc-router/src/lib.rs @@ -63,7 +63,7 @@ pub struct MethodId(usize); impl From for MethodId { fn from(id: usize) -> Self { - MethodId(id) + Self(id) } } @@ -71,7 +71,7 @@ impl Add for MethodId { type Output = Self; fn add(self, rhs: Self) -> Self::Output { - MethodId(self.0 + rhs.0) + Self(self.0 + rhs.0) } } @@ -79,7 +79,7 @@ impl Add for MethodId { type Output = Self; fn add(self, rhs: usize) -> Self::Output { - MethodId(self.0 + rhs) + Self(self.0 + rhs) } } @@ -141,6 +141,7 @@ enum Method { } /// The inner state of a [`Router`]. Maps methods to their handlers. +#[derive(Default)] pub struct RouterInner { routes: BTreeMap>, @@ -158,7 +159,7 @@ impl fmt::Debug for RouterInner { impl RouterInner { /// Create a new, empty router. pub fn new() -> Self { - RouterInner { + Self { routes: BTreeMap::new(), last_id: Default::default(), name_to_id: BTreeMap::new(), @@ -178,7 +179,7 @@ impl RouterInner { } fn enroll_method_name(&mut self, method: Cow<'static, str>) -> MethodId { - if let Some(_) = self.name_to_id.get(&method) { + if self.name_to_id.contains_key(&method) { panic!("Method name already exists in the router."); } @@ -239,12 +240,12 @@ impl RouterInner { } /// Call a method on the router, with the provided state. - fn call_with_state<'a>( - &'a self, + fn call_with_state( + &self, method: impl Into>, params: Box, state: S, - ) -> impl Future + Captures<'a> { + ) -> impl Future + Captures<'_> { let method = method.into(); let method = self.method_by_name(method.as_ref()).ok_or_else(ResponsePayload::method_not_found); @@ -303,7 +304,7 @@ where impl HandlerService { /// Create a new handler service. - pub fn new(handler: H, state: S) -> Self { + pub const fn new(handler: H, state: S) -> Self { Self { handler, state, _marker: PhantomData } } } From 7d7585b3aaf3700d06bdcc03ddcaa7e8a75362e9 Mon Sep 17 00:00:00 2001 From: James Date: Thu, 26 Sep 2024 09:26:38 -0400 Subject: [PATCH 09/12] work: cleanup --- crates/json-rpc-router/src/lib.rs | 278 ++++++++++++++++++------------ 1 file changed, 163 insertions(+), 115 deletions(-) diff --git a/crates/json-rpc-router/src/lib.rs b/crates/json-rpc-router/src/lib.rs index 21b80cd4fd0..9b7837e3a08 100644 --- a/crates/json-rpc-router/src/lib.rs +++ b/crates/json-rpc-router/src/lib.rs @@ -57,89 +57,6 @@ impl fmt::Debug for Router { } } -/// A unique internal identifier for a method. -#[derive(Debug, Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord)] -pub struct MethodId(usize); - -impl From for MethodId { - fn from(id: usize) -> Self { - Self(id) - } -} - -impl Add for MethodId { - type Output = Self; - - fn add(self, rhs: Self) -> Self::Output { - Self(self.0 + rhs.0) - } -} - -impl Add for MethodId { - type Output = Self; - - fn add(self, rhs: usize) -> Self::Output { - Self(self.0 + rhs) - } -} - -impl AddAssign for MethodId { - fn add_assign(&mut self, rhs: Self) { - self.0 += rhs.0; - } -} - -impl AddAssign for MethodId { - fn add_assign(&mut self, rhs: usize) { - self.0 += rhs; - } -} - -/// A boxed, erased type that can be converted into a Handler. Similar to -/// axum's `ErasedIntoRoute` -/// -/// Currently this is a placeholder to enable future convenience functions -pub trait ErasedIntoRoute: Send { - /// Take a reference to this type, clone it, box it, and type erase it. - /// - /// This allows it to be stored in a collection of `dyn - /// ErasedIntoRoute`. - fn clone_box(&self) -> Box>; - - /// Convert this type into a handler. - fn into_route(self: Box, state: S) -> Route; - - /// Call this handler with the given state. - #[allow(dead_code)] - fn call_with_state( - self: Box, - params: Box, - state: S, - ) -> >>::Future; -} - -/// A boxed, erased type that can be converted into a handler. -/// -/// Similar to axum's `BoxedIntoRoute` -struct BoxedIntoHandler(Box>); - -impl Clone for BoxedIntoHandler { - fn clone(&self) -> Self { - Self(self.0.clone_box()) - } -} - -/// A method, which may be ready to handle requests or may need to be -/// initialized with some state. -/// -/// Analagous to axum's `MethodEndpoint` -enum Method { - /// A method that needs to be initialized with some state. - Needs(BoxedIntoHandler), - /// A method that is ready to handle requests. - Ready(Route), -} - /// The inner state of a [`Router`]. Maps methods to their handlers. #[derive(Default)] pub struct RouterInner { @@ -167,6 +84,23 @@ impl RouterInner { } } + /// Add state to the router, readying methods that require that state. + pub fn with_state(self, state: &S) -> RouterInner + where + S: Clone, + { + RouterInner { + routes: self + .routes + .into_iter() + .map(|(id, method)| (id, method.with_state(state))) + .collect(), + last_id: self.last_id, + name_to_id: self.name_to_id, + id_to_name: self.id_to_name, + } + } + /// Get the next available ID. fn get_id(&mut self) -> MethodId { self.last_id += 1; @@ -178,6 +112,8 @@ impl RouterInner { self.name_to_id.get(name).and_then(|id| self.routes.get(id)) } + /// Enroll a method name, returning an ID assignment. Panics if the method + /// name already exists in the router. Future: don't panic. fn enroll_method_name(&mut self, method: Cow<'static, str>) -> MethodId { if self.name_to_id.contains_key(&method) { panic!("Method name already exists in the router."); @@ -189,8 +125,20 @@ impl RouterInner { id } + /// Add a handler to the router. This method is complete and ready to call. + fn route(mut self, method: impl Into>, handler: Route) -> Self { + let method = method.into(); + let id = self.get_id(); + + self.name_to_id.insert(method.clone(), id); + self.id_to_name.insert(id, method.clone()); + self.routes.insert(id, Method::Ready(handler)); + + self + } + /// Add a method to the router. This method may be missing state `S`. - pub fn add_into_route(mut self, method: impl Into>, handler: H) -> Self + pub fn route_erased(mut self, method: impl Into>, handler: H) -> Self where H: ErasedIntoRoute, { @@ -212,18 +160,6 @@ impl RouterInner { self } - /// Add a handler to the router. This method is complete and ready to call. - pub fn add_route(mut self, method: impl Into>, handler: Route) -> Self { - let method = method.into(); - let id = self.get_id(); - - self.name_to_id.insert(method.clone(), id); - self.id_to_name.insert(id, method.clone()); - self.routes.insert(id, Method::Ready(handler)); - - self - } - /// Add a service to the router. pub fn route_service(self, method: impl Into>, service: T) -> Self where @@ -236,7 +172,7 @@ impl RouterInner { + Send + 'static, { - self.add_route(method, BoxCloneService::new(service)) + self.route(method, BoxCloneService::new(service)) } /// Call a method on the router, with the provided state. @@ -267,8 +203,89 @@ impl RouterInner { } } -trait Captures<'a> {} -impl<'a, T: ?Sized> Captures<'a> for T {} +/// A unique internal identifier for a method. +#[derive(Debug, Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord)] +pub struct MethodId(usize); + +impl From for MethodId { + fn from(id: usize) -> Self { + Self(id) + } +} + +impl Add for MethodId { + type Output = Self; + + fn add(self, rhs: usize) -> Self::Output { + Self(self.0 + rhs) + } +} + +impl AddAssign for MethodId { + fn add_assign(&mut self, rhs: usize) { + self.0 += rhs; + } +} + +/// A boxed, erased type that can be converted into a Handler. Similar to +/// axum's `ErasedIntoRoute` +/// +/// Currently this is a placeholder to enable future convenience functions +pub trait ErasedIntoRoute: Send { + /// Take a reference to this type, clone it, box it, and type erase it. + /// + /// This allows it to be stored in a collection of `dyn + /// ErasedIntoRoute`. + fn clone_box(&self) -> Box>; + + /// Convert this type into a handler. + fn into_route(self: Box, state: S) -> Route; + + /// Call this handler with the given state. + #[allow(dead_code)] + fn call_with_state( + self: Box, + params: Box, + state: S, + ) -> >>::Future; +} + +/// A boxed, erased type that can be converted into a handler. +/// +/// Similar to axum's `BoxedIntoRoute` +/// +/// Currently this is a placeholder to enable future convenience functions +struct BoxedIntoHandler(Box>); + +impl Clone for BoxedIntoHandler { + fn clone(&self) -> Self { + Self(self.0.clone_box()) + } +} + +/// A method, which may be ready to handle requests or may need to be +/// initialized with some state. +/// +/// Analagous to axum's `MethodEndpoint` +enum Method { + /// A method that needs to be initialized with some state. + Needs(BoxedIntoHandler), + /// A method that is ready to handle requests. + Ready(Route), +} + +impl Method +where + S: Clone, +{ + /// Add state to a method, converting + fn with_state(self, state: &S) -> Method { + match self { + Self::Ready(route) => Method::Ready(route), + Self::Needs(handler) => Method::Ready(handler.0.into_route(state.clone())), + } + } +} /// A handler for a JSON-RPC method. pub trait Handler: Clone + Send + Sized + 'static { @@ -333,7 +350,7 @@ where impl Handler<(Params,), S> for F where F: FnOnce(Params) -> Fut + Clone + Send + 'static, - Fut: Future> + Send + 'static, + Fut: Future> + Send + 'static, Params: RpcObject, Payload: RpcObject, ErrData: RpcObject, @@ -348,6 +365,8 @@ where self(params) .await + .map(ResponsePayload::Success) + .unwrap_or_else(ResponsePayload::internal_error_with_obj) .serialize_payload() .ok() .unwrap_or_else(ResponsePayload::internal_error) @@ -358,7 +377,7 @@ where impl Handler<(Params, S), S> for F where F: FnOnce(Params, S) -> Fut + Clone + Send + 'static, - Fut: Future> + Send + 'static, + Fut: Future> + Send + 'static, Params: RpcObject, Payload: RpcObject, ErrData: RpcObject, @@ -372,13 +391,22 @@ where return ResponsePayload::invalid_params(); }; - self(params, state).await.serialize_payload().ok().unwrap_or_else(|| { - ResponsePayload::internal_error_message("Failed to serialize response".into()) - }) + self(params, state) + .await + .map(ResponsePayload::Success) + .unwrap_or_else(ResponsePayload::internal_error_with_obj) + .serialize_payload() + .ok() + .unwrap_or_else(|| { + ResponsePayload::internal_error_message("Failed to serialize response".into()) + }) }) } } +trait Captures<'a> {} +impl<'a, T: ?Sized> Captures<'a> for T {} + #[cfg(test)] mod test { use alloy_json_rpc::ErrorPayload; @@ -388,17 +416,25 @@ mod test { #[tokio::test] async fn example() { - let router: RouterInner<()> = RouterInner::new().route_service( - "hello_world", - tower::service_fn(|_: Box| async { - Ok(ResponsePayload::<(), u8>::internal_error_with_message_and_obj( - Cow::Borrowed("Hello, world!"), - 30u8, - ) - .serialize_payload() - .unwrap()) - }), - ); + let router: RouterInner<()> = RouterInner::new() + .route_service( + "hello_world", + tower::service_fn(|_: Box| async { + Ok(ResponsePayload::<(), u8>::internal_error_with_message_and_obj( + Cow::Borrowed("Hello, world!"), + 30u8, + ) + .serialize_payload() + .unwrap()) + }), + ) + .route_service( + "foo", + Handler::with_state( + |a: Box| async move { Ok::<_, ()>(a.get().to_owned()) }, + (), + ), + ); let res = router .call_with_state("hello_world", Default::default(), ()) @@ -414,5 +450,17 @@ mod test { data: Some(30u8) }) ),); + + let res2 = dbg!( + router + .call_with_state("foo", RawValue::from_string("{}".to_owned()).unwrap(), ()) + .await + ) + .deserialize_success::() + .unwrap() + .deserialize_error::<()>() + .unwrap(); + + assert_eq!(res2, ResponsePayload::Success("{}".to_owned())); } } From ad6c5577d3d62b69b600cc326696b8136de1830b Mon Sep 17 00:00:00 2001 From: James Date: Mon, 30 Sep 2024 09:23:52 -0400 Subject: [PATCH 10/12] fix: axum to string --- crates/json-rpc/src/support/axum.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/json-rpc/src/support/axum.rs b/crates/json-rpc/src/support/axum.rs index 386ffff4bd4..b5cb9e64c2a 100644 --- a/crates/json-rpc/src/support/axum.rs +++ b/crates/json-rpc/src/support/axum.rs @@ -7,7 +7,7 @@ impl From for Response<(), ()> { id: Id::None, payload: ResponsePayload::Failure(ErrorPayload { code: -32600, - message: value.to_string(), + message: value.to_string().into(), data: None, }), } From 2a13d6c7cd26ec6c6e62db5632752584d224a549 Mon Sep 17 00:00:00 2001 From: James Date: Mon, 30 Sep 2024 09:48:55 -0400 Subject: [PATCH 11/12] fix: lints and tests --- crates/json-rpc-router/Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/json-rpc-router/Cargo.toml b/crates/json-rpc-router/Cargo.toml index c957d88614a..f539751c3df 100644 --- a/crates/json-rpc-router/Cargo.toml +++ b/crates/json-rpc-router/Cargo.toml @@ -21,7 +21,6 @@ workspace = true [dependencies] alloy-json-rpc.workspace = true -async-trait = { workspace = true, optional = true } serde_json = { version = "1.0.128" } tower.workspace = true From 45fc342774b0a28d27499ee7fd517f6290f220e8 Mon Sep 17 00:00:00 2001 From: James Date: Mon, 30 Sep 2024 11:58:50 -0400 Subject: [PATCH 12/12] fix: dep spec for axum feature --- crates/json-rpc-router/src/lib.rs | 8 +++++++- crates/json-rpc/Cargo.toml | 2 +- crates/json-rpc/src/support/axum.rs | 4 ++-- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/crates/json-rpc-router/src/lib.rs b/crates/json-rpc-router/src/lib.rs index 9b7837e3a08..fedaaa18727 100644 --- a/crates/json-rpc-router/src/lib.rs +++ b/crates/json-rpc-router/src/lib.rs @@ -29,11 +29,17 @@ use tower::{util::BoxCloneService, Service}; pub type Route = BoxCloneService, ResponsePayload, E>; /// A JSON-RPC router. -#[derive(Clone)] +#[must_use = "Routers do nothing unless served."] pub struct Router { inner: Arc>, } +impl Clone for Router { + fn clone(&self) -> Self { + Self { inner: Arc::clone(&self.inner) } + } +} + impl Router { /// Call a method on the router. pub async fn call_with_state(&self, req: PartiallySerializedRequest, state: S) -> Response { diff --git a/crates/json-rpc/Cargo.toml b/crates/json-rpc/Cargo.toml index 552992835b0..04aede78e94 100644 --- a/crates/json-rpc/Cargo.toml +++ b/crates/json-rpc/Cargo.toml @@ -30,4 +30,4 @@ async-trait = { workspace = true, optional = true } axum = { version = "0.7.6", features = ["json"], optional = true } [features] -axum = ["dep:axum"] +axum = ["dep:axum", "dep:async-trait"] diff --git a/crates/json-rpc/src/support/axum.rs b/crates/json-rpc/src/support/axum.rs index b5cb9e64c2a..8d5f2a33b4c 100644 --- a/crates/json-rpc/src/support/axum.rs +++ b/crates/json-rpc/src/support/axum.rs @@ -3,7 +3,7 @@ use axum::extract; impl From for Response<(), ()> { fn from(value: extract::rejection::JsonRejection) -> Self { - Response { + Self { id: Id::None, payload: ResponsePayload::Failure(ErrorPayload { code: -32600, @@ -34,7 +34,7 @@ where type Rejection = Response<(), ()>; async fn from_request(req: extract::Request, state: &S) -> Result { - let json = extract::Json::>::from_request(req, state).await?; + let json = extract::Json::::from_request(req, state).await?; Ok(json.0) }