Transaction Lifecycle
Every transaction on Xian follows a defined path from creation to finalization. Understanding this lifecycle helps you debug failed transactions, optimize chi usage, and build reliable applications.
Overview
Step-by-Step
1. Transaction Creation
Using the Python SDK (xian-py), construct a transaction payload:
from xian_py import Wallet, Xian
wallet = Wallet()
xian = Xian("http://localhost:26657", "xian-testnet-1", wallet=wallet)
result = xian.send_tx(
contract="currency",
function="transfer",
kwargs={"amount": 100, "to": "recipient_address"},
chi=50000,
)The payload contains:
contract-- the target contract namefunction-- the exported function to callkwargs-- keyword arguments for the functionchi-- maximum chi to spendchain_id-- network identifier (prevents cross-chain replay)nonce-- sequential counter (prevents replay on the same chain)
2. Signing
The SDK signs the transaction payload with the sender's Ed25519 private key. The signature is attached to the transaction and verified by validators.
3. Broadcasting
The signed transaction is sent to a CometBFT node via its RPC interface. Two modes:
| Mode | Behavior |
|---|---|
broadcast_tx_sync | Waits for CHECK_TX result, then returns |
broadcast_tx_async | Returns immediately, no validation feedback |
4-7. CHECK_TX (Mempool Validation)
Before entering the mempool, the transaction passes through validation:
- Signature verification -- the Ed25519 signature must be valid for the payload and sender's public key
- Chain ID check -- the transaction's chain_id must match the network
- Nonce check -- the nonce must be the next expected value for this sender
- Balance check -- the sender must have enough XIAN to cover the requested chi limit
If any check fails, the transaction is rejected and never enters the mempool. Local pending-nonce reservations only happen after the transaction passes the static checks above, so bad signatures, wrong chain_id values, and malformed wire payloads do not temporarily block the sender's next valid nonce.
8. Mempool
Valid transactions sit in the mempool until a block proposer includes them in a block proposal.
9. Consensus
CometBFT runs Byzantine Fault Tolerant consensus. Once 2/3+ of validators agree on the block contents and order, the block is finalized.
The block header also fixes the timestamp that contracts will see as now during execution of that block.
10-14. FINALIZE_BLOCK (Execution)
Canonical block semantics are sequential: every validator must end up with the same result as if the transactions were executed in block order, one after the other.
Nodes may optionally speculate in parallel before acceptance, but any accepted speculative result still has to pass serial-equivalence checks against earlier transactions in the block. Conflicting speculative results are re-executed serially.
See Parallel Block Execution for the mechanism.
Per transaction, the execution flow is:
- Sandbox setup -- the contract runtime initializes with the sender's context (
ctx.caller,ctx.signer) - Function dispatch -- the specified
@exportfunction is called with the provided kwargs - Metering -- the selected execution policy charges compute units through tracer-backed Python execution or the native VM gas schedule
- Storage operations -- reads and writes are charged per byte according to the selected execution policy; tracer-backed execution uses 1 meter unit per byte read and 25 meter units per byte written, while VM-native execution charges storage through the VM host-operation schedule
- Block time injection -- all transactions in the block observe the same consensus timestamp as
now - Completion or failure:
- Success -- state changes are buffered for commit, chi consumed are recorded
- Failure (assertion, out of chi, runtime error) -- state changes are rolled back, chi are still charged
15-17. Commit
After all transactions in the block are executed:
- All successful state changes are written to LMDB in a single atomic batch
- The
app_hashis computed as a hash over the entire state database - The app_hash is returned to CometBFT for inclusion in the next block header
18-20. Post-Finalization
- Event indexing -- CometBFT indexes events emitted during execution, making them searchable via
/tx_search - WebSocket notifications -- subscribers receive real-time event data
- RPC availability -- the block, transactions, and results are queryable via the CometBFT RPC
Transaction Result
After finalization, querying a transaction returns:
| Field | Description |
|---|---|
hash | Transaction hash |
height | Block height |
status | 0 (success) or 1 (failure) |
chi_used | Actual chi consumed |
result | Return value from the contract function |
state | State changes made (key-value pairs) |
events | Events emitted by the contract |
Failure Modes
| Failure | When | State Changes | Chi Charged |
|---|---|---|---|
| CHECK_TX rejection | Before mempool | None | None |
| Assertion error | During execution | Rolled back | Yes (chi consumed up to failure) |
| Out of chi | During execution | Rolled back | Yes (full chi limit) |
| Runtime error | During execution | Rolled back | Yes (chi consumed up to failure) |