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.
Files changed (137) 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 +46 -7
  23. package/src/config/env.js +33 -6
  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/ConfirmationWatch.js +82 -0
  102. package/src/core/state/State.js +1794 -173
  103. package/src/core/state/StateEventQueue.js +69 -0
  104. package/src/core/state/utils/address.js +10 -6
  105. package/src/core/state/utils/epochProof.js +109 -0
  106. package/src/core/state/utils/escrowEntry.js +178 -0
  107. package/src/core/state/validators/StateValidationSchema.js +757 -0
  108. package/src/core/state/validators/stateValidationRules.js +312 -0
  109. package/src/index.js +255 -65
  110. package/src/messages/consensus/v1/ConsensusMessageBuilder.js +368 -0
  111. package/src/messages/consensus/v1/ConsensusMessageDirector.js +142 -0
  112. package/src/messages/consensus/v1/consensusMessageFactory.js +12 -0
  113. package/src/messages/consensus/v1/epochAppendMessage.js +58 -0
  114. package/src/messages/network/v1/NetworkMessageBuilder.js +15 -7
  115. package/src/messages/state/ApplyStateMessageBuilder.js +499 -13
  116. package/src/messages/state/ApplyStateMessageDirector.js +261 -2
  117. package/src/utils/StateMachine.js +125 -0
  118. package/src/utils/applyOperations.js +45 -2
  119. package/src/utils/buffer.js +239 -59
  120. package/src/utils/consensusConfig.js +76 -0
  121. package/src/utils/constants.js +174 -73
  122. package/src/utils/helpers.js +0 -25
  123. package/src/utils/htlcLock.js +122 -0
  124. package/src/utils/logger.js +10 -4
  125. package/src/utils/migrationUtils.js +2 -1
  126. package/src/utils/normalizers.js +113 -0
  127. package/src/utils/scheduler/SchedulableService.js +107 -0
  128. package/src/utils/{Scheduler.js → scheduler/Scheduler.js} +56 -17
  129. package/src/core/network/services/ConnectionManager.js +0 -406
  130. package/src/utils/check.js +0 -525
  131. package/src/utils/protobuf/applyOperations.cjs +0 -1373
  132. package/src/utils/protobuf/operationHelpers.js +0 -81
  133. /package/src/core/network/protocols/{legacy → validators/legacy}/handlers/LegacyResponseHandler.js +0 -0
  134. /package/src/core/network/protocols/{legacy → validators/legacy}/validators/ValidatorResponse.js +0 -0
  135. /package/src/core/network/protocols/{legacy → validators/legacy}/validators/base/BaseResponse.js +0 -0
  136. /package/src/core/network/protocols/{v1 → validators/v1}/validators/V1LivenessRequest.js +0 -0
  137. /package/src/core/network/protocols/{v1 → validators/v1}/validators/V1LivenessResponse.js +0 -0
@@ -1,6 +1,6 @@
1
1
  import V1BaseOperation from "./V1BaseOperation.js";
2
2
  import b4a from "b4a";
3
- import {MAX_PARTIAL_TX_PAYLOAD_BYTE_SIZE, ResultCode} from '../../../../../utils/constants.js';
3
+ import {MAX_PARTIAL_TX_PAYLOAD_BYTE_SIZE, ResultCode} from '../../../../../../utils/constants.js';
4
4
  import {V1ProtocolError} from "../V1ProtocolError.js";
5
5
 
