light_client/
testing.rs

1#![cfg(any(test, feature = "testing"))]
2
3use std::{
4    cmp::max,
5    collections::{HashMap, HashSet},
6    sync::Arc,
7};
8
9use alloy::primitives::{Address, U256};
10use anyhow::{Context, Result, bail, ensure};
11use async_lock::Mutex;
12use bitvec::vec::BitVec;
13use committable::{Commitment, Committable};
14use derivative::Derivative;
15use espresso_types::{
16    BLOCK_MERKLE_TREE_HEIGHT, BlockMerkleTree, EpochVersion, Leaf2, NamespaceId, NodeState,
17    NsProof, Payload, PrivKey, PubKey, RegisteredValidatorMap, SeqTypes, StakeTableHash,
18    StakeTableState, Transaction,
19    v0_3::{AuthenticatedValidator, RegisteredValidator, StakeTableEvent},
20};
21use hotshot_contract_adapter::sol_types::StakeTableV2::{Delegated, ValidatorRegistered};
22use hotshot_query_service::{
23    availability::{LeafHash, LeafId, LeafQueryData},
24    node::{BlockHash, BlockId},
25};
26use hotshot_types::{
27    data::{
28        EpochNumber, QuorumProposal2, QuorumProposalWrapper, VidCommitment, VidCommon, ViewNumber,
29        vid_commitment,
30    },
31    message::UpgradeLock,
32    signature_key::SchnorrPubKey,
33    simple_certificate::{NextEpochQuorumCertificate2, QuorumCertificate2, UpgradeCertificate},
34    simple_vote::{NextEpochQuorumData2, QuorumData2, UpgradeProposalData, VersionedVoteData},
35    stake_table::{StakeTableEntry, supermajority_threshold},
36    traits::{
37        block_contents::EncodeBytes,
38        signature_key::{SignatureKey, StateSignatureKey},
39    },
40    utils::{epoch_from_block_number, is_epoch_transition, is_ge_epoch_root},
41    vid::avidm::init_avidm_param,
42    vote::Certificate as _,
43};
44use jf_merkle_tree_compat::{
45    AppendableMerkleTreeScheme, MerkleTreeScheme, prelude::SHA3MerkleTree,
46};
47use rand::RngCore;
48use vbs::version::{StaticVersionType, Version};
49use versions::{DRB_AND_HEADER_UPGRADE_VERSION, EPOCH_VERSION, FEE_VERSION, Upgrade, version};
50
51use crate::{
52    client::Client,
53    consensus::{
54        header::HeaderProof,
55        leaf::LeafProof,
56        namespace::NamespaceProof,
57        payload::PayloadProof,
58        quorum::{Certificate, Quorum},
59    },
60    state::Genesis,
61    storage::LeafRequest,
62};
63
64pub const ENABLE_EPOCHS: Upgrade = Upgrade::new(FEE_VERSION, DRB_AND_HEADER_UPGRADE_VERSION);
65
66pub const LEGACY_VERSION: Version = FEE_VERSION;
67
68/// Extract a chain of QCs from a chain of leaves.
69///
70/// The resulting QC chain will be one shorter than the leaf chain, and will justify the finality of
71/// the leaf _preceding_ this leaf chain, since we extract QCs from the justifying QC of each leaf.
72pub fn qc_chain_from_leaf_chain<'a>(
73    leaves: impl IntoIterator<Item = &'a LeafQueryData<SeqTypes>>,
74) -> Vec<Certificate> {
75    leaves
76        .into_iter()
77        .map(|leaf| Certificate::for_parent(leaf.leaf()))
78        .collect()
79}
80
81/// Construct a valid leaf chain for the given height range.
82pub async fn leaf_chain(
83    range: impl IntoIterator<Item = u64>,
84    base: Version,
85) -> Vec<LeafQueryData<SeqTypes>> {
86    custom_leaf_chain(Upgrade::trivial(base), range, |_| {}).await
87}
88
89/// Construct a valid leaf chain for the given height range.
90///
91/// The chain will upgrade from `base` to `upgrade` at height `upgrade_height`.
92pub async fn leaf_chain_with_upgrade(
93    range: impl IntoIterator<Item = u64>,
94    upgrade_height: u64,
95    upgrade: Upgrade,
96) -> Vec<LeafQueryData<SeqTypes>> {
97    custom_leaf_chain_with_upgrade(range, upgrade_height, upgrade, |_| {}).await
98}
99
100/// Construct a customized leaf chain for the given height range.
101///
102/// The chain will upgrade from `base` to `upgrade` at height `upgrade_height`.
103pub async fn custom_leaf_chain_with_upgrade(
104    range: impl IntoIterator<Item = u64>,
105    upgrade_height: u64,
106    upgrade: Upgrade,
107    map: impl Fn(&mut QuorumProposal2<SeqTypes>),
108) -> Vec<LeafQueryData<SeqTypes>> {
109    let upgrade_leaf: Leaf2 = Leaf2::genesis(
110        &Default::default(),
111        &NodeState::mock()
112            .with_genesis_version(upgrade.target)
113            .with_current_version(upgrade.target),
114        upgrade.base,
115    )
116    .await;
117    let upgrade_data = UpgradeProposalData {
118        old_version: upgrade.base,
119        new_version: upgrade.target,
120        new_version_hash: Default::default(),
121        old_version_last_view: ViewNumber::new(upgrade_height - 1),
122        new_version_first_view: ViewNumber::new(upgrade_height),
123        decide_by: ViewNumber::new(upgrade_height),
124    };
125    let upgrade_commit = upgrade_data.commit();
126    let upgrade_cert = UpgradeCertificate::new(
127        upgrade_data,
128        upgrade_commit,
129        ViewNumber::new(upgrade_height),
130        Default::default(),
131        Default::default(),
132    );
133
134    custom_leaf_chain(upgrade, range, |proposal| {
135        let height = proposal.block_header.height();
136        if height < upgrade_height {
137            // All views leading up to the upgrade get a certificate indicating the coming upgrade.
138            proposal.upgrade_certificate = Some(upgrade_cert.clone());
139        } else {
140            // After the upgrade takes effect we stop attaching the upgrade certificate, and we use
141            // the upgraded header version.
142            proposal.upgrade_certificate = None;
143            proposal.block_header = upgrade_leaf.block_header().clone();
144            *proposal.block_header.height_mut() = height;
145        }
146        map(proposal);
147    })
148    .await
149}
150
151/// Construct a customized leaf chain for the given height range.
152pub async fn custom_leaf_chain(
153    upgrade: Upgrade,
154    range: impl IntoIterator<Item = u64>,
155    map: impl Fn(&mut QuorumProposal2<SeqTypes>),
156) -> Vec<LeafQueryData<SeqTypes>> {
157    let node_state = NodeState::mock()
158        .with_genesis_version(upgrade.base)
159        .with_current_version(upgrade.base);
160    let genesis_leaf: Leaf2 = Leaf2::genesis(&Default::default(), &node_state, upgrade.base).await;
161    tracing::info!(?genesis_leaf, "leaf chain");
162
163    let mut qc = QuorumCertificate2::genesis(&Default::default(), &node_state, upgrade).await;
164    let mut quorum_proposal = QuorumProposalWrapper::<SeqTypes> {
165        proposal: QuorumProposal2::<SeqTypes> {
166            epoch: None,
167            block_header: genesis_leaf.block_header().clone(),
168            view_number: genesis_leaf.view_number(),
169            justify_qc: qc.clone(),
170            upgrade_certificate: None,
171            view_change_evidence: None,
172            next_drb_result: None,
173            next_epoch_justify_qc: None,
174            state_cert: None,
175        },
176    };
177
178    let mut block_merkle_tree = BlockMerkleTree::new(BLOCK_MERKLE_TREE_HEIGHT);
179    let mut leaves = vec![];
180    for height in range {
181        *quorum_proposal.proposal.block_header.height_mut() = height;
182        *quorum_proposal
183            .proposal
184            .block_header
185            .block_merkle_tree_root_mut() = block_merkle_tree.commitment();
186        quorum_proposal.proposal.view_number = ViewNumber::new(height);
187        map(&mut quorum_proposal.proposal);
188        let leaf = Leaf2::from_quorum_proposal(&quorum_proposal);
189
190        qc.view_number = ViewNumber::new(height);
191        qc.data.leaf_commit = Committable::commit(&leaf);
192        if leaf.block_header().version() >= EPOCH_VERSION {
193            qc.data.block_number = Some(height);
194        }
195
196        block_merkle_tree
197            .push(leaf.block_header().commit())
198            .unwrap();
199        leaves.push(LeafQueryData::new(leaf, qc.clone()).unwrap());
200        quorum_proposal.proposal.justify_qc = qc.clone();
201    }
202
203    leaves
204}
205
206/// Construct a valid leaf chain during which the epoch advances.
207pub async fn epoch_change_leaf_chain(
208    range: impl IntoIterator<Item = u64>,
209    epoch_height: u64,
210    version: Version,
211) -> Vec<LeafQueryData<SeqTypes>> {
212    custom_epoch_change_leaf_chain(range, epoch_height, version, |_| {}).await
213}
214
215/// Construct a customized leaf chain during which the epoch advances.
216pub async fn custom_epoch_change_leaf_chain(
217    range: impl IntoIterator<Item = u64>,
218    epoch_height: u64,
219    version: Version,
220    map: impl Fn(&mut QuorumProposal2<SeqTypes>),
221) -> Vec<LeafQueryData<SeqTypes>> {
222    custom_leaf_chain(Upgrade::trivial(version), range, |proposal| {
223        if is_epoch_transition(proposal.block_header.height(), epoch_height) {
224            let data: NextEpochQuorumData2<SeqTypes> = proposal.justify_qc.data.clone().into();
225            let commit = data.commit();
226            proposal.next_epoch_justify_qc = Some(NextEpochQuorumCertificate2::new(
227                data,
228                commit,
229                proposal.justify_qc.view_number,
230                Default::default(),
231                Default::default(),
232            ));
233            map(proposal);
234        }
235    })
236    .await
237}
238
239#[derive(Clone, Copy, Debug, Default)]
240pub struct AlwaysTrueQuorum;
241
242impl Quorum for AlwaysTrueQuorum {
243    async fn verify_static<V: StaticVersionType + 'static>(&self, _: &Certificate) -> Result<()> {
244        Ok(())
245    }
246}
247
248#[derive(Clone, Copy, Debug, Default)]
249pub struct AlwaysFalseQuorum;
250
251impl Quorum for AlwaysFalseQuorum {
252    async fn verify_static<V: StaticVersionType + 'static>(&self, _: &Certificate) -> Result<()> {
253        bail!("always false quorum");
254    }
255}
256
257/// A quorum which verifies that calls to `verify` use the correct version, but does not check
258/// signatures.
259#[derive(Clone, Debug, Default)]
260pub struct VersionCheckQuorum {
261    leaves: HashMap<Commitment<Leaf2>, Leaf2>,
262}
263
264impl VersionCheckQuorum {
265    pub fn new(leaves: impl IntoIterator<Item = Leaf2>) -> Self {
266        Self {
267            leaves: leaves
268                .into_iter()
269                .map(|leaf| (leaf.commit(), leaf))
270                .collect(),
271        }
272    }
273}
274
275impl Quorum for VersionCheckQuorum {
276    async fn verify_static<V: StaticVersionType + 'static>(
277        &self,
278        cert: &Certificate,
279    ) -> anyhow::Result<()> {
280        let leaf = self
281            .leaves
282            .get(&cert.leaf_commit())
283            .context(format!("unknown leaf {}", cert.leaf_commit()))?;
284        ensure!(
285            leaf.block_header().version() == version(V::MAJOR, V::MINOR),
286            "version mismatch: leaf has version {}, but verifier is using version {}",
287            leaf.block_header().version(),
288            V::version()
289        );
290        Ok(())
291    }
292}
293
294/// A quorum which verifies that epoch change QCs are provided, but does not check signatures.
295#[derive(Clone, Debug, Default)]
296pub struct EpochChangeQuorum {
297    epoch_height: u64,
298}
299
300impl EpochChangeQuorum {
301    pub fn new(epoch_height: u64) -> Self {
302        Self { epoch_height }
303    }
304}
305
306impl Quorum for EpochChangeQuorum {
307    async fn verify_static<V: StaticVersionType + 'static>(
308        &self,
309        cert: &Certificate,
310    ) -> anyhow::Result<()> {
311        if V::version() >= EpochVersion::version() {
312            cert.verify_next_epoch_qc(self.epoch_height)?;
313        }
314        Ok(())
315    }
316}
317
318#[derive(Clone, Debug)]
319pub struct TestClient {
320    inner: Arc<Mutex<InnerTestClient>>,
321    epoch_height: u64,
322}
323
324impl Default for TestClient {
325    fn default() -> Self {
326        Self {
327            inner: Default::default(),
328            epoch_height: 100,
329        }
330    }
331}
332
333#[derive(Debug, Derivative)]
334#[derivative(Default)]
335struct InnerTestClient {
336    leaves: Vec<LeafQueryData<SeqTypes>>,
337    payloads: Vec<Payload>,
338    // Use an appendable `MerkleTree` rather than a `BlockMerkleTree` (which is a
339    // `LightweightMerkleTree`) so we can look up paths for previously inserted elements.
340    merkle_trees: Vec<SHA3MerkleTree<BlockHash<SeqTypes>>>,
341    leaf_hashes: HashMap<LeafHash<SeqTypes>, usize>,
342    block_hashes: HashMap<BlockHash<SeqTypes>, usize>,
343    payload_hashes: HashMap<VidCommitment, usize>,
344    missing_leaves: HashSet<usize>,
345    invalid_proofs: HashSet<usize>,
346    swapped_leaves: HashMap<usize, usize>,
347    invalid_payloads: HashSet<usize>,
348    quorum: Vec<(PrivKey, AuthenticatedValidator<PubKey>)>,
349    #[derivative(Default(value = "3"))]
350    first_epoch_with_dynamic_stake_table: u64,
351    missing_quorums: HashSet<u64>,
352    invalid_quorums: HashSet<u64>,
353    mock_block_height: Option<u64>,
354}
355
356impl InnerTestClient {
357    fn quorum_for_epoch(&mut self, epoch: u64) -> &[(PrivKey, AuthenticatedValidator<PubKey>)] {
358        // For testing purposes, we will say that one new node joins the quorum each epoch. The
359        // static stake table used before the first epoch with dynamic stake is the same as the
360        // stake table used in that epoch.
361        let quorum_size = max(epoch, self.first_epoch_with_dynamic_stake_table) as usize;
362
363        while self.quorum.len() < quorum_size {
364            let (stake_table_key, priv_key) =
365                PubKey::generated_from_seed_indexed(Default::default(), self.quorum.len() as u64);
366            let (state_ver_key, _) = SchnorrPubKey::generated_from_seed_indexed(
367                Default::default(),
368                self.quorum.len() as u64,
369            );
370            let stake = U256::from(self.quorum.len() + 1) * U256::from(1_000_000_000u128);
371            let validator: AuthenticatedValidator<PubKey> = RegisteredValidator {
372                account: Address::random(),
373                stake_table_key,
374                state_ver_key,
375                stake,
376                commission: 1,
377                delegators: [(Address::random(), stake)].into_iter().collect(),
378                authenticated: true,
379                x25519_key: None,
380                p2p_addr: None,
381            }
382            .try_into()
383            .expect("authenticated validator");
384            self.quorum.push((priv_key, validator));
385        }
386
387        &self.quorum[..quorum_size]
388    }
389
390    fn stake_table_hash(&mut self, epoch: u64) -> StakeTableHash {
391        let quorum = self.quorum_for_epoch(epoch);
392        let mut validators = RegisteredValidatorMap::default();
393        let mut used_bls_keys = HashSet::default();
394        let mut used_schnorr_keys = HashSet::default();
395        for (_, validator) in quorum {
396            validators.insert(validator.account, validator.clone().into());
397            used_bls_keys.insert(validator.stake_table_key);
398            used_schnorr_keys.insert(validator.state_ver_key.clone());
399        }
400
401        let state = StakeTableState::new(
402            validators,
403            Default::default(),
404            used_bls_keys,
405            used_schnorr_keys,
406        );
407        state.commit()
408    }
409
410    async fn leaf(&mut self, height: usize, epoch_height: u64) -> LeafQueryData<SeqTypes> {
411        let epoch = epoch_from_block_number(height as u64, epoch_height);
412        let (quorum, stake_table): (Vec<_>, Vec<_>) =
413            self.quorum_for_epoch(epoch).iter().cloned().unzip();
414        let mut stake_entries = vec![];
415        let mut total_stake = U256::ZERO;
416        for validator in &stake_table {
417            stake_entries.push(StakeTableEntry {
418                stake_key: validator.stake_table_key,
419                stake_amount: validator.stake,
420            });
421            total_stake += validator.stake;
422        }
423
424        let pp = PubKey::public_parameter(&stake_entries, supermajority_threshold(total_stake));
425
426        for i in self.leaves.len()..=height {
427            let epoch = EpochNumber::new(epoch_from_block_number(i as u64, epoch_height));
428            let view_number = ViewNumber::new(i as u64);
429
430            let upgrade = Upgrade::trivial(DRB_AND_HEADER_UPGRADE_VERSION);
431            let node_state = NodeState::mock_v3().with_genesis_version(upgrade.base);
432            let (justify_qc, mt) = if i == 0 {
433                (
434                    QuorumCertificate2::genesis(&Default::default(), &node_state, upgrade).await,
435                    SHA3MerkleTree::new(BLOCK_MERKLE_TREE_HEIGHT),
436                )
437            } else {
438                let parent = &self.leaves[i - 1];
439                let mut mt = self.merkle_trees[i - 1].clone();
440                mt.push(parent.block_hash()).unwrap();
441                (parent.qc().clone(), mt)
442            };
443
444            let transactions = vec![Transaction::random(&mut rand::thread_rng())];
445            let (payload, ns_table) =
446                Payload::from_transactions_sync(transactions, node_state.chain_config).unwrap();
447            let payload_comm = vid_commitment(
448                &payload.encode(),
449                &ns_table.encode(),
450                quorum.len(),
451                upgrade.base,
452            );
453
454            let mut block_header = Leaf2::genesis(&Default::default(), &node_state, upgrade.base)
455                .await
456                .block_header()
457                .clone();
458            *block_header.height_mut() = i as u64;
459            *block_header.block_merkle_tree_root_mut() = mt.commitment();
460            *block_header.payload_commitment_mut() = payload_comm;
461            *block_header.ns_table_mut() = ns_table;
462            if *epoch + 1 >= self.first_epoch_with_dynamic_stake_table
463                && is_ge_epoch_root(block_header.height(), epoch_height)
464            {
465                assert!(block_header.set_next_stake_table_hash(self.stake_table_hash(*epoch + 1)));
466            }
467            let quorum_proposal = QuorumProposalWrapper::<SeqTypes> {
468                proposal: QuorumProposal2::<SeqTypes> {
469                    epoch: Some(epoch),
470                    block_header,
471                    view_number,
472                    justify_qc,
473                    upgrade_certificate: None,
474                    view_change_evidence: None,
475                    next_drb_result: None,
476                    next_epoch_justify_qc: None,
477                    state_cert: None,
478                },
479            };
480            let leaf = Leaf2::from_quorum_proposal(&quorum_proposal);
481            let quorum_data = QuorumData2 {
482                leaf_commit: leaf.commit(),
483                epoch: Some(epoch),
484                block_number: Some(i as u64),
485            };
486            let quorum_data_comm = VersionedVoteData::new_infallible(
487                quorum_data.clone(),
488                view_number,
489                &UpgradeLock::<SeqTypes>::new(upgrade),
490            )
491            .commit();
492            let signatures = quorum
493                .iter()
494                .map(|key| PubKey::sign(key, quorum_data_comm.as_ref()).unwrap())
495                .collect::<Vec<_>>();
496            let assembled = PubKey::assemble(
497                &pp,
498                &std::iter::repeat_n(true, quorum.len()).collect::<BitVec>(),
499                &signatures,
500            );
501            let qc = QuorumCertificate2::create_signed_certificate(
502                quorum_data_comm,
503                quorum_data,
504                assembled,
505                view_number,
506            );
507            let leaf = LeafQueryData::new(leaf, qc).unwrap();
508            self.leaf_hashes.insert(leaf.hash(), i);
509            self.block_hashes.insert(leaf.block_hash(), i);
510            self.payload_hashes.entry(leaf.payload_hash()).or_insert(i);
511            self.leaves.push(leaf);
512            self.payloads.push(payload);
513            self.merkle_trees.push(mt);
514        }
515
516        self.leaves[height].clone()
517    }
518
519    fn leaf_height(&self, req: LeafRequest) -> Result<usize> {
520        match req {
521            LeafRequest::Leaf(LeafId::Number(h)) | LeafRequest::Header(BlockId::Number(h)) => Ok(h),
522            LeafRequest::Leaf(LeafId::Hash(h)) => self
523                .leaf_hashes
524                .get(&h)
525                .copied()
526                .context(format!("missing leaf {h}")),
527            LeafRequest::Header(BlockId::Hash(h)) => self
528                .block_hashes
529                .get(&h)
530                .copied()
531                .context(format!("missing block {h}")),
532            LeafRequest::Header(BlockId::PayloadHash(h)) => self
533                .payload_hashes
534                .get(&h)
535                .copied()
536                .context(format!("missing payload {h}")),
537        }
538    }
539
540    fn vid_common(&mut self, height: u64, epoch_height: u64) -> VidCommon {
541        let epoch = epoch_from_block_number(height, epoch_height);
542        let quorum = self.quorum_for_epoch(epoch);
543        VidCommon::V1(init_avidm_param(quorum.len()).unwrap())
544    }
545}
546
547impl TestClient {
548    pub async fn genesis(&self) -> Genesis {
549        let mut inner = self.inner.lock().await;
550        Genesis {
551            epoch_height: self.epoch_height,
552            stake_table: inner
553                .quorum_for_epoch(1)
554                .iter()
555                .map(|(_, validator)| StakeTableEntry {
556                    stake_key: validator.stake_table_key,
557                    stake_amount: validator.stake,
558                })
559                .collect(),
560            first_epoch_with_dynamic_stake_table: EpochNumber::new(
561                inner.first_epoch_with_dynamic_stake_table,
562            ),
563
564            #[cfg(feature = "decaf")]
565            decaf_first_pos_epoch: None,
566        }
567    }
568
569    pub async fn leaf(&self, height: usize) -> LeafQueryData<SeqTypes> {
570        let mut inner = self.inner.lock().await;
571        inner.leaf(height, self.epoch_height).await
572    }
573
574    pub async fn payload(&self, height: usize) -> Payload {
575        let mut inner = self.inner.lock().await;
576        inner.leaf(height, self.epoch_height).await;
577        inner.payloads[height].clone()
578    }
579
580    pub async fn return_invalid_payload(&self, for_height: usize) {
581        let mut inner = self.inner.lock().await;
582        inner.invalid_payloads.insert(for_height);
583    }
584
585    pub async fn remember_leaf(&self, height: usize) -> LeafQueryData<SeqTypes> {
586        let mut inner = self.inner.lock().await;
587        inner.missing_leaves.remove(&height);
588        inner.invalid_proofs.remove(&height);
589        inner.swapped_leaves.remove(&height);
590        inner.leaf(height, self.epoch_height).await
591    }
592
593    pub async fn forget_leaf(&self, height: usize) -> LeafQueryData<SeqTypes> {
594        let mut inner = self.inner.lock().await;
595        inner.missing_leaves.insert(height);
596        inner.leaf(height, self.epoch_height).await
597    }
598
599    pub async fn return_invalid_proof(&self, for_height: usize) {
600        let mut inner = self.inner.lock().await;
601        inner.invalid_proofs.insert(for_height);
602    }
603
604    pub async fn return_wrong_leaf(&self, for_height: usize, substitute: usize) {
605        let mut inner = self.inner.lock().await;
606        inner.swapped_leaves.insert(for_height, substitute);
607    }
608
609    pub async fn quorum_for_epoch(&self, epoch: EpochNumber) -> Vec<StakeTableEntry<PubKey>> {
610        let mut inner = self.inner.lock().await;
611        inner
612            .quorum_for_epoch(*epoch)
613            .iter()
614            .map(|(_, validator)| StakeTableEntry {
615                stake_key: validator.stake_table_key,
616                stake_amount: validator.stake,
617            })
618            .collect()
619    }
620
621    pub async fn remember_quorum(&self, epoch: EpochNumber) {
622        let mut inner = self.inner.lock().await;
623        inner.missing_quorums.remove(&*epoch);
624    }
625
626    pub async fn forget_quorum(&self, epoch: EpochNumber) {
627        let mut inner = self.inner.lock().await;
628        inner.missing_quorums.insert(*epoch);
629    }
630
631    pub async fn return_invalid_quorum(&self, epoch: EpochNumber) {
632        let mut inner = self.inner.lock().await;
633        inner.invalid_quorums.insert(*epoch);
634    }
635
636    pub async fn vid_common(&self, height: u64) -> VidCommon {
637        let mut inner = self.inner.lock().await;
638        inner.vid_common(height, self.epoch_height)
639    }
640
641    pub async fn mock_block_height(&self, height: u64) {
642        let mut inner = self.inner.lock().await;
643        inner.mock_block_height = Some(height);
644    }
645}
646
647impl Client for TestClient {
648    async fn block_height(&self) -> Result<u64> {
649        let inner = self.inner.lock().await;
650        Ok(inner
651            .mock_block_height
652            .unwrap_or_else(|| inner.leaves.len() as u64))
653    }
654
655    async fn leaf_proof(
656        &self,
657        id: impl Into<LeafRequest> + Send,
658        finalized: Option<u64>,
659    ) -> Result<LeafProof> {
660        let mut inner = self.inner.lock().await;
661
662        let mut height = inner.leaf_height(id.into())?;
663        ensure!(
664            !inner.missing_leaves.contains(&height),
665            "missing leaf {height}"
666        );
667        if inner.invalid_proofs.contains(&height) {
668            tracing::info!(height, "return mock incorrect proof");
669            return Ok(LeafProof::default());
670        }
671        if let Some(sub) = inner.swapped_leaves.get(&height) {
672            tracing::info!(height, sub, "return wrong leaf");
673            height = *sub;
674        };
675
676        let leaf = inner.leaf(height, self.epoch_height).await;
677
678        let mut proof = LeafProof::default();
679        proof.push(leaf);
680        if let Some(finalized) = finalized {
681            ensure!(
682                finalized > (height as u64),
683                "assumed finalized leaf must be after requested leaf"
684            );
685            if finalized <= (height as u64) + 2 {
686                tracing::info!(
687                    height,
688                    finalized,
689                    "path to finalized is shorter than path to QC-chain, using finalized"
690                );
691                return Ok(proof);
692            }
693        }
694
695        proof.push(inner.leaf(height + 1, self.epoch_height).await);
696        assert!(proof.push(inner.leaf(height + 2, self.epoch_height).await));
697
698        Ok(proof)
699    }
700
701    async fn header_proof(&self, root: u64, id: BlockId<SeqTypes>) -> Result<HeaderProof> {
702        let mut inner = self.inner.lock().await;
703
704        let root = root as usize;
705        let mut height = inner.leaf_height(id.into())?;
706        ensure!(
707            !inner.missing_leaves.contains(&height),
708            "missing leaf {height}"
709        );
710        if inner.invalid_proofs.contains(&height) {
711            tracing::info!(root, height, "return mock invalid proof");
712            let leaf = inner.leaf(height, self.epoch_height).await;
713            // Construct a proof using a Merkle tree of the wrong height.
714            let mt = BlockMerkleTree::from_elems(
715                Some(BLOCK_MERKLE_TREE_HEIGHT + 1),
716                [leaf.block_hash()],
717            )
718            .unwrap();
719            let proof = mt.lookup(0).expect_ok().unwrap().1;
720            return Ok(HeaderProof::new(leaf.header().clone(), proof));
721        }
722        if let Some(sub) = inner.swapped_leaves.get(&height) {
723            tracing::info!(height, sub, "return wrong leaf");
724            height = *sub;
725        };
726
727        ensure!(height < root);
728
729        let mt = &inner.merkle_trees[root];
730        tracing::info!(height, root = %mt.commitment(), "get proof from Merkle tree");
731        let proof = mt.lookup(height as u64).expect_ok().unwrap().1;
732        let header = inner.leaf(height, self.epoch_height).await.header().clone();
733        Ok(HeaderProof::new(header, proof))
734    }
735
736    async fn get_leaves_in_range(
737        &self,
738        start_height: usize,
739        end_height: usize,
740    ) -> Result<Vec<LeafQueryData<SeqTypes>>> {
741        let mut leaves = Vec::new();
742        let mut inner = self.inner.lock().await;
743        for h in start_height..end_height {
744            let height = *inner.swapped_leaves.get(&h).unwrap_or(&h);
745            leaves.push(inner.leaf(height, self.epoch_height).await);
746        }
747        Ok(leaves)
748    }
749
750    async fn stake_table_events(&self, epoch: EpochNumber) -> Result<Vec<StakeTableEvent>> {
751        let mut inner = self.inner.lock().await;
752
753        ensure!(
754            !inner.missing_quorums.contains(&*epoch),
755            "missing quorum for epoch {epoch}"
756        );
757
758        if inner.invalid_quorums.contains(&*epoch) {
759            // Return random events to create an invalid stake table.
760            let mut events = vec![];
761            let mut seed = [0; 32];
762            rand::thread_rng().fill_bytes(&mut seed);
763            register_validator_events(&mut events, &random_validator());
764            return Ok(events);
765        }
766
767        let mut events = vec![];
768        if *epoch == inner.first_epoch_with_dynamic_stake_table {
769            // Generate events such that the first dynamic stake table is the same as the static
770            // stake table we started with.
771            for (_, validator) in inner.quorum_for_epoch(*epoch) {
772                register_validator_events(&mut events, validator);
773            }
774        } else if *epoch > inner.first_epoch_with_dynamic_stake_table {
775            // For each subsequent epoch, just generate one event for the new validator which joined
776            // in that epoch.
777            let (_, validator) = &inner.quorum_for_epoch(*epoch)[(*epoch as usize) - 1];
778            register_validator_events(&mut events, validator);
779        }
780        Ok(events)
781    }
782
783    async fn payload_proof(&self, height: u64) -> Result<PayloadProof> {
784        let mut inner = self.inner.lock().await;
785
786        let vid_common = inner.vid_common(height, self.epoch_height);
787        let height = height as usize;
788        ensure!(height < inner.payloads.len());
789
790        let payload = if inner.invalid_payloads.contains(&height) {
791            tracing::info!(height, "return mock incorrect payload proof");
792            Payload::from_transactions_sync(
793                [Transaction::random(&mut rand::thread_rng())],
794                NodeState::mock_v3().chain_config,
795            )
796            .unwrap()
797            .0
798        } else {
799            inner.payloads[height].clone()
800        };
801
802        Ok(PayloadProof::new(payload, vid_common))
803    }
804
805    async fn namespace_proof(
806        &self,
807        height: u64,
808        mut namespace: NamespaceId,
809    ) -> Result<NamespaceProof> {
810        let mut inner = self.inner.lock().await;
811
812        let vid_common = inner.vid_common(height, self.epoch_height);
813        let height = height as usize;
814        ensure!(height < inner.payloads.len());
815
816        let (payload, ns_table) = if inner.invalid_payloads.contains(&height) {
817            // To mock an invalid proof, we use a different payload which doesn't match the actual
818            // payload for the requested block.
819            tracing::info!(height, "return mock incorrect namespace proof");
820            let mut payload = vec![0; 32];
821            rand::thread_rng().fill_bytes(&mut payload);
822            let tx = Transaction::new(namespace, payload);
823            let node_state = NodeState::mock_v3();
824            Payload::from_transactions_sync([tx], node_state.chain_config).unwrap()
825        } else {
826            (
827                inner.payloads[height].clone(),
828                inner.leaves[height].header().ns_table().clone(),
829            )
830        };
831
832        if inner.invalid_proofs.contains(&height) {
833            // If we are returning a mock invalid proof, return a proof for the wrong namespace so
834            // that verification will fail.
835            namespace = NamespaceId::from(u64::from(namespace) + 1);
836        }
837
838        let Some(ns_index) = ns_table.find_ns_id(&namespace) else {
839            return Ok(NamespaceProof::not_present());
840        };
841        let proof = NsProof::new(&payload, &ns_index, &vid_common)
842            .context("failed to construct NsProof")?;
843        Ok(NamespaceProof::new(proof, vid_common))
844    }
845
846    async fn namespace_proofs_in_range(
847        &self,
848        start: u64,
849        end: u64,
850        namespace: NamespaceId,
851    ) -> Result<Vec<NamespaceProof>> {
852        let mut proofs = vec![];
853        for i in start..end {
854            proofs.push(self.namespace_proof(i, namespace).await?);
855        }
856        Ok(proofs)
857    }
858}
859
860fn register_validator_events(
861    events: &mut Vec<StakeTableEvent>,
862    validator: &AuthenticatedValidator<PubKey>,
863) {
864    events.push(StakeTableEvent::Register(ValidatorRegistered {
865        account: validator.account,
866        blsVk: validator.stake_table_key.into(),
867        schnorrVk: validator.state_ver_key.clone().into(),
868        commission: validator.commission,
869    }));
870    for (&delegator, &amount) in &validator.delegators {
871        events.push(StakeTableEvent::Delegate(Delegated {
872            delegator,
873            validator: validator.account,
874            amount,
875        }));
876    }
877}
878
879pub fn random_validator() -> AuthenticatedValidator<PubKey> {
880    let account = Address::random();
881    let mut seed = [0; 32];
882    rand::thread_rng().fill_bytes(&mut seed);
883    let stake = U256::from(rand::thread_rng().next_u64());
884    RegisteredValidator {
885        account,
886        stake_table_key: PubKey::generated_from_seed_indexed(seed, 0).0,
887        state_ver_key: SchnorrPubKey::generated_from_seed_indexed(seed, 0).0,
888        stake,
889        commission: 1,
890        delegators: [(Address::random(), stake)].into_iter().collect(),
891        authenticated: true,
892        x25519_key: None,
893        p2p_addr: None,
894    }
895    .try_into()
896    .expect("authenticated validator")
897}