#![doc = include_str!("../README.md")]
#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
use crate::{
eth::{
backend::{info::StorageInfo, mem},
fees::{FeeHistoryService, FeeManager},
miner::{Miner, MiningMode},
pool::Pool,
sign::{DevSigner, Signer as EthSigner},
EthApi,
},
filter::Filters,
logging::{LoggingManager, NodeLogLayer},
server::error::{NodeError, NodeResult},
service::NodeService,
shutdown::Signal,
tasks::TaskManager,
};
use alloy_primitives::{Address, U256};
use alloy_signer_local::PrivateKeySigner;
use eth::backend::fork::ClientFork;
use foundry_common::provider::{ProviderBuilder, RetryProvider};
use foundry_evm::revm;
use futures::{FutureExt, TryFutureExt};
use parking_lot::Mutex;
use server::try_spawn_ipc;
use std::{
future::Future,
io,
net::SocketAddr,
pin::Pin,
sync::Arc,
task::{Context, Poll},
};
use tokio::{
runtime::Handle,
task::{JoinError, JoinHandle},
};
mod service;
mod config;
pub use config::{
AccountGenerator, ForkChoice, NodeConfig, CHAIN_ID, DEFAULT_GAS_LIMIT, VERSION_MESSAGE,
};
mod hardfork;
pub use hardfork::EthereumHardfork;
pub mod eth;
mod evm;
pub use evm::{inject_precompiles, PrecompileFactory};
pub mod filter;
pub mod logging;
pub mod pubsub;
pub mod server;
mod shutdown;
mod tasks;
#[cfg(feature = "cmd")]
pub mod cmd;
#[macro_use]
extern crate foundry_common;
#[macro_use]
extern crate tracing;
pub async fn spawn(config: NodeConfig) -> (EthApi, NodeHandle) {
try_spawn(config).await.expect("failed to spawn node")
}
pub async fn try_spawn(mut config: NodeConfig) -> io::Result<(EthApi, NodeHandle)> {
let logger = if config.enable_tracing { init_tracing() } else { Default::default() };
logger.set_enabled(!config.silent);
let backend = Arc::new(config.setup().await);
if config.enable_auto_impersonate {
backend.auto_impersonate_account(true);
}
let fork = backend.get_fork();
let NodeConfig {
signer_accounts,
block_time,
port,
max_transactions,
server_config,
no_mining,
transaction_order,
genesis,
mixed_mining,
..
} = config.clone();
let pool = Arc::new(Pool::default());
let mode = if let Some(block_time) = block_time {
if mixed_mining {
let listener = pool.add_ready_listener();
MiningMode::mixed(max_transactions, listener, block_time)
} else {
MiningMode::interval(block_time)
}
} else if no_mining {
MiningMode::None
} else {
let listener = pool.add_ready_listener();
MiningMode::instant(max_transactions, listener)
};
let miner = match &fork {
Some(fork) => {
Miner::new(mode).with_forced_transactions(fork.config.read().force_transactions.clone())
}
_ => Miner::new(mode),
};
let dev_signer: Box<dyn EthSigner> = Box::new(DevSigner::new(signer_accounts));
let mut signers = vec![dev_signer];
if let Some(genesis) = genesis {
let genesis_signers = genesis
.alloc
.values()
.filter_map(|acc| acc.private_key)
.flat_map(|k| PrivateKeySigner::from_bytes(&k))
.collect::<Vec<_>>();
if !genesis_signers.is_empty() {
signers.push(Box::new(DevSigner::new(genesis_signers)));
}
}
let fee_history_cache = Arc::new(Mutex::new(Default::default()));
let fee_history_service = FeeHistoryService::new(
backend.new_block_notifications(),
Arc::clone(&fee_history_cache),
StorageInfo::new(Arc::clone(&backend)),
);
if let Some(header) = backend.get_block(backend.best_number()).map(|block| block.header) {
fee_history_service.insert_cache_entry_for_block(header.hash_slow(), &header);
}
let filters = Filters::default();
let api = EthApi::new(
Arc::clone(&pool),
Arc::clone(&backend),
Arc::new(signers),
fee_history_cache,
fee_history_service.fee_history_limit(),
miner.clone(),
logger,
filters.clone(),
transaction_order,
);
let node_service =
tokio::task::spawn(NodeService::new(pool, backend, miner, fee_history_service, filters));
let mut servers = Vec::with_capacity(config.host.len());
let mut addresses = Vec::with_capacity(config.host.len());
for addr in &config.host {
let sock_addr = SocketAddr::new(*addr, port);
let tcp_listener = tokio::net::TcpListener::bind(sock_addr).await?;
addresses.push(tcp_listener.local_addr()?);
let srv = server::serve_on(tcp_listener, api.clone(), server_config.clone());
servers.push(tokio::task::spawn(srv.map_err(Into::into)));
}
let tokio_handle = Handle::current();
let (signal, on_shutdown) = shutdown::signal();
let task_manager = TaskManager::new(tokio_handle, on_shutdown);
let ipc_task =
config.get_ipc_path().map(|path| try_spawn_ipc(api.clone(), path)).transpose()?;
let handle = NodeHandle {
config,
node_service,
servers,
ipc_task,
addresses,
_signal: Some(signal),
task_manager,
};
handle.print(fork.as_ref());
Ok((api, handle))
}
type IpcTask = JoinHandle<()>;
pub struct NodeHandle {
config: NodeConfig,
addresses: Vec<SocketAddr>,
pub node_service: JoinHandle<Result<(), NodeError>>,
pub servers: Vec<JoinHandle<Result<(), NodeError>>>,
ipc_task: Option<IpcTask>,
_signal: Option<Signal>,
task_manager: TaskManager,
}
impl Drop for NodeHandle {
fn drop(&mut self) {
if let Some(signal) = self._signal.take() {
signal.fire().unwrap()
}
}
}
impl NodeHandle {
pub fn config(&self) -> &NodeConfig {
&self.config
}
pub(crate) fn print(&self, fork: Option<&ClientFork>) {
self.config.print(fork);
if !self.config.silent {
if let Some(ipc_path) = self.ipc_path() {
let _ = sh_println!("IPC path: {ipc_path}");
}
let _ = sh_println!(
"Listening on {}",
self.addresses
.iter()
.map(|addr| { addr.to_string() })
.collect::<Vec<String>>()
.join(", ")
);
}
}
pub fn socket_address(&self) -> &SocketAddr {
&self.addresses[0]
}
pub fn http_endpoint(&self) -> String {
format!("http://{}", self.socket_address())
}
pub fn ws_endpoint(&self) -> String {
format!("ws://{}", self.socket_address())
}
pub fn ipc_path(&self) -> Option<String> {
self.config.get_ipc_path()
}
pub fn http_provider(&self) -> RetryProvider {
ProviderBuilder::new(&self.http_endpoint()).build().expect("failed to build HTTP provider")
}
pub fn ws_provider(&self) -> RetryProvider {
ProviderBuilder::new(&self.ws_endpoint()).build().expect("failed to build WS provider")
}
pub fn ipc_provider(&self) -> Option<RetryProvider> {
ProviderBuilder::new(&self.config.get_ipc_path()?).build().ok()
}
pub fn dev_accounts(&self) -> impl Iterator<Item = Address> + '_ {
self.config.signer_accounts.iter().map(|wallet| wallet.address())
}
pub fn dev_wallets(&self) -> impl Iterator<Item = PrivateKeySigner> + '_ {
self.config.signer_accounts.iter().cloned()
}
pub fn genesis_accounts(&self) -> impl Iterator<Item = Address> + '_ {
self.config.genesis_accounts.iter().map(|w| w.address())
}
pub fn genesis_balance(&self) -> U256 {
self.config.genesis_balance
}
pub fn gas_price(&self) -> u128 {
self.config.get_gas_price()
}
pub fn shutdown_signal(&self) -> &Option<Signal> {
&self._signal
}
pub fn shutdown_signal_mut(&mut self) -> &mut Option<Signal> {
&mut self._signal
}
pub fn task_manager(&self) -> &TaskManager {
&self.task_manager
}
}
impl Future for NodeHandle {
type Output = Result<NodeResult<()>, JoinError>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let pin = self.get_mut();
if let Some(mut ipc) = pin.ipc_task.take() {
if let Poll::Ready(res) = ipc.poll_unpin(cx) {
return Poll::Ready(res.map(|()| Ok(())));
} else {
pin.ipc_task = Some(ipc);
}
}
if let Poll::Ready(res) = pin.node_service.poll_unpin(cx) {
return Poll::Ready(res);
}
for server in pin.servers.iter_mut() {
if let Poll::Ready(res) = server.poll_unpin(cx) {
return Poll::Ready(res);
}
}
Poll::Pending
}
}
#[doc(hidden)]
pub fn init_tracing() -> LoggingManager {
use tracing_subscriber::prelude::*;
let manager = LoggingManager::default();
let _ = if std::env::var("RUST_LOG").is_ok() {
tracing_subscriber::Registry::default()
.with(tracing_subscriber::EnvFilter::from_default_env())
.with(tracing_subscriber::fmt::layer())
.try_init()
} else {
tracing_subscriber::Registry::default()
.with(NodeLogLayer::new(manager.clone()))
.with(
tracing_subscriber::fmt::layer()
.without_time()
.with_target(false)
.with_level(false),
)
.try_init()
};
manager
}