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
@@ -4,61 +4,71 @@ import w from 'protomux-wakeup';
4
4
  import b4a from 'b4a';
5
5
  import TransactionPoolService from './services/TransactionPoolService.js';
6
6
  import ValidatorObserverService from './services/ValidatorObserverService.js';
7
- import NetworkMessages from './protocols/NetworkMessages.js';
7
+ import ValidatorMessages from './protocols/validators/ValidatorMessages.js';
8
8
  import { sleep } from '../../utils/helpers.js';
9
- import {
10
- TRAC_NAMESPACE,
11
- EventType,
12
- CONNECTION_STATUS
13
- } from '../../utils/constants.js';
14
- import ConnectionManager from './services/ConnectionManager.js';
15
- import MessageOrchestrator from './services/MessageOrchestrator.js';
9
+ import { TRAC_NAMESPACE, CONNECTION_STATUS } from '../../utils/constants.js';
10
+ import ValidatorConnectionManager from './services/ValidatorConnectionManager.js';
11
+ import ValidatorMessageOrchestrator from './services/ValidatorMessageOrchestrator.js';
16
12
  import TransactionRateLimiterService from './services/TransactionRateLimiterService.js';
17
- import PendingRequestService from './services/PendingRequestService.js';
13
+ import ValidatorPendingRequestService from './protocols/validators/v1/ValidatorPendingRequestService.js';
18
14
  import TransactionCommitService from "./services/TransactionCommitService.js";
19
- import ValidatorHealthCheckService from './services/ValidatorHealthCheckService.js';
15
+ import EpochCoordinatorService from '../consensus/services/EpochCoordinatorService.js';
16
+ import IndexerConnectionManager from './services/IndexerConnectionManager.js';
20
17
  import { Logger } from '../../utils/logger.js';
21
18
  import { WalletProvider } from 'trac-wallet';
19
+ import { CustomEventType } from '../../utils/constants.js';
20
+ import tracCryptoApi from 'trac-crypto-api'
21
+ import IndexerMessages from './protocols/consensus/IndexerMessages.js';
22
+ import IndexerPendingRequestService from './protocols/consensus/v1/IndexerPendingRequestService.js';
23
+ import { createEpochAppenderSelector } from '../consensus/epochAppenderSelection/createEpochAppenderSelector.js';
22
24
 
23
25
  const wakeup = new w();
24
26
 
