2022-08-26 20:26:17 +03:00
use super ::blockchain ::BlockId ;
use super ::connections ::BlockMap ;
2022-08-24 03:11:49 +03:00
///! Rate-limited communication with a web3 provider.
2022-08-24 02:56:47 +03:00
use super ::provider ::Web3Provider ;
2022-08-24 03:14:49 +03:00
use super ::request ::OpenRequestHandle ;
use super ::request ::OpenRequestResult ;
2022-08-24 02:56:47 +03:00
use crate ::app ::{ flatten_handle , AnyhowJoinHandle } ;
use crate ::config ::BlockAndRpc ;
2022-06-16 05:53:37 +03:00
use anyhow ::Context ;
2022-08-26 20:26:17 +03:00
use dashmap ::mapref ::entry ::Entry ;
use dashmap ::DashMap ;
2022-07-19 04:31:12 +03:00
use ethers ::prelude ::{ Block , Bytes , Middleware , ProviderError , TxHash , H256 , U64 } ;
2022-06-16 05:53:37 +03:00
use futures ::future ::try_join_all ;
2022-05-05 22:07:09 +03:00
use futures ::StreamExt ;
2022-08-10 05:37:34 +03:00
use parking_lot ::RwLock ;
2022-08-16 01:50:56 +03:00
use redis_rate_limit ::{ RedisPool , RedisRateLimit , ThrottleResult } ;
2022-05-21 01:16:15 +03:00
use serde ::ser ::{ SerializeStruct , Serializer } ;
use serde ::Serialize ;
2022-05-05 22:07:09 +03:00
use std ::fmt ;
2022-06-14 07:04:14 +03:00
use std ::hash ::{ Hash , Hasher } ;
2022-07-19 04:31:12 +03:00
use std ::sync ::atomic ::{ self , AtomicU32 , AtomicU64 } ;
2022-05-05 22:07:09 +03:00
use std ::{ cmp ::Ordering , sync ::Arc } ;
2022-06-29 22:15:05 +03:00
use tokio ::sync ::broadcast ;
2022-08-10 05:37:34 +03:00
use tokio ::sync ::RwLock as AsyncRwLock ;
2022-08-24 03:14:49 +03:00
use tokio ::time ::{ interval , sleep , sleep_until , Duration , MissedTickBehavior } ;
2022-08-26 20:26:17 +03:00
use tracing ::debug ;
2022-08-24 03:14:49 +03:00
use tracing ::{ error , info , instrument , trace , warn } ;
2022-06-14 08:43:28 +03:00
2022-08-24 02:13:56 +03:00
/// An active connection to a Web3Rpc
pub struct Web3Connection {
2022-08-24 03:11:49 +03:00
pub name : String ,
2022-08-24 02:13:56 +03:00
/// TODO: can we get this from the provider? do we even need it?
2022-08-26 20:26:17 +03:00
url : String ,
2022-08-24 02:13:56 +03:00
/// keep track of currently open requests. We sort on this
2022-08-24 03:11:49 +03:00
pub ( super ) active_requests : AtomicU32 ,
2022-08-24 02:13:56 +03:00
/// provider is in a RwLock so that we can replace it if re-connecting
/// it is an async lock because we hold it open across awaits
2022-08-24 03:11:49 +03:00
pub ( super ) provider : AsyncRwLock < Option < Arc < Web3Provider > > > ,
2022-08-24 02:13:56 +03:00
/// rate limits are stored in a central redis so that multiple proxies can share their rate limits
hard_limit : Option < RedisRateLimit > ,
/// used for load balancing to the least loaded server
2022-08-26 20:26:17 +03:00
pub ( super ) soft_limit : u32 ,
2022-08-24 02:13:56 +03:00
block_data_limit : AtomicU64 ,
2022-08-26 20:26:17 +03:00
/// Lower weight are higher priority when sending requests
pub ( super ) weight : u32 ,
// TODO: async lock?
pub ( super ) head_block : RwLock < BlockId > ,
2022-08-24 02:13:56 +03:00
}
2022-05-05 22:07:09 +03:00
impl Web3Connection {
2022-06-14 07:04:14 +03:00
/// Connect to a web3 rpc
2022-06-14 08:43:28 +03:00
// #[instrument(name = "spawn_Web3Connection", skip(hard_limit, http_client))]
2022-07-19 07:21:32 +03:00
// TODO: have this take a builder (which will have channels attached)
2022-06-14 08:43:28 +03:00
#[ allow(clippy::too_many_arguments) ]
2022-06-14 07:04:14 +03:00
pub async fn spawn (
2022-08-10 08:56:09 +03:00
name : String ,
2022-07-19 07:21:32 +03:00
chain_id : u64 ,
2022-05-05 22:07:09 +03:00
url_str : String ,
2022-05-15 04:51:24 +03:00
// optional because this is only used for http providers. websocket providers don't use it
2022-07-19 07:21:32 +03:00
http_client : Option < reqwest ::Client > ,
2022-06-29 22:15:05 +03:00
http_interval_sender : Option < Arc < broadcast ::Sender < ( ) > > > ,
2022-08-16 01:50:56 +03:00
// TODO: have a builder struct for this.
hard_limit : Option < ( u64 , RedisPool ) > ,
2022-05-05 22:07:09 +03:00
// TODO: think more about this type
soft_limit : u32 ,
2022-08-26 20:26:17 +03:00
block_map : BlockMap ,
2022-07-22 08:11:26 +03:00
block_sender : Option < flume ::Sender < BlockAndRpc > > ,
2022-06-14 08:43:28 +03:00
tx_id_sender : Option < flume ::Sender < ( TxHash , Arc < Self > ) > > ,
reconnect : bool ,
2022-08-08 22:57:54 +03:00
weight : u32 ,
2022-06-14 08:43:28 +03:00
) -> anyhow ::Result < ( Arc < Web3Connection > , AnyhowJoinHandle < ( ) > ) > {
2022-05-22 21:39:06 +03:00
let hard_limit = hard_limit . map ( | ( hard_rate_limit , redis_conection ) | {
2022-08-06 05:29:55 +03:00
// TODO: allow configurable period and max_burst
2022-05-22 02:34:05 +03:00
let period = 1 ;
2022-08-16 01:50:56 +03:00
RedisRateLimit ::new (
2022-07-19 07:21:32 +03:00
redis_conection ,
2022-08-06 08:46:33 +03:00
" web3_proxy " ,
2022-08-06 05:29:55 +03:00
& format! ( " {} : {} " , chain_id , url_str ) ,
2022-05-22 02:34:05 +03:00
hard_rate_limit ,
period ,
)
} ) ;
2022-05-05 22:07:09 +03:00
2022-05-17 05:26:47 +03:00
let provider = Web3Provider ::from_str ( & url_str , http_client ) . await ? ;
2022-05-05 22:07:09 +03:00
2022-07-09 07:25:59 +03:00
let new_connection = Self {
2022-08-10 08:56:09 +03:00
name ,
2022-05-05 22:07:09 +03:00
url : url_str . clone ( ) ,
2022-05-15 04:51:24 +03:00
active_requests : 0. into ( ) ,
2022-08-10 05:37:34 +03:00
provider : AsyncRwLock ::new ( Some ( Arc ::new ( provider ) ) ) ,
2022-05-22 02:34:05 +03:00
hard_limit ,
2022-05-05 22:07:09 +03:00
soft_limit ,
2022-07-19 04:31:12 +03:00
block_data_limit : Default ::default ( ) ,
2022-08-26 20:26:17 +03:00
head_block : RwLock ::new ( Default ::default ( ) ) ,
2022-08-08 22:57:54 +03:00
weight ,
2022-05-12 21:49:57 +03:00
} ;
2022-07-09 07:25:59 +03:00
let new_connection = Arc ::new ( new_connection ) ;
2022-05-12 21:49:57 +03:00
2022-05-15 04:51:24 +03:00
// check the server's chain_id here
2022-07-19 04:31:12 +03:00
// TODO: move this outside the `new` function and into a `start` function or something. that way we can do retries from there
2022-05-15 04:51:24 +03:00
// TODO: some public rpcs (on bsc and fantom) do not return an id and so this ends up being an error
2022-06-17 01:23:41 +03:00
// TODO: this will wait forever. do we want that?
2022-07-19 07:21:32 +03:00
let found_chain_id : Result < U64 , _ > = new_connection
2022-06-14 07:04:14 +03:00
. wait_for_request_handle ( )
2022-08-06 08:26:43 +03:00
. await ?
2022-05-15 04:51:24 +03:00
. request ( " eth_chainId " , Option ::None ::< ( ) > )
2022-05-13 00:20:33 +03:00
. await ;
match found_chain_id {
Ok ( found_chain_id ) = > {
2022-06-29 22:15:05 +03:00
// TODO: there has to be a cleaner way to do this
2022-07-19 07:21:32 +03:00
if chain_id ! = found_chain_id . as_u64 ( ) {
2022-05-13 00:20:33 +03:00
return Err ( anyhow ::anyhow! (
" incorrect chain id! Expected {}. Found {} " ,
chain_id ,
found_chain_id
2022-07-19 07:21:32 +03:00
)
2022-07-19 09:41:04 +03:00
. context ( format! ( " failed @ {} " , new_connection ) ) ) ;
2022-05-13 00:20:33 +03:00
}
}
Err ( e ) = > {
2022-07-19 09:41:04 +03:00
let e = anyhow ::Error ::from ( e ) . context ( format! ( " failed @ {} " , new_connection ) ) ;
2022-05-13 00:20:33 +03:00
return Err ( e ) ;
}
2022-05-12 21:49:57 +03:00
}
2022-08-11 00:52:28 +03:00
let will_subscribe_to_blocks = block_sender . is_some ( ) ;
2022-07-19 04:31:12 +03:00
// subscribe to new blocks and new transactions
// TODO: make transaction subscription optional (just pass None for tx_id_sender)
2022-06-14 08:43:28 +03:00
let handle = {
2022-07-09 07:25:59 +03:00
let new_connection = new_connection . clone ( ) ;
2022-06-14 08:43:28 +03:00
tokio ::spawn ( async move {
2022-07-09 07:25:59 +03:00
new_connection
2022-08-26 20:26:17 +03:00
. subscribe (
http_interval_sender ,
block_map ,
block_sender ,
tx_id_sender ,
reconnect ,
)
2022-06-14 08:43:28 +03:00
. await
} )
} ;
2022-07-19 04:31:12 +03:00
// we could take "archive" as a parameter, but we would want a safety check on it regardless
2022-07-19 07:22:02 +03:00
// check common archive thresholds
2022-07-23 03:19:13 +03:00
// TODO: would be great if rpcs exposed this
// TODO: move this to a helper function so we can recheck on errors or as the chain grows
2022-08-03 03:27:26 +03:00
// TODO: move this to a helper function that checks
2022-08-11 00:52:28 +03:00
if will_subscribe_to_blocks {
// TODO: make sure the server isn't still syncing
2022-07-19 04:31:12 +03:00
2022-08-11 00:52:28 +03:00
// TODO: don't sleep. wait for new heads subscription instead
// TODO: i think instead of atomics, we could maybe use a watch channel
sleep ( Duration ::from_millis ( 250 ) ) . await ;
2022-07-19 04:31:12 +03:00
2022-08-11 00:52:28 +03:00
for block_data_limit in [ u64 ::MAX , 90_000 , 128 , 64 , 32 ] {
2022-08-26 20:26:17 +03:00
let mut head_block_num = new_connection . head_block . read ( ) . num ;
2022-07-19 04:31:12 +03:00
2022-08-11 00:52:28 +03:00
// TODO: wait until head block is set outside the loop? if we disconnect while starting we could actually get 0 though
while head_block_num = = U64 ::zero ( ) {
warn! ( ? new_connection , " no head block " ) ;
2022-07-19 04:31:12 +03:00
2022-08-11 00:52:28 +03:00
// TODO: subscribe to a channel instead of polling? subscribe to http_interval_sender?
sleep ( Duration ::from_secs ( 1 ) ) . await ;
2022-08-26 20:26:17 +03:00
head_block_num = new_connection . head_block . read ( ) . num ;
2022-08-11 00:52:28 +03:00
}
// TODO: subtract 1 from block_data_limit for safety?
let maybe_archive_block = head_block_num
. saturating_sub ( ( block_data_limit ) . into ( ) )
. max ( U64 ::one ( ) ) ;
let archive_result : Result < Bytes , _ > = new_connection
. wait_for_request_handle ( )
. await ?
. request (
" eth_getCode " ,
(
" 0xdead00000000000000000000000000000000beef " ,
maybe_archive_block ,
) ,
)
. await ;
2022-07-19 04:31:12 +03:00
2022-08-11 00:52:28 +03:00
trace! ( ? archive_result , " {} " , new_connection ) ;
if archive_result . is_ok ( ) {
new_connection
. block_data_limit
. store ( block_data_limit , atomic ::Ordering ::Release ) ;
break ;
}
2022-07-19 04:31:12 +03:00
}
}
info! ( ? new_connection , " success " ) ;
2022-07-09 07:25:59 +03:00
Ok ( ( new_connection , handle ) )
}
2022-07-19 04:31:12 +03:00
/// TODO: this might be too simple. different nodes can prune differently
2022-07-25 03:27:00 +03:00
pub fn block_data_limit ( & self ) -> U64 {
2022-07-19 04:31:12 +03:00
self . block_data_limit . load ( atomic ::Ordering ::Acquire ) . into ( )
}
2022-07-22 22:30:39 +03:00
pub fn has_block_data ( & self , needed_block_num : & U64 ) -> bool {
2022-07-25 03:27:00 +03:00
let block_data_limit : U64 = self . block_data_limit ( ) ;
2022-07-19 04:31:12 +03:00
2022-08-26 20:26:17 +03:00
let newest_block_num = self . head_block . read ( ) . num ;
2022-07-19 04:31:12 +03:00
let oldest_block_num = newest_block_num
. saturating_sub ( block_data_limit )
. max ( U64 ::one ( ) ) ;
2022-07-22 22:30:39 +03:00
needed_block_num > = & oldest_block_num & & needed_block_num < = & newest_block_num
2022-05-05 22:07:09 +03:00
}
2022-06-14 07:04:14 +03:00
#[ instrument(skip_all) ]
pub async fn reconnect (
self : & Arc < Self > ,
2022-07-22 08:11:26 +03:00
block_sender : Option < flume ::Sender < BlockAndRpc > > ,
2022-06-14 07:04:14 +03:00
) -> anyhow ::Result < ( ) > {
// websocket doesn't need the http client
let http_client = None ;
2022-07-19 04:31:12 +03:00
info! ( ? self , " reconnecting " ) ;
2022-06-14 07:04:14 +03:00
// since this lock is held open over an await, we use tokio's locking
2022-07-19 04:31:12 +03:00
// TODO: timeout on this lock. if its slow, something is wrong
2022-06-14 07:04:14 +03:00
let mut provider = self . provider . write ( ) . await ;
* provider = None ;
2022-07-25 03:27:00 +03:00
// tell the block subscriber that we don't have any blocks
2022-06-16 05:53:37 +03:00
if let Some ( block_sender ) = block_sender {
block_sender
2022-07-22 08:11:26 +03:00
. send_async ( ( Arc ::new ( Block ::default ( ) ) , self . clone ( ) ) )
2022-06-16 05:53:37 +03:00
. await
. context ( " block_sender at 0 " ) ? ;
}
2022-06-14 07:04:14 +03:00
2022-06-16 05:53:37 +03:00
// TODO: if this fails, keep retrying
2022-06-14 07:04:14 +03:00
let new_provider = Web3Provider ::from_str ( & self . url , http_client ) . await ? ;
* provider = Some ( Arc ::new ( new_provider ) ) ;
Ok ( ( ) )
}
2022-05-06 09:07:01 +03:00
#[ inline ]
2022-05-05 22:07:09 +03:00
pub fn active_requests ( & self ) -> u32 {
self . active_requests . load ( atomic ::Ordering ::Acquire )
}
2022-06-04 01:22:55 +03:00
#[ inline ]
pub async fn has_provider ( & self ) -> bool {
self . provider . read ( ) . await . is_some ( )
}
2022-05-17 03:56:56 +03:00
#[ instrument(skip_all) ]
2022-08-26 20:26:17 +03:00
async fn send_head_block_result (
self : & Arc < Self > ,
new_head_block : Result < Arc < Block < TxHash > > , ProviderError > ,
2022-07-22 08:11:26 +03:00
block_sender : & flume ::Sender < BlockAndRpc > ,
2022-08-26 20:26:17 +03:00
block_map : BlockMap ,
2022-05-30 07:30:13 +03:00
) -> anyhow ::Result < ( ) > {
2022-08-26 20:26:17 +03:00
match new_head_block {
Ok ( new_head_block ) = > {
// TODO: is unwrap_or_default ok? we might have an empty block
let new_hash = new_head_block . hash . unwrap_or_default ( ) ;
// if we already have this block saved, we don't need to store this copy
let new_head_block = match block_map . entry ( new_hash ) {
Entry ::Occupied ( x ) = > x . get ( ) . clone ( ) ,
Entry ::Vacant ( x ) = > {
// TODO: remove this once https://github.com/ledgerwatch/erigon/issues/5190 is closed
// TODO: include transactions?
let new_head_block = if new_head_block . total_difficulty . is_none ( ) {
self . wait_for_request_handle ( )
. await ?
. request ( " eth_getBlockByHash " , ( new_hash , false ) )
. await ?
} else {
new_head_block
} ;
x . insert ( new_head_block ) . clone ( )
}
} ;
2022-07-19 04:31:12 +03:00
2022-08-26 20:26:17 +03:00
let new_num = new_head_block . number . unwrap_or_default ( ) ;
// save the block so we don't send the same one multiple times
// also save so that archive checks can know how far back to query
{
2022-07-19 04:31:12 +03:00
let mut head_block = self . head_block . write ( ) ;
2022-08-26 20:26:17 +03:00
head_block . hash = new_hash ;
head_block . num = new_num ;
2022-07-19 04:31:12 +03:00
}
2022-06-16 05:53:37 +03:00
block_sender
2022-08-26 20:26:17 +03:00
. send_async ( ( new_head_block , self . clone ( ) ) )
2022-06-16 05:53:37 +03:00
. await
. context ( " block_sender " ) ? ;
2022-05-15 09:27:13 +03:00
}
Err ( e ) = > {
warn! ( " unable to get block from {}: {} " , self , e ) ;
2022-08-26 20:26:17 +03:00
// TODO: do something to rpc_chain?
2022-08-07 09:48:57 +03:00
// send an empty block to take this server out of rotation
block_sender
2022-08-26 20:26:17 +03:00
. send_async ( ( Arc ::new ( Block ::default ( ) ) , self . clone ( ) ) )
2022-08-07 09:48:57 +03:00
. await
. context ( " block_sender " ) ? ;
2022-05-15 09:27:13 +03:00
}
}
2022-05-30 07:30:13 +03:00
Ok ( ( ) )
2022-05-15 09:27:13 +03:00
}
2022-06-14 08:43:28 +03:00
async fn subscribe (
2022-06-14 07:04:14 +03:00
self : Arc < Self > ,
2022-06-29 22:15:05 +03:00
http_interval_sender : Option < Arc < broadcast ::Sender < ( ) > > > ,
2022-08-26 20:26:17 +03:00
block_map : BlockMap ,
2022-07-22 08:11:26 +03:00
block_sender : Option < flume ::Sender < BlockAndRpc > > ,
2022-06-14 08:43:28 +03:00
tx_id_sender : Option < flume ::Sender < ( TxHash , Arc < Self > ) > > ,
2022-06-14 07:04:14 +03:00
reconnect : bool ,
) -> anyhow ::Result < ( ) > {
2022-06-16 05:53:37 +03:00
loop {
2022-07-19 07:24:16 +03:00
let http_interval_receiver = http_interval_sender . as_ref ( ) . map ( | x | x . subscribe ( ) ) ;
2022-06-29 22:15:05 +03:00
2022-06-16 05:53:37 +03:00
let mut futures = vec! [ ] ;
if let Some ( block_sender ) = & block_sender {
2022-08-26 20:26:17 +03:00
let f = self . clone ( ) . subscribe_new_heads (
http_interval_receiver ,
block_sender . clone ( ) ,
block_map . clone ( ) ,
) ;
2022-06-16 05:53:37 +03:00
futures . push ( flatten_handle ( tokio ::spawn ( f ) ) ) ;
}
if let Some ( tx_id_sender ) = & tx_id_sender {
let f = self
. clone ( )
. subscribe_pending_transactions ( tx_id_sender . clone ( ) ) ;
futures . push ( flatten_handle ( tokio ::spawn ( f ) ) ) ;
}
if futures . is_empty ( ) {
2022-06-14 08:43:28 +03:00
// TODO: is there a better way to make a channel that is never ready?
2022-06-16 05:53:37 +03:00
info! ( ? self , " no-op subscription " ) ;
return Ok ( ( ) ) ;
2022-06-14 07:04:14 +03:00
}
2022-06-16 05:53:37 +03:00
match try_join_all ( futures ) . await {
Ok ( _ ) = > break ,
Err ( err ) = > {
if reconnect {
// TODO: exponential backoff
let retry_in = Duration ::from_secs ( 1 ) ;
warn! (
? self ,
" subscription exited. Attempting to reconnect in {:?}. {:?} " ,
retry_in ,
err
) ;
sleep ( retry_in ) . await ;
// TODO: loop on reconnecting! do not return with a "?" here
// TODO: this isn't going to work. it will get in a loop with newHeads
self . reconnect ( block_sender . clone ( ) ) . await ? ;
} else {
error! ( ? self , ? err , " subscription exited " ) ;
return Err ( err ) ;
}
2022-06-14 08:43:28 +03:00
}
2022-06-14 07:04:14 +03:00
}
}
Ok ( ( ) )
}
2022-05-17 05:26:47 +03:00
/// Subscribe to new blocks. If `reconnect` is true, this runs forever.
2022-05-17 07:24:13 +03:00
/// TODO: instrument with the url
2022-05-17 03:56:56 +03:00
#[ instrument(skip_all) ]
2022-06-14 07:04:14 +03:00
async fn subscribe_new_heads (
2022-05-05 22:07:09 +03:00
self : Arc < Self > ,
2022-06-29 22:15:05 +03:00
http_interval_receiver : Option < broadcast ::Receiver < ( ) > > ,
2022-07-22 08:11:26 +03:00
block_sender : flume ::Sender < BlockAndRpc > ,
2022-08-26 20:26:17 +03:00
block_map : BlockMap ,
2022-05-05 22:07:09 +03:00
) -> anyhow ::Result < ( ) > {
2022-08-26 20:26:17 +03:00
info! ( ? self , " watching new_heads " ) ;
2022-06-14 07:04:14 +03:00
// TODO: is a RwLock of an Option<Arc> the right thing here?
if let Some ( provider ) = self . provider . read ( ) . await . clone ( ) {
match & * provider {
Web3Provider ::Http ( _provider ) = > {
// there is a "watch_blocks" function, but a lot of public nodes do not support the necessary rpc endpoints
2022-06-29 22:15:05 +03:00
// TODO: try watch_blocks and fall back to this?
let mut http_interval_receiver = http_interval_receiver . unwrap ( ) ;
2022-06-14 07:04:14 +03:00
2022-07-19 04:31:12 +03:00
let mut last_hash = H256 ::zero ( ) ;
2022-06-14 07:04:14 +03:00
loop {
2022-08-26 20:26:17 +03:00
// TODO: try, or wait_for?
match self . wait_for_request_handle ( ) . await {
Ok ( active_request_handle ) = > {
2022-06-14 07:04:14 +03:00
let block : Result < Block < TxHash > , _ > = active_request_handle
. request ( " eth_getBlockByNumber " , ( " latest " , false ) )
. await ;
2022-08-26 20:26:17 +03:00
match block {
Ok ( block ) = > {
// don't send repeat blocks
let new_hash = block
. hash
. expect ( " blocks here should always have hashes " ) ;
if new_hash ! = last_hash {
// new hash!
last_hash = new_hash ;
self . send_head_block_result (
Ok ( Arc ::new ( block ) ) ,
& block_sender ,
block_map . clone ( ) ,
)
2022-08-07 09:48:57 +03:00
. await ? ;
2022-08-26 20:26:17 +03:00
}
}
Err ( err ) = > {
// we did not get a block back. something is up with the server. take it out of rotation
self . send_head_block_result (
Err ( err ) ,
& block_sender ,
block_map . clone ( ) ,
)
. await ? ;
2022-07-19 07:31:30 +03:00
}
2022-05-16 22:15:40 +03:00
}
2022-06-14 07:04:14 +03:00
}
2022-07-09 01:14:45 +03:00
Err ( err ) = > {
2022-08-11 00:29:50 +03:00
warn! ( ? err , " Internal error on latest block from {} " , self ) ;
// TODO: what should we do? sleep? extra time?
2022-05-16 22:15:40 +03:00
}
}
2022-07-19 04:31:12 +03:00
2022-08-26 20:26:17 +03:00
// wait for the next interval
2022-07-19 04:31:12 +03:00
// TODO: if error or rate limit, increase interval?
while let Err ( err ) = http_interval_receiver . recv ( ) . await {
match err {
broadcast ::error ::RecvError ::Closed = > {
2022-08-26 20:26:17 +03:00
// channel is closed! that's not good. bubble the error up
2022-07-19 04:31:12 +03:00
return Err ( err . into ( ) ) ;
}
broadcast ::error ::RecvError ::Lagged ( lagged ) = > {
2022-08-26 20:26:17 +03:00
// querying the block was delayed
// this can happen if tokio is very busy or waiting for requests limits took too long
2022-07-19 04:31:12 +03:00
warn! ( ? err , ? self , " http interval lagging by {}! " , lagged ) ;
}
}
}
trace! ( ? self , " ok http interval " ) ;
2022-05-15 22:28:22 +03:00
}
2022-06-14 07:04:14 +03:00
}
Web3Provider ::Ws ( provider ) = > {
let active_request_handle = self . wait_for_request_handle ( ) . await ;
let mut stream = provider . subscribe_blocks ( ) . await ? ;
drop ( active_request_handle ) ;
// query the block once since the subscription doesn't send the current block
// there is a very small race condition here where the stream could send us a new block right now
// all it does is print "new block" for the same block as current block
2022-08-26 20:26:17 +03:00
let block : Result < Arc < Block < TxHash > > , _ > = self
2022-06-14 07:04:14 +03:00
. wait_for_request_handle ( )
2022-08-06 08:26:43 +03:00
. await ?
2022-06-14 07:04:14 +03:00
. request ( " eth_getBlockByNumber " , ( " latest " , false ) )
2022-08-26 20:26:17 +03:00
. await
. map ( Arc ::new ) ;
2022-06-14 07:04:14 +03:00
2022-08-26 20:26:17 +03:00
self . send_head_block_result ( block , & block_sender , block_map . clone ( ) )
. await ? ;
2022-06-14 07:04:14 +03:00
2022-07-09 01:14:45 +03:00
while let Some ( new_block ) = stream . next ( ) . await {
2022-08-26 20:26:17 +03:00
self . send_head_block_result (
Ok ( Arc ::new ( new_block ) ) ,
& block_sender ,
block_map . clone ( ) ,
)
. await ? ;
2022-05-17 05:26:47 +03:00
}
2022-07-09 01:14:45 +03:00
warn! ( ? self , " subscription ended " ) ;
2022-05-05 22:07:09 +03:00
}
2022-05-17 05:26:47 +03:00
}
2022-06-14 07:04:14 +03:00
}
2022-05-17 03:56:56 +03:00
2022-06-14 07:04:14 +03:00
Ok ( ( ) )
}
2022-05-17 05:26:47 +03:00
2022-06-14 07:04:14 +03:00
#[ instrument(skip_all) ]
async fn subscribe_pending_transactions (
self : Arc < Self > ,
tx_id_sender : flume ::Sender < ( TxHash , Arc < Self > ) > ,
) -> anyhow ::Result < ( ) > {
2022-08-16 07:56:01 +03:00
// TODO: move this data into a span?
2022-06-14 08:43:28 +03:00
info! ( " watching {} " , self ) ;
2022-06-14 07:04:14 +03:00
// TODO: is a RwLock of an Option<Arc> the right thing here?
if let Some ( provider ) = self . provider . read ( ) . await . clone ( ) {
match & * provider {
2022-06-18 10:06:54 +03:00
Web3Provider ::Http ( provider ) = > {
2022-06-14 07:04:14 +03:00
// there is a "watch_pending_transactions" function, but a lot of public nodes do not support the necessary rpc endpoints
// TODO: what should this interval be? probably automatically set to some fraction of block time
// TODO: maybe it would be better to have one interval for all of the http providers, but this works for now
// TODO: if there are some websocket providers, maybe have a longer interval and a channel that tells the https to update when a websocket gets a new head? if they are slow this wouldn't work well though
2022-06-14 08:43:28 +03:00
let mut interval = interval ( Duration ::from_secs ( 60 ) ) ;
2022-06-14 07:04:14 +03:00
interval . set_missed_tick_behavior ( MissedTickBehavior ::Delay ) ;
loop {
2022-06-14 08:43:28 +03:00
// TODO: actually do something here
/*
2022-06-14 07:04:14 +03:00
match self . try_request_handle ( ) . await {
Ok ( active_request_handle ) = > {
// TODO: check the filter
2022-07-22 22:30:39 +03:00
todo! ( " actually send a request " ) ;
2022-06-14 07:04:14 +03:00
}
Err ( e ) = > {
warn! ( " Failed getting latest block from {}: {:?} " , self , e ) ;
}
}
2022-06-14 08:43:28 +03:00
* /
2022-07-07 06:22:09 +03:00
// wait for the interval
// TODO: if error or rate limit, increase interval?
interval . tick ( ) . await ;
2022-06-14 07:04:14 +03:00
}
}
Web3Provider ::Ws ( provider ) = > {
2022-07-09 02:02:32 +03:00
// TODO: maybe the subscribe_pending_txs function should be on the active_request_handle
2022-06-14 07:04:14 +03:00
let active_request_handle = self . wait_for_request_handle ( ) . await ;
let mut stream = provider . subscribe_pending_txs ( ) . await ? ;
drop ( active_request_handle ) ;
2022-07-08 21:27:06 +03:00
while let Some ( pending_tx_id ) = stream . next ( ) . await {
tx_id_sender
. send_async ( ( pending_tx_id , self . clone ( ) ) )
. await
. context ( " tx_id_sender " ) ? ;
2022-08-11 00:29:50 +03:00
// TODO: periodically check for listeners. if no one is subscribed, unsubscribe and wait for a subscription
2022-06-14 07:04:14 +03:00
}
2022-07-08 21:27:06 +03:00
2022-08-11 05:57:01 +03:00
warn! ( ? self , " subscription ended " ) ;
2022-06-14 07:04:14 +03:00
}
2022-05-05 22:07:09 +03:00
}
}
Ok ( ( ) )
}
2022-06-17 01:23:41 +03:00
/// be careful with this; it will wait forever!
2022-08-24 03:59:05 +03:00
// TODO: maximum wait time?
2022-05-17 03:56:56 +03:00
#[ instrument(skip_all) ]
2022-08-24 03:14:49 +03:00
pub async fn wait_for_request_handle ( self : & Arc < Self > ) -> anyhow ::Result < OpenRequestHandle > {
2022-07-09 02:02:32 +03:00
// TODO: maximum wait time? i think timeouts in other parts of the code are probably best
2022-05-16 22:15:40 +03:00
2022-06-17 01:23:41 +03:00
loop {
2022-08-26 20:26:17 +03:00
match self . try_request_handle ( ) . await {
2022-08-24 03:59:05 +03:00
Ok ( OpenRequestResult ::Handle ( handle ) ) = > return Ok ( handle ) ,
2022-08-24 03:14:49 +03:00
Ok ( OpenRequestResult ::RetryAt ( retry_at ) ) = > {
2022-08-07 09:48:57 +03:00
// TODO: emit a stat?
sleep_until ( retry_at ) . await ;
}
2022-08-24 03:14:49 +03:00
Ok ( OpenRequestResult ::None ) = > {
2022-08-24 03:59:05 +03:00
// TODO: when can this happen? log? emit a stat?
// TODO: subscribe to the head block on this
2022-08-07 09:48:57 +03:00
// TODO: sleep how long? maybe just error?
sleep ( Duration ::from_secs ( 1 ) ) . await ;
2022-05-06 07:29:25 +03:00
}
2022-08-07 09:48:57 +03:00
Err ( err ) = > return Err ( err ) ,
2022-05-06 07:29:25 +03:00
}
2022-05-05 22:07:09 +03:00
}
}
2022-08-26 20:26:17 +03:00
pub async fn try_request_handle ( self : & Arc < Self > ) -> anyhow ::Result < OpenRequestResult > {
2022-06-04 01:22:55 +03:00
// check that we are connected
if ! self . has_provider ( ) . await {
2022-08-07 09:48:57 +03:00
// TODO: emit a stat?
2022-08-24 03:14:49 +03:00
return Ok ( OpenRequestResult ::None ) ;
2022-06-04 01:22:55 +03:00
}
2022-05-05 22:07:09 +03:00
// check rate limits
2022-05-22 02:34:05 +03:00
if let Some ( ratelimiter ) = self . hard_limit . as_ref ( ) {
match ratelimiter . throttle ( ) . await {
2022-08-07 09:48:57 +03:00
Ok ( ThrottleResult ::Allowed ) = > {
2022-05-05 22:07:09 +03:00
// rate limit succeeded
}
2022-08-07 09:48:57 +03:00
Ok ( ThrottleResult ::RetryAt ( retry_at ) ) = > {
2022-05-05 22:07:09 +03:00
// rate limit failed
2022-05-22 02:34:05 +03:00
// save the smallest retry_after. if nothing succeeds, return an Err with retry_after in it
2022-05-05 22:07:09 +03:00
// TODO: use tracing better
2022-06-25 06:33:49 +03:00
// TODO: i'm seeing "Exhausted rate limit on moralis: 0ns". How is it getting 0?
2022-08-07 09:48:57 +03:00
warn! ( ? retry_at , ? self , " Exhausted rate limit " ) ;
2022-05-05 22:07:09 +03:00
2022-08-24 03:14:49 +03:00
return Ok ( OpenRequestResult ::RetryAt ( retry_at . into ( ) ) ) ;
2022-08-07 09:48:57 +03:00
}
2022-08-16 01:50:56 +03:00
Ok ( ThrottleResult ::RetryNever ) = > {
return Err ( anyhow ::anyhow! ( " Rate limit failed. " ) ) ;
}
2022-08-07 09:48:57 +03:00
Err ( err ) = > {
return Err ( err ) ;
2022-05-05 22:07:09 +03:00
}
}
} ;
2022-08-24 03:14:49 +03:00
let handle = OpenRequestHandle ::new ( self . clone ( ) ) ;
2022-08-24 03:11:49 +03:00
2022-08-24 03:59:05 +03:00
Ok ( OpenRequestResult ::Handle ( handle ) )
2022-08-24 03:11:49 +03:00
}
}
2022-08-07 09:48:57 +03:00
2022-08-24 03:11:49 +03:00
impl fmt ::Debug for Web3Provider {
fn fmt ( & self , f : & mut fmt ::Formatter < '_ > ) -> fmt ::Result {
// TODO: the default Debug takes forever to write. this is too quiet though. we at least need the url
f . debug_struct ( " Web3Provider " ) . finish_non_exhaustive ( )
2022-05-06 07:29:25 +03:00
}
}
2022-06-14 07:04:14 +03:00
impl Hash for Web3Connection {
fn hash < H : Hasher > ( & self , state : & mut H ) {
2022-08-24 03:32:16 +03:00
// TODO: is this enough?
self . name . hash ( state ) ;
2022-06-14 07:04:14 +03:00
}
}
2022-05-05 22:07:09 +03:00
impl Eq for Web3Connection { }
impl Ord for Web3Connection {
fn cmp ( & self , other : & Self ) -> std ::cmp ::Ordering {
2022-08-24 03:32:16 +03:00
self . name . cmp ( & other . name )
2022-05-05 22:07:09 +03:00
}
}
impl PartialOrd for Web3Connection {
fn partial_cmp ( & self , other : & Self ) -> Option < Ordering > {
Some ( self . cmp ( other ) )
}
}
impl PartialEq for Web3Connection {
fn eq ( & self , other : & Self ) -> bool {
2022-08-24 03:32:16 +03:00
self . name = = other . name
2022-05-05 22:07:09 +03:00
}
}
2022-08-10 08:56:09 +03:00
impl Serialize for Web3Connection {
fn serialize < S > ( & self , serializer : S ) -> Result < S ::Ok , S ::Error >
where
S : Serializer ,
{
// 3 is the number of fields in the struct.
let mut state = serializer . serialize_struct ( " Web3Connection " , 4 ) ? ;
// the url is excluded because it likely includes private information. just show the name
state . serialize_field ( " name " , & self . name ) ? ;
let block_data_limit = self . block_data_limit . load ( atomic ::Ordering ::Relaxed ) ;
state . serialize_field ( " block_data_limit " , & block_data_limit ) ? ;
state . serialize_field ( " soft_limit " , & self . soft_limit ) ? ;
state . serialize_field (
" active_requests " ,
& self . active_requests . load ( atomic ::Ordering ::Relaxed ) ,
) ? ;
state . end ( )
}
}
impl fmt ::Debug for Web3Connection {
fn fmt ( & self , f : & mut fmt ::Formatter < '_ > ) -> fmt ::Result {
let mut f = f . debug_struct ( " Web3Connection " ) ;
2022-08-24 03:32:16 +03:00
f . field ( " name " , & self . name ) ;
2022-08-10 08:56:09 +03:00
let block_data_limit = self . block_data_limit . load ( atomic ::Ordering ::Relaxed ) ;
if block_data_limit = = u64 ::MAX {
f . field ( " data " , & " archive " ) ;
} else {
f . field ( " data " , & block_data_limit ) ;
}
f . finish_non_exhaustive ( )
}
}
impl fmt ::Display for Web3Connection {
fn fmt ( & self , f : & mut fmt ::Formatter < '_ > ) -> fmt ::Result {
// TODO: filter basic auth and api keys
2022-08-24 03:32:16 +03:00
write! ( f , " {} " , & self . name )
2022-08-10 08:56:09 +03:00
}
}