Reading — step 1 of 5
Read
Transactions
Bitcoin has no account balances. Seriously — nowhere in the chain is there a row that says "Alice: 1.7 BTC." What exists is a set of UTXOs (Unspent Transaction Outputs): discrete, indivisible chunks of value, each locked to an owner. A transaction consumes some UTXOs whole as inputs and mints fresh UTXOs as outputs. Your "balance" is a fiction your wallet computes by summing the UTXOs it can spend.
Tx1 outputs: u0: 0.5 BTC -> alice u1: 1.5 BTC -> bob
Tx2: inputs: (Tx1, u0) consumes alice's 0.5, entirely
outputs: 0.3 -> charlie, 0.19 -> alice (change back to herself)
0.01 left over = miner fee
Three rules to internalize:
- Inputs are consumed whole. There is no partial spend. Paying 0.3 from a 0.5 UTXO means sending 0.3 to the payee and the remainder back to yourself as a change output — forget the change and the difference silently becomes miner fee (people have burned fortunes this way).
- Fee is implicit. No fee field exists:
fee = sum(inputs) − sum(outputs). Conservation is the core validity rule — outputs may not exceed inputs, or money is created from nothing. - New coins enter only via the coinbase — the special first transaction of each block with no inputs, paying the miner the block subsidy plus all the block's fees. In this exercise,
MINTplays that role.
To spend a UTXO in real Bitcoin you present an ECDSA signature over the spending transaction, made with the private key matching the output's lock — proving authorization for this exact transaction, which is why a relayed signature can't be reused to pay someone else. This exercise abstracts signatures to a claimed-owner check (u1:alice fails with wrong_owner if u1 belongs to someone else); the real thing arrives in the ECDSA lesson.
UTXO vs. accounts
Ethereum chose the other model: addresses have mutable balances, transactions debit and credit them. Accounts make state trivial to query and contracts natural, but need a per-sender nonce so a payment can't be replayed. UTXOs make replay structurally impossible (the input is gone after one spend), validate in parallel nicely (each spend touches distinct objects), and offer better privacy on paper — every payment can use fresh addresses — though address reuse and chain analysis defeat much of it in practice. Neither is "correct"; it's a data-model trade-off you should be able to argue both ways.
Your exercise
Implement the ledger: MINT <id> <amount> <owner>, TX <inputs> -> <outputs> (inputs id:owner, outputs id:amount:owner), BALANCE <owner>. A valid TX prints OK fee=<n>; invalid ones print BAD double_spend (input already consumed), BAD wrong_owner (claimed owner mismatch), or BAD insufficient (outputs exceed inputs) — exact lowercase tokens, underscores included. The graded trap is atomicity: validate the whole transaction before mutating anything. In one test, TX u1:alice -> u2:30,u3:65 succeeds and is followed by a second spend of u1 — which must print BAD double_spend and leave the world untouched. If your code marks inputs spent (or creates outputs) as it scans and then hits the rejection, the balances that follow are corrupted. Check everything, then commit. And remember the fee: MINT u1 100 then outputs of 30+65 prints OK fee=5, not OK fee=0.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…