25
27
  class Network extends ReadyResource {
26
- #swarm = null;
27
- #networkMessages;
28
+ #swarm;
28
29
  #transactionPoolService;
29
30
  #validatorObserverService;
30
31
  #validatorConnectionManager;
31
32
  #validatorMessageOrchestrator;
32
33
  #config;
33
34
  #pendingConnections;
34
- #connectTimeoutMs;
35
- #maxPendingConnections;
36
35
  #rateLimiter;
37
- #pendingRequestsService;
36
+ #validatorPendingRequestService;
38
37
  #transactionCommitService;
39
38
  #wallet;
40
- #validatorHealthCheckService;
39
+ #epochCoordinatorService;
41
40
  #logger;
41
+ #state;
42
+ #store;
43
+ #indexerPendingRequestService;
44
+ #validatorMessages;
45
+ #indexerMessages;
46
+ #indexerConnectionManager;
47
+ #consensusPeerBanRequestedListener;
48
+ #epochAppenderSelector;
42
49
  #closing = false;
43
50
 
44
51
  /**
45
52
  * @param {State} state
53
+ * @param {object} store
46
54
  * @param {Config} config
47
- * @param {string} address
55
+ * @param {object} address
48
56
  **/
49
- constructor(state, config, address = null) {
57
+ constructor(state, store, config, wallet = null) {
50
58
  super();
59
+
51
60
  this.#config = config
52
- this.#connectTimeoutMs = config.connectTimeoutMs || 5000;
53
- this.#maxPendingConnections = config.maxPendingConnections || 50;
61
+ this.#state = state
62
+ this.#store = store
63
+ this.#wallet = wallet
54
64
  this.#pendingConnections = new Map();
55
65
  this.#transactionCommitService = new TransactionCommitService(this.#config);
56
- this.#transactionPoolService = new TransactionPoolService(state, address, this.#transactionCommitService ,this.#config);
57
- this.#validatorObserverService = new ValidatorObserverService(this, state, address, this.#config);
58
- this.#validatorConnectionManager = new ConnectionManager(this.#config);
59
- this.#validatorMessageOrchestrator = new MessageOrchestrator(this.#validatorConnectionManager, state, this.#config);
60
- this.#pendingRequestsService = new PendingRequestService(this.#config);
66
+ this.#transactionPoolService = new TransactionPoolService(state, wallet?.address, this.#transactionCommitService ,this.#config);
67
+ this.#validatorObserverService = new ValidatorObserverService(this, state, wallet?.address, this.#config);
68
+ this.#validatorPendingRequestService = new ValidatorPendingRequestService(this.#config);
69
+ this.#indexerPendingRequestService = new IndexerPendingRequestService(this.#config);
61
70
  this.#logger = new Logger(this.#config);
71
+ this.#consensusPeerBanRequestedListener = this.#handleConsensusPeerBanRequested.bind(this);
62
72
  }
63
73
 
64
74
  get swarm() {
@@ -81,221 +91,235 @@ class Network extends ReadyResource {
81
91
  return this.#validatorMessageOrchestrator;
82
92
  }
83
93
 
84
- async _open() {
85
- this.#logger.info('Network initialization...');
86
- this.#closing = false;
94
+ get indexerConnectionManager() {
95
+ return this.#indexerConnectionManager;
96
+ }
87
97
 
88
- this.setupNetworkListeners();
98
+ async _open() {
99
+ this.#logger.info('Network initialization...');
89
100
 
101
+ this.#assertCanReplicate();
102
+ const { wallet: wrappedWallet, keyPair } = await this.#getOrGenerateWallet();
103
+ this.#assertCanReplicate();
104
+ this.#wallet = wrappedWallet
105
+
106
+ // Recover durable votes before starting network resources.
107
+ this.#epochAppenderSelector = createEpochAppenderSelector(
108
+ this.#state,
109
+ this.#wallet,
110
+ this.#config,
111
+ () => this.#indexerConnectionManager,
112
+ this.#logger,
113
+ this.#indexerPendingRequestService
114
+ );
115
+ await this.#epochAppenderSelector.open();
116
+ this.#assertCanReplicate();
90
117
  this.transactionPoolService.start();
118
+
119
+ this.#swarm = new Hyperswarm({
120
+ keyPair,
121
+ bootstrap: this.#config.dhtBootstrap,
122
+ maxPeers: this.#config.maxPeers,
123
+ maxParallel: this.#config.maxParallel,
124
+ maxServerConnections: this.#config.maxServerConnections,
125
+ maxClientConnections: this.#config.maxClientConnections
126
+ });
127
+
128
+ this.#rateLimiter = new TransactionRateLimiterService(this.#swarm, this.#config);
129
+ this.#validatorMessages = new ValidatorMessages(
130
+ this.#state,
131
+ this.#wallet,
132
+ this.#rateLimiter,
133
+ this.#transactionPoolService,
134
+ this.#validatorPendingRequestService,
135
+ this.#transactionCommitService,
136
+ this.#config
137
+ );
138
+
139
+ this.#validatorConnectionManager = new ValidatorConnectionManager(this.#config.maxValidators, this.#config, this.#logger, this.#validatorMessages);
140
+ await this.#validatorConnectionManager.ready();
141
+
142
+ this.#validatorMessageOrchestrator = new ValidatorMessageOrchestrator(this.#validatorConnectionManager, this.#state, this.#config, this.#wallet);
143
+
144
+ this.#indexerMessages = new IndexerMessages(
145
+ this.#state, this.#wallet, this.#config, this.#indexerPendingRequestService,
146
+ this.#handleConsensusSessionClosed.bind(this), this.#epochAppenderSelector,
147
+ );
148
+
149
+ const indexerCount = await this.#state.indexerCount();
150
+ this.#indexerConnectionManager = new IndexerConnectionManager(
151
+ indexerCount,
152
+ this.#config,
153
+ this.#logger,
154
+ this.#indexerMessages,
155
+ this.#state,
156
+ this,
157
+ this.#wallet,
158
+ );
159
+ await this.#indexerConnectionManager.ready();
160
+
161
+ this.#epochCoordinatorService = new EpochCoordinatorService(
162
+ this.#state, this.#wallet, this.#config, this.#indexerConnectionManager, this.#epochAppenderSelector,
163
+ );
164
+ await this.#epochCoordinatorService.ready();
165
+ this.#assertCanReplicate();
166
+
167
+ this.#logger.info(`Channel: ${b4a.toString(this.#config.channel)}`);
168
+ this.#listeners();
169
+
170
+ this.#swarm.join(this.#config.channel, { server: true, client: true });
171
+ this.#swarm.flush();
91
172
  this.validatorObserverService.start();
