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_types::{
14    BackoffParams, BlockMerkleTree, Certificate2, FeeAccount, FeeAccountProof, FeeMerkleCommitment,
15    FeeMerkleTree, Leaf2, NodeState, SeqTypes, ValidatedState,
16    config::PublicNetworkConfig,
17    v0::traits::StateCatchup,
18    v0_3::{
19        ChainConfig, RewardAccountProofV1, RewardAccountV1, RewardMerkleCommitmentV1,
20        RewardMerkleTreeV1,
21    },
22    v0_4::{
23        PermittedRewardMerkleTreeV2, RewardAccountProofV2, RewardAccountV2,
24        RewardMerkleCommitmentV2, RewardMerkleTreeV2, forgotten_accounts_include,
25    },
26};
27use futures::{
28    StreamExt,
29    future::{Future, FutureExt, TryFuture, TryFutureExt},
30    stream::FuturesUnordered,
31};
32use hotshot_new_protocol::{storage::NewProtocolStorage, utils::verify_new_protocol_leaf_chain};
33use hotshot_types::{
34    ValidatorConfig,
35    data::{EpochNumber, ViewNumber},
36    epoch_membership::EpochMembershipCoordinator,
37    message::UpgradeLock,
38    network::NetworkConfig,
39    simple_certificate::LightClientStateUpdateCertificateV2,
40    traits::{
41        ValidatedState as ValidatedStateTrait,
42        metrics::{Counter, CounterFamily, Metrics},
43    },
44    utils::{epoch_from_block_number, verify_leaf_chain},
45};
46use itertools::Itertools;
47use jf_merkle_tree_compat::{ForgetableMerkleTreeScheme, MerkleTreeScheme, prelude::MerkleNode};
48use parking_lot::Mutex;
49use priority_queue::PriorityQueue;
50use serde::de::DeserializeOwned;
51use surf_disco::Request;
52use tide_disco::error::ServerError;
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<ServerError, ApiVer: StaticVersionType> {
68    inner: surf_disco::Client<ServerError, ApiVer>,
69    url: Url,
70    requests: Arc<Box<dyn Counter>>,
71    failures: Arc<Box<dyn Counter>>,
72}
73
74impl<ApiVer: StaticVersionType> Client<ServerError, ApiVer> {
75    pub fn new(
76        url: Url,
77        requests: &(impl CounterFamily + ?Sized),
78        failures: &(impl CounterFamily + ?Sized),
79    ) -> Self {
80        Self {
81            inner: surf_disco::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, ServerError, 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<ServerError, 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<ServerError, 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![format!("Fetching failed.\n")];
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("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    let epoch = EpochNumber::new(epoch_from_block_number(height, *coordinator.epoch_height()));
289    let membership = coordinator
290        .stake_table_for_epoch(Some(epoch))
291        .map_err(|err| anyhow!("no stake table available for epoch {epoch}: {err:?}"))?;
292    let stake_table: Vec<_> = membership.stake_table().cloned().collect();
293    verify_leaf_chain(
294        leaf_chain,
295        &stake_table,
296        membership.success_threshold(),
297        height,
298        &upgrade_lock,
299    )
300    .await
301    .with_context(|| format!("failed to verify leaf chain at height {height}"))
302}
303
304#[async_trait]
305impl<ApiVer: StaticVersionType> StateCatchup for StatePeers<ApiVer> {
306    #[tracing::instrument(skip(self, _instance))]
307    async fn try_fetch_accounts(
308        &self,
309        retry: usize,
310        _instance: &NodeState,
311        height: u64,
312        view: ViewNumber,
313        fee_merkle_tree_root: FeeMerkleCommitment,
314        accounts: &[FeeAccount],
315    ) -> anyhow::Result<Vec<FeeAccountProof>> {
316        self.fetch(retry, |client| async move {
317            let tree = client
318                .inner
319                .post::<FeeMerkleTree>(&format!("catchup/{height}/{}/accounts", view.u64()))
320                .body_binary(&accounts.to_vec())?
321                .send()
322                .await?;
323
324            // Verify proofs.
325            let mut proofs = Vec::new();
326            for account in accounts {
327                let (proof, _) = FeeAccountProof::prove(&tree, (*account).into())
328                    .context(format!("response missing fee account {account}"))?;
329                proof.verify(&fee_merkle_tree_root).context(format!(
330                    "invalid proof for fee account {account}, root: {fee_merkle_tree_root}"
331                ))?;
332                proofs.push(proof);
333            }
334
335            anyhow::Ok(proofs)
336        })
337        .await
338    }
339
340    #[tracing::instrument(skip(self, _instance, mt))]
341    async fn try_remember_blocks_merkle_tree(
342        &self,
343        retry: usize,
344        _instance: &NodeState,
345        height: u64,
346        view: ViewNumber,
347        mt: &mut BlockMerkleTree,
348    ) -> anyhow::Result<()> {
349        *mt = self
350            .fetch(retry, |client| {
351                let mut mt = mt.clone();
352                async move {
353                    let frontier = client
354                        .get::<BlocksFrontier>(&format!("catchup/{height}/{}/blocks", view.u64()))
355                        .send()
356                        .await?;
357                    let elem = frontier
358                        .elem()
359                        .context("provided frontier is missing leaf element")?;
360                    mt.remember(mt.num_leaves() - 1, *elem, &frontier)
361                        .context("verifying block proof")?;
362                    anyhow::Ok(mt)
363                }
364            })
365            .await?;
366        Ok(())
367    }
368
369    async fn try_fetch_chain_config(
370        &self,
371        retry: usize,
372        commitment: Commitment<ChainConfig>,
373    ) -> anyhow::Result<ChainConfig> {
374        self.fetch(retry, |client| async move {
375            let cf = client
376                .get::<ChainConfig>(&format!("catchup/chain-config/{commitment}"))
377                .send()
378                .await?;
379            ensure!(
380                cf.commit() == commitment,
381                "received chain config with mismatched commitment: expected {commitment}, got {}",
382                cf.commit()
383            );
384            Ok(cf)
385        })
386        .await
387    }
388
389    async fn try_fetch_leaf(
390        &self,
391        retry: usize,
392        coordinator: EpochMembershipCoordinator<SeqTypes>,
393        height: u64,
394    ) -> anyhow::Result<Leaf2> {
395        // Fetch the leaf chain. For new protocol heights this is a leaf range
396        // `[height..=cert2_height]`
397        // for legacy-protocol heights it's a 3-chain.
398        let leaf_chain = self
399            .fetch(retry, |client| async move {
400                let chain = client
401                    .get::<Vec<Leaf2>>(&format!("catchup/{height}/leafchain"))
402                    .send()
403                    .await?;
404                anyhow::Ok(chain)
405            })
406            .await
407            .with_context(|| format!("failed to fetch leaf chain at height {height}"))?;
408
409        let first = leaf_chain
410            .first()
411            .ok_or_else(|| anyhow!("empty leaf chain returned for height {height}"))?;
412
413        if first.block_header().version() >= NEW_PROTOCOL_VERSION {
414            let upgrade_lock =
415                UpgradeLock::<SeqTypes>::new(versions::Upgrade::trivial(NEW_PROTOCOL_VERSION));
416
417            // The chain terminates at the leaf the cert2 finalizes, so fetch that cert2 by its
418            // exact height
419            let cert2_height = leaf_chain
420                .last()
421                .ok_or_else(|| anyhow!("empty leaf chain returned for height {height}"))?
422                .height();
423            let cert2 = self
424                .fetch(retry, |client| async move {
425                    let cert2 = client
426                        .get::<Certificate2<SeqTypes>>(&format!("catchup/{cert2_height}/cert2"))
427                        .send()
428                        .await?;
429                    anyhow::Ok(cert2)
430                })
431                .await
432                .with_context(|| format!("failed to fetch cert2 for height {cert2_height}"))?;
433
434            verify_new_protocol_leaf_chain(leaf_chain, &coordinator, height, &upgrade_lock, cert2)
435                .await
436                .with_context(|| {
437                    format!("failed to verify leaf chain with cert2 at height {height}")
438                })
439        } else {
440            verify_legacy_leaf_chain(leaf_chain, &coordinator, height).await
441        }
442    }
443
444    async fn try_fetch_reward_merkle_tree_v2(
445        &self,
446        retry: usize,
447        height: u64,
448        view: ViewNumber,
449        reward_merkle_tree_root: RewardMerkleCommitmentV2,
450        accounts: Arc<Vec<RewardAccountV2>>,
451    ) -> anyhow::Result<PermittedRewardMerkleTreeV2> {
452        let result = self
453            .fetch(retry, |client| async move {
454                // Try the catchup endpoint first which returns tree from consensuss decided state
455                // if not present, then fall back to
456                // the reward-state-v2 endpoint which returns from storage decided state
457                let tree_bytes = match client
458                    .inner
459                    .get::<Vec<u8>>(&format!("catchup/reward-merkle-tree-v2/{height}/{}", *view))
460                    .send()
461                    .await
462                {
463                    Ok(bytes) => bytes,
464                    Err(err) => {
465                        tracing::info!(
466                            "catchup endpoint failed, falling back to reward-state-v2: {err:#}"
467                        );
468                        client
469                            .inner
470                            .get::<Vec<u8>>(&format!(
471                                "reward-state-v2/reward-merkle-tree-v2/{height}"
472                            ))
473                            .send()
474                            .await?
475                    },
476                };
477
478                Ok::<Vec<u8>, anyhow::Error>(tree_bytes)
479            })
480            .await
481            .context("Fetching from peer failed")?;
482
483        let tree_data = bincode::deserialize::<RewardMerkleTreeV2Data>(&result)
484            .context("Failed to deserialize merkle tree from catchup")?;
485
486        let tree: PermittedRewardMerkleTreeV2 =
487            PermittedRewardMerkleTreeV2::try_from_kv_set(tree_data.balances).await?;
488
489        ensure!(
490            tree.tree.commitment() == reward_merkle_tree_root,
491            "RewardMerkleTreeV2 from peer failed commitment check."
492        );
493        ensure!(!forgotten_accounts_include(&tree, &accounts));
494
495        Ok(tree)
496    }
497
498    #[tracing::instrument(skip(self, _instance))]
499    async fn try_fetch_reward_accounts_v1(
500        &self,
501        retry: usize,
502        _instance: &NodeState,
503        height: u64,
504        view: ViewNumber,
505        reward_merkle_tree_root: RewardMerkleCommitmentV1,
506        accounts: &[RewardAccountV1],
507    ) -> anyhow::Result<Vec<RewardAccountProofV1>> {
508        self.fetch(retry, |client| async move {
509            let tree = client
510                .inner
511                .post::<RewardMerkleTreeV1>(&format!(
512                    "catchup/{height}/{}/reward-accounts",
513                    view.u64()
514                ))
515                .body_binary(&accounts.to_vec())?
516                .send()
517                .await?;
518
519            // Verify proofs.
520            let mut proofs = Vec::new();
521            for account in accounts {
522                let (proof, _) = RewardAccountProofV1::prove(&tree, (*account).into())
523                    .context(format!("response missing reward account {account}"))?;
524                proof.verify(&reward_merkle_tree_root).context(format!(
525                    "invalid proof for v1 reward account {account}, root: \
526                     {reward_merkle_tree_root} height {height} view {view}"
527                ))?;
528                proofs.push(proof);
529            }
530
531            anyhow::Ok(proofs)
532        })
533        .await
534    }
535
536    async fn try_fetch_state_cert(
537        &self,
538        retry: usize,
539        epoch: u64,
540    ) -> anyhow::Result<LightClientStateUpdateCertificateV2<SeqTypes>> {
541        self.fetch(retry, |client| async move {
542            client
543                .get::<LightClientStateUpdateCertificateV2<SeqTypes>>(&format!(
544                    "catchup/{epoch}/state-cert"
545                ))
546                .send()
547                .await
548        })
549        .await
550    }
551
552    fn backoff(&self) -> &BackoffParams {
553        &self.backoff
554    }
555
556    fn name(&self) -> String {
557        format!(
558            "StatePeers({})",
559            self.clients
560                .iter()
561                .map(|client| client.url.to_string())
562                .join(",")
563        )
564    }
565
566    fn is_local(&self) -> bool {
567        false
568    }
569}
570
571pub(crate) trait CatchupStorage: Sync {
572    /// Get the state of the requested `accounts`.
573    ///
574    /// The state is fetched from a snapshot at the given height and view, which _must_ correspond!
575    /// `height` is provided to simplify lookups for backends where data is not indexed by view.
576    /// This function is intended to be used for catchup, so `view` should be no older than the last
577    /// decided view.
578    ///
579    /// If successful, this function also returns the leaf from `view`, if it is available. This can
580    /// be used to add the recovered state to HotShot's state map, so that future requests can get
581    /// the state from memory rather than storage.
582    fn get_accounts(
583        &self,
584        _instance: &NodeState,
585        _height: u64,
586        _view: ViewNumber,
587        _accounts: &[FeeAccount],
588    ) -> impl Send + Future<Output = anyhow::Result<(FeeMerkleTree, Leaf2)>> {
589        // Merklized state catchup is only supported by persistence backends that provide merklized
590        // state storage. This default implementation is overridden for those that do. Otherwise,
591        // catchup can still be provided by fetching undecided merklized state from consensus
592        // memory.
593        async {
594            bail!("merklized state catchup is not supported for this data source");
595        }
596    }
597
598    fn get_reward_accounts_v1(
599        &self,
600        _instance: &NodeState,
601        _height: u64,
602        _view: ViewNumber,
603        _accounts: &[RewardAccountV1],
604    ) -> impl Send + Future<Output = anyhow::Result<(RewardMerkleTreeV1, Leaf2)>> {
605        async {
606            bail!("merklized state catchup is not supported for this data source");
607        }
608    }
609
610    fn get_reward_accounts_v2(
611        &self,
612        _instance: &NodeState,
613        _height: u64,
614        _view: ViewNumber,
615        _accounts: &[RewardAccountV2],
616    ) -> impl Send + Future<Output = anyhow::Result<(RewardMerkleTreeV2, Leaf2)>> {
617        async {
618            bail!("merklized state catchup is not supported for this data source");
619        }
620    }
621
622    /// Get the blocks Merkle tree frontier.
623    ///
624    /// The state is fetched from a snapshot at the given height and view, which _must_ correspond!
625    /// `height` is provided to simplify lookups for backends where data is not indexed by view.
626    /// This function is intended to be used for catchup, so `view` should be no older than the last
627    /// decided view.
628    fn get_frontier(
629        &self,
630        _instance: &NodeState,
631        _height: u64,
632        _view: ViewNumber,
633    ) -> impl Send + Future<Output = anyhow::Result<BlocksFrontier>> {
634        // Merklized state catchup is only supported by persistence backends that provide merklized
635        // state storage. This default implementation is overridden for those that do. Otherwise,
636        // catchup can still be provided by fetching undecided merklized state from consensus
637        // memory.
638        async {
639            bail!("merklized state catchup is not supported for this data source");
640        }
641    }
642
643    fn get_chain_config(
644        &self,
645        _commitment: Commitment<ChainConfig>,
646    ) -> impl Send + Future<Output = anyhow::Result<ChainConfig>> {
647        async {
648            bail!("chain config catchup is not supported for this data source");
649        }
650    }
651
652    fn get_leaf_chain(
653        &self,
654        _height: u64,
655    ) -> impl Send + Future<Output = anyhow::Result<Vec<Leaf2>>> {
656        async {
657            bail!("leaf chain catchup is not supported for this data source");
658        }
659    }
660
661    /// Load the cert2 stored at exactly `height`, if one exists.
662    fn load_cert2(
663        &self,
664        _height: u64,
665    ) -> impl Send + Future<Output = anyhow::Result<Option<Certificate2<SeqTypes>>>> {
666        async { Ok(None) }
667    }
668
669    /// Load a decided leaf at the given height.
670    fn get_leaf(&self, _height: u64) -> impl Send + Future<Output = anyhow::Result<Leaf2>> {
671        async {
672            bail!("leaf fetch is not supported for this data source");
673        }
674    }
675}
676
677impl CatchupStorage for hotshot_query_service::data_source::MetricsDataSource {}
678
679impl<T, S> CatchupStorage for hotshot_query_service::data_source::ExtensibleDataSource<T, S>
680where
681    T: CatchupStorage,
682    S: Sync,
683{
684    async fn get_accounts(
685        &self,
686        instance: &NodeState,
687        height: u64,
688        view: ViewNumber,
689        accounts: &[FeeAccount],
690    ) -> anyhow::Result<(FeeMerkleTree, Leaf2)> {
691        self.inner()
692            .get_accounts(instance, height, view, accounts)
693            .await
694    }
695
696    async fn get_reward_accounts_v2(
697        &self,
698        instance: &NodeState,
699        height: u64,
700        view: ViewNumber,
701        accounts: &[RewardAccountV2],
702    ) -> anyhow::Result<(RewardMerkleTreeV2, Leaf2)> {
703        self.inner()
704            .get_reward_accounts_v2(instance, height, view, accounts)
705            .await
706    }
707
708    async fn get_reward_accounts_v1(
709        &self,
710        instance: &NodeState,
711        height: u64,
712        view: ViewNumber,
713        accounts: &[RewardAccountV1],
714    ) -> anyhow::Result<(RewardMerkleTreeV1, Leaf2)> {
715        self.inner()
716            .get_reward_accounts_v1(instance, height, view, accounts)
717            .await
718    }
719
720    async fn get_frontier(
721        &self,
722        instance: &NodeState,
723        height: u64,
724        view: ViewNumber,
725    ) -> anyhow::Result<BlocksFrontier> {
726        self.inner().get_frontier(instance, height, view).await
727    }
728
729    async fn get_chain_config(
730        &self,
731        commitment: Commitment<ChainConfig>,
732    ) -> anyhow::Result<ChainConfig> {
733        self.inner().get_chain_config(commitment).await
734    }
735    async fn get_leaf_chain(&self, height: u64) -> anyhow::Result<Vec<Leaf2>> {
736        self.inner().get_leaf_chain(height).await
737    }
738
739    async fn load_cert2(&self, height: u64) -> anyhow::Result<Option<Certificate2<SeqTypes>>> {
740        self.inner().load_cert2(height).await
741    }
742
743    async fn get_leaf(&self, height: u64) -> anyhow::Result<Leaf2> {
744        self.inner().get_leaf(height).await
745    }
746}
747
748#[derive(Debug)]
749pub(crate) struct SqlStateCatchup<T> {
750    db: Arc<T>,
751    backoff: BackoffParams,
752}
753
754impl<T> SqlStateCatchup<T> {
755    pub(crate) fn new(db: Arc<T>, backoff: BackoffParams) -> Self {
756        Self { db, backoff }
757    }
758}
759
760#[async_trait]
761impl<T> StateCatchup for SqlStateCatchup<T>
762where
763    T: CatchupStorage + RewardMerkleTreeDataSource + Send + Sync,
764{
765    async fn try_fetch_leaf(
766        &self,
767        _retry: usize,
768        _coordinator: EpochMembershipCoordinator<SeqTypes>,
769        height: u64,
770    ) -> anyhow::Result<Leaf2> {
771        // Leaves in our local DB were verified before they were stored, so we can return the leaf
772        // at `height` directly without re-verifying.
773        self.db
774            .get_leaf(height)
775            .await
776            .with_context(|| format!("failed to load leaf at height {height} from DB"))
777    }
778
779    // TODO: add a test for the account proof validation
780    // issue # 2102 (https://github.com/EspressoSystems/espresso-network/issues/2102)
781    #[tracing::instrument(skip(self, _retry, instance))]
782    async fn try_fetch_accounts(
783        &self,
784        _retry: usize,
785        instance: &NodeState,
786        block_height: u64,
787        view: ViewNumber,
788        fee_merkle_tree_root: FeeMerkleCommitment,
789        accounts: &[FeeAccount],
790    ) -> anyhow::Result<Vec<FeeAccountProof>> {
791        // Get the accounts
792        let (fee_merkle_tree_from_db, _) = self
793            .db
794            .get_accounts(instance, block_height, view, accounts)
795            .await
796            .with_context(|| "failed to get fee accounts from DB")?;
797
798        // Verify the accounts
799        let mut proofs = Vec::new();
800        for account in accounts {
801            let (proof, _) = FeeAccountProof::prove(&fee_merkle_tree_from_db, (*account).into())
802                .context(format!("response missing account {account}"))?;
803            proof.verify(&fee_merkle_tree_root).context(format!(
804                "invalid proof for fee account {account}, root: {fee_merkle_tree_root}"
805            ))?;
806            proofs.push(proof);
807        }
808
809        Ok(proofs)
810    }
811
812    #[tracing::instrument(skip(self, _retry, instance, mt))]
813    async fn try_remember_blocks_merkle_tree(
814        &self,
815        _retry: usize,
816        instance: &NodeState,
817        bh: u64,
818        view: ViewNumber,
819        mt: &mut BlockMerkleTree,
820    ) -> anyhow::Result<()> {
821        if bh == 0 {
822            return Ok(());
823        }
824
825        let proof = self.db.get_frontier(instance, bh, view).await?;
826        match proof
827            .proof
828            .first()
829            .context(format!("empty proof for frontier at height {bh}"))?
830        {
831            MerkleNode::Leaf { pos, elem, .. } => mt
832                .remember(pos, elem, proof.clone())
833                .context("failed to remember proof"),
834            _ => bail!("invalid proof"),
835        }
836    }
837
838    async fn try_fetch_chain_config(
839        &self,
840        _retry: usize,
841        commitment: Commitment<ChainConfig>,
842    ) -> anyhow::Result<ChainConfig> {
843        let cf = self.db.get_chain_config(commitment).await?;
844
845        if cf.commit() != commitment {
846            panic!(
847                "Critical error: Mismatched chain config detected. Expected chain config: {:?}, \
848                 but got: {:?}.
849                This may indicate a compromised database",
850                commitment,
851                cf.commit()
852            )
853        }
854
855        Ok(cf)
856    }
857
858    async fn try_fetch_reward_merkle_tree_v2(
859        &self,
860        _retry: usize,
861        height: u64,
862        _view: ViewNumber,
863        reward_merkle_tree_root: RewardMerkleCommitmentV2,
864        accounts: Arc<Vec<RewardAccountV2>>,
865    ) -> anyhow::Result<PermittedRewardMerkleTreeV2> {
866        let tree: PermittedRewardMerkleTreeV2 = self.db.load_reward_merkle_tree_v2(height).await?;
867
868        ensure!(tree.tree.commitment() == reward_merkle_tree_root);
869        ensure!(!forgotten_accounts_include(&tree, &accounts));
870
871        Ok(tree)
872    }
873
874    #[tracing::instrument(skip(self, _retry, instance))]
875    async fn try_fetch_reward_accounts_v1(
876        &self,
877        _retry: usize,
878        instance: &NodeState,
879        block_height: u64,
880        view: ViewNumber,
881        reward_merkle_tree_root: RewardMerkleCommitmentV1,
882        accounts: &[RewardAccountV1],
883    ) -> anyhow::Result<Vec<RewardAccountProofV1>> {
884        // Get the accounts
885        let (reward_merkle_tree_from_db, _) = self
886            .db
887            .get_reward_accounts_v1(instance, block_height, view, accounts)
888            .await
889            .with_context(|| "failed to get reward accounts from DB")?;
890        // Verify the accounts
891        let mut proofs = Vec::new();
892        for account in accounts {
893            let (proof, _) =
894                RewardAccountProofV1::prove(&reward_merkle_tree_from_db, (*account).into())
895                    .context(format!("response missing account {account}"))?;
896            proof.verify(&reward_merkle_tree_root).context(format!(
897                "invalid proof for v1 reward account {account}, root: {reward_merkle_tree_root}"
898            ))?;
899            proofs.push(proof);
900        }
901
902        Ok(proofs)
903    }
904
905    async fn try_fetch_state_cert(
906        &self,
907        _retry: usize,
908        _epoch: u64,
909    ) -> anyhow::Result<LightClientStateUpdateCertificateV2<SeqTypes>> {
910        bail!("state cert catchup not supported for SqlStateCatchup");
911    }
912
913    fn backoff(&self) -> &BackoffParams {
914        &self.backoff
915    }
916
917    fn name(&self) -> String {
918        "SqlStateCatchup".into()
919    }
920
921    fn is_local(&self) -> bool {
922        true
923    }
924}
925
926/// Disable catchup entirely.
927#[derive(Clone, Debug)]
928pub struct NullStateCatchup {
929    backoff: BackoffParams,
930    chain_configs: HashMap<Commitment<ChainConfig>, ChainConfig>,
931}
932
933impl Default for NullStateCatchup {
934    fn default() -> Self {
935        Self {
936            backoff: BackoffParams::disabled(),
937            chain_configs: Default::default(),
938        }
939    }
940}
941
942impl NullStateCatchup {
943    /// Add a chain config preimage which can be fetched by hash during STF evaluation.
944    ///
945    /// [`NullStateCatchup`] is used to disable catchup entirely when evaluating the STF, which
946    /// requires the [`ValidatedState`](espresso_types::ValidatedState) to be pre-seeded with all
947    /// the dependencies of STF evaluation. However, the STF also depends on having the preimage of
948    /// various [`ChainConfig`] commitments, which are not stored in the
949    /// [`ValidatedState`](espresso_types::ValidatedState), but which instead must be supplied by a
950    /// separate preimage oracle. Thus, [`NullStateCatchup`] may be populated with a set of
951    /// [`ChainConfig`]s, which it can feed to the STF during evaluation.
952    pub fn add_chain_config(&mut self, cf: ChainConfig) {
953        self.chain_configs.insert(cf.commit(), cf);
954    }
955}
956
957#[async_trait]
958impl StateCatchup for NullStateCatchup {
959    async fn try_fetch_leaf(
960        &self,
961        _retry: usize,
962        _coordinator: EpochMembershipCoordinator<SeqTypes>,
963        _height: u64,
964    ) -> anyhow::Result<Leaf2> {
965        bail!("state catchup is disabled")
966    }
967
968    async fn try_fetch_accounts(
969        &self,
970        _retry: usize,
971        _instance: &NodeState,
972        _height: u64,
973        _view: ViewNumber,
974        _fee_merkle_tree_root: FeeMerkleCommitment,
975        _account: &[FeeAccount],
976    ) -> anyhow::Result<Vec<FeeAccountProof>> {
977        bail!("state catchup is disabled");
978    }
979
980    async fn try_remember_blocks_merkle_tree(
981        &self,
982        _retry: usize,
983        _instance: &NodeState,
984        _height: u64,
985        _view: ViewNumber,
986        _mt: &mut BlockMerkleTree,
987    ) -> anyhow::Result<()> {
988        bail!("state catchup is disabled");
989    }
990
991    async fn try_fetch_chain_config(
992        &self,
993        _retry: usize,
994        commitment: Commitment<ChainConfig>,
995    ) -> anyhow::Result<ChainConfig> {
996        self.chain_configs
997            .get(&commitment)
998            .copied()
999            .context(format!("chain config {commitment} not available"))
1000    }
1001
1002    async fn try_fetch_reward_merkle_tree_v2(
1003        &self,
1004        _retry: usize,
1005        _height: u64,
1006        _view: ViewNumber,
1007        _reward_merkle_tree_root: RewardMerkleCommitmentV2,
1008        _accounts: Arc<Vec<RewardAccountV2>>,
1009    ) -> anyhow::Result<PermittedRewardMerkleTreeV2> {
1010        bail!("state catchup is disabled");
1011    }
1012
1013    async fn try_fetch_reward_accounts_v1(
1014        &self,
1015        _retry: usize,
1016        _instance: &NodeState,
1017        _height: u64,
1018        _view: ViewNumber,
1019        _fee_merkle_tree_root: RewardMerkleCommitmentV1,
1020        _account: &[RewardAccountV1],
1021    ) -> anyhow::Result<Vec<RewardAccountProofV1>> {
1022        bail!("state catchup is disabled");
1023    }
1024
1025    async fn try_fetch_state_cert(
1026        &self,
1027        _retry: usize,
1028        _epoch: u64,
1029    ) -> anyhow::Result<LightClientStateUpdateCertificateV2<SeqTypes>> {
1030        bail!("state catchup is disabled");
1031    }
1032
1033    fn backoff(&self) -> &BackoffParams {
1034        &self.backoff
1035    }
1036
1037    fn name(&self) -> String {
1038        "NullStateCatchup".into()
1039    }
1040
1041    fn is_local(&self) -> bool {
1042        true
1043    }
1044}
1045
1046/// A catchup implementation that parallelizes requests to many providers.
1047/// It returns the result of the first non-erroring provider to complete.
1048#[derive(Clone)]
1049pub struct ParallelStateCatchup {
1050    providers: Arc<Mutex<Vec<Arc<dyn StateCatchup>>>>,
1051    backoff: BackoffParams,
1052    /// Timeout for local provider requests
1053    local_timeout: Duration,
1054}
1055
1056impl ParallelStateCatchup {
1057    /// Create a new [`ParallelStateCatchup`] with the given providers and local timeout.
1058    pub fn new(providers: &[Arc<dyn StateCatchup>], local_timeout: Duration) -> Self {
1059        Self {
1060            providers: Arc::new(Mutex::new(providers.to_vec())),
1061            backoff: BackoffParams::disabled(),
1062            local_timeout,
1063        }
1064    }
1065
1066    /// Add a provider to the list of providers
1067    pub fn add_provider(&self, provider: Arc<dyn StateCatchup>) {
1068        self.providers.lock().push(provider);
1069    }
1070
1071    /// Perform an async operation on all local providers, returning the first result to succeed.
1072    ///
1073    /// A timeout is applied so that a slow local lookup does not prevent the node from
1074    /// falling back to remote providers in time to vote within the current view.
1075    pub async fn on_local_providers<C, F, RT>(&self, closure: C) -> anyhow::Result<RT>
1076    where
1077        C: Fn(Arc<dyn StateCatchup>) -> F + Clone + Send + Sync + 'static,
1078        F: Future<Output = anyhow::Result<RT>> + Send + 'static,
1079        RT: Send + Sync + 'static,
1080    {
1081        let local_timeout = self.local_timeout;
1082        match timeout(
1083            local_timeout,
1084            self.on_providers(|provider| provider.is_local(), closure),
1085        )
1086        .await
1087        {
1088            Ok(result) => result,
1089            Err(_) => {
1090                let err = format!("local provider timed out after {local_timeout:?}");
1091                tracing::warn!("{err}");
1092                Err(anyhow::anyhow!(err))
1093            },
1094        }
1095    }
1096
1097    /// Perform an async operation on all remote providers, returning the first result to succeed
1098    pub async fn on_remote_providers<C, F, RT>(&self, closure: C) -> anyhow::Result<RT>
1099    where
1100        C: Fn(Arc<dyn StateCatchup>) -> F + Clone + Send + Sync + 'static,
1101        F: Future<Output = anyhow::Result<RT>> + Send + 'static,
1102        RT: Send + Sync + 'static,
1103    {
1104        self.on_providers(|provider| !provider.is_local(), closure)
1105            .await
1106    }
1107
1108    /// Perform an async operation on all providers matching the given predicate, returning the first result to succeed
1109    pub async fn on_providers<P, C, F, RT>(&self, predicate: P, closure: C) -> anyhow::Result<RT>
1110    where
1111        P: Fn(&Arc<dyn StateCatchup>) -> bool + Clone + Send + Sync + 'static,
1112        C: Fn(Arc<dyn StateCatchup>) -> F + Clone + Send + Sync + 'static,
1113        F: Future<Output = anyhow::Result<RT>> + Send + 'static,
1114        RT: Send + Sync + 'static,
1115    {
1116        // Make sure we have at least one provider
1117        let providers = self.providers.lock().clone();
1118        if providers.is_empty() {
1119            return Err(anyhow::anyhow!("no providers were initialized"));
1120        }
1121
1122        // Filter the providers by the predicate
1123        let providers = providers.into_iter().filter(predicate).collect::<Vec<_>>();
1124        if providers.is_empty() {
1125            return Err(anyhow::anyhow!("no providers matched the given predicate"));
1126        }
1127
1128        // Spawn futures for each provider
1129        let mut futures = FuturesUnordered::new();
1130        for provider in providers {
1131            let closure = closure.clone();
1132            futures.push(AbortOnDropHandle::new(tokio::spawn(closure(provider))));
1133        }
1134
1135        let mut logs = vec![format!("No providers returned a successful result.\n")];
1136        // Return the first successful result
1137        while let Some(result) = futures.next().await {
1138            // Unwrap the inner (join) result
1139            let result = match result {
1140                Ok(res) => res,
1141                Err(err) => {
1142                    tracing::debug!("Failed to join on provider: {err:#}.");
1143                    logs.push(format!("Failed to join on provider: {err:#}."));
1144                    continue;
1145                },
1146            };
1147
1148            // If a provider fails, print why
1149            let result = match result {
1150                Ok(res) => res,
1151                Err(err) => {
1152                    tracing::debug!("Failed to fetch data: {err:#}.");
1153                    logs.push(format!("Failed to fetch data: {err:#}."));
1154                    continue;
1155                },
1156            };
1157
1158            return Ok(result);
1159        }
1160
1161        Err(anyhow::anyhow!(logs.join("\n")))
1162    }
1163}
1164
1165macro_rules! clone {
1166    ( ($( $x:ident ),*) $y:expr ) => {
1167        {
1168            $(let $x = $x.clone();)*
1169            $y
1170        }
1171    };
1172}
1173
1174/// A catchup implementation that parallelizes requests to a local and remote provider.
1175/// It returns the result of the first provider to complete.
1176#[async_trait]
1177impl StateCatchup for ParallelStateCatchup {
1178    async fn try_fetch_leaf(
1179        &self,
1180        retry: usize,
1181        coordinator: EpochMembershipCoordinator<SeqTypes>,
1182        height: u64,
1183    ) -> anyhow::Result<Leaf2> {
1184        // Try fetching the leaf on the local providers first
1185        let local_result = self
1186            .on_local_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        // Check if we were successful locally
1196        match &local_result {
1197            Ok(_) => return local_result,
1198            Err(err) => tracing::debug!("{err:#}"),
1199        }
1200
1201        // If that fails, try the remote ones
1202        self.on_remote_providers(clone! {(coordinator) move |provider| {
1203            clone!{(coordinator) async move {
1204                provider
1205                    .try_fetch_leaf(retry, coordinator, height)
1206                    .await
1207            }}
1208        }})
1209        .await
1210    }
1211
1212    async fn try_fetch_accounts(
1213        &self,
1214        retry: usize,
1215        instance: &NodeState,
1216        height: u64,
1217        view: ViewNumber,
1218        fee_merkle_tree_root: FeeMerkleCommitment,
1219        accounts: &[FeeAccount],
1220    ) -> anyhow::Result<Vec<FeeAccountProof>> {
1221        // Try to get the accounts on local providers first
1222        let accounts_vec = accounts.to_vec();
1223        let local_result = self
1224            .on_local_providers(clone! {(instance, accounts_vec) move |provider| {
1225                clone! {(instance, accounts_vec) async move {
1226                    provider
1227                        .try_fetch_accounts(
1228                            retry,
1229                            &instance,
1230                            height,
1231                            view,
1232                            fee_merkle_tree_root,
1233                            &accounts_vec,
1234                        )
1235                        .await
1236                }}
1237            }})
1238            .await;
1239
1240        // Check if we were successful locally
1241        match &local_result {
1242            Ok(_) => return local_result,
1243            Err(err) => tracing::debug!("{err:#}"),
1244        }
1245
1246        // If that fails, try the remote ones
1247        self.on_remote_providers(clone! {(instance, accounts_vec) move |provider| {
1248            clone!{(instance, accounts_vec) async move {
1249                provider
1250                .try_fetch_accounts(
1251                    retry,
1252                    &instance,
1253                    height,
1254                    view,
1255                    fee_merkle_tree_root,
1256                    &accounts_vec,
1257                ).await
1258            }}
1259        }})
1260        .await
1261    }
1262
1263    async fn try_remember_blocks_merkle_tree(
1264        &self,
1265        retry: usize,
1266        instance: &NodeState,
1267        height: u64,
1268        view: ViewNumber,
1269        mt: &mut BlockMerkleTree,
1270    ) -> anyhow::Result<()> {
1271        // Try to remember the blocks merkle tree on local providers first
1272        let local_result = self
1273            .on_local_providers(clone! {(mt, instance) move |provider| {
1274                let mut mt = mt.clone();
1275                clone! {(instance) async move {
1276                    // Perform the call
1277                    provider
1278                        .try_remember_blocks_merkle_tree(
1279                            retry,
1280                            &instance,
1281                            height,
1282                            view,
1283                            &mut mt,
1284                        )
1285                        .await?;
1286
1287                    // Return the merkle tree so we can modify it
1288                    Ok(mt)
1289                }}
1290            }})
1291            .await;
1292
1293        // Check if we were successful locally
1294        if let Ok(modified_mt) = local_result {
1295            // Set the merkle tree to the output of the successful local call
1296            *mt = modified_mt;
1297
1298            return Ok(());
1299        }
1300
1301        // If that fails, try the remote ones
1302        let remote_result = self
1303            .on_remote_providers(clone! {(mt, instance) move |provider| {
1304                let mut mt = mt.clone();
1305                clone!{(instance) async move {
1306                    // Perform the call
1307                    provider
1308                    .try_remember_blocks_merkle_tree(
1309                        retry,
1310                        &instance,
1311                        height,
1312                        view,
1313                        &mut mt,
1314                    )
1315                    .await?;
1316
1317                    // Return the merkle tree
1318                    Ok(mt)
1319                }}
1320            }})
1321            .await?;
1322
1323        // Update the original, local merkle tree
1324        *mt = remote_result;
1325
1326        Ok(())
1327    }
1328
1329    async fn try_fetch_chain_config(
1330        &self,
1331        retry: usize,
1332        commitment: Commitment<ChainConfig>,
1333    ) -> anyhow::Result<ChainConfig> {
1334        // Try fetching the chain config on the local providers first
1335        let local_result = self
1336            .on_local_providers(move |provider| async move {
1337                provider.try_fetch_chain_config(retry, commitment).await
1338            })
1339            .await;
1340
1341        // Check if we were successful locally
1342        match &local_result {
1343            Ok(_) => return local_result,
1344            Err(err) => tracing::debug!("{err:#}"),
1345        }
1346
1347        // If that fails, try the remote ones
1348        self.on_remote_providers(move |provider| async move {
1349            provider.try_fetch_chain_config(retry, commitment).await
1350        })
1351        .await
1352    }
1353
1354    async fn try_fetch_reward_merkle_tree_v2(
1355        &self,
1356        retry: usize,
1357        height: u64,
1358        view: ViewNumber,
1359        reward_merkle_tree_root: RewardMerkleCommitmentV2,
1360        accounts: Arc<Vec<RewardAccountV2>>,
1361    ) -> anyhow::Result<PermittedRewardMerkleTreeV2> {
1362        let local_result = self
1363            .on_local_providers(clone! {(accounts) move |provider| {
1364                clone! {(accounts) async move {
1365                    provider
1366                        .try_fetch_reward_merkle_tree_v2(
1367                            retry,
1368                            height,
1369                            view,
1370                            reward_merkle_tree_root,
1371                            accounts,
1372                        )
1373                        .await
1374                }}
1375            }})
1376            .await;
1377
1378        // Check if we were successful locally
1379        match &local_result {
1380            Ok(_) => return local_result,
1381            Err(err) => tracing::debug!("{err:#}"),
1382        }
1383
1384        // If that fails, try the remote ones
1385        self.on_remote_providers(clone! {(accounts) move |provider| {
1386            clone!{(accounts) async move {
1387                provider
1388                .try_fetch_reward_merkle_tree_v2(
1389                    retry,
1390                    height,
1391                    view,
1392                    reward_merkle_tree_root,
1393                    accounts
1394                ).await
1395            }}
1396        }})
1397        .await
1398    }
1399
1400    async fn try_fetch_reward_accounts_v1(
1401        &self,
1402        retry: usize,
1403        instance: &NodeState,
1404        height: u64,
1405        view: ViewNumber,
1406        reward_merkle_tree_root: RewardMerkleCommitmentV1,
1407        accounts: &[RewardAccountV1],
1408    ) -> anyhow::Result<Vec<RewardAccountProofV1>> {
1409        // Try to get the accounts on local providers first
1410        let accounts_vec = accounts.to_vec();
1411        let local_result = self
1412            .on_local_providers(clone! {(instance, accounts_vec) move |provider| {
1413                clone! {(instance, accounts_vec) async move {
1414                    provider
1415                        .try_fetch_reward_accounts_v1(
1416                            retry,
1417                            &instance,
1418                            height,
1419                            view,
1420                            reward_merkle_tree_root,
1421                            &accounts_vec,
1422                        )
1423                        .await
1424                }}
1425            }})
1426            .await;
1427
1428        // Check if we were successful locally
1429        match &local_result {
1430            Ok(_) => return local_result,
1431            Err(err) => tracing::debug!("{err:#}"),
1432        }
1433
1434        // If that fails, try the remote ones
1435        self.on_remote_providers(clone! {(instance, accounts_vec) move |provider| {
1436            clone!{(instance, accounts_vec) async move {
1437                provider
1438                .try_fetch_reward_accounts_v1(
1439                    retry,
1440                    &instance,
1441                    height,
1442                    view,
1443                    reward_merkle_tree_root,
1444                    &accounts_vec,
1445                ).await
1446            }}
1447        }})
1448        .await
1449    }
1450
1451    async fn try_fetch_state_cert(
1452        &self,
1453        retry: usize,
1454        epoch: u64,
1455    ) -> anyhow::Result<LightClientStateUpdateCertificateV2<SeqTypes>> {
1456        // Try fetching the state cert on the local providers first
1457        let local_result = self
1458            .on_local_providers(move |provider| async move {
1459                provider.try_fetch_state_cert(retry, epoch).await
1460            })
1461            .await;
1462
1463        // Check if we were successful locally
1464        match &local_result {
1465            Ok(_) => return local_result,
1466            Err(err) => tracing::debug!("{err:#}"),
1467        }
1468
1469        // If that fails, try the remote ones
1470        self.on_remote_providers(move |provider| async move {
1471            provider.try_fetch_state_cert(retry, epoch).await
1472        })
1473        .await
1474    }
1475
1476    fn backoff(&self) -> &BackoffParams {
1477        &self.backoff
1478    }
1479
1480    fn name(&self) -> String {
1481        format!(
1482            "[{}]",
1483            self.providers
1484                .lock()
1485                .iter()
1486                .map(|p| p.name())
1487                .collect::<Vec<_>>()
1488                .join(", ")
1489        )
1490    }
1491
1492    async fn fetch_accounts(
1493        &self,
1494        instance: &NodeState,
1495        height: u64,
1496        view: ViewNumber,
1497        fee_merkle_tree_root: FeeMerkleCommitment,
1498        accounts: Vec<FeeAccount>,
1499    ) -> anyhow::Result<Vec<FeeAccountProof>> {
1500        // Try to get the accounts on local providers first
1501        let accounts_vec = accounts.to_vec();
1502        let local_result = self
1503            .on_local_providers(clone! {(instance, accounts_vec) move |provider| {
1504                clone! {(instance, accounts_vec) async move {
1505                    provider
1506                        .try_fetch_accounts(
1507                            0,
1508                            &instance,
1509                            height,
1510                            view,
1511                            fee_merkle_tree_root,
1512                            &accounts_vec,
1513                        )
1514                        .await
1515                }}
1516            }})
1517            .await;
1518
1519        // Check if we were successful locally
1520        match &local_result {
1521            Ok(_) => return local_result,
1522            Err(err) => tracing::debug!("{err:#}"),
1523        }
1524
1525        // If that fails, try the remote ones (with retry)
1526        self.on_remote_providers(clone! {(instance, accounts_vec) move |provider| {
1527            clone!{(instance, accounts_vec) async move {
1528                provider
1529                .fetch_accounts(
1530                    &instance,
1531                    height,
1532                    view,
1533                    fee_merkle_tree_root,
1534                    accounts_vec,
1535                ).await
1536            }}
1537        }})
1538        .await
1539    }
1540
1541    async fn fetch_leaf(
1542        &self,
1543        coordinator: EpochMembershipCoordinator<SeqTypes>,
1544        height: u64,
1545    ) -> anyhow::Result<Leaf2> {
1546        // Try fetching the leaf on the local providers first
1547        let local_result = self
1548            .on_local_providers(clone! {(coordinator) move |provider| {
1549                clone!{(coordinator) async move {
1550                    provider
1551                        .try_fetch_leaf(0, coordinator, height)
1552                        .await
1553                }}
1554            }})
1555            .await;
1556
1557        // Check if we were successful locally
1558        match &local_result {
1559            Ok(_) => return local_result,
1560            Err(err) => tracing::debug!("{err:#}"),
1561        }
1562
1563        // If that fails, try the remote ones (with retry)
1564        self.on_remote_providers(clone! {(coordinator) move |provider| {
1565        clone!{(coordinator) async move {
1566            provider
1567                .fetch_leaf(coordinator, height)
1568                .await
1569        }}
1570        }})
1571        .await
1572    }
1573
1574    async fn fetch_chain_config(
1575        &self,
1576        commitment: Commitment<ChainConfig>,
1577    ) -> anyhow::Result<ChainConfig> {
1578        // Try fetching the chain config on the local providers first
1579        let local_result = self
1580            .on_local_providers(move |provider| async move {
1581                provider.try_fetch_chain_config(0, commitment).await
1582            })
1583            .await;
1584
1585        // Check if we were successful locally
1586        match &local_result {
1587            Ok(_) => return local_result,
1588            Err(err) => tracing::debug!("{err:#}"),
1589        }
1590
1591        // If that fails, try the remote ones (with retry)
1592        self.on_remote_providers(move |provider| async move {
1593            provider.fetch_chain_config(commitment).await
1594        })
1595        .await
1596    }
1597
1598    async fn fetch_reward_accounts_v1(
1599        &self,
1600        instance: &NodeState,
1601        height: u64,
1602        view: ViewNumber,
1603        reward_merkle_tree_root: RewardMerkleCommitmentV1,
1604        accounts: Vec<RewardAccountV1>,
1605    ) -> anyhow::Result<Vec<RewardAccountProofV1>> {
1606        // Try to get the accounts on local providers first
1607        let accounts_vec = accounts.to_vec();
1608        let local_result = self
1609            .on_local_providers(clone! {(instance, accounts_vec) move |provider| {
1610                clone! {(instance, accounts_vec) async move {
1611                    provider
1612                        .try_fetch_reward_accounts_v1(
1613                            0,
1614                            &instance,
1615                            height,
1616                            view,
1617                            reward_merkle_tree_root,
1618                            &accounts_vec,
1619                        )
1620                        .await
1621                }}
1622            }})
1623            .await;
1624
1625        // Check if we were successful locally
1626        match &local_result {
1627            Ok(_) => return local_result,
1628            Err(err) => tracing::debug!("{err:#}"),
1629        }
1630
1631        // If that fails, try the remote ones (with retry)
1632        self.on_remote_providers(clone! {(instance, accounts_vec) move |provider| {
1633            clone!{(instance, accounts_vec) async move {
1634                provider
1635                .fetch_reward_accounts_v1(
1636                    &instance,
1637                    height,
1638                    view,
1639                    reward_merkle_tree_root,
1640                    accounts_vec,
1641                ).await
1642            }}
1643        }})
1644        .await
1645    }
1646
1647    async fn fetch_state_cert(
1648        &self,
1649        epoch: u64,
1650    ) -> anyhow::Result<LightClientStateUpdateCertificateV2<SeqTypes>> {
1651        let local_result = self
1652            .on_local_providers(move |provider| async move {
1653                provider.try_fetch_state_cert(0, epoch).await
1654            })
1655            .await;
1656
1657        // Check if we were successful locally
1658        match &local_result {
1659            Ok(_) => return local_result,
1660            Err(err) => tracing::debug!("{err:#}"),
1661        }
1662
1663        // If that fails, try the remote ones (with retry)
1664        self.on_remote_providers(
1665            move |provider| async move { provider.fetch_state_cert(epoch).await },
1666        )
1667        .await
1668    }
1669
1670    async fn remember_blocks_merkle_tree(
1671        &self,
1672        instance: &NodeState,
1673        height: u64,
1674        view: ViewNumber,
1675        mt: &mut BlockMerkleTree,
1676    ) -> anyhow::Result<()> {
1677        // Try to remember the blocks merkle tree on local providers first
1678        let local_result = self
1679            .on_local_providers(clone! {(mt, instance) move |provider| {
1680                let mut mt = mt.clone();
1681                clone! {(instance) async move {
1682                    // Perform the call
1683                    provider
1684                        .try_remember_blocks_merkle_tree(
1685                            0,
1686                            &instance,
1687                            height,
1688                            view,
1689                            &mut mt,
1690                        )
1691                        .await?;
1692
1693                    // Return the merkle tree so we can modify it
1694                    Ok(mt)
1695                }}
1696            }})
1697            .await;
1698
1699        // Check if we were successful locally
1700        if let Ok(modified_mt) = local_result {
1701            // Set the merkle tree to the one with the
1702            // successful call
1703            *mt = modified_mt;
1704
1705            return Ok(());
1706        }
1707
1708        // If that fails, try the remote ones (with retry)
1709        let remote_result = self
1710            .on_remote_providers(clone! {(mt, instance) move |provider| {
1711                let mut mt = mt.clone();
1712                clone!{(instance) async move {
1713                    // Perform the call
1714                    provider
1715                    .remember_blocks_merkle_tree(
1716                        &instance,
1717                        height,
1718                        view,
1719                        &mut mt,
1720                    )
1721                    .await?;
1722
1723                    // Return the merkle tree
1724                    Ok(mt)
1725                }}
1726            }})
1727            .await?;
1728
1729        // Update the original, local merkle tree
1730        *mt = remote_result;
1731
1732        Ok(())
1733    }
1734
1735    fn is_local(&self) -> bool {
1736        self.providers.lock().iter().all(|p| p.is_local())
1737    }
1738}
1739
1740/// Add accounts to the in-memory consensus state.
1741/// We use this during catchup after receiving verified accounts.
1742pub async fn add_fee_accounts_to_state<I: hotshot::traits::NodeImplementation<SeqTypes>>(
1743    consensus_handle: &ConsensusHandle<SeqTypes, I>,
1744    view: &ViewNumber,
1745    accounts: &[FeeAccount],
1746    tree: &FeeMerkleTree,
1747    leaf: Leaf2,
1748) -> anyhow::Result<()>
1749where
1750    I::Storage: NewProtocolStorage<SeqTypes>,
1751{
1752    let (existing_state, delta) = consensus_handle.state_and_delta(*view).await;
1753    let (state, delta) = match existing_state {
1754        Some(existing) => {
1755            let mut state = (*existing).clone();
1756            for account in accounts {
1757                if let Some((proof, _)) = FeeAccountProof::prove(tree, (*account).into()) {
1758                    if let Err(err) = proof.remember(&mut state.fee_merkle_tree) {
1759                        tracing::warn!(
1760                            ?view,
1761                            %account,
1762                            "cannot update fetched account state: {err:#}"
1763                        );
1764                    }
1765                } else {
1766                    tracing::warn!(?view, %account, "cannot update fetched account state because account is not in the merkle tree");
1767                };
1768            }
1769            (Arc::new(state), delta)
1770        },
1771        None => {
1772            // If we don't already have a leaf for this view, or if we don't have the view
1773            // at all, we can create a new view based on the recovered leaf and add it to
1774            // our state map. In this case, we must also add the leaf to the saved leaves
1775            // map to ensure consistency.
1776            let mut state = ValidatedState::from_header(leaf.block_header());
1777            state.fee_merkle_tree = tree.clone();
1778            (Arc::new(state), None)
1779        },
1780    };
1781
1782    consensus_handle
1783        .update_leaf(leaf, state, delta)
1784        .await
1785        .with_context(|| "failed to update leaf")?;
1786
1787    Ok(())
1788}
1789
1790/// Add accounts to the in-memory consensus state.
1791/// We use this during catchup after receiving verified accounts.
1792pub async fn add_v2_reward_accounts_to_state<I: hotshot::traits::NodeImplementation<SeqTypes>>(
1793    consensus_handle: &ConsensusHandle<SeqTypes, I>,
1794    view: &ViewNumber,
1795    accounts: &[RewardAccountV2],
1796    tree: &RewardMerkleTreeV2,
1797    leaf: Leaf2,
1798) -> anyhow::Result<()>
1799where
1800    I::Storage: NewProtocolStorage<SeqTypes>,
1801{
1802    let (existing_state, delta) = consensus_handle.state_and_delta(*view).await;
1803    let (state, delta) = match existing_state {
1804        Some(existing) => {
1805            let mut state = (*existing).clone();
1806            for account in accounts {
1807                if let Some((proof, _)) = RewardAccountProofV2::prove(tree, (*account).into()) {
1808                    if let Err(err) = proof.remember(&mut state.reward_merkle_tree_v2) {
1809                        tracing::warn!(
1810                            ?view,
1811                            %account,
1812                            "cannot update fetched account state: {err:#}"
1813                        );
1814                    }
1815                } else {
1816                    tracing::warn!(?view, %account, "cannot update fetched account state because account is not in the merkle tree");
1817                };
1818            }
1819            (Arc::new(state), delta)
1820        },
1821        None => {
1822            // If we don't already have a leaf for this view, or if we don't have the view
1823            // at all, we can create a new view based on the recovered leaf and add it to
1824            // our state map. In this case, we must also add the leaf to the saved leaves
1825            // map to ensure consistency.
1826            let mut state = ValidatedState::from_header(leaf.block_header());
1827            state.reward_merkle_tree_v2 = tree.clone();
1828            (Arc::new(state), None)
1829        },
1830    };
1831
1832    consensus_handle
1833        .update_leaf(leaf, state, delta)
1834        .await
1835        .with_context(|| "failed to update leaf")?;
1836
1837    Ok(())
1838}
1839
1840/// Add accounts to the in-memory consensus state.
1841/// We use this during catchup after receiving verified accounts.
1842pub async fn add_v1_reward_accounts_to_state<I: hotshot::traits::NodeImplementation<SeqTypes>>(
1843    consensus_handle: &ConsensusHandle<SeqTypes, I>,
1844    view: &ViewNumber,
1845    accounts: &[RewardAccountV1],
1846    tree: &RewardMerkleTreeV1,
1847    leaf: Leaf2,
1848) -> anyhow::Result<()>
1849where
1850    I::Storage: NewProtocolStorage<SeqTypes>,
1851{
1852    let (existing_state, delta) = consensus_handle.state_and_delta(*view).await;
1853    let (state, delta) = match existing_state {
1854        Some(existing) => {
1855            let mut state = (*existing).clone();
1856            for account in accounts {
1857                if let Some((proof, _)) = RewardAccountProofV1::prove(tree, (*account).into()) {
1858                    if let Err(err) = proof.remember(&mut state.reward_merkle_tree_v1) {
1859                        tracing::warn!(
1860                            ?view,
1861                            %account,
1862                            "cannot update fetched account state: {err:#}"
1863                        );
1864                    }
1865                } else {
1866                    tracing::warn!(?view, %account, "cannot update fetched account state because account is not in the merkle tree");
1867                };
1868            }
1869            (Arc::new(state), delta)
1870        },
1871        None => {
1872            // If we don't already have a leaf for this view, or if we don't have the view
1873            // at all, we can create a new view based on the recovered leaf and add it to
1874            // our state map. In this case, we must also add the leaf to the saved leaves
1875            // map to ensure consistency.
1876            let mut state = ValidatedState::from_header(leaf.block_header());
1877            state.reward_merkle_tree_v1 = tree.clone();
1878            (Arc::new(state), None)
1879        },
1880    };
1881
1882    consensus_handle
1883        .update_leaf(leaf, state, delta)
1884        .await
1885        .with_context(|| "failed to update leaf")?;
1886
1887    Ok(())
1888}
1889
1890#[cfg(test)]
1891mod test {
1892    use super::*;
1893
1894    #[test]
1895    fn test_peer_priority() {
1896        let good_peer = PeerScore {
1897            requests: 1000,
1898            failures: 2,
1899        };
1900        let bad_peer = PeerScore {
1901            requests: 10,
1902            failures: 1,
1903        };
1904        assert!(good_peer > bad_peer);
1905
1906        let mut peers: PriorityQueue<_, _> = [(0, good_peer), (1, bad_peer)].into_iter().collect();
1907        assert_eq!(peers.pop(), Some((0, good_peer)));
1908        assert_eq!(peers.pop(), Some((1, bad_peer)));
1909    }
1910}