Skip to content

Commit

Permalink
Blob tx and instructions (#780)
Browse files Browse the repository at this point in the history
* Add blob tx

* Add some tests

* Add opcodes for blob storage access

* Freeze stack on DLDC instead of copying it over the data

* Add internal context test cases

* Add changelog entry

* Remove bldc. Allow executable stack.

* Merge RunResult helper type with AluResult

* Move blob tx data to witness like other txs do

* Fixes for fuel-core

* Merge RunResult types after branch merge

* Fix no_std imports

* Add blob id validation rule

* Add base_asset_id to blob::CheckedMetadata

* Use LDC with mode argument instead of having separate blob instructions

* Restore BSIZ and BLDD instructions

* fmt

* Update doc comment (PR feedback)

* Improve test case names (pr feedback)

* Apply PR review suggestions

* Fix the serialization for the blob

* Fix the serialization for the blob

* Make clippy happy

* Add offset and encoding tests

* Add as_blob(_mut) for tx

* fix clippy

* Fix offset tests (were targeting wrong tx type)

* Update fuel-tx/src/transaction/types/blob.rs

Co-authored-by: Green Baneling <[email protected]>

* Address PR feedback

* More PR comments

* Disallow blob reupload

* Make memory access determintistic across 32 and 64 bit platforms

* Remove tests that were dependent on executable stack

* Charge BLDD based on max(load_len, blob_size)

* Add blob_id field getter test

* clippy

* Fix wrong test name

* Address PR feedback

* Remove redundant coin validity check clause

---------

Co-authored-by: green <[email protected]>
  • Loading branch information
Dentosal and xgreenx authored Jul 26, 2024
1 parent 00c5b61 commit d2c76e2
Show file tree
Hide file tree
Showing 50 changed files with 3,584 additions and 206 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/).

## [Unreleased]

### Changed