173
+
174
+ if (this.#state.isIndexer()) {
175
+ if (await this.#state.getCurrentEpoch() !== null) {
176
+ this.#epochCoordinatorService.start();
177
+ }
178
+ }
92
179
  }
93
180
 
94
181
  async _close() {
95
182
  this.#logger.info('Network: closing gracefully...');
96
183
  this.#closing = true;
97
- await this.transactionPoolService.stopPool();
184
+ this.#state.off(CustomEventType.CONSENSUS_PEER_BAN_REQUESTED, this.#consensusPeerBanRequestedListener);
185
+ await this.#epochCoordinatorService.close();
186
+ await this.#epochAppenderSelector.close();
187
+ await this.transactionPoolService.stop();
98
188
  await sleep(100);
99
- await this.#validatorObserverService.stopValidatorObserver();
100
- await sleep(5_000);
101
- if (this.#validatorHealthCheckService) {
102
- await this.#validatorHealthCheckService.close();
103
- }
189
+ await this.#validatorObserverService.stop();
104
190
 
105
- this.cleanupNetworkListeners();
106
191
  this.cleanupPendingConnections();
107
- this.#pendingRequestsService.close();
192
+ this.#validatorPendingRequestService.close();
108
193
  this.#transactionCommitService.close();
109
-
194
+ this.#indexerPendingRequestService.close();
195
+ await this.#indexerConnectionManager.close();
196
+ await this.#validatorConnectionManager.close()
110
197
  const swarm = this.#swarm;
111
- if (swarm !== null) {
112
- if (typeof swarm.removeAllListeners === 'function') {
113
- swarm.removeAllListeners('connection');
114
- }
198
+ if (swarm) {
199
+ swarm.removeAllListeners('connection');
115
200
  await swarm.destroy();
116
- if (this.#swarm === swarm) {
117
- this.#swarm = null;
118
- }
201
+ if (this.#swarm === swarm) this.#swarm = null;
119
202
  }
120
203
  }
121
204
 
122
- setupNetworkListeners() {
123
- this.on(EventType.VALIDATOR_CONNECTION_TIMEOUT, ({ publicKey, type, timeoutMs }) => {
124
- this.#logger.debug(`Network Event: VALIDATOR_CONNECTION_TIMEOUT | PublicKey: ${publicKey} | Type: ${type} | TimeoutMs: ${timeoutMs}`);
125
- this.#pendingConnections.delete(publicKey);
126
- });
127
-
128
- this.on(EventType.VALIDATOR_CONNECTION_READY, async ({ publicKey, type, connection }) => {
129
- this.#logger.debug(`Network Event: VALIDATOR_CONNECTION_READY | PublicKey: ${publicKey} | Type: ${type}`);
130
- const { timeoutId } = this.#pendingConnections.get(publicKey);
131
-
132
- if (!timeoutId) return;
133
-
134
- clearTimeout(timeoutId);
135
- this.#pendingConnections.delete(publicKey);
205
+ #handleConsensusSessionClosed(connection) {
206
+ this.#indexerConnectionManager.remove(connection.remotePublicKey, connection);
207
+ }
136
208
 
137
- if (type === 'validator') {
138
- try {
139
- if (!connection.protocolSession.isProbed()) await connection.protocolSession.probe();
140
- } catch (err) {
141
- this.#logger.debug(`failed to probe peer with publicKey ${publicKey}: ${err?.message ?? err}`);
142
- }
209
+ #prepareConnection(connection) {
210
+ this.#assertCanReplicate();
211
+ connection.protocolSessions = {};
212
+ const stream = this.#store.replicate(connection);
213
+ wakeup.addStream(stream);
214
+ }
143
215
 
144
- this.#validatorConnectionManager.addValidator(publicKey, connection);
216
+ #listeners() {
217
+ this.#state.on(CustomEventType.CONSENSUS_PEER_BAN_REQUESTED, this.#consensusPeerBanRequestedListener);
218
+ this.#state.on(CustomEventType.IS_INDEXER, async (publicKey) => {
219
+ const publicKeyHex = this.#normalizePublicKey(publicKey);
220
+ this.#validatorConnectionManager.remove(publicKeyHex);
221
+ const indexerCount = await this.#state.indexerCount();
222
+ this.#indexerConnectionManager.setMax(indexerCount - 1);
223
+ });
145
224
 
146
- let healthCheckSupported = false;
147
- try {
148
- healthCheckSupported = connection.protocolSession.isHealthCheckSupported();
149
- } catch (err) {
150
- this.#logger.debug(`health check support unknown for peer with publicKey ${publicKey}: ${err?.message ?? err}`);
151
- }
225
+ this.#state.on(CustomEventType.UNWRITABLE, (publicKey) => {
226
+ this.disconnectValidatorPeer(publicKey, 'peer became unwritable');
227
+ });
152
228
 
