ab_core_primitives/
transaction.rs

1//! Transaction-related primitives
2
3#[cfg(feature = "alloc")]
4pub mod owned;
5
6use crate::address::Address;
7use crate::block::BlockHash;
8use crate::hashes::Blake3Hash;
9use ab_io_type::trivial_type::TrivialType;
10use blake3::Hasher;
11use core::slice;
12
13/// A measure of compute resources, 1 Gas == 1 ns of compute on reference hardware
14#[derive(Debug, Default, Copy, Clone, TrivialType)]
15#[repr(C)]
16pub struct Gas(u64);
17
18/// Transaction hash
19#[derive(Debug, Default, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, TrivialType)]
20#[repr(C)]
21pub struct TransactionHash(Blake3Hash);
22
23/// Transaction header
24#[derive(Debug, Copy, Clone, TrivialType)]
25#[repr(C)]
26pub struct TransactionHeader {
27    // TODO: Some more complex field?
28    /// Transaction version
29    pub version: u64,
30    /// Block hash at which transaction was created
31    pub block_hash: BlockHash,
32    /// Gas limit
33    pub gas_limit: Gas,
34    /// Contract implementing `TxHandler` trait to use for transaction verification and execution
35    pub contract: Address,
36}
37
38impl TransactionHeader {
39    /// The only supported transaction version right now
40    pub const TRANSACTION_VERSION: u64 = 0;
41}
42
43/// Transaction slot
44#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, TrivialType)]
45#[repr(C)]
46pub struct TransactionSlot {
47    /// Slot owner
48    pub owner: Address,
49    /// Contract that manages the slot
50    pub contract: Address,
51}
52
53/// Similar to `Transaction`, but doesn't require `allow` or data ownership.
54///
55/// Can be created with `Transaction::as_ref()` call.
56#[derive(Debug, Copy, Clone)]
57pub struct Transaction<'a> {
58    /// Transaction header
59    pub header: &'a TransactionHeader,
60    /// Slots in the form of [`TransactionSlot`] that may be read during transaction processing.
61    ///
62    /// These are the only slots that can be used in authorization code.
63    ///
64    /// The code slot of the contract that is being executed and balance of native token are
65    /// implicitly included and doesn't need to be specified (see [`Transaction::read_slots()`].
66    /// Also slots that may also be written to do not need to be repeated in the read slots.
67    pub read_slots: &'a [TransactionSlot],
68    /// Slots in the form of [`TransactionSlot`] that may be written during transaction processing
69    pub write_slots: &'a [TransactionSlot],
70    /// Transaction payload
71    pub payload: &'a [u128],
72    /// Transaction seal
73    pub seal: &'a [u8],
74}
75
76impl Transaction<'_> {
77    /// Compute transaction hash.
78    ///
79    /// Note: this computes transaction hash on every call, so worth caching if it is expected to be
80    /// called often.
81    pub fn hash(&self) -> TransactionHash {
82        let mut hasher = Hasher::new();
83
84        hasher.update(self.header.as_bytes());
85        // SAFETY: `TransactionSlot` is `TrivialType` and can be treated as bytes
86        hasher.update(unsafe {
87            slice::from_raw_parts(
88                self.read_slots.as_ptr().cast::<u8>(),
89                size_of_val(self.read_slots),
90            )
91        });
92        // SAFETY: `TransactionSlot` is `TrivialType` and can be treated as bytes
93        hasher.update(unsafe {
94            slice::from_raw_parts(
95                self.write_slots.as_ptr().cast::<u8>(),
96                size_of_val(self.write_slots),
97            )
98        });
99        // SAFETY: `u128` and can be treated as bytes
100        hasher.update(unsafe {
101            slice::from_raw_parts(
102                self.payload.as_ptr().cast::<u8>(),
103                size_of_val(self.payload),
104            )
105        });
106        hasher.update(self.seal);
107
108        TransactionHash(Blake3Hash::new(*hasher.finalize().as_bytes()))
109    }
110
111    /// Read slots touched by the transaction.
112    ///
113    /// In contrast to `read_slots` property, this includes implicitly used slots.
114    pub fn read_slots(&self) -> impl Iterator<Item = TransactionSlot> {
115        // Slots included implicitly that are always used
116        let implicit_slots = [
117            TransactionSlot {
118                owner: self.header.contract,
119                contract: Address::SYSTEM_CODE,
120            },
121            // TODO: Uncomment once system token contract exists
122            // TransactionSlot {
123            //     owner: self.header.contract,
124            //     contract: Address::SYSTEM_TOKEN,
125            // },
126        ];
127
128        implicit_slots
129            .into_iter()
130            .chain(self.read_slots.iter().copied())
131    }
132
133    /// All slots touched by the transaction.
134    ///
135    /// In contrast to `read_slots` and `write_slots` properties, this includes implicitly used
136    /// slots.
137    pub fn slots(&self) -> impl Iterator<Item = TransactionSlot> {
138        self.read_slots().chain(self.write_slots.iter().copied())
139    }
140}