learn-blockchain-with-phoebe / Practitioner session 2 of 10
Learn Blockchain with Phoebe · Practitioner session 2 of 10

Building the chain

Session 1 left you with a function and a warning: the digest of an entry's text is not the entry's block hash. Tonight the block arrives and the warning is cashed in. Six fields, five of which are joined and hashed, one of which is the result. A first block whose predecessor field is filled with zeros because there is nothing to fill it with. And six real hashes, from 608eba0870199b95 to 1e9fa1742f98715d, that you can reproduce on your own machine in a second.

🟡 Foundations Engineers and data people Anyone who will build or audit one 45 min
0-4 · Welcome 4-16 · The block 16-28 · Genesis and assembly 28-45 · Build-along and Q&A
Part 0

Where session 2 sits in the ten

The practitioner track works through one engine in a fixed order: the primitive, the structure, the attack, the cost of the attack, then the things built on top, and finally a bench where all of it runs at once. Session 2 is the structure. Everything from session 3 onwards attacks it, prices it, or extends it, so a shaky picture here shows up four times later.

Ten sessions, one engine Session 1 is done. Session 2 is the one you are reading. SESSION 1 · DONE Hashing, hands on SESSION 2 · HERE Building the chain SESSION 3 Breaking the chain SESSION 4 Mining SESSION 5 The cost of a rewrite SESSION 6 Merkle trees SESSION 7 Signatures and identity SESSION 8 Consensus SESSION 9 Smart contracts, mechanically SESSION 10 The chain bench 1 and 2 the primitive and the structure · 3 to 5 the attack and what it costs 6 and 7 proving and permitting · 8 and 9 agreeing and executing · 10 all of it at once Sessions 3, 5, 6 and 10 all reach back into the block object assembled tonight, so this is the one to get right.
🔍 Click to zoom - the order is not arbitrary. Each group needs the one before it, and the bench needs all of them.
Live = presented in the 45 minutes Self-study = the full depth, after class 🔍 Every diagram zooms

★ What you walk out with tonight. The six fields of a block, in order, and which five of them are hashed. The reason the genesis block's previous hash is a row of zeros rather than a computed value. And the Harbourgate register assembled in front of you, with the same six hashes any correct run produces.

Self-studyThe words that will keep coming back4 min▶
  • Block. One record: index, timestamp, data, prev, nonce, hash.
  • prev. The field holding the previous block's hash, all 64 characters of it.
  • Genesis block. Block 0, whose prev is 64 zero characters by convention.
  • Block hash. The digest of the whole assembled block. Not the digest of the entry text.
  • Nonce. A field that can change without changing what the block says. Idle at difficulty 0, and the subject of session 4.
  • Difficulty. How many leading zeros a block hash must have. Zero for the Harbourgate register.
  • Reproducible. Rebuilt from the same entries, the register gives the same six hashes, because the timestamps are fixed.
  • verify. Recompute every hash and check every link. Returns ok on the untampered register, and session 3 breaks it.
Part 1 · covers the block

Six fields, five of them hashed

12 min live

A block is an ordinary record. The interesting part is not any one field but the fact that the digest is taken over all five of the stored fields joined together, in a fixed order, rather than over the entry text on its own. The sixth field, the hash itself, is the output and cannot be an input.

Block 2 of the Harbourgate register, taken apart index 2 its position timestamp 1758001200 fixed, not a clock data Consignment 41 inspected, seal intact the entry itself prev 86202a0bab83df26 and 48 more characters nonce 0 idle at difficulty 0 The five stored fields, joined in that order with a separator between them 2 | 1758001200 | Consignment 41 inspected, seal intact | 86202a0bab83df26 and 48 more | 0 SHA-256 hash, the sixth field f325e5d96bee6a1d The hash is an output, so it cannot also be an input. Change the index, the timestamp, the entry, the previous hash or the nonce, and the digest is unrecognisable.
🔍 Click to zoom - the entry text is one of five things in the digest, which is why the block hash and the entry's own digest are different values.
Live Block 2 rehashed in this page with one field nudged. Block hashes are shown at their first 16 characters.

