Transactions
Transactions on the Thru blockchain are the core atomic unit of computation that a user can submit for execution. Each transaction represents an indivisible set of operations that either all succeed or all fail together.
Format
Section titled “Format”A Thru transaction consists of the following components:
Transaction Header (v1)
Section titled “Transaction Header (v1)”The transaction header contains critical metadata and parameters:
struct { uint8_t transaction_version; // [0,1) bytes: Must be 0x01 uint8_t flags; // [1,2) bytes: Transaction flags uint16_t readwrite_accounts_cnt; // [2,4) bytes: Number of writable accounts uint16_t readonly_accounts_cnt; // [4,6) bytes: Number of readonly accounts uint16_t instr_data_sz; // [6,8) bytes: Size of instruction data uint32_t req_compute_units; // [8,12) bytes: Requested compute units uint16_t req_state_units; // [12,14) bytes: Requested state units uint16_t req_memory_units; // [14,16) bytes: Requested memory units uint64_t fee; // [16,24) bytes: Transaction fee uint64_t nonce; // [24,32) bytes: Transaction nonce uint64_t start_slot; // [32,40) bytes: Earliest valid slot uint32_t expiry_after; // [40,44) bytes: Slots until expiry uint16_t chain_id; // [44,46) bytes: Chain identifier uint16_t padding_0; // [46,48) bytes: Reserved padding pubkey_t fee_payer_pubkey; // [48,80) bytes: Fee payer's public key pubkey_t program_pubkey; // [80,112) bytes: Program's public key}Account Addresses
Section titled “Account Addresses”At the end of and after the header, the transaction contains arrays of account addresses that will be accessed during execution. The account addresses are organized in a specific order:
- The fee payer account is always listed first in the transaction. This is also
header field
fee_payer_pubkey. - The program account is always listed second in the transaction. This is
header field
program_pubkey. - The writable accounts array follows, containing all accounts that the program may modify.
- The read-only accounts array comes last, containing all accounts that the program may read but not modify.
Instruction Data
Section titled “Instruction Data”Following the account addresses is the instruction data section. This contains the raw data that will be passed to the program during execution. The size of this data is specified in the header’s instr_data_sz field.
Optional State Proof
Section titled “Optional State Proof”For fee payer accounts that are being created or decompressed from a compressed state, an optional state proof may be included. This proof validates the account’s existence or non-existence in the state tree.
Transaction Signature
Section titled “Transaction Signature”The transaction concludes with a 64-byte Ed25519 signature from the fee payer. This signature is placed at the very end of the transaction data, after all other components including optional state proofs. The signature covers all transaction data preceding it (the “message”), ensuring the transaction’s integrity and authenticity.
Complete Transaction Data Layout
Section titled “Complete Transaction Data Layout”The following shows the complete binary layout of a Thru transaction:
struct thru_transaction_layout { // Transaction Header (112 bytes) struct { uint8_t transaction_version; // [0,1) bytes: Must be 0x01 uint8_t flags; // [1,2) bytes: Transaction flags uint16_t readwrite_accounts_cnt; // [2,4) bytes: Number of writable accounts uint16_t readonly_accounts_cnt; // [4,6) bytes: Number of readonly accounts uint16_t instr_data_sz; // [6,8) bytes: Size of instruction data uint32_t req_compute_units; // [8,12) bytes: Requested compute units uint16_t req_state_units; // [12,14) bytes: Requested state units uint16_t req_memory_units; // [14,16) bytes: Requested memory units uint64_t fee; // [16,24) bytes: Transaction fee uint64_t nonce; // [24,32) bytes: Transaction nonce uint64_t start_slot; // [32,40) bytes: Earliest valid slot uint32_t expiry_after; // [40,44) bytes: Slots until expiry uint16_t chain_id; // [44,46) bytes: Chain identifier uint16_t padding_0; // [46,48) bytes: Reserved padding uint8_t fee_payer_pubkey[32]; // [48,80) bytes: Fee payer's public key uint8_t program_pubkey[32]; // [80,112) bytes: Program's public key } header;
// Account Addresses (variable size) uint8_t readwrite_accounts[readwrite_accounts_cnt][32]; // Writable account addresses uint8_t readonly_accounts[readonly_accounts_cnt][32]; // Read-only account addresses
// Instruction Data (variable size) uint8_t instruction_data[instr_data_sz]; // Program instruction data
// Optional State Proof (if TN_TXN_FLAG_HAS_FEE_PAYER_PROOF flag is set) struct { struct { uint64_t type_slot; // [high 2 bits: proof type, low 62 bits: slot] uint8_t path_bitset[32]; // Bitset indicating proof path indices } proof_header;
// Variable proof body based on type: // For CREATION (type=2): existing_leaf_pubkey[32] + existing_leaf_hash[32] + sibling_hashes[] // For EXISTING (type=0): sibling_hashes[] // For UPDATING (type=1): existing_leaf_hash[32] + sibling_hashes[] uint8_t proof_body[]; // byte length = (proof_type + popcount(path_bitset)) * sizeof(fd_hash_t) } fee_payer_state_proof;
// Optional Account Metadata (only for EXISTING state proof type) struct { uint16_t magic; // [0,2) bytes: Magic number (0xC7A3) uint8_t version; // [2,3) bytes: Account version (0x00) uint8_t flags; // [3,4) bytes: Account flags uint32_t data_sz; // [4,8) bytes: Account data size uint64_t seq; // [8,16) bytes: State counter uint8_t owner[32]; // [16,48) bytes: Account owner pubkey uint64_t balance; // [48,56) bytes: Account balance uint64_t nonce; // [56,64) bytes: Account nonce } fee_payer_account_meta;
// Transaction Signature (always at the end, 64 bytes) uint8_t fee_payer_signature[64]; // Ed25519 signature covering all preceding data};Field Descriptions
Section titled “Field Descriptions”The following table describes each field in the transaction structure:
| Field | Offset | Size | Type | Description |
|---|---|---|---|---|
| Transaction Header | ||||
transaction_version | 0 | 1 byte | uint8 | Transaction format version, must be 0x01 |
flags | 1 | 1 byte | uint8 | Transaction flags (bit 0: has fee payer proof, bits 1-7: reserved) |
readwrite_accounts_cnt | 2-3 | 2 bytes | uint16 | Number of accounts that can be modified by the program |
readonly_accounts_cnt | 4-5 | 2 bytes | uint16 | Number of accounts that can only be read by the program |
instr_data_sz | 6-7 | 2 bytes | uint16 | Size in bytes of the instruction data section |
req_compute_units | 8-11 | 4 bytes | uint32 | Maximum compute units the transaction may consume |
req_state_units | 12-13 | 2 bytes | uint16 | Maximum state units the transaction may consume |
req_memory_units | 14-15 | 2 bytes | uint16 | Maximum memory units the transaction may consume |
fee | 16-23 | 8 bytes | uint64 | Transaction fee in native tokens |
nonce | 24-31 | 8 bytes | uint64 | Transaction nonce, must match fee payer’s current nonce |
start_slot | 32-39 | 8 bytes | uint64 | Earliest slot when transaction becomes valid |
expiry_after | 40-43 | 4 bytes | uint32 | Number of slots after start_slot when transaction expires |
chain_id | 44-45 | 2 bytes | uint16 | Chain identifier to prevent cross-chain replay attacks |
padding_0 | 46-47 | 2 bytes | uint16 | Reserved padding, must be zero |
fee_payer_pubkey | 48-79 | 32 bytes | Ed25519 pubkey | Public key of the account paying transaction fees |
program_pubkey | 80-111 | 32 bytes | Ed25519 pubkey | Public key of the program to execute |
| Account Addresses | ||||
readwrite_accounts | 112+ | 32×N bytes | Ed25519 pubkey[] | Array of writable account addresses (sorted ascending) |
readonly_accounts | Variable | 32×M bytes | Ed25519 pubkey[] | Array of read-only account addresses (sorted ascending) |
| Instruction Data | ||||
instruction_data | Variable | Variable | uint8[] | Raw data passed to the program during execution |
| Optional State Proof | ||||
type_slot | Variable | 8 bytes | uint64 | Proof type (bits 62-63) and slot number (bits 0-61) |
path_bitset | Variable | 32 bytes | uint8[32] | Bitset indicating which indices have sibling hashes |
proof_body | Variable | Variable | uint8[] | Variable proof data based on type and path_bitset |
| Optional Account Metadata | ||||
magic | Variable | 2 bytes | uint16 | Magic number identifying account metadata (0xC7A3) |
version | Variable | 1 byte | uint8 | Account metadata version (0x00) |
flags | Variable | 1 byte | uint8 | Account flags (program, privileged, compressed, etc.) |
data_sz | Variable | 4 bytes | uint32 | Size of account data in bytes |
seq | Variable | 8 bytes | uint64 | Account state modification counter |
owner | Variable | 32 bytes | Ed25519 pubkey | Public key of the program that owns this account |
balance | Variable | 8 bytes | uint64 | Account balance in native tokens |
nonce | Variable | 8 bytes | uint64 | Account nonce for transaction ordering |
| Transaction Signature | ||||
fee_payer_signature | Last 64 bytes | 64 bytes | Ed25519 signature | Cryptographic signature from the fee payer covering all preceding transaction data |
Transaction Flags
Section titled “Transaction Flags”The flags field at byte offset 1 controls optional transaction features. Currently, only one flag is defined:
| Flag | Bit Position | Value | Description |
|---|---|---|---|
TN_TXN_FLAG_HAS_FEE_PAYER_PROOF | 0 | 0x01 | Indicates that the transaction includes a state proof for the fee payer account. When this flag is set, a state proof must be included at the end of the transaction after the instruction data. |
All other bits in the flags field must be set to 0. The transaction parser will reject any transaction with unknown flags set to ensure forward compatibility.
Limitations
Section titled “Limitations”The Thru blockchain enforces several limits on transaction structure and size:
- The maximum transaction size is 32KiB.
- Each transaction can reference at most 1024 accounts.
- All account addresses must be exactly 32 bytes in length.
Validity Criteria
Section titled “Validity Criteria”A valid transaction is one that is includable in the chain. A block is itself unincludable if it contains an invalid transaction. A transaction must meet the following criteria to be considered valid.
Format Validation
Section titled “Format Validation”- The transaction size must not exceed the maximum transmission unit (MTU) of 32KiB.
- The transaction version must be exactly
0x01. - All fields in the transaction header must be properly formatted and aligned.
- The account arrays must contain the correct number of accounts as specified in the header.
- The total number of accounts referenced must not exceed 1024.
Signature Verification
Section titled “Signature Verification”- The fee payer’s Ed25519 signature must be cryptographically valid.
- The signature is located at the end of the transaction (last 64 bytes).
- The signature must cover all transaction data preceding it (the “message”), which includes the header, account addresses, instruction data, and optional state proofs.
Account List Verification
Section titled “Account List Verification”- No duplicate accounts are allowed anywhere in the transaction.
- Writable accounts must be sorted in ascending lexicographic order.
- Read-only accounts must be sorted in ascending lexicographic order.