@optimystic/db-p2p 1.0.0-beta.1 → 1.0.0-beta.3
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/dist/src/cluster/cluster-repo.d.ts +46 -11
- package/dist/src/cluster/cluster-repo.d.ts.map +1 -1
- package/dist/src/cluster/cluster-repo.js +188 -102
- package/dist/src/cluster/cluster-repo.js.map +1 -1
- package/dist/src/libp2p-key-network.d.ts.map +1 -1
- package/dist/src/libp2p-key-network.js +7 -0
- package/dist/src/libp2p-key-network.js.map +1 -1
- package/dist/src/logger.d.ts.map +1 -1
- package/dist/src/logger.js +13 -6
- package/dist/src/logger.js.map +1 -1
- package/dist/src/repo/cluster-coordinator.d.ts +42 -0
- package/dist/src/repo/cluster-coordinator.d.ts.map +1 -1
- package/dist/src/repo/cluster-coordinator.js +50 -9
- package/dist/src/repo/cluster-coordinator.js.map +1 -1
- package/dist/src/repo/coordinator-repo.d.ts +74 -7
- package/dist/src/repo/coordinator-repo.d.ts.map +1 -1
- package/dist/src/repo/coordinator-repo.js +327 -80
- package/dist/src/repo/coordinator-repo.js.map +1 -1
- package/dist/src/storage/storage-repo.d.ts +15 -0
- package/dist/src/storage/storage-repo.d.ts.map +1 -1
- package/dist/src/storage/storage-repo.js +28 -0
- package/dist/src/storage/storage-repo.js.map +1 -1
- package/dist/src/testing/mesh-harness.d.ts +15 -0
- package/dist/src/testing/mesh-harness.d.ts.map +1 -1
- package/dist/src/testing/mesh-harness.js +21 -4
- package/dist/src/testing/mesh-harness.js.map +1 -1
- package/package.json +2 -2
- package/src/cluster/cluster-repo.ts +2749 -2662
- package/src/libp2p-key-network.ts +7 -0
- package/src/logger.ts +14 -6
- package/src/repo/cluster-coordinator.ts +1170 -1113
- package/src/repo/coordinator-repo.ts +2940 -2675
- package/src/storage/storage-repo.ts +30 -0
- package/src/testing/mesh-harness.ts +37 -4
|
@@ -223,13 +223,15 @@ export declare class ClusterMember implements ICluster {
|
|
|
223
223
|
*/
|
|
224
224
|
getExecutedPendResult(messageHash: string): PendResult | undefined;
|
|
225
225
|
/**
|
|
226
|
-
* Commit-shaped sibling of {@link getExecutedPendResult}:
|
|
227
|
-
* operation applied during consensus, when
|
|
228
|
-
*
|
|
229
|
-
*
|
|
230
|
-
*
|
|
231
|
-
*
|
|
232
|
-
*
|
|
226
|
+
* Commit-shaped sibling of {@link getExecutedPendResult}: this member's post-reconcile durable
|
|
227
|
+
* verdict for a commit operation applied during consensus, when it retained one — `success: true`
|
|
228
|
+
* iff local storage holds the committed revision under the record's action for every block the
|
|
229
|
+
* commit named, after the apply and any behind-reconcile it triggered. `CoordinatorRepo.commit`
|
|
230
|
+
* reads it after a locally-executed commit-consensus, both as this node's own contribution to the
|
|
231
|
+
* durable-holder count its durability gate needs, and — for a retained refusal whose cause a
|
|
232
|
+
* local re-read confirms as a rival holding the requested revision — as the trigger for a
|
|
233
|
+
* retryable conflict answer. Same availability caveats as the pend accessor: in-memory only,
|
|
234
|
+
* absent for pre-restart applies, pruned on the executed-transaction TTL.
|
|
233
235
|
*/
|
|
234
236
|
getExecutedCommitResult(messageHash: string): CommitResult | undefined;
|
|
235
237
|
/**
|
|
@@ -247,10 +249,12 @@ export declare class ClusterMember implements ICluster {
|
|
|
247
249
|
update(record: ClusterRecord): Promise<ClusterRecord>;
|
|
248
250
|
private processUpdate;
|
|
249
251
|
/**
|
|
250
|
-
* Add this member's own
|
|
251
|
-
*
|
|
252
|
-
*
|
|
253
|
-
*
|
|
252
|
+
* Add this member's own apply verdicts to a record's {@link ClusterRecord.applyOutcomes}, when
|
|
253
|
+
* storage produced any for this transaction. The two arms follow the rules on
|
|
254
|
+
* {@link MemberApplyOutcome}: the pend arm carries ONLY a conflict-shaped refusal (a success, a
|
|
255
|
+
* bare-reason fault, or no retained verdict leaves it off — the coordinator's rule is "an entry
|
|
256
|
+
* means retry", so an entry that does not mean retry must not exist); the commit arm carries the
|
|
257
|
+
* retained durable verdict whatever it says, because the coordinator counts the successes.
|
|
254
258
|
*
|
|
255
259
|
* The member never signs this and never writes another peer's entry.
|
|
256
260
|
*/
|
|
@@ -585,8 +589,39 @@ export declare class ClusterMember implements ICluster {
|
|
|
585
589
|
* mid-commit `internalCommit` fault (`success:false` with a bare `reason`, no
|
|
586
590
|
* `missing`) is propagated so {@link handleConsensus} rolls back the executed marker
|
|
587
591
|
* and rethrows — exactly like an unexpected thrown fault.
|
|
592
|
+
*
|
|
593
|
+
* Tolerated is not the same as acknowledged. Whatever shape a commit's apply took, this
|
|
594
|
+
* member retains one post-reconcile durable verdict for it ({@link durableCommitVerdict}) and
|
|
595
|
+
* reports it to the coordinator, whose durability gate (`CoordinatorRepo.commit`) acknowledges
|
|
596
|
+
* the writer only when a majority of the cohort reports holding the revision.
|
|
588
597
|
*/
|
|
589
598
|
private applyConsensusOperation;
|
|
599
|
+
/**
|
|
600
|
+
* Apply one consensus commit to local storage, tolerating every divergence shape the way the
|
|
601
|
+
* doc comment on {@link applyConsensusOperation} describes and reconciling the behind ones.
|
|
602
|
+
* Returns storage's own result — or, for the thrown missing-pend shape, which produces no
|
|
603
|
+
* `CommitResult` at all, a refusal built from the throw — so {@link durableCommitVerdict} works
|
|
604
|
+
* from one uniform shape. Genuine faults (a bare-reason returned failure, an unrecognized throw)
|
|
605
|
+
* propagate so {@link handleConsensus} rolls back the executed marker and rethrows.
|
|
606
|
+
*/
|
|
607
|
+
private applyCommitToStorage;
|
|
608
|
+
/**
|
|
609
|
+
* The durable verdict for a consensus commit: `applied` itself when storage landed it (a
|
|
610
|
+
* success from `StorageRepo.commit` means every block is committed, or already was, under this
|
|
611
|
+
* action), otherwise a fresh look at what local storage holds NOW — after the reconcile the
|
|
612
|
+
* refusal triggered. Durable means every block in `commit.blockIds` records `commit.rev` under
|
|
613
|
+
* `commit.actionId`. A refusal that is not durable is returned as-is, so the coordinating node
|
|
614
|
+
* can still classify its own member's ahead-shaped refusal against a rival. A read fault counts
|
|
615
|
+
* as not durable: the gate this feeds fails toward retry.
|
|
616
|
+
*/
|
|
617
|
+
private durableCommitVerdict;
|
|
618
|
+
/**
|
|
619
|
+
* True when local storage records `commit.rev` of `blockId` under `commit.actionId`. Read from
|
|
620
|
+
* the revision index (the {@link IRevisionActionReader} capability) so a member whose `latest` has
|
|
621
|
+
* since moved past the revision still counts as holding it; a repo without the capability is
|
|
622
|
+
* judged on `latest` alone.
|
|
623
|
+
*/
|
|
624
|
+
private holdsCommittedRevision;
|
|
590
625
|
/**
|
|
591
626
|
* Applies a consensus-ordered invalidation on this member: dedup → certificate verification →
|
|
592
627
|
* capture the invalidation's commit cert (for reactivity reuse) → delegate the compensating write +
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cluster-repo.d.ts","sourceRoot":"","sources":["../../../src/cluster/cluster-repo.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,aAAa,EAAE,YAAY,EAAa,WAAW,EAAE,qBAAqB,EAAE,sBAAsB,EAA2B,YAAY,EAAE,UAAU,EAAE,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAiB,UAAU,EAAE,iBAAiB,
|
|
1
|
+
{"version":3,"file":"cluster-repo.d.ts","sourceRoot":"","sources":["../../../src/cluster/cluster-repo.ts"],"names":[],"mappings":"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"}
|
|
@@ -99,13 +99,17 @@ export class ClusterMember {
|
|
|
99
99
|
// a pend that every member refused (rival pending action, or the revision already taken) must
|
|
100
100
|
// reach the writer as a conflict, not a win. Pruned alongside executedTransactions (same TTL).
|
|
101
101
|
executedPendResults = new Map();
|
|
102
|
-
//
|
|
103
|
-
// CommitResult)
|
|
102
|
+
// This member's POST-RECONCILE durable verdict for a COMMIT operation applied during consensus
|
|
103
|
+
// (messageHash -> CommitResult): whether local storage holds the committed revision under the
|
|
104
|
+
// record's action once the apply — and any behind-reconcile it triggered — has run. Two readers,
|
|
105
|
+
// which must see the same answer: the coordinating node reads its own member's directly
|
|
106
|
+
// (getExecutedCommitResult), every other member's reaches the coordinator on the response record
|
|
107
|
+
// (withOwnApplyOutcome → ClusterRecord.applyOutcomes[peer].commit). Together they feed the
|
|
108
|
+
// durability gate in CoordinatorRepo.commit, which acknowledges a commit only when a majority of
|
|
109
|
+
// the cohort reports holding it — consensus votes were never evidence of storage. A retained
|
|
110
|
+
// refusal also lets the coordinator detect when the ahead-divergence tolerance in
|
|
104
111
|
// applyConsensusOperation swallowed a refusal whose real cause was a RIVAL action holding the
|
|
105
|
-
// requested revision
|
|
106
|
-
// inside every member's signed-but-not-yet-applied window is refused by every member's storage
|
|
107
|
-
// at apply, and without this verdict the coordinator fabricates a success no member durably
|
|
108
|
-
// stored. Pruned alongside executedTransactions (same TTL).
|
|
112
|
+
// requested revision. Pruned alongside executedTransactions (same TTL).
|
|
109
113
|
executedCommitResults = new Map();
|
|
110
114
|
// Conflict-shaped pend refusals this member's storage produced at consensus-apply, keyed by the
|
|
111
115
|
// refused action's id rather than by messageHash. The messageHash-keyed map above cannot serve the
|
|
@@ -243,13 +247,15 @@ export class ClusterMember {
|
|
|
243
247
|
return this.executedPendResults.get(messageHash);
|
|
244
248
|
}
|
|
245
249
|
/**
|
|
246
|
-
* Commit-shaped sibling of {@link getExecutedPendResult}:
|
|
247
|
-
* operation applied during consensus, when
|
|
248
|
-
*
|
|
249
|
-
*
|
|
250
|
-
*
|
|
251
|
-
*
|
|
252
|
-
*
|
|
250
|
+
* Commit-shaped sibling of {@link getExecutedPendResult}: this member's post-reconcile durable
|
|
251
|
+
* verdict for a commit operation applied during consensus, when it retained one — `success: true`
|
|
252
|
+
* iff local storage holds the committed revision under the record's action for every block the
|
|
253
|
+
* commit named, after the apply and any behind-reconcile it triggered. `CoordinatorRepo.commit`
|
|
254
|
+
* reads it after a locally-executed commit-consensus, both as this node's own contribution to the
|
|
255
|
+
* durable-holder count its durability gate needs, and — for a retained refusal whose cause a
|
|
256
|
+
* local re-read confirms as a rival holding the requested revision — as the trigger for a
|
|
257
|
+
* retryable conflict answer. Same availability caveats as the pend accessor: in-memory only,
|
|
258
|
+
* absent for pre-restart applies, pruned on the executed-transaction TTL.
|
|
253
259
|
*/
|
|
254
260
|
getExecutedCommitResult(messageHash) {
|
|
255
261
|
return this.executedCommitResults.get(messageHash);
|
|
@@ -472,21 +478,28 @@ export class ClusterMember {
|
|
|
472
478
|
return currentRecord;
|
|
473
479
|
}
|
|
474
480
|
/**
|
|
475
|
-
* Add this member's own
|
|
476
|
-
*
|
|
477
|
-
*
|
|
478
|
-
*
|
|
481
|
+
* Add this member's own apply verdicts to a record's {@link ClusterRecord.applyOutcomes}, when
|
|
482
|
+
* storage produced any for this transaction. The two arms follow the rules on
|
|
483
|
+
* {@link MemberApplyOutcome}: the pend arm carries ONLY a conflict-shaped refusal (a success, a
|
|
484
|
+
* bare-reason fault, or no retained verdict leaves it off — the coordinator's rule is "an entry
|
|
485
|
+
* means retry", so an entry that does not mean retry must not exist); the commit arm carries the
|
|
486
|
+
* retained durable verdict whatever it says, because the coordinator counts the successes.
|
|
479
487
|
*
|
|
480
488
|
* The member never signs this and never writes another peer's entry.
|
|
481
489
|
*/
|
|
482
490
|
withOwnApplyOutcome(record) {
|
|
483
|
-
const
|
|
484
|
-
|
|
491
|
+
const pend = this.executedPendResults.get(record.messageHash);
|
|
492
|
+
const commit = this.executedCommitResults.get(record.messageHash);
|
|
493
|
+
const own = {
|
|
494
|
+
...(pend !== undefined && !pend.success && isConflictFailure(pend) ? { pend } : {}),
|
|
495
|
+
...(commit !== undefined ? { commit } : {})
|
|
496
|
+
};
|
|
497
|
+
if (Object.keys(own).length === 0) {
|
|
485
498
|
return record;
|
|
486
499
|
}
|
|
487
500
|
return {
|
|
488
501
|
...record,
|
|
489
|
-
applyOutcomes: { ...record.applyOutcomes, [this.peerId.toString()]:
|
|
502
|
+
applyOutcomes: { ...record.applyOutcomes, [this.peerId.toString()]: own }
|
|
490
503
|
};
|
|
491
504
|
}
|
|
492
505
|
/**
|
|
@@ -1750,6 +1763,11 @@ export class ClusterMember {
|
|
|
1750
1763
|
* mid-commit `internalCommit` fault (`success:false` with a bare `reason`, no
|
|
1751
1764
|
* `missing`) is propagated so {@link handleConsensus} rolls back the executed marker
|
|
1752
1765
|
* and rethrows — exactly like an unexpected thrown fault.
|
|
1766
|
+
*
|
|
1767
|
+
* Tolerated is not the same as acknowledged. Whatever shape a commit's apply took, this
|
|
1768
|
+
* member retains one post-reconcile durable verdict for it ({@link durableCommitVerdict}) and
|
|
1769
|
+
* reports it to the coordinator, whose durability gate (`CoordinatorRepo.commit`) acknowledges
|
|
1770
|
+
* the writer only when a majority of the cohort reports holding the revision.
|
|
1753
1771
|
*/
|
|
1754
1772
|
async applyConsensusOperation(record, operation) {
|
|
1755
1773
|
const messageHash = record.messageHash;
|
|
@@ -1776,7 +1794,17 @@ export class ClusterMember {
|
|
|
1776
1794
|
// conflict-shaped refusals are kept — a bare-reason fault is local trouble, not evidence
|
|
1777
1795
|
// that a rival holds the blocks, and must not veto the cohort's commit.
|
|
1778
1796
|
if (!result.success && isConflictFailure(result)) {
|
|
1779
|
-
|
|
1797
|
+
// `Date.now()`, not the injected `this.now()`: the prune sweep and the executed-marker
|
|
1798
|
+
// timestamps this retention shares a TTL with are both on the wall clock, and a mixed
|
|
1799
|
+
// pair would prune on the first sweep (or never) under an injected clock.
|
|
1800
|
+
this.refusedPendActions.set(operation.pend.actionId, Date.now());
|
|
1801
|
+
}
|
|
1802
|
+
else if (result.success) {
|
|
1803
|
+
// A later apply of the SAME action that storage accepted retires the refusal: the
|
|
1804
|
+
// retention means "the last thing my storage said about this action's pend was no",
|
|
1805
|
+
// and a stale yes-then-no would veto the commit of an action this member now holds
|
|
1806
|
+
// pended, whenever any unrelated rival happens to sit on one of its blocks.
|
|
1807
|
+
this.refusedPendActions.delete(operation.pend.actionId);
|
|
1780
1808
|
}
|
|
1781
1809
|
if (!result.success) {
|
|
1782
1810
|
log('cluster-member:consensus-pend-diverged', {
|
|
@@ -1822,87 +1850,16 @@ export class ClusterMember {
|
|
|
1822
1850
|
membershipVersion: record.membershipVersion
|
|
1823
1851
|
});
|
|
1824
1852
|
}
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
messageHash,
|
|
1836
|
-
actionId: commit.actionId,
|
|
1837
|
-
divergence: 'behind',
|
|
1838
|
-
reason: err.message
|
|
1839
|
-
});
|
|
1840
|
-
// We hold no revision of these blocks; pull the committed revision from a
|
|
1841
|
-
// cohort peer so the block is no longer under-replicated. Best-effort:
|
|
1842
|
-
// failures are logged inside, never thrown (a throw would reset the stream).
|
|
1843
|
-
await this.reconcileDivergentCommit(record, commit);
|
|
1844
|
-
return;
|
|
1845
|
-
}
|
|
1846
|
-
throw err;
|
|
1847
|
-
}
|
|
1848
|
-
// Retain the verdict either way (see getExecutedCommitResult): a success confirms local
|
|
1849
|
-
// durability, and an ahead-shaped refusal is the only evidence the coordinator has that
|
|
1850
|
-
// the tolerance below swallowed a rival's win at the requested revision. The
|
|
1851
|
-
// missing-pending throw path above retains nothing — no CommitResult exists there, and
|
|
1852
|
-
// the coordinator's fabricated-success fallback plus cohort reconcile is the right shape
|
|
1853
|
-
// for a member that is genuinely behind.
|
|
1854
|
-
this.executedCommitResults.set(messageHash, result);
|
|
1855
|
-
if (!result.success) {
|
|
1856
|
-
// success:false is a StaleFailure. `missing` ⇒ ahead/stale divergence
|
|
1857
|
-
// (we already hold ≥ this rev): tolerate, do NOT reconcile downward. A
|
|
1858
|
-
// missing-base reason ⇒ behind divergence, reconcile (below). Any other bare
|
|
1859
|
-
// `reason` with no `missing` ⇒ a genuine internalCommit fault: propagate so
|
|
1860
|
-
// handleConsensus rolls back the executed marker and rethrows.
|
|
1861
|
-
//
|
|
1862
|
-
// NOTE: this 'ahead' tolerance is what turns a rival's commit that somehow reaches
|
|
1863
|
-
// consensus into a reported success no member durably stored (consensus without
|
|
1864
|
-
// durability — the commit-tier acknowledgement hole). It must stay: a member
|
|
1865
|
-
// genuinely ahead of a redelivered/lagging commit is the common, correct case. The
|
|
1866
|
-
// guards live UPSTREAM: `validateCommitRevisions` rejects the rival at the promise
|
|
1867
|
-
// round, `CoordinatorRepo.commit` returns lost races as retryable conflicts instead
|
|
1868
|
-
// of re-driving them, and the verdict retained just above (getExecutedCommitResult)
|
|
1869
|
-
// lets the coordinating node convert its OWN member's rival-confirmed refusal into a
|
|
1870
|
-
// conflict answer — that last guard is what closes the signed-but-not-yet-applied
|
|
1871
|
-
// window, where two commits for one revision both assemble consensus because signing
|
|
1872
|
-
// drops each member's reservation before applying advances its storage —
|
|
1873
|
-
// `validateCommitAgainstRefusedPend` now refuses to sign that commit in the first
|
|
1874
|
-
// place on any member that refused its pend. If consensus-without-durability is ever
|
|
1875
|
-
// observed again, look at those guards' remaining abstain residuals (capability-less
|
|
1876
|
-
// or history-truncated storage; a member whose refusal has aged out of retention),
|
|
1877
|
-
// not at this branch.
|
|
1878
|
-
if (result.missing?.length) {
|
|
1879
|
-
log('cluster-member:consensus-commit-diverged', {
|
|
1880
|
-
messageHash,
|
|
1881
|
-
actionId: commit.actionId,
|
|
1882
|
-
divergence: 'ahead',
|
|
1883
|
-
reason: result.reason,
|
|
1884
|
-
hasMissing: true
|
|
1885
|
-
});
|
|
1886
|
-
return;
|
|
1887
|
-
}
|
|
1888
|
-
// This member holds no materializable base for one of the blocks, so
|
|
1889
|
-
// `StorageRepo.commit` REFUSED rather than record a revision it could never serve.
|
|
1890
|
-
// Same "behind" divergence as a missing pend — and the same cure: pull the committed
|
|
1891
|
-
// revision from a cohort peer. Reconciling here (after commit released its per-block
|
|
1892
|
-
// latches) is what makes refusing safe; fetching inside the commit path would deadlock
|
|
1893
|
-
// against the latch `saveReplicatedBlock` needs to persist what it fetched.
|
|
1894
|
-
if (isMissingBaseRevisionFailure(result)) {
|
|
1895
|
-
log('cluster-member:consensus-commit-diverged', {
|
|
1896
|
-
messageHash,
|
|
1897
|
-
actionId: commit.actionId,
|
|
1898
|
-
divergence: 'behind',
|
|
1899
|
-
reason: result.reason
|
|
1900
|
-
});
|
|
1901
|
-
await this.reconcileDivergentCommit(record, commit);
|
|
1902
|
-
return;
|
|
1903
|
-
}
|
|
1904
|
-
throw new Error(`Consensus commit for action ${commit.actionId} failed: ${result.reason ?? 'unknown reason'}`);
|
|
1905
|
-
}
|
|
1853
|
+
const applied = await this.applyCommitToStorage(record, commit, proof);
|
|
1854
|
+
// Retain the POST-RECONCILE durable verdict (see getExecutedCommitResult and
|
|
1855
|
+
// withOwnApplyOutcome): what this member's storage holds NOW, after any behind-reconcile the
|
|
1856
|
+
// apply triggered — not what the first apply attempt said. A member that pulled the
|
|
1857
|
+
// revision from a cohort peer is a durable holder and reports one; a member that could not
|
|
1858
|
+
// is not, whichever shape its refusal took. The coordinator's durability gate counts these
|
|
1859
|
+
// verdicts — its own member's through getExecutedCommitResult, every other member's off the
|
|
1860
|
+
// response record — and the two readers must see the same answer, hence one verdict,
|
|
1861
|
+
// computed once, retained here for both.
|
|
1862
|
+
this.executedCommitResults.set(messageHash, await this.durableCommitVerdict(commit, applied));
|
|
1906
1863
|
return;
|
|
1907
1864
|
}
|
|
1908
1865
|
if ('invalidate' in operation) {
|
|
@@ -1910,6 +1867,135 @@ export class ClusterMember {
|
|
|
1910
1867
|
return;
|
|
1911
1868
|
}
|
|
1912
1869
|
}
|
|
1870
|
+
/**
|
|
1871
|
+
* Apply one consensus commit to local storage, tolerating every divergence shape the way the
|
|
1872
|
+
* doc comment on {@link applyConsensusOperation} describes and reconciling the behind ones.
|
|
1873
|
+
* Returns storage's own result — or, for the thrown missing-pend shape, which produces no
|
|
1874
|
+
* `CommitResult` at all, a refusal built from the throw — so {@link durableCommitVerdict} works
|
|
1875
|
+
* from one uniform shape. Genuine faults (a bare-reason returned failure, an unrecognized throw)
|
|
1876
|
+
* propagate so {@link handleConsensus} rolls back the executed marker and rethrows.
|
|
1877
|
+
*/
|
|
1878
|
+
async applyCommitToStorage(record, commit, proof) {
|
|
1879
|
+
const messageHash = record.messageHash;
|
|
1880
|
+
let result;
|
|
1881
|
+
try {
|
|
1882
|
+
result = await this.storageRepo.commit(commit, undefined, proof);
|
|
1883
|
+
}
|
|
1884
|
+
catch (err) {
|
|
1885
|
+
// `StorageRepo.commit` throws (rather than returning success:false) when
|
|
1886
|
+
// the pending action is missing — the canonical "behind" signal: this
|
|
1887
|
+
// member reached commit-consensus without the matching pend (cohort drift).
|
|
1888
|
+
if (isMissingPendingActionError(err)) {
|
|
1889
|
+
log('cluster-member:consensus-commit-diverged', {
|
|
1890
|
+
messageHash,
|
|
1891
|
+
actionId: commit.actionId,
|
|
1892
|
+
divergence: 'behind',
|
|
1893
|
+
reason: err.message
|
|
1894
|
+
});
|
|
1895
|
+
// We hold no revision of these blocks; pull the committed revision from a
|
|
1896
|
+
// cohort peer so the block is no longer under-replicated. Best-effort:
|
|
1897
|
+
// failures are logged inside, never thrown (a throw would reset the stream).
|
|
1898
|
+
await this.reconcileDivergentCommit(record, commit);
|
|
1899
|
+
return { success: false, reason: err.message };
|
|
1900
|
+
}
|
|
1901
|
+
throw err;
|
|
1902
|
+
}
|
|
1903
|
+
if (result.success) {
|
|
1904
|
+
return result;
|
|
1905
|
+
}
|
|
1906
|
+
// success:false is a StaleFailure. `missing` ⇒ ahead/stale divergence
|
|
1907
|
+
// (we already hold ≥ this rev): tolerate, do NOT reconcile downward. A
|
|
1908
|
+
// missing-base reason ⇒ behind divergence, reconcile (below). Any other bare
|
|
1909
|
+
// `reason` with no `missing` ⇒ a genuine internalCommit fault: propagate so
|
|
1910
|
+
// handleConsensus rolls back the executed marker and rethrows.
|
|
1911
|
+
//
|
|
1912
|
+
// NOTE: this 'ahead' tolerance is what turns a rival's commit that somehow reaches
|
|
1913
|
+
// consensus into a member-side no-op rather than a stream reset. It must stay: a member
|
|
1914
|
+
// genuinely ahead of a redelivered/lagging commit is the common, correct case. What keeps
|
|
1915
|
+
// the tolerance from turning into an acknowledged write no member stored is downstream:
|
|
1916
|
+
// the durable verdict retained by the caller reports this member as NOT holding the
|
|
1917
|
+
// revision (unless the revision index names this very action — an ahead member that did
|
|
1918
|
+
// land it), and `CoordinatorRepo.commit`'s durability gate acknowledges only a majority of
|
|
1919
|
+
// such reports; a retained refusal whose cause a local re-read confirms as a rival at the
|
|
1920
|
+
// requested revision is answered as a retryable conflict there. Upstream,
|
|
1921
|
+
// `validateCommitRevisions` rejects the rival at the promise round and
|
|
1922
|
+
// `validateCommitAgainstRefusedPend` refuses to sign the commit of a pend this member
|
|
1923
|
+
// refused. If consensus-without-durability is ever observed again, look at the gate's
|
|
1924
|
+
// inputs (a member reporting a durable success it does not hold, or a majority that is
|
|
1925
|
+
// genuinely durable on a forked lineage), not at this branch.
|
|
1926
|
+
if (result.missing?.length) {
|
|
1927
|
+
log('cluster-member:consensus-commit-diverged', {
|
|
1928
|
+
messageHash,
|
|
1929
|
+
actionId: commit.actionId,
|
|
1930
|
+
divergence: 'ahead',
|
|
1931
|
+
reason: result.reason,
|
|
1932
|
+
hasMissing: true
|
|
1933
|
+
});
|
|
1934
|
+
return result;
|
|
1935
|
+
}
|
|
1936
|
+
// This member holds no materializable base for one of the blocks, so
|
|
1937
|
+
// `StorageRepo.commit` REFUSED rather than record a revision it could never serve.
|
|
1938
|
+
// Same "behind" divergence as a missing pend — and the same cure: pull the committed
|
|
1939
|
+
// revision from a cohort peer. Reconciling here (after commit released its per-block
|
|
1940
|
+
// latches) is what makes refusing safe; fetching inside the commit path would deadlock
|
|
1941
|
+
// against the latch `saveReplicatedBlock` needs to persist what it fetched.
|
|
1942
|
+
if (isMissingBaseRevisionFailure(result)) {
|
|
1943
|
+
log('cluster-member:consensus-commit-diverged', {
|
|
1944
|
+
messageHash,
|
|
1945
|
+
actionId: commit.actionId,
|
|
1946
|
+
divergence: 'behind',
|
|
1947
|
+
reason: result.reason
|
|
1948
|
+
});
|
|
1949
|
+
await this.reconcileDivergentCommit(record, commit);
|
|
1950
|
+
return result;
|
|
1951
|
+
}
|
|
1952
|
+
throw new Error(`Consensus commit for action ${commit.actionId} failed: ${result.reason ?? 'unknown reason'}`);
|
|
1953
|
+
}
|
|
1954
|
+
/**
|
|
1955
|
+
* The durable verdict for a consensus commit: `applied` itself when storage landed it (a
|
|
1956
|
+
* success from `StorageRepo.commit` means every block is committed, or already was, under this
|
|
1957
|
+
* action), otherwise a fresh look at what local storage holds NOW — after the reconcile the
|
|
1958
|
+
* refusal triggered. Durable means every block in `commit.blockIds` records `commit.rev` under
|
|
1959
|
+
* `commit.actionId`. A refusal that is not durable is returned as-is, so the coordinating node
|
|
1960
|
+
* can still classify its own member's ahead-shaped refusal against a rival. A read fault counts
|
|
1961
|
+
* as not durable: the gate this feeds fails toward retry.
|
|
1962
|
+
*/
|
|
1963
|
+
async durableCommitVerdict(commit, applied) {
|
|
1964
|
+
if (applied.success) {
|
|
1965
|
+
return applied;
|
|
1966
|
+
}
|
|
1967
|
+
try {
|
|
1968
|
+
for (const blockId of commit.blockIds) {
|
|
1969
|
+
if (!(await this.holdsCommittedRevision(blockId, commit))) {
|
|
1970
|
+
return applied;
|
|
1971
|
+
}
|
|
1972
|
+
}
|
|
1973
|
+
}
|
|
1974
|
+
catch (err) {
|
|
1975
|
+
log('cluster-member:consensus-commit-durability-read-error', {
|
|
1976
|
+
actionId: commit.actionId,
|
|
1977
|
+
rev: commit.rev,
|
|
1978
|
+
error: err.message
|
|
1979
|
+
});
|
|
1980
|
+
return applied;
|
|
1981
|
+
}
|
|
1982
|
+
log('cluster-member:consensus-commit-durable-after-reconcile', { actionId: commit.actionId, rev: commit.rev });
|
|
1983
|
+
return { success: true };
|
|
1984
|
+
}
|
|
1985
|
+
/**
|
|
1986
|
+
* True when local storage records `commit.rev` of `blockId` under `commit.actionId`. Read from
|
|
1987
|
+
* the revision index (the {@link IRevisionActionReader} capability) so a member whose `latest` has
|
|
1988
|
+
* since moved past the revision still counts as holding it; a repo without the capability is
|
|
1989
|
+
* judged on `latest` alone.
|
|
1990
|
+
*/
|
|
1991
|
+
async holdsCommittedRevision(blockId, commit) {
|
|
1992
|
+
const reader = this.storageRepo;
|
|
1993
|
+
if (typeof reader.getRevisionAction === 'function') {
|
|
1994
|
+
return await reader.getRevisionAction(blockId, commit.rev) === commit.actionId;
|
|
1995
|
+
}
|
|
1996
|
+
const latest = (await this.storageRepo.get({ blockIds: [blockId] }))[blockId]?.state?.latest;
|
|
1997
|
+
return latest?.rev === commit.rev && latest.actionId === commit.actionId;
|
|
1998
|
+
}
|
|
1913
1999
|
/**
|
|
1914
2000
|
* Applies a consensus-ordered invalidation on this member: dedup → certificate verification →
|
|
1915
2001
|
* capture the invalidation's commit cert (for reactivity reuse) → delegate the compensating write +
|