ab_core_primitives/transaction/
owned.rs

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