Selective Abort: 12s-Timeout in Commit/Reveal-Phase, ABORTED-State, Betrüger-Erkennung, protocol_start-Resilienz
This commit is contained in:
parent
e9ba5b5b46
commit
ab14ac8cc3
2 changed files with 76 additions and 6 deletions
14
app.js
14
app.js
|
|
@ -103,7 +103,8 @@
|
||||||
function updatePhases(state) {
|
function updatePhases(state) {
|
||||||
const map = { IDLE:'ready', STARTING:'ready', COMMITTING:'commit',
|
const map = { IDLE:'ready', STARTING:'ready', COMMITTING:'commit',
|
||||||
WAITING_FOR_COMMITS:'commit', REVEALING:'reveal',
|
WAITING_FOR_COMMITS:'commit', REVEALING:'reveal',
|
||||||
WAITING_FOR_REVEALS:'reveal', VERIFYING:'verify', COMPLETE:'done' };
|
WAITING_FOR_REVEALS:'reveal', VERIFYING:'verify',
|
||||||
|
COMPLETE:'done', ABORTED:'done' };
|
||||||
const active = map[state] || 'ready';
|
const active = map[state] || 'ready';
|
||||||
let doneSeen = false;
|
let doneSeen = false;
|
||||||
['ready','commit','reveal','verify','done'].forEach(p => {
|
['ready','commit','reveal','verify','done'].forEach(p => {
|
||||||
|
|
@ -141,12 +142,19 @@
|
||||||
$('btn-start-protocol').hidden = true;
|
$('btn-start-protocol').hidden = true;
|
||||||
};
|
};
|
||||||
protocol.onerror = err => {
|
protocol.onerror = err => {
|
||||||
$('protocol-text').textContent = 'Fehler: ' + err;
|
$('protocol-text').textContent = err;
|
||||||
|
$('result-area').hidden = false;
|
||||||
|
$('result-text').textContent = 'Abgebrochen';
|
||||||
|
$('btn-start-protocol').hidden = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
mesh.ondata = (peerId, msg) => {
|
mesh.ondata = (peerId, msg) => {
|
||||||
if (msg.type === 'protocol_start' && protocol && protocol.state === 'IDLE') {
|
if (msg.type === 'protocol_start') {
|
||||||
|
if (protocol) protocol.reset();
|
||||||
|
$('coin-area').hidden = true;
|
||||||
|
$('result-area').hidden = true;
|
||||||
$('btn-start-protocol').hidden = true;
|
$('btn-start-protocol').hidden = true;
|
||||||
|
updatePhases('IDLE');
|
||||||
protocol.start();
|
protocol.start();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
68
protocol.js
68
protocol.js
|
|
@ -7,6 +7,10 @@ class CoinFlipProtocol {
|
||||||
this.reveals = new Map();
|
this.reveals = new Map();
|
||||||
this.result = null;
|
this.result = null;
|
||||||
this.totalPeers = 0;
|
this.totalPeers = 0;
|
||||||
|
this.expectedPeers = new Set();
|
||||||
|
this.TIMEOUT_MS = 12000;
|
||||||
|
this._commitTimer = null;
|
||||||
|
this._revealTimer = null;
|
||||||
|
|
||||||
this.onstatechange = null;
|
this.onstatechange = null;
|
||||||
this.onprogress = null;
|
this.onprogress = null;
|
||||||
|
|
@ -17,7 +21,8 @@ class CoinFlipProtocol {
|
||||||
async start() {
|
async start() {
|
||||||
if (this.state !== 'IDLE') return;
|
if (this.state !== 'IDLE') return;
|
||||||
this.state = 'STARTING';
|
this.state = 'STARTING';
|
||||||
this.totalPeers = this.mesh.getCount() + 1;
|
this.expectedPeers = new Set([this.mesh.myPeerId, ...this.mesh.getPeerIds()]);
|
||||||
|
this.totalPeers = this.expectedPeers.size;
|
||||||
this.secret = await generateSecret();
|
this.secret = await generateSecret();
|
||||||
const myCommit = await commit(this.secret);
|
const myCommit = await commit(this.secret);
|
||||||
this.commits.set(this.mesh.myPeerId, myCommit);
|
this.commits.set(this.mesh.myPeerId, myCommit);
|
||||||
|
|
@ -26,6 +31,7 @@ class CoinFlipProtocol {
|
||||||
this.mesh.broadcast({ type: 'protocol', phase: 'commit', data: bufToBase64(myCommit) });
|
this.mesh.broadcast({ type: 'protocol', phase: 'commit', data: bufToBase64(myCommit) });
|
||||||
this.state = 'WAITING_FOR_COMMITS';
|
this.state = 'WAITING_FOR_COMMITS';
|
||||||
if (this.onstatechange) this.onstatechange('WAITING_FOR_COMMITS');
|
if (this.onstatechange) this.onstatechange('WAITING_FOR_COMMITS');
|
||||||
|
this._startCommitTimer();
|
||||||
this._checkCommits();
|
this._checkCommits();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -49,9 +55,54 @@ class CoinFlipProtocol {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ─── Timeout ─── */
|
||||||
|
|
||||||
|
_startCommitTimer() {
|
||||||
|
this._clearCommitTimer();
|
||||||
|
this._commitTimer = setTimeout(() => {
|
||||||
|
if (this.commits.size >= this.totalPeers) return;
|
||||||
|
const missing = [...this.expectedPeers].filter(p => !this.commits.has(p));
|
||||||
|
this._abort(`Timeout auf Commits von: ${missing.map(p => p.slice(0, 8)).join(', ')}`);
|
||||||
|
}, this.TIMEOUT_MS);
|
||||||
|
}
|
||||||
|
|
||||||
|
_startRevealTimer() {
|
||||||
|
this._clearRevealTimer();
|
||||||
|
this._revealTimer = setTimeout(() => {
|
||||||
|
if (this.reveals.size >= this.totalPeers) return;
|
||||||
|
const missing = [...this.expectedPeers].filter(p => !this.reveals.has(p));
|
||||||
|
this._abort(`Timeout auf Reveals von: ${missing.map(p => p.slice(0, 8)).join(', ')}`);
|
||||||
|
}, this.TIMEOUT_MS);
|
||||||
|
}
|
||||||
|
|
||||||
|
_clearCommitTimer() {
|
||||||
|
if (this._commitTimer) {
|
||||||
|
clearTimeout(this._commitTimer);
|
||||||
|
this._commitTimer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_clearRevealTimer() {
|
||||||
|
if (this._revealTimer) {
|
||||||
|
clearTimeout(this._revealTimer);
|
||||||
|
this._revealTimer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_abort(reason) {
|
||||||
|
this._clearCommitTimer();
|
||||||
|
this._clearRevealTimer();
|
||||||
|
this.state = 'ABORTED';
|
||||||
|
if (this.onstatechange) this.onstatechange('ABORTED');
|
||||||
|
if (this.onerror) this.onerror(reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── Commit phase ─── */
|
||||||
|
|
||||||
_checkCommits() {
|
_checkCommits() {
|
||||||
if (this.state !== 'WAITING_FOR_COMMITS') return;
|
if (this.state !== 'WAITING_FOR_COMMITS') return;
|
||||||
if (this.commits.size >= this.totalPeers) {
|
if (this.commits.size >= this.totalPeers) {
|
||||||
|
this._clearCommitTimer();
|
||||||
this._allCommitsReceived();
|
this._allCommitsReceived();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -64,17 +115,23 @@ class CoinFlipProtocol {
|
||||||
if (this.onprogress) this.onprogress('reveal', 1, this.totalPeers);
|
if (this.onprogress) this.onprogress('reveal', 1, this.totalPeers);
|
||||||
this.state = 'WAITING_FOR_REVEALS';
|
this.state = 'WAITING_FOR_REVEALS';
|
||||||
if (this.onstatechange) this.onstatechange('WAITING_FOR_REVEALS');
|
if (this.onstatechange) this.onstatechange('WAITING_FOR_REVEALS');
|
||||||
|
this._startRevealTimer();
|
||||||
this._checkReveals();
|
this._checkReveals();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ─── Reveal phase ─── */
|
||||||
|
|
||||||
_checkReveals() {
|
_checkReveals() {
|
||||||
if (this.state !== 'WAITING_FOR_REVEALS') return;
|
if (this.state !== 'WAITING_FOR_REVEALS') return;
|
||||||
if (this.reveals.size >= this.totalPeers) {
|
if (this.reveals.size >= this.totalPeers) {
|
||||||
|
this._clearRevealTimer();
|
||||||
this._allRevealsReceived();
|
this._allRevealsReceived();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async _allRevealsReceived() {
|
async _allRevealsReceived() {
|
||||||
|
this._clearRevealTimer();
|
||||||
|
|
||||||
this.state = 'VERIFYING';
|
this.state = 'VERIFYING';
|
||||||
if (this.onstatechange) this.onstatechange('VERIFYING');
|
if (this.onstatechange) this.onstatechange('VERIFYING');
|
||||||
|
|
||||||
|
|
@ -82,12 +139,12 @@ class CoinFlipProtocol {
|
||||||
if (peerId === this.mesh.myPeerId) continue;
|
if (peerId === this.mesh.myPeerId) continue;
|
||||||
const commitment = this.commits.get(peerId);
|
const commitment = this.commits.get(peerId);
|
||||||
if (!commitment) {
|
if (!commitment) {
|
||||||
if (this.onerror) this.onerror(`Fehler: Kein Commit von ${peerId}`);
|
this._abort(`Fehler: Kein Commit von ${peerId.slice(0, 8)}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const expected = await commit(secret);
|
const expected = await commit(secret);
|
||||||
if (!arraysEqual(expected, commitment)) {
|
if (!arraysEqual(expected, commitment)) {
|
||||||
if (this.onerror) this.onerror(`Betrug erkannt! ${peerId} hat gefälscht.`);
|
this._abort(`Betrug erkannt! ${peerId.slice(0, 8)} hat gefälscht.`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -98,12 +155,17 @@ class CoinFlipProtocol {
|
||||||
if (this.oncomplete) this.oncomplete(this.result);
|
if (this.oncomplete) this.oncomplete(this.result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ─── Reset ─── */
|
||||||
|
|
||||||
reset() {
|
reset() {
|
||||||
|
this._clearCommitTimer();
|
||||||
|
this._clearRevealTimer();
|
||||||
this.state = 'IDLE';
|
this.state = 'IDLE';
|
||||||
this.secret = null;
|
this.secret = null;
|
||||||
this.commits.clear();
|
this.commits.clear();
|
||||||
this.reveals.clear();
|
this.reveals.clear();
|
||||||
this.result = null;
|
this.result = null;
|
||||||
this.totalPeers = 0;
|
this.totalPeers = 0;
|
||||||
|
this.expectedPeers = new Set();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue