Open this lesson in your favourite AI. It'll walk you through the why, explain the demo, and quiz you on the try-it list.
Before smart contracts, validators, or wallets, a blockchain is one data structure: a singly-linked list where each node embeds the hash of the previous node. Understanding this structure in 20 lines of Python is the single best anchor for every later concept — gas, finality, reorgs, light clients all trace back to the fact that each block commits to its predecessor.
We'll build three blocks where each block's content includes the SHA-256 of the previous block. The output will show that block 2's prev equals the hash of block 1's content, and so on. Run it, then inspect the dict — you're looking at the core abstraction behind every chain in production.
prev field. Hash block 2 by hand (paste its JSON into an online SHA-256 tool) and verify you get the same value.data in the list — notice nothing recomputes downstream because we stored the hash already. This is the key insight.prev. Now you're one step away from validating the chain on reload.Use these three in order. Each builds on the one before.
What is SHA-256 in one sentence, and why is it 'deterministic but one-way'? Give me the hex length of its output and one real-world use.
Walk through what changes in my three-block Python script if I modify block 1's data AFTER the chain is built. Which fields become inconsistent, and how would a validator detect it in O(n)?
Extend the script so each block carries a list of transactions instead of a single string, and compute a Merkle root over them before hashing the block. Show me the smallest change and explain why real chains prefer a Merkle root to a flat list hash.
// main.go
package main
import (
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"log"
"sort"
)
type Block struct {
I int `json:"i"`
Data string `json:"data"`
Prev string `json:"prev"`
}
// h deterministically serialises the block (keys sorted) and returns its SHA-256 hex digest.
func h(b Block) string {
// json.Marshal already sorts struct fields by field-tag order, but we want
// explicit key-sorted output identical to Python's json.dumps(sort_keys=True).
m := map[string]interface{}{"i": b.I, "data": b.Data, "prev": b.Prev}
keys := []string{"data", "i", "prev"}
sort.Strings(keys)
ordered := make([]byte, 0, 64)
ordered = append(ordered, '{')
for idx, k := range keys {
if idx > 0 {
ordered = append(ordered, ',')
}
kb, _ := json.Marshal(k)
vb, _ := json.Marshal(m[k])
ordered = append(ordered, kb...)
ordered = append(ordered, ':')
ordered = append(ordered, vb...)
}
ordered = append(ordered, '}')
sum := sha256.Sum256(ordered)
return hex.EncodeToString(sum[:])
}
func main() {
blocks := []Block{{I: 0, Data: "genesis", Prev: "0000000000000000000000000000000000000000000000000000000000000000"}}
for i := 1; i < 4; i++ {
prevHash := h(blocks[len(blocks)-1])
blocks = append(blocks, Block{I: i, Data: fmt.Sprintf("tx-%d", i), Prev: prevHash})
}
for _, b := range blocks {
prev := b.Prev
if len(prev) > 12 {
prev = prev[:12] + "\u2026"
}
fmt.Println(b.I, b.Data, "prev="+prev)
}
_ = log.Writer() // satisfy import
}
go run main.go