wip
This commit is contained in:
parent
18f644e8fe
commit
c9e5661c5b
1
TODO.md
1
TODO.md
|
@ -496,6 +496,7 @@ These are not yet ordered. There might be duplicates. We might not actually need
|
||||||
|
|
||||||
These are not ordered. I think some rows also accidently got deleted here. Check git history.
|
These are not ordered. I think some rows also accidently got deleted here. Check git history.
|
||||||
|
|
||||||
|
- [ ] less Arc (and more pin?). we use arcs on a lot of things where i think a &self should work fine.
|
||||||
- [ ] automatically tune database and redis connection pool size
|
- [ ] automatically tune database and redis connection pool size
|
||||||
- [ ] if db is down, keep logins cached longer. at least only new logins will have trouble then
|
- [ ] if db is down, keep logins cached longer. at least only new logins will have trouble then
|
||||||
- [ ] handle user payments
|
- [ ] handle user payments
|
||||||
|
|
|
@ -354,8 +354,6 @@ pub async fn get_migrated_db(
|
||||||
pub struct Web3ProxyAppSpawn {
|
pub struct Web3ProxyAppSpawn {
|
||||||
/// the app. probably clone this to use in other groups of handles
|
/// the app. probably clone this to use in other groups of handles
|
||||||
pub app: Arc<Web3ProxyApp>,
|
pub app: Arc<Web3ProxyApp>,
|
||||||
// cancellable handles
|
|
||||||
pub app_handles: FuturesUnordered<AnyhowJoinHandle<()>>,
|
|
||||||
/// these are important and must be allowed to finish
|
/// these are important and must be allowed to finish
|
||||||
pub background_handles: FuturesUnordered<AnyhowJoinHandle<()>>,
|
pub background_handles: FuturesUnordered<AnyhowJoinHandle<()>>,
|
||||||
}
|
}
|
||||||
|
@ -367,27 +365,8 @@ impl Web3ProxyApp {
|
||||||
num_workers: usize,
|
num_workers: usize,
|
||||||
shutdown_receiver: broadcast::Receiver<()>,
|
shutdown_receiver: broadcast::Receiver<()>,
|
||||||
) -> anyhow::Result<Web3ProxyAppSpawn> {
|
) -> anyhow::Result<Web3ProxyAppSpawn> {
|
||||||
// safety checks on the config
|
// we must wait for these to end on their own (and they need to subscribe to shutdown_sender)
|
||||||
if let Some(redirect) = &top_config.app.redirect_rpc_key_url {
|
let important_background_handles = FuturesUnordered::new();
|
||||||
assert!(
|
|
||||||
redirect.contains("{{rpc_key_id}}"),
|
|
||||||
"redirect_rpc_key_url user url must contain \"{{rpc_key_id}}\""
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if !top_config.extra.is_empty() {
|
|
||||||
warn!(
|
|
||||||
"unknown TopConfig fields!: {:?}",
|
|
||||||
top_config.app.extra.keys()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if !top_config.app.extra.is_empty() {
|
|
||||||
warn!(
|
|
||||||
"unknown Web3ProxyAppConfig fields!: {:?}",
|
|
||||||
top_config.app.extra.keys()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut db_conn = None::<DatabaseConnection>;
|
let mut db_conn = None::<DatabaseConnection>;
|
||||||
let mut db_replica = None::<DatabaseReplica>;
|
let mut db_replica = None::<DatabaseReplica>;
|
||||||
|
@ -447,46 +426,7 @@ impl Web3ProxyApp {
|
||||||
warn!("no database. some features will be disabled");
|
warn!("no database. some features will be disabled");
|
||||||
};
|
};
|
||||||
|
|
||||||
let balanced_rpcs = top_config.balanced_rpcs;
|
// TODO: do this during apply_config so that we can change redis url while running
|
||||||
|
|
||||||
// safety check on balanced_rpcs
|
|
||||||
if balanced_rpcs.len() < top_config.app.min_synced_rpcs {
|
|
||||||
return Err(anyhow::anyhow!(
|
|
||||||
"Only {}/{} rpcs! Add more balanced_rpcs or reduce min_synced_rpcs.",
|
|
||||||
balanced_rpcs.len(),
|
|
||||||
top_config.app.min_synced_rpcs
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
// safety check on sum soft limit
|
|
||||||
let sum_soft_limit = balanced_rpcs.values().fold(0, |acc, x| acc + x.soft_limit);
|
|
||||||
|
|
||||||
if sum_soft_limit < top_config.app.min_sum_soft_limit {
|
|
||||||
return Err(anyhow::anyhow!(
|
|
||||||
"Only {}/{} soft limit! Add more balanced_rpcs, increase soft limits, or reduce min_sum_soft_limit.",
|
|
||||||
sum_soft_limit,
|
|
||||||
top_config.app.min_sum_soft_limit
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
let private_rpcs = top_config.private_rpcs.unwrap_or_default();
|
|
||||||
|
|
||||||
// these are safe to cancel
|
|
||||||
let cancellable_handles = FuturesUnordered::new();
|
|
||||||
// we must wait for these to end on their own (and they need to subscribe to shutdown_sender)
|
|
||||||
let important_background_handles = FuturesUnordered::new();
|
|
||||||
|
|
||||||
// make a http shared client
|
|
||||||
// TODO: can we configure the connection pool? should we?
|
|
||||||
// TODO: timeouts from config. defaults are hopefully good
|
|
||||||
let http_client = Some(
|
|
||||||
reqwest::ClientBuilder::new()
|
|
||||||
.connect_timeout(Duration::from_secs(5))
|
|
||||||
.timeout(Duration::from_secs(5 * 60))
|
|
||||||
.user_agent(APP_USER_AGENT)
|
|
||||||
.build()?,
|
|
||||||
);
|
|
||||||
|
|
||||||
// create a connection pool for redis
|
// create a connection pool for redis
|
||||||
// a failure to connect does NOT block the application from starting
|
// a failure to connect does NOT block the application from starting
|
||||||
let vredis_pool = match top_config.app.volatile_redis_url.as_ref() {
|
let vredis_pool = match top_config.app.volatile_redis_url.as_ref() {
|
||||||
|
@ -540,6 +480,17 @@ impl Web3ProxyApp {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// make a http shared client
|
||||||
|
// TODO: can we configure the connection pool? should we?
|
||||||
|
// TODO: timeouts from config. defaults are hopefully good
|
||||||
|
let http_client = Some(
|
||||||
|
reqwest::ClientBuilder::new()
|
||||||
|
.connect_timeout(Duration::from_secs(5))
|
||||||
|
.timeout(Duration::from_secs(5 * 60))
|
||||||
|
.user_agent(APP_USER_AGENT)
|
||||||
|
.build()?,
|
||||||
|
);
|
||||||
|
|
||||||
// TODO: i don't like doing Block::default here! Change this to "None"?
|
// TODO: i don't like doing Block::default here! Change this to "None"?
|
||||||
let (watch_consensus_head_sender, watch_consensus_head_receiver) = watch::channel(None);
|
let (watch_consensus_head_sender, watch_consensus_head_receiver) = watch::channel(None);
|
||||||
// TODO: will one receiver lagging be okay? how big should this be?
|
// TODO: will one receiver lagging be okay? how big should this be?
|
||||||
|
@ -575,110 +526,6 @@ impl Web3ProxyApp {
|
||||||
.time_to_idle(Duration::from_secs(300))
|
.time_to_idle(Duration::from_secs(300))
|
||||||
.build_with_hasher(hashbrown::hash_map::DefaultHashBuilder::default());
|
.build_with_hasher(hashbrown::hash_map::DefaultHashBuilder::default());
|
||||||
|
|
||||||
// connect to the load balanced rpcs
|
|
||||||
let (balanced_rpcs, balanced_handle) = Web3Rpcs::spawn(
|
|
||||||
block_map.clone(),
|
|
||||||
top_config.app.chain_id,
|
|
||||||
db_conn.clone(),
|
|
||||||
http_client.clone(),
|
|
||||||
top_config.app.max_block_age,
|
|
||||||
top_config.app.max_block_lag,
|
|
||||||
top_config.app.min_synced_rpcs,
|
|
||||||
top_config.app.min_sum_soft_limit,
|
|
||||||
pending_transactions.clone(),
|
|
||||||
Some(pending_tx_sender.clone()),
|
|
||||||
vredis_pool.clone(),
|
|
||||||
balanced_rpcs,
|
|
||||||
Some(watch_consensus_head_sender),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.context("spawning balanced rpcs")?;
|
|
||||||
|
|
||||||
// save the handle to catch any errors
|
|
||||||
cancellable_handles.push(balanced_handle);
|
|
||||||
|
|
||||||
// connect to the private rpcs
|
|
||||||
// only some chains have this, so this is optional
|
|
||||||
let private_rpcs = if private_rpcs.is_empty() {
|
|
||||||
// TODO: do None instead of clone?
|
|
||||||
warn!("No private relays configured. Any transactions will be broadcast to the public mempool!");
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
let (private_rpcs, private_handle) = Web3Rpcs::spawn(
|
|
||||||
block_map,
|
|
||||||
top_config.app.chain_id,
|
|
||||||
db_conn.clone(),
|
|
||||||
http_client.clone(),
|
|
||||||
// private rpcs don't get subscriptions, so no need for max_block_age or max_block_lag
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
pending_transactions.clone(),
|
|
||||||
// TODO: subscribe to pending transactions on the private rpcs? they seem to have low rate limits, but they should have
|
|
||||||
None,
|
|
||||||
vredis_pool.clone(),
|
|
||||||
private_rpcs,
|
|
||||||
// subscribing to new heads here won't work well. if they are fast, they might be ahead of balanced_rpcs
|
|
||||||
// they also often have low rate limits
|
|
||||||
// however, they are well connected to miners/validators. so maybe using them as a safety check would be good
|
|
||||||
// TODO: but maybe we could include privates in the "backup" tier
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.context("spawning private_rpcs")?;
|
|
||||||
|
|
||||||
if private_rpcs.by_name.is_empty() {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
// save the handle to catch any errors
|
|
||||||
cancellable_handles.push(private_handle);
|
|
||||||
|
|
||||||
Some(private_rpcs)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// create rate limiters
|
|
||||||
// these are optional. they require redis
|
|
||||||
let mut frontend_ip_rate_limiter = None;
|
|
||||||
let mut frontend_registered_user_rate_limiter = None;
|
|
||||||
let mut login_rate_limiter = None;
|
|
||||||
|
|
||||||
if let Some(redis_pool) = vredis_pool.as_ref() {
|
|
||||||
if let Some(public_requests_per_period) = top_config.app.public_requests_per_period {
|
|
||||||
// chain id is included in the app name so that rpc rate limits are per-chain
|
|
||||||
let rpc_rrl = RedisRateLimiter::new(
|
|
||||||
&format!("web3_proxy:{}", top_config.app.chain_id),
|
|
||||||
"frontend",
|
|
||||||
public_requests_per_period,
|
|
||||||
60.0,
|
|
||||||
redis_pool.clone(),
|
|
||||||
);
|
|
||||||
|
|
||||||
// these two rate limiters can share the base limiter
|
|
||||||
// these are deferred rate limiters because we don't want redis network requests on the hot path
|
|
||||||
// TODO: take cache_size from config
|
|
||||||
frontend_ip_rate_limiter = Some(DeferredRateLimiter::<IpAddr>::new(
|
|
||||||
10_000,
|
|
||||||
"ip",
|
|
||||||
rpc_rrl.clone(),
|
|
||||||
None,
|
|
||||||
));
|
|
||||||
frontend_registered_user_rate_limiter = Some(DeferredRateLimiter::<u64>::new(
|
|
||||||
10_000, "key", rpc_rrl, None,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
// login rate limiter
|
|
||||||
login_rate_limiter = Some(RedisRateLimiter::new(
|
|
||||||
"web3_proxy",
|
|
||||||
"login",
|
|
||||||
top_config.app.login_rate_limit_per_period,
|
|
||||||
60.0,
|
|
||||||
redis_pool.clone(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
// responses can be very different in sizes, so this is a cache with a max capacity and a weigher
|
// responses can be very different in sizes, so this is a cache with a max capacity and a weigher
|
||||||
// TODO: don't allow any response to be bigger than X% of the cache
|
// TODO: don't allow any response to be bigger than X% of the cache
|
||||||
let response_cache = Cache::builder()
|
let response_cache = Cache::builder()
|
||||||
|
@ -720,6 +567,68 @@ impl Web3ProxyApp {
|
||||||
.time_to_idle(Duration::from_secs(120))
|
.time_to_idle(Duration::from_secs(120))
|
||||||
.build_with_hasher(hashbrown::hash_map::DefaultHashBuilder::default());
|
.build_with_hasher(hashbrown::hash_map::DefaultHashBuilder::default());
|
||||||
|
|
||||||
|
// set up a Web3Rpcs object to hold all our connections
|
||||||
|
// TODO: for now, only the server_configs can change
|
||||||
|
let (balanced_rpcs, balanced_handle) = Web3Rpcs::spawn(
|
||||||
|
block_map.clone(),
|
||||||
|
top_config.app.chain_id,
|
||||||
|
db_conn.clone(),
|
||||||
|
http_client.clone(),
|
||||||
|
top_config.app.max_block_age,
|
||||||
|
top_config.app.max_block_lag,
|
||||||
|
top_config.app.min_synced_rpcs,
|
||||||
|
top_config.app.min_sum_soft_limit,
|
||||||
|
pending_transactions.clone(),
|
||||||
|
Some(pending_tx_sender.clone()),
|
||||||
|
vredis_pool.clone(),
|
||||||
|
Some(watch_consensus_head_sender),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.context("spawning balanced rpcs")?;
|
||||||
|
|
||||||
|
// connect to the load balanced rpcs
|
||||||
|
balanced_rpcs.apply_server_configs(top_config.balanced_rpcs);
|
||||||
|
|
||||||
|
// connect to the private rpcs
|
||||||
|
// only some chains have this, so this is optional
|
||||||
|
let private_rpc_configs = top_config.private_rpcs.unwrap_or_default();
|
||||||
|
let private_rpcs = if private_rpc_configs.is_empty() {
|
||||||
|
warn!("No private relays configured. Any transactions will be broadcast to the public mempool!");
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
// TODO: do something with the spawn handle
|
||||||
|
let (private_rpcs, _) = Web3Rpcs::spawn(
|
||||||
|
block_map,
|
||||||
|
top_config.app.chain_id,
|
||||||
|
db_conn.clone(),
|
||||||
|
http_client.clone(),
|
||||||
|
// private rpcs don't get subscriptions, so no need for max_block_age or max_block_lag
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
pending_transactions.clone(),
|
||||||
|
// TODO: subscribe to pending transactions on the private rpcs? they seem to have low rate limits, but they should have
|
||||||
|
None,
|
||||||
|
vredis_pool.clone(),
|
||||||
|
// subscribing to new heads here won't work well. if they are fast, they might be ahead of balanced_rpcs
|
||||||
|
// they also often have low rate limits
|
||||||
|
// however, they are well connected to miners/validators. so maybe using them as a safety check would be good
|
||||||
|
// TODO: but maybe we could include privates in the "backup" tier
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.context("spawning private_rpcs")?;
|
||||||
|
|
||||||
|
private_rpcs.apply_server_configs(private_rpc_configs);
|
||||||
|
|
||||||
|
if private_rpcs.by_name.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(private_rpcs)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let app = Self {
|
let app = Self {
|
||||||
config: top_config.app,
|
config: top_config.app,
|
||||||
balanced_rpcs,
|
balanced_rpcs,
|
||||||
|
@ -743,7 +652,106 @@ impl Web3ProxyApp {
|
||||||
|
|
||||||
let app = Arc::new(app);
|
let app = Arc::new(app);
|
||||||
|
|
||||||
Ok((app, cancellable_handles, important_background_handles).into())
|
app.apply_config(top_config).await?;
|
||||||
|
|
||||||
|
// TODO: use channel for receiving new top_configs
|
||||||
|
// TODO: return a channel for sending new top_configs
|
||||||
|
|
||||||
|
Ok((app, important_background_handles).into())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// update the app's balanced_rpcs and private_rpcs
|
||||||
|
/// TODO: make more of the app mutable. for now, db and
|
||||||
|
pub async fn apply_server_configs(
|
||||||
|
self: Arc<Self>,
|
||||||
|
top_config: TopConfig,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
// safety checks on the config
|
||||||
|
if let Some(redirect) = &top_config.app.redirect_rpc_key_url {
|
||||||
|
assert!(
|
||||||
|
redirect.contains("{{rpc_key_id}}"),
|
||||||
|
"redirect_rpc_key_url user url must contain \"{{rpc_key_id}}\""
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if !top_config.extra.is_empty() {
|
||||||
|
warn!(
|
||||||
|
"unknown TopConfig fields!: {:?}",
|
||||||
|
top_config.app.extra.keys()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if !top_config.app.extra.is_empty() {
|
||||||
|
warn!(
|
||||||
|
"unknown Web3ProxyAppConfig fields!: {:?}",
|
||||||
|
top_config.app.extra.keys()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let balanced_rpcs = top_config.balanced_rpcs;
|
||||||
|
|
||||||
|
// safety check on balanced_rpcs
|
||||||
|
if balanced_rpcs.len() < top_config.app.min_synced_rpcs {
|
||||||
|
return Err(anyhow::anyhow!(
|
||||||
|
"Only {}/{} rpcs! Add more balanced_rpcs or reduce min_synced_rpcs.",
|
||||||
|
balanced_rpcs.len(),
|
||||||
|
top_config.app.min_synced_rpcs
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// safety check on sum soft limit
|
||||||
|
let sum_soft_limit = balanced_rpcs.values().fold(0, |acc, x| acc + x.soft_limit);
|
||||||
|
|
||||||
|
if sum_soft_limit < top_config.app.min_sum_soft_limit {
|
||||||
|
return Err(anyhow::anyhow!(
|
||||||
|
"Only {}/{} soft limit! Add more balanced_rpcs, increase soft limits, or reduce min_sum_soft_limit.",
|
||||||
|
sum_soft_limit,
|
||||||
|
top_config.app.min_sum_soft_limit
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// create rate limiters
|
||||||
|
// these are optional. they require redis
|
||||||
|
let mut frontend_ip_rate_limiter = None;
|
||||||
|
let mut frontend_registered_user_rate_limiter = None;
|
||||||
|
let mut login_rate_limiter = None;
|
||||||
|
|
||||||
|
if let Some(redis_pool) = vredis_pool.as_ref() {
|
||||||
|
if let Some(public_requests_per_period) = top_config.app.public_requests_per_period {
|
||||||
|
// chain id is included in the app name so that rpc rate limits are per-chain
|
||||||
|
let rpc_rrl = RedisRateLimiter::new(
|
||||||
|
&format!("web3_proxy:{}", top_config.app.chain_id),
|
||||||
|
"frontend",
|
||||||
|
public_requests_per_period,
|
||||||
|
60.0,
|
||||||
|
redis_pool.clone(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// these two rate limiters can share the base limiter
|
||||||
|
// these are deferred rate limiters because we don't want redis network requests on the hot path
|
||||||
|
// TODO: take cache_size from config
|
||||||
|
frontend_ip_rate_limiter = Some(DeferredRateLimiter::<IpAddr>::new(
|
||||||
|
10_000,
|
||||||
|
"ip",
|
||||||
|
rpc_rrl.clone(),
|
||||||
|
None,
|
||||||
|
));
|
||||||
|
frontend_registered_user_rate_limiter = Some(DeferredRateLimiter::<u64>::new(
|
||||||
|
10_000, "key", rpc_rrl, None,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// login rate limiter
|
||||||
|
login_rate_limiter = Some(RedisRateLimiter::new(
|
||||||
|
"web3_proxy",
|
||||||
|
"login",
|
||||||
|
top_config.app.login_rate_limit_per_period,
|
||||||
|
60.0,
|
||||||
|
redis_pool.clone(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn head_block_receiver(&self) -> watch::Receiver<Option<Web3ProxyBlock>> {
|
pub fn head_block_receiver(&self) -> watch::Receiver<Option<Web3ProxyBlock>> {
|
||||||
|
|
|
@ -31,7 +31,7 @@ use std::sync::atomic::{self, Ordering};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::{cmp, fmt};
|
use std::{cmp, fmt};
|
||||||
use thread_fast_rng::rand::seq::SliceRandom;
|
use thread_fast_rng::rand::seq::SliceRandom;
|
||||||
use tokio::sync::{broadcast, watch};
|
use tokio::sync::{broadcast, watch, RwLock as AsyncRwLock};
|
||||||
use tokio::task;
|
use tokio::task;
|
||||||
use tokio::time::{interval, sleep, sleep_until, Duration, Instant, MissedTickBehavior};
|
use tokio::time::{interval, sleep, sleep_until, Duration, Instant, MissedTickBehavior};
|
||||||
|
|
||||||
|
@ -62,6 +62,91 @@ pub struct Web3Rpcs {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Web3Rpcs {
|
impl Web3Rpcs {
|
||||||
|
pub async fn min_head_rpcs(&self) -> usize {
|
||||||
|
self.min_head_rpcs
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn apply_server_configs(
|
||||||
|
&self,
|
||||||
|
server_configs: HashMap<String, Web3RpcConfig>,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
// turn configs into connections (in parallel)
|
||||||
|
// TODO: move this into a helper function. then we can use it when configs change (will need a remove function too)
|
||||||
|
let mut spawn_handles: FuturesUnordered<_> = server_configs
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|(server_name, server_config)| {
|
||||||
|
if server_config.disabled {
|
||||||
|
info!("{} is disabled", server_name);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let db_conn = db_conn.clone();
|
||||||
|
let http_client = http_client.clone();
|
||||||
|
let redis_pool = redis_pool.clone();
|
||||||
|
let http_interval_sender = http_interval_sender.clone();
|
||||||
|
|
||||||
|
let block_sender = if watch_consensus_head_sender.is_some() {
|
||||||
|
Some(block_sender.clone())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let pending_tx_id_sender = Some(pending_tx_id_sender.clone());
|
||||||
|
let block_map = block_map.clone();
|
||||||
|
|
||||||
|
debug!("spawning {}", server_name);
|
||||||
|
|
||||||
|
let handle = tokio::spawn(async move {
|
||||||
|
server_config
|
||||||
|
.spawn(
|
||||||
|
server_name,
|
||||||
|
db_conn,
|
||||||
|
redis_pool,
|
||||||
|
chain_id,
|
||||||
|
http_client,
|
||||||
|
http_interval_sender,
|
||||||
|
block_map,
|
||||||
|
block_sender,
|
||||||
|
pending_tx_id_sender,
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
});
|
||||||
|
|
||||||
|
Some(handle)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// map of connection names to their connection
|
||||||
|
let mut connections = AsyncRwLock::new(HashMap::new());
|
||||||
|
let mut handles = vec![];
|
||||||
|
|
||||||
|
while let Some(x) = spawn_handles.next().await {
|
||||||
|
match x {
|
||||||
|
Ok(Ok((connection, _handle))) => {
|
||||||
|
// web3 connection worked
|
||||||
|
connections
|
||||||
|
.write()
|
||||||
|
.await
|
||||||
|
.insert(connection.name.clone(), connection);
|
||||||
|
|
||||||
|
// TODO: what should we do with the handle? at least log any errors
|
||||||
|
}
|
||||||
|
Ok(Err(err)) => {
|
||||||
|
// if we got an error here, the app can continue on
|
||||||
|
// TODO: include context about which connection failed
|
||||||
|
error!("Unable to create connection. err={:?}", err);
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
// something actually bad happened. exit with an error
|
||||||
|
return Err(err.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Spawn durable connections to multiple Web3 providers.
|
/// Spawn durable connections to multiple Web3 providers.
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub async fn spawn(
|
pub async fn spawn(
|
||||||
|
@ -76,13 +161,12 @@ impl Web3Rpcs {
|
||||||
pending_transactions: Cache<TxHash, TxStatus, hashbrown::hash_map::DefaultHashBuilder>,
|
pending_transactions: Cache<TxHash, TxStatus, hashbrown::hash_map::DefaultHashBuilder>,
|
||||||
pending_tx_sender: Option<broadcast::Sender<TxStatus>>,
|
pending_tx_sender: Option<broadcast::Sender<TxStatus>>,
|
||||||
redis_pool: Option<redis_rate_limiter::RedisPool>,
|
redis_pool: Option<redis_rate_limiter::RedisPool>,
|
||||||
server_configs: HashMap<String, Web3RpcConfig>,
|
|
||||||
watch_consensus_head_sender: Option<watch::Sender<Option<Web3ProxyBlock>>>,
|
watch_consensus_head_sender: Option<watch::Sender<Option<Web3ProxyBlock>>>,
|
||||||
) -> anyhow::Result<(Arc<Self>, AnyhowJoinHandle<()>)> {
|
) -> anyhow::Result<(Arc<Self>, AnyhowJoinHandle<()>)> {
|
||||||
let (pending_tx_id_sender, pending_tx_id_receiver) = flume::unbounded();
|
let (pending_tx_id_sender, pending_tx_id_receiver) = flume::unbounded();
|
||||||
let (block_sender, block_receiver) = flume::unbounded::<BlockAndRpc>();
|
let (block_sender, block_receiver) = flume::unbounded::<BlockAndRpc>();
|
||||||
|
|
||||||
// TODO: query the rpc to get the actual expected block time, or get from config?
|
// TODO: query the rpc to get the actual expected block time, or get from config? maybe have this be part of a health check?
|
||||||
let expected_block_time_ms = match chain_id {
|
let expected_block_time_ms = match chain_id {
|
||||||
// ethereum
|
// ethereum
|
||||||
1 => 12_000,
|
1 => 12_000,
|
||||||
|
@ -133,77 +217,6 @@ impl Web3Rpcs {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
// turn configs into connections (in parallel)
|
|
||||||
// TODO: move this into a helper function. then we can use it when configs change (will need a remove function too)
|
|
||||||
let mut spawn_handles: FuturesUnordered<_> = server_configs
|
|
||||||
.into_iter()
|
|
||||||
.filter_map(|(server_name, server_config)| {
|
|
||||||
if server_config.disabled {
|
|
||||||
info!("{} is disabled", server_name);
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let db_conn = db_conn.clone();
|
|
||||||
let http_client = http_client.clone();
|
|
||||||
let redis_pool = redis_pool.clone();
|
|
||||||
let http_interval_sender = http_interval_sender.clone();
|
|
||||||
|
|
||||||
let block_sender = if watch_consensus_head_sender.is_some() {
|
|
||||||
Some(block_sender.clone())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
let pending_tx_id_sender = Some(pending_tx_id_sender.clone());
|
|
||||||
let block_map = block_map.clone();
|
|
||||||
|
|
||||||
debug!("spawning {}", server_name);
|
|
||||||
|
|
||||||
let handle = tokio::spawn(async move {
|
|
||||||
server_config
|
|
||||||
.spawn(
|
|
||||||
server_name,
|
|
||||||
db_conn,
|
|
||||||
redis_pool,
|
|
||||||
chain_id,
|
|
||||||
http_client,
|
|
||||||
http_interval_sender,
|
|
||||||
block_map,
|
|
||||||
block_sender,
|
|
||||||
pending_tx_id_sender,
|
|
||||||
true,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
});
|
|
||||||
|
|
||||||
Some(handle)
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
// map of connection names to their connection
|
|
||||||
let mut connections = HashMap::new();
|
|
||||||
let mut handles = vec![];
|
|
||||||
|
|
||||||
// TODO: futures unordered?
|
|
||||||
while let Some(x) = spawn_handles.next().await {
|
|
||||||
match x {
|
|
||||||
Ok(Ok((connection, handle))) => {
|
|
||||||
// web3 connection worked
|
|
||||||
connections.insert(connection.name.clone(), connection);
|
|
||||||
handles.push(handle);
|
|
||||||
}
|
|
||||||
Ok(Err(err)) => {
|
|
||||||
// if we got an error here, the app can continue on
|
|
||||||
// TODO: include context about which connection failed
|
|
||||||
error!("Unable to create connection. err={:?}", err);
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
// something actually bad happened. exit with an error
|
|
||||||
return Err(err.into());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: max_capacity and time_to_idle from config
|
// TODO: max_capacity and time_to_idle from config
|
||||||
// all block hashes are the same size, so no need for weigher
|
// all block hashes are the same size, so no need for weigher
|
||||||
let block_hashes = Cache::builder()
|
let block_hashes = Cache::builder()
|
||||||
|
|
Loading…
Reference in New Issue