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