@serfab/cadre-core 0.9.0 → 0.11.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 +255 -253
- package/dist/bootstrap-peer-store-file.d.ts +13 -0
- package/dist/bootstrap-peer-store-file.js +42 -0
- package/dist/bootstrap-peer-store-file.js.map +1 -0
- package/dist/bootstrap-peer-store.d.ts +73 -0
- package/dist/bootstrap-peer-store.js +128 -0
- package/dist/bootstrap-peer-store.js.map +1 -0
- package/dist/cadre-node.d.ts +1121 -81
- package/dist/cadre-node.js +2276 -262
- package/dist/cadre-node.js.map +1 -1
- package/dist/control-authorization.d.ts +93 -0
- package/dist/control-authorization.js +57 -0
- package/dist/control-authorization.js.map +1 -0
- package/dist/control-cohort.d.ts +26 -0
- package/dist/control-cohort.js +26 -0
- package/dist/control-cohort.js.map +1 -1
- package/dist/control-database.d.ts +959 -54
- package/dist/control-database.js +1421 -148
- package/dist/control-database.js.map +1 -1
- package/dist/control-formation-recorder.d.ts +90 -10
- package/dist/control-formation-recorder.js +130 -14
- package/dist/control-formation-recorder.js.map +1 -1
- package/dist/control-schema.d.ts +1 -1
- package/dist/control-schema.js +651 -72
- package/dist/control-schema.js.map +1 -1
- package/dist/control-stream.d.ts +38 -4
- package/dist/control-stream.js +70 -4
- package/dist/control-stream.js.map +1 -1
- package/dist/control-write-retry.d.ts +161 -0
- package/dist/control-write-retry.js +412 -0
- package/dist/control-write-retry.js.map +1 -0
- package/dist/delegate-admission.d.ts +134 -0
- package/dist/delegate-admission.js +244 -0
- package/dist/delegate-admission.js.map +1 -0
- package/dist/device-token.d.ts +11 -11
- package/dist/device-token.js +14 -13
- package/dist/device-token.js.map +1 -1
- package/dist/ed25519-key.d.ts +26 -0
- package/dist/ed25519-key.js +56 -1
- package/dist/ed25519-key.js.map +1 -1
- package/dist/file-durable-slot.d.ts +20 -0
- package/dist/file-durable-slot.js +53 -0
- package/dist/file-durable-slot.js.map +1 -0
- package/dist/formation-approval.d.ts +157 -0
- package/dist/formation-approval.js +375 -0
- package/dist/formation-approval.js.map +1 -0
- package/dist/fs-atomic.d.ts +38 -0
- package/dist/fs-atomic.js +97 -0
- package/dist/fs-atomic.js.map +1 -0
- package/dist/identity-key.d.ts +49 -0
- package/dist/identity-key.js +81 -0
- package/dist/identity-key.js.map +1 -0
- package/dist/index.d.ts +17 -7
- package/dist/index.js +40 -6
- package/dist/index.js.map +1 -1
- package/dist/key-store-file.d.ts +2 -24
- package/dist/key-store-file.js +10 -77
- package/dist/key-store-file.js.map +1 -1
- package/dist/membership-connection-gater.d.ts +146 -0
- package/dist/membership-connection-gater.js +180 -0
- package/dist/membership-connection-gater.js.map +1 -0
- package/dist/node-local-snapshot.d.ts +109 -0
- package/dist/node-local-snapshot.js +182 -0
- package/dist/node-local-snapshot.js.map +1 -0
- package/dist/peer-addr-book.d.ts +92 -0
- package/dist/peer-addr-book.js +220 -0
- package/dist/peer-addr-book.js.map +1 -0
- package/dist/peer-authorization.d.ts +135 -18
- package/dist/peer-authorization.js +152 -21
- package/dist/peer-authorization.js.map +1 -1
- package/dist/peer-record.d.ts +21 -8
- package/dist/peer-record.js +26 -10
- package/dist/peer-record.js.map +1 -1
- package/dist/relay-addrs.d.ts +45 -0
- package/dist/relay-addrs.js +103 -0
- package/dist/relay-addrs.js.map +1 -0
- package/dist/relay-reservation.d.ts +266 -0
- package/dist/relay-reservation.js +613 -0
- package/dist/relay-reservation.js.map +1 -0
- package/dist/seed-bootstrap.d.ts +228 -56
- package/dist/seed-bootstrap.js +340 -198
- package/dist/seed-bootstrap.js.map +1 -1
- package/dist/seed-trust-policy.d.ts +47 -21
- package/dist/seed-trust-policy.js +24 -15
- package/dist/seed-trust-policy.js.map +1 -1
- package/dist/strand-addr-protocol.d.ts +42 -13
- package/dist/strand-addr-protocol.js +58 -52
- package/dist/strand-addr-protocol.js.map +1 -1
- package/dist/strand-backfill.d.ts +109 -0
- package/dist/strand-backfill.js +279 -0
- package/dist/strand-backfill.js.map +1 -0
- package/dist/strand-cohort.d.ts +4 -37
- package/dist/strand-cohort.js +4 -15
- package/dist/strand-cohort.js.map +1 -1
- package/dist/strand-database.d.ts +11 -16
- package/dist/strand-database.js +19 -7
- package/dist/strand-database.js.map +1 -1
- package/dist/strand-formation-manager.d.ts +36 -7
- package/dist/strand-formation-manager.js +115 -15
- package/dist/strand-formation-manager.js.map +1 -1
- package/dist/strand-formation-protocol.d.ts +103 -4
- package/dist/strand-formation-protocol.js +232 -4
- package/dist/strand-formation-protocol.js.map +1 -1
- package/dist/strand-instance-manager.d.ts +88 -19
- package/dist/strand-instance-manager.js +178 -34
- package/dist/strand-instance-manager.js.map +1 -1
- package/dist/strand-member-registry.d.ts +5 -5
- package/dist/strand-member-registry.js +18 -10
- package/dist/strand-member-registry.js.map +1 -1
- package/dist/strand-membership-writer.d.ts +484 -88
- package/dist/strand-membership-writer.js +751 -128
- package/dist/strand-membership-writer.js.map +1 -1
- package/dist/strand-solicitation.d.ts +99 -33
- package/dist/strand-solicitation.js +100 -52
- package/dist/strand-solicitation.js.map +1 -1
- package/dist/strand-transport-key.d.ts +40 -0
- package/dist/strand-transport-key.js +56 -0
- package/dist/strand-transport-key.js.map +1 -0
- package/dist/strand-wake-protocol.d.ts +9 -7
- package/dist/strand-wake-protocol.js +22 -46
- package/dist/strand-wake-protocol.js.map +1 -1
- package/dist/strand-watcher.d.ts +17 -2
- package/dist/strand-watcher.js +47 -3
- package/dist/strand-watcher.js.map +1 -1
- package/dist/trusted-owner-store-file.d.ts +12 -0
- package/dist/trusted-owner-store-file.js +39 -0
- package/dist/trusted-owner-store-file.js.map +1 -0
- package/dist/trusted-owner-store.d.ts +64 -0
- package/dist/trusted-owner-store.js +126 -0
- package/dist/trusted-owner-store.js.map +1 -0
- package/dist/types.d.ts +307 -44
- package/dist/types.js +13 -0
- package/dist/types.js.map +1 -1
- package/package.json +20 -8
- package/src/arachnode-stub.ts +159 -159
- package/src/bootstrap-peer-store-file.ts +44 -0
- package/src/bootstrap-peer-store.ts +172 -0
- package/src/cadre-node.ts +5158 -2981
- package/src/canonical-json.ts +30 -30
- package/src/control-authorization.ts +114 -0
- package/src/control-cohort.ts +130 -103
- package/src/control-database.ts +2406 -948
- package/src/control-formation-recorder.ts +305 -151
- package/src/control-schema.ts +651 -72
- package/src/control-stream.ts +205 -128
- package/src/control-write-retry.ts +483 -0
- package/src/delegate-admission.ts +302 -0
- package/src/device-token.ts +94 -89
- package/src/diagnostics/connection-path.ts +277 -277
- package/src/diagnostics/webrtc-turn-tracker.ts +222 -222
- package/src/ed25519-key.ts +66 -1
- package/src/enrollment.ts +199 -199
- package/src/file-durable-slot.ts +61 -0
- package/src/formation-approval.ts +604 -0
- package/src/fs-atomic.ts +107 -0
- package/src/hibernation-manager.ts +437 -437
- package/src/identity-key.ts +109 -0
- package/src/index.ts +137 -5
- package/src/key-store-file.ts +132 -203
- package/src/key-store.ts +99 -99
- package/src/membership-connection-gater.ts +204 -0
- package/src/node-local-snapshot.ts +260 -0
- package/src/peer-addr-book.ts +250 -0
- package/src/peer-authorization.ts +259 -81
- package/src/peer-record.ts +164 -146
- package/src/push-credentials.ts +84 -84
- package/src/push-node.ts +64 -64
- package/src/push-notifier-apns.ts +247 -247
- package/src/push-notifier-fcm.ts +220 -220
- package/src/push-notifier-shared.ts +28 -28
- package/src/push-notifier.ts +52 -52
- package/src/relay-addrs.ts +111 -0
- package/src/relay-reservation.ts +823 -0
- package/src/seed-bootstrap.ts +409 -209
- package/src/seed-trust-policy.ts +140 -107
- package/src/strand-addr-protocol.ts +441 -410
- package/src/strand-backfill.ts +366 -0
- package/src/strand-cohort.ts +38 -77
- package/src/strand-database.ts +202 -204
- package/src/strand-formation-manager.ts +491 -368
- package/src/strand-formation-protocol.ts +692 -391
- package/src/strand-instance-manager.ts +201 -46
- package/src/strand-member-key.ts +39 -39
- package/src/strand-member-registry.ts +182 -177
- package/src/strand-membership-writer.ts +938 -146
- package/src/strand-solicitation.ts +472 -407
- package/src/strand-transport-key.ts +58 -0
- package/src/strand-wake-payload.ts +32 -32
- package/src/strand-wake-protocol.ts +314 -331
- package/src/strand-watcher.ts +314 -251
- package/src/trusted-owner-store-file.ts +41 -0
- package/src/trusted-owner-store.ts +171 -0
- package/src/types.ts +1309 -1032
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Merging verified cadre addresses into a libp2p node's **address book** (its
|
|
3
|
+
* peerStore), so that every layer below cadre-core which dials by bare peer id
|
|
4
|
+
* — Optimystic's cluster client, repo client, FRET ping/announce, all funnelling
|
|
5
|
+
* into `libp2p.dialProtocol(peerId, …)` — has an address to use for a sibling
|
|
6
|
+
* whose connection has dropped, instead of failing with `NoValidAddressesError`
|
|
7
|
+
* until the next reconcile pass happens to re-dial it.
|
|
8
|
+
*
|
|
9
|
+
* Shared by the control-cohort reconcile pass and the strand one (both in
|
|
10
|
+
* `CadreNode`): each resolves a peer's addresses its own way and then needs
|
|
11
|
+
* exactly this write, with exactly this workaround.
|
|
12
|
+
*
|
|
13
|
+
* Two exports, in the order a caller uses them: {@link groupAddrsByPeerId} turns
|
|
14
|
+
* a flat address list into per-peer groups (the strand path starts from the
|
|
15
|
+
* peer-agnostic union the strand-addr RPC returns), and {@link mergePeerAddrs}
|
|
16
|
+
* writes one peer's group.
|
|
17
|
+
*/
|
|
18
|
+
import type { PeerId, PeerStore } from '@libp2p/interface';
|
|
19
|
+
import { type Multiaddr } from '@multiformats/multiaddr';
|
|
20
|
+
/** Outcome of one address-book merge, for logging and tests. */
|
|
21
|
+
export type MergeAddrsResult = 'merged' | 'restamped' | 'skipped' | 'failed';
|
|
22
|
+
/**
|
|
23
|
+
* The slice of a libp2p node {@link mergePeerAddrs} touches. Structurally
|
|
24
|
+
* satisfied by `Libp2p`; narrow so a caller (or a test) can pass a bare
|
|
25
|
+
* peerStore holder.
|
|
26
|
+
*/
|
|
27
|
+
export interface PeerAddrBookHost {
|
|
28
|
+
peerStore: PeerStore;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Write `addrs` into `host`'s address book for `peerId`, best-effort. `peerId`
|
|
32
|
+
* may be the string form callers hold (a `CadrePeer.PeerId` column, a strand-addr
|
|
33
|
+
* RPC result); parsing it is part of the best-effort contract below, so no caller
|
|
34
|
+
* has to wrap this in a second try/catch of its own.
|
|
35
|
+
*
|
|
36
|
+
* - **Empty `addrs` writes nothing** and reports `'skipped'`. That is how a
|
|
37
|
+
* revoked / stale / untrusted peer's existing entry is allowed to age out on
|
|
38
|
+
* its own: the caller resolves `[]` for it, and nothing here refreshes it.
|
|
39
|
+
* - Otherwise the addresses are merged, and — see the timestamp trap below —
|
|
40
|
+
* re-saved when the merge alone leaves them invisible (`'restamped'`).
|
|
41
|
+
* - Any throw (unparsable peer id, datastore failure, a peerStore double without
|
|
42
|
+
* `merge`) is logged and folded to `'failed'`. Callers are best-effort loops:
|
|
43
|
+
* an address-book failure must never abort a reconcile pass.
|
|
44
|
+
*
|
|
45
|
+
* NOTE: `peerStore.merge` cannot refresh an address's `observed` timestamp in
|
|
46
|
+
* `@libp2p/peer-store` 12.0.10 — `to-peer-pb.js` shadows its own loop variable
|
|
47
|
+
* (`…addresses?.find(addr => uint8ArrayEquals(addr.multiaddr, addr.multiaddr))`,
|
|
48
|
+
* always true, so it returns the FIRST stored address's timestamp), and every
|
|
49
|
+
* read filters out addresses older than `MAX_ADDRESS_AGE` (1 hour). So an entry
|
|
50
|
+
* silently dies at the one-hour mark and no amount of re-merging revives it —
|
|
51
|
+
* not even merging a brand-new address. The `save` below is the workaround:
|
|
52
|
+
* `save` is the one write path that omits `existingPeer` and therefore stamps
|
|
53
|
+
* `Date.now()`. Drop it once upstream stamps `Date.now()` on merge.
|
|
54
|
+
*
|
|
55
|
+
* NOTE: raising the store's own `maxAddressAge` (libp2p forwards `init.peerStore`
|
|
56
|
+
* straight into `persistentPeerStore`) would sidestep the bug in one line, and is
|
|
57
|
+
* deliberately NOT done: expiry is load-bearing here. Only *verified* addresses
|
|
58
|
+
* come through this helper, and the design relies on everything else — the
|
|
59
|
+
* cold-start seed entries, identify-learned addresses — still ageing out on the
|
|
60
|
+
* stock schedule. A global age bump would keep those alive too.
|
|
61
|
+
*
|
|
62
|
+
* NOTE: if the store ever REJECTS an address we hand it, the restamp repeats
|
|
63
|
+
* every pass forever — `allVisible` stays false, and `save` re-submits the same
|
|
64
|
+
* rejected address. libp2p wires the store's `addressFilter` to
|
|
65
|
+
* `connectionGater.filterMultiaddrForPeer`, which nothing in this repo implements
|
|
66
|
+
* today (`createMembershipConnectionGater` gates dials, not addresses), so the
|
|
67
|
+
* loop is unreachable. If an app ever supplies that gater hook via
|
|
68
|
+
* `NetworkConfig.connectionGater`, bound the retry — the cost is one redundant
|
|
69
|
+
* datastore write per gated sibling per reconcile pass.
|
|
70
|
+
*/
|
|
71
|
+
export declare function mergePeerAddrs(host: PeerAddrBookHost, peerId: PeerId | string, addrs: Multiaddr[]): Promise<MergeAddrsResult>;
|
|
72
|
+
/**
|
|
73
|
+
* Group multiaddr strings by the peer id in their final `/p2p/` component — the
|
|
74
|
+
* attribution step between a flat, peer-agnostic address list and the per-peer
|
|
75
|
+
* writes {@link mergePeerAddrs} takes.
|
|
76
|
+
*
|
|
77
|
+
* The **last** `/p2p/` component is the addressed peer, not the first: a relayed
|
|
78
|
+
* address (`…/p2p/<relay>/p2p-circuit/p2p/<dst>`) names the relay first and the
|
|
79
|
+
* destination last, while a direct address (`…/tcp/…/p2p/<dst>`) names only the
|
|
80
|
+
* destination. Taking the last therefore attributes both shapes to the peer the
|
|
81
|
+
* address actually reaches.
|
|
82
|
+
*
|
|
83
|
+
* Three shapes are dropped rather than attributed, because none of them names a
|
|
84
|
+
* peer the address reaches: one that does not parse, one carrying no `/p2p/`
|
|
85
|
+
* component at all (a peer advertising a bare listen addr), and one whose
|
|
86
|
+
* `p2p-circuit` comes AFTER its last `/p2p/` (`…/p2p/<relay>/p2p-circuit`) — that
|
|
87
|
+
* last one is a relay hop with the destination missing, so its trailing peer id
|
|
88
|
+
* is the RELAY's and filing it under the relay would claim the relay is reachable
|
|
89
|
+
* at a circuit leading nowhere. Duplicates collapse within a group. Insertion
|
|
90
|
+
* order is preserved, both between groups and inside one.
|
|
91
|
+
*/
|
|
92
|
+
export declare function groupAddrsByPeerId(addrs: string[]): Map<string, Multiaddr[]>;
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Merging verified cadre addresses into a libp2p node's **address book** (its
|
|
3
|
+
* peerStore), so that every layer below cadre-core which dials by bare peer id
|
|
4
|
+
* — Optimystic's cluster client, repo client, FRET ping/announce, all funnelling
|
|
5
|
+
* into `libp2p.dialProtocol(peerId, …)` — has an address to use for a sibling
|
|
6
|
+
* whose connection has dropped, instead of failing with `NoValidAddressesError`
|
|
7
|
+
* until the next reconcile pass happens to re-dial it.
|
|
8
|
+
*
|
|
9
|
+
* Shared by the control-cohort reconcile pass and the strand one (both in
|
|
10
|
+
* `CadreNode`): each resolves a peer's addresses its own way and then needs
|
|
11
|
+
* exactly this write, with exactly this workaround.
|
|
12
|
+
*
|
|
13
|
+
* Two exports, in the order a caller uses them: {@link groupAddrsByPeerId} turns
|
|
14
|
+
* a flat address list into per-peer groups (the strand path starts from the
|
|
15
|
+
* peer-agnostic union the strand-addr RPC returns), and {@link mergePeerAddrs}
|
|
16
|
+
* writes one peer's group.
|
|
17
|
+
*/
|
|
18
|
+
import debug from 'debug';
|
|
19
|
+
import { peerIdFromString } from '@libp2p/peer-id';
|
|
20
|
+
import { multiaddr, CODE_P2P, CODE_P2P_CIRCUIT } from '@multiformats/multiaddr';
|
|
21
|
+
const log = debug('sereus:cadre:peer-addr-book');
|
|
22
|
+
/**
|
|
23
|
+
* Write `addrs` into `host`'s address book for `peerId`, best-effort. `peerId`
|
|
24
|
+
* may be the string form callers hold (a `CadrePeer.PeerId` column, a strand-addr
|
|
25
|
+
* RPC result); parsing it is part of the best-effort contract below, so no caller
|
|
26
|
+
* has to wrap this in a second try/catch of its own.
|
|
27
|
+
*
|
|
28
|
+
* - **Empty `addrs` writes nothing** and reports `'skipped'`. That is how a
|
|
29
|
+
* revoked / stale / untrusted peer's existing entry is allowed to age out on
|
|
30
|
+
* its own: the caller resolves `[]` for it, and nothing here refreshes it.
|
|
31
|
+
* - Otherwise the addresses are merged, and — see the timestamp trap below —
|
|
32
|
+
* re-saved when the merge alone leaves them invisible (`'restamped'`).
|
|
33
|
+
* - Any throw (unparsable peer id, datastore failure, a peerStore double without
|
|
34
|
+
* `merge`) is logged and folded to `'failed'`. Callers are best-effort loops:
|
|
35
|
+
* an address-book failure must never abort a reconcile pass.
|
|
36
|
+
*
|
|
37
|
+
* NOTE: `peerStore.merge` cannot refresh an address's `observed` timestamp in
|
|
38
|
+
* `@libp2p/peer-store` 12.0.10 — `to-peer-pb.js` shadows its own loop variable
|
|
39
|
+
* (`…addresses?.find(addr => uint8ArrayEquals(addr.multiaddr, addr.multiaddr))`,
|
|
40
|
+
* always true, so it returns the FIRST stored address's timestamp), and every
|
|
41
|
+
* read filters out addresses older than `MAX_ADDRESS_AGE` (1 hour). So an entry
|
|
42
|
+
* silently dies at the one-hour mark and no amount of re-merging revives it —
|
|
43
|
+
* not even merging a brand-new address. The `save` below is the workaround:
|
|
44
|
+
* `save` is the one write path that omits `existingPeer` and therefore stamps
|
|
45
|
+
* `Date.now()`. Drop it once upstream stamps `Date.now()` on merge.
|
|
46
|
+
*
|
|
47
|
+
* NOTE: raising the store's own `maxAddressAge` (libp2p forwards `init.peerStore`
|
|
48
|
+
* straight into `persistentPeerStore`) would sidestep the bug in one line, and is
|
|
49
|
+
* deliberately NOT done: expiry is load-bearing here. Only *verified* addresses
|
|
50
|
+
* come through this helper, and the design relies on everything else — the
|
|
51
|
+
* cold-start seed entries, identify-learned addresses — still ageing out on the
|
|
52
|
+
* stock schedule. A global age bump would keep those alive too.
|
|
53
|
+
*
|
|
54
|
+
* NOTE: if the store ever REJECTS an address we hand it, the restamp repeats
|
|
55
|
+
* every pass forever — `allVisible` stays false, and `save` re-submits the same
|
|
56
|
+
* rejected address. libp2p wires the store's `addressFilter` to
|
|
57
|
+
* `connectionGater.filterMultiaddrForPeer`, which nothing in this repo implements
|
|
58
|
+
* today (`createMembershipConnectionGater` gates dials, not addresses), so the
|
|
59
|
+
* loop is unreachable. If an app ever supplies that gater hook via
|
|
60
|
+
* `NetworkConfig.connectionGater`, bound the retry — the cost is one redundant
|
|
61
|
+
* datastore write per gated sibling per reconcile pass.
|
|
62
|
+
*/
|
|
63
|
+
export async function mergePeerAddrs(host, peerId, addrs) {
|
|
64
|
+
if (addrs.length === 0) {
|
|
65
|
+
return 'skipped';
|
|
66
|
+
}
|
|
67
|
+
try {
|
|
68
|
+
const id = typeof peerId === 'string' ? peerIdFromString(peerId) : peerId;
|
|
69
|
+
const peer = await host.peerStore.merge(id, { multiaddrs: addrs });
|
|
70
|
+
// The returned Peer is already expiry-filtered, so it says whether the merge
|
|
71
|
+
// is actually VISIBLE — which is the only question that matters downstream.
|
|
72
|
+
if (allVisible(peer.addresses, addrs, id)) {
|
|
73
|
+
return 'merged';
|
|
74
|
+
}
|
|
75
|
+
await host.peerStore.save(id, restampData(peer, addrs, id));
|
|
76
|
+
return 'restamped';
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
log('mergePeerAddrs: address-book write for %s failed: %o', peerId.toString(), error);
|
|
80
|
+
return 'failed';
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Group multiaddr strings by the peer id in their final `/p2p/` component — the
|
|
85
|
+
* attribution step between a flat, peer-agnostic address list and the per-peer
|
|
86
|
+
* writes {@link mergePeerAddrs} takes.
|
|
87
|
+
*
|
|
88
|
+
* The **last** `/p2p/` component is the addressed peer, not the first: a relayed
|
|
89
|
+
* address (`…/p2p/<relay>/p2p-circuit/p2p/<dst>`) names the relay first and the
|
|
90
|
+
* destination last, while a direct address (`…/tcp/…/p2p/<dst>`) names only the
|
|
91
|
+
* destination. Taking the last therefore attributes both shapes to the peer the
|
|
92
|
+
* address actually reaches.
|
|
93
|
+
*
|
|
94
|
+
* Three shapes are dropped rather than attributed, because none of them names a
|
|
95
|
+
* peer the address reaches: one that does not parse, one carrying no `/p2p/`
|
|
96
|
+
* component at all (a peer advertising a bare listen addr), and one whose
|
|
97
|
+
* `p2p-circuit` comes AFTER its last `/p2p/` (`…/p2p/<relay>/p2p-circuit`) — that
|
|
98
|
+
* last one is a relay hop with the destination missing, so its trailing peer id
|
|
99
|
+
* is the RELAY's and filing it under the relay would claim the relay is reachable
|
|
100
|
+
* at a circuit leading nowhere. Duplicates collapse within a group. Insertion
|
|
101
|
+
* order is preserved, both between groups and inside one.
|
|
102
|
+
*/
|
|
103
|
+
export function groupAddrsByPeerId(addrs) {
|
|
104
|
+
const groups = new Map();
|
|
105
|
+
const seen = new Set();
|
|
106
|
+
for (const addr of addrs) {
|
|
107
|
+
const attributed = attributeAddr(addr);
|
|
108
|
+
if (!attributed) {
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
const key = `${attributed.peerId}\n${addr}`;
|
|
112
|
+
if (seen.has(key)) {
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
seen.add(key);
|
|
116
|
+
const group = groups.get(attributed.peerId);
|
|
117
|
+
if (group) {
|
|
118
|
+
group.push(attributed.multiaddr);
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
groups.set(attributed.peerId, [attributed.multiaddr]);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return groups;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Parse one address and pull out the peer id it addresses (its last `/p2p/`
|
|
128
|
+
* value); null when the address is unparsable or names no destination. Every case
|
|
129
|
+
* is logged at the same level — an unattributable address is a sibling telling us
|
|
130
|
+
* something we cannot use, which is worth seeing but never worth throwing over.
|
|
131
|
+
*/
|
|
132
|
+
function attributeAddr(addr) {
|
|
133
|
+
let parsed;
|
|
134
|
+
try {
|
|
135
|
+
parsed = multiaddr(addr);
|
|
136
|
+
}
|
|
137
|
+
catch (error) {
|
|
138
|
+
log('groupAddrsByPeerId: skipping unparsable addr %s: %o', addr, error);
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
const peerId = lastAddressedPeerId(parsed);
|
|
142
|
+
if (peerId === null) {
|
|
143
|
+
log('groupAddrsByPeerId: skipping addr naming no destination peer: %s', addr);
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
return { peerId, multiaddr: parsed };
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* The peer id a parsed address reaches: its last `/p2p/` value, or null when it
|
|
150
|
+
* has none — or when a `p2p-circuit` sits after it, which makes that value the
|
|
151
|
+
* relay's rather than a destination's (see {@link groupAddrsByPeerId}). Walking
|
|
152
|
+
* backwards, meeting the circuit first is exactly that case.
|
|
153
|
+
*/
|
|
154
|
+
function lastAddressedPeerId(parsed) {
|
|
155
|
+
const components = parsed.getComponents();
|
|
156
|
+
for (let i = components.length - 1; i >= 0; i--) {
|
|
157
|
+
const component = components[i];
|
|
158
|
+
if (component.code === CODE_P2P_CIRCUIT) {
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
if (component.code === CODE_P2P && component.value) {
|
|
162
|
+
return component.value;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* The peerStore strips a trailing `/p2p/<peerId>` from an address it stores when
|
|
169
|
+
* that id is the address's FIRST `/p2p/` component (`dedupe-addresses.js`), so a
|
|
170
|
+
* plain address round-trips without its suffix while a relayed one — whose first
|
|
171
|
+
* `/p2p/` names the relay — keeps it. Comparing and de-duplicating on this
|
|
172
|
+
* suffix-stripped form matches either shape.
|
|
173
|
+
*/
|
|
174
|
+
function addrKey(addr, peerId) {
|
|
175
|
+
const suffix = `/p2p/${peerId.toString()}`;
|
|
176
|
+
return addr.endsWith(suffix) ? addr.slice(0, -suffix.length) : addr;
|
|
177
|
+
}
|
|
178
|
+
/** Is every address in `addrs` readable back from the stored, expiry-filtered set? */
|
|
179
|
+
function allVisible(stored, addrs, peerId) {
|
|
180
|
+
const keys = new Set(stored.map((a) => addrKey(a.multiaddr.toString(), peerId)));
|
|
181
|
+
return addrs.every((addr) => keys.has(addrKey(addr.toString(), peerId)));
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* The full `save` payload for a restamp: `save` re-uses the `patch` strategy with
|
|
185
|
+
* no existing peer, so every field it is not handed is DROPPED — and Optimystic
|
|
186
|
+
* classifies peers by their peerStore protocol list (`libp2p-key-network.ts`
|
|
187
|
+
* `membershipOf`), so losing protocols here would break cluster/repo routing.
|
|
188
|
+
* Everything therefore carries forward from the just-merged `peer`.
|
|
189
|
+
*
|
|
190
|
+
* The read-modify-write is NOT atomic against a concurrent `identify` write to
|
|
191
|
+
* the same entry. The loss window is milliseconds and costs at most one identify
|
|
192
|
+
* update, recovered by the next identify push — not worth a lock.
|
|
193
|
+
*
|
|
194
|
+
* A tag's remaining TTL is not carried: the public `Tag` type exposes only
|
|
195
|
+
* `value`, so the stored expiry is unreadable from here and the tag comes back
|
|
196
|
+
* non-expiring. `publicKey` is likewise not passed, which is lossless for the
|
|
197
|
+
* Ed25519 peer ids a cadre uses (the key is embedded in the id).
|
|
198
|
+
*/
|
|
199
|
+
function restampData(peer, addrs, peerId) {
|
|
200
|
+
const addresses = new Map();
|
|
201
|
+
for (const address of peer.addresses) {
|
|
202
|
+
addresses.set(addrKey(address.multiaddr.toString(), peerId), address);
|
|
203
|
+
}
|
|
204
|
+
for (const addr of addrs) {
|
|
205
|
+
const key = addrKey(addr.toString(), peerId);
|
|
206
|
+
if (!addresses.has(key)) {
|
|
207
|
+
// Uncertified, matching what `merge`'s own `multiaddrs` path would have
|
|
208
|
+
// stored; an address already present keeps whatever flag it carried.
|
|
209
|
+
addresses.set(key, { multiaddr: addr, isCertified: false });
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return {
|
|
213
|
+
addresses: [...addresses.values()],
|
|
214
|
+
protocols: peer.protocols,
|
|
215
|
+
metadata: new Map(peer.metadata),
|
|
216
|
+
tags: new Map([...peer.tags].map(([name, tag]) => [name, { value: tag.value }])),
|
|
217
|
+
peerRecordEnvelope: peer.peerRecordEnvelope
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
//# sourceMappingURL=peer-addr-book.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"peer-addr-book.js","sourceRoot":"","sources":["../src/peer-addr-book.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,36 +1,110 @@
|
|
|
1
|
+
import type { RevocableTable } from './control-authorization.js';
|
|
1
2
|
/**
|
|
2
|
-
* Canonical digest an owner signs to
|
|
3
|
+
* Canonical digest an owner signs to vouch a peer's ENROLLMENT — the offline
|
|
4
|
+
* credential `cadre enroll register` verifies. No table checks this digest, so it
|
|
5
|
+
* carries its own `'Cadre.Enrollment'` domain tag to stay disjoint from every
|
|
6
|
+
* CadreControl table rule (pre-tag it collided with the `DeviceToken` owner digests,
|
|
7
|
+
* so an enrollment vouch doubled as a push-token delete approval).
|
|
3
8
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* producer (owner signing) and the verifier (the offline `cadre enroll
|
|
8
|
-
* register` check) can never drift apart — change the digest here and both move
|
|
9
|
-
* together. The SQL mirror is the bare single-field `digest(coalesce(new.PeerId, old.PeerId))`.
|
|
9
|
+
* Factored into one place so the producer (owner signing) and the verifier (the
|
|
10
|
+
* offline `cadre enroll register` check) can never drift apart — change the digest
|
|
11
|
+
* here and both move together.
|
|
10
12
|
*/
|
|
11
13
|
export declare function peerAuthorizationDigest(peerId: string): string;
|
|
14
|
+
/**
|
|
15
|
+
* The `CadreControl.DeviceToken` columns {@link deviceTokenAddDigest} binds, in the
|
|
16
|
+
* schema's order. A whole-row struct rather than six positional strings, so a caller
|
|
17
|
+
* cannot silently transpose `platform` and `token` (both opaque strings) and mint a
|
|
18
|
+
* signature over a row it did not mean to approve.
|
|
19
|
+
*
|
|
20
|
+
* Structurally satisfied by a `DeviceTokenRecord` spread with the row's `stampId`;
|
|
21
|
+
* `types.ts:DeviceTokenRecord` deliberately does NOT carry the stamp — it is the
|
|
22
|
+
* SELF-signed record shape, and the peer's own `Sig` does not cover `StampId`.
|
|
23
|
+
* `updatedAt` / `sig` are nullable because the columns are: both sign as `''` when
|
|
24
|
+
* absent, mirroring the schema's `coalesce(...)`.
|
|
25
|
+
*/
|
|
26
|
+
export interface DeviceTokenAuthorizedRow {
|
|
27
|
+
peerId: string;
|
|
28
|
+
platform: string;
|
|
29
|
+
token: string;
|
|
30
|
+
updatedAt: number | null;
|
|
31
|
+
sig: string | null;
|
|
32
|
+
stampId: string;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Canonical digest an owner signs to authorize a `DeviceToken` INSERT — the WHOLE row,
|
|
36
|
+
* ending in its single-use `StampId` nonce. SQL mirror:
|
|
37
|
+
* `digest('CadreControl.DeviceToken', 'add', new.PeerId, new.Platform, new.Token,
|
|
38
|
+
* coalesce(cast(new.UpdatedAt as text), ''), coalesce(new.Sig, ''), new.StampId)` in
|
|
39
|
+
* `DeviceToken.AuthorizedInsert`.
|
|
40
|
+
*
|
|
41
|
+
* Binding every column means a captured approval can only ever reproduce the exact row
|
|
42
|
+
* it approved — never one carrying attacker-chosen `Platform`/`Token`/`UpdatedAt` — and
|
|
43
|
+
* binding the stamp makes it single-use: while the row lives the `unique` column blocks
|
|
44
|
+
* a replay, and after a clear the stamp is retired permanently into
|
|
45
|
+
* `CadreControl.Revocation` (`DeviceToken.NotRevoked`). This matters more here than for
|
|
46
|
+
* `CadrePeer`: a resurrected push token has NO freshness ceiling to retire it
|
|
47
|
+
* (`CadreNode.resolveDeviceToken` defaults `maxAgeMs` to infinity by design), so stamp
|
|
48
|
+
* retirement is the only thing that sticks.
|
|
49
|
+
*
|
|
50
|
+
* Distinct from {@link deviceTokenRemoveDigest} so a captured insert approval can never
|
|
51
|
+
* be replayed to delete the token, and vice versa.
|
|
52
|
+
*/
|
|
53
|
+
export declare function deviceTokenAddDigest(row: DeviceTokenAuthorizedRow): string;
|
|
54
|
+
/**
|
|
55
|
+
* Canonical digest an owner signs to authorize a `DeviceToken` DELETE, bound to the
|
|
56
|
+
* STORED row's (PeerId, StampId). SQL mirror:
|
|
57
|
+
* `digest('CadreControl.DeviceToken', 'remove', old.PeerId, old.StampId)` in
|
|
58
|
+
* `DeviceToken.AuthorizedDelete`.
|
|
59
|
+
*
|
|
60
|
+
* A narrower vector than {@link deviceTokenAddDigest} on purpose (the same split
|
|
61
|
+
* `CadrePeer` makes): the clear approval names only which row is being retired, so it
|
|
62
|
+
* cannot be re-cut into an insert approval, and it is dead the moment the stamp it
|
|
63
|
+
* names is tombstoned.
|
|
64
|
+
*/
|
|
65
|
+
export declare function deviceTokenRemoveDigest(peerId: string, stampId: string): string;
|
|
12
66
|
/**
|
|
13
67
|
* Canonical digest an owner signs to VOUCH a `CadrePeer` membership row (insert
|
|
14
|
-
* and the owner re-touch update
|
|
15
|
-
* `StampId` nonce, so a captured signed
|
|
16
|
-
*
|
|
17
|
-
*
|
|
68
|
+
* and the owner re-touch update — same semantics, deliberately the same digest).
|
|
69
|
+
* Binds the peer id to the row's single-use `StampId` nonce, so a captured signed
|
|
70
|
+
* insert cannot be replayed — while the row lives the `unique` column blocks it, and
|
|
71
|
+
* after a removal the stamp is retired permanently into `CadreControl.Revocation`
|
|
72
|
+
* (`CadrePeer.NotRevoked`) — and, because {@link cadrePeerRemoveDigest} scopes a
|
|
73
|
+
* DIFFERENT payload, the stored voucher (`VouchSig`) cannot be replayed to authorize
|
|
74
|
+
* a delete. The domain tag keeps the stored, replicated `VouchSig` useless against
|
|
75
|
+
* every OTHER table's rules.
|
|
18
76
|
*
|
|
19
|
-
*
|
|
20
|
-
* canonical multi-field framing in control-database.ts:buildAuthorizationMessage.
|
|
77
|
+
* SQL mirror: `digest('CadreControl.CadrePeer', 'vouch', new.PeerId, new.StampId)`.
|
|
21
78
|
*/
|
|
22
79
|
export declare function cadrePeerVoucherDigest(peerId: string, stampId: string): string;
|
|
23
80
|
/**
|
|
24
81
|
* Canonical digest an owner signs to REMOVE a `CadrePeer` row. Deliberately a
|
|
25
|
-
* distinct payload from {@link cadrePeerVoucherDigest} (
|
|
26
|
-
*
|
|
82
|
+
* distinct payload from {@link cadrePeerVoucherDigest} (the `'remove'` action tag)
|
|
83
|
+
* so the row's stored voucher — a signature over the voucher digest — can never
|
|
27
84
|
* satisfy this delete check. The signature is supplied in write context and never
|
|
28
|
-
* stored, so no reader can replay it;
|
|
29
|
-
*
|
|
85
|
+
* stored, so no reader can replay it; a captured remove is also dead after the
|
|
86
|
+
* delete lands, because a re-added row carries a FRESH `StampId` (the removed row's
|
|
87
|
+
* stamp is retired into `CadreControl.Revocation` and never reused).
|
|
30
88
|
*
|
|
31
|
-
* SQL mirror: `digest(old.PeerId, old.StampId
|
|
89
|
+
* SQL mirror: `digest('CadreControl.CadrePeer', 'remove', old.PeerId, old.StampId)`.
|
|
32
90
|
*/
|
|
33
91
|
export declare function cadrePeerRemoveDigest(peerId: string, stampId: string): string;
|
|
92
|
+
/**
|
|
93
|
+
* Canonical digest an owner signs to APPEND a `CadreControl.Revocation` tombstone —
|
|
94
|
+
* the row retiring `stampId` for the named guarded table, and recording `rowKey`
|
|
95
|
+
* (the removed row's primary key: OwnerKey.Key / ValidationKey.Key /
|
|
96
|
+
* CadrePeer.PeerId / DeviceToken.PeerId / Strand.Id) as which row was retired.
|
|
97
|
+
* SQL mirror:
|
|
98
|
+
* `digest('CadreControl.Revocation', 'remove', new.TableName, new.RowKey, new.StampId)`
|
|
99
|
+
* in `Revocation.Authorized`.
|
|
100
|
+
*
|
|
101
|
+
* Its own `'CadreControl.Revocation'` domain tag makes it disjoint from the
|
|
102
|
+
* `'CadreControl.CadrePeer'` (or `OwnerKey` / `ValidationKey` / `Strand`)
|
|
103
|
+
* `'remove'` digest the same owner signs in the SAME transaction for the delete
|
|
104
|
+
* this tombstone accompanies — a removal signature is not a retirement
|
|
105
|
+
* signature and cannot be replayed as one.
|
|
106
|
+
*/
|
|
107
|
+
export declare function revocationDigest(tableName: RevocableTable, rowKey: string, stampId: string): string;
|
|
34
108
|
/**
|
|
35
109
|
* Verify that `signature` is a valid owner ed25519 signature over `peerId`'s
|
|
36
110
|
* authorization digest, using `ownerPublicKey` (base64url).
|
|
@@ -46,3 +120,46 @@ export declare function cadrePeerRemoveDigest(peerId: string, stampId: string):
|
|
|
46
120
|
* exception). The catch is logged at debug.
|
|
47
121
|
*/
|
|
48
122
|
export declare function verifyPeerAuthorization(peerId: string, ownerPublicKey: string, signature: string): boolean;
|
|
123
|
+
/**
|
|
124
|
+
* Verify that `signature` is a valid owner ed25519 signature over the
|
|
125
|
+
* `CadrePeer` voucher digest for (`peerId`, `stampId`) — the read-side mirror
|
|
126
|
+
* of the voucher {@link ControlDatabase.insertCadrePeer} signs and persists into
|
|
127
|
+
* `VouchOwner`/`VouchSig` (see {@link cadrePeerVoucherDigest}).
|
|
128
|
+
*
|
|
129
|
+
* A `true` result means the holder of `ownerPublicKey` vouched THIS membership
|
|
130
|
+
* row (the peer id bound to the row's single-use `StampId` nonce). It says
|
|
131
|
+
* nothing about whether that owner key is itself trustworthy — the caller must
|
|
132
|
+
* separately check the key against the node-local trusted-owner anchor
|
|
133
|
+
* (`TrustedOwnerStore`), never the replicated `OwnerKey` table.
|
|
134
|
+
*
|
|
135
|
+
* Returns a boolean and never throws (same contract as
|
|
136
|
+
* {@link verifyPeerAuthorization}): malformed input or any crypto failure
|
|
137
|
+
* resolves to `false`, logged at debug.
|
|
138
|
+
*/
|
|
139
|
+
export declare function verifyCadrePeerVoucher(peerId: string, stampId: string, ownerPublicKey: string, signature: string): boolean;
|
|
140
|
+
/**
|
|
141
|
+
* Canonical digest a JOINING peer signs to consent to ONE `FormationUsage`
|
|
142
|
+
* redemption — the read-side mirror of `formationConsentMessage` in
|
|
143
|
+
* control-database.ts, base64url-encoded instead of raw bytes (see
|
|
144
|
+
* {@link taggedDigest}). That doc comment carries the field-vector rationale
|
|
145
|
+
* (notably why `strandId` is not signed); the two vectors must not drift, which
|
|
146
|
+
* peer-authorization.spec.ts pins by signing one form and verifying the other.
|
|
147
|
+
*/
|
|
148
|
+
export declare function formationConsentDigest(token: string, usageStampId: string, peerKey: string, disclosure: string): string;
|
|
149
|
+
/**
|
|
150
|
+
* Verify that `row.peerSig` is a valid ed25519 signature over the joining peer's
|
|
151
|
+
* consent digest (see {@link formationConsentDigest}) — the row's OWN `peerKey`.
|
|
152
|
+
*
|
|
153
|
+
* Unlike {@link verifyCadrePeerVoucher} there is no separate enrolled/owner row to
|
|
154
|
+
* look up: the identity IS the key carried on the row, so a forged consent would
|
|
155
|
+
* need that joiner's own private key. Returns a boolean and never throws (same
|
|
156
|
+
* contract as the siblings above): malformed input or any crypto failure resolves
|
|
157
|
+
* to `false`, logged at debug.
|
|
158
|
+
*/
|
|
159
|
+
export declare function verifyFormationConsent(row: {
|
|
160
|
+
token: string;
|
|
161
|
+
usageStampId: string;
|
|
162
|
+
peerKey: string;
|
|
163
|
+
disclosure: string;
|
|
164
|
+
peerSig: string;
|
|
165
|
+
}): boolean;
|