fuel_tx/transaction/types/
blob.rs

1use crate::{
2    transaction::{
3        fee::min_gas,
4        id::PrepareSign,
5        metadata::CommonMetadata,
6        types::chargeable_transaction::{
7            ChargeableMetadata,
8            ChargeableTransaction,
9            UniqueFormatValidityChecks,
10        },
11        Chargeable,
12    },
13    ConsensusParameters,
14    FeeParameters,
15    GasCosts,
16    Input,
17    Output,
18    TransactionRepr,
19    ValidityError,
20};
21use educe::Educe;
22use fuel_types::{
23    bytes::WORD_SIZE,
24    canonical::Serialize,
25    BlobId,
26    ChainId,
27    Word,
28};
29
30/// Adds method to `BlobId` to compute the it from blob data.
31pub trait BlobIdExt {
32    /// Computes the `BlobId` from by hashing the given data.
33    fn compute(data: &[u8]) -> BlobId;
34}
35
36impl BlobIdExt for BlobId {
37    fn compute(data: &[u8]) -> Self {
38        Self::new(*fuel_crypto::Hasher::hash(data))
39    }
40}
41
42pub type Blob = ChargeableTransaction<BlobBody, BlobMetadata>;
43
44#[derive(Default, Debug, Clone, PartialEq, Eq, Hash)]
45pub struct BlobMetadata;
46
47/// The body of the [`Blob`] transaction.
48#[derive(Clone, Default, Educe, serde::Serialize, serde::Deserialize)]
49#[cfg_attr(
50    feature = "da-compression",
51    derive(fuel_compression::Compress, fuel_compression::Decompress)
52)]
53#[derive(fuel_types::canonical::Deserialize, fuel_types::canonical::Serialize)]
54#[canonical(prefix = TransactionRepr::Blob)]
55#[educe(Eq, PartialEq, Hash, Debug)]
56pub struct BlobBody {
57    /// Hash of the bytecode. Used both as a unique identifier and to verify the
58    /// bytecode.
59    pub id: BlobId,
60    /// The witness index of the payload.
61    pub witness_index: u16,
62}
63
64impl PrepareSign for BlobBody {
65    fn prepare_sign(&mut self) {}
66}
67
68impl Chargeable for Blob {
69    fn min_gas(&self, gas_costs: &GasCosts, fee: &FeeParameters) -> fuel_asm::Word {
70        min_gas(self, gas_costs, fee)
71    }
72
73    #[inline(always)]
74    fn metered_bytes_size(&self) -> usize {
75        Serialize::size(self)
76    }
77
78    #[inline(always)]
79    fn gas_used_by_metadata(&self, gas_cost: &GasCosts) -> Word {
80        let bytes = Serialize::size(self);
81        let blob_len = self
82            .witnesses
83            .get(self.body.witness_index as usize)
84            .map(|c| c.as_ref().len())
85            .unwrap_or(0);
86
87        // Gas required to calculate the `tx_id` and `blob_id`.
88        gas_cost
89            .s256()
90            .resolve(bytes as u64)
91            .saturating_add(gas_cost.s256().resolve(blob_len as u64))
92    }
93}
94
95impl UniqueFormatValidityChecks for Blob {
96    fn check_unique_rules(
97        &self,
98        consensus_params: &ConsensusParameters,
99    ) -> Result<(), ValidityError> {
100        let index = self.body.witness_index as usize;
101        let witness = self
102            .witnesses
103            .get(index)
104            .ok_or(ValidityError::InputWitnessIndexBounds { index })?;
105
106        // Verify that blob id is correct
107        if BlobId::compute(witness.as_ref()) != self.body.id {
108            return Err(ValidityError::TransactionBlobIdVerificationFailed);
109        }
110
111        self.inputs
112            .iter()
113            .enumerate()
114            .try_for_each(|(index, input)| {
115                if let Some(asset_id) = input.asset_id(consensus_params.base_asset_id()) {
116                    if asset_id != consensus_params.base_asset_id() {
117                        return Err(
118                            ValidityError::TransactionInputContainsNonBaseAssetId {
119                                index,
120                            },
121                        );
122                    }
123                }
124
125                match input {
126                    Input::Contract(_) => {
127                        Err(ValidityError::TransactionInputContainsContract { index })
128                    }
129                    Input::MessageDataSigned(_) | Input::MessageDataPredicate(_) => {
130                        Err(ValidityError::TransactionInputContainsMessageData { index })
131                    }
132                    _ => Ok(()),
133                }
134            })?;
135
136        self.outputs
137            .iter()
138            .enumerate()
139            .try_for_each(|(index, output)| match output {
140                Output::Contract(_) => {
141                    Err(ValidityError::TransactionOutputContainsContract { index })
142                }
143
144                Output::Variable { .. } => {
145                    Err(ValidityError::TransactionOutputContainsVariable { index })
146                }
147
148                Output::Change { asset_id, .. } => {
149                    if asset_id != consensus_params.base_asset_id() {
150                        Err(ValidityError::TransactionChangeChangeUsesNotBaseAsset {
151                            index,
152                        })
153                    } else {
154                        Ok(())
155                    }
156                }
157
158                Output::ContractCreated { .. } => {
159                    Err(ValidityError::TransactionOutputContainsContractCreated { index })
160                }
161
162                Output::Coin { .. } => Ok(()),
163            })?;
164
165        Ok(())
166    }
167}
168
169impl crate::Cacheable for Blob {
170    fn is_computed(&self) -> bool {
171        self.metadata.is_some()
172    }
173
174    fn precompute(&mut self, chain_id: &ChainId) -> Result<(), ValidityError> {
175        self.metadata = None;
176        self.metadata = Some(ChargeableMetadata {
177            common: CommonMetadata::compute(self, chain_id)?,
178            body: BlobMetadata {},
179        });
180        Ok(())
181    }
182}
183
184mod field {
185    use super::*;
186    use crate::field::{
187        self,
188        BlobId as BlobIdField,
189        BytecodeWitnessIndex,
190    };
191
192    impl field::BlobId for Blob {
193        #[inline(always)]
194        fn blob_id(&self) -> &BlobId {
195            &self.body.id
196        }
197
198        #[inline(always)]
199        fn blob_id_mut(&mut self) -> &mut BlobId {
200            &mut self.body.id
201        }
202
203        #[inline(always)]
204        fn blob_id_offset_static() -> usize {
205            WORD_SIZE // `Transaction` enum discriminant
206        }
207    }
208
209    impl field::BytecodeWitnessIndex for Blob {
210        #[inline(always)]
211        fn bytecode_witness_index(&self) -> &u16 {
212            &self.body.witness_index
213        }
214
215        #[inline(always)]
216        fn bytecode_witness_index_mut(&mut self) -> &mut u16 {
217            &mut self.body.witness_index
218        }
219
220        #[inline(always)]
221        fn bytecode_witness_index_offset_static() -> usize {
222            Self::blob_id_offset_static().saturating_add(BlobId::LEN)
223        }
224    }
225
226    impl field::ChargeableBody<BlobBody> for Blob {
227        fn body(&self) -> &BlobBody {
228            &self.body
229        }
230
231        fn body_mut(&mut self) -> &mut BlobBody {
232            &mut self.body
233        }
234
235        #[allow(clippy::arithmetic_side_effects)] // Statically known to be ok
236        fn body_offset_end(&self) -> usize {
237            Self::bytecode_witness_index_offset_static().saturating_add(
238                WORD_SIZE // witness_index
239                + WORD_SIZE // Policies size
240                + WORD_SIZE // Inputs size
241                + WORD_SIZE // Outputs size
242                + WORD_SIZE, // Witnesses size
243            )
244        }
245    }
246}