iota_light_client/
construct.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use anyhow::{Result, anyhow, bail};
6use iota_types::{
7    effects::TransactionEffectsAPI,
8    full_checkpoint_content::{CheckpointData, CheckpointTransaction},
9};
10
11use crate::proof::{Proof, ProofTargets, TransactionProof};
12
13/// Construct a proof from the given checkpoint data and proof targets.
14///
15/// Only minimal cheaper checks are performed to ensure the proof is valid. If
16/// you need guaranteed validity consider calling `verify_proof` function on the
17/// constructed proof. It either returns `Ok` with a proof, or `Err` with a
18/// description of the error.
19pub fn construct_proof(targets: ProofTargets, checkpoint: &CheckpointData) -> Result<Proof> {
20    let checkpoint_summary = checkpoint.checkpoint_summary.clone();
21    let mut proof = Proof {
22        targets,
23        checkpoint_summary,
24        contents_proof: None,
25    };
26
27    // Do a minimal check that the given checkpoint data is consistent with the
28    // committee
29    if let Some(committee_target) = &proof.targets.committee {
30        // Check we have the correct epoch
31        if proof.checkpoint_summary.epoch() + 1 != committee_target.epoch {
32            bail!("Epoch mismatch between checkpoint and committee");
33        }
34
35        // Check its an end of epoch checkpoint
36        if proof.checkpoint_summary.end_of_epoch_data.is_none() {
37            bail!("Expected end of epoch checkpoint");
38        }
39    }
40
41    // If proof targets include objects or events, we need to include the contents
42    // proof Need to ensure that all targets refer to the same transaction first
43    // of all
44    let object_tx = proof
45        .targets
46        .objects
47        .iter()
48        .map(|(_, o)| o.previous_transaction);
49    let event_tx = proof.targets.events.iter().map(|(eid, _)| eid.tx_digest);
50    let mut all_tx = object_tx.chain(event_tx);
51
52    // Get the first tx ID
53    let target_tx_id = if let Some(first_tx) = all_tx.next() {
54        first_tx
55    } else {
56        // Since there is no target we just return the summary proof
57        return Ok(proof);
58    };
59
60    // Basic check that all targets refer to the same transaction
61    if !all_tx.all(|tx| tx == target_tx_id) {
62        bail!("All targets must refer to the same transaction");
63    }
64
65    // Find the transaction in the checkpoint data
66    let tx = checkpoint
67        .transactions
68        .iter()
69        .find(|t| t.effects.transaction_digest() == &target_tx_id)
70        .ok_or_else(|| anyhow!("Transaction not found in checkpoint data"))?
71        .clone();
72
73    let CheckpointTransaction {
74        transaction,
75        effects,
76        events,
77        ..
78    } = tx;
79
80    // Add all the transaction data in there
81    proof.contents_proof = Some(TransactionProof {
82        checkpoint_contents: checkpoint.checkpoint_contents.clone(),
83        transaction,
84        effects,
85        events,
86    });
87
88    // TODO: should we check that the objects & events are in the transaction, to
89    //       avoid constructing invalid proofs? I opt to not check because the check
90    //       is expensive (sequential scan of all objects).
91
92    Ok(proof)
93}