1use 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#[derive(Clone, Debug, PartialEq, Eq, Hash)]
37pub struct ForkId(pub String);
38
39impl ForkId {
40 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 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 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 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
108pub struct ForkResult<N: Network, SPEC, BLOCK: ForkBlockEnv> {
110 pub id: ForkId,
112 pub backend: SharedBackend<N, BLOCK>,
114 pub env: EvmEnv<SPEC, BLOCK>,
116 pub fork: Fork,
118}
119
120#[derive(Clone, Debug)]
123#[must_use]
124pub struct MultiFork<N: Network, SPEC, BLOCK: ForkBlockEnv> {
125 handler: Sender<Request<N, SPEC, BLOCK>>,
127 _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 pub fn spawn() -> Self {
139 Self::spawn_with_forks(HashMap::default(), None)
140 }
141
142 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 let fut = async move {
164 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 #[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 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 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 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 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 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 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 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 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 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#[derive(Debug)]
318enum Request<N: Network, SPEC, BLOCK: ForkBlockEnv> {
319 CloneForks(OneshotSender<HashMap<ForkId, CreatedFork<N, SPEC, BLOCK>>>),
320 CreateFork(Box<CreateFork>, CreateSender<N, SPEC, BLOCK>),
322 GetFork(ForkId, OneshotSender<Option<SharedBackend<N, BLOCK>>>),
324 RollFork(ForkId, u64, CreateSender<N, SPEC, BLOCK>),
326 RollForkExact(ForkId, BlockNumHash, bool, CreateSender<N, SPEC, BLOCK>),
328 GetEvmEnv(ForkId, GetEvmEnvSender<SPEC, BLOCK>),
330 ShutDown(OneshotSender<()>),
332 GetForkUrl(ForkId, OneshotSender<Option<String>>),
334 GetForkInfo(ForkId, OneshotSender<Option<Fork>>),
335 GetForkOptions(ForkId, OneshotSender<Option<CreateFork>>),
337}
338
339enum ForkTask<N: Network, SPEC, BLOCK: ForkBlockEnv> {
340 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#[must_use = "futures do nothing unless polled"]
353pub struct MultiForkHandler<N: Network, SPEC, BLOCK: ForkBlockEnv> {
354 incoming: Fuse<Receiver<Request<N, SPEC, BLOCK>>>,
356
357 handlers: Vec<(ForkId, BackendHandler<N, BLOCK>)>,
361
362 pending_tasks: Vec<ForkTask<N, SPEC, BLOCK>>,
364
365 forks: HashMap<ForkId, CreatedFork<N, SPEC, BLOCK>>,
370
371 flush_cache_interval: Option<tokio::time::Interval>,
373
374 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 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 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 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 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 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 self.forks.clear();
552 self.handlers.clear();
553 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
575impl<
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 loop {
590 match this.incoming.poll_next_unpin(cx) {
591 Poll::Ready(Some(req)) => this.on_request(req),
592 Poll::Ready(None) => {
593 trace!(target: "fork::multi", "request channel closed");
595 break;
596 }
597 Poll::Pending => break,
598 }
599 }
600
601 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 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 let (backend, handler) =
639 create_backend(&opts, &resolved, &evm_env.block_env)?;
640 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 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 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 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 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 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#[derive(Debug, Clone)]
740struct CreatedFork<N: Network, SPEC, BLOCK: ForkBlockEnv> {
741 opts: CreateFork,
743 fork: Fork,
745 evm_env: EvmEnv<SPEC, BLOCK>,
747 backend: SharedBackend<N, BLOCK>,
749 num_senders: Arc<AtomicUsize>,
752 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 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#[derive(Debug)]
791struct ShutDownMultiFork<N: Network, SPEC, BLOCK: ForkBlockEnv> {
792 handler: Option<Sender<Request<N, SPEC, BLOCK>>>,
793 _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
811async 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 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 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 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
898fn 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 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 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 .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 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 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 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 *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 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}