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