Skip to main content

ab_core_primitives/transaction/
owned.rs

1//! Data structures related to the owned version of [`Transaction`]
2
3#[cfg(test)]
4mod tests;
5
6use crate::transaction::{
7    SerializedTransactionLengths, Transaction, TransactionHeader, TransactionSlot,
8};
9use ab_aligned_buffer::{OwnedAlignedBuffer, SharedAlignedBuffer};
10use ab_io_type::alignment::Alignment;
11use ab_io_type::trivial_type::TrivialType;
12
13/// Errors for [`OwnedTransaction`]
14#[derive(Debug, thiserror::Error)]
15pub enum OwnedTransactionError {
16    /// Too many read slots
17    #[error("Too many read slots")]
18    TooManyReadSlots,
19    /// Too many write slots
20    #[error("Too many write slots")]
21    TooManyWriteSlots,
22    /// Payload too large
23    #[error("The payload is too large")]
24    PayloadTooLarge,
25    /// Payload is not a multiple of `u128`
26    #[error("The payload is not a multiple of `u128`")]
27    PayloadIsNotMultipleOfU128,
28    /// Seal too large
29    #[error("The leal too large")]
30    SealTooLarge,
31    /// Transaction too large
32    #[error("The transaction is too large")]
33    TransactionTooLarge,
34    /// Not enough bytes
35    #[error("Not enough bytes")]
36    NotEnoughBytes,
37    /// Invalid padding
38    #[error("Invalid padding")]
39    InvalidPadding,
40    /// Expected number of bytes
41    #[error("Expected number of bytes: {actual} != {expected}")]
42    UnexpectedNumberOfBytes {
43        /// Actual number of bytes
44        actual: u32,
45        /// Expected number of bytes
46        expected: u32,
47    },
48}
49
50/// An owned version of [`Transaction`].
51///
52/// It is correctly aligned in memory and well suited for sending and receiving over the network
53/// efficiently or storing in memory or on disk.
54#[derive(Debug, Clone)]
55pub struct OwnedTransaction {
56    buffer: SharedAlignedBuffer,
57}
58
59impl OwnedTransaction {
60    /// Create owned transaction from its parts
61    pub fn from_parts(
62        header: &TransactionHeader,
63        read_slots: &[TransactionSlot],
64        write_slots: &[TransactionSlot],
65        payload: &[u128],
66        seal: &[u8],
67    ) -> Result<Self, OwnedTransactionError> {
68        let mut buffer = OwnedAlignedBuffer::with_capacity(
69            (TransactionHeader::SIZE + SerializedTransactionLengths::SIZE)
70                .saturating_add(size_of_val(read_slots) as u32)
71                .saturating_add(size_of_val(write_slots) as u32)
72                .saturating_add(size_of_val(payload) as u32)
73                .saturating_add(size_of_val(seal) as u32),
74        );
75
76        Self::from_parts_into(header, read_slots, write_slots, payload, seal, &mut buffer)?;
77
78        Ok(Self {
79            buffer: buffer.into_shared(),
80        })
81    }
82
83    /// Create owned transaction from its parts and write it into provided buffer.
84    ///
85    /// On error, part of the transaction might have already been appended to the buffer.
86    pub fn from_parts_into(
87        header: &TransactionHeader,
88        read_slots: &[TransactionSlot],
89        write_slots: &[TransactionSlot],
90        payload: &[u128],
91        seal: &[u8],
92        buffer: &mut OwnedAlignedBuffer,
93    ) -> Result<(), OwnedTransactionError> {
94        const {
95            // Writing `OwnedTransactionLengths` after `TransactionHeader` must be aligned
96            assert!(
97                size_of::<TransactionHeader>()
98                    .is_multiple_of(align_of::<SerializedTransactionLengths>())
99            );
100        }
101
102        let transaction_lengths = SerializedTransactionLengths {
103            read_slots: read_slots
104                .len()
105                .try_into()
106                .map_err(|_error| OwnedTransactionError::TooManyReadSlots)?,
107            write_slots: write_slots
108                .len()
109                .try_into()
110                .map_err(|_error| OwnedTransactionError::TooManyWriteSlots)?,
111            payload: size_of_val(payload)
112                .try_into()
113                .map_err(|_error| OwnedTransactionError::PayloadTooLarge)?,
114            seal: seal
115                .len()
116                .try_into()
117                .map_err(|_error| OwnedTransactionError::SealTooLarge)?,
118            padding: [0; _],
119        };
120
121        if !buffer.append(header.as_bytes()) {
122            return Err(OwnedTransactionError::TransactionTooLarge);
123        }
124        if !buffer.append(transaction_lengths.as_bytes()) {
125            return Err(OwnedTransactionError::TransactionTooLarge);
126        }
127
128        const {
129            // Writing `TransactionSlot` after `OwnedTransactionLengths` and `TransactionHeader`
130            // must be aligned
131            assert!(
132                (size_of::<TransactionHeader>() + size_of::<SerializedTransactionLengths>())
133                    .is_multiple_of(align_of::<TransactionSlot>())
134            );
135        }
136        if transaction_lengths.read_slots > 0
137            && !buffer.append(TransactionSlot::slice_as_bytes(read_slots))
138        {
139            return Err(OwnedTransactionError::TransactionTooLarge);
140        }
141        if transaction_lengths.write_slots > 0
142            && !buffer.append(TransactionSlot::slice_as_bytes(write_slots))
143        {
144            return Err(OwnedTransactionError::TransactionTooLarge);
145        }
146
147        const {
148            // Writing after `OwnedTransactionLengths`, `TransactionHeader` and (optionally)
149            // `TransactionSlot` must be aligned to `Alignment::MAX`
150            assert!(
151                (size_of::<TransactionHeader>() + size_of::<SerializedTransactionLengths>())
152                    .is_multiple_of(usize::from(Alignment::MAX.get()))
153            );
154            assert!(
155                (size_of::<TransactionHeader>()
156                    + size_of::<SerializedTransactionLengths>()
157                    + size_of::<TransactionSlot>())
158                .is_multiple_of(usize::from(Alignment::MAX.get()))
159            );
160        }
161        if transaction_lengths.payload > 0 {
162            if !transaction_lengths.payload.is_multiple_of(u128::SIZE) {
163                return Err(OwnedTransactionError::PayloadIsNotMultipleOfU128);
164            }
165
166            if !buffer.append(u128::slice_as_bytes(payload)) {
167                return Err(OwnedTransactionError::TransactionTooLarge);
168            }
169        }
170
171        const {
172            // Writing after `OwnedTransactionLengths`, `TransactionHeader` and (optionally)
173            // `TransactionSlot` must be aligned to `Alignment::MAX`
174            assert!(
175                (size_of::<TransactionHeader>() + size_of::<SerializedTransactionLengths>())
176                    .is_multiple_of(usize::from(Alignment::MAX.get()))
177            );
178            assert!(
179                (size_of::<TransactionHeader>()
180                    + size_of::<SerializedTransactionLengths>()
181                    + size_of::<TransactionSlot>())
182                .is_multiple_of(usize::from(Alignment::MAX.get()))
183            );
184        }
185        if transaction_lengths.seal > 0 && !buffer.append(seal) {
186            return Err(OwnedTransactionError::TransactionTooLarge);
187        }
188
189        Ok(())
190    }
191
192    /// Create owned transaction from a reference
193    #[inline(always)]
194    pub fn from_transaction(transaction: Transaction<'_>) -> Result<Self, OwnedTransactionError> {
195        Self::from_parts(
196            transaction.header,
197            transaction.read_slots,
198            transaction.write_slots,
199            transaction.payload,
200            transaction.seal,
201        )
202    }
203
204    /// Create owned transaction from a buffer.
205    ///
206    /// Returns [`OwnedTransactionError::NotEnoughBytes`] if the buffer is shorter than the
207    /// transaction it encodes, and [`OwnedTransactionError::UnexpectedNumberOfBytes`] if it is
208    /// longer.
209    pub fn from_buffer(buffer: SharedAlignedBuffer) -> Result<Self, OwnedTransactionError> {
210        // Alignment is guaranteed by `SharedAlignedBuffer`, so only too few bytes are rejected
211        let (lengths, _remainder) = buffer
212            .as_slice()
213            .get(size_of::<TransactionHeader>()..)
214            .and_then(SerializedTransactionLengths::from_bytes)
215            .ok_or(OwnedTransactionError::NotEnoughBytes)?;
216        let SerializedTransactionLengths {
217            read_slots: _,
218            write_slots: _,
219            payload,
220            seal: _,
221            padding,
222        } = *lengths;
223
224        // `Transaction::try_from_bytes()` below checks these too, they are checked here first to
225        // return a more specific error
226        if padding != [0; _] {
227            return Err(OwnedTransactionError::InvalidPadding);
228        }
229
230        if !payload.is_multiple_of(u128::SIZE) {
231            return Err(OwnedTransactionError::PayloadIsNotMultipleOfU128);
232        }
233
234        let Some((transaction, remainder)) = Transaction::try_from_bytes(buffer.as_slice()) else {
235            // Alignment is guaranteed by `SharedAlignedBuffer`, padding and payload length are
236            // checked above, so insufficient size is the only remaining reason
237            return Err(OwnedTransactionError::NotEnoughBytes);
238        };
239
240        if !remainder.is_empty() {
241            return Err(OwnedTransactionError::UnexpectedNumberOfBytes {
242                actual: buffer.len(),
243                expected: u32::try_from(transaction.encoded_size())
244                    .expect("Smaller than the buffer, whose length is `u32`; qed"),
245            });
246        }
247
248        Ok(Self { buffer })
249    }
250
251    /// Inner buffer with owned transaction contents
252    pub fn buffer(&self) -> &SharedAlignedBuffer {
253        &self.buffer
254    }
255
256    /// Get [`Transaction`] out of owned transaction
257    pub fn transaction(&self) -> Transaction<'_> {
258        let (transaction, _remainder) = Transaction::try_from_bytes(self.buffer.as_slice())
259            .expect("Constructors either write a valid transaction or check it; qed");
260        transaction
261    }
262}