Capstone: Bitcoin-Lite Node
Work through examples, try the reasoning yourself, then put it into practice.
View SlidesBefore you start
- Explain serialization, UTXOs, scripts, Merkle commitments, PoW, and parent-relative branch state.
What you will practice
- Build a node whose accepted blocks conserve value and preserve distinct rewards.
- Validate on private parent views and publish a consistent tip with its UTXO state.
- Explain integration changes from the isolated exercise contracts.
In this lesson
Lesson
7 min readBuild a Node Whose State You Can Explain
Your deliverable is an in-memory node that admits transactions, mines blocks, validates competing branches, and changes its active UTXO view when a longer valid branch arrives. The achievement is a coherent state machine. It is not a networked Bitcoin implementation: networking, disk recovery, changing difficulty, production script rules, and mempool package selection are outside this capstone.
Check readiness by following one output
Before opening the editor, explain how a serialized transaction becomes a txid,
how (txid,index) identifies a live output, how a signature authorizes a spend,
and how a Merkle root binds the transaction list. If one link is unclear, revisit
that module before trying to debug the whole node.
The capstone statement is the integration
contract. Earlier exercises intentionally simplify different boundaries. This
one uses actual serialized sizes rather than the assembly exercise's fixed
100-byte coinbase estimate. Its TxIn has no Sequence field, so serialize every
sequence as ffffffff. It uses the course's raw hash byte order, unsigned compact
targets, P-256 signatures, and HASH256 script operation. These conventions are
not a promise of compatibility with Bitcoin peers.
Give each operation one publication point
SubmitTx checks against confirmed outputs at the current tip, verifies scripts
and value sums, and rejects conflicts with transactions already in the mempool.
It stores a candidate; it does not spend confirmed outputs. An unconfirmed child
whose input exists only in the mempool is rejected in this version.
ReceiveBlock checks the header/body commitment, fixed difficulty, serialized
transaction-byte budget, parent identity, and every transaction. Build a private
UTXO view of the candidate's parent. Apply and validate there. Only a fully
valid candidate may enter the block index, and only a strictly longer valid branch
replaces the active tip and UTXO map. Equal length preserves the current tip.
The constructor accepts one trusted genesis. Subsequent blocks must extend an indexed parent; receiving another zero-parent block must not create a second root. If a header is already known, compare the supplied body with the stored body before returning idempotent success. A known header does not excuse a caller that supplies different transactions.
Integrate signatures without circular input
The txid hashes the full serialized transaction, including input scripts. A signature cannot sign a digest that includes that same signature. For this capstone, calculate:
signature digest = doubleSHA256(
transaction serialized with every ScriptSig empty
|| inputIndex as uint32 little-endian
|| referenced output's ScriptPubKey
)
The sequence remains ffffffff; output scripts remain present. This deliberately
replaces the standalone script exercise's externally supplied transaction ID.
Use the exact formula when creating test signatures and when verifying them.
A signature copied onto a transaction with a changed output must fail.
Work a complete accounting trace
Genesis G creates G:0=50 for Alice. Transaction T spends it, pays Bob 43, and
pays Alice change 5. Both outputs are covered by its signature; its fee is
50 - (43 + 5) = 2. Suppose the configured subsidy is 10.
| Event | Confirmed UTXOs | Mempool | Reason |
|---|---|---|---|
| Construct node | G:0=50 | empty | Trusted starting state |
| Admit T | G:0=50 | T | Admission does not spend confirmed money |
| Mine and accept A1 | T:0=43, T:1=5, C1:0=12 | empty | T spends G:0; coinbase collects 10+2 |
| Mine empty A2 | prior outputs plus C2:0=10 | empty | Another subsidy; no transaction fees |
The totals are 50, 60, then 70. Fees move value to the miner; subsidy increases
the total. A block may claim less than its allowed reward, but never more.
Checked arithmetic must cover input sums, output sums, cumulative fees, and
subsidy + fees. A wrapped sum is not a valid small reward.
In this course's mined coinbase, use LockTime = new block height. LockTime has
no scheduling effect here; it changes the serialized identity. Reusing identical
empty-input transactions at every height would reuse the same outpoint and
silently overwrite an earlier unspent reward. Block validation also rejects
repeated txids within a block and collisions with live parent outpoints. Real
Bitcoin's duplicate-transaction concern is documented in
BIP 30; our
height-in-LockTime convention is specific to this teaching API.
Assemble with the actual budget
Reserve the serialized size of the coinbase you will actually emit. Its value
and LockTime fields have fixed widths. Compare fee rates by exact cross-products,
then fee descending and txid bytes ascending. Integer division would turn both
10/62 and 11/160 into zero, incorrectly preferring the larger absolute fee.
Maintain a private evolving UTXO view for selected ordinary transactions. Reject an oversized or invalid candidate without consuming inputs. Skip a transaction if adding its fee would overflow the reward. After ordinary transactions pass, credit coinbase. Disallow spending the new coinbase within that same block; its reward cannot fund fees used to justify itself. This course has no further coinbase-maturity delay.
With a 21-byte coinbase, a 62-byte transaction fits a budget of 83 exactly. A hardcoded 100-byte reservation would incorrectly exclude it. Conversely, a long miner script can make coinbase larger than an assumed reservation. Include these cases in tests rather than relying on a generous default budget.
Complete a competing-branch trace
Your active path is G → A1 → A2. An alternative B1 spends G:0 differently from A1. Predict which UTXO view validates B1, whether B2 replaces A2 at equal height, and what must happen when valid B3 arrives.
Check branch-relative reasoning
Validate B1 against G, where G:0 still exists. The fact that A1 spent it on the active branch does not invalidate B1. Store valid B1 and B2, but keep A2 at the equal-height tie. B3 makes the B branch longer, so rebuild or undo/reapply to the B3 state and publish that tip with its matching UTXOs. Remove confirmed mempool transactions and candidates whose inputs or scripts are invalid under the new view. This version does not resurrect disconnected transactions automatically.
A simple correct baseline replays the selected path from trusted genesis into a
fresh map. The public Undo map records spent outputs for the active path, but
recording undo entries is not evidence that replay has become an incremental
undo algorithm. If you optimize later, process transactions in reverse when
disconnecting: an output created and consumed inside the block must not survive
rollback. Keep replay as an oracle for the optimized implementation.
Build and review in observable stages
First verify byte/digest vectors and script checks without a Node. Then construct genesis and admit one valid spend plus one conflicting spend. Next mine one block and reconcile its total value. Mine a second empty block and ensure both rewards still exist. Finally send sibling branches and an invalid longer candidate; rejection must preserve Tip, UTXO, Mempool, Blocks, Height, and Undo.
Implement Bitcoin-Lite Node. Review the artifact using invariants, not only green tests: every live output is identified unambiguously, no input is spent twice on one branch, value changes match subsidy and fees, and active state belongs to the displayed tip. After the checkpoint, explain one counterexample to validating against the active tip and one to integer-divided fee sorting without reopening this lesson. Those explanations test whether you can debug the system you built; the checkpoint alone does not establish that ability.
Practice sequence
Use the order below for a guided path, or choose an item to revisit. A pass records a successful attempt; try a fresh example later to check what you can reconstruct.
- Step 1
Bitcoin-Lite Node
- Step 2
Capstone Checkpoint
Trace exact representations and state, diagnose a failure, and return to its specific explanation.