#### Breaking
- [#780](https://github.com/FuelLabs/fuel-vm/pull/780): Added `Blob` transaction, and `BSIZ` and `BLDD` instructions. Also allows `LDC` to load blobs.

## [Version 0.55.0]

### Added
Expand Down
9 changes: 7 additions & 2 deletions fuel-asm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ impl_instructions! {
"Get current block proposer's address."
0x31 CB cb [dst: RegId]
"Load a contract's code as executable."
0x32 LDC ldc [contract_id_addr: RegId offset: RegId len: RegId]
0x32 LDC ldc [contract_id_addr: RegId offset: RegId len: RegId mode: Imm06]
"Log an event."
0x33 LOG log [a: RegId b: RegId c: RegId d: RegId]
"Log data."
Expand Down Expand Up @@ -340,6 +340,11 @@ impl_instructions! {

"Call external function"
0xb0 ECAL ecal [a: RegId b: RegId c: RegId d: RegId]

"Get blob size"
0xba BSIZ bsiz [dst: RegId blob_id_ptr: RegId]
"Load blob as data"
0xbb BLDD bldd [dst_ptr: RegId blob_id_ptr: RegId offset: RegId len: RegId]
}

impl Instruction {
Expand Down Expand Up @@ -988,7 +993,7 @@ fn check_predicate_allowed() {
let should_allow = match repr {
BAL | BHEI | BHSH | BURN | CALL | CB | CCP | CROO | CSIZ | LDC | LOG
| LOGD | MINT | RETD | RVRT | SMO | SCWQ | SRW | SRWQ | SWW | SWWQ
| TIME | TR | TRO | ECAL => false,
| TIME | TR | TRO | ECAL | BSIZ | BLDD => false,
_ => true,
};
assert_eq!(should_allow, repr.is_predicate_allowed());
Expand Down
6 changes: 6 additions & 0 deletions fuel-asm/src/panic_reason.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,12 @@ enum_from! {
BytecodeAlreadyUploaded = 0x34,
/// The part of the bytecode is not sequentially connected to the previous parts.
ThePartIsNotSequentiallyConnected = 0x35,
/// The requested blob is not found.
BlobNotFound = 0x36,
/// The blob was already
BlobIdAlreadyUploaded = 0x37,
/// Active gas costs do not define the cost for this instruction.
GasCostNotDefined = 0x38,
}
}

Expand Down
16 changes: 16 additions & 0 deletions fuel-tx/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ use crate::{
Executable,
Script,
},
Blob,
BlobBody,
ConsensusParameters,
ContractParameters,
CreateMetadata,
Expand Down Expand Up @@ -210,6 +212,20 @@ impl TransactionBuilder<Upload> {
}
}

impl TransactionBuilder<Blob> {
pub fn blob(body: BlobBody) -> Self {
let tx = Blob {
body,
policies: Policies::new().with_max_fee(0),
inputs: Default::default(),
outputs: Default::default(),
witnesses: Default::default(),
metadata: None,
};
Self::with_tx(tx)
}
}

impl TransactionBuilder<Mint> {
pub fn mint(
block_height: BlockHeight,
Expand Down
5 changes: 5 additions & 0 deletions fuel-tx/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ pub use fuel_asm::{
pub use fuel_types::{
Address,
AssetId,
BlobId,
Bytes32,
Bytes4,
Bytes64,
Expand Down Expand Up @@ -83,6 +84,10 @@ pub use transaction::{
output::Output,
output::OutputRepr,
policies,
Blob,
BlobBody,
BlobIdExt,
BlobMetadata,
Cacheable,
Chargeable,
ChargeableMetadata,
Expand Down
46 changes: 45 additions & 1 deletion fuel-tx/src/test_helper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ mod use_std {
};
use crate::{
field,
Blob,
BlobBody,
BlobIdExt,
Buildable,
ConsensusParameters,
Contract,
Expand All @@ -68,7 +71,10 @@ mod use_std {
Hasher,
SecretKey,
};
use fuel_types::canonical::Deserialize;
use fuel_types::{
canonical::Deserialize,
BlobId,
};
use rand::{
distributions::{
Distribution,
Expand Down Expand Up @@ -134,6 +140,7 @@ mod use_std {
Transaction::Mint(_) => (),
Transaction::Upgrade(_) => (),
Transaction::Upload(_) => (),
Transaction::Blob(_) => (),
})
.unwrap_or(());

Expand Down Expand Up @@ -443,6 +450,32 @@ mod use_std {
}
}

impl<R> TransactionFactory<R, Blob>
where
R: Rng + CryptoRng,
{
pub fn transaction(&mut self) -> Blob {
self.transaction_with_keys().0
}

pub fn transaction_with_keys(&mut self) -> (Blob, Vec<SecretKey>) {
let len = self.rng.gen_range(1..1024 * 1024);

let mut bytecode = alloc::vec![0u8; len];
self.rng.fill_bytes(bytecode.as_mut_slice());

let mut builder = TransactionBuilder::<Blob>::blob(BlobBody {
id: BlobId::compute(&bytecode),
witness_index: 0,
});
debug_assert_eq!(builder.witnesses().len(), 0);
builder.add_witness(bytecode.into());

let keys = self.fill_transaction(&mut builder);
(builder.finalize(), keys)
}
}

impl<R> TransactionFactory<R, Mint>
where
R: Rng + CryptoRng,
Expand Down Expand Up @@ -506,6 +539,17 @@ mod use_std {
}
}

impl<R> Iterator for TransactionFactory<R, Blob>
where
R: Rng + CryptoRng,
{
type Item = (Blob, Vec<SecretKey>);

fn next(&mut self) -> Option<(Blob, Vec<SecretKey>)> {
Some(self.transaction_with_keys())
}
}

impl<R> Iterator for TransactionFactory<R, Mint>
where
R: Rng + CryptoRng,
Expand Down
56 changes: 54 additions & 2 deletions fuel-tx/src/tests/offset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

use crate::{
field::{
BlobId as BlobIdField,
InputContract,
Inputs,
MintAmount,
Expand Down Expand Up @@ -68,6 +69,14 @@ struct TestedFields {
}

fn chargeable_transaction_parts<Tx>(tx: &Tx, bytes: &[u8], cases: &mut TestedFields)
where
Tx: Buildable,
{
inputs_assert(tx, bytes, cases);
outputs_assert(tx, bytes, cases);
}

fn inputs_assert<Tx: Inputs>(tx: &Tx, bytes: &[u8], cases: &mut TestedFields)
where
Tx: Buildable,
{
Expand Down Expand Up @@ -247,8 +256,6 @@ where
}
}
});

outputs_assert(tx, bytes, cases);
}

fn outputs_assert<Tx: Outputs>(tx: &Tx, bytes: &[u8], cases: &mut TestedFields) {
Expand Down Expand Up @@ -530,6 +537,51 @@ fn tx_offset_upload() {
assert!(cases.output_contract_created_id);
}

#[test]
fn tx_offset_blob() {
let mut cases = TestedFields::default();
let number_cases = 100;

// The seed will define how the transaction factory will generate a new transaction.
// Different seeds might implicate on how many of the cases we cover - since we
// assert coverage for all scenarios with the boolean variables above, we need to
// pick a seed that, with low number of cases, will cover everything.
TransactionFactory::<_, Blob>::from_seed(1295)
.take(number_cases)
.for_each(|(tx, _)| {
let bytes = tx.to_bytes();

// Blob id
let offs = tx.blob_id_offset();
assert_eq!(bytes[offs..offs + BlobId::LEN], **tx.blob_id());

chargeable_transaction_parts(&tx, &bytes, &mut cases);
});

// Chargeable parts
assert!(cases.utxo_id);
assert!(cases.owner);
assert!(cases.asset_id);
assert!(cases.predicate_coin);
assert!(cases.predicate_message);
assert!(cases.predicate_data_coin);
assert!(cases.predicate_data_message);
assert!(cases.contract_balance_root);
assert!(cases.contract_state_root);
assert!(cases.contract_id);
assert!(cases.sender);
assert!(cases.recipient);
assert!(cases.message_data);
assert!(cases.message_predicate);
assert!(cases.message_predicate_data);
assert!(cases.output_to);
assert!(cases.output_asset_id);
assert!(cases.output_balance_root);
assert!(cases.output_contract_state_root);
assert!(cases.output_contract_created_state_root);
assert!(cases.output_contract_created_id);
}

#[test]
fn tx_offset_mint() {
let number_cases = 100;
Expand Down
1 change: 1 addition & 0 deletions fuel-tx/src/tests/valid_cases/transaction.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#![allow(clippy::cast_possible_truncation)]
#![allow(non_snake_case)]

mod blob;
mod upgrade;
mod upload;

Expand Down
Loading

0 comments on commit d2c76e2

Please sign in to comment.