trac-msb 0.2.26 → 0.3.0-beta.2

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 +121 -0
  10. package/rpc/routes/v1.js +12 -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 +1803 -159
  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 +280 -67
  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
@@ -4,15 +4,15 @@ import { networkMessageFactory } from "../../../messages/network/v1/networkMessa
4
4
  import { NETWORK_CAPABILITIES, ResultCode } from "../../../utils/constants.js";
5
5
  import {
6
6
  unsafeEncodeApplyOperation
7
- } from "../../../utils/protobuf/operationHelpers.js";
7
+ } from "../../../codecs/apply/applyOperationCodec.js";
8
8
  import { normalizeMessageByOperationType } from "../../../utils/normalizers.js";
9
- import { resultToValidatorAction, SENDER_ACTION } from "../protocols/connectionPolicies.js";
10
- import { ConnectionManagerError } from './ConnectionManager.js';
9
+ import { resultToValidatorAction, SENDER_ACTION } from "../protocols/validators/v1/connectionPolicies.js";
10
+ import { isPeerConnectionError } from '../shared/PeerConnectionManager.js';
11
11
  /**
12
- * MessageOrchestrator coordinates message submission, retry, and validator management.
13
- * It works with ConnectionManager and ledger state to ensure reliable message delivery.
12
+ * ValidatorMessageOrchestrator coordinates message submission, retry, and validator management.
13
+ * It works with ValidatorConnectionManager and ledger state to ensure reliable message delivery.
14
14
  */
