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