@optimystic/db-p2p 1.0.0 → 1.2.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.
@@ -182,6 +182,7 @@ export declare class ClusterMember implements ICluster {
182
182
  private executedTransactions;
183
183
  private executedPendResults;
184
184
  private executedCommitResults;
185
+ private behindCommitRefusals;
185
186
  private refusedPendActions;
186
187
  private appliedInvalidations;
187
188
  /**
@@ -276,12 +277,13 @@ export declare class ClusterMember implements ICluster {
276
277
  update(record: ClusterRecord): Promise<ClusterRecord>;
277
278
  private processUpdate;
278
279
  /**
279
- * Add this member's own apply verdicts to a record's {@link ClusterRecord.applyOutcomes}, when
280
- * storage produced any for this transaction. The two arms follow the rules on
281
- * {@link MemberApplyOutcome}: the pend arm carries ONLY a conflict-shaped refusal (a success, a
282
- * bare-reason fault, or no retained verdict leaves it off the coordinator's rule is "an entry
283
- * means retry", so an entry that does not mean retry must not exist); the commit arm carries the
284
- * retained durable verdict whatever it says, because the coordinator counts the successes.
280
+ * Add this member's own apply report to a record's {@link ClusterRecord.applyOutcomes}, when it
281
+ * has anything to report for this transaction. The fields follow the rules on
282
+ * {@link MemberApplyOutcome}: `executed` is set once this member has run the consensus apply; the
283
+ * pend arm carries ONLY a conflict-shaped refusal (a success, a bare-reason fault, or no retained
284
+ * verdict leaves it off the coordinator's rule is "an entry means retry", so an entry that does
285
+ * not mean retry must not exist); the commit arm carries the retained durable verdict whatever it
286
+ * says, because the coordinator counts the successes.
285
287
  *
286
288
  * The member never signs this and never writes another peer's entry.
287
289
  */
@@ -658,6 +660,16 @@ export declare class ClusterMember implements ICluster {
658
660
  * @see docs/internals.md "Check-Then-Act Race in Consensus" and "Independent Node Storage" pitfalls
659
661
  */
660
662
  private handleConsensus;
663
+ /**
664
+ * A delivery of a record this member already applied. Nothing is applied again, but a retained
665
+ * behind-shaped commit refusal gets one more reconcile ({@link reconcileRefusedCommit}, a no-op for
666
+ * every other verdict). This is how a behind remote member heals when it applied on receipt of the
667
+ * commit round, before the coordinating member held the revision: the coordinator re-sends it the
668
+ * merged record once its own member has applied, and this delivery's response carries the
669
+ * refreshed verdict (`withOwnApplyOutcome`), which the durability gate reads. A scheduled commit
670
+ * retry reaching such a member heals it the same way.
671
+ */
672
+ private handleAlreadyExecuted;
661
673
  /**
662
674
  * Applies one consensus-approved operation to local storage.
663
675
  *
@@ -709,12 +721,39 @@ export declare class ClusterMember implements ICluster {
709
721
  /**
710
722
  * Apply one consensus commit to local storage, tolerating every divergence shape the way the
711
723
  * doc comment on {@link applyConsensusOperation} describes and reconciling the behind ones.
712
- * Returns storage's own result — or, for the thrown missing-pend shape, which produces no
724
+ * `applied` is storage's own result — or, for the thrown missing-pend shape, which produces no
713
725
  * `CommitResult` at all, a refusal built from the throw — so {@link durableCommitVerdict} works
714
- * from one uniform shape. Genuine faults (a bare-reason returned failure, an unrecognized throw)
715
- * propagate so {@link handleConsensus} rolls back the executed marker and rethrows.
726
+ * from one uniform shape. `behind` is true exactly when one of the two behind branches ran, which
727
+ * is what makes a refusal eligible for {@link reconcileRefusedCommit}. Genuine faults (a
728
+ * bare-reason returned failure, an unrecognized throw) propagate so {@link handleConsensus} rolls
729
+ * back the executed marker and rethrows.
716
730
  */
717
731
  private applyCommitToStorage;
732
+ /**
733
+ * Retain `verdict` as this member's durable verdict for `record`'s commit (see
734
+ * {@link executedCommitResults}), remember whether a refusal was the behind shape, and — once the
735
+ * verdict is a success — report the committed holders. Shared by the consensus apply and
736
+ * {@link reconcileRefusedCommit} so both leave the same state behind.
737
+ */
738
+ private retainCommitVerdict;
739
+ /**
740
+ * One more reconcile for a commit this member already applied at consensus but could not hold:
741
+ * its retained verdict is a BEHIND-shaped refusal (missing pend or missing base), and the
742
+ * reconcile that ran during the apply found no cohort peer holding the revision yet. Two callers.
743
+ * The coordinating node calls it for its own member once a remote member reports holding the
744
+ * revision (`ClusterCoordinator.broadcastMergedRecord`), for a cohort where the remote members
745
+ * applied after it did. And every member runs it on a redelivery of a record it already applied
746
+ * ({@link handleAlreadyExecuted}), which is how a remote member that applied on receipt of the
747
+ * commit round, before anyone held the revision, heals once the coordinator re-sends it the merged
748
+ * record. The holder's copy carries the cohort's commit proof, so the existing certified
749
+ * single-holder path adopts it; no new trust is involved.
750
+ *
751
+ * Does nothing unless the retained verdict for `record.messageHash` is a behind refusal; in
752
+ * particular an ahead-shaped refusal (`missing`) is never reconciled downward. Recomputes and
753
+ * re-retains the durable verdict afterwards, so `getExecutedCommitResult` then answers a success
754
+ * when the revision landed. Bounded by the same per-block reconcile timeout and never throws.
755
+ */
756
+ reconcileRefusedCommit(record: ClusterRecord): Promise<void>;
718
757
  /**
719
758
  * The durable verdict for a consensus commit: `applied` itself when storage landed it (a
720
759
  * success from `StorageRepo.commit` means every block is committed, or already was, under this
@@ -754,6 +793,16 @@ export declare class ClusterMember implements ICluster {
754
793
  * **synchronous** (no `await` here) so the cert is retained before `StorageRepo.commit` emits its
755
794
  * change event. No-op (zero cost) when no sink is configured; a throwing sink is isolated + logged so
756
795
  * it can never break consensus.
796
+ *
797
+ * A record carrying fewer approving commit signatures than `minSigs` retains no cert, so the bridge
798
+ * skips origination on this member rather than forwarding a cert below its own threshold. Consensus
799
+ * needs only a simple majority of commits, so a member can apply with fewer.
800
+ *
801
+ * NOTE: in a three-member cohort that is the ordinary case for the two remote members. Each applies
802
+ * on receipt of the commit round holding its own commit and the coordinator's (2 of `minSigs` 3), and
803
+ * the coordinator does not broadcast the full set to a member that already applied, so only the
804
+ * coordinating member, which applies with every commit merged, originates. If origination from the
805
+ * other members is ever needed, have the coordinator deliver the merged record to them too.
757
806
  */
758
807
  private captureCommitCert;
759
808
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"cluster-repo.d.ts","sourceRoot":"","sources":["../../../src/cluster/cluster-repo.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"cluster-repo.d.ts","sourceRoot":"","sources":["../../../src/cluster/cluster-repo.ts"],"names":[],"mappings":"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"}
@@ -134,6 +134,12 @@ export class ClusterMember {
134
134
  // applyConsensusOperation swallowed a refusal whose real cause was a RIVAL action holding the
135
135
  // requested revision. Pruned alongside executedTransactions (same TTL).
136
136
  executedCommitResults = new Map();
137
+ // The messageHashes whose retained commit refusal above has the BEHIND shape (missing pend or
138
+ // missing base — this member held no usable revision), as opposed to the ahead shape, which must
139
+ // never be reconciled downward. Recorded from the apply branch that produced the refusal rather
140
+ // than read back from its prose. Only these are eligible for reconcileRefusedCommit. Same TTL,
141
+ // pruning and rollback as executedCommitResults.
142
+ behindCommitRefusals = new Set();
137
143
  // Conflict-shaped pend refusals this member's storage produced at consensus-apply, keyed by the
138
144
  // refused action's id rather than by messageHash. The messageHash-keyed map above cannot serve the
139
145
  // commit-promise guard: a commit is a DIFFERENT message with a different hash, so a member holding
@@ -256,6 +262,7 @@ export class ClusterMember {
256
262
  this.cleanupQueue.length = 0;
257
263
  this.executedPendResults.clear();
258
264
  this.executedCommitResults.clear();
265
+ this.behindCommitRefusals.clear();
259
266
  this.refusedPendActions.clear();
260
267
  }
261
268
  /**
@@ -519,12 +526,13 @@ export class ClusterMember {
519
526
  return currentRecord;
520
527
  }
521
528
  /**
522
- * Add this member's own apply verdicts to a record's {@link ClusterRecord.applyOutcomes}, when
523
- * storage produced any for this transaction. The two arms follow the rules on
524
- * {@link MemberApplyOutcome}: the pend arm carries ONLY a conflict-shaped refusal (a success, a
525
- * bare-reason fault, or no retained verdict leaves it off the coordinator's rule is "an entry
526
- * means retry", so an entry that does not mean retry must not exist); the commit arm carries the
527
- * retained durable verdict whatever it says, because the coordinator counts the successes.
529
+ * Add this member's own apply report to a record's {@link ClusterRecord.applyOutcomes}, when it
530
+ * has anything to report for this transaction. The fields follow the rules on
531
+ * {@link MemberApplyOutcome}: `executed` is set once this member has run the consensus apply; the
532
+ * pend arm carries ONLY a conflict-shaped refusal (a success, a bare-reason fault, or no retained
533
+ * verdict leaves it off the coordinator's rule is "an entry means retry", so an entry that does
534
+ * not mean retry must not exist); the commit arm carries the retained durable verdict whatever it
535
+ * says, because the coordinator counts the successes.
528
536
  *
529
537
  * The member never signs this and never writes another peer's entry.
530
538
  */
@@ -532,6 +540,7 @@ export class ClusterMember {
532
540
  const pend = this.executedPendResults.get(record.messageHash);
533
541
  const commit = this.executedCommitResults.get(record.messageHash);
534
542
  const own = {
543
+ ...(this.executedTransactions.has(record.messageHash) ? { executed: true } : {}),
535
544
  ...(pend !== undefined && !pend.success && isConflictFailure(pend) ? { pend } : {}),
536
545
  ...(commit !== undefined ? { commit } : {})
537
546
  };
@@ -1968,14 +1977,14 @@ export class ClusterMember {
1968
1977
  // Check persistent store first for post-recovery dedup (in-memory map is cleared on restart).
1969
1978
  // wasTransactionExecutedAsync also checks the in-memory map as a fast path.
1970
1979
  if (await this.wasTransactionExecutedAsync(record.messageHash)) {
1971
- log('cluster-member:consensus-already-executed', { messageHash: record.messageHash });
1980
+ await this.handleAlreadyExecuted(record);
1972
1981
  return;
1973
1982
  }
1974
1983
  // Check-and-set ATOMICALLY to prevent race condition where multiple calls
1975
1984
  // pass the async check before any completes. Since JavaScript is single-threaded,
1976
1985
  // this synchronous check-and-set is atomic before any await.
1977
1986
  if (this.executedTransactions.has(record.messageHash)) {
1978
- log('cluster-member:consensus-already-executed', { messageHash: record.messageHash });
1987
+ await this.handleAlreadyExecuted(record);
1979
1988
  return;
1980
1989
  }
1981
1990
  // Set the in-memory guard IMMEDIATELY, before any async operations: its synchronous
@@ -2002,6 +2011,7 @@ export class ClusterMember {
2002
2011
  this.executedTransactions.delete(record.messageHash);
2003
2012
  this.executedPendResults.delete(record.messageHash);
2004
2013
  this.executedCommitResults.delete(record.messageHash);
2014
+ this.behindCommitRefusals.delete(record.messageHash);
2005
2015
  for (const operation of record.message.operations) {
2006
2016
  if ('pend' in operation)
2007
2017
  this.refusedPendActions.delete(operation.pend.actionId);
@@ -2021,6 +2031,19 @@ export class ClusterMember {
2021
2031
  this.stateStore?.markExecuted(record.messageHash, executedAt)
2022
2032
  .catch(err => log('cluster-member:persist-executed-error', { messageHash: record.messageHash, error: err.message }));
2023
2033
  }
2034
+ /**
2035
+ * A delivery of a record this member already applied. Nothing is applied again, but a retained
2036
+ * behind-shaped commit refusal gets one more reconcile ({@link reconcileRefusedCommit}, a no-op for
2037
+ * every other verdict). This is how a behind remote member heals when it applied on receipt of the
2038
+ * commit round, before the coordinating member held the revision: the coordinator re-sends it the
2039
+ * merged record once its own member has applied, and this delivery's response carries the
2040
+ * refreshed verdict (`withOwnApplyOutcome`), which the durability gate reads. A scheduled commit
2041
+ * retry reaching such a member heals it the same way.
2042
+ */
2043
+ async handleAlreadyExecuted(record) {
2044
+ log('cluster-member:consensus-already-executed', { messageHash: record.messageHash });
2045
+ await this.reconcileRefusedCommit(record);
2046
+ }
2024
2047
  /**
2025
2048
  * Applies one consensus-approved operation to local storage.
2026
2049
  *
@@ -2149,7 +2172,7 @@ export class ClusterMember {
2149
2172
  membershipVersion: record.membershipVersion
2150
2173
  });
2151
2174
  }
2152
- const applied = await this.applyCommitToStorage(record, commit, proof);
2175
+ const { applied, behind } = await this.applyCommitToStorage(record, commit, proof);
2153
2176
  // Retain the POST-RECONCILE durable verdict (see getExecutedCommitResult and
2154
2177
  // withOwnApplyOutcome): what this member's storage holds NOW, after any behind-reconcile the
2155
2178
  // apply triggered — not what the first apply attempt said. A member that pulled the
@@ -2158,10 +2181,7 @@ export class ClusterMember {
2158
2181
  // verdicts — its own member's through getExecutedCommitResult, every other member's off the
2159
2182
  // response record — and the two readers must see the same answer, hence one verdict,
2160
2183
  // computed once, retained here for both.
2161
- const verdict = await this.durableCommitVerdict(commit, applied);
2162
- this.executedCommitResults.set(messageHash, verdict);
2163
- if (verdict.success)
2164
- this.reportCommittedHolders(record, commit);
2184
+ this.retainCommitVerdict(record, commit, await this.durableCommitVerdict(commit, applied), behind);
2165
2185
  return;
2166
2186
  }
2167
2187
  if ('invalidate' in operation) {
@@ -2172,10 +2192,12 @@ export class ClusterMember {
2172
2192
  /**
2173
2193
  * Apply one consensus commit to local storage, tolerating every divergence shape the way the
2174
2194
  * doc comment on {@link applyConsensusOperation} describes and reconciling the behind ones.
2175
- * Returns storage's own result — or, for the thrown missing-pend shape, which produces no
2195
+ * `applied` is storage's own result — or, for the thrown missing-pend shape, which produces no
2176
2196
  * `CommitResult` at all, a refusal built from the throw — so {@link durableCommitVerdict} works
2177
- * from one uniform shape. Genuine faults (a bare-reason returned failure, an unrecognized throw)
2178
- * propagate so {@link handleConsensus} rolls back the executed marker and rethrows.
2197
+ * from one uniform shape. `behind` is true exactly when one of the two behind branches ran, which
2198
+ * is what makes a refusal eligible for {@link reconcileRefusedCommit}. Genuine faults (a
2199
+ * bare-reason returned failure, an unrecognized throw) propagate so {@link handleConsensus} rolls
2200
+ * back the executed marker and rethrows.
2179
2201
  */
2180
2202
  async applyCommitToStorage(record, commit, proof) {
2181
2203
  const messageHash = record.messageHash;
@@ -2198,12 +2220,12 @@ export class ClusterMember {
2198
2220
  // cohort peer so the block is no longer under-replicated. Best-effort:
2199
2221
  // failures are logged inside, never thrown (a throw would reset the stream).
2200
2222
  await this.reconcileDivergentCommit(record, commit);
2201
- return { success: false, reason: err.message };
2223
+ return { applied: { success: false, reason: err.message }, behind: true };
2202
2224
  }
2203
2225
  throw err;
2204
2226
  }
2205
2227
  if (result.success) {
2206
- return result;
2228
+ return { applied: result, behind: false };
2207
2229
  }
2208
2230
  // success:false is a StaleFailure. `missing` ⇒ ahead/stale divergence
2209
2231
  // (we already hold ≥ this rev): tolerate, do NOT reconcile downward. A
@@ -2233,7 +2255,7 @@ export class ClusterMember {
2233
2255
  reason: result.reason,
2234
2256
  hasMissing: true
2235
2257
  });
2236
- return result;
2258
+ return { applied: result, behind: false };
2237
2259
  }
2238
2260
  // This member holds no materializable base for one of the blocks, so
2239
2261
  // `StorageRepo.commit` REFUSED rather than record a revision it could never serve.
@@ -2249,10 +2271,64 @@ export class ClusterMember {
2249
2271
  reason: result.reason
2250
2272
  });
2251
2273
  await this.reconcileDivergentCommit(record, commit);
2252
- return result;
2274
+ return { applied: result, behind: true };
2253
2275
  }
2254
2276
  throw new Error(`Consensus commit for action ${commit.actionId} failed: ${result.reason ?? 'unknown reason'}`);
2255
2277
  }
2278
+ /**
2279
+ * Retain `verdict` as this member's durable verdict for `record`'s commit (see
2280
+ * {@link executedCommitResults}), remember whether a refusal was the behind shape, and — once the
2281
+ * verdict is a success — report the committed holders. Shared by the consensus apply and
2282
+ * {@link reconcileRefusedCommit} so both leave the same state behind.
2283
+ */
2284
+ retainCommitVerdict(record, commit, verdict, behind) {
2285
+ this.executedCommitResults.set(record.messageHash, verdict);
2286
+ if (verdict.success) {
2287
+ this.behindCommitRefusals.delete(record.messageHash);
2288
+ this.reportCommittedHolders(record, commit);
2289
+ }
2290
+ else if (behind) {
2291
+ this.behindCommitRefusals.add(record.messageHash);
2292
+ }
2293
+ }
2294
+ /**
2295
+ * One more reconcile for a commit this member already applied at consensus but could not hold:
2296
+ * its retained verdict is a BEHIND-shaped refusal (missing pend or missing base), and the
2297
+ * reconcile that ran during the apply found no cohort peer holding the revision yet. Two callers.
2298
+ * The coordinating node calls it for its own member once a remote member reports holding the
2299
+ * revision (`ClusterCoordinator.broadcastMergedRecord`), for a cohort where the remote members
2300
+ * applied after it did. And every member runs it on a redelivery of a record it already applied
2301
+ * ({@link handleAlreadyExecuted}), which is how a remote member that applied on receipt of the
2302
+ * commit round, before anyone held the revision, heals once the coordinator re-sends it the merged
2303
+ * record. The holder's copy carries the cohort's commit proof, so the existing certified
2304
+ * single-holder path adopts it; no new trust is involved.
2305
+ *
2306
+ * Does nothing unless the retained verdict for `record.messageHash` is a behind refusal; in
2307
+ * particular an ahead-shaped refusal (`missing`) is never reconciled downward. Recomputes and
2308
+ * re-retains the durable verdict afterwards, so `getExecutedCommitResult` then answers a success
2309
+ * when the revision landed. Bounded by the same per-block reconcile timeout and never throws.
2310
+ */
2311
+ async reconcileRefusedCommit(record) {
2312
+ const messageHash = record.messageHash;
2313
+ const refused = this.executedCommitResults.get(messageHash);
2314
+ if (refused === undefined || refused.success || !this.behindCommitRefusals.has(messageHash)) {
2315
+ return;
2316
+ }
2317
+ const operation = record.message.operations.find((op) => 'commit' in op);
2318
+ if (operation === undefined) {
2319
+ return;
2320
+ }
2321
+ const commit = operation.commit;
2322
+ log('cluster-member:consensus-commit-reconcile-again', { messageHash, actionId: commit.actionId, rev: commit.rev });
2323
+ await this.reconcileDivergentCommit(record, commit);
2324
+ const verdict = await this.durableCommitVerdict(commit, refused);
2325
+ // The apply's retention may have been pruned or rolled back while the reconcile ran; never
2326
+ // resurrect it.
2327
+ if (this.executedCommitResults.get(messageHash) !== refused) {
2328
+ return;
2329
+ }
2330
+ this.retainCommitVerdict(record, commit, verdict, true);
2331
+ }
2256
2332
  /**
2257
2333
  * The durable verdict for a consensus commit: `applied` itself when storage landed it (a
2258
2334
  * success from `StorageRepo.commit` means every block is committed, or already was, under this
@@ -2386,6 +2462,16 @@ export class ClusterMember {
2386
2462
  * **synchronous** (no `await` here) so the cert is retained before `StorageRepo.commit` emits its
2387
2463
  * change event. No-op (zero cost) when no sink is configured; a throwing sink is isolated + logged so
2388
2464
  * it can never break consensus.
2465
+ *
2466
+ * A record carrying fewer approving commit signatures than `minSigs` retains no cert, so the bridge
2467
+ * skips origination on this member rather than forwarding a cert below its own threshold. Consensus
2468
+ * needs only a simple majority of commits, so a member can apply with fewer.
2469
+ *
2470
+ * NOTE: in a three-member cohort that is the ordinary case for the two remote members. Each applies
2471
+ * on receipt of the commit round holding its own commit and the coordinator's (2 of `minSigs` 3), and
2472
+ * the coordinator does not broadcast the full set to a member that already applied, so only the
2473
+ * coordinating member, which applies with every commit merged, originates. If origination from the
2474
+ * other members is ever needed, have the coordinator deliver the merged record to them too.
2389
2475
  */
2390
2476
  captureCommitCert(record, actionId, signedPayload) {
2391
2477
  if (!this.onCommitCertificate) {
@@ -2393,7 +2479,12 @@ export class ClusterMember {
2393
2479
  }
2394
2480
  const minSigs = Math.ceil(Object.keys(record.peers).length * this.superMajorityThreshold);
2395
2481
  try {
2396
- this.onCommitCertificate(actionId, buildCommitCert(record, minSigs, signedPayload));
2482
+ const cert = buildCommitCert(record, minSigs, signedPayload);
2483
+ if (cert.signers.length < minSigs) {
2484
+ log('cluster-member:commit-cert-below-threshold', { actionId, signers: cert.signers.length, minSigs });
2485
+ return;
2486
+ }
2487
+ this.onCommitCertificate(actionId, cert);
2397
2488
  }
2398
2489
  catch (err) {
2399
2490
  log('cluster-member:commit-cert-sink-error', { actionId, error: err.message });
@@ -2619,6 +2710,7 @@ export class ClusterMember {
2619
2710
  this.executedTransactions.delete(messageHash);
2620
2711
  this.executedPendResults.delete(messageHash);
2621
2712
  this.executedCommitResults.delete(messageHash);
2713
+ this.behindCommitRefusals.delete(messageHash);
2622
2714
  }
2623
2715
  }
2624
2716
  // Prune actionId-keyed pend refusals on the same TTL as the verdicts they were derived from.