153
- if (healthCheckSupported) {
154
- this.#validatorHealthCheckService.start(publicKey);
155
- } else {
156
- this.#validatorHealthCheckService.stop(publicKey);
229
+ this.#state.on(CustomEventType.IS_INDEXER, async bufferAddress => {
230
+ const address = tracCryptoApi.address.encode(this.#config.addressPrefix, bufferAddress);
231
+ if (address === this.#wallet.address) {
232
+ if (await this.#state.getCurrentEpoch() !== null) {
233
+ this.#epochCoordinatorService.start();
157
234
  }
158
235
  }
159
-
160
236
  });
161
- }
162
237
 
163
- cleanupNetworkListeners() {
164
- this.removeAllListeners(EventType.VALIDATOR_CONNECTION_TIMEOUT);
165
- this.removeAllListeners(EventType.VALIDATOR_CONNECTION_READY);
166
- }
167
-
168
- cleanupPendingConnections() {
169
- for (const { timeoutId } of this.#pendingConnections.values()) {
170
- clearTimeout(timeoutId);
171
- }
172
- this.#pendingConnections.clear();
173
- }
174
-
175
- async replicate(
176
- state,
177
- store,
178
- wallet,
179
- ) {
180
- this.#assertCanReplicate();
238
+ this.#state.on(CustomEventType.IS_NON_INDEXER, async (bufferAddress) => {
239
+ const address = tracCryptoApi.address.encode(this.#config.addressPrefix, bufferAddress);
240
+ if (address === this.#wallet.address) {
241
+ await this.#epochCoordinatorService.stop();
242
+ } else {
243
+ this.#indexerConnectionManager.remove(bufferAddress);
244
+ const indexerCount = await this.#state.indexerCount();
245
+ this.#indexerConnectionManager.setMax(indexerCount - 1);
246
+ }
247
+ });
181
248
 
182
- if (!this.#swarm) {
183
- const { wallet: wrappedWallet, keyPair } = await this.#getOrGenerateWallet(store, wallet);
184
- this.#assertCanReplicate();
249
+ this.#state.on(CustomEventType.GENESIS_EPOCH_CREATED, () => {
250
+ if (this.#state.isIndexer()) {
251
+ this.#epochCoordinatorService.start();
252
+ }
253
+ });
185
254
 
186
- this.#wallet = wrappedWallet
187
- this.#validatorMessageOrchestrator.setWallet(this.#wallet);
255
+ this.#swarm.prependListener('connection', async (connection) => {
256
+ if (this.#closing || this.closed) {
257
+ this.#destroyConnection(connection);
258
+ return;
259
+ }
260
+ // A handshake accepted before a ban can finish after the ban was applied.
261
+ if (this.#isPeerBanned(connection.remotePublicKey)) {
262
+ connection.destroy();
263
+ return;
264
+ }
188
265
 
