web3.path

PHASE 03 Distsys · ~3 hours

Distributed Systems

Blockchain is "a replicated log agreed on by mutually distrustful parties". You've built replicated logs (Postgres replicas, Kafka). The new variable is Byzantine — some replicas are lying.

Goal — intuition for CAP/FLP/BFT, understand why PoW and PoS exist, and spin up a small P2P mesh that gossips messages.

1. The models you already know

SystemFailure modelConsensus
Postgres replicationCrash-stop (nodes die, don't lie)None — primary decides
Kafka ISRCrash-stopLeader + ZooKeeper/KRaft
etcd / ConsulCrash-stopRaft
CassandraCrash-stop, partitionQuorum + vector clocks
EthereumByzantine (nodes lie)Nakamoto / Gasper (BFT)
Analogy — Raft is a tech company: there's a CEO (leader), if they die the board elects a new one. BFT is a group of rival warlords who won't trust a single leader; they need cryptographic proof and a majority vote.

2. CAP, FLP, and what you actually trade off

3. Byzantine Fault Tolerance (BFT)

The classic result: you can tolerate up to f malicious nodes if you have 3f + 1 total. Need a 2/3 supermajority to decide.

33 honest nodes + 1 attacker → safe 50 honest nodes + 25 attackers → NOT safe (attackers > 1/3)

Ethereum PoS (Gasper) is BFT-flavored: a block is finalized once 2/3 of staked ETH attests to it. Reverting requires either (a) burning 1/3 of the stake or (b) breaking the crypto. This is "economic finality".

4. Proof of Work vs Proof of Stake

PoW (Bitcoin)PoS (Ethereum post-Merge)
Sybil defenseHardware + electricityLocked-up capital (stake)
Block producerFirst to find a nonceRandom committee from stakers
FinalityProbabilistic (~6 blocks)Deterministic (~2 epochs, ~13 min)
EnergyHighLow
Attack costRent hashrateBuy & stake 1/3+ of ETH
Analogy — PoW is a lottery where the ticket is a graphics card. PoS is collateralized voting: liars get their stake slashed, which is the same idea as a security deposit on a rental apartment.

5. P2P networking: gossip and DHTs

Ethereum's execution layer uses devp2p; the consensus layer uses libp2p with gossipsub. Gossip = each node tells k neighbors about a new message; with k=8 a 10k-node net saturates in ~4 hops.

# Gossip pseudo-code
on receive(msg):
  if seen(msg.id): return
  mark_seen(msg.id)
  for peer in pick_random(peers, k=8):
      send(peer, msg)
Why you care — propagation delay is why blocks can't come every 100ms. Ethereum's 12s slot is mostly a "let the network catch up" budget.

6. Project — simulate a P2P mesh

Spin up 10 Node.js processes, each listening on a different port. On boot, each picks 3 random peers and opens sockets. When you POST a message to any one, it should reach all 10 via gossip within a second. Log the hop count.

Starter (Node, ~50 lines)
// node peer.js <myPort> <peer1> <peer2> ...
const net = require('net'), http = require('http');
const crypto = require('crypto');
const [,, myPort, ...peers] = process.argv;
const seen = new Set();

const forward = (msg) => {
  for (const p of peers) {
    const s = net.connect(+p, 'localhost', () => s.end(JSON.stringify(msg)));
    s.on('error', () => {});
  }
};

net.createServer(sock => {
  let buf = '';
  sock.on('data', d => buf += d);
  sock.on('end', () => {
    const msg = JSON.parse(buf);
    if (seen.has(msg.id)) return;
    seen.add(msg.id);
    console.log(myPort, 'got', msg.id, 'hop', msg.hop);
    forward({ ...msg, hop: msg.hop + 1 });
  });
}).listen(+myPort);

http.createServer((req, res) => {
  const msg = { id: crypto.randomUUID(), hop: 0, body: 'hi' };
  seen.add(msg.id); forward(msg); res.end('ok');
}).listen(+myPort + 1000);

7. Docker + libp2p (optional)

For a "real" taste, run js-libp2p nodes in Docker containers on a shared network. This is exactly the stack Polkadot and IPFS use.

Quiz

Q. Your 100-validator PoS chain has 40 validators collude to censor transactions. What happens?
BFT safety needs >2/3 to violate; 40/100 can censor (liveness attack) but can't finalize a fraudulent block.
← Phase 2Phase 4: Blockchain Fundamentals →