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