6
6
  class V1BroadcastTransactionRequest extends V1BaseOperation {
@@ -18,7 +18,7 @@ class V1BroadcastTransactionRequest extends V1BaseOperation {
18
18
  isDataPropertySizeValid(payload) {
19
19
  if (b4a.byteLength(payload.broadcast_transaction_request.data) > MAX_PARTIAL_TX_PAYLOAD_BYTE_SIZE) {
20
20
  throw new V1ProtocolError(
21
- ResultCode.INVALID_PAYLOAD,
21
+ ResultCode.SCHEMA_VALIDATION_FAILED,
22
22
  `The 'data' field exceeds the maximum allowed byte size of ${MAX_PARTIAL_TX_PAYLOAD_BYTE_SIZE}. Actual size: ${b4a.byteLength(payload.broadcast_transaction_request.data)}`);
23
23
  }
24
24
  }
@@ -2,44 +2,44 @@ import Autobase from "autobase";
2
2
  import b4a from "b4a";
3
3
  import Hypercore from 'hypercore';
4
4
  import V1BaseOperation from "./V1BaseOperation.js";
5
- import {unsafeDecodeApplyOperation} from "../../../../../utils/protobuf/operationHelpers.js";
6
- import {isDeepEqualApplyPayload} from "../../../../../utils/deepEqualApplyPayload.js";
7
- import {addressToBuffer, bufferToAddress} from "../../../../state/utils/address.js";
8
- import {publicKeyToAddress} from "../../../../../utils/helpers.js";
9
- import Check from "../../../../../utils/check.js";
10
- import {OperationType, ResultCode} from "../../../../../utils/constants.js";
5
+ import {unsafeDecodeApplyOperation} from "../../../../../../codecs/apply/applyOperationCodec.js";
6
+ import {isDeepEqualApplyPayload} from "../../../../../../utils/deepEqualApplyPayload.js";
7
+ import {addressToBuffer, bufferToAddress} from "../../../../../state/utils/address.js";
8
+ import {publicKeyToAddress} from "../../../../../../utils/helpers.js";
9
+ import StateValidationSchema from "../../../../../state/validators/StateValidationSchema.js";
10
+ import {OperationType, ResultCode} from "../../../../../../utils/constants.js";
11
11
  import {V1ProtocolError} from "../V1ProtocolError.js";
12
12
 
13
13
  const VALIDATOR_METADATA_FIELDS = new Set(["va", "vn", "vs"]);
14
14
 
15
- const stripValidatorMetadata = (value) => {
15
+ const stripValidatorMetadata = (value, omitFields = false) => {
16
16
 
17
17
  if (value === null || value === undefined) return value;
18
18
  if (b4a.isBuffer(value)) return value;
19
- if (Array.isArray(value)) return value.map(stripValidatorMetadata);
19
+ if (Array.isArray(value)) return value.map(item => stripValidatorMetadata(item, omitFields));
20
20
  if (typeof value !== "object") return value;
21
21
 
22
22
  const result = {};
23
23
  for (const [key, nestedValue] of Object.entries(value)) {
24
24
  if (VALIDATOR_METADATA_FIELDS.has(key)) {
25
- result[key] = null;
25
+ if (!omitFields) result[key] = null;
26
26
  continue;
27
27
  }
28
- result[key] = stripValidatorMetadata(nestedValue);
28
+ result[key] = stripValidatorMetadata(nestedValue, omitFields);
29
29
  }
30
30
  return result;
31
31
  };
32
32
 
33
33
  class V1BroadcastTransactionResponse extends V1BaseOperation {
34
34
  #config;
35
- #check;
35
+ #stateValidationSchema;
36
36
  #state;
37
37
 
38
38
  constructor(state, config) {
39
39
  super(config);
40
40
  this.#state = state;
41
41
  this.#config = config;
42
- this.#check = new Check(config);
42
+ this.#stateValidationSchema = new StateValidationSchema(config);
43
43
  }
44
44
 
45
45
  async validate(payload, connection, pendingRequestServiceEntry) {
@@ -90,19 +90,31 @@ class V1BroadcastTransactionResponse extends V1BaseOperation {
90
90
 
91
91
  let isValid = false;
92
92
  switch (type) {
93
+ case OperationType.HTLC_LOCK:
94
+ isValid = this.#stateValidationSchema.validateHtlcLockOperation(validatorDecodedTx) &&
95
+ [...VALIDATOR_METADATA_FIELDS].every(field => b4a.isBuffer(validatorDecodedTx.hlo[field]));
96
+ break;
97
+ case OperationType.HTLC_CLAIM:
98
+ isValid = this.#stateValidationSchema.validateHtlcClaimOperation(validatorDecodedTx) &&
99
+ [...VALIDATOR_METADATA_FIELDS].every(field => b4a.isBuffer(validatorDecodedTx.hco?.[field]));
100
+ break;
101
+ case OperationType.HTLC_REFUND:
102
+ isValid = this.#stateValidationSchema.validateHtlcRefundOperation(validatorDecodedTx) &&
103
+ [...VALIDATOR_METADATA_FIELDS].every(field => b4a.isBuffer(validatorDecodedTx.hro?.[field]));
104
+ break;
93
105
  case OperationType.ADD_WRITER:
94
106
  case OperationType.REMOVE_WRITER:
95
107
  case OperationType.ADMIN_RECOVERY:
96
- isValid = this.#check.validateRoleAccessOperation(validatorDecodedTx);
108
+ isValid = this.#stateValidationSchema.validateRoleAccessOperation(validatorDecodedTx);
97
109
  break;
98
110
  case OperationType.BOOTSTRAP_DEPLOYMENT:
99
- isValid = this.#check.validateBootstrapDeploymentOperation(validatorDecodedTx);
111
+ isValid = this.#stateValidationSchema.validateBootstrapDeploymentOperation(validatorDecodedTx);
100
112
  break;
101
113
  case OperationType.TX:
102
- isValid = this.#check.validateTransactionOperation(validatorDecodedTx);
114
+ isValid = this.#stateValidationSchema.validateTransactionOperation(validatorDecodedTx);
103
115
  break;
104
116
  case OperationType.TRANSFER:
105
- isValid = this.#check.validateTransferOperation(validatorDecodedTx);
117
+ isValid = this.#stateValidationSchema.validateTransferOperation(validatorDecodedTx);
106
118
  break;
107
119
  default:
108
120
  throw new V1ProtocolError(
@@ -138,7 +150,11 @@ class V1BroadcastTransactionResponse extends V1BaseOperation {
138
150
 
139
151
  const stateTxDecodedFromRequest = unsafeDecodeApplyOperation(stateTxEncodedFromRequest);
140
152
  const stateTxDecodedFromResponse = unsafeDecodeApplyOperation(stateTxEncodedFromResponse);
141
- const strippedValidatorPayload = stripValidatorMetadata(stateTxDecodedFromResponse);
153
+ const strippedValidatorPayload = stripValidatorMetadata(
154
+ stateTxDecodedFromResponse,
155
+ stateTxDecodedFromRequest.type === OperationType.HTLC_CLAIM ||
156
+ stateTxDecodedFromRequest.type === OperationType.HTLC_REFUND
157
+ );
142
158
 
143
159
  if (!isDeepEqualApplyPayload(stateTxDecodedFromRequest, strippedValidatorPayload)) {
144
160
  throw new V1ProtocolError(
@@ -168,12 +184,12 @@ class V1BroadcastTransactionResponse extends V1BaseOperation {
168
184
  }
169
185
 
170
186
  async validateValidatorCorrectness(validatorDecodedTx, connectionRemotePublicKey, writerKeyFromManifest, validatorAddressCorrelatedWithManifest) {
171
- const validatorAddressFromTx = extractRequiredVaFromDecodedTx(validatorDecodedTx);
172
187
  const validatorAddressFromConnectionPublicKey = addressToBuffer(
173
188
  publicKeyToAddress(connectionRemotePublicKey, this.#config),
174
189
  this.#config.addressPrefix
175
190
  );
176
191
 
192
+ const validatorAddressFromTx = extractRequiredVaFromDecodedTx(validatorDecodedTx);
177
193
  if (!b4a.equals(validatorAddressFromTx, validatorAddressFromConnectionPublicKey)) {
178
194
  throw new V1ProtocolError(
179
195
  ResultCode.VALIDATOR_ADDRESS_MISMATCH,
@@ -5,10 +5,10 @@ import {
5
5
  NONCE_BYTE_LENGTH,
6
6
  SIGNATURE_BYTE_LENGTH,
7
7
  MAX_PARTIAL_TX_PAYLOAD_BYTE_SIZE,
8
- ResultCode
9
- } from '../../../../../utils/constants.js';
8
+ NetworkResultCode
9
+ } from '../../../../../../utils/constants.js';
10
10
 
11
- const ALLOWED_RESULT_CODES = Object.values(ResultCode);
11
+ const ALLOWED_RESULT_CODES = Object.values(NetworkResultCode);
12
12
 
13
13
  class V1ValidationSchema {
14
14
  #validator;
@@ -0,0 +1,88 @@
1
+ import tracCryptoApi from 'trac-crypto-api'
2
+ import { PeerConnectionManager } from '../shared/PeerConnectionManager.js'
3
+ import b4a from "b4a"
4
+ import { bufferToAddress } from '../../state/utils/address.js'
5
+
6
+ class IndexerConnectionManager extends PeerConnectionManager {
7
+ #messages
8
+ #state
9
+ #network
10
+ #wallet
11
+
12
+
13
+ constructor(maxIndexers, config, logger, messages, state, network, wallet) {
14
+ super(maxIndexers, config, logger);
15
+ this.#messages = messages;
16
+ this.#state = state;
17
+ this.#network = network;
18
+ this.#wallet = wallet;
19
+ }
20
+
21
+ async add(publicKey, connection) {
22
+ await this.#messages.attachChannel(connection);
23
+ if (connection.destroyed) return;
24
+ if (!connection.protocolSessions?.indexer || connection.protocolSessions.indexer.closed) return;
25
+ this._add(publicKey, connection)
26
+ }
27
+
28
+ async connect() {
29
+ const entries = await this.#state.getIndexersEntry();
30
+ // Promotion events can precede Autobase's membership update. Refresh the
31
+ // capacity from the same snapshot used for discovery on every retry.
32
+ this.setMax(Math.max(0, entries.length - 1));
33
+
34
+ for (const entry of entries) {
35
+ const writerKeyHex = b4a.toString(entry.key, 'hex');
36
+ const addressBuffer = await this.#state.getRegisteredWriterKey(writerKeyHex);
37
+ if (!addressBuffer) continue;
38
+
39
+ const addr = bufferToAddress(addressBuffer, this._config.addressPrefix);
40
+ if (!addr || addr === this.#wallet.address) continue;
41
+
42
+ const publicKey = tracCryptoApi.address.decode(addr);
43
+ const publicKeyHex = b4a.toString(publicKey, 'hex');
44
+
45
+ const isConnected = this.connected(publicKey);
46
+ const isPending = this.#network.isConnectionPending(publicKeyHex);
47
+ if (isConnected || isPending) continue;
48
+
49
+ await this.#network.tryConnect(publicKeyHex, "indexer");
50
+ }
51
+ }
52
+
53
+ remove(publicKey, connection = null) {
54
+ const key = this._toHexString(publicKey);
55
+ const entry = this._connections.get(key);
56
+ if (!entry) return;
57
+ if (connection && entry.connection !== connection) return;
58
+
59
+ const targetConnection = connection ?? entry.connection;
60
+ this._connections.delete(key);
61
+ targetConnection.protocolSessions.indexer?.close();
62
+ }
63
+
64
+ async send(publicKey, message) {
65
+ const connection = this.getConnection(publicKey);
66
+ if (!connection) {
67
+ throw new Error(`PeerConnectionManager: no session for ${this._toHexString(publicKey)}`);
68
+ }
69
+ if (!connection.connected) {
70
+ this.remove(publicKey, connection);
71
+ throw new Error(`PeerConnectionManager: stale connection for ${this._toHexString(publicKey)}`);
72
+ }
73
+ return connection.protocolSessions.indexer.send(message);
74
+ }
75
+
76
+ sendAndForget(publicKey, message) {
77
+ const connection = this.getConnection(publicKey);
78
+ if (!connection) return;
79
+ connection.protocolSessions.indexer.sendAndForget(message);
80
+ }
81
+
82
+ prettyPrint() {
83
+ console.log(`Connection count: ${this._connections.size}`);
84
+ console.log(`Indexer map keys:\n${Array.from(this._connections.keys()).join('\n')}`);
85
+ }
86
+ }
87
+
88
+ export default IndexerConnectionManager;
@@ -1,6 +1,6 @@
1
1
  // PoolService.js
2
2
  import { BATCH_SIZE } from '../../../utils/constants.js';
3
- import Scheduler from '../../../utils/Scheduler.js';
3
+ import SchedulableService from '../../../utils/scheduler/SchedulableService.js';
4
4
  import Denque from "denque";
5
5
  import b4a from "b4a";
6
6
 
@@ -43,13 +43,12 @@ export class TransactionPoolAlreadyQueuedError extends Error {
43
43
  }
44
44
  }
45
45
 
46
- class TransactionPoolService {
46
+ class TransactionPoolService extends SchedulableService {
47
47
  #state;
48
48
  #address;
49
49
  #config;
50
50
  #txPool = new Denque();
51
51
  #transactionCommitService
52
- #scheduler = null;
53
52
  #queuedTxHashes
54
53
 
55
54
  /**
@@ -59,6 +58,7 @@ class TransactionPoolService {
59
58
  * @param {Config} config
60
59
  **/
61
60
  constructor(state, address, transactionCommitService, config) {
61
+ super();
62
62
  this.#state = state;
63
63
  this.#address = address;
64
64
  this.#transactionCommitService = transactionCommitService;
@@ -79,16 +79,15 @@ class TransactionPoolService {
79
79
  console.info('TransactionPoolService can not start. Wallet is not enabled');
80
80
  return;
81
81
  }
82
- if (this.#scheduler && this.#scheduler.isRunning) {
83
- console.info('TransactionPoolService is already started');
84
- return;
85
- }
86
82
 
87
- this.#scheduler = this.#createScheduler();
88
- this.#scheduler.start();
83
+ return super.start();
84
+ }
85
+
86
+ getScheduleInterval() {
87
+ return this.#config.processIntervalMs;
89
88
  }
90
89
 
91
- async #worker(next) {
90
+ async worker(next) {
92
91
  try {
93
92
  await this.#processTransactions();
94
93
  if (this.#txPool.size() > 0) {
@@ -101,10 +100,6 @@ class TransactionPoolService {
101
100
  }
102
101
  }
103
102
 
104
- #createScheduler() {
105
- return new Scheduler((next) => this.#worker(next), this.#config.processIntervalMs);
106
- }
107
-
108
103
  async #processTransactions() {
109
104
  const canValidate = await this.#checkValidationPermissions();
110
105
  if (!canValidate || this.#txPool.size() === 0) return;
@@ -187,10 +182,8 @@ class TransactionPoolService {
187
182
  this.txPool.push(txData);
188
183
  }
189
184
 
190
- async stopPool(waitForCurrent = true) {
191
- if (!this.#scheduler) return;
192
- await this.#scheduler.stop(waitForCurrent);
193
- this.#scheduler = null;
185
+ async stop(waitForCurrent = true) {
186
+ await super.stop(waitForCurrent);
194
187
  this.#queuedTxHashes.clear();
195
188
  this.#txPool.clear();
196
189
  console.info('TransactionPoolService: closing gracefully...');
@@ -1,5 +1,5 @@
1
1
  import b4a from 'b4a';
2
- import {V1ProtocolError} from "../protocols/v1/V1ProtocolError.js";
2
+ import {V1ProtocolError} from "../protocols/validators/v1/V1ProtocolError.js";
3
3
  import {ResultCode} from "../../../utils/constants.js";
4
4
  import {publicKeyToAddress} from "../../../utils/helpers.js";
5
5
 
@@ -0,0 +1,163 @@
1
+ import {EventType, ResultCode} from '../../../utils/constants.js';
2
+ import {publicKeyToAddress} from "../../../utils/helpers.js";
3
+ import { PeerConnectionManager, PeerConnectionManagerError } from '../shared/PeerConnectionManager.js'
4
+ import ValidatorHealthCheckService from './ValidatorHealthCheckService.js'
5
+
6
+ const DEFAULT_HEALTH_CHECK_FAILURE_THRESHOLD = 3;
7
+ /**
8
+ * @typedef {import('hyperswarm').Connection} Connection
9
+ */
10
+
11
+ class ValidatorConnectionManager extends PeerConnectionManager {
12
+ #healthCheckService
13
+ #messages
14
+ #healthCheckFailures = new Map();
15
+ #healthCheckFailureThreshold;
16
+ // Note: _connections is using publicKey (Buffer) as key
17
+ // As Buffers are objects, we will rely on internal conversions done by JS to compare them.
18
+ // It would be better to handle these conversions manually by using hex strings as keys to avoid issues
19
+ /**
20
+ * @param {Config} config
21
+ **/
22
+ constructor(maxValidators, config, logger, messages) {
23
+ super(maxValidators, config, logger, messages);
24
+ this.#messages = messages
25
+ this.#healthCheckService = new ValidatorHealthCheckService(config);
26
+ const threshold = Number(config.validatorHealthCheckFailureThreshold);
27
+ this.#healthCheckFailureThreshold = Number.isInteger(threshold) && threshold > 0
28
+ ? threshold : DEFAULT_HEALTH_CHECK_FAILURE_THRESHOLD;
29
+ }
30
+
31
+ async _open() {
32
+ await this.#healthCheckService.ready();
33
+ this.#subscribeToHealthChecks();
34
+ }
35
+
36
+ async _close() {
37
+ await this.#healthCheckService.close();
38
+ }
39
+
40
+ /**
41
+ * Subscribes to periodic validator health checks.
42
+ * @param {ReadyResource} healthCheckService
43
+ */
44
+ // TODO: We should consider moving this to ValidatorObserver instead.
45
+ // Keep here only if we forsee having health checks for non-validator connections in the future.
46
+ // For now, it seems that it would be better to keep this logic here.
47
+ #subscribeToHealthChecks() {
48
+ this.#healthCheckService.on(EventType.VALIDATOR_HEALTH_CHECK, async publicKey => {
49
+ if (!this.exists(publicKey) || !this.connected(publicKey)) {
50
+ this.#stopHealthCheck(publicKey);
51
+ return;
52
+ }
53
+
54
+ const connection = this.getConnection(publicKey);
55
+ if (!connection) {
56
+ this.#stopHealthCheck(publicKey);
57
+ this.remove(publicKey);
58
+ return;
59
+ }
60
+
61
+ let success = false;
62
+ try {
63
+ const resultCode = await connection.protocolSessions.validator.sendHealthCheck();
64
+ success = resultCode === ResultCode.OK;
65
+ } catch {
66
+ success = false;
67
+ }
68
+
69
+ // An in-flight health check must not affect a replacement connection.
70
+ if (this.getConnection(publicKey) !== connection) return;
71
+ const publicKeyHex = this._toHexString(publicKey);
72
+ if (!success) {
73
+ const failures = (this.#healthCheckFailures.get(publicKeyHex) ?? 0) + 1;
74
+ this.#healthCheckFailures.set(publicKeyHex, failures);
75
+ if (failures >= this.#healthCheckFailureThreshold) this.remove(publicKey, connection);
76
+ } else {
77
+ this.#healthCheckFailures.delete(publicKeyHex);
78
+ }
79
+ });
80
+ }
81
+
82
+ #stopHealthCheck(publicKeyHex) {
83
+ try {
84
+ if (this.#healthCheckService.has(publicKeyHex)) {
85
+ this.#healthCheckService.stop(publicKeyHex);
86
+ }
87
+ } catch {}
88
+ }
89
+
90
+ async add(publicKey, connection) {
91
+ this.#messages.attachChannel(connection);
92
+ try {
93
+ if (!connection.protocolSessions.validator.isProbed()) {
94
+ await connection.protocolSessions.validator.probe();
95
+ }
96
+ } catch (err) {
97
+ this._logger.error(`failed to probe peer with publicKey ${publicKey}: ${err?.message ?? err}`);
98
+ }
99
+ if (this._add(publicKey, connection)) {
100
+ this.#healthCheckFailures.delete(this._toHexString(publicKey));
101
+ }
102
+
103
+ if (connection.protocolSessions.validator.isHealthCheckSupported()) {
104
+ this.#healthCheckService.start(publicKey);
105
+ } else {
106
+ this.#healthCheckService.stop(publicKey);
107
+ }
108
+ }
109
+
110
+ remove(publicKey, connection = null) {
111
+ const publicKeyHex = this._toHexString(publicKey);
112
+ const entry = this._connections.get(publicKeyHex);
113
+ if (!entry) return;
114
+ if (connection && entry.connection !== connection) return;
115
+
116
+ this.#stopHealthCheck(publicKeyHex);
117
+ this.#healthCheckFailures.delete(publicKeyHex);
118
+ this._connections.delete(publicKeyHex);
119
+ }
120
+
121
+ async sendSingleMessage(message, publicKey) {
122
+ const publicKeyHex = this._toHexString(publicKey);
123
+ if (!this.connected(publicKeyHex)) {
124
+ throw new PeerConnectionManagerError(
125
+ `Cannot send message: peer ${publicKeyToAddress(publicKey, this._config)} is not connected.`
126
+ );
127
+ }
128
+
129
+ const connection = this.getConnection(publicKeyHex);
130
+ if (!connection) {
131
+ throw new PeerConnectionManagerError(
132
+ `Cannot send message: no valid connection found for peer ${publicKeyToAddress(publicKeyHex, this._config)}.`
133
+ );
134
+ }
135
+
136
+ return connection.protocolSessions.validator.send(message);
137
+ }
138
+
139
+ sendAndForget(publicKey, message) {
140
+ const connection = this.getConnection(publicKey);
141
+ if (!connection) return;
142
+ connection.protocolSessions.validator.sendAndForget(message);
143
+ }
144
+
145
+ async send(publicKey, message) {
146
+ const connection = this.getConnection(publicKey);
147
+ if (!connection) {
148
+ throw new Error(`PeerConnectionManager: no session for ${this._toHexString(publicKey)}`);
149
+ }
150
+ return connection.protocolSessions.validator.send(message);
151
+ }
152
+
153
+ prettyPrint() {
154
+ this._logger.info(`Connection count: ${this.connectionCount()}`);
155
+ this._logger.info(`Validator map keys count: ${this._connections.size}`);
156
+ this._logger.info(`Validator map keys:\n${Array.from(this._connections.entries()).map(([publicKey, val]) => {
157
+ const protocols = val.connection?.protocolSessions?.validator?.preferredProtocol || 'none';
158
+ return `${publicKeyToAddress(publicKey, this._config)}: ${protocols}`;
159
+ }).join('\n')}`);
160
+ }
161
+ }
162
+
163
+ export default ValidatorConnectionManager;