iota_config/
migration_tx_data.rs1use std::{
5 collections::{BTreeMap, HashSet},
6 fs::File,
7 io::{BufReader, BufWriter},
8 path::Path,
9};
10
11use anyhow::{Context, Result};
12use iota_genesis_common::prepare_and_execute_genesis_transaction;
13use iota_types::{
14 balance::Balance,
15 digests::TransactionDigest,
16 effects::{TransactionEffects, TransactionEffectsAPI, TransactionEvents},
17 gas_coin::GasCoin,
18 message_envelope::Message,
19 messages_checkpoint::{CheckpointContents, CheckpointSummary},
20 object::{Data, Object},
21 stardust::output::{AliasOutput, BasicOutput, NftOutput},
22 timelock::timelock::{TimeLock, is_timelocked_gas_balance},
23 transaction::Transaction,
24};
25use serde::{Deserialize, Serialize};
26use tracing::trace;
27
28use crate::genesis::{Genesis, GenesisCeremonyParameters, UnsignedGenesis};
29
30pub type TransactionsData =
31 BTreeMap<TransactionDigest, (Transaction, TransactionEffects, TransactionEvents)>;
32
33#[derive(Eq, PartialEq, Debug, Clone, Deserialize, Serialize, Default)]
36pub struct MigrationTxData {
37 inner: TransactionsData,
38}
39
40impl MigrationTxData {
41 pub fn new(txs_data: TransactionsData) -> Self {
42 Self { inner: txs_data }
43 }
44
45 pub fn txs_data(&self) -> &TransactionsData {
46 &self.inner
47 }
48
49 pub fn is_empty(&self) -> bool {
50 self.inner.is_empty()
51 }
52
53 pub fn get_objects(&self) -> impl Iterator<Item = Object> + '_ {
56 self.inner.values().flat_map(|(tx, _, _)| {
57 self.objects_by_tx_digest(*tx.digest())
58 .expect("the migration data is corrupted")
59 .into_iter()
60 })
61 }
62
63 pub fn objects_by_tx_digest(&self, digest: TransactionDigest) -> Option<Vec<Object>> {
66 let (tx, effects, _) = self.inner.get(&digest)?;
67
68 let default_ceremony_parameters = GenesisCeremonyParameters::default();
71
72 let (execution_effects, _, execution_objects) = prepare_and_execute_genesis_transaction(
74 default_ceremony_parameters.chain_start_timestamp_ms,
75 default_ceremony_parameters.protocol_version,
76 tx,
77 );
78
79 assert_eq!(
81 effects.digest(),
82 execution_effects.digest(),
83 "invalid execution"
84 );
85
86 Some(execution_objects)
88 }
89
90 fn validate_from_genesis_components(
91 &self,
92 checkpoint: &CheckpointSummary,
93 contents: &CheckpointContents,
94 genesis_tx_digest: TransactionDigest,
95 ) -> anyhow::Result<()> {
96 anyhow::ensure!(
97 checkpoint.content_digest == *contents.digest(),
98 "checkpoint's content digest is corrupted"
99 );
100 let mut validation_digests_queue: HashSet<TransactionDigest> =
101 self.inner.keys().copied().collect();
102 for (valid_tx_digest, valid_effects_digest) in contents.iter().filter_map(|exec_digest| {
105 (exec_digest.transaction != genesis_tx_digest)
106 .then_some((&exec_digest.transaction, &exec_digest.effects))
107 }) {
108 let (tx, effects, events) = self
109 .inner
110 .get(valid_tx_digest)
111 .ok_or(anyhow::anyhow!("missing transaction digest"))?;
112
113 if &effects.digest() != valid_effects_digest
114 || effects.transaction_digest() != valid_tx_digest
115 || &tx.data().digest() != valid_tx_digest
116 {
117 anyhow::bail!("invalid transaction or effects data");
118 }
119
120 if let Some(valid_events_digest) = effects.events_digest() {
121 if &events.digest() != valid_events_digest {
122 anyhow::bail!("invalid events data");
123 }
124 } else if !events.data.is_empty() {
125 anyhow::bail!("invalid events data");
126 }
127 validation_digests_queue.remove(valid_tx_digest);
128 }
129 anyhow::ensure!(
130 validation_digests_queue.is_empty(),
131 "the migration data is corrupted"
132 );
133 Ok(())
134 }
135
136 pub fn validate_from_genesis(&self, genesis: &Genesis) -> anyhow::Result<()> {
140 self.validate_from_genesis_components(
141 &genesis.checkpoint(),
142 genesis.checkpoint_contents(),
143 *genesis.transaction().digest(),
144 )
145 }
146
147 pub fn validate_from_unsigned_genesis(
152 &self,
153 unsigned_genesis: &UnsignedGenesis,
154 ) -> anyhow::Result<()> {
155 self.validate_from_genesis_components(
156 unsigned_genesis.checkpoint(),
157 unsigned_genesis.checkpoint_contents(),
158 *unsigned_genesis.transaction().digest(),
159 )
160 }
161
162 pub fn validate_total_supply(&self, expected_total_supply: u64) -> anyhow::Result<()> {
165 let total_supply: u64 = self
166 .get_objects()
167 .map(|object| match &object.data {
168 Data::Move(_) => GasCoin::try_from(&object)
169 .map(|gas| gas.value())
170 .or_else(|_| {
171 TimeLock::<Balance>::try_from(&object).map(|t| {
172 assert!(is_timelocked_gas_balance(
173 &object.struct_tag().expect("should not be a package")
174 ));
175 t.locked().value()
176 })
177 })
178 .or_else(|_| AliasOutput::try_from(&object).map(|a| a.balance.value()))
179 .or_else(|_| BasicOutput::try_from(&object).map(|b| b.balance.value()))
180 .or_else(|_| NftOutput::try_from(&object).map(|n| n.balance.value()))
181 .unwrap_or(0),
182 Data::Package(_) => 0,
183 })
184 .sum();
185
186 anyhow::ensure!(
187 total_supply == expected_total_supply,
188 "the migration data total supply of {total_supply} does not match the expected total supply of {expected_total_supply}"
189 );
190 Ok(())
191 }
192
193 pub fn load<P: AsRef<Path>>(path: P) -> Result<Self, anyhow::Error> {
195 let path = path.as_ref();
196 trace!("reading Migration transaction data from {}", path.display());
197 let read = File::open(path).with_context(|| {
198 format!(
199 "unable to load Migration transaction data from {}",
200 path.display()
201 )
202 })?;
203 bcs::from_reader(BufReader::new(read)).with_context(|| {
204 format!(
205 "unable to parse Migration transaction data from {}",
206 path.display()
207 )
208 })
209 }
210
211 pub fn save<P: AsRef<Path>>(&self, path: P) -> Result<(), anyhow::Error> {
213 let path = path.as_ref();
214 trace!("writing Migration transaction data to {}", path.display());
215 let mut write = BufWriter::new(File::create(path)?);
216 bcs::serialize_into(&mut write, &self).with_context(|| {
217 format!(
218 "unable to save Migration transaction data to {}",
219 path.display()
220 )
221 })?;
222 Ok(())
223 }
224}