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