189
- this.#swarm = new Hyperswarm({
190
- keyPair,
191
- bootstrap: this.#config.dhtBootstrap,
192
- maxPeers: this.#config.maxPeers,
193
- maxParallel: this.#config.maxParallel,
194
- maxServerConnections: this.#config.maxServerConnections,
195
- maxClientConnections: this.#config.maxClientConnections
266
+ /*
267
+ Here is the issue:
268
+
269
+ The current session is supposed to be attached as soon as possible (mostly to respond to probe since there is no connection ready signal on this level)
270
+ Since the connection was started from the other side, this havent gone through "qualification" which happens on tryConnect.
271
+ Because of that, we need to assume the current connection is that of a validator (who responds to probe) and later override it if necessary.
272
+ This is leaky for two reasons: first we need to keep a reference to messages and disclose the connection structure in this class.
273
+ second is that the protocol itself doesnt fit the connection life-cycle (this is a bigger problem that also touched on DHT factory structure being "swallowed by swarm")
274
+ */
275
+ const publicKey = b4a.toString(connection.remotePublicKey, 'hex');
276
+
277
+ connection.on('close', () => {
278
+ this.#rejectAllPendingRequests(publicKey, new Error('Connection closed before response'));
279
+ this.#swarm?.leavePeer(connection.remotePublicKey);
280
+ this.#validatorConnectionManager.remove(publicKey, connection);
281
+ this.#closeProtocolSessions(connection);
196
282
  });
197
283
 
198
- this.#rateLimiter = new TransactionRateLimiterService(this.#swarm, this.#config);
199
- this.#networkMessages = new NetworkMessages(
200
- state,
201
- this.#wallet,
202
- this.#rateLimiter,
203
- this.#transactionPoolService,
204
- this.#pendingRequestsService,
205
- this.#transactionCommitService,
206
- this.#config
207
- );
208
- this.#validatorHealthCheckService = new ValidatorHealthCheckService(this.#config);
209
- await this.#validatorHealthCheckService.ready();
210
- this.#assertCanReplicate();
211
-
212
- this.#validatorConnectionManager.subscribeToHealthChecks(this.#validatorHealthCheckService);
213
-
214
- this.#logger.info(`Channel: ${b4a.toString(this.#config.channel)}`);
215
-
216
- this.#swarm.on('connection', async (connection) => {
217
- if (this.#closing) {
218
- this.#destroyConnection(connection);
284
+ connection.on('error', (error) => {
285
+ this.#rejectAllPendingRequests(publicKey, error ?? new Error('Connection error before response'));
286
+ if (
287
+ error && error.message && (
288
+ error.message.includes('connection reset by peer') ||
289
+ error.message.includes('Duplicate connection') ||
290
+ error.message.includes('connection timed out'))
291
+ ) {
292
+ // TODO: decide if we want to handle this error in a specific way. It generates a lot of logs.
219
293
  return;
220
294
  }
295
+ this.#logger.error(error?.message ?? 'Unknown network connection error');
296
+ });
221
297
 
222
- try {
223
- // Per-peer connection initialization:
224
- // - attach Protomux (legacy + v1 channels/messages)
225
- // - attach connection.protocolSession (used later by tryConnect / orchestrators to send messages)
226
- await this.#networkMessages.setupProtomuxMessages(connection);
227
-
228
- // Pear v3 can deliver a late swarm connection while shutdown is
229
- // already closing the Corestore. Do not replicate a connection
230
- // after close has begun; store.replicate would otherwise throw.
231
- if (this.#closing || this.#swarm === null) {
232
- this.#destroyConnection(connection);
233
- return;
234
- }
235
-
236
- // ATTENTION: Must be called AFTER the protomux init above
237
- const stream = store.replicate(connection);
238
- wakeup.addStream(stream);
239
- } catch (error) {
240
- const publicKey = connection.remotePublicKey
241
- ? b4a.toString(connection.remotePublicKey, 'hex')
242
- : 'unknown';
243
- this.#pendingRequestsService.rejectPendingRequestsForPeer(
244
- publicKey,
245
- error ?? new Error('Connection setup failed')
246
- );
298
+ try {
299
+ this.#prepareConnection(connection);
300
+ this.#validatorMessages.prepareConnection(connection);
301
+ this.#indexerMessages.prepareConnection(connection);
302
+ await this.#promotePendingConnection(publicKey, connection);
303
+ if (this.#closing || this.closed) {
304
+ this.#validatorConnectionManager.remove(publicKey, connection);
305
+ this.#indexerConnectionManager.remove(publicKey, connection);
247
306
  this.#destroyConnection(connection);
248
- if (!this.#closing) {
249
- this.#logger.error(error?.message ?? 'Unknown network connection setup error');
250
- }
251
- return;
252
307
  }
253
-
254
- const publicKey = b4a.toString(connection.remotePublicKey, 'hex');
255
- if (this.#pendingConnections.has(publicKey)) {
256
- const { type } = this.#pendingConnections.get(publicKey);
257
- await this.#finalizeConnection(publicKey, type, connection);
308
+ } catch (error) {
309
+ this.#rejectAllPendingRequests(publicKey, error ?? new Error('Connection setup failed'));
310
+ this.#destroyConnection(connection);
311
+ if (!this.#closing) {
312
+ this.#logger.error(error?.message ?? 'Unknown network connection setup error');
258
313
  }
314
+ }
315
+ });
316
+ }
259
317
 
