fuels_core/types/transaction_builders/
blob.rs1use 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 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}