p2p-fret 1.0.2 → 1.1.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/src/estimate/size-estimator.js +1 -1
- package/dist/src/index.d.ts +37 -0
- package/dist/src/rpc/ping.d.ts +19 -11
- package/dist/src/rpc/ping.js +27 -21
- package/dist/src/rpc/validate.d.ts +19 -10
- package/dist/src/rpc/validate.js +37 -10
- package/dist/src/service/fret-service.d.ts +447 -55
- package/dist/src/service/fret-service.js +966 -227
- package/dist/src/service/idle-backoff.d.ts +68 -0
- package/dist/src/service/idle-backoff.js +99 -0
- package/dist/src/service/near-exchange.d.ts +94 -0
- package/dist/src/service/near-exchange.js +144 -0
- package/dist/src/service/size-observer.d.ts +1 -1
- package/dist/src/service/size-observer.js +1 -1
- package/dist/src/utils/deadline.d.ts +18 -0
- package/dist/src/utils/deadline.js +28 -0
- package/package.json +1 -1
- package/src/estimate/size-estimator.ts +185 -185
- package/src/index.ts +37 -0
- package/src/rpc/ping.ts +39 -24
- package/src/rpc/validate.ts +499 -474
- package/src/service/fret-service.ts +4644 -3904
- package/src/service/idle-backoff.ts +107 -0
- package/src/service/near-exchange.ts +165 -0
- package/src/service/size-observer.ts +227 -227
- package/src/utils/deadline.ts +31 -0
|
@@ -81,7 +81,7 @@ function windowGaps(store, m, selfCoord, selfId, filter) {
|
|
|
81
81
|
* into a tiny, evenly-spaced arc — an eclipse, or a very young ring — scores high here while
|
|
82
82
|
* `n` is wildly wrong, because no statistic over the sampled arc can see the arc that was never
|
|
83
83
|
* sampled. The defense is corroboration from peer-reported estimates (`reportNetworkSize` /
|
|
84
|
-
* `
|
|
84
|
+
* `calibrateSizeFromPeer`), not a better local formula.
|
|
85
85
|
*/
|
|
86
86
|
function dispersionFactor(gaps) {
|
|
87
87
|
const count = gaps.length;
|
package/dist/src/index.d.ts
CHANGED
|
@@ -18,6 +18,36 @@ export interface FretConfig {
|
|
|
18
18
|
* spacing window count once, so the run is genuinely spread over time.
|
|
19
19
|
*/
|
|
20
20
|
deadAfterFailures?: number;
|
|
21
|
+
/**
|
|
22
|
+
* Whole-dial budget, in milliseconds, for a maintenance **redial** — the connection FRET opens
|
|
23
|
+
* on its own to a routing-table peer it is not connected to (default 10 000 ms).
|
|
24
|
+
*
|
|
25
|
+
* A maintenance ping assumes an open connection and is bounded by a 2 s exchange budget; opening
|
|
26
|
+
* one is a separate step with this budget, run outside the stabilization tick. The default
|
|
27
|
+
* restates libp2p's own `dialTimeout` default (10 s), because libp2p applies that only to a dial
|
|
28
|
+
* that carries *no* abort signal — FRET always passes one, so this is the only whole-dial limit
|
|
29
|
+
* in force (libp2p's per-address `addressDialTimeout` still applies underneath). A host on slow
|
|
30
|
+
* relayed links sets it from its link budget: a circuit-relay dial that completes in 9 s is
|
|
31
|
+
* ordinary there. Must be a positive finite number; the constructor throws otherwise.
|
|
32
|
+
*/
|
|
33
|
+
dialTimeoutMs?: number;
|
|
34
|
+
/**
|
|
35
|
+
* The ceiling, in milliseconds, the passive stabilization interval may grow to while nothing in
|
|
36
|
+
* the network changes (default 60 000 ms).
|
|
37
|
+
*
|
|
38
|
+
* Passive stabilization runs every 1.5 s while things are changing and doubles that interval
|
|
39
|
+
* after every pass that finds nothing to act on — 1.5 → 3 → 6 → 12 → 24 → 48 s, then this
|
|
40
|
+
* ceiling — snapping back to 1.5 s the moment anything changes (a connection forms or drops, a
|
|
41
|
+
* peer is discovered, a label or liveness verdict moves, an address becomes known). At the
|
|
42
|
+
* default an idle node runs about one pass a minute instead of forty. The cost of a higher
|
|
43
|
+
* ceiling is detection latency for what only a pass can see: a peer that went silent with no
|
|
44
|
+
* disconnect event is first struck on the next pass (up to one interval away) and dead a few
|
|
45
|
+
* seconds after, a neighbour's content is re-checked once per interval, and an off-ring peer
|
|
46
|
+
* whose backoff window expired is re-probed up to one interval late. A host that wants faster
|
|
47
|
+
* detection sets this lower; a value at or below 1.5 s disables backoff altogether.
|
|
48
|
+
* Must be a positive finite number; the constructor throws otherwise.
|
|
49
|
+
*/
|
|
50
|
+
maxPassiveIntervalMs?: number;
|
|
21
51
|
/**
|
|
22
52
|
* This node's libp2p private key — the key `node.peerId` was derived from.
|
|
23
53
|
*
|
|
@@ -62,6 +92,13 @@ export interface NeighborSnapshotV1 {
|
|
|
62
92
|
* an id-keyed object so it truncates like the other lists and has no `__proto__`-key hazard.
|
|
63
93
|
*/
|
|
64
94
|
hints?: AddressHintV1[];
|
|
95
|
+
/**
|
|
96
|
+
* Fingerprint of the content a receiver acts on — the id lists, the sample's ids, `metadata`,
|
|
97
|
+
* `hints` (see `snapshotDigest` in `service/near-exchange.ts`). A receiver that merged this
|
|
98
|
+
* snapshot caches the digest and skips its next fetch while the sender's ping replies carry the
|
|
99
|
+
* same one. Optional and unversioned like `hints`: absent means "unknown, fetch as before".
|
|
100
|
+
*/
|
|
101
|
+
digest?: string;
|
|
65
102
|
}
|
|
66
103
|
export interface RouteAndMaybeActV1 {
|
|
67
104
|
v: 1;
|
package/dist/src/rpc/ping.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Libp2p } from 'libp2p';
|
|
2
2
|
import type { StreamCaps } from './protocols.js';
|
|
3
|
+
import { type RpcRequestOptions } from './request.js';
|
|
3
4
|
import type { RpcOutcome } from './outcome.js';
|
|
4
5
|
import type { BusyResponseV1 } from '../index.js';
|
|
5
6
|
export interface PingResponseV1 {
|
|
@@ -7,26 +8,33 @@ export interface PingResponseV1 {
|
|
|
7
8
|
ts: number;
|
|
8
9
|
size_estimate?: number;
|
|
9
10
|
confidence?: number;
|
|
11
|
+
/**
|
|
12
|
+
* The responder's current `NeighborSnapshotV1.digest`, so a neighbour that already merged that
|
|
13
|
+
* content can skip its snapshot fetch. Optional, no version bump: an older responder omits it
|
|
14
|
+
* and the sender fetches as before.
|
|
15
|
+
*/
|
|
16
|
+
snapshot_digest?: string;
|
|
10
17
|
}
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
confidence?: number;
|
|
17
|
-
} | BusyResponseV1>;
|
|
18
|
-
export declare function registerPing(node: Libp2p, protocol?: string, getSizeEstimate?: SizeEstimateProvider, onInbound?: (from: string) => void, streamCaps?: StreamCaps): Promise<void>;
|
|
18
|
+
/** The optional fields a responder adds to its pong — see {@link PingResponseV1}. */
|
|
19
|
+
export type PingReplyExtras = Pick<PingResponseV1, 'size_estimate' | 'confidence' | 'snapshot_digest'>;
|
|
20
|
+
/** Supplies {@link PingReplyExtras} per inbound ping, or a busy refusal in place of the pong. */
|
|
21
|
+
export type PingReplyProvider = () => PingReplyExtras | BusyResponseV1 | Promise<PingReplyExtras | BusyResponseV1>;
|
|
22
|
+
export declare function registerPing(node: Libp2p, protocol?: string, getReplyExtras?: PingReplyProvider, onInbound?: (from: string) => void, streamCaps?: StreamCaps): Promise<void>;
|
|
19
23
|
/**
|
|
20
24
|
* Ping `peer` over this network's namespaced protocol. Read-only request (the handler replies
|
|
21
|
-
* without reading)
|
|
22
|
-
*
|
|
23
|
-
*
|
|
25
|
+
* without reading). `dial` defaults to `'always'`; the service's maintenance passes pass
|
|
26
|
+
* `'never'`, because opening a connection is the redial seam's job under its own budget and a
|
|
27
|
+
* 2 s ping budget cannot cover a dial on a slow link. `ok.value.ok` is the peer's own pong flag;
|
|
28
|
+
* rttMs on the outcome. Truncation/undecodable replies are `decode-error` — the service scores
|
|
29
|
+
* them as proof of life.
|
|
24
30
|
*/
|
|
25
31
|
export declare function sendPing(node: Libp2p, peer: string, protocol?: string, opts?: {
|
|
26
32
|
signal?: AbortSignal;
|
|
27
33
|
timeoutMs?: number;
|
|
34
|
+
dial?: RpcRequestOptions<unknown>['dial'];
|
|
28
35
|
}): Promise<RpcOutcome<{
|
|
29
36
|
ok: boolean;
|
|
30
37
|
size_estimate?: number;
|
|
31
38
|
confidence?: number;
|
|
39
|
+
snapshot_digest?: string;
|
|
32
40
|
}>>;
|
package/dist/src/rpc/ping.js
CHANGED
|
@@ -3,7 +3,7 @@ import { rpcRequest } from './request.js';
|
|
|
3
3
|
import { parseOrThrow, parsePingResponse } from './validate.js';
|
|
4
4
|
import { createLogger } from '../logger.js';
|
|
5
5
|
const log = createLogger('rpc:ping');
|
|
6
|
-
export async function registerPing(node, protocol = PROTOCOL_PING,
|
|
6
|
+
export async function registerPing(node, protocol = PROTOCOL_PING, getReplyExtras, onInbound,
|
|
7
7
|
// Trailing and optional so existing positional callers keep compiling. Forwarded verbatim to
|
|
8
8
|
// `node.handle`; omitting it leaves libp2p's own per-connection defaults.
|
|
9
9
|
streamCaps = {}) {
|
|
@@ -17,51 +17,57 @@ streamCaps = {}) {
|
|
|
17
17
|
...streamCaps,
|
|
18
18
|
serve: (connection) => {
|
|
19
19
|
onInbound?.(connection.remotePeer.toString());
|
|
20
|
-
return pingReply(
|
|
20
|
+
return pingReply(getReplyExtras);
|
|
21
21
|
},
|
|
22
22
|
});
|
|
23
23
|
}
|
|
24
24
|
/**
|
|
25
|
-
* The body of a ping answer. Split out so the
|
|
25
|
+
* The body of a ping answer. Split out so the provider's `try` covers the *provider* alone: while
|
|
26
26
|
* it also wrapped the send-and-close tail, a stream that failed mid-reply was logged as a failed
|
|
27
|
-
*
|
|
27
|
+
* provider and then answered a second time on the same broken stream.
|
|
28
28
|
*
|
|
29
|
-
* A failed
|
|
29
|
+
* A failed provider is not a failed ping — it degrades to a plain pong.
|
|
30
30
|
*/
|
|
31
|
-
async function pingReply(
|
|
32
|
-
// Busy test on the
|
|
33
|
-
//
|
|
31
|
+
async function pingReply(getReplyExtras) {
|
|
32
|
+
// Busy test on the *provider's return* — an in-process value, not a wire reply, so this is not
|
|
33
|
+
// a copy of `rpcRequest`'s busy classification.
|
|
34
34
|
const isBusy = (res) => typeof res === 'object' && res !== null && 'busy' in res && res.busy === true;
|
|
35
35
|
const pong = () => ({ ok: true, ts: Date.now() });
|
|
36
|
-
if (!
|
|
36
|
+
if (!getReplyExtras)
|
|
37
37
|
return pong();
|
|
38
|
-
let
|
|
38
|
+
let extras;
|
|
39
39
|
try {
|
|
40
|
-
|
|
40
|
+
extras = await getReplyExtras();
|
|
41
41
|
}
|
|
42
42
|
catch (err) {
|
|
43
|
-
log.error('
|
|
43
|
+
log.error('ping reply provider failed - %e', err);
|
|
44
44
|
return pong();
|
|
45
45
|
}
|
|
46
|
-
if (isBusy(
|
|
47
|
-
return
|
|
46
|
+
if (isBusy(extras))
|
|
47
|
+
return extras;
|
|
48
48
|
const response = pong();
|
|
49
|
-
if (
|
|
50
|
-
response.size_estimate =
|
|
51
|
-
response.confidence =
|
|
49
|
+
if (extras.size_estimate !== undefined) {
|
|
50
|
+
response.size_estimate = extras.size_estimate;
|
|
51
|
+
response.confidence = extras.confidence;
|
|
52
52
|
}
|
|
53
|
+
if (extras.snapshot_digest !== undefined)
|
|
54
|
+
response.snapshot_digest = extras.snapshot_digest;
|
|
53
55
|
return response;
|
|
54
56
|
}
|
|
55
57
|
/**
|
|
56
58
|
* Ping `peer` over this network's namespaced protocol. Read-only request (the handler replies
|
|
57
|
-
* without reading)
|
|
58
|
-
*
|
|
59
|
-
*
|
|
59
|
+
* without reading). `dial` defaults to `'always'`; the service's maintenance passes pass
|
|
60
|
+
* `'never'`, because opening a connection is the redial seam's job under its own budget and a
|
|
61
|
+
* 2 s ping budget cannot cover a dial on a slow link. `ok.value.ok` is the peer's own pong flag;
|
|
62
|
+
* rttMs on the outcome. Truncation/undecodable replies are `decode-error` — the service scores
|
|
63
|
+
* them as proof of life.
|
|
60
64
|
*/
|
|
61
65
|
export async function sendPing(node, peer, protocol = PROTOCOL_PING, opts = {}) {
|
|
62
66
|
return rpcRequest(node, peer, protocol, {
|
|
63
67
|
...opts,
|
|
64
|
-
|
|
68
|
+
// A ping reply is tiny and fixed-shape ({ok, ts, size_estimate?, confidence?,
|
|
69
|
+
// snapshot_digest?}) — ~140 bytes at its worst against this cap.
|
|
70
|
+
maxBytes: 1024,
|
|
65
71
|
// The parser replaces the old `Boolean(r.ok)` coercion: a legal value always encodes as a
|
|
66
72
|
// boolean, so the coercion could only ever have hidden a malformed peer. A rejection
|
|
67
73
|
// throws (see `parseOrThrow`) and surfaces as `decode-error` — proof of life, never a
|
|
@@ -48,16 +48,17 @@ export declare const MAYBE_ACT_OVERHEAD_BYTES: number;
|
|
|
48
48
|
* `FretService.snapshot` produces (53-char peer ids, a 32-byte base64url coordinate per sample
|
|
49
49
|
* entry, `metadata` grown until `JSON.stringify(metadata)` hits
|
|
50
50
|
* {@link MAX_SNAPSHOT_METADATA_BYTES_CORE} exactly, 256 bytes reserved for the still-empty `sig`
|
|
51
|
-
* field, and a `hints` array packed to
|
|
51
|
+
* field, a `digest` at the parser's 64-character cap, and a `hints` array packed to
|
|
52
|
+
* {@link MAX_SNAPSHOT_HINT_BYTES_CORE}):
|
|
52
53
|
*
|
|
53
54
|
* 16 successors + 16 predecessors + 8 sample entries (the *merge* caps, which are wider than
|
|
54
|
-
* today's emission caps of 12/12/8)
|
|
55
|
+
* today's emission caps of 12/12/8), plus the 64-char digest ............. 3,447 bytes
|
|
55
56
|
* + 8 KiB metadata (including the `,"metadata":` key) .................. 8,204 bytes
|
|
56
|
-
* = 11,
|
|
57
|
+
* = 11,651 bytes of fixed fields
|
|
57
58
|
* + 48 KiB of hints (the whole array, plus its `,"hints":[]` framing) .... 49,163 bytes
|
|
58
|
-
* = 60,
|
|
59
|
+
* = 60,814 bytes
|
|
59
60
|
*
|
|
60
|
-
* 64 KiB leaves ~4.
|
|
61
|
+
* 64 KiB leaves ~4.6 KiB of headroom over that. Sereus used the same 64 KiB for the same payload.
|
|
61
62
|
*
|
|
62
63
|
* The invariant this constant carries, pinned by `test/rpc.codec-properties.spec.ts`: **the
|
|
63
64
|
* largest legal emission of *any* profile — fixed fields, metadata allowance and hint budget
|
|
@@ -69,12 +70,12 @@ export declare const MAX_NEIGHBORS_BYTES: number;
|
|
|
69
70
|
* application metadata it will carry — not an acceptance limit; acceptance is
|
|
70
71
|
* {@link MAX_NEIGHBORS_BYTES}, one network-wide number. The invariant the emission budgets must
|
|
71
72
|
* keep is `largest emission of any profile <= MAX_NEIGHBORS_BYTES`: at the merge caps the Core
|
|
72
|
-
* fixed fields cost 3,
|
|
73
|
-
* ({@link MAX_SNAPSHOT_HINT_BYTES_CORE}) on top of that at 60,
|
|
73
|
+
* fixed fields cost 3,447 bytes, so this allowance plus them lands at 11,651, and the hint budget
|
|
74
|
+
* ({@link MAX_SNAPSHOT_HINT_BYTES_CORE}) on top of that at 60,814 — under. Over-cap metadata is
|
|
74
75
|
* omitted from the snapshot rather than truncated — see `FretService.snapshot`. */
|
|
75
76
|
export declare const MAX_SNAPSHOT_METADATA_BYTES_CORE: number;
|
|
76
77
|
/** Same, Edge profile: an emission budget, half of Core's because an edge node carries less.
|
|
77
|
-
* Its worst legal emission is 6,
|
|
78
|
+
* Its worst legal emission is 6,377 bytes of fixed fields plus {@link MAX_SNAPSHOT_HINT_BYTES_EDGE}
|
|
78
79
|
* of hints, also under {@link MAX_NEIGHBORS_BYTES} — which is the only bound any budget has to
|
|
79
80
|
* satisfy. Deliberately *not* sized against a per-profile acceptance cap; there is no such thing. */
|
|
80
81
|
export declare const MAX_SNAPSHOT_METADATA_BYTES_EDGE: number;
|
|
@@ -86,15 +87,22 @@ export declare const MAX_SNAPSHOT_METADATA_BYTES_EDGE: number;
|
|
|
86
87
|
* `SelfAddressRecord` omits its own record rather than emit one the receiver would drop.
|
|
87
88
|
*/
|
|
88
89
|
export declare const MAX_ADDRESS_RECORD_CHARS = 2048;
|
|
90
|
+
/**
|
|
91
|
+
* Longest snapshot digest a parser keeps (`NeighborSnapshotV1.digest`,
|
|
92
|
+
* `PingResponseV1.snapshot_digest`). Wider than the 22 characters `snapshotDigest`
|
|
93
|
+
* (`service/near-exchange.ts`) emits so a longer digest can be introduced later without a parser
|
|
94
|
+
* change; a receiver compares digests for equality only, so the width never matters to it.
|
|
95
|
+
*/
|
|
96
|
+
export declare const MAX_SNAPSHOT_DIGEST_CHARS = 64;
|
|
89
97
|
/**
|
|
90
98
|
* Emission budget for the whole `hints` array of a Core snapshot — the sum over hints of
|
|
91
99
|
* `JSON.stringify(hint).length + 1` (the `+ 1` is the separating comma), which is what
|
|
92
100
|
* `FretService.snapshot` counts. Profile-split like the metadata allowance, and for the same
|
|
93
101
|
* reason: it is this node's own choice of how much it forwards, not a limit on what it accepts.
|
|
94
|
-
* Worst case: 11,
|
|
102
|
+
* Worst case: 11,651 (fixed fields) + 49,152 + the field's own framing < {@link MAX_NEIGHBORS_BYTES}.
|
|
95
103
|
*/
|
|
96
104
|
export declare const MAX_SNAPSHOT_HINT_BYTES_CORE: number;
|
|
97
|
-
/** Same, Edge profile: an edge node forwards less. 6,
|
|
105
|
+
/** Same, Edge profile: an edge node forwards less. 6,377 + 16,384 + framing is far under the cap. */
|
|
98
106
|
export declare const MAX_SNAPSHOT_HINT_BYTES_EDGE: number;
|
|
99
107
|
/**
|
|
100
108
|
* Structural validity of an inbound `RouteAndMaybeAct` — everything downstream code touches
|
|
@@ -182,6 +190,7 @@ export declare const parsePingResponse: Parser<{
|
|
|
182
190
|
ok: boolean;
|
|
183
191
|
size_estimate?: number;
|
|
184
192
|
confidence?: number;
|
|
193
|
+
snapshot_digest?: string;
|
|
185
194
|
}>;
|
|
186
195
|
/**
|
|
187
196
|
* A NearAnchor reply. The two id lists are hints, so they truncate and drop bad entries; missing
|
package/dist/src/rpc/validate.js
CHANGED
|
@@ -78,16 +78,17 @@ export const MAYBE_ACT_OVERHEAD_BYTES = 16 * 1024;
|
|
|
78
78
|
* `FretService.snapshot` produces (53-char peer ids, a 32-byte base64url coordinate per sample
|
|
79
79
|
* entry, `metadata` grown until `JSON.stringify(metadata)` hits
|
|
80
80
|
* {@link MAX_SNAPSHOT_METADATA_BYTES_CORE} exactly, 256 bytes reserved for the still-empty `sig`
|
|
81
|
-
* field, and a `hints` array packed to
|
|
81
|
+
* field, a `digest` at the parser's 64-character cap, and a `hints` array packed to
|
|
82
|
+
* {@link MAX_SNAPSHOT_HINT_BYTES_CORE}):
|
|
82
83
|
*
|
|
83
84
|
* 16 successors + 16 predecessors + 8 sample entries (the *merge* caps, which are wider than
|
|
84
|
-
* today's emission caps of 12/12/8)
|
|
85
|
+
* today's emission caps of 12/12/8), plus the 64-char digest ............. 3,447 bytes
|
|
85
86
|
* + 8 KiB metadata (including the `,"metadata":` key) .................. 8,204 bytes
|
|
86
|
-
* = 11,
|
|
87
|
+
* = 11,651 bytes of fixed fields
|
|
87
88
|
* + 48 KiB of hints (the whole array, plus its `,"hints":[]` framing) .... 49,163 bytes
|
|
88
|
-
* = 60,
|
|
89
|
+
* = 60,814 bytes
|
|
89
90
|
*
|
|
90
|
-
* 64 KiB leaves ~4.
|
|
91
|
+
* 64 KiB leaves ~4.6 KiB of headroom over that. Sereus used the same 64 KiB for the same payload.
|
|
91
92
|
*
|
|
92
93
|
* The invariant this constant carries, pinned by `test/rpc.codec-properties.spec.ts`: **the
|
|
93
94
|
* largest legal emission of *any* profile — fixed fields, metadata allowance and hint budget
|
|
@@ -99,12 +100,12 @@ export const MAX_NEIGHBORS_BYTES = 64 * 1024;
|
|
|
99
100
|
* application metadata it will carry — not an acceptance limit; acceptance is
|
|
100
101
|
* {@link MAX_NEIGHBORS_BYTES}, one network-wide number. The invariant the emission budgets must
|
|
101
102
|
* keep is `largest emission of any profile <= MAX_NEIGHBORS_BYTES`: at the merge caps the Core
|
|
102
|
-
* fixed fields cost 3,
|
|
103
|
-
* ({@link MAX_SNAPSHOT_HINT_BYTES_CORE}) on top of that at 60,
|
|
103
|
+
* fixed fields cost 3,447 bytes, so this allowance plus them lands at 11,651, and the hint budget
|
|
104
|
+
* ({@link MAX_SNAPSHOT_HINT_BYTES_CORE}) on top of that at 60,814 — under. Over-cap metadata is
|
|
104
105
|
* omitted from the snapshot rather than truncated — see `FretService.snapshot`. */
|
|
105
106
|
export const MAX_SNAPSHOT_METADATA_BYTES_CORE = 8 * 1024;
|
|
106
107
|
/** Same, Edge profile: an emission budget, half of Core's because an edge node carries less.
|
|
107
|
-
* Its worst legal emission is 6,
|
|
108
|
+
* Its worst legal emission is 6,377 bytes of fixed fields plus {@link MAX_SNAPSHOT_HINT_BYTES_EDGE}
|
|
108
109
|
* of hints, also under {@link MAX_NEIGHBORS_BYTES} — which is the only bound any budget has to
|
|
109
110
|
* satisfy. Deliberately *not* sized against a per-profile acceptance cap; there is no such thing. */
|
|
110
111
|
export const MAX_SNAPSHOT_METADATA_BYTES_EDGE = 4 * 1024;
|
|
@@ -116,15 +117,22 @@ export const MAX_SNAPSHOT_METADATA_BYTES_EDGE = 4 * 1024;
|
|
|
116
117
|
* `SelfAddressRecord` omits its own record rather than emit one the receiver would drop.
|
|
117
118
|
*/
|
|
118
119
|
export const MAX_ADDRESS_RECORD_CHARS = 2048;
|
|
120
|
+
/**
|
|
121
|
+
* Longest snapshot digest a parser keeps (`NeighborSnapshotV1.digest`,
|
|
122
|
+
* `PingResponseV1.snapshot_digest`). Wider than the 22 characters `snapshotDigest`
|
|
123
|
+
* (`service/near-exchange.ts`) emits so a longer digest can be introduced later without a parser
|
|
124
|
+
* change; a receiver compares digests for equality only, so the width never matters to it.
|
|
125
|
+
*/
|
|
126
|
+
export const MAX_SNAPSHOT_DIGEST_CHARS = 64;
|
|
119
127
|
/**
|
|
120
128
|
* Emission budget for the whole `hints` array of a Core snapshot — the sum over hints of
|
|
121
129
|
* `JSON.stringify(hint).length + 1` (the `+ 1` is the separating comma), which is what
|
|
122
130
|
* `FretService.snapshot` counts. Profile-split like the metadata allowance, and for the same
|
|
123
131
|
* reason: it is this node's own choice of how much it forwards, not a limit on what it accepts.
|
|
124
|
-
* Worst case: 11,
|
|
132
|
+
* Worst case: 11,651 (fixed fields) + 49,152 + the field's own framing < {@link MAX_NEIGHBORS_BYTES}.
|
|
125
133
|
*/
|
|
126
134
|
export const MAX_SNAPSHOT_HINT_BYTES_CORE = 48 * 1024;
|
|
127
|
-
/** Same, Edge profile: an edge node forwards less. 6,
|
|
135
|
+
/** Same, Edge profile: an edge node forwards less. 6,377 + 16,384 + framing is far under the cap. */
|
|
128
136
|
export const MAX_SNAPSHOT_HINT_BYTES_EDGE = 16 * 1024;
|
|
129
137
|
/**
|
|
130
138
|
* Structural validity of an inbound `RouteAndMaybeAct` — everything downstream code touches
|
|
@@ -275,10 +283,25 @@ export function makeSnapshotParser(caps) {
|
|
|
275
283
|
delete out.hints;
|
|
276
284
|
else
|
|
277
285
|
out.hints = hints;
|
|
286
|
+
// Advisory like the numerics: a usable digest lets the receiver skip its next fetch, an
|
|
287
|
+
// unusable one costs it nothing but that.
|
|
288
|
+
const digest = snapshotDigestOr(msg.digest, undefined);
|
|
289
|
+
if (digest === undefined)
|
|
290
|
+
delete out.digest;
|
|
291
|
+
else
|
|
292
|
+
out.digest = digest;
|
|
278
293
|
// `sig` is deliberately unchecked: message signing is unimplemented, so nothing reads it.
|
|
279
294
|
return out;
|
|
280
295
|
};
|
|
281
296
|
}
|
|
297
|
+
/**
|
|
298
|
+
* The value when it is a snapshot digest a receiver can compare — a non-empty string of at most
|
|
299
|
+
* {@link MAX_SNAPSHOT_DIGEST_CHARS} characters — else `fallback`. Width-checked only: the receiver
|
|
300
|
+
* tests equality against a digest it cached from the same peer, so no encoding rule is enforced.
|
|
301
|
+
*/
|
|
302
|
+
function snapshotDigestOr(value, fallback) {
|
|
303
|
+
return typeof value === 'string' && value.length > 0 && value.length <= MAX_SNAPSHOT_DIGEST_CHARS ? value : fallback;
|
|
304
|
+
}
|
|
282
305
|
/**
|
|
283
306
|
* The address hints, vetted per entry against the snapshot's named set — see
|
|
284
307
|
* {@link makeSnapshotParser}. An entry survives when it is an object whose `id` is a named id not
|
|
@@ -416,6 +439,10 @@ export const parsePingResponse = (msg) => {
|
|
|
416
439
|
const confidence = finiteNumberInRangeOr(msg.confidence, 0, 1, undefined);
|
|
417
440
|
if (confidence !== undefined)
|
|
418
441
|
out.confidence = confidence;
|
|
442
|
+
// Same drop-not-reject rule as the snapshot's `digest`, which this fingerprints.
|
|
443
|
+
const snapshotDigest = snapshotDigestOr(msg.snapshot_digest, undefined);
|
|
444
|
+
if (snapshotDigest !== undefined)
|
|
445
|
+
out.snapshot_digest = snapshotDigest;
|
|
419
446
|
return out;
|
|
420
447
|
};
|
|
421
448
|
/**
|