ab_core_primitives/transaction/
owned.rs

1//! Data structures related to the owned version of [`Transaction`]
2
3mod builder_buffer;
4
5use crate::transaction::owned::builder_buffer::BuilderBuffer;
6use crate::transaction::{Transaction, TransactionHeader, TransactionSlot};
7use ab_aligned_buffer::SharedAlignedBuffer;
8use ab_io_type::trivial_type::TrivialType;
9use core::slice;
10
11/// Lengths of various components inside [`OwnedTransaction`]
12#[derive(Debug, Default, Copy, Clone, TrivialType)]
13#[repr(C)]
14pub struct OwnedTransactionLengths {
15    /// Number of read-only slots
16    pub read_slots: u16,
17    /// Number of read-write slots
18    pub write_slots: u16,
19    /// Payload length
20    pub payload: u32,
21    /// Seal length
22    pub seal: u32,
23    /// Not used and must be set to `0`
24    pub padding: [u8; 4],
25}
26
27/// Errors for [`OwnedTransaction`]
28#[derive(Debug, thiserror::Error)]
29pub enum OwnedTransactionError {
30    /// Not enough bytes
31    #[error("Not enough bytes")]
32    NotEnoughBytes,
33    /// Invalid padding
34    #[error("Invalid padding")]
35    InvalidPadding,
36    /// Payload is not a multiple of `u128`
37    #[error("Payload is not a multiple of `u128`")]
38    PayloadIsNotMultipleOfU128,
39    /// Expected number of bytes
40    #[error("Expected number of bytes: {actual} != {expected}")]
41    UnexpectedNumberOfBytes {
42        /// Actual number of bytes
43        actual: u32,
44        /// Expected number of bytes
45        expected: u32,
46    },
47}
48
49/// An owned version of [`Transaction`].
50///
51/// It is correctly aligned in memory and well suited for sending and receiving over the network
52/// efficiently or storing in memory or on disk.
53///
54/// The internal layout of the owned transaction is following data structures concatenated as bytes
55/// (they are carefully picked to ensure alignment):
56/// * [`TransactionHeader`]
57/// * [`OwnedTransactionLengths`] (with values set to correspond to below contents
58/// * All read [`TransactionSlot`]
59/// * All write [`TransactionSlot`]
60/// * Payload as `u128`s
61/// * Seal as `u8`s
62#[derive(Debug, Clone)]
63pub struct OwnedTransaction {
64    buffer: SharedAlignedBuffer,
65}
66
67impl OwnedTransaction {
68    /// Create transaction builder with provided transaction header
69    pub fn build(header: &TransactionHeader) -> OwnedTransactionBuilder {
70        OwnedTransactionBuilder {
71            buffer: BuilderBuffer::new(header),
72        }
73    }
74
75    /// Create an owned transaction from a buffer
76    pub fn from_buffer(buffer: SharedAlignedBuffer) -> Result<Self, OwnedTransactionError> {
77        if (buffer.len() as usize)
78            < size_of::<TransactionHeader>() + size_of::<OwnedTransactionLengths>()
79        {
80            return Err(OwnedTransactionError::NotEnoughBytes);
81        }
82
83        // SAFETY: Checked above that there are enough bytes and they are correctly aligned
84        let lengths = unsafe {
85            buffer
86                .as_ptr()
87                .add(size_of::<TransactionHeader>())
88                .cast::<OwnedTransactionLengths>()
89                .read()
90        };
91        let OwnedTransactionLengths {
92            read_slots,
93            write_slots,
94            payload,
95            seal,
96            padding,
97        } = lengths;
98
99        if padding != [0; _] {
100            return Err(OwnedTransactionError::InvalidPadding);
101        }
102
103        if payload % u128::SIZE != 0 {
104            return Err(OwnedTransactionError::PayloadIsNotMultipleOfU128);
105        }
106
107        let expected = (size_of::<TransactionHeader>() as u32
108            + size_of::<OwnedTransactionLengths>() as u32)
109            .saturating_add(u32::from(read_slots))
110            .saturating_add(u32::from(write_slots))
111            .saturating_add(payload)
112            .saturating_add(seal);
113
114        if buffer.len() != expected {
115            return Err(OwnedTransactionError::UnexpectedNumberOfBytes {
116                actual: buffer.len(),
117                expected,
118            });
119        }
120
121        Ok(Self { buffer })
122    }
123
124    // TODO: Implement
125    // pub fn from_transaction(transaction: Transaction<'_>) -> Result<Self, OwnedTransactionError> {
126    //     todo!()
127    // }
128
129    /// Inner buffer with owned transaction contents
130    pub fn buffer(&self) -> &SharedAlignedBuffer {
131        &self.buffer
132    }
133
134    /// Get [`Transaction`] out of owned transaction
135    pub fn transaction(&self) -> Transaction<'_> {
136        // SAFETY: All constructors ensure there are enough bytes and they are correctly aligned
137        let lengths = unsafe {
138            self.buffer
139                .as_ptr()
140                .add(size_of::<TransactionHeader>())
141                .cast::<OwnedTransactionLengths>()
142                .read()
143        };
144        let OwnedTransactionLengths {
145            read_slots,
146            write_slots,
147            payload,
148            seal,
149            padding: _,
150        } = lengths;
151
152        Transaction {
153            // SAFETY: Any bytes are valid for `TransactionHeader` and all constructors ensure there
154            // are enough bytes for header in the buffer
155            header: unsafe {
156                self.buffer
157                    .as_ptr()
158                    .cast::<TransactionHeader>()
159                    .as_ref_unchecked()
160            },
161            // SAFETY: Any bytes are valid for `TransactionSlot` and all constructors ensure there
162            // are enough bytes for read slots in the buffer
163            read_slots: unsafe {
164                slice::from_raw_parts(
165                    self.buffer
166                        .as_ptr()
167                        .add(size_of::<TransactionHeader>())
168                        .add(size_of::<OwnedTransactionLengths>())
169                        .cast::<TransactionSlot>(),
170                    usize::from(read_slots),
171                )
172            },
173            // SAFETY: Any bytes are valid for `TransactionSlot` and all constructors ensure there
174            // are enough bytes for write slots in the buffer
175            write_slots: unsafe {
176                slice::from_raw_parts(
177                    self.buffer
178                        .as_ptr()
179                        .add(size_of::<TransactionHeader>())
180                        .add(size_of::<OwnedTransactionLengths>())
181                        .cast::<TransactionSlot>()
182                        .add(usize::from(read_slots)),
183                    usize::from(write_slots),
184                )
185            },
186            // SAFETY: Any bytes are valid for `payload` and all constructors ensure there are
187            // enough bytes for payload in the buffer
188            payload: unsafe {
189                slice::from_raw_parts(
190                    self.buffer
191                        .as_ptr()
192                        .add(size_of::<TransactionHeader>())
193                        .add(size_of::<OwnedTransactionLengths>())
194                        .add(
195                            size_of::<TransactionSlot>()
196                                * (usize::from(read_slots) + usize::from(write_slots)),
197                        )
198                        .cast::<u128>(),
199                    payload as usize,
200                )
201            },
202            // SAFETY: Any bytes are valid for `seal` and all constructors ensure there are
203            // enough bytes for seal in the buffer
204            seal: unsafe {
205                slice::from_raw_parts(
206                    self.buffer
207                        .as_ptr()
208                        .add(size_of::<TransactionHeader>())
209                        .add(size_of::<OwnedTransactionLengths>())
210                        .add(
211                            size_of::<TransactionSlot>()
212                                * (usize::from(read_slots) + usize::from(write_slots))
213                                + payload as usize,
214                        ),
215                    seal as usize,
216                )
217            },
218        }
219    }
220}
221
222/// Errors for [`OwnedTransactionBuilder`]
223#[derive(Debug, thiserror::Error)]
224pub enum OwnedTransactionBuilderError {
225    /// Too many read slots
226    #[error("Too many read slots")]
227    TooManyReadSlots,
228    /// Too many write slots
229    #[error("Too many write slots")]
230    TooManyWriteSlots,
231    /// Payload too large
232    #[error("Payload too large")]
233    PayloadTooLarge,
234    /// Payload is not a multiple of `u128`
235    #[error("Payload is not a multiple of `u128`")]
236    PayloadIsNotMultipleOfU128,
237    /// Seal too large
238    #[error("Seal too large")]
239    SealTooLarge,
240    /// Transaction too large
241    #[error("Transaction too large")]
242    TransactionTooLarge,
243}
244
245/// Builder for [`OwnedTransaction`]
246#[derive(Debug, Clone)]
247pub struct OwnedTransactionBuilder {
248    buffer: BuilderBuffer,
249}
250
251impl OwnedTransactionBuilder {
252    /// Add read-only slot to the transaction
253    pub fn with_read_slot(
254        mut self,
255        slot: &TransactionSlot,
256    ) -> Result<OwnedTransactionBuilder, OwnedTransactionBuilderError> {
257        self.buffer.append_read_slots(slice::from_ref(slot))?;
258        Ok(OwnedTransactionBuilder {
259            buffer: self.buffer,
260        })
261    }
262
263    /// Add many read-only slots to the transaction
264    pub fn with_read_slots(
265        mut self,
266        slots: &[TransactionSlot],
267    ) -> Result<OwnedTransactionBuilder, OwnedTransactionBuilderError> {
268        self.buffer.append_read_slots(slots)?;
269        Ok(OwnedTransactionBuilder {
270            buffer: self.buffer,
271        })
272    }
273
274    /// Add read-write slot to the transaction
275    pub fn with_write_slot(
276        mut self,
277        slot: &TransactionSlot,
278    ) -> Result<OwnedTransactionBuilderWithWriteSlot, OwnedTransactionBuilderError> {
279        self.buffer.append_write_slots(slice::from_ref(slot))?;
280        Ok(OwnedTransactionBuilderWithWriteSlot {
281            buffer: self.buffer,
282        })
283    }
284
285    /// Add many read-write slots to the transaction
286    pub fn with_write_slots(
287        mut self,
288        slots: &[TransactionSlot],
289    ) -> Result<OwnedTransactionBuilderWithWriteSlot, OwnedTransactionBuilderError> {
290        self.buffer.append_write_slots(slots)?;
291        Ok(OwnedTransactionBuilderWithWriteSlot {
292            buffer: self.buffer,
293        })
294    }
295
296    /// Add transaction payload
297    pub fn with_payload(
298        mut self,
299        payload: &[u8],
300    ) -> Result<OwnedTransactionBuilderWithPayload, OwnedTransactionBuilderError> {
301        self.buffer.append_payload(payload)?;
302        Ok(OwnedTransactionBuilderWithPayload {
303            buffer: self.buffer,
304        })
305    }
306
307    /// Add transaction seal
308    pub fn with_seal(
309        mut self,
310        seal: &[u8],
311    ) -> Result<OwnedTransaction, OwnedTransactionBuilderError> {
312        self.buffer.append_seal(seal)?;
313        self.finish()
314    }
315
316    /// Get owned transaction
317    pub fn finish(self) -> Result<OwnedTransaction, OwnedTransactionBuilderError> {
318        let buffer = self.buffer.finish()?.into_shared();
319        Ok(OwnedTransaction { buffer })
320    }
321}
322
323/// Builder for [`OwnedTransaction`] with at least one read-write slot
324#[derive(Debug, Clone)]
325pub struct OwnedTransactionBuilderWithWriteSlot {
326    buffer: BuilderBuffer,
327}
328
329impl OwnedTransactionBuilderWithWriteSlot {
330    /// Add read-write slot to the transaction
331    pub fn with_write_slot(
332        mut self,
333        slot: &TransactionSlot,
334    ) -> Result<Self, OwnedTransactionBuilderError> {
335        self.buffer.append_write_slots(slice::from_ref(slot))?;
336        Ok(Self {
337            buffer: self.buffer,
338        })
339    }
340
341    /// Add many read-write slots to the transaction
342    pub fn with_write_slots(
343        mut self,
344        slots: &[TransactionSlot],
345    ) -> Result<Self, OwnedTransactionBuilderError> {
346        self.buffer.append_write_slots(slots)?;
347        Ok(Self {
348            buffer: self.buffer,
349        })
350    }
351
352    /// Add transaction payload
353    pub fn with_payload(
354        mut self,
355        payload: &[u8],
356    ) -> Result<OwnedTransactionBuilderWithPayload, OwnedTransactionBuilderError> {
357        self.buffer.append_payload(payload)?;
358        Ok(OwnedTransactionBuilderWithPayload {
359            buffer: self.buffer,
360        })
361    }
362
363    /// Add transaction seal
364    pub fn with_seal(
365        mut self,
366        seal: &[u8],
367    ) -> Result<OwnedTransaction, OwnedTransactionBuilderError> {
368        self.buffer.append_seal(seal)?;
369        self.finish()
370    }
371
372    /// Get owned transaction
373    pub fn finish(self) -> Result<OwnedTransaction, OwnedTransactionBuilderError> {
374        let buffer = self.buffer.finish()?.into_shared();
375        Ok(OwnedTransaction { buffer })
376    }
377}
378
379/// Builder for [`OwnedTransaction`] with payload
380#[derive(Debug, Clone)]
381pub struct OwnedTransactionBuilderWithPayload {
382    buffer: BuilderBuffer,
383}
384
385impl OwnedTransactionBuilderWithPayload {
386    /// Add transaction payload
387    pub fn with_payload(mut self, payload: &[u8]) -> Result<Self, OwnedTransactionBuilderError> {
388        self.buffer.append_payload(payload)?;
389        Ok(Self {
390            buffer: self.buffer,
391        })
392    }
393
394    /// Add transaction seal
395    pub fn with_seal(
396        mut self,
397        seal: &[u8],
398    ) -> Result<OwnedTransaction, OwnedTransactionBuilderError> {
399        self.buffer.append_seal(seal)?;
400        self.finish()
401    }
402
403    /// Get owned transaction
404    pub fn finish(self) -> Result<OwnedTransaction, OwnedTransactionBuilderError> {
405        let buffer = self.buffer.finish()?.into_shared();
406        Ok(OwnedTransaction { buffer })
407    }
408}