trac-msb 0.2.26 → 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.
Files changed (136) hide show
  1. package/SECURITY.md +2 -2
  2. package/cli/commandHandler.js +317 -20
  3. package/cli/handlers.js +9 -1
  4. package/cli/interactive.js +4 -3
  5. package/cli/tokenSupplyAudit.js +90 -0
  6. package/msb.mjs +1 -0
  7. package/package.json +15 -6
  8. package/rpc/create_server.js +12 -3
  9. package/rpc/handlers.js +103 -0
  10. package/rpc/routes/v1.js +8 -0
  11. package/src/codecs/apply/applyOperationCodec.js +334 -0
  12. package/src/codecs/apply/applyOperations.generated.cjs +6380 -0
  13. package/src/codecs/consensus/v1/consensusV1.generated.cjs +3508 -0
  14. package/src/codecs/consensus/v1/consensusV1OperationCodec.js +161 -0
  15. package/src/codecs/consensus/v1/vdfConfigCodec.js +83 -0
  16. package/src/{utils/protobuf → codecs/network/v1}/networkV1.generated.cjs +664 -393
  17. package/src/codecs/network/v1/networkV1OperationCodec.js +25 -0
  18. package/src/codecs/protobufRuntime.cjs +95 -0
  19. package/src/codecs/state/escrowEntryCodec.js +45 -0
  20. package/src/codecs/state/state.generated.cjs +659 -0
  21. package/src/config/args.js +0 -1
  22. package/src/config/config.js +41 -7
  23. package/src/config/env.js +33 -9
  24. package/src/core/consensus/epochAppenderSelection/EpochAppenderSelectionError.js +55 -0
  25. package/src/core/consensus/epochAppenderSelection/EpochAppenderSelectionOperations.js +118 -0
  26. package/src/core/consensus/epochAppenderSelection/EpochAppenderSelector.js +1042 -0
  27. package/src/core/consensus/epochAppenderSelection/createEpochAppenderSelector.js +23 -0
  28. package/src/core/consensus/epochAppenderSelection/epochAppenderSelectionLedgerReader.js +274 -0
  29. package/src/core/consensus/epochAppenderSelection/epochAppenderSelectionState.js +488 -0
  30. package/src/core/consensus/epochAppenderSelection/epochAppenderSelectionStorage.js +93 -0
  31. package/src/core/consensus/quorum.js +11 -0
  32. package/src/core/consensus/services/EpochCoordinationRound.js +839 -0
  33. package/src/core/consensus/services/EpochCoordinatorOperations.js +211 -0
  34. package/src/core/consensus/services/EpochCoordinatorService.js +335 -0
  35. package/src/core/consensus/services/EpochRoundListeners.js +116 -0
  36. package/src/core/consensus/services/EpochStateMachine.js +141 -0
  37. package/src/core/consensus/services/VDFBare.js +291 -0
  38. package/src/core/consensus/services/VDFNode.js +111 -0
  39. package/src/core/consensus/services/VDFProgressWatchdog.js +79 -0
  40. package/src/core/consensus/services/VDFService.js +87 -0
  41. package/src/core/consensus/services/VDFServiceManager.js +147 -0
  42. package/src/core/consensus/services/createVDFService.js +16 -0
  43. package/src/core/consensus/services/vdf-guardian.js +32 -0
  44. package/src/core/consensus/services/vdf-worker.js +90 -0
  45. package/src/core/network/Network.js +327 -238
  46. package/src/core/network/protocols/consensus/IndexerMessages.js +72 -0
  47. package/src/core/network/protocols/consensus/v1/ConsensusV1ConnectionPolicy.js +57 -0
  48. package/src/core/network/protocols/consensus/v1/ConsensusV1MessageRouter.js +147 -0
  49. package/src/core/network/protocols/consensus/v1/ConsensusV1Protocol.js +72 -0
  50. package/src/core/network/protocols/consensus/v1/IndexerPendingRequestService.js +108 -0
  51. package/src/core/network/protocols/consensus/v1/V1ConsensusProtocolError.js +29 -0
  52. package/src/core/network/protocols/consensus/v1/handlers/V1EpochAppendOperationHandler.js +68 -0
  53. package/src/core/network/protocols/consensus/v1/handlers/V1EpochProofProposalOperationHandler.js +226 -0
  54. package/src/core/network/protocols/consensus/v1/validators/ConsensusValidationSchema.js +192 -0
  55. package/src/core/network/protocols/consensus/v1/validators/V1BaseConsensusOperation.js +428 -0
  56. package/src/core/network/protocols/consensus/v1/validators/V1EpochAppendReply.js +47 -0
  57. package/src/core/network/protocols/consensus/v1/validators/V1EpochAppendRequest.js +17 -0
  58. package/src/core/network/protocols/consensus/v1/validators/V1EpochProofProposalApproval.js +116 -0
  59. package/src/core/network/protocols/consensus/v1/validators/V1EpochProofProposalRequest.js +152 -0
  60. package/src/core/network/protocols/consensus/v1/validators/consensusValidationSchemaHelpers.js +145 -0
  61. package/src/core/network/protocols/shared/ConnectionOperationHandler.js +27 -0
  62. package/src/core/network/protocols/{NetworkMessages.js → validators/ValidatorMessages.js} +24 -14
  63. package/src/core/network/protocols/{ProtocolSession.js → validators/ValidatorProtocolSession.js} +22 -25
  64. package/src/core/network/protocols/{legacy/NetworkMessageRouter.js → validators/legacy/ValidatorLegacyMessageRouter.js} +6 -6
  65. package/src/core/network/protocols/{LegacyProtocol.js → validators/legacy/ValidatorLegacyProtocol.js} +9 -7
  66. package/src/core/network/protocols/{legacy → validators/legacy}/handlers/BaseStateOperationHandler.js +1 -1
  67. package/src/core/network/protocols/{legacy → validators/legacy}/handlers/LegacyGetRequestHandler.js +5 -5
  68. package/src/core/network/protocols/{legacy → validators/legacy}/handlers/LegacyRoleOperationHandler.js +5 -5
  69. package/src/core/network/protocols/{legacy → validators/legacy}/handlers/LegacySubnetworkOperationHandler.js +5 -5
  70. package/src/core/network/protocols/{legacy → validators/legacy}/handlers/LegacyTransferOperationHandler.js +5 -5
  71. package/src/core/network/protocols/{ProtocolInterface.js → validators/shared/ValidatorProtocolInterface.js} +5 -5
  72. package/src/core/network/protocols/{shared → validators/shared}/validators/PartialBootstrapDeploymentValidator.js +1 -1
  73. package/src/core/network/protocols/validators/shared/validators/PartialHtlcValidator.js +325 -0
  74. package/src/core/network/protocols/{shared → validators/shared}/validators/PartialOperationValidator.js +44 -21
  75. package/src/core/network/protocols/{shared → validators/shared}/validators/PartialRoleAccessValidator.js +3 -3
  76. package/src/core/network/protocols/{shared → validators/shared}/validators/PartialTransactionValidator.js +3 -3
  77. package/src/core/network/protocols/{shared → validators/shared}/validators/PartialTransferValidator.js +4 -4
  78. package/src/core/network/protocols/{v1 → validators/v1}/V1ProtocolError.js +1 -1
  79. package/src/core/network/{services/PendingRequestService.js → protocols/validators/v1/ValidatorPendingRequestService.js} +19 -82
  80. package/src/core/network/protocols/{v1/NetworkMessageRouter.js → validators/v1/ValidatorV1MessageRouter.js} +9 -9
  81. package/src/core/network/protocols/{V1Protocol.js → validators/v1/ValidatorV1Protocol.js} +10 -8
  82. package/src/core/network/protocols/{connectionPolicies.js → validators/v1/connectionPolicies.js} +1 -1
  83. package/src/core/network/protocols/{v1 → validators/v1}/handlers/V1BaseOperationHandler.js +6 -23
  84. package/src/core/network/protocols/{v1 → validators/v1}/handlers/V1BroadcastTransactionOperationHandler.js +14 -9
  85. package/src/core/network/protocols/{v1 → validators/v1}/handlers/V1LivenessOperationHandler.js +5 -4
  86. package/src/core/network/protocols/{v1 → validators/v1}/handlers/broadcastTransaction/BroadcastTransactionErrorMapper.js +3 -3
  87. package/src/core/network/protocols/{v1 → validators/v1}/handlers/broadcastTransaction/BroadcastTransactionOperationStrategies.js +44 -1
  88. package/src/core/network/protocols/{v1 → validators/v1}/handlers/broadcastTransaction/BroadcastTransactionPayloadUtils.js +1 -1
  89. package/src/core/network/protocols/{v1 → validators/v1}/validators/V1BaseOperation.js +19 -15
  90. package/src/core/network/protocols/{v1 → validators/v1}/validators/V1BroadcastTransactionRequest.js +2 -2
  91. package/src/core/network/protocols/{v1 → validators/v1}/validators/V1BroadcastTransactionResponse.js +34 -18
  92. package/src/core/network/protocols/{v1 → validators/v1}/validators/V1ValidationSchema.js +3 -3
  93. package/src/core/network/services/IndexerConnectionManager.js +88 -0
  94. package/src/core/network/services/TransactionPoolService.js +11 -18
  95. package/src/core/network/services/TransactionRateLimiterService.js +1 -1
  96. package/src/core/network/services/ValidatorConnectionManager.js +163 -0
  97. package/src/core/network/services/{MessageOrchestrator.js → ValidatorMessageOrchestrator.js} +34 -42
  98. package/src/core/network/services/ValidatorObserverService.js +21 -36
  99. package/src/core/network/shared/BasePendingRequestService.js +67 -0
  100. package/src/core/network/shared/PeerConnectionManager.js +185 -0
  101. package/src/core/state/State.js +1789 -173
  102. package/src/core/state/StateEventQueue.js +69 -0
  103. package/src/core/state/utils/address.js +10 -6
  104. package/src/core/state/utils/epochProof.js +109 -0
  105. package/src/core/state/utils/escrowEntry.js +178 -0
  106. package/src/core/state/validators/StateValidationSchema.js +757 -0
  107. package/src/core/state/validators/stateValidationRules.js +312 -0
  108. package/src/index.js +255 -65
  109. package/src/messages/consensus/v1/ConsensusMessageBuilder.js +368 -0
  110. package/src/messages/consensus/v1/ConsensusMessageDirector.js +142 -0
  111. package/src/messages/consensus/v1/consensusMessageFactory.js +12 -0
  112. package/src/messages/consensus/v1/epochAppendMessage.js +58 -0
  113. package/src/messages/network/v1/NetworkMessageBuilder.js +15 -7
  114. package/src/messages/state/ApplyStateMessageBuilder.js +499 -13
  115. package/src/messages/state/ApplyStateMessageDirector.js +261 -2
  116. package/src/utils/StateMachine.js +125 -0
  117. package/src/utils/applyOperations.js +45 -2
  118. package/src/utils/buffer.js +239 -59
  119. package/src/utils/consensusConfig.js +76 -0
  120. package/src/utils/constants.js +174 -73
  121. package/src/utils/helpers.js +0 -25
  122. package/src/utils/htlcLock.js +122 -0
  123. package/src/utils/logger.js +10 -4
  124. package/src/utils/migrationUtils.js +2 -1
  125. package/src/utils/normalizers.js +113 -0
  126. package/src/utils/scheduler/SchedulableService.js +107 -0
  127. package/src/utils/{Scheduler.js → scheduler/Scheduler.js} +56 -17
  128. package/src/core/network/services/ConnectionManager.js +0 -406
  129. package/src/utils/check.js +0 -525
  130. package/src/utils/protobuf/applyOperations.cjs +0 -1373
  131. package/src/utils/protobuf/operationHelpers.js +0 -81
  132. /package/src/core/network/protocols/{legacy → validators/legacy}/handlers/LegacyResponseHandler.js +0 -0
  133. /package/src/core/network/protocols/{legacy → validators/legacy}/validators/ValidatorResponse.js +0 -0
  134. /package/src/core/network/protocols/{legacy → validators/legacy}/validators/base/BaseResponse.js +0 -0
  135. /package/src/core/network/protocols/{v1 → validators/v1}/validators/V1LivenessRequest.js +0 -0
  136. /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 null;
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
- let nextDelay = null;
87
+
88
+ const hold = () => {
89
+ scheduleCalled = true;
90
+ }
65
91
 
