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::{anyhow, bail};
6use iota_types::{
7    effects::TransactionEffectsAPI,
8    full_checkpoint_content::{CheckpointData, CheckpointTransaction},
9};
10
11use crate::proof::{Proof, ProofTarget, 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: ProofTarget, data: &CheckpointData) -> anyhow::Result<Proof> {
20    let checkpoint_summary = data.checkpoint_summary.clone();
21    let mut this_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) = &this_proof.targets.committee {
30        // Check we have the correct epoch
31        if this_proof.checkpoint_summary.epoch() + 1 != committee.epoch {
32            bail!("Epoch mismatch between checkpoint and committee");
33        }
34
35        // Check its an end of epoch checkpoint
36        if this_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 = this_proof
45        .targets
46        .objects
47        .iter()
48        .map(|(_, o)| o.previous_transaction);
49    let event_tx = this_proof
50        .targets
51        .events
52        .iter()
53        .map(|(eid, _)| eid.tx_digest);
54    let mut all_tx = object_tx.chain(event_tx);
55
56    // Get the first tx ID
57    let target_tx_id = if let Some(first_tx) = all_tx.next() {
58        first_tx
59    } else {
60        // Since there is no target we just return the summary proof
61        return Ok(this_proof);
62    };
63
64    // Basic check that all targets refer to the same transaction
65    if !all_tx.all(|tx| tx == target_tx_id) {
66        bail!("All targets must refer to the same transaction");
67    }
68
69    // Find the transaction in the checkpoint data
70    let tx = data
71        .transactions
72        .iter()
73        .find(|t| t.effects.transaction_digest() == &target_tx_id)
74        .ok_or_else(|| anyhow!("Transaction not found in checkpoint data"))?
75        .clone();
76
77    let CheckpointTransaction {
78        transaction,
79        effects,
80        events,
81        ..
82    } = tx;
83
84    // Add all the transaction data in there
85    this_proof.contents_proof = Some(TransactionProof {
86        checkpoint_contents: data.checkpoint_contents.clone(),
87        transaction,
88        effects,
89        events,
90    });
91
92    // TODO: should we check that the objects & events are in the transaction, to
93    //       avoid constructing invalid proofs? I opt to not check because the check
94    //       is expensive (sequential scan of all objects).
95
96    Ok(this_proof)
97}