2022-11-03 19:24:17 +00:00
use std ::path ::PathBuf ;
2022-08-27 00:05:12 +00:00
use std ::sync ::Arc ;
2022-11-03 19:24:17 +00:00
use config ::networks ::Network ;
2022-08-21 13:13:56 +00:00
use ethers ::prelude ::{ Address , U256 } ;
2022-11-17 17:14:13 +00:00
use ethers ::types ::{ Filter , Log , Transaction , TransactionReceipt , H256 } ;
2022-09-16 19:32:15 +00:00
use eyre ::{ eyre , Result } ;
2022-08-21 13:13:56 +00:00
2022-09-29 23:35:43 +00:00
use common ::types ::BlockTag ;
2022-12-02 01:18:23 +00:00
use config ::{ CheckpointFallback , Config } ;
use consensus ::{ types ::Header , ConsensusClient } ;
2022-09-01 19:58:45 +00:00
use execution ::types ::{ CallOpts , ExecutionBlock } ;
2022-09-16 19:32:15 +00:00
use log ::{ info , warn } ;
2022-09-10 04:01:23 +00:00
use tokio ::spawn ;
2022-09-22 21:30:47 +00:00
use tokio ::sync ::RwLock ;
2022-09-10 04:01:23 +00:00
use tokio ::time ::sleep ;
2022-09-16 19:32:15 +00:00
use crate ::database ::{ Database , FileDB } ;
2022-09-29 23:35:43 +00:00
use crate ::node ::Node ;
2022-09-10 04:01:23 +00:00
use crate ::rpc ::Rpc ;
2022-08-21 13:13:56 +00:00
2022-11-30 01:31:25 +00:00
#[ derive(Default) ]
2022-11-03 19:24:17 +00:00
pub struct ClientBuilder {
2022-11-30 17:01:43 +00:00
pub network : Option < Network > ,
pub consensus_rpc : Option < String > ,
pub execution_rpc : Option < String > ,
pub checkpoint : Option < Vec < u8 > > ,
pub rpc_port : Option < u16 > ,
pub data_dir : Option < PathBuf > ,
pub config : Option < Config > ,
2022-12-02 19:45:53 +00:00
pub fallback : Option < String > ,
pub load_external_fallback : bool ,
2022-11-30 17:01:43 +00:00
pub with_ws : bool ,
pub with_http : bool ,
2022-11-03 19:24:17 +00:00
}
impl ClientBuilder {
pub fn new ( ) -> Self {
2022-11-30 17:01:43 +00:00
Self ::default ( ) . with_http ( true )
2022-11-03 19:24:17 +00:00
}
pub fn network ( mut self , network : Network ) -> Self {
self . network = Some ( network ) ;
self
}
pub fn consensus_rpc ( mut self , consensus_rpc : & str ) -> Self {
self . consensus_rpc = Some ( consensus_rpc . to_string ( ) ) ;
self
}
pub fn execution_rpc ( mut self , execution_rpc : & str ) -> Self {
self . execution_rpc = Some ( execution_rpc . to_string ( ) ) ;
self
}
pub fn checkpoint ( mut self , checkpoint : & str ) -> Self {
2022-12-02 16:24:03 +00:00
let checkpoint = hex ::decode ( checkpoint . strip_prefix ( " 0x " ) . unwrap_or ( checkpoint ) )
. expect ( " cannot parse checkpoint " ) ;
2022-11-03 19:24:17 +00:00
self . checkpoint = Some ( checkpoint ) ;
self
}
2022-11-30 17:01:43 +00:00
/// Enables the client to serve a websocket connection.
///
/// # Example
/// ```rust
/// let mut client_builder = client::ClientBuilder::new().with_ws(true);
/// assert_eq!(client_builder.with_ws, true);
/// client_builder = client_builder.with_ws(false);
/// assert_eq!(client_builder.with_ws, false);
/// ```
pub fn with_ws ( mut self , option : bool ) -> Self {
self . with_ws = option ;
self
}
/// Enables the client to serve an http connection (enabled by default).
///
/// # Example
/// ```rust
/// let mut client_builder = client::ClientBuilder::new();
/// assert_eq!(client_builder.with_http, true);
/// client_builder = client_builder.with_http(false);
/// assert_eq!(client_builder.with_http, false);
/// ```
pub fn with_http ( mut self , option : bool ) -> Self {
self . with_http = option ;
self
}
2022-11-03 19:24:17 +00:00
pub fn rpc_port ( mut self , port : u16 ) -> Self {
self . rpc_port = Some ( port ) ;
self
}
pub fn data_dir ( mut self , data_dir : PathBuf ) -> Self {
self . data_dir = Some ( data_dir ) ;
self
}
pub fn config ( mut self , config : Config ) -> Self {
self . config = Some ( config ) ;
self
}
2022-12-02 01:18:23 +00:00
pub fn fallback ( mut self , fallback : & str ) -> Self {
self . fallback = Some ( fallback . to_string ( ) ) ;
self
}
pub fn load_external_fallback ( mut self ) -> Self {
self . load_external_fallback = true ;
self
}
2022-11-03 19:24:17 +00:00
pub fn build ( self ) -> Result < Client < FileDB > > {
let base_config = if let Some ( network ) = self . network {
network . to_base_config ( )
} else {
let config = self
. config
. as_ref ( )
. ok_or ( eyre! ( " missing network config " ) ) ? ;
config . to_base_config ( )
} ;
2022-11-03 23:36:14 +00:00
let consensus_rpc = self . consensus_rpc . unwrap_or_else ( | | {
2022-11-03 19:24:17 +00:00
self . config
. as_ref ( )
2022-11-03 23:36:14 +00:00
. expect ( " missing consensus rpc " )
2022-11-03 19:24:17 +00:00
. consensus_rpc
2022-11-03 23:36:14 +00:00
. clone ( )
} ) ;
2022-11-03 19:24:17 +00:00
2022-11-03 23:36:14 +00:00
let execution_rpc = self . execution_rpc . unwrap_or_else ( | | {
2022-11-03 19:24:17 +00:00
self . config
. as_ref ( )
2022-11-03 23:36:14 +00:00
. expect ( " missing execution rpc " )
2022-11-03 19:24:17 +00:00
. execution_rpc
2022-11-03 23:36:14 +00:00
. clone ( )
} ) ;
2022-11-03 19:24:17 +00:00
2022-11-03 23:36:14 +00:00
let checkpoint = if let Some ( checkpoint ) = self . checkpoint {
checkpoint
} else if let Some ( config ) = & self . config {
config . checkpoint . clone ( )
} else {
base_config . checkpoint
} ;
2022-11-03 19:24:17 +00:00
let rpc_port = if self . rpc_port . is_some ( ) {
self . rpc_port
} else if let Some ( config ) = & self . config {
config . rpc_port
} else {
None
} ;
let data_dir = if self . data_dir . is_some ( ) {
self . data_dir
} else if let Some ( config ) = & self . config {
config . data_dir . clone ( )
} else {
None
} ;
2022-12-02 01:18:23 +00:00
let fallback = if self . fallback . is_some ( ) {
self . fallback
} else if let Some ( config ) = & self . config {
config . fallback . clone ( )
} else {
None
} ;
let load_external_fallback = if let Some ( config ) = & self . config {
self . load_external_fallback | | config . load_external_fallback
} else {
self . load_external_fallback
} ;
2022-12-02 19:45:53 +00:00
let with_ws = if let Some ( config ) = & self . config {
self . with_ws | | config . with_ws
} else {
self . with_ws
} ;
let with_http = if let Some ( config ) = & self . config {
self . with_http | | config . with_http
} else {
self . with_http
} ;
2022-11-03 19:24:17 +00:00
let config = Config {
consensus_rpc ,
execution_rpc ,
checkpoint ,
rpc_port ,
data_dir ,
chain : base_config . chain ,
forks : base_config . forks ,
2022-11-14 20:23:51 +00:00
max_checkpoint_age : base_config . max_checkpoint_age ,
2022-12-02 01:18:23 +00:00
fallback ,
load_external_fallback ,
2022-12-02 19:45:53 +00:00
with_ws ,
with_http ,
2022-11-03 19:24:17 +00:00
} ;
Client ::new ( config )
}
}
2022-12-02 01:18:23 +00:00
pub struct Client < DB : Database > {
node : Arc < RwLock < Node > > ,
rpc : Option < Rpc > ,
db : Option < DB > ,
fallback : Option < String > ,
load_external_fallback : bool ,
}
impl Client < FileDB > {
fn new ( config : Config ) -> Result < Self > {
let config = Arc ::new ( config ) ;
let node = Node ::new ( config . clone ( ) ) ? ;
let node = Arc ::new ( RwLock ::new ( node ) ) ;
2022-11-30 17:01:43 +00:00
let rpc = config
. rpc_port
. map ( | port | Rpc ::new ( node . clone ( ) , config . with_http , config . with_ws , port ) ) ;
2022-12-02 01:18:23 +00:00
let data_dir = config . data_dir . clone ( ) ;
let db = data_dir . map ( FileDB ::new ) ;
Ok ( Client {
node ,
rpc ,
db ,
fallback : config . fallback . clone ( ) ,
load_external_fallback : config . load_external_fallback ,
} )
}
}
2022-09-16 19:32:15 +00:00
impl < DB : Database > Client < DB > {
2022-09-10 04:01:23 +00:00
pub async fn start ( & mut self ) -> Result < ( ) > {
2022-11-03 23:36:14 +00:00
if let Some ( rpc ) = & mut self . rpc {
2022-11-30 17:01:43 +00:00
// We can start both ws and http servers since they only run if enabled in the config.
rpc . start_ws ( ) . await ? ;
rpc . start_http ( ) . await ? ;
2022-11-03 23:36:14 +00:00
}
2022-12-02 01:18:23 +00:00
if self . node . write ( ) . await . sync ( ) . await . is_err ( ) {
warn! (
" failed to sync consensus node with checkpoint: 0x{} " ,
hex ::encode ( & self . node . read ( ) . await . config . checkpoint ) ,
) ;
let fallback = self . boot_from_fallback ( ) . await ;
if fallback . is_err ( ) & & self . load_external_fallback {
self . boot_from_external_fallbacks ( ) . await ?
} else if fallback . is_err ( ) {
return Err ( eyre ::eyre! ( " Checkpoint is too old. Please update your checkpoint. Alternatively, set an explicit checkpoint fallback service url with the `-f` flag or use the configured external fallback services with `-l` (NOT RECOMMENED). See https://github.com/a16z/helios#additional-options for more information. " ) ) ;
}
2022-11-03 23:36:14 +00:00
}
2022-09-10 04:01:23 +00:00
let node = self . node . clone ( ) ;
spawn ( async move {
loop {
2022-09-22 21:30:47 +00:00
let res = node . write ( ) . await . advance ( ) . await ;
2022-09-10 04:01:23 +00:00
if let Err ( err ) = res {
2022-09-29 23:35:43 +00:00
warn! ( " consensus error: {} " , err ) ;
2022-09-10 04:01:23 +00:00
}
2022-09-02 04:13:22 +00:00
2022-09-28 20:48:24 +00:00
let next_update = node . read ( ) . await . duration_until_next_update ( ) ;
sleep ( next_update ) . await ;
2022-09-10 04:01:23 +00:00
}
} ) ;
2022-08-31 21:40:44 +00:00
Ok ( ( ) )
2022-08-31 00:31:58 +00:00
}
2022-12-02 01:18:23 +00:00
async fn boot_from_fallback ( & self ) -> eyre ::Result < ( ) > {
if let Some ( fallback ) = & self . fallback {
info! (
" attempting to load checkpoint from fallback \" {} \" " ,
fallback
) ;
let checkpoint = CheckpointFallback ::fetch_checkpoint_from_api ( fallback )
. await
. map_err ( | _ | {
eyre ::eyre! ( " Failed to fetch checkpoint from fallback \" {} \" " , fallback )
} ) ? ;
info! (
" external fallbacks responded with checkpoint 0x{:?} " ,
checkpoint
) ;
// Try to sync again with the new checkpoint by reconstructing the consensus client
// We fail fast here since the node is unrecoverable at this point
let config = self . node . read ( ) . await . config . clone ( ) ;
let consensus =
ConsensusClient ::new ( & config . consensus_rpc , checkpoint . as_bytes ( ) , config . clone ( ) ) ? ;
self . node . write ( ) . await . consensus = consensus ;
self . node . write ( ) . await . sync ( ) . await ? ;
Ok ( ( ) )
} else {
Err ( eyre ::eyre! ( " no explicit fallback specified " ) )
}
}
async fn boot_from_external_fallbacks ( & self ) -> eyre ::Result < ( ) > {
info! ( " attempting to fetch checkpoint from external fallbacks... " ) ;
// Build the list of external checkpoint fallback services
let list = CheckpointFallback ::new ( )
. build ( )
. await
. map_err ( | _ | eyre ::eyre! ( " Failed to construct external checkpoint sync fallbacks " ) ) ? ;
let checkpoint = if self . node . read ( ) . await . config . chain . chain_id = = 5 {
list . fetch_latest_checkpoint ( & Network ::GOERLI )
. await
. map_err ( | _ | {
eyre ::eyre! ( " Failed to fetch latest goerli checkpoint from external fallbacks " )
} ) ?
} else {
list . fetch_latest_checkpoint ( & Network ::MAINNET )
. await
. map_err ( | _ | {
eyre ::eyre! ( " Failed to fetch latest mainnet checkpoint from external fallbacks " )
} ) ?
} ;
info! (
" external fallbacks responded with checkpoint {:?} " ,
checkpoint
) ;
// Try to sync again with the new checkpoint by reconstructing the consensus client
// We fail fast here since the node is unrecoverable at this point
let config = self . node . read ( ) . await . config . clone ( ) ;
let consensus =
ConsensusClient ::new ( & config . consensus_rpc , checkpoint . as_bytes ( ) , config . clone ( ) ) ? ;
self . node . write ( ) . await . consensus = consensus ;
self . node . write ( ) . await . sync ( ) . await ? ;
Ok ( ( ) )
}
2022-09-16 19:32:15 +00:00
pub async fn shutdown ( & self ) {
2022-09-22 21:30:47 +00:00
let node = self . node . read ( ) . await ;
2022-09-16 19:32:15 +00:00
let checkpoint = if let Some ( checkpoint ) = node . get_last_checkpoint ( ) {
checkpoint
} else {
return ;
} ;
info! ( " saving last checkpoint hash " ) ;
2022-11-03 23:36:14 +00:00
let res = self . db . as_ref ( ) . map ( | db | db . save_checkpoint ( checkpoint ) ) ;
if res . is_some ( ) & & res . unwrap ( ) . is_err ( ) {
2022-09-16 19:32:15 +00:00
warn! ( " checkpoint save failed " ) ;
}
}
2022-11-03 23:36:14 +00:00
pub async fn call ( & self , opts : & CallOpts , block : BlockTag ) -> Result < Vec < u8 > > {
2022-11-12 00:41:37 +00:00
self . node
. read ( )
. await
. call ( opts , block )
. await
. map_err ( | err | err . into ( ) )
2022-08-24 01:33:48 +00:00
}
2022-09-10 04:01:23 +00:00
pub async fn estimate_gas ( & self , opts : & CallOpts ) -> Result < u64 > {
2022-11-12 00:41:37 +00:00
self . node
. read ( )
. await
. estimate_gas ( opts )
. await
. map_err ( | err | err . into ( ) )
2022-08-27 20:43:27 +00:00
}
2022-11-03 23:36:14 +00:00
pub async fn get_balance ( & self , address : & Address , block : BlockTag ) -> Result < U256 > {
2022-09-22 21:30:47 +00:00
self . node . read ( ) . await . get_balance ( address , block ) . await
2022-08-21 13:13:56 +00:00
}
2022-11-03 23:36:14 +00:00
pub async fn get_nonce ( & self , address : & Address , block : BlockTag ) -> Result < u64 > {
2022-09-22 21:30:47 +00:00
self . node . read ( ) . await . get_nonce ( address , block ) . await
2022-08-21 13:13:56 +00:00
}
2022-11-03 23:36:14 +00:00
pub async fn get_code ( & self , address : & Address , block : BlockTag ) -> Result < Vec < u8 > > {
2022-09-22 21:30:47 +00:00
self . node . read ( ) . await . get_code ( address , block ) . await
2022-08-21 16:59:47 +00:00
}
2022-09-06 17:57:47 +00:00
pub async fn get_storage_at ( & self , address : & Address , slot : H256 ) -> Result < U256 > {
2022-09-22 21:30:47 +00:00
self . node . read ( ) . await . get_storage_at ( address , slot ) . await
2022-08-21 21:51:11 +00:00
}
2022-11-30 01:31:25 +00:00
pub async fn send_raw_transaction ( & self , bytes : & [ u8 ] ) -> Result < H256 > {
2022-09-22 21:30:47 +00:00
self . node . read ( ) . await . send_raw_transaction ( bytes ) . await
2022-09-01 21:07:30 +00:00
}
2022-09-02 00:28:12 +00:00
pub async fn get_transaction_receipt (
& self ,
2022-09-06 17:57:47 +00:00
tx_hash : & H256 ,
2022-09-02 00:28:12 +00:00
) -> Result < Option < TransactionReceipt > > {
2022-09-10 04:01:23 +00:00
self . node
2022-09-22 21:30:47 +00:00
. read ( )
2022-09-10 04:01:23 +00:00
. await
. get_transaction_receipt ( tx_hash )
2022-09-02 00:28:12 +00:00
. await
}
2022-09-06 17:57:47 +00:00
pub async fn get_transaction_by_hash ( & self , tx_hash : & H256 ) -> Result < Option < Transaction > > {
2022-09-10 04:01:23 +00:00
self . node
2022-09-22 21:30:47 +00:00
. read ( )
2022-09-10 04:01:23 +00:00
. await
. get_transaction_by_hash ( tx_hash )
2022-09-02 03:28:37 +00:00
. await
}
2022-11-17 17:14:13 +00:00
pub async fn get_logs ( & self , filter : & Filter ) -> Result < Vec < Log > > {
self . node . read ( ) . await . get_logs ( filter ) . await
}
2022-09-10 04:01:23 +00:00
pub async fn get_gas_price ( & self ) -> Result < U256 > {
2022-09-22 21:30:47 +00:00
self . node . read ( ) . await . get_gas_price ( )
2022-08-29 15:59:02 +00:00
}
2022-09-10 04:01:23 +00:00
pub async fn get_priority_fee ( & self ) -> Result < U256 > {
2022-09-22 21:30:47 +00:00
self . node . read ( ) . await . get_priority_fee ( )
2022-08-29 16:06:50 +00:00
}
2022-09-10 04:01:23 +00:00
pub async fn get_block_number ( & self ) -> Result < u64 > {
2022-09-22 21:30:47 +00:00
self . node . read ( ) . await . get_block_number ( )
2022-08-31 21:40:44 +00:00
}
2022-10-27 17:46:32 +00:00
pub async fn get_block_by_number (
& self ,
2022-11-03 23:36:14 +00:00
block : BlockTag ,
2022-10-27 17:46:32 +00:00
full_tx : bool ,
) -> Result < Option < ExecutionBlock > > {
self . node
. read ( )
. await
. get_block_by_number ( block , full_tx )
. await
2022-09-02 04:13:22 +00:00
}
2022-10-27 17:46:32 +00:00
pub async fn get_block_by_hash (
& self ,
hash : & Vec < u8 > ,
full_tx : bool ,
) -> Result < Option < ExecutionBlock > > {
self . node
. read ( )
. await
. get_block_by_hash ( hash , full_tx )
. await
2022-08-27 00:05:12 +00:00
}
2022-09-10 04:01:23 +00:00
pub async fn chain_id ( & self ) -> u64 {
2022-09-22 21:30:47 +00:00
self . node . read ( ) . await . chain_id ( )
2022-08-21 13:13:56 +00:00
}
2022-08-31 21:40:44 +00:00
2022-09-28 21:50:39 +00:00
pub async fn get_header ( & self ) -> Result < Header > {
self . node . read ( ) . await . get_header ( )
2022-08-31 21:40:44 +00:00
}
2022-08-21 13:13:56 +00:00
}