Blocks described what a block does. This one describes what it is made of: two objects, one of which is a fixed list of twelve fields and nothing else.
Two objects, nested
Block
├─ header → Header
│ ├─ version
│ ├─ parentsByLevel[][] → 32-byte block hashes, grouped by level
│ ├─ hashMerkleRoot
│ ├─ acceptedIdMerkleRoot
│ ├─ utxoCommitment
│ ├─ timestamp
│ ├─ bits
│ ├─ nonce
│ ├─ daaScore
│ ├─ blueScore
│ ├─ blueWork
│ └─ pruningPoint
└─ transactions[]
└─ Transaction → the object tree on Transaction Model The header is a leaf. It holds no nested objects at all - twelve scalars and one array of arrays of hashes. That flatness is what lets the header travel on its own: headers-first sync, pruning proofs, and proof-of-work checking all operate on this object without ever fetching a body.
The header
| Field | Holds | Why it exists |
|---|---|---|
version | u16 | Selects how the rest is read. |
parentsByLevel | array of arrays of 32-byte hashes | The blocks this one builds on. See below. |
hashMerkleRoot | 32 bytes | Commits to the transactions in this block’s own body. |
acceptedIdMerkleRoot | 32 bytes | Commits to the transaction ids this block accepts. |
utxoCommitment | 32 bytes | Commits to the whole UTXO set after those acceptances. |
timestamp | u64 | Milliseconds since the Unix epoch, as claimed by the miner. |
bits | u32 | The difficulty target, compactly encoded. |
nonce | u64 | What the miner varies to search for a hash under the target. |
daaScore | u64 | The blocks in this block’s past that the difficulty window counted. |
blueScore | u64 | How many blue blocks are in this block’s past. |
blueWork | 192-bit integer | The work accumulated across those blue blocks. |
pruningPoint | 32 bytes | The pruning point this block commits to. |
Three of those fields are claims, not facts. daaScore, blueScore and blueWork are values the miner writes into the header, and every node recomputes all three from the DAG itself and rejects the block if its own answer differs. They are in the header so that a node holding only headers can still order them - not because anyone is trusted to report them.
Parents are grouped by level
parentsByLevel is an array of arrays. The outer index is the block level, and only the first one is about consensus ordering.
| Level | Contains | Used for |
|---|---|---|
0 | The tips the miner had seen. At most 16. | GHOSTDAG ordering, mergeset selection, everything a node does with the DAG normally. |
1+ | Sparser links to blocks that themselves cleared a harder target. | Pruning proofs - letting a syncing node verify a long stretch of history without downloading every block in it. |
A block’s own level is not a stored field. It falls out of its hash: a block whose proof of work landed far under the required target would also have satisfied a much harder one, so it counts as a block at every level it could have cleared. Lucky blocks become the skeleton of the proof.
Two consensus limits bound what level 0 can do:
| Limit | Value |
|---|---|
| Max parents (level 0) | 16 |
| Mergeset size limit | 248 |
| GHOSTDAG k | 124 |
The three commitments
The header carries three 32-byte hashes, and the difference between them is the difference between what a block holds, what it decides, and what the ledger becomes.
| Commitment | Covers | Answers |
|---|---|---|
hashMerkleRoot | The transactions in this block’s body | “What is this block carrying?” |
acceptedIdMerkleRoot | The transaction ids this block accepts from its mergeset | “What did this block put into the ledger?” |
utxoCommitment | The full UTXO set once those acceptances are applied | “What does the ledger look like now?” |
The second one is the one without a blockchain analogue. A Kaspa block accepts transactions from blocks mined in parallel with it, not only from its own body, so what a block carries and what it settles are genuinely different sets and each needs its own commitment.
The third is what makes pruning possible. Because every block commits to the resulting UTXO set, a node that discarded old block data can still prove its current state is the one the DAG arrived at - it does not need the history to re-derive it.
The body
| Field | Holds |
|---|---|
transactions | An ordered array of Transaction objects |
That is the entire body. The ordering is not incidental - the first transaction must be the coinbase, and it is the one transaction in the system with no inputs.
The coinbase’s payload is structured rather than arbitrary:
| Field | Holds |
|---|---|
blueScore | u64 - the blue score of the block carrying it |
subsidy | u64 - the per-block subsidy in sompi |
scriptPublicKey | The payout lock this block’s miner nominates |
extraData | Arbitrary bytes - where pools stamp their own identifiers |
The payout lock is the part worth pausing on. It is not used to pay this block. It is the address recorded so that whichever later block merges this one can pay it - the mergeset coinbase mechanics in full.
What is computed rather than stored
| Value | Where it comes from |
|---|---|
| Block hash | A hash over the header. The identity of the block, the proof work was spent, and the commitment to its contents are the same 32 bytes. |
| Block level | Derived from how far under the target the proof-of-work hash landed. |
| Selected parent | The level-0 parent with the highest blueWork. GHOSTDAG picks it; no field names it. |
| Mergeset | The blocks in this block’s past that are not in its selected parent’s past. |
| Block mass | Summed from the body’s transactions, in three dimensions at once. |
The mass totals are what the block-size limits actually apply to:
| Limit | Value |
|---|---|
| Max compute mass | 500,000 grams |
| Max storage mass | 500,000 grams |
| Max transient mass | 1,000,000 grams - roughly 250 KB of block body, raised from 500,000 at Toccata |
There is no separate per-transaction ceiling in consensus. A transaction only has to fit in a block, which is why Fees & Mass is where the pricing story lives rather than here.
See Also: Blocks, Transaction Model