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