260
- connection.on('close', () => {
261
- this.#pendingRequestsService.rejectPendingRequestsForPeer(
262
- publicKey,
263
- new Error('Connection closed before response')
264
- );
265
- this.#swarm?.leavePeer(connection.remotePublicKey);
266
- // only act on the connection this event belongs to
267
- if (this.#validatorConnectionManager.isCurrent(publicKey, connection)) {
268
- this.#validatorConnectionManager.remove(publicKey);
269
- }
270
- if (connection.protocolSession) {
271
- try {
272
- connection.protocolSession.close();
273
- } catch {}
274
- }
275
- });
276
-
277
- connection.on('error', (error) => {
278
- this.#pendingRequestsService.rejectPendingRequestsForPeer(
279
- publicKey,
280
- error ?? new Error('Connection error before response')
281
- );
282
- if (
283
- error && error.message && (
284
- error.message.includes('connection reset by peer') ||
285
- error.message.includes('Duplicate connection') ||
286
- error.message.includes('connection timed out'))
287
- ) {
288
- // TODO: decide if we want to handle this error in a specific way. It generates a lot of logs.
289
- return;
290
- }
291
- this.#logger.error(error?.message ?? 'Unknown network connection error');
292
- });
293
-
294
- });
295
-
296
- this.#swarm.join(this.#config.channel, { server: true, client: true });
297
- this.#swarm.flush();
318
+ cleanupPendingConnections() {
319
+ for (const { timeoutId } of this.#pendingConnections.values()) {
320
+ clearTimeout(timeoutId);
298
321
  }
322
+ this.#pendingConnections.clear();
299
323
  }
300
324
 
