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>()
93                    .is_multiple_of(align_of::<SerializedTransactionLengths>())
94            );
95        };
96
97        let transaction_lengths = SerializedTransactionLengths {
98            read_slots: read_slots
99                .len()
100                .try_into()
101                .map_err(|_error| OwnedTransactionError::TooManyReadSlots)?,
102            write_slots: read_slots
103                .len()
104                .try_into()
105                .map_err(|_error| OwnedTransactionError::TooManyWriteSlots)?,
106            payload: size_of_val(payload)
107                .try_into()
108                .map_err(|_error| OwnedTransactionError::PayloadTooLarge)?,
109            seal: seal
110                .len()
111                .try_into()
112                .map_err(|_error| OwnedTransactionError::SealTooLarge)?,
113            padding: [0; _],
114        };
115
116        let true = buffer.append(header.as_bytes()) else {
117            unreachable!("Always fits into `u32`");
118        };
119        let true = buffer.append(transaction_lengths.as_bytes()) else {
120            unreachable!("Always fits into `u32`");
121        };
122
123        const _: () = {
124            // Writing `TransactionSlot` after `OwnedTransactionLengths` and `TransactionHeader`
125            // must be aligned
126            assert!(
127                (size_of::<TransactionHeader>() + size_of::<SerializedTransactionLengths>())
128                    .is_multiple_of(align_of::<TransactionSlot>())
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                    .is_multiple_of(align_of::<u128>())
154            );
155            assert!(
156                (size_of::<TransactionHeader>()
157                    + size_of::<SerializedTransactionLengths>()
158                    + size_of::<TransactionSlot>())
159                .is_multiple_of(align_of::<u128>())
160            );
161        };
162        if transaction_lengths.payload > 0 {
163            if !transaction_lengths.payload.is_multiple_of(u128::SIZE) {
164                return Err(OwnedTransactionError::PayloadIsNotMultipleOfU128);
165            }
166
167            // SAFETY: `u128` is safe to copy as bytes
168            if !buffer.append(unsafe {
169                slice::from_raw_parts(payload.as_ptr().cast::<u8>(), size_of_val(payload))
170            }) {
171                return Err(OwnedTransactionError::TransactionTooLarge);
172            }
173        }
174
175        const _: () = {
176            // Writing after `OwnedTransactionLengths`, `TransactionHeader` and (optionally)
177            // `TransactionSlot` must be aligned to `u128`
178            assert!(
179                (size_of::<TransactionHeader>() + size_of::<SerializedTransactionLengths>())
180                    .is_multiple_of(align_of::<u128>())
181            );
182            assert!(
183                (size_of::<TransactionHeader>()
184                    + size_of::<SerializedTransactionLengths>()
185                    + size_of::<TransactionSlot>())
186                .is_multiple_of(align_of::<u128>())
187            );
188        };
189        if transaction_lengths.seal > 0 && !buffer.append(seal) {
190            return Err(OwnedTransactionError::TransactionTooLarge);
191        }
192
193        Ok(())
194    }
195
196    /// Create owned transaction from a reference
197    #[inline(always)]
198    pub fn from_transaction(transaction: Transaction<'_>) -> Result<Self, OwnedTransactionError> {
199        Self::from_parts(
200            transaction.header,
201            transaction.read_slots,
202            transaction.write_slots,
203            transaction.payload,
204            transaction.seal,
205        )
206    }
207
208    /// Create owned transaction from a buffer
209    pub fn from_buffer(buffer: SharedAlignedBuffer) -> Result<Self, OwnedTransactionError> {
210        if (buffer.len() as usize)
211            < size_of::<TransactionHeader>() + size_of::<SerializedTransactionLengths>()
212        {
213            return Err(OwnedTransactionError::NotEnoughBytes);
214        }
215
216        // SAFETY: Checked above that there are enough bytes and they are correctly aligned
217        let lengths = unsafe {
218            buffer
219                .as_ptr()
220                .add(size_of::<TransactionHeader>())
221                .cast::<SerializedTransactionLengths>()
222                .read()
223        };
224        let SerializedTransactionLengths {
225            read_slots,
226            write_slots,
227            payload,
228            seal,
229            padding,
230        } = lengths;
231
232        if padding != [0; _] {
233            return Err(OwnedTransactionError::InvalidPadding);
234        }
235
236        if !payload.is_multiple_of(u128::SIZE) {
237            return Err(OwnedTransactionError::PayloadIsNotMultipleOfU128);
238        }
239
240        let expected = (size_of::<TransactionHeader>() as u32
241            + size_of::<SerializedTransactionLengths>() as u32)
242            .saturating_add(u32::from(read_slots))
243            .saturating_add(u32::from(write_slots))
244            .saturating_add(payload)
245            .saturating_add(seal);
246
247        if buffer.len() != expected {
248            return Err(OwnedTransactionError::UnexpectedNumberOfBytes {
249                actual: buffer.len(),
250                expected,
251            });
252        }
253
254        Ok(Self { buffer })
255    }
256
257    /// Inner buffer with owned transaction contents
258    pub fn buffer(&self) -> &SharedAlignedBuffer {
259        &self.buffer
260    }
261
262    /// Get [`Transaction`] out of owned transaction
263    pub fn transaction(&self) -> Transaction<'_> {
264        // SAFETY: Size and alignment checked in constructor
265        unsafe { Transaction::from_bytes_unchecked(self.buffer.as_slice()) }
266    }
267}