Skip to content

Commit 00be715

Browse files
Melvin Carvalhoclaude
andcommitted
Blog: add SVG diagrams (architecture, SwiftSync cancellation, 25 GB→32 B)
Three on-palette inline-SVG explainers: (1) the node-in-a-tab architecture (only the bridge is non-browser; WebTorrent is direct); (2) the SwiftSync cancellation — created/spent pairs vanish, survivors are the UTXO set in 32 bytes; (3) the 25 GB-vs-32-byte size contrast. Verified rendering in-browser. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 20329fb commit 00be715

1 file changed

Lines changed: 79 additions & 0 deletions

File tree

‎how-it-works.html‎

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@
2323
.foot { margin-top:48px; border-top:1px solid var(--bd); padding-top:18px; color:var(--mut); font-size:14px; }
2424
hr { border:0; border-top:1px solid var(--bd); margin:34px 0; }
2525
em { color:var(--fg); }
26+
figure { margin:30px 0; }
27+
figure svg { width:100%; height:auto; display:block; background:#fff; }
28+
figcaption { color:var(--mut); font-size:13.5px; font-family:var(--mono); text-align:center; margin-top:10px; line-height:1.5; }
29+
.svgtxt { font-family:var(--mono); }
2630
</style>
2731
</head>
2832
<body>
@@ -87,6 +91,46 @@ <h2>Building the node, one "but how do you…" at a time</h2>
8791
because the tab does the checking. With it, the tab reached the live tip — and caught blocks that were mined
8892
<em>while it was running</em>.</p>
8993

94+
<figure>
95+
<svg viewBox="0 0 680 290" class="svgtxt" role="img" aria-label="The node lives entirely in the browser tab. A thin WS-to-TCP bridge relays Bitcoin p2p to public peers; WebTorrent delivers the snapshot directly.">
96+
<defs>
97+
<marker id="ar" markerWidth="9" markerHeight="9" refX="7.5" refY="4.5" orient="auto"><path d="M0,0 L8,4.5 L0,9 z" fill="#5b6470"/></marker>
98+
<marker id="arb" markerWidth="9" markerHeight="9" refX="7.5" refY="4.5" orient="auto"><path d="M0,0 L8,4.5 L0,9 z" fill="#0969da"/></marker>
99+
</defs>
100+
<!-- tab -->
101+
<rect x="18" y="46" width="250" height="208" rx="12" fill="#fafbfc" stroke="#e6e8eb"/>
102+
<line x1="18" y1="76" x2="268" y2="76" stroke="#e6e8eb"/>
103+
<circle cx="36" cy="61" r="4" fill="#e6e8eb"/><circle cx="50" cy="61" r="4" fill="#e6e8eb"/><circle cx="64" cy="61" r="4" fill="#e6e8eb"/>
104+
<text x="156" y="66" text-anchor="middle" font-size="13" font-weight="700" fill="#16181d">Browser tab — the node</text>
105+
<g font-size="11" fill="#16181d" text-anchor="middle">
106+
<rect x="32" y="90" width="100" height="32" rx="6" fill="#fff" stroke="#e6e8eb"/><text x="82" y="110">consensus engine</text>
107+
<rect x="146" y="90" width="106" height="32" rx="6" fill="#fff" stroke="#e6e8eb"/><text x="199" y="110">UTXO · 32-byte Σ</text>
108+
<rect x="32" y="130" width="100" height="32" rx="6" fill="#fff" stroke="#e6e8eb"/><text x="82" y="150">WASM secp256k1</text>
109+
<rect x="146" y="130" width="106" height="32" rx="6" fill="#fff" stroke="#e6e8eb"/><text x="199" y="150">OPFS storage</text>
110+
</g>
111+
<text x="143" y="188" text-anchor="middle" font-size="11.5" fill="#1a7f37">every rule checked in the tab ✓</text>
112+
<text x="143" y="208" text-anchor="middle" font-size="10.5" fill="#5b6470">no server trusted</text>
113+
<!-- upper path: bridge -> peers -->
114+
<line x1="268" y1="110" x2="320" y2="110" stroke="#5b6470" marker-end="url(#ar)"/>
115+
<text x="385" y="73" text-anchor="middle" font-size="10" fill="#e8830c">the only non-browser piece ↓</text>
116+
<rect x="326" y="88" width="118" height="44" rx="8" fill="#fff" stroke="#e8830c" stroke-width="1.5"/>
117+
<text x="385" y="106" text-anchor="middle" font-size="11.5" font-weight="700" fill="#16181d">WS↔TCP bridge</text>
118+
<text x="385" y="121" text-anchor="middle" font-size="10" fill="#5b6470">≈40-line relay</text>
119+
<line x1="444" y1="110" x2="496" y2="110" stroke="#5b6470" marker-end="url(#ar)"/>
120+
<rect x="502" y="88" width="160" height="44" rx="8" fill="#fafbfc" stroke="#e6e8eb"/>
121+
<text x="582" y="106" text-anchor="middle" font-size="11.5" font-weight="700" fill="#16181d">public testnet4 peers</text>
122+
<text x="582" y="121" text-anchor="middle" font-size="10" fill="#5b6470">Bitcoin p2p</text>
123+
<text x="385" y="150" text-anchor="middle" font-size="10" fill="#5b6470">headers · blocks</text>
124+
<!-- lower path: webtorrent, direct -->
125+
<path d="M268,210 C 330,210 340,214 392,214" fill="none" stroke="#0969da" stroke-dasharray="4 3" marker-end="url(#arb)"/>
126+
<text x="330" y="203" font-size="9.5" fill="#0969da">WebRTC, direct</text>
127+
<rect x="398" y="192" width="264" height="46" rx="8" fill="#fff" stroke="#0969da" stroke-opacity=".5"/>
128+
<text x="530" y="211" text-anchor="middle" font-size="11.5" font-weight="700" fill="#16181d">WebTorrent swarm</text>
129+
<text x="530" y="227" text-anchor="middle" font-size="9.5" fill="#5b6470">snapshot + hints — browser-native, no relay</text>
130+
</svg>
131+
<figcaption>Everything that validates lives in the tab. The bridge is the one piece a browser can't be — a dumb<br>byte-relay that can withhold but never forge. The snapshot comes peer-to-peer, straight into the page.</figcaption>
132+
</figure>
133+
90134
<p><b>But the snapshot is in Bitcoin Core's format.</b> So you write a parser for it — Core's <code>dumptxoutset</code>
91135
v2 layout, with its compressed amounts and script encodings. The proof that the parser is correct is delicious:
92136
feed a real block's real signatures against coins decoded from the file, and they verify. A segwit signature
@@ -139,10 +183,45 @@ <h2>The punchline: 32 bytes</h2>
139183
end is a fingerprint of precisely the coins still unspent: the UTXO set, distilled to a constant
140184
<b>32 bytes</b>.</p>
141185

186+
<figure>
187+
<svg viewBox="0 0 680 132" class="svgtxt" role="img" aria-label="Add a hash for every created output, subtract one for every spent input. Created-then-spent coins cancel; the unspent survivors are the whole UTXO set, held as 32 bytes.">
188+
<defs><marker id="ar2" markerWidth="9" markerHeight="9" refX="7.5" refY="4.5" orient="auto"><path d="M0,0 L8,4.5 L0,9 z" fill="#5b6470"/></marker></defs>
189+
<text x="196" y="20" text-anchor="middle" font-size="10.5" fill="#5b6470">created, then later spent → cancels</text>
190+
<path d="M76,52 C 120,28 232,28 272,52" fill="none" stroke="#5b6470" stroke-opacity=".35" stroke-dasharray="3 3"/>
191+
<path d="M136,52 C 188,36 296,36 332,52" fill="none" stroke="#5b6470" stroke-opacity=".3" stroke-dasharray="3 3"/>
192+
<g font-size="12" font-weight="700" text-anchor="middle">
193+
<g opacity=".45"><rect x="54" y="52" width="44" height="30" rx="6" fill="#fff" stroke="#1a7f37"/><text x="76" y="72" fill="#1a7f37">+A</text></g>
194+
<g opacity=".45"><rect x="114" y="52" width="44" height="30" rx="6" fill="#fff" stroke="#1a7f37"/><text x="136" y="72" fill="#1a7f37">+B</text></g>
195+
<rect x="174" y="50" width="44" height="34" rx="6" fill="#eaf6ee" stroke="#1a7f37" stroke-width="1.6"/><text x="196" y="72" fill="#1a7f37">+C</text>
196+
<g opacity=".45"><rect x="250" y="52" width="44" height="30" rx="6" fill="#fff" stroke="#e8830c"/><text x="272" y="72" fill="#e8830c">−A</text></g>
197+
<g opacity=".45"><rect x="310" y="52" width="44" height="30" rx="6" fill="#fff" stroke="#e8830c"/><text x="332" y="72" fill="#e8830c">−B</text></g>
198+
</g>
199+
<text x="196" y="103" text-anchor="middle" font-size="9.5" fill="#1a7f37">never spent → survives</text>
200+
<line x1="362" y1="67" x2="416" y2="67" stroke="#5b6470" marker-end="url(#ar2)"/>
201+
<rect x="422" y="44" width="210" height="46" rx="9" fill="#fafbfc" stroke="#e6e8eb"/>
202+
<text x="527" y="64" text-anchor="middle" font-size="12" font-weight="700" fill="#16181d">= the unspent set · 32 bytes</text>
203+
<text x="527" y="80" text-anchor="middle" font-size="10" fill="#5b6470">two 128-bit lanes, always</text>
204+
</svg>
205+
<figcaption>Add a hash for every output created, subtract one for every input spent. Created-then-spent pairs<br>vanish; the survivors are the UTXO set — and the running total is always just 32 bytes.</figcaption>
206+
</figure>
207+
142208
<div class="big">We streamed the full testnet4 UTXO set — all <b>14,129,063 coins</b> — through this accumulator.
143209
Memory stayed flat at the size of the input, never the set. The set-state never left <b>32 bytes</b>.
144210
What was 25 GB is now a number you could write on a napkin.</div>
145211

212+
<figure>
213+
<svg viewBox="0 0 680 100" class="svgtxt" role="img" aria-label="25 gigabytes held in RAM versus a 32-byte number — the same set, the same guarantee.">
214+
<defs><marker id="ar3" markerWidth="9" markerHeight="9" refX="7.5" refY="4.5" orient="auto"><path d="M0,0 L8,4.5 L0,9 z" fill="#5b6470"/></marker></defs>
215+
<rect x="18" y="26" width="470" height="50" rx="8" fill="#fdecec" stroke="#cf222e" stroke-opacity=".55"/>
216+
<text x="253" y="48" text-anchor="middle" font-size="13" font-weight="700" fill="#16181d">25 GB held in RAM</text>
217+
<text x="253" y="65" text-anchor="middle" font-size="10.5" fill="#5b6470">14,129,063 coins · crashed a 24 GB heap</text>
218+
<line x1="494" y1="51" x2="526" y2="51" stroke="#5b6470" marker-end="url(#ar3)"/>
219+
<rect x="532" y="35" width="130" height="32" rx="8" fill="#eaf6ee" stroke="#1a7f37"/>
220+
<text x="597" y="55" text-anchor="middle" font-size="13" font-weight="700" fill="#1a7f37">32 bytes</text>
221+
</svg>
222+
<figcaption>The same set, the same guarantee against double-spends and forgeries. Only one of them fits in a tab.</figcaption>
223+
</figure>
224+
146225
<p>And it's not a trust shortcut. If a single block tried to spend a coin that never existed, or spend one twice,
147226
the sums wouldn't cancel — the final number wouldn't match the known commitment, and validation fails. The hints
148227
that make it fast (a tiny file marking which outputs survive — about <b>25 bytes per block</b>, a few megabytes

0 commit comments

Comments
 (0)