@serfab/cadre-core 1.13.0 → 1.14.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/dist/cadre-invite-protocol.d.ts +348 -0
- package/dist/cadre-invite-protocol.js +605 -0
- package/dist/cadre-invite-protocol.js.map +1 -0
- package/dist/cadre-node.d.ts +523 -180
- package/dist/cadre-node.js +921 -293
- package/dist/cadre-node.js.map +1 -1
- package/dist/claim-proof.d.ts +45 -0
- package/dist/claim-proof.js +132 -0
- package/dist/claim-proof.js.map +1 -0
- package/dist/control-authorization.d.ts +80 -19
- package/dist/control-authorization.js +55 -13
- package/dist/control-authorization.js.map +1 -1
- package/dist/control-cohort.js +1 -1
- package/dist/control-cohort.js.map +1 -1
- package/dist/control-database.d.ts +514 -97
- package/dist/control-database.js +1033 -195
- package/dist/control-database.js.map +1 -1
- package/dist/control-protocol-guard.d.ts +142 -0
- package/dist/control-protocol-guard.js +293 -0
- package/dist/control-protocol-guard.js.map +1 -0
- package/dist/control-schema.d.ts +1 -1
- package/dist/control-schema.js +801 -1067
- package/dist/control-schema.js.map +1 -1
- package/dist/control-stream.d.ts +4 -0
- package/dist/control-stream.js +22 -7
- package/dist/control-stream.js.map +1 -1
- package/dist/delegate-admission.d.ts +1 -1
- package/dist/delegate-admission.js +1 -1
- package/dist/device-token.d.ts +11 -9
- package/dist/device-token.js +14 -12
- package/dist/device-token.js.map +1 -1
- package/dist/ed25519-key.d.ts +15 -0
- package/dist/ed25519-key.js +28 -1
- package/dist/ed25519-key.js.map +1 -1
- package/dist/index.d.ts +14 -9
- package/dist/index.js +27 -9
- package/dist/index.js.map +1 -1
- package/dist/link-budget.d.ts +16 -29
- package/dist/link-budget.js +16 -31
- package/dist/link-budget.js.map +1 -1
- package/dist/membership-connection-gater.d.ts +190 -151
- package/dist/membership-connection-gater.js +235 -173
- package/dist/membership-connection-gater.js.map +1 -1
- package/dist/node-claim-payload.d.ts +19 -0
- package/dist/node-claim-payload.js +120 -0
- package/dist/node-claim-payload.js.map +1 -0
- package/dist/owner-anchor-sync.d.ts +22 -0
- package/dist/owner-anchor-sync.js +143 -0
- package/dist/owner-anchor-sync.js.map +1 -0
- package/dist/peer-authorization.d.ts +113 -18
- package/dist/peer-authorization.js +225 -29
- package/dist/peer-authorization.js.map +1 -1
- package/dist/peer-dial.d.ts +19 -0
- package/dist/peer-dial.js +22 -1
- package/dist/peer-dial.js.map +1 -1
- package/dist/peer-join-backfill.d.ts +26 -2
- package/dist/peer-join-backfill.js +54 -12
- package/dist/peer-join-backfill.js.map +1 -1
- package/dist/peer-record.d.ts +13 -9
- package/dist/peer-record.js +16 -12
- package/dist/peer-record.js.map +1 -1
- package/dist/pending-join-runner.d.ts +66 -58
- package/dist/pending-join-runner.js +136 -145
- package/dist/pending-join-runner.js.map +1 -1
- package/dist/push-fanout.d.ts +8 -2
- package/dist/push-fanout.js +8 -2
- package/dist/push-fanout.js.map +1 -1
- package/dist/seed-bootstrap.d.ts +147 -122
- package/dist/seed-bootstrap.js +284 -269
- package/dist/seed-bootstrap.js.map +1 -1
- package/dist/seed-trust-policy.d.ts +98 -37
- package/dist/seed-trust-policy.js +188 -2
- package/dist/seed-trust-policy.js.map +1 -1
- package/dist/strand-addr-protocol.d.ts +24 -26
- package/dist/strand-addr-protocol.js +19 -26
- package/dist/strand-addr-protocol.js.map +1 -1
- package/dist/strand-formation-protocol.js +1 -1
- package/dist/strand-formation-protocol.js.map +1 -1
- package/dist/strand-revocation-enforcer.d.ts +11 -11
- package/dist/strand-revocation-enforcer.js +11 -11
- package/dist/strand-wake-protocol.d.ts +13 -16
- package/dist/strand-wake-protocol.js +13 -20
- package/dist/strand-wake-protocol.js.map +1 -1
- package/dist/trusted-owner-store.d.ts +35 -5
- package/dist/trusted-owner-store.js +50 -8
- package/dist/trusted-owner-store.js.map +1 -1
- package/dist/types.d.ts +302 -87
- package/dist/types.js.map +1 -1
- package/package.json +8 -7
- package/src/cadre-invite-protocol.ts +864 -0
- package/src/cadre-node.ts +9381 -8686
- package/src/claim-proof.ts +147 -0
- package/src/control-authorization.ts +203 -117
- package/src/control-cohort.ts +1 -1
- package/src/control-database.ts +1216 -218
- package/src/control-protocol-guard.ts +348 -0
- package/src/control-schema.ts +812 -1078
- package/src/control-stream.ts +26 -7
- package/src/delegate-admission.ts +1 -1
- package/src/device-token.ts +14 -10
- package/src/ed25519-key.ts +28 -1
- package/src/index.ts +84 -10
- package/src/link-budget.ts +16 -32
- package/src/membership-connection-gater.ts +637 -570
- package/src/node-claim-payload.ts +125 -0
- package/src/owner-anchor-sync.ts +171 -0
- package/src/peer-authorization.ts +258 -29
- package/src/peer-dial.ts +22 -1
- package/src/peer-join-backfill.ts +56 -11
- package/src/peer-record.ts +16 -11
- package/src/pending-join-runner.ts +729 -735
- package/src/push-fanout.ts +8 -2
- package/src/seed-bootstrap.ts +344 -321
- package/src/seed-trust-policy.ts +353 -140
- package/src/strand-addr-protocol.ts +26 -35
- package/src/strand-formation-protocol.ts +1 -1
- package/src/strand-revocation-enforcer.ts +11 -11
- package/src/strand-wake-protocol.ts +13 -23
- package/src/trusted-owner-store.ts +242 -171
- package/src/types.ts +2471 -2242
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cadre invitation redemption transport: `/sereus/cadre-invite/1.0.0`.
|
|
3
|
+
*
|
|
4
|
+
* A device holding a cadre invitation (an owner-signed `CadreInvite` row plus the private
|
|
5
|
+
* half of the invitation keypair, see {@link CadreInvitation}) dials ANY member machine the
|
|
6
|
+
* invitation names and proves possession; the member seats the row if it does not hold it
|
|
7
|
+
* yet, writes the device's `CadrePeer` row (and `OwnerKey` row, when the invitation grants
|
|
8
|
+
* ownership) through `ControlDatabase.redeemCadreInvite`, and answers with the party id,
|
|
9
|
+
* the row and dial hints. The owner that minted the invitation need not be online.
|
|
10
|
+
*
|
|
11
|
+
* Same shape as the seed and wake protocols: one length-prefixed JSON frame each way over
|
|
12
|
+
* the shared primitives in `control-stream.ts`. Stranger-facing by design — the device is
|
|
13
|
+
* not a member when it dials — so the connection gate admits a stranger while a live
|
|
14
|
+
* invitation exists (`membership-connection-gater.ts`), and the handler's own trust
|
|
15
|
+
* decision is the proof of possession: nothing touches the database for a caller that
|
|
16
|
+
* cannot prove it holds the invitation and is the identity it names.
|
|
17
|
+
*
|
|
18
|
+
* The seed protocol is left untouched so `SeedMessage` can grow independently.
|
|
19
|
+
*/
|
|
20
|
+
import type { Libp2p } from '@libp2p/interface';
|
|
21
|
+
import { type ControlStream } from './control-stream.js';
|
|
22
|
+
import { type ControlDatabase } from './control-database.js';
|
|
23
|
+
import type { CadreInviteRow, SeedPeer } from './types.js';
|
|
24
|
+
/** Protocol id for cadre invitation redemption (parallel to `/sereus/seed/1.0.0`). */
|
|
25
|
+
export declare const CADRE_INVITE_PROTOCOL = "/sereus/cadre-invite/1.0.0";
|
|
26
|
+
/**
|
|
27
|
+
* What an owner hands out (QR, paste, link) and a device redeems. An invitation is an
|
|
28
|
+
* ed25519 keypair: the row holds the public half, `invitePrivateKey` is the proof of
|
|
29
|
+
* possession, and whoever holds this bundle can redeem it — treat it as a bearer
|
|
30
|
+
* credential for the row's lifetime (an untargeted owner-granting one defaults to a
|
|
31
|
+
* 15-minute expiry for that reason).
|
|
32
|
+
*
|
|
33
|
+
* Carrying the signed row is what makes redemption work at a member that has not received
|
|
34
|
+
* the row by replication: the member seats it from the bundle under its stored issuer
|
|
35
|
+
* signature. The row's own signature covers every column, so a bundle whose row was
|
|
36
|
+
* altered cannot be seated anywhere.
|
|
37
|
+
*/
|
|
38
|
+
export interface CadreInvitation {
|
|
39
|
+
v: 1;
|
|
40
|
+
/** The cadre the invitation joins; a device refuses one for another party. */
|
|
41
|
+
partyId: string;
|
|
42
|
+
/** The invitation's ed25519 seed, base64url: the proof of possession. */
|
|
43
|
+
invitePrivateKey: string;
|
|
44
|
+
/** The owner-signed row exactly as issued (`ControlDatabase.insertCadreInvite`). */
|
|
45
|
+
invite: CadreInviteRow;
|
|
46
|
+
/**
|
|
47
|
+
* The issuer's anchored owner keys. The redeeming device pins them before it dials
|
|
48
|
+
* (`CadreNode.trustOwnerKeys`) and checks the member's reply against them. Sourced from
|
|
49
|
+
* the issuer's node-local anchor only, never the replicated `OwnerKey` table, for the
|
|
50
|
+
* reason the seed invite gives: the device anchors whatever arrives here.
|
|
51
|
+
*/
|
|
52
|
+
ownerKeys: string[];
|
|
53
|
+
/**
|
|
54
|
+
* Multiaddrs ending in `/p2p/<peerId>`: the issuer's own first, then up to
|
|
55
|
+
* `INVITATION_SIBLING_MACHINES` other members (`invitation-bootstrap.ts`). The device
|
|
56
|
+
* tries them in order.
|
|
57
|
+
*/
|
|
58
|
+
members: string[];
|
|
59
|
+
}
|
|
60
|
+
/** Encode a bundle for out-of-band delivery: base64url over JSON, as seeds and open invitations are. */
|
|
61
|
+
export declare function encodeCadreInvitation(invitation: CadreInvitation): string;
|
|
62
|
+
/**
|
|
63
|
+
* Decode a bundle, refusing anything that is not a well-formed version-1
|
|
64
|
+
* {@link CadreInvitation}. Standalone so a client without a node (a settings screen that
|
|
65
|
+
* wants to show which cadre an invitation joins) can decode one.
|
|
66
|
+
*
|
|
67
|
+
* @throws on a string that is not base64url JSON, or decodes to another shape or version.
|
|
68
|
+
*/
|
|
69
|
+
export declare function decodeCadreInvitation(encoded: string): CadreInvitation;
|
|
70
|
+
/** Device → member: one frame, the whole redemption. */
|
|
71
|
+
export interface CadreInviteRedeemRequest {
|
|
72
|
+
v: 1;
|
|
73
|
+
/** The cadre the device believes it is joining; a machine serving another party refuses by name. */
|
|
74
|
+
partyId: string;
|
|
75
|
+
/** The owner-signed row from the bundle. */
|
|
76
|
+
invite: CadreInviteRow;
|
|
77
|
+
/** The device's ed25519 public key, base64url; must be the key behind the connecting peer id. */
|
|
78
|
+
peerKey: string;
|
|
79
|
+
/** The device's dialable addresses; empty for a phone. Stored on its `CadrePeer` row. */
|
|
80
|
+
multiaddrs: string[];
|
|
81
|
+
/** Single-use nonce the device minted (`generateStampId`); both signatures cover it. */
|
|
82
|
+
usageStampId: string;
|
|
83
|
+
/** By `invitePrivateKey` over the `'redeem'` digest. */
|
|
84
|
+
inviteSig: string;
|
|
85
|
+
/** By the device's identity key over the `'consent'` digest. */
|
|
86
|
+
peerSig: string;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Why a member refused a redemption. Retryability is {@link CADRE_INVITE_REJECTION_RETRYABLE}'s;
|
|
90
|
+
* a retryable code moves the device to the next address the bundle names.
|
|
91
|
+
*/
|
|
92
|
+
export type CadreInviteRejectionCode =
|
|
93
|
+
/**
|
|
94
|
+
* The issuer's `OwnerKey` row is not on this member (not replicated yet), or the issuer was
|
|
95
|
+
* removed; the member cannot tell which, so the device tries the next address.
|
|
96
|
+
*/
|
|
97
|
+
'issuer-unknown'
|
|
98
|
+
/**
|
|
99
|
+
* A signature does not verify, the peer key is not the connecting identity, the request or
|
|
100
|
+
* the row is malformed, or the invitation names another device.
|
|
101
|
+
*/
|
|
102
|
+
| 'invite-invalid'
|
|
103
|
+
/** Expired, withdrawn, exhausted, or issued by a key that is no longer an owner here. */
|
|
104
|
+
| 'invite-spent'
|
|
105
|
+
/** This machine serves another cadre than the one the invitation names. */
|
|
106
|
+
| 'party-mismatch'
|
|
107
|
+
/** The member is at its cap on concurrent redemptions. */
|
|
108
|
+
| 'busy'
|
|
109
|
+
/** The write failed transiently (the write retry gave up); nothing was recorded. */
|
|
110
|
+
| 'conflict'
|
|
111
|
+
/**
|
|
112
|
+
* An unexpected member-side failure, or a request frame the member could not read (timed
|
|
113
|
+
* out, oversized, not JSON): nothing was proved either way, so the device tries the next
|
|
114
|
+
* address, as the formation responder's `internal` is read.
|
|
115
|
+
*/
|
|
116
|
+
| 'internal';
|
|
117
|
+
/** Which refusals are worth repeating at the next address — the one place that says so. */
|
|
118
|
+
export declare const CADRE_INVITE_REJECTION_RETRYABLE: Readonly<Record<CadreInviteRejectionCode, boolean>>;
|
|
119
|
+
/** Is `value` a code this build knows? Own keys only, so a peer's `'constructor'` does not pass. */
|
|
120
|
+
export declare function isCadreInviteRejectionCode(value: unknown): value is CadreInviteRejectionCode;
|
|
121
|
+
/** Member → device. */
|
|
122
|
+
export interface CadreInviteRedeemReply {
|
|
123
|
+
accepted: boolean;
|
|
124
|
+
/** Present iff `!accepted`. */
|
|
125
|
+
code?: CadreInviteRejectionCode;
|
|
126
|
+
/** Human-facing text; present iff `!accepted`. */
|
|
127
|
+
reason?: string;
|
|
128
|
+
/** The member's party id; present iff `accepted`. */
|
|
129
|
+
partyId?: string;
|
|
130
|
+
/** The invitation row as this member holds it; present iff `accepted`. */
|
|
131
|
+
invite?: CadreInviteRow;
|
|
132
|
+
/**
|
|
133
|
+
* Dial hints for the rest of the cadre, unsigned: the member's own projection of its
|
|
134
|
+
* `CadrePeer` rows (`ControlDatabase.querySeedPeers`). `isOwner` is a dial hint only, as
|
|
135
|
+
* the seed's NOTE says; the device derives trust from its anchor, never from this.
|
|
136
|
+
*/
|
|
137
|
+
peers?: SeedPeer[];
|
|
138
|
+
}
|
|
139
|
+
/** An accepting reply, every optional field present. */
|
|
140
|
+
export interface CadreInviteAccepted extends CadreInviteRedeemReply {
|
|
141
|
+
accepted: true;
|
|
142
|
+
partyId: string;
|
|
143
|
+
invite: CadreInviteRow;
|
|
144
|
+
peers: SeedPeer[];
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* A member answered and refused. `code` is `'unrecognized'` when the frame carried none this
|
|
148
|
+
* build knows (the two machines may run different versions), and such a refusal counts as
|
|
149
|
+
* retryable: trying the next address on an unknown answer is safe, giving up is not.
|
|
150
|
+
*/
|
|
151
|
+
export declare class CadreInviteRejectedError extends Error {
|
|
152
|
+
readonly code: CadreInviteRejectionCode | 'unrecognized';
|
|
153
|
+
readonly reason: string;
|
|
154
|
+
readonly retryable: boolean;
|
|
155
|
+
/** Both arguments come straight off the wire, so neither is trusted to have its declared type. */
|
|
156
|
+
constructor(code: unknown, reason: unknown);
|
|
157
|
+
}
|
|
158
|
+
/** What one address the bundle named came to. */
|
|
159
|
+
export interface CadreInviteAddressOutcome {
|
|
160
|
+
addr: string;
|
|
161
|
+
/** A dial or exchange failure, or a retryable {@link CadreInviteRejectedError}. */
|
|
162
|
+
error: Error;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Every address the bundle names was tried and none accepted: each failed to dial, answered
|
|
166
|
+
* malformed, or refused retryably. Always retryable; `outcomes` says what each address did.
|
|
167
|
+
*/
|
|
168
|
+
export declare class CadreInviteUnreachableError extends Error {
|
|
169
|
+
readonly outcomes: readonly CadreInviteAddressOutcome[];
|
|
170
|
+
readonly retryable = true;
|
|
171
|
+
constructor(outcomes: readonly CadreInviteAddressOutcome[]);
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* A member accepted but its reply does not check out: another party id, another invitation
|
|
175
|
+
* key, or a row not signed by one of the pinned owner keys. Final: a machine that answers
|
|
176
|
+
* like this is not a member of the cadre the invitation names, and the row it wrote (if any)
|
|
177
|
+
* is in a database the device will never trust.
|
|
178
|
+
*/
|
|
179
|
+
export declare class CadreInviteReplyInvalidError extends Error {
|
|
180
|
+
readonly retryable = false;
|
|
181
|
+
constructor(message: string);
|
|
182
|
+
}
|
|
183
|
+
/** Is `value` a {@link CadreInviteRow} with every column in its stored form and bounded? */
|
|
184
|
+
export declare function isWellFormedCadreInviteRow(value: unknown): value is CadreInviteRow;
|
|
185
|
+
/**
|
|
186
|
+
* Keep only parsable, distinct multiaddr strings, capped at {@link MAX_ADDRS}, order kept.
|
|
187
|
+
* Applied on both sides: the member bounds what it stores on the device's row, the device
|
|
188
|
+
* bounds what it merges into its peer store. A malformed entry is skipped, not fatal.
|
|
189
|
+
*/
|
|
190
|
+
export declare function sanitizeAddrs(addrs: unknown): string[];
|
|
191
|
+
/** What the device signs with: its identity keypair in the crypto plugin's base64url form. */
|
|
192
|
+
export interface RedeemSigner {
|
|
193
|
+
/** The device's ed25519 public key, base64url — the key behind its peer id. */
|
|
194
|
+
peerKey: string;
|
|
195
|
+
/** The device's ed25519 seed, base64url. */
|
|
196
|
+
peerPrivateKey: string;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Build and sign the request for `invitation`: the holder's `'redeem'` signature with the
|
|
200
|
+
* invitation's private key and the device's `'consent'` signature with its own, both over
|
|
201
|
+
* (invitation key, `usageStampId`, device key) for the invitation's party. The same bytes
|
|
202
|
+
* the schema verifies at the member (`cadreInviteRedeemMessage` / `cadreInviteConsentMessage`),
|
|
203
|
+
* whose own `party_id()` must therefore be the party the bundle names.
|
|
204
|
+
*/
|
|
205
|
+
export declare function signRedeemRequest(invitation: CadreInvitation, signer: RedeemSigner, usageStampId: string, multiaddrs: string[]): CadreInviteRedeemRequest;
|
|
206
|
+
/**
|
|
207
|
+
* The device's check on an acceptance: the same party, the same invitation key, and a row
|
|
208
|
+
* signed by an issuer the device pinned from the bundle. Throws
|
|
209
|
+
* {@link CadreInviteReplyInvalidError} naming the first failure.
|
|
210
|
+
*
|
|
211
|
+
* What it proves is bounded: the request already carried the owner-signed row, so a machine
|
|
212
|
+
* that is not a member can echo it. The check catches a reply for another party or
|
|
213
|
+
* invitation and a row the pinned owners never signed; whether the answering machine is a
|
|
214
|
+
* member is settled afterwards, when the device syncs the control database and judges every
|
|
215
|
+
* row against its anchor (`CadreNode.listAuthorizedMembers`).
|
|
216
|
+
*/
|
|
217
|
+
export declare function verifyRedeemReply(reply: CadreInviteAccepted, invitation: CadreInvitation, isTrustedIssuer: (ownerKey: string) => boolean): void;
|
|
218
|
+
/** The control-database surface a redemption needs; a `ControlDatabase` satisfies it. */
|
|
219
|
+
export type CadreInviteStore = Pick<ControlDatabase, 'seatCadreInvite' | 'isCadreInviteLive' | 'redeemCadreInvite' | 'querySeedPeers'>;
|
|
220
|
+
export interface CadreInviteHandlerOptions {
|
|
221
|
+
/** This member's party id; a request for another party is refused by name. */
|
|
222
|
+
partyId: string;
|
|
223
|
+
store: CadreInviteStore;
|
|
224
|
+
/**
|
|
225
|
+
* Push this member's control store to the device and resolve once the push has finished,
|
|
226
|
+
* called after the request verified and before the row is seated and the admission is
|
|
227
|
+
* written. Absent, the writes go ahead with the device holding nothing; see
|
|
228
|
+
* `catchUpDeviceIfLive` for why that tears on a member alone with the device.
|
|
229
|
+
*/
|
|
230
|
+
catchUpDevice?: (peerId: string) => Promise<void>;
|
|
231
|
+
/** Time to wait for the request frame; default {@link DEFAULT_REDEEM_READ_TIMEOUT_MS}. */
|
|
232
|
+
redeemReadTimeoutMs?: number;
|
|
233
|
+
/** Cap on concurrent inbound redemptions; default {@link DEFAULT_MAX_CONCURRENT_REDEMPTIONS}. */
|
|
234
|
+
maxConcurrentRedemptions?: number;
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Member side: registers the handler and drives each inbound stream through the checks in
|
|
238
|
+
* cost order — shape, connecting identity, both signatures, target device — so no database
|
|
239
|
+
* work happens for a stranger that cannot prove anything, then hands the device the control
|
|
240
|
+
* store (`catchUpDevice`), seats the row and redeems.
|
|
241
|
+
* Hardened as the seed handler is: a concurrency cap (over it, `busy`) and a read timeout.
|
|
242
|
+
*
|
|
243
|
+
* Registered only by a started `CadreNode`, after its control database is up, like the
|
|
244
|
+
* formation responder; nothing here is reachable on a node without one.
|
|
245
|
+
*/
|
|
246
|
+
export declare class CadreInviteHandler {
|
|
247
|
+
private readonly options;
|
|
248
|
+
private readonly readTimeoutMs;
|
|
249
|
+
private readonly maxConcurrent;
|
|
250
|
+
private readonly registered;
|
|
251
|
+
private active;
|
|
252
|
+
constructor(options: CadreInviteHandlerOptions);
|
|
253
|
+
/** In-flight inbound redemptions. */
|
|
254
|
+
get activeCount(): number;
|
|
255
|
+
/**
|
|
256
|
+
* Register on `node`; rejects when libp2p refuses the registration. `runOnLimitedConnection`
|
|
257
|
+
* because a phone redeems through a relay, whose connection libp2p marks limited; the
|
|
258
|
+
* exchange is two small frames, well inside any relay's cap.
|
|
259
|
+
*/
|
|
260
|
+
register(node: Libp2p, protocolId?: string): Promise<void>;
|
|
261
|
+
unregister(node: Libp2p, protocolId?: string): Promise<void>;
|
|
262
|
+
/**
|
|
263
|
+
* One inbound redemption: read the frame, decide, reply, close. Public as the seam the
|
|
264
|
+
* protocol spec drives directly; `remotePeerId` is the connection's authenticated peer.
|
|
265
|
+
*/
|
|
266
|
+
handleStream(stream: ControlStream, remotePeerId: string): Promise<void>;
|
|
267
|
+
/**
|
|
268
|
+
* Read the request frame to EOF, bounded and size-capped; JSON only, shape is `decide`'s.
|
|
269
|
+
* Throws on a frame that cannot be read, which {@link handleStream} answers `internal`:
|
|
270
|
+
* a read timeout or a cut frame proves nothing about the invitation, so the device must
|
|
271
|
+
* stay free to try the next address.
|
|
272
|
+
*/
|
|
273
|
+
private readRequest;
|
|
274
|
+
private decide;
|
|
275
|
+
/**
|
|
276
|
+
* The checks that cost no database work, in order: shape, party, connecting identity (an
|
|
277
|
+
* ed25519 peer id is an identity multihash of the very key, and this is the only layer that
|
|
278
|
+
* can check the pair), both signatures, and a targeted invitation's device.
|
|
279
|
+
*/
|
|
280
|
+
private checkRequest;
|
|
281
|
+
/**
|
|
282
|
+
* Hand the device this member's control store, seat the row from the bundle (its own
|
|
283
|
+
* transaction; a no-op when held), then redeem. The push comes first because the seat is
|
|
284
|
+
* already a write the device takes part in (see {@link catchUpDeviceIfLive}). An issuer this
|
|
285
|
+
* member does not know as an owner is the one retryable refusal left: the device tries the
|
|
286
|
+
* next address, which may hold the issuer's row.
|
|
287
|
+
*/
|
|
288
|
+
private seatAndRedeem;
|
|
289
|
+
/**
|
|
290
|
+
* Push this member's control store to the device before the first write of the exchange
|
|
291
|
+
* (the seat, when this member does not hold the row; otherwise the admission). The device
|
|
292
|
+
* joined this member's control write cohort when it connected, and a cohort node with no
|
|
293
|
+
* base revision of a block refuses a commit to it; a member alone with the device then has
|
|
294
|
+
* half its cohort refusing, and the write tears instead of committing. With the store in
|
|
295
|
+
* hand the device holds the write too.
|
|
296
|
+
*
|
|
297
|
+
* Only for an invitation still live here, judged on the row this member holds or, before
|
|
298
|
+
* the seat, on the bundle's copy once its issuer signature verifies
|
|
299
|
+
* (`ControlDatabase.isCadreInviteLive`): the holder of a withdrawn, expired or spent one,
|
|
300
|
+
* or of a forged copy naming a real owner, is refused without being sent anything. The
|
|
301
|
+
* retry of an admission already written can read as spent too and is sent nothing; it
|
|
302
|
+
* needs nothing, since the device is a member and the ordinary catch-up reaches it.
|
|
303
|
+
* Best-effort: a failed check or push is logged and the writes go ahead, then commit or are
|
|
304
|
+
* answered retryably.
|
|
305
|
+
*/
|
|
306
|
+
private catchUpDeviceIfLive;
|
|
307
|
+
/** Dial hints are an optimization; a failed read costs the device its hints, not its membership. */
|
|
308
|
+
private peersBestEffort;
|
|
309
|
+
}
|
|
310
|
+
export interface RedeemAtMembersOptions {
|
|
311
|
+
/** The bundle: its `members` are dialed in order. */
|
|
312
|
+
invitation: CadreInvitation;
|
|
313
|
+
/** The signed request ({@link signRedeemRequest}); the same one goes to every address. */
|
|
314
|
+
request: CadreInviteRedeemRequest;
|
|
315
|
+
/** Is this owner key one the device pinned from the bundle? The reply check's input. */
|
|
316
|
+
isTrustedIssuer: (ownerKey: string) => boolean;
|
|
317
|
+
/**
|
|
318
|
+
* This machine's declared link round trip (`NetworkConfig.linkRoundTripMs`), from which
|
|
319
|
+
* the per-address deadline derives: a dial that may need a relay plus one request
|
|
320
|
+
* (`relayedRequestBudgetMs`, 28.5 s at the default declaration), as seed delivery's is.
|
|
321
|
+
*/
|
|
322
|
+
linkRoundTripMs?: number;
|
|
323
|
+
/** Per-address deadline override; default derived from `linkRoundTripMs`. */
|
|
324
|
+
addressBudgetMs?: number;
|
|
325
|
+
protocolId?: string;
|
|
326
|
+
}
|
|
327
|
+
/** What a successful redemption yields the device. */
|
|
328
|
+
export interface CadreInviteRedemption {
|
|
329
|
+
reply: CadreInviteAccepted;
|
|
330
|
+
/** The answering member's peer id, from the address's trailing `/p2p/`; null when it named none. */
|
|
331
|
+
memberPeerId: string | null;
|
|
332
|
+
/** The address that answered. */
|
|
333
|
+
memberAddr: string;
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Device side: try the bundle's addresses one at a time, each under its own deadline, and
|
|
337
|
+
* stop at the first acceptance or the first final refusal. A dial failure, a malformed
|
|
338
|
+
* reply or a retryable refusal moves to the next address.
|
|
339
|
+
*
|
|
340
|
+
* Every address gets the SAME request, with one `usageStampId`: `CadreInviteUsage` is keyed
|
|
341
|
+
* by it, so a member the device gave up on that was still committing cannot admit it twice,
|
|
342
|
+
* and a retry at the next member after a dropped reply is answered as already a member.
|
|
343
|
+
*
|
|
344
|
+
* @throws {CadreInviteRejectedError} on a final refusal (its `retryable` is false).
|
|
345
|
+
* @throws {CadreInviteReplyInvalidError} on an acceptance that fails {@link verifyRedeemReply}.
|
|
346
|
+
* @throws {CadreInviteUnreachableError} when every address was tried and none accepted.
|
|
347
|
+
*/
|
|
348
|
+
export declare function redeemAtMembers(node: Libp2p, options: RedeemAtMembersOptions): Promise<CadreInviteRedemption>;
|