...

...
LiveWhy the position and the timestamp are inside the digest4 min▶

If only the entry text were hashed, two identical entries would produce identical digests and could be swapped, duplicated or reordered without leaving a mark. Putting the index in the digest fixes the block to a position. Putting the timestamp in fixes it to a moment. Putting the previous hash in fixes it to a history.

So a block hash is a statement about five things at once, and any claim you make from a matching hash inherits all five. That is stronger than people expect and still narrower than the word immutable suggests.

LiveThe nonce is doing nothing, on purpose3 min▶

Every block in the register carries a nonce of 0, and at difficulty 0 nothing ever changes it. It is in the record because a block needs one field that can be varied freely without altering what the block says, so that a hash can be searched for rather than merely computed.

Press the nonce button in the box above and watch the digest move completely for a change from 0 to 1 that alters nothing anybody would read. Session 4 turns that uselessness into the entire cost model of mining.

Self-studyField order is part of the format3 min▶

The five fields are joined in a fixed order with a separator before they are hashed. Any other order, or any other separator, produces different digests for the same register, which would still be perfectly self consistent and would disagree with every other implementation.

That is the unglamorous reason serialisation formats get specified to the byte in real systems. A verifier and a writer that disagree about field order do not fail loudly. They just never agree on a hash, and the register looks permanently tampered with.

Part 2 · covers genesis

The first block has nothing to point at

7 min live

Every block's previous hash field holds a computed value: the hash of the block above it, all 64 characters. Block 0 has no block above it, so the field is filled with 64 zero characters. That value was not computed by anything. It is a convention, and knowing that it is a convention is the point.

A computed field, and a field filled by agreement Blocks 1 to 5 · prev is computed block 1 hash 86202a0bab83df26 block 2 prev 86202a0bab83df26 The same 64 characters, copied into the next block when it is written. Shown here at 16 characters, as every hash in the register is. Block 0 · prev is a convention 64 zero characters, standing in for a block that does not exist: 0000000000000000000000000000000000000000000000000000000000000000 No hash function produced that value and none can check it. It is written there because the field has to hold something. What the zeros buy, and what they cannot. They make block 0 identifiable as the start of the register, so nobody can quietly graft an earlier history onto it. They cannot supply any confidence in block 0 itself. Whoever wrote it, and who was watching, is where that comes from.
🔍 Click to zoom - the left panel is arithmetic. The right panel is an agreement, and the difference between those two is worth saying out loud.
LiveWhy not hash something real into block 04 min▶

Some registers do exactly that. They put a newspaper headline, a court filing reference or a published document digest into the first block, so the start of the register can be dated from outside the system. It is a genuinely useful trick, and it does not change the structure at all, because that value still enters the block as data rather than as a verified link.

The zeros are the honest default. They announce that nothing precedes this block and make no claim at all about when it was written or by whom, which keeps the unanswered question visible instead of dressing it up.

LiveWhat a verifier does with the zeros3 min▶

Verification walks the register recomputing each block's hash from its own fields, and comparing each block's prev field against the hash of the block above. Block 0 has no block above it, so the link check simply does not run there, and only the hash check applies.

That asymmetry is worth holding on to. Block 0 is the one block whose position in history rests on nothing inside the register, which is why an attacker who can replace the whole register from block 0 onwards is a different threat from the one in session 3, and a much harder one to reason about.

Self-studyWhere the disagreement really is4 min▶

One camp holds that batching entries into blocks is an accident of network economics. If every entry simply carried the hash of the entry before it, you would get the same tamper evidence with less machinery, no block assembly, no batching delay, and a structure a reader can hold in their head. On this view the block is an optimisation that leaked into the conceptual model and then got taught as though it were the idea.

The other camp holds that the block is the unit of agreement, and that without it there is nothing to agree on. A batch is what participants vote on, what work is attached to, and what gets accepted or rejected as a whole. Remove the block and the tamper evidence survives but the mechanism for many parties converging on one history does not.

Both are right about different things, which is why the argument survives. Notice that a single organisation keeping one register has no need of the second camp's answer, and that is the shape most internal projects actually have.

