Skip to main content

iota_config/
migration_tx_data.rs

1// Copyright (c) 2024 IOTA Stiftung
2// SPDX-License-Identifier: Apache-2.0
3
4use std::{
5    collections::{BTreeMap, HashSet},
6    fs::File,
7    io::BufReader,
8    path::Path,
9};
10
11use anyhow::{Context, Result};
12use iota_sdk_types::{
13    CheckpointContents, CheckpointSummary, TransactionDigest, TransactionEffects, TransactionEvents,
14};
15use iota_types::{
16    effects::TransactionEffectsAPI, message_envelope::Message,
17    messages_checkpoint::CheckpointContentsExt, transaction::TransactionEnvelope,
18};
19use serde::{Deserialize, Serialize};
20use tracing::trace;
21
22use crate::genesis::Genesis;
23
24pub type TransactionsData =
25    BTreeMap<TransactionDigest, (TransactionEnvelope, TransactionEffects, TransactionEvents)>;
26
27// Migration data from the Stardust network is loaded separately after genesis
28// to reduce the size of the genesis transaction.
29#[derive(Eq, PartialEq, Debug, Clone, Deserialize, Serialize, Default)]
30pub struct MigrationTxData {
31    inner: TransactionsData,
32}
33
34impl MigrationTxData {
35    pub fn txs_data(&self) -> &TransactionsData {
36        &self.inner
37    }
38
39    fn validate_from_genesis_components(
40        &self,
41        checkpoint: &CheckpointSummary,
42        contents: &CheckpointContents,
43        genesis_tx_digest: TransactionDigest,
44    ) -> anyhow::Result<()> {
45        anyhow::ensure!(
46            checkpoint.contents_digest == contents.digest(),
47            "checkpoint contents digest is corrupted"
48        );
49        let mut validation_digests_queue: HashSet<TransactionDigest> =
50            self.inner.keys().copied().collect();
51        for exec_digest in contents.iter() {
52            // We skip the genesis transaction to process only migration transactions from
53            // the migration.blob.
54            if exec_digest.transaction == genesis_tx_digest {
55                continue;
56            }
57            let valid_tx_digest = &exec_digest.transaction;
58            let valid_effects_digest = &exec_digest.effects;
59            let (tx, effects, events) = self
60                .inner
61                .get(valid_tx_digest)
62                .ok_or(anyhow::anyhow!("missing transaction digest"))?;
63
64            if &effects.digest() != valid_effects_digest
65                || effects.transaction_digest() != valid_tx_digest
66                || &tx.data().digest() != valid_tx_digest
67            {
68                anyhow::bail!("invalid transaction or effects data");
69            }
70
71            if let Some(valid_events_digest) = effects.events_digest() {
72                if &events.digest() != valid_events_digest {
73                    anyhow::bail!("invalid events data");
74                }
75            } else if !events.is_empty() {
76                anyhow::bail!("invalid events data");
77            }
78            validation_digests_queue.remove(valid_tx_digest);
79        }
80        anyhow::ensure!(
81            validation_digests_queue.is_empty(),
82            "the migration data is corrupted"
83        );
84        Ok(())
85    }
86
87    /// Validates the content of the migration data through a `Genesis`. The
88    /// validation is based on cryptographic links (i.e., hash digests) between
89    /// transactions, transaction effects and events.
90    pub fn validate_from_genesis(&self, genesis: &Genesis) -> anyhow::Result<()> {
91        self.validate_from_genesis_components(
92            &genesis.checkpoint(),
93            genesis.checkpoint_contents(),
94            *genesis.transaction().digest(),
95        )
96    }
97
98    /// Loads a `MigrationTxData` in memory from a file found in `path`.
99    pub fn load<P: AsRef<Path>>(path: P) -> Result<Self, anyhow::Error> {
100        let path = path.as_ref();
101        trace!("reading Migration transaction data from {}", path.display());
102        let read = File::open(path).with_context(|| {
103            format!(
104                "unable to load Migration transaction data from {}",
105                path.display()
106            )
107        })?;
108        bcs::from_reader(BufReader::new(read)).with_context(|| {
109            format!(
110                "unable to parse Migration transaction data from {}",
111                path.display()
112            )
113        })
114    }
115}