Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

A new Upload transaction to upload the huge bytecode on the chain #714

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).

#### Breaking

- [#714](https://github.com/FuelLabs/fuel-vm/pull/714): The change adds a new `Upload` transaction that allows uploading huge byte code on chain subsection by subsection. This transaction is chargeable and is twice as expensive as the `Create` transaction. Anyone can submit this transaction.
- [#712](https://github.com/FuelLabs/fuel-vm/pull/712): The `Interpreter` supports the processing of the `Upgrade` transaction. The change affects `InterpreterStorage`, adding 5 new methods that must be implemented.
- [#707](https://github.com/FuelLabs/fuel-vm/pull/707): The change adds a new `Upgrade` transaction that allows upgrading either consensus parameters or state transition function used by the network to produce future blocks.
The purpose of the upgrade is defined by the `Upgrade Purpose` type:
Expand Down Expand Up @@ -64,6 +65,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).

#### Breaking

- [#714](https://github.com/FuelLabs/fuel-vm/pull/714): Added `max_bytecode_subsections` field to the `TxParameters` to limit the number of subsections that can be uploaded.
- [#707](https://github.com/FuelLabs/fuel-vm/pull/707): Side small breaking for tests changes from the `Upgrade` transaction:
- Moved `fuel-tx-test-helpers` logic into the `fuel_tx::test_helpers` module.
- Added a new rule for `Create` transaction: all inputs should use base asset otherwise it returns `TransactionInputContainsNonBaseAssetId` error.
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 @@ -29,6 +29,8 @@ use crate::{
TxPointer,
Upgrade,
UpgradePurpose,
Upload,
UploadBody,
Witness,
};

Expand Down Expand Up @@ -181,6 +183,20 @@ impl TransactionBuilder<Upgrade> {
}
}

impl TransactionBuilder<Upload> {
pub fn upload(body: UploadBody) -> Self {
let tx = Upload {
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
4 changes: 4 additions & 0 deletions fuel-tx/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,10 @@ pub use transaction::{
UpgradeBody,
UpgradeMetadata,
UpgradePurpose,
Upload,
UploadBody,
UploadMetadata,
UploadSubsection,
UtxoId,
ValidityError,
Witness,
Expand Down
48 changes: 48 additions & 0 deletions fuel-tx/src/test_helper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ mod use_std {
TransactionBuilder,
Upgrade,
UpgradePurpose,
Upload,
UploadBody,
UploadSubsection,
};
use core::marker::PhantomData;
use fuel_crypto::{
Expand Down Expand Up @@ -130,6 +133,7 @@ mod use_std {
Transaction::Create(_) => (),
Transaction::Mint(_) => (),
Transaction::Upgrade(_) => (),
Transaction::Upload(_) => (),
})
.unwrap_or(());

Expand Down Expand Up @@ -406,6 +410,39 @@ mod use_std {
}
}

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

pub fn transaction_with_keys(&mut self) -> (Upload, 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 subsection = UploadSubsection::split_bytecode(&bytecode, len / 10)
.expect("Should split the bytecode")[0]
.clone();

let mut builder = TransactionBuilder::<Upload>::upload(UploadBody {
root: subsection.root,
witness_index: 0,
subsection_index: subsection.subsection_index,
subsections_number: subsection.subsections_number,
proof_set: subsection.proof_set,
});
debug_assert_eq!(builder.witnesses().len(), 0);
builder.add_witness(subsection.subsection.into());

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

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

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

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

impl<R> Iterator for TransactionFactory<R, Mint>
where
R: Rng + CryptoRng,
Expand Down
166 changes: 164 additions & 2 deletions fuel-tx/src/tests/bytes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -453,7 +453,7 @@ fn transaction_serde_serialization_deserialization() {
.with_maturity((u32::MAX >> 3).into())
.with_witness_limit(Word::MAX >> 4)
.with_max_fee(Word::MAX >> 5),
vec![i],
vec![i.clone()],
vec![o],
vec![w.clone()],
),
Expand Down Expand Up @@ -481,7 +481,7 @@ fn transaction_serde_serialization_deserialization() {
.with_max_fee(Word::MAX >> 5),
vec![],
vec![],
vec![w],
vec![w.clone()],
),
Transaction::upgrade(
UpgradePurpose::StateTransition {
Expand All @@ -497,6 +497,76 @@ fn transaction_serde_serialization_deserialization() {
vec![],
),
]);
assert_encoding_correct(&[
Transaction::upload(
UploadBody {
root: [6; 32].into(),
witness_index: 0,
subsection_index: 0x1234,
subsections_number: 0x4321,
proof_set: vec![[1; 32].into(), [2; 32].into(), [3; 32].into()],
},
Policies::new()
.with_tip(Word::MAX >> 1)
.with_maturity((u32::MAX >> 3).into())
.with_witness_limit(Word::MAX >> 4)
.with_max_fee(Word::MAX >> 5),
vec![i.clone()],
vec![o],
vec![w.clone()],
),
Transaction::upload(
UploadBody {
root: [6; 32].into(),
witness_index: 0,
subsection_index: 0x1234,
subsections_number: 0x4321,
proof_set: vec![[1; 32].into(), [2; 32].into(), [3; 32].into()],
},
Policies::new()
.with_tip(Word::MAX >> 1)
.with_maturity((u32::MAX >> 3).into())
.with_witness_limit(Word::MAX >> 4)
.with_max_fee(Word::MAX >> 5),
vec![],
vec![o],
vec![w.clone()],
),
Transaction::upload(
UploadBody {
root: [6; 32].into(),
witness_index: 0,
subsection_index: 0x1234,
subsections_number: 0x4321,
proof_set: vec![[1; 32].into(), [2; 32].into(), [3; 32].into()],
},
Policies::new()
.with_tip(Word::MAX >> 1)
.with_maturity((u32::MAX >> 3).into())
.with_witness_limit(Word::MAX >> 4)
.with_max_fee(Word::MAX >> 5),
vec![],
vec![],
vec![w.clone()],
),
Transaction::upload(
UploadBody {
root: [6; 32].into(),
witness_index: 0,
subsection_index: 0x1234,
subsections_number: 0x4321,
proof_set: vec![[1; 32].into(), [2; 32].into(), [3; 32].into()],
},
Policies::new()
.with_tip(Word::MAX >> 1)
.with_maturity((u32::MAX >> 3).into())
.with_witness_limit(Word::MAX >> 4)
.with_max_fee(Word::MAX >> 5),
vec![],
vec![],
vec![],
),
]);
assert_encoding_correct(&[Transaction::mint(
rng.gen(),
rng.gen(),
Expand Down Expand Up @@ -837,3 +907,95 @@ fn upgrade_input_coin_data_offset() {
}
}
}

#[test]
fn upload_input_coin_data_offset() {
let rng = &mut StdRng::seed_from_u64(8586);

let maturity = 10.into();

let inputs: Vec<Vec<Input>> = vec![
vec![],
vec![Input::contract(
rng.gen(),
rng.gen(),
rng.gen(),
rng.gen(),
rng.gen(),
)],
vec![
Input::contract(rng.gen(), rng.gen(), rng.gen(), rng.gen(), rng.gen()),
Input::contract(rng.gen(), rng.gen(), rng.gen(), rng.gen(), rng.gen()),
],
];
let outputs: Vec<Vec<Output>> = vec![
vec![],
vec![Output::coin(rng.gen(), rng.next_u64(), rng.gen())],
vec![Output::contract(rng.gen(), rng.gen(), rng.gen())],
];
let witnesses: Vec<Vec<Witness>> = vec![
vec![],
vec![generate_bytes(rng).into()],
vec![generate_bytes(rng).into(), generate_bytes(rng).into()],
];

let mut predicate = generate_nonempty_padded_bytes(rng);

// force word-unaligned predicate
if predicate.len() % 2 == 0 {
predicate.push(0xff);
}

let predicate_data = generate_bytes(rng);
let predicate_gas_used = rng.gen();

let owner = (*Contract::root_from_code(&predicate)).into();

let input_coin = Input::coin_predicate(
rng.gen(),
owner,
rng.next_u64(),
rng.gen(),
rng.gen(),
predicate_gas_used,
predicate.clone(),
predicate_data,
);

for inputs in inputs.iter() {
for outputs in outputs.iter() {
for witnesses in witnesses.iter() {
let mut inputs = inputs.clone();
let offset = inputs.len();
inputs.push(input_coin.clone());

let subsections = UploadSubsection::split_bytecode(&[123; 2048], 1023)
.expect("Failed to split bytecode");
let tx = Transaction::upload_from_subsection(
subsections[0].clone(),
Policies::new().with_maturity(maturity),
inputs,
outputs.clone(),
witnesses.clone(),
);

let mut tx_p = tx.clone();
tx_p.precompute(&Default::default())
.expect("Should be able to calculate cache");

let bytes = tx.to_bytes();
let (offset, len) = tx
.inputs_predicate_offset_at(offset)
.expect("Failed to fetch offset");

assert_ne!(bytes::padded_len(&predicate), predicate.len());
assert_eq!(bytes::padded_len(&predicate), len);

assert_eq!(
predicate.as_slice(),
&bytes[offset..offset + predicate.len()]
);
}
}
}
}
9 changes: 9 additions & 0 deletions fuel-tx/src/tests/display.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,15 @@ fn to_from_str() {

assert_eq!(tx, tx_p);
});
TransactionFactory::<_, Upload>::from_seed(1295)
.take(20)
.for_each(|(tx, _)| {
let tx: Transaction = tx.into();
let tx_p = tx.to_json();
let tx_p = Transaction::from_json(tx_p).expect("failed to restore tx");

assert_eq!(tx, tx_p);
});
TransactionFactory::<_, Mint>::from_seed(1295)
.take(20)
.for_each(|tx| {
Expand Down
42 changes: 41 additions & 1 deletion fuel-tx/src/tests/offset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -455,7 +455,7 @@ fn tx_offset_upgrade() {
chargeable_transaction_parts(&tx, &bytes, &mut cases);
cases.upgrade_purpose = true;

let ofs = Upgrade::upgrade_purpose_offset_static();
let ofs = tx.upgrade_purpose_offset();
let size = tx.upgrade_purpose().size();

let purpose_p = UpgradePurpose::from_bytes(&bytes[ofs..ofs + size]).unwrap();
Expand Down Expand Up @@ -490,6 +490,46 @@ fn tx_offset_upgrade() {
assert!(cases.output_contract_created_id);
}

#[test]
fn tx_offset_upload() {
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::<_, Upload>::from_seed(1295)
.take(number_cases)
.for_each(|(tx, _)| {
let bytes = tx.to_bytes();
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
Loading
Loading