1use super::{CreateFork, ResolvedFork};
7use crate::{FoundryBlock, opts::ForkContext};
8use alloy_eips::BlockNumHash;
9use alloy_evm::EvmEnv;
10use alloy_network::{AnyNetwork, Network};
11use alloy_primitives::{U256, 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::AtomicUsize,
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: &ResolvedFork) -> Self {
76 let mut id = Self::new_with_context(url, Some(fork.number()), Some(&fork.context())).0;
77 write!(id, "#{}:{}", fork.hash(), fork.source_id()).unwrap();
78 Self(id)
79 }
80
81 pub fn as_str(&self) -> &str {
83 &self.0
84 }
85}
86
87impl fmt::Display for ForkId {
88 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
89 self.0.fmt(f)
90 }
91}
92
93impl<T: Into<String>> From<T> for ForkId {
94 fn from(id: T) -> Self {
95 Self(id.into())
96 }
97}
98
99pub struct ForkResult<N: Network, SPEC, BLOCK: ForkBlockEnv> {
101 pub id: ForkId,
103 pub backend: SharedBackend<N, BLOCK>,
105 pub env: EvmEnv<SPEC, BLOCK>,
107 pub resolved: ResolvedFork,
109}
110
111#[derive(Clone, Debug)]
114#[must_use]
115pub struct MultiFork<N: Network, SPEC, BLOCK: ForkBlockEnv> {
116 handler: Sender<Request<N, SPEC, BLOCK>>,
118 _shutdown: Arc<ShutDownMultiFork<N, SPEC, BLOCK>>,
120}
121
122impl<
123 N: Network,
124 SPEC: Into<SpecId> + Default + Copy + Unpin + Send + 'static,
125 BLOCK: FoundryBlock + ForkBlockEnv + Default + Unpin,
126> MultiFork<N, SPEC, BLOCK>
127{
128 pub fn spawn() -> Self {
130 trace!(target: "fork::multi", "spawning multifork");
131
132 let (fork, mut handler) = Self::new();
133
134 let fut = async move {
137 handler.set_flush_cache_interval(Duration::from_secs(60));
142 handler.await
143 };
144 match tokio::runtime::Handle::try_current() {
145 Ok(rt) => _ = rt.spawn(fut),
146 Err(_) => {
147 trace!(target: "fork::multi", "spawning multifork backend thread");
148 _ = std::thread::Builder::new()
149 .name("multi-fork-backend".into())
150 .spawn(move || {
151 tokio::runtime::Builder::new_current_thread()
152 .enable_all()
153 .build()
154 .expect("failed to build tokio runtime")
155 .block_on(fut)
156 })
157 .expect("failed to spawn thread")
158 }
159 }
160
161 trace!(target: "fork::multi", "spawned MultiForkHandler thread");
162 fork
163 }
164
165 #[doc(hidden)]
169 pub fn new() -> (Self, MultiForkHandler<N, SPEC, BLOCK>) {
170 let (handler, handler_rx) = channel(1);
171 let _shutdown = Arc::new(ShutDownMultiFork { handler: Some(handler.clone()) });
172 (Self { handler, _shutdown }, MultiForkHandler::new(handler_rx))
173 }
174
175 pub fn create_fork(&self, fork: CreateFork) -> eyre::Result<ForkResult<N, SPEC, BLOCK>> {
179 trace!("Creating new fork, url={}, block={:?}", fork.url, fork.evm_opts.fork_block_number);
180 let (sender, rx) = oneshot_channel();
181 let req = Request::CreateFork(Box::new(fork), sender);
182 self.handler.clone().try_send(req).map_err(|e| eyre::eyre!("{:?}", e))?;
183 rx.recv()?
184 }
185
186 pub fn roll_fork(&self, fork: ForkId, block: u64) -> eyre::Result<ForkResult<N, SPEC, BLOCK>> {
190 trace!(?fork, ?block, "rolling fork");
191 let (sender, rx) = oneshot_channel();
192 let req = Request::RollFork(fork, block, sender);
193 self.handler.clone().try_send(req).map_err(|e| eyre::eyre!("{:?}", e))?;
194 rx.recv()?
195 }
196
197 pub fn roll_fork_exact(
199 &self,
200 fork: ForkId,
201 block: BlockNumHash,
202 ) -> eyre::Result<ForkResult<N, SPEC, BLOCK>> {
203 trace!(?fork, ?block, "rolling fork to exact block");
204 let (sender, rx) = oneshot_channel();
205 let req = Request::RollForkExact(fork, block, sender);
206 self.handler.clone().try_send(req).map_err(|e| eyre::eyre!("{:?}", e))?;
207 rx.recv()?
208 }
209
210 pub fn get_evm_env(&self, fork: ForkId) -> eyre::Result<Option<EvmEnv<SPEC, BLOCK>>> {
212 trace!(?fork, "getting env config");
213 let (sender, rx) = oneshot_channel();
214 let req = Request::GetEvmEnv(fork, sender);
215 self.handler.clone().try_send(req).map_err(|e| eyre::eyre!("{:?}", e))?;
216 Ok(rx.recv()?)
217 }
218
219 pub fn update_block(&self, fork: ForkId, number: U256, timestamp: U256) -> eyre::Result<()> {
221 trace!(?fork, ?number, ?timestamp, "update fork block");
222 self.handler
223 .clone()
224 .try_send(Request::UpdateBlock(fork, number, timestamp))
225 .map_err(|e| eyre::eyre!("{:?}", e))
226 }
227
228 pub fn update_block_env(&self, fork: ForkId, env: BLOCK) -> eyre::Result<()>
233 where
234 BLOCK: fmt::Debug,
235 {
236 trace!(?fork, ?env, "update fork block");
237 self.handler
238 .clone()
239 .try_send(Request::UpdateEnv(fork, env))
240 .map_err(|e| eyre::eyre!("{:?}", e))
241 }
242
243 pub fn get_fork(&self, id: impl Into<ForkId>) -> eyre::Result<Option<SharedBackend<N, BLOCK>>> {
247 let id = id.into();
248 trace!(?id, "get fork backend");
249 let (sender, rx) = oneshot_channel();
250 let req = Request::GetFork(id, sender);
251 self.handler.clone().try_send(req).map_err(|e| eyre::eyre!("{:?}", e))?;
252 Ok(rx.recv()?)
253 }
254
255 pub fn get_fork_url(&self, id: impl Into<ForkId>) -> eyre::Result<Option<String>> {
259 let (sender, rx) = oneshot_channel();
260 let req = Request::GetForkUrl(id.into(), sender);
261 self.handler.clone().try_send(req).map_err(|e| eyre::eyre!("{:?}", e))?;
262 Ok(rx.recv()?)
263 }
264}
265
266type CreateFuture<N, SPEC, BLOCK> = Pin<
267 Box<
268 dyn Future<
269 Output = eyre::Result<(
270 ForkId,
271 CreatedFork<N, SPEC, BLOCK>,
272 BackendHandler<N, BLOCK>,
273 )>,
274 > + Send,
275 >,
276>;
277type CreateSender<N, SPEC, BLOCK> = OneshotSender<eyre::Result<ForkResult<N, SPEC, BLOCK>>>;
278type GetEvmEnvSender<SPEC, BLOCK> = OneshotSender<Option<EvmEnv<SPEC, BLOCK>>>;
279
280#[derive(Debug)]
282enum Request<N: Network, SPEC, BLOCK: ForkBlockEnv> {
283 CreateFork(Box<CreateFork>, CreateSender<N, SPEC, BLOCK>),
285 GetFork(ForkId, OneshotSender<Option<SharedBackend<N, BLOCK>>>),
287 RollFork(ForkId, u64, CreateSender<N, SPEC, BLOCK>),
289 RollForkExact(ForkId, BlockNumHash, CreateSender<N, SPEC, BLOCK>),
291 GetEvmEnv(ForkId, GetEvmEnvSender<SPEC, BLOCK>),
293 UpdateBlock(ForkId, U256, U256),
295 UpdateEnv(ForkId, BLOCK),
297 ShutDown(OneshotSender<()>),
299 GetForkUrl(ForkId, OneshotSender<Option<String>>),
301}
302
303enum ForkTask<N: Network, SPEC, BLOCK: ForkBlockEnv> {
304 Create(
306 CreateFuture<N, SPEC, BLOCK>,
307 ForkId,
308 CreateSender<N, SPEC, BLOCK>,
309 Vec<CreateSender<N, SPEC, BLOCK>>,
310 ),
311}
312
313#[must_use = "futures do nothing unless polled"]
315pub struct MultiForkHandler<N: Network, SPEC, BLOCK: ForkBlockEnv> {
316 incoming: Fuse<Receiver<Request<N, SPEC, BLOCK>>>,
318
319 handlers: Vec<(ForkId, BackendHandler<N, BLOCK>)>,
323
324 pending_tasks: Vec<ForkTask<N, SPEC, BLOCK>>,
326
327 forks: HashMap<ForkId, CreatedFork<N, SPEC, BLOCK>>,
332
333 flush_cache_interval: Option<tokio::time::Interval>,
335}
336
337impl<
338 N: Network,
339 SPEC: Into<SpecId> + Default + Copy + 'static,
340 BLOCK: FoundryBlock + ForkBlockEnv + Default,
341> MultiForkHandler<N, SPEC, BLOCK>
342{
343 fn new(incoming: Receiver<Request<N, SPEC, BLOCK>>) -> Self {
344 Self {
345 incoming: incoming.fuse(),
346 handlers: Default::default(),
347 pending_tasks: Default::default(),
348 forks: Default::default(),
349 flush_cache_interval: None,
350 }
351 }
352
353 pub fn set_flush_cache_interval(&mut self, period: Duration) -> &mut Self {
355 self.flush_cache_interval =
356 Some(tokio::time::interval_at(tokio::time::Instant::now() + period, period));
357 self
358 }
359
360 fn find_in_progress_task(
362 &mut self,
363 id: &ForkId,
364 ) -> Option<&mut Vec<CreateSender<N, SPEC, BLOCK>>> {
365 for ForkTask::Create(_, in_progress, _, additional) in &mut self.pending_tasks {
366 if in_progress == id {
367 return Some(additional);
368 }
369 }
370 None
371 }
372
373 fn create_fork(&mut self, fork: CreateFork, sender: CreateSender<N, SPEC, BLOCK>) {
374 self.create_fork_with_identity(fork, None, sender);
375 }
376
377 fn create_fork_with_identity(
378 &mut self,
379 fork: CreateFork,
380 expected_identity: Option<ForkContext>,
381 sender: CreateSender<N, SPEC, BLOCK>,
382 ) {
383 let resolved_id =
384 fork.resolved.as_ref().map(|resolved| ForkId::resolved(&fork.url, resolved));
385 trace!(?resolved_id, "creating fork");
386
387 if let Some(fork_id) = &resolved_id
390 && let Some(in_progress) = self.find_in_progress_task(fork_id)
391 {
392 in_progress.push(sender);
393 return;
394 }
395
396 let task_id =
398 resolved_id.unwrap_or_else(|| ForkId::new(&fork.url, fork.evm_opts.fork_block_number));
399 let task = Box::pin(create_fork(fork, expected_identity));
400 self.pending_tasks.push(ForkTask::Create(task, task_id, sender, Vec::new()));
401 }
402
403 fn insert_new_fork(
404 &mut self,
405 fork_id: ForkId,
406 fork: CreatedFork<N, SPEC, BLOCK>,
407 sender: CreateSender<N, SPEC, BLOCK>,
408 additional_senders: Vec<CreateSender<N, SPEC, BLOCK>>,
409 ) {
410 self.forks.insert(fork_id.clone(), fork.clone());
411 let resolved = fork
412 .opts
413 .resolved
414 .as_ref()
415 .expect("created forks always retain their resolved identity")
416 .clone();
417 let _ = sender.send(Ok(ForkResult {
418 id: fork_id.clone(),
419 backend: fork.backend.clone(),
420 env: fork.evm_env.clone(),
421 resolved: resolved.clone(),
422 }));
423
424 for sender in additional_senders {
426 let next_fork_id = fork.inc_senders(fork_id.clone());
427 self.forks.insert(next_fork_id.clone(), fork.clone());
428 let _ = sender.send(Ok(ForkResult {
429 id: next_fork_id,
430 backend: fork.backend.clone(),
431 env: fork.evm_env.clone(),
432 resolved: resolved.clone(),
433 }));
434 }
435 }
436
437 fn update_env(&mut self, fork_id: ForkId, env: BLOCK) {
439 if let Some(fork) = self.forks.get_mut(&fork_id) {
440 fork.evm_env.block_env = env;
441 }
442 }
443 fn update_block(&mut self, fork_id: ForkId, block_number: U256, block_timestamp: U256) {
446 if let Some(fork) = self.forks.get_mut(&fork_id) {
447 fork.evm_env.block_env.set_number(block_number);
448 fork.evm_env.block_env.set_timestamp(block_timestamp);
449 }
450 }
451
452 fn on_request(&mut self, req: Request<N, SPEC, BLOCK>) {
453 match req {
454 Request::CreateFork(fork, sender) => self.create_fork(*fork, sender),
455 Request::GetFork(fork_id, sender) => {
456 let fork = self.forks.get(&fork_id).map(|f| f.backend.clone());
457 let _ = sender.send(fork);
458 }
459 Request::RollFork(fork_id, block, sender) => {
460 if let Some(fork) = self.forks.get(&fork_id) {
461 trace!(target: "fork::multi", "rolling {} to {}", fork_id, block);
462 let expected_identity = fork.opts.resolved.as_ref().map(ResolvedFork::context);
463 let mut opts = fork.opts.clone();
464 opts.evm_opts.fork_block_number = Some(block);
465 opts.evm_opts.fork_block_number_is_inferred = false;
466 opts.resolved = None;
467 self.create_fork_with_identity(opts, expected_identity, sender)
468 } else {
469 let _ =
470 sender.send(Err(eyre::eyre!("No matching fork exists for {}", fork_id)));
471 }
472 }
473 Request::RollForkExact(fork_id, block, sender) => {
474 if let Some(fork) = self.forks.get(&fork_id) {
475 trace!(target: "fork::multi", "rolling {} to exact block {:?}", fork_id, block);
476 let mut opts = fork.opts.clone();
477 opts.evm_opts.fork_block_number = Some(block.number);
478 opts.evm_opts.fork_block_number_is_inferred = false;
479 opts.resolved = Some(
480 opts.resolved
481 .as_ref()
482 .expect("an exact roll requires an existing resolved fork")
483 .at_block(block),
484 );
485 self.create_fork(opts, sender)
486 } else {
487 let _ =
488 sender.send(Err(eyre::eyre!("No matching fork exists for {}", fork_id)));
489 }
490 }
491 Request::GetEvmEnv(fork_id, sender) => {
492 let _ = sender.send(self.forks.get(&fork_id).map(|fork| fork.evm_env.clone()));
493 }
494 Request::UpdateBlock(fork_id, block_number, block_timestamp) => {
495 self.update_block(fork_id, block_number, block_timestamp);
496 }
497 Request::UpdateEnv(fork_id, block_env) => {
498 self.update_env(fork_id, block_env);
499 }
500 Request::ShutDown(sender) => {
501 trace!(target: "fork::multi", "received shutdown signal");
502 self.forks.clear();
504 self.handlers.clear();
505 let _ = sender.send(());
506 }
507 Request::GetForkUrl(fork_id, sender) => {
508 let fork = self.forks.get(&fork_id).map(|f| f.opts.url.clone());
509 let _ = sender.send(fork);
510 }
511 }
512 }
513}
514
515impl<
518 N: Network,
519 SPEC: Into<SpecId> + Default + Copy + Unpin + 'static,
520 BLOCK: FoundryBlock + ForkBlockEnv + Default + Unpin,
521> Future for MultiForkHandler<N, SPEC, BLOCK>
522{
523 type Output = ();
524
525 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
526 let this = self.get_mut();
527
528 loop {
530 match this.incoming.poll_next_unpin(cx) {
531 Poll::Ready(Some(req)) => this.on_request(req),
532 Poll::Ready(None) => {
533 trace!(target: "fork::multi", "request channel closed");
535 break;
536 }
537 Poll::Pending => break,
538 }
539 }
540
541 for n in (0..this.pending_tasks.len()).rev() {
543 let task = this.pending_tasks.swap_remove(n);
544 match task {
545 ForkTask::Create(mut fut, id, sender, additional_senders) => {
546 if let Poll::Ready(resp) = fut.poll_unpin(cx) {
547 match resp {
548 Ok((fork_id, fork, handler)) => {
549 if let Some(fork) = this.forks.get(&fork_id).cloned() {
550 this.insert_new_fork(
551 fork.inc_senders(fork_id),
552 fork,
553 sender,
554 additional_senders,
555 );
556 } else {
557 this.handlers.push((fork_id.clone(), handler));
558 this.insert_new_fork(fork_id, fork, sender, additional_senders);
559 }
560 }
561 Err(err) => {
562 let _ = sender.send(Err(eyre::eyre!("{err}")));
563 for sender in additional_senders {
564 let _ = sender.send(Err(eyre::eyre!("{err}")));
565 }
566 }
567 }
568 } else {
569 this.pending_tasks.push(ForkTask::Create(
570 fut,
571 id,
572 sender,
573 additional_senders,
574 ));
575 }
576 }
577 }
578 }
579
580 for n in (0..this.handlers.len()).rev() {
582 let (id, mut handler) = this.handlers.swap_remove(n);
583 match handler.poll_unpin(cx) {
584 Poll::Ready(_) => {
585 trace!(target: "fork::multi", "fork {:?} completed", id);
586 }
587 Poll::Pending => {
588 this.handlers.push((id, handler));
589 }
590 }
591 }
592
593 if this.handlers.is_empty() && this.incoming.is_done() {
594 trace!(target: "fork::multi", "completed");
595 return Poll::Ready(());
596 }
597
598 if this
600 .flush_cache_interval
601 .as_mut()
602 .map(|interval| interval.poll_tick(cx).is_ready())
603 .unwrap_or_default()
604 && !this.forks.is_empty()
605 {
606 trace!(target: "fork::multi", "tick flushing caches");
607 let forks = this.forks.values().map(|f| f.backend.clone()).collect::<Vec<_>>();
608 std::thread::Builder::new()
610 .name("flusher".into())
611 .spawn(move || {
612 for fork in forks {
613 fork.flush_cache();
614 }
615 })
616 .expect("failed to spawn thread");
617 }
618
619 Poll::Pending
620 }
621}
622
623#[derive(Debug, Clone)]
625struct CreatedFork<N: Network, SPEC, BLOCK: ForkBlockEnv> {
626 opts: CreateFork,
628 evm_env: EvmEnv<SPEC, BLOCK>,
630 backend: SharedBackend<N, BLOCK>,
632 num_senders: Arc<AtomicUsize>,
635}
636
637impl<N: Network, SPEC, BLOCK: ForkBlockEnv> CreatedFork<N, SPEC, BLOCK> {
638 pub fn new(
639 opts: CreateFork,
640 evm_env: EvmEnv<SPEC, BLOCK>,
641 backend: SharedBackend<N, BLOCK>,
642 ) -> Self {
643 Self { opts, evm_env, backend, num_senders: Arc::new(AtomicUsize::new(1)) }
644 }
645
646 fn inc_senders(&self, fork_id: ForkId) -> ForkId {
648 format!(
649 "{}-{}",
650 fork_id.as_str(),
651 self.num_senders.fetch_add(1, std::sync::atomic::Ordering::Relaxed)
652 )
653 .into()
654 }
655}
656
657#[derive(Debug)]
664struct ShutDownMultiFork<N: Network, SPEC, BLOCK: ForkBlockEnv> {
665 handler: Option<Sender<Request<N, SPEC, BLOCK>>>,
666}
667
668impl<N: Network, SPEC, BLOCK: ForkBlockEnv> Drop for ShutDownMultiFork<N, SPEC, BLOCK> {
669 fn drop(&mut self) {
670 trace!(target: "fork::multi", "initiating shutdown");
671 let (sender, rx) = oneshot_channel();
672 let req = Request::ShutDown(sender);
673 if let Some(mut handler) = self.handler.take()
674 && handler.try_send(req).is_ok()
675 {
676 let _ = rx.recv();
677 trace!(target: "fork::cache", "multifork backend shutdown");
678 }
679 }
680}
681
682async fn create_fork<
686 N: Network,
687 SPEC: Into<SpecId> + Default + Copy,
688 BLOCK: FoundryBlock + ForkBlockEnv + Default,
689>(
690 mut fork: CreateFork,
691 expected_identity: Option<ForkContext>,
692) -> eyre::Result<(ForkId, CreatedFork<N, SPEC, BLOCK>, BackendHandler<N, BLOCK>)> {
693 let execution_networks = fork.evm_opts.networks;
696 let require_endpoint_family_match =
697 fork.evm_opts.fork_network_is_inferred || !execution_networks.has_network_selection();
698 let targets_new_endpoint =
699 fork.evm_opts.fork_url.as_ref().is_some_and(|endpoint| endpoint != &fork.url)
700 || fork
701 .evm_opts
702 .fork_endpoint
703 .as_ref()
704 .is_some_and(|identity| identity.endpoint != fork.url);
705 if targets_new_endpoint {
706 fork.evm_opts.fork_endpoint = None;
710 fork.evm_opts.expected_fork_endpoint = None;
711 fork.evm_opts.fork_network_is_inferred = false;
712 if fork.evm_opts.fork_chain_id_is_inferred {
713 fork.evm_opts.env.chain_id = None;
714 fork.evm_opts.fork_chain_id_is_inferred = false;
715 }
716 if fork.evm_opts.fork_block_number_is_inferred {
717 fork.evm_opts.fork_block_number = None;
718 fork.evm_opts.fork_block_number_is_inferred = false;
719 }
720 }
721 fork.evm_opts.fork_url = Some(fork.url.clone());
722
723 let any_provider = fork.evm_opts.fork_provider_with_url::<AnyNetwork>(&fork.url)?;
727 let (evm_env, resolved) = if let Some(resolved) = fork.resolved.clone() {
728 let evm_env = fork
729 .evm_opts
730 .fork_evm_env_at_resolved::<_, BLOCK, _, _>(&any_provider, &resolved)
731 .await?;
732 (evm_env, resolved)
733 } else {
734 let (evm_env, resolved) =
735 fork.evm_opts.fork_evm_env_resolved::<_, BLOCK, _, _>(&any_provider).await?;
736 (evm_env, resolved)
737 };
738 let fork_context = resolved.context();
739 if require_endpoint_family_match
740 && !execution_networks.supports_fork_source(&fork_context.network_profile)
741 {
742 eyre::bail!(
743 "cannot create a `{}` fork with an EVM instantiated for `{}`",
744 fork_context.network,
745 execution_networks.execution_network()
746 );
747 }
748 if let Some(expected) = expected_identity {
749 eyre::ensure!(
750 fork_context.has_same_endpoint_identity(expected),
751 "fork endpoint identity changed while the fork was being rolled"
752 );
753 }
754 let number = resolved.number();
755 let meta = BlockchainDbMeta::new(evm_env.block_env.clone(), fork.url.clone())
756 .with_fork_identity(resolved.hash(), resolved.source_id());
757
758 let cache_path = if fork.enable_caching {
760 Config::foundry_block_cache_dir(fork_context.source_chain_id, number)
761 } else {
762 None
763 };
764
765 let provider = fork.evm_opts.fork_provider_with_url::<N>(&fork.url)?;
766 let db = BlockchainDb::new(meta, cache_path);
767 let anchor = ForkBlock::with_rpc_number(
768 evm_env.block_env.number().saturating_to(),
769 resolved.number(),
770 resolved.hash(),
771 );
772 let (backend, handler) = SharedBackend::new_with_anchor(provider, db, anchor)?;
773 let fork_id = ForkId::resolved(&fork.url, &resolved);
774 fork.resolved = Some(resolved);
775 let fork = CreatedFork::new(fork, evm_env, backend);
776
777 Ok((fork_id, fork, handler))
778}
779
780#[cfg(test)]
781mod tests {
782 use super::*;
783 use alloy_primitives::B256;
784 use foundry_evm_networks::{NetworkConfigs, NetworkVariant};
785
786 fn context(block_number: u64) -> ForkContext {
787 ForkContext {
788 execution_chain_id: 1,
789 source_chain_id: 1,
790 network: NetworkVariant::Ethereum,
791 network_profile: NetworkConfigs::default(),
792 block_number,
793 hardfork: None,
794 instance_id: None,
795 source_fork_block_number: None,
796 source_fork_block_hash: None,
797 }
798 }
799
800 #[test]
801 fn resolved_fork_ids_include_hash_and_source_identity() {
802 let url = "http://localhost:8545";
803 let first = ResolvedFork::new(
804 url,
805 None,
806 None,
807 Some(1),
808 BlockNumHash::new(1, B256::with_last_byte(1)),
809 context(1),
810 );
811 let replacement = ResolvedFork::new(
812 url,
813 None,
814 None,
815 Some(1),
816 BlockNumHash::new(1, B256::with_last_byte(2)),
817 context(1),
818 );
819 let authenticated = ResolvedFork::new(
820 url,
821 Some(&["Authorization: secret".to_string()]),
822 None,
823 Some(1),
824 BlockNumHash::new(1, B256::with_last_byte(1)),
825 context(1),
826 );
827
828 assert_ne!(ForkId::resolved(url, &first), ForkId::resolved(url, &replacement));
829 assert_ne!(ForkId::resolved(url, &first), ForkId::resolved(url, &authenticated));
830 }
831}