From 41d1d50e5688d18cecc2e3d6c440c1503ccceae6 Mon Sep 17 00:00:00 2001 From: Hannes Karppila Date: Thu, 20 Jun 2024 21:05:48 +0300 Subject: [PATCH 01/43] Add blob tx --- fuel-tx/src/lib.rs | 3 + fuel-tx/src/test_helper.rs | 1 + fuel-tx/src/transaction.rs | 15 ++ fuel-tx/src/transaction/id.rs | 2 + fuel-tx/src/transaction/metadata.rs | 2 + fuel-tx/src/transaction/repr.rs | 2 + fuel-tx/src/transaction/types.rs | 6 + fuel-tx/src/transaction/types/blob.rs | 165 ++++++++++++++++++++++ fuel-tx/src/transaction/validity.rs | 2 + fuel-tx/src/transaction/validity/error.rs | 4 + fuel-vm/src/checked_transaction.rs | 38 +++++ fuel-vm/src/checked_transaction/types.rs | 73 ++++++++++ fuel-vm/src/interpreter.rs | 39 ++++- 13 files changed, 351 insertions(+), 1 deletion(-) create mode 100644 fuel-tx/src/transaction/types/blob.rs diff --git a/fuel-tx/src/lib.rs b/fuel-tx/src/lib.rs index e16196f196..1050bb7cc9 100644 --- a/fuel-tx/src/lib.rs +++ b/fuel-tx/src/lib.rs @@ -83,6 +83,9 @@ pub use transaction::{ output::Output, output::OutputRepr, policies, + Blob, + BlobBody, + BlobMetadata, Cacheable, Chargeable, ChargeableMetadata, diff --git a/fuel-tx/src/test_helper.rs b/fuel-tx/src/test_helper.rs index 1049fb6dcf..eda5d8cb69 100644 --- a/fuel-tx/src/test_helper.rs +++ b/fuel-tx/src/test_helper.rs @@ -134,6 +134,7 @@ mod use_std { Transaction::Mint(_) => (), Transaction::Upgrade(_) => (), Transaction::Upload(_) => (), + Transaction::Blob(_) => (), }) .unwrap_or(()); diff --git a/fuel-tx/src/transaction.rs b/fuel-tx/src/transaction.rs index d7b38544b0..46ab5e3614 100644 --- a/fuel-tx/src/transaction.rs +++ b/fuel-tx/src/transaction.rs @@ -100,6 +100,7 @@ pub enum Transaction { Mint(Mint), Upgrade(Upgrade), Upload(Upload), + Blob(Blob), } #[cfg(feature = "test-helpers")] @@ -563,6 +564,12 @@ impl From for Transaction { } } +impl From for Transaction { + fn from(tx: Blob) -> Self { + Self::Blob(tx) + } +} + impl Serialize for Transaction { fn size_static(&self) -> usize { match self { @@ -571,6 +578,7 @@ impl Serialize for Transaction { Self::Mint(tx) => tx.size_static(), Self::Upgrade(tx) => tx.size_static(), Self::Upload(tx) => tx.size_static(), + Self::Blob(tx) => tx.size_static(), } } @@ -581,6 +589,7 @@ impl Serialize for Transaction { Self::Mint(tx) => tx.size_dynamic(), Self::Upgrade(tx) => tx.size_dynamic(), Self::Upload(tx) => tx.size_dynamic(), + Self::Blob(tx) => tx.size_dynamic(), } } @@ -594,6 +603,7 @@ impl Serialize for Transaction { Self::Mint(tx) => tx.encode_static(buffer), Self::Upgrade(tx) => tx.encode_static(buffer), Self::Upload(tx) => tx.encode_static(buffer), + Self::Blob(tx) => tx.encode_static(buffer), } } @@ -607,6 +617,7 @@ impl Serialize for Transaction { Self::Mint(tx) => tx.encode_dynamic(buffer), Self::Upgrade(tx) => tx.encode_dynamic(buffer), Self::Upload(tx) => tx.encode_dynamic(buffer), + Self::Blob(tx) => tx.encode_dynamic(buffer), } } } @@ -637,6 +648,9 @@ impl Deserialize for Transaction { TransactionRepr::Upload => { Ok(::decode_static(buffer)?.into()) } + TransactionRepr::Blob => { + Ok(::decode_static(buffer)?.into()) + } } } @@ -650,6 +664,7 @@ impl Deserialize for Transaction { Self::Mint(tx) => tx.decode_dynamic(buffer), Self::Upgrade(tx) => tx.decode_dynamic(buffer), Self::Upload(tx) => tx.decode_dynamic(buffer), + Self::Blob(tx) => tx.decode_dynamic(buffer), } } } diff --git a/fuel-tx/src/transaction/id.rs b/fuel-tx/src/transaction/id.rs index 92b21598b4..f570c15b0c 100644 --- a/fuel-tx/src/transaction/id.rs +++ b/fuel-tx/src/transaction/id.rs @@ -45,6 +45,7 @@ impl UniqueIdentifier for Transaction { Self::Mint(tx) => tx.id(chain_id), Self::Upgrade(tx) => tx.id(chain_id), Self::Upload(tx) => tx.id(chain_id), + Self::Blob(tx) => tx.id(chain_id), } } @@ -55,6 +56,7 @@ impl UniqueIdentifier for Transaction { Self::Mint(tx) => tx.cached_id(), Self::Upgrade(tx) => tx.cached_id(), Self::Upload(tx) => tx.cached_id(), + Self::Blob(tx) => tx.cached_id(), } } } diff --git a/fuel-tx/src/transaction/metadata.rs b/fuel-tx/src/transaction/metadata.rs index c73acfb221..b66dde9b6b 100644 --- a/fuel-tx/src/transaction/metadata.rs +++ b/fuel-tx/src/transaction/metadata.rs @@ -30,6 +30,7 @@ impl Cacheable for super::Transaction { Self::Mint(tx) => tx.is_computed(), Self::Upgrade(tx) => tx.is_computed(), Self::Upload(tx) => tx.is_computed(), + Self::Blob(tx) => tx.is_computed(), } } @@ -40,6 +41,7 @@ impl Cacheable for super::Transaction { Self::Mint(tx) => tx.precompute(chain_id), Self::Upgrade(tx) => tx.precompute(chain_id), Self::Upload(tx) => tx.precompute(chain_id), + Self::Blob(tx) => tx.precompute(chain_id), } } } diff --git a/fuel-tx/src/transaction/repr.rs b/fuel-tx/src/transaction/repr.rs index 9da0231e43..231f3498ef 100644 --- a/fuel-tx/src/transaction/repr.rs +++ b/fuel-tx/src/transaction/repr.rs @@ -10,6 +10,7 @@ pub enum TransactionRepr { Mint = 0x02, Upgrade = 0x03, Upload = 0x04, + Blob = 0x05, } impl From<&Transaction> for TransactionRepr { @@ -20,6 +21,7 @@ impl From<&Transaction> for TransactionRepr { Transaction::Mint { .. } => Self::Mint, Transaction::Upgrade { .. } => Self::Upgrade, Transaction::Upload { .. } => Self::Upload, + Transaction::Blob { .. } => Self::Blob, } } } diff --git a/fuel-tx/src/transaction/types.rs b/fuel-tx/src/transaction/types.rs index 06346cfd10..1ba62bf7bc 100644 --- a/fuel-tx/src/transaction/types.rs +++ b/fuel-tx/src/transaction/types.rs @@ -1,3 +1,4 @@ +mod blob; mod chargeable_transaction; mod create; pub mod input; @@ -10,6 +11,11 @@ mod upload; mod utxo_id; mod witness; +pub use blob::{ + Blob, + BlobBody, + BlobMetadata, +}; pub use chargeable_transaction::{ ChargeableMetadata, ChargeableTransaction, diff --git a/fuel-tx/src/transaction/types/blob.rs b/fuel-tx/src/transaction/types/blob.rs new file mode 100644 index 0000000000..a3a93548ea --- /dev/null +++ b/fuel-tx/src/transaction/types/blob.rs @@ -0,0 +1,165 @@ +use crate::{ + transaction::{ + fee::min_gas, + id::PrepareSign, + metadata::CommonMetadata, + types::chargeable_transaction::{ + ChargeableMetadata, + ChargeableTransaction, + UniqueFormatValidityChecks, + }, + Chargeable, + }, + ConsensusParameters, + FeeParameters, + GasCosts, + Input, + Output, + TransactionRepr, + ValidityError, +}; +use derivative::Derivative; +use fuel_types::{ + bytes::WORD_SIZE, + canonical::Serialize, + ChainId, + Word, +}; + +#[cfg(feature = "alloc")] +use alloc::vec::Vec; + +pub type Blob = ChargeableTransaction; + +#[derive(Default, Debug, Clone, PartialEq, Eq, Hash)] +pub struct BlobMetadata; + +/// The body of the [`Blob`] transaction. +#[derive(Clone, Default, Derivative)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(fuel_types::canonical::Deserialize, fuel_types::canonical::Serialize)] +#[canonical(prefix = TransactionRepr::Blob)] +#[derivative(Eq, PartialEq, Hash, Debug)] +pub struct BlobBody { + data: Vec, +} + +impl PrepareSign for BlobBody { + fn prepare_sign(&mut self) {} +} + +impl Chargeable for Blob { + fn min_gas(&self, gas_costs: &GasCosts, fee: &FeeParameters) -> fuel_asm::Word { + min_gas(self, gas_costs, fee) + } + + #[inline(always)] + fn metered_bytes_size(&self) -> usize { + Serialize::size(self) + } + + #[inline(always)] + fn gas_used_by_metadata(&self, gas_cost: &GasCosts) -> Word { + let bytes = Serialize::size(self); + // Gas required to calculate the `tx_id`. + gas_cost.s256().resolve(bytes as u64) + } +} + +impl UniqueFormatValidityChecks for Blob { + fn check_unique_rules( + &self, + consensus_params: &ConsensusParameters, + ) -> Result<(), ValidityError> { + self.inputs + .iter() + .enumerate() + .try_for_each(|(index, input)| { + if let Some(asset_id) = input.asset_id(consensus_params.base_asset_id()) { + if asset_id != consensus_params.base_asset_id() { + return Err( + ValidityError::TransactionInputContainsNonBaseAssetId { + index, + }, + ); + } + } + + match input { + Input::Contract(_) => { + Err(ValidityError::TransactionInputContainsContract { index }) + } + Input::MessageDataSigned(_) | Input::MessageDataPredicate(_) => { + Err(ValidityError::TransactionInputContainsMessageData { index }) + } + _ => Ok(()), + } + })?; + + self.outputs + .iter() + .enumerate() + .try_for_each(|(index, output)| match output { + Output::Contract(_) => { + Err(ValidityError::TransactionOutputContainsContract { index }) + } + + Output::Variable { .. } => { + Err(ValidityError::TransactionOutputContainsVariable { index }) + } + + Output::Change { asset_id, .. } => { + if asset_id != consensus_params.base_asset_id() { + Err(ValidityError::TransactionChangeChangeUsesNotBaseAsset { + index, + }) + } else { + Ok(()) + } + } + + Output::ContractCreated { .. } => { + Err(ValidityError::TransactionOutputContainsContractCreated { index }) + } + Output::Coin { .. } => { + Err(ValidityError::TransactionOutputContainsCoin { index }) + } + })?; + + Ok(()) + } +} + +impl crate::Cacheable for Blob { + fn is_computed(&self) -> bool { + self.metadata.is_some() + } + + fn precompute(&mut self, chain_id: &ChainId) -> Result<(), ValidityError> { + self.metadata = None; + self.metadata = Some(ChargeableMetadata { + common: CommonMetadata::compute(self, chain_id)?, + body: BlobMetadata {}, + }); + Ok(()) + } +} + +mod field { + use super::*; + use crate::field::ChargeableBody; + + impl ChargeableBody for Blob { + fn body(&self) -> &BlobBody { + &self.body + } + + fn body_mut(&mut self) -> &mut BlobBody { + &mut self.body + } + + fn body_offset_end(&self) -> usize { + WORD_SIZE // `Transaction` enum discriminant + } + } +} diff --git a/fuel-tx/src/transaction/validity.rs b/fuel-tx/src/transaction/validity.rs index 98bbfd7a3b..6fabb318c1 100644 --- a/fuel-tx/src/transaction/validity.rs +++ b/fuel-tx/src/transaction/validity.rs @@ -274,6 +274,7 @@ impl FormatValidityChecks for Transaction { Self::Mint(tx) => tx.check_signatures(chain_id), Self::Upgrade(tx) => tx.check_signatures(chain_id), Self::Upload(tx) => tx.check_signatures(chain_id), + Self::Blob(tx) => tx.check_signatures(chain_id), } } @@ -296,6 +297,7 @@ impl FormatValidityChecks for Transaction { Self::Upload(tx) => { tx.check_without_signatures(block_height, consensus_params) } + Self::Blob(tx) => tx.check_without_signatures(block_height, consensus_params), } } } diff --git a/fuel-tx/src/transaction/validity/error.rs b/fuel-tx/src/transaction/validity/error.rs index f42e7fd7ef..e002fed03d 100644 --- a/fuel-tx/src/transaction/validity/error.rs +++ b/fuel-tx/src/transaction/validity/error.rs @@ -90,6 +90,10 @@ pub enum ValidityError { TransactionOutputContainsContractCreated { index: usize, }, + /// The output contains a `Output::Coin` which is not allowed. + TransactionOutputContainsCoin { + index: usize, + }, /// The block height of the checking doesn't match the transaction's block height. /// `Mint` transaction only exists in the scope of the block. TransactionMintIncorrectBlockHeight, diff --git a/fuel-vm/src/checked_transaction.rs b/fuel-vm/src/checked_transaction.rs index 0b39c442f5..36c33dc0c1 100644 --- a/fuel-vm/src/checked_transaction.rs +++ b/fuel-vm/src/checked_transaction.rs @@ -519,6 +519,7 @@ impl EstimatePredicates for Transaction { Self::Mint(_) => Ok(()), Self::Upgrade(tx) => tx.estimate_predicates(params, memory), Self::Upload(tx) => tx.estimate_predicates(params, memory), + Self::Blob(tx) => tx.estimate_predicates(params, memory), } } @@ -533,6 +534,7 @@ impl EstimatePredicates for Transaction { Self::Mint(_) => Ok(()), Self::Upgrade(tx) => tx.estimate_predicates_async::(params, pool).await, Self::Upload(tx) => tx.estimate_predicates_async::(params, pool).await, + Self::Blob(tx) => tx.estimate_predicates_async::(params, pool).await, } } } @@ -582,6 +584,9 @@ impl CheckPredicates for Checked { CheckedTransaction::Upload(tx) => { CheckPredicates::check_predicates(tx, params, memory)?.into() } + CheckedTransaction::Blob(tx) => { + CheckPredicates::check_predicates(tx, params, memory)?.into() + } }; Ok(checked_transaction.into()) } @@ -622,6 +627,11 @@ impl CheckPredicates for Checked { .await? .into() } + CheckedTransaction::Blob(tx) => { + CheckPredicates::check_predicates_async::(tx, params, pool) + .await? + .into() + } }; Ok(checked_transaction.into()) @@ -641,6 +651,7 @@ pub enum CheckedTransaction { Mint(Checked), Upgrade(Checked), Upload(Checked), + Blob(Checked), } impl From> for CheckedTransaction { @@ -668,6 +679,9 @@ impl From> for CheckedTransaction { (Transaction::Upload(transaction), CheckedMetadata::Upload(metadata)) => { Self::Upload(Checked::new(transaction, metadata, checks_bitmask)) } + (Transaction::Blob(transaction), CheckedMetadata::Blob(metadata)) => { + Self::Blob(Checked::new(transaction, metadata, checks_bitmask)) + } // The code should produce the `CheckedMetadata` for the corresponding // transaction variant. It is done in the implementation of the // `IntoChecked` trait for `Transaction`. With the current @@ -677,6 +691,7 @@ impl From> for CheckedTransaction { (Transaction::Mint(_), _) => unreachable!(), (Transaction::Upgrade(_), _) => unreachable!(), (Transaction::Upload(_), _) => unreachable!(), + (Transaction::Blob(_), _) => unreachable!(), } } } @@ -711,6 +726,12 @@ impl From> for CheckedTransaction { } } +impl From> for CheckedTransaction { + fn from(checked: Checked) -> Self { + Self::Blob(checked) + } +} + impl From for Checked { fn from(checked: CheckedTransaction) -> Self { match checked { @@ -739,6 +760,11 @@ impl From for Checked { metadata, checks_bitmask, }) => Checked::new(transaction.into(), metadata.into(), checks_bitmask), + CheckedTransaction::Blob(Checked { + transaction, + metadata, + checks_bitmask, + }) => Checked::new(transaction.into(), metadata.into(), checks_bitmask), } } } @@ -752,6 +778,7 @@ pub enum CheckedMetadata { Mint(::Metadata), Upgrade(::Metadata), Upload(::Metadata), + Blob(::Metadata), } impl From<