fuels_core/types/transaction_builders/
blob.rs

1use std::{fmt::Debug, iter::repeat, sync::Arc};
2
3use async_trait::async_trait;
4use fuel_crypto::Signature;
5use fuel_tx::{
6    BlobIdExt, Chargeable, Output, Transaction as FuelTransaction, UniqueIdentifier, Witness,
7    field::{Policies as PoliciesField, Witnesses},
8    policies::{Policies, PolicyType},
9};
10use fuel_types::bytes::padded_len_usize;
11use itertools::Itertools;
12
13use super::{
14    BuildableTransaction, GAS_ESTIMATION_BLOCK_HORIZON, Strategy, TransactionBuilder,
15    UnresolvedWitnessIndexes, generate_missing_witnesses, impl_tx_builder_trait,
16    resolve_fuel_inputs,
17};
18use crate::{
19    constants::SIGNATURE_WITNESS_SIZE,
20    traits::Signer,
21    types::{
22        DryRunner,
23        errors::{Result, error, error_transaction},
24        input::Input,
25        transaction::{BlobTransaction, EstimablePredicates, Transaction, TxPolicies},
26    },
27    utils::{calculate_witnesses_size, sealed},
28};
29
30#[derive(Default, Clone, Debug, PartialEq)]
31pub struct Blob {
32    data: Vec<u8>,
33}
34
35pub type BlobId = [u8; 32];
36
37impl From<Vec<u8>> for Blob {
38    fn from(data: Vec<u8>) -> Self {
39        Self { data }
40    }
41}
42
43impl AsRef<[u8]> for Blob {
44    fn as_ref(&self) -> &[u8] {
45        &self.data
46    }
47}
48
49impl Blob {
50    pub fn new(data: Vec<u8>) -> Self {
51        Self { data }
52    }
53
54    pub fn len(&self) -> usize {
55        self.data.len()
56    }
57
58    pub fn is_empty(&self) -> bool {
59        self.data.is_empty()
60    }
61
62    pub fn id(&self) -> BlobId {
63        fuel_tx::BlobId::compute(&self.data).into()
64    }
65
66    pub fn bytes(&self) -> &[u8] {
67        self.data.as_slice()
68    }
69
70    fn as_blob_body(&self, witness_index: u16) -> fuel_tx::BlobBody {
71        fuel_tx::BlobBody {
72            id: self.id().into(),
73            witness_index,
74        }
75    }
76}
77
78impl From<Blob> for Vec<u8> {
79    fn from(value: Blob) -> Self {
80        value.data
81    }
82}
83
84impl From<Blob> for fuel_tx::Witness {
85    fn from(blob: Blob) -> Self {
86        blob.data.into()
87    }
88}
89
90#[derive(Debug, Clone)]
91pub struct BlobTransactionBuilder {
92    pub inputs: Vec<Input>,
93    pub outputs: Vec<Output>,
94    pub witnesses: Vec<Witness>,
95    pub tx_policies: TxPolicies,
96    pub gas_price_estimation_block_horizon: u32,
97    pub max_fee_estimation_tolerance: f32,
98    pub build_strategy: Strategy,
99    pub blob: Blob,
100    unresolved_witness_indexes: UnresolvedWitnessIndexes,
101    unresolved_signers: Vec<Arc<dyn Signer + Send + Sync>>,
102    enable_burn: bool,
103}
104
105impl Default for BlobTransactionBuilder {
106    fn default() -> Self {
107        Self {
108            inputs: Default::default(),
109            outputs: Default::default(),
110            witnesses: Default::default(),
111            tx_policies: Default::default(),
112            gas_price_estimation_block_horizon: GAS_ESTIMATION_BLOCK_HORIZON,
113            max_fee_estimation_tolerance: Default::default(),
114            build_strategy: Default::default(),
115            blob: Default::default(),
116            unresolved_witness_indexes: Default::default(),
117            unresolved_signers: Default::default(),
118            enable_burn: false,
119        }
120    }
121}
122impl_tx_builder_trait!(BlobTransactionBuilder, BlobTransaction);
123
124impl BlobTransactionBuilder {
125    /// Calculates the maximum possible blob size by determining the remaining space available in the current transaction before it reaches the maximum allowed size.
126    /// Note: This calculation only considers the transaction size limit and does not account for the maximum gas per transaction.
127    pub async fn estimate_max_blob_size(&self, provider: &impl DryRunner) -> Result<usize> {
128        let mut tb = self.clone();
129        tb.blob = Blob::new(vec![]);
130
131        let tx = tb
132            .with_build_strategy(Strategy::NoSignatures)
133            .build(provider)
134            .await?;
135
136        let current_tx_size = tx.size();
137        let max_tx_size = usize::try_from(
138            provider
139                .consensus_parameters()
140                .await?
141                .tx_params()
142                .max_size(),
143        )
144        .unwrap_or(usize::MAX);
145
146        Ok(max_tx_size.saturating_sub(current_tx_size))
147    }
148
149    pub async fn build(mut self, provider: impl DryRunner) -> Result<BlobTransaction> {
150        let consensus_parameters = provider.consensus_parameters().await?;
151        self.intercept_burn(consensus_parameters.base_asset_id())?;
152
153        let is_using_predicates = self.is_using_predicates();
154
155        let tx = match self.build_strategy {
156            Strategy::Complete => self.resolve_fuel_tx(&provider).await?,
157            Strategy::NoSignatures => {
158                self.set_witness_indexes();
159                self.unresolved_signers = Default::default();
160                self.resolve_fuel_tx(&provider).await?
161            }
162        };
163
164        Ok(BlobTransaction {
165            is_using_predicates,
166            tx,
167        })
168    }
169
170    async fn resolve_fuel_tx(mut self, provider: &impl DryRunner) -> Result<fuel_tx::Blob> {
171        let chain_id = provider.consensus_parameters().await?.chain_id();
172
173        let free_witness_index = self.num_witnesses()?;
174        let body = self.blob.as_blob_body(free_witness_index);
175
176        let blob_witness = std::mem::take(&mut self.blob).into();
177        self.witnesses_mut().push(blob_witness);
178
179        let num_witnesses = self.num_witnesses()?;
180        let policies = self.generate_fuel_policies()?;
181        let is_using_predicates = self.is_using_predicates();
182
183        let mut tx = FuelTransaction::blob(
184            body,
185            policies,
186            resolve_fuel_inputs(self.inputs, num_witnesses, &self.unresolved_witness_indexes)?,
187            self.outputs,
188            self.witnesses,
189        );
190
191        if let Some(max_fee) = self.tx_policies.max_fee() {
192            tx.policies_mut().set(PolicyType::MaxFee, Some(max_fee));
193        } else {
194            Self::set_max_fee_policy(
195                &mut tx,
196                &provider,
197                self.gas_price_estimation_block_horizon,
198                is_using_predicates,
199                self.max_fee_estimation_tolerance,
200            )
201            .await?;
202        }
203
204        let signatures =
205            generate_missing_witnesses(tx.id(&chain_id), &self.unresolved_signers).await?;
206        tx.witnesses_mut().extend(signatures);
207
208        Ok(tx)
209    }
210
211    pub fn with_blob(mut self, blob: Blob) -> Self {
212        self.blob = blob;
213        self
214    }
215
216    pub fn with_max_fee_estimation_tolerance(mut self, max_fee_estimation_tolerance: f32) -> Self {
217        self.max_fee_estimation_tolerance = max_fee_estimation_tolerance;
218        self
219    }
220}
221
222impl sealed::Sealed for BlobTransactionBuilder {}
223
224#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
225impl BuildableTransaction for BlobTransactionBuilder {
226    type TxType = BlobTransaction;
227    type Strategy = Strategy;
228
229    fn with_build_strategy(mut self, strategy: Self::Strategy) -> Self {
230        self.build_strategy = strategy;
231        self
232    }
233
234    async fn build(self, provider: impl DryRunner) -> Result<Self::TxType> {
235        BlobTransactionBuilder::build(self, provider).await
236    }
237}