1#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
4
5use crate::{
6 eth::{
7 backend::{info::StorageInfo, mem},
8 fees::{FeeHistoryService, FeeManager},
9 miner::{Miner, MiningMode},
10 pool::Pool,
11 sign::{DevSigner, Signer as EthSigner},
12 EthApi,
13 },
14 filter::Filters,
15 logging::{LoggingManager, NodeLogLayer},
16 server::error::{NodeError, NodeResult},
17 service::NodeService,
18 shutdown::Signal,
19 tasks::TaskManager,
20};
21use alloy_primitives::{Address, U256};
22use alloy_signer_local::PrivateKeySigner;
23use eth::backend::fork::ClientFork;
24use eyre::Result;
25use foundry_common::provider::{ProviderBuilder, RetryProvider};
26use futures::{FutureExt, TryFutureExt};
27use parking_lot::Mutex;
28use server::try_spawn_ipc;
29use std::{
30 future::Future,
31 net::SocketAddr,
32 pin::Pin,
33 sync::Arc,
34 task::{Context, Poll},
35};
36use tokio::{
37 runtime::Handle,
38 task::{JoinError, JoinHandle},
39};
40
41mod service;
43
44mod config;
45pub use config::{
46 AccountGenerator, ForkChoice, NodeConfig, CHAIN_ID, DEFAULT_GAS_LIMIT, VERSION_MESSAGE,
47};
48
49mod hardfork;
50pub use alloy_hardforks::EthereumHardfork;
51pub mod eth;
53mod evm;
55pub use evm::{inject_precompiles, PrecompileFactory};
56
57pub mod filter;
59pub mod logging;
61pub mod pubsub;
63pub mod server;
65mod shutdown;
67mod tasks;
69
70#[cfg(feature = "cmd")]
72pub mod cmd;
73
74#[cfg(feature = "cmd")]
75pub mod args;
76
77#[cfg(feature = "cmd")]
78pub mod opts;
79
80#[macro_use]
81extern crate foundry_common;
82
83#[macro_use]
84extern crate tracing;
85
86pub async fn spawn(config: NodeConfig) -> (EthApi, NodeHandle) {
112 try_spawn(config).await.expect("failed to spawn node")
113}
114
115pub async fn try_spawn(mut config: NodeConfig) -> Result<(EthApi, NodeHandle)> {
136 let logger = if config.enable_tracing { init_tracing() } else { Default::default() };
137 logger.set_enabled(!config.silent);
138
139 let backend = Arc::new(config.setup().await?);
140
141 if config.enable_auto_impersonate {
142 backend.auto_impersonate_account(true);
143 }
144
145 let fork = backend.get_fork();
146
147 let NodeConfig {
148 signer_accounts,
149 block_time,
150 port,
151 max_transactions,
152 server_config,
153 no_mining,
154 transaction_order,
155 genesis,
156 mixed_mining,
157 ..
158 } = config.clone();
159
160 let pool = Arc::new(Pool::default());
161
162 let mode = if let Some(block_time) = block_time {
163 if mixed_mining {
164 let listener = pool.add_ready_listener();
165 MiningMode::mixed(max_transactions, listener, block_time)
166 } else {
167 MiningMode::interval(block_time)
168 }
169 } else if no_mining {
170 MiningMode::None
171 } else {
172 let listener = pool.add_ready_listener();
174 MiningMode::instant(max_transactions, listener)
175 };
176
177 let miner = match &fork {
178 Some(fork) => {
179 Miner::new(mode).with_forced_transactions(fork.config.read().force_transactions.clone())
180 }
181 _ => Miner::new(mode),
182 };
183
184 let dev_signer: Box<dyn EthSigner> = Box::new(DevSigner::new(signer_accounts));
185 let mut signers = vec![dev_signer];
186 if let Some(genesis) = genesis {
187 let genesis_signers = genesis
188 .alloc
189 .values()
190 .filter_map(|acc| acc.private_key)
191 .flat_map(|k| PrivateKeySigner::from_bytes(&k))
192 .collect::<Vec<_>>();
193 if !genesis_signers.is_empty() {
194 signers.push(Box::new(DevSigner::new(genesis_signers)));
195 }
196 }
197
198 let fee_history_cache = Arc::new(Mutex::new(Default::default()));
199 let fee_history_service = FeeHistoryService::new(
200 backend.new_block_notifications(),
201 Arc::clone(&fee_history_cache),
202 StorageInfo::new(Arc::clone(&backend)),
203 );
204 if let Some(header) = backend.get_block(backend.best_number()).map(|block| block.header) {
206 fee_history_service.insert_cache_entry_for_block(header.hash_slow(), &header);
207 }
208
209 let filters = Filters::default();
210
211 let api = EthApi::new(
213 Arc::clone(&pool),
214 Arc::clone(&backend),
215 Arc::new(signers),
216 fee_history_cache,
217 fee_history_service.fee_history_limit(),
218 miner.clone(),
219 logger,
220 filters.clone(),
221 transaction_order,
222 );
223
224 let node_service =
226 tokio::task::spawn(NodeService::new(pool, backend, miner, fee_history_service, filters));
227
228 let mut servers = Vec::with_capacity(config.host.len());
229 let mut addresses = Vec::with_capacity(config.host.len());
230
231 for addr in &config.host {
232 let sock_addr = SocketAddr::new(*addr, port);
233
234 let tcp_listener = tokio::net::TcpListener::bind(sock_addr).await?;
236 addresses.push(tcp_listener.local_addr()?);
237
238 let srv = server::serve_on(tcp_listener, api.clone(), server_config.clone());
240 servers.push(tokio::task::spawn(srv.map_err(Into::into)));
241 }
242
243 let tokio_handle = Handle::current();
244 let (signal, on_shutdown) = shutdown::signal();
245 let task_manager = TaskManager::new(tokio_handle, on_shutdown);
246
247 let ipc_task =
248 config.get_ipc_path().map(|path| try_spawn_ipc(api.clone(), path)).transpose()?;
249
250 let handle = NodeHandle {
251 config,
252 node_service,
253 servers,
254 ipc_task,
255 addresses,
256 _signal: Some(signal),
257 task_manager,
258 };
259
260 handle.print(fork.as_ref())?;
261
262 Ok((api, handle))
263}
264
265type IpcTask = JoinHandle<()>;
266
267pub struct NodeHandle {
271 config: NodeConfig,
272 addresses: Vec<SocketAddr>,
274 pub node_service: JoinHandle<Result<(), NodeError>>,
276 pub servers: Vec<JoinHandle<Result<(), NodeError>>>,
278 ipc_task: Option<IpcTask>,
280 _signal: Option<Signal>,
282 task_manager: TaskManager,
284}
285
286impl Drop for NodeHandle {
287 fn drop(&mut self) {
288 if let Some(signal) = self._signal.take() {
290 let _ = signal.fire();
291 }
292 }
293}
294
295impl NodeHandle {
296 pub fn config(&self) -> &NodeConfig {
298 &self.config
299 }
300
301 pub(crate) fn print(&self, fork: Option<&ClientFork>) -> Result<()> {
303 self.config.print(fork)?;
304 if !self.config.silent {
305 if let Some(ipc_path) = self.ipc_path() {
306 sh_println!("IPC path: {ipc_path}")?;
307 }
308 sh_println!(
309 "Listening on {}",
310 self.addresses
311 .iter()
312 .map(|addr| { addr.to_string() })
313 .collect::<Vec<String>>()
314 .join(", ")
315 )?;
316 }
317 Ok(())
318 }
319
320 pub fn socket_address(&self) -> &SocketAddr {
325 &self.addresses[0]
326 }
327
328 pub fn http_endpoint(&self) -> String {
330 format!("http://{}", self.socket_address())
331 }
332
333 pub fn ws_endpoint(&self) -> String {
335 format!("ws://{}", self.socket_address())
336 }
337
338 pub fn ipc_path(&self) -> Option<String> {
340 self.config.get_ipc_path()
341 }
342
343 pub fn http_provider(&self) -> RetryProvider {
345 ProviderBuilder::new(&self.http_endpoint()).build().expect("failed to build HTTP provider")
346 }
347
348 pub fn ws_provider(&self) -> RetryProvider {
350 ProviderBuilder::new(&self.ws_endpoint()).build().expect("failed to build WS provider")
351 }
352
353 pub fn ipc_provider(&self) -> Option<RetryProvider> {
355 ProviderBuilder::new(&self.config.get_ipc_path()?).build().ok()
356 }
357
358 pub fn dev_accounts(&self) -> impl Iterator<Item = Address> + '_ {
360 self.config.signer_accounts.iter().map(|wallet| wallet.address())
361 }
362
363 pub fn dev_wallets(&self) -> impl Iterator<Item = PrivateKeySigner> + '_ {
365 self.config.signer_accounts.iter().cloned()
366 }
367
368 pub fn genesis_accounts(&self) -> impl Iterator<Item = Address> + '_ {
370 self.config.genesis_accounts.iter().map(|w| w.address())
371 }
372
373 pub fn genesis_balance(&self) -> U256 {
375 self.config.genesis_balance
376 }
377
378 pub fn gas_price(&self) -> u128 {
380 self.config.get_gas_price()
381 }
382
383 pub fn shutdown_signal(&self) -> &Option<Signal> {
385 &self._signal
386 }
387
388 pub fn shutdown_signal_mut(&mut self) -> &mut Option<Signal> {
392 &mut self._signal
393 }
394
395 pub fn task_manager(&self) -> &TaskManager {
411 &self.task_manager
412 }
413}
414
415impl Future for NodeHandle {
416 type Output = Result<NodeResult<()>, JoinError>;
417
418 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
419 let pin = self.get_mut();
420
421 if let Some(mut ipc) = pin.ipc_task.take() {
423 if let Poll::Ready(res) = ipc.poll_unpin(cx) {
424 return Poll::Ready(res.map(|()| Ok(())));
425 } else {
426 pin.ipc_task = Some(ipc);
427 }
428 }
429
430 if let Poll::Ready(res) = pin.node_service.poll_unpin(cx) {
432 return Poll::Ready(res);
433 }
434
435 for server in &mut pin.servers {
437 if let Poll::Ready(res) = server.poll_unpin(cx) {
438 return Poll::Ready(res);
439 }
440 }
441
442 Poll::Pending
443 }
444}
445
446#[doc(hidden)]
447pub fn init_tracing() -> LoggingManager {
448 use tracing_subscriber::prelude::*;
449
450 let manager = LoggingManager::default();
451 let _ = if std::env::var("RUST_LOG").is_ok() {
453 tracing_subscriber::Registry::default()
454 .with(tracing_subscriber::EnvFilter::from_default_env())
455 .with(tracing_subscriber::fmt::layer())
456 .try_init()
457 } else {
458 tracing_subscriber::Registry::default()
459 .with(NodeLogLayer::new(manager.clone()))
460 .with(
461 tracing_subscriber::fmt::layer()
462 .without_time()
463 .with_target(false)
464 .with_level(false),
465 )
466 .try_init()
467 };
468
469 manager
470}