1#![cfg_attr(not(test), warn(unused_crate_dependencies))]
4#![cfg_attr(docsrs, feature(doc_cfg))]
5
6#[cfg(feature = "optimism")]
7use op_alloy_rpc_types as _;
8
9use crate::{
10 error::{NodeError, NodeResult},
11 eth::{
12 EthApi,
13 backend::{info::StorageInfo, mem},
14 fees::{FeeHistoryService, FeeManager},
15 miner::{Miner, MiningMode},
16 pool::Pool,
17 sign::{DevSigner, Signer as EthSigner},
18 },
19 filter::Filters,
20 logging::{LoggingManager, NodeLogLayer},
21 service::NodeService,
22 shutdown::Signal,
23 tasks::TaskManager,
24};
25use alloy_primitives::{Address, U256};
26use alloy_signer_local::PrivateKeySigner;
27use eth::backend::fork::ClientFork;
28use eyre::{Result, WrapErr};
29use foundry_common::provider::{ProviderBuilder, RetryProvider};
30pub use foundry_evm::hardfork::EthereumHardfork;
31use foundry_primitives::FoundryNetwork;
32use futures::{FutureExt, TryFutureExt};
33use parking_lot::Mutex;
34use server::try_spawn_ipc;
35use std::{
36 net::SocketAddr,
37 pin::Pin,
38 sync::Arc,
39 task::{Context, Poll},
40};
41use tokio::{
42 runtime::Handle,
43 task::{JoinError, JoinHandle},
44};
45use tracing_subscriber::EnvFilter;
46
47mod service;
49
50mod config;
51pub use config::{
52 AccountGenerator, CHAIN_ID, DEFAULT_GAS_LIMIT, ForkChoice, NodeConfig, VERSION_MESSAGE,
53};
54
55mod error;
56pub mod eth;
58mod evm;
60pub use evm::PrecompileFactory;
61
62pub mod filter;
64pub mod logging;
66pub mod pubsub;
68pub mod server;
70mod shutdown;
72mod tasks;
74
75#[cfg(feature = "cmd")]
77pub mod cmd;
78
79#[cfg(feature = "cmd")]
80pub mod args;
81
82#[cfg(feature = "cmd")]
83pub mod opts;
84
85#[macro_use]
86extern crate foundry_common;
87
88#[macro_use]
89extern crate tracing;
90
91pub async fn spawn(config: NodeConfig) -> (EthApi<FoundryNetwork>, NodeHandle) {
117 try_spawn(config).await.expect("failed to spawn node")
118}
119
120pub async fn try_spawn(mut config: NodeConfig) -> Result<(EthApi<FoundryNetwork>, NodeHandle)> {
141 let logger = if config.enable_tracing { init_tracing() } else { Default::default() };
142 logger.set_enabled(!config.silent);
143
144 let init_state = config.init_state.take();
145 let (backend, fork_transaction_replay) = config.setup::<FoundryNetwork>().await?;
146
147 if let Some(state) = init_state {
148 backend.load_state(state).await.wrap_err("failed to load init state")?;
149 }
150
151 if let Some(replay) = fork_transaction_replay {
152 backend
153 .apply_fork_transaction_replay(replay)
154 .await
155 .wrap_err("failed to replay fork transaction prefix")?;
156 }
157
158 let backend = Arc::new(backend);
159
160 if config.enable_auto_impersonate {
161 backend.auto_impersonate_account(true);
162 }
163
164 let fork = backend.get_fork();
165
166 let NodeConfig {
167 signer_accounts,
168 block_time,
169 port,
170 max_transactions,
171 server_config,
172 no_mining,
173 transaction_order,
174 genesis,
175 mixed_mining,
176 ..
177 } = config.clone();
178
179 let pool = Arc::new(Pool::default());
180
181 let mode = if let Some(block_time) = block_time {
182 if mixed_mining {
183 let listener = pool.add_ready_listener();
184 MiningMode::mixed(max_transactions, listener, block_time)
185 } else {
186 MiningMode::interval(block_time)
187 }
188 } else if no_mining {
189 MiningMode::None
190 } else {
191 let listener = pool.add_ready_listener();
193 MiningMode::instant(max_transactions, listener)
194 };
195
196 let miner = Miner::new(mode);
197
198 let dev_signer: Box<dyn EthSigner<foundry_primitives::FoundryNetwork>> =
199 Box::new(DevSigner::new(signer_accounts));
200 let mut signers = vec![dev_signer];
201 if let Some(genesis) = genesis {
202 let genesis_signers = genesis
203 .alloc
204 .values()
205 .filter_map(|acc| acc.private_key)
206 .flat_map(|k| PrivateKeySigner::from_bytes(&k))
207 .collect::<Vec<_>>();
208 if !genesis_signers.is_empty() {
209 signers.push(Box::new(DevSigner::new(genesis_signers)));
210 }
211 }
212
213 let fee_history_cache = Arc::new(Mutex::new(Default::default()));
214 let fee_history_service = FeeHistoryService::new(
215 backend.blob_params(),
216 backend.new_block_notifications(),
217 Arc::clone(&fee_history_cache),
218 StorageInfo::new(Arc::clone(&backend)),
219 );
220 if let Some(header) = backend.get_block(backend.best_number()).map(|block| block.header) {
222 fee_history_service.insert_cache_entry_for_block(header.hash_slow(), &header);
223 }
224
225 let filters = Filters::default();
226
227 let api = EthApi::new(
229 Arc::clone(&pool),
230 Arc::clone(&backend),
231 Arc::new(signers),
232 fee_history_cache,
233 fee_history_service.fee_history_limit(),
234 miner.clone(),
235 logger,
236 filters.clone(),
237 transaction_order,
238 );
239
240 let node_service =
242 tokio::task::spawn(NodeService::new(pool, backend, miner, fee_history_service, filters));
243
244 let mut servers = Vec::with_capacity(config.host.len());
245 let mut addresses = Vec::with_capacity(config.host.len());
246
247 for addr in &config.host {
248 let sock_addr = SocketAddr::new(*addr, port);
249
250 let tcp_listener = tokio::net::TcpListener::bind(sock_addr).await?;
252 addresses.push(tcp_listener.local_addr()?);
253
254 let srv = server::serve_on(tcp_listener, api.clone(), server_config.clone());
256 servers.push(tokio::task::spawn(srv.map_err(Into::into)));
257 }
258
259 let tokio_handle = Handle::current();
260 let (signal, on_shutdown) = shutdown::signal();
261 let task_manager = TaskManager::new(tokio_handle, on_shutdown);
262
263 let ipc_task =
264 config.get_ipc_path().map(|path| try_spawn_ipc(api.clone(), path)).transpose()?;
265
266 let handle = NodeHandle {
267 config,
268 node_service,
269 servers,
270 ipc_task,
271 addresses,
272 _signal: Some(signal),
273 task_manager,
274 };
275
276 handle.print(fork.as_ref())?;
277
278 Ok((api, handle))
279}
280
281type IpcTask = JoinHandle<()>;
282
283pub struct NodeHandle {
287 config: NodeConfig,
288 addresses: Vec<SocketAddr>,
290 node_service: JoinHandle<Result<(), NodeError>>,
292 servers: Vec<JoinHandle<Result<(), NodeError>>>,
294 ipc_task: Option<IpcTask>,
296 _signal: Option<Signal>,
298 task_manager: TaskManager,
300}
301
302impl Drop for NodeHandle {
303 fn drop(&mut self) {
304 if let Some(signal) = self._signal.take() {
306 let _ = signal.fire();
307 }
308 self.node_service.abort();
309 for server in &self.servers {
310 server.abort();
311 }
312 if let Some(ipc_task) = &self.ipc_task {
313 ipc_task.abort();
314 }
315 }
316}
317
318impl NodeHandle {
319 pub const fn config(&self) -> &NodeConfig {
321 &self.config
322 }
323
324 pub(crate) fn print(&self, fork: Option<&ClientFork>) -> Result<()> {
326 self.config.print(fork)?;
327 if !self.config.silent {
328 if let Some(ipc_path) = self.ipc_path() {
329 sh_println!("IPC path: {ipc_path}")?;
330 }
331 sh_println!(
332 "Listening on {}",
333 self.addresses
334 .iter()
335 .map(|addr| { addr.to_string() })
336 .collect::<Vec<String>>()
337 .join(", ")
338 )?;
339 }
340 Ok(())
341 }
342
343 pub fn socket_address(&self) -> &SocketAddr {
348 &self.addresses[0]
349 }
350
351 pub fn http_endpoint(&self) -> String {
353 format!("http://{}", self.socket_address())
354 }
355
356 pub fn ws_endpoint(&self) -> String {
358 format!("ws://{}", self.socket_address())
359 }
360
361 pub fn ipc_path(&self) -> Option<String> {
363 self.config.get_ipc_path()
364 }
365
366 pub fn http_provider(&self) -> RetryProvider {
368 ProviderBuilder::new(&self.http_endpoint()).build().expect("failed to build HTTP provider")
369 }
370
371 pub fn ws_provider(&self) -> RetryProvider {
373 ProviderBuilder::new(&self.ws_endpoint()).build().expect("failed to build WS provider")
374 }
375
376 pub fn ipc_provider(&self) -> Option<RetryProvider> {
378 ProviderBuilder::new(&self.config.get_ipc_path()?).build().ok()
379 }
380
381 pub fn dev_accounts(&self) -> impl Iterator<Item = Address> + '_ {
383 self.config.signer_accounts.iter().map(|wallet| wallet.address())
384 }
385
386 pub fn dev_wallets(&self) -> impl Iterator<Item = PrivateKeySigner> + '_ {
388 self.config.signer_accounts.iter().cloned()
389 }
390
391 pub fn genesis_accounts(&self) -> impl Iterator<Item = Address> + '_ {
393 self.config.genesis_accounts.iter().map(|w| w.address())
394 }
395
396 pub const fn genesis_balance(&self) -> U256 {
398 self.config.genesis_balance
399 }
400
401 pub fn gas_price(&self) -> u128 {
403 self.config.get_gas_price()
404 }
405
406 pub const fn shutdown_signal(&self) -> &Option<Signal> {
408 &self._signal
409 }
410
411 pub const fn shutdown_signal_mut(&mut self) -> &mut Option<Signal> {
415 &mut self._signal
416 }
417
418 pub const fn task_manager(&self) -> &TaskManager {
434 &self.task_manager
435 }
436}
437
438impl Future for NodeHandle {
439 type Output = Result<NodeResult<()>, JoinError>;
440
441 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
442 let pin = self.get_mut();
443
444 if let Some(mut ipc) = pin.ipc_task.take() {
446 if let Poll::Ready(res) = ipc.poll_unpin(cx) {
447 return Poll::Ready(res.map(|()| Ok(())));
448 }
449 pin.ipc_task = Some(ipc);
450 }
451
452 if let Poll::Ready(res) = pin.node_service.poll_unpin(cx) {
454 return Poll::Ready(res);
455 }
456
457 for server in &mut pin.servers {
459 if let Poll::Ready(res) = server.poll_unpin(cx) {
460 return Poll::Ready(res);
461 }
462 }
463
464 Poll::Pending
465 }
466}
467
468#[doc(hidden)]
469pub fn init_tracing() -> LoggingManager {
470 use tracing_subscriber::prelude::*;
471
472 let manager = LoggingManager::default();
473
474 let _ = if let Ok(rust_log_val) = std::env::var("RUST_LOG")
475 && !rust_log_val.contains('=')
476 {
477 let rust_log_val = if rust_log_val.contains("node") {
481 rust_log_val
482 } else {
483 format!("{rust_log_val},node=info")
484 };
485
486 let env_filter: EnvFilter =
487 rust_log_val.parse().expect("failed to parse modified RUST_LOG");
488 tracing_subscriber::registry()
489 .with(env_filter)
490 .with(tracing_subscriber::fmt::layer())
491 .try_init()
492 } else {
493 tracing_subscriber::Registry::default()
494 .with(NodeLogLayer::new(manager.clone()))
495 .with(
496 tracing_subscriber::fmt::layer()
497 .without_time()
498 .with_target(false)
499 .with_level(false),
500 )
501 .try_init()
502 };
503
504 manager
505}