From aa06d95d60705c5f56002d1c26e8f05c40553c83 Mon Sep 17 00:00:00 2001 From: Green Baneling Date: Mon, 15 Apr 2024 17:22:53 +0200 Subject: [PATCH] A new `Upload` transaction to upload the huge bytecode on the chain (#570) Corresponding implementation: https://github.com/FuelLabs/fuel-vm/pull/720 The change adds a new `Upload` transaction that allows uploading huge byte code on chain subsection by subsection. The `Upload` transaction is chargeable and is twice as expensive as the `Create` transaction. Anyone can submit this transaction. The specification contains more description about the flow. --- src/protocol/tx-validity.md | 16 ++++++++- src/tx-format/consensus_parameters.md | 3 +- src/tx-format/transaction.md | 49 ++++++++++++++++++++++++--- src/tx-format/upgrade_purpose.md | 2 +- 4 files changed, 62 insertions(+), 8 deletions(-) diff --git a/src/protocol/tx-validity.md b/src/protocol/tx-validity.md index 2eeefefc..022e4c38 100644 --- a/src/protocol/tx-validity.md +++ b/src/protocol/tx-validity.md @@ -162,6 +162,11 @@ def input_gas_fees(tx) -> int: def metadata_gas_fees(tx) -> int: """ Computes the intrinsic gas cost of processing transaction outputs + + The `contract_code_root_gas_fee`, `sha256_gas_fee`, and `contract_state_root_gas_fee` + are based on the benchmarked gas costs of these operations. + + Consensus parameters contain definitions of gas costs for all operations and opcodes in the network. """ total: int = 0 if tx.type == TransactionType.Create: @@ -178,6 +183,11 @@ def metadata_gas_fees(tx) -> int: if tx.upgradePurpose.type == UpgradePurposeType.ConsensusParameters: # add intrinsic cost of calculating the consensus parameters hash total += sha256_gas_fee(size(tx.witnesses[tx.upgradePurpose.witnessIndex].data)) + elif tx.type == TransactionType.Upload: + # add intrinsic cost of calculating the root based on the number of bytecode subsections + total += contract_state_root_gas_fee(tx.subsectionsNumber) + # add intrinsic cost of hashing the subsection for verification of the connection with Binary Merkle tree root + total += sha256_gas_fee(size(tx.witnesses[tx.witnessIndex])) if tx.type != TransactionType.Mint: # add intrinsic cost of calculating the transaction id @@ -203,6 +213,10 @@ def min_gas(tx) -> int: Comutes the minimum amount of gas required for a transaction to begin processing. """ gas = transaction_size_gas_fees(tx) + intrinsic_gas_fees(tx) + if tx.type == TransactionType.Upload + # charge additionally for storing bytecode on chain + gas += transaction_size_gas_fees(size(tx.witnesses[tx.witnessIndex])) + return gas @@ -213,7 +227,7 @@ def max_gas(tx) -> int: gas = min_gas(tx) gas = gas + (tx.witnessBytesLimit - tx.witnessBytes) * GAS_PER_BYTE if tx.type == TransactionType.Script: - gas = gas + tx.gasLimit + gas += tx.gasLimit return gas diff --git a/src/tx-format/consensus_parameters.md b/src/tx-format/consensus_parameters.md index 9ad11f59..90ba76b9 100644 --- a/src/tx-format/consensus_parameters.md +++ b/src/tx-format/consensus_parameters.md @@ -16,6 +16,7 @@ | `MAX_STORAGE_SLOTS` | `uint64` | Maximum number of initial storage slots. | | `MAX_TRANSACTION_SIZE` | `uint64` | Maximum size of a transaction, in bytes. | | `MAX_WITNESSES` | `uint64` | Maximum number of witnesses. | +| `MAX_BYTECODE_SUBSECTIONS` | `uint64` | Maximum number of bytecode subsections. | | `CHAIN_ID` | `uint64` | A unique per-chain identifier. | | `BASE_ASSET_ID` | `bytes32` | The base asset of the chain. | -| `PRIVELEGED_ADDRESS` | `bytes32` | The privileged address of the network who can perform upgrade. | +| `PRIVILEGED_ADDRESS` | `bytes32` | The privileged address of the network who can perform upgrade. | diff --git a/src/tx-format/transaction.md b/src/tx-format/transaction.md index bd8f9d75..06bfc67a 100644 --- a/src/tx-format/transaction.md +++ b/src/tx-format/transaction.md @@ -6,13 +6,14 @@ enum TransactionType : uint8 { Create = 1, Mint = 2, Upgrade = 3, + Upload = 4, } ``` -| name | type | description | -|--------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------| -| `type` | [`TransactionType`](#transaction) | Transaction type. | -| `data` | One of [`TransactionScript`](#transactionscript), [`TransactionCreate`](#transactioncreate), [`TransactionMint`](#transactionmint), or [`TransactionUpgrade`](#transactionupgrade) | Transaction data. | +| name | type | description | +|--------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------| +| `type` | [`TransactionType`](#transaction) | Transaction type. | +| `data` | One of [`TransactionScript`](#transactionscript), [`TransactionCreate`](#transactioncreate), [`TransactionMint`](#transactionmint), [`TransactionUpgrade`](#transactionupgrade), or [`TransactionUpload`](#transactionupload) | Transaction data. | Given helper `max_gas()` returns the maximum gas that the transaction can use. Given helper `count_ones()` that returns the number of ones in the binary representation of a field. @@ -163,7 +164,7 @@ Only the privileged address from [`ConsensusParameters`](./consensus_parameters. When the upgrade type is `UpgradePurposeType.ConsensusParameters` serialized consensus parameters are available in the witnesses and the `Upgrade` transaction is self-contained because it has all the required information. -When the upgrade type is `UpgradePurposeType.StateTransition`, the `bytecodeHash` field contains the hash of the new bytecode of the state transition function. The bytecode should already be available on the blockchain at the upgrade point; otherwise, the upgrade will fail. The bytecode can be part of the genesis block or can be uploaded via the `TransactionUpload` transaction. +When the upgrade type is `UpgradePurposeType.StateTransition`, the `bytecodeRoot` field contains the Merkle root of the new bytecode of the state transition function. The bytecode should already be available on the blockchain at the upgrade point; otherwise, the upgrade will fail. The bytecode can be part of the genesis block or can be uploaded via the `TransactionUpload` transaction. The block header contains information about which versions of consensus parameters and state transition function are used to produce a block, and the `Upgrade` transaction defines behavior corresponding to the version. When the block executes the `Upgrade` transaction, it defines new behavior for either `BlockHeader.consensusParametersVersion + 1` or `BlockHeader.stateTransitionBytecodeVersion + 1`(it depends on the purpose of the upgrade). @@ -193,3 +194,41 @@ Transaction is invalid if: - Any output is of type `OutputType.Change` with non-base `asset_id` - No input where `InputType.Message.owner == PRIVILEGED_ADDRESS` or `InputType.Coint.owner == PRIVILEGED_ADDRESS` - The `UpgradePurpose` is invalid + +## `TransactionUpload` + +The `Upload` transaction allows the huge bytecode to be divided into subsections and uploaded slowly to the chain. The [Binary Merkle root](../protocol/cryptographic-primitives.md#binary-merkle-tree) built on top of subsections is an identifier of the bytecode. + +Each transaction uploads a subsection of the code and must contain proof of connection to the root. All subsections should be uploaded sequentially, which allows the concatenation of previously uploaded subsections with new subsection. The bytecode is considered final when the last subsection is uploaded, and future `Upload` transactions with the same `root` fields should be rejected. + +When the bytecode is completed it can be used to upgrade the network. + +The size of each subsection can be arbitrary; the only limit is the maximum number of subsections allowed by the network. The combination of the transaction gas limit and the number of subsections limits the final maximum size of the bytecode. + +| name | type | description | +|---------------------|-----------------------------|-----------------------------------------------------------------------------------------| +| `root` | `byte[32]` | The root of the Merkle tree is created over the bytecode. | +| `witnessIndex` | `uint16` | The witness index of the subsection of the bytecode. | +| `subsectionIndex` | `uint16` | The index of the subsection of the bytecode. | +| `subsectionsNumber` | `uint16` | The total number of subsections on which bytecode was divided. | +| `proofSetCount` | `uint16` | Number of Merkle nodes in the proof. | +| `policyTypes` | `uint32` | Bitfield of used policy types. | +| `inputsCount` | `uint16` | Number of inputs. | +| `outputsCount` | `uint16` | Number of outputs. | +| `witnessesCount` | `uint16` | Number of witnesses. | +| `proofSet` | `byte[32][]` | The proof set of Merkle nodes to verify the connection of the subsection to the `root`. | +| `policies` | [Policy](./policy.md)`[]` | List of policies. | +| `inputs` | [Input](./input.md)`[]` | List of inputs. | +| `outputs` | [Output](./output.md)`[]` | List of outputs. | +| `witnesses` | [Witness](./witness.md)`[]` | List of witnesses. | + +Transaction is invalid if: + +- Any input is of type `InputType.Contract` or `InputType.Message` where `input.dataLength > 0` +- Any input uses non-base asset. +- Any output is of type `OutputType.Contract` or `OutputType.Variable` or `OutputType.Message` or `OutputType.ContractCreated` +- Any output is of type `OutputType.Change` with non-base `asset_id` +- `witnessIndex >= tx.witnessesCount` +- `subsectionIndex` >= `subsectionsNumber` +- `subsectionsNumber > MAX_BYTECODE_SUBSECTIONS` +- The [Binary Merkle tree](../protocol/cryptographic-primitives.md#binary-merkle-tree) root calculated from `(witnesses[witnessIndex], subsectionIndex, subsectionsNumber, proofSet)` is not equal to the `root`. Root calculation is affected by all fields, so modification of one of them invalidates the proof. diff --git a/src/tx-format/upgrade_purpose.md b/src/tx-format/upgrade_purpose.md index 687b9106..aca65714 100644 --- a/src/tx-format/upgrade_purpose.md +++ b/src/tx-format/upgrade_purpose.md @@ -35,4 +35,4 @@ Transaction is invalid if: | name | type | description | |----------------|------------|----------------------------------------------------------------| -| `bytecodeHash` | `byte[32]` | The hash of the new bytecode of the state transition function. | +| `bytecodeRoot` | `byte[32]` | The root of the new bytecode of the state transition function. |