Skip to main content

foundry_evm_core/fork/
multi.rs

1//! Support for running multiple fork backends.
2//!
3//! The design is similar to the single `SharedBackend`, `BackendHandler` but supports multiple
4//! concurrently active pairs at once.
5
6use super::{CreateFork, Fork, bal};
7use crate::{FoundryBlock, opts::ForkContext};
8use alloy_eips::{BlockNumHash, eip7928::BlockAccessList};
9use alloy_evm::EvmEnv;
10use alloy_network::{AnyNetwork, Network};
11use alloy_primitives::map::HashMap;
12use foundry_config::Config;
13use foundry_fork_db::{
14    BackendHandler, BlockchainDb, ForkBlock, ForkBlockEnv, SharedBackend, cache::BlockchainDbMeta,
15};
16use futures::{
17    FutureExt, StreamExt,
18    channel::mpsc::{Receiver, Sender, channel},
19    stream::Fuse,
20    task::{Context, Poll},
21};
22use revm::primitives::hardfork::SpecId;
23use std::{
24    fmt::{self, Write},
25    pin::Pin,
26    sync::{
27        Arc,
28        atomic::{AtomicBool, AtomicUsize, Ordering},
29        mpsc::{Sender as OneshotSender, channel as oneshot_channel},
30    },
31    time::Duration,
32};
33
34/// The _unique_ identifier for a specific fork, this could be the name of the network a custom
35/// descriptive name.
36#[derive(Clone, Debug, PartialEq, Eq, Hash)]
37pub struct ForkId(pub String);
38
39impl ForkId {
40    /// Returns the identifier for a Fork from a URL and block number.
41    pub fn new(url: &str, num: Option<u64>) -> Self {
42        Self::new_with_context(url, num, None)
43    }
44
45    fn new_with_context(
46        url: &str,
47        num: Option<u64>,
48        context: Option<&crate::opts::ForkContext>,
49    ) -> Self {
50        let mut id = url.to_string();
51        if let Some(context) = context {
52            write!(
53                id,
54                "#{}:{}:{}:{}:{:?}:{:?}:{:?}:{:?}",
55                context.execution_chain_id,
56                context.source_chain_id,
57                context.network,
58                context.network_profile.execution_profile_name(),
59                context.hardfork,
60                context.instance_id,
61                context.source_fork_block_number,
62                context.source_fork_block_hash
63            )
64            .unwrap();
65        }
66        id.push('@');
67        match num {
68            Some(n) => write!(id, "{n:#x}").unwrap(),
69            None => id.push_str("latest"),
70        }
71        Self(id)
72    }
73
74    /// Returns the identifier for an exactly resolved fork.
75    fn resolved(url: &str, fork: &Fork) -> Self {
76        let mut id = Self::exact(url, fork, fork.block());
77        if fork.state_by_number {
78            id.0.push_str("#state-by-number");
79        }
80        id
81    }
82
83    /// Exact rolls always read state by hash, regardless of the parent fork's state mode.
84    fn exact(url: &str, fork: &Fork, block: BlockNumHash) -> Self {
85        let mut id = Self::new_with_context(url, Some(block.number), Some(&fork.context())).0;
86        write!(id, "#{}:{}", block.hash, fork.source_id()).unwrap();
87        Self(id)
88    }
89
90    /// Returns the identifier of the fork.
91    pub fn as_str(&self) -> &str {
92        &self.0
93    }
94}
95
96impl fmt::Display for ForkId {
97    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
98        self.0.fmt(f)
99    }
100}
101
102impl<T: Into<String>> From<T> for ForkId {
103    fn from(id: T) -> Self {
104        Self(id.into())
105    }
106}
107
108/// Backend, environment, and identity returned after creating or rolling a fork.
109pub struct ForkResult<N: Network, SPEC, BLOCK: ForkBlockEnv> {
110    /// Identifier assigned to the fork.
111    pub id: ForkId,
112    /// Backend pinned to the resolved fork block.
113    pub backend: SharedBackend<N, BLOCK>,
114    /// EVM environment reconstructed from the resolved fork block.
115    pub env: EvmEnv<SPEC, BLOCK>,
116    /// Exact source and block identity used to construct the backend.
117    pub fork: Fork,
118}
119
120/// The Sender half of multi fork pair.
121/// Can send requests to the `MultiForkHandler` to create forks.
122#[derive(Clone, Debug)]
123#[must_use]
124pub struct MultiFork<N: Network, SPEC, BLOCK: ForkBlockEnv> {
125    /// Channel to send `Request`s to the handler.
126    handler: Sender<Request<N, SPEC, BLOCK>>,
127    /// Ensures that all rpc resources get flushed properly.
128    _shutdown: Arc<ShutDownMultiFork<N, SPEC, BLOCK>>,
129}
130
131impl<
132    N: Network,
133    SPEC: Into<SpecId> + Default + Copy + Unpin + Send + 'static,
134    BLOCK: FoundryBlock + ForkBlockEnv + Default + Unpin,
135> MultiFork<N, SPEC, BLOCK>
136{
137    /// Creates a new pair and spawns the `MultiForkHandler` on a background thread.
138    pub fn spawn() -> Self {
139        Self::spawn_with_forks(HashMap::default(), None)
140    }
141
142    /// Starts an independent registry sharing the existing remote caches.
143    pub(crate) fn scoped(&self) -> eyre::Result<Self> {
144        let (sender, rx) = oneshot_channel();
145        self.handler
146            .clone()
147            .try_send(Request::CloneForks(sender))
148            .map_err(|e| eyre::eyre!("{e:?}"))?;
149        Ok(Self::spawn_with_forks(rx.recv()?, Some(self._shutdown.clone())))
150    }
151
152    fn spawn_with_forks(
153        forks: HashMap<ForkId, CreatedFork<N, SPEC, BLOCK>>,
154        parent: Option<Arc<ShutDownMultiFork<N, SPEC, BLOCK>>>,
155    ) -> Self {
156        trace!(target: "fork::multi", "spawning multifork");
157        let (mut fork, mut handler) = Self::new();
158        handler.forks = forks;
159        Arc::get_mut(&mut fork._shutdown).unwrap()._parent = parent;
160
161        // Spawn a light-weight thread just for sending and receiving data from the remote
162        // client(s).
163        let fut = async move {
164            // Flush cache every 60s, this ensures that long-running fork tests get their
165            // cache flushed from time to time.
166            // NOTE: we install the interval here because the `tokio::timer::Interval`
167            // requires a rt.
168            handler.set_flush_cache_interval(Duration::from_secs(60));
169            handler.await
170        };
171        match tokio::runtime::Handle::try_current() {
172            Ok(rt) => _ = rt.spawn(fut),
173            Err(_) => {
174                trace!(target: "fork::multi", "spawning multifork backend thread");
175                _ = std::thread::Builder::new()
176                    .name("multi-fork-backend".into())
177                    .spawn(move || {
178                        tokio::runtime::Builder::new_current_thread()
179                            .enable_all()
180                            .build()
181                            .expect("failed to build tokio runtime")
182                            .block_on(fut)
183                    })
184                    .expect("failed to spawn thread")
185            }
186        }
187
188        trace!(target: "fork::multi", "spawned MultiForkHandler thread");
189        fork
190    }
191
192    /// Creates a new pair multi fork pair.
193    ///
194    /// Use [`spawn`](Self::spawn) instead.
195    #[doc(hidden)]
196    pub fn new() -> (Self, MultiForkHandler<N, SPEC, BLOCK>) {
197        let (handler, handler_rx) = channel(1);
198        let _shutdown =
199            Arc::new(ShutDownMultiFork { handler: Some(handler.clone()), _parent: None });
200        (Self { handler, _shutdown }, MultiForkHandler::new(handler_rx))
201    }
202
203    /// Returns a fork backend.
204    ///
205    /// If no matching fork backend exists it will be created.
206    pub fn create_fork(&self, fork: CreateFork) -> eyre::Result<ForkResult<N, SPEC, BLOCK>> {
207        trace!("Creating new fork, url={}, block={:?}", fork.url, fork.evm_opts.fork_block_number);
208        let (sender, rx) = oneshot_channel();
209        let req = Request::CreateFork(Box::new(fork), sender);
210        self.handler.clone().try_send(req).map_err(|e| eyre::eyre!("{:?}", e))?;
211        rx.recv()?
212    }
213
214    /// Rolls the block of the fork.
215    ///
216    /// If no matching fork backend exists it will be created.
217    pub fn roll_fork(&self, fork: ForkId, block: u64) -> eyre::Result<ForkResult<N, SPEC, BLOCK>> {
218        trace!(?fork, ?block, "rolling fork");
219        let (sender, rx) = oneshot_channel();
220        let req = Request::RollFork(fork, block, sender);
221        self.handler.clone().try_send(req).map_err(|e| eyre::eyre!("{:?}", e))?;
222        rx.recv()?
223    }
224
225    /// Rolls a fork to an already resolved exact block.
226    pub fn roll_fork_exact(
227        &self,
228        fork: ForkId,
229        block: BlockNumHash,
230    ) -> eyre::Result<ForkResult<N, SPEC, BLOCK>> {
231        self.roll_fork_exact_with_bal(fork, block, false)
232    }
233
234    /// Rolls to an exact parent block, optionally warming its cache before transaction replay.
235    pub(crate) fn roll_fork_exact_with_bal(
236        &self,
237        fork: ForkId,
238        block: BlockNumHash,
239        prewarm_bal: bool,
240    ) -> eyre::Result<ForkResult<N, SPEC, BLOCK>> {
241        trace!(?fork, ?block, "rolling fork to exact block");
242        let (sender, rx) = oneshot_channel();
243        let req = Request::RollForkExact(fork, block, prewarm_bal, sender);
244        self.handler.clone().try_send(req).map_err(|e| eyre::eyre!("{:?}", e))?;
245        rx.recv()?
246    }
247
248    /// Returns the `EvmEnv` of the given fork, if any.
249    pub fn get_evm_env(&self, fork: ForkId) -> eyre::Result<Option<EvmEnv<SPEC, BLOCK>>> {
250        trace!(?fork, "getting env config");
251        let (sender, rx) = oneshot_channel();
252        let req = Request::GetEvmEnv(fork, sender);
253        self.handler.clone().try_send(req).map_err(|e| eyre::eyre!("{:?}", e))?;
254        Ok(rx.recv()?)
255    }
256
257    /// Returns the corresponding fork if it exists.
258    ///
259    /// Returns `None` if no matching fork backend is available.
260    pub fn get_fork(&self, id: impl Into<ForkId>) -> eyre::Result<Option<SharedBackend<N, BLOCK>>> {
261        let id = id.into();
262        trace!(?id, "get fork backend");
263        let (sender, rx) = oneshot_channel();
264        let req = Request::GetFork(id, sender);
265        self.handler.clone().try_send(req).map_err(|e| eyre::eyre!("{:?}", e))?;
266        Ok(rx.recv()?)
267    }
268
269    /// Returns the immutable remote fork owned by the selected backend.
270    pub fn get_fork_info(&self, id: impl Into<ForkId>) -> eyre::Result<Option<Fork>> {
271        let (sender, rx) = oneshot_channel();
272        self.handler
273            .clone()
274            .try_send(Request::GetForkInfo(id.into(), sender))
275            .map_err(|e| eyre::eyre!("{e:?}"))?;
276        Ok(rx.recv()?)
277    }
278
279    /// Returns the corresponding fork url if it exists.
280    ///
281    /// Returns `None` if no matching fork is available.
282    pub fn get_fork_url(&self, id: impl Into<ForkId>) -> eyre::Result<Option<String>> {
283        let (sender, rx) = oneshot_channel();
284        let req = Request::GetForkUrl(id.into(), sender);
285        self.handler.clone().try_send(req).map_err(|e| eyre::eyre!("{:?}", e))?;
286        Ok(rx.recv()?)
287    }
288
289    /// Returns the options used to create the corresponding fork if it exists.
290    ///
291    /// Returns `None` if no matching fork is available.
292    pub fn get_fork_options(&self, id: impl Into<ForkId>) -> eyre::Result<Option<CreateFork>> {
293        let (sender, rx) = oneshot_channel();
294        let req = Request::GetForkOptions(id.into(), sender);
295        self.handler.clone().try_send(req).map_err(|e| eyre::eyre!("{:?}", e))?;
296        Ok(rx.recv()?)
297    }
298}
299
300type CreateFuture<SPEC, BLOCK> = Pin<
301    Box<
302        dyn Future<
303                Output = eyre::Result<(
304                    ForkId,
305                    CreateFork,
306                    Fork,
307                    EvmEnv<SPEC, BLOCK>,
308                    Option<BlockAccessList>,
309                )>,
310            > + Send,
311    >,
312>;
313type CreateSender<N, SPEC, BLOCK> = OneshotSender<eyre::Result<ForkResult<N, SPEC, BLOCK>>>;
314type GetEvmEnvSender<SPEC, BLOCK> = OneshotSender<Option<EvmEnv<SPEC, BLOCK>>>;
315
316/// Request that's send to the handler.
317#[derive(Debug)]
318enum Request<N: Network, SPEC, BLOCK: ForkBlockEnv> {
319    CloneForks(OneshotSender<HashMap<ForkId, CreatedFork<N, SPEC, BLOCK>>>),
320    /// Creates a new ForkBackend.
321    CreateFork(Box<CreateFork>, CreateSender<N, SPEC, BLOCK>),
322    /// Returns the Fork backend for the `ForkId` if it exists.
323    GetFork(ForkId, OneshotSender<Option<SharedBackend<N, BLOCK>>>),
324    /// Adjusts the block that's being forked, by creating a new fork at the new block.
325    RollFork(ForkId, u64, CreateSender<N, SPEC, BLOCK>),
326    /// Adjusts the fork to an already resolved exact block.
327    RollForkExact(ForkId, BlockNumHash, bool, CreateSender<N, SPEC, BLOCK>),
328    /// Returns the environment of the fork.
329    GetEvmEnv(ForkId, GetEvmEnvSender<SPEC, BLOCK>),
330    /// Shutdowns the entire `MultiForkHandler`, see `ShutDownMultiFork`
331    ShutDown(OneshotSender<()>),
332    /// Returns the Fork Url for the `ForkId` if it exists.
333    GetForkUrl(ForkId, OneshotSender<Option<String>>),
334    GetForkInfo(ForkId, OneshotSender<Option<Fork>>),
335    /// Returns the options used to create the `ForkId` if it exists.
336    GetForkOptions(ForkId, OneshotSender<Option<CreateFork>>),
337}
338
339enum ForkTask<N: Network, SPEC, BLOCK: ForkBlockEnv> {
340    /// Contains the future that will establish a new fork.
341    Create {
342        future: CreateFuture<SPEC, BLOCK>,
343        id: ForkId,
344        prewarm_bal: bool,
345        no_fork_bal: bool,
346        sender: CreateSender<N, SPEC, BLOCK>,
347        additional_senders: Vec<CreateSender<N, SPEC, BLOCK>>,
348    },
349}
350
351/// The type that manages connections in the background.
352#[must_use = "futures do nothing unless polled"]
353pub struct MultiForkHandler<N: Network, SPEC, BLOCK: ForkBlockEnv> {
354    /// Incoming requests from the `MultiFork`.
355    incoming: Fuse<Receiver<Request<N, SPEC, BLOCK>>>,
356
357    /// All active handlers.
358    ///
359    /// It's expected that this list will be rather small (<10).
360    handlers: Vec<(ForkId, BackendHandler<N, BLOCK>)>,
361
362    // tasks currently in progress
363    pending_tasks: Vec<ForkTask<N, SPEC, BLOCK>>,
364
365    /// All _unique_ forkids mapped to their corresponding backend.
366    ///
367    /// Note: The backend can be shared by multiple ForkIds if the target the same provider and
368    /// block number.
369    forks: HashMap<ForkId, CreatedFork<N, SPEC, BLOCK>>,
370
371    /// Optional periodic interval to flush rpc cache.
372    flush_cache_interval: Option<tokio::time::Interval>,
373
374    /// Prefetch workers of the backends created by this handler.
375    prefetches: Vec<super::prefetch::Prefetch>,
376}
377
378impl<
379    N: Network,
380    SPEC: Into<SpecId> + Default + Copy + Send + 'static,
381    BLOCK: FoundryBlock + ForkBlockEnv + Default,
382> MultiForkHandler<N, SPEC, BLOCK>
383{
384    fn new(incoming: Receiver<Request<N, SPEC, BLOCK>>) -> Self {
385        Self {
386            incoming: incoming.fuse(),
387            handlers: Default::default(),
388            pending_tasks: Default::default(),
389            forks: Default::default(),
390            flush_cache_interval: None,
391            prefetches: Default::default(),
392        }
393    }
394
395    /// Sets the interval after which all rpc caches should be flushed periodically.
396    pub fn set_flush_cache_interval(&mut self, period: Duration) -> &mut Self {
397        self.flush_cache_interval =
398            Some(tokio::time::interval_at(tokio::time::Instant::now() + period, period));
399        self
400    }
401
402    /// Returns the list of additional senders of a matching task for the given id, if any.
403    fn find_in_progress_task(
404        &mut self,
405        id: &ForkId,
406        prewarm_bal: bool,
407        no_fork_bal: bool,
408    ) -> Option<&mut Vec<CreateSender<N, SPEC, BLOCK>>> {
409        for ForkTask::Create {
410            id: in_progress,
411            prewarm_bal: pending_prewarm,
412            no_fork_bal: pending_opt_out,
413            additional_senders,
414            ..
415        } in &mut self.pending_tasks
416        {
417            if in_progress == id
418                && *pending_prewarm == prewarm_bal
419                && *pending_opt_out == no_fork_bal
420            {
421                return Some(additional_senders);
422            }
423        }
424        None
425    }
426
427    fn create_fork(&mut self, fork: CreateFork, sender: CreateSender<N, SPEC, BLOCK>) {
428        self.create_fork_with_identity(fork, None, false, None, sender);
429    }
430
431    fn create_fork_with_identity(
432        &mut self,
433        fork: CreateFork,
434        expected_identity: Option<ForkContext>,
435        prewarm_bal: bool,
436        exact_block: Option<(Fork, BlockNumHash)>,
437        sender: CreateSender<N, SPEC, BLOCK>,
438    ) {
439        let no_fork_bal = fork.evm_opts.no_fork_bal;
440        let prewarm_bal = prewarm_bal && !no_fork_bal;
441        let resolved_id =
442            exact_block.as_ref().map(|(parent, block)| ForkId::exact(&fork.url, parent, *block));
443        trace!(?resolved_id, "creating fork");
444
445        // Only deduplicate requests that already carry an exact identity. Unresolved requests at
446        // the same URL and height can resolve to different blocks across a reorganization.
447        if let Some(fork_id) = &resolved_id
448            && let Some(in_progress) = self.find_in_progress_task(fork_id, prewarm_bal, no_fork_bal)
449        {
450            in_progress.push(sender);
451            return;
452        }
453
454        let already_prewarmed = resolved_id
455            .as_ref()
456            .and_then(|id| self.forks.get(id))
457            .is_some_and(|fork| fork.bal_prewarmed.load(Ordering::Relaxed));
458
459        // Need to create a new fork.
460        let task_id =
461            resolved_id.unwrap_or_else(|| ForkId::new(&fork.url, fork.evm_opts.fork_block_number));
462        let needs_bal = prewarm_bal && !already_prewarmed;
463        let future = Box::pin(create_fork(fork, expected_identity, needs_bal, exact_block));
464        self.pending_tasks.push(ForkTask::Create {
465            future,
466            id: task_id,
467            prewarm_bal,
468            no_fork_bal,
469            sender,
470            additional_senders: Vec::new(),
471        });
472    }
473
474    fn insert_new_fork(
475        &mut self,
476        fork_id: ForkId,
477        fork: CreatedFork<N, SPEC, BLOCK>,
478        sender: CreateSender<N, SPEC, BLOCK>,
479        additional_senders: Vec<CreateSender<N, SPEC, BLOCK>>,
480    ) {
481        self.forks.insert(fork_id.clone(), fork.clone());
482        let resolved = fork.fork.clone();
483        let _ = sender.send(Ok(ForkResult {
484            id: fork_id.clone(),
485            backend: fork.backend.clone(),
486            env: fork.evm_env.clone(),
487            fork: resolved.clone(),
488        }));
489
490        // Notify all additional senders and track unique forkIds.
491        for sender in additional_senders {
492            let next_fork_id = fork.inc_senders(fork_id.clone());
493            self.forks.insert(next_fork_id.clone(), fork.clone());
494            let _ = sender.send(Ok(ForkResult {
495                id: next_fork_id,
496                backend: fork.backend.clone(),
497                env: fork.evm_env.clone(),
498                fork: resolved.clone(),
499            }));
500        }
501    }
502
503    fn on_request(&mut self, req: Request<N, SPEC, BLOCK>) {
504        match req {
505            Request::CloneForks(sender) => {
506                let _ = sender.send(self.forks.clone());
507            }
508            Request::CreateFork(fork, sender) => self.create_fork(*fork, sender),
509            Request::GetFork(fork_id, sender) => {
510                let fork = self.forks.get(&fork_id).map(|f| f.backend.clone());
511                let _ = sender.send(fork);
512            }
513            Request::RollFork(fork_id, block, sender) => {
514                if let Some(fork) = self.forks.get(&fork_id) {
515                    trace!(target: "fork::multi", "rolling {} to {}", fork_id, block);
516                    let expected_identity = Some(fork.fork.context());
517                    let mut opts = fork.opts.clone();
518                    opts.evm_opts.fork_block_number = Some(block);
519                    opts.evm_opts.fork_block_number_is_inferred = false;
520                    self.create_fork_with_identity(opts, expected_identity, false, None, sender)
521                } else {
522                    let _ =
523                        sender.send(Err(eyre::eyre!("No matching fork exists for {}", fork_id)));
524                }
525            }
526            Request::RollForkExact(fork_id, block, prewarm_bal, sender) => {
527                if let Some(fork) = self.forks.get(&fork_id) {
528                    trace!(target: "fork::multi", "rolling {} to exact block {:?}", fork_id, block);
529                    let mut opts = fork.opts.clone();
530                    opts.evm_opts.fork_block_number = Some(block.number);
531                    opts.evm_opts.fork_state_by_number = false;
532                    opts.evm_opts.fork_block_number_is_inferred = false;
533                    self.create_fork_with_identity(
534                        opts,
535                        None,
536                        prewarm_bal,
537                        Some((fork.fork.clone(), block)),
538                        sender,
539                    )
540                } else {
541                    let _ =
542                        sender.send(Err(eyre::eyre!("No matching fork exists for {}", fork_id)));
543                }
544            }
545            Request::GetEvmEnv(fork_id, sender) => {
546                let _ = sender.send(self.forks.get(&fork_id).map(|fork| fork.evm_env.clone()));
547            }
548            Request::ShutDown(sender) => {
549                trace!(target: "fork::multi", "received shutdown signal");
550                // We're emptying all fork backends, this way we ensure all caches get flushed.
551                self.forks.clear();
552                self.handlers.clear();
553                // Reads fail once the handlers are gone, so the workers exit promptly, and the
554                // last of them flushes the cache before the shutdown completes.
555                for prefetch in self.prefetches.drain(..) {
556                    prefetch.stop();
557                }
558                let _ = sender.send(());
559            }
560            Request::GetForkInfo(fork_id, sender) => {
561                let _ = sender.send(self.forks.get(&fork_id).map(|fork| fork.fork.clone()));
562            }
563            Request::GetForkUrl(fork_id, sender) => {
564                let fork = self.forks.get(&fork_id).map(|f| f.opts.url.clone());
565                let _ = sender.send(fork);
566            }
567            Request::GetForkOptions(fork_id, sender) => {
568                let fork = self.forks.get(&fork_id).map(|f| f.opts.clone());
569                let _ = sender.send(fork);
570            }
571        }
572    }
573}
574
575// Drives all handler to completion.
576// This future will finish once all underlying BackendHandler are completed.
577impl<
578    N: Network,
579    SPEC: Into<SpecId> + Default + Copy + Unpin + Send + 'static,
580    BLOCK: FoundryBlock + ForkBlockEnv + Default + Unpin,
581> Future for MultiForkHandler<N, SPEC, BLOCK>
582{
583    type Output = ();
584
585    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
586        let this = self.get_mut();
587
588        // Receive new requests.
589        loop {
590            match this.incoming.poll_next_unpin(cx) {
591                Poll::Ready(Some(req)) => this.on_request(req),
592                Poll::Ready(None) => {
593                    // Channel closed, but we still need to drive the fork handlers to completion.
594                    trace!(target: "fork::multi", "request channel closed");
595                    break;
596                }
597                Poll::Pending => break,
598            }
599        }
600
601        // Advance all tasks.
602        for n in (0..this.pending_tasks.len()).rev() {
603            let task = this.pending_tasks.swap_remove(n);
604            match task {
605                ForkTask::Create {
606                    mut future,
607                    id,
608                    prewarm_bal,
609                    no_fork_bal,
610                    sender,
611                    additional_senders,
612                } => {
613                    if let Poll::Ready(resp) = future.poll_unpin(cx) {
614                        let resp = resp.and_then(|(fork_id, opts, resolved, evm_env, mut bal)| {
615                            let (fork_id, fork) = if let Some(mut cached) =
616                                this.forks.get(&fork_id).cloned()
617                            {
618                                if bal.is_some()
619                                    && resolved.fingerprint() != cached.fork.fingerprint()
620                                {
621                                    debug!(target: "backend::fork", "ignoring fork BAL for a different cache identity");
622                                    bal = None;
623                                }
624                                // Share the remote cache, RPC client, and block while retaining
625                                // this consumer's selector, execution environment, and BAL
626                                // policy. The fork ID includes the block hash, so the cached
627                                // block is the same block.
628                                cached.opts = opts;
629                                cached.fork = Fork {
630                                    client: cached.fork.client,
631                                    block: cached.fork.block,
632                                    ..resolved
633                                };
634                                cached.evm_env = evm_env;
635                                (cached.inc_senders(fork_id), cached)
636                            } else {
637                                // Only a new backend loads the disk cache.
638                                let (backend, handler) =
639                                    create_backend(&opts, &resolved, &evm_env.block_env)?;
640                                // Only a fork of `latest` starts cold on each new head; rolls and
641                                // pinned blocks do not prefetch. Prefetching reads older cache
642                                // files, so it follows the disk caching settings.
643                                let evm_opts = &opts.evm_opts;
644                                if evm_opts.fork_prefetch
645                                    && opts.enable_caching
646                                    && !resolved.state_by_number
647                                    && (evm_opts.fork_block_number.is_none()
648                                        || evm_opts.fork_block_number_is_inferred)
649                                {
650                                    this.prefetches
651                                        .extend(super::prefetch::spawn(&resolved, &backend));
652                                }
653                                this.handlers.push((fork_id.clone(), handler));
654                                (fork_id, CreatedFork::new(opts, resolved, evm_env, backend))
655                            };
656                            Ok((fork_id, fork, bal))
657                        });
658                        match resp {
659                            Ok((fork_id, fork, bal)) => {
660                                // Apply only after choosing the backend, including an existing
661                                // cache.
662                                if let Some(bal) = bal {
663                                    bal::cache_bal(&fork.backend.data(), bal);
664                                    fork.bal_prewarmed.store(true, Ordering::Relaxed);
665                                }
666                                this.insert_new_fork(fork_id, fork, sender, additional_senders);
667                            }
668                            Err(err) => {
669                                let _ = sender.send(Err(eyre::eyre!("{err}")));
670                                for sender in additional_senders {
671                                    let _ = sender.send(Err(eyre::eyre!("{err}")));
672                                }
673                            }
674                        }
675                    } else {
676                        this.pending_tasks.push(ForkTask::Create {
677                            future,
678                            id,
679                            prewarm_bal,
680                            no_fork_bal,
681                            sender,
682                            additional_senders,
683                        });
684                    }
685                }
686            }
687        }
688
689        // Advance all handlers.
690        for n in (0..this.handlers.len()).rev() {
691            let (id, mut handler) = this.handlers.swap_remove(n);
692            match handler.poll_unpin(cx) {
693                Poll::Ready(_) => {
694                    trace!(target: "fork::multi", "fork {:?} completed", id);
695                }
696                Poll::Pending => {
697                    this.handlers.push((id, handler));
698                }
699            }
700        }
701
702        if this.handlers.is_empty() && this.incoming.is_done() {
703            trace!(target: "fork::multi", "completed");
704            return Poll::Ready(());
705        }
706
707        // Periodically flush cached RPC state.
708        if this
709            .flush_cache_interval
710            .as_mut()
711            .map(|interval| interval.poll_tick(cx).is_ready())
712            .unwrap_or_default()
713            && !this.forks.is_empty()
714        {
715            trace!(target: "fork::multi", "tick flushing caches");
716            // Only the registry owning a handler flushes its cache, once per backend.
717            let forks = this
718                .handlers
719                .iter()
720                .filter_map(|(id, _)| this.forks.get(id))
721                .map(|fork| fork.backend.clone())
722                .collect::<Vec<_>>();
723            // Flush this on new thread to not block here.
724            std::thread::Builder::new()
725                .name("flusher".into())
726                .spawn(move || {
727                    for fork in forks {
728                        fork.flush_cache();
729                    }
730                })
731                .expect("failed to spawn thread");
732        }
733
734        Poll::Pending
735    }
736}
737
738/// Tracks the created Fork
739#[derive(Debug, Clone)]
740struct CreatedFork<N: Network, SPEC, BLOCK: ForkBlockEnv> {
741    /// How the fork was initially created.
742    opts: CreateFork,
743    /// The immutable remote fork owned by this backend.
744    fork: Fork,
745    /// The resolved EVM environment (fetched from the provider).
746    evm_env: EvmEnv<SPEC, BLOCK>,
747    /// Copy of the sender.
748    backend: SharedBackend<N, BLOCK>,
749    /// How many consumers there are, since a `SharedBacked` can be used by multiple
750    /// consumers.
751    num_senders: Arc<AtomicUsize>,
752    /// Whether this exact shared backend has successfully received a validated parent BAL.
753    bal_prewarmed: Arc<AtomicBool>,
754}
755
756impl<N: Network, SPEC, BLOCK: ForkBlockEnv> CreatedFork<N, SPEC, BLOCK> {
757    pub fn new(
758        opts: CreateFork,
759        fork: Fork,
760        evm_env: EvmEnv<SPEC, BLOCK>,
761        backend: SharedBackend<N, BLOCK>,
762    ) -> Self {
763        Self {
764            opts,
765            fork,
766            evm_env,
767            backend,
768            num_senders: Arc::new(AtomicUsize::new(1)),
769            bal_prewarmed: Arc::default(),
770        }
771    }
772
773    /// Increment senders and return unique identifier of the fork.
774    fn inc_senders(&self, fork_id: ForkId) -> ForkId {
775        format!(
776            "{}-{}",
777            fork_id.as_str(),
778            self.num_senders.fetch_add(1, std::sync::atomic::Ordering::Relaxed)
779        )
780        .into()
781    }
782}
783
784/// A type that's used to signaling the `MultiForkHandler` when it's time to shut down.
785///
786/// This is essentially a sync on drop, so that the `MultiForkHandler` can flush all rpc cashes.
787///
788/// This type intentionally does not implement `Clone` since it's intended that there's only once
789/// instance.
790#[derive(Debug)]
791struct ShutDownMultiFork<N: Network, SPEC, BLOCK: ForkBlockEnv> {
792    handler: Option<Sender<Request<N, SPEC, BLOCK>>>,
793    // Keep shared backend handlers alive until this registry has shut down.
794    _parent: Option<Arc<Self>>,
795}
796
797impl<N: Network, SPEC, BLOCK: ForkBlockEnv> Drop for ShutDownMultiFork<N, SPEC, BLOCK> {
798    fn drop(&mut self) {
799        trace!(target: "fork::multi", "initiating shutdown");
800        let (sender, rx) = oneshot_channel();
801        let req = Request::ShutDown(sender);
802        if let Some(mut handler) = self.handler.take()
803            && handler.try_send(req).is_ok()
804        {
805            let _ = rx.recv();
806            trace!(target: "fork::cache", "multifork backend shutdown");
807        }
808    }
809}
810
811/// Creates a new fork.
812///
813/// This resolves the fork block and environment through the endpoint. The handler creates the
814/// backend with [`create_backend`] only if no backend exists for the fork ID.
815async fn create_fork<
816    SPEC: Into<SpecId> + Default + Copy + Send,
817    BLOCK: FoundryBlock + ForkBlockEnv + Default,
818>(
819    mut fork: CreateFork,
820    expected_identity: Option<ForkContext>,
821    prewarm_bal: bool,
822    exact_block: Option<(Fork, BlockNumHash)>,
823) -> eyre::Result<(ForkId, CreateFork, Fork, EvmEnv<SPEC, BLOCK>, Option<BlockAccessList>)> {
824    // Ensure evm_opts reflects the fork URL (may differ from the resolved CreateFork url when
825    // created via cheatcodes, where evm_opts is cloned from the base config).
826    let execution_networks = fork.evm_opts.networks;
827    let require_endpoint_family_match =
828        fork.evm_opts.fork_network_is_inferred || !execution_networks.has_network_selection();
829    let targets_new_endpoint =
830        fork.evm_opts.fork_url.as_ref().is_some_and(|endpoint| endpoint != &fork.url)
831            || fork
832                .evm_opts
833                .fork_endpoint
834                .as_ref()
835                .is_some_and(|identity| identity.endpoint != fork.url);
836    if targets_new_endpoint {
837        // The EVM implementation is already fixed, so use its family as the fallback for a custom
838        // endpoint without metadata. Clear identity and chain values inferred from the old URL;
839        // authoritative metadata from the new endpoint is still checked below.
840        fork.evm_opts.fork_endpoint = None;
841        fork.evm_opts.expected_fork_endpoint = None;
842        fork.evm_opts.fork_network_is_inferred = false;
843        if fork.evm_opts.fork_chain_id_is_inferred {
844            fork.evm_opts.env.chain_id = None;
845            fork.evm_opts.fork_chain_id_is_inferred = false;
846        }
847        if fork.evm_opts.fork_block_number_is_inferred {
848            fork.evm_opts.fork_block_number = None;
849            fork.evm_opts.fork_block_number_is_inferred = false;
850        }
851    }
852    fork.evm_opts.fork_url = Some(fork.url.clone());
853
854    // Initialise the fork environment.
855    // Here we use [`AnyNetwork`] to maximize compatibility with custom chains, aligned with
856    // `EvmOpts::env` impl.
857    let resolved = if let Some((parent, block)) = exact_block {
858        let provider = parent.provider::<AnyNetwork>();
859        fork.evm_opts.check_fork_endpoint(&provider, &parent).await?;
860        let fork_state = parent.at_block(block).await?;
861        fork.evm_opts.check_fork_endpoint(&provider, &fork_state).await?;
862        fork_state
863    } else {
864        fork.evm_opts.prepare_fork().await?.expect("fork URL is configured")
865    };
866    let any_provider = resolved.provider::<AnyNetwork>();
867    let fork_context = resolved.context();
868    let evm_env = fork.evm_opts.fork_env_from_block::<SPEC, BLOCK, AnyNetwork>(
869        fork.evm_opts.chain_id_override().unwrap_or(fork_context.execution_chain_id),
870        fork_context.source_chain_id,
871        &resolved.block,
872    );
873    if require_endpoint_family_match
874        && !execution_networks.supports_fork_source(&fork_context.network_profile)
875    {
876        eyre::bail!(
877            "cannot create a `{}` fork with an EVM instantiated for `{}`; run the script with --rpc-url pointing to the forked chain to select its EVM",
878            fork_context.network,
879            execution_networks.execution_network()
880        );
881    }
882    if let Some(expected) = expected_identity {
883        eyre::ensure!(
884            fork_context.has_same_endpoint_identity(expected),
885            "fork endpoint identity changed while the fork was being rolled"
886        );
887    }
888    let bal = if prewarm_bal {
889        bal::prepare(&any_provider, &resolved, &resolved.block).await
890    } else {
891        None
892    };
893    let fork_id = ForkId::resolved(&fork.url, &resolved);
894
895    Ok((fork_id, fork, resolved, evm_env, bal))
896}
897
898/// Creates the backend of a resolved fork.
899///
900/// This loads the disk cache of the fork block, so it only runs for a new fork ID.
901fn create_backend<N: Network, BLOCK: FoundryBlock + ForkBlockEnv>(
902    fork: &CreateFork,
903    resolved: &Fork,
904    block_env: &BLOCK,
905) -> eyre::Result<(SharedBackend<N, BLOCK>, BackendHandler<N, BLOCK>)> {
906    let fork_context = resolved.context();
907    let number = resolved.number();
908    let account_fetch_policy = crate::backend::account_fetch_policy_for_source(
909        fork_context.source_chain_id,
910        fork_context.network_profile,
911    );
912    let meta = BlockchainDbMeta::new(block_env.clone(), fork.url.clone())
913        .with_fork_identity(resolved.hash(), resolved.source_id())
914        .with_account_fetch_policy(account_fetch_policy);
915
916    // Determine the cache path if caching is enabled.
917    // Number-addressed state can follow a replacement block, so keep it in memory only.
918    let cache_path = if fork.enable_caching && !resolved.state_by_number {
919        Config::foundry_block_cache_dir(fork_context.source_chain_id, number)
920    } else {
921        None
922    };
923
924    let provider = resolved.provider::<N>();
925    let db = BlockchainDb::new(meta, cache_path);
926    let anchor = ForkBlock::with_rpc_number(
927        block_env.number().saturating_to(),
928        resolved.number(),
929        resolved.hash(),
930    );
931    if resolved.state_by_number {
932        SharedBackend::new_with_anchor_by_number(provider, db, anchor)
933    } else {
934        SharedBackend::new_with_anchor(provider, db, anchor)
935    }
936}
937
938#[cfg(test)]
939mod tests {
940    use super::*;
941    use crate::opts::EvmOpts;
942    use alloy_chains::NamedChain;
943    use alloy_eips::eip7928::{AccountChanges, BlockAccessIndex, SlotChanges, StorageChange};
944    use alloy_network::TransactionBuilder;
945    use alloy_primitives::{Address, B256, U256, bytes};
946    use alloy_provider::{Provider, ProviderBuilder, mock::Asserter};
947    use alloy_rpc_types::TransactionRequest;
948    use alloy_serde::WithOtherFields;
949    use foundry_evm_networks::{NetworkConfigs, NetworkVariant};
950    use foundry_fork_db::AccountFetchPolicy;
951    use foundry_test_utils::rpc::{
952        spawn_rpc_proxy_method_not_found_before, spawn_rpc_proxy_recording_method,
953    };
954    use futures::{channel::oneshot, task::noop_waker_ref};
955    use revm::context::{BlockEnv, TxEnv};
956    use std::sync::mpsc::{Receiver as OneshotReceiver, TryRecvError};
957
958    fn context(block_number: u64) -> ForkContext {
959        ForkContext {
960            execution_chain_id: 1,
961            source_chain_id: 1,
962            network: NetworkVariant::Ethereum,
963            network_profile: NetworkConfigs::default(),
964            block_number,
965            hardfork: None,
966            instance_id: None,
967            source_fork_block_number: None,
968            source_fork_block_hash: None,
969        }
970    }
971
972    #[test]
973    fn resolved_fork_ids_include_hash_and_source_identity() {
974        let url = "http://localhost:8545";
975        let first = Fork::test(
976            url,
977            None,
978            None,
979            Some(1),
980            BlockNumHash::new(1, B256::with_last_byte(1)),
981            context(1),
982        );
983        let replacement = Fork::test(
984            url,
985            None,
986            None,
987            Some(1),
988            BlockNumHash::new(1, B256::with_last_byte(2)),
989            context(1),
990        );
991        let authenticated = Fork::test(
992            url,
993            Some(&["Authorization: secret".to_string()]),
994            None,
995            Some(1),
996            BlockNumHash::new(1, B256::with_last_byte(1)),
997            context(1),
998        );
999
1000        assert_ne!(ForkId::resolved(url, &first), ForkId::resolved(url, &replacement));
1001        assert_ne!(ForkId::resolved(url, &first), ForkId::resolved(url, &authenticated));
1002        let mut numbered = first.clone();
1003        numbered.state_by_number = true;
1004        assert_ne!(ForkId::resolved(url, &first), ForkId::resolved(url, &numbered));
1005        assert_eq!(ForkId::resolved(url, &first), ForkId::exact(url, &numbered, first.block()));
1006    }
1007
1008    #[tokio::test(flavor = "multi_thread")]
1009    async fn fork_state_by_number_does_not_load_or_overwrite_hash_cache() {
1010        let chain_id = u64::from_be_bytes(B256::random()[..8].try_into().unwrap());
1011        let (_api, handle) = anvil::spawn(
1012            anvil::NodeConfig::test().with_chain_id(Some(chain_id)).with_no_mining(true),
1013        )
1014        .await;
1015        let opts = EvmOpts {
1016            fork_url: Some(handle.http_endpoint()),
1017            fork_block_number: Some(0),
1018            ..Default::default()
1019        };
1020        let resolved = opts.prepare_fork().await.unwrap().unwrap();
1021        let (env, _) = opts.env_at_fork::<SpecId, BlockEnv, TxEnv>(Some(&resolved)).await.unwrap();
1022        let path = Config::foundry_block_cache_dir(chain_id, resolved.number()).unwrap();
1023        assert!(!path.exists());
1024        let meta = BlockchainDbMeta::new(env.block_env, handle.http_endpoint())
1025            .with_fork_identity(resolved.hash(), resolved.source_id());
1026        let cached = BlockchainDb::new(meta.clone(), Some(path.clone()));
1027        let address = Address::with_last_byte(0x42);
1028        cached.storage().write().entry(address).or_default().insert(U256::ZERO, U256::from(42));
1029        cached.cache().flush();
1030        assert!(path.exists());
1031
1032        for state_by_number in [true, false] {
1033            let fork = CreateFork {
1034                url: handle.http_endpoint(),
1035                enable_caching: true,
1036                evm_opts: EvmOpts { fork_state_by_number: state_by_number, ..opts.clone() },
1037            };
1038            let (_, fork, fork_state, evm_env, _) =
1039                create_fork::<SpecId, BlockEnv>(fork, None, false, None).await.unwrap();
1040            let (backend, handler) =
1041                create_backend::<AnyNetwork, _>(&fork, &fork_state, &evm_env.block_env).unwrap();
1042            assert_eq!(backend.data().storage.read().contains_key(&address), !state_by_number);
1043            if state_by_number {
1044                backend
1045                    .data()
1046                    .storage
1047                    .write()
1048                    .entry(address)
1049                    .or_default()
1050                    .insert(U256::ZERO, U256::from(99));
1051            }
1052            backend.flush_cache();
1053            drop(backend);
1054            drop(handler);
1055            let reloaded = BlockchainDb::new(meta.clone(), Some(path.clone()));
1056            assert_eq!(reloaded.storage().read()[&address][&U256::ZERO], U256::from(42));
1057        }
1058        std::fs::remove_file(&path).unwrap();
1059        std::fs::remove_dir(path.parent().unwrap()).unwrap();
1060    }
1061
1062    #[test]
1063    fn account_fetch_policy_follows_source_identity() {
1064        assert_eq!(
1065            crate::backend::account_fetch_policy_for_source(
1066                NamedChain::Tempo as u64,
1067                NetworkConfigs::with_ethereum(),
1068            ),
1069            AccountFetchPolicy::RequireAccountInfo,
1070        );
1071        assert_eq!(
1072            crate::backend::account_fetch_policy_for_source(
1073                NamedChain::Mainnet as u64,
1074                NetworkConfigs::with_tempo(),
1075            ),
1076            AccountFetchPolicy::Auto,
1077        );
1078        assert_eq!(
1079            crate::backend::account_fetch_policy_for_source(123_456, NetworkConfigs::with_tempo(),),
1080            AccountFetchPolicy::RequireAccountInfo,
1081        );
1082    }
1083
1084    #[test]
1085    fn fork_bal_pending_requests_preserve_opt_out() {
1086        let url = "http://localhost:8545";
1087        let resolved = Fork::test(
1088            url,
1089            None,
1090            None,
1091            Some(1),
1092            BlockNumHash::new(1, B256::with_last_byte(1)),
1093            context(1),
1094        );
1095        let (_, receiver) = channel(1);
1096        let mut handler =
1097            MultiForkHandler::<AnyNetwork, SpecId, revm::context::BlockEnv>::new(receiver);
1098        let mut fork = CreateFork {
1099            enable_caching: false,
1100            url: url.to_string(),
1101            evm_opts: Default::default(),
1102        };
1103        let (sender, _) = oneshot_channel();
1104        handler.create_fork_with_identity(
1105            fork.clone(),
1106            None,
1107            false,
1108            Some((resolved.clone(), resolved.block())),
1109            sender,
1110        );
1111        fork.evm_opts.no_fork_bal = true;
1112        let (sender, _) = oneshot_channel();
1113        handler.create_fork_with_identity(
1114            fork.clone(),
1115            None,
1116            false,
1117            Some((resolved.clone(), resolved.block())),
1118            sender,
1119        );
1120        assert_eq!(handler.pending_tasks.len(), 2, "each fork must retain its opt-out policy");
1121        let (sender, _) = oneshot_channel();
1122        handler.create_fork_with_identity(
1123            fork.clone(),
1124            None,
1125            false,
1126            Some((resolved.clone(), resolved.block())),
1127            sender,
1128        );
1129        assert_eq!(handler.pending_tasks.len(), 2, "identical policies can share creation");
1130        fork.evm_opts.no_fork_bal = false;
1131        let (sender, _) = oneshot_channel();
1132        handler.create_fork_with_identity(
1133            fork.clone(),
1134            None,
1135            true,
1136            Some((resolved.clone(), resolved.block())),
1137            sender,
1138        );
1139        assert_eq!(handler.pending_tasks.len(), 3, "prewarming must not join ordinary creation");
1140        let (sender, _) = oneshot_channel();
1141        handler.create_fork_with_identity(
1142            fork,
1143            None,
1144            true,
1145            Some((resolved.clone(), resolved.block())),
1146            sender,
1147        );
1148        assert_eq!(handler.pending_tasks.len(), 3, "matching prewarm requests can share creation");
1149    }
1150
1151    #[test]
1152    fn fork_bal_fills_selected_cache_for_equivalent_profiles() {
1153        let url = "http://localhost:8545";
1154        let block = BlockNumHash::new(1, B256::with_last_byte(1));
1155        let address = Address::with_last_byte(1);
1156        let create = |resolved: Fork| {
1157            let provider = ProviderBuilder::<_, _, AnyNetwork>::default()
1158                .connect_mocked_client(Asserter::new());
1159            let env = EvmEnv::<SpecId>::default();
1160            let meta = BlockchainDbMeta::new(env.block_env.clone(), url.to_string())
1161                .with_fork_identity(resolved.hash(), resolved.source_id());
1162            let (backend, handler) = SharedBackend::new_with_anchor(
1163                provider,
1164                BlockchainDb::new(meta, None),
1165                ForkBlock::with_rpc_number(1, 1, resolved.hash()),
1166            )
1167            .unwrap();
1168            let opts = CreateFork {
1169                enable_caching: false,
1170                url: url.to_string(),
1171                evm_opts: Default::default(),
1172            };
1173            (CreatedFork::new(opts, resolved, env, backend), handler)
1174        };
1175
1176        for profile in [NetworkConfigs::default(), NetworkConfigs::with_ethereum()] {
1177            let cached = Fork::test(url, None, None, None, block, context(1));
1178            let candidate = Fork::test(
1179                url,
1180                None,
1181                None,
1182                Some(1),
1183                block,
1184                ForkContext { network_profile: profile, ..context(1) },
1185            );
1186            let id = ForkId::resolved(url, &candidate);
1187            assert_eq!(id, ForkId::resolved(url, &cached));
1188            assert_eq!(candidate.fingerprint(), cached.fingerprint());
1189            let (cached, cached_handler) = create(cached);
1190            let cached_db = cached.backend.data();
1191            let bal = vec![AccountChanges::new(address).with_storage_change(SlotChanges::new(
1192                U256::ONE,
1193                vec![StorageChange::new(BlockAccessIndex::new(1), U256::from(42))],
1194            ))];
1195            let future = futures::future::ready(Ok((
1196                id.clone(),
1197                cached.opts.clone(),
1198                candidate,
1199                cached.evm_env.clone(),
1200                Some(bal),
1201            )));
1202            let (_incoming, receiver) = channel(1);
1203            let mut manager = MultiForkHandler::<AnyNetwork, SpecId, BlockEnv>::new(receiver);
1204            manager.forks.insert(id.clone(), cached);
1205            manager.handlers.push((id.clone(), cached_handler));
1206            let (sender, receiver) = oneshot_channel();
1207            manager.pending_tasks.push(ForkTask::Create {
1208                future: Box::pin(future),
1209                id: id.clone(),
1210                prewarm_bal: true,
1211                no_fork_bal: false,
1212                sender,
1213                additional_senders: Vec::new(),
1214            });
1215
1216            assert!(manager.poll_unpin(&mut Context::from_waker(noop_waker_ref())).is_pending());
1217            let result = receiver.try_recv().unwrap().unwrap();
1218            assert!(Arc::ptr_eq(&result.backend.data(), &cached_db));
1219            // Reusing the cached backend creates no second backend.
1220            assert_eq!(manager.handlers.len(), 1);
1221            assert_eq!(
1222                cached_db
1223                    .storage
1224                    .read()
1225                    .get(&address)
1226                    .and_then(|slots| slots.get(&U256::ONE))
1227                    .copied(),
1228                Some(U256::from(42)),
1229            );
1230            assert!(manager.forks[&id].bal_prewarmed.load(Ordering::Relaxed));
1231        }
1232    }
1233
1234    #[tokio::test(flavor = "multi_thread")]
1235    async fn fork_bal_reuses_prewarmed_cache_only_for_equivalent_identities() {
1236        let (api, handle) = anvil::spawn(
1237            anvil::NodeConfig::test()
1238                .with_chain_id(Some(1u64))
1239                .with_hardfork(Some(anvil::EthereumHardfork::Amsterdam.into()))
1240                .with_genesis_timestamp(Some(1_800_000_000u64))
1241                .with_no_mining(true),
1242        )
1243        .await;
1244        let address = Address::with_last_byte(0x42);
1245        api.anvil_set_code(address, bytes!("602a60015500")).await.unwrap();
1246        api.send_transaction(WithOtherFields::new(
1247            TransactionRequest::default()
1248                .with_from(handle.dev_accounts().next().unwrap())
1249                .with_to(address)
1250                .with_nonce(0)
1251                // Enough for the EIP-8037 state gas of creating slot one.
1252                .with_gas_limit(1_000_000)
1253                .with_gas_price(2_000_000_000),
1254        ))
1255        .await
1256        .unwrap();
1257        api.mine_one().await.unwrap();
1258        let block = handle.http_provider().get_block_by_number(1.into()).await.unwrap().unwrap();
1259        let block = BlockNumHash::new(1, block.header.hash);
1260        // Expose native BALs through an endpoint without mutable Anvil identity.
1261        let endpoint = spawn_rpc_proxy_method_not_found_before(
1262            handle.http_endpoint(),
1263            "anvil_nodeInfo",
1264            usize::MAX,
1265        )
1266        .await;
1267        let (endpoint, bal_requests) =
1268            spawn_rpc_proxy_recording_method(endpoint, "eth_getBlockAccessList").await;
1269        let (endpoint, probes) = spawn_rpc_proxy_recording_method(endpoint, "anvil_nodeInfo").await;
1270        let (_incoming, receiver) = channel(1);
1271        let mut manager = MultiForkHandler::<AnyNetwork, SpecId, BlockEnv>::new(receiver);
1272        let mut first = None;
1273        let mut probe_counts = Vec::new();
1274        let other_headers = ["X-Test-Source: other".to_string()];
1275
1276        for (profile, headers) in [
1277            (NetworkConfigs::default(), None),
1278            (NetworkConfigs::with_ethereum(), None),
1279            (NetworkConfigs::default(), None),
1280            (NetworkConfigs::default(), Some(other_headers.as_slice())),
1281        ] {
1282            let evm_opts = EvmOpts {
1283                fork_url: Some(endpoint.clone()),
1284                fork_block_number: Some(1),
1285                fork_headers: headers.map(<[_]>::to_vec),
1286                networks: profile,
1287                ..Default::default()
1288            };
1289            let resolved = evm_opts.prepare_fork().await.unwrap().unwrap();
1290            assert_eq!(resolved.block(), block);
1291            let fingerprint = resolved.fingerprint();
1292            let fork = CreateFork { enable_caching: false, url: endpoint.clone(), evm_opts };
1293            let before = probes.lock().unwrap().len();
1294            let (sender, receiver) = oneshot_channel();
1295            manager.create_fork_with_identity(
1296                fork,
1297                None,
1298                true,
1299                Some((resolved.clone(), resolved.block())),
1300                sender,
1301            );
1302            let result = tokio::time::timeout(
1303                Duration::from_secs(10),
1304                futures::future::poll_fn(|cx| {
1305                    assert!(manager.poll_unpin(cx).is_pending());
1306                    match receiver.try_recv() {
1307                        Ok(result) => Poll::Ready(result),
1308                        Err(std::sync::mpsc::TryRecvError::Empty) => Poll::Pending,
1309                        Err(error) => panic!("fork response channel closed: {error}"),
1310                    }
1311                }),
1312            )
1313            .await
1314            .unwrap()
1315            .unwrap();
1316
1317            let db = result.backend.data();
1318            assert_eq!(
1319                Arc::ptr_eq(first.get_or_insert_with(|| db.clone()), &db),
1320                headers.is_none(),
1321            );
1322            assert_eq!(result.fork.fingerprint(), fingerprint);
1323            assert_eq!(db.storage.read()[&address][&U256::ONE], U256::from(42));
1324            assert!(manager.forks[&result.id].bal_prewarmed.load(Ordering::Relaxed));
1325            assert_eq!(bal_requests.lock().unwrap().len(), if headers.is_some() { 2 } else { 1 });
1326            probe_counts.push(probes.lock().unwrap().len() - before);
1327        }
1328
1329        // Only cold caches need the two source probes surrounding BAL preparation.
1330        assert_eq!(probe_counts[0], probe_counts[1] + 2);
1331        assert_eq!(probe_counts[1], probe_counts[2]);
1332        assert_eq!(probe_counts[0], probe_counts[3]);
1333    }
1334
1335    #[tokio::test(flavor = "multi_thread")]
1336    async fn fork_bal_shared_backend_preserves_opposite_policies_on_roll() {
1337        let (api, handle) = anvil::spawn(
1338            anvil::NodeConfig::test()
1339                .with_chain_id(Some(1u64))
1340                .with_hardfork(Some(anvil::EthereumHardfork::Amsterdam.into()))
1341                .with_genesis_timestamp(Some(1_800_000_000u64))
1342                .with_no_mining(true),
1343        )
1344        .await;
1345        let mut blocks = Vec::new();
1346        for number in 1..=3 {
1347            api.mine_one().await.unwrap();
1348            let block =
1349                handle.http_provider().get_block_by_number(number.into()).await.unwrap().unwrap();
1350            blocks.push(BlockNumHash::new(number, block.header.hash));
1351        }
1352        // Expose native BALs through an endpoint without mutable Anvil identity.
1353        let endpoint = spawn_rpc_proxy_method_not_found_before(
1354            handle.http_endpoint(),
1355            "anvil_nodeInfo",
1356            usize::MAX,
1357        )
1358        .await;
1359        let (endpoint, bal_requests) =
1360            spawn_rpc_proxy_recording_method(endpoint, "eth_getBlockAccessList").await;
1361        let resolved = EvmOpts {
1362            fork_url: Some(endpoint.clone()),
1363            fork_block_number: Some(1),
1364            ..Default::default()
1365        }
1366        .prepare_fork()
1367        .await
1368        .unwrap()
1369        .unwrap();
1370        assert_eq!(resolved.block(), blocks[0]);
1371
1372        for disabled_first in [false, true] {
1373            let (_incoming, receiver) = channel(1);
1374            let mut manager = MultiForkHandler::<AnyNetwork, SpecId, BlockEnv>::new(receiver);
1375            bal_requests.lock().unwrap().clear();
1376            let mut requests = [false, true].map(|no_fork_bal| {
1377                let fork = CreateFork {
1378                    enable_caching: false,
1379                    url: endpoint.clone(),
1380                    evm_opts: EvmOpts {
1381                        fork_url: Some(endpoint.clone()),
1382                        fork_block_number: Some(1),
1383                        no_fork_bal,
1384                        ..Default::default()
1385                    },
1386                };
1387                let (sender, receiver) = oneshot_channel();
1388                manager.create_fork(fork, sender);
1389                let ForkTask::Create { future, .. } = manager.pending_tasks.last_mut().unwrap();
1390                let original = std::mem::replace(future, Box::pin(futures::future::pending()));
1391                let (release, gate) = oneshot::channel();
1392                // Run real creation, but explicitly control which result reaches backend reuse.
1393                *future = Box::pin(async move {
1394                    let result = original.await;
1395                    gate.await.unwrap();
1396                    result
1397                });
1398                (no_fork_bal, release, receiver)
1399            });
1400            assert_eq!(manager.pending_tasks.len(), 2);
1401            if disabled_first {
1402                requests.reverse();
1403            }
1404            let [
1405                (first_policy, first_gate, first_receiver),
1406                (second_policy, second_gate, second_receiver),
1407            ] = requests;
1408            first_gate.send(()).unwrap();
1409            let first = complete_fork(&mut manager, first_receiver).await;
1410            assert!(matches!(second_receiver.try_recv(), Err(TryRecvError::Empty)));
1411            assert_eq!(manager.pending_tasks.len(), 1);
1412            second_gate.send(()).unwrap();
1413            let second = complete_fork(&mut manager, second_receiver).await;
1414
1415            assert_ne!(first.id, second.id);
1416            assert!(Arc::ptr_eq(&first.backend.data(), &second.backend.data()));
1417            assert!(Arc::ptr_eq(first.fork.client.inner(), second.fork.client.inner()));
1418            assert!(Arc::ptr_eq(&first.fork.block, &second.fork.block));
1419            assert_eq!(manager.forks[&first.id].opts.evm_opts.no_fork_bal, first_policy);
1420            assert_eq!(manager.forks[&second.id].opts.evm_opts.no_fork_bal, second_policy);
1421            assert!(bal_requests.lock().unwrap().is_empty());
1422
1423            // Each roll targets a cold block so shared prewarming cannot mask a lost policy.
1424            for (no_fork_bal, fork, block) in
1425                [(first_policy, first, blocks[1]), (second_policy, second, blocks[2])]
1426            {
1427                bal_requests.lock().unwrap().clear();
1428                let (sender, receiver) = oneshot_channel();
1429                manager.on_request(Request::RollForkExact(fork.id, block, true, sender));
1430                let rolled = complete_fork(&mut manager, receiver).await;
1431                assert_eq!(rolled.fork.block(), block);
1432                assert_eq!(manager.forks[&rolled.id].opts.evm_opts.no_fork_bal, no_fork_bal);
1433                assert_eq!(
1434                    manager.forks[&rolled.id].bal_prewarmed.load(Ordering::Relaxed),
1435                    !no_fork_bal,
1436                );
1437                let expected =
1438                    if no_fork_bal { vec![] } else { vec![serde_json::json!([block.hash])] };
1439                assert_eq!(*bal_requests.lock().unwrap(), expected);
1440            }
1441        }
1442    }
1443
1444    async fn complete_fork(
1445        manager: &mut MultiForkHandler<AnyNetwork, SpecId, BlockEnv>,
1446        receiver: OneshotReceiver<eyre::Result<ForkResult<AnyNetwork, SpecId, BlockEnv>>>,
1447    ) -> ForkResult<AnyNetwork, SpecId, BlockEnv> {
1448        tokio::time::timeout(
1449            Duration::from_secs(10),
1450            futures::future::poll_fn(|cx| {
1451                assert!(manager.poll_unpin(cx).is_pending());
1452                match receiver.try_recv() {
1453                    Ok(result) => Poll::Ready(result),
1454                    Err(TryRecvError::Empty) => Poll::Pending,
1455                    Err(error) => panic!("fork response channel closed: {error}"),
1456                }
1457            }),
1458        )
1459        .await
1460        .unwrap()
1461        .unwrap()
1462    }
1463}