fuel_tx/transaction/types/
upload.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 core::ops::Deref;
22use educe::Educe;
23use fuel_types::{
24    bytes::WORD_SIZE,
25    canonical::Serialize,
26    Bytes32,
27    ChainId,
28    Word,
29};
30
31#[cfg(feature = "alloc")]
32use alloc::vec::Vec;
33
34pub type Upload = ChargeableTransaction<UploadBody, UploadMetadata>;
35
36#[derive(Default, Debug, Clone, PartialEq, Eq, Hash)]
37pub struct UploadMetadata;
38
39/// The body of the [`Upload`] transaction.
40#[derive(Clone, Default, Educe, serde::Serialize, serde::Deserialize)]
41#[cfg_attr(
42    feature = "da-compression",
43    derive(fuel_compression::Compress, fuel_compression::Decompress)
44)]
45#[derive(fuel_types::canonical::Deserialize, fuel_types::canonical::Serialize)]
46#[canonical(prefix = TransactionRepr::Upload)]
47#[educe(Eq, PartialEq, Hash, Debug)]
48pub struct UploadBody {
49    /// The root of the Merkle tree is created over the bytecode.
50    pub root: Bytes32,
51    /// The witness index of the subsection of the bytecode.
52    pub witness_index: u16,
53    /// The index of the subsection of the bytecode.
54    pub subsection_index: u16,
55    /// The total number of subsections on which bytecode was divided.
56    pub subsections_number: u16,
57    /// The proof set helps to verify the connection of the subsection to the `root`.
58    pub proof_set: Vec<Bytes32>,
59}
60
61#[derive(
62    Clone, Default, Eq, PartialEq, Hash, Debug, serde::Serialize, serde::Deserialize,
63)]
64pub struct UploadSubsection {
65    /// The root of the Merkle tree is created over the bytecode.
66    pub root: Bytes32,
67    /// The subsection of the bytecode.
68    pub subsection: Vec<u8>,
69    /// The index of the subsection.
70    pub subsection_index: u16,
71    /// The total number of subsections on which bytecode was divided.
72    pub subsections_number: u16,
73    /// The proof set helps to verify the connection of the subsection to the `root`.
74    pub proof_set: Vec<Bytes32>,
75}
76
77#[derive(
78    Copy, Clone, Eq, PartialEq, Hash, Debug, serde::Serialize, serde::Deserialize,
79)]
80pub enum SplitError {
81    /// The size of the subsection is too small to fit all subsections into `u16::MAX`.
82    SubsectionSizeTooSmall,
83}
84
85impl UploadSubsection {
86    /// Splits the bytecode into verifiable subsections and returns a vector of
87    /// [`UploadSubsection`]s.
88    pub fn split_bytecode(
89        bytecode: &[u8],
90        subsection_size: usize,
91    ) -> Result<Vec<UploadSubsection>, SplitError> {
92        let subsections = bytecode
93            .chunks(subsection_size)
94            .map(|subsection| subsection.to_vec())
95            .collect::<Vec<_>>();
96
97        if subsections.len() > u16::MAX as usize {
98            return Err(SplitError::SubsectionSizeTooSmall);
99        }
100        let subsections_number =
101            u16::try_from(subsections.len()).expect("We've just checked it; qed");
102
103        let mut merkle_tree = fuel_merkle::binary::in_memory::MerkleTree::new();
104        subsections
105            .iter()
106            .for_each(|subsection| merkle_tree.push(subsection));
107
108        let merkle_root = merkle_tree.root();
109
110        let subsections = subsections
111            .into_iter()
112            .enumerate()
113            .map(|(index, subsection)| {
114                let (root, proof_set) = merkle_tree
115                    .prove(index as u64)
116                    .expect("We've just created a merkle tree, so it is valid; qed");
117                debug_assert_eq!(root, merkle_root);
118
119                UploadSubsection {
120                    root: merkle_root.into(),
121                    subsection,
122                    subsection_index: u16::try_from(index).expect(
123                        "The total number of subsections is less than u16::MAX; qed",
124                    ),
125                    subsections_number,
126                    proof_set: proof_set.into_iter().map(Into::into).collect(),
127                }
128            })
129            .collect();
130
131        Ok(subsections)
132    }
133}
134
135impl PrepareSign for UploadBody {
136    fn prepare_sign(&mut self) {}
137}
138
139impl Chargeable for Upload {
140    fn min_gas(&self, gas_costs: &GasCosts, fee: &FeeParameters) -> fuel_asm::Word {
141        let bytecode_len = self
142            .witnesses
143            .get(self.body.witness_index as usize)
144            .map(|c| c.as_ref().len())
145            .unwrap_or(0);
146
147        // Since the `Upload` transaction occupies much of the storage, we want to
148        // discourage people from using it too much. For that, we charge additional gas
149        // for the storage.
150        let additional_charge_for_storage = gas_costs
151            .new_storage_per_byte()
152            .saturating_mul(bytecode_len as u64);
153
154        min_gas(self, gas_costs, fee).saturating_add(additional_charge_for_storage)
155    }
156
157    #[inline(always)]
158    fn metered_bytes_size(&self) -> usize {
159        Serialize::size(self)
160    }
161
162    #[inline(always)]
163    fn gas_used_by_metadata(&self, gas_cost: &GasCosts) -> Word {
164        let bytes = Serialize::size(self);
165        // Gas required to calculate the `tx_id`.
166        let tx_id_gas = gas_cost.s256().resolve(bytes as u64);
167
168        let bytecode_len = self
169            .witnesses
170            .get(self.body.witness_index as usize)
171            .map(|c| c.as_ref().len())
172            .unwrap_or(0);
173
174        let leaf_hash_gas = gas_cost.s256().resolve(bytecode_len as u64);
175        let verify_proof_gas = gas_cost
176            .state_root()
177            .resolve(self.body.subsections_number as u64);
178
179        tx_id_gas
180            .saturating_add(leaf_hash_gas)
181            .saturating_add(verify_proof_gas)
182    }
183}
184
185impl UniqueFormatValidityChecks for Upload {
186    fn check_unique_rules(
187        &self,
188        consensus_params: &ConsensusParameters,
189    ) -> Result<(), ValidityError> {
190        if self.body.subsections_number
191            > consensus_params.tx_params().max_bytecode_subsections()
192        {
193            return Err(ValidityError::TransactionUploadTooManyBytecodeSubsections);
194        }
195
196        let index = self.body.witness_index as usize;
197        let witness = self
198            .witnesses
199            .get(index)
200            .ok_or(ValidityError::InputWitnessIndexBounds { index })?;
201
202        let proof_set = self
203            .body
204            .proof_set
205            .iter()
206            .map(|proof| (*proof).into())
207            .collect::<Vec<_>>();
208
209        // Verify that subsection of the bytecode is connected to the `root` of the
210        // bytecode.
211        let result = fuel_merkle::binary::verify(
212            self.body.root.deref(),
213            witness,
214            &proof_set,
215            self.body.subsection_index as u64,
216            self.body.subsections_number as u64,
217        );
218
219        if !result {
220            return Err(ValidityError::TransactionUploadRootVerificationFailed);
221        }
222
223        self.inputs
224            .iter()
225            .enumerate()
226            .try_for_each(|(index, input)| {
227                if let Some(asset_id) = input.asset_id(consensus_params.base_asset_id()) {
228                    if asset_id != consensus_params.base_asset_id() {
229                        return Err(
230                            ValidityError::TransactionInputContainsNonBaseAssetId {
231                                index,
232                            },
233                        );
234                    }
235                }
236
237                match input {
238                    Input::Contract(_) => {
239                        Err(ValidityError::TransactionInputContainsContract { index })
240                    }
241                    Input::MessageDataSigned(_) | Input::MessageDataPredicate(_) => {
242                        Err(ValidityError::TransactionInputContainsMessageData { index })
243                    }
244                    _ => Ok(()),
245                }
246            })?;
247
248        self.outputs
249            .iter()
250            .enumerate()
251            .try_for_each(|(index, output)| match output {
252                Output::Contract(_) => {
253                    Err(ValidityError::TransactionOutputContainsContract { index })
254                }
255
256                Output::Variable { .. } => {
257                    Err(ValidityError::TransactionOutputContainsVariable { index })
258                }
259
260                Output::Change { asset_id, .. }
261                    if asset_id != consensus_params.base_asset_id() =>
262                {
263                    Err(ValidityError::TransactionChangeChangeUsesNotBaseAsset { index })
264                }
265
266                Output::ContractCreated { .. } => {
267                    Err(ValidityError::TransactionOutputContainsContractCreated { index })
268                }
269                _ => Ok(()),
270            })?;
271
272        Ok(())
273    }
274}
275
276impl crate::Cacheable for Upload {
277    fn is_computed(&self) -> bool {
278        self.metadata.is_some()
279    }
280
281    fn precompute(&mut self, chain_id: &ChainId) -> Result<(), ValidityError> {
282        self.metadata = None;
283        self.metadata = Some(ChargeableMetadata {
284            common: CommonMetadata::compute(self, chain_id)?,
285            body: UploadMetadata {},
286        });
287        Ok(())
288    }
289}
290
291mod field {
292    use super::*;
293    use crate::field::{
294        BytecodeRoot,
295        BytecodeWitnessIndex,
296        ChargeableBody,
297        ProofSet,
298        SubsectionIndex,
299        SubsectionsNumber,
300    };
301
302    impl BytecodeRoot for Upload {
303        #[inline(always)]
304        fn bytecode_root(&self) -> &Bytes32 {
305            &self.body.root
306        }
307
308        #[inline(always)]
309        fn bytecode_root_mut(&mut self) -> &mut Bytes32 {
310            &mut self.body.root
311        }
312
313        #[inline(always)]
314        fn bytecode_root_offset_static() -> usize {
315            WORD_SIZE // `Transaction` enum discriminant
316        }
317    }
318
319    impl BytecodeWitnessIndex for Upload {
320        #[inline(always)]
321        fn bytecode_witness_index(&self) -> &u16 {
322            &self.body.witness_index
323        }
324
325        #[inline(always)]
326        fn bytecode_witness_index_mut(&mut self) -> &mut u16 {
327            &mut self.body.witness_index
328        }
329
330        #[inline(always)]
331        fn bytecode_witness_index_offset_static() -> usize {
332            Self::bytecode_root_offset_static().saturating_add(Bytes32::LEN)
333        }
334    }
335
336    impl SubsectionIndex for Upload {
337        #[inline(always)]
338        fn subsection_index(&self) -> &u16 {
339            &self.body.subsection_index
340        }
341
342        #[inline(always)]
343        fn subsection_index_mut(&mut self) -> &mut u16 {
344            &mut self.body.subsection_index
345        }
346
347        #[inline(always)]
348        fn subsection_index_offset_static() -> usize {
349            Self::bytecode_witness_index_offset_static().saturating_add(WORD_SIZE)
350        }
351    }
352
353    impl SubsectionsNumber for Upload {
354        #[inline(always)]
355        fn subsections_number(&self) -> &u16 {
356            &self.body.subsections_number
357        }
358
359        #[inline(always)]
360        fn subsections_number_mut(&mut self) -> &mut u16 {
361            &mut self.body.subsections_number
362        }
363
364        #[inline(always)]
365        fn subsections_number_offset_static() -> usize {
366            Self::subsection_index_offset_static().saturating_add(WORD_SIZE)
367        }
368    }
369
370    impl ProofSet for Upload {
371        #[inline(always)]
372        fn proof_set(&self) -> &Vec<Bytes32> {
373            &self.body.proof_set
374        }
375
376        #[inline(always)]
377        fn proof_set_mut(&mut self) -> &mut Vec<Bytes32> {
378            &mut self.body.proof_set
379        }
380
381        #[inline(always)]
382        fn proof_set_offset_static() -> usize {
383            Self::subsections_number_offset_static().saturating_add(
384                WORD_SIZE
385                + WORD_SIZE // Proof set size
386                + WORD_SIZE // Policies size
387                + WORD_SIZE // Inputs size
388                + WORD_SIZE // Outputs size
389                + WORD_SIZE, // Witnesses size
390            )
391        }
392
393        #[inline(always)]
394        fn proof_set_offset_at(&self, idx: usize) -> Option<usize> {
395            if idx < self.body.proof_set.len() {
396                Some(
397                    Self::proof_set_offset_static()
398                        .checked_add(idx.checked_mul(Bytes32::LEN)?)?,
399                )
400            } else {
401                None
402            }
403        }
404    }
405
406    impl ChargeableBody<UploadBody> for Upload {
407        fn body(&self) -> &UploadBody {
408            &self.body
409        }
410
411        fn body_mut(&mut self) -> &mut UploadBody {
412            &mut self.body
413        }
414
415        fn body_offset_end(&self) -> usize {
416            Self::proof_set_offset_static()
417                .saturating_add(self.body.proof_set.len().saturating_mul(Bytes32::LEN))
418        }
419    }
420}