@optimystic/db-p2p 0.28.0 → 1.0.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/cluster/block-transfer-service.d.ts +0 -10
- package/dist/src/cluster/block-transfer-service.d.ts.map +1 -1
- package/dist/src/cluster/block-transfer-service.js +4 -2
- package/dist/src/cluster/block-transfer-service.js.map +1 -1
- package/dist/src/cluster/cluster-policy.d.ts +101 -18
- package/dist/src/cluster/cluster-policy.d.ts.map +1 -1
- package/dist/src/cluster/cluster-policy.js +153 -30
- package/dist/src/cluster/cluster-policy.js.map +1 -1
- package/dist/src/cluster/cluster-repo.d.ts +75 -16
- package/dist/src/cluster/cluster-repo.d.ts.map +1 -1
- package/dist/src/cluster/cluster-repo.js +195 -24
- package/dist/src/cluster/cluster-repo.js.map +1 -1
- package/dist/src/cluster/quorum-restore.d.ts +4 -2
- package/dist/src/cluster/quorum-restore.d.ts.map +1 -1
- package/dist/src/cluster/quorum-restore.js +4 -2
- package/dist/src/cluster/quorum-restore.js.map +1 -1
- package/dist/src/cluster/service.d.ts +12 -9
- package/dist/src/cluster/service.d.ts.map +1 -1
- package/dist/src/cluster/service.js +6 -6
- package/dist/src/cluster/service.js.map +1 -1
- package/dist/src/dispute/service.d.ts +1 -4
- package/dist/src/dispute/service.d.ts.map +1 -1
- package/dist/src/dispute/service.js +2 -1
- package/dist/src/dispute/service.js.map +1 -1
- package/dist/src/libp2p-key-network.d.ts +8 -2
- package/dist/src/libp2p-key-network.d.ts.map +1 -1
- package/dist/src/libp2p-key-network.js +8 -2
- package/dist/src/libp2p-key-network.js.map +1 -1
- package/dist/src/libp2p-node-base.d.ts.map +1 -1
- package/dist/src/libp2p-node-base.js +22 -19
- package/dist/src/libp2p-node-base.js.map +1 -1
- package/dist/src/logger.d.ts +28 -1
- package/dist/src/logger.d.ts.map +1 -1
- package/dist/src/logger.js +143 -1
- package/dist/src/logger.js.map +1 -1
- package/dist/src/network/network-manager-service.d.ts +1 -4
- package/dist/src/network/network-manager-service.d.ts.map +1 -1
- package/dist/src/network/network-manager-service.js +2 -1
- package/dist/src/network/network-manager-service.js.map +1 -1
- package/dist/src/repo/cluster-coordinator.d.ts +16 -1
- package/dist/src/repo/cluster-coordinator.d.ts.map +1 -1
- package/dist/src/repo/cluster-coordinator.js +66 -7
- package/dist/src/repo/cluster-coordinator.js.map +1 -1
- package/dist/src/repo/coordinator-repo.d.ts +188 -14
- package/dist/src/repo/coordinator-repo.d.ts.map +1 -1
- package/dist/src/repo/coordinator-repo.js +714 -110
- package/dist/src/repo/coordinator-repo.js.map +1 -1
- package/dist/src/repo/service.d.ts +9 -6
- package/dist/src/repo/service.d.ts.map +1 -1
- package/dist/src/repo/service.js +4 -5
- package/dist/src/repo/service.js.map +1 -1
- package/dist/src/storage/storage-repo.d.ts +6 -0
- package/dist/src/storage/storage-repo.d.ts.map +1 -1
- package/dist/src/storage/storage-repo.js +62 -2
- package/dist/src/storage/storage-repo.js.map +1 -1
- package/dist/src/sync/service.d.ts +1 -2
- package/dist/src/sync/service.d.ts.map +1 -1
- package/dist/src/sync/service.js +2 -1
- package/dist/src/sync/service.js.map +1 -1
- package/dist/src/testing/mesh-harness.d.ts +7 -1
- package/dist/src/testing/mesh-harness.d.ts.map +1 -1
- package/dist/src/testing/mesh-harness.js +2 -1
- package/dist/src/testing/mesh-harness.js.map +1 -1
- package/package.json +2 -2
- package/readme.md +19 -0
- package/src/cluster/block-transfer-service.ts +4 -8
- package/src/cluster/cluster-policy.ts +196 -36
- package/src/cluster/cluster-repo.ts +2662 -2488
- package/src/cluster/quorum-restore.ts +4 -2
- package/src/cluster/service.ts +14 -9
- package/src/dispute/service.ts +3 -3
- package/src/libp2p-key-network.ts +8 -2
- package/src/libp2p-node-base.ts +22 -19
- package/src/logger.ts +196 -2
- package/src/network/network-manager-service.ts +414 -414
- package/src/protocol-client.ts +196 -196
- package/src/repo/cluster-coordinator.ts +1113 -1039
- package/src/repo/coordinator-repo.ts +2675 -1937
- package/src/repo/service.ts +12 -9
- package/src/storage/storage-repo.ts +65 -2
- package/src/sync/service.ts +3 -5
- package/src/testing/mesh-harness.ts +8 -1
|
@@ -117,8 +117,10 @@ export function quorumSize(
|
|
|
117
117
|
*
|
|
118
118
|
* An unconfigured node resolves this to its `clusterSize` (default 10), so the floor of two binds and
|
|
119
119
|
* a shrunken view gains nothing. The escape hatch for a real two-node deployment is one explicit
|
|
120
|
-
* operator declaration
|
|
121
|
-
*
|
|
120
|
+
* operator declaration, any of three: `clusterPolicy.repairCorroborationClusterSize: 2`, which moves
|
|
121
|
+
* only this yardstick; `clusterPolicy.assumedClusterSize: 2`, which also raises the membership
|
|
122
|
+
* admission gate's low-confidence write floor; or an honest `clusterSize: 2`. Only the last drops the
|
|
123
|
+
* replication factor.
|
|
122
124
|
*
|
|
123
125
|
* Shared by both restoration paths so the two can never drift apart on the rule that decides how
|
|
124
126
|
* much trust a lone peer gets.
|
package/src/cluster/service.ts
CHANGED
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
import { pipe } from 'it-pipe';
|
|
2
2
|
import { decode as lpDecode, encode as lpEncode } from 'it-length-prefixed';
|
|
3
3
|
import { peerIdFromString } from '@libp2p/peer-id';
|
|
4
|
-
import type { Startable,
|
|
4
|
+
import type { Startable, Stream, Connection, StreamHandler, PeerId } from '@libp2p/interface';
|
|
5
5
|
import type { ICluster, ClusterRecord } from '@optimystic/db-core';
|
|
6
6
|
import { encodePeers, type RedirectPayload } from '../repo/redirect.js';
|
|
7
7
|
import { toClusterErrorEnvelope } from './cluster-error.js';
|
|
8
8
|
import { mergeRecordPeerAddresses, publishableAddrsForPeer, type AddressLog, type DirectionalConnection } from '../peer-address-book.js';
|
|
9
9
|
import { MAX_CONTROL_MESSAGE_BYTES } from '../protocol-limits.js';
|
|
10
10
|
import type { Uint8ArrayList } from 'uint8arraylist';
|
|
11
|
-
import { createLogger } from '../logger.js';
|
|
11
|
+
import { createLogger, type Logger } from '../logger.js';
|
|
12
12
|
import { createInboundStreamAuthorization, type InboundStreamAuthorization, type InboundStreamAuthorizationInit } from '../inbound-authorization.js';
|
|
13
13
|
import { registerProtocolHandler } from '../network/register-protocol-handler.js';
|
|
14
14
|
|
|
15
15
|
interface BaseComponents {
|
|
16
|
-
logger: { forComponent: (name: string) => Logger },
|
|
17
16
|
registrar: {
|
|
18
17
|
handle: (protocol: string, handler: StreamHandler, options: any) => Promise<void>,
|
|
19
18
|
unhandle: (protocol: string) => Promise<void>
|
|
@@ -49,6 +48,12 @@ export interface ClusterServiceInit extends InboundStreamAuthorizationInit {
|
|
|
49
48
|
protocolPrefix?: string,
|
|
50
49
|
maxInboundStreams?: number,
|
|
51
50
|
maxOutboundStreams?: number,
|
|
51
|
+
/**
|
|
52
|
+
* Sub-namespace this service logs under, i.e. the `<x>` in `optimystic:db-p2p:<x>`.
|
|
53
|
+
* NOT a full namespace: an embedder cannot use this to move the service's lines outside the
|
|
54
|
+
* `optimystic:db-p2p:*` tree `docs/debugging.md` tells operators to filter on.
|
|
55
|
+
* Default: `cluster-service`.
|
|
56
|
+
*/
|
|
52
57
|
logPrefix?: string,
|
|
53
58
|
/**
|
|
54
59
|
* Responsibility K - the replica set size for determining cluster membership.
|
|
@@ -74,11 +79,11 @@ export class ClusterService implements Startable {
|
|
|
74
79
|
private readonly maxOutboundStreams: number;
|
|
75
80
|
private readonly log: Logger;
|
|
76
81
|
/**
|
|
77
|
-
* Sink for this service's `peer-address-book:*` lines. Deliberately NOT `this.log.error
|
|
78
|
-
*
|
|
79
|
-
* `
|
|
80
|
-
*
|
|
81
|
-
* family, one namespace
|
|
82
|
+
* Sink for this service's `peer-address-book:*` lines. Deliberately NOT `this.log.error`: the
|
|
83
|
+
* `peer-address-book:*` tag family is emitted from several unrelated call sites (this service,
|
|
84
|
+
* `ClusterClient`, `Libp2pKeyPeerNetwork`) and must be filterable as ONE namespace, rather than
|
|
85
|
+
* scattered across whichever service happened to be the ingress point. That scattering is what
|
|
86
|
+
* gotchoices/Optimystic#12 read as proof the mechanism never ran. One tag family, one namespace.
|
|
82
87
|
*/
|
|
83
88
|
private readonly addressLog: AddressLog;
|
|
84
89
|
private readonly cluster: ICluster;
|
|
@@ -94,7 +99,7 @@ export class ClusterService implements Startable {
|
|
|
94
99
|
this.protocol = init.protocol ?? (init.protocolPrefix ?? '/db-p2p') + '/cluster/1.0.0';
|
|
95
100
|
this.maxInboundStreams = init.maxInboundStreams ?? 32;
|
|
96
101
|
this.maxOutboundStreams = init.maxOutboundStreams ?? 64;
|
|
97
|
-
this.log =
|
|
102
|
+
this.log = createLogger(init.logPrefix ?? 'cluster-service');
|
|
98
103
|
this.addressLog = createLogger('peer-address-book', components.peerId?.toString());
|
|
99
104
|
this.cluster = components.cluster;
|
|
100
105
|
this.running = false;
|
package/src/dispute/service.ts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { pipe } from 'it-pipe';
|
|
2
2
|
import { decode as lpDecode, encode as lpEncode } from 'it-length-prefixed';
|
|
3
|
-
import type { Startable,
|
|
3
|
+
import type { Startable, Stream, Connection, StreamHandler } from '@libp2p/interface';
|
|
4
4
|
import type { Uint8ArrayList } from 'uint8arraylist';
|
|
5
5
|
import { MAX_CONTROL_MESSAGE_BYTES } from '../protocol-limits.js';
|
|
6
6
|
import type { DisputeMessage } from './types.js';
|
|
7
7
|
import type { DisputeService } from './dispute-service.js';
|
|
8
8
|
import { registerProtocolHandler } from '../network/register-protocol-handler.js';
|
|
9
|
+
import { createLogger, type Logger } from '../logger.js';
|
|
9
10
|
|
|
10
11
|
interface BaseComponents {
|
|
11
|
-
logger: { forComponent: (name: string) => Logger };
|
|
12
12
|
registrar: {
|
|
13
13
|
handle: (protocol: string, handler: StreamHandler, options: any) => Promise<void>;
|
|
14
14
|
unhandle: (protocol: string) => Promise<void>;
|
|
@@ -48,7 +48,7 @@ export class DisputeProtocolService implements Startable {
|
|
|
48
48
|
this.protocol = init.protocol ?? (init.protocolPrefix ?? '/db-p2p') + '/dispute/1.0.0';
|
|
49
49
|
this.maxInboundStreams = init.maxInboundStreams ?? 16;
|
|
50
50
|
this.maxOutboundStreams = init.maxOutboundStreams ?? 32;
|
|
51
|
-
this.log =
|
|
51
|
+
this.log = createLogger('dispute-protocol');
|
|
52
52
|
this.disputeService = components.disputeService;
|
|
53
53
|
this.running = false;
|
|
54
54
|
}
|
|
@@ -304,8 +304,14 @@ export class Libp2pKeyPeerNetwork implements IKeyNetwork, IPeerNetwork {
|
|
|
304
304
|
/**
|
|
305
305
|
* This node's own dialable addresses, as strings, memoized between address changes.
|
|
306
306
|
*
|
|
307
|
-
* `libp2p.getMultiaddrs()` is NOT cheap
|
|
308
|
-
* `
|
|
307
|
+
* `libp2p.getMultiaddrs()` is NOT cheap **when this node has a wildcard listen address**:
|
|
308
|
+
* expanding `0.0.0.0` across the host's interfaces goes through `os.networkInterfaces()`, a
|
|
309
|
+
* full NIC sweep measured at **3.19 ms of a 3.49 ms call**. Measured per configuration on one
|
|
310
|
+
* host: 4.44 ms/call with a TCP listener, **0.001 ms/call with no listen addrs at all** (an
|
|
311
|
+
* edge/mobile client dialling out over websockets — nothing to expand, empty address list).
|
|
312
|
+
* So the win here is real for listening peers (service and reference nodes, which commit too)
|
|
313
|
+
* and near-zero for a dial-only client; do not quote the headline figure at a profile that
|
|
314
|
+
* never pays it. Every
|
|
309
315
|
* `findCluster` builds a cluster record containing self's addresses, and every commit calls
|
|
310
316
|
* `findCluster` through `getClusterPeerIds` — so a cold `apply schema` paid one NIC sweep per
|
|
311
317
|
* commit. On a solo node with zero peers that was ~13.8 ms per call and **49% of the whole
|
package/src/libp2p-node-base.ts
CHANGED
|
@@ -508,8 +508,10 @@ export async function createLibp2pNodeBase(
|
|
|
508
508
|
: options.onCommitCertificate;
|
|
509
509
|
|
|
510
510
|
// Every cluster-policy default lives in `cluster/cluster-policy.ts` — including WHY the admission
|
|
511
|
-
// gate and the repair corroboration floor resolve
|
|
512
|
-
//
|
|
511
|
+
// gate and the repair corroboration floor resolve to different values when nothing is declared,
|
|
512
|
+
// and why they take TWO operator fields (`clusterPolicy.assumedClusterSize`, which sets both, and
|
|
513
|
+
// `clusterPolicy.repairCorroborationClusterSize`, which sets the repair yardstick alone so a host
|
|
514
|
+
// can tighten repair without also raising the low-confidence write floor). Resolved ONCE, here,
|
|
513
515
|
// before anything that reads a cluster size is constructed: `networkManagerService` below,
|
|
514
516
|
// `Libp2pKeyPeerNetwork`, and the spread-on-churn monitor init must all read `consensusConfig.clusterSize`
|
|
515
517
|
// rather than `options.clusterSize` directly, or they can each apply their own fallback default and
|
|
@@ -608,7 +610,6 @@ export async function createLibp2pNodeBase(
|
|
|
608
610
|
...inboundAuthorization
|
|
609
611
|
});
|
|
610
612
|
return serviceFactory({
|
|
611
|
-
logger: components.logger,
|
|
612
613
|
registrar: components.registrar,
|
|
613
614
|
cluster: clusterProxy,
|
|
614
615
|
// Identity for membership scoping on the update path. peerId is a core
|
|
@@ -652,7 +653,6 @@ export async function createLibp2pNodeBase(
|
|
|
652
653
|
// (getCluster(encode(blockKey)) → hashKey(encode(...))), matching the
|
|
653
654
|
// coordinator's findCluster(encode(blockId)) — same cohort, no spurious redirect.
|
|
654
655
|
return serviceFactory({
|
|
655
|
-
logger: components.logger,
|
|
656
656
|
registrar: components.registrar,
|
|
657
657
|
repo: repoProxy
|
|
658
658
|
});
|
|
@@ -664,7 +664,6 @@ export async function createLibp2pNodeBase(
|
|
|
664
664
|
...inboundAuthorization
|
|
665
665
|
});
|
|
666
666
|
return serviceFactory({
|
|
667
|
-
logger: components.logger,
|
|
668
667
|
registrar: components.registrar,
|
|
669
668
|
repo: repoProxy
|
|
670
669
|
});
|
|
@@ -689,9 +688,7 @@ export async function createLibp2pNodeBase(
|
|
|
689
688
|
// Read from the SAME resolved `consensusConfig` the member and coordinator read (whose
|
|
690
689
|
// coupling `assertSuperMajorityCoupling` below already asserts) — a third copy resolving
|
|
691
690
|
// its own default would defeat that.
|
|
692
|
-
superMajorityThreshold: consensusConfig.superMajorityThreshold
|
|
693
|
-
// So this service's authorization denials reach the same error sink as the other three.
|
|
694
|
-
logger: components.logger
|
|
691
|
+
superMajorityThreshold: consensusConfig.superMajorityThreshold
|
|
695
692
|
});
|
|
696
693
|
},
|
|
697
694
|
|
|
@@ -1082,7 +1079,7 @@ export async function createLibp2pNodeBase(
|
|
|
1082
1079
|
// Spread is a resilience optimization, not a correctness requirement - a wiring
|
|
1083
1080
|
// failure (e.g. FRET briefly unavailable) must NOT hard-fail node startup, unlike the
|
|
1084
1081
|
// operator-opted-in cohortTopic block. Log and continue with spread inert.
|
|
1085
|
-
(
|
|
1082
|
+
wiringLog('spread-on-churn init failed: %o', err);
|
|
1086
1083
|
}
|
|
1087
1084
|
}
|
|
1088
1085
|
|
|
@@ -1108,7 +1105,13 @@ export async function createLibp2pNodeBase(
|
|
|
1108
1105
|
// Initialize Arachnode ring membership and restoration
|
|
1109
1106
|
const enableArachnode = options.arachnode?.enableRingZulu ?? true;
|
|
1110
1107
|
if (enableArachnode) {
|
|
1111
|
-
|
|
1108
|
+
// A child of the file's own `node-wiring` channel (same nesting convention as
|
|
1109
|
+
// `storage:restoration` and `network:get-manager`): these lines explain a half-started
|
|
1110
|
+
// node, so `optimystic:db-p2p:node-wiring*` shows them next to the rest of the wiring
|
|
1111
|
+
// story, while the child name still lets ring/rebalance chatter be filtered on its own.
|
|
1112
|
+
// Unconditional — the previous `(node as any).logger?.forComponent?.(...)` reach-through
|
|
1113
|
+
// silently dropped every one of these lines when the node exposed no logger.
|
|
1114
|
+
const log = createLogger('node-wiring:arachnode');
|
|
1112
1115
|
const fret = (node as any).services?.fret as any;
|
|
1113
1116
|
|
|
1114
1117
|
if (fret) {
|
|
@@ -1150,7 +1153,7 @@ export async function createLibp2pNodeBase(
|
|
|
1150
1153
|
const arachnodeInfo = await ringSelector.createArachnodeInfo(peerId);
|
|
1151
1154
|
fretAdapter.setArachnodeInfo(arachnodeInfo);
|
|
1152
1155
|
|
|
1153
|
-
log
|
|
1156
|
+
log('Announced Arachnode membership: Ring %d', arachnodeInfo.ringDepth);
|
|
1154
1157
|
|
|
1155
1158
|
// Setup restoration coordinator with FRET adapter
|
|
1156
1159
|
const restorationCoordinatorV2 = new RestorationCoordinator(
|
|
@@ -1253,7 +1256,7 @@ export async function createLibp2pNodeBase(
|
|
|
1253
1256
|
}
|
|
1254
1257
|
if (result.underReplicated.length > 0) {
|
|
1255
1258
|
const growthDiag = rebalanceMonitor.getGrowthDiagnostics();
|
|
1256
|
-
log
|
|
1259
|
+
log('cohort-growth: %d of %d grown blocks not confirmed on new peers this pass ' +
|
|
1257
1260
|
'(awaiting-confirmation=%d given-up-pairs=%d)',
|
|
1258
1261
|
result.underReplicated.length, event.grown.size,
|
|
1259
1262
|
growthDiag.blocksAwaitingConfirmation, growthDiag.abandonedPairs);
|
|
@@ -1267,7 +1270,7 @@ export async function createLibp2pNodeBase(
|
|
|
1267
1270
|
// and a loud unbounded retry is the right way to surface one — silently abandoning
|
|
1268
1271
|
// the block would hide it and leave the block singly held. Revisit if a legitimate
|
|
1269
1272
|
// recoverable condition is ever allowed to throw out of the reaction.
|
|
1270
|
-
log
|
|
1273
|
+
log('rebalance reaction failed: %o', err);
|
|
1271
1274
|
});
|
|
1272
1275
|
});
|
|
1273
1276
|
|
|
@@ -1320,7 +1323,7 @@ export async function createLibp2pNodeBase(
|
|
|
1320
1323
|
} catch (err) {
|
|
1321
1324
|
// Rebalance is a resilience optimization, not a correctness requirement - a wiring
|
|
1322
1325
|
// failure (e.g. FRET briefly unavailable) must NOT hard-fail node startup.
|
|
1323
|
-
log
|
|
1326
|
+
log('rebalance wiring init failed: %o', err);
|
|
1324
1327
|
}
|
|
1325
1328
|
}
|
|
1326
1329
|
|
|
@@ -1337,15 +1340,15 @@ export async function createLibp2pNodeBase(
|
|
|
1337
1340
|
if (!ringShift) return;
|
|
1338
1341
|
const transition = await ringSelector.shouldTransition();
|
|
1339
1342
|
if (transition.shouldMove && transition.direction && transition.newRingDepth !== undefined) {
|
|
1340
|
-
log
|
|
1343
|
+
log('Ring transition needed: moving %s to Ring %d', transition.direction, transition.newRingDepth);
|
|
1341
1344
|
try {
|
|
1342
1345
|
const outcome = await ringShift.executeShift({
|
|
1343
1346
|
direction: transition.direction,
|
|
1344
1347
|
newRingDepth: transition.newRingDepth
|
|
1345
1348
|
});
|
|
1346
|
-
log
|
|
1349
|
+
log('Ring shift outcome: %o', outcome);
|
|
1347
1350
|
} catch (err) {
|
|
1348
|
-
log
|
|
1351
|
+
log('Ring shift failed: %o', err);
|
|
1349
1352
|
} finally {
|
|
1350
1353
|
// Measure the minimum dwell from the SETTLED shift (completed or rolled back), not
|
|
1351
1354
|
// just the trigger stamped inside shouldTransition (docs/arachnode-ring-handoff.md §1.3).
|
|
@@ -1361,7 +1364,7 @@ export async function createLibp2pNodeBase(
|
|
|
1361
1364
|
await originalStop();
|
|
1362
1365
|
};
|
|
1363
1366
|
} else {
|
|
1364
|
-
log
|
|
1367
|
+
log('FRET service not available, Arachnode disabled');
|
|
1365
1368
|
}
|
|
1366
1369
|
}
|
|
1367
1370
|
|
|
@@ -1393,7 +1396,7 @@ export async function createLibp2pNodeBase(
|
|
|
1393
1396
|
await previousStop();
|
|
1394
1397
|
};
|
|
1395
1398
|
void seedOwnedBlocksFromStorage(rawStorage, ownedBlocks, () => seedStopping)
|
|
1396
|
-
.catch((err) =>
|
|
1399
|
+
.catch((err) => { wiringLog('owned-block-seed seed failed: %o', err); });
|
|
1397
1400
|
}
|
|
1398
1401
|
|
|
1399
1402
|
// [dispute-subsystem-dormant] The DisputeService object is constructed below so tests and
|
package/src/logger.ts
CHANGED
|
@@ -1,7 +1,181 @@
|
|
|
1
|
+
import { base32 } from 'multiformats/bases/base32'
|
|
2
|
+
import { base58btc } from 'multiformats/bases/base58'
|
|
3
|
+
import { base64 } from 'multiformats/bases/base64'
|
|
1
4
|
import debug from 'debug'
|
|
5
|
+
import type { PeerId } from '@libp2p/interface'
|
|
6
|
+
import type { Multiaddr } from '@multiformats/multiaddr'
|
|
7
|
+
import type { CID } from 'multiformats/cid'
|
|
2
8
|
|
|
3
9
|
const BASE_NAMESPACE = 'optimystic:db-p2p'
|
|
4
10
|
|
|
11
|
+
/*
|
|
12
|
+
* Format specifiers, ported from `@libp2p/logger`'s `src/index.ts` (MIT, same license as this
|
|
13
|
+
* repo). Call sites in this package log lines like `'error handling X from %p - %e'`; those used
|
|
14
|
+
* to reach libp2p's own logger, which registers these. Without the port they would print the
|
|
15
|
+
* literal text `%p` / `%e`.
|
|
16
|
+
*
|
|
17
|
+
* NOTE: these are registered on the `debug` module instance THIS file imports. libp2p's own
|
|
18
|
+
* loggers use `weald` (via @libp2p/logger) and carry their own copy — the two registries are
|
|
19
|
+
* independent, and enabling one from test code does not enable the other.
|
|
20
|
+
*
|
|
21
|
+
* NOTE: `debug.formatters` is process-wide, and the other six packages' `createLogger` factories
|
|
22
|
+
* import the same `debug` module — so importing this file makes `%p`/`%e`/… work from their
|
|
23
|
+
* loggers too. Harmless today (no other package registers any specifier, and none of these letters
|
|
24
|
+
* is a `util.format` specifier except `%c`, whose Node meaning is a no-op that swallows its
|
|
25
|
+
* argument). If a second package ever registers one of these letters, last import wins silently;
|
|
26
|
+
* move the registry somewhere both can share rather than racing on it.
|
|
27
|
+
*
|
|
28
|
+
* NOTE: only `%e` is guaranteed not to throw — its call sites take a `catch`-bound `unknown`. The
|
|
29
|
+
* other six call `.toString()` / an encoder on whatever they are handed, so a caller that passes
|
|
30
|
+
* the wrong type turns a log line into an exception. That matches upstream; tighten them if a
|
|
31
|
+
* call site ever formats a value it did not construct.
|
|
32
|
+
*
|
|
33
|
+
* `%k` (`interface-datastore`'s `Key`) is deliberately NOT ported: `interface-datastore` is not a
|
|
34
|
+
* declared dependency of this package and no call site formats one. Add it if that changes.
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
// Add a formatter for converting to a base58 string
|
|
38
|
+
debug.formatters['b'] = (v?: Uint8Array): string => {
|
|
39
|
+
return v == null ? 'undefined' : base58btc.baseEncode(v)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Add a formatter for converting to a base32 string
|
|
43
|
+
debug.formatters['t'] = (v?: Uint8Array): string => {
|
|
44
|
+
return v == null ? 'undefined' : base32.baseEncode(v)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Add a formatter for converting to a base64 string
|
|
48
|
+
debug.formatters['m'] = (v?: Uint8Array): string => {
|
|
49
|
+
return v == null ? 'undefined' : base64.baseEncode(v)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Add a formatter for stringifying peer ids
|
|
53
|
+
debug.formatters['p'] = (v?: PeerId): string => {
|
|
54
|
+
return v == null ? 'undefined' : v.toString()
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Add a formatter for stringifying CIDs
|
|
58
|
+
debug.formatters['c'] = (v?: CID): string => {
|
|
59
|
+
return v == null ? 'undefined' : v.toString()
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Add a formatter for stringifying Multiaddrs
|
|
63
|
+
debug.formatters['a'] = (v?: Multiaddr): string => {
|
|
64
|
+
return v == null ? 'undefined' : v.toString()
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function notEmpty(str?: string): string | undefined {
|
|
68
|
+
if (str == null) {
|
|
69
|
+
return
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
str = str.trim()
|
|
73
|
+
|
|
74
|
+
if (str.length === 0) {
|
|
75
|
+
return
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return str
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function formatError(v: Error, indent = ''): string {
|
|
82
|
+
const message = notEmpty(v.message)
|
|
83
|
+
const stack = notEmpty(v.stack)
|
|
84
|
+
|
|
85
|
+
// some browser errors (mostly from Firefox) have no message or no stack,
|
|
86
|
+
// sometimes both, sometimes neither. Sometimes the message is in the stack,
|
|
87
|
+
// sometimes it isn't so try to do *something* useful
|
|
88
|
+
if (message != null && stack != null) {
|
|
89
|
+
if (stack.includes(message)) {
|
|
90
|
+
return `${stack.split('\n').join(`\n${indent}`)}`
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return `${message}\n${indent}${stack.split('\n').join(`\n${indent}`)}`
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (stack != null) {
|
|
97
|
+
return `${stack.split('\n').join(`\n${indent}`)}`
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (message != null) {
|
|
101
|
+
return `${message}`
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return `${v.toString()}`
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function isAggregateError(err?: any): err is AggregateError {
|
|
108
|
+
return err instanceof AggregateError || (err?.name === 'AggregateError' && Array.isArray(err.errors))
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function printError(err: Error, indent = ''): string {
|
|
112
|
+
if (isAggregateError(err)) {
|
|
113
|
+
let output = formatError(err, indent)
|
|
114
|
+
|
|
115
|
+
if (err.errors.length > 0) {
|
|
116
|
+
indent = `${indent} `
|
|
117
|
+
|
|
118
|
+
output += `\n${indent}${err.errors
|
|
119
|
+
.map(err => `${printError(err, `${indent}`)}`)
|
|
120
|
+
.join(`\n${indent}`)
|
|
121
|
+
}`
|
|
122
|
+
} else {
|
|
123
|
+
output += `\n${indent}[Error list was empty]`
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return output.trim()
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return formatError(err, indent)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Add a formatter for stringifying Errors.
|
|
133
|
+
//
|
|
134
|
+
// A call site can hand `%e` anything — `catch (err)` binds `unknown`, and this package's `%e` sites
|
|
135
|
+
// are all in catch blocks. This must never throw: a logger that throws turns a caught error into an
|
|
136
|
+
// uncaught one at exactly the site that was trying to report it.
|
|
137
|
+
//
|
|
138
|
+
// The try/catch is a deliberate deviation from the upstream `@libp2p/logger` port. `formatError`'s
|
|
139
|
+
// last resort is `${v.toString()}`, which raises for the two values that have no usable primitive
|
|
140
|
+
// conversion — a null-prototype object (`throw Object.create(null)`) and a symbol. Upstream has the
|
|
141
|
+
// same hole; here the stated contract is "never throw", so the fallback is explicit.
|
|
142
|
+
debug.formatters['e'] = (v?: Error): string => {
|
|
143
|
+
if (v == null) {
|
|
144
|
+
return 'undefined'
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
try {
|
|
148
|
+
return printError(v)
|
|
149
|
+
} catch {
|
|
150
|
+
return '[unformattable error]'
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* A `debug` channel plus the severity sub-channels libp2p's own `Logger` exposes, so a call site
|
|
156
|
+
* can be moved between the two factories without changing what it calls.
|
|
157
|
+
*
|
|
158
|
+
* `error` and `trace` are ordinary child namespaces (`<namespace>:error`), so a wildcard filter
|
|
159
|
+
* the operator already uses — `optimystic:db-p2p:*` — keeps matching them. An EXACT-match filter
|
|
160
|
+
* (`DEBUG=optimystic:db-p2p:repo-service`) does not; that is the same caveat the peer-id suffix
|
|
161
|
+
* already carries, and `docs/debugging.md` already documents it.
|
|
162
|
+
*
|
|
163
|
+
* Unlike libp2p we do NOT conditionally stub out `trace`: libp2p builds a no-op unless a `:trace`
|
|
164
|
+
* namespace is explicitly enabled, but a disabled `debug` channel is already near-free, and the
|
|
165
|
+
* conditional version reads its enablement once at construction — wrong for anything built before
|
|
166
|
+
* `DEBUG` is set. `newScope` is deliberately omitted; `createLogger('parent:child')` says the same
|
|
167
|
+
* thing and nothing calls it.
|
|
168
|
+
*
|
|
169
|
+
* NOTE: because `trace` is a real channel, the wildcard `optimystic:db-p2p:*` that
|
|
170
|
+
* `docs/debugging.md` tells operators to set will also show trace lines. Nothing calls `.trace`
|
|
171
|
+
* yet, so that is currently free; if trace logging ever becomes voluminous, give the docs a
|
|
172
|
+
* narrower default filter rather than stubbing the channel back out.
|
|
173
|
+
*/
|
|
174
|
+
export interface Logger extends debug.Debugger {
|
|
175
|
+
error: debug.Debugger
|
|
176
|
+
trace: debug.Debugger
|
|
177
|
+
}
|
|
178
|
+
|
|
5
179
|
/**
|
|
6
180
|
* Build a `debug` logger under `optimystic:db-p2p:<subNamespace>`, optionally suffixed with the
|
|
7
181
|
* owning node's peer id (`:<first 12 chars>`) so lines from several nodes sharing one process —
|
|
@@ -17,10 +191,30 @@ const BASE_NAMESPACE = 'optimystic:db-p2p'
|
|
|
17
191
|
* the three `peer-address-book` sinks); the package's other ~30 `createLogger` call sites
|
|
18
192
|
* still log under a flat namespace. Thread a peer id through any of them if a future diagnosis
|
|
19
193
|
* needs per-node attribution from that subsystem — the mechanism is already here.
|
|
194
|
+
*
|
|
195
|
+
* The peer-id suffix goes BEFORE `:error` / `:trace` — those are children of the concrete channel,
|
|
196
|
+
* so a two-node process gets `…:x:12D3KooWAb:error`, not `…:x:error:12D3KooWAb`.
|
|
197
|
+
*/
|
|
198
|
+
/*
|
|
199
|
+
* NOTE: `packages/db-p2p/test/logger.spec.ts` asserts that every namespace this package emits has
|
|
200
|
+
* a row in the db-p2p table of `docs/debugging.md`, and it finds them by scanning `src/` for
|
|
201
|
+
* `createLogger("…")` / `createLogger('…')` — STRING LITERALS ONLY. A namespace built from a
|
|
202
|
+
* variable (`createLogger(someName)`) is invisible to that guard, so it would go undocumented
|
|
203
|
+
* silently. No such call site exists today; the two that pass a fallback
|
|
204
|
+
* (`createLogger(init.logPrefix ?? 'repo-service')`) still carry the literal, which is what the
|
|
205
|
+
* scan picks up. If you ever need a fully computed namespace, document it by hand and say so here.
|
|
20
206
|
*/
|
|
21
|
-
export function createLogger(subNamespace: string, peerId?: string):
|
|
207
|
+
export function createLogger(subNamespace: string, peerId?: string): Logger {
|
|
22
208
|
const suffix = peerId ? `:${peerId.substring(0, 12)}` : ''
|
|
23
|
-
|
|
209
|
+
const namespace = `${BASE_NAMESPACE}:${subNamespace}${suffix}`
|
|
210
|
+
// NOTE: `Object.assign` onto the Debugger, never a spread into a fresh object. `debug` defines
|
|
211
|
+
// `enabled` as an accessor property on the function object it returns; assigning onto that
|
|
212
|
+
// object preserves the accessor, whereas spreading would flatten it to a construction-time
|
|
213
|
+
// snapshot boolean and silently break any `if (log.enabled)` guard.
|
|
214
|
+
return Object.assign(debug(namespace), {
|
|
215
|
+
error: debug(`${namespace}:error`),
|
|
216
|
+
trace: debug(`${namespace}:trace`)
|
|
217
|
+
})
|
|
24
218
|
}
|
|
25
219
|
|
|
26
220
|
export const verbose = typeof process !== 'undefined'
|