66
92
  const scheduleNext = (ms) => {
67
93
  scheduleCalled = true;
68
- nextDelay = Scheduler.#validateDelay(ms, 'scheduleNext delayMs');
94
+ this.#next(Scheduler.#validateDelay(ms, 'scheduleNext delayMs'));
69
95
  };
70
96
 
71
- this.#currentWorkerRun = this.#worker(scheduleNext);
97
+ const workerRun = this.#worker(scheduleNext, hold);
98
+ this.#currentWorkerRun = workerRun;
72
99
  try {
73
- await this.#currentWorkerRun; // this await is needed here because #worker can be async
100
+ await workerRun;
74
101
  } catch (error) {
75
102
  console.error('Worker error:', error);
76
- return this.defaultInterval;
103
+ this.#next(this.defaultInterval);
104
+ return;
77
105
  } finally {
78
- this.#currentWorkerRun = null;
106
+ if (this.#currentWorkerRun === workerRun) {
107
+ this.#currentWorkerRun = null;
108
+ }
79
109
  }
80
110
 
81
- return scheduleCalled ? nextDelay : this.defaultInterval;
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 = setTimeout(async () => {
88
- const nextDelay = await this.run();
89
- if (this.isRunning) {
90
- this.#next(nextDelay);
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; // this await is needed here because #worker can be async
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;