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