Once a transaction is seen, it goes through several stages of validation, in this order:
The validity rules below assume sequential transaction validation for side effects (i.e. state changes). However, by construction, transactions with disjoint write access lists can be validated in parallel, including with overlapping read-only access lists. Transactions with overlapping write access lists must be validated and placed in blocks in topological order.
UTXOs and contracts in the read-only and write-destroy access lists must exist (i.e. have been created previously) in order for a transaction to be valid. In other words, for a unique state element ID, the write-create must precede the write-destroy.
Read-only access list:
Write-destroy access list:
Write-create access list:
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 where
input.dataLength > 0are not dropped from the
messagesmessage set until they are included in a transaction of type
resultis equal to
0indicating a successful script exit.
Base sanity checks are defined in the transaction format .
for input in tx.inputs: if input.type == InputType.Contract: if not input.contractID in contracts: return False elif input.type == InputType.Message: if not input.nonce in messages: return False else: if not (input.txID, input.outputIndex) in state: return False return True
If this check passes, the UTXO ID
(txID, outputIndex) fields of each contract input is set to the UTXO ID of the respective contract. The
txPointer of each input is also set to the TX pointer of the UTXO with ID
For each asset ID
asset_id in the input and output set:
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: 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: total += output.amount return total def available_balance(tx, asset_id) -> int: """ 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: 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 + 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))
def address_from(pubkey: bytes) -> bytes: return sha256(pubkey)[0:32] for input in tx.inputs: if (input.type == InputType.Coin || input.type == InputType.Message) and input.predicateLength == 0: # ECDSA signatures must be 64 bytes if tx.witnesses[input.witnessIndex].dataLength != 64: return False # Signature must be from owner if address_from(ecrecover_k1(txhash(), tx.witnesses[input.witnessIndex].data)) != input.owner: return False return True
Signatures and signature verification are specified here .
The transaction hash is computed as defined here .
For each input of type
predicateLength > 0, verify its predicate .
tx, the following checks must pass:
tx.scriptLength == 0, there is no script and the transaction defines a simple balance transfer, so no further checks are required.
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
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 . After execution, the following is extracted:
unspentBalancefor each asset ID.
The fees incurred for a transaction are
ceiling(((size(tx) * GAS_PER_BYTE) + (tx.gasLimit - unspentGas)) * tx.gasPrice / GAS_PRICE_FACTOR).
If the transaction as included in a block does not match this final transaction, the block is invalid.
This section defines VM postcondition validity rules for transactions: the requirements for a transaction to be valid after it has been executed.
state, and contract set
contracts, the following checks must pass.
If change outputs are present, they must have:
unspentBalance + floor((unspentGas * tx.gasPrice) / GAS_PRICE_FACTOR)
amountof the unspent free balance for that asset ID after VM execution is complete
amountof the initial free balance plus
(unspentGas * tx.gasPrice) - messageBalance
amountof the initial free balance for that asset ID.
Transaction processing is completed by removing spent UTXOs from the state and adding created UTXOs to the state.
The coinbase transaction is a mechanism for block creators to convert fees into spendable UTXOs.
In order for a coinbase transaction to be valid:
asset_idfor coinbase transaction outputs must match the
asset_idthat fees are paid in (
asset_id == 0).