Skip to main content

espresso_node/request_response/catchup/
state.rs

1use std::sync::Arc;
2
3use anyhow::Context;
4use async_trait::async_trait;
5use committable::{Commitment, Committable};
6use espresso_types::{
7    BackoffParams, BlockMerkleTree, FeeAccount, FeeAccountProof, FeeMerkleCommitment, Leaf2,
8    NodeState, SeqTypes,
9    traits::{SequencerPersistence, StateCatchup},
10    v0_3::{ChainConfig, RewardAccountProofV1, RewardAccountV1, RewardMerkleCommitmentV1},
11    v0_4::{
12        PermittedRewardMerkleTreeV2, RewardAccountV2, RewardMerkleCommitmentV2,
13        forgotten_accounts_include,
14    },
15};
16use hotshot_new_protocol::utils::verify_new_protocol_leaf_chain;
17use hotshot_types::{
18    data::ViewNumber, epoch_membership::EpochMembershipCoordinator, message::UpgradeLock,
19    simple_certificate::LightClientStateUpdateCertificateV2,
20};
21use jf_merkle_tree_compat::{ForgetableMerkleTreeScheme, MerkleTreeScheme};
22use tokio::time::timeout;
23use versions::NEW_PROTOCOL_VERSION;
24
25use crate::{
26    api::RewardMerkleTreeV2Data,
27    catchup::verify_legacy_leaf_chain,
28    request_response::{
29        RequestResponseProtocol,
30        request::{Request, Response},
31    },
32};
33
34#[async_trait]
35impl<P: SequencerPersistence> StateCatchup for RequestResponseProtocol<P> {
36    async fn try_fetch_leaf(
37        &self,
38        _retry: usize,
39        coordinator: EpochMembershipCoordinator<SeqTypes>,
40        height: u64,
41    ) -> anyhow::Result<Leaf2> {
42        // Timeout after a few batches
43        let timeout_duration = self.config.request_batch_interval * 3;
44
45        // Fetch the leaf
46        timeout(timeout_duration, self.fetch_leaf(coordinator, height))
47            .await
48            .with_context(|| "timed out while fetching leaf")?
49    }
50
51    async fn try_fetch_accounts(
52        &self,
53        _retry: usize,
54        instance: &NodeState,
55        height: u64,
56        view: ViewNumber,
57        fee_merkle_tree_root: FeeMerkleCommitment,
58        accounts: &[FeeAccount],
59    ) -> anyhow::Result<Vec<FeeAccountProof>> {
60        // Timeout after a few batches
61        let timeout_duration = self.config.request_batch_interval * 3;
62
63        // Fetch the accounts
64        timeout(
65            timeout_duration,
66            self.fetch_accounts(
67                instance,
68                height,
69                view,
70                fee_merkle_tree_root,
71                accounts.to_vec(),
72            ),
73        )
74        .await
75        .with_context(|| "timed out while fetching accounts")?
76    }
77
78    async fn try_remember_blocks_merkle_tree(
79        &self,
80        _retry: usize,
81        instance: &NodeState,
82        height: u64,
83        view: ViewNumber,
84        mt: &mut BlockMerkleTree,
85    ) -> anyhow::Result<()> {
86        // Timeout after a few batches
87        let timeout_duration = self.config.request_batch_interval * 3;
88
89        // Remember the blocks merkle tree
90        timeout(
91            timeout_duration,
92            self.remember_blocks_merkle_tree(instance, height, view, mt),
93        )
94        .await
95        .with_context(|| "timed out while remembering blocks merkle tree")?
96    }
97
98    async fn try_fetch_chain_config(
99        &self,
100        _retry: usize,
101        commitment: Commitment<ChainConfig>,
102    ) -> anyhow::Result<ChainConfig> {
103        // Timeout after a few batches
104        let timeout_duration = self.config.request_batch_interval * 3;
105
106        // Fetch the chain config
107        timeout(timeout_duration, self.fetch_chain_config(commitment))
108            .await
109            .with_context(|| "timed out while fetching chain config")?
110    }
111
112    async fn try_fetch_reward_merkle_tree_v2(
113        &self,
114        _retry: usize,
115        height: u64,
116        view: ViewNumber,
117        reward_merkle_tree_root: RewardMerkleCommitmentV2,
118        accounts: Arc<Vec<RewardAccountV2>>,
119    ) -> anyhow::Result<PermittedRewardMerkleTreeV2> {
120        // Timeout after a few batches
121        let timeout_duration = self.config.request_batch_interval * 3;
122
123        // Fetch the reward accounts
124        timeout(
125            timeout_duration,
126            self.fetch_reward_merkle_tree_v2(height, view, reward_merkle_tree_root, accounts),
127        )
128        .await
129        .with_context(|| "timed out while fetching reward merkle tree v2")?
130    }
131
132    async fn try_fetch_reward_accounts_v1(
133        &self,
134        _retry: usize,
135        instance: &NodeState,
136        height: u64,
137        view: ViewNumber,
138        reward_merkle_tree_root: RewardMerkleCommitmentV1,
139        accounts: &[RewardAccountV1],
140    ) -> anyhow::Result<Vec<RewardAccountProofV1>> {
141        // Timeout after a few batches
142        let timeout_duration = self.config.request_batch_interval * 3;
143
144        // Fetch the reward accounts
145        timeout(
146            timeout_duration,
147            self.fetch_reward_accounts_v1(
148                instance,
149                height,
150                view,
151                reward_merkle_tree_root,
152                accounts.to_vec(),
153            ),
154        )
155        .await
156        .with_context(|| "timed out while fetching reward accounts")?
157    }
158
159    async fn try_fetch_state_cert(
160        &self,
161        _retry: usize,
162        epoch: u64,
163    ) -> anyhow::Result<LightClientStateUpdateCertificateV2<SeqTypes>> {
164        let timeout_duration = self.config.request_batch_interval * 3;
165
166        timeout(timeout_duration, self.fetch_state_cert(epoch))
167            .await
168            .with_context(|| "timed out while fetching state cert")?
169    }
170
171    fn backoff(&self) -> &BackoffParams {
172        unreachable!()
173    }
174
175    fn name(&self) -> String {
176        "request-response".to_string()
177    }
178
179    async fn fetch_accounts(
180        &self,
181        _instance: &NodeState,
182        height: u64,
183        view: ViewNumber,
184        fee_merkle_tree_root: FeeMerkleCommitment,
185        accounts: Vec<FeeAccount>,
186    ) -> anyhow::Result<Vec<FeeAccountProof>> {
187        tracing::info!("Fetching accounts for height: {height}, view: {view}");
188
189        // Clone things we need in the first closure
190        let accounts_clone = accounts.clone();
191        let response_validation_fn = move |_request: &Request, response: Response| {
192            // Clone again
193            let accounts_clone = accounts_clone.clone();
194
195            async move {
196                // Make sure the response is an accounts response
197                let Response::Accounts(fee_merkle_tree) = response else {
198                    return Err(anyhow::anyhow!("expected accounts response"));
199                };
200
201                // Verify the merkle proofs
202                let mut proofs = Vec::new();
203                for account in accounts_clone {
204                    let (proof, _) = FeeAccountProof::prove(&fee_merkle_tree, account.into())
205                        .with_context(|| format!("response was missing account {account}"))?;
206                    proof.verify(&fee_merkle_tree_root).with_context(|| {
207                        format!(
208                            "invalid proof for fee account {account}, root: {fee_merkle_tree_root}"
209                        )
210                    })?;
211                    proofs.push(proof);
212                }
213
214                Ok(proofs)
215            }
216        };
217
218        // Wait for the protocol to send us the accounts
219        let response = self
220            .request_indefinitely(
221                Request::Accounts(height, *view, accounts),
222                response_validation_fn,
223            )
224            .await
225            .with_context(|| "failed to request accounts")?;
226
227        tracing::info!("Fetched accounts for height: {height}, view: {view}");
228
229        Ok(response)
230    }
231
232    async fn fetch_leaf(
233        &self,
234        coordinator: EpochMembershipCoordinator<SeqTypes>,
235        height: u64,
236    ) -> anyhow::Result<Leaf2> {
237        tracing::info!("Fetching leaf for height: {height}");
238
239        // Fetch the leaf chain. For new-protocol heights the responder returns the leaf range
240        // `[height..=cert2_height]`; for legacy-protocol heights, it returns a 3-chain.
241        let leaf_chain = self
242            .request_indefinitely(
243                Request::Leaf(height),
244                move |_request: &Request, response: Response| async move {
245                    let Response::Leaf(leaves) = response else {
246                        return Err(anyhow::anyhow!("expected leaf response"));
247                    };
248                    if leaves.is_empty() {
249                        return Err(anyhow::anyhow!(
250                            "received empty leaf chain for height {height}"
251                        ));
252                    }
253                    Ok(leaves)
254                },
255            )
256            .await
257            .with_context(|| "failed to request leaf chain")?;
258
259        let result = if leaf_chain[0].block_header().version() >= NEW_PROTOCOL_VERSION {
260            // New protocol: pair the leaf range with its finalizing cert2 for verification.
261            let upgrade_lock =
262                UpgradeLock::<SeqTypes>::new(versions::Upgrade::trivial(NEW_PROTOCOL_VERSION));
263            // The chain terminates at the leaf the cert2 finalizes, so request that cert2 by its
264            // exact height
265            let cert2_height = leaf_chain
266                .last()
267                .ok_or_else(|| anyhow::anyhow!("empty leaf chain for height {height}"))?
268                .height();
269            let cert2 = self
270                .request_indefinitely(
271                    Request::Cert2(cert2_height),
272                    move |_request: &Request, response: Response| async move {
273                        let Response::Cert2(cert2) = response else {
274                            return Err(anyhow::anyhow!("expected cert2 response"));
275                        };
276                        if cert2.data.block_number != cert2_height {
277                            return Err(anyhow::anyhow!(
278                                "received cert2 at height {} but expected exactly {cert2_height}",
279                                cert2.data.block_number
280                            ));
281                        }
282                        Ok(cert2)
283                    },
284                )
285                .await
286                .with_context(|| format!("failed to request cert2 at height {cert2_height}"))?;
287
288            verify_new_protocol_leaf_chain(leaf_chain, &coordinator, height, &upgrade_lock, cert2)
289                .await
290                .with_context(|| "leaf chain verification with cert2 failed")?
291        } else {
292            verify_legacy_leaf_chain(leaf_chain, &coordinator, height).await?
293        };
294
295        tracing::info!("Fetched leaf for height: {height}");
296        Ok(result)
297    }
298
299    async fn fetch_chain_config(
300        &self,
301        commitment: Commitment<ChainConfig>,
302    ) -> anyhow::Result<ChainConfig> {
303        tracing::info!("Fetching chain config with commitment: {commitment}");
304
305        // Create the response validation function
306        let response_validation_fn = move |_request: &Request, response: Response| {
307            async move {
308                // Make sure the response is a chain config response
309                let Response::ChainConfig(chain_config) = response else {
310                    return Err(anyhow::anyhow!("expected chain config response"));
311                };
312
313                // Make sure the commitments match
314                if commitment != chain_config.commit() {
315                    return Err(anyhow::anyhow!("chain config commitment mismatch"));
316                }
317
318                Ok(chain_config)
319            }
320        };
321
322        // Wait for the protocol to send us the chain config
323        let response = self
324            .request_indefinitely(Request::ChainConfig(commitment), response_validation_fn)
325            .await
326            .with_context(|| "failed to request chain config")?;
327
328        tracing::info!("Fetched chain config with commitment: {commitment}");
329
330        Ok(response)
331    }
332
333    async fn remember_blocks_merkle_tree(
334        &self,
335        _instance: &NodeState,
336        height: u64,
337        view: ViewNumber,
338        mt: &mut BlockMerkleTree,
339    ) -> anyhow::Result<()> {
340        tracing::info!("Fetching blocks frontier for height: {height}, view: {view}");
341
342        // Clone the merkle tree
343        let mt_clone = mt.clone();
344
345        // Create the response validation function
346        let response_validation_fn = move |_request: &Request, response: Response| {
347            // Clone the merkle tree
348            let mut block_merkle_tree = mt_clone.clone();
349
350            async move {
351                // Make sure the response is a blocks frontier response
352                let Response::BlocksFrontier(blocks_frontier) = response else {
353                    return Err(anyhow::anyhow!("expected blocks frontier response"));
354                };
355
356                // Get the leaf element associated with the proof
357                let leaf_elem = blocks_frontier
358                    .elem()
359                    .with_context(|| "provided frontier is missing leaf element")?;
360
361                // Verify the block proof
362                block_merkle_tree
363                    .remember(
364                        block_merkle_tree.num_leaves() - 1,
365                        *leaf_elem,
366                        blocks_frontier,
367                    )
368                    .with_context(|| "merkle tree verification failed")?;
369
370                // Return the verified merkle tree
371                Ok(block_merkle_tree)
372            }
373        };
374
375        // Wait for the protocol to send us the blocks frontier
376        let response = self
377            .request_indefinitely(
378                Request::BlocksFrontier(height, *view),
379                response_validation_fn,
380            )
381            .await
382            .with_context(|| "failed to request blocks frontier")?;
383
384        // Replace the merkle tree
385        *mt = response;
386
387        tracing::info!("Fetched blocks frontier for height: {height}, view: {view}");
388
389        Ok(())
390    }
391
392    async fn fetch_reward_merkle_tree_v2(
393        &self,
394        height: u64,
395        view: ViewNumber,
396        reward_merkle_tree_root: RewardMerkleCommitmentV2,
397        accounts: Arc<Vec<RewardAccountV2>>,
398    ) -> anyhow::Result<PermittedRewardMerkleTreeV2> {
399        tracing::info!("Fetching RewardMerkleTreeV2 for height: {height}");
400
401        // Create the response validation function
402        let response_validation_fn = move |_request: &Request, response: Response| {
403            let accounts = accounts.clone();
404            async move {
405                // Make sure the response is a reward accounts response
406                let Response::RewardMerkleTreeV2(tree_bytes) = response else {
407                    return Err(anyhow::anyhow!("expected reward accounts response"));
408                };
409
410                let tree_data = bincode::deserialize::<RewardMerkleTreeV2Data>(&tree_bytes)
411                    .context(
412                        "Failed to deserialize RewardMerkleTreeV2 for height {height} from remote",
413                    )?;
414
415                // Verify the tree's commitment
416                let reward_merkle_tree: PermittedRewardMerkleTreeV2 =
417                    PermittedRewardMerkleTreeV2::try_from_kv_set(tree_data.balances).await?;
418
419                anyhow::ensure!(reward_merkle_tree.tree.commitment() == reward_merkle_tree_root);
420                anyhow::ensure!(!forgotten_accounts_include(&reward_merkle_tree, &accounts));
421
422                Ok(reward_merkle_tree)
423            }
424        };
425
426        // Wait for the protocol to send us the reward accounts
427        let response = self
428            .request_indefinitely(
429                Request::RewardMerkleTreeV2(height, *view),
430                response_validation_fn,
431            )
432            .await
433            .with_context(|| "failed to request reward accounts")?;
434
435        tracing::info!("Fetched RewardMerkleTreeV2 for height: {height}");
436
437        Ok(response)
438    }
439
440    async fn fetch_reward_accounts_v1(
441        &self,
442        _instance: &NodeState,
443        height: u64,
444        view: ViewNumber,
445        reward_merkle_tree_root: RewardMerkleCommitmentV1,
446        accounts: Vec<RewardAccountV1>,
447    ) -> anyhow::Result<Vec<RewardAccountProofV1>> {
448        tracing::info!("Fetching v1 reward accounts for height: {height}, view: {view}");
449
450        // Clone things we need in the first closure
451        let accounts_clone = accounts.clone();
452
453        // Create the response validation function
454        let response_validation_fn = move |_request: &Request, response: Response| {
455            // Clone again
456            let accounts_clone = accounts_clone.clone();
457
458            async move {
459                // Make sure the response is a reward accounts response
460                let Response::RewardAccountsV1(reward_merkle_tree) = response else {
461                    return Err(anyhow::anyhow!("expected v1 reward accounts response"));
462                };
463
464                // Verify the merkle proofs
465                let mut proofs = Vec::new();
466                for account in accounts_clone {
467                    let (proof, _) =
468                        RewardAccountProofV1::prove(&reward_merkle_tree, account.into())
469                            .with_context(|| format!("response was missing account {account}"))?;
470                    proof.verify(&reward_merkle_tree_root).with_context(|| {
471                        format!(
472                            "invalid proof for v1 reward account {account}, root: \
473                             {reward_merkle_tree_root}"
474                        )
475                    })?;
476                    proofs.push(proof);
477                }
478
479                Ok(proofs)
480            }
481        };
482
483        // Wait for the protocol to send us the reward accounts
484        let response = self
485            .request_indefinitely(
486                Request::RewardAccountsV1(height, *view, accounts),
487                response_validation_fn,
488            )
489            .await
490            .with_context(|| "failed to request v1 reward accounts")?;
491
492        tracing::info!("Fetched v1 reward accounts for height: {height}, view: {view}");
493
494        Ok(response)
495    }
496
497    async fn fetch_state_cert(
498        &self,
499        epoch: u64,
500    ) -> anyhow::Result<LightClientStateUpdateCertificateV2<SeqTypes>> {
501        tracing::info!("Fetching state cert for epoch: {epoch}");
502
503        // Create the response validation function
504        let response_validation_fn = move |_request: &Request, response: Response| async move {
505            // Make sure the response is a state cert response
506            let Response::StateCert(state_cert) = response else {
507                return Err(anyhow::anyhow!("expected state cert response"));
508            };
509
510            Ok(state_cert)
511        };
512
513        // Wait for the protocol to send us the state cert
514        let response = self
515            .request_indefinitely(Request::StateCert(epoch), response_validation_fn)
516            .await
517            .with_context(|| "failed to request state cert")?;
518
519        tracing::info!("Fetched state cert for epoch: {epoch}");
520
521        Ok(response)
522    }
523
524    fn is_local(&self) -> bool {
525        false
526    }
527}