1use std::{
2 cmp::Ordering,
3 collections::HashMap,
4 fmt::{Debug, Display},
5 sync::Arc,
6 time::Duration,
7};
8
9use anyhow::{Context, anyhow, bail, ensure};
10use async_lock::RwLock;
11use async_trait::async_trait;
12use committable::{Commitment, Committable};
13use espresso_api::routes::v1 as paths;
14use espresso_types::{
15 BackoffParams, BlockMerkleCommitment, BlockMerkleTree, Certificate2, FeeAccount,
16 FeeAccountProof, FeeMerkleCommitment, FeeMerkleTree, Leaf2, NodeState, SeqTypes,
17 ValidatedState,
18 config::PublicNetworkConfig,
19 v0::traits::StateCatchup,
20 v0_3::{
21 ChainConfig, RewardAccountProofV1, RewardAccountV1, RewardMerkleCommitmentV1,
22 RewardMerkleTreeV1,
23 },
24 v0_4::{
25 PermittedRewardMerkleTreeV2, RewardAccountProofV2, RewardAccountV2,
26 RewardMerkleCommitmentV2, RewardMerkleTreeV2, forgotten_accounts_include,
27 },
28};
29use futures::{
30 StreamExt,
31 future::{Future, FutureExt, TryFuture, TryFutureExt},
32 stream::FuturesUnordered,
33};
34use hotshot_new_protocol::utils::verify_new_protocol_leaf_chain;
35use hotshot_types::{
36 ValidatorConfig,
37 data::ViewNumber,
38 epoch_membership::EpochMembershipCoordinator,
39 message::UpgradeLock,
40 network::NetworkConfig,
41 simple_certificate::LightClientStateUpdateCertificateV2,
42 traits::{
43 ValidatedState as ValidatedStateTrait,
44 metrics::{Counter, CounterFamily, Metrics},
45 },
46 utils::verify_leaf_chain,
47};
48use http_client::{Request, error::ClientErr};
49use itertools::Itertools;
50use jf_merkle_tree_compat::{ForgetableMerkleTreeScheme, MerkleTreeScheme, prelude::MerkleNode};
51use parking_lot::Mutex;
52use priority_queue::PriorityQueue;
53use serde::de::DeserializeOwned;
54use tokio::time::{sleep, timeout};
55use tokio_util::task::AbortOnDropHandle;
56use url::Url;
57use vbs::version::StaticVersionType;
58use versions::{EPOCH_VERSION, NEW_PROTOCOL_VERSION};
59
60use crate::api::{
61 BlocksFrontier, RewardMerkleTreeDataSource, RewardMerkleTreeV2Data, context::ConsensusSource,
62};
63
64#[derive(Debug, Clone)]
67struct Client<ApiVer: StaticVersionType> {
68 inner: http_client::Client<ClientErr, ApiVer>,
69 url: Url,
70 requests: Arc<Box<dyn Counter>>,
71 failures: Arc<Box<dyn Counter>>,
72}
73
74impl<ApiVer: StaticVersionType> Client<ApiVer> {
75 pub fn new(
76 url: Url,
77 requests: &(impl CounterFamily + ?Sized),
78 failures: &(impl CounterFamily + ?Sized),
79 ) -> Self {
80 Self {
81 inner: http_client::Client::new(url.clone()),
82 requests: Arc::new(requests.create(vec![url.to_string()])),
83 failures: Arc::new(failures.create(vec![url.to_string()])),
84 url,
85 }
86 }
87
88 pub fn get<T: DeserializeOwned>(&self, route: &str) -> Request<T, ClientErr, ApiVer> {
89 self.inner.get(route)
90 }
91}
92
93#[derive(Clone, Copy, Debug, Default)]
102struct PeerScore {
103 requests: usize,
104 failures: usize,
105}
106
107impl Ord for PeerScore {
108 fn cmp(&self, other: &Self) -> Ordering {
109 (other.failures * self.requests).cmp(&(self.failures * other.requests))
114 }
115}
116
117impl PartialOrd for PeerScore {
118 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
119 Some(self.cmp(other))
120 }
121}
122
123impl PartialEq for PeerScore {
124 fn eq(&self, other: &Self) -> bool {
125 self.cmp(other).is_eq()
126 }
127}
128
129impl Eq for PeerScore {}
130
131#[derive(Debug, Clone, Default)]
132pub struct StatePeers<ApiVer: StaticVersionType> {
133 scores: Arc<RwLock<PriorityQueue<usize, PeerScore>>>,
135 clients: Vec<Client<ApiVer>>,
136 backoff: BackoffParams,
137 base_timeout: Duration,
139}
140
141impl<ApiVer: StaticVersionType> StatePeers<ApiVer> {
142 async fn fetch<Fut>(
143 &self,
144 retry: usize,
145 f: impl Fn(Client<ApiVer>) -> Fut,
146 ) -> anyhow::Result<Fut::Ok>
147 where
148 Fut: TryFuture<Error: Display>,
149 {
150 let timeout_dur = self.base_timeout * (retry as u32 + 1);
161
162 let mut requests = HashMap::new();
165 let mut res = Err(anyhow!("failed fetching from every peer"));
166
167 let mut scores = { (*self.scores.read().await).clone() };
172 let mut logs = vec!["Fetching failed.\n".to_string()];
173 while let Some((id, score)) = scores.pop() {
174 let client = &self.clients[id];
175 tracing::info!("fetching from {}", client.url);
176 match timeout(timeout_dur, TryFutureExt::into_future(f(client.clone()))).await {
177 Ok(Ok(t)) => {
178 requests.insert(id, true);
179 res = Ok(t);
180 logs = Vec::new();
181 break;
182 },
183 Ok(Err(err)) => {
184 tracing::debug!(id, ?score, peer = %client.url, "error from peer: {err:#}");
185 logs.push(format!(
186 "Error from peer {} with id {id} and score {score:?}: {err:#}",
187 client.url
188 ));
189 requests.insert(id, false);
190 },
191 Err(_) => {
192 tracing::debug!(id, ?score, peer = %client.url, ?timeout_dur, "request timed out");
193 logs.push(format!(
194 "Error from peer {} with id {id} and score {score:?}: request timed out",
195 client.url
196 ));
197 requests.insert(id, false);
198 },
199 }
200 }
201
202 if !logs.is_empty() {
203 tracing::warn!("{}", logs.join("\n"));
204 }
205
206 let mut scores = self.scores.write().await;
208 for (id, success) in requests {
209 scores.change_priority_by(&id, |score| {
210 score.requests += 1;
211 self.clients[id].requests.add(1);
212 if !success {
213 score.failures += 1;
214 self.clients[id].failures.add(1);
215 }
216 });
217 }
218
219 res
220 }
221
222 pub fn from_urls(
223 urls: Vec<Url>,
224 backoff: BackoffParams,
225 base_timeout: Duration,
226 metrics: &(impl Metrics + ?Sized),
227 ) -> Self {
228 if urls.is_empty() {
229 panic!("Cannot create StatePeers with no peers");
230 }
231
232 let metrics = metrics.subgroup("catchup".into());
233 let requests = metrics.counter_family("requests".into(), vec!["peer".into()]);
234 let failures = metrics.counter_family("request_failures".into(), vec!["peer".into()]);
235
236 let scores = urls
237 .iter()
238 .enumerate()
239 .map(|(i, _)| (i, PeerScore::default()))
240 .collect();
241 let clients = urls
242 .into_iter()
243 .map(|url| Client::new(url, &*requests, &*failures))
244 .collect();
245
246 Self {
247 clients,
248 scores: Arc::new(RwLock::new(scores)),
249 backoff,
250 base_timeout,
251 }
252 }
253
254 #[tracing::instrument(skip(self, my_own_validator_config))]
255 pub async fn fetch_config(
256 &self,
257 my_own_validator_config: ValidatorConfig<SeqTypes>,
258 ) -> anyhow::Result<NetworkConfig<SeqTypes>> {
259 self.backoff()
260 .retry(self, move |provider, retry| {
261 let my_own_validator_config = my_own_validator_config.clone();
262 async move {
263 let cfg: PublicNetworkConfig = provider
264 .fetch(retry, |client| {
265 let url = client.url.join(&paths::config_hotshot()).unwrap();
266
267 reqwest::get(url.clone())
268 })
269 .await?
270 .json()
271 .await?;
272 cfg.into_network_config(my_own_validator_config)
273 .context("fetched config, but failed to convert to private config")
274 }
275 .boxed()
276 })
277 .await
278 }
279}
280
281pub(crate) async fn verify_legacy_leaf_chain(
283 leaf_chain: Vec<Leaf2>,
284 coordinator: &EpochMembershipCoordinator<SeqTypes>,
285 height: u64,
286) -> anyhow::Result<Leaf2> {
287 let upgrade_lock = UpgradeLock::<SeqTypes>::new(versions::Upgrade::trivial(EPOCH_VERSION));
288 verify_leaf_chain(leaf_chain, coordinator, height, &upgrade_lock)
289 .await
290 .with_context(|| format!("failed to verify leaf chain at height {height}"))
291}
292
293#[async_trait]
294impl<ApiVer: StaticVersionType> StateCatchup for StatePeers<ApiVer> {
295 #[tracing::instrument(skip(self, _instance))]
296 async fn try_fetch_accounts(
297 &self,
298 retry: usize,
299 _instance: &NodeState,
300 height: u64,
301 view: ViewNumber,
302 fee_merkle_tree_root: FeeMerkleCommitment,
303 accounts: &[FeeAccount],
304 ) -> anyhow::Result<Vec<FeeAccountProof>> {
305 self.fetch(retry, |client| async move {
306 let tree = client
307 .inner
308 .post::<FeeMerkleTree>(&paths::catchup_accounts(height, view.u64()))
309 .body_binary(&accounts.to_vec())?
310 .send()
311 .await?;
312
313 let mut proofs = Vec::new();
315 for account in accounts {
316 let (proof, _) = FeeAccountProof::prove(&tree, (*account).into())
317 .context(format!("response missing fee account {account}"))?;
318 proof.verify(&fee_merkle_tree_root).context(format!(
319 "invalid proof for fee account {account}, root: {fee_merkle_tree_root}"
320 ))?;
321 proofs.push(proof);
322 }
323
324 anyhow::Ok(proofs)
325 })
326 .await
327 }
328
329 #[tracing::instrument(skip(self, _instance, mt))]
330 async fn try_remember_blocks_merkle_tree(
331 &self,
332 retry: usize,
333 _instance: &NodeState,
334 height: u64,
335 view: ViewNumber,
336 mt: &mut BlockMerkleTree,
337 ) -> anyhow::Result<()> {
338 *mt = self
339 .fetch(retry, |client| {
340 let mut mt = mt.clone();
341 async move {
342 let frontier = client
343 .get::<BlocksFrontier>(&paths::catchup_blocks(height, view.u64()))
344 .send()
345 .await?;
346 let elem = frontier
347 .elem()
348 .context("provided frontier is missing leaf element")?;
349 mt.remember(mt.num_leaves() - 1, *elem, &frontier)
350 .context("verifying block proof")?;
351 anyhow::Ok(mt)
352 }
353 })
354 .await?;
355 Ok(())
356 }
357
358 async fn try_fetch_chain_config(
359 &self,
360 retry: usize,
361 commitment: Commitment<ChainConfig>,
362 ) -> anyhow::Result<ChainConfig> {
363 self.fetch(retry, |client| async move {
364 let cf = client
365 .get::<ChainConfig>(&paths::catchup_chainconfig(commitment))
366 .send()
367 .await?;
368 ensure!(
369 cf.commit() == commitment,
370 "received chain config with mismatched commitment: expected {commitment}, got {}",
371 cf.commit()
372 );
373 Ok(cf)
374 })
375 .await
376 }
377
378 async fn try_fetch_leaf(
379 &self,
380 retry: usize,
381 coordinator: EpochMembershipCoordinator<SeqTypes>,
382 height: u64,
383 ) -> anyhow::Result<Leaf2> {
384 let leaf_chain = self
388 .fetch(retry, |client| async move {
389 let chain = client
390 .get::<Vec<Leaf2>>(&paths::catchup_leafchain(height))
391 .send()
392 .await?;
393 anyhow::Ok(chain)
394 })
395 .await
396 .with_context(|| format!("failed to fetch leaf chain at height {height}"))?;
397
398 let first = leaf_chain
399 .first()
400 .ok_or_else(|| anyhow!("empty leaf chain returned for height {height}"))?;
401
402 if first.block_header().version() >= NEW_PROTOCOL_VERSION {
403 let upgrade_lock =
404 UpgradeLock::<SeqTypes>::new(versions::Upgrade::trivial(NEW_PROTOCOL_VERSION));
405
406 let cert2_height = leaf_chain
409 .last()
410 .ok_or_else(|| anyhow!("empty leaf chain returned for height {height}"))?
411 .height();
412 let cert2 = self
413 .fetch(retry, |client| async move {
414 let cert2 = client
415 .get::<Certificate2<SeqTypes>>(&paths::catchup_cert2(cert2_height))
416 .send()
417 .await?;
418 anyhow::Ok(cert2)
419 })
420 .await
421 .with_context(|| format!("failed to fetch cert2 for height {cert2_height}"))?;
422
423 verify_new_protocol_leaf_chain(leaf_chain, &coordinator, height, &upgrade_lock, cert2)
424 .await
425 .with_context(|| {
426 format!("failed to verify leaf chain with cert2 at height {height}")
427 })
428 } else {
429 verify_legacy_leaf_chain(leaf_chain, &coordinator, height).await
430 }
431 }
432
433 async fn try_fetch_reward_merkle_tree_v2(
434 &self,
435 retry: usize,
436 height: u64,
437 view: ViewNumber,
438 reward_merkle_tree_root: RewardMerkleCommitmentV2,
439 accounts: Arc<Vec<RewardAccountV2>>,
440 ) -> anyhow::Result<PermittedRewardMerkleTreeV2> {
441 let result = self
442 .fetch(retry, |client| async move {
443 let tree_bytes = match client
447 .inner
448 .get::<Vec<u8>>(&paths::catchup_reward_merkle_tree_v2(height, *view))
449 .send()
450 .await
451 {
452 Ok(bytes) => bytes,
453 Err(err) => {
454 tracing::info!(
455 "catchup endpoint failed, falling back to reward-state-v2: {err:#}"
456 );
457 client
458 .inner
459 .get::<Vec<u8>>(&paths::reward_merkle_tree_v2(height))
460 .send()
461 .await?
462 },
463 };
464
465 Ok::<Vec<u8>, anyhow::Error>(tree_bytes)
466 })
467 .await
468 .context("Fetching from peer failed")?;
469
470 let tree_data = bincode::deserialize::<RewardMerkleTreeV2Data>(&result)
471 .context("Failed to deserialize merkle tree from catchup")?;
472
473 let tree: PermittedRewardMerkleTreeV2 =
474 PermittedRewardMerkleTreeV2::try_from_kv_set(tree_data.balances).await?;
475
476 ensure!(
477 tree.tree.commitment() == reward_merkle_tree_root,
478 "RewardMerkleTreeV2 from peer failed commitment check."
479 );
480 ensure!(!forgotten_accounts_include(&tree, &accounts));
481
482 Ok(tree)
483 }
484
485 #[tracing::instrument(skip(self, _instance))]
486 async fn try_fetch_reward_accounts_v1(
487 &self,
488 retry: usize,
489 _instance: &NodeState,
490 height: u64,
491 view: ViewNumber,
492 reward_merkle_tree_root: RewardMerkleCommitmentV1,
493 accounts: &[RewardAccountV1],
494 ) -> anyhow::Result<Vec<RewardAccountProofV1>> {
495 self.fetch(retry, |client| async move {
496 let tree = client
497 .inner
498 .post::<RewardMerkleTreeV1>(&paths::catchup_reward_accounts(height, view.u64()))
499 .body_binary(&accounts.to_vec())?
500 .send()
501 .await?;
502
503 let mut proofs = Vec::new();
505 for account in accounts {
506 let (proof, _) = RewardAccountProofV1::prove(&tree, (*account).into())
507 .context(format!("response missing reward account {account}"))?;
508 proof.verify(&reward_merkle_tree_root).context(format!(
509 "invalid proof for v1 reward account {account}, root: \
510 {reward_merkle_tree_root} height {height} view {view}"
511 ))?;
512 proofs.push(proof);
513 }
514
515 anyhow::Ok(proofs)
516 })
517 .await
518 }
519
520 async fn try_fetch_state_cert(
521 &self,
522 retry: usize,
523 epoch: u64,
524 ) -> anyhow::Result<LightClientStateUpdateCertificateV2<SeqTypes>> {
525 self.fetch(retry, |client| async move {
526 client
527 .get::<LightClientStateUpdateCertificateV2<SeqTypes>>(&paths::catchup_state_cert(
528 epoch,
529 ))
530 .send()
531 .await
532 })
533 .await
534 }
535
536 fn backoff(&self) -> &BackoffParams {
537 &self.backoff
538 }
539
540 fn name(&self) -> String {
541 format!(
542 "StatePeers({})",
543 self.clients
544 .iter()
545 .map(|client| client.url.to_string())
546 .join(",")
547 )
548 }
549
550 fn is_local(&self) -> bool {
551 false
552 }
553}
554
555pub(crate) trait CatchupStorage: Sync {
556 fn get_accounts(
567 &self,
568 _instance: &NodeState,
569 _height: u64,
570 _view: ViewNumber,
571 _accounts: &[FeeAccount],
572 ) -> impl Send + Future<Output = anyhow::Result<(FeeMerkleTree, Leaf2)>> {
573 async {
578 bail!("merklized state catchup is not supported for this data source");
579 }
580 }
581
582 fn get_accounts_at(
589 &self,
590 _height: u64,
591 _root: FeeMerkleCommitment,
592 _accounts: &[FeeAccount],
593 ) -> impl Send + Future<Output = anyhow::Result<FeeMerkleTree>> {
594 async {
595 bail!("merklized state catchup is not supported for this data source");
596 }
597 }
598
599 fn get_reward_accounts_v1(
600 &self,
601 _instance: &NodeState,
602 _height: u64,
603 _view: ViewNumber,
604 _accounts: &[RewardAccountV1],
605 ) -> impl Send + Future<Output = anyhow::Result<(RewardMerkleTreeV1, Leaf2)>> {
606 async {
607 bail!("merklized state catchup is not supported for this data source");
608 }
609 }
610
611 fn get_reward_accounts_v2(
612 &self,
613 _instance: &NodeState,
614 _height: u64,
615 _view: ViewNumber,
616 _accounts: &[RewardAccountV2],
617 ) -> impl Send + Future<Output = anyhow::Result<(RewardMerkleTreeV2, Leaf2)>> {
618 async {
619 bail!("merklized state catchup is not supported for this data source");
620 }
621 }
622
623 fn get_frontier(
630 &self,
631 _instance: &NodeState,
632 _height: u64,
633 _view: ViewNumber,
634 ) -> impl Send + Future<Output = anyhow::Result<BlocksFrontier>> {
635 async {
640 bail!("merklized state catchup is not supported for this data source");
641 }
642 }
643
644 fn get_frontier_at(
649 &self,
650 _height: u64,
651 _root: BlockMerkleCommitment,
652 ) -> impl Send + Future<Output = anyhow::Result<BlocksFrontier>> {
653 async {
654 bail!("merklized state catchup is not supported for this data source");
655 }
656 }
657
658 fn get_chain_config(
659 &self,
660 _commitment: Commitment<ChainConfig>,
661 ) -> impl Send + Future<Output = anyhow::Result<ChainConfig>> {
662 async {
663 bail!("chain config catchup is not supported for this data source");
664 }
665 }
666
667 fn get_leaf_chain(
668 &self,
669 _height: u64,
670 ) -> impl Send + Future<Output = anyhow::Result<Vec<Leaf2>>> {
671 async {
672 bail!("leaf chain catchup is not supported for this data source");
673 }
674 }
675
676 fn load_cert2(
678 &self,
679 _height: u64,
680 ) -> impl Send + Future<Output = anyhow::Result<Option<Certificate2<SeqTypes>>>> {
681 async { Ok(None) }
682 }
683
684 fn get_leaf(&self, _height: u64) -> impl Send + Future<Output = anyhow::Result<Leaf2>> {
686 async {
687 bail!("leaf fetch is not supported for this data source");
688 }
689 }
690
691 fn load_serialized_reward_merkle_tree_v2(
693 &self,
694 _height: u64,
695 ) -> impl Send + Future<Output = anyhow::Result<Vec<u8>>> {
696 async {
697 bail!("reward merkle tree is not supported for this data source");
698 }
699 }
700}
701
702impl CatchupStorage for hotshot_query_service::data_source::MetricsDataSource {}
703
704impl<T, S> CatchupStorage for hotshot_query_service::data_source::ExtensibleDataSource<T, S>
705where
706 T: CatchupStorage,
707 S: Sync,
708{
709 async fn get_accounts(
710 &self,
711 instance: &NodeState,
712 height: u64,
713 view: ViewNumber,
714 accounts: &[FeeAccount],
715 ) -> anyhow::Result<(FeeMerkleTree, Leaf2)> {
716 self.inner()
717 .get_accounts(instance, height, view, accounts)
718 .await
719 }
720
721 async fn get_accounts_at(
722 &self,
723 height: u64,
724 root: FeeMerkleCommitment,
725 accounts: &[FeeAccount],
726 ) -> anyhow::Result<FeeMerkleTree> {
727 self.inner().get_accounts_at(height, root, accounts).await
728 }
729
730 async fn get_reward_accounts_v2(
731 &self,
732 instance: &NodeState,
733 height: u64,
734 view: ViewNumber,
735 accounts: &[RewardAccountV2],
736 ) -> anyhow::Result<(RewardMerkleTreeV2, Leaf2)> {
737 self.inner()
738 .get_reward_accounts_v2(instance, height, view, accounts)
739 .await
740 }
741
742 async fn get_reward_accounts_v1(
743 &self,
744 instance: &NodeState,
745 height: u64,
746 view: ViewNumber,
747 accounts: &[RewardAccountV1],
748 ) -> anyhow::Result<(RewardMerkleTreeV1, Leaf2)> {
749 self.inner()
750 .get_reward_accounts_v1(instance, height, view, accounts)
751 .await
752 }
753
754 async fn get_frontier(
755 &self,
756 instance: &NodeState,
757 height: u64,
758 view: ViewNumber,
759 ) -> anyhow::Result<BlocksFrontier> {
760 self.inner().get_frontier(instance, height, view).await
761 }
762
763 async fn get_frontier_at(
764 &self,
765 height: u64,
766 root: BlockMerkleCommitment,
767 ) -> anyhow::Result<BlocksFrontier> {
768 self.inner().get_frontier_at(height, root).await
769 }
770
771 async fn get_chain_config(
772 &self,
773 commitment: Commitment<ChainConfig>,
774 ) -> anyhow::Result<ChainConfig> {
775 self.inner().get_chain_config(commitment).await
776 }
777 async fn get_leaf_chain(&self, height: u64) -> anyhow::Result<Vec<Leaf2>> {
778 self.inner().get_leaf_chain(height).await
779 }
780
781 async fn load_cert2(&self, height: u64) -> anyhow::Result<Option<Certificate2<SeqTypes>>> {
782 self.inner().load_cert2(height).await
783 }
784
785 async fn get_leaf(&self, height: u64) -> anyhow::Result<Leaf2> {
786 self.inner().get_leaf(height).await
787 }
788
789 async fn load_serialized_reward_merkle_tree_v2(&self, height: u64) -> anyhow::Result<Vec<u8>> {
790 self.inner()
791 .load_serialized_reward_merkle_tree_v2(height)
792 .await
793 }
794}
795
796#[derive(Debug)]
797pub(crate) struct SqlStateCatchup<T> {
798 db: Arc<T>,
799 backoff: BackoffParams,
800}
801
802impl<T> SqlStateCatchup<T> {
803 pub(crate) fn new(db: Arc<T>, backoff: BackoffParams) -> Self {
804 Self { db, backoff }
805 }
806}
807
808#[async_trait]
809impl<T> StateCatchup for SqlStateCatchup<T>
810where
811 T: CatchupStorage + RewardMerkleTreeDataSource + Send + Sync,
812{
813 async fn try_fetch_leaf(
814 &self,
815 _retry: usize,
816 _coordinator: EpochMembershipCoordinator<SeqTypes>,
817 height: u64,
818 ) -> anyhow::Result<Leaf2> {
819 self.db
822 .get_leaf(height)
823 .await
824 .with_context(|| format!("failed to load leaf at height {height} from DB"))
825 }
826
827 #[tracing::instrument(skip(self, _retry, instance))]
830 async fn try_fetch_accounts(
831 &self,
832 _retry: usize,
833 instance: &NodeState,
834 block_height: u64,
835 view: ViewNumber,
836 fee_merkle_tree_root: FeeMerkleCommitment,
837 accounts: &[FeeAccount],
838 ) -> anyhow::Result<Vec<FeeAccountProof>> {
839 let fee_merkle_tree_from_db = match self
842 .db
843 .get_accounts(instance, block_height, view, accounts)
844 .await
845 {
846 Ok((tree, _)) => tree,
847 Err(err) => {
848 let tree = self
849 .db
850 .get_accounts_at(block_height, fee_merkle_tree_root, accounts)
851 .await
852 .with_context(|| format!("failed to get fee accounts from DB: {err:#}"))?;
853 tracing::debug!("get_accounts failed, read the accounts by root instead: {err:#}");
854 tree
855 },
856 };
857
858 let mut proofs = Vec::new();
860 for account in accounts {
861 let (proof, _) = FeeAccountProof::prove(&fee_merkle_tree_from_db, (*account).into())
862 .context(format!("response missing account {account}"))?;
863 proof.verify(&fee_merkle_tree_root).context(format!(
864 "invalid proof for fee account {account}, root: {fee_merkle_tree_root}"
865 ))?;
866 proofs.push(proof);
867 }
868
869 Ok(proofs)
870 }
871
872 #[tracing::instrument(skip(self, _retry, instance, mt))]
873 async fn try_remember_blocks_merkle_tree(
874 &self,
875 _retry: usize,
876 instance: &NodeState,
877 bh: u64,
878 view: ViewNumber,
879 mt: &mut BlockMerkleTree,
880 ) -> anyhow::Result<()> {
881 if bh == 0 {
882 return Ok(());
883 }
884
885 let proof = match self.db.get_frontier(instance, bh, view).await {
887 Ok(proof) => proof,
888 Err(err) => {
889 let proof = self
890 .db
891 .get_frontier_at(bh, mt.commitment())
892 .await
893 .with_context(|| format!("failed to get frontier from DB: {err:#}"))?;
894 tracing::debug!("get_frontier failed, read the frontier by root instead: {err:#}");
895 proof
896 },
897 };
898 match proof
899 .proof
900 .first()
901 .context(format!("empty proof for frontier at height {bh}"))?
902 {
903 MerkleNode::Leaf { pos, elem, .. } => mt
904 .remember(pos, elem, proof.clone())
905 .context("failed to remember proof"),
906 _ => bail!("invalid proof"),
907 }
908 }
909
910 async fn try_fetch_chain_config(
911 &self,
912 _retry: usize,
913 commitment: Commitment<ChainConfig>,
914 ) -> anyhow::Result<ChainConfig> {
915 let cf = self.db.get_chain_config(commitment).await?;
916
917 if cf.commit() != commitment {
918 panic!(
919 "Critical error: Mismatched chain config detected. Expected chain config: {:?}, \
920 but got: {:?}.
921 This may indicate a compromised database",
922 commitment,
923 cf.commit()
924 )
925 }
926
927 Ok(cf)
928 }
929
930 async fn try_fetch_reward_merkle_tree_v2(
931 &self,
932 _retry: usize,
933 height: u64,
934 _view: ViewNumber,
935 reward_merkle_tree_root: RewardMerkleCommitmentV2,
936 accounts: Arc<Vec<RewardAccountV2>>,
937 ) -> anyhow::Result<PermittedRewardMerkleTreeV2> {
938 let tree: PermittedRewardMerkleTreeV2 = self.db.load_reward_merkle_tree_v2(height).await?;
939
940 ensure!(tree.tree.commitment() == reward_merkle_tree_root);
941 ensure!(!forgotten_accounts_include(&tree, &accounts));
942
943 Ok(tree)
944 }
945
946 #[tracing::instrument(skip(self, _retry, instance))]
947 async fn try_fetch_reward_accounts_v1(
948 &self,
949 _retry: usize,
950 instance: &NodeState,
951 block_height: u64,
952 view: ViewNumber,
953 reward_merkle_tree_root: RewardMerkleCommitmentV1,
954 accounts: &[RewardAccountV1],
955 ) -> anyhow::Result<Vec<RewardAccountProofV1>> {
956 let (reward_merkle_tree_from_db, _) = self
958 .db
959 .get_reward_accounts_v1(instance, block_height, view, accounts)
960 .await
961 .with_context(|| "failed to get reward accounts from DB")?;
962 let mut proofs = Vec::new();
964 for account in accounts {
965 let (proof, _) =
966 RewardAccountProofV1::prove(&reward_merkle_tree_from_db, (*account).into())
967 .context(format!("response missing account {account}"))?;
968 proof.verify(&reward_merkle_tree_root).context(format!(
969 "invalid proof for v1 reward account {account}, root: {reward_merkle_tree_root}"
970 ))?;
971 proofs.push(proof);
972 }
973
974 Ok(proofs)
975 }
976
977 async fn try_fetch_state_cert(
978 &self,
979 _retry: usize,
980 _epoch: u64,
981 ) -> anyhow::Result<LightClientStateUpdateCertificateV2<SeqTypes>> {
982 bail!("state cert catchup not supported for SqlStateCatchup");
983 }
984
985 fn backoff(&self) -> &BackoffParams {
986 &self.backoff
987 }
988
989 fn name(&self) -> String {
990 "SqlStateCatchup".into()
991 }
992
993 fn is_local(&self) -> bool {
994 true
995 }
996}
997
998#[derive(Clone, Debug)]
1000pub struct NullStateCatchup {
1001 backoff: BackoffParams,
1002 chain_configs: HashMap<Commitment<ChainConfig>, ChainConfig>,
1003}
1004
1005impl Default for NullStateCatchup {
1006 fn default() -> Self {
1007 Self {
1008 backoff: BackoffParams::disabled(),
1009 chain_configs: Default::default(),
1010 }
1011 }
1012}
1013
1014impl NullStateCatchup {
1015 pub fn add_chain_config(&mut self, cf: ChainConfig) {
1025 self.chain_configs.insert(cf.commit(), cf);
1026 }
1027}
1028
1029#[async_trait]
1030impl StateCatchup for NullStateCatchup {
1031 async fn try_fetch_leaf(
1032 &self,
1033 _retry: usize,
1034 _coordinator: EpochMembershipCoordinator<SeqTypes>,
1035 _height: u64,
1036 ) -> anyhow::Result<Leaf2> {
1037 bail!("state catchup is disabled")
1038 }
1039
1040 async fn try_fetch_accounts(
1041 &self,
1042 _retry: usize,
1043 _instance: &NodeState,
1044 _height: u64,
1045 _view: ViewNumber,
1046 _fee_merkle_tree_root: FeeMerkleCommitment,
1047 _account: &[FeeAccount],
1048 ) -> anyhow::Result<Vec<FeeAccountProof>> {
1049 bail!("state catchup is disabled");
1050 }
1051
1052 async fn try_remember_blocks_merkle_tree(
1053 &self,
1054 _retry: usize,
1055 _instance: &NodeState,
1056 _height: u64,
1057 _view: ViewNumber,
1058 _mt: &mut BlockMerkleTree,
1059 ) -> anyhow::Result<()> {
1060 bail!("state catchup is disabled");
1061 }
1062
1063 async fn try_fetch_chain_config(
1064 &self,
1065 _retry: usize,
1066 commitment: Commitment<ChainConfig>,
1067 ) -> anyhow::Result<ChainConfig> {
1068 self.chain_configs
1069 .get(&commitment)
1070 .copied()
1071 .context(format!("chain config {commitment} not available"))
1072 }
1073
1074 async fn try_fetch_reward_merkle_tree_v2(
1075 &self,
1076 _retry: usize,
1077 _height: u64,
1078 _view: ViewNumber,
1079 _reward_merkle_tree_root: RewardMerkleCommitmentV2,
1080 _accounts: Arc<Vec<RewardAccountV2>>,
1081 ) -> anyhow::Result<PermittedRewardMerkleTreeV2> {
1082 bail!("state catchup is disabled");
1083 }
1084
1085 async fn try_fetch_reward_accounts_v1(
1086 &self,
1087 _retry: usize,
1088 _instance: &NodeState,
1089 _height: u64,
1090 _view: ViewNumber,
1091 _fee_merkle_tree_root: RewardMerkleCommitmentV1,
1092 _account: &[RewardAccountV1],
1093 ) -> anyhow::Result<Vec<RewardAccountProofV1>> {
1094 bail!("state catchup is disabled");
1095 }
1096
1097 async fn try_fetch_state_cert(
1098 &self,
1099 _retry: usize,
1100 _epoch: u64,
1101 ) -> anyhow::Result<LightClientStateUpdateCertificateV2<SeqTypes>> {
1102 bail!("state catchup is disabled");
1103 }
1104
1105 fn backoff(&self) -> &BackoffParams {
1106 &self.backoff
1107 }
1108
1109 fn name(&self) -> String {
1110 "NullStateCatchup".into()
1111 }
1112
1113 fn is_local(&self) -> bool {
1114 true
1115 }
1116}
1117
1118pub(crate) struct SingleAttemptCatchup<C> {
1125 inner: C,
1126 retry: usize,
1129 backoff: BackoffParams,
1130}
1131
1132impl<C> SingleAttemptCatchup<C> {
1133 pub(crate) fn new(inner: C, retry: usize) -> Self {
1134 Self {
1135 inner,
1136 retry,
1137 backoff: BackoffParams::disabled(),
1138 }
1139 }
1140}
1141
1142#[async_trait]
1143impl<C: StateCatchup> StateCatchup for SingleAttemptCatchup<C> {
1144 async fn try_fetch_leaf(
1145 &self,
1146 retry: usize,
1147 coordinator: EpochMembershipCoordinator<SeqTypes>,
1148 height: u64,
1149 ) -> anyhow::Result<Leaf2> {
1150 self.inner
1151 .try_fetch_leaf(self.retry + retry, coordinator, height)
1152 .await
1153 }
1154
1155 async fn try_fetch_accounts(
1156 &self,
1157 retry: usize,
1158 instance: &NodeState,
1159 height: u64,
1160 view: ViewNumber,
1161 fee_merkle_tree_root: FeeMerkleCommitment,
1162 accounts: &[FeeAccount],
1163 ) -> anyhow::Result<Vec<FeeAccountProof>> {
1164 self.inner
1165 .try_fetch_accounts(
1166 self.retry + retry,
1167 instance,
1168 height,
1169 view,
1170 fee_merkle_tree_root,
1171 accounts,
1172 )
1173 .await
1174 }
1175
1176 async fn try_remember_blocks_merkle_tree(
1177 &self,
1178 retry: usize,
1179 instance: &NodeState,
1180 height: u64,
1181 view: ViewNumber,
1182 mt: &mut BlockMerkleTree,
1183 ) -> anyhow::Result<()> {
1184 self.inner
1185 .try_remember_blocks_merkle_tree(self.retry + retry, instance, height, view, mt)
1186 .await
1187 }
1188
1189 async fn try_fetch_chain_config(
1190 &self,
1191 retry: usize,
1192 commitment: Commitment<ChainConfig>,
1193 ) -> anyhow::Result<ChainConfig> {
1194 self.inner
1195 .try_fetch_chain_config(self.retry + retry, commitment)
1196 .await
1197 }
1198
1199 async fn try_fetch_reward_merkle_tree_v2(
1200 &self,
1201 retry: usize,
1202 height: u64,
1203 view: ViewNumber,
1204 reward_merkle_tree_root: RewardMerkleCommitmentV2,
1205 accounts: Arc<Vec<RewardAccountV2>>,
1206 ) -> anyhow::Result<PermittedRewardMerkleTreeV2> {
1207 self.inner
1208 .try_fetch_reward_merkle_tree_v2(
1209 self.retry + retry,
1210 height,
1211 view,
1212 reward_merkle_tree_root,
1213 accounts,
1214 )
1215 .await
1216 }
1217
1218 async fn try_fetch_reward_accounts_v1(
1219 &self,
1220 retry: usize,
1221 instance: &NodeState,
1222 height: u64,
1223 view: ViewNumber,
1224 reward_merkle_tree_root: RewardMerkleCommitmentV1,
1225 accounts: &[RewardAccountV1],
1226 ) -> anyhow::Result<Vec<RewardAccountProofV1>> {
1227 self.inner
1228 .try_fetch_reward_accounts_v1(
1229 self.retry + retry,
1230 instance,
1231 height,
1232 view,
1233 reward_merkle_tree_root,
1234 accounts,
1235 )
1236 .await
1237 }
1238
1239 async fn try_fetch_state_cert(
1240 &self,
1241 retry: usize,
1242 epoch: u64,
1243 ) -> anyhow::Result<LightClientStateUpdateCertificateV2<SeqTypes>> {
1244 self.inner
1245 .try_fetch_state_cert(self.retry + retry, epoch)
1246 .await
1247 }
1248
1249 fn backoff(&self) -> &BackoffParams {
1250 &self.backoff
1251 }
1252
1253 fn name(&self) -> String {
1254 self.inner.name()
1255 }
1256
1257 fn is_local(&self) -> bool {
1258 self.inner.is_local()
1259 }
1260}
1261
1262#[derive(Clone)]
1264struct Provider {
1265 catchup: Arc<dyn StateCatchup>,
1266 fallback: bool,
1268}
1269
1270#[derive(Clone)]
1273pub struct ParallelStateCatchup {
1274 providers: Arc<Mutex<Vec<Provider>>>,
1275 backoff: BackoffParams,
1276 local_timeout: Duration,
1278 fallback_delay: Duration,
1279}
1280
1281impl ParallelStateCatchup {
1282 pub fn new(providers: &[Arc<dyn StateCatchup>], local_timeout: Duration) -> Self {
1284 Self {
1285 providers: Arc::new(Mutex::new(
1286 providers
1287 .iter()
1288 .map(|catchup| Provider {
1289 catchup: catchup.clone(),
1290 fallback: false,
1291 })
1292 .collect(),
1293 )),
1294 backoff: BackoffParams::disabled(),
1295 local_timeout,
1296 fallback_delay: Duration::ZERO,
1297 }
1298 }
1299
1300 pub fn with_fallback_delay(mut self, delay: Duration) -> Self {
1302 self.fallback_delay = delay;
1303 self
1304 }
1305
1306 pub fn add_provider(&self, provider: Arc<dyn StateCatchup>) {
1308 self.add(provider, false);
1309 }
1310
1311 pub fn add_fallback_provider(&self, provider: Arc<dyn StateCatchup>) {
1313 self.add(provider, true);
1314 }
1315
1316 fn add(&self, catchup: Arc<dyn StateCatchup>, fallback: bool) {
1317 self.providers.lock().push(Provider { catchup, fallback });
1318 }
1319
1320 async fn on_providers<C, F, RT>(&self, closure: C) -> anyhow::Result<RT>
1322 where
1323 C: Fn(Arc<dyn StateCatchup>) -> F + Clone + Send + Sync + 'static,
1324 F: Future<Output = anyhow::Result<RT>> + Send + 'static,
1325 RT: Send + Sync + 'static,
1326 {
1327 let providers = self.providers.lock().clone();
1328 if providers.is_empty() {
1329 return Err(anyhow::anyhow!("no providers were initialized"));
1330 }
1331
1332 let has_primary_remote = providers
1333 .iter()
1334 .any(|p| !p.fallback && !p.catchup.is_local());
1335 let local_timeout = self.local_timeout;
1336 let fallback_delay = self.fallback_delay;
1337
1338 let mut futures = FuturesUnordered::new();
1339 for Provider { catchup, fallback } in providers {
1340 let closure = closure.clone();
1341 let name = catchup.name();
1342 futures.push(AbortOnDropHandle::new(tokio::spawn(async move {
1343 let result = if catchup.is_local() {
1344 timeout(local_timeout, closure(catchup))
1345 .await
1346 .unwrap_or_else(|_| {
1347 let err = format!("{name} timed out after {local_timeout:?}");
1348 tracing::warn!("{err}");
1349 Err(anyhow::anyhow!(err))
1350 })
1351 } else {
1352 if fallback && has_primary_remote {
1353 sleep(fallback_delay).await;
1354 }
1355 closure(catchup).await
1356 };
1357 (name, result)
1358 })));
1359 }
1360
1361 let mut logs = vec!["No providers returned a successful result.\n".to_string()];
1362 while let Some(result) = futures.next().await {
1364 let (name, result) = match result {
1366 Ok(res) => res,
1367 Err(err) => {
1368 tracing::debug!("Failed to join on provider: {err:#}.");
1369 logs.push(format!("Failed to join on provider: {err:#}."));
1370 continue;
1371 },
1372 };
1373
1374 let result = match result {
1376 Ok(res) => res,
1377 Err(err) => {
1378 tracing::debug!("Failed to fetch data from {name}: {err:#}.");
1379 logs.push(format!("Failed to fetch data from {name}: {err:#}."));
1380 continue;
1381 },
1382 };
1383
1384 return Ok(result);
1385 }
1386
1387 Err(anyhow::anyhow!(logs.join("\n")))
1388 }
1389}
1390
1391macro_rules! clone {
1392 ( ($( $x:ident ),*) $y:expr ) => {
1393 {
1394 $(let $x = $x.clone();)*
1395 $y
1396 }
1397 };
1398}
1399
1400#[async_trait]
1401impl StateCatchup for ParallelStateCatchup {
1402 async fn try_fetch_leaf(
1403 &self,
1404 retry: usize,
1405 coordinator: EpochMembershipCoordinator<SeqTypes>,
1406 height: u64,
1407 ) -> anyhow::Result<Leaf2> {
1408 self.on_providers(clone! {(coordinator) move |provider| {
1409 clone!{(coordinator) async move {
1410 provider
1411 .try_fetch_leaf(retry, coordinator, height)
1412 .await
1413 }}
1414 }})
1415 .await
1416 }
1417
1418 async fn try_fetch_accounts(
1419 &self,
1420 retry: usize,
1421 instance: &NodeState,
1422 height: u64,
1423 view: ViewNumber,
1424 fee_merkle_tree_root: FeeMerkleCommitment,
1425 accounts: &[FeeAccount],
1426 ) -> anyhow::Result<Vec<FeeAccountProof>> {
1427 let accounts_vec = accounts.to_vec();
1428 self.on_providers(clone! {(instance, accounts_vec) move |provider| {
1429 clone! {(instance, accounts_vec) async move {
1430 provider
1431 .try_fetch_accounts(
1432 retry,
1433 &instance,
1434 height,
1435 view,
1436 fee_merkle_tree_root,
1437 &accounts_vec,
1438 )
1439 .await
1440 }}
1441 }})
1442 .await
1443 }
1444
1445 async fn try_remember_blocks_merkle_tree(
1446 &self,
1447 retry: usize,
1448 instance: &NodeState,
1449 height: u64,
1450 view: ViewNumber,
1451 mt: &mut BlockMerkleTree,
1452 ) -> anyhow::Result<()> {
1453 *mt = self
1454 .on_providers(clone! {(mt, instance) move |provider| {
1455 let mut mt = mt.clone();
1456 clone! {(instance) async move {
1457 provider
1458 .try_remember_blocks_merkle_tree(
1459 retry,
1460 &instance,
1461 height,
1462 view,
1463 &mut mt,
1464 )
1465 .await?;
1466
1467 Ok(mt)
1468 }}
1469 }})
1470 .await?;
1471
1472 Ok(())
1473 }
1474
1475 async fn try_fetch_chain_config(
1476 &self,
1477 retry: usize,
1478 commitment: Commitment<ChainConfig>,
1479 ) -> anyhow::Result<ChainConfig> {
1480 self.on_providers(move |provider| async move {
1481 provider.try_fetch_chain_config(retry, commitment).await
1482 })
1483 .await
1484 }
1485
1486 async fn try_fetch_reward_merkle_tree_v2(
1487 &self,
1488 retry: usize,
1489 height: u64,
1490 view: ViewNumber,
1491 reward_merkle_tree_root: RewardMerkleCommitmentV2,
1492 accounts: Arc<Vec<RewardAccountV2>>,
1493 ) -> anyhow::Result<PermittedRewardMerkleTreeV2> {
1494 self.on_providers(clone! {(accounts) move |provider| {
1495 clone! {(accounts) async move {
1496 provider
1497 .try_fetch_reward_merkle_tree_v2(
1498 retry,
1499 height,
1500 view,
1501 reward_merkle_tree_root,
1502 accounts,
1503 )
1504 .await
1505 }}
1506 }})
1507 .await
1508 }
1509
1510 async fn try_fetch_reward_accounts_v1(
1511 &self,
1512 retry: usize,
1513 instance: &NodeState,
1514 height: u64,
1515 view: ViewNumber,
1516 reward_merkle_tree_root: RewardMerkleCommitmentV1,
1517 accounts: &[RewardAccountV1],
1518 ) -> anyhow::Result<Vec<RewardAccountProofV1>> {
1519 let accounts_vec = accounts.to_vec();
1520 self.on_providers(clone! {(instance, accounts_vec) move |provider| {
1521 clone! {(instance, accounts_vec) async move {
1522 provider
1523 .try_fetch_reward_accounts_v1(
1524 retry,
1525 &instance,
1526 height,
1527 view,
1528 reward_merkle_tree_root,
1529 &accounts_vec,
1530 )
1531 .await
1532 }}
1533 }})
1534 .await
1535 }
1536
1537 async fn try_fetch_state_cert(
1538 &self,
1539 retry: usize,
1540 epoch: u64,
1541 ) -> anyhow::Result<LightClientStateUpdateCertificateV2<SeqTypes>> {
1542 self.on_providers(move |provider| async move {
1543 provider.try_fetch_state_cert(retry, epoch).await
1544 })
1545 .await
1546 }
1547
1548 fn backoff(&self) -> &BackoffParams {
1549 &self.backoff
1550 }
1551
1552 fn name(&self) -> String {
1553 format!(
1554 "[{}]",
1555 self.providers
1556 .lock()
1557 .iter()
1558 .map(|p| p.catchup.name())
1559 .collect::<Vec<_>>()
1560 .join(", ")
1561 )
1562 }
1563
1564 async fn fetch_accounts(
1565 &self,
1566 instance: &NodeState,
1567 height: u64,
1568 view: ViewNumber,
1569 fee_merkle_tree_root: FeeMerkleCommitment,
1570 accounts: Vec<FeeAccount>,
1571 ) -> anyhow::Result<Vec<FeeAccountProof>> {
1572 self.on_providers(clone! {(instance, accounts) move |provider| {
1573 clone! {(instance, accounts) async move {
1574 provider
1575 .fetch_accounts(&instance, height, view, fee_merkle_tree_root, accounts)
1576 .await
1577 }}
1578 }})
1579 .await
1580 }
1581
1582 async fn fetch_leaf(
1583 &self,
1584 coordinator: EpochMembershipCoordinator<SeqTypes>,
1585 height: u64,
1586 ) -> anyhow::Result<Leaf2> {
1587 self.on_providers(clone! {(coordinator) move |provider| {
1588 clone! {(coordinator) async move {
1589 provider.fetch_leaf(coordinator, height).await
1590 }}
1591 }})
1592 .await
1593 }
1594
1595 async fn fetch_chain_config(
1596 &self,
1597 commitment: Commitment<ChainConfig>,
1598 ) -> anyhow::Result<ChainConfig> {
1599 self.on_providers(
1600 move |provider| async move { provider.fetch_chain_config(commitment).await },
1601 )
1602 .await
1603 }
1604
1605 async fn fetch_reward_accounts_v1(
1606 &self,
1607 instance: &NodeState,
1608 height: u64,
1609 view: ViewNumber,
1610 reward_merkle_tree_root: RewardMerkleCommitmentV1,
1611 accounts: Vec<RewardAccountV1>,
1612 ) -> anyhow::Result<Vec<RewardAccountProofV1>> {
1613 self.on_providers(clone! {(instance, accounts) move |provider| {
1614 clone! {(instance, accounts) async move {
1615 provider
1616 .fetch_reward_accounts_v1(
1617 &instance,
1618 height,
1619 view,
1620 reward_merkle_tree_root,
1621 accounts,
1622 )
1623 .await
1624 }}
1625 }})
1626 .await
1627 }
1628
1629 async fn fetch_state_cert(
1630 &self,
1631 epoch: u64,
1632 ) -> anyhow::Result<LightClientStateUpdateCertificateV2<SeqTypes>> {
1633 self.on_providers(move |provider| async move { provider.fetch_state_cert(epoch).await })
1634 .await
1635 }
1636
1637 async fn remember_blocks_merkle_tree(
1638 &self,
1639 instance: &NodeState,
1640 height: u64,
1641 view: ViewNumber,
1642 mt: &mut BlockMerkleTree,
1643 ) -> anyhow::Result<()> {
1644 *mt = self
1645 .on_providers(clone! {(mt, instance) move |provider| {
1646 let mut mt = mt.clone();
1647 clone! {(instance) async move {
1648 provider
1649 .remember_blocks_merkle_tree(&instance, height, view, &mut mt)
1650 .await?;
1651 Ok(mt)
1652 }}
1653 }})
1654 .await?;
1655
1656 Ok(())
1657 }
1658
1659 fn is_local(&self) -> bool {
1660 self.providers.lock().iter().all(|p| p.catchup.is_local())
1661 }
1662
1663 fn with_local_timeout(&self, local_timeout: Duration) -> Option<Arc<dyn StateCatchup>> {
1664 Some(Arc::new(Self {
1666 local_timeout,
1667 ..self.clone()
1668 }))
1669 }
1670}
1671
1672pub async fn add_fee_accounts_to_state(
1675 consensus_handle: &dyn ConsensusSource,
1676 view: &ViewNumber,
1677 accounts: &[FeeAccount],
1678 tree: &FeeMerkleTree,
1679 leaf: Leaf2,
1680) -> anyhow::Result<()> {
1681 let (existing_state, delta) = consensus_handle.state_and_delta(*view).await;
1682 let (state, delta) = match existing_state {
1683 Some(existing) => {
1684 let mut state = (*existing).clone();
1685 for account in accounts {
1686 if let Some((proof, _)) = FeeAccountProof::prove(tree, (*account).into()) {
1687 if let Err(err) = proof.remember(&mut state.fee_merkle_tree) {
1688 tracing::warn!(
1689 ?view,
1690 %account,
1691 "cannot update fetched account state: {err:#}"
1692 );
1693 }
1694 } else {
1695 tracing::warn!(?view, %account, "cannot update fetched account state because account is not in the merkle tree");
1696 };
1697 }
1698 (Arc::new(state), delta)
1699 },
1700 None => {
1701 let mut state = ValidatedState::from_header(leaf.block_header());
1706 state.fee_merkle_tree = tree.clone();
1707 (Arc::new(state), None)
1708 },
1709 };
1710
1711 consensus_handle
1712 .update_leaf(leaf, state, delta)
1713 .await
1714 .with_context(|| "failed to update leaf")?;
1715
1716 Ok(())
1717}
1718
1719pub async fn add_v2_reward_accounts_to_state(
1722 consensus_handle: &dyn ConsensusSource,
1723 view: &ViewNumber,
1724 accounts: &[RewardAccountV2],
1725 tree: &RewardMerkleTreeV2,
1726 leaf: Leaf2,
1727) -> anyhow::Result<()> {
1728 let (existing_state, delta) = consensus_handle.state_and_delta(*view).await;
1729 let (state, delta) = match existing_state {
1730 Some(existing) => {
1731 let mut state = (*existing).clone();
1732 for account in accounts {
1733 if let Some((proof, _)) = RewardAccountProofV2::prove(tree, (*account).into()) {
1734 if let Err(err) = proof.remember(&mut state.reward_merkle_tree_v2) {
1735 tracing::warn!(
1736 ?view,
1737 %account,
1738 "cannot update fetched account state: {err:#}"
1739 );
1740 }
1741 } else {
1742 tracing::warn!(?view, %account, "cannot update fetched account state because account is not in the merkle tree");
1743 };
1744 }
1745 (Arc::new(state), delta)
1746 },
1747 None => {
1748 let mut state = ValidatedState::from_header(leaf.block_header());
1753 state.reward_merkle_tree_v2 = tree.clone();
1754 (Arc::new(state), None)
1755 },
1756 };
1757
1758 consensus_handle
1759 .update_leaf(leaf, state, delta)
1760 .await
1761 .with_context(|| "failed to update leaf")?;
1762
1763 Ok(())
1764}
1765
1766pub async fn add_v1_reward_accounts_to_state(
1769 consensus_handle: &dyn ConsensusSource,
1770 view: &ViewNumber,
1771 accounts: &[RewardAccountV1],
1772 tree: &RewardMerkleTreeV1,
1773 leaf: Leaf2,
1774) -> anyhow::Result<()> {
1775 let (existing_state, delta) = consensus_handle.state_and_delta(*view).await;
1776 let (state, delta) = match existing_state {
1777 Some(existing) => {
1778 let mut state = (*existing).clone();
1779 for account in accounts {
1780 if let Some((proof, _)) = RewardAccountProofV1::prove(tree, (*account).into()) {
1781 if let Err(err) = proof.remember(&mut state.reward_merkle_tree_v1) {
1782 tracing::warn!(
1783 ?view,
1784 %account,
1785 "cannot update fetched account state: {err:#}"
1786 );
1787 }
1788 } else {
1789 tracing::warn!(?view, %account, "cannot update fetched account state because account is not in the merkle tree");
1790 };
1791 }
1792 (Arc::new(state), delta)
1793 },
1794 None => {
1795 let mut state = ValidatedState::from_header(leaf.block_header());
1800 state.reward_merkle_tree_v1 = tree.clone();
1801 (Arc::new(state), None)
1802 },
1803 };
1804
1805 consensus_handle
1806 .update_leaf(leaf, state, delta)
1807 .await
1808 .with_context(|| "failed to update leaf")?;
1809
1810 Ok(())
1811}
1812
1813#[cfg(test)]
1814mod test {
1815 use std::{
1816 sync::atomic::{AtomicUsize, Ordering as AtomicOrdering},
1817 time::Instant,
1818 };
1819
1820 use espresso_types::v0::mock::MockStateCatchup;
1821 use hotshot_types::traits::metrics::NoMetrics;
1822 use test_utils::reserve_tcp_port;
1823
1824 use super::*;
1825 use crate::SequencerApiVersion;
1826
1827 #[test_log::test(tokio::test(flavor = "multi_thread"))]
1831 async fn single_attempt_catchup_does_not_retry_peers() {
1832 let port = reserve_tcp_port().expect("OS should have ephemeral ports available");
1834 let peer = StatePeers::<SequencerApiVersion>::from_urls(
1835 vec![format!("http://127.0.0.1:{port}").parse().unwrap()],
1836 BackoffParams::default(),
1837 Duration::from_secs(1),
1838 &NoMetrics,
1839 );
1840 let peers = ParallelStateCatchup::new(&[Arc::new(peer)], Duration::from_secs(1));
1841 let chain_config = ChainConfig::default().commit();
1842 assert!(
1843 timeout(
1844 Duration::from_secs(3),
1845 peers.fetch_chain_config(chain_config)
1846 )
1847 .await
1848 .is_err(),
1849 "the parallel catchup should retry the peer until the timeout"
1850 );
1851
1852 let once = SingleAttemptCatchup::new(peers, 0);
1853 let res = timeout(
1854 Duration::from_secs(3),
1855 once.fetch_chain_config(chain_config),
1856 )
1857 .await
1858 .expect("fetching the chain config should not retry");
1859 assert!(res.is_err());
1860 let instance = NodeState::mock();
1861 let fee_root = ValidatedState::default().fee_merkle_tree.commitment();
1862 let res = timeout(
1863 Duration::from_secs(3),
1864 once.fetch_accounts(
1865 &instance,
1866 1,
1867 ViewNumber::new(1),
1868 fee_root,
1869 vec![FeeAccount::default()],
1870 ),
1871 )
1872 .await
1873 .expect("fetching fee accounts should not retry");
1874 assert!(res.is_err());
1875 let mut frontier = ValidatedState::default().block_merkle_tree;
1876 let res = timeout(
1877 Duration::from_secs(3),
1878 once.remember_blocks_merkle_tree(&instance, 1, ViewNumber::new(1), &mut frontier),
1879 )
1880 .await
1881 .expect("fetching the block frontier should not retry");
1882 assert!(res.is_err());
1883 }
1884
1885 #[test_log::test(tokio::test(flavor = "multi_thread"))]
1888 async fn single_attempt_catchup_passes_the_try_number_on() {
1889 const BASE_TIMEOUT: Duration = Duration::from_millis(200);
1890 let silent = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
1892 let peer = StatePeers::<SequencerApiVersion>::from_urls(
1893 vec![
1894 format!("http://{}", silent.local_addr().unwrap())
1895 .parse()
1896 .unwrap(),
1897 ],
1898 BackoffParams::default(),
1899 BASE_TIMEOUT,
1900 &NoMetrics,
1901 );
1902 let peers = ParallelStateCatchup::new(&[Arc::new(peer)], Duration::from_secs(1));
1903
1904 let start = Instant::now();
1905 let res = SingleAttemptCatchup::new(peers, 4)
1906 .fetch_chain_config(ChainConfig::default().commit())
1907 .await;
1908 assert!(res.is_err());
1909 let elapsed = start.elapsed();
1911 assert!(
1912 elapsed >= 5 * BASE_TIMEOUT,
1913 "the peer got only {elapsed:?} to answer"
1914 );
1915 }
1916
1917 #[test_log::test(tokio::test(flavor = "multi_thread"))]
1920 async fn with_local_timeout_waits_longer_on_local_providers() {
1921 let slow = MockStateCatchup::default().with_delay(Duration::from_millis(500));
1922 let catchup = ParallelStateCatchup::new(&[Arc::new(slow)], Duration::from_millis(50));
1923 let chain_config = ChainConfig::default().commit();
1924 assert!(
1926 catchup
1927 .try_fetch_chain_config(0, chain_config)
1928 .await
1929 .is_err()
1930 );
1931
1932 let longer = catchup
1933 .with_local_timeout(Duration::from_secs(5))
1934 .expect("a parallel catchup has a local timeout");
1935 longer
1936 .try_fetch_chain_config(0, chain_config)
1937 .await
1938 .expect("the local provider answers within the longer timeout");
1939
1940 catchup.add_provider(Arc::new(StatePeers::<SequencerApiVersion>::from_urls(
1942 vec!["http://127.0.0.1:1".parse().unwrap()],
1943 BackoffParams::default(),
1944 Duration::from_secs(1),
1945 &NoMetrics,
1946 )));
1947 assert!(!longer.is_local());
1948 }
1949
1950 #[test]
1951 fn test_peer_priority() {
1952 let good_peer = PeerScore {
1953 requests: 1000,
1954 failures: 2,
1955 };
1956 let bad_peer = PeerScore {
1957 requests: 10,
1958 failures: 1,
1959 };
1960 assert!(good_peer > bad_peer);
1961
1962 let mut peers: PriorityQueue<_, _> = [(0, good_peer), (1, bad_peer)].into_iter().collect();
1963 assert_eq!(peers.pop(), Some((0, good_peer)));
1964 assert_eq!(peers.pop(), Some((1, bad_peer)));
1965 }
1966
1967 struct DelayedCatchup {
1969 local: bool,
1970 delay: Duration,
1971 backoff: BackoffParams,
1972 calls: Arc<AtomicUsize>,
1973 }
1974
1975 impl DelayedCatchup {
1976 fn provider(local: bool, delay: Duration) -> Arc<dyn StateCatchup> {
1977 Self::counted(local, delay).0
1978 }
1979
1980 fn counted(local: bool, delay: Duration) -> (Arc<dyn StateCatchup>, Arc<AtomicUsize>) {
1981 let calls = Arc::new(AtomicUsize::new(0));
1982 let provider = Arc::new(Self {
1983 local,
1984 delay,
1985 backoff: BackoffParams::disabled(),
1986 calls: calls.clone(),
1987 });
1988 (provider, calls)
1989 }
1990 }
1991
1992 #[async_trait]
1993 impl StateCatchup for DelayedCatchup {
1994 async fn try_fetch_chain_config(
1995 &self,
1996 _retry: usize,
1997 _commitment: Commitment<ChainConfig>,
1998 ) -> anyhow::Result<ChainConfig> {
1999 self.calls.fetch_add(1, AtomicOrdering::SeqCst);
2000 tokio::time::sleep(self.delay).await;
2001 Ok(ChainConfig::default())
2002 }
2003
2004 async fn try_fetch_leaf(
2005 &self,
2006 _retry: usize,
2007 _coordinator: EpochMembershipCoordinator<SeqTypes>,
2008 _height: u64,
2009 ) -> anyhow::Result<Leaf2> {
2010 bail!("unsupported")
2011 }
2012
2013 async fn try_fetch_accounts(
2014 &self,
2015 _retry: usize,
2016 _instance: &NodeState,
2017 _height: u64,
2018 _view: ViewNumber,
2019 _fee_merkle_tree_root: FeeMerkleCommitment,
2020 _account: &[FeeAccount],
2021 ) -> anyhow::Result<Vec<FeeAccountProof>> {
2022 bail!("unsupported")
2023 }
2024
2025 async fn try_remember_blocks_merkle_tree(
2026 &self,
2027 _retry: usize,
2028 _instance: &NodeState,
2029 _height: u64,
2030 _view: ViewNumber,
2031 _mt: &mut BlockMerkleTree,
2032 ) -> anyhow::Result<()> {
2033 bail!("unsupported")
2034 }
2035
2036 async fn try_fetch_reward_merkle_tree_v2(
2037 &self,
2038 _retry: usize,
2039 _height: u64,
2040 _view: ViewNumber,
2041 _reward_merkle_tree_root: RewardMerkleCommitmentV2,
2042 _accounts: Arc<Vec<RewardAccountV2>>,
2043 ) -> anyhow::Result<PermittedRewardMerkleTreeV2> {
2044 bail!("unsupported")
2045 }
2046
2047 async fn try_fetch_reward_accounts_v1(
2048 &self,
2049 _retry: usize,
2050 _instance: &NodeState,
2051 _height: u64,
2052 _view: ViewNumber,
2053 _reward_merkle_tree_root: RewardMerkleCommitmentV1,
2054 _accounts: &[RewardAccountV1],
2055 ) -> anyhow::Result<Vec<RewardAccountProofV1>> {
2056 bail!("unsupported")
2057 }
2058
2059 async fn try_fetch_state_cert(
2060 &self,
2061 _retry: usize,
2062 _epoch: u64,
2063 ) -> anyhow::Result<LightClientStateUpdateCertificateV2<SeqTypes>> {
2064 bail!("unsupported")
2065 }
2066
2067 fn backoff(&self) -> &BackoffParams {
2068 &self.backoff
2069 }
2070
2071 fn name(&self) -> String {
2072 format!(
2073 "DelayedCatchup({})",
2074 if self.local { "local" } else { "remote" }
2075 )
2076 }
2077
2078 fn is_local(&self) -> bool {
2079 self.local
2080 }
2081 }
2082
2083 #[tokio::test(start_paused = true)]
2084 async fn test_remote_result_does_not_wait_for_local() {
2085 let local = DelayedCatchup::provider(true, Duration::from_secs(60));
2086 let remote = DelayedCatchup::provider(false, Duration::ZERO);
2087 let catchup = ParallelStateCatchup::new(&[local, remote], Duration::from_secs(30));
2088
2089 let config = ChainConfig::default();
2090 timeout(
2091 Duration::from_secs(1),
2092 catchup.try_fetch_chain_config(0, config.commit()),
2093 )
2094 .await
2095 .expect("remote result should not wait for the local provider")
2096 .unwrap();
2097 }
2098
2099 #[tokio::test(start_paused = true)]
2100 async fn test_local_result_does_not_wait_for_remote() {
2101 let local = DelayedCatchup::provider(true, Duration::ZERO);
2102 let remote = DelayedCatchup::provider(false, Duration::from_secs(60));
2103 let catchup = ParallelStateCatchup::new(&[local, remote], Duration::from_secs(30));
2104
2105 let config = ChainConfig::default();
2106 timeout(
2107 Duration::from_secs(1),
2108 catchup.fetch_chain_config(config.commit()),
2109 )
2110 .await
2111 .expect("local result should not wait for the remote provider")
2112 .unwrap();
2113 }
2114
2115 #[tokio::test(start_paused = true)]
2116 async fn test_all_providers_failing_reports_every_error() {
2117 let local = DelayedCatchup::provider(true, Duration::ZERO);
2118 let remote = DelayedCatchup::provider(false, Duration::ZERO);
2119 let catchup = ParallelStateCatchup::new(&[local, remote], Duration::from_secs(30));
2120
2121 let err = format!("{:#}", catchup.fetch_state_cert(1).await.unwrap_err());
2122 assert_eq!(err.matches("unsupported").count(), 2, "{err}");
2123 assert!(err.contains("DelayedCatchup(local)"), "{err}");
2124 assert!(err.contains("DelayedCatchup(remote)"), "{err}");
2125 }
2126
2127 const FALLBACK_DELAY: Duration = Duration::from_secs(2);
2128
2129 fn fallback_catchup(
2130 primary: Arc<dyn StateCatchup>,
2131 fallback: Arc<dyn StateCatchup>,
2132 ) -> ParallelStateCatchup {
2133 let catchup = ParallelStateCatchup::new(&[primary], Duration::from_secs(30))
2134 .with_fallback_delay(FALLBACK_DELAY);
2135 catchup.add_fallback_provider(fallback);
2136 catchup
2137 }
2138
2139 #[tokio::test(start_paused = true)]
2140 async fn test_fallback_not_called_when_primary_answers_first() {
2141 let (primary, _) = DelayedCatchup::counted(false, Duration::from_secs(1));
2142 let (fallback, fallback_calls) = DelayedCatchup::counted(false, Duration::ZERO);
2143 let catchup = fallback_catchup(primary, fallback);
2144
2145 catchup
2146 .fetch_chain_config(ChainConfig::default().commit())
2147 .await
2148 .unwrap();
2149 tokio::time::sleep(FALLBACK_DELAY * 2).await;
2150 assert_eq!(fallback_calls.load(AtomicOrdering::SeqCst), 0);
2151 }
2152
2153 #[tokio::test(start_paused = true)]
2154 async fn test_fallback_answers_after_delay_when_primary_hangs() {
2155 let (primary, _) = DelayedCatchup::counted(false, Duration::from_secs(3600));
2156 let (fallback, fallback_calls) = DelayedCatchup::counted(false, Duration::ZERO);
2157 let catchup = fallback_catchup(primary, fallback);
2158
2159 let start = tokio::time::Instant::now();
2160 catchup
2161 .fetch_chain_config(ChainConfig::default().commit())
2162 .await
2163 .unwrap();
2164 assert_eq!(start.elapsed(), FALLBACK_DELAY);
2165 assert_eq!(fallback_calls.load(AtomicOrdering::SeqCst), 1);
2166 }
2167
2168 #[tokio::test(start_paused = true)]
2169 async fn test_fallback_immediate_without_primary_remote() {
2170 let local = DelayedCatchup::provider(true, Duration::from_secs(3600));
2171 let (fallback, fallback_calls) = DelayedCatchup::counted(false, Duration::ZERO);
2172 let catchup = fallback_catchup(local, fallback);
2173
2174 let start = tokio::time::Instant::now();
2175 catchup
2176 .fetch_chain_config(ChainConfig::default().commit())
2177 .await
2178 .unwrap();
2179 assert_eq!(start.elapsed(), Duration::ZERO);
2180 assert_eq!(fallback_calls.load(AtomicOrdering::SeqCst), 1);
2181 }
2182
2183 #[tokio::test(start_paused = true)]
2184 async fn test_fallback_delay_is_decided_per_call() {
2185 let (fallback, fallback_calls) = DelayedCatchup::counted(false, Duration::ZERO);
2186 let catchup = ParallelStateCatchup::new(&[], Duration::from_secs(30))
2187 .with_fallback_delay(FALLBACK_DELAY);
2188 catchup.add_fallback_provider(fallback);
2189 let config = ChainConfig::default();
2190
2191 let start = tokio::time::Instant::now();
2192 catchup.fetch_chain_config(config.commit()).await.unwrap();
2193 assert_eq!(start.elapsed(), Duration::ZERO);
2194
2195 catchup.add_provider(DelayedCatchup::provider(false, Duration::from_secs(3600)));
2196 let start = tokio::time::Instant::now();
2197 catchup.fetch_chain_config(config.commit()).await.unwrap();
2198 assert_eq!(start.elapsed(), FALLBACK_DELAY);
2199 assert_eq!(fallback_calls.load(AtomicOrdering::SeqCst), 2);
2200 }
2201}