Below is a tiny blockchain. Every block extends one before it, and the network always trusts the longest valid chain. Mine on the honest fork or on an attacker fork and watch which history the network accepts.
<p class="hint">A genesis block is shared. Two forks compete. Mine a block on the <b>honest</b> chain or the <b>attacker</b> chain. The network always trusts the <b>longest valid chain</b> — to rewrite history, the attacker must overtake, not just catch up.</p>
<div class="rows">
<div class="row">
<span class="tag honest">Honest</span>
<div id="honest" class="chain"></div>
</div>
<div class="row">
<span class="tag attacker">Attacker</span>
<div id="attacker" class="chain"></div>
</div>
</div>
<div class="status" id="status">The network follows the longest chain.</div>
<div class="btns">
<button id="mineH" type="button">Mine honest block</button>
<button id="mineA" type="button" class="alt">Mine attacker block</button>
<button id="auto" type="button" class="ghost">Auto-mine 10 rounds (honest majority)</button>
<button id="reset" type="button" class="ghost">Reset</button>
</div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.hint { font-size: .9rem; color: #444; margin: 0 0 .8rem; line-height: 1.45; }
.rows { display: flex; flex-direction: column; gap: .6rem; }
.row { display: flex; align-items: center; gap: .5rem; }
.tag { font: 700 11px system-ui; padding: .2rem .5rem; border-radius: 6px; width: 72px; text-align: center; }
.tag.honest { background: #1d3557; color: #fff; }
.tag.attacker { background: #e63946; color: #fff; }
.chain { display: flex; gap: 4px; flex-wrap: wrap; align-items: center; min-height: 40px; }
.blk { width: 38px; height: 38px; display: flex; align-items: center; justify-content: center;
font: 700 13px ui-monospace, monospace; border-radius: 8px; border: 2px solid; }
.blk.gen { background: #e8eef3; color: #1d3557; border-color: #cdd9e3; }
.blk.h { background: #dbe4ee; color: #1d3557; border-color: #1d3557; }
.blk.a { background: #fbdde0; color: #c92f3c; border-color: #e63946; }
.arrow { color: #9aa3ad; font-weight: 700; }
.win { box-shadow: 0 0 0 3px #0a7d3344; }
.status { font-size: 1rem; font-weight: 600; margin: .7rem 0 .5rem; min-height: 1.4em; }
.status.ok { color: #0a7d33; }
.status.bad { color: #c92f3c; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; }
button { font: 600 13px system-ui; padding: .45rem .8rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button.alt { background: #e63946; border-color: #c92f3c; }
button.ghost { background: #fff; color: #1d3557; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Nakamoto consensus: each fork is a chain of blocks built on a shared genesis.
// The network rule is dead simple — follow the LONGEST valid chain. To rewrite a
// confirmed block the attacker must make their chain strictly longer than the
// honest one, i.e. out-work the rest of the network. With an honest majority of hash power that almost never happens: the honest lead grows over time.
let honest = 0; // blocks mined on the honest fork (beyond genesis)
let attacker = 0; // blocks mined on the attacker fork
function render(){
drawChain('honest', honest, 'h');
drawChain('attacker', attacker, 'a');
const hLen = honest + 1, aLen = attacker + 1; // +1 for shared genesis
const hWin = hLen >= aLen;
mark('honest', hWin);
mark('attacker', !hWin);
const s = document.getElementById('status');
if (attacker === 0 && honest === 0) {
s.textContent = 'The network follows the longest chain.'; s.className = 'status';
} else if (hWin) {
s.textContent = fmt('✓ Honest chain leads %hLen% to %aLen% — the network keeps it. Attacker must OVERTAKE.', { hLen: hLen, aLen: aLen });
s.className = 'status ok';
} else {
s.textContent = fmt('✗ Attacker chain is longer (%aLen% vs %hLen%) — history was rewritten! This needs majority hash power.', { aLen: aLen, hLen: hLen });
s.className = 'status bad';
}
}
function drawChain(id, n, cls){
const el = document.getElementById(id); el.innerHTML = '';
addBlk(el, 'gen', 'G');
for (let i = 1; i <= n; i++){ addArrow(el); addBlk(el, cls, '#' + i); }
}
function addBlk(el, cls, label){ const d = document.createElement('div'); d.className = 'blk ' + cls; d.textContent = label; el.appendChild(d); }
function addArrow(el){ const a = document.createElement('span'); a.className = 'arrow'; a.textContent = '→'; el.appendChild(a); }
function mark(id, win){ document.getElementById(id).querySelectorAll('.blk').forEach(b => b.classList.toggle('win', win)); }
document.getElementById('mineH').addEventListener('click', () => { honest++; render(); });
document.getElementById('mineA').addEventListener('click', () => { attacker++; render(); });
document.getElementById('reset').addEventListener('click', () => { honest = 0; attacker = 0; render(); });
// Honest majority (~70% of hash power) for 10 rounds: each round one block is found,
// and it lands on the honest fork with probability 0.7. The honest lead grows.
document.getElementById('auto').addEventListener('click', () => {
for (let r = 0; r < 10; r++){ if (Math.random() < 0.7) honest++; else attacker++; }
render();
console.log(fmt('After auto-mine: honest=%honest% attacker= %attacker% | network follows %v%', { honest: honest, attacker: attacker, v: honest >= attacker ? 'HONEST' : 'ATTACKER' }));
});
render();
<p class="hint">A genesis block is shared. Two forks compete. Mine a block on the <b>honest</b> chain or the <b>attacker</b> chain. The network always trusts the <b>longest valid chain</b> — to rewrite history, the attacker must overtake, not just catch up.</p>
<div class="rows">
<div class="row">
<span class="tag honest">Honest</span>
<div id="honest" class="chain"></div>
</div>
<div class="row">
<span class="tag attacker">Attacker</span>
<div id="attacker" class="chain"></div>
</div>
</div>
<div class="status" id="status">The network follows the longest chain.</div>
<div class="btns">
<button id="mineH" type="button">Mine honest block</button>
<button id="mineA" type="button" class="alt">Mine attacker block</button>
<button id="auto" type="button" class="ghost">Auto-mine 10 rounds (honest majority)</button>
<button id="reset" type="button" class="ghost">Reset</button>
</div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.hint { font-size: .9rem; color: #444; margin: 0 0 .8rem; line-height: 1.45; }
.rows { display: flex; flex-direction: column; gap: .6rem; }
.row { display: flex; align-items: center; gap: .5rem; }
.tag { font: 700 11px system-ui; padding: .2rem .5rem; border-radius: 6px; width: 72px; text-align: center; }
.tag.honest { background: #1d3557; color: #fff; }
.tag.attacker { background: #e63946; color: #fff; }
.chain { display: flex; gap: 4px; flex-wrap: wrap; align-items: center; min-height: 40px; }
.blk { width: 38px; height: 38px; display: flex; align-items: center; justify-content: center;
font: 700 13px ui-monospace, monospace; border-radius: 8px; border: 2px solid; }
.blk.gen { background: #e8eef3; color: #1d3557; border-color: #cdd9e3; }
.blk.h { background: #dbe4ee; color: #1d3557; border-color: #1d3557; }
.blk.a { background: #fbdde0; color: #c92f3c; border-color: #e63946; }
.arrow { color: #9aa3ad; font-weight: 700; }
.win { box-shadow: 0 0 0 3px #0a7d3344; }
.status { font-size: 1rem; font-weight: 600; margin: .7rem 0 .5rem; min-height: 1.4em; }
.status.ok { color: #0a7d33; }
.status.bad { color: #c92f3c; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; }
button { font: 600 13px system-ui; padding: .45rem .8rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button.alt { background: #e63946; border-color: #c92f3c; }
button.ghost { background: #fff; color: #1d3557; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Nakamoto consensus: each fork is a chain of blocks built on a shared genesis.
// The network rule is dead simple — follow the LONGEST valid chain. To rewrite a
// confirmed block the attacker must make their chain strictly longer than the
// honest one, i.e. out-work the rest of the network. With an honest majority of hash power that almost never happens: the honest lead grows over time.
let honest = 0; // blocks mined on the honest fork (beyond genesis)
let attacker = 0; // blocks mined on the attacker fork
function render(){
drawChain('honest', honest, 'h');
drawChain('attacker', attacker, 'a');
const hLen = honest + 1, aLen = attacker + 1; // +1 for shared genesis
const hWin = hLen >= aLen;
mark('honest', hWin);
mark('attacker', !hWin);
const s = document.getElementById('status');
if (attacker === 0 && honest === 0) {
s.textContent = 'The network follows the longest chain.'; s.className = 'status';
} else if (hWin) {
s.textContent = fmt('✓ Honest chain leads %hLen% to %aLen% — the network keeps it. Attacker must OVERTAKE.', { hLen: hLen, aLen: aLen });
s.className = 'status ok';
} else {
s.textContent = fmt('✗ Attacker chain is longer (%aLen% vs %hLen%) — history was rewritten! This needs majority hash power.', { aLen: aLen, hLen: hLen });
s.className = 'status bad';
}
}
function drawChain(id, n, cls){
const el = document.getElementById(id); el.innerHTML = '';
addBlk(el, 'gen', 'G');
for (let i = 1; i <= n; i++){ addArrow(el); addBlk(el, cls, '#' + i); }
}
function addBlk(el, cls, label){ const d = document.createElement('div'); d.className = 'blk ' + cls; d.textContent = label; el.appendChild(d); }
function addArrow(el){ const a = document.createElement('span'); a.className = 'arrow'; a.textContent = '→'; el.appendChild(a); }
function mark(id, win){ document.getElementById(id).querySelectorAll('.blk').forEach(b => b.classList.toggle('win', win)); }
document.getElementById('mineH').addEventListener('click', () => { honest++; render(); });
document.getElementById('mineA').addEventListener('click', () => { attacker++; render(); });
document.getElementById('reset').addEventListener('click', () => { honest = 0; attacker = 0; render(); });
// Honest majority (~70% of hash power) for 10 rounds: each round one block is found,
// and it lands on the honest fork with probability 0.7. The honest lead grows.
document.getElementById('auto').addEventListener('click', () => {
for (let r = 0; r < 10; r++){ if (Math.random() < 0.7) honest++; else attacker++; }
render();
console.log(fmt('After auto-mine: honest=%honest% attacker= %attacker% | network follows %v%', { honest: honest, attacker: attacker, v: honest >= attacker ? 'HONEST' : 'ATTACKER' }));
});
render();
Notice the asymmetry. To overwrite a confirmed block, the attacker must not only catch up but overtake the honest chain — building more blocks than the rest of the network combined. As long as honest miners hold the majority of the work, the attacker falls further behind with every round. Honesty is not enforced by a referee; it is simply the cheaper strategy.
Comments
Loading comments...