@serfab/cadre-core 1.16.0 → 1.18.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 +4 -3
- package/dist/bootstrap-peer-store-file.js +1 -1
- package/dist/bootstrap-peer-store.d.ts +1 -1
- package/dist/bootstrap-peer-store.js +4 -4
- package/dist/cadre-invite-protocol.d.ts +81 -142
- package/dist/cadre-invite-protocol.js +168 -458
- package/dist/cadre-invite-protocol.js.map +1 -1
- package/dist/cadre-join-request.d.ts +15 -2
- package/dist/cadre-join-request.js +22 -3
- package/dist/cadre-join-request.js.map +1 -1
- package/dist/cadre-node.d.ts +1089 -475
- package/dist/cadre-node.js +2170 -1037
- package/dist/cadre-node.js.map +1 -1
- package/dist/control-authorization.d.ts +36 -14
- package/dist/control-authorization.js +20 -3
- package/dist/control-authorization.js.map +1 -1
- package/dist/control-cohort.d.ts +88 -32
- package/dist/control-cohort.js +91 -33
- package/dist/control-cohort.js.map +1 -1
- package/dist/control-database.d.ts +856 -318
- package/dist/control-database.js +1422 -701
- package/dist/control-database.js.map +1 -1
- package/dist/control-formation-recorder.d.ts +46 -43
- package/dist/control-formation-recorder.js +77 -78
- package/dist/control-formation-recorder.js.map +1 -1
- package/dist/control-protocol-guard.d.ts +11 -4
- package/dist/control-protocol-guard.js +20 -5
- package/dist/control-protocol-guard.js.map +1 -1
- package/dist/control-schema-version.d.ts +97 -0
- package/dist/control-schema-version.js +225 -0
- package/dist/control-schema-version.js.map +1 -0
- package/dist/control-schema.d.ts +45 -5
- package/dist/control-schema.js +262 -78
- package/dist/control-schema.js.map +1 -1
- package/dist/control-write-retry.d.ts +45 -10
- package/dist/control-write-retry.js +92 -22
- package/dist/control-write-retry.js.map +1 -1
- package/dist/delegate-admission.d.ts +13 -0
- package/dist/delegate-admission.js +27 -0
- package/dist/delegate-admission.js.map +1 -1
- package/dist/ed25519-key.d.ts +10 -7
- package/dist/ed25519-key.js +23 -20
- package/dist/ed25519-key.js.map +1 -1
- package/dist/edge-node/config.d.ts +122 -0
- package/dist/edge-node/config.js +199 -0
- package/dist/edge-node/config.js.map +1 -0
- package/dist/edge-node/dissolve.d.ts +95 -0
- package/dist/edge-node/dissolve.js +184 -0
- package/dist/edge-node/dissolve.js.map +1 -0
- package/dist/edge-node/edge-join.d.ts +44 -0
- package/dist/edge-node/edge-join.js +227 -0
- package/dist/edge-node/edge-join.js.map +1 -0
- package/dist/edge-node/edge-node.d.ts +295 -0
- package/dist/edge-node/edge-node.js +704 -0
- package/dist/edge-node/edge-node.js.map +1 -0
- package/dist/edge-node/index.d.ts +18 -0
- package/dist/edge-node/index.js +18 -0
- package/dist/edge-node/index.js.map +1 -0
- package/dist/edge-node/join-credential.d.ts +11 -0
- package/dist/edge-node/join-credential.js +11 -0
- package/dist/edge-node/join-credential.js.map +1 -0
- package/dist/edge-node/lazy-raw-storage.d.ts +40 -0
- package/dist/edge-node/lazy-raw-storage.js +134 -0
- package/dist/edge-node/lazy-raw-storage.js.map +1 -0
- package/dist/edge-node/node-local.d.ts +60 -0
- package/dist/edge-node/node-local.js +58 -0
- package/dist/edge-node/node-local.js.map +1 -0
- package/dist/edge-node/options.d.ts +83 -0
- package/dist/edge-node/options.js +110 -0
- package/dist/edge-node/options.js.map +1 -0
- package/dist/edge-node/owner-grant.d.ts +41 -0
- package/dist/edge-node/owner-grant.js +38 -0
- package/dist/edge-node/owner-grant.js.map +1 -0
- package/dist/edge-node/platform.d.ts +140 -0
- package/dist/edge-node/platform.js +12 -0
- package/dist/edge-node/platform.js.map +1 -0
- package/dist/edge-node/strand-move.d.ts +115 -0
- package/dist/edge-node/strand-move.js +223 -0
- package/dist/edge-node/strand-move.js.map +1 -0
- package/dist/edge-node/strands.d.ts +28 -0
- package/dist/edge-node/strands.js +60 -0
- package/dist/edge-node/strands.js.map +1 -0
- package/dist/enrolled-machine-store.d.ts +3 -3
- package/dist/enrolled-machine-store.js +3 -3
- package/dist/formation-token.d.ts +48 -0
- package/dist/formation-token.js +26 -0
- package/dist/formation-token.js.map +1 -0
- package/dist/index.d.ts +27 -17
- package/dist/index.js +39 -17
- package/dist/index.js.map +1 -1
- package/dist/joined-strand-store.d.ts +29 -12
- package/dist/joined-strand-store.js +66 -27
- package/dist/joined-strand-store.js.map +1 -1
- package/dist/key-store.d.ts +9 -4
- package/dist/key-store.js +9 -4
- package/dist/key-store.js.map +1 -1
- package/dist/link-budget.d.ts +9 -7
- package/dist/link-budget.js +9 -7
- package/dist/link-budget.js.map +1 -1
- package/dist/machine-removal.d.ts +19 -39
- package/dist/machine-removal.js +24 -98
- package/dist/machine-removal.js.map +1 -1
- package/dist/member-redemption.d.ts +256 -0
- package/dist/member-redemption.js +490 -0
- package/dist/member-redemption.js.map +1 -0
- package/dist/membership-view.d.ts +91 -0
- package/dist/membership-view.js +110 -0
- package/dist/membership-view.js.map +1 -0
- package/dist/owner-anchor-sync.d.ts +4 -4
- package/dist/owner-anchor-sync.js +16 -15
- package/dist/owner-anchor-sync.js.map +1 -1
- package/dist/owner-grant-protocol.d.ts +251 -0
- package/dist/owner-grant-protocol.js +344 -0
- package/dist/owner-grant-protocol.js.map +1 -0
- package/dist/owner-key-export.d.ts +70 -0
- package/dist/owner-key-export.js +279 -0
- package/dist/owner-key-export.js.map +1 -0
- package/dist/owner-key-flows.d.ts +55 -0
- package/dist/owner-key-flows.js +60 -0
- package/dist/owner-key-flows.js.map +1 -0
- package/dist/owner-key-store.d.ts +31 -0
- package/dist/owner-key-store.js +81 -0
- package/dist/owner-key-store.js.map +1 -0
- package/dist/owner-keys.d.ts +78 -0
- package/dist/owner-keys.js +133 -0
- package/dist/owner-keys.js.map +1 -0
- package/dist/owner-signer.d.ts +54 -0
- package/dist/owner-signer.js +51 -0
- package/dist/owner-signer.js.map +1 -0
- package/dist/peer-addr-book.d.ts +10 -1
- package/dist/peer-addr-book.js +4 -3
- package/dist/peer-addr-book.js.map +1 -1
- package/dist/peer-authorization.d.ts +83 -18
- package/dist/peer-authorization.js +123 -53
- package/dist/peer-authorization.js.map +1 -1
- package/dist/peer-join-backfill.d.ts +89 -20
- package/dist/peer-join-backfill.js +265 -108
- package/dist/peer-join-backfill.js.map +1 -1
- package/dist/pending-join-runner.d.ts +48 -18
- package/dist/pending-join-runner.js +74 -56
- package/dist/pending-join-runner.js.map +1 -1
- package/dist/provider/rent-node.js +1 -1
- package/dist/provider/rent-node.js.map +1 -1
- package/dist/seed-bootstrap.d.ts +162 -133
- package/dist/seed-bootstrap.js +300 -283
- package/dist/seed-bootstrap.js.map +1 -1
- package/dist/seed-trust-policy.js +4 -4
- package/dist/strand-addr-protocol.d.ts +4 -1
- package/dist/strand-addr-protocol.js +8 -2
- package/dist/strand-addr-protocol.js.map +1 -1
- package/dist/strand-cohort.d.ts +3 -0
- package/dist/strand-cohort.js.map +1 -1
- package/dist/strand-database.d.ts +24 -2
- package/dist/strand-database.js +29 -6
- package/dist/strand-database.js.map +1 -1
- package/dist/strand-first-sync-gate.d.ts +176 -88
- package/dist/strand-first-sync-gate.js +288 -121
- package/dist/strand-first-sync-gate.js.map +1 -1
- package/dist/strand-formation-deadlines.d.ts +9 -9
- package/dist/strand-formation-deadlines.js +9 -9
- package/dist/strand-formation-manager.d.ts +23 -17
- package/dist/strand-formation-manager.js +46 -44
- package/dist/strand-formation-manager.js.map +1 -1
- package/dist/strand-formation-protocol.d.ts +5 -8
- package/dist/strand-formation-protocol.js +101 -16
- package/dist/strand-formation-protocol.js.map +1 -1
- package/dist/strand-instance-manager.d.ts +27 -18
- package/dist/strand-instance-manager.js +119 -50
- package/dist/strand-instance-manager.js.map +1 -1
- package/dist/strand-membership-reconciler.d.ts +16 -13
- package/dist/strand-membership-reconciler.js +30 -22
- package/dist/strand-membership-reconciler.js.map +1 -1
- package/dist/strand-membership-writer.d.ts +45 -22
- package/dist/strand-membership-writer.js +226 -131
- package/dist/strand-membership-writer.js.map +1 -1
- package/dist/strand-move.d.ts +6 -5
- package/dist/strand-move.js +36 -32
- package/dist/strand-move.js.map +1 -1
- package/dist/strand-peer-redial.js +2 -3
- package/dist/strand-peer-redial.js.map +1 -1
- package/dist/strand-revocation-enforcer.d.ts +134 -60
- package/dist/strand-revocation-enforcer.js +144 -71
- package/dist/strand-revocation-enforcer.js.map +1 -1
- package/dist/strand-solicitation.d.ts +50 -39
- package/dist/strand-solicitation.js +10 -3
- package/dist/strand-solicitation.js.map +1 -1
- package/dist/strand-watcher.d.ts +80 -13
- package/dist/strand-watcher.js +152 -48
- package/dist/strand-watcher.js.map +1 -1
- package/dist/table-change-watch.d.ts +63 -0
- package/dist/table-change-watch.js +209 -0
- package/dist/table-change-watch.js.map +1 -0
- package/dist/timeout-scheduler.d.ts +4 -3
- package/dist/timeout-scheduler.js +4 -3
- package/dist/timeout-scheduler.js.map +1 -1
- package/dist/types.d.ts +374 -150
- package/dist/types.js +4 -4
- package/dist/types.js.map +1 -1
- package/package.json +17 -8
- package/src/bootstrap-peer-store-file.ts +44 -44
- package/src/bootstrap-peer-store.ts +214 -214
- package/src/cadre-invite-protocol.ts +740 -1052
- package/src/cadre-join-request.ts +24 -3
- package/src/cadre-node.ts +2402 -1079
- package/src/control-authorization.ts +47 -14
- package/src/control-cohort.ts +204 -128
- package/src/control-database.ts +1749 -820
- package/src/control-formation-recorder.ts +359 -353
- package/src/control-protocol-guard.ts +23 -6
- package/src/control-schema-version.ts +301 -0
- package/src/control-schema.ts +273 -78
- package/src/control-write-retry.ts +641 -567
- package/src/delegate-admission.ts +351 -314
- package/src/ed25519-key.ts +174 -171
- package/src/edge-node/config.ts +277 -0
- package/src/edge-node/dissolve.ts +251 -0
- package/src/edge-node/edge-join.ts +319 -0
- package/src/edge-node/edge-node.ts +1045 -0
- package/src/edge-node/index.ts +59 -0
- package/src/edge-node/join-credential.ts +16 -0
- package/src/edge-node/lazy-raw-storage.ts +178 -0
- package/src/edge-node/node-local.ts +84 -0
- package/src/edge-node/options.ts +179 -0
- package/src/edge-node/owner-grant.ts +63 -0
- package/src/edge-node/platform.ts +151 -0
- package/src/edge-node/strand-move.ts +270 -0
- package/src/edge-node/strands.ts +72 -0
- package/src/enrolled-machine-store.ts +3 -3
- package/src/formation-token.ts +63 -0
- package/src/index.ts +899 -789
- package/src/joined-strand-store.ts +73 -28
- package/src/key-store.ts +10 -4
- package/src/link-budget.ts +9 -7
- package/src/machine-removal.ts +26 -111
- package/src/member-redemption.ts +649 -0
- package/src/membership-view.ts +144 -0
- package/src/owner-anchor-sync.ts +19 -18
- package/src/owner-grant-protocol.ts +547 -0
- package/src/owner-key-export.ts +348 -0
- package/src/owner-key-flows.ts +109 -0
- package/src/owner-key-store.ts +85 -0
- package/src/owner-keys.ts +178 -0
- package/src/owner-signer.ts +94 -0
- package/src/peer-addr-book.ts +4 -3
- package/src/peer-authorization.ts +558 -488
- package/src/peer-join-backfill.ts +971 -761
- package/src/pending-join-runner.ts +76 -53
- package/src/provider/rent-node.ts +1 -1
- package/src/seed-bootstrap.ts +343 -349
- package/src/seed-trust-policy.ts +4 -4
- package/src/strand-addr-protocol.ts +547 -538
- package/src/strand-cohort.ts +4 -0
- package/src/strand-database.ts +352 -319
- package/src/strand-first-sync-gate.ts +334 -133
- package/src/strand-formation-deadlines.ts +227 -227
- package/src/strand-formation-manager.ts +731 -725
- package/src/strand-formation-protocol.ts +122 -24
- package/src/strand-instance-manager.ts +2092 -2026
- package/src/strand-membership-reconciler.ts +33 -25
- package/src/strand-membership-writer.ts +2093 -1974
- package/src/strand-move.ts +50 -38
- package/src/strand-peer-redial.ts +2 -3
- package/src/strand-revocation-enforcer.ts +740 -654
- package/src/strand-solicitation.ts +61 -34
- package/src/strand-watcher.ts +175 -56
- package/src/table-change-watch.ts +285 -0
- package/src/timeout-scheduler.ts +29 -28
- package/src/types.ts +391 -152
|
@@ -0,0 +1,490 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The member-dialed redemption exchange shared by cadre invitations
|
|
3
|
+
* (`cadre-invite-protocol.ts`) and owner grants (`owner-grant-protocol.ts`): a holder dials ANY
|
|
4
|
+
* member machine its bundle names, proves possession in one signed frame, and the member writes
|
|
5
|
+
* rows on the issuer's behalf and answers in one frame. The issuer need not be online.
|
|
6
|
+
*
|
|
7
|
+
* What is the same for both credentials lives here, generic over the request and reply types:
|
|
8
|
+
*
|
|
9
|
+
* - the member side's hardening and sequencing ({@link MemberRedemptionHandler}): a concurrency
|
|
10
|
+
* cap, a read timeout, the checks before any database work, the push of this member's control
|
|
11
|
+
* store to the holder for a credential still live here, and the writes under that admission;
|
|
12
|
+
* - the holder side's dialing ({@link dialRedemption}, {@link dialRedemptionInPasses}): the
|
|
13
|
+
* bundle's addresses one at a time under a per-address deadline, one signed request for every
|
|
14
|
+
* address and every pass, and the pacing between passes;
|
|
15
|
+
* - the shape checks both directions apply off the wire, and the frame cap.
|
|
16
|
+
*
|
|
17
|
+
* What differs — the bundle, the request's fields and signatures, the refusal codes, the rows
|
|
18
|
+
* written and the reply's contents — stays in each protocol module, which passes it in. Same
|
|
19
|
+
* shape as the seed and wake protocols underneath: one length-prefixed JSON frame each way over
|
|
20
|
+
* the primitives in `control-stream.ts`.
|
|
21
|
+
*/
|
|
22
|
+
import debug from 'debug';
|
|
23
|
+
import { multiaddr } from '@multiformats/multiaddr';
|
|
24
|
+
import { withDeadline, exchangeFrame, readStreamToEnd, replyAndClose } from './control-stream.js';
|
|
25
|
+
import { decodeLengthPrefixedFrame } from './seed-bootstrap.js';
|
|
26
|
+
import { relayedRequestBudgetMs } from './link-budget.js';
|
|
27
|
+
import { IssuedRowNotHereYetError, parseNullableStoredDatetimeMs } from './control-database.js';
|
|
28
|
+
import { requireEd25519PublicKeyB64 } from './ed25519-key.js';
|
|
29
|
+
import { dialAddr, nextMacrotask, trailingPeerId } from './peer-dial.js';
|
|
30
|
+
import { chainMessages } from './control-retry.js';
|
|
31
|
+
import { isRetriableControlWriteFailure } from './control-write-retry.js';
|
|
32
|
+
const log = debug('sereus:cadre:member-redemption');
|
|
33
|
+
/**
|
|
34
|
+
* Cap on either frame of either protocol. A request is one signed row, two signatures and a few
|
|
35
|
+
* addresses; a reply adds at most a peer list of a cadre's size. Both fit in a few KB; 64 KiB is
|
|
36
|
+
* the defensive bound a stranger can make the handler buffer.
|
|
37
|
+
*/
|
|
38
|
+
export const MAX_REDEMPTION_FRAME_BYTES = 64 * 1024;
|
|
39
|
+
/**
|
|
40
|
+
* Default time the member waits for the request frame before aborting the read (ms). The read
|
|
41
|
+
* only: a stranger opens the stream, so the bound is on a caller that opens one and never sends.
|
|
42
|
+
* Same figure as the seed handler's.
|
|
43
|
+
*/
|
|
44
|
+
// eslint-disable-next-line no-restricted-syntax -- link-independent: a receiver cap on one frame on a stream the peer already opened; it bounds a peer that opens a stream and never sends, not the dial
|
|
45
|
+
const DEFAULT_REDEEM_READ_TIMEOUT_MS = 10000;
|
|
46
|
+
/** Default cap on concurrent inbound redemptions; over it the handler answers `busy`. */
|
|
47
|
+
const DEFAULT_MAX_CONCURRENT_REDEMPTIONS = 100;
|
|
48
|
+
/** Longest string field accepted off the wire; a stamp is 342 base64url chars, a signature 86. */
|
|
49
|
+
export const MAX_WIRE_FIELD_LENGTH = 1024;
|
|
50
|
+
const BASE64URL = /^[A-Za-z0-9_-]+$/;
|
|
51
|
+
// ── Shape checks (both directions, both protocols) ───────────────────────────
|
|
52
|
+
export function isBoundedString(value, max = MAX_WIRE_FIELD_LENGTH) {
|
|
53
|
+
return typeof value === 'string' && value.length > 0 && value.length <= max;
|
|
54
|
+
}
|
|
55
|
+
export function isEd25519KeyB64(value) {
|
|
56
|
+
if (!isBoundedString(value) || !BASE64URL.test(value))
|
|
57
|
+
return false;
|
|
58
|
+
try {
|
|
59
|
+
requireEd25519PublicKeyB64(value, 'key');
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/** Is `value` a null-or-bounded-string column, as the nullable text columns of a travelling row are? */
|
|
67
|
+
export function isNullableBoundedString(value, max = MAX_WIRE_FIELD_LENGTH) {
|
|
68
|
+
return value === null || isBoundedString(value, max);
|
|
69
|
+
}
|
|
70
|
+
/** Is `value` a null-or-positive-integer use cap? */
|
|
71
|
+
export function isNullableUseCap(value) {
|
|
72
|
+
return value === null || (Number.isInteger(value) && value >= 1);
|
|
73
|
+
}
|
|
74
|
+
export function rejection(code, reason) {
|
|
75
|
+
return { accepted: false, code, reason };
|
|
76
|
+
}
|
|
77
|
+
/** Which named constraint, if any, refused the write — read off the error's cause chain. */
|
|
78
|
+
export function constraintRefusal(error, table) {
|
|
79
|
+
if (!(error instanceof Error))
|
|
80
|
+
return null;
|
|
81
|
+
const messages = chainMessages(error);
|
|
82
|
+
for (const [pattern, code, reason] of table) {
|
|
83
|
+
if (messages.some((message) => pattern.test(message)))
|
|
84
|
+
return rejection(code, reason);
|
|
85
|
+
}
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* A write failure that is not a refusal: this member cannot read the credential row the cadre
|
|
90
|
+
* holds yet, or the retry gave up (`conflict`), or something else (`internal`).
|
|
91
|
+
*/
|
|
92
|
+
export function writeFailureRefusal(error) {
|
|
93
|
+
if (error instanceof IssuedRowNotHereYetError) {
|
|
94
|
+
return rejection('conflict', 'This member has not synced the credential yet; nothing was recorded');
|
|
95
|
+
}
|
|
96
|
+
// NOTE: a seat torn on a member alone with an uncaught-up device was measured answering
|
|
97
|
+
// `internal`, not `conflict` (cadre-invite-redeemed-at-a-member-without-the-row). Both are
|
|
98
|
+
// retryable, so the holder is unaffected; if the two codes ever diverge in retryability,
|
|
99
|
+
// find which tear `isRetriableControlWriteFailure` does not recognise.
|
|
100
|
+
return isRetriableControlWriteFailure(error)
|
|
101
|
+
? rejection('conflict', 'The redemption write failed transiently; nothing was recorded')
|
|
102
|
+
: rejection('internal', 'Internal redemption error');
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Member side of one redemption protocol: registers the handler and drives each inbound stream
|
|
106
|
+
* through the checks in cost order — shape, then whatever the protocol checks without database
|
|
107
|
+
* work (party, signatures, and for an invitation the connecting identity and target device) —
|
|
108
|
+
* so no database work happens for a stranger that cannot prove anything; then admits the holder
|
|
109
|
+
* to this member's control replication (`admitDevice`) when the credential is live here, and
|
|
110
|
+
* runs the protocol's writes. Hardened as the seed handler is: a concurrency cap (over it,
|
|
111
|
+
* `busy`) and a read timeout.
|
|
112
|
+
*
|
|
113
|
+
* Registered only by a started `CadreNode`, after its control database is up, like the
|
|
114
|
+
* formation responder; nothing here is reachable on a node without one.
|
|
115
|
+
*
|
|
116
|
+
* A subclass supplies the six credential-specific pieces: the request's shape, the checks after
|
|
117
|
+
* it, the read of the credential row this member holds, the liveness judgement behind the push,
|
|
118
|
+
* the writes, and the two fixed refusals the base sends on its own (`busy`, and `internal` for a
|
|
119
|
+
* frame it could not read or a failure it did not expect).
|
|
120
|
+
*
|
|
121
|
+
* The held row is read once, after the checks, and carried to the liveness judgement and the
|
|
122
|
+
* writes: the schema refuses every update and delete of an issued credential row, so the row
|
|
123
|
+
* read before the write lock is the row a read inside it would return.
|
|
124
|
+
*/
|
|
125
|
+
export class MemberRedemptionHandler {
|
|
126
|
+
/**
|
|
127
|
+
* @param label - Names the credential in log lines and stream labels ("Cadre invite", "Owner grant").
|
|
128
|
+
* @param protocolId - The protocol id {@link register} handles by default.
|
|
129
|
+
*/
|
|
130
|
+
constructor(label, protocolId, options) {
|
|
131
|
+
this.label = label;
|
|
132
|
+
this.protocolId = protocolId;
|
|
133
|
+
this.registered = new Set();
|
|
134
|
+
this.active = 0;
|
|
135
|
+
this.partyId = options.partyId;
|
|
136
|
+
this.admitDevice = options.admitDevice;
|
|
137
|
+
this.readTimeoutMs = options.redeemReadTimeoutMs ?? DEFAULT_REDEEM_READ_TIMEOUT_MS;
|
|
138
|
+
this.maxConcurrent = options.maxConcurrentRedemptions ?? DEFAULT_MAX_CONCURRENT_REDEMPTIONS;
|
|
139
|
+
}
|
|
140
|
+
/** In-flight inbound redemptions. */
|
|
141
|
+
get activeCount() {
|
|
142
|
+
return this.active;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Register on `node`; rejects when libp2p refuses the registration. `runOnLimitedConnection`
|
|
146
|
+
* because a phone redeems through a relay, whose connection libp2p marks limited; the
|
|
147
|
+
* exchange is two small frames, well inside any relay's cap.
|
|
148
|
+
*/
|
|
149
|
+
async register(node, protocolId = this.protocolId) {
|
|
150
|
+
if (this.registered.has(node))
|
|
151
|
+
return;
|
|
152
|
+
await node.handle(protocolId, async (rawStream, rawConnection) => {
|
|
153
|
+
await this.handleStream(rawStream, rawConnection.remotePeer.toString());
|
|
154
|
+
}, { runOnLimitedConnection: true });
|
|
155
|
+
this.registered.add(node);
|
|
156
|
+
log('%s handler registered (%s)', this.label, protocolId);
|
|
157
|
+
}
|
|
158
|
+
async unregister(node, protocolId = this.protocolId) {
|
|
159
|
+
if (!this.registered.has(node))
|
|
160
|
+
return;
|
|
161
|
+
await node.unhandle(protocolId);
|
|
162
|
+
this.registered.delete(node);
|
|
163
|
+
log('%s handler unregistered (%s)', this.label, protocolId);
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* One inbound redemption: read the frame, decide, reply, close. Public as the seam a protocol
|
|
167
|
+
* spec drives directly; `remotePeerId` is the connection's authenticated peer.
|
|
168
|
+
*/
|
|
169
|
+
async handleStream(stream, remotePeerId) {
|
|
170
|
+
if (this.active >= this.maxConcurrent) {
|
|
171
|
+
log('%s: refusing redemption from %s: %d in flight at cap %d', this.label, remotePeerId, this.active, this.maxConcurrent);
|
|
172
|
+
await replyAndClose(stream, this.busyRefusal, this.label);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
this.active++;
|
|
176
|
+
try {
|
|
177
|
+
const reply = await this.decide(stream, remotePeerId);
|
|
178
|
+
log('%s: redemption from %s: %s', this.label, remotePeerId, reply.accepted ? 'accepted' : `refused (${reply.code})`);
|
|
179
|
+
await replyAndClose(stream, reply, this.label);
|
|
180
|
+
}
|
|
181
|
+
catch (error) {
|
|
182
|
+
log('%s: redemption from %s failed: %o', this.label, remotePeerId, error);
|
|
183
|
+
await replyAndClose(stream, this.internalRefusal, this.label);
|
|
184
|
+
}
|
|
185
|
+
finally {
|
|
186
|
+
this.active--;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Read the request frame to EOF, bounded and size-capped; JSON only, shape is the subclass's.
|
|
191
|
+
* Throws on a frame that cannot be read, which {@link handleStream} answers `internal`: a read
|
|
192
|
+
* timeout or a cut frame proves nothing about the credential, so the holder must stay free to
|
|
193
|
+
* try the next address.
|
|
194
|
+
*/
|
|
195
|
+
async readRequest(stream) {
|
|
196
|
+
const data = await readStreamToEnd(stream, { maxBytes: MAX_REDEMPTION_FRAME_BYTES, timeoutMs: this.readTimeoutMs, label: this.label });
|
|
197
|
+
return JSON.parse(new TextDecoder().decode(decodeLengthPrefixedFrame(data, MAX_REDEMPTION_FRAME_BYTES)));
|
|
198
|
+
}
|
|
199
|
+
async decide(stream, remotePeerId) {
|
|
200
|
+
const request = await this.readRequest(stream);
|
|
201
|
+
if (!this.isWellFormedRequest(request))
|
|
202
|
+
return this.malformedRefusal;
|
|
203
|
+
const refused = this.checkRequest(request, remotePeerId);
|
|
204
|
+
if (refused)
|
|
205
|
+
return refused;
|
|
206
|
+
const held = await this.readHeld(request);
|
|
207
|
+
const release = await this.admitIfLive(request, remotePeerId, held);
|
|
208
|
+
try {
|
|
209
|
+
return await this.redeemAdmitted(request, remotePeerId, held);
|
|
210
|
+
}
|
|
211
|
+
finally {
|
|
212
|
+
release();
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Admit the holder to this member's control replication before the first write of the
|
|
217
|
+
* exchange (the seat, when this member does not hold the row; otherwise the redemption), and
|
|
218
|
+
* resolve to the release that ends the admission. The holder is not a member, so only this
|
|
219
|
+
* puts it in this member's control write cohort, and it must be there to hold the rows the
|
|
220
|
+
* exchange writes. It is pushed the control store first, because a cohort node with no base
|
|
221
|
+
* revision of a block refuses a commit to it; a member alone with the holder would then have
|
|
222
|
+
* half its cohort refusing, and the write would tear instead of committing.
|
|
223
|
+
*
|
|
224
|
+
* Only for a credential still live here ({@link isLive}, judged on the row this member holds
|
|
225
|
+
* or, before the seat, on the bundle's copy once its issuer signature verifies): the holder
|
|
226
|
+
* of a withdrawn, expired or spent one, or of a forged copy naming a real owner, is refused
|
|
227
|
+
* without being sent anything. The retry of a redemption already written can read as spent
|
|
228
|
+
* too and is sent nothing; it needs nothing, since the rows it would hold are written and
|
|
229
|
+
* the ordinary catch-up reaches a member. Best-effort: a failed check or push is logged and
|
|
230
|
+
* the writes go ahead without the holder admitted, then commit or are answered retryably.
|
|
231
|
+
*/
|
|
232
|
+
async admitIfLive(request, remotePeerId, held) {
|
|
233
|
+
const notAdmitted = () => undefined;
|
|
234
|
+
if (!this.admitDevice)
|
|
235
|
+
return notAdmitted;
|
|
236
|
+
try {
|
|
237
|
+
if (!await this.isLive(request, held)) {
|
|
238
|
+
log('%s: the credential is not live here; %s is not admitted before the write', this.label, remotePeerId);
|
|
239
|
+
return notAdmitted;
|
|
240
|
+
}
|
|
241
|
+
// NOTE: accepted tradeoff — when a write then fails (the seat refused, the last use
|
|
242
|
+
// taken by a race, the row expiring between this check and the write), the holder keeps
|
|
243
|
+
// a copy of a control store it was not admitted to. It holds a live owner-signed
|
|
244
|
+
// credential and proved possession and consent, and a seed hands an un-enrolled machine
|
|
245
|
+
// the same information. Revisit if a control table ever carries data a non-member must not see.
|
|
246
|
+
return await this.admitDevice(remotePeerId);
|
|
247
|
+
}
|
|
248
|
+
catch (error) {
|
|
249
|
+
log('%s: admitting %s before its write failed (writing anyway): %o', this.label, remotePeerId, error);
|
|
250
|
+
return notAdmitted;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Dial `addr`, open one stream to it, send the request, half-close, read the one reply frame.
|
|
256
|
+
* `signal` is the per-address deadline: it cancels the dial and resets a live stream.
|
|
257
|
+
*
|
|
258
|
+
* The address is dialed outside libp2p's dial queue (`dialAddr`), so another dial of the
|
|
259
|
+
* member in flight cannot capture this attempt; an open connection is still reused.
|
|
260
|
+
*/
|
|
261
|
+
async function exchangeRedemption(node, addr, options, signal, budgetMs) {
|
|
262
|
+
const connection = await dialAddr(node, addr, signal, `${options.label} redemption`);
|
|
263
|
+
// `runOnLimitedConnection`: a member may be reachable only through a relay (see `register`).
|
|
264
|
+
const rawStream = await connection.newStream(options.protocolId, { runOnLimitedConnection: true, signal });
|
|
265
|
+
return await exchangeFrame(rawStream, signal, options.request, async (stream) => {
|
|
266
|
+
const data = await readStreamToEnd(stream, { maxBytes: MAX_REDEMPTION_FRAME_BYTES, timeoutMs: budgetMs, label: `${options.label} reply` });
|
|
267
|
+
return decodeReply(JSON.parse(new TextDecoder().decode(decodeLengthPrefixedFrame(data, MAX_REDEMPTION_FRAME_BYTES))), options);
|
|
268
|
+
}, `${options.label} redemption aborted by its deadline`);
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Read a reply frame into a typed reply. A refusal keeps only its code and reason, as sent;
|
|
272
|
+
* an acceptance is the protocol's to read. Throws on a frame that is neither.
|
|
273
|
+
*/
|
|
274
|
+
function decodeReply(value, options) {
|
|
275
|
+
if (typeof value !== 'object' || value === null) {
|
|
276
|
+
throw new Error(`${options.label} reply is not an object`);
|
|
277
|
+
}
|
|
278
|
+
const reply = value;
|
|
279
|
+
if (reply.accepted !== true) {
|
|
280
|
+
return { accepted: false, code: reply.code, reason: reply.reason };
|
|
281
|
+
}
|
|
282
|
+
return options.decodeAccepted(reply);
|
|
283
|
+
}
|
|
284
|
+
function isRefusal(reply) {
|
|
285
|
+
return reply.accepted === false;
|
|
286
|
+
}
|
|
287
|
+
function asError(error) {
|
|
288
|
+
if (error instanceof Error)
|
|
289
|
+
return error;
|
|
290
|
+
// A WebSocket transport rejects a refused dial with an `ErrorEvent`, whose text is its `message`.
|
|
291
|
+
const message = error?.message;
|
|
292
|
+
return new Error(typeof message === 'string' && message.length > 0 ? message : String(error), { cause: error });
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Run `op` with a signal that aborts when either `deadline` or the caller's `signal` does.
|
|
296
|
+
* Relayed by hand rather than with `AbortSignal.any`, which the phone runtimes lack.
|
|
297
|
+
*/
|
|
298
|
+
async function underDeadlineAndCaller(deadline, caller, op) {
|
|
299
|
+
if (!caller)
|
|
300
|
+
return await op(deadline);
|
|
301
|
+
const either = new AbortController();
|
|
302
|
+
const abort = () => either.abort();
|
|
303
|
+
deadline.addEventListener('abort', abort, { once: true });
|
|
304
|
+
caller.addEventListener('abort', abort, { once: true });
|
|
305
|
+
// An abort between the caller's last check and the listener above fired no event.
|
|
306
|
+
if (caller.aborted)
|
|
307
|
+
abort();
|
|
308
|
+
try {
|
|
309
|
+
return await op(either.signal);
|
|
310
|
+
}
|
|
311
|
+
finally {
|
|
312
|
+
deadline.removeEventListener('abort', abort);
|
|
313
|
+
caller.removeEventListener('abort', abort);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* One pass over the bundle's addresses, one at a time, each under its own deadline, stopping at
|
|
318
|
+
* the first acceptance or the first final refusal. A dial failure, a malformed reply or a
|
|
319
|
+
* retryable refusal moves to the next address. A final refusal and an acceptance that fails
|
|
320
|
+
* the holder's check are thrown from outside the per-address catch, so neither is ever
|
|
321
|
+
* mistaken for an address that could not be reached.
|
|
322
|
+
*/
|
|
323
|
+
async function dialOnce(node, options) {
|
|
324
|
+
const budgetMs = options.addressBudgetMs ?? relayedRequestBudgetMs(options.linkRoundTripMs);
|
|
325
|
+
const outcomes = [];
|
|
326
|
+
let memberAnswered = false;
|
|
327
|
+
for (const addr of options.members) {
|
|
328
|
+
options.signal?.throwIfAborted();
|
|
329
|
+
// Between attempts: a relayed address's relay leg goes through libp2p's dial queue, which
|
|
330
|
+
// needs a macrotask to drop an aborted job (see `tryAddrsInTurn`).
|
|
331
|
+
if (outcomes.length > 0)
|
|
332
|
+
await nextMacrotask();
|
|
333
|
+
let parsed;
|
|
334
|
+
try {
|
|
335
|
+
parsed = multiaddr(addr);
|
|
336
|
+
}
|
|
337
|
+
catch (error) {
|
|
338
|
+
outcomes.push({ addr, error: new Error('not a multiaddr', { cause: error }) });
|
|
339
|
+
continue;
|
|
340
|
+
}
|
|
341
|
+
let reply;
|
|
342
|
+
try {
|
|
343
|
+
reply = await withDeadline(budgetMs, `${options.label} redemption via ${addr}`, (deadline) => underDeadlineAndCaller(deadline, options.signal, (signal) => exchangeRedemption(node, parsed, options, signal, budgetMs)));
|
|
344
|
+
}
|
|
345
|
+
catch (error) {
|
|
346
|
+
// The caller's abort cut this attempt short; its reason, not the cut, is the answer.
|
|
347
|
+
options.signal?.throwIfAborted();
|
|
348
|
+
log('%s redemption via %s failed before an answer (%s); trying the next address', options.label, addr, asError(error).message);
|
|
349
|
+
outcomes.push({ addr, error: asError(error) });
|
|
350
|
+
continue;
|
|
351
|
+
}
|
|
352
|
+
if (isRefusal(reply)) {
|
|
353
|
+
const refusal = options.refusal(reply.code, reply.reason);
|
|
354
|
+
if (!refusal.retryable)
|
|
355
|
+
throw refusal;
|
|
356
|
+
log('%s redemption via %s refused retryably (%s); trying the next address', options.label, addr, refusal.message);
|
|
357
|
+
outcomes.push({ addr, error: refusal });
|
|
358
|
+
memberAnswered = true;
|
|
359
|
+
continue;
|
|
360
|
+
}
|
|
361
|
+
options.verifyAccepted(reply);
|
|
362
|
+
return { redeemed: { reply, memberPeerId: trailingPeerId(parsed), memberAddr: addr } };
|
|
363
|
+
}
|
|
364
|
+
return { outcomes, memberAnswered };
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Holder side: try the bundle's addresses one at a time ({@link dialOnce}) and throw the
|
|
368
|
+
* protocol's unreachable error when none accepts.
|
|
369
|
+
*
|
|
370
|
+
* Every address gets the SAME request, with one usage nonce: the usage table is keyed by it,
|
|
371
|
+
* so a member the holder gave up on that was still committing cannot redeem it twice, and a
|
|
372
|
+
* retry at the next member after a dropped reply is answered as already done.
|
|
373
|
+
*
|
|
374
|
+
* @throws the protocol's rejected error on a final refusal, its reply-invalid error on an
|
|
375
|
+
* acceptance that fails `verifyAccepted`, its unreachable error when every address was tried
|
|
376
|
+
* and none accepted, and the reason of `options.signal` once it aborts.
|
|
377
|
+
*/
|
|
378
|
+
export async function dialRedemption(node, options) {
|
|
379
|
+
const outcome = await dialOnce(node, options);
|
|
380
|
+
if ('redeemed' in outcome)
|
|
381
|
+
return outcome.redeemed;
|
|
382
|
+
throw options.unreachable(outcome.outcomes);
|
|
383
|
+
}
|
|
384
|
+
/** Pause after the first pass in which no member accepted; doubled after each further one. */
|
|
385
|
+
const REDEEM_PASS_FIRST_PAUSE_MS = 5000;
|
|
386
|
+
/** Longest pause between passes. */
|
|
387
|
+
const REDEEM_PASS_MAX_PAUSE_MS = 60000;
|
|
388
|
+
/**
|
|
389
|
+
* Holder side, in passes: one {@link dialOnce} pass over the bundle's addresses, or, with
|
|
390
|
+
* `waitForMember`, as many as it takes, pausing between them (5 s after the first, doubling to
|
|
391
|
+
* 60 s, never past the credential's expiry). Every pass sends the same signed request, so a
|
|
392
|
+
* member that wrote the redemption on a pass whose reply was lost answers a later pass as
|
|
393
|
+
* already done, with no second usage row.
|
|
394
|
+
*
|
|
395
|
+
* A wait ends at the first acceptance, at a final refusal or an invalid reply (thrown as
|
|
396
|
+
* {@link dialRedemption} throws them), when the credential expires by this holder's clock
|
|
397
|
+
* (thrown as `expired()`), when `signal` aborts (its reason thrown), and when `node` stops. A
|
|
398
|
+
* bundle that names no address has nothing to wait for and throws at once.
|
|
399
|
+
*/
|
|
400
|
+
export async function dialRedemptionInPasses(node, options) {
|
|
401
|
+
for (let pass = 1;; pass++) {
|
|
402
|
+
if (options.waitForMember) {
|
|
403
|
+
// One pass leaves expiry to the member, whose clock decides; a wait has nothing else to end it.
|
|
404
|
+
refuseIfExpired(options);
|
|
405
|
+
refuseIfStopped(node, options.label);
|
|
406
|
+
}
|
|
407
|
+
reportProgress(options.onProgress, { state: 'redeeming', pass });
|
|
408
|
+
const outcome = await dialOnce(node, options);
|
|
409
|
+
if ('redeemed' in outcome)
|
|
410
|
+
return outcome.redeemed;
|
|
411
|
+
if (!options.waitForMember || outcome.outcomes.length === 0)
|
|
412
|
+
throw options.unreachable(outcome.outcomes);
|
|
413
|
+
const pauseMs = pauseAfterPass(pass, options.expiresAt);
|
|
414
|
+
reportProgress(options.onProgress, {
|
|
415
|
+
state: 'waiting',
|
|
416
|
+
pass,
|
|
417
|
+
retryAtMs: Date.now() + pauseMs,
|
|
418
|
+
// Any refusal means a member answered and is not ready yet; otherwise none answered at all.
|
|
419
|
+
reason: outcome.memberAnswered ? 'member-not-ready' : 'no-member-answered',
|
|
420
|
+
outcomes: outcome.outcomes
|
|
421
|
+
});
|
|
422
|
+
log('%s redemption pass %d found no member ready; the next pass in %d ms', options.label, pass, pauseMs);
|
|
423
|
+
await pause(pauseMs, options.signal, node);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
// NOTE: judged by this holder's clock, so one running ahead ends a wait before the members
|
|
427
|
+
// would refuse it. If waits are seen ending early on skewed phones, allow a grace margin here.
|
|
428
|
+
function refuseIfExpired(options) {
|
|
429
|
+
const expiresAtMs = parseNullableStoredDatetimeMs(options.expiresAt);
|
|
430
|
+
if (expiresAtMs !== null && expiresAtMs <= Date.now()) {
|
|
431
|
+
throw options.expired();
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
/** A stopped node's every dial fails at once, which would otherwise read as "no member answered" until expiry. */
|
|
435
|
+
function refuseIfStopped(node, label) {
|
|
436
|
+
if (node.status !== 'started') {
|
|
437
|
+
throw new Error(`${label} redemption stopped waiting: the node is ${node.status}`);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
/** The pause after pass `pass`, cut short at the credential's expiry so the next check ends the wait then. */
|
|
441
|
+
function pauseAfterPass(pass, expiresAt) {
|
|
442
|
+
const backoffMs = Math.min(REDEEM_PASS_FIRST_PAUSE_MS * 2 ** (pass - 1), REDEEM_PASS_MAX_PAUSE_MS);
|
|
443
|
+
const expiresAtMs = parseNullableStoredDatetimeMs(expiresAt);
|
|
444
|
+
return expiresAtMs === null ? backoffMs : Math.max(0, Math.min(backoffMs, expiresAtMs - Date.now()));
|
|
445
|
+
}
|
|
446
|
+
/** A UI callback must not break the join. */
|
|
447
|
+
function reportProgress(onProgress, progress) {
|
|
448
|
+
if (!onProgress)
|
|
449
|
+
return;
|
|
450
|
+
try {
|
|
451
|
+
onProgress(progress);
|
|
452
|
+
}
|
|
453
|
+
catch (error) {
|
|
454
|
+
log('redemption progress callback threw (ignored): %o', error);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
/**
|
|
458
|
+
* Wait `ms`, or reject with `signal`'s reason as soon as it aborts. Ends early, resolving, when
|
|
459
|
+
* `node` stops (or is already stopped), so the next pass's stop check ends the wait without a
|
|
460
|
+
* timer holding the process open. Listeners and timer are cleared however it ends.
|
|
461
|
+
*/
|
|
462
|
+
function pause(ms, signal, node) {
|
|
463
|
+
return new Promise((resolve, reject) => {
|
|
464
|
+
if (signal?.aborted) {
|
|
465
|
+
reject(signal.reason);
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
if (node.status !== 'started') {
|
|
469
|
+
resolve();
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
const settle = () => {
|
|
473
|
+
clearTimeout(timer);
|
|
474
|
+
signal?.removeEventListener('abort', onAbort);
|
|
475
|
+
node.removeEventListener('stop', onStop);
|
|
476
|
+
};
|
|
477
|
+
const onAbort = () => {
|
|
478
|
+
settle();
|
|
479
|
+
reject(signal.reason);
|
|
480
|
+
};
|
|
481
|
+
const onStop = () => {
|
|
482
|
+
settle();
|
|
483
|
+
resolve();
|
|
484
|
+
};
|
|
485
|
+
const timer = setTimeout(onStop, ms);
|
|
486
|
+
signal?.addEventListener('abort', onAbort, { once: true });
|
|
487
|
+
node.addEventListener('stop', onStop, { once: true });
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
//# sourceMappingURL=member-redemption.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"member-redemption.js","sourceRoot":"","sources":["../src/member-redemption.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An in-memory copy of one small control table's rows, kept until the table changes, so the
|
|
3
|
+
* membership, owner, peer-record and admission-chain reads that every flow repeats cost nothing
|
|
4
|
+
* after the first.
|
|
5
|
+
* `ControlDatabase` keeps one per cached table and drops it from its table-change subscription
|
|
6
|
+
* (`docs/architecture.md` → "Membership rows view"); this class holds only the caching rules, so
|
|
7
|
+
* they are testable without a database.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* How long installed rows are served before the next read reloads them, change or no change.
|
|
11
|
+
* The reload is the only path for a change that reaches this node with no storage event: a
|
|
12
|
+
* machine outside the block's cohort (a cadre larger than its replication breadth), or one in
|
|
13
|
+
* the cohort that the writer could not reach when it committed (one side of a NAT pair; the
|
|
14
|
+
* cohort auto-connect scenarios). Longer than Optimystic's read-repair window
|
|
15
|
+
* ({@link OPTIMYSTIC_READ_REPAIR_WINDOW_MS}), so the reload's storage read starts the cohort
|
|
16
|
+
* consult that brings such a change in instead of being served from the local copy, and
|
|
17
|
+
* shorter than any reconcile backstop (`DEFAULT_CONTROL_COHORT_RECONCILE_MS`, 2 minutes), so
|
|
18
|
+
* the backstop pass, the only reader on an idle party, reloads rather than serving kept rows.
|
|
19
|
+
* The reload rate on an idle party is therefore the pass rate, and the staleness bound for
|
|
20
|
+
* such a change is the backstop, not this value.
|
|
21
|
+
*
|
|
22
|
+
* A machine that was offline when a sibling committed is covered sooner than either:
|
|
23
|
+
* `CadreNode` drops every view when a sibling becomes reachable (its identify on a new
|
|
24
|
+
* connection; `ControlDatabase.invalidateMembershipViews`), so the pass that starts then reads
|
|
25
|
+
* storage.
|
|
26
|
+
*/
|
|
27
|
+
export declare const MEMBERSHIP_VIEW_MAX_AGE_MS = 15000;
|
|
28
|
+
/**
|
|
29
|
+
* How long after a sibling becomes reachable (its identify on a new connection) `CadreNode`
|
|
30
|
+
* drops the views a second time: one read-repair window, plus a second for timer slop. The
|
|
31
|
+
* drop at the identify is not enough by itself. A node's reads before the sibling arrived
|
|
32
|
+
* (its start pass, with no cohort to ask) stamp the window, so the reload the identify
|
|
33
|
+
* starts is served from the local copy without a consult and the view would keep the
|
|
34
|
+
* pre-connection rows until {@link MEMBERSHIP_VIEW_MAX_AGE_MS}. The reload past the window
|
|
35
|
+
* starts the consults that bring in what the sibling committed while this node was away.
|
|
36
|
+
*/
|
|
37
|
+
export declare const MEMBERSHIP_VIEW_RECONNECT_RELOAD_MS: number;
|
|
38
|
+
/** One load's rows, and whether the view may keep them. */
|
|
39
|
+
export interface MembershipViewLoad<Row> {
|
|
40
|
+
rows: readonly Row[];
|
|
41
|
+
/**
|
|
42
|
+
* False when no later change event is guaranteed to reach the view for these rows (the
|
|
43
|
+
* read that produced them did not arm the table's change bridge), so they serve only the
|
|
44
|
+
* callers of this load.
|
|
45
|
+
*/
|
|
46
|
+
installable: boolean;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Reads the whole table. `retry` is the caller's: false from a caller holding the control
|
|
50
|
+
* write lock, whose read must not sleep a retry backoff.
|
|
51
|
+
*/
|
|
52
|
+
export type MembershipViewLoader<Row> = (retry: boolean) => Promise<MembershipViewLoad<Row>>;
|
|
53
|
+
export interface MembershipViewOptions {
|
|
54
|
+
/** Default {@link MEMBERSHIP_VIEW_MAX_AGE_MS}. */
|
|
55
|
+
maxAgeMs?: number;
|
|
56
|
+
/** Monotonic clock in milliseconds; default `performance.now`. */
|
|
57
|
+
now?: () => number;
|
|
58
|
+
}
|
|
59
|
+
export declare class MembershipView<Row> {
|
|
60
|
+
private readonly loader;
|
|
61
|
+
private rows;
|
|
62
|
+
/** When the load that produced {@link rows} started: the rows are no older than this. */
|
|
63
|
+
private loadedAt;
|
|
64
|
+
/** Bumped by every {@link invalidate}; a load installs only if it is unchanged since the load began. */
|
|
65
|
+
private generation;
|
|
66
|
+
/**
|
|
67
|
+
* The load each `retry` flag's callers share, current generation only. Separate per flag
|
|
68
|
+
* so a caller under the write lock never awaits a retrying load's backoff sleep.
|
|
69
|
+
*/
|
|
70
|
+
private readonly inFlight;
|
|
71
|
+
private readonly maxAgeMs;
|
|
72
|
+
private readonly now;
|
|
73
|
+
constructor(loader: MembershipViewLoader<Row>, options?: MembershipViewOptions);
|
|
74
|
+
/**
|
|
75
|
+
* The table's rows: the installed copy while it is younger than the maximum age, otherwise a
|
|
76
|
+
* load, shared with every caller of the same `retry` flag that arrives while it runs. A load
|
|
77
|
+
* that raced an {@link invalidate} answers its own callers with what it read, as an uncached
|
|
78
|
+
* read would have, and keeps nothing. A failed load keeps nothing and rejects.
|
|
79
|
+
*/
|
|
80
|
+
read(retry: boolean): Promise<readonly Row[]>;
|
|
81
|
+
/** Read the table without serving, joining or keeping anything. */
|
|
82
|
+
readThrough(retry: boolean): Promise<readonly Row[]>;
|
|
83
|
+
/**
|
|
84
|
+
* Drop the installed rows and every load in flight, so the next read loads afresh and no
|
|
85
|
+
* earlier load installs. Synchronous and flag-setting only: the table-change handler calls
|
|
86
|
+
* it inside the committing call.
|
|
87
|
+
*/
|
|
88
|
+
invalidate(): void;
|
|
89
|
+
private startLoad;
|
|
90
|
+
private load;
|
|
91
|
+
}
|