Core protocol
Blocks & validation
The canonical block encoding, block ID preimage, validation order, transaction state transition, size schedule, and linear-chain consensus boundary.
This page documents the implemented alpha. Executable source, interoperability vectors, and tests remain authoritative if prose and code ever diverge.
Block model
A block is a fixed-format header followed by one or more complete 213-byte transactions. The first transaction is the unique coinbase. Luracoin does not use a Merkle root in this alpha: the canonical block hash commits directly to the transaction count and every serialized transaction.
The wire and disk representation begins with a 118-byte transmitted header:
| Offset | Size | Field | Encoding |
|---|---|---|---|
| 0 | 4 | version |
unsigned little-endian; currently 1 |
| 4 | 32 | declared block ID | raw 32-byte digest |
| 36 | 32 | prev_block_hash |
raw 32-byte digest |
| 68 | 4 | height |
unsigned little-endian |
| 72 | 34 | miner |
canonical address as ASCII |
| 106 | 4 | timestamp |
Unix seconds, unsigned big-endian |
| 110 | 4 | bits |
compact target bytes |
| 114 | 4 | nonce |
unsigned little-endian |
| 118 | N × 213 |
transactions | complete transactions, no count on wire |
The mixed timestamp endianness is intentional and consensus-critical. Do not normalize it in an independent implementation.
Canonical block ID
The declared block ID stored at bytes 4–35 cannot hash itself. The canonical preimage omits that field and inserts a two-byte transaction count before the transaction bodies:
version_le_u32
|| prev_block_hash_32
|| height_le_u32
|| miner_ascii_34
|| timestamp_be_u32
|| bits_4
|| nonce_le_u32
|| transaction_count_le_u16
|| transaction_0_213
|| ...
|| transaction_n_213
Then:
block_id = SHA256(SHA256(canonical_preimage))
The displayed identifier is lowercase hexadecimal. Deserialization recalculates this digest and rejects a declared-ID mismatch before the block can be accepted.
This design commits to transaction order and signatures. Reordering two otherwise valid transactions changes the ID. Re-signing a transaction also changes its transaction ID and therefore its containing block ID.
Structural parsing rules
A decoder must reject a block when any of these conditions holds:
- the payload is shorter than the 118-byte header;
- the trailing byte length is zero or is not divisible by 213;
- the transaction count exceeds the unsigned 16-bit limit;
- the version, height, timestamp, or nonce is outside its encoded integer domain;
- the miner is not exactly 34 ASCII bytes;
- any embedded transaction fails exact 213-byte deserialization;
- the computed ID differs from the declared 32-byte ID;
- bytes remain after the expected transaction sequence.
There is no permissive extension area. A future format must introduce an explicit compatibility boundary rather than appending fields to version 1.
Block size schedule
The maximum serialized block size changes at fixed heights:
| Starting height | Maximum serialized bytes |
|---|---|
| 0 | 10,000 |
| 28,800 | 50,000 |
| 57,600 | 75,000 |
| 86,400 | 200,000 |
| 172,800 | 1,000,000 |
| 259,200 | 2,000,000 |
| 345,600 | 3,000,000 |
| 432,000 | 8,000,000 |
The limit includes the full 118-byte header and every 213-byte transaction. It is not a transaction-body-only allowance. The P2P transport independently caps block payloads at 8,000,000 bytes and all message payloads at 8,000,004 bytes.
Because the transaction count is uint16, a block can contain at most 65,535 transactions regardless of byte capacity. One slot is reserved for coinbase, so a miner selects at most 65,534 normal transactions.
Context-free checks
Before reading mutable account state, the validator establishes:
- the block object has a canonical serialization and matching ID;
- its serialized size is within the limit for its own height;
- its version is supported;
- its miner address is valid;
- it contains at least one transaction;
- transaction IDs are unique within the block;
- every transaction belongs to the active chain ID;
- exactly one coinbase exists and it is transaction zero.
A block with a second zero-origin transaction is invalid even if its amount is zero. A normal transaction cannot occupy index zero in front of coinbase.
Chain-link checks
For a non-genesis candidate at local tip height H, the next block must satisfy:
candidate.height == H + 1
candidate.prev_block_hash == local_tip.id
candidate.bits == expected_bits(candidate.height)
candidate.timestamp > local_tip.timestamp
candidate.timestamp <= local_clock_unix + 500
numeric(candidate.id) <= target(candidate.bits)
The future-time window is evaluated against the validating node’s clock. Operators therefore need reliable time synchronization. A timestamp equal to the predecessor is invalid; monotonicity is strict.
The PoW comparison treats the 32-byte digest as a big-endian integer. <= is valid at the target boundary.
Coinbase contract
At height greater than zero, the first transaction must have:
| Field | Required value |
|---|---|
chain |
active chain ID |
nonce |
candidate block height |
fee |
0 |
value |
subsidy at height + sum of all normal fees |
from_address |
34 ASCII zero characters |
to_address |
exactly the block header’s miner address |
| unlocking field | 128 zero bytes |
The fee sum is computed with bounded integer arithmetic. A coinbase that underpays or overpays is invalid; there is no permitted donation by omitting part of the subsidy or fees.
Genesis is created from its network manifest and uses the manifest allocation instead of the recurring subsidy rule. Testnet and devnet genesis material is educational and public. Mainnet has no manifest and initialization fails closed.
Ordered state transition
The validator copies the accounts touched by the candidate into a temporary state. It then processes normal transactions in their block order:
require sender exists
require tx.nonce == sender.nonce + 1
require sender.balance >= tx.value + tx.fee
require signature, sender key, address, and chain are valid
sender.balance -= tx.value + tx.fee
sender.nonce = tx.nonce
receiver.balance += tx.value
Each later transaction observes the earlier temporary changes. This has important consequences:
- multiple outgoing transactions from one account must use contiguous nonces;
- an account may spend value received earlier in the same block;
- a self-transfer consumes only the fee but still increments the nonce;
- order can determine whether a set of individually well-formed transactions is valid;
- any
uint64receiver-credit overflow invalidates the whole candidate.
After all normal transactions validate, the coinbase value is credited to the miner. Coinbase funds therefore cannot be spent inside the block that creates them.
No partial state is published if any transaction fails. Validation is an all-or-nothing transition.
Acceptance sequence
The implemented acceptance path is serialized by a state-transition lock. Conceptually it performs:
- validate the candidate against the current tip and a temporary account view;
- append the exact block record to the active
blkNNNNNN.datfile; - publish height and transaction-location indexes;
- commit updated accounts and chain metadata;
- remove confirmed and conflicting pending transactions from Redis;
- notify relay and optional explorer-index components.
Disk append and indexing include narrow crash-recovery logic described in State, mempool & storage. This does not turn the overall alpha into a transactional database across RocksDB, block files, Redis, and SQLite.
Genesis validation
Genesis is not received and accepted as an arbitrary next block. The node selects a built-in manifest for the requested network, constructs the canonical block, verifies its expected ID and proof, and initializes an empty data directory.
Known alpha IDs are:
testnet 0000009e2bcc8e56cee8b5e0fbdac9edc04513542af5c5791ea6165179c468bf
devnet 00000033e4111007fd85423c7a527595e8efefe158a1250db62a32870857ef91
These identify the current educational chains. They are not promises that pre-release balances will survive later resets.
Linear-chain consensus boundary
The alpha validates only the next block extending its current local tip. It does not implement:
- side-branch storage;
- cumulative-chain-work comparison;
- a fork-choice rule;
- reorganizations or state rollback;
- orphan-block pools;
- header-first synchronization;
- probabilistic or economic finality.
Consequently, “confirmations” means depth in one node’s currently stored linear history. It must not be interpreted as public-chain finality. Two nodes that accept different valid children of the same height have no implemented mechanism to converge automatically.
This is the most important limitation in the current protocol. Production-network work must define branch data structures, cumulative work, deterministic fork choice, reorg-safe state and indexes, mempool resurrection, and adversarial tests before claiming decentralized consensus.
Independent implementation checklist
An implementation is not block-compatible until it can prove all of the following:
- exact 118-byte transmitted header;
- ID preimage excludes the declared ID and includes
tx_count_le_u16; - timestamp uses big-endian while other listed integers retain their specified order;
- block size is calculated from canonical serialized bytes;
- transaction order is preserved during hashing and validation;
- duplicate transaction IDs and misplaced coinbase are rejected;
- expected bits, PoW boundary, predecessor, height, and timestamps are contextual;
- normal transfers are applied before the coinbase credit;
- all monetary arithmetic rejects overflow;
- unknown versions and trailing data fail closed.
Use the Python tests as executable consensus fixtures, especially regression cases at size, timestamp, overflow, target, duplicate, and coinbase boundaries.
Source anchors
Primary implementation files used for this chapter:
