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

Make message retryable and not spendable on gas #443

Merged
merged 17 commits into from
Feb 27, 2023
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
6 changes: 3 additions & 3 deletions src/protocol/abi/receipts.md
Original file line number Diff line number Diff line change
Expand Up @@ -260,9 +260,9 @@ _Important note:_ For the JSON representation of receipts, we represent 64-bit u
- `recipient`: Hexadecimal string representation of the 256-bit (32-byte) address of the message recipient: `MEM[$rA, 32]`.
- `amount`: Hexadecimal string representation of a 64-bit unsigned integer; value of register `$rD`.
- `nonce`: Hexadecimal string representation of the 256-bit (32-byte) message nonce as described [here](../id/utxo.md#message-nonce).
- `len`: Decimal string representation of a 16-bit unsigned integer; value of register `$rB`.
- `digest`: Hexadecimal string representation of 256-bit (32-byte), hash of `MEM[$rA + 32, $rB]`.
- `data`: Hexadecimal string representation of the value of the memory range `MEM[$rA + 32, $rB]`.
- `len`: Decimal string representation of a 16-bit unsigned integer; value of register `$rC`.
- `digest`: Hexadecimal string representation of 256-bit (32-byte), hash of `MEM[$rB, $rC]`.
- `data`: Hexadecimal string representation of the value of the memory range `MEM[$rB, $rC]`.

```json
{
Expand Down
14 changes: 7 additions & 7 deletions src/protocol/block_header.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@

The application header is a network-agnostic block header. Different [networks](../network/index.md) may wrap the application header in a consensus header, depending on their consensus protocol.

name | type | description
----------------------|------------|-----------------------------------------------------------------------------------------------------------------------------------------
`da_height` | `uint64` | Height of the data availability layer up to which (inclusive) input messages are processed.
`txCount` | `uint64` | Number of [transaction](./tx_format/transaction.md)s in this block.
`outputMessagesCount` | `uint64` | Number of [output message](./tx_format/output.md#outputmessage)s in this block.
`txRoot` | `byte[32]` | [Merkle root](./cryptographic_primitives.md#binary-merkle-tree) of [transaction](./tx_format/transaction.md)s in this block.
`outputMessagesRoot` | `byte[32]` | [Merkle root](./cryptographic_primitives.md#binary-merkle-tree) of [output message](./tx_format/output.md#outputmessage)s in this block.
name | type | description
------------------------|------------|-----------------------------------------------------------------------------------------------------------------------------------------
`da_height` | `uint64` | Height of the data availability layer up to which (inclusive) input messages are processed.
`txCount` | `uint64` | Number of [transaction](./tx_format/transaction.md)s in this block.
`message_receipt_count` | `uint64` | Number of [output message](../protocol/abi/receipts.md#messageout_receipt)s in this block.
`txRoot` | `byte[32]` | [Merkle root](./cryptographic_primitives.md#binary-merkle-tree) of [transaction](./tx_format/transaction.md)s in this block.
`message_receipt_root` | `byte[32]` | [Merkle root](./cryptographic_primitives.md#binary-merkle-tree) of [output message](../protocol/abi/receipts.md#messageout_receipt)s in this block.
2 changes: 1 addition & 1 deletion src/protocol/id/utxo.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ The ID of a message is computed as the [hash](../cryptographic_primitives.md#has

### Message Nonce

The nonce value for `InputMessage` is determined by the sending system and is published at the time the message is sent. The nonce value for `OutputMessage` is computed as the [hash](../cryptographic_primitives.md#hashing) of the [Transaction ID](./transaction.md) that the message is an output for and the index of the output as a `uint8`: `hash(byte[32] ++ uint8)`.
The nonce value for `InputMessage` is determined by the sending system and is published at the time the message is sent. The nonce value for `OutputMessage` is computed as the [hash](../cryptographic_primitives.md#hashing) of the [Transaction ID](./transaction.md) that emitted the message and the index of the message receipt `uint8`: `hash(byte[32] ++ uint8)`.

## Fee ID

Expand Down
1 change: 0 additions & 1 deletion src/protocol/tx_format/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ The Fuel Transaction Format.
- [Output](./output.md)
- [OutputCoin](./output.md#outputcoin)
- [OutputContract](./output.md#outputcontract)
- [OutputMessage](./output.md#outputmessage)
- [OutputChange](./output.md#outputchange)
- [OutputVariable](./output.md#outputvariable)
- [OutputContractCreated](./output.md#outputcontractcreated)
Expand Down
31 changes: 16 additions & 15 deletions src/protocol/tx_format/input.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,20 +78,19 @@ Transaction is invalid if:

## InputMessage

| name | type | description |
|-----------------------|------------|--------------------------------------------------------------|
| `messageID` | `byte[32]` | The messageID as described [here](../id/utxo.md#message-id). |
| `sender` | `byte[32]` | The address of the message sender. |
| `recipient` | `byte[32]` | The address or predicate root of the message recipient. |
| `amount` | `uint64` | Amount of base asset coins sent with message. |
| `nonce` | `uint64` | The message nonce. |
| `witnessIndex` | `uint8` | Index of witness that authorizes spending the coin. |
| `dataLength` | `uint16` | Length of message data, in bytes. |
| `predicateLength` | `uint16` | Length of predicate, in instructions. |
| `predicateDataLength` | `uint16` | Length of predicate input data, in bytes. |
| `data` | `byte[]` | The message data. |
| `predicate` | `byte[]` | Predicate bytecode. |
| `predicateData` | `byte[]` | Predicate input data (parameters). |
| name | type | description |
|-----------------------|----------------------------------------|--------------------------------------------------------------|
| `sender` | `byte[32]` | The address of the message sender. |
| `recipient` | `byte[32]` | The address or predicate root of the message recipient. |
| `amount` | `uint64` | Amount of base asset coins sent with message. |
| `nonce` | `uint64` | The message nonce. |
| `witnessIndex` | `uint8` | Index of witness that authorizes spending the coin. |
| `dataLength` | `uint16` | Length of message data, in bytes. |
| `predicateLength` | `uint16` | Length of predicate, in instructions. |
| `predicateDataLength` | `uint16` | Length of predicate input data, in bytes. |
| `data` | `byte[]` | The message data. |
| `predicate` | `byte[]` | Predicate bytecode. |
| `predicateData` | `byte[]` | Predicate input data (parameters). |

Given helper `len()` that returns the number of bytes of a field.

Expand All @@ -101,9 +100,11 @@ Transaction is invalid if:
- `dataLength > MAX_MESSAGE_DATA_LENGTH`
- `predicateLength > MAX_PREDICATE_LENGTH`
- `predicateDataLength > MAX_PREDICATE_DATA_LENGTH`
- If `predicateLength > 0`; the computed predicate root (see below) is not equal `owner`
- If `predicateLength > 0`; the computed predicate root (see below) is not equal `recipient`
- `dataLength != len(data)`
- `predicateLength * 4 != len(predicate)`
- `predicateDataLength != len(predicateData)`

The predicate root is computed identically to the contract root, used to compute the contract ID, [here](../id/contract.md).

> **Note:** `InputMessages` with data length greater than zero are not considered spent until they are included in a transaction of type `TransactionType.Script` with a `ScriptResult` receipt where `result` is equal to `0` indicating a successful script exit
28 changes: 7 additions & 21 deletions src/protocol/tx_format/output.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,16 @@
enum OutputType : uint8 {
Coin = 0,
Contract = 1,
Message = 2,
Change = 3,
Variable = 4,
ContractCreated = 5,
Change = 2,
Variable = 3,
ContractCreated = 4,
}
```

| name | type | description |
|--------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------|
| `type` | [OutputType](#output) | Type of output. |
| `data` | One of [OutputCoin](#outputcoin), [OutputContract](#outputcontract), [OutputMessage](#outputmessage) [OutputChange](#outputchange), [OutputVariable](#outputvariable), or [OutputContractCreated](#outputcontractcreated). | Output data. |
| name | type | description |
|--------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------|
| `type` | [OutputType](#output) | Type of output. |
| `data` | One of [OutputCoin](#outputcoin), [OutputContract](#outputcontract), [OutputChange](#outputchange), [OutputVariable](#outputvariable), or [OutputContractCreated](#outputcontractcreated). | Output data. |

Transaction is invalid if:

Expand Down Expand Up @@ -51,19 +50,6 @@ The balance root `balanceRoot` is the root of the [SMT](../cryptographic_primiti

The state root `stateRoot` is the root of the [SMT](../cryptographic_primitives.md#sparse-merkle-tree) of storage slots. Each storage slot is a `byte[32]`, keyed by a `byte[32]`.

## OutputMessage

| name | type | description |
|-------------|------------|-----------------------------------------------|
| `recipient` | `byte[32]` | The address of the message recipient. |
| `amount` | `uint64` | Amount of base asset coins sent with message. |

> **Note:** when signing a transaction `recipient` and `amount` are set to zero.
>
> **Note:** when verifying a predicate or executing a script, `recipient` and `amount` are initialized to zero.
>
> **Note:** this output type is unspendable and can be pruned from the UTXO set.

## OutputChange

| name | type | description |
Expand Down
6 changes: 3 additions & 3 deletions src/protocol/tx_format/transaction.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ Transaction is invalid if:
- `inputsCount > MAX_INPUTS`
- `outputsCount > MAX_OUTPUTS`
- `witnessesCount > MAX_WITNESSES`
- No inputs are of type `InputType.Coin` or `InputType.Message`
- No inputs are of type `InputType.Coin` or `InputType.Message` with `input.dataLength` == 0
- More than one output is of type `OutputType.Change` for any asset ID in the input set
- Any output is of type `OutputType.Change` for any asset ID not in the input set
- More than one input of type `InputType.Coin` for any [Coin ID](../id/utxo.md#coin-id) in the input set
Expand Down Expand Up @@ -110,8 +110,8 @@ The receipts root `receiptsRoot` is the root of the [binary Merkle tree](../cryp

Transaction is invalid if:

- Any input is of type `InputType.Contract`
- Any output is of type `OutputType.Contract` or `OutputType.Variable`
- Any input is of type `InputType.Contract` or `InputType.Message` where `input.dataLength > 0`
- Any output is of type `OutputType.Contract` or `OutputType.Variable` or `OutputType.Message`
- More than one output is of type `OutputType.Change` with `asset_id` of zero
- Any output is of type `OutputType.Change` with non-zero `asset_id`
- It does not have exactly one output of type `OutputType.ContractCreated`
Expand Down
54 changes: 39 additions & 15 deletions src/protocol/tx_validity.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,14 @@ Write-create access list:

Note that block proposers use the contract ID `contractID` for inputs and outputs of type [`InputType.Contract`](./tx_format/input.md#inputcontract) and [`OutputType.Contract`](./tx_format/output.md#outputcontract) rather than the pair of `txID` and `outputIndex`.

Note that [`OutputType.Message` outputs](./tx_format/output.md#outputmessage) do not have a [UTXO ID](./id/utxo.md), and are unspendable.

## VM Precondition Validity Rules

This section defines _VM precondition validity rules_ for transactions: the bare minimum required to accept an unconfirmed transaction into a mempool, and preconditions that the VM assumes to hold prior to execution. Chains of unconfirmed transactions are omitted.

For a transaction `tx`, UTXO set `state`, contract set `contracts`, and message set `messages`, the following checks must pass.

> **Note:** [InputMessages](./tx_format/input.md#inputmessage) where `input.dataLength > 0` are not dropped from the `messages` message set until they are included in a transaction of type `TransactionType.Script` with a `ScriptResult` receipt where `result` is equal to `0` indicating a successful script exit.

### Base Sanity Checks

Base sanity checks are defined in the [transaction format](./tx_format/index.md).
Expand All @@ -68,7 +68,7 @@ for input in tx.inputs:
if not input.contractID in contracts:
return False
elif input.type == InputType.Message:
if not input.messageID in messages:
if not input.nonce in messages:
return False
else:
if not (input.txID, input.outputIndex) in state:
Expand All @@ -83,41 +83,60 @@ If this check passes, the UTXO ID `(txID, outputIndex)` fields of each contract
For each asset ID `asset_id` in the input and output set:

```py
def sum_data_messages(tx, asset_id) -> int:
"""
Returns the total balance available from messages containing data
"""
total: int = 0
if asset_id == 0:
for input in tx.inputs:
if input.type == InputType.Message and input.dataLength > 0:
total += input.amount
return total

def sum_inputs(tx, asset_id) -> int:
total: int = 0
for input in tx.inputs:
if (input.type == InputType.Coin and input.asset_id == asset_id) or (input.type == InputType.Message and asset_id == 0):
if input.type == InputType.Coin and input.asset_id == asset_id:
total += input.amount
elif input.type == InputType.Message and asset_id == 0 and input.dataLength == 0:
total += input.amount
return total

def sum_outputs(tx, asset_id) -> int:
total: int = 0
for output in tx.outputs:
if (output.type == OutputType.Coin and output.asset_id == asset_id) or (output.type == OutputType.Message and asset_id == 0):
if output.type == OutputType.Coin and output.asset_id == asset_id:
total += output.amount
return total

def available_balance(tx, asset_id) -> int:
availableBalance = sum_inputs(tx, asset_id)
"""
Make the data message balance available to the script
"""
availableBalance = sum_inputs(tx, asset_id) + sum_data_messages(tx, asset_id)
return availableBalance

def unavailable_balance(tx, asset_id) -> int:
"""
Note: we don't charge for predicate verification because predicates are
monotonic and the cost of bytes should approximately makes up for this.
"""
sentBalance = sum_outputs(tx, col)
sentBalance = sum_outputs(tx, asset_id)
gasBalance = gasPrice * gasLimit / GAS_PRICE_FACTOR
# Size excludes witness data as it is malleable (even by third parties!)
bytesBalance = size(tx) * GAS_PER_BYTE * gasPrice / GAS_PRICE_FACTOR
# Total fee balance
feeBalance = ceiling(gasBalance + bytesBalance)
# Only base asset can be used to pay for gas
if asset_id != 0:
return sentBalance
return sentBalance + feeBalance

return available_balance(tx, asset_id) >= unavailable_balance(tx, asset_id)
if asset_id == 0:
return sentBalance + feeBalance
return sentBalance

# The sum_data_messages total is not included in the unavailable_balance since it is spendable as long as there
# is enough base asset amount to cover gas costs without using data messages. Messages containing data can't
# cover gas costs since they are retryable.
return available_balance(tx, asset_id) >= (unavailable_balance(tx, asset_id) + sum_data_messages(tx, asset_id))
```

### Valid Signatures
Expand Down Expand Up @@ -151,10 +170,11 @@ Given transaction `tx`, the following checks must pass:

If `tx.scriptLength == 0`, there is no script and the transaction defines a simple balance transfer, so no further checks are required.

If `tx.scriptLength > 0`, the script must be executed. For each asset ID `asset_id` in the input set, the free balance available to be moved around by the script and called contracts is `freeBalance[asset_id]`:
If `tx.scriptLength > 0`, the script must be executed. For each asset ID `asset_id` in the input set, the free balance available to be moved around by the script and called contracts is `freeBalance[asset_id]`. The initial message balance available to be moved around by the script and called contracts is `messageBalance`:

```py
freeBalance[asset_id] = available_balance(tx, asset_id) - unavailable_balance(tx, asset_id)
messageBalance = sum_data_messages(tx, 0)
```

Once the free balances are computed, the [script is executed](../vm/index.md#script-execution). After execution, the following is extracted:
Expand All @@ -177,8 +197,12 @@ Given transaction `tx`, state `state`, and contract set `contracts`, the followi

If change outputs are present, they must have:

1. if the transaction does not revert; an `amount` of `unspentBalance + floor((unspentGas * tx.gasPrice) / GAS_PRICE_FACTOR)` if their asset ID is `0`, or an `amount` of the unspent free balance for that asset ID after VM execution is complete, or
1. if the transaction reverts; an `amount` of the initial free balance plus `unspentGas * tx.gasPrice` if their asset ID is `0`, or an `amount` of the initial free balance for that asset ID.
- if the transaction does not revert;
- if the asset ID is `0`; an `amount` of `unspentBalance + floor((unspentGas * tx.gasPrice) / GAS_PRICE_FACTOR)`
- otherwise; an `amount` of the unspent free balance for that asset ID after VM execution is complete
- if the transaction reverts;
- if the asset ID is `0`; an `amount` of the initial free balance plus `(unspentGas * tx.gasPrice) - messageBalance`
- otherwise; an `amount` of the initial free balance for that asset ID.

### State Changes

Expand Down
Loading