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