Skip to content

Commit 4c5f8e5

Browse files
authored
Document tx lifecycle better (#95)
1 parent e16bf55 commit 4c5f8e5

5 files changed

Lines changed: 102 additions & 88 deletions

File tree

File renamed without changes.

specs/protocol/identifiers.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Identifiers
2+
3+
- [Transaction ID](#transaction-id)
4+
- [UTXO ID](#utxo-id)
5+
6+
This document defines how to compute the unique identifiers used for transactions and state elements.
7+
8+
## Transaction ID
9+
10+
The _transaction ID_ (also called _transaction hash_) of a transaction is computed as the [hash](./cryptographic_primitives.md#hashing) of the [serialized transaction](./tx_format.md#transaction) with [fields zeroed out for signing](./tx_format.md) (see different inputs and outputs for which fields are set to zero).
11+
12+
## UTXO ID
13+
14+
The UTXO ID of a transaction's output is computed as the [hash](./cryptographic_primitives.md#hashing) of the concatenation of the [transaction ID](#transaction-id) and the output index as a `uint8`.
15+
16+
The UTXO ID of a deposit is computed as the [hash](./cryptographic_primitives.md#hashing) of TODO.

specs/protocol/tx_format.md

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
| name | type | value | description |
2121
| --------------------------- | -------- | ----- | --------------------------------------------- |
2222
| `GAS_PER_BYTE` | `uint64` | | Gas charged per byte of the transaction. |
23+
| `MAX_GAS_PER_TX` | `uint64` | | Maximum gas per transaction. |
2324
| `MAX_INPUTS` | `uint64` | `8` | Maximum number of inputs. |
2425
| `MAX_OUTPUTS` | `uint64` | `8` | Maximum number of outputs. |
2526
| `MAX_PREDICATE_LENGTH` | `uint64` | | Maximum length of predicate, in instructions. |
@@ -42,11 +43,21 @@ enum TransactionType : uint8 {
4243
| `type` | `TransactionType` | Transaction type. |
4344
| `data` | One of [TransactionScript](#transactionscript) or [TransactionCreate](#transactioncreate) | Transaction data. |
4445

46+
Transaction is invalid if:
47+
* `type > TransactionType.Create`
48+
* `gasLimit > MAX_GAS_PER_TX`
49+
* `blockheight() < maturity`
50+
* `inputsCount > MAX_INPUTS`
51+
* `outputsCount > MAX_OUTPUTS`
52+
* `witnessesCount > MAX_WITNESSES`
53+
4554
When serializing a transaction, fields are serialized as follows (with inner structs serialized recursively):
4655
1. `uint8`, `uint16`, `uint32`, `uint64`: big-endian right-aligned to 8 bytes.
4756
1. `byte[32]`: as-is.
4857
1. `byte[]`: as-is, with padding zeroes aligned to 8 bytes.
4958

59+
When deserializing a transaction, the reverse is done. If there are insufficient bytes or too many bytes, the transaction is invalid.
60+
5061
### TransactionScript
5162

5263
| name | type | description |
@@ -66,8 +77,9 @@ When serializing a transaction, fields are serialized as follows (with inner str
6677
| `witnesses` | [Witness](#witness)`[]` | List of witnesses. |
6778

6879
Transaction is invalid if:
69-
* `blockheight() < maturity`
7080
* Any output is of type `OutputType.ContractCreated`
81+
* `scriptLength > MAX_SCRIPT_LENGTH`
82+
* `scriptDataLength > MAX_SCRIPT_DATA_LENGTH`
7183

7284
### TransactionCreate
7385

@@ -87,13 +99,12 @@ Transaction is invalid if:
8799
| `witnesses` | [Witness](#witness)`[]` | List of witnesses. |
88100

89101
Transaction is invalid if:
90-
* `blockheight() < maturity`
91102
* Any input is of type `InputType.Contract`
92103
* Any output is of type `OutputType.Contract` or `OutputType.Variable`
93104
* More than one output is of type `OutputType.ContractCreated`
94105
* `bytecodeLength * 4 > CONTRACT_MAX_SIZE`
95106

96-
Creates a contract with contract ID `sha256(0x4655454C ++ tx.data.salt ++ root(tx.data.bytecode))`, where `root` is the Merkle root of [the binary Merkle tree](./cryptographic_primitives.md) with each leaf being an 8-byte word of bytecode. If the bytecode is not a multiple of 8 bytes (i.e. if there are an odd number of instructions), the last opcode is padding with 4-byte zero.
107+
Creates a contract with contract ID `sha256(0x4655454C ++ tx.data.salt ++ root(tx.data.bytecode))`, where `root` is the Merkle root of [the binary Merkle tree](./cryptographic_primitives.md) with each leaf being an 8-byte word of bytecode. If the bytecode is not a multiple of 8 bytes (i.e. if there are an odd number of instructions), the last opcode is padded with 4-byte zero.
97108

98109
## Input
99110

@@ -121,6 +132,11 @@ enum InputType : uint8 {
121132
| `predicate` | `byte[]` | Predicate bytecode. |
122133
| `predicateData` | `byte[]` | Predicate input data (parameters). |
123134

135+
Transaction is invalid if:
136+
* `witnessIndex >= tx.witnessesCount`
137+
* `predicateLength > MAX_PREDICATE_LENGTH`
138+
* `predicateDataLength > MAX_PREDICATE_DATA_LENGTH`
139+
124140
If `h` is the block height the UTXO being spent was created, transaction is invalid if `blockheight() < h + maturity`.
125141

126142
### InputContract
@@ -130,8 +146,13 @@ If `h` is the block height the UTXO being spent was created, transaction is inva
130146
| `utxoID` | `byte[32]` | UTXO ID. |
131147
| `contractID` | `byte[32]` | Contract ID. |
132148

149+
Transaction is invalid if:
150+
* there is not exactly one output of type `OutputType.Contract` with `inputIndex` equal to this input's index
151+
133152
Note: when signing a transaction, `utxoID` is set to zero.
134153

154+
Note: when verifying a predicate, `utxoID` is initialized to zero.
155+
135156
## Output
136157

137158
```
@@ -164,9 +185,13 @@ enum OutputType : uint8 {
164185
| `amount` | `uint64` | Amount of coins owned by contract after transaction execution. |
165186
| `stateRoot` | `byte[32]` | State root of contract after transaction execution. |
166187

188+
Transaction is invalid if:
189+
* `inputIndex >= tx.inputsCount`
190+
* `tx.inputs[inputIndex].type != InputType.Contract`
191+
167192
Note: when signing a transaction, `amount` and `stateRoot` are set to zero.
168193

169-
Note: when executing a transaction, `amount` and `stateRoot` are initialized to the balance and state root of the contract with ID `tx.inputs[inputIndex].contractID`.
194+
Note: when verifying a predicate or executing a script, `amount` and `stateRoot` are initialized to the balance and state root of the contract with ID `tx.inputs[inputIndex].contractID`.
170195

171196
### OutputChange
172197

@@ -177,7 +202,7 @@ Note: when executing a transaction, `amount` and `stateRoot` are initialized to
177202

178203
Note: when signing a transaction, `amount` is set to zero.
179204

180-
Note: when executing a transaction, `amount` is initialized to zero.
205+
Note: when verifying a predicate or executing a script, `amount` is initialized to zero.
181206

182207
This output type indicates that the output's amount may vary based on transaction execution, but is otherwise identical to a [Coin](#outputcoin) output. An `amount` of zero after transaction execution indicates that the output is unspendable and can be pruned from the UTXO set.
183208

@@ -190,7 +215,7 @@ This output type indicates that the output's amount may vary based on transactio
190215

191216
Note: when signing a transaction, `to` and `amount` are set to zero.
192217

193-
Note: when executing a transaction, `to` and `amount` are initialized to zero.
218+
Note: when verifying a predicate or executing a script, `to` and `amount` are initialized to zero.
194219

195220
This output type indicates that the output's amount and owner may vary based on transaction execution, but is otherwise identical to a [Coin](#outputcoin) output. An `amount` of zero after transaction execution indicates that the output is unspendable and can be pruned from the UTXO set.
196221

specs/protocol/tx_validity.md

Lines changed: 53 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,90 +1,51 @@
11
# Transaction Validity
22

3-
- [Standardness Rules](#standardness-rules)
4-
- [Version](#version)
5-
- [Spending UTXOs or Created Contracts](#spending-utxos-or-created-contracts)
6-
- [Transaction Maturity](#transaction-maturity)
7-
- [Input Maturity](#input-maturity)
8-
- [Counts](#counts)
9-
- [Script Length](#script-length)
10-
- [Predicate Lengths](#predicate-lengths)
3+
- [Transaction Lifecycle](#transaction-lifecycle)
4+
- [VM Precondition Validity Rules](#vm-precondition-validity-rules)
5+
- [Base Sanity Checks](#base-sanity-checks)
6+
- [Spending UTXOs and Created Contracts](#spending-utxos-and-created-contracts)
117
- [Sufficient Balance](#sufficient-balance)
128
- [Valid Signatures](#valid-signatures)
13-
- [Predicate Validity](#predicate-validity)
14-
- [Validity Rules](#validity-rules)
9+
- [Predicate Verification](#predicate-verification)
10+
- [Script Execution](#script-execution)
11+
- [VM Postcondition Validity Rules](#vm-postcondition-validity-rules)
1512
- [No Inflation](#no-inflation)
13+
- [State Changes](#state-changes)
1614

17-
## Standardness Rules
15+
## Transaction Lifecycle
1816

19-
This section defines _standardness rules_ for transactions: the bare minimum required to accept an unconfirmed transaction into a mempool. Chains of unconfirmed transactions are omitted.
17+
Once a transaction is seen, it goes through several stages of validation, in this order:
18+
1. [Pre-checks](#vm-precondition-validity-rules)
19+
1. [Predicate verification](#predicate-verification)
20+
1. [Script execution](#script-execution)
21+
1. [Post-checks](#vm-postcondition-validity-rules)
2022

21-
For a transaction `tx`, state `state`, and contract set `contracts`, the following checks must pass.
22-
23-
### Version
23+
## VM Precondition Validity Rules
2424

25-
```py
26-
return tx.version == 0
27-
```
25+
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.
2826

29-
### Spending UTXOs or Created Contracts
27+
The validity rules assuming sequential transaction validation for side effects (i.e. state changes). However, by construction, transactions with different access lists can be validated in parallel. Transactions with overlapping access lists must be validated and placed in blocks in topological order.
3028

31-
```py
32-
for input in tx.inputs:
33-
if input.type == InputType.Coin:
34-
if not input.utxoID in state:
35-
return False
36-
if input.type == InputType.Contract:
37-
if not input.contractID in contracts:
38-
return True
39-
```
29+
For a transaction `tx`, state `state`, and contract set `contracts`, the following checks must pass.
4030

41-
### Transaction Maturity
31+
### Base Sanity Checks
4232

43-
```py
44-
return blockheight() >= tx.maturity
45-
```
33+
Base sanity checks are defined in the [transaction format](./tx_format.md).
4634

47-
### Input Maturity
35+
### Spending UTXOs and Created Contracts
4836

4937
```py
5038
for input in tx.inputs:
51-
if input.type == InputType.Coin:
52-
if blockheight() < state[input.utxoID].created + input.maturity:
39+
if input.type == InputType.Contract:
40+
if not input.contractID in contracts:
41+
return False
42+
else:
43+
if not input.utxoID in state:
5344
return False
5445
return True
5546
```
5647

57-
### Counts
58-
59-
```py
60-
return (
61-
tx.inputsCount <= MAX_INPUTS and
62-
tx.outputsCount <= MAX_OUTPUTS and
63-
tx.witnessesCount <= MAX_WITNESSES
64-
)
65-
```
66-
67-
### Script Length
68-
69-
```py
70-
return (
71-
scriptLength <= MAX_SCRIPT_LENGTH and
72-
scriptDataLength <= MAX_SCRIPT_DATA_LENGTH
73-
)
74-
```
75-
76-
### Predicate Lengths
77-
78-
```py
79-
for input in tx.inputs:
80-
if input.type == InputType.Coin:
81-
if (
82-
input.predicateLength > MAX_PREDICATE_LENGTH or
83-
input.predicateDataLength > MAX_PREDICATE_DATA_LENGTH
84-
):
85-
return False
86-
return True
87-
```
48+
If this check passes, the `utxoID` field of each input is set to the UTXO ID of the respective contract.
8849

8950
### Sufficient Balance
9051

@@ -124,7 +85,7 @@ return available_balance(tx) >= unavailable_balance(tx)
12485

12586
```py
12687
def address_from(pubkey: bytes) -> bytes:
127-
return sha256(pubkey)[0:31]
88+
return sha256(pubkey)[0:32]
12889

12990
for input in tx.inputs:
13091
if input.type == InputType.Coin:
@@ -133,46 +94,57 @@ for input in tx.inputs:
13394
return True
13495
```
13596

136-
### Predicate Validity
97+
The transaction hash is computed as defined [here](./identifiers.md#transaction-id).
13798

138-
For each input of type `InputType.Coin` and `predicateLength > 0`, [verify its predicate](./main.md#predicate-verification).
99+
## Predicate Verification
139100

140-
## Validity Rules
101+
For each input of type `InputType.Coin` and `predicateLength > 0`, [verify its predicate](../vm/main.md#predicate-verification).
141102

142-
This section defines _validity rules_ for transactions: the requirements for a confirmed transaction to be valid.
103+
## Script Execution
143104

144-
Given transaction `tx`, state `state`, and contract set `contracts`:
105+
Given transaction `tx`, the following checks must pass:
145106

146-
If `tx.scriptLength == 0`, there is no script and the transaction defines a simple balance transfer, and no further checks are required. Transaction processing is completed by removing spent UTXOs from the state and adding created UTXOs to the state.
107+
If `tx.scriptLength == 0`, there is no script and the transaction defines a simple balance transfer, so no further checks are required.
147108

148109
If `tx.scriptLength > 0`, the script must be executed. The free balance available to be moved around by the script and called contracts is `freeBalance`:
149110

150111
```py
151112
freeBalance = available_balance(tx) - unavailable_balance(tx)
152113
```
153114

154-
The following checks must pass.
115+
Once the free balance is computed, the [script is executed](../vm/main.md#script-execution) and the transaction in memory on VM termination is used as the final transaction which is included in the block, i.e.:
116+
117+
```
118+
tx = MEM[40, MEM[32, 8]]
119+
```
120+
121+
If the transaction as included in a block does not match the final transaction, the block is invalid.
122+
123+
## VM Postcondition Validity Rules
124+
125+
This section defines _VM postcondition validity rules_ for transactions: the requirements for a transaction to be valid after it has been executed.
126+
127+
Given transaction `tx`, state `state`, and contract set `contracts`, the following checks must pass.
155128

156129
### No Inflation
157130

158131
```py
159132
def sum_all_inputs(tx) -> int:
160133
total: int = 0
161134
for input in tx.inputs:
162-
if input.type == InputType.Coin:
163-
total += state[input.utxoID].amount
164-
else if input.type == InputType.Contract:
165-
total += state[tx.witnesses[input.witnessIndex]].amount
135+
total += state[input.utxoID].amount
166136
return total
167137

168138
def sum_all_outputs(tx) -> int:
169139
total: int = 0
170140
for output in tx.outputs:
171-
if output.type == OutputType.Coin:
141+
if output.type != OutputType.ContractCreated:
172142
total += output.amount
173-
else if output.type == OutputType.Contract:
174-
total += tx.witnesses[output.amountWitnessIndex]
175143
return total
176144

177145
return sum_all_inputs(tx) >= sum_all_outputs(tx)
178146
```
147+
148+
### State Changes
149+
150+
Transaction processing is completed by removing spent UTXOs from the state and adding created UTXOs to the state.

specs/vm/main.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,8 @@ A complete list of opcodes in the Fuel VM is documented [here](./opcodes.md).
7373
Every time the VM runs, a single monolithic memory of size `VM_MAX_RAM` bytes is allocated, indexed by individual byte. A stack and heap memory model is used, allowing for dynamic memory allocation in higher-level languages. The stack begins at `0` and grows upward. The heap begins at `VM_MAX_RAM - 1` and grows downward.
7474

7575
To initialize the VM, the following is pushed on the stack sequentially:
76-
1. Transaction hash (`byte[32]`, word-aligned).
76+
1. Transaction hash (`byte[32]`, word-aligned), computed as defined [here](../protocol/identifiers.md#transaction-id).
77+
1. Transaction length, in bytes (`uint64`, word-aligned).
7778
1. The [transaction, serialized](../protocol/tx_format.md).
7879

7980
Then the following registers are initialized (without explicit initialization, all registers are initialized to zero):

0 commit comments

Comments
 (0)