Part 3 · covers the register

makeChain, and the six blocks it produces

9 min live

Assembly is a loop with one carried variable. Start with the previous hash set to 64 zeros, build a block from the next entry, hash it, store the hash, and carry that hash forward as the next block's previous hash. Six entries in, six blocks out, and the hashes below are what any correct run produces.

makeChain(entries, { difficulty: 0 }) BLOCK TIMESTAMP ENTRY HASH PREV IT RECORDS 0 1758000000 Harbourgate opens the register 608eba0870199b95 64 zeros, by convention 1 1758000600 Consignment 41 signed over to Pell and Sons 86202a0bab83df26 608eba0870199b95 2 1758001200 Consignment 41 inspected, seal intact f325e5d96bee6a1d 86202a0bab83df26 3 1758001800 Consignment 42 signed over to Ardwick Cold Store fc189d9dba7c4311 f325e5d96bee6a1d 4 1758002400 Consignment 41 released to the buyer e5526e6e0bbc1aed fc189d9dba7c4311 5 1758003000 Consignment 42 inspected, seal broken 1e9fa1742f98715d e5526e6e0bbc1aed Fixed timestamps: 1758000000, advancing 600 seconds a block. That is what makes these six hashes reproducible on your machine, and it is a property of this teaching model rather than of a real register, which takes its timestamps from a clock and never repeats a run.
🔍 Click to zoom - the hash column and the prev column hold the same values, shifted down one row. That shift is the chain.
Live makeChain running in this page. Add one entry at a time and watch the previous hash field carry forward.

...

...
LiveWhy the register is reproducible, and why that is artificial4 min▶

The timestamps are set by the loop, starting at 1758000000 and advancing 600 seconds per block, so rebuilding the register from the same six entries produces the same six hashes every time. That is what lets a number in a diagram be checked against a number on your screen, and it is the only reason these pages can quote hashes at all.

A real register reads a clock, so two runs never agree, and the hashes are unique to the moment. Take the reproducibility here as a property of the teaching model. Everything else on this page survives the switch to a real clock. That one does not.

LiveThe loop is four lines, and one of them is the whole idea3 min▶

Build a block from the entry. Set its prev to the carried value. Hash it. Carry that hash forward. The third and fourth lines are ordinary; the second is the structure. Everything sessions 3 and 5 do is an attempt to survive that one assignment being wrong for a single block.

Notice also what the loop does not do. It does not check the entry, it does not ask who submitted it, and it does not compare it with anything. Those are sessions 7 and beyond, and none of them are supplied by the arrangement you are looking at.

Self-studyDifficulty 0 is a deliberate choice here3 min▶

Assembly takes a difficulty option, and the register uses 0, meaning each block's hash is computed once and accepted. Every structural property on this page holds exactly as it would at any difficulty: the fields, the link, the genesis convention, the reproducibility.

Raising the difficulty changes one thing only, which is how much a block costs to produce and therefore to redo. It is worth seeing the structure work with no cost attached at all, so that when session 4 attaches one you can say precisely what it bought.

Part 4 · build-along

Assemble it yourself

17 min hands on

Console open on this page. The same six entries, the same engine, and the hashes in the table above are what you should get back.

Consolevar ENTRIES = [ "Harbourgate opens the register", "Consignment 41 signed over to Pell and Sons", "Consignment 41 inspected, seal intact", "Consignment 42 signed over to Ardwick Cold Store", "Consignment 41 released to the buyer", "Consignment 42 inspected, seal broken" ]; var chain = CHAIN.makeChain(ENTRIES, { difficulty: 0 }); chain.map(function (b) { return b.index + " " + b.hash.slice(0, 16); }) chain[0].prev CHAIN.hashBlock(chain[2]) === chain[2].hash CHAIN.verify(chain).ok

Check the hash before you read the chain

Run CHAIN.selfTest().ok first, as in session 1. Then build the chain and compare the six truncated hashes with the table above. If the first one is not 608eba0870199b95, stop and find out why before going further.

Look at block 0's prev

