Skip to content

Commit 7428fff

Browse files
authored
fix false positive in conflict detection (#677)
* fix false positive in conflict detection * dont clear bitfield, just dont reply * no error * add tmp log for awareness * test still flaky
1 parent c5f437b commit 7428fff

File tree

3 files changed

+61
-9
lines changed

3 files changed

+61
-9
lines changed

lib/core.js

+48-8
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@ module.exports = class Core {
171171
key: opts.key || (compat ? manifest.signers[0].publicKey : Verifier.manifestHash(manifest)),
172172
manifest,
173173
keyPair: keyPair ? { publicKey: keyPair.publicKey, secretKey: keyPair.secretKey || null } : null,
174+
frozen: false,
174175
userData: [],
175176
tree: {
176177
fork: 0,
@@ -190,6 +191,7 @@ module.exports = class Core {
190191
key: header.key,
191192
manifest,
192193
keyPair,
194+
frozen: false,
193195
discoveryKey,
194196
userData: opts.userData || [],
195197
alias: opts.alias || null
@@ -494,6 +496,11 @@ module.exports = class Core {
494496
return false
495497
}
496498

499+
// sanity check -> no manifest, no way to verify
500+
if (!this.header.manifest) {
501+
return false
502+
}
503+
497504
const batch = MerkleTree.verifyFullyRemote(this.state, proof)
498505

499506
try {
@@ -502,24 +509,57 @@ module.exports = class Core {
502509
return true
503510
}
504511

512+
const roots = await MerkleTree.getRootsFromStorage(this.storage, proof.upgrade.length)
513+
const remoteTreeHash = crypto.tree(proof.upgrade.nodes)
514+
const localTreeHash = crypto.tree(roots)
515+
516+
try {
517+
const rx = this.state.storage.read()
518+
const treeProofPromise = MerkleTree.proof(this.state, rx, {
519+
block: null,
520+
hash: null,
521+
seek: null,
522+
upgrade: {
523+
start: 0,
524+
length: proof.upgrade.length
525+
}
526+
})
527+
528+
rx.tryFlush()
529+
530+
const treeProof = await treeProofPromise
531+
532+
const verifyBatch = MerkleTree.verifyFullyRemote(this.state, await treeProof.settle())
533+
this._verifyBatchUpgrade(verifyBatch, this.header.manifest)
534+
} catch {
535+
return true
536+
}
537+
538+
// both proofs are valid, now check if they forked
539+
if (b4a.equals(localTreeHash, remoteTreeHash)) return false
540+
505541
await this.state.mutex.lock()
506542

507543
try {
508544
const tx = this.state.createWriteBatch()
509-
if (this.header.manifest === null && proof.manifest) {
510-
this._setManifest(tx, proof.manifest, null)
511-
}
545+
546+
this.header.frozen = true
547+
548+
tx.setAuth({
549+
key: this.header.key,
550+
discoveryKey: this.discoveryKey,
551+
manifest: this.header.manifest,
552+
keyPair: this.header.keyPair,
553+
frozen: true
554+
})
512555

513556
await this.state.flush()
514557
} finally {
515558
this.state.mutex.unlock()
516559
}
517560

518-
const remoteTreeHash = crypto.tree(proof.upgrade.nodes)
519-
const localTreeHash = crypto.tree(await MerkleTree.getRootsFromStorage(this.storage, proof.upgrade.length))
520-
521-
if (b4a.equals(localTreeHash, remoteTreeHash)) return false
522-
561+
// tmp log so we can see these
562+
console.log('[hypercore] conflict detected in ' + this.id + ' (writable=' + !!this.header.keyPair + ',quorum=' + this.header.manifest.quorum + ')')
523563
await this._onconflict(proof)
524564
return true
525565
}

lib/replicator.js

+9-1
Original file line numberDiff line numberDiff line change
@@ -475,6 +475,8 @@ class Peer {
475475
}
476476

477477
broadcastRange (start, length, drop) {
478+
if (!this.isActive()) return
479+
478480
if (drop) this._unclearLocalRange(start, length)
479481
else this._clearLocalRange(start, length)
480482

@@ -779,6 +781,10 @@ class Peer {
779781
// sync from now on, so safe to delete from the map
780782
this.remoteRequests.delete(req.msg.id)
781783

784+
if (!this.isActive() && proof.block !== null) {
785+
return
786+
}
787+
782788
if (proof === null) {
783789
if (req.msg.manifest && this.core.header.manifest) {
784790
const manifest = this.core.header.manifest
@@ -1389,7 +1395,7 @@ class Peer {
13891395
}
13901396

13911397
isActive () {
1392-
if (this.paused || this.removed) return false
1398+
if (this.paused || this.removed || this.core.header.frozen) return false
13931399
return true
13941400
}
13951401

@@ -2120,6 +2126,8 @@ module.exports = class Replicator {
21202126
}
21212127

21222128
_onwant (peer, start, length) {
2129+
if (!peer.isActive()) return
2130+
21232131
const contig = Math.min(this.core.state.length, this.core.header.hints.contiguousLength)
21242132

21252133
if (start + length < contig || (this.core.state.length === contig)) {

test/conflicts.js

+4
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,14 @@ test.skip('one forks', async function (t) {
1111
const a = await create(t)
1212
await a.append(['a', 'b', 'c', 'd', 'e'])
1313

14+
a.core.name = 'a'
15+
1416
const b = await create(t, a.key)
17+
b.core.name = 'b'
1518

1619
const c = await create(t, { keyPair: a.core.header.keyPair })
1720
await c.append(['a', 'b', 'c', 'd', 'f', 'e'])
21+
c.core.name = 'c'
1822

1923
const streams = replicate(a, b, t)
2024

0 commit comments

Comments
 (0)