Skip to main content

espresso_node/
catchup.rs

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, BlockMerkleTree, Certificate2, FeeAccount, FeeAccountProof, FeeMerkleCommitment,
16    FeeMerkleTree, Leaf2, NodeState, SeqTypes, ValidatedState,
17    config::PublicNetworkConfig,
18    v0::traits::StateCatchup,
19    v0_3::{
20        ChainConfig, RewardAccountProofV1, RewardAccountV1, RewardMerkleCommitmentV1,
21        RewardMerkleTreeV1,
22    },
23    v0_4::{
24        PermittedRewardMerkleTreeV2, RewardAccountProofV2, RewardAccountV2,
25        RewardMerkleCommitmentV2, RewardMerkleTreeV2, forgotten_accounts_include,
26    },
27};
28use futures::{
29    StreamExt,
30    future::{Future, FutureExt, TryFuture, TryFutureExt},
31    stream::FuturesUnordered,
32};
33use hotshot_new_protocol::{storage::NewProtocolStorage, utils::verify_new_protocol_leaf_chain};
34use hotshot_types::{
35    ValidatorConfig,
36    data::ViewNumber,
37    epoch_membership::EpochMembershipCoordinator,
38    message::UpgradeLock,
39    network::NetworkConfig,
40    simple_certificate::LightClientStateUpdateCertificateV2,
41    traits::{
42        ValidatedState as ValidatedStateTrait,
43        metrics::{Counter, CounterFamily, Metrics},
44    },
45    utils::verify_leaf_chain,
46};
47use http_client::{Request, error::ClientErr};
48use itertools::Itertools;
49use jf_merkle_tree_compat::{ForgetableMerkleTreeScheme, MerkleTreeScheme, prelude::MerkleNode};
50use parking_lot::Mutex;
51use priority_queue::PriorityQueue;
52use serde::de::DeserializeOwned;
53use tokio::time::timeout;
54use tokio_util::task::AbortOnDropHandle;
55use url::Url;
56use vbs::version::StaticVersionType;
57use versions::{EPOCH_VERSION, NEW_PROTOCOL_VERSION};
58
59use crate::{
60    api::{BlocksFrontier, RewardMerkleTreeDataSource, RewardMerkleTreeV2Data},
61    consensus_handle::ConsensusHandle,
62};
63
64// This newtype is probably not worth having. It's only used to be able to log
65// URLs before doing requests.
66#[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/// A score of a catchup peer, based on our interactions with that peer.
94///
95/// The score accounts for malicious peers -- i.e. peers that gave us an invalid response to a
96/// verifiable request -- and faulty/unreliable peers -- those that fail to respond to requests at
97/// all. The score has a comparison function where higher is better, or in other words `p1 > p2`
98/// means we believe we are more likely to successfully catch up using `p1` than `p2`. This makes it
99/// convenient and efficient to collect peers in a priority queue which we can easily convert to a
100/// list sorted by reliability.
101#[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        // Compare failure rates: `self` is better than `other` if
110        //      self.failures / self.requests < other.failures / other.requests
111        // or equivalently
112        //      other.failures * self.requests > self.failures * other.requests
113        (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    // Peer IDs, ordered by reliability score. Each ID is an index into `clients`.
134    scores: Arc<RwLock<PriorityQueue<usize, PeerScore>>>,
135    clients: Vec<Client<ApiVer>>,
136    backoff: BackoffParams,
137    /// Base timeout for per peer catchup request
138    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        // Since we have generally have multiple peers we can catch up from, we want a fairly
151        // aggressive timeout for requests: if a peer is not responding quickly, we're better off
152        // just trying the next one rather than waiting, and this prevents a malicious peer from
153        // delaying catchup for a long time.
154        //
155        // However, if we set the timeout _too_ aggressively, we might fail to catch up even from an
156        // honest peer, and thus never make progress. Thus, we start with a base timeout (default
157        // 2s), which is reasonable for an HTTP request. If that fails with all of our peers, we
158        // increase the timeout by the base amount for each successive retry, until we eventually
159        // succeed. The base timeout is configurable via ESPRESSO_NODE_CATCHUP_BASE_TIMEOUT.
160        let timeout_dur = self.base_timeout * (retry as u32 + 1);
161
162        // Keep track of which peers we make requests to and which succeed (`true`) or fail (`false`),
163        // so we can update reliability scores at the end.
164        let mut requests = HashMap::new();
165        let mut res = Err(anyhow!("failed fetching from every peer"));
166
167        // Try each peer in order of reliability score, until we succeed. We clone out of
168        // `self.scores` because it is small (contains only numeric IDs and scores), so this clone
169        // is a lot cheaper than holding the read lock the entire time we are making requests (which
170        // could be a while).
171        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        // Update client scores.
207        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
281/// Verify a legacy (pre-V6) leaf chain
282pub(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            // Verify proofs.
314            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        // Fetch the leaf chain. For new protocol heights this is a leaf range
385        // `[height..=cert2_height]`
386        // for legacy-protocol heights it's a 3-chain.
387        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            // The chain terminates at the leaf the cert2 finalizes, so fetch that cert2 by its
407            // exact height
408            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                // Try the catchup endpoint first which returns tree from consensuss decided state
444                // if not present, then fall back to
445                // the reward-state-v2 endpoint which returns from storage decided state
446                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            // Verify proofs.
504            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    /// Get the state of the requested `accounts`.
557    ///
558    /// The state is fetched from a snapshot at the given height and view, which _must_ correspond!
559    /// `height` is provided to simplify lookups for backends where data is not indexed by view.
560    /// This function is intended to be used for catchup, so `view` should be no older than the last
561    /// decided view.
562    ///
563    /// If successful, this function also returns the leaf from `view`, if it is available. This can
564    /// be used to add the recovered state to HotShot's state map, so that future requests can get
565    /// the state from memory rather than storage.
566    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        // Merklized state catchup is only supported by persistence backends that provide merklized
574        // state storage. This default implementation is overridden for those that do. Otherwise,
575        // catchup can still be provided by fetching undecided merklized state from consensus
576        // memory.
577        async {
578            bail!("merklized state catchup is not supported for this data source");
579        }
580    }
581
582    fn get_reward_accounts_v1(
583        &self,
584        _instance: &NodeState,
585        _height: u64,
586        _view: ViewNumber,
587        _accounts: &[RewardAccountV1],
588    ) -> impl Send + Future<Output = anyhow::Result<(RewardMerkleTreeV1, Leaf2)>> {
589        async {
590            bail!("merklized state catchup is not supported for this data source");
591        }
592    }
593
594    fn get_reward_accounts_v2(
595        &self,
596        _instance: &NodeState,
597        _height: u64,
598        _view: ViewNumber,
599        _accounts: &[RewardAccountV2],
600    ) -> impl Send + Future<Output = anyhow::Result<(RewardMerkleTreeV2, Leaf2)>> {
601        async {
602            bail!("merklized state catchup is not supported for this data source");
603        }
604    }
605
606    /// Get the blocks Merkle tree frontier.
607    ///
608    /// The state is fetched from a snapshot at the given height and view, which _must_ correspond!
609    /// `height` is provided to simplify lookups for backends where data is not indexed by view.
610    /// This function is intended to be used for catchup, so `view` should be no older than the last
611    /// decided view.
612    fn get_frontier(
613        &self,
614        _instance: &NodeState,
615        _height: u64,
616        _view: ViewNumber,
617    ) -> impl Send + Future<Output = anyhow::Result<BlocksFrontier>> {
618        // Merklized state catchup is only supported by persistence backends that provide merklized
619        // state storage. This default implementation is overridden for those that do. Otherwise,
620        // catchup can still be provided by fetching undecided merklized state from consensus
621        // memory.
622        async {
623            bail!("merklized state catchup is not supported for this data source");
624        }
625    }
626
627    fn get_chain_config(
628        &self,
629        _commitment: Commitment<ChainConfig>,
630    ) -> impl Send + Future<Output = anyhow::Result<ChainConfig>> {
631        async {
632            bail!("chain config catchup is not supported for this data source");
633        }
634    }
635
636    fn get_leaf_chain(
637        &self,
638        _height: u64,
639    ) -> impl Send + Future<Output = anyhow::Result<Vec<Leaf2>>> {
640        async {
641            bail!("leaf chain catchup is not supported for this data source");
642        }
643    }
644
645    /// Load the cert2 stored at exactly `height`, if one exists.
646    fn load_cert2(
647        &self,
648        _height: u64,
649    ) -> impl Send + Future<Output = anyhow::Result<Option<Certificate2<SeqTypes>>>> {
650        async { Ok(None) }
651    }
652
653    /// Load a decided leaf at the given height.
654    fn get_leaf(&self, _height: u64) -> impl Send + Future<Output = anyhow::Result<Leaf2>> {
655        async {
656            bail!("leaf fetch is not supported for this data source");
657        }
658    }
659}
660
661impl CatchupStorage for hotshot_query_service::data_source::MetricsDataSource {}
662
663impl<T, S> CatchupStorage for hotshot_query_service::data_source::ExtensibleDataSource<T, S>
664where
665    T: CatchupStorage,
666    S: Sync,
667{
668    async fn get_accounts(
669        &self,
670        instance: &NodeState,
671        height: u64,
672        view: ViewNumber,
673        accounts: &[FeeAccount],
674    ) -> anyhow::Result<(FeeMerkleTree, Leaf2)> {
675        self.inner()
676            .get_accounts(instance, height, view, accounts)
677            .await
678    }
679
680    async fn get_reward_accounts_v2(
681        &self,
682        instance: &NodeState,
683        height: u64,
684        view: ViewNumber,
685        accounts: &[RewardAccountV2],
686    ) -> anyhow::Result<(RewardMerkleTreeV2, Leaf2)> {
687        self.inner()
688            .get_reward_accounts_v2(instance, height, view, accounts)
689            .await
690    }
691
692    async fn get_reward_accounts_v1(
693        &self,
694        instance: &NodeState,
695        height: u64,
696        view: ViewNumber,
697        accounts: &[RewardAccountV1],
698    ) -> anyhow::Result<(RewardMerkleTreeV1, Leaf2)> {
699        self.inner()
700            .get_reward_accounts_v1(instance, height, view, accounts)
701            .await
702    }
703
704    async fn get_frontier(
705        &self,
706        instance: &NodeState,
707        height: u64,
708        view: ViewNumber,
709    ) -> anyhow::Result<BlocksFrontier> {
710        self.inner().get_frontier(instance, height, view).await
711    }
712
713    async fn get_chain_config(
714        &self,
715        commitment: Commitment<ChainConfig>,
716    ) -> anyhow::Result<ChainConfig> {
717        self.inner().get_chain_config(commitment).await
718    }
719    async fn get_leaf_chain(&self, height: u64) -> anyhow::Result<Vec<Leaf2>> {
720        self.inner().get_leaf_chain(height).await
721    }
722
723    async fn load_cert2(&self, height: u64) -> anyhow::Result<Option<Certificate2<SeqTypes>>> {
724        self.inner().load_cert2(height).await
725    }
726
727    async fn get_leaf(&self, height: u64) -> anyhow::Result<Leaf2> {
728        self.inner().get_leaf(height).await
729    }
730}
731
732#[derive(Debug)]
733pub(crate) struct SqlStateCatchup<T> {
734    db: Arc<T>,
735    backoff: BackoffParams,
736}
737
738impl<T> SqlStateCatchup<T> {
739    pub(crate) fn new(db: Arc<T>, backoff: BackoffParams) -> Self {
740        Self { db, backoff }
741    }
742}
743
744#[async_trait]
745impl<T> StateCatchup for SqlStateCatchup<T>
746where
747    T: CatchupStorage + RewardMerkleTreeDataSource + Send + Sync,
748{
749    async fn try_fetch_leaf(
750        &self,
751        _retry: usize,
752        _coordinator: EpochMembershipCoordinator<SeqTypes>,
753        height: u64,
754    ) -> anyhow::Result<Leaf2> {
755        // Leaves in our local DB were verified before they were stored, so we can return the leaf
756        // at `height` directly without re-verifying.
757        self.db
758            .get_leaf(height)
759            .await
760            .with_context(|| format!("failed to load leaf at height {height} from DB"))
761    }
762
763    // TODO: add a test for the account proof validation
764    // issue # 2102 (https://github.com/EspressoSystems/espresso-network/issues/2102)
765    #[tracing::instrument(skip(self, _retry, instance))]
766    async fn try_fetch_accounts(
767        &self,
768        _retry: usize,
769        instance: &NodeState,
770        block_height: u64,
771        view: ViewNumber,
772        fee_merkle_tree_root: FeeMerkleCommitment,
773        accounts: &[FeeAccount],
774    ) -> anyhow::Result<Vec<FeeAccountProof>> {
775        // Get the accounts
776        let (fee_merkle_tree_from_db, _) = self
777            .db
778            .get_accounts(instance, block_height, view, accounts)
779            .await
780            .with_context(|| "failed to get fee accounts from DB")?;
781
782        // Verify the accounts
783        let mut proofs = Vec::new();
784        for account in accounts {
785            let (proof, _) = FeeAccountProof::prove(&fee_merkle_tree_from_db, (*account).into())
786                .context(format!("response missing account {account}"))?;
787            proof.verify(&fee_merkle_tree_root).context(format!(
788                "invalid proof for fee account {account}, root: {fee_merkle_tree_root}"
789            ))?;
790            proofs.push(proof);
791        }
792
793        Ok(proofs)
794    }
795
796    #[tracing::instrument(skip(self, _retry, instance, mt))]
797    async fn try_remember_blocks_merkle_tree(
798        &self,
799        _retry: usize,
800        instance: &NodeState,
801        bh: u64,
802        view: ViewNumber,
803        mt: &mut BlockMerkleTree,
804    ) -> anyhow::Result<()> {
805        if bh == 0 {
806            return Ok(());
807        }
808
809        let proof = self.db.get_frontier(instance, bh, view).await?;
810        match proof
811            .proof
812            .first()
813            .context(format!("empty proof for frontier at height {bh}"))?
814        {
815            MerkleNode::Leaf { pos, elem, .. } => mt
816                .remember(pos, elem, proof.clone())
817                .context("failed to remember proof"),
818            _ => bail!("invalid proof"),
819        }
820    }
821
822    async fn try_fetch_chain_config(
823        &self,
824        _retry: usize,
825        commitment: Commitment<ChainConfig>,
826    ) -> anyhow::Result<ChainConfig> {
827        let cf = self.db.get_chain_config(commitment).await?;
828
829        if cf.commit() != commitment {
830            panic!(
831                "Critical error: Mismatched chain config detected. Expected chain config: {:?}, \
832                 but got: {:?}.
833                This may indicate a compromised database",
834                commitment,
835                cf.commit()
836            )
837        }
838
839        Ok(cf)
840    }
841
842    async fn try_fetch_reward_merkle_tree_v2(
843        &self,
844        _retry: usize,
845        height: u64,
846        _view: ViewNumber,
847        reward_merkle_tree_root: RewardMerkleCommitmentV2,
848        accounts: Arc<Vec<RewardAccountV2>>,
849    ) -> anyhow::Result<PermittedRewardMerkleTreeV2> {
850        let tree: PermittedRewardMerkleTreeV2 = self.db.load_reward_merkle_tree_v2(height).await?;
851
852        ensure!(tree.tree.commitment() == reward_merkle_tree_root);
853        ensure!(!forgotten_accounts_include(&tree, &accounts));
854
855        Ok(tree)
856    }
857
858    #[tracing::instrument(skip(self, _retry, instance))]
859    async fn try_fetch_reward_accounts_v1(
860        &self,
861        _retry: usize,
862        instance: &NodeState,
863        block_height: u64,
864        view: ViewNumber,
865        reward_merkle_tree_root: RewardMerkleCommitmentV1,
866        accounts: &[RewardAccountV1],
867    ) -> anyhow::Result<Vec<RewardAccountProofV1>> {
868        // Get the accounts
869        let (reward_merkle_tree_from_db, _) = self
870            .db
871            .get_reward_accounts_v1(instance, block_height, view, accounts)
872            .await
873            .with_context(|| "failed to get reward accounts from DB")?;
874        // Verify the accounts
875        let mut proofs = Vec::new();
876        for account in accounts {
877            let (proof, _) =
878                RewardAccountProofV1::prove(&reward_merkle_tree_from_db, (*account).into())
879                    .context(format!("response missing account {account}"))?;
880            proof.verify(&reward_merkle_tree_root).context(format!(
881                "invalid proof for v1 reward account {account}, root: {reward_merkle_tree_root}"
882            ))?;
883            proofs.push(proof);
884        }
885
886        Ok(proofs)
887    }
888
889    async fn try_fetch_state_cert(
890        &self,
891        _retry: usize,
892        _epoch: u64,
893    ) -> anyhow::Result<LightClientStateUpdateCertificateV2<SeqTypes>> {
894        bail!("state cert catchup not supported for SqlStateCatchup");
895    }
896
897    fn backoff(&self) -> &BackoffParams {
898        &self.backoff
899    }
900
901    fn name(&self) -> String {
902        "SqlStateCatchup".into()
903    }
904
905    fn is_local(&self) -> bool {
906        true
907    }
908}
909
910/// Disable catchup entirely.
911#[derive(Clone, Debug)]
912pub struct NullStateCatchup {
913    backoff: BackoffParams,
914    chain_configs: HashMap<Commitment<ChainConfig>, ChainConfig>,
915}
916
917impl Default for NullStateCatchup {
918    fn default() -> Self {
919        Self {
920            backoff: BackoffParams::disabled(),
921            chain_configs: Default::default(),
922        }
923    }
924}
925
926impl NullStateCatchup {
927    /// Add a chain config preimage which can be fetched by hash during STF evaluation.
928    ///
929    /// [`NullStateCatchup`] is used to disable catchup entirely when evaluating the STF, which
930    /// requires the [`ValidatedState`](espresso_types::ValidatedState) to be pre-seeded with all
931    /// the dependencies of STF evaluation. However, the STF also depends on having the preimage of
932    /// various [`ChainConfig`] commitments, which are not stored in the
933    /// [`ValidatedState`](espresso_types::ValidatedState), but which instead must be supplied by a
934    /// separate preimage oracle. Thus, [`NullStateCatchup`] may be populated with a set of
935    /// [`ChainConfig`]s, which it can feed to the STF during evaluation.
936    pub fn add_chain_config(&mut self, cf: ChainConfig) {
937        self.chain_configs.insert(cf.commit(), cf);
938    }
939}
940
941#[async_trait]
942impl StateCatchup for NullStateCatchup {
943    async fn try_fetch_leaf(
944        &self,
945        _retry: usize,
946        _coordinator: EpochMembershipCoordinator<SeqTypes>,
947        _height: u64,
948    ) -> anyhow::Result<Leaf2> {
949        bail!("state catchup is disabled")
950    }
951
952    async fn try_fetch_accounts(
953        &self,
954        _retry: usize,
955        _instance: &NodeState,
956        _height: u64,
957        _view: ViewNumber,
958        _fee_merkle_tree_root: FeeMerkleCommitment,
959        _account: &[FeeAccount],
960    ) -> anyhow::Result<Vec<FeeAccountProof>> {
961        bail!("state catchup is disabled");
962    }
963
964    async fn try_remember_blocks_merkle_tree(
965        &self,
966        _retry: usize,
967        _instance: &NodeState,
968        _height: u64,
969        _view: ViewNumber,
970        _mt: &mut BlockMerkleTree,
971    ) -> anyhow::Result<()> {
972        bail!("state catchup is disabled");
973    }
974
975    async fn try_fetch_chain_config(
976        &self,
977        _retry: usize,
978        commitment: Commitment<ChainConfig>,
979    ) -> anyhow::Result<ChainConfig> {
980        self.chain_configs
981            .get(&commitment)
982            .copied()
983            .context(format!("chain config {commitment} not available"))
984    }
985
986    async fn try_fetch_reward_merkle_tree_v2(
987        &self,
988        _retry: usize,
989        _height: u64,
990        _view: ViewNumber,
991        _reward_merkle_tree_root: RewardMerkleCommitmentV2,
992        _accounts: Arc<Vec<RewardAccountV2>>,
993    ) -> anyhow::Result<PermittedRewardMerkleTreeV2> {
994        bail!("state catchup is disabled");
995    }
996
997    async fn try_fetch_reward_accounts_v1(
998        &self,
999        _retry: usize,
1000        _instance: &NodeState,
1001        _height: u64,
1002        _view: ViewNumber,
1003        _fee_merkle_tree_root: RewardMerkleCommitmentV1,
1004        _account: &[RewardAccountV1],
1005    ) -> anyhow::Result<Vec<RewardAccountProofV1>> {
1006        bail!("state catchup is disabled");
1007    }
1008
1009    async fn try_fetch_state_cert(
1010        &self,
1011        _retry: usize,
1012        _epoch: u64,
1013    ) -> anyhow::Result<LightClientStateUpdateCertificateV2<SeqTypes>> {
1014        bail!("state catchup is disabled");
1015    }
1016
1017    fn backoff(&self) -> &BackoffParams {
1018        &self.backoff
1019    }
1020
1021    fn name(&self) -> String {
1022        "NullStateCatchup".into()
1023    }
1024
1025    fn is_local(&self) -> bool {
1026        true
1027    }
1028}
1029
1030/// A catchup implementation that parallelizes requests to many providers.
1031/// It returns the result of the first non-erroring provider to complete.
1032#[derive(Clone)]
1033pub struct ParallelStateCatchup {
1034    providers: Arc<Mutex<Vec<Arc<dyn StateCatchup>>>>,
1035    backoff: BackoffParams,
1036    /// Timeout for local provider requests
1037    local_timeout: Duration,
1038}
1039
1040impl ParallelStateCatchup {
1041    /// Create a new [`ParallelStateCatchup`] with the given providers and local timeout.
1042    pub fn new(providers: &[Arc<dyn StateCatchup>], local_timeout: Duration) -> Self {
1043        Self {
1044            providers: Arc::new(Mutex::new(providers.to_vec())),
1045            backoff: BackoffParams::disabled(),
1046            local_timeout,
1047        }
1048    }
1049
1050    /// Add a provider to the list of providers
1051    pub fn add_provider(&self, provider: Arc<dyn StateCatchup>) {
1052        self.providers.lock().push(provider);
1053    }
1054
1055    /// Perform an async operation on all local providers, returning the first result to succeed.
1056    ///
1057    /// A timeout is applied so that a slow local lookup does not prevent the node from
1058    /// falling back to remote providers in time to vote within the current view.
1059    pub async fn on_local_providers<C, F, RT>(&self, closure: C) -> anyhow::Result<RT>
1060    where
1061        C: Fn(Arc<dyn StateCatchup>) -> F + Clone + Send + Sync + 'static,
1062        F: Future<Output = anyhow::Result<RT>> + Send + 'static,
1063        RT: Send + Sync + 'static,
1064    {
1065        let local_timeout = self.local_timeout;
1066        match timeout(
1067            local_timeout,
1068            self.on_providers(|provider| provider.is_local(), closure),
1069        )
1070        .await
1071        {
1072            Ok(result) => result,
1073            Err(_) => {
1074                let err = format!("local provider timed out after {local_timeout:?}");
1075                tracing::warn!("{err}");
1076                Err(anyhow::anyhow!(err))
1077            },
1078        }
1079    }
1080
1081    /// Perform an async operation on all remote providers, returning the first result to succeed
1082    pub async fn on_remote_providers<C, F, RT>(&self, closure: C) -> anyhow::Result<RT>
1083    where
1084        C: Fn(Arc<dyn StateCatchup>) -> F + Clone + Send + Sync + 'static,
1085        F: Future<Output = anyhow::Result<RT>> + Send + 'static,
1086        RT: Send + Sync + 'static,
1087    {
1088        self.on_providers(|provider| !provider.is_local(), closure)
1089            .await
1090    }
1091
1092    /// Perform an async operation on all providers matching the given predicate, returning the first result to succeed
1093    pub async fn on_providers<P, C, F, RT>(&self, predicate: P, closure: C) -> anyhow::Result<RT>
1094    where
1095        P: Fn(&Arc<dyn StateCatchup>) -> bool + Clone + Send + Sync + 'static,
1096        C: Fn(Arc<dyn StateCatchup>) -> F + Clone + Send + Sync + 'static,
1097        F: Future<Output = anyhow::Result<RT>> + Send + 'static,
1098        RT: Send + Sync + 'static,
1099    {
1100        // Make sure we have at least one provider
1101        let providers = self.providers.lock().clone();
1102        if providers.is_empty() {
1103            return Err(anyhow::anyhow!("no providers were initialized"));
1104        }
1105
1106        // Filter the providers by the predicate
1107        let providers = providers.into_iter().filter(predicate).collect::<Vec<_>>();
1108        if providers.is_empty() {
1109            return Err(anyhow::anyhow!("no providers matched the given predicate"));
1110        }
1111
1112        // Spawn futures for each provider
1113        let mut futures = FuturesUnordered::new();
1114        for provider in providers {
1115            let closure = closure.clone();
1116            futures.push(AbortOnDropHandle::new(tokio::spawn(closure(provider))));
1117        }
1118
1119        let mut logs = vec!["No providers returned a successful result.\n".to_string()];
1120        // Return the first successful result
1121        while let Some(result) = futures.next().await {
1122            // Unwrap the inner (join) result
1123            let result = match result {
1124                Ok(res) => res,
1125                Err(err) => {
1126                    tracing::debug!("Failed to join on provider: {err:#}.");
1127                    logs.push(format!("Failed to join on provider: {err:#}."));
1128                    continue;
1129                },
1130            };
1131
1132            // If a provider fails, print why
1133            let result = match result {
1134                Ok(res) => res,
1135                Err(err) => {
1136                    tracing::debug!("Failed to fetch data: {err:#}.");
1137                    logs.push(format!("Failed to fetch data: {err:#}."));
1138                    continue;
1139                },
1140            };
1141
1142            return Ok(result);
1143        }
1144
1145        Err(anyhow::anyhow!(logs.join("\n")))
1146    }
1147}
1148
1149macro_rules! clone {
1150    ( ($( $x:ident ),*) $y:expr ) => {
1151        {
1152            $(let $x = $x.clone();)*
1153            $y
1154        }
1155    };
1156}
1157
1158/// A catchup implementation that parallelizes requests to a local and remote provider.
1159/// It returns the result of the first provider to complete.
1160#[async_trait]
1161impl StateCatchup for ParallelStateCatchup {
1162    async fn try_fetch_leaf(
1163        &self,
1164        retry: usize,
1165        coordinator: EpochMembershipCoordinator<SeqTypes>,
1166        height: u64,
1167    ) -> anyhow::Result<Leaf2> {
1168        // Try fetching the leaf on the local providers first
1169        let local_result = self
1170            .on_local_providers(clone! {(coordinator) move |provider| {
1171                clone!{(coordinator) async move {
1172                    provider
1173                        .try_fetch_leaf(retry, coordinator, height)
1174                        .await
1175                }}
1176            }})
1177            .await;
1178
1179        // Check if we were successful locally
1180        match &local_result {
1181            Ok(_) => return local_result,
1182            Err(err) => tracing::debug!("{err:#}"),
1183        }
1184
1185        // If that fails, try the remote ones
1186        self.on_remote_providers(clone! {(coordinator) move |provider| {
1187            clone!{(coordinator) async move {
1188                provider
1189                    .try_fetch_leaf(retry, coordinator, height)
1190                    .await
1191            }}
1192        }})
1193        .await
1194    }
1195
1196    async fn try_fetch_accounts(
1197        &self,
1198        retry: usize,
1199        instance: &NodeState,
1200        height: u64,
1201        view: ViewNumber,
1202        fee_merkle_tree_root: FeeMerkleCommitment,
1203        accounts: &[FeeAccount],
1204    ) -> anyhow::Result<Vec<FeeAccountProof>> {
1205        // Try to get the accounts on local providers first
1206        let accounts_vec = accounts.to_vec();
1207        let local_result = self
1208            .on_local_providers(clone! {(instance, accounts_vec) move |provider| {
1209                clone! {(instance, accounts_vec) async move {
1210                    provider
1211                        .try_fetch_accounts(
1212                            retry,
1213                            &instance,
1214                            height,
1215                            view,
1216                            fee_merkle_tree_root,
1217                            &accounts_vec,
1218                        )
1219                        .await
1220                }}
1221            }})
1222            .await;
1223
1224        // Check if we were successful locally
1225        match &local_result {
1226            Ok(_) => return local_result,
1227            Err(err) => tracing::debug!("{err:#}"),
1228        }
1229
1230        // If that fails, try the remote ones
1231        self.on_remote_providers(clone! {(instance, accounts_vec) move |provider| {
1232            clone!{(instance, accounts_vec) async move {
1233                provider
1234                .try_fetch_accounts(
1235                    retry,
1236                    &instance,
1237                    height,
1238                    view,
1239                    fee_merkle_tree_root,
1240                    &accounts_vec,
1241                ).await
1242            }}
1243        }})
1244        .await
1245    }
1246
1247    async fn try_remember_blocks_merkle_tree(
1248        &self,
1249        retry: usize,
1250        instance: &NodeState,
1251        height: u64,
1252        view: ViewNumber,
1253        mt: &mut BlockMerkleTree,
1254    ) -> anyhow::Result<()> {
1255        // Try to remember the blocks merkle tree on local providers first
1256        let local_result = self
1257            .on_local_providers(clone! {(mt, instance) move |provider| {
1258                let mut mt = mt.clone();
1259                clone! {(instance) async move {
1260                    // Perform the call
1261                    provider
1262                        .try_remember_blocks_merkle_tree(
1263                            retry,
1264                            &instance,
1265                            height,
1266                            view,
1267                            &mut mt,
1268                        )
1269                        .await?;
1270
1271                    // Return the merkle tree so we can modify it
1272                    Ok(mt)
1273                }}
1274            }})
1275            .await;
1276
1277        // Check if we were successful locally
1278        if let Ok(modified_mt) = local_result {
1279            // Set the merkle tree to the output of the successful local call
1280            *mt = modified_mt;
1281
1282            return Ok(());
1283        }
1284
1285        // If that fails, try the remote ones
1286        let remote_result = self
1287            .on_remote_providers(clone! {(mt, instance) move |provider| {
1288                let mut mt = mt.clone();
1289                clone!{(instance) async move {
1290                    // Perform the call
1291                    provider
1292                    .try_remember_blocks_merkle_tree(
1293                        retry,
1294                        &instance,
1295                        height,
1296                        view,
1297                        &mut mt,
1298                    )
1299                    .await?;
1300
1301                    // Return the merkle tree
1302                    Ok(mt)
1303                }}
1304            }})
1305            .await?;
1306
1307        // Update the original, local merkle tree
1308        *mt = remote_result;
1309
1310        Ok(())
1311    }
1312
1313    async fn try_fetch_chain_config(
1314        &self,
1315        retry: usize,
1316        commitment: Commitment<ChainConfig>,
1317    ) -> anyhow::Result<ChainConfig> {
1318        // Try fetching the chain config on the local providers first
1319        let local_result = self
1320            .on_local_providers(move |provider| async move {
1321                provider.try_fetch_chain_config(retry, commitment).await
1322            })
1323            .await;
1324
1325        // Check if we were successful locally
1326        match &local_result {
1327            Ok(_) => return local_result,
1328            Err(err) => tracing::debug!("{err:#}"),
1329        }
1330
1331        // If that fails, try the remote ones
1332        self.on_remote_providers(move |provider| async move {
1333            provider.try_fetch_chain_config(retry, commitment).await
1334        })
1335        .await
1336    }
1337
1338    async fn try_fetch_reward_merkle_tree_v2(
1339        &self,
1340        retry: usize,
1341        height: u64,
1342        view: ViewNumber,
1343        reward_merkle_tree_root: RewardMerkleCommitmentV2,
1344        accounts: Arc<Vec<RewardAccountV2>>,
1345    ) -> anyhow::Result<PermittedRewardMerkleTreeV2> {
1346        let local_result = self
1347            .on_local_providers(clone! {(accounts) move |provider| {
1348                clone! {(accounts) async move {
1349                    provider
1350                        .try_fetch_reward_merkle_tree_v2(
1351                            retry,
1352                            height,
1353                            view,
1354                            reward_merkle_tree_root,
1355                            accounts,
1356                        )
1357                        .await
1358                }}
1359            }})
1360            .await;
1361
1362        // Check if we were successful locally
1363        match &local_result {
1364            Ok(_) => return local_result,
1365            Err(err) => tracing::debug!("{err:#}"),
1366        }
1367
1368        // If that fails, try the remote ones
1369        self.on_remote_providers(clone! {(accounts) move |provider| {
1370            clone!{(accounts) async move {
1371                provider
1372                .try_fetch_reward_merkle_tree_v2(
1373                    retry,
1374                    height,
1375                    view,
1376                    reward_merkle_tree_root,
1377                    accounts
1378                ).await
1379            }}
1380        }})
1381        .await
1382    }
1383
1384    async fn try_fetch_reward_accounts_v1(
1385        &self,
1386        retry: usize,
1387        instance: &NodeState,
1388        height: u64,
1389        view: ViewNumber,
1390        reward_merkle_tree_root: RewardMerkleCommitmentV1,
1391        accounts: &[RewardAccountV1],
1392    ) -> anyhow::Result<Vec<RewardAccountProofV1>> {
1393        // Try to get the accounts on local providers first
1394        let accounts_vec = accounts.to_vec();
1395        let local_result = self
1396            .on_local_providers(clone! {(instance, accounts_vec) move |provider| {
1397                clone! {(instance, accounts_vec) async move {
1398                    provider
1399                        .try_fetch_reward_accounts_v1(
1400                            retry,
1401                            &instance,
1402                            height,
1403                            view,
1404                            reward_merkle_tree_root,
1405                            &accounts_vec,
1406                        )
1407                        .await
1408                }}
1409            }})
1410            .await;
1411
1412        // Check if we were successful locally
1413        match &local_result {
1414            Ok(_) => return local_result,
1415            Err(err) => tracing::debug!("{err:#}"),
1416        }
1417
1418        // If that fails, try the remote ones
1419        self.on_remote_providers(clone! {(instance, accounts_vec) move |provider| {
1420            clone!{(instance, accounts_vec) async move {
1421                provider
1422                .try_fetch_reward_accounts_v1(
1423                    retry,
1424                    &instance,
1425                    height,
1426                    view,
1427                    reward_merkle_tree_root,
1428                    &accounts_vec,
1429                ).await
1430            }}
1431        }})
1432        .await
1433    }
1434
1435    async fn try_fetch_state_cert(
1436        &self,
1437        retry: usize,
1438        epoch: u64,
1439    ) -> anyhow::Result<LightClientStateUpdateCertificateV2<SeqTypes>> {
1440        // Try fetching the state cert on the local providers first
1441        let local_result = self
1442            .on_local_providers(move |provider| async move {
1443                provider.try_fetch_state_cert(retry, epoch).await
1444            })
1445            .await;
1446
1447        // Check if we were successful locally
1448        match &local_result {
1449            Ok(_) => return local_result,
1450            Err(err) => tracing::debug!("{err:#}"),
1451        }
1452
1453        // If that fails, try the remote ones
1454        self.on_remote_providers(move |provider| async move {
1455            provider.try_fetch_state_cert(retry, epoch).await
1456        })
1457        .await
1458    }
1459
1460    fn backoff(&self) -> &BackoffParams {
1461        &self.backoff
1462    }
1463
1464    fn name(&self) -> String {
1465        format!(
1466            "[{}]",
1467            self.providers
1468                .lock()
1469                .iter()
1470                .map(|p| p.name())
1471                .collect::<Vec<_>>()
1472                .join(", ")
1473        )
1474    }
1475
1476    async fn fetch_accounts(
1477        &self,
1478        instance: &NodeState,
1479        height: u64,
1480        view: ViewNumber,
1481        fee_merkle_tree_root: FeeMerkleCommitment,
1482        accounts: Vec<FeeAccount>,
1483    ) -> anyhow::Result<Vec<FeeAccountProof>> {
1484        // Try to get the accounts on local providers first
1485        let accounts_vec = accounts.to_vec();
1486        let local_result = self
1487            .on_local_providers(clone! {(instance, accounts_vec) move |provider| {
1488                clone! {(instance, accounts_vec) async move {
1489                    provider
1490                        .try_fetch_accounts(
1491                            0,
1492                            &instance,
1493                            height,
1494                            view,
1495                            fee_merkle_tree_root,
1496                            &accounts_vec,
1497                        )
1498                        .await
1499                }}
1500            }})
1501            .await;
1502
1503        // Check if we were successful locally
1504        match &local_result {
1505            Ok(_) => return local_result,
1506            Err(err) => tracing::debug!("{err:#}"),
1507        }
1508
1509        // If that fails, try the remote ones (with retry)
1510        self.on_remote_providers(clone! {(instance, accounts_vec) move |provider| {
1511            clone!{(instance, accounts_vec) async move {
1512                provider
1513                .fetch_accounts(
1514                    &instance,
1515                    height,
1516                    view,
1517                    fee_merkle_tree_root,
1518                    accounts_vec,
1519                ).await
1520            }}
1521        }})
1522        .await
1523    }
1524
1525    async fn fetch_leaf(
1526        &self,
1527        coordinator: EpochMembershipCoordinator<SeqTypes>,
1528        height: u64,
1529    ) -> anyhow::Result<Leaf2> {
1530        // Try fetching the leaf on the local providers first
1531        let local_result = self
1532            .on_local_providers(clone! {(coordinator) move |provider| {
1533                clone!{(coordinator) async move {
1534                    provider
1535                        .try_fetch_leaf(0, coordinator, height)
1536                        .await
1537                }}
1538            }})
1539            .await;
1540
1541        // Check if we were successful locally
1542        match &local_result {
1543            Ok(_) => return local_result,
1544            Err(err) => tracing::debug!("{err:#}"),
1545        }
1546
1547        // If that fails, try the remote ones (with retry)
1548        self.on_remote_providers(clone! {(coordinator) move |provider| {
1549        clone!{(coordinator) async move {
1550            provider
1551                .fetch_leaf(coordinator, height)
1552                .await
1553        }}
1554        }})
1555        .await
1556    }
1557
1558    async fn fetch_chain_config(
1559        &self,
1560        commitment: Commitment<ChainConfig>,
1561    ) -> anyhow::Result<ChainConfig> {
1562        // Try fetching the chain config on the local providers first
1563        let local_result = self
1564            .on_local_providers(move |provider| async move {
1565                provider.try_fetch_chain_config(0, commitment).await
1566            })
1567            .await;
1568
1569        // Check if we were successful locally
1570        match &local_result {
1571            Ok(_) => return local_result,
1572            Err(err) => tracing::debug!("{err:#}"),
1573        }
1574
1575        // If that fails, try the remote ones (with retry)
1576        self.on_remote_providers(move |provider| async move {
1577            provider.fetch_chain_config(commitment).await
1578        })
1579        .await
1580    }
1581
1582    async fn fetch_reward_accounts_v1(
1583        &self,
1584        instance: &NodeState,
1585        height: u64,
1586        view: ViewNumber,
1587        reward_merkle_tree_root: RewardMerkleCommitmentV1,
1588        accounts: Vec<RewardAccountV1>,
1589    ) -> anyhow::Result<Vec<RewardAccountProofV1>> {
1590        // Try to get the accounts on local providers first
1591        let accounts_vec = accounts.to_vec();
1592        let local_result = self
1593            .on_local_providers(clone! {(instance, accounts_vec) move |provider| {
1594                clone! {(instance, accounts_vec) async move {
1595                    provider
1596                        .try_fetch_reward_accounts_v1(
1597                            0,
1598                            &instance,
1599                            height,
1600                            view,
1601                            reward_merkle_tree_root,
1602                            &accounts_vec,
1603                        )
1604                        .await
1605                }}
1606            }})
1607            .await;
1608
1609        // Check if we were successful locally
1610        match &local_result {
1611            Ok(_) => return local_result,
1612            Err(err) => tracing::debug!("{err:#}"),
1613        }
1614
1615        // If that fails, try the remote ones (with retry)
1616        self.on_remote_providers(clone! {(instance, accounts_vec) move |provider| {
1617            clone!{(instance, accounts_vec) async move {
1618                provider
1619                .fetch_reward_accounts_v1(
1620                    &instance,
1621                    height,
1622                    view,
1623                    reward_merkle_tree_root,
1624                    accounts_vec,
1625                ).await
1626            }}
1627        }})
1628        .await
1629    }
1630
1631    async fn fetch_state_cert(
1632        &self,
1633        epoch: u64,
1634    ) -> anyhow::Result<LightClientStateUpdateCertificateV2<SeqTypes>> {
1635        let local_result = self
1636            .on_local_providers(move |provider| async move {
1637                provider.try_fetch_state_cert(0, epoch).await
1638            })
1639            .await;
1640
1641        // Check if we were successful locally
1642        match &local_result {
1643            Ok(_) => return local_result,
1644            Err(err) => tracing::debug!("{err:#}"),
1645        }
1646
1647        // If that fails, try the remote ones (with retry)
1648        self.on_remote_providers(
1649            move |provider| async move { provider.fetch_state_cert(epoch).await },
1650        )
1651        .await
1652    }
1653
1654    async fn remember_blocks_merkle_tree(
1655        &self,
1656        instance: &NodeState,
1657        height: u64,
1658        view: ViewNumber,
1659        mt: &mut BlockMerkleTree,
1660    ) -> anyhow::Result<()> {
1661        // Try to remember the blocks merkle tree on local providers first
1662        let local_result = self
1663            .on_local_providers(clone! {(mt, instance) move |provider| {
1664                let mut mt = mt.clone();
1665                clone! {(instance) async move {
1666                    // Perform the call
1667                    provider
1668                        .try_remember_blocks_merkle_tree(
1669                            0,
1670                            &instance,
1671                            height,
1672                            view,
1673                            &mut mt,
1674                        )
1675                        .await?;
1676
1677                    // Return the merkle tree so we can modify it
1678                    Ok(mt)
1679                }}
1680            }})
1681            .await;
1682
1683        // Check if we were successful locally
1684        if let Ok(modified_mt) = local_result {
1685            // Set the merkle tree to the one with the
1686            // successful call
1687            *mt = modified_mt;
1688
1689            return Ok(());
1690        }
1691
1692        // If that fails, try the remote ones (with retry)
1693        let remote_result = self
1694            .on_remote_providers(clone! {(mt, instance) move |provider| {
1695                let mut mt = mt.clone();
1696                clone!{(instance) async move {
1697                    // Perform the call
1698                    provider
1699                    .remember_blocks_merkle_tree(
1700                        &instance,
1701                        height,
1702                        view,
1703                        &mut mt,
1704                    )
1705                    .await?;
1706
1707                    // Return the merkle tree
1708                    Ok(mt)
1709                }}
1710            }})
1711            .await?;
1712
1713        // Update the original, local merkle tree
1714        *mt = remote_result;
1715
1716        Ok(())
1717    }
1718
1719    fn is_local(&self) -> bool {
1720        self.providers.lock().iter().all(|p| p.is_local())
1721    }
1722}
1723
1724/// Add accounts to the in-memory consensus state.
1725/// We use this during catchup after receiving verified accounts.
1726pub async fn add_fee_accounts_to_state<I: hotshot::traits::NodeImplementation<SeqTypes>>(
1727    consensus_handle: &ConsensusHandle<SeqTypes, I>,
1728    view: &ViewNumber,
1729    accounts: &[FeeAccount],
1730    tree: &FeeMerkleTree,
1731    leaf: Leaf2,
1732) -> anyhow::Result<()>
1733where
1734    I::Storage: NewProtocolStorage<SeqTypes>,
1735{
1736    let (existing_state, delta) = consensus_handle.state_and_delta(*view).await;
1737    let (state, delta) = match existing_state {
1738        Some(existing) => {
1739            let mut state = (*existing).clone();
1740            for account in accounts {
1741                if let Some((proof, _)) = FeeAccountProof::prove(tree, (*account).into()) {
1742                    if let Err(err) = proof.remember(&mut state.fee_merkle_tree) {
1743                        tracing::warn!(
1744                            ?view,
1745                            %account,
1746                            "cannot update fetched account state: {err:#}"
1747                        );
1748                    }
1749                } else {
1750                    tracing::warn!(?view, %account, "cannot update fetched account state because account is not in the merkle tree");
1751                };
1752            }
1753            (Arc::new(state), delta)
1754        },
1755        None => {
1756            // If we don't already have a leaf for this view, or if we don't have the view
1757            // at all, we can create a new view based on the recovered leaf and add it to
1758            // our state map. In this case, we must also add the leaf to the saved leaves
1759            // map to ensure consistency.
1760            let mut state = ValidatedState::from_header(leaf.block_header());
1761            state.fee_merkle_tree = tree.clone();
1762            (Arc::new(state), None)
1763        },
1764    };
1765
1766    consensus_handle
1767        .update_leaf(leaf, state, delta)
1768        .await
1769        .with_context(|| "failed to update leaf")?;
1770
1771    Ok(())
1772}
1773
1774/// Add accounts to the in-memory consensus state.
1775/// We use this during catchup after receiving verified accounts.
1776pub async fn add_v2_reward_accounts_to_state<I: hotshot::traits::NodeImplementation<SeqTypes>>(
1777    consensus_handle: &ConsensusHandle<SeqTypes, I>,
1778    view: &ViewNumber,
1779    accounts: &[RewardAccountV2],
1780    tree: &RewardMerkleTreeV2,
1781    leaf: Leaf2,
1782) -> anyhow::Result<()>
1783where
1784    I::Storage: NewProtocolStorage<SeqTypes>,
1785{
1786    let (existing_state, delta) = consensus_handle.state_and_delta(*view).await;
1787    let (state, delta) = match existing_state {
1788        Some(existing) => {
1789            let mut state = (*existing).clone();
1790            for account in accounts {
1791                if let Some((proof, _)) = RewardAccountProofV2::prove(tree, (*account).into()) {
1792                    if let Err(err) = proof.remember(&mut state.reward_merkle_tree_v2) {
1793                        tracing::warn!(
1794                            ?view,
1795                            %account,
1796                            "cannot update fetched account state: {err:#}"
1797                        );
1798                    }
1799                } else {
1800                    tracing::warn!(?view, %account, "cannot update fetched account state because account is not in the merkle tree");
1801                };
1802            }
1803            (Arc::new(state), delta)
1804        },
1805        None => {
1806            // If we don't already have a leaf for this view, or if we don't have the view
1807            // at all, we can create a new view based on the recovered leaf and add it to
1808            // our state map. In this case, we must also add the leaf to the saved leaves
1809            // map to ensure consistency.
1810            let mut state = ValidatedState::from_header(leaf.block_header());
1811            state.reward_merkle_tree_v2 = tree.clone();
1812            (Arc::new(state), None)
1813        },
1814    };
1815
1816    consensus_handle
1817        .update_leaf(leaf, state, delta)
1818        .await
1819        .with_context(|| "failed to update leaf")?;
1820
1821    Ok(())
1822}
1823
1824/// Add accounts to the in-memory consensus state.
1825/// We use this during catchup after receiving verified accounts.
1826pub async fn add_v1_reward_accounts_to_state<I: hotshot::traits::NodeImplementation<SeqTypes>>(
1827    consensus_handle: &ConsensusHandle<SeqTypes, I>,
1828    view: &ViewNumber,
1829    accounts: &[RewardAccountV1],
1830    tree: &RewardMerkleTreeV1,
1831    leaf: Leaf2,
1832) -> anyhow::Result<()>
1833where
1834    I::Storage: NewProtocolStorage<SeqTypes>,
1835{
1836    let (existing_state, delta) = consensus_handle.state_and_delta(*view).await;
1837    let (state, delta) = match existing_state {
1838        Some(existing) => {
1839            let mut state = (*existing).clone();
1840            for account in accounts {
1841                if let Some((proof, _)) = RewardAccountProofV1::prove(tree, (*account).into()) {
1842                    if let Err(err) = proof.remember(&mut state.reward_merkle_tree_v1) {
1843                        tracing::warn!(
1844                            ?view,
1845                            %account,
1846                            "cannot update fetched account state: {err:#}"
1847                        );
1848                    }
1849                } else {
1850                    tracing::warn!(?view, %account, "cannot update fetched account state because account is not in the merkle tree");
1851                };
1852            }
1853            (Arc::new(state), delta)
1854        },
1855        None => {
1856            // If we don't already have a leaf for this view, or if we don't have the view
1857            // at all, we can create a new view based on the recovered leaf and add it to
1858            // our state map. In this case, we must also add the leaf to the saved leaves
1859            // map to ensure consistency.
1860            let mut state = ValidatedState::from_header(leaf.block_header());
1861            state.reward_merkle_tree_v1 = tree.clone();
1862            (Arc::new(state), None)
1863        },
1864    };
1865
1866    consensus_handle
1867        .update_leaf(leaf, state, delta)
1868        .await
1869        .with_context(|| "failed to update leaf")?;
1870
1871    Ok(())
1872}
1873
1874#[cfg(test)]
1875mod test {
1876    use super::*;
1877
1878    #[test]
1879    fn test_peer_priority() {
1880        let good_peer = PeerScore {
1881            requests: 1000,
1882            failures: 2,
1883        };
1884        let bad_peer = PeerScore {
1885            requests: 10,
1886            failures: 1,
1887        };
1888        assert!(good_peer > bad_peer);
1889
1890        let mut peers: PriorityQueue<_, _> = [(0, good_peer), (1, bad_peer)].into_iter().collect();
1891        assert_eq!(peers.pop(), Some((0, good_peer)));
1892        assert_eq!(peers.pop(), Some((1, bad_peer)));
1893    }
1894}