1#[cfg(feature = "alloc")]
4pub mod owned;
5
6use crate::address::Address;
7use crate::block::BlockRoot;
8use crate::hashes::Blake3Hash;
9#[cfg(feature = "alloc")]
10use crate::transaction::owned::{OwnedTransaction, OwnedTransactionError};
11use ab_io_type::alignment::Alignment;
12use ab_io_type::trivial_type::TrivialType;
13use core::ops::Deref;
14use derive_more::{Deref, DerefMut, Display, From, Into};
15
16#[derive(Debug, Default, Copy, Clone, TrivialType)]
18#[repr(C)]
19pub struct Gas(u64);
20
21#[derive(
23 Debug,
24 Display,
25 Default,
26 Copy,
27 Clone,
28 Ord,
29 PartialOrd,
30 Eq,
31 PartialEq,
32 Hash,
33 From,
34 Into,
35 Deref,
36 DerefMut,
37 TrivialType,
38)]
39#[repr(C)]
40pub struct TransactionHash(Blake3Hash);
41
42impl AsRef<[u8]> for TransactionHash {
43 #[inline(always)]
44 fn as_ref(&self) -> &[u8] {
45 self.0.as_ref()
46 }
47}
48
49impl AsMut<[u8]> for TransactionHash {
50 #[inline(always)]
51 fn as_mut(&mut self) -> &mut [u8] {
52 self.0.as_mut()
53 }
54}
55
56#[derive(Debug, Copy, Clone, TrivialType)]
58#[repr(C)]
59pub struct TransactionHeader {
60 pub version: u64,
64 pub block_root: BlockRoot,
66 pub gas_limit: Gas,
68 pub contract: Address,
70}
71
72impl TransactionHeader {
73 pub const TRANSACTION_VERSION: u64 = 0;
75}
76
77#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, TrivialType)]
79#[repr(C)]
80pub struct TransactionSlot {
81 pub owner: Address,
83 pub contract: Address,
85}
86
87#[derive(Debug, Default, Copy, Clone, TrivialType)]
89#[repr(C)]
90pub struct SerializedTransactionLengths {
91 pub read_slots: u16,
93 pub write_slots: u16,
95 pub payload: u32,
97 pub seal: u32,
99 pub padding: [u8; 4],
101}
102
103#[derive(Debug, Clone)]
108#[expect(
109 clippy::partial_pub_fields,
110 reason = "Intentionally exposing decoded fields and hiding the bytes they were decoded from"
111)]
112pub struct Transaction<'a> {
113 pub mut(self) header: &'a TransactionHeader,
115 pub mut(self) read_slots: &'a [TransactionSlot],
123 pub mut(self) write_slots: &'a [TransactionSlot],
125 pub mut(self) payload: &'a [u128],
127 pub mut(self) seal: &'a [u8],
129 bytes: &'a [u8],
131 #[cfg(all(feature = "alloc", any(target_os = "none", target_os = "unknown")))]
132 cached_hash: once_cell::race::OnceBox<TransactionHash>,
133 #[cfg(not(any(target_os = "none", target_os = "unknown")))]
134 cached_hash: std::sync::OnceLock<TransactionHash>,
135}
136
137impl<'a> Transaction<'a> {
138 #[inline]
155 pub fn try_from_bytes(bytes: &'a [u8]) -> Option<(Self, &'a [u8])> {
156 const {
157 assert!(
159 (size_of::<TransactionHeader>() + size_of::<SerializedTransactionLengths>())
160 .is_multiple_of(usize::from(Alignment::MAX.get()))
161 );
162 assert!(size_of::<TransactionSlot>().is_multiple_of(usize::from(Alignment::MAX.get())));
163 }
164
165 if !Alignment::MAX.is_slice_aligned(bytes) {
166 return None;
167 }
168
169 let (header, remainder) = TransactionHeader::from_bytes(bytes)?;
170 let (lengths, remainder) = SerializedTransactionLengths::from_bytes(remainder)?;
171 let SerializedTransactionLengths {
172 read_slots,
173 write_slots,
174 payload,
175 seal,
176 padding,
177 } = *lengths;
178
179 if padding != [0; _] {
180 return None;
181 }
182
183 if !payload.is_multiple_of(u128::SIZE) {
184 return None;
185 }
186
187 let payload = payload as usize;
190 let seal = seal as usize;
191
192 let (read_slots, remainder) =
193 TransactionSlot::slice_from_bytes(remainder, usize::from(read_slots))?;
194 let (write_slots, remainder) =
195 TransactionSlot::slice_from_bytes(remainder, usize::from(write_slots))?;
196 let (payload, remainder) = u128::slice_from_bytes(remainder, payload / size_of::<u128>())?;
197 let (seal, remainder) = remainder.split_at_checked(seal)?;
198 let (bytes, _remainder) = bytes.split_at(bytes.len() - remainder.len());
200
201 let transaction = Self {
202 header,
203 read_slots,
204 write_slots,
205 payload,
206 seal,
207 bytes,
208 #[cfg(all(feature = "alloc", any(target_os = "none", target_os = "unknown")))]
209 cached_hash: once_cell::race::OnceBox::new(),
210 #[cfg(not(any(target_os = "none", target_os = "unknown")))]
211 cached_hash: std::sync::OnceLock::new(),
212 };
213
214 Some((transaction, remainder))
215 }
216
217 #[cfg(feature = "alloc")]
219 #[inline(always)]
220 pub fn to_owned(self) -> Result<OwnedTransaction, OwnedTransactionError> {
221 OwnedTransaction::from_transaction(self)
222 }
223
224 #[inline(always)]
226 pub const fn encoded_size(&self) -> usize {
227 self.bytes.len()
228 }
229
230 #[cfg(feature = "alloc")]
232 #[inline(always)]
233 pub(crate) const fn bytes(&self) -> &'a [u8] {
234 self.bytes
235 }
236
237 #[inline]
242 pub fn hash(&self) -> impl Deref<Target = TransactionHash> + Send + Sync {
243 let compute_hash = || TransactionHash(Blake3Hash::from(blake3::hash(self.bytes)));
244
245 #[cfg(not(any(target_os = "none", target_os = "unknown")))]
246 {
247 self.cached_hash.get_or_init(compute_hash)
248 }
249 #[cfg(all(feature = "alloc", any(target_os = "none", target_os = "unknown")))]
250 {
251 self.cached_hash
252 .get_or_init(|| alloc::boxed::Box::new(compute_hash()))
253 }
254 #[cfg(all(not(feature = "alloc"), any(target_os = "none", target_os = "unknown")))]
255 {
256 struct Wrapper(TransactionHash);
257
258 impl Deref for Wrapper {
259 type Target = TransactionHash;
260
261 #[inline(always)]
262 fn deref(&self) -> &Self::Target {
263 &self.0
264 }
265 }
266
267 Wrapper(compute_hash())
268 }
269 }
270
271 pub fn read_slots(&self) -> impl Iterator<Item = TransactionSlot> {
275 let implicit_slots = [
277 TransactionSlot {
278 owner: self.header.contract,
279 contract: Address::SYSTEM_CODE,
280 },
281 ];
287
288 implicit_slots
289 .into_iter()
290 .chain(self.read_slots.iter().copied())
291 }
292
293 pub fn slots(&self) -> impl Iterator<Item = TransactionSlot> {
298 self.read_slots().chain(self.write_slots.iter().copied())
299 }
300}