301
325
  isConnectionPending(publicKey) {
@@ -306,6 +330,42 @@ class Network extends ReadyResource {
306
330
  return this.#pendingConnections.size;
307
331
  }
308
332
 
333
+ /** Executes a ban requested by the consensus connection policy. */
334
+ #handleConsensusPeerBanRequested({ connection, error } = {}) {
335
+ if (this.closing !== null || this.closed) return;
336
+
337
+ const publicKey = this.#normalizePublicKey(connection?.remotePublicKey);
338
+ if (!publicKey) return;
339
+
340
+ // Hyperswarm retains the ban for this swarm's lifetime, but does not close the socket.
341
+ const peerInfo = this.#swarm.peers.get(publicKey);
342
+ peerInfo?.ban(true);
343
+ const connections = new Set([connection]);
344
+ for (const activeConnection of this.#swarm.connections) {
345
+ if (this.#normalizePublicKey(activeConnection.remotePublicKey) === publicKey) {
346
+ connections.add(activeConnection);
347
+ }
348
+ }
349
+ const pending = this.#pendingConnections.get(publicKey);
350
+ if (pending) {
351
+ clearTimeout(pending.timeoutId);
352
+ this.#pendingConnections.delete(publicKey);
353
+ }
354
+ try {
355
+ this.#rejectAllPendingRequests(publicKey, error);
356
+ this.#validatorConnectionManager.remove(publicKey);
357
+ this.#indexerConnectionManager.remove(publicKey);
358
+ } finally {
359
+ for (const activeConnection of connections) activeConnection.destroy();
360
+ }
361
+ this.#logger.info(`Network: ${peerInfo?.banned ? 'banned' : 'disconnected'} consensus peer ${publicKey}: ${error.message}`);
362
+ }
363
+
364
+ #isPeerBanned(publicKey) {
365
+ const key = this.#normalizePublicKey(publicKey);
366
+ return this.#swarm.peers.get(key)?.banned === true;
367
+ }
368
+
309
369
  disconnectValidatorPeer(publicKey, reason = 'validator peer invalidated') {
310
370
  const publicKeyHex = this.#normalizePublicKey(publicKey);
311
371
  if (!publicKeyHex) return false;
@@ -322,62 +382,85 @@ class Network extends ReadyResource {
322
382
 
323
383
  if (isTrackedValidator) {
324
384
  this.#logger.debug(`Network.disconnectValidatorPeer: detaching tracked validator ${publicKeyHex}. Reason: ${reason}`);
325
- this.#validatorConnectionManager.remove(publicKeyHex, { endConnection: false });
385
+ this.#validatorConnectionManager.remove(publicKeyHex);
326
386
  }
327
387
 
328
388
  return hadPendingValidatorConnection || isTrackedValidator;
329
389
  }
330
390
 
331
- async #getOrGenerateWallet(store, wallet) {
332
- if (!this.#config.enableWallet) {
333
- const keyPair = await store.createKeyPair(TRAC_NAMESPACE);
334
- const wallet = await new WalletProvider(this.#config).fromSecretKey(keyPair.secretKey)
335
- return { keyPair, wallet }
391
+ async #getOrGenerateWallet() {
392
+ if (this.#config.enableWallet) {
393
+ const keyPair = { publicKey: this.#wallet.publicKey, secretKey: this.#wallet.secretKey }
394
+ return { keyPair, wallet: this.#wallet }
336
395
  } else {
337
- const keyPair = { publicKey: wallet.publicKey, secretKey: wallet.secretKey }
396
+ // This creates an ephemeral private key via holepunch facilities
397
+ const keyPair = await this.#store.createKeyPair(TRAC_NAMESPACE);
398
+ const wallet = await new WalletProvider(this.#config).fromSecretKey(keyPair.secretKey)
338
399
  return { keyPair, wallet }
339
400
  }
340
401
  }
341
402
 
342
- async tryConnect(publicKey, type = null) {
343
- if (this.#swarm === null) throw new Error('Network swarm is not initialized');
344
- if (this.#pendingConnections.has(publicKey) || this.#pendingConnections.size >= this.#maxPendingConnections) {
403
+ async tryConnect(publicKey, type) {
404
+ this.#assertCanReplicate();
405
+ if (!this.#swarm) throw new Error('Network swarm is not initialized');
406
+ if (this.#isPeerBanned(publicKey)) return CONNECTION_STATUS.IGNORED;
407
+ if (this.#pendingConnections.has(publicKey) || this.#pendingConnections.size >= this.#config.maxPendingConnections) {
345
408
  this.#logger.debug(`Network.tryConnect: Connection to peer: ${publicKey} as type: ${type} is already pending or max pending connections reached.`);
346
409
  return CONNECTION_STATUS.IGNORED;
347
410
  }
348
411
 
349
412
  const timeoutId = setTimeout(() => {
350
- if (!this.#pendingConnections.has(publicKey)) return;
351
- this.emit(EventType.VALIDATOR_CONNECTION_TIMEOUT, { publicKey, type, timeoutMs: this.#connectTimeoutMs });
352
- }, this.#connectTimeoutMs);
413
+ // timeout is here to manage the scenario which we will join peer and estabilish a connection (a bit more overhead for validators)
414
+ if (this.#pendingConnections.has(publicKey)) {
415
+ this.#pendingConnections.delete(publicKey)
416
+ }
417
+ }, this.#config.connectTimeoutMs);
418
+
353
419
  this.#pendingConnections.set(publicKey, { type, timeoutId });
354
420
 
355
421
  const target = b4a.from(publicKey, 'hex');
356
422
  // A retained peer may have lost explicit retries after leavePeer().
357
423
  if (!this.#swarm.peers.get(publicKey)?.explicit) {
358
424
  this.#swarm.joinPeer(target);
359
- }
425
+ }
360
426
 
361
427
  const peerInfo = this.#swarm.peers.get(publicKey);
362
- if (peerInfo) {
363
- const connection = this.#swarm._allConnections.get(peerInfo.publicKey);
364
-
365
- if (connection &&
366
- connection.protocolSession &&
367
- !this.#pendingRequestsService.isProbePending(connection.remotePublicKey.toString('hex'))
368
- ) {
369
- await this.#finalizeConnection(publicKey, type, connection);
370
- return CONNECTION_STATUS.CONNECTED;
428
+ if (!peerInfo) return CONNECTION_STATUS.PENDING;
429
+
430
+ const connection = this.#swarm._allConnections.get(peerInfo.publicKey);
431
+ // _allConnections also contains transports whose handshake is still in progress.
432
+ if (!connection || !this.#swarm.connections.has(connection)) return CONNECTION_STATUS.PENDING;
433
+
434
+ const isConnectionReady = (type === 'validator' && !this.#validatorPendingRequestService.isProbePending(connection.remotePublicKey.toString('hex'))) || type === 'indexer'
435
+ if (isConnectionReady) {
436
+ await this.#promotePendingConnection(publicKey, connection);
437
+ if (this.#closing || this.closed) return CONNECTION_STATUS.IGNORED;
438
+ if (this.#isPeerBanned(publicKey)) return CONNECTION_STATUS.IGNORED;
439
+ if (type === 'indexer' && !this.#indexerConnectionManager.connected(publicKey)) {
440
+ return CONNECTION_STATUS.IGNORED;
371
441
  }
372
- }
442
+ return CONNECTION_STATUS.CONNECTED;
443
+ }
373
444
 
374
445
  return CONNECTION_STATUS.PENDING;
375
446
  }
376
447
 
377
- async #finalizeConnection(publicKey, type, connection) {
378
- if (!this.#pendingConnections.has(publicKey)) return;
379
- this.emit(EventType.VALIDATOR_CONNECTION_READY, { publicKey, type, connection });
380
- this.#logger.debug(`Network.finalizeConnection: Connected to peer: ${publicKey} as type: ${type}`);
448
+ async #promotePendingConnection(publicKey, connection) {
449
+ const pending = this.#pendingConnections.get(publicKey);
450
+ if (pending) {
451
+ clearTimeout(pending.timeoutId);
452
+ this.#pendingConnections.delete(publicKey);
453
+
454
+ if (pending.type === 'indexer') {
455
+ await this.#indexerConnectionManager.add(connection.remotePublicKey, connection);
456
+ } else if (pending.type === 'validator') {
457
+ await this.#validatorConnectionManager.add(publicKey, connection);
458
+ // A pending probe may settle after its peer has been banned.
459
+ if (this.#closing || this.closed || this.#isPeerBanned(publicKey)) {
460
+ this.#validatorConnectionManager.remove(publicKey, connection);
461
+ }
462
+ }
463
+ }
381
464
  }
382
465
 
383
466
  #normalizePublicKey(publicKey) {
@@ -386,6 +469,11 @@ class Network extends ReadyResource {
386
469
  return null;
387
470
  }
388
471
 
472
+ #rejectAllPendingRequests(publicKey, error) {
473
+ this.#validatorPendingRequestService.rejectPendingRequestsForPeer(publicKey, error);
474
+ this.#indexerPendingRequestService.rejectPendingRequestsForPeer(publicKey, error);
475
+ }
476
+
389
477
  #clearPendingValidatorConnection(publicKeyHex) {
390
478
  if (!this.#pendingConnections.has(publicKeyHex)) return false;
391
479
 
@@ -403,18 +491,19 @@ class Network extends ReadyResource {
403
491
  }
404
492
  }
405
493
 
406
- #destroyConnection(connection) {
407
- if (!connection) return;
408
- if (connection.protocolSession) {
494
+ #closeProtocolSessions(connection) {
495
+ for (const session of Object.values(connection.protocolSessions ?? {})) {
409
496
  try {
410
- connection.protocolSession.close();
497
+ session?.close();
411
498
  } catch {}
412
499
  }
413
- if (typeof connection.destroy === 'function') {
414
- connection.destroy();
415
- } else if (typeof connection.end === 'function') {
416
- connection.end();
417
- }
500
+ }
501
+
502
+ #destroyConnection(connection) {
503
+ if (!connection) return;
504
+ this.#closeProtocolSessions(connection);
505
+ if (typeof connection.destroy === 'function') connection.destroy();
506
+ else if (typeof connection.end === 'function') connection.end();
418
507
  }
419
508
  }
420
509