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