trac-msb 0.2.19 → 0.2.21
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/SECURITY.md
CHANGED
|
@@ -6,13 +6,11 @@ The following table shows which versions of this project are currently receiving
|
|
|
6
6
|
|
|
7
7
|
| Version | Supported | Pear compatibility |
|
|
8
8
|
| -------- | ------------------ | ------------------ |
|
|
9
|
-
| 0.2.
|
|
10
|
-
| 0.2.18 | :white_check_mark: | Pear v2 only |
|
|
11
|
-
| 0.2.17 | :white_check_mark: | Pear v2 only |
|
|
9
|
+
| 0.2.21 | :white_check_mark: | Pear v2 and v3 |
|
|
12
10
|
| main | :warning: unstable | Pear v2 and v3 |
|
|
13
|
-
| < 0.2.
|
|
11
|
+
| < 0.2.21 | :x: | N/A |
|
|
14
12
|
|
|
15
|
-
Versions older than 0.2.
|
|
13
|
+
Versions older than 0.2.21 are deprecated.
|
|
16
14
|
Please upgrade to the latest release to ensure you receive security fixes.
|
|
17
15
|
|
|
18
16
|
---
|
package/package.json
CHANGED
|
@@ -39,6 +39,7 @@ class Network extends ReadyResource {
|
|
|
39
39
|
#wallet;
|
|
40
40
|
#validatorHealthCheckService;
|
|
41
41
|
#logger;
|
|
42
|
+
#closing = false;
|
|
42
43
|
|
|
43
44
|
/**
|
|
44
45
|
* @param {State} state
|
|
@@ -82,6 +83,7 @@ class Network extends ReadyResource {
|
|
|
82
83
|
|
|
83
84
|
async _open() {
|
|
84
85
|
this.#logger.info('Network initialization...');
|
|
86
|
+
this.#closing = false;
|
|
85
87
|
|
|
86
88
|
this.setupNetworkListeners();
|
|
87
89
|
|
|
@@ -91,6 +93,7 @@ class Network extends ReadyResource {
|
|
|
91
93
|
|
|
92
94
|
async _close() {
|
|
93
95
|
this.#logger.info('Network: closing gracefully...');
|
|
96
|
+
this.#closing = true;
|
|
94
97
|
await this.transactionPoolService.stopPool();
|
|
95
98
|
await sleep(100);
|
|
96
99
|
await this.#validatorObserverService.stopValidatorObserver();
|
|
@@ -104,8 +107,15 @@ class Network extends ReadyResource {
|
|
|
104
107
|
this.#pendingRequestsService.close();
|
|
105
108
|
this.#transactionCommitService.close();
|
|
106
109
|
|
|
107
|
-
|
|
108
|
-
|
|
110
|
+
const swarm = this.#swarm;
|
|
111
|
+
if (swarm !== null) {
|
|
112
|
+
if (typeof swarm.removeAllListeners === 'function') {
|
|
113
|
+
swarm.removeAllListeners('connection');
|
|
114
|
+
}
|
|
115
|
+
await swarm.destroy();
|
|
116
|
+
if (this.#swarm === swarm) {
|
|
117
|
+
this.#swarm = null;
|
|
118
|
+
}
|
|
109
119
|
}
|
|
110
120
|
}
|
|
111
121
|
|
|
@@ -167,8 +177,12 @@ class Network extends ReadyResource {
|
|
|
167
177
|
store,
|
|
168
178
|
wallet,
|
|
169
179
|
) {
|
|
180
|
+
this.#assertCanReplicate();
|
|
181
|
+
|
|
170
182
|
if (!this.#swarm) {
|
|
171
183
|
const { wallet: wrappedWallet, keyPair } = await this.#getOrGenerateWallet(store, wallet);
|
|
184
|
+
this.#assertCanReplicate();
|
|
185
|
+
|
|
172
186
|
this.#wallet = wrappedWallet
|
|
173
187
|
this.#validatorMessageOrchestrator.setWallet(this.#wallet);
|
|
174
188
|
|
|
@@ -193,19 +207,49 @@ class Network extends ReadyResource {
|
|
|
193
207
|
);
|
|
194
208
|
this.#validatorHealthCheckService = new ValidatorHealthCheckService(this.#config);
|
|
195
209
|
await this.#validatorHealthCheckService.ready();
|
|
210
|
+
this.#assertCanReplicate();
|
|
211
|
+
|
|
196
212
|
this.#validatorConnectionManager.subscribeToHealthChecks(this.#validatorHealthCheckService);
|
|
197
213
|
|
|
198
214
|
this.#logger.info(`Channel: ${b4a.toString(this.#config.channel)}`);
|
|
199
215
|
|
|
200
216
|
this.#swarm.on('connection', async (connection) => {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
217
|
+
if (this.#closing) {
|
|
218
|
+
this.#destroyConnection(connection);
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
205
221
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
222
|
+
try {
|
|
223
|
+
// Per-peer connection initialization:
|
|
224
|
+
// - attach Protomux (legacy + v1 channels/messages)
|
|
225
|
+
// - attach connection.protocolSession (used later by tryConnect / orchestrators to send messages)
|
|
226
|
+
await this.#networkMessages.setupProtomuxMessages(connection);
|
|
227
|
+
|
|
228
|
+
// Pear v3 can deliver a late swarm connection while shutdown is
|
|
229
|
+
// already closing the Corestore. Do not replicate a connection
|
|
230
|
+
// after close has begun; store.replicate would otherwise throw.
|
|
231
|
+
if (this.#closing || this.#swarm === null) {
|
|
232
|
+
this.#destroyConnection(connection);
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// ATTENTION: Must be called AFTER the protomux init above
|
|
237
|
+
const stream = store.replicate(connection);
|
|
238
|
+
wakeup.addStream(stream);
|
|
239
|
+
} catch (error) {
|
|
240
|
+
const publicKey = connection.remotePublicKey
|
|
241
|
+
? b4a.toString(connection.remotePublicKey, 'hex')
|
|
242
|
+
: 'unknown';
|
|
243
|
+
this.#pendingRequestsService.rejectPendingRequestsForPeer(
|
|
244
|
+
publicKey,
|
|
245
|
+
error ?? new Error('Connection setup failed')
|
|
246
|
+
);
|
|
247
|
+
this.#destroyConnection(connection);
|
|
248
|
+
if (!this.#closing) {
|
|
249
|
+
this.#logger.error(error?.message ?? 'Unknown network connection setup error');
|
|
250
|
+
}
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
209
253
|
|
|
210
254
|
const publicKey = b4a.toString(connection.remotePublicKey, 'hex');
|
|
211
255
|
if (this.#pendingConnections.has(publicKey)) {
|
|
@@ -218,9 +262,13 @@ class Network extends ReadyResource {
|
|
|
218
262
|
publicKey,
|
|
219
263
|
new Error('Connection closed before response')
|
|
220
264
|
);
|
|
221
|
-
this.#swarm
|
|
265
|
+
this.#swarm?.leavePeer(connection.remotePublicKey);
|
|
222
266
|
this.#validatorConnectionManager.remove(publicKey);
|
|
223
|
-
connection.protocolSession
|
|
267
|
+
if (connection.protocolSession) {
|
|
268
|
+
try {
|
|
269
|
+
connection.protocolSession.close();
|
|
270
|
+
} catch {}
|
|
271
|
+
}
|
|
224
272
|
});
|
|
225
273
|
|
|
226
274
|
connection.on('error', (error) => {
|
|
@@ -344,6 +392,26 @@ class Network extends ReadyResource {
|
|
|
344
392
|
this.#pendingConnections.delete(publicKeyHex);
|
|
345
393
|
return true;
|
|
346
394
|
}
|
|
395
|
+
|
|
396
|
+
#assertCanReplicate() {
|
|
397
|
+
if (this.#closing || this.closed) {
|
|
398
|
+
throw new Error('Network is closing or already closed');
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
#destroyConnection(connection) {
|
|
403
|
+
if (!connection) return;
|
|
404
|
+
if (connection.protocolSession) {
|
|
405
|
+
try {
|
|
406
|
+
connection.protocolSession.close();
|
|
407
|
+
} catch {}
|
|
408
|
+
}
|
|
409
|
+
if (typeof connection.destroy === 'function') {
|
|
410
|
+
connection.destroy();
|
|
411
|
+
} else if (typeof connection.end === 'function') {
|
|
412
|
+
connection.end();
|
|
413
|
+
}
|
|
414
|
+
}
|
|
347
415
|
}
|
|
348
416
|
|
|
349
417
|
export default Network;
|
|
@@ -373,8 +373,8 @@ class ValidatorObserverService {
|
|
|
373
373
|
* Rules:
|
|
374
374
|
* - Skips invalid/malformed entries.
|
|
375
375
|
* - Skips non-admin indexers.
|
|
376
|
-
* - Deduplicates
|
|
377
|
-
* - Removes stale connections
|
|
376
|
+
* - Deduplicates active writers by wallet address.
|
|
377
|
+
* - Removes stale connections only when no active writer remains for an address.
|
|
378
378
|
* - Includes only valid and active writer nodes.
|
|
379
379
|
* @param {Object} adminEntry - The current admin node entry.
|
|
380
380
|
* @returns {Promise<Array<Object>>} A promise resolving to the deduplicated array of active writers.
|
|
@@ -385,13 +385,13 @@ class ValidatorObserverService {
|
|
|
385
385
|
this.#keyDecodeCache.clear();
|
|
386
386
|
}
|
|
387
387
|
|
|
388
|
-
//
|
|
389
|
-
// occupies only a single slot despite the append-only nature of Autobase
|
|
388
|
+
// Historical writer keys can share an address with an active replacement.
|
|
390
389
|
const activeMap = new Map();
|
|
390
|
+
const removedMap = new Map();
|
|
391
391
|
|
|
392
392
|
try {
|
|
393
393
|
for await (const { key, value } of this.#state.base.system.list()) {
|
|
394
|
-
if (!this.#shouldRun())
|
|
394
|
+
if (!this.#shouldRun()) return Array.from(activeMap.values());
|
|
395
395
|
|
|
396
396
|
if (!key || b4a.byteLength(key) !== WRITER_BYTE_LENGTH) continue;
|
|
397
397
|
if (!value) continue;
|
|
@@ -426,16 +426,20 @@ class ValidatorObserverService {
|
|
|
426
426
|
|
|
427
427
|
if (value.isIndexer && addr !== adminEntry?.address) continue;
|
|
428
428
|
|
|
429
|
-
// Handles soft-delete: drops stale connections and removes the node from the pool
|
|
430
429
|
if (value.isRemoved) {
|
|
431
|
-
|
|
432
|
-
activeMap.delete(addr);
|
|
430
|
+
removedMap.set(addr, publicKey);
|
|
433
431
|
continue;
|
|
434
432
|
}
|
|
435
433
|
|
|
436
|
-
// Registers or overwrites to maintain a single source of truth per machine
|
|
437
434
|
activeMap.set(addr, { key, address: addr, publicKey, publicKeyHex });
|
|
438
435
|
}
|
|
436
|
+
|
|
437
|
+
// Writer iteration order does not represent key age. Only a complete scan
|
|
438
|
+
// can establish that a removed writer has no active replacement.
|
|
439
|
+
for (const [addr, publicKey] of removedMap) {
|
|
440
|
+
if (!this.#shouldRun()) break;
|
|
441
|
+
if (!activeMap.has(addr)) this.#enforceNoStaleConnections(publicKey);
|
|
442
|
+
}
|
|
439
443
|
} catch (err) {
|
|
440
444
|
this.#logger.error(`Autobase writer scan error: ${err.message}`);
|
|
441
445
|
}
|
|
@@ -462,4 +466,4 @@ class ValidatorObserverService {
|
|
|
462
466
|
}
|
|
463
467
|
}
|
|
464
468
|
|
|
465
|
-
export default ValidatorObserverService;
|
|
469
|
+
export default ValidatorObserverService;
|