@optimystic/db-p2p 0.24.2 → 0.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/{readme.md → README.md} +688 -668
- package/dist/src/cluster/block-transfer-service.d.ts +133 -10
- package/dist/src/cluster/block-transfer-service.d.ts.map +1 -1
- package/dist/src/cluster/block-transfer-service.js +155 -9
- package/dist/src/cluster/block-transfer-service.js.map +1 -1
- package/dist/src/cluster/block-transfer.d.ts +67 -7
- package/dist/src/cluster/block-transfer.d.ts.map +1 -1
- package/dist/src/cluster/block-transfer.js +127 -24
- package/dist/src/cluster/block-transfer.js.map +1 -1
- package/dist/src/cluster/certified-claims.d.ts +170 -0
- package/dist/src/cluster/certified-claims.d.ts.map +1 -0
- package/dist/src/cluster/certified-claims.js +200 -0
- package/dist/src/cluster/certified-claims.js.map +1 -0
- package/dist/src/cluster/client-signature-verifier.d.ts +23 -0
- package/dist/src/cluster/client-signature-verifier.d.ts.map +1 -0
- package/dist/src/cluster/client-signature-verifier.js +33 -0
- package/dist/src/cluster/client-signature-verifier.js.map +1 -0
- package/dist/src/cluster/cluster-policy.d.ts +9 -3
- package/dist/src/cluster/cluster-policy.d.ts.map +1 -1
- package/dist/src/cluster/cluster-policy.js +111 -27
- package/dist/src/cluster/cluster-policy.js.map +1 -1
- package/dist/src/cluster/cluster-repo.d.ts +140 -11
- package/dist/src/cluster/cluster-repo.d.ts.map +1 -1
- package/dist/src/cluster/cluster-repo.js +485 -29
- package/dist/src/cluster/cluster-repo.js.map +1 -1
- package/dist/src/cluster/commit-proof.d.ts +134 -0
- package/dist/src/cluster/commit-proof.d.ts.map +1 -0
- package/dist/src/cluster/commit-proof.js +263 -0
- package/dist/src/cluster/commit-proof.js.map +1 -0
- package/dist/src/cluster/quorum-restore.d.ts +95 -8
- package/dist/src/cluster/quorum-restore.d.ts.map +1 -1
- package/dist/src/cluster/quorum-restore.js +113 -14
- package/dist/src/cluster/quorum-restore.js.map +1 -1
- package/dist/src/cluster/rebalance-monitor.d.ts +84 -0
- package/dist/src/cluster/rebalance-monitor.d.ts.map +1 -1
- package/dist/src/cluster/rebalance-monitor.js +207 -5
- package/dist/src/cluster/rebalance-monitor.js.map +1 -1
- package/dist/src/cluster/reconcile-block.d.ts +27 -7
- package/dist/src/cluster/reconcile-block.d.ts.map +1 -1
- package/dist/src/cluster/reconcile-block.js +179 -38
- package/dist/src/cluster/reconcile-block.js.map +1 -1
- package/dist/src/cluster/service.d.ts +13 -4
- package/dist/src/cluster/service.d.ts.map +1 -1
- package/dist/src/cluster/service.js +17 -18
- package/dist/src/cluster/service.js.map +1 -1
- package/dist/src/cluster/spread-on-churn.d.ts +8 -2
- package/dist/src/cluster/spread-on-churn.d.ts.map +1 -1
- package/dist/src/cluster/spread-on-churn.js +7 -7
- package/dist/src/cluster/spread-on-churn.js.map +1 -1
- package/dist/src/cohort-topic/host.d.ts.map +1 -1
- package/dist/src/cohort-topic/host.js +8 -7
- package/dist/src/cohort-topic/host.js.map +1 -1
- package/dist/src/cohort-topic/stream-util.d.ts +7 -6
- package/dist/src/cohort-topic/stream-util.d.ts.map +1 -1
- package/dist/src/cohort-topic/stream-util.js +14 -41
- package/dist/src/cohort-topic/stream-util.js.map +1 -1
- package/dist/src/dispute/cascade.d.ts +7 -8
- package/dist/src/dispute/cascade.d.ts.map +1 -1
- package/dist/src/dispute/cascade.js +24 -7
- package/dist/src/dispute/cascade.js.map +1 -1
- package/dist/src/dispute/dispute-service.d.ts +5 -0
- package/dist/src/dispute/dispute-service.d.ts.map +1 -1
- package/dist/src/dispute/dispute-service.js +13 -3
- package/dist/src/dispute/dispute-service.js.map +1 -1
- package/dist/src/dispute/invalidation.d.ts +33 -15
- package/dist/src/dispute/invalidation.d.ts.map +1 -1
- package/dist/src/dispute/invalidation.js +127 -34
- package/dist/src/dispute/invalidation.js.map +1 -1
- package/dist/src/dispute/service.d.ts.map +1 -1
- package/dist/src/dispute/service.js +2 -1
- package/dist/src/dispute/service.js.map +1 -1
- package/dist/src/index.d.ts +7 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +7 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/libp2p-key-network.d.ts +14 -10
- package/dist/src/libp2p-key-network.d.ts.map +1 -1
- package/dist/src/libp2p-key-network.js +28 -58
- package/dist/src/libp2p-key-network.js.map +1 -1
- package/dist/src/libp2p-node-base.d.ts +38 -7
- package/dist/src/libp2p-node-base.d.ts.map +1 -1
- package/dist/src/libp2p-node-base.js +148 -63
- package/dist/src/libp2p-node-base.js.map +1 -1
- package/dist/src/network/open-protocol-stream.d.ts +56 -0
- package/dist/src/network/open-protocol-stream.d.ts.map +1 -0
- package/dist/src/network/open-protocol-stream.js +60 -0
- package/dist/src/network/open-protocol-stream.js.map +1 -0
- package/dist/src/network/register-protocol-handler.d.ts +66 -0
- package/dist/src/network/register-protocol-handler.d.ts.map +1 -0
- package/dist/src/network/register-protocol-handler.js +51 -0
- package/dist/src/network/register-protocol-handler.js.map +1 -0
- package/dist/src/peer-address-book.d.ts +60 -2
- package/dist/src/peer-address-book.d.ts.map +1 -1
- package/dist/src/peer-address-book.js +89 -1
- package/dist/src/peer-address-book.js.map +1 -1
- package/dist/src/pend-validation.d.ts +65 -0
- package/dist/src/pend-validation.d.ts.map +1 -0
- package/dist/src/pend-validation.js +69 -0
- package/dist/src/pend-validation.js.map +1 -0
- package/dist/src/reactivity/notify-transport.d.ts.map +1 -1
- package/dist/src/reactivity/notify-transport.js +2 -1
- package/dist/src/reactivity/notify-transport.js.map +1 -1
- package/dist/src/reactivity/push-state-gossip.d.ts.map +1 -1
- package/dist/src/reactivity/push-state-gossip.js +2 -1
- package/dist/src/reactivity/push-state-gossip.js.map +1 -1
- package/dist/src/repo/cluster-coordinator.d.ts +32 -6
- package/dist/src/repo/cluster-coordinator.d.ts.map +1 -1
- package/dist/src/repo/cluster-coordinator.js +44 -10
- package/dist/src/repo/cluster-coordinator.js.map +1 -1
- package/dist/src/repo/coordinator-repo.d.ts +181 -25
- package/dist/src/repo/coordinator-repo.d.ts.map +1 -1
- package/dist/src/repo/coordinator-repo.js +650 -62
- package/dist/src/repo/coordinator-repo.js.map +1 -1
- package/dist/src/repo/served-repo-proxy.d.ts +30 -0
- package/dist/src/repo/served-repo-proxy.d.ts.map +1 -0
- package/dist/src/repo/served-repo-proxy.js +50 -0
- package/dist/src/repo/served-repo-proxy.js.map +1 -0
- package/dist/src/repo/service.d.ts +16 -1
- package/dist/src/repo/service.d.ts.map +1 -1
- package/dist/src/repo/service.js +18 -16
- package/dist/src/repo/service.js.map +1 -1
- package/dist/src/reputation/types.d.ts +3 -1
- package/dist/src/reputation/types.d.ts.map +1 -1
- package/dist/src/reputation/types.js +3 -1
- package/dist/src/reputation/types.js.map +1 -1
- package/dist/src/rn.d.ts +24 -2
- package/dist/src/rn.d.ts.map +1 -1
- package/dist/src/rn.js +33 -4
- package/dist/src/rn.js.map +1 -1
- package/dist/src/storage/block-archive.d.ts +153 -0
- package/dist/src/storage/block-archive.d.ts.map +1 -0
- package/dist/src/storage/block-archive.js +194 -0
- package/dist/src/storage/block-archive.js.map +1 -0
- package/dist/src/storage/block-latch.d.ts +92 -0
- package/dist/src/storage/block-latch.d.ts.map +1 -0
- package/dist/src/storage/block-latch.js +132 -0
- package/dist/src/storage/block-latch.js.map +1 -0
- package/dist/src/storage/block-storage.d.ts +169 -12
- package/dist/src/storage/block-storage.d.ts.map +1 -1
- package/dist/src/storage/block-storage.js +415 -152
- package/dist/src/storage/block-storage.js.map +1 -1
- package/dist/src/storage/cached-raw-storage.d.ts +5 -1
- package/dist/src/storage/cached-raw-storage.d.ts.map +1 -1
- package/dist/src/storage/cached-raw-storage.js +16 -1
- package/dist/src/storage/cached-raw-storage.js.map +1 -1
- package/dist/src/storage/cached-store-driver.d.ts +14 -4
- package/dist/src/storage/cached-store-driver.d.ts.map +1 -1
- package/dist/src/storage/cached-store-driver.js +39 -5
- package/dist/src/storage/cached-store-driver.js.map +1 -1
- package/dist/src/storage/i-block-storage.d.ts +70 -19
- package/dist/src/storage/i-block-storage.d.ts.map +1 -1
- package/dist/src/storage/i-block-storage.js +21 -1
- package/dist/src/storage/i-block-storage.js.map +1 -1
- package/dist/src/storage/i-raw-storage.d.ts +38 -0
- package/dist/src/storage/i-raw-storage.d.ts.map +1 -1
- package/dist/src/storage/kv-raw-storage.d.ts +14 -8
- package/dist/src/storage/kv-raw-storage.d.ts.map +1 -1
- package/dist/src/storage/kv-raw-storage.js +29 -9
- package/dist/src/storage/kv-raw-storage.js.map +1 -1
- package/dist/src/storage/memory-store-driver.d.ts +4 -1
- package/dist/src/storage/memory-store-driver.d.ts.map +1 -1
- package/dist/src/storage/memory-store-driver.js +22 -2
- package/dist/src/storage/memory-store-driver.js.map +1 -1
- package/dist/src/storage/raw-store-codec.js +2 -2
- package/dist/src/storage/raw-store-driver.d.ts +44 -3
- package/dist/src/storage/raw-store-driver.d.ts.map +1 -1
- package/dist/src/storage/shared-cache-pool.d.ts +60 -3
- package/dist/src/storage/shared-cache-pool.d.ts.map +1 -1
- package/dist/src/storage/shared-cache-pool.js +68 -4
- package/dist/src/storage/shared-cache-pool.js.map +1 -1
- package/dist/src/storage/storage-repo.d.ts +204 -26
- package/dist/src/storage/storage-repo.d.ts.map +1 -1
- package/dist/src/storage/storage-repo.js +422 -119
- package/dist/src/storage/storage-repo.js.map +1 -1
- package/dist/src/storage/store-identity.d.ts +21 -0
- package/dist/src/storage/store-identity.d.ts.map +1 -0
- package/dist/src/storage/store-identity.js +33 -0
- package/dist/src/storage/store-identity.js.map +1 -0
- package/dist/src/storage/struct.d.ts +15 -0
- package/dist/src/storage/struct.d.ts.map +1 -1
- package/dist/src/storage/with-read-cache.d.ts +92 -0
- package/dist/src/storage/with-read-cache.d.ts.map +1 -0
- package/dist/src/storage/with-read-cache.js +132 -0
- package/dist/src/storage/with-read-cache.js.map +1 -0
- package/dist/src/sync/service.d.ts +13 -3
- package/dist/src/sync/service.d.ts.map +1 -1
- package/dist/src/sync/service.js +7 -30
- package/dist/src/sync/service.js.map +1 -1
- package/dist/src/testing/mesh-harness.d.ts +75 -2
- package/dist/src/testing/mesh-harness.d.ts.map +1 -1
- package/dist/src/testing/mesh-harness.js +193 -81
- package/dist/src/testing/mesh-harness.js.map +1 -1
- package/dist/src/testing/raw-storage-conformance.d.ts +8 -0
- package/dist/src/testing/raw-storage-conformance.d.ts.map +1 -1
- package/dist/src/testing/raw-storage-conformance.js +97 -2
- package/dist/src/testing/raw-storage-conformance.js.map +1 -1
- package/package.json +5 -6
- package/src/cluster/block-transfer-service.ts +528 -312
- package/src/cluster/block-transfer.ts +547 -424
- package/src/cluster/certified-claims.ts +319 -0
- package/src/cluster/client-signature-verifier.ts +34 -0
- package/src/cluster/cluster-policy.ts +294 -203
- package/src/cluster/cluster-repo.ts +2501 -2034
- package/src/cluster/commit-proof.ts +344 -0
- package/src/cluster/quorum-restore.ts +348 -223
- package/src/cluster/rebalance-monitor.ts +539 -254
- package/src/cluster/reconcile-block.ts +404 -203
- package/src/cluster/service.ts +23 -20
- package/src/cluster/spread-on-churn.ts +352 -346
- package/src/cohort-topic/host.ts +8 -7
- package/src/cohort-topic/stream-util.ts +14 -43
- package/src/dispute/cascade.ts +31 -15
- package/src/dispute/dispute-service.ts +706 -696
- package/src/dispute/invalidation.ts +162 -52
- package/src/dispute/service.ts +126 -125
- package/src/index.ts +7 -0
- package/src/libp2p-key-network.ts +1204 -1235
- package/src/libp2p-node-base.ts +185 -70
- package/src/network/open-protocol-stream.ts +87 -0
- package/src/network/register-protocol-handler.ts +85 -0
- package/src/peer-address-book.ts +96 -2
- package/src/pend-validation.ts +89 -0
- package/src/reactivity/notify-transport.ts +2 -1
- package/src/reactivity/push-state-gossip.ts +2 -1
- package/src/repo/cluster-coordinator.ts +65 -11
- package/src/repo/coordinator-repo.ts +729 -71
- package/src/repo/served-repo-proxy.ts +55 -0
- package/src/repo/service.ts +25 -16
- package/src/reputation/types.ts +3 -1
- package/src/rn.ts +34 -4
- package/src/storage/block-archive.ts +242 -0
- package/src/storage/block-latch.ts +144 -0
- package/src/storage/block-storage.ts +787 -498
- package/src/storage/cached-raw-storage.ts +21 -1
- package/src/storage/cached-store-driver.ts +898 -859
- package/src/storage/i-block-storage.ts +172 -116
- package/src/storage/i-raw-storage.ts +40 -0
- package/src/storage/kv-raw-storage.ts +34 -9
- package/src/storage/memory-store-driver.ts +26 -2
- package/src/storage/raw-store-codec.ts +42 -42
- package/src/storage/raw-store-driver.ts +45 -3
- package/src/storage/shared-cache-pool.ts +516 -452
- package/src/storage/storage-repo.ts +1421 -1014
- package/src/storage/store-identity.ts +41 -0
- package/src/storage/struct.ts +52 -34
- package/src/storage/with-read-cache.ts +182 -0
- package/src/sync/service.ts +178 -198
- package/src/testing/mesh-harness.ts +524 -344
- package/src/testing/raw-storage-conformance.ts +114 -2
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import { canonicalBlockHash } from "@optimystic/db-core";
|
|
2
|
+
import { maxArchiveRevision } from "../storage/block-archive.js";
|
|
1
3
|
import { PenaltyReason } from "../reputation/types.js";
|
|
2
|
-
import { selectQuorumRev, selectQuorumBlock,
|
|
4
|
+
import { selectQuorumRev, selectQuorumBlock, corroboratorCapacity, quorumSize, certifiedEquivocation, certifiedContentEquivocation } from "./quorum-restore.js";
|
|
5
|
+
import { certifyClaim, certifyContent, isAttributableProofFailure, proofThresholds } from "./certified-claims.js";
|
|
3
6
|
import { createLogger } from '../logger.js';
|
|
4
7
|
const log = createLogger('reconcile-block');
|
|
5
8
|
/**
|
|
@@ -9,35 +12,22 @@ const log = createLogger('reconcile-block');
|
|
|
9
12
|
* path (`CoordinatorRepo.restoreCorroborated` — a stall there holds up a caller's `get`).
|
|
10
13
|
*/
|
|
11
14
|
export const RECONCILE_TIMEOUT_MS = 5000;
|
|
12
|
-
/**
|
|
13
|
-
* Highest revision an archive covers. Keys arrive as strings off the wire from an untrusted peer,
|
|
14
|
-
* so a non-numeric one is skipped rather than poisoning the maximum with `NaN`; folding instead of
|
|
15
|
-
* `Math.max(...keys)` also keeps a wide archive off the argument-count limit.
|
|
16
|
-
*/
|
|
17
|
-
function maxRevision(revisions) {
|
|
18
|
-
let max;
|
|
19
|
-
for (const key of Object.keys(revisions)) {
|
|
20
|
-
const rev = Number(key);
|
|
21
|
-
if (Number.isFinite(rev) && (max === undefined || rev > max))
|
|
22
|
-
max = rev;
|
|
23
|
-
}
|
|
24
|
-
return max;
|
|
25
|
-
}
|
|
26
15
|
/**
|
|
27
16
|
* The claim a peer's archive makes: its highest revision, provided that revision is at least the
|
|
28
|
-
* one we committed. `undefined` when the
|
|
29
|
-
* or
|
|
17
|
+
* one we committed. `undefined` when the archive carries only revisions older than the commit we are
|
|
18
|
+
* healing, or no usable action at its highest.
|
|
30
19
|
*/
|
|
31
20
|
function toCandidate(peerId, archive, committedRev) {
|
|
32
|
-
|
|
33
|
-
return undefined;
|
|
34
|
-
const maxRev = maxRevision(archive.revisions);
|
|
21
|
+
const maxRev = maxArchiveRevision(archive.revisions);
|
|
35
22
|
if (maxRev === undefined || maxRev < committedRev)
|
|
36
23
|
return undefined;
|
|
37
24
|
const data = archive.revisions[maxRev];
|
|
38
25
|
if (!data?.action)
|
|
39
26
|
return undefined;
|
|
40
|
-
return {
|
|
27
|
+
return {
|
|
28
|
+
peerId, rev: maxRev, actionId: data.action.actionId, block: data.block,
|
|
29
|
+
...(data.proof ? { proof: data.proof } : {})
|
|
30
|
+
};
|
|
41
31
|
}
|
|
42
32
|
/**
|
|
43
33
|
* One peer's answer, isolated. `fetchArchive` is contracted to answer `undefined` for an
|
|
@@ -45,25 +35,117 @@ function toCandidate(peerId, archive, committedRev) {
|
|
|
45
35
|
* discard the answers every other peer already gave — turning a heal the cohort could complete
|
|
46
36
|
* into a decline. One peer's failure costs only that peer's vote.
|
|
47
37
|
*/
|
|
48
|
-
async function
|
|
38
|
+
async function fetchAnswer(deps, peerId, blockId, committedRev) {
|
|
39
|
+
let archive;
|
|
49
40
|
try {
|
|
50
|
-
|
|
41
|
+
archive = await deps.fetchArchive(peerId, blockId);
|
|
51
42
|
}
|
|
52
43
|
catch (err) {
|
|
53
44
|
log('reconcile:fetch-error', { blockId, peerId, error: err.message });
|
|
54
|
-
return
|
|
45
|
+
return { kind: 'error' };
|
|
55
46
|
}
|
|
47
|
+
if (!archive)
|
|
48
|
+
return { kind: 'no-archive' };
|
|
49
|
+
const candidate = toCandidate(peerId, archive, committedRev);
|
|
50
|
+
return candidate ? { kind: 'claim', candidate } : { kind: 'behind' };
|
|
56
51
|
}
|
|
57
52
|
/**
|
|
58
|
-
*
|
|
53
|
+
* Run every proof-carrying candidate through the shared certification layer
|
|
54
|
+
* (`cluster/certified-claims.ts`) and inject the verdicts, BEFORE selection reads the claim set.
|
|
55
|
+
* Mirrors the read path's certifyClaim pass (`CoordinatorRepo.queryClusterForLatest`) — same
|
|
56
|
+
* thresholds, same failure logging, same penalty discipline:
|
|
57
|
+
*
|
|
58
|
+
* - A candidate whose claim half fails stays an ORDINARY uncertified corroborator (its vote is
|
|
59
|
+
* never dropped — a peer that could fabricate a bad proof could equally have sent none), and
|
|
60
|
+
* only an attributable failure (`isAttributableProofFailure`) penalizes the serving peer.
|
|
61
|
+
* - `digest-mismatch` is the one content-side rejection: the served bytes provably contradict
|
|
62
|
+
* the proof's declared digest, so the candidate is dropped from the content quorum and the
|
|
63
|
+
* server penalized — while its (genuinely verified) rev claim still counts.
|
|
64
|
+
* - `no-digest-declared` leaves the content an ordinary uncertified carrier: the cohort declared
|
|
65
|
+
* nothing to compare against, which is a verdict, never misbehavior.
|
|
66
|
+
*
|
|
67
|
+
* NOTE: cost is one verification pass per proof-carrying answer per reconcile, each bounded by
|
|
68
|
+
* MAX_PROOF_SIGNERS (256, the shared layer's cap) signature checks × cohort width — fine at
|
|
69
|
+
* deployment cohort sizes (~10).
|
|
70
|
+
*/
|
|
71
|
+
async function certifyCandidates(deps, blockId, candidates) {
|
|
72
|
+
// Shared with the read path, so the two cannot drift on what the members actually enforced —
|
|
73
|
+
// see `proofThresholds` for why the simple-majority term is not deps.simpleMajorityThreshold.
|
|
74
|
+
const thresholds = proofThresholds(deps.superMajorityThreshold);
|
|
75
|
+
await Promise.all(candidates.map(async (c) => {
|
|
76
|
+
if (!c.proof)
|
|
77
|
+
return;
|
|
78
|
+
const claim = { blockId, rev: c.rev, actionId: c.actionId };
|
|
79
|
+
if (c.block) {
|
|
80
|
+
const verdict = await certifyContent(c.proof, claim, c.block, thresholds, deps.anchoring);
|
|
81
|
+
if (verdict.revCertified) {
|
|
82
|
+
c.revCertified = true;
|
|
83
|
+
if (verdict.contentCertified) {
|
|
84
|
+
c.contentCertified = true;
|
|
85
|
+
}
|
|
86
|
+
else if (verdict.failure === 'digest-mismatch') {
|
|
87
|
+
// The claim half genuinely passed; the bytes provably lie. Drop them from the
|
|
88
|
+
// content quorum and penalize the server — repair continues on the other holders.
|
|
89
|
+
c.contentRejected = true;
|
|
90
|
+
log('reconcile:content-rejected', { blockId, peerId: c.peerId, rev: c.rev });
|
|
91
|
+
penalizeProofService(deps.reputation, c.peerId, blockId);
|
|
92
|
+
}
|
|
93
|
+
// no-digest-declared: content stays an ordinary uncertified carrier, no penalty.
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
logUncertified(blockId, c, verdict.failure);
|
|
97
|
+
if (isAttributableProofFailure(verdict.failure)) {
|
|
98
|
+
penalizeProofService(deps.reputation, c.peerId, blockId);
|
|
99
|
+
}
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
const verdict = await certifyClaim(c.proof, claim, thresholds, deps.anchoring);
|
|
103
|
+
if (verdict.certified) {
|
|
104
|
+
c.revCertified = true;
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
logUncertified(blockId, c, verdict.failure);
|
|
108
|
+
if (isAttributableProofFailure(verdict.failure)) {
|
|
109
|
+
penalizeProofService(deps.reputation, c.peerId, blockId);
|
|
110
|
+
}
|
|
111
|
+
}));
|
|
112
|
+
}
|
|
113
|
+
/** Mirror of `cluster-fetch:proof-uncertified` on the read path — same fields, reconcile-side name. */
|
|
114
|
+
function logUncertified(blockId, c, failure) {
|
|
115
|
+
log('reconcile:proof-uncertified', { blockId, peerId: c.peerId, rev: c.rev, failure });
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Best-effort penalty for a peer whose served proof (or the bytes served under it) provably lies.
|
|
119
|
+
* Mirrors `CoordinatorRepo.penalizeProofService` — never throws.
|
|
120
|
+
*/
|
|
121
|
+
function penalizeProofService(reputation, peerId, blockId) {
|
|
122
|
+
if (!reputation)
|
|
123
|
+
return;
|
|
124
|
+
try {
|
|
125
|
+
reputation.reportPeer(peerId, PenaltyReason.InvalidRestoration, `reconcile:${blockId}`);
|
|
126
|
+
}
|
|
127
|
+
catch (err) {
|
|
128
|
+
log('reconcile:penalize-error', { blockId, error: err.message });
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Hash the block bytes of every candidate that both corroborates `selected` and actually carried
|
|
133
|
+
* content — minus any whose bytes were rejected against their own proof's digest
|
|
134
|
+
* (`contentRejected`). A carrier whose content CERTIFIED carries its `certified` flag into the
|
|
135
|
+
* selector and its proof alongside: that is the only proof safe to persist, because it was
|
|
136
|
+
* verified against these exact bytes.
|
|
59
137
|
*
|
|
60
138
|
* NOTE: this canonical-JSON-serializes and sha256s every carrier's whole block on every reconcile.
|
|
61
139
|
* Negligible at today's cohort widths and block sizes; if blocks grow large or cohorts wide enough
|
|
62
140
|
* for this to show up on a commit-path profile, hash incrementally at receive time instead.
|
|
63
141
|
*/
|
|
64
142
|
async function hashCarriers(candidates, selected) {
|
|
65
|
-
const carriers = candidates.filter(c => c.rev === selected.rev && c.actionId === selected.actionId && c.block);
|
|
66
|
-
return await Promise.all(carriers.map(async (c) => ({
|
|
143
|
+
const carriers = candidates.filter(c => c.rev === selected.rev && c.actionId === selected.actionId && c.block && !c.contentRejected);
|
|
144
|
+
return await Promise.all(carriers.map(async (c) => ({
|
|
145
|
+
peerId: c.peerId, hash: await canonicalBlockHash(c.block), block: c.block,
|
|
146
|
+
...(c.contentCertified ? { certified: true } : {}),
|
|
147
|
+
...(c.contentCertified && c.proof ? { proof: c.proof } : {})
|
|
148
|
+
})));
|
|
67
149
|
}
|
|
68
150
|
/** Report cohort members that served content contradicting the agreed hash. Best-effort; never throws. */
|
|
69
151
|
function penalizeContradictingContent(reputation, candidates, agreedHash, blockId) {
|
|
@@ -88,9 +170,15 @@ function penalizeContradictingContent(reputation, candidates, agreedHash, blockI
|
|
|
88
170
|
* corroboration rather than raw `Math.max` (a lone peer inflating its rev cannot steer
|
|
89
171
|
* reconciliation), verifies the cohort agrees on the *content* at that revision, and persists it.
|
|
90
172
|
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
173
|
+
* Peer-attached cohort commit proofs are verified first ({@link certifyCandidates}) and both gates
|
|
174
|
+
* weigh the verdicts: a claim — and, separately, the bytes — that a verified proof certifies is
|
|
175
|
+
* accepted with no second peer at any cohort size, and the proof that certified the bytes is
|
|
176
|
+
* persisted alongside them so the repaired replica serves it onward.
|
|
177
|
+
*
|
|
178
|
+
* Both quorums are capped by {@link corroboratorCapacity} for the claims that still need
|
|
179
|
+
* corroboration: demanding two corroborators from a cohort that contains exactly one other peer is
|
|
180
|
+
* a permanent deadlock, not a safety property — the node can never heal and stays unreadable
|
|
181
|
+
* forever.
|
|
94
182
|
*
|
|
95
183
|
* **Exposure at capacity 1 (documented, not accidental).** Block ids are random 256-bit strings
|
|
96
184
|
* (`db-core` `structs.ts`), NOT content-addressed, so nothing on the receive path can re-derive
|
|
@@ -98,8 +186,12 @@ function penalizeContradictingContent(reputation, candidates, agreedHash, blockI
|
|
|
98
186
|
* against `blockId`. A sole cohort peer's content is therefore believed on its word. That adds no
|
|
99
187
|
* trust the cohort had not already extended — the same peer's `(rev, actionId)` claim is likewise
|
|
100
188
|
* uncorroborable at that size (see `selectQuorumRev`'s capacity note), and a two-member cohort has
|
|
101
|
-
* no honest majority to appeal to in the first place.
|
|
102
|
-
*
|
|
189
|
+
* no honest majority to appeal to in the first place. It is closed for a candidate that carries a
|
|
190
|
+
* verified cohort commit proof — {@link certifyCandidates} binds the proof's declared digest to the
|
|
191
|
+
* served bytes, which is a check against the *cohort's own signatures* rather than against other
|
|
192
|
+
* peers, so a certified carrier wins the content gate outright and this exposure never applies to
|
|
193
|
+
* it. It remains open for a proof-less candidate, and for the residual that layer 1 proves only
|
|
194
|
+
* that the listed signers signed (`feat-cluster-membership-threshold-cert-anchoring`).
|
|
103
195
|
*
|
|
104
196
|
* Declines are cheap and retryable: nothing is persisted, nothing is marked, and the next commit
|
|
105
197
|
* or churn/rebalance pass tries again.
|
|
@@ -109,25 +201,61 @@ export function createReconcileBlock(deps) {
|
|
|
109
201
|
const targets = cohortPeerIds.filter(id => id !== deps.selfPeerId);
|
|
110
202
|
if (targets.length === 0)
|
|
111
203
|
return;
|
|
112
|
-
const
|
|
113
|
-
const candidates =
|
|
204
|
+
const answers = await Promise.all(targets.map(peerId => fetchAnswer(deps, peerId, blockId, committed.rev)));
|
|
205
|
+
const candidates = answers.flatMap(a => a.kind === 'claim' ? [a.candidate] : []);
|
|
206
|
+
const tally = (kind) => answers.filter(a => a.kind === kind).length;
|
|
114
207
|
const capacity = corroboratorCapacity(targets.length, deps.repairCorroborationClusterSize);
|
|
115
|
-
|
|
208
|
+
await certifyCandidates(deps, blockId, candidates);
|
|
209
|
+
const revClaims = candidates.map(({ peerId, rev, actionId, revCertified }) => ({
|
|
210
|
+
peerId, rev, actionId, ...(revCertified ? { certified: true } : {})
|
|
211
|
+
}));
|
|
116
212
|
const selected = selectQuorumRev(revClaims, deps.simpleMajorityThreshold, capacity);
|
|
117
213
|
if (!selected) {
|
|
214
|
+
// Asked ONLY on a decline: a certified-vs-certified conflict can coexist with a successful
|
|
215
|
+
// selection (a corroborated pair strictly above the top certified rev wins), so a
|
|
216
|
+
// non-undefined answer here explains THIS decline, nothing more. Two verified proofs for
|
|
217
|
+
// distinct actions at one revision means the cohort (or whoever holds its keys) provably
|
|
218
|
+
// signed both sides — an incident, not a shortage of answers; neither claimant is
|
|
219
|
+
// penalized, because which side is wrong is exactly what this node cannot know.
|
|
220
|
+
const equivocation = certifiedEquivocation(revClaims);
|
|
221
|
+
if (equivocation) {
|
|
222
|
+
log('reconcile:certified-equivocation', {
|
|
223
|
+
blockId, rev: equivocation.rev, actionIds: equivocation.actionIds
|
|
224
|
+
});
|
|
225
|
+
}
|
|
118
226
|
// Leave the block behind; churn/rebalance and the next commit retry.
|
|
119
227
|
log('reconcile:no-rev-quorum', {
|
|
120
228
|
blockId,
|
|
121
229
|
rev: committed.rev,
|
|
122
|
-
|
|
230
|
+
cohortPeers: targets.length,
|
|
231
|
+
holders: revClaims.length,
|
|
232
|
+
behind: tally('behind'),
|
|
233
|
+
noArchive: tally('no-archive'),
|
|
234
|
+
fetchErrors: tally('error'),
|
|
123
235
|
required: quorumSize(revClaims.length, deps.simpleMajorityThreshold, capacity),
|
|
124
236
|
repairCorroborationClusterSize: deps.repairCorroborationClusterSize
|
|
125
237
|
});
|
|
126
238
|
return;
|
|
127
239
|
}
|
|
240
|
+
if (selected.certified) {
|
|
241
|
+
// Which rule won matters when reading a repair log: a certified selection may rest on a
|
|
242
|
+
// SINGLE claimant whose corroboration is the cohort's signature set, not other voters.
|
|
243
|
+
log('reconcile:certified-selected', {
|
|
244
|
+
blockId, rev: selected.rev, claimants: selected.supporters.length
|
|
245
|
+
});
|
|
246
|
+
}
|
|
128
247
|
const hashCandidates = await hashCarriers(candidates, selected);
|
|
129
248
|
const agreed = selectQuorumBlock(hashCandidates, deps.simpleMajorityThreshold, capacity);
|
|
130
249
|
if (!agreed) {
|
|
250
|
+
// Two certified hashes at one (rev, actionId) means the cohort's keys signed two digests
|
|
251
|
+
// into one revision — a provable compromise an operator must be able to tell apart from a
|
|
252
|
+
// routine carrier shortfall; without this line the two declines log identically.
|
|
253
|
+
const equivocation = certifiedContentEquivocation(hashCandidates);
|
|
254
|
+
if (equivocation) {
|
|
255
|
+
log('reconcile:certified-content-equivocation', {
|
|
256
|
+
blockId, rev: selected.rev, hashes: equivocation.hashes
|
|
257
|
+
});
|
|
258
|
+
}
|
|
131
259
|
log('reconcile:no-content-quorum', {
|
|
132
260
|
blockId,
|
|
133
261
|
rev: selected.rev,
|
|
@@ -137,8 +265,21 @@ export function createReconcileBlock(deps) {
|
|
|
137
265
|
});
|
|
138
266
|
return;
|
|
139
267
|
}
|
|
140
|
-
|
|
141
|
-
|
|
268
|
+
// Detection is sound: selectQuorumBlock's certified branch fires iff exactly one distinct
|
|
269
|
+
// certified hash exists, so a defined `agreed` with a certified carrier at `agreed.hash` ⇔
|
|
270
|
+
// the certified rule won.
|
|
271
|
+
const certifiedCarrier = hashCandidates.find(c => c.certified === true && c.hash === agreed.hash);
|
|
272
|
+
// Contradicting-content penalties run ONLY on a corroborated win, mirroring the read path's
|
|
273
|
+
// rule (CoordinatorRepo.penalizeContradictingRevClaims): an unanchored proof must not be able
|
|
274
|
+
// to convict the honest cohort — anyone holding N keys can mint a proof that verifies, and
|
|
275
|
+
// penalizing dissenters against it would hand a forged proof a reputation lever. Revisit when
|
|
276
|
+
// certification is anchored to the block's derived cohort
|
|
277
|
+
// (`feat-cluster-membership-threshold-cert-anchoring`).
|
|
278
|
+
if (!certifiedCarrier) {
|
|
279
|
+
penalizeContradictingContent(deps.reputation, hashCandidates, agreed.hash, blockId);
|
|
280
|
+
}
|
|
281
|
+
// A corroboration-won heal with no certified carrier persists no proof — today's behavior.
|
|
282
|
+
await deps.saveReplicatedBlock(blockId, agreed.block, { actionId: selected.actionId, rev: selected.rev }, certifiedCarrier?.proof);
|
|
142
283
|
log('reconcile:restored', { blockId, rev: selected.rev, actionId: selected.actionId });
|
|
143
284
|
};
|
|
144
285
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reconcile-block.js","sourceRoot":"","sources":["../../../src/cluster/reconcile-block.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"reconcile-block.js","sourceRoot":"","sources":["../../../src/cluster/reconcile-block.ts"],"names":[],"mappings":"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"}
|
|
@@ -20,10 +20,13 @@ export interface ClusterServiceComponents extends BaseComponents {
|
|
|
20
20
|
*/
|
|
21
21
|
peerId?: PeerId;
|
|
22
22
|
/**
|
|
23
|
-
* Optional resolver for a peer
|
|
24
|
-
* a redirect target has no multiaddrs embedded in `record.peers`.
|
|
23
|
+
* Optional resolver for the addresses this node may publish for a peer, used as a fallback
|
|
24
|
+
* when a redirect target has no multiaddrs embedded in `record.peers`. Async because the
|
|
25
|
+
* answer includes the peer's own advertised addresses, which live in the peerStore — see
|
|
26
|
+
* `publishableAddrsForPeer`. A synchronous `string[]` is still accepted so an embedder's
|
|
27
|
+
* connections-only stub keeps working.
|
|
25
28
|
*/
|
|
26
|
-
getConnectionAddrs?: (peerId: PeerId) => string[]
|
|
29
|
+
getConnectionAddrs?: (peerId: PeerId) => string[] | Promise<string[]>;
|
|
27
30
|
/**
|
|
28
31
|
* Optional sink for dialable addresses carried by an inbound cluster record, so this
|
|
29
32
|
* node can later dial a cohort sibling it has never had a connection to. Omitted →
|
|
@@ -82,6 +85,12 @@ export declare class ClusterService implements Startable {
|
|
|
82
85
|
*/
|
|
83
86
|
private getLibp2p;
|
|
84
87
|
private getSelfId;
|
|
88
|
+
/**
|
|
89
|
+
* The addresses this node may publish for `id` in a redirect payload, when the record carried
|
|
90
|
+
* none. A redirect goes to a THIRD party, so it asks `publishableAddrsForPeer` — the one
|
|
91
|
+
* definition, shared with `RepoService` and `findCluster` — rather than reading connections
|
|
92
|
+
* alone: a cohort sibling that only ever dialed US has its real address in the peerStore only.
|
|
93
|
+
*/
|
|
85
94
|
private getPeerAddrs;
|
|
86
95
|
/**
|
|
87
96
|
* Decide whether this node should redirect a cluster update instead of
|
|
@@ -98,7 +107,7 @@ export declare class ClusterService implements Startable {
|
|
|
98
107
|
* when the update should be processed locally (we are a member, the mesh is too
|
|
99
108
|
* small to scope, or we lack the identity/peer set to make a decision).
|
|
100
109
|
*/
|
|
101
|
-
checkRedirect(record: ClusterRecord): RedirectPayload | null
|
|
110
|
+
checkRedirect(record: ClusterRecord): Promise<RedirectPayload | null>;
|
|
102
111
|
start(): Promise<void>;
|
|
103
112
|
stop(): Promise<void>;
|
|
104
113
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../../src/cluster/service.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,EAAsB,aAAa,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AACtG,OAAO,KAAK,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACnE,OAAO,EAAe,KAAK,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAMxE,OAAO,EAAqE,KAAK,8BAA8B,EAAE,MAAM,6BAA6B,CAAC;
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../../src/cluster/service.ts"],"names":[],"mappings":"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"}
|
|
@@ -3,10 +3,11 @@ import { decode as lpDecode, encode as lpEncode } from 'it-length-prefixed';
|
|
|
3
3
|
import { peerIdFromString } from '@libp2p/peer-id';
|
|
4
4
|
import { encodePeers } from '../repo/redirect.js';
|
|
5
5
|
import { toClusterErrorEnvelope } from './cluster-error.js';
|
|
6
|
-
import { mergeRecordPeerAddresses,
|
|
6
|
+
import { mergeRecordPeerAddresses, publishableAddrsForPeer } from '../peer-address-book.js';
|
|
7
7
|
import { MAX_CONTROL_MESSAGE_BYTES } from '../protocol-limits.js';
|
|
8
8
|
import { createLogger } from '../logger.js';
|
|
9
9
|
import { createInboundStreamAuthorization } from '../inbound-authorization.js';
|
|
10
|
+
import { registerProtocolHandler } from '../network/register-protocol-handler.js';
|
|
10
11
|
export function clusterService(init = {}) {
|
|
11
12
|
return (components) => new ClusterService(components, init);
|
|
12
13
|
}
|
|
@@ -66,7 +67,13 @@ export class ClusterService {
|
|
|
66
67
|
return this.components.peerId;
|
|
67
68
|
return this.getLibp2p()?.peerId;
|
|
68
69
|
}
|
|
69
|
-
|
|
70
|
+
/**
|
|
71
|
+
* The addresses this node may publish for `id` in a redirect payload, when the record carried
|
|
72
|
+
* none. A redirect goes to a THIRD party, so it asks `publishableAddrsForPeer` — the one
|
|
73
|
+
* definition, shared with `RepoService` and `findCluster` — rather than reading connections
|
|
74
|
+
* alone: a cohort sibling that only ever dialed US has its real address in the peerStore only.
|
|
75
|
+
*/
|
|
76
|
+
async getPeerAddrs(id) {
|
|
70
77
|
let pid;
|
|
71
78
|
try {
|
|
72
79
|
pid = peerIdFromString(id);
|
|
@@ -75,20 +82,12 @@ export class ClusterService {
|
|
|
75
82
|
return [];
|
|
76
83
|
}
|
|
77
84
|
if (this.components.getConnectionAddrs)
|
|
78
|
-
return this.components.getConnectionAddrs(pid);
|
|
85
|
+
return await this.components.getConnectionAddrs(pid);
|
|
79
86
|
const libp2p = this.getLibp2p();
|
|
80
87
|
if (!libp2p?.getConnections)
|
|
81
88
|
return [];
|
|
82
|
-
// A redirect payload goes to a THIRD party, so only an outbound connection's remoteAddr
|
|
83
|
-
// qualifies — see `publishableConnectionAddr`.
|
|
84
89
|
const conns = libp2p.getConnections(pid) ?? [];
|
|
85
|
-
|
|
86
|
-
for (const c of conns) {
|
|
87
|
-
const addr = publishableConnectionAddr(c, this.addressLog);
|
|
88
|
-
if (addr !== undefined)
|
|
89
|
-
addrs.push(addr);
|
|
90
|
-
}
|
|
91
|
-
return addrs;
|
|
90
|
+
return await publishableAddrsForPeer(libp2p, conns, pid, this.addressLog);
|
|
92
91
|
}
|
|
93
92
|
/**
|
|
94
93
|
* Decide whether this node should redirect a cluster update instead of
|
|
@@ -105,7 +104,7 @@ export class ClusterService {
|
|
|
105
104
|
* when the update should be processed locally (we are a member, the mesh is too
|
|
106
105
|
* small to scope, or we lack the identity/peer set to make a decision).
|
|
107
106
|
*/
|
|
108
|
-
checkRedirect(record) {
|
|
107
|
+
async checkRedirect(record) {
|
|
109
108
|
const selfId = this.getSelfId();
|
|
110
109
|
if (!selfId)
|
|
111
110
|
return null; // no identity → can't scope, process locally
|
|
@@ -118,11 +117,11 @@ export class ClusterService {
|
|
|
118
117
|
const smallMesh = peerIds.length < this.responsibilityK;
|
|
119
118
|
if (!smallMesh && !isMember) {
|
|
120
119
|
const others = peerIds.filter(id => id !== selfStr);
|
|
121
|
-
return encodePeers(others.map(id => {
|
|
120
|
+
return encodePeers(await Promise.all(others.map(async (id) => {
|
|
122
121
|
const recAddrs = peers[id]?.multiaddrs ?? [];
|
|
123
|
-
const addrs = recAddrs.length > 0 ? recAddrs : this.getPeerAddrs(id);
|
|
122
|
+
const addrs = recAddrs.length > 0 ? recAddrs : await this.getPeerAddrs(id);
|
|
124
123
|
return { id, addrs };
|
|
125
|
-
}));
|
|
124
|
+
})));
|
|
126
125
|
}
|
|
127
126
|
return null;
|
|
128
127
|
}
|
|
@@ -130,7 +129,7 @@ export class ClusterService {
|
|
|
130
129
|
if (this.running) {
|
|
131
130
|
return;
|
|
132
131
|
}
|
|
133
|
-
await this.components.registrar
|
|
132
|
+
await registerProtocolHandler(this.components.registrar, this.protocol, this.handleIncomingStream.bind(this), {
|
|
134
133
|
maxInboundStreams: this.maxInboundStreams,
|
|
135
134
|
maxOutboundStreams: this.maxOutboundStreams
|
|
136
135
|
});
|
|
@@ -163,7 +162,7 @@ export class ClusterService {
|
|
|
163
162
|
this.learnPeerAddresses(message.record);
|
|
164
163
|
// Scope consensus to responsible peers: redirect when we are not a
|
|
165
164
|
// member of the record's authoritative peer set, otherwise process.
|
|
166
|
-
const redirect = this.checkRedirect(message.record);
|
|
165
|
+
const redirect = await this.checkRedirect(message.record);
|
|
167
166
|
return redirect ?? await this.cluster.update(message.record);
|
|
168
167
|
}
|
|
169
168
|
throw new Error(`Unknown operation: ${message.operation}`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.js","sourceRoot":"","sources":["../../../src/cluster/service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAC/B,OAAO,EAAE,MAAM,IAAI,QAAQ,EAAE,MAAM,IAAI,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC5E,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAGnD,OAAO,EAAE,WAAW,EAAwB,MAAM,qBAAqB,CAAC;AACxE,OAAO,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAC5D,OAAO,EAAE,wBAAwB,EAAE,
|
|
1
|
+
{"version":3,"file":"service.js","sourceRoot":"","sources":["../../../src/cluster/service.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { Startable, Libp2p } from '@libp2p/interface';
|
|
2
|
-
import type {
|
|
2
|
+
import type { IPeerNetwork } from '@optimystic/db-core';
|
|
3
3
|
import type { FretService } from 'p2p-fret';
|
|
4
4
|
import type { PartitionDetector } from './partition-detector.js';
|
|
5
|
+
import type { ProofRetainingRepo } from '../storage/block-archive.js';
|
|
5
6
|
export interface SpreadOnChurnConfig {
|
|
6
7
|
/** Enable the churn-resilient spread protocol. Default: true */
|
|
7
8
|
enabled: boolean;
|
|
@@ -33,7 +34,12 @@ export interface SpreadOnChurnDeps {
|
|
|
33
34
|
libp2p: Libp2p;
|
|
34
35
|
fret: FretService;
|
|
35
36
|
partitionDetector: PartitionDetector;
|
|
36
|
-
|
|
37
|
+
/**
|
|
38
|
+
* The node's OWN store, proof accessor REQUIRED — see the same requirement on
|
|
39
|
+
* `BlockTransferCoordinator`: a repo without it spreads meta-only pushes that every
|
|
40
|
+
* default-configured receiver refuses, with no type error to say so.
|
|
41
|
+
*/
|
|
42
|
+
repo: ProofRetainingRepo;
|
|
37
43
|
peerNetwork: IPeerNetwork;
|
|
38
44
|
clusterSize: number;
|
|
39
45
|
protocolPrefix?: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spread-on-churn.d.ts","sourceRoot":"","sources":["../../../src/cluster/spread-on-churn.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAA;AAC1D,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"spread-on-churn.d.ts","sourceRoot":"","sources":["../../../src/cluster/spread-on-churn.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAA;AAC1D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAEvD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAA;AAE3C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAA;AAChE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAA;AASrE,MAAM,WAAW,mBAAmB;IACnC,gEAAgE;IAChE,OAAO,EAAE,OAAO,CAAA;IAChB,wEAAwE;IACxE,cAAc,EAAE,MAAM,CAAA;IACtB,sEAAsE;IACtE,qBAAqB,EAAE,OAAO,CAAA;IAC9B,kFAAkF;IAClF,eAAe,EAAE,MAAM,CAAA;IACvB,kEAAkE;IAClE,mBAAmB,EAAE,MAAM,CAAA;IAC3B,oEAAoE;IACpE,aAAa,EAAE,MAAM,CAAA;IACrB;;;;OAIG;IACH,iBAAiB,EAAE,MAAM,CAAA;IACzB;;;;;OAKG;IACH,qBAAqB,EAAE,MAAM,CAAA;CAC7B;AAED,MAAM,WAAW,iBAAiB;IACjC,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,WAAW,CAAA;IACjB,iBAAiB,EAAE,iBAAiB,CAAA;IACpC;;;;OAIG;IACH,IAAI,EAAE,kBAAkB,CAAA;IACxB,WAAW,EAAE,YAAY,CAAA;IACzB,WAAW,EAAE,MAAM,CAAA;IACnB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;CAC3B;AAED,MAAM,WAAW,WAAW;IAC3B,8BAA8B;IAC9B,MAAM,EAAE,KAAK,CAAC;QACb,OAAO,EAAE,MAAM,CAAA;QACf,OAAO,EAAE,MAAM,EAAE,CAAA;QACjB,SAAS,EAAE,MAAM,EAAE,CAAA;QACnB,MAAM,EAAE,MAAM,EAAE,CAAA;KAChB,CAAC,CAAA;IACF,0BAA0B;IAC1B,UAAU,EAAE,MAAM,CAAA;IAClB,qDAAqD;IACrD,WAAW,EAAE,MAAM,CAAA;CACnB;AAED,KAAK,aAAa,GAAG,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAA;AAiBjD,qBAAa,oBAAqB,YAAW,SAAS;IAYpD,OAAO,CAAC,QAAQ,CAAC,IAAI;IAXtB,OAAO,CAAC,OAAO,CAAQ;IACvB,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAa;IAC3C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAsB;IAC/C,OAAO,CAAC,aAAa,CAA6C;IAClE,OAAO,CAAC,mBAAmB,CAAe;IAC1C,OAAO,CAAC,kBAAkB,CAAI;IAE9B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAqB;IAC5C,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAY;gBAG5B,IAAI,EAAE,iBAAiB,EACxC,MAAM,GAAE,OAAO,CAAC,mBAAmB,CAAM;IAWpC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAStB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAgB3B,QAAQ,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI;IAItC,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAIjC,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAInC,oBAAoB,IAAI,MAAM;IAI9B,4DAA4D;IACtD,QAAQ,IAAI,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;IAM7C,OAAO,CAAC,eAAe;YA2BT,aAAa;IAkH3B,OAAO,CAAC,iBAAiB;IAiCzB,OAAO,CAAC,SAAS;CASjB"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { hashKey } from 'p2p-fret';
|
|
2
2
|
import { peerIdFromString } from '@libp2p/peer-id';
|
|
3
|
-
import { BlockTransferClient } from './block-transfer-service.js';
|
|
3
|
+
import { BlockTransferClient, sourceBlockCertification } from './block-transfer-service.js';
|
|
4
4
|
import { createLogger } from '../logger.js';
|
|
5
5
|
const log = createLogger('spread-on-churn');
|
|
6
6
|
const textEncoder = new TextEncoder();
|
|
@@ -143,11 +143,11 @@ export class SpreadOnChurnMonitor {
|
|
|
143
143
|
}
|
|
144
144
|
const blockData = textEncoder.encode(JSON.stringify(blockResult.block));
|
|
145
145
|
// Carry the source's revision metadata so the replica's `latest` matches the
|
|
146
|
-
// source instead of being fabricated as rev 1 on the receiver
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
146
|
+
// source instead of being fabricated as rev 1 on the receiver, plus the cohort commit
|
|
147
|
+
// proof for that same revision — a receiver running the default `requirePushCertificate`
|
|
148
|
+
// rejects an uncertified push, so a block this node holds no proof for simply fails to
|
|
149
|
+
// spread (logged by the receiver as `push:reject-uncertified`) rather than being planted.
|
|
150
|
+
const certification = await sourceBlockCertification(this.deps.repo, blockId, blockResult);
|
|
151
151
|
// Push to each target
|
|
152
152
|
const succeeded = [];
|
|
153
153
|
const failed = [];
|
|
@@ -159,7 +159,7 @@ export class SpreadOnChurnMonitor {
|
|
|
159
159
|
// never replies would otherwise hang this sequential loop forever during
|
|
160
160
|
// churn (exactly when slow/dying peers are common); the deadline makes the
|
|
161
161
|
// push throw, which the catch below records as `failed` so the pass advances.
|
|
162
|
-
const response = await client.pushBlocks([blockId], [blockData], 'replication',
|
|
162
|
+
const response = await client.pushBlocks([blockId], [blockData], 'replication', certification, {
|
|
163
163
|
dialTimeoutMs: this.config.pushDialTimeoutMs,
|
|
164
164
|
responseTimeoutMs: this.config.pushResponseTimeoutMs,
|
|
165
165
|
});
|