less spawns and more logs
This commit is contained in:
parent
dcfad0c1b5
commit
05563b46b1
@ -4,36 +4,21 @@ chain_id = 1
|
||||
[balanced_rpcs]
|
||||
|
||||
[balanced_rpcs.erigon_archive]
|
||||
url = "http://127.0.0.1:8549"
|
||||
# TODO: double check soft_limit on erigon
|
||||
soft_limit = 100_000
|
||||
|
||||
[balanced_rpcs.erigon_archive_ws]
|
||||
url = "ws://127.0.0.1:8549"
|
||||
# TODO: double check soft_limit on erigon
|
||||
soft_limit = 100_000
|
||||
|
||||
[balanced_rpcs.geth]
|
||||
url = "http://127.0.0.1:8545"
|
||||
soft_limit = 200_000
|
||||
|
||||
[balanced_rpcs.geth_ws]
|
||||
url = "ws://127.0.0.1:8546"
|
||||
soft_limit = 200_000
|
||||
|
||||
[balanced_rpcs.ankr]
|
||||
url = "https://rpc.ankr.com/eth"
|
||||
soft_limit = 3_000
|
||||
|
||||
[private_rpcs]
|
||||
|
||||
[private_rpcs.eden]
|
||||
url = "https://api.edennetwork.io/v1/"
|
||||
soft_limit = 1_805
|
||||
|
||||
[private_rpcs.eden_beta]
|
||||
url = "https://api.edennetwork.io/v1/beta"
|
||||
soft_limit = 5_861
|
||||
|
||||
[private_rpcs.ethermine]
|
||||
url = "https://rpc.ethermine.org"
|
||||
soft_limit = 5_861
|
||||
|
||||
[private_rpcs.flashbots]
|
||||
url = "https://rpc.flashbots.net"
|
||||
soft_limit = 7074
|
||||
|
||||
[private_rpcs.securerpc]
|
||||
url = "https://gibson.securerpc.com/v1"
|
||||
soft_limit = 4560
|
||||
|
@ -4,10 +4,14 @@ use std::time::Duration;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
console_subscriber::init();
|
||||
|
||||
fdlimit::raise_fd_limit();
|
||||
|
||||
// erigon
|
||||
let url = "ws://10.11.12.16:8545";
|
||||
// let url = "ws://10.11.12.16:8548";
|
||||
// geth
|
||||
// let url = "ws://10.11.12.16:8946";
|
||||
let url = "ws://10.11.12.16:8546";
|
||||
|
||||
println!("Subscribing to blocks from {}", url);
|
||||
|
||||
@ -15,7 +19,7 @@ async fn main() -> anyhow::Result<()> {
|
||||
|
||||
let provider = Provider::new(provider).interval(Duration::from_secs(1));
|
||||
|
||||
let mut stream = provider.subscribe_blocks().await?.take(3);
|
||||
let mut stream = provider.subscribe_blocks().await?;
|
||||
while let Some(block) = stream.next().await {
|
||||
println!(
|
||||
"{:?} = Ts: {:?}, block number: {}",
|
||||
|
@ -4,10 +4,14 @@ use std::{str::FromStr, time::Duration};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
console_subscriber::init();
|
||||
|
||||
fdlimit::raise_fd_limit();
|
||||
|
||||
// erigon does not support most filters
|
||||
// let url = "http://10.11.12.16:8545";
|
||||
// geth
|
||||
let url = "http://10.11.12.16:8945";
|
||||
let url = "http://10.11.12.16:8545";
|
||||
|
||||
println!("Watching blocks from {:?}", url);
|
||||
|
||||
@ -15,7 +19,7 @@ async fn main() -> anyhow::Result<()> {
|
||||
|
||||
let provider = Provider::new(provider).interval(Duration::from_secs(1));
|
||||
|
||||
let mut stream = provider.watch_blocks().await?.take(3);
|
||||
let mut stream = provider.watch_blocks().await?;
|
||||
while let Some(block_number) = stream.next().await {
|
||||
let block = provider.get_block(block_number).await?.unwrap();
|
||||
println!(
|
||||
|
@ -71,27 +71,28 @@ impl Web3ProxyApp {
|
||||
// TODO: 5 minutes is probably long enough. unlimited is a bad idea if something is wrong with the remote server
|
||||
let http_client = reqwest::ClientBuilder::new()
|
||||
.connect_timeout(Duration::from_secs(5))
|
||||
.timeout(Duration::from_secs(300))
|
||||
.timeout(Duration::from_secs(60))
|
||||
.user_agent(APP_USER_AGENT)
|
||||
.build()?;
|
||||
|
||||
// TODO: attach context to this error
|
||||
let balanced_rpcs = Web3Connections::try_new(
|
||||
chain_id,
|
||||
balanced_rpcs,
|
||||
Some(http_client.clone()),
|
||||
&clock,
|
||||
true,
|
||||
)
|
||||
.await?;
|
||||
let balanced_rpcs =
|
||||
Web3Connections::try_new(chain_id, balanced_rpcs, Some(http_client.clone()), &clock)
|
||||
.await?;
|
||||
|
||||
{
|
||||
let balanced_rpcs = balanced_rpcs.clone();
|
||||
task::spawn(async move {
|
||||
balanced_rpcs.subscribe_heads().await;
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: attach context to this error
|
||||
let private_rpcs = if private_rpcs.is_empty() {
|
||||
warn!("No private relays configured. Any transactions will be broadcast to the public mempool!");
|
||||
balanced_rpcs.clone()
|
||||
} else {
|
||||
Web3Connections::try_new(chain_id, private_rpcs, Some(http_client), &clock, false)
|
||||
.await?
|
||||
Web3Connections::try_new(chain_id, private_rpcs, Some(http_client), &clock).await?
|
||||
};
|
||||
|
||||
Ok(Web3ProxyApp {
|
||||
@ -140,12 +141,7 @@ impl Web3ProxyApp {
|
||||
let responses = join_all(
|
||||
requests
|
||||
.into_iter()
|
||||
.map(|request| {
|
||||
let clone = self.clone();
|
||||
task::Builder::default()
|
||||
.name("proxy_web3_rpc_request")
|
||||
.spawn(async move { clone.proxy_web3_rpc_request(request).await })
|
||||
})
|
||||
.map(|request| self.clone().proxy_web3_rpc_request(request))
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.await;
|
||||
@ -153,7 +149,7 @@ impl Web3ProxyApp {
|
||||
// TODO: i'm sure this could be done better with iterators
|
||||
let mut collected: Vec<JsonRpcForwardedResponse> = Vec::with_capacity(num_requests);
|
||||
for response in responses {
|
||||
collected.push(response??);
|
||||
collected.push(response?);
|
||||
}
|
||||
|
||||
Ok(collected)
|
||||
@ -166,8 +162,6 @@ impl Web3ProxyApp {
|
||||
) -> anyhow::Result<JsonRpcForwardedResponse> {
|
||||
trace!("Received request: {:?}", request);
|
||||
|
||||
// TODO: apparently json_body can be a vec of multiple requests. should we split them up? we need to respond with a Vec too
|
||||
|
||||
if request.method == "eth_sendRawTransaction" {
|
||||
// there are private rpcs configured and the request is eth_sendSignedTransaction. send to all private rpcs
|
||||
// TODO: think more about this lock. i think it won't actually help the herd. it probably makes it worse if we have a tight lag_limit
|
||||
@ -231,7 +225,7 @@ impl Web3ProxyApp {
|
||||
// TODO: how much should we retry?
|
||||
for i in 0..10 {
|
||||
// TODO: think more about this loop.
|
||||
// TODO: set tracing span to have the loop count in it
|
||||
// TODO: add more to this span
|
||||
let span = info_span!("i", i);
|
||||
let _enter = span.enter();
|
||||
|
||||
|
@ -15,6 +15,10 @@ pub struct CliConfig {
|
||||
#[argh(option, default = "8544")]
|
||||
pub listen_port: u16,
|
||||
|
||||
/// number of worker threads
|
||||
#[argh(option, default = "0")]
|
||||
pub worker_threads: usize,
|
||||
|
||||
/// path to a toml of rpc servers
|
||||
#[argh(option, default = "\"./config/example.toml\".to_string()")]
|
||||
pub rpc_config_path: String,
|
||||
|
@ -415,9 +415,10 @@ impl ActiveRequestHandle {
|
||||
T: fmt::Debug + serde::Serialize + Send + Sync,
|
||||
R: serde::Serialize + serde::de::DeserializeOwned + fmt::Debug,
|
||||
{
|
||||
// TODO: this should probably be trace level and use a span
|
||||
// TODO: use tracing spans properly
|
||||
// TODO: it would be nice to have the request id on this
|
||||
trace!("Sending {}({:?}) to {}", method, params, self.0);
|
||||
// TODO: including params in this is way too verbose
|
||||
trace!("Sending {} to {}", method, self.0);
|
||||
|
||||
let provider = self.0.provider.read().await.clone();
|
||||
|
||||
@ -428,7 +429,8 @@ impl ActiveRequestHandle {
|
||||
|
||||
// TODO: i think ethers already has trace logging (and does it much more fancy)
|
||||
// TODO: at least instrument this with more useful information
|
||||
trace!("Response from {}: {:?}", self.0, response);
|
||||
// trace!("Reply from {}: {:?}", self.0, response);
|
||||
trace!("Reply from {}", self.0);
|
||||
|
||||
response
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
///! Load balanced communication with a group of web3 providers
|
||||
use derive_more::From;
|
||||
use ethers::prelude::H256;
|
||||
use futures::future::join_all;
|
||||
use futures::stream::FuturesUnordered;
|
||||
use futures::StreamExt;
|
||||
use governor::clock::{QuantaClock, QuantaInstant};
|
||||
@ -11,8 +12,10 @@ use serde_json::value::RawValue;
|
||||
use std::cmp;
|
||||
use std::fmt;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::Mutex;
|
||||
use tokio::task;
|
||||
use tracing::{info, instrument, trace, warn};
|
||||
use tracing::Instrument;
|
||||
use tracing::{info, info_span, instrument, trace, warn};
|
||||
|
||||
use crate::config::Web3ConnectionConfig;
|
||||
use crate::connection::{ActiveRequestHandle, Web3Connection};
|
||||
@ -88,6 +91,8 @@ impl SyncedConnections {
|
||||
// TODO: better log
|
||||
if log {
|
||||
trace!("Now synced: {:?}", self.inner);
|
||||
} else {
|
||||
trace!("Now synced #2: {:?}", self.inner);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -134,6 +139,7 @@ impl Absorb<SyncedConnectionsOp> for SyncedConnections {
|
||||
pub struct Web3Connections {
|
||||
inner: Vec<Arc<Web3Connection>>,
|
||||
synced_connections_reader: ReadHandleFactory<SyncedConnections>,
|
||||
synced_connections_writer: Mutex<WriteHandle<SyncedConnections, SyncedConnectionsOp>>,
|
||||
}
|
||||
|
||||
impl fmt::Debug for Web3Connections {
|
||||
@ -152,7 +158,6 @@ impl Web3Connections {
|
||||
servers: Vec<Web3ConnectionConfig>,
|
||||
http_client: Option<reqwest::Client>,
|
||||
clock: &QuantaClock,
|
||||
subscribe_heads: bool,
|
||||
) -> anyhow::Result<Arc<Self>> {
|
||||
let num_connections = servers.len();
|
||||
|
||||
@ -175,60 +180,62 @@ impl Web3Connections {
|
||||
));
|
||||
}
|
||||
|
||||
let (block_sender, block_receiver) = flume::unbounded();
|
||||
|
||||
let (mut synced_connections_writer, synced_connections_reader) =
|
||||
left_right::new::<SyncedConnections, SyncedConnectionsOp>();
|
||||
|
||||
if subscribe_heads {
|
||||
for connection in connections.iter() {
|
||||
// subscribe to new heads in a spawned future
|
||||
// TODO: channel instead. then we can have one future with write access to a left-right?
|
||||
let connection = Arc::clone(connection);
|
||||
let block_sender = block_sender.clone();
|
||||
task::Builder::default()
|
||||
.name("subscribe_new_heads")
|
||||
.spawn(async move {
|
||||
let url = connection.url().to_string();
|
||||
|
||||
// loop to automatically reconnect
|
||||
// TODO: make this cancellable?
|
||||
loop {
|
||||
// TODO: instead of passing Some(connections), pass Some(channel_sender). Then listen on the receiver below to keep local heads up-to-date
|
||||
if let Err(e) = connection
|
||||
.clone()
|
||||
.subscribe_new_heads(block_sender.clone(), true)
|
||||
.await
|
||||
{
|
||||
warn!("new_heads error on {}: {:?}", url, e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
synced_connections_writer.append(SyncedConnectionsOp::SyncedConnectionsCapacity(
|
||||
num_connections,
|
||||
));
|
||||
trace!("publishing synced connections");
|
||||
synced_connections_writer.publish();
|
||||
trace!("published synced connections");
|
||||
|
||||
let connections = Arc::new(Self {
|
||||
inner: connections,
|
||||
synced_connections_reader: synced_connections_reader.factory(),
|
||||
synced_connections_writer: Mutex::new(synced_connections_writer),
|
||||
});
|
||||
|
||||
if subscribe_heads {
|
||||
let connections = Arc::clone(&connections);
|
||||
task::Builder::default()
|
||||
.name("update_synced_rpcs")
|
||||
Ok(connections)
|
||||
}
|
||||
|
||||
pub async fn subscribe_heads(self: &Arc<Self>) {
|
||||
let (block_sender, block_receiver) = flume::unbounded();
|
||||
|
||||
let mut handles = vec![];
|
||||
|
||||
for connection in self.inner.iter() {
|
||||
// subscribe to new heads in a spawned future
|
||||
// TODO: channel instead. then we can have one future with write access to a left-right?
|
||||
let connection = Arc::clone(connection);
|
||||
let block_sender = block_sender.clone();
|
||||
|
||||
// let url = connection.url().to_string();
|
||||
|
||||
let handle = task::Builder::default()
|
||||
.name("subscribe_new_heads")
|
||||
.spawn(async move {
|
||||
connections
|
||||
.update_synced_rpcs(block_receiver, synced_connections_writer)
|
||||
// loop to automatically reconnect
|
||||
// TODO: make this cancellable?
|
||||
// TODO: instead of passing Some(connections), pass Some(channel_sender). Then listen on the receiver below to keep local heads up-to-date
|
||||
// TODO: proper spann
|
||||
connection
|
||||
.subscribe_new_heads(block_sender.clone(), true)
|
||||
.instrument(tracing::info_span!("url"))
|
||||
.await
|
||||
});
|
||||
|
||||
handles.push(handle);
|
||||
}
|
||||
|
||||
Ok(connections)
|
||||
let connections = Arc::clone(self);
|
||||
let handle = task::Builder::default()
|
||||
.name("update_synced_rpcs")
|
||||
.spawn(async move { connections.update_synced_rpcs(block_receiver).await });
|
||||
|
||||
handles.push(handle);
|
||||
|
||||
join_all(handles).await;
|
||||
}
|
||||
|
||||
pub fn get_synced_rpcs(&self) -> left_right::ReadHandle<SyncedConnections> {
|
||||
@ -306,14 +313,18 @@ impl Web3Connections {
|
||||
async fn update_synced_rpcs(
|
||||
&self,
|
||||
block_receiver: flume::Receiver<(u64, H256, Arc<Web3Connection>)>,
|
||||
mut synced_connections_writer: WriteHandle<SyncedConnections, SyncedConnectionsOp>,
|
||||
) -> anyhow::Result<()> {
|
||||
let mut synced_connections_writer = self.synced_connections_writer.lock().await;
|
||||
|
||||
while let Ok((new_block_num, new_block_hash, rpc)) = block_receiver.recv_async().await {
|
||||
if new_block_num == 0 {
|
||||
warn!("{} is still syncing", rpc);
|
||||
continue;
|
||||
}
|
||||
|
||||
let span = info_span!("new_block_num", new_block_num,);
|
||||
let _enter = span.enter();
|
||||
|
||||
synced_connections_writer.append(SyncedConnectionsOp::SyncedConnectionsUpdate(
|
||||
new_block_num,
|
||||
new_block_hash,
|
||||
@ -321,9 +332,19 @@ impl Web3Connections {
|
||||
));
|
||||
|
||||
// TODO: only publish when the second block arrives?
|
||||
// TODO: use spans properly
|
||||
trace!("publishing synced connections for block {}", new_block_num,);
|
||||
synced_connections_writer.publish();
|
||||
trace!(
|
||||
"published synced connections for block {} from {}",
|
||||
new_block_num,
|
||||
"some rpc"
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: if there was an error, we should return it
|
||||
warn!("block_receiver exited!");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -7,6 +7,7 @@ mod jsonrpc;
|
||||
use jsonrpc::{JsonRpcErrorData, JsonRpcForwardedResponse};
|
||||
use parking_lot::deadlock;
|
||||
use serde_json::value::RawValue;
|
||||
use std::env;
|
||||
use std::fs;
|
||||
use std::sync::atomic::{self, AtomicUsize};
|
||||
use std::sync::Arc;
|
||||
@ -21,8 +22,12 @@ use crate::app::Web3ProxyApp;
|
||||
use crate::config::{CliConfig, RpcConfig};
|
||||
|
||||
fn main() -> anyhow::Result<()> {
|
||||
// TODO: is there a better way to do this?
|
||||
if env::var("RUST_LOG").is_err() {
|
||||
env::set_var("RUST_LOG", "web3_proxy=debug");
|
||||
}
|
||||
|
||||
// install global collector configured based on RUST_LOG env var.
|
||||
// TODO: if RUST_LOG isn't set, set it to "web3_proxy=debug" or something
|
||||
console_subscriber::init();
|
||||
|
||||
fdlimit::raise_fd_limit();
|
||||
@ -38,18 +43,22 @@ fn main() -> anyhow::Result<()> {
|
||||
|
||||
let chain_id = rpc_config.shared.chain_id;
|
||||
|
||||
// TODO: get worker_threads from config
|
||||
let rt = runtime::Builder::new_multi_thread()
|
||||
.enable_all()
|
||||
.worker_threads(8)
|
||||
.thread_name_fn(move || {
|
||||
static ATOMIC_ID: AtomicUsize = AtomicUsize::new(0);
|
||||
// TODO: what ordering? i think we want seqcst so that these all happen in order, but that might be stricter than we really need
|
||||
let worker_id = ATOMIC_ID.fetch_add(1, atomic::Ordering::SeqCst);
|
||||
// TODO: i think these max at 15 characters
|
||||
format!("web3-{}-{}", chain_id, worker_id)
|
||||
})
|
||||
.build()?;
|
||||
// TODO: multithreaded runtime once i'm done debugging
|
||||
let mut rt_builder = runtime::Builder::new_current_thread();
|
||||
|
||||
rt_builder.enable_all().thread_name_fn(move || {
|
||||
static ATOMIC_ID: AtomicUsize = AtomicUsize::new(0);
|
||||
// TODO: what ordering? i think we want seqcst so that these all happen in order, but that might be stricter than we really need
|
||||
let worker_id = ATOMIC_ID.fetch_add(1, atomic::Ordering::SeqCst);
|
||||
// TODO: i think these max at 15 characters
|
||||
format!("web3-{}-{}", chain_id, worker_id)
|
||||
});
|
||||
|
||||
if cli_config.worker_threads > 0 {
|
||||
rt_builder.worker_threads(cli_config.worker_threads);
|
||||
}
|
||||
|
||||
let rt = rt_builder.build()?;
|
||||
|
||||
// spawn a thread for deadlock detection
|
||||
thread::spawn(move || loop {
|
||||
@ -73,7 +82,7 @@ fn main() -> anyhow::Result<()> {
|
||||
rt.block_on(async {
|
||||
let listen_port = cli_config.listen_port;
|
||||
|
||||
let app = rpc_config.try_build().await.unwrap();
|
||||
let app = rpc_config.try_build().await?;
|
||||
|
||||
let app: Arc<Web3ProxyApp> = Arc::new(app);
|
||||
|
||||
@ -89,9 +98,9 @@ fn main() -> anyhow::Result<()> {
|
||||
let routes = proxy_rpc_filter.map(handle_anyhow_errors);
|
||||
|
||||
warp::serve(routes).run(([0, 0, 0, 0], listen_port)).await;
|
||||
});
|
||||
|
||||
Ok(())
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
/// convert result into a jsonrpc error. use this at the end of your warp filter
|
||||
|
Loading…
Reference in New Issue
Block a user