Skip to main content

miden_client/transaction/
store_update.rs

1use alloc::vec::Vec;
2
3use miden_protocol::block::BlockNumber;
4use miden_protocol::note::{NoteDetails, NoteTag};
5use miden_protocol::transaction::ExecutedTransaction;
6use miden_tx::utils::serde::{
7    ByteReader,
8    ByteWriter,
9    Deserializable,
10    DeserializationError,
11    Serializable,
12};
13
14use crate::note::NoteUpdateTracker;
15use crate::sync::NoteTagRecord;
16
17// TRANSACTION STORE UPDATE
18// ================================================================================================
19
20/// Represents the changes that need to be applied to the client store as a result of a
21/// transaction execution.
22#[derive(Debug, Clone, PartialEq)]
23pub struct TransactionStoreUpdate {
24    /// Details of the executed transaction to be inserted.
25    executed_transaction: ExecutedTransaction,
26    /// Block number at which the transaction was submitted.
27    submission_height: BlockNumber,
28    /// Future notes that are expected to be created as a result of the transaction.
29    future_notes: Vec<(NoteDetails, NoteTag)>,
30    /// Information about note changes after the transaction execution.
31    note_updates: NoteUpdateTracker,
32    /// New note tags to be tracked.
33    new_tags: Vec<NoteTagRecord>,
34}
35
36impl TransactionStoreUpdate {
37    /// Creates a new [`TransactionStoreUpdate`] instance populated with all relevant note data.
38    ///
39    /// # Arguments
40    /// - `executed_transaction`: The executed transaction details.
41    /// - `submission_height`: The block number at which the transaction was submitted.
42    /// - `note_updates`: The note updates that need to be applied to the store after the
43    ///   transaction execution.
44    /// - `future_notes`: Notes expected to be received in follow-up transactions (e.g. swap
45    ///   paybacks).
46    /// - `new_tags`: New note tags that need to be tracked because of created notes.
47    pub fn new(
48        executed_transaction: ExecutedTransaction,
49        submission_height: BlockNumber,
50        note_updates: NoteUpdateTracker,
51        future_notes: Vec<(NoteDetails, NoteTag)>,
52        new_tags: Vec<NoteTagRecord>,
53    ) -> Self {
54        Self {
55            executed_transaction,
56            submission_height,
57            future_notes,
58            note_updates,
59            new_tags,
60        }
61    }
62    /// Returns the executed transaction.
63    pub fn executed_transaction(&self) -> &ExecutedTransaction {
64        &self.executed_transaction
65    }
66
67    /// Returns the block number at which the transaction was submitted.
68    pub fn submission_height(&self) -> BlockNumber {
69        self.submission_height
70    }
71
72    /// Returns the future notes that should be tracked as a result of the transaction.
73    pub fn future_notes(&self) -> &[(NoteDetails, NoteTag)] {
74        &self.future_notes
75    }
76
77    /// Returns the note updates that need to be applied after the transaction execution.
78    pub fn note_updates(&self) -> &NoteUpdateTracker {
79        &self.note_updates
80    }
81
82    /// Returns the new tags that were created as part of the transaction.
83    pub fn new_tags(&self) -> &[NoteTagRecord] {
84        &self.new_tags
85    }
86}
87
88impl Serializable for TransactionStoreUpdate {
89    fn write_into<W: ByteWriter>(&self, target: &mut W) {
90        self.executed_transaction.write_into(target);
91        self.submission_height.write_into(target);
92        self.future_notes.write_into(target);
93        self.note_updates.write_into(target);
94        self.new_tags.write_into(target);
95    }
96}
97
98impl Deserializable for TransactionStoreUpdate {
99    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
100        let executed_transaction = ExecutedTransaction::read_from(source)?;
101        let submission_height = BlockNumber::read_from(source)?;
102        let future_notes = Vec::<(NoteDetails, NoteTag)>::read_from(source)?;
103        let note_updates = NoteUpdateTracker::read_from(source)?;
104        let new_tags = Vec::<NoteTagRecord>::read_from(source)?;
105
106        Ok(Self {
107            executed_transaction,
108            submission_height,
109            future_notes,
110            note_updates,
111            new_tags,
112        })
113    }
114}
115
116// TESTS
117// ================================================================================================
118
119#[cfg(test)]
120mod tests {
121    use alloc::boxed::Box;
122
123    use miden_protocol::asset::{Asset, FungibleAsset};
124    use miden_protocol::note::NoteType;
125    use miden_protocol::testing::account_id::{
126        ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET,
127        ACCOUNT_ID_SENDER,
128    };
129    use miden_testing::{MockChainBuilder, TxContextInput};
130
131    use super::*;
132    use crate::note::NoteUpdateTracker;
133    use crate::store::InputNoteRecord;
134    use crate::sync::NoteTagRecord;
135
136    #[tokio::test]
137    async fn transaction_store_update_serialization_roundtrip() {
138        // Build a minimal MockChain with an account consuming a P2ID note so that we can
139        // produce a real `ExecutedTransaction`.
140        let sender_id = ACCOUNT_ID_SENDER.try_into().unwrap();
141        let faucet_id = ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET.try_into().unwrap();
142        let asset = Asset::Fungible(FungibleAsset::new(faucet_id, 100u64).unwrap());
143
144        let mut builder = MockChainBuilder::new();
145        let account = builder.add_existing_mock_account(miden_testing::Auth::IncrNonce).unwrap();
146        let note = builder
147            .add_p2id_note(sender_id, account.id(), &[asset], NoteType::Public)
148            .unwrap();
149        let mut chain = builder.build().unwrap();
150        chain.prove_next_block().unwrap();
151
152        let executed_tx = Box::pin(
153            chain
154                .build_tx_context(
155                    TxContextInput::Account(account.clone()),
156                    &[],
157                    core::slice::from_ref(&note),
158                )
159                .unwrap()
160                .build()
161                .unwrap()
162                .execute(),
163        )
164        .await
165        .unwrap();
166
167        // Build non-trivial `note_updates` and `new_tags` so that the round-trip covers all
168        // fields that were previously dropped.
169        let input_note = InputNoteRecord::from(note.clone());
170        let note_updates = NoteUpdateTracker::for_transaction_updates([input_note], [], []);
171
172        let tag = NoteTag::with_account_target(account.id());
173        let new_tags = vec![NoteTagRecord::with_account_source(tag, account.id())];
174
175        let future_notes = vec![(Into::<NoteDetails>::into(note.clone()), tag)];
176
177        let update = TransactionStoreUpdate::new(
178            executed_tx,
179            BlockNumber::from(42u32),
180            note_updates,
181            future_notes,
182            new_tags,
183        );
184
185        let bytes = update.to_bytes();
186        let deserialized = TransactionStoreUpdate::read_from_bytes(&bytes).unwrap();
187
188        assert_eq!(update, deserialized);
189    }
190}