15
- class MessageOrchestrator {
15
+ class ValidatorMessageOrchestrator {
16
16
  #config;
17
17
  #wallet;
18
18
  #idempotentSuccessCodes = new Set([
@@ -21,18 +21,14 @@ class MessageOrchestrator {
21
21
  ]);
22
22
  /**
23
23
  * Attempts to send a message to validators with retries and state checks.
24
- * @param {ConnectionManager} connectionManager - The connection manager instance
24
+ * @param {ValidatorConnectionManager} validatorConnectionManager - The connection manager instance
25
25
  * @param {object} state - The state to look for the message outcome
26
26
  * @param {Config} config - Configuration options:
27
27
  */
28
- constructor(connectionManager, state, config) {
29
- this.connectionManager = connectionManager;
28
+ constructor(validatorConnectionManager, state, config, wallet) {
29
+ this.validatorConnectionManager = validatorConnectionManager;
30
30
  this.state = state;
31
31
  this.#config = config;
32
- this.#wallet = null;
33
- }
34
-
35
- setWallet(wallet) {
36
32
  this.#wallet = wallet;
37
33
  }
38
34
 
@@ -50,11 +46,10 @@ class MessageOrchestrator {
50
46
  */
51
47
  #pickValidatorForMessage(message) {
52
48
  const requesterAddress = message?.address;
53
- if (!requesterAddress || typeof this.connectionManager.connectedValidators !== 'function') {
54
- return this.connectionManager.pickRandomConnectedValidator();
55
- }
56
49
 
57
- const connected = this.connectionManager.connectedValidators();
50
+ if (!requesterAddress) return null
51
+
52
+ const connected = this.validatorConnectionManager.connectedPeers();
58
53
  if (!Array.isArray(connected) || connected.length === 0) {
59
54
  return null;
60
55
  }
@@ -64,9 +59,6 @@ class MessageOrchestrator {
64
59
  });
65
60
 
66
61
  const pool = eligible.length > 0 ? eligible : connected;
67
- if (typeof this.connectionManager.pickRandomValidator === 'function') {
68
- return this.connectionManager.pickRandomValidator(pool);
69
- }
70
62
 
71
63
  const index = Math.floor(Math.random() * pool.length);
72
64
  return pool[index] ?? null;
@@ -80,7 +72,7 @@ class MessageOrchestrator {
80
72
  */
81
73
  async send(message, retries = 0) {
82
74
  if (retries > this.#config.maxRetries) {
83
- console.warn(`MessageOrchestrator: Max retries reached for message ${JSON.stringify(message)}. Aborting send.`);
75
+ console.warn(`ValidatorMessageOrchestrator: Max retries reached for message ${JSON.stringify(message)}. Aborting send.`);
84
76
  return false;
85
77
  }
86
78
 
@@ -89,19 +81,19 @@ class MessageOrchestrator {
89
81
  console.log("Sending message to validator:", publicKeyToAddress(validatorPublicKey, this.#config));
90
82
 
91
83
  /* NOTE: Since the retry logic for Legacy is handled here, and is very unique to the protocol,
92
- * it was decided to not change MessageOrchestrator send method in the refactor to make protocols transparent.
84
+ * it was decided to not change ValidatorMessageOrchestrator send method in the refactor to make protocols transparent.
93
85
  * As the Legacy protocol is going to be deprecated soon, it was decided to keep the retry logic
94
86
  * here instead of abstracting it in the protocol implementation.
95
- * If we were to abstract it, we would need to add protocol-specific logic in the ProtocolSession
96
- * or ProtocolInterface, which would make them less clean and more coupled with the specifics of the protocols.
87
+ * If we were to abstract it, we would need to add protocol-specific logic in the ValidatorProtocolSession
88
+ * or ValidatorProtocolInterface, which would make them less clean and more coupled with the specifics of the protocols.
97
89
  * The parts to be refactored in the future are marked with TODO comments.
98
90
  */
99
91
 
100
92
  // TODO: After Legacy is deprecated, we don't need to check preferred protocol here.
101
- const validatorConnection = this.connectionManager.getConnection(validatorPublicKey);
102
- const preferredProtocol = validatorConnection.protocolSession.preferredProtocol;
93
+ const validatorConnection = this.validatorConnectionManager.getConnection(validatorPublicKey);
94
+ const preferredProtocol = validatorConnection.protocolSessions.validator.preferredProtocol;
103
95
  let success = false;
104
- if (preferredProtocol === validatorConnection.protocolSession.supportedProtocols.LEGACY) {
96
+ if (preferredProtocol === validatorConnection.protocolSessions.validator.supportedProtocols.LEGACY) {
105
97
 
106
98
  try {
107
99
  success = await this.#attemptSendMessageForLegacy(validatorPublicKey, message);
@@ -110,10 +102,10 @@ class MessageOrchestrator {
110
102
  }
111
103
  if (!success) {
112
104
  // Remove validator and retry
113
- this.connectionManager.remove(validatorPublicKey);
105
+ this.validatorConnectionManager.remove(validatorPublicKey);
114
106
  success = await this.send(message, retries + 1);
115
107
  }
116
- } else if (preferredProtocol === validatorConnection.protocolSession.supportedProtocols.V1) {
108
+ } else if (preferredProtocol === validatorConnection.protocolSessions.validator.supportedProtocols.V1) {
117
109
  // TODO: This is probably better placed inside the V1 protocol definition.
118
110
  // Both protocols should receive a 'canonical' message and solve the encodings internally
119
111
  // Refactor
@@ -126,7 +118,7 @@ class MessageOrchestrator {
126
118
  NETWORK_CAPABILITIES
127
119
  );
128
120
 
129
- await this.connectionManager.sendSingleMessage(v1Message, validatorPublicKey)
121
+ await this.validatorConnectionManager.sendSingleMessage(v1Message, validatorPublicKey)
130
122
  .then(
131
123
  async (resultCode) => {
132
124
  if (await this.#isIdempotentSuccess(resultCode, message)) {
@@ -142,19 +134,19 @@ class MessageOrchestrator {
142
134
  //TODO: Create a function for action below, and replace it also in legacy flow.
143
135
  this.incrementSentCount(validatorPublicKey);
144
136
  if (this.shouldRemove(validatorPublicKey)) {
145
- this.connectionManager.remove(validatorPublicKey);
137
+ this.validatorConnectionManager.remove(validatorPublicKey);
146
138
  }
147
139
  break;
148
140
  case SENDER_ACTION.ROTATE:
149
- this.connectionManager.remove(validatorPublicKey);
141
+ this.validatorConnectionManager.remove(validatorPublicKey);
150
142
  break;
151
143
  case SENDER_ACTION.NO_ROTATE:
152
144
  // ignore
153
145
  break;
154
146
  default:
155
- this.connectionManager.remove(validatorPublicKey);
147
+ this.validatorConnectionManager.remove(validatorPublicKey);
156
148
  console.warn(
157
- `MessageOrchestrator: Unrecognized action from connectionPolicies: ${action}.
149
+ `ValidatorMessageOrchestrator: Unrecognized action from connectionPolicies: ${action}.
158
150
  ResultCode was: ${resultCode}. Removing validator ${publicKeyToAddress(validatorPublicKey, this.#config)}`
159
151
  );
160
152
  break;
@@ -163,11 +155,11 @@ class MessageOrchestrator {
163
155
  )
164
156
  .catch(
165
157
  async (err) => {
166
- if (err instanceof ConnectionManagerError) {
158
+ if (isPeerConnectionError(err)) {
167
159
  success = await this.send(message, retries + 1);
168
- console.warn(`MessageOrchestrator: Connection Error: ${err.message}`);
160
+ console.warn(`ValidatorMessageOrchestrator: Connection Error: ${err.message}`);
169
161
  } else {
170
- this.connectionManager.remove(validatorPublicKey);
162
+ this.validatorConnectionManager.remove(validatorPublicKey);
171
163
  success = await this.send(message, retries + 1);
172
164
  }
173
165
  }
@@ -216,7 +208,7 @@ class MessageOrchestrator {
216
208
  // TODO: Delete this function after legacy protocol is deprecated
217
209
  async #attemptSendMessageForLegacy(validatorPublicKey, message) {
218
210
  const deductedTxType = operationToPayload(message.type);
219
- await this.connectionManager.sendSingleMessage(message, validatorPublicKey);
211
+ await this.validatorConnectionManager.sendSingleMessage(message, validatorPublicKey);
220
212
  const appeared = await this.state.waitForUnsigned(
221
213
  message[deductedTxType].tx,
222
214
  this.#config.messageValidatorResponseTimeout
@@ -224,7 +216,7 @@ class MessageOrchestrator {
224
216
  if (appeared) {
225
217
  this.incrementSentCount(validatorPublicKey);
226
218
  if (this.shouldRemove(validatorPublicKey)) {
227
- this.connectionManager.remove(validatorPublicKey);
219
+ this.validatorConnectionManager.remove(validatorPublicKey);
228
220
  }
229
221
  return true;
230
222
  }
@@ -232,12 +224,12 @@ class MessageOrchestrator {
232
224
  }
233
225
 
234
226
  incrementSentCount(validatorPubKey) {
235
- this.connectionManager.incrementSentCount(validatorPubKey);
227
+ this.validatorConnectionManager.incrementSentCount(validatorPubKey);
236
228
  }
237
229
 
238
230
  shouldRemove(validatorPubKey) {
239
- return this.connectionManager.getSentCount(validatorPubKey) >= this.#config.messageThreshold;
231
+ return this.validatorConnectionManager.getSentCount(validatorPubKey) >= this.#config.messageThreshold;
240
232
  }
241
233
  }
242
234
 
243
- export default MessageOrchestrator;
235
+ export default ValidatorMessageOrchestrator;
@@ -1,4 +1,4 @@
1
- import Scheduler from "../../../utils/Scheduler.js";
1
+ import SchedulableService from "../../../utils/scheduler/SchedulableService.js";
2
2
  import { Logger } from "../../../utils/logger.js";
3
3
  import b4a from "b4a";
4
4
  import { bufferToAddress } from "../../../core/state/utils/address.js";
@@ -9,18 +9,16 @@ import { WRITER_BYTE_LENGTH, CONNECTION_STATUS } from "../../../utils/constants.
9
9
  const VALIDATOR_CANDIDATES_PER_CYCLE = 10;
10
10
  const MAX_KEY_DECODE_CACHE_SIZE = 10_000;
11
11
 
12
- class ValidatorObserverService {
12
+ class ValidatorObserverService extends SchedulableService {
13
13
  #network;
14
14
  #state;
15
15
  #address;
16
16
  #config;
17
- #scheduler;
18
17
  #logger;
19
18
  #hasBootstrapped; // Whether initial connection phase is complete
20
19
  #bootstrapStartedAt; // Timestamp to enforce bootstrap timeout
21
20
  #adminCache; // Cached admin entry with TTL
22
21
  #writersCache; // Cached writer list with dynamic TTL
23
- #isInterrupted; // Used to stop execution mid-cycle
24
22
  #keyDecodeCache;
25
23
 
26
24
  /**
@@ -30,6 +28,7 @@ class ValidatorObserverService {
30
28
  * @param {Object} config - The application configuration object.
31
29
  */
32
30
  constructor(network, state, address, config) {
31
+ super();
33
32
  this.#network = network;
34
33
  this.#state = state;
35
34
  this.#address = address;
@@ -39,18 +38,9 @@ class ValidatorObserverService {
39
38
  this.#bootstrapStartedAt = 0;
40
39
  this.#adminCache = { entry: null, lastUpdated: 0 };
41
40
  this.#writersCache = { list: [], lastUpdated: 0 };
42
- this.#isInterrupted = false;
43
41
  this.#keyDecodeCache = new Map();
44
42
  }
45
43
 
46
- /**
47
- * Returns configurable poll interval.
48
- * @returns {number} The poll interval in milliseconds.
49
- */
50
- #getPollInterval() {
51
- return this.#config.pollInterval;
52
- }
53
-
54
44
  /**
55
45
  * Selects a subset of valid validator candidates to connect to.
56
46
  *
@@ -79,7 +69,7 @@ class ValidatorObserverService {
79
69
  const target = Math.min(networkSize, this.#config.maxValidators);
80
70
 
81
71
  const isSelfAdmin = this.#address === adminEntry?.address;
82
- const totalConnected = manager.connectedValidators().length;
72
+ const totalConnected = manager.connectionCount();
83
73
 
84
74
  const available = writers.filter((w) => {
85
75
  if (w.address === this.#address) return false;
@@ -144,7 +134,7 @@ class ValidatorObserverService {
144
134
  // Non-admin nodes: ensure the admin is not kept as a validator connection
145
135
  const adminWriter = writers.find((w) => w.address === adminEntry?.address);
146
136
  if (adminWriter?.publicKey) {
147
- this.#network.validatorConnectionManager.remove(adminWriter.publicKey, { endConnection: false });
137
+ this.#network.validatorConnectionManager.remove(adminWriter.publicKey);
148
138
  }
149
139
  }
150
140
 
@@ -157,7 +147,7 @@ class ValidatorObserverService {
157
147
 
158
148
  if (manager.connected(publicKey)) {
159
149
  // leave the transport to the swarm
160
- manager.remove(publicKey, { endConnection: false });
150
+ manager.remove(publicKey);
161
151
  this.#logger.debug(`Removed stale validator connection: ${b4a.toString(publicKey, "hex")}`);
162
152
  }
163
153
  }
@@ -168,7 +158,7 @@ class ValidatorObserverService {
168
158
  * @returns {boolean} True if the observer is enabled and not interrupted.
169
159
  */
170
160
  #shouldRun() {
171
- return this.#config.enableValidatorObserver && !this.#isInterrupted;
161
+ return !this.isInterrupted;
172
162
  }
173
163
 
174
164
  /**
@@ -214,18 +204,10 @@ class ValidatorObserverService {
214
204
  this.#logger.info('ValidatorObserverService can not start. Disabled by configuration.');
215
205
  return;
216
206
  }
217
-
218
- if (this.#scheduler && this.#scheduler.isRunning) {
219
- this.#logger.info('ValidatorObserverService is already started');
220
- return;
221
- }
222
207
 
223
- const interval = this.#getPollInterval();
224
- this.#scheduler = new Scheduler((next) => this.#worker(next), interval);
225
208
  this.#bootstrapStartedAt = Date.now();
226
209
  this.#hasBootstrapped = false;
227
- this.#isInterrupted = false;
228
- this.#scheduler.start();
210
+ super.start();
229
211
  this.#logger.debug("ValidatorObserverService started");
230
212
  }
231
213
 
@@ -234,13 +216,9 @@ class ValidatorObserverService {
234
216
  * Prevents new work and waits for the current cycle to finish if requested.
235
217
  * @param {boolean} [waitForCurrent=true] - Whether to wait for the current worker cycle to finish.
236
218
  */
237
- async stopValidatorObserver(waitForCurrent = true) {
238
- if (!this.#scheduler) return;
239
-
240
- this.#isInterrupted = true;
241
-
242
- await this.#scheduler.stop(waitForCurrent);
243
- this.#scheduler = null;
219
+ async stop(waitForCurrent = true) {
220
+ const stopped = await super.stop(waitForCurrent);
221
+ if (!stopped) return;
244
222
  this.#logger.debug("ValidatorObserverService stopped");
245
223
  }
246
224
 
@@ -255,9 +233,16 @@ class ValidatorObserverService {
255
233
  * 5. Attempt connections (with pacing).
256
234
  * @param {Function} next - Callback to schedule the next execution.
257
235
  */
258
- async #worker(next) {
259
- const interval = this.#getPollInterval();
260
- if (!this.#shouldRun()) return next(interval);
236
+ getScheduleInterval() {
237
+ return this.#config.pollInterval;
238
+ }
239
+
240
+ async worker(next) {
241
+ const interval = this.#config.pollInterval;
242
+ if (!this.#shouldRun()) {
243
+ next(interval);
244
+ return;
245
+ }
261
246
 
262
247
  try {
263
248
  const adminEntry = await this.#getAdminEntryCached();
@@ -0,0 +1,67 @@
1
+ export default class BasePendingRequestService {
2
+ _pendingRequests = new Map();
3
+
4
+ has(id) {
5
+ return this._pendingRequests.has(id);
6
+ }
7
+
8
+ getAndDeletePendingRequest(id) {
9
+ const entry = this._pendingRequests.get(id);
10
+ if (!entry) return null;
11
+
12
+ clearTimeout(entry.timeoutId);
13
+ this._pendingRequests.delete(id);
14
+ return entry;
15
+ }
16
+
17
+ getPendingRequest(id) {
18
+ const entry = this._pendingRequests.get(id);
19
+ if (!entry) return null;
20
+ return entry;
21
+ }
22
+
23
+ rejectPendingRequest(id, error) {
24
+ const entry = this.getAndDeletePendingRequest(id);
25
+ if (!entry) return false;
26
+ entry.reject(error instanceof Error ? error : new Error(error?.message ?? 'Unexpected error'));
27
+ return true;
28
+ }
29
+
30
+ rejectPendingRequestsForPeer(peerPubKeyHex, error) {
31
+ const idsToReject = [];
32
+ for (const [id, entry] of this._pendingRequests) {
33
+ if (entry.requestedTo === peerPubKeyHex) idsToReject.push(id);
34
+ }
35
+
36
+ for (const id of idsToReject) {
37
+ this.rejectPendingRequest(id, error);
38
+ }
39
+
40
+ return idsToReject.length;
41
+ }
42
+
43
+ stopPendingRequestTimeout(id) {
44
+ const entry = this._pendingRequests.get(id);
45
+ if (!entry) return false;
46
+
47
+ clearTimeout(entry.timeoutId);
48
+ entry.timeoutId = null;
49
+ return true;
50
+ }
51
+
52
+ close() {
53
+ for (const [id, entry] of this._pendingRequests) {
54
+ clearTimeout(entry.timeoutId);
55
+ try {
56
+ entry.reject(this._createShutdownError(id));
57
+ } catch (error) {
58
+ console.error(`${this.constructor.name}.close: failed to reject pending request ${id}:`, error);
59
+ }
60
+ }
61
+ this._pendingRequests.clear();
62
+ }
63
+
64
+ _createShutdownError(id) {
65
+ return new Error(`Pending request ${id} cancelled (shutdown).`);
66
+ }
67
+ }
@@ -0,0 +1,185 @@
1
+ import ReadyResource from "ready-resource"
2
+ import { toHex } from "../../../utils/buffer.js";
3
+ import { publicKeyToAddress } from "../../../utils/helpers.js";
4
+
5
+ export class PeerConnectionManagerError extends Error {
6
+ constructor(message) {
7
+ super(message);
8
+ this.name = this.constructor.name;
9
+ }
10
+ }
11
+
12
+ export class PeerConnectionManager extends ReadyResource {
13
+ _connections = new Map();
14
+ _max;
15
+
16
+ constructor(max, config, logger) {
17
+ super();
18
+ this._max = max;
19
+ this._config = config;
20
+ this._logger = logger
21
+ }
22
+
23
+
24
+ setMax(maxPeers) {
25
+ this._max = maxPeers;
26
+ }
27
+
28
+ _toHexString(publicKey) {
29
+ return toHex(publicKey);
30
+ }
31
+
32
+ /**
33
+ * Adds a peer to the pool if not already present.
34
+ * @param {String | Buffer} publicKey - The public key hex string of the peer to add
35
+ * @param {Object} connection - The connection object associated with the peer
36
+ * @returns {Boolean} - Returns true if the peer was added or updated, false otherwise
37
+ */
38
+ _add(publicKey, connection) {
39
+ const publicKeyHex = this._toHexString(publicKey);
40
+ if (this.maxConnectionsReached()) {
41
+ this._logger.debug('add: max connections reached.');
42
+ return false;
43
+ }
44
+ this._logger.debug(`add: adding peer ${publicKeyToAddress(publicKeyHex, this._config)}`);
45
+ if (!this.exists(publicKeyHex)) {
46
+ this._logger.debug(`add: appending peer ${publicKeyToAddress(publicKeyHex, this._config)}`);
47
+ this.#append(publicKeyHex, connection);
48
+ return true;
49
+ } else if (!this.connected(publicKeyHex)) {
50
+ this._logger.debug(`add: updating peer ${publicKeyToAddress(publicKeyHex, this._config)}`);
51
+ this.#update(publicKeyHex, connection);
52
+ return true;
53
+ }
54
+
55
+ this._logger.debug(`add: didn't add peer ${publicKeyToAddress(publicKeyHex, this._config)}`);
56
+ return false; // TODO: Implement better success/failure reporting
57
+ }
58
+
59
+ /**
60
+ * Checks if a peer exists in the pool.
61
+ * @param {String | Buffer} publicKey - The public key hex string of the peer to check
62
+ * @returns {Boolean} - Returns true if the peer exists, false otherwise
63
+ */
64
+ exists(publicKey) {
65
+ const publicKeyHex = this._toHexString(publicKey);
66
+ return this._connections.has(publicKeyHex);
67
+ }
68
+
69
+ /**
70
+ * Checks if a peer is currently connected.
71
+ * @param {String | Buffer} publicKey - The public key hex string of the peer to check
72
+ * @returns {Boolean} - Returns true if the peer is connected, false otherwise
73
+ */
74
+ connected(publicKey) {
75
+ const publicKeyHex = this._toHexString(publicKey);
76
+ return this.exists(publicKeyHex) && this._connections.get(publicKeyHex).connection !== null;
77
+ }
78
+
79
+ /**
80
+ * Retrieves the connection object for a given peer public key.
81
+ * @param {String | Buffer} publicKey - The public key (Buffer or hex string) of the peer.
82
+ * @returns {Connection|undefined} - The connection object if found, otherwise undefined.
83
+ */
84
+ getConnection(publicKey) {
85
+ const publicKeyHex = this._toHexString(publicKey);
86
+ const entry = this._connections.get(publicKeyHex);
87
+ return entry ? entry.connection : undefined;
88
+ }
89
+
90
+ /**
91
+ * Gets a list of all currently connected peers' public keys.
92
+ * @returns {Array} - An array of public key hex strings of connected peers
93
+ */
94
+ connectedPeers() {
95
+ return Array.from(this._connections.keys()).filter(pk => this.connected(pk));
96
+ }
97
+
98
+ /**
99
+ * Gets the current number of connected peers.
100
+ * @returns {Number} - The count of connected peers
101
+ */
102
+ connectionCount() {
103
+ return this.connectedPeers().length;
104
+ }
105
+
106
+ /**
107
+ * Checks if the maximum number of connections has been reached.
108
+ * @returns {Boolean} - Returns true if the maximum number of connections has been reached, false otherwise.
109
+ */
110
+ // Note: this function name is a bit misleading. It checks if we have reached max connections and returns boolean
111
+ // The name leads to think it returns the number of max connections
112
+ maxConnectionsReached() {
113
+ return this.connectionCount() >= this._max;
114
+ }
115
+
116
+ clear() {
117
+ this._connections.clear()
118
+ }
119
+
120
+ /**
121
+ * Appends a new peer connection.
122
+ * @param {String|Buffer} publicKey - The public key hex string of the peer
123
+ * @param {Object} connection - The connection object
124
+ */
125
+ #append(publicKey, connection) {
126
+ this._logger.debug(`#append: appending peer ${publicKeyToAddress(publicKey, this._config)}`);
127
+ const publicKeyHex = this._toHexString(publicKey);
128
+ if (this._connections.has(publicKeyHex)) {
129
+ this._logger.debug(`#append: tried to append existing peer: ${publicKeyToAddress(publicKey, this._config)}`);
130
+ return;
131
+ }
132
+ this._connections.set(publicKeyHex, {connection, sent: 0});
133
+ connection.on('close', () => {
134
+ this._logger.debug(`#append: connection closing for peer ${publicKeyToAddress(publicKey, this._config)}`);
135
+ this.remove(publicKeyHex, connection);
136
+ this._logger.debug(`#append: connection closed for peer ${publicKeyToAddress(publicKey, this._config)}`);
137
+ });
138
+ }
139
+
140
+ /**
141
+ * Updates an existing peer connection or adds it if not present.
142
+ * @param {String|Buffer} publicKey - The public key hex string of the peer
143
+ * @param {Object} connection - The connection object
144
+ */
145
+ #update(publicKey, connection) {
146
+ // Note: Is there a good reason for the function 'update' to exist separately from 'append'?
147
+ // It seems that both could be merged into a single function that either adds or updates the entry.
148
+ // It would be preferable to keep them separated though, but we would need to review all usages to ensure correctness.
149
+ // Also, we should remove the 'else' branch below if we decide to keep 'update' and 'append' separated.
150
+ const publicKeyHex = this._toHexString(publicKey);
151
+ this._logger.debug(`#update: updating peer ${publicKeyToAddress(publicKey, this._config)}`);
152
+ if (this._connections.has(publicKeyHex)) {
153
+ this._connections.get(publicKeyHex).connection = connection;
154
+ } else {
155
+ this._connections.set(publicKeyHex, {connection, sent: 0});
156
+ }
157
+ }
158
+
159
+ /**
160
+ * Gets the number of messages sent through a peer.
161
+ * @param {String | Buffer} publicKey - The public key hex string of the peer
162
+ * @returns {Number} - The count of messages sent
163
+ */
164
+ getSentCount(publicKey) {
165
+ const publicKeyHex = this._toHexString(publicKey);
166
+ const entry = this._connections.get(publicKeyHex);
167
+ return entry ? (entry.sent || 0) : 0;
168
+ }
169
+
170
+ /**
171
+ * Increments the count of messages sent through a peer.
172
+ * @param {String | Buffer} publicKey - The public key hex string of the peer
173
+ */
174
+ incrementSentCount(publicKey) {
175
+ const publicKeyHex = this._toHexString(publicKey);
176
+ const entry = this._connections.get(publicKeyHex);
177
+ if (entry) {
178
+ entry.sent = (entry.sent || 0) + 1;
179
+ }
180
+ }
181
+ }
182
+
183
+ export function isPeerConnectionError(err) {
184
+ return (err instanceof PeerConnectionManagerError)
185
+ }