Repository navigation
Expand file tree
/
Copy pathtutorial.html
More file actions
221 lines (213 loc) · 11.2 KB
/
Copy pathtutorial.html
File metadata and controls
221 lines (213 loc) · 11.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>ChainDemo Tutorial</title>
<style>
:root {
--bg: #f5f6fa; --panel: #ffffff; --text: #1a1d29; --muted: #565d6a;
--accent: #3b5fe0; --border: #dde1e8; --code-bg: #eef0f6;
}
* { box-sizing: border-box; }
body {
margin: 0; background: var(--bg); color: var(--text);
font-family: system-ui, -apple-system, "Segoe UI", sans-serif; line-height: 1.6;
}
header {
padding: 1rem 2rem; border-bottom: 1px solid var(--border);
display: flex; justify-content: space-between; align-items: center;
}
header a { color: var(--accent); text-decoration: none; }
main { max-width: 800px; margin: 0 auto; padding: 1.5rem 2rem 4rem; }
h1 { font-size: 1.8rem; }
h2 { margin-top: 2.5rem; border-bottom: 1px solid var(--border); padding-bottom: 0.4rem; }
p { color: var(--text); }
code { font-family: ui-monospace, monospace; background: var(--code-bg); padding: 0.1rem 0.3rem; border-radius: 4px; }
pre {
background: var(--code-bg); border: 1px solid var(--border); border-radius: 8px;
padding: 1rem; overflow-x: auto; font-size: 0.85rem;
}
pre code { background: none; padding: 0; }
.step { background: var(--panel); border: 1px solid var(--border); border-radius: 8px; padding: 1rem 1.25rem; margin: 1rem 0; box-shadow: 0 1px 3px rgba(15, 23, 42, 0.06); }
.callout { border-left: 3px solid var(--accent); padding-left: 1rem; color: var(--muted); }
</style>
</head>
<body>
<header>
<strong>⛓ ChainDemo Tutorial</strong>
<a href="index.html">← Back to the app</a>
</header>
<main>
<h1>How ChainDemo Works</h1>
<p>
ChainDemo is a small, self-contained blockchain simulator. This page walks through
the four ideas that make a blockchain a blockchain — hashing, chaining, proof-of-work,
and tamper-evidence — and shows exactly where each one lives in this project's code.
</p>
<h2>1. What is a blockchain, really?</h2>
<p>
At its core, a blockchain is a list of records ("blocks") where each block stores a
cryptographic fingerprint (a <strong>hash</strong>) of its own contents, and also stores
the hash of the block before it. That second part is what turns a plain list into a
<em>chain</em>: change anything in an earlier block, and its hash changes, which breaks
the link the next block was pointing to.
</p>
<h2>2. Anatomy of a block</h2>
<p>Every block in <code>blockchain.js</code> is an instance of the <code>Block</code> class:</p>
<pre><code>class Block {
constructor(index, timestamp, transactions, previousHash = '') {
this.index = index;
this.timestamp = timestamp;
this.transactions = transactions; // the data this block stores
this.previousHash = previousHash; // the link to the prior block
this.nonce = 0; // explained in section 3
this.hash = this.calculateHash();
}
}</code></pre>
<p>
<code>transactions</code> is a plain array of <code>{ from, to, amount }</code> objects —
the "ledger entries" you create in the app's "Add Transaction" form.
</p>
<h2>3. Hashing and chaining</h2>
<p>
<code>calculateHash()</code> feeds every field of the block through SHA-256, a one-way
function: the same input always produces the same 64-character hash, but you can't work
backwards from a hash to guess the input, and changing even one character of the input
produces a completely different hash.
</p>
<pre><code>calculateHash() {
return crypto
.createHash('sha256')
.update(buildMiningInput(this.index, this.timestamp, this.transactions, this.previousHash, this.nonce))
.digest('hex');
}</code></pre>
<p>
<code>buildMiningInput</code> (<code>public/mining-input.js</code>) just concatenates those
same fields — <code>index + timestamp + JSON.stringify(transactions) + previousHash +
nonce</code> — but it's pulled out into its own tiny module so the server and the browser's
mining Web Worker build the exact same string to hash from the exact same code, instead of
two copies that could quietly drift apart.
</p>
<p>
Because <code>previousHash</code> is one of the ingredients, each block is cryptographically
stitched to the one before it. That's the "chain" in blockchain.
</p>
<h2>4. Proof-of-work mining — done live, in your browser</h2>
<p>
Anyone could compute a valid-looking hash instantly, so blockchains add an artificial
cost: a block's hash must start with a certain number of zero characters (the
<strong>difficulty</strong>). There's no shortcut to find such a hash except to keep
changing a throwaway number — the <strong>nonce</strong> — and re-hashing until you get
lucky. That repeated search is "mining."
</p>
<p>
In this app, that search doesn't happen on the server — it happens for real, in your
browser, inside a <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API">Web
Worker</a> (<code>public/miner-worker.js</code>), so the rest of the page stays responsive
while it churns:
</p>
<pre><code>while (true) {
const hash = sha256Hex(buildMiningInput(index, timestamp, transactions, previousHash, nonce));
attempts++;
if (hash.startsWith(target)) {
self.postMessage({ type: 'found', nonce, hash, attempts, elapsedMs });
return;
}
nonce++;
}</code></pre>
<p>
Cancelling a run doesn't work by sending the worker a message — a Web Worker is
single-threaded, so it can't read an incoming <code>{ type: 'cancel' }</code> message
until this tight loop yields, which only happens when it finds a result. Instead, the
<strong>✕ Cancel</strong> button calls <code>Worker.terminate()</code> from the main
thread — a hard stop that kills the worker immediately, no cooperation from the loop
required.
</p>
<p>
The worker uses a small hand-written SHA-256 implementation
(<code>public/sha256.js</code>) rather than the browser's built-in Web Crypto API — Web
Crypto's <code>subtle.digest()</code> is asynchronous (one Promise per hash), which is far
too slow for a tight search loop trying thousands of hashes a second. This hand-written
version is tested against Node's own <code>crypto</code> module for a range of inputs, so
it can never silently drift from what the server checks.
</p>
<p>
Since the browser is now the one doing the mining, the server can't just trust whatever
hash it's told. So the flow is: <code>POST /api/mine/start</code> hands the browser a
snapshot of what to mine against (the pending transactions, the previous hash, a fixed
timestamp, and the difficulty); the browser searches for a nonce and shows you every
step live; then <code>POST /api/mine/submit</code> sends back the nonce it found, and the
server <strong>recomputes the hash itself</strong> before accepting the block. A client
that lied about the nonce, or mined against stale pending transactions, gets rejected.
</p>
<p class="callout">
Try the difficulty slider in the app: each extra leading zero roughly multiplies the
number of attempts needed by 16, so mining visibly takes longer — and you can watch it
happen in the live mining console instead of just waiting.
</p>
<h2>5. Why tampering gets caught</h2>
<p>
The "tamper" control in the app lets you edit a mined transaction's amount directly —
on purpose, without recomputing that block's hash, exactly like an attacker rewriting
history would try to do. <code>isChainValid()</code> catches it by walking the chain
from the start, checking each block against its own recalculated hash and against its
neighbor's stored hash:
</p>
<pre><code>isChainValid() {
let brokenFrom = null;
for (let i = 1; i < this.chain.length; i++) {
const current = this.chain[i];
const previous = this.chain[i - 1];
const selfConsistent = current.hash === current.calculateHash();
const linkedToPrevious = current.previousHash === previous.hash;
if (!selfConsistent || !linkedToPrevious) {
brokenFrom = i;
break;
}
}
const invalidBlocks = brokenFrom === null
? []
: this.chain.slice(brokenFrom).map(block => block.index);
return { valid: invalidBlocks.length === 0, invalidBlocks };
}</code></pre>
<p>
The tampered block fails immediately because its stored data no longer matches its
stored hash. But notice what happens one block further downstream: that next block's
own data never changed, so recalculating <em>its</em> hash still matches what's stored
— the only thing that broke is the tampered block itself. If <code>isChainValid()</code>
only compared each block to its immediate neighbor, the "invalid" flag would stop
spreading right there, one hop past the tamper, even though everything after it now
sits on top of a corrupted link.
</p>
<p>
That's why the loop stops at the <em>first</em> broken block and marks
<strong>everything from that point to the end of the chain</strong> as invalid, rather
than only flagging blocks whose direct link is broken. Once one block in the chain
can't be trusted, nothing built on top of it can be trusted either — which is also why
rewriting history in a real blockchain means re-mining every block after your edit, not
just the one you changed.
</p>
<h2>6. Run it yourself</h2>
<div class="step">
<p><strong>1.</strong> Install dependencies:</p>
<pre><code>npm install</code></pre>
<p><strong>2.</strong> Start the server:</p>
<pre><code>npm start</code></pre>
<p><strong>3.</strong> Open <code>http://localhost:3000</code> in your browser.</p>
<p><strong>4.</strong> (Optional) Run the unit tests for the core logic:</p>
<pre><code>npm test</code></pre>
</div>
<h2>7. Using the GUI</h2>
<ul>
<li><strong>Add Transaction</strong> — fill in from/to/amount and submit; it joins the pending pool waiting to be mined.</li>
<li><strong>Difficulty slider</strong> — sets how many leading zeros the next mined block's hash must have (1–5).</li>
<li><strong>⛏ Start Mining</strong> — opens the live mining console: watch the nonce, the current hash attempt (color-coded against the target), attempts, elapsed time, and hashes/sec update in real time as your browser searches.</li>
<li><strong>✕ Cancel</strong> — stops an in-progress mining run immediately.</li>
<li><strong>Tamper</strong> — edit any mined transaction's amount inline to see the chain turn invalid.</li>
<li><strong>✓ Validate Chain</strong> — walks the chain block-by-block, pulsing each one while it's checked, then shows exactly where (and everything after where) it breaks.</li>
<li><strong>Stats bar</strong> — chain length, total transactions, current difficulty, and a live validity badge, always visible at the top of the page.</li>
</ul>
</main>
</body>
</html>