Print chain[0].prev and count the characters. Then say, out loud, which part of the engine decided that value and what would happen if a second register used a different convention.

Rehash a block and compare

CHAIN.hashBlock(chain[2]) === chain[2].hash should be true. That single comparison is what verification does six times, and it is the whole of tamper detection before an attacker gets clever.

Prove the entry digest is not the block hash

Compare CHAIN.sha256(chain[2].data) with chain[2].hash. Two different values for the same entry. Write one sentence explaining why, using the word fields, and keep it for session 3.

Move the clock by one second

Copy block 4, add 1 to its timestamp, and rehash it. Count how many of the 64 characters moved for a change nobody in a cold store would notice. Then say what that implies for any register whose timestamps come from more than one machine.

Break a link on purpose

Set chain[3].prev to block 1's hash instead of block 2's and run CHAIN.verify(chain). Read what comes back, including which index it names. Session 3 starts from exactly this output, with an attacker who is trying to make it go away.

The habit to keep. When you are handed a register, recompute one block's hash from its own fields before you believe anything else about it. If that single check fails, no argument about consensus, mining or governance is worth having yet.

Check yourself

Three questions

What is a block's hash taken over?

Five stored fields go in and the hash comes out, which is why the sixth field cannot be one of the inputs. It also explains why the digest of "Consignment 41 inspected, seal intact" is not block 2's hash: the entry is one of five things in the digest, not the only thing.

The genesis block's prev field holds 64 zero characters. Where did that value come from?

The hash of the empty string is e3b0c442 and so on, and it is not what sits in block 0. The zeros are written there because the field has to hold something and there is nothing to put in it. That makes block 0 identifiable as the start, and supplies no confidence in block 0 itself.

Rebuilding the Harbourgate register from the same six entries gives the same six hashes every time. Why, and what should you conclude about real registers?

Timestamps start at 1758000000 and advance 600 seconds per block, which is what lets a printed hash be checked against your screen. It is a property of the teaching model. Every other property on this page survives a real clock; this one does not.

Before session 3

Homework

Twenty five minutes, writing a register of your own rather than reading one.

Build a register from your own records

Take six real rows from a log or audit trail you work with and assemble them the way the loop does: fixed timestamps, prev carried forward, 64 zeros in the first block. Confirm your own verification passes on the result.

Decide what your fields are

Write down which fields you chose to hash and which you left out, and why. Bring the list to session 3, which attacks it. Anything you left out of the digest is something an attacker can change for free, and that is the sentence worth arriving with.

Covered tonight

What this session covers

✓The block as a recordindex, timestamp, data, prev, nonce and hash, and which five of them are hashed.
✓The hash covers the whole assembled blockNot the entry text, which is why block 2's hash is f325e5d96bee6a1d and the entry's own digest is not.
✓The genesis convention64 zero characters in block 0's prev, written by agreement rather than computed by anything.
✓makeChain and the six block registerAssembled live, from 608eba0870199b95 to 1e9fa1742f98715d, with fixed timestamps and difficulty 0.
◐Reading verify() when it failsNamed tonight in the last build-along step. Worked through in session 3.
◐What the nonce is forIdle at difficulty 0 and demonstrated as a free variable. Session 4 gives it a job.
○Who is allowed to add an entryNothing in the assembly loop asks. Signatures and identity are session 7.
○Named commercial chains and their roadmapsThey move faster than a course can, and none of the mechanism on this page depends on which one.
Take this with you

Cheat sheet

The block. index, timestamp, data, prev, nonce, hash.
Hashed. The first five, joined in a fixed order. The sixth is the result.
The trap. The digest of an entry's text is not that block's hash, and never will be.
Genesis. Block 0's prev is 64 zero characters, by convention rather than by computation.
The loop. Build, set prev to the carried hash, hash, carry forward. One line of the four is the idea.
The register. Six entries, difficulty 0, 608eba0870199b95 first and 1e9fa1742f98715d last.
Reproducible. Timestamps from 1758000000, 600 seconds apart. True here, not true of a real clock.
The one check. Rehash a block from its own fields and compare. Everything else comes after that passes.