trac-msb 0.2.24 → 0.3.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/SECURITY.md +2 -2
- package/cli/commandHandler.js +317 -20
- package/cli/handlers.js +9 -1
- package/cli/interactive.js +4 -3
- package/cli/tokenSupplyAudit.js +90 -0
- package/msb.mjs +1 -0
- package/package.json +15 -6
- package/rpc/create_server.js +12 -3
- package/rpc/handlers.js +103 -0
- package/rpc/routes/v1.js +8 -0
- package/src/codecs/apply/applyOperationCodec.js +334 -0
- package/src/codecs/apply/applyOperations.generated.cjs +6380 -0
- package/src/codecs/consensus/v1/consensusV1.generated.cjs +3508 -0
- package/src/codecs/consensus/v1/consensusV1OperationCodec.js +161 -0
- package/src/codecs/consensus/v1/vdfConfigCodec.js +83 -0
- package/src/{utils/protobuf → codecs/network/v1}/networkV1.generated.cjs +664 -393
- package/src/codecs/network/v1/networkV1OperationCodec.js +25 -0
- package/src/codecs/protobufRuntime.cjs +95 -0
- package/src/codecs/state/escrowEntryCodec.js +45 -0
- package/src/codecs/state/state.generated.cjs +659 -0
- package/src/config/args.js +0 -1
- package/src/config/config.js +46 -7
- package/src/config/env.js +33 -6
- package/src/core/consensus/epochAppenderSelection/EpochAppenderSelectionError.js +55 -0
- package/src/core/consensus/epochAppenderSelection/EpochAppenderSelectionOperations.js +118 -0
- package/src/core/consensus/epochAppenderSelection/EpochAppenderSelector.js +1042 -0
- package/src/core/consensus/epochAppenderSelection/createEpochAppenderSelector.js +23 -0
- package/src/core/consensus/epochAppenderSelection/epochAppenderSelectionLedgerReader.js +274 -0
- package/src/core/consensus/epochAppenderSelection/epochAppenderSelectionState.js +488 -0
- package/src/core/consensus/epochAppenderSelection/epochAppenderSelectionStorage.js +93 -0
- package/src/core/consensus/quorum.js +11 -0
- package/src/core/consensus/services/EpochCoordinationRound.js +839 -0
- package/src/core/consensus/services/EpochCoordinatorOperations.js +211 -0
- package/src/core/consensus/services/EpochCoordinatorService.js +335 -0
- package/src/core/consensus/services/EpochRoundListeners.js +116 -0
- package/src/core/consensus/services/EpochStateMachine.js +141 -0
- package/src/core/consensus/services/VDFBare.js +291 -0
- package/src/core/consensus/services/VDFNode.js +111 -0
- package/src/core/consensus/services/VDFProgressWatchdog.js +79 -0
- package/src/core/consensus/services/VDFService.js +87 -0
- package/src/core/consensus/services/VDFServiceManager.js +147 -0
- package/src/core/consensus/services/createVDFService.js +16 -0
- package/src/core/consensus/services/vdf-guardian.js +32 -0
- package/src/core/consensus/services/vdf-worker.js +90 -0
- package/src/core/network/Network.js +327 -238
- package/src/core/network/protocols/consensus/IndexerMessages.js +72 -0
- package/src/core/network/protocols/consensus/v1/ConsensusV1ConnectionPolicy.js +57 -0
- package/src/core/network/protocols/consensus/v1/ConsensusV1MessageRouter.js +147 -0
- package/src/core/network/protocols/consensus/v1/ConsensusV1Protocol.js +72 -0
- package/src/core/network/protocols/consensus/v1/IndexerPendingRequestService.js +108 -0
- package/src/core/network/protocols/consensus/v1/V1ConsensusProtocolError.js +29 -0
- package/src/core/network/protocols/consensus/v1/handlers/V1EpochAppendOperationHandler.js +68 -0
- package/src/core/network/protocols/consensus/v1/handlers/V1EpochProofProposalOperationHandler.js +226 -0
- package/src/core/network/protocols/consensus/v1/validators/ConsensusValidationSchema.js +192 -0
- package/src/core/network/protocols/consensus/v1/validators/V1BaseConsensusOperation.js +428 -0
- package/src/core/network/protocols/consensus/v1/validators/V1EpochAppendReply.js +47 -0
- package/src/core/network/protocols/consensus/v1/validators/V1EpochAppendRequest.js +17 -0
- package/src/core/network/protocols/consensus/v1/validators/V1EpochProofProposalApproval.js +116 -0
- package/src/core/network/protocols/consensus/v1/validators/V1EpochProofProposalRequest.js +152 -0
- package/src/core/network/protocols/consensus/v1/validators/consensusValidationSchemaHelpers.js +145 -0
- package/src/core/network/protocols/shared/ConnectionOperationHandler.js +27 -0
- package/src/core/network/protocols/{NetworkMessages.js → validators/ValidatorMessages.js} +24 -14
- package/src/core/network/protocols/{ProtocolSession.js → validators/ValidatorProtocolSession.js} +22 -25
- package/src/core/network/protocols/{legacy/NetworkMessageRouter.js → validators/legacy/ValidatorLegacyMessageRouter.js} +6 -6
- package/src/core/network/protocols/{LegacyProtocol.js → validators/legacy/ValidatorLegacyProtocol.js} +9 -7
- package/src/core/network/protocols/{legacy → validators/legacy}/handlers/BaseStateOperationHandler.js +1 -1
- package/src/core/network/protocols/{legacy → validators/legacy}/handlers/LegacyGetRequestHandler.js +5 -5
- package/src/core/network/protocols/{legacy → validators/legacy}/handlers/LegacyRoleOperationHandler.js +5 -5
- package/src/core/network/protocols/{legacy → validators/legacy}/handlers/LegacySubnetworkOperationHandler.js +5 -5
- package/src/core/network/protocols/{legacy → validators/legacy}/handlers/LegacyTransferOperationHandler.js +5 -5
- package/src/core/network/protocols/{ProtocolInterface.js → validators/shared/ValidatorProtocolInterface.js} +5 -5
- package/src/core/network/protocols/{shared → validators/shared}/validators/PartialBootstrapDeploymentValidator.js +1 -1
- package/src/core/network/protocols/validators/shared/validators/PartialHtlcValidator.js +325 -0
- package/src/core/network/protocols/{shared → validators/shared}/validators/PartialOperationValidator.js +44 -21
- package/src/core/network/protocols/{shared → validators/shared}/validators/PartialRoleAccessValidator.js +3 -3
- package/src/core/network/protocols/{shared → validators/shared}/validators/PartialTransactionValidator.js +3 -3
- package/src/core/network/protocols/{shared → validators/shared}/validators/PartialTransferValidator.js +4 -4
- package/src/core/network/protocols/{v1 → validators/v1}/V1ProtocolError.js +1 -1
- package/src/core/network/{services/PendingRequestService.js → protocols/validators/v1/ValidatorPendingRequestService.js} +19 -82
- package/src/core/network/protocols/{v1/NetworkMessageRouter.js → validators/v1/ValidatorV1MessageRouter.js} +9 -9
- package/src/core/network/protocols/{V1Protocol.js → validators/v1/ValidatorV1Protocol.js} +10 -8
- package/src/core/network/protocols/{connectionPolicies.js → validators/v1/connectionPolicies.js} +1 -1
- package/src/core/network/protocols/{v1 → validators/v1}/handlers/V1BaseOperationHandler.js +6 -23
- package/src/core/network/protocols/{v1 → validators/v1}/handlers/V1BroadcastTransactionOperationHandler.js +14 -9
- package/src/core/network/protocols/{v1 → validators/v1}/handlers/V1LivenessOperationHandler.js +5 -4
- package/src/core/network/protocols/{v1 → validators/v1}/handlers/broadcastTransaction/BroadcastTransactionErrorMapper.js +3 -3
- package/src/core/network/protocols/{v1 → validators/v1}/handlers/broadcastTransaction/BroadcastTransactionOperationStrategies.js +44 -1
- package/src/core/network/protocols/{v1 → validators/v1}/handlers/broadcastTransaction/BroadcastTransactionPayloadUtils.js +1 -1
- package/src/core/network/protocols/{v1 → validators/v1}/validators/V1BaseOperation.js +19 -15
- package/src/core/network/protocols/{v1 → validators/v1}/validators/V1BroadcastTransactionRequest.js +2 -2
- package/src/core/network/protocols/{v1 → validators/v1}/validators/V1BroadcastTransactionResponse.js +34 -18
- package/src/core/network/protocols/{v1 → validators/v1}/validators/V1ValidationSchema.js +3 -3
- package/src/core/network/services/IndexerConnectionManager.js +88 -0
- package/src/core/network/services/TransactionPoolService.js +11 -18
- package/src/core/network/services/TransactionRateLimiterService.js +1 -1
- package/src/core/network/services/ValidatorConnectionManager.js +163 -0
- package/src/core/network/services/{MessageOrchestrator.js → ValidatorMessageOrchestrator.js} +34 -42
- package/src/core/network/services/ValidatorObserverService.js +21 -36
- package/src/core/network/shared/BasePendingRequestService.js +67 -0
- package/src/core/network/shared/PeerConnectionManager.js +185 -0
- package/src/core/state/ConfirmationWatch.js +82 -0
- package/src/core/state/State.js +1794 -173
- package/src/core/state/StateEventQueue.js +69 -0
- package/src/core/state/utils/address.js +10 -6
- package/src/core/state/utils/epochProof.js +109 -0
- package/src/core/state/utils/escrowEntry.js +178 -0
- package/src/core/state/validators/StateValidationSchema.js +757 -0
- package/src/core/state/validators/stateValidationRules.js +312 -0
- package/src/index.js +255 -65
- package/src/messages/consensus/v1/ConsensusMessageBuilder.js +368 -0
- package/src/messages/consensus/v1/ConsensusMessageDirector.js +142 -0
- package/src/messages/consensus/v1/consensusMessageFactory.js +12 -0
- package/src/messages/consensus/v1/epochAppendMessage.js +58 -0
- package/src/messages/network/v1/NetworkMessageBuilder.js +15 -7
- package/src/messages/state/ApplyStateMessageBuilder.js +499 -13
- package/src/messages/state/ApplyStateMessageDirector.js +261 -2
- package/src/utils/StateMachine.js +125 -0
- package/src/utils/applyOperations.js +45 -2
- package/src/utils/buffer.js +239 -59
- package/src/utils/consensusConfig.js +76 -0
- package/src/utils/constants.js +174 -73
- package/src/utils/helpers.js +0 -25
- package/src/utils/htlcLock.js +122 -0
- package/src/utils/logger.js +10 -4
- package/src/utils/migrationUtils.js +2 -1
- package/src/utils/normalizers.js +113 -0
- package/src/utils/scheduler/SchedulableService.js +107 -0
- package/src/utils/{Scheduler.js → scheduler/Scheduler.js} +56 -17
- package/src/core/network/services/ConnectionManager.js +0 -406
- package/src/utils/check.js +0 -525
- package/src/utils/protobuf/applyOperations.cjs +0 -1373
- package/src/utils/protobuf/operationHelpers.js +0 -81
- /package/src/core/network/protocols/{legacy → validators/legacy}/handlers/LegacyResponseHandler.js +0 -0
- /package/src/core/network/protocols/{legacy → validators/legacy}/validators/ValidatorResponse.js +0 -0
- /package/src/core/network/protocols/{legacy → validators/legacy}/validators/base/BaseResponse.js +0 -0
- /package/src/core/network/protocols/{v1 → validators/v1}/validators/V1LivenessRequest.js +0 -0
- /package/src/core/network/protocols/{v1 → validators/v1}/validators/V1LivenessResponse.js +0 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import ReadyResource from 'ready-resource';
|
|
2
|
+
import Scheduler from './Scheduler.js';
|
|
3
|
+
|
|
4
|
+
export const SCHEDULABLE_SERVICE_EVENTS = Object.freeze({
|
|
5
|
+
STOP: 'schedulable-service-stop',
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Base class for services that execute recurring work through the shared scheduler.
|
|
10
|
+
*
|
|
11
|
+
* Subclasses should implement:
|
|
12
|
+
* - {@link SchedulableService#getScheduleInterval}
|
|
13
|
+
* - {@link SchedulableService#worker}
|
|
14
|
+
*/
|
|
15
|
+
class SchedulableService extends ReadyResource {
|
|
16
|
+
#scheduler = null;
|
|
17
|
+
#isInterrupted = false;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Returns the default interval in milliseconds used between scheduled runs.
|
|
21
|
+
*
|
|
22
|
+
* @protected
|
|
23
|
+
* @returns {number}
|
|
24
|
+
*/
|
|
25
|
+
getScheduleInterval() {
|
|
26
|
+
return 0;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Runs a single scheduled cycle.
|
|
31
|
+
*
|
|
32
|
+
* Call `next(delayMs)` to override the next run delay; otherwise the scheduler uses
|
|
33
|
+
* {@link SchedulableService#getScheduleInterval}.
|
|
34
|
+
*
|
|
35
|
+
* @protected
|
|
36
|
+
* @param {(delayMs: number) => void} _next
|
|
37
|
+
* @returns {Promise<void>|void}
|
|
38
|
+
*/
|
|
39
|
+
worker(_next, _hold) {}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Indicates whether the service has requested interruption of scheduled work.
|
|
43
|
+
*
|
|
44
|
+
* @protected
|
|
45
|
+
* @returns {boolean}
|
|
46
|
+
*/
|
|
47
|
+
get isInterrupted() {
|
|
48
|
+
return this.#isInterrupted;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Indicates whether the scheduler is currently running.
|
|
53
|
+
*
|
|
54
|
+
* @protected
|
|
55
|
+
* @returns {boolean}
|
|
56
|
+
*/
|
|
57
|
+
get isSchedulerRunning() {
|
|
58
|
+
return this.#scheduler?.isRunning ?? false;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Starts the scheduler if it is not already running.
|
|
63
|
+
*
|
|
64
|
+
* @param {number} [initialDelayMs=0]
|
|
65
|
+
* @returns {boolean}
|
|
66
|
+
*/
|
|
67
|
+
start(initialDelayMs = 0) {
|
|
68
|
+
this.#ensureScheduler();
|
|
69
|
+
if (this.isSchedulerRunning) {
|
|
70
|
+
console.info(`${this.constructor.name} is already started`);
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
this.#isInterrupted = false;
|
|
75
|
+
this.#scheduler.start(initialDelayMs);
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Stops the scheduler if it is currently running.
|
|
81
|
+
*
|
|
82
|
+
* @param {boolean} [waitForCurrent=true]
|
|
83
|
+
* @returns {Promise<boolean>}
|
|
84
|
+
*/
|
|
85
|
+
async stop(waitForCurrent = true) {
|
|
86
|
+
this.emit(SCHEDULABLE_SERVICE_EVENTS.STOP);
|
|
87
|
+
if (!this.isSchedulerRunning) return false;
|
|
88
|
+
|
|
89
|
+
this.#isInterrupted = true;
|
|
90
|
+
await this.#scheduler.stop(waitForCurrent);
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async _close() {
|
|
95
|
+
await this.stop(true);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
#ensureScheduler() {
|
|
99
|
+
if (this.#scheduler) return;
|
|
100
|
+
this.#scheduler = new Scheduler(
|
|
101
|
+
(next, hold) => this.worker(next, hold),
|
|
102
|
+
this.getScheduleInterval(),
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export default SchedulableService;
|
|
@@ -25,23 +25,33 @@ class Scheduler {
|
|
|
25
25
|
#timer = null;
|
|
26
26
|
#currentWorkerRun = null;
|
|
27
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Creates a scheduler for one worker and its default interval.
|
|
30
|
+
*
|
|
31
|
+
* @param {Function} worker
|
|
32
|
+
* @param {number} defaultInterval
|
|
33
|
+
*/
|
|
28
34
|
constructor(worker, defaultInterval) {
|
|
29
|
-
if (typeof worker !== 'function') {
|
|
30
|
-
throw new TypeError('Worker must be a function');
|
|
31
|
-
}
|
|
32
35
|
const interval = Scheduler.#validateDelay(defaultInterval ?? 100, 'defaultInterval');
|
|
33
36
|
this.#worker = worker;
|
|
34
37
|
this.#defaultInterval = interval;
|
|
35
38
|
}
|
|
36
39
|
|
|
40
|
+
/** @returns {boolean} true when the scheduler is running */
|
|
37
41
|
get isRunning() {
|
|
38
42
|
return this.#isRunning;
|
|
39
43
|
}
|
|
40
44
|
|
|
45
|
+
/** @returns {number} delay used when the worker does not set one */
|
|
41
46
|
get defaultInterval() {
|
|
42
47
|
return this.#defaultInterval;
|
|
43
48
|
}
|
|
44
49
|
|
|
50
|
+
/**
|
|
51
|
+
* Converts and validates a scheduler delay.
|
|
52
|
+
*
|
|
53
|
+
* @returns {number}
|
|
54
|
+
*/
|
|
45
55
|
static #validateDelay(delayMs, scope = 'delayMs') {
|
|
46
56
|
const ms = Number(delayMs);
|
|
47
57
|
if (!Number.isFinite(ms) || ms < 0) {
|
|
@@ -50,6 +60,7 @@ class Scheduler {
|
|
|
50
60
|
return ms;
|
|
51
61
|
}
|
|
52
62
|
|
|
63
|
+
/** Starts the scheduler and sets the timer for the first run. */
|
|
53
64
|
start(initialDelayMs = 0) {
|
|
54
65
|
if (this.isRunning) return;
|
|
55
66
|
const delayMs = Scheduler.#validateDelay(initialDelayMs, 'start delayMs');
|
|
@@ -57,41 +68,69 @@ class Scheduler {
|
|
|
57
68
|
this.#next(delayMs);
|
|
58
69
|
}
|
|
59
70
|
|
|
71
|
+
/**
|
|
72
|
+
* Runs one worker and schedules the next run.
|
|
73
|
+
* hold() and scheduleNext() allow the worker to control the next timer.
|
|
74
|
+
*
|
|
75
|
+
* @returns {Promise<void>}
|
|
76
|
+
*/
|
|
60
77
|
async run() {
|
|
61
|
-
if (!this.isRunning) return
|
|
78
|
+
if (!this.isRunning) return;
|
|
62
79
|
|
|
80
|
+
// `hold()` means the worker has taken responsibility for scheduling the next run itself,
|
|
81
|
+
// by calling `scheduleNext(ms)` later - possibly long after this `run()` call has already
|
|
82
|
+
// returned (e.g. from an event listener waiting on external confirmation). Because of that,
|
|
83
|
+
// `scheduleNext` arms the timer directly instead of returning a delay for `run()` to hand
|
|
84
|
+
// back to its caller - a delay handed back here would be meaningless once `run()` has
|
|
85
|
+
// already resolved.
|
|
63
86
|
let scheduleCalled = false;
|
|
64
|
-
|
|
87
|
+
|
|
88
|
+
const hold = () => {
|
|
89
|
+
scheduleCalled = true;
|
|
90
|
+
}
|
|
65
91
|
|
|
66
92
|
const scheduleNext = (ms) => {
|
|
67
93
|
scheduleCalled = true;
|
|
68
|
-
|
|
94
|
+
this.#next(Scheduler.#validateDelay(ms, 'scheduleNext delayMs'));
|
|
69
95
|
};
|
|
70
96
|
|
|
71
|
-
|
|
97
|
+
const workerRun = this.#worker(scheduleNext, hold);
|
|
98
|
+
this.#currentWorkerRun = workerRun;
|
|
72
99
|
try {
|
|
73
|
-
await
|
|
100
|
+
await workerRun;
|
|
74
101
|
} catch (error) {
|
|
75
102
|
console.error('Worker error:', error);
|
|
76
|
-
|
|
103
|
+
this.#next(this.defaultInterval);
|
|
104
|
+
return;
|
|
77
105
|
} finally {
|
|
78
|
-
this.#currentWorkerRun
|
|
106
|
+
if (this.#currentWorkerRun === workerRun) {
|
|
107
|
+
this.#currentWorkerRun = null;
|
|
108
|
+
}
|
|
79
109
|
}
|
|
80
110
|
|
|
81
|
-
|
|
111
|
+
if (!scheduleCalled) {
|
|
112
|
+
this.#next(this.defaultInterval);
|
|
113
|
+
}
|
|
82
114
|
}
|
|
83
115
|
|
|
116
|
+
/** Replaces the current timer with a new timer. */
|
|
84
117
|
#next(delayMs) {
|
|
85
118
|
if (!this.isRunning) return;
|
|
86
119
|
const ms = Scheduler.#validateDelay(delayMs, 'next delayMs');
|
|
87
|
-
this.#timer
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
120
|
+
if (this.#timer) {
|
|
121
|
+
clearTimeout(this.#timer);
|
|
122
|
+
}
|
|
123
|
+
this.#timer = setTimeout(() => {
|
|
124
|
+
this.run();
|
|
92
125
|
}, ms);
|
|
93
126
|
}
|
|
94
127
|
|
|
128
|
+
/**
|
|
129
|
+
* Stops future runs and can wait for the current worker.
|
|
130
|
+
*
|
|
131
|
+
* @param {boolean} waitForCurrent
|
|
132
|
+
* @returns {Promise<void>}
|
|
133
|
+
*/
|
|
95
134
|
async stop(waitForCurrent = true) {
|
|
96
135
|
if (!this.isRunning) return;
|
|
97
136
|
this.#isRunning = false;
|
|
@@ -102,7 +141,7 @@ class Scheduler {
|
|
|
102
141
|
}
|
|
103
142
|
|
|
104
143
|
if (waitForCurrent && this.#currentWorkerRun) {
|
|
105
|
-
await this.#currentWorkerRun;
|
|
144
|
+
await this.#currentWorkerRun;
|
|
106
145
|
}
|
|
107
146
|
}
|
|
108
147
|
}
|
|
@@ -1,406 +0,0 @@
|
|
|
1
|
-
import b4a from 'b4a'
|
|
2
|
-
import {EventType, ResultCode} from '../../../utils/constants.js';
|
|
3
|
-
import {publicKeyToAddress} from "../../../utils/helpers.js";
|
|
4
|
-
import {Logger} from "../../../utils/logger.js";
|
|
5
|
-
/**
|
|
6
|
-
* @typedef {import('hyperswarm').Connection} Connection
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
const DEFAULT_HEALTH_CHECK_FAILURE_THRESHOLD = 3;
|
|
10
|
-
|
|
11
|
-
export class ConnectionManagerError extends Error {
|
|
12
|
-
constructor(message) {
|
|
13
|
-
super(message);
|
|
14
|
-
this.name = this.constructor.name;
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
class ConnectionManager {
|
|
19
|
-
#validators
|
|
20
|
-
#maxValidators
|
|
21
|
-
#config
|
|
22
|
-
#healthCheckService
|
|
23
|
-
#healthCheckFailures
|
|
24
|
-
#healthCheckFailureThreshold
|
|
25
|
-
#boundedHealthCheckHandler
|
|
26
|
-
#logger
|
|
27
|
-
// Note: #validators is using publicKey (Buffer) as key
|
|
28
|
-
// As Buffers are objects, we will rely on internal conversions done by JS to compare them.
|
|
29
|
-
// It would be better to handle these conversions manually by using hex strings as keys to avoid issues
|
|
30
|
-
/**
|
|
31
|
-
* @param {Config} config
|
|
32
|
-
**/
|
|
33
|
-
constructor(config) {
|
|
34
|
-
this.#validators = new Map();
|
|
35
|
-
// consecutive failed health checks per validator
|
|
36
|
-
this.#healthCheckFailures = new Map();
|
|
37
|
-
this.#config = config
|
|
38
|
-
this.#maxValidators = config.maxValidators
|
|
39
|
-
this.#healthCheckFailureThreshold = this.#resolveHealthCheckFailureThreshold(config);
|
|
40
|
-
this.#boundedHealthCheckHandler = this.#healthCheckHandler.bind(this);
|
|
41
|
-
this.#logger = new Logger(config)
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Subscribes to periodic validator health checks.
|
|
46
|
-
* @param {ReadyResource} healthCheckService
|
|
47
|
-
*/
|
|
48
|
-
// TODO: We should consider moving this to ValidatorObserver instead.
|
|
49
|
-
// Keep here only if we forsee having health checks for non-validator connections in the future.
|
|
50
|
-
// For now, it seems that it would be better to keep this logic here.
|
|
51
|
-
subscribeToHealthChecks(healthCheckService) {
|
|
52
|
-
this.#logger.debug('subscribeToHealthChecks: subscribing to health check events');
|
|
53
|
-
if (!healthCheckService || typeof healthCheckService.on !== 'function' || typeof healthCheckService.off !== 'function') {
|
|
54
|
-
throw new Error('ConnectionManager: health check service must implement on/off');
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
if (this.#healthCheckService && this.#boundedHealthCheckHandler) {
|
|
58
|
-
this.#logger.debug('subscribeToHealthChecks: removing previous health check handler');
|
|
59
|
-
// Unsubscribe from previous health check service if already subscribed
|
|
60
|
-
// TODO: Maybe we should not allow switching to a new health check service
|
|
61
|
-
this.#healthCheckService.off(EventType.VALIDATOR_HEALTH_CHECK, this.#boundedHealthCheckHandler);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
this.#healthCheckService = healthCheckService; // TODO: Maybe this should be handled in the constructor directly?
|
|
65
|
-
// TODO: declare this method outside this function to avoid redeclaring it every time we subscribe to health checks. We can just bind it to 'this' in the constructor.
|
|
66
|
-
|
|
67
|
-
this.#healthCheckService.on(EventType.VALIDATOR_HEALTH_CHECK, this.#boundedHealthCheckHandler);
|
|
68
|
-
this.#logger.debug('subscribeToHealthChecks: subscribed to health check events');
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
async #healthCheckHandler(publicKey, requestId) {
|
|
72
|
-
if (typeof publicKey !== 'string' || typeof requestId !== 'string') {
|
|
73
|
-
// We can't throw here because this is an event handler, but we should at least log the error and return early to avoid further issues.
|
|
74
|
-
this.#logger.error(`healthCheck: malformed event payload. Typeof publicKey = ${typeof publicKey}. Typeof requestId = ${typeof requestId}`);
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const targetAddress = publicKeyToAddress(publicKey, this.#config);
|
|
79
|
-
|
|
80
|
-
if (!this.exists(publicKey) || !this.connected(publicKey)) {
|
|
81
|
-
this.#logger.debug(`healthCheck: validator not connected, stopping checks. Address = ${targetAddress}; Request ID = ${requestId}`);
|
|
82
|
-
this.#stopHealthCheck(publicKey);
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
const connection = this.getConnection(publicKey);
|
|
87
|
-
if (!connection || !connection.protocolSession || typeof connection.protocolSession.sendHealthCheck !== 'function') {
|
|
88
|
-
this.#logger.debug(`healthCheck: missing protocol session, removing validator. Address = ${targetAddress}; Request ID = ${requestId}`);
|
|
89
|
-
this.#stopHealthCheck(publicKey);
|
|
90
|
-
this.remove(publicKey);
|
|
91
|
-
return;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
let success = false;
|
|
95
|
-
try {
|
|
96
|
-
this.#logger.debug(`healthCheck: sending liveness request. Address = ${targetAddress}; Request ID = ${requestId}`);
|
|
97
|
-
|
|
98
|
-
const resultCode = await connection.protocolSession.sendHealthCheck();
|
|
99
|
-
success = resultCode === ResultCode.OK;
|
|
100
|
-
if (!success) {
|
|
101
|
-
this.#logger.debug(`healthCheck: non-OK result code. Address = ${targetAddress}; Request ID = ${requestId}`);
|
|
102
|
-
}
|
|
103
|
-
} catch {
|
|
104
|
-
success = false;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
if (!success) {
|
|
108
|
-
const failures = this.#countHealthCheckFailure(publicKey);
|
|
109
|
-
this.#logger.debug(`healthCheck: liveness request failed. Address = ${targetAddress}; Request ID = ${requestId}; Consecutive failures = ${failures}`);
|
|
110
|
-
if (failures >= this.#healthCheckFailureThreshold) {
|
|
111
|
-
this.#logger.debug(`healthCheck: failure threshold reached, removing validator. Address = ${targetAddress}; Request ID = ${requestId}`);
|
|
112
|
-
this.remove(publicKey);
|
|
113
|
-
this.#stopHealthCheck(publicKey);
|
|
114
|
-
}
|
|
115
|
-
} else {
|
|
116
|
-
this.#clearHealthCheckFailures(publicKey);
|
|
117
|
-
this.#logger.debug(`healthCheck: success. Address = ${targetAddress}; Request ID = ${requestId}`);
|
|
118
|
-
}
|
|
119
|
-
};
|
|
120
|
-
|
|
121
|
-
#stopHealthCheck(publicKeyHex) {
|
|
122
|
-
const targetAddress = publicKeyToAddress(publicKeyHex, this.#config);
|
|
123
|
-
|
|
124
|
-
if (!this.#healthCheckService) {
|
|
125
|
-
this.#logger.debug(`stopHealthCheck: no health check service, cannot stop checks for ${targetAddress}`);
|
|
126
|
-
return;
|
|
127
|
-
}
|
|
128
|
-
try {
|
|
129
|
-
if (this.#healthCheckService.has(publicKeyHex)) {
|
|
130
|
-
this.#logger.debug(`stopHealthCheck: stopping scheduled checks for ${targetAddress}`);
|
|
131
|
-
this.#healthCheckService.stop(publicKeyHex);
|
|
132
|
-
}
|
|
133
|
-
} catch (error) {
|
|
134
|
-
this.#logger.debug(`stopHealthCheck: failed to stop health check for validator ${targetAddress}. Error: ${error.message}`);
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
#resolveHealthCheckFailureThreshold(config) {
|
|
139
|
-
const threshold = Number(config.validatorHealthCheckFailureThreshold);
|
|
140
|
-
if (!Number.isInteger(threshold) || threshold < 1) return DEFAULT_HEALTH_CHECK_FAILURE_THRESHOLD;
|
|
141
|
-
return threshold;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
#countHealthCheckFailure(publicKey) {
|
|
145
|
-
const publicKeyHex = this.#toHexString(publicKey);
|
|
146
|
-
const failures = (this.#healthCheckFailures.get(publicKeyHex) || 0) + 1;
|
|
147
|
-
this.#healthCheckFailures.set(publicKeyHex, failures);
|
|
148
|
-
return failures;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
#clearHealthCheckFailures(publicKey) {
|
|
152
|
-
this.#healthCheckFailures.delete(this.#toHexString(publicKey));
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* Retrieves the Hyperswarm connection object for a given validator public key.
|
|
157
|
-
* @param {String | Buffer} publicKey - The public key (Buffer or hex string) of the validator.
|
|
158
|
-
* @returns {Connection|undefined} - The connection object if found, otherwise undefined.
|
|
159
|
-
*/
|
|
160
|
-
getConnection(publicKey) {
|
|
161
|
-
const publicKeyHex = this.#toHexString(publicKey);
|
|
162
|
-
const entry = this.#validators.get(publicKeyHex);
|
|
163
|
-
return entry ? entry.connection : undefined;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* Checks whether a connection is the one currently tracked for a validator.
|
|
168
|
-
* @param {String | Buffer} publicKey - The public key (Buffer or hex string) of the validator.
|
|
169
|
-
* @param {Object} connection - The connection object to compare with the tracked one.
|
|
170
|
-
* @returns {Boolean} - Returns true if the tracked connection is the given connection, false otherwise.
|
|
171
|
-
*/
|
|
172
|
-
isCurrent(publicKey, connection) {
|
|
173
|
-
const publicKeyHex = this.#toHexString(publicKey);
|
|
174
|
-
const entry = this.#validators.get(publicKeyHex);
|
|
175
|
-
return !!entry && entry.connection === connection;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
/**
|
|
179
|
-
* Sends a message through a specific validator without increasing sent messages count.
|
|
180
|
-
* @param {Object} message - The message to send to the validator.
|
|
181
|
-
* @param {String | Buffer} publicKey - A validator public key hex string to be fetched from the pool.
|
|
182
|
-
* @returns {Promise<*>} A promise returned by `validator.connection.protocolSession.send(message)`.
|
|
183
|
-
* @throws {ConnectionManagerError} If the validator is not connected.
|
|
184
|
-
* @throws {ConnectionManagerError} If the validator has no valid connection or protocol session.
|
|
185
|
-
*/
|
|
186
|
-
async sendSingleMessage(message, publicKey) {
|
|
187
|
-
let publicKeyHex = this.#toHexString(publicKey);
|
|
188
|
-
if (!this.connected(publicKeyHex)) {
|
|
189
|
-
throw new ConnectionManagerError(
|
|
190
|
-
`Cannot send message: validator ${publicKeyToAddress(publicKey, this.#config)} is not connected.`
|
|
191
|
-
);
|
|
192
|
-
}
|
|
193
|
-
const validator = this.#validators.get(publicKeyHex);
|
|
194
|
-
if (!validator || !validator.connection || !validator.connection.protocolSession) {
|
|
195
|
-
throw new ConnectionManagerError(
|
|
196
|
-
`Cannot send message: no valid connection found for validator ${publicKeyToAddress(publicKey, this.#config)}.`
|
|
197
|
-
);
|
|
198
|
-
}
|
|
199
|
-
return validator.connection.protocolSession.send(message)
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
/**
|
|
203
|
-
* Adds a validator to the pool if not already present.
|
|
204
|
-
* @param {String | Buffer} publicKey - The public key hex string of the validator to add
|
|
205
|
-
* @param {Object} connection - The connection object associated with the validator
|
|
206
|
-
* @returns {Boolean} - Returns true if the validator was added or updated, false otherwise
|
|
207
|
-
*/
|
|
208
|
-
addValidator(publicKey, connection) {
|
|
209
|
-
let publicKeyHex = this.#toHexString(publicKey);
|
|
210
|
-
if (this.maxConnectionsReached()) {
|
|
211
|
-
this.#logger.debug('addValidator: max connections reached.');
|
|
212
|
-
return false;
|
|
213
|
-
}
|
|
214
|
-
this.#logger.debug(`addValidator: adding validator ${publicKeyToAddress(publicKeyHex, this.#config)}`);
|
|
215
|
-
if (!this.exists(publicKeyHex)) {
|
|
216
|
-
this.#logger.debug(`addValidator: appending validator ${publicKeyToAddress(publicKeyHex, this.#config)}`);
|
|
217
|
-
this.#append(publicKeyHex, connection);
|
|
218
|
-
return true;
|
|
219
|
-
} else if (!this.connected(publicKeyHex)) {
|
|
220
|
-
this.#logger.debug(`addValidator: updating validator ${publicKeyToAddress(publicKeyHex, this.#config)}`);
|
|
221
|
-
this.#update(publicKeyHex, connection);
|
|
222
|
-
return true;
|
|
223
|
-
}
|
|
224
|
-
this.#logger.debug(`addValidator: didn't add validator ${publicKeyToAddress(publicKeyHex, this.#config)}`);
|
|
225
|
-
return false; // TODO: Implement better success/failure reporting
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
/**
|
|
229
|
-
* Removes a validator from the pool.
|
|
230
|
-
* @param {String | Buffer} publicKey - The public key hex string of the validator to remove
|
|
231
|
-
* @param {object} [options]
|
|
232
|
-
* @param {boolean} [options.endConnection=true] - Whether to close the underlying socket.
|
|
233
|
-
*/
|
|
234
|
-
remove(publicKey, { endConnection = true } = {}) {
|
|
235
|
-
this.#logger.debug(`remove: removing validator ${publicKeyToAddress(publicKey, this.#config)}`);
|
|
236
|
-
const publicKeyHex = this.#toHexString(publicKey);
|
|
237
|
-
this.#stopHealthCheck(publicKeyHex);
|
|
238
|
-
this.#clearHealthCheckFailures(publicKeyHex);
|
|
239
|
-
if (this.exists(publicKeyHex)) {
|
|
240
|
-
const entry = this.#validators.get(publicKeyHex);
|
|
241
|
-
if (endConnection && entry && entry.connection && typeof entry.connection.end === 'function') {
|
|
242
|
-
try {
|
|
243
|
-
entry.connection.end();
|
|
244
|
-
} catch (e) {
|
|
245
|
-
// Ignore errors on connection end
|
|
246
|
-
this.#logger.debug(`remove: failed to end connection: ${e.message}`);
|
|
247
|
-
// TODO: Consider logging these errors here in verbose mode
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
this.#logger.debug(`remove: removing validator from map: ${publicKeyToAddress(publicKeyHex, this.#config)}. Map size before removal: ${this.#validators.size}.`);
|
|
251
|
-
this.#validators.delete(publicKeyHex);
|
|
252
|
-
this.#logger.debug(`remove: validator removed successfully. Map size is now ${this.#validators.size}.`);
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
/**
|
|
257
|
-
* Checks if the maximum number of connections has been reached.
|
|
258
|
-
* @returns {Boolean} - Returns true if the maximum number of connections has been reached, false otherwise.
|
|
259
|
-
*/
|
|
260
|
-
// Note: this function name is a bit misleading. It checks if we have reached max connections and returns boolean
|
|
261
|
-
// The name leads to think it returns the number of max connections
|
|
262
|
-
maxConnectionsReached() {
|
|
263
|
-
return this.connectionCount() >= this.#maxValidators
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
/**
|
|
267
|
-
* Gets a list of all currently connected validators' public keys.
|
|
268
|
-
* @returns {Array} - An array of public key hex strings of connected validators
|
|
269
|
-
*/
|
|
270
|
-
connectedValidators() {
|
|
271
|
-
return Array.from(this.#validators.keys()).filter(pk => this.connected(pk));
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
/**
|
|
275
|
-
* Gets the current number of connected validators.
|
|
276
|
-
* @returns {Number} - The count of connected validators
|
|
277
|
-
*/
|
|
278
|
-
connectionCount() {
|
|
279
|
-
return this.connectedValidators().length;
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
/**
|
|
283
|
-
* Checks if a validator is currently connected.
|
|
284
|
-
* @param {String | Buffer} publicKey - The public key hex string of the validator to check
|
|
285
|
-
* @returns {Boolean} - Returns true if the validator is connected, false otherwise
|
|
286
|
-
*/
|
|
287
|
-
connected(publicKey) {
|
|
288
|
-
const publicKeyHex = this.#toHexString(publicKey);
|
|
289
|
-
return this.exists(publicKeyHex) && this.#validators.get(publicKeyHex).connection !== null;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
/**
|
|
293
|
-
* Checks if a validator exists in the pool.
|
|
294
|
-
* @param {String | Buffer} publicKey - The public key hex string of the validator to check
|
|
295
|
-
* @returns {Boolean} - Returns true if the validator exists, false otherwise
|
|
296
|
-
*/
|
|
297
|
-
exists(publicKey) {
|
|
298
|
-
const publicKeyHex = this.#toHexString(publicKey);
|
|
299
|
-
return this.#validators.has(publicKeyHex);
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
/**
|
|
303
|
-
* Gets the number of messages sent through a validator.
|
|
304
|
-
* @param {String | Buffer} publicKey - The public key hex string of the validator
|
|
305
|
-
* @returns {Number} - The count of messages sent
|
|
306
|
-
*/
|
|
307
|
-
getSentCount(publicKey) {
|
|
308
|
-
const publicKeyHex = this.#toHexString(publicKey);
|
|
309
|
-
const entry = this.#validators.get(publicKeyHex);
|
|
310
|
-
return entry ? (entry.sent || 0) : 0;
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
/**
|
|
314
|
-
* Increments the count of messages sent through a validator.
|
|
315
|
-
* @param {String | Buffer} publicKey - The public key hex string of the validator
|
|
316
|
-
*/
|
|
317
|
-
incrementSentCount(publicKey) {
|
|
318
|
-
const publicKeyHex = this.#toHexString(publicKey);
|
|
319
|
-
const entry = this.#validators.get(publicKeyHex);
|
|
320
|
-
if (entry) {
|
|
321
|
-
entry.sent = (entry.sent || 0) + 1;
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
prettyPrint() {
|
|
326
|
-
this.#logger.info(`Connection count: ${this.connectionCount()}`);
|
|
327
|
-
this.#logger.info(`Validator map keys count: ${this.#validators.size}`);
|
|
328
|
-
this.#logger.info(`Validator map keys:\n${Array.from(this.#validators.entries()).map(([publicKey, val]) => {
|
|
329
|
-
const protocols = val.connection?.protocolSession?.preferredProtocol || 'none';
|
|
330
|
-
return `${publicKeyToAddress(publicKey, this.#config)}: ${protocols}`;
|
|
331
|
-
}).join('\n')}`);
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
/**
|
|
335
|
-
* Picks a random validator from the given array of validator public keys.
|
|
336
|
-
* @param {String[]} validatorPubKeys - An array of validator public key hex strings
|
|
337
|
-
* @returns {String|null} - A randomly selected validator public key
|
|
338
|
-
*/
|
|
339
|
-
pickRandomValidator(validatorPubKeys) {
|
|
340
|
-
if (validatorPubKeys.length === 0) {
|
|
341
|
-
return null;
|
|
342
|
-
}
|
|
343
|
-
const index = Math.floor(Math.random() * validatorPubKeys.length);
|
|
344
|
-
return validatorPubKeys[index];
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
/**
|
|
348
|
-
* Picks a random connected validator.
|
|
349
|
-
* @returns {String|null} - A randomly selected connected validator public key, or null if none are connected
|
|
350
|
-
*/
|
|
351
|
-
pickRandomConnectedValidator() {
|
|
352
|
-
const connected = this.connectedValidators();
|
|
353
|
-
if (connected.length === 0) return null;
|
|
354
|
-
return this.pickRandomValidator(connected);
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
/**
|
|
358
|
-
* Appends a new validator connection.
|
|
359
|
-
* @param {String|Buffer} publicKey - The public key hex string of the validator
|
|
360
|
-
* @param {Object} connection - The connection object
|
|
361
|
-
*/
|
|
362
|
-
#append(publicKey, connection) {
|
|
363
|
-
this.#logger.debug(`#append: appending validator ${publicKeyToAddress(publicKey, this.#config)}`);
|
|
364
|
-
const publicKeyHex = this.#toHexString(publicKey);
|
|
365
|
-
if (this.#validators.has(publicKeyHex)) {
|
|
366
|
-
// This should never happen, but just in case, we log it
|
|
367
|
-
this.#logger.debug(`#append: tried to append existing validator: ${publicKeyToAddress(publicKey, this.#config)}`);
|
|
368
|
-
return;
|
|
369
|
-
}
|
|
370
|
-
this.#validators.set(publicKeyHex, {connection, sent: 0});
|
|
371
|
-
this.#clearHealthCheckFailures(publicKeyHex);
|
|
372
|
-
connection.on('close', () => {
|
|
373
|
-
this.#logger.debug(`#append: connection closing for validator ${publicKeyToAddress(publicKey, this.#config)}`);
|
|
374
|
-
// only act on the connection this event belongs to
|
|
375
|
-
if (!this.isCurrent(publicKeyHex, connection)) return;
|
|
376
|
-
this.remove(publicKeyHex);
|
|
377
|
-
this.#logger.debug(`#append: connection closed for validator ${publicKeyToAddress(publicKey, this.#config)}`);
|
|
378
|
-
});
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
/**
|
|
382
|
-
* Updates an existing validator connection or adds it if not present.
|
|
383
|
-
* @param {String|Buffer} publicKey - The public key hex string of the validator
|
|
384
|
-
* @param {Object} connection - The connection object
|
|
385
|
-
*/
|
|
386
|
-
#update(publicKey, connection) {
|
|
387
|
-
// Note: Is there a good reason for the function 'update' to exist separately from 'append'?
|
|
388
|
-
// It seems that both could be merged into a single function that either adds or updates the entry.
|
|
389
|
-
// It would be preferable to keep them separated though, but we would need to review all usages to ensure correctness.
|
|
390
|
-
// Also, we should remove the 'else' branch below if we decide to keep 'update' and 'append' separated.
|
|
391
|
-
const publicKeyHex = this.#toHexString(publicKey);
|
|
392
|
-
this.#logger.debug(`#update: updating validator ${publicKeyToAddress(publicKey, this.#config)}`);
|
|
393
|
-
if (this.#validators.has(publicKeyHex)) {
|
|
394
|
-
this.#validators.get(publicKeyHex).connection = connection;
|
|
395
|
-
} else {
|
|
396
|
-
this.#validators.set(publicKeyHex, {connection, sent: 0});
|
|
397
|
-
}
|
|
398
|
-
this.#clearHealthCheckFailures(publicKeyHex);
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
#toHexString(publicKey) {
|
|
402
|
-
return b4a.isBuffer(publicKey) ? publicKey.toString('hex') : publicKey;
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
export default ConnectionManager;
|