Skip to content

Commit

Permalink
A new Upgrade transaction to perform network upgrades (#707)
Browse files Browse the repository at this point in the history
* Versioned `GasCosts`

* Versioned `ConsensusParameters`ю
Reduced default `MAX_SIZE` to be 110kb.
Reduced default `MAX_CONTRACT_SIZE` to be 100kb.

* Updated CHANGELOG.md

* Versioned `FeeParameters`

* Versioned `PredicateParameters`, `ScriptParameters`, `ContractParameters`,

* Versioned `TxParameters`

* Updated CHANGELOG.md

* Make CI happy

* Reshuffled fields `Script` and `Create` transactions to unify part used by all chargeable transactions

* Updated CHANGELOG.md

* Added privileged address to the `ConsensusParameters`

* Updated CHANGELOG.md

* Unified `Create` and `Script` logic via `ChargeableTransaction`

* Updated CHANGELOG.md

* A new `Upgrade` transaction to perform network upgrades

* Update fuel-tx/src/transaction/id.rs

Co-authored-by: Hannes Karppila <[email protected]>

* Added tests required for any transaction

* Updated CHANGELOG.md

* Updated CHANGELOG.md

* Updated CHANGELOG.md

* Added a comment

* Use right discriminant for the `Upgrade` transaction

* Applied comments from PR

* Removed iow

---------

Co-authored-by: Hannes Karppila <[email protected]>
  • Loading branch information
xgreenx and Dentosal authored Apr 9, 2024
1 parent 4725d57 commit 2ee9c12
Show file tree
Hide file tree
Showing 32 changed files with 2,184 additions and 250 deletions.
62 changes: 62 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,72 @@ and this project adheres to [Semantic Versioning](http://semver.org/).

### Added

#### Breaking

- [#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:

```rust
pub enum UpgradePurpose {
/// The upgrade is performed to change the consensus parameters.
ConsensusParameters {
/// The index of the witness in the [`Witnesses`] field that contains
/// the serialized consensus parameters.
witness_index: u16,
/// The hash of the serialized consensus parameters.
/// Since the serialized consensus parameters live inside witnesses(malleable
/// data), any party can override them. The `checksum` is used to verify that the
/// data was not modified.
checksum: Bytes32,
},
/// The upgrade is performed to change the state transition function.
StateTransition {
/// The hash of the new bytecode of the state transition function.
/// The bytecode must be present on the blockchain(should be known by the
/// network) at the moment of inclusion of this transaction.
bytecode_hash: Bytes32,
},
}
```

The `Upgrade` transaction is chargeable, and the sender should pay for it. Transaction inputs should contain only base assets.

Only the privileged address can upgrade the network. The privileged address can be either a real account or a predicate.

Since serialized consensus parameters are small(< 2kb), they can be part of the upgrade transaction and live inside of witness data. The bytecode of the blockchain state transition function is huge ~1.6MB(relative to consensus parameters), and it is impossible to fit it into one transaction. So when we perform the upgrade of the state transition function, it should already be available on the blockchain. The transaction to actually upload the bytecode(`Upload` transaction) will implemented in the https://github.com/FuelLabs/fuel-core/issues/1754.

### Changed

- [#707](https://github.com/FuelLabs/fuel-vm/pull/707): Used the same pattern everywhere in the codebase:
```rust
Self::Script(tx) => tx.encode_static(buffer),
Self::Create(tx) => tx.encode_static(buffer),
Self::Mint(tx) => tx.encode_static(buffer),
Self::Upgrade(tx) => tx.encode_static(buffer),
```

Instead of:
```rust
Transaction::Script(script) => script.encode_static(buffer),
Transaction::Create(create) => create.encode_static(buffer),
Transaction::Mint(mint) => mint.encode_static(buffer),
Transaction::Upgrade(upgrade) => upgrade.encode_static(buffer),
```

#### Breaking

- [#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.
- Renamed some errors because now they are used for several transactions(`Upgrade` uses some errors from `Create` and some from `Script` transactions):
- `TransactionScriptOutputContractCreated` -> `TransactionOutputContainsContractCreated`.
- `TransactionCreateOutputContract` -> `TransactionOutputContainsContract`.
- `TransactionCreateOutputVariable` -> `TransactionOutputContainsVariable`.
- `TransactionCreateOutputChangeNotBaseAsset` -> `TransactionChangeChangeUsesNotBaseAsset`.
- `TransactionCreateInputContract` -> `TransactionInputContainsContract`.
- `TransactionCreateMessageData` -> `TransactionInputContainsMessageData`.
- The combination of `serde` and `postcard` is used to serialize and deserialize `ConsensusParameters` during the upgrade. This means the protocol and state transition function requires the `serde` feature by default for `ConsensusParameters` and `fuel-types`.

- [#697](https://github.com/FuelLabs/fuel-vm/pull/697): Changed the VM to internally use separate buffers for the stack and the heap to improve startup time. After this change, memory that was never part of the stack or the heap cannot be accessed, even for reading. Also, even if the whole memory is allocated, accesses spanning from the stack to the heap are not allowed. This PR also fixes a bug that required one-byte gap between the stack and the heap. Multiple errors have been changed to be more sensible ones, and sometimes the order of which error is returned has changed. `ALOC` opcode now zeroes the newly allocated memory.

## [Version 0.48.0]
Expand Down
1 change: 0 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ members = [
"fuel-merkle",
"fuel-storage",
"fuel-tx",
"fuel-tx/test-helpers",
"fuel-types",
"fuel-vm",
]
Expand Down
10 changes: 5 additions & 5 deletions fuel-tx/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,13 @@ derive_more = { version = "0.99", default-features = false, features = ["display
fuel-asm = { workspace = true, default-features = false }
fuel-crypto = { workspace = true, default-features = false }
fuel-merkle = { workspace = true, default-features = false, optional = true }
fuel-types = { workspace = true, default-features = false }
fuel-types = { workspace = true, default-features = false, features = ["serde"] }
hashbrown = { version = "0.14", optional = true }
itertools = { version = "0.10", default-features = false, optional = true }
js-sys = { version = "0.3", optional = true }
postcard = { version = "1.0", features = ["alloc"] }
rand = { version = "0.8", default-features = false, features = ["std_rng"], optional = true }
serde = { version = "1.0", default-features = false, features = ["alloc", "derive"], optional = true }
serde = { version = "1.0", default-features = false, features = ["alloc", "derive"] }
serde-wasm-bindgen = { version = "0.6", optional = true }
serde_json = { version = "1.0", default-features = false, features = ["alloc"], optional = true }
strum = { version = "0.24", default-features = false, optional = true }
Expand All @@ -33,7 +34,6 @@ wasm-bindgen = { version = "0.2.88", optional = true }
bincode = { workspace = true }
fuel-crypto = { workspace = true, default-features = false, features = ["random"] }
fuel-tx = { path = ".", features = ["random", "serde", "test-helpers"] }
fuel-tx-test-helpers = { path = "test-helpers" }
fuel-types = { workspace = true, default-features = false, features = ["random"] }
hex = { version = "0.4", default-features = false }
insta = "1.0"
Expand All @@ -49,7 +49,7 @@ test-helpers = ["alloc", "internals"]
internals = []
typescript = ["alloc", "js-sys", "wasm-bindgen", "serde", "serde-wasm-bindgen", "fuel-types/typescript"]
random = ["fuel-crypto/random", "fuel-types/random", "rand"]
std = ["alloc", "fuel-asm/std", "fuel-crypto/std", "fuel-merkle/std", "fuel-types/std", "itertools/default", "rand?/default", "serde?/default", "hex/std"]
std = ["alloc", "fuel-asm/std", "fuel-crypto/std", "fuel-merkle/std", "fuel-types/std", "itertools/default", "rand?/default", "serde/default", "hex/std"]
alloc = ["hashbrown", "fuel-types/alloc", "itertools/use_alloc", "derivative", "fuel-merkle", "strum", "strum_macros"]
# serde is requiring alloc because its mandatory for serde_json. to avoid adding a new feature only for serde_json, we just require `alloc` here since as of the moment we don't have a use case of serde without alloc.
serde = ["alloc", "dep:serde", "fuel-asm/serde", "fuel-crypto/serde", "fuel-types/serde", "fuel-merkle/serde", "serde_json", "hashbrown/serde", "bitflags/serde"]
serde = ["alloc", "fuel-asm/serde", "fuel-crypto/serde", "fuel-merkle/serde", "serde_json", "hashbrown/serde", "bitflags/serde"]
40 changes: 25 additions & 15 deletions fuel-tx/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ use crate::{
Transaction,
TxParameters,
TxPointer,
Upgrade,
UpgradePurpose,
Witness,
};

Expand All @@ -44,6 +46,7 @@ use crate::{
transaction::{
CreateBody,
ScriptBody,
UpgradeBody,
},
};
use alloc::{
Expand Down Expand Up @@ -164,6 +167,20 @@ impl TransactionBuilder<Create> {
}
}

impl TransactionBuilder<Upgrade> {
pub fn upgrade(purpose: UpgradePurpose) -> Self {
let tx = Upgrade {
body: UpgradeBody { purpose },
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 Expand Up @@ -355,8 +372,8 @@ impl<Tx: Buildable> TransactionBuilder<Tx> {
self.add_unsigned_coin_input(
SecretKey::random(&mut rng),
rng.gen(),
rng.gen(),
rng.gen(),
u32::MAX as u64,
*self.params.base_asset_id(),
Default::default(),
)
}
Expand Down Expand Up @@ -477,22 +494,15 @@ impl Finalizable<Mint> for TransactionBuilder<Mint> {
}
}

impl Finalizable<Create> for TransactionBuilder<Create> {
fn finalize(&self) -> Create {
self.finalize_inner()
}

fn finalize_without_signature(&self) -> Create {
self.finalize_without_signature_inner()
}
}

impl Finalizable<Script> for TransactionBuilder<Script> {
fn finalize(&self) -> Script {
impl<Tx> Finalizable<Tx> for TransactionBuilder<Tx>
where
Tx: Buildable,
{
fn finalize(&self) -> Tx {
self.finalize_inner()
}

fn finalize_without_signature(&self) -> Script {
fn finalize_without_signature(&self) -> Tx {
self.finalize_without_signature_inner()
}
}
Expand Down
4 changes: 3 additions & 1 deletion fuel-tx/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,9 @@ impl TryFrom<&Transaction> for Contract {
fn try_from(tx: &Transaction) -> Result<Self, Self::Error> {
match tx {
Transaction::Create(create) => TryFrom::try_from(create),
_ => Err(ValidityError::TransactionScriptOutputContractCreated { index: 0 }),
_ => {
Err(ValidityError::TransactionOutputContainsContractCreated { index: 0 })
}
}
}
}
Expand Down
6 changes: 6 additions & 0 deletions fuel-tx/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ mod transaction;
#[cfg(test)]
mod tests;

#[cfg(feature = "test-helpers")]
pub mod test_helper;

#[cfg(feature = "test-helpers")]
pub use builder::{
Buildable,
Expand Down Expand Up @@ -97,6 +100,9 @@ pub use transaction::{
TransactionRepr,
TxId,
TxParameters,
Upgrade,
UpgradeBody,
UpgradePurpose,
UtxoId,
ValidityError,
Witness,
Expand Down
70 changes: 61 additions & 9 deletions fuel-tx/test-helpers/src/lib.rs → fuel-tx/src/test_helper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,14 @@ where

#[cfg(feature = "std")]
mod use_std {
use core::marker::PhantomData;
use fuel_crypto::SecretKey;
use fuel_tx::{
use super::{
generate_bytes,
generate_nonempty_padded_bytes,
};
use crate::{
field,
Buildable,
ConsensusParameters,
Contract,
Create,
Finalizable,
Expand All @@ -54,6 +57,13 @@ mod use_std {
Script,
Transaction,
TransactionBuilder,
Upgrade,
UpgradePurpose,
};
use core::marker::PhantomData;
use fuel_crypto::{
Hasher,
SecretKey,
};
use fuel_types::canonical::Deserialize;
use rand::{
Expand All @@ -66,11 +76,7 @@ mod use_std {
Rng,
SeedableRng,
};

use crate::{
generate_bytes,
generate_nonempty_padded_bytes,
};
use strum::EnumCount;

pub struct TransactionFactory<R, Tx>
where
Expand All @@ -87,7 +93,6 @@ mod use_std {
R: Rng + CryptoRng,
{
fn from(rng: R) -> Self {
use strum::EnumCount;
let input_sampler = Uniform::from(0..Input::COUNT);
let output_sampler = Uniform::from(0..Output::COUNT);

Expand Down Expand Up @@ -124,6 +129,7 @@ mod use_std {
Transaction::Script(_) => (),
Transaction::Create(_) => (),
Transaction::Mint(_) => (),
Transaction::Upgrade(_) => (),
})
.unwrap_or(());

Expand Down Expand Up @@ -365,6 +371,41 @@ mod use_std {
}
}

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

pub fn transaction_with_keys(&mut self) -> (Upgrade, Vec<SecretKey>) {
let variant = self.rng.gen_range(0..UpgradePurpose::COUNT);
let consensus_params =
postcard::to_allocvec(&ConsensusParameters::default()).unwrap();
let checksum = Hasher::hash(consensus_params.as_slice());

let purpose = match variant {
0 => UpgradePurpose::StateTransition {
bytecode_hash: self.rng.gen(),
},
1 => UpgradePurpose::ConsensusParameters {
witness_index: 0,
checksum,
},
_ => {
panic!("Not supported")
}
};

let mut builder = TransactionBuilder::<Upgrade>::upgrade(purpose);
builder.add_witness(consensus_params.into());

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

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

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

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

impl<R> Iterator for TransactionFactory<R, Mint>
where
R: Rng + CryptoRng,
Expand Down
Loading

0 comments on commit 2ee9c12

Please sign in to comment.