trac-msb 0.2.7 → 0.2.9

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 (187) hide show
  1. package/.github/workflows/publish.yml +8 -16
  2. package/msb.mjs +13 -25
  3. package/package.json +8 -4
  4. package/proto/network.proto +74 -0
  5. package/rpc/{create_server.mjs → create_server.js} +4 -4
  6. package/rpc/{handlers.mjs → handlers.js} +7 -7
  7. package/rpc/routes/{index.mjs → index.js} +1 -1
  8. package/rpc/routes/{v1.mjs → v1.js} +1 -1
  9. package/rpc/rpc_server.js +10 -0
  10. package/rpc/rpc_services.js +48 -7
  11. package/rpc/utils/{helpers.mjs → helpers.js} +1 -1
  12. package/src/config/config.js +137 -0
  13. package/src/config/env.js +63 -0
  14. package/src/core/network/Network.js +133 -119
  15. package/src/core/network/identity/NetworkWalletFactory.js +5 -6
  16. package/src/core/network/protocols/LegacyProtocol.js +67 -0
  17. package/src/core/network/protocols/NetworkMessages.js +48 -0
  18. package/src/core/network/protocols/ProtocolInterface.js +31 -0
  19. package/src/core/network/protocols/ProtocolSession.js +59 -0
  20. package/src/core/network/protocols/V1Protocol.js +64 -0
  21. package/src/core/network/protocols/legacy/NetworkMessageRouter.js +84 -0
  22. package/src/core/network/protocols/legacy/handlers/GetRequestHandler.js +53 -0
  23. package/src/core/network/protocols/legacy/handlers/ResponseHandler.js +37 -0
  24. package/src/core/network/{messaging → protocols/legacy}/validators/ValidatorResponse.js +2 -2
  25. package/src/core/network/{messaging → protocols/legacy}/validators/base/BaseResponse.js +13 -6
  26. package/src/core/network/protocols/shared/handlers/RoleOperationHandler.js +88 -0
  27. package/src/core/network/protocols/shared/handlers/SubnetworkOperationHandler.js +93 -0
  28. package/src/core/network/protocols/shared/handlers/TransferOperationHandler.js +57 -0
  29. package/src/core/network/{messaging → protocols/shared}/handlers/base/BaseOperationHandler.js +21 -26
  30. package/src/core/network/{messaging → protocols/shared}/validators/PartialBootstrapDeployment.js +3 -3
  31. package/src/core/network/{messaging → protocols/shared}/validators/PartialRoleAccess.js +15 -12
  32. package/src/core/network/{messaging → protocols/shared}/validators/PartialTransaction.js +10 -11
  33. package/src/core/network/{messaging → protocols/shared}/validators/PartialTransfer.js +10 -7
  34. package/src/core/network/{messaging → protocols/shared}/validators/base/PartialOperation.js +40 -22
  35. package/src/core/network/protocols/v1/NetworkMessageRouter.js +15 -0
  36. package/src/core/network/services/ConnectionManager.js +13 -19
  37. package/src/core/network/services/MessageOrchestrator.js +10 -22
  38. package/src/core/network/services/TransactionPoolService.js +10 -10
  39. package/src/core/network/services/TransactionRateLimiterService.js +5 -3
  40. package/src/core/network/services/ValidatorObserverService.js +46 -21
  41. package/src/core/state/State.js +137 -141
  42. package/src/core/state/utils/address.js +18 -16
  43. package/src/core/state/utils/adminEntry.js +17 -16
  44. package/src/core/state/utils/deploymentEntry.js +15 -15
  45. package/src/core/state/utils/transaction.js +3 -95
  46. package/src/index.js +250 -325
  47. package/src/messages/network/v1/NetworkMessageBuilder.js +325 -0
  48. package/src/messages/network/v1/NetworkMessageDirector.js +137 -0
  49. package/src/messages/network/v1/networkMessageFactory.js +12 -0
  50. package/src/messages/state/ApplyStateMessageBuilder.js +661 -0
  51. package/src/messages/state/ApplyStateMessageDirector.js +516 -0
  52. package/src/messages/state/applyStateMessageFactory.js +12 -0
  53. package/src/utils/buffer.js +53 -1
  54. package/src/utils/check.js +21 -17
  55. package/src/utils/cli.js +0 -8
  56. package/src/utils/cliCommands.js +11 -11
  57. package/src/utils/constants.js +36 -24
  58. package/src/utils/fileUtils.js +1 -4
  59. package/src/utils/helpers.js +9 -20
  60. package/src/utils/migrationUtils.js +2 -2
  61. package/src/utils/normalizers.js +94 -11
  62. package/src/utils/protobuf/network.cjs +840 -0
  63. package/src/utils/protobuf/operationHelpers.js +10 -0
  64. package/tests/acceptance/v1/account/account.test.mjs +2 -2
  65. package/tests/acceptance/v1/balance/balance.test.mjs +1 -1
  66. package/tests/acceptance/v1/broadcast-transaction/broadcast-transaction.test.mjs +11 -2
  67. package/tests/acceptance/v1/rpc.test.mjs +10 -10
  68. package/tests/acceptance/v1/tx/tx.test.mjs +4 -2
  69. package/tests/acceptance/v1/tx-details/tx-details.test.mjs +7 -3
  70. package/tests/fixtures/check.fixtures.js +42 -42
  71. package/tests/fixtures/networkV1.fixtures.js +84 -0
  72. package/tests/fixtures/protobuf.fixtures.js +110 -26
  73. package/tests/helpers/StateNetworkFactory.js +3 -5
  74. package/tests/helpers/autobaseTestHelpers.js +1 -2
  75. package/tests/helpers/config.js +3 -0
  76. package/tests/helpers/setupApplyTests.js +113 -99
  77. package/tests/helpers/transactionPayloads.mjs +26 -12
  78. package/tests/unit/messages/messages.test.js +12 -0
  79. package/tests/unit/messages/network/NetworkMessageBuilder.test.js +276 -0
  80. package/tests/unit/messages/network/NetworkMessageDirector.test.js +203 -0
  81. package/tests/unit/messages/state/applyStateMessageBuilder.complete.test.js +521 -0
  82. package/tests/unit/messages/state/applyStateMessageBuilder.partial.test.js +233 -0
  83. package/tests/unit/network/ConnectionManager.test.js +10 -7
  84. package/tests/unit/network/NetworkWalletFactory.test.js +14 -14
  85. package/tests/unit/network/networkModule.test.js +3 -2
  86. package/tests/unit/state/apply/addAdmin/addAdminHappyPathScenario.js +10 -6
  87. package/tests/unit/state/apply/addAdmin/addAdminScenarioHelpers.js +11 -8
  88. package/tests/unit/state/apply/addAdmin/state.apply.addAdmin.test.js +11 -7
  89. package/tests/unit/state/apply/addIndexer/addIndexerScenarioHelpers.js +18 -20
  90. package/tests/unit/state/apply/addWriter/addWriterScenarioHelpers.js +57 -48
  91. package/tests/unit/state/apply/addWriter/addWriterValidatorRewardScenario.js +2 -1
  92. package/tests/unit/state/apply/adminRecovery/adminRecoveryScenarioHelpers.js +72 -57
  93. package/tests/unit/state/apply/adminRecovery/state.apply.adminRecovery.test.js +3 -7
  94. package/tests/unit/state/apply/appendWhitelist/appendWhitelistScenarioHelpers.js +12 -14
  95. package/tests/unit/state/apply/balanceInitialization/balanceInitializationScenarioHelpers.js +18 -13
  96. package/tests/unit/state/apply/balanceInitialization/nodeEntryBalanceUpdateFailureScenario.js +2 -1
  97. package/tests/unit/state/apply/banValidator/banValidatorBanAndReWhitelistScenario.js +2 -1
  98. package/tests/unit/state/apply/banValidator/banValidatorScenarioHelpers.js +27 -30
  99. package/tests/unit/state/apply/bootstrapDeployment/bootstrapDeploymentDuplicateRegistrationScenario.js +2 -1
  100. package/tests/unit/state/apply/bootstrapDeployment/bootstrapDeploymentScenarioHelpers.js +24 -21
  101. package/tests/unit/state/apply/common/access-control/adminConsistencyMismatchScenario.js +5 -4
  102. package/tests/unit/state/apply/common/access-control/adminPublicKeyDecodeFailureScenario.js +4 -3
  103. package/tests/unit/state/apply/common/balances/base/requesterBalanceScenarioBase.js +2 -1
  104. package/tests/unit/state/apply/common/commonScenarioHelper.js +16 -16
  105. package/tests/unit/state/apply/common/payload-structure/initializationDisabledScenario.js +10 -5
  106. package/tests/unit/state/apply/common/payload-structure/invalidHashValidationScenario.js +2 -2
  107. package/tests/unit/state/apply/common/requester/requesterNodeEntryBufferMissingScenario.js +2 -1
  108. package/tests/unit/state/apply/common/requester/requesterNodeEntryDecodeFailureScenario.js +2 -1
  109. package/tests/unit/state/apply/common/validatorConsistency/base/validatorConsistencyScenarioBase.js +2 -1
  110. package/tests/unit/state/apply/common/validatorEntryValidation/base/validatorEntryValidationScenarioBase.js +2 -1
  111. package/tests/unit/state/apply/disableInitialization/disableInitializationScenarioHelpers.js +16 -9
  112. package/tests/unit/state/apply/removeIndexer/removeIndexerScenarioHelpers.js +6 -5
  113. package/tests/unit/state/apply/removeWriter/removeWriterScenarioHelpers.js +23 -19
  114. package/tests/unit/state/apply/transfer/transferDoubleSpendAcrossValidatorsScenario.js +45 -36
  115. package/tests/unit/state/apply/transfer/transferScenarioHelpers.js +48 -45
  116. package/tests/unit/state/apply/txOperation/txOperationScenarioHelpers.js +32 -29
  117. package/tests/unit/state/apply/txOperation/txOperationTransferFeeGuardScenarioFactory.js +2 -1
  118. package/tests/unit/state/stateModule.test.js +0 -1
  119. package/tests/unit/state/stateTestUtils.js +7 -3
  120. package/tests/unit/state/utils/address.test.js +3 -3
  121. package/tests/unit/state/utils/adminEntry.test.js +10 -9
  122. package/tests/unit/unit.test.js +1 -1
  123. package/tests/unit/utils/buffer/buffer.test.js +62 -1
  124. package/tests/unit/utils/check/adminControlOperation.test.js +3 -3
  125. package/tests/unit/utils/check/balanceInitializationOperation.test.js +2 -2
  126. package/tests/unit/utils/check/bootstrapDeploymentOperation.test.js +2 -3
  127. package/tests/unit/utils/check/common.test.js +7 -6
  128. package/tests/unit/utils/check/coreAdminOperation.test.js +3 -3
  129. package/tests/unit/utils/check/roleAccessOperation.test.js +3 -2
  130. package/tests/unit/utils/check/transactionOperation.test.js +3 -3
  131. package/tests/unit/utils/check/transferOperation.test.js +3 -3
  132. package/tests/unit/utils/fileUtils/readAddressesFromWhitelistFile.test.js +2 -1
  133. package/tests/unit/utils/fileUtils/readBalanceMigrationFile.test.js +2 -1
  134. package/tests/unit/utils/migrationUtils/validateAddressFromIncomingFile.test.js +7 -0
  135. package/tests/unit/utils/normalizers/normalizers.test.js +469 -0
  136. package/tests/unit/utils/protobuf/operationHelpers.test.js +120 -2
  137. package/tests/unit/utils/utils.test.js +0 -1
  138. package/rpc/rpc_server.mjs +0 -10
  139. package/src/core/network/messaging/NetworkMessages.js +0 -63
  140. package/src/core/network/messaging/handlers/GetRequestHandler.js +0 -112
  141. package/src/core/network/messaging/handlers/ResponseHandler.js +0 -108
  142. package/src/core/network/messaging/handlers/RoleOperationHandler.js +0 -116
  143. package/src/core/network/messaging/handlers/SubnetworkOperationHandler.js +0 -143
  144. package/src/core/network/messaging/handlers/TransferOperationHandler.js +0 -52
  145. package/src/core/network/messaging/routes/NetworkMessageRouter.js +0 -94
  146. package/src/core/network/messaging/validators/AdminResponse.js +0 -58
  147. package/src/core/network/messaging/validators/CustomNodeResponse.js +0 -46
  148. package/src/core/state/utils/indexerEntry.js +0 -105
  149. package/src/messages/base/StateBuilder.js +0 -25
  150. package/src/messages/completeStateMessages/CompleteStateMessageBuilder.js +0 -421
  151. package/src/messages/completeStateMessages/CompleteStateMessageDirector.js +0 -252
  152. package/src/messages/completeStateMessages/CompleteStateMessageOperations.js +0 -299
  153. package/src/messages/partialStateMessages/PartialStateMessageBuilder.js +0 -272
  154. package/src/messages/partialStateMessages/PartialStateMessageDirector.js +0 -137
  155. package/src/messages/partialStateMessages/PartialStateMessageOperations.js +0 -131
  156. package/src/utils/crypto.js +0 -11
  157. package/tests/integration/apply/addAdmin/addAdminBasic.test.js +0 -68
  158. package/tests/integration/apply/addAdmin/addAdminRecovery.test.js +0 -125
  159. package/tests/integration/apply/addIndexer.test.js +0 -237
  160. package/tests/integration/apply/addWhitelist.test.js +0 -53
  161. package/tests/integration/apply/addWriter.test.js +0 -244
  162. package/tests/integration/apply/apply.test.js +0 -19
  163. package/tests/integration/apply/banValidator.test.js +0 -109
  164. package/tests/integration/apply/postTx/invalidSubValues.test.js +0 -103
  165. package/tests/integration/apply/postTx/postTx.test.js +0 -222
  166. package/tests/integration/apply/removeIndexer.test.js +0 -128
  167. package/tests/integration/apply/removeWriter.test.js +0 -167
  168. package/tests/integration/apply/transfer.test.js +0 -81
  169. package/tests/integration/integration.test.js +0 -9
  170. package/tests/unit/messageOperations/assembleAddIndexerMessage.test.js +0 -21
  171. package/tests/unit/messageOperations/assembleAddWriterMessage.test.js +0 -16
  172. package/tests/unit/messageOperations/assembleAdminMessage.test.js +0 -69
  173. package/tests/unit/messageOperations/assembleBanWriterMessage.test.js +0 -16
  174. package/tests/unit/messageOperations/assemblePostTransaction.test.js +0 -442
  175. package/tests/unit/messageOperations/assembleRemoveIndexerMessage.test.js +0 -19
  176. package/tests/unit/messageOperations/assembleRemoveWriterMessage.test.js +0 -17
  177. package/tests/unit/messageOperations/assembleWhitelistMessages.test.js +0 -58
  178. package/tests/unit/messageOperations/commonsStateMessageOperationsTest.js +0 -277
  179. package/tests/unit/messageOperations/stateMessageOperations.test.js +0 -19
  180. package/tests/unit/state/utils/indexerEntry.test.js +0 -83
  181. package/tests/unit/state/utils/transaction.test.js +0 -97
  182. package/tests/unit/utils/crypto/createHash.test.js +0 -15
  183. /package/rpc/{constants.mjs → constants.js} +0 -0
  184. /package/rpc/{cors.mjs → cors.js} +0 -0
  185. /package/rpc/utils/{confirmedParameter.mjs → confirmedParameter.js} +0 -0
  186. /package/rpc/utils/{url.mjs → url.js} +0 -0
  187. /package/src/utils/{operations.js → applyOperations.js} +0 -0
@@ -2,10 +2,9 @@ import ReadyResource from 'ready-resource';
2
2
  import Hyperswarm from 'hyperswarm';
3
3
  import w from 'protomux-wakeup';
4
4
  import b4a from 'b4a';
5
-
6
5
  import TransactionPoolService from './services/TransactionPoolService.js';
7
6
  import ValidatorObserverService from './services/ValidatorObserverService.js';
8
- import NetworkMessages from './messaging/NetworkMessages.js';
7
+ import NetworkMessages from './protocols/NetworkMessages.js';
9
8
  import { sleep } from '../../utils/helpers.js';
10
9
  import {
11
10
  TRAC_NAMESPACE,
@@ -13,52 +12,61 @@ import {
13
12
  MAX_PARALLEL,
14
13
  MAX_SERVER_CONNECTIONS,
15
14
  MAX_CLIENT_CONNECTIONS,
16
- NETWORK_MESSAGE_TYPES,
17
- DHT_BOOTSTRAPS
15
+ NETWORK_MESSAGE_TYPES
18
16
  } from '../../utils/constants.js';
19
17
  import ConnectionManager from './services/ConnectionManager.js';
20
18
  import MessageOrchestrator from './services/MessageOrchestrator.js';
21
19
  import NetworkWalletFactory from './identity/NetworkWalletFactory.js';
20
+ import { EventType } from '../../utils/constants.js';
21
+ import { networkMessageFactory } from '../../messages/network/v1/networkMessageFactory.js';
22
+ import TransactionRateLimiterService from './services/TransactionRateLimiterService.js';
23
+ // -- Debug Mode --
24
+ // TODO: Implement a better debug system in the future. This is just temporary.
25
+ const DEBUG = false;
26
+ const debugLog = (...args) => {
27
+ if (DEBUG) {
28
+ console.log('DEBUG [Network] ==> ', ...args);
29
+ }
30
+ };
31
+
22
32
  const wakeup = new w();
23
33
 
24
34
  class Network extends ReadyResource {
25
- #dht_bootstrap = DHT_BOOTSTRAPS;
26
35
  #swarm = null;
27
- #enable_wallet;
28
- #channel;
29
36
  #networkMessages;
30
37
  #transactionPoolService;
31
38
  #validatorObserverService;
32
39
  #validatorConnectionManager;
33
40
  #validatorMessageOrchestrator;
34
- #options;
41
+ #config;
35
42
  #identityProvider = null;
36
-
37
- constructor(state, channel, address = null, options = {}) {
43
+ #pendingConnections;
44
+ #connectTimeoutMs;
45
+ #maxPendingConnections;
46
+ #rateLimiter;
47
+
48
+ /**
49
+ * @param {State} state
50
+ * @param {object} config
51
+ * @param {string} address
52
+ **/
53
+ constructor(state, config, address = null) {
38
54
  super();
39
- this.#options = options;
40
- this.#enable_wallet = options.enable_wallet !== false;
41
- this.#channel = channel;
42
- this.#transactionPoolService = new TransactionPoolService(state, address, options);
43
- this.#validatorObserverService = new ValidatorObserverService(this, state, address, options);
44
- this.#networkMessages = new NetworkMessages(this, options);
45
- this.#validatorConnectionManager = new ConnectionManager({ maxValidators: options.max_validators });
46
- this.#validatorMessageOrchestrator = new MessageOrchestrator(this.#validatorConnectionManager, state);
47
- this.admin_stream = null;
48
- this.admin = null;
49
- this.validator = null;
50
- this.custom_stream = null;
51
- this.custom_node = null;
55
+ this.#config = config
56
+ this.#connectTimeoutMs = config.connectTimeoutMs || 5000;
57
+ this.#maxPendingConnections = config.maxPendingConnections || 50;
58
+ this.#pendingConnections = new Map();
59
+ this.#transactionPoolService = new TransactionPoolService(state, address, this.#config);
60
+ this.#validatorObserverService = new ValidatorObserverService(this, state, address, this.#config);
61
+ this.#validatorConnectionManager = new ConnectionManager(this.#config);
62
+ this.#validatorMessageOrchestrator = new MessageOrchestrator(this.#validatorConnectionManager, state, this.#config);
63
+
52
64
  }
53
65
 
54
66
  get swarm() {
55
67
  return this.#swarm;
56
68
  }
57
69
 
58
- get channel() {
59
- return this.#channel;
60
- }
61
-
62
70
  get transactionPoolService() {
63
71
  return this.#transactionPoolService;
64
72
  }
@@ -77,22 +85,64 @@ class Network extends ReadyResource {
77
85
 
78
86
  async _open() {
79
87
  console.log('Network initialization...');
88
+
89
+ this.setupNetworkListeners();
90
+
80
91
  this.transactionPoolService.start();
81
92
  this.validatorObserverService.start();
82
93
  }
83
94
 
84
95
  async _close() {
96
+ // TODO: Implement better "await" logic for stopping services
85
97
  console.log('Network: closing gracefully...');
86
98
  this.transactionPoolService.stopPool();
87
99
  await sleep(100);
88
100
  this.#validatorObserverService.stopValidatorObserver();
89
101
  await sleep(5_000);
90
102
 
103
+ this.cleanupNetworkListeners();
104
+ this.cleanupPendingConnections();
105
+
91
106
  if (this.#swarm !== null) {
92
107
  this.#swarm.destroy();
93
108
  }
94
109
  }
95
110
 
111
+ setupNetworkListeners() {
112
+ // VALIDATOR_CONNECTION_TIMEOUT
113
+ this.on(EventType.VALIDATOR_CONNECTION_TIMEOUT, ({ publicKey, type, timeoutMs }) => {
114
+ debugLog(`Network Event: VALIDATOR_CONNECTION_TIMEOUT | PublicKey: ${publicKey} | Type: ${type} | TimeoutMs: ${timeoutMs}`);
115
+ this.#pendingConnections.delete(publicKey);
116
+ });
117
+
118
+ // VALIDATOR_CONNECTION_READY
119
+ this.on(EventType.VALIDATOR_CONNECTION_READY, async ({ publicKey, type, connection }) => {
120
+ debugLog(`Network Event: VALIDATOR_CONNECTION_READY | PublicKey: ${publicKey} | Type: ${type}`);
121
+ const { timeoutId } = this.#pendingConnections.get(publicKey);
122
+ if (!timeoutId) return;
123
+
124
+ clearTimeout(timeoutId);
125
+ this.#pendingConnections.delete(publicKey);
126
+
127
+ if (type === 'validator') {
128
+ await connection.protocolSession.send(NETWORK_MESSAGE_TYPES.GET.VALIDATOR);
129
+ }
130
+
131
+ });
132
+ }
133
+
134
+ cleanupNetworkListeners() {
135
+ this.removeAllListeners(EventType.VALIDATOR_CONNECTION_TIMEOUT);
136
+ this.removeAllListeners(EventType.VALIDATOR_CONNECTION_READY);
137
+ }
138
+
139
+ cleanupPendingConnections() {
140
+ for (const { timeoutId } of this.#pendingConnections.values()) {
141
+ clearTimeout(timeoutId);
142
+ }
143
+ this.#pendingConnections.clear();
144
+ }
145
+
96
146
  async replicate(
97
147
  state,
98
148
  store,
@@ -103,36 +153,45 @@ class Network extends ReadyResource {
103
153
  const wrappedWallet = this.#getNetworkWalletWrapper(wallet, keyPair);
104
154
  this.#swarm = new Hyperswarm({
105
155
  keyPair,
106
- bootstrap: this.#dht_bootstrap,
156
+ bootstrap: this.#config.dhtBootstrap,
107
157
  maxPeers: MAX_PEERS,
108
158
  maxParallel: MAX_PARALLEL,
109
159
  maxServerConnections: MAX_SERVER_CONNECTIONS,
110
160
  maxClientConnections: MAX_CLIENT_CONNECTIONS
111
161
  });
112
162
 
113
- console.log(`Channel: ${b4a.toString(this.#channel)}`);
114
- this.#networkMessages.initializeMessageRouter(state, wrappedWallet);
163
+ this.#rateLimiter = new TransactionRateLimiterService(this.#swarm);
164
+ this.#networkMessages = new NetworkMessages(
165
+ state,
166
+ wrappedWallet,
167
+ this.#rateLimiter,
168
+ this.#transactionPoolService,
169
+ this.#validatorConnectionManager,
170
+ this.#config
171
+ );
172
+
173
+ console.log(`Channel: ${b4a.toString(this.#config.channel)}`);
115
174
 
116
175
  this.#swarm.on('connection', async (connection) => {
117
- const { message_channel, message } = await this.#networkMessages.setupProtomuxMessages(connection);
118
- connection.messenger = message;
176
+ // Per-peer connection initialization:
177
+ // - attach Protomux (legacy + v1 channels/messages)
178
+ // - attach connection.protocolSession (used later by tryConnect / orchestrators to send messages)
179
+ await this.#networkMessages.setupProtomuxMessages(connection);
119
180
 
120
181
  // ATTENTION: Must be called AFTER the protomux init above
121
182
  const stream = store.replicate(connection);
122
183
  wakeup.addStream(stream);
123
184
 
124
- connection.on('close', () => {
125
- if (this.admin_stream === connection) {
126
- this.admin_stream = null;
127
- this.admin = null;
128
- }
129
-
130
- if (this.custom_stream === connection) {
131
- this.custom_stream = null;
132
- this.custom_node = null;
133
- }
134
- try { message_channel.close() } catch (e) { }
185
+ const publicKey = b4a.toString(connection.remotePublicKey, 'hex');
186
+ if (this.#pendingConnections.has(publicKey)) {
187
+ const { type } = this.#pendingConnections.get(publicKey);
188
+ await this.#finalizeConnection(publicKey, type, connection);
189
+ }
135
190
 
191
+ connection.on('close', () => {
192
+ this.#swarm.leavePeer(connection.remotePublicKey);
193
+ this.#validatorConnectionManager.remove(publicKey);
194
+ connection.protocolSession.close();
136
195
  });
137
196
 
138
197
  connection.on('error', (error) => {
@@ -146,18 +205,25 @@ class Network extends ReadyResource {
146
205
  return;
147
206
  }
148
207
  console.error(error.message)
149
-
150
208
  });
151
209
 
152
210
  });
153
211
 
154
- this.#swarm.join(this.#channel, { server: true, client: true });
212
+ this.#swarm.join(this.#config.channel, { server: true, client: true });
155
213
  this.#swarm.flush();
156
214
  }
157
215
  }
158
216
 
217
+ isConnectionPending(publicKey) {
218
+ return this.#pendingConnections.has(publicKey);
219
+ }
220
+
221
+ pendingConnectionsCount() {
222
+ return this.#pendingConnections.size;
223
+ }
224
+
159
225
  async initializeNetworkingKeyPair(store, wallet) {
160
- if (!this.#enable_wallet) {
226
+ if (!this.#config.enableWallet) {
161
227
  return await store.createKeyPair(TRAC_NAMESPACE);
162
228
  } else {
163
229
  return {
@@ -169,34 +235,29 @@ class Network extends ReadyResource {
169
235
 
170
236
  async tryConnect(publicKey, type = null) {
171
237
  if (this.#swarm === null) throw new Error('Network swarm is not initialized');
238
+ if (this.#pendingConnections.has(publicKey) || this.#pendingConnections.size >= this.#maxPendingConnections) {
239
+ debugLog(`Network.tryConnect: Connection to peer: ${publicKey} as type: ${type} is already pending or max pending connections reached.`);
240
+ return;
241
+ }
242
+
243
+ const timeoutId = setTimeout(() => {
244
+ if (!this.#pendingConnections.has(publicKey)) return;
245
+ this.emit(EventType.VALIDATOR_CONNECTION_TIMEOUT, { publicKey, type, timeoutMs: this.#connectTimeoutMs });
246
+ }, this.#connectTimeoutMs);
247
+ this.#pendingConnections.set(publicKey, { type, timeoutId });
172
248
 
173
249
  const target = b4a.from(publicKey, 'hex');
174
250
  if (!this.#swarm.peers.has(publicKey)) {
175
251
  this.#swarm.joinPeer(target);
176
- let cnt = 0;
177
- while (!this.#swarm.peers.has(publicKey) && cnt < 1500) { // TODO: Get rid of the magic number and add a config option for this
178
- await sleep(10);
179
- cnt += 1;
180
- }
181
252
  }
182
253
 
183
254
  const peerInfo = this.#swarm.peers.get(publicKey);
184
- if (!peerInfo) return;
185
-
186
- // Wait for the swarm to establish the connection and for protomux to attach
187
- let stream = this.#swarm._allConnections.get(peerInfo.publicKey);
188
- let attempts = 0;
189
- while ((!stream || !stream.messenger) && attempts < 1500) { // TODO: Get rid of the magic number and add a config option
190
- await sleep(10);
191
- attempts += 1;
192
- stream = this.#swarm._allConnections.get(peerInfo.publicKey);
193
- }
194
- if (!stream || !stream.messenger) return;
195
-
196
- if (type === 'validator') {
197
- this.#validatorConnectionManager.addValidator(target, stream);
255
+ if (peerInfo) {
256
+ const connection = this.#swarm._allConnections.get(peerInfo.publicKey);
257
+ if (connection && connection.protocolSession) {
258
+ await this.#finalizeConnection(publicKey, type, connection);
259
+ }
198
260
  }
199
- await this.#sendRequestByType(stream, type);
200
261
  }
201
262
 
202
263
  async isConnected(publicKey) {
@@ -204,66 +265,19 @@ class Network extends ReadyResource {
204
265
  this.#swarm.peers.get(publicKey).connectedTime != -1
205
266
  }
206
267
 
207
- async #sendRequestByType(stream, type) {
208
- const waitFor = {
209
- validator: () => this.validatorConnectionManager.connectionCount(),
210
- admin: () => this.admin_stream,
211
- node: () => this.custom_stream
212
- }[type];
213
-
214
- if (type === 'validator') {
215
- await stream.messenger.send(NETWORK_MESSAGE_TYPES.GET.VALIDATOR);
216
- } else if (type === 'admin') {
217
- await stream.messenger.send(NETWORK_MESSAGE_TYPES.GET.ADMIN);
218
- } else if (type === 'node') {
219
- await stream.messenger.send(NETWORK_MESSAGE_TYPES.GET.NODE);
220
- } else {
221
- return;
222
- }
223
- await this.spinLock(() => !waitFor())
224
- };
225
-
226
- async spinLock(conditionFn, maxIterations = 1500, intervalMs = 10) {
227
- let counter = 0;
228
- while (conditionFn() && counter < maxIterations) {
229
- await sleep(intervalMs);
230
- counter++;
231
- }
232
- }
233
-
234
- async sendMessageToNode(nodePublicKey, message) {
235
- try {
236
- if (!nodePublicKey || !message) {
237
- return;
238
- }
239
- await this.tryConnect(nodePublicKey, 'node');
240
-
241
- await this.spinLock(() =>
242
- this.custom_stream === null ||
243
- !b4a.equals(this.custom_node, b4a.from(nodePublicKey, 'hex'))
244
- );
245
- if (
246
- this.custom_stream !== null &&
247
- this.custom_node !== null &&
248
- b4a.equals(this.custom_node, b4a.from(nodePublicKey, 'hex'))
249
- ) {
250
- await this.custom_stream.messenger.send(message);
251
- } else {
252
- throw new Error(`Failed to send message to node: ${nodePublicKey}`);
253
- }
254
-
255
- } catch (e) {
256
- console.log(e)
257
- }
268
+ async #finalizeConnection(publicKey, type, connection) {
269
+ if (!this.#pendingConnections.has(publicKey)) return;
270
+ this.emit(EventType.VALIDATOR_CONNECTION_READY, { publicKey, type, connection });
271
+ debugLog(`Network.finalizeConnection: Connected to peer: ${publicKey} as type: ${type}`);
258
272
  }
259
273
 
260
274
  #getNetworkWalletWrapper(wallet, keyPair) {
261
275
  if (!this.#identityProvider) {
262
276
  this.#identityProvider = NetworkWalletFactory.provide({
263
- enableWallet: this.#enable_wallet,
277
+ enableWallet: this.#config.enableWallet,
264
278
  wallet,
265
279
  keyPair,
266
- networkPrefix: this.#options?.networkPrefix
280
+ networkPrefix: this.#config.addressPrefix
267
281
  });
268
282
  }
269
283
  return this.#identityProvider;
@@ -1,14 +1,13 @@
1
1
  import PeerWallet from 'trac-wallet';
2
- import { TRAC_NETWORK_MSB_MAINNET_PREFIX } from 'trac-wallet/constants.js';
3
2
  import b4a from 'b4a';
4
3
 
5
- export class NetworkWalletFactory {
4
+ class NetworkWalletFactory {
6
5
  static provide(options = {}) {
7
6
  const {
8
- enableWallet = true,
7
+ enableWallet,
9
8
  wallet,
10
9
  keyPair,
11
- networkPrefix = TRAC_NETWORK_MSB_MAINNET_PREFIX
10
+ networkPrefix
12
11
  } = options;
13
12
 
14
13
  if (enableWallet) {
@@ -29,12 +28,12 @@ export class NetworkWalletFactory {
29
28
  // TODO: Once Wallet class in trac-wallet exposes a constructor/factory that accepts an existing keyPair
30
29
  // (e.g. Wallet.fromKeyPair({ publicKey, secretKey }, networkPrefix)), replace EphemeralWallet
31
30
  // with a thin wrapper around that functionality instead of duplicating signing/verification logic.
32
- class EphemeralWallet {
31
+ export class EphemeralWallet {
33
32
  #publicKey;
34
33
  #secretKey;
35
34
  #address;
36
35
 
37
- constructor(keyPair, networkPrefix = TRAC_NETWORK_MSB_MAINNET_PREFIX) {
36
+ constructor(keyPair, networkPrefix) {
38
37
 
39
38
  if (!keyPair?.publicKey || !keyPair?.secretKey) {
40
39
  throw new Error('NetworkIdentityProvider: keyPair with publicKey and secretKey is required');
@@ -0,0 +1,67 @@
1
+ import Protomux from 'protomux';
2
+ import ProtocolInterface from './ProtocolInterface.js';
3
+ import b4a from 'b4a';
4
+ import c from 'compact-encoding';
5
+
6
+ class LegacyProtocol extends ProtocolInterface {
7
+ #channel;
8
+ #session;
9
+ #config;
10
+ #router;
11
+
12
+ constructor(router, connection, config) {
13
+ super(router, connection, config);
14
+ this.#config = config;
15
+ this.#router = router;
16
+ this.init(connection);
17
+ }
18
+
19
+ get channel() {
20
+ return this.#channel;
21
+ }
22
+
23
+ get session() {
24
+ return this.#session;
25
+ }
26
+
27
+ init(connection) {
28
+ // TODO: Abstract in a separate function
29
+ const mux = Protomux.from(connection);
30
+ connection.userData = mux;
31
+
32
+ this.#channel = mux.createChannel({
33
+ protocol: b4a.toString(this.#config.channel, 'utf8'),
34
+ onopen() { },
35
+ onclose() { }
36
+ });
37
+
38
+ this.#channel.open();
39
+
40
+ // Todo: Abstract in a separate function
41
+ this.#session = this.#channel.addMessage({
42
+ encoding: c.json,
43
+ onmessage: async (incomingMessage) => {
44
+ try {
45
+ if (typeof incomingMessage === 'object' || typeof incomingMessage === 'string') {
46
+ await this.#router.route(incomingMessage, connection, this.#session);
47
+ } else {
48
+ throw new Error('NetworkMessages: Received message is undefined');
49
+ }
50
+ } catch (error) {
51
+ console.error(`NetworkMessages: Failed to handle incoming message: ${error.message}`);
52
+ }
53
+ }
54
+ });
55
+ }
56
+
57
+ send(message) {
58
+ this.#session.send(message);
59
+ }
60
+
61
+ close() {
62
+ this.#channel.close();
63
+ }
64
+
65
+ }
66
+
67
+ export default LegacyProtocol;
@@ -0,0 +1,48 @@
1
+
2
+ import b4a from 'b4a';
3
+ import NetworkMessageRouter from './legacy/NetworkMessageRouter.js';
4
+ import NetworkMessageRouterV1 from './v1/NetworkMessageRouter.js';
5
+ import ProtocolSession from './ProtocolSession.js';
6
+ import LegacyProtocol from './LegacyProtocol.js';
7
+ import V1Protocol from './V1Protocol.js';
8
+ class NetworkMessages {
9
+ #legacyMessageRouter;
10
+ #v1MessageRouter;
11
+ #config;
12
+
13
+ /**
14
+ * @param {object} config
15
+ **/
16
+ constructor(state, wallet, rateLimiterService, txPoolService, connectionManager, config) {
17
+ this.#config = config;
18
+ this.#initializeMessageRouter(state, wallet, rateLimiterService, txPoolService, connectionManager);
19
+ }
20
+
21
+ #initializeMessageRouter(state, wallet, rateLimiterService, txPoolService, connectionManager) {
22
+ this.#legacyMessageRouter = new NetworkMessageRouter(
23
+ state,
24
+ wallet,
25
+ rateLimiterService,
26
+ txPoolService,
27
+ connectionManager,
28
+ this.#config
29
+ );
30
+ this.#v1MessageRouter = new NetworkMessageRouterV1(this.#config);
31
+ }
32
+
33
+ async setupProtomuxMessages(connection) {
34
+ // Attach a Protomux instance to this Hyperswarm connection.
35
+ // Protomux multiplexes multiple logical protocol channels over a single encrypted stream.
36
+
37
+ const legacyProtocol = new LegacyProtocol(this.#legacyMessageRouter, connection, this.#config);
38
+ const v1Protocol = new V1Protocol(this.#v1MessageRouter, connection, this.#config);
39
+
40
+ // ProtocolSession is attached to the Hyperswarm connection so other parts of the system (e.g. tryConnect)
41
+ // can send messages without knowing how Protomux was initialized.
42
+ const protocolSession = new ProtocolSession(legacyProtocol, v1Protocol);
43
+ connection.protocolSession = protocolSession;
44
+
45
+ }
46
+ }
47
+
48
+ export default NetworkMessages;
@@ -0,0 +1,31 @@
1
+ /**
2
+ * ProtocolInterface serves as a base class for all network protocol implementations.
3
+ * @interface
4
+ */
5
+
6
+ class ProtocolInterface {
7
+
8
+ // TODO: Refactor this so we don't need to pass a reference for the whole network instance
9
+ constructor(router, connection, config) {
10
+ if (new.target === ProtocolInterface) {
11
+ throw new Error('ProtocolInterface cannot be instantiated directly');
12
+ }
13
+ }
14
+
15
+ init(connection) {
16
+ // Abstract method. Need to be implemented by subclasses.
17
+ throw new Error('init() method must be implemented by subclass');
18
+ }
19
+
20
+ send(message) {
21
+ // Abstract method. Need to be implemented by subclasses.
22
+ throw new Error('send() method must be implemented by subclass');
23
+ }
24
+
25
+ close() {
26
+ // Abstract method. Need to be implemented by subclasses.
27
+ throw new Error('close() method must be implemented by subclass');
28
+ }
29
+ }
30
+
31
+ export default ProtocolInterface;
@@ -0,0 +1,59 @@
1
+ // ProtocolSession is a per-peer (per Hyperswarm connection) wrapper that exposes the available
2
+ // protocol messengers (legacy JSON, v1 binary) in one place.
3
+ //
4
+ // Why it exists:
5
+ // - `setupProtomuxMessages(connection)` creates Protomux channels/messages for a specific peer.
6
+ class ProtocolSession {
7
+ #legacyProtocol;
8
+ #v1Protocol;
9
+
10
+ constructor(legacyProtocol, v1Protocol) {
11
+ // These are Protomux "message" objects (returned by channel.addMessage).
12
+ // They are connection-scoped and expose .send(...), already wired to the channel's encoding.
13
+ this.#legacyProtocol = legacyProtocol;
14
+ this.#v1Protocol = v1Protocol;
15
+ }
16
+
17
+ getLegacy() {
18
+ return this.#legacyProtocol;
19
+ }
20
+
21
+ getV1() {
22
+ return this.#v1Protocol;
23
+ }
24
+
25
+ get(protocol) {
26
+ if (protocol === 'legacy') return this.#legacyProtocol;
27
+ if (protocol === 'v1') return this.#v1Protocol;
28
+ return null;
29
+ }
30
+
31
+ has(protocol) {
32
+ return Boolean(this.get(protocol));
33
+ }
34
+
35
+ send(message) {
36
+ // TODO: Support v1 messages
37
+ this.#legacyProtocol.send(message);
38
+ }
39
+
40
+ close() {
41
+ if (this.#legacyProtocol) {
42
+ try {
43
+ this.#legacyProtocol.close();
44
+ } catch (e) {
45
+ console.error('Failed to close legacy channel:', e); // TODO: Think about throwing instead
46
+ }
47
+ }
48
+
49
+ if (this.#v1Protocol) {
50
+ try {
51
+ this.#v1Protocol.close();
52
+ } catch (e) {
53
+ console.error('Failed to close v1 channel:', e); // TODO: Think about throwing instead
54
+ }
55
+ }
56
+ }
57
+ }
58
+
59
+ export default ProtocolSession;
@@ -0,0 +1,64 @@
1
+ import Protomux from 'protomux';
2
+ import ProtocolInterface from './ProtocolInterface.js';
3
+ import b4a from 'b4a';
4
+ import c from 'compact-encoding';
5
+
6
+ class V1Protocol extends ProtocolInterface {
7
+ #channel;
8
+ #session;
9
+ #config;
10
+ #router;
11
+
12
+ constructor(router, connection, config) {
13
+ super(router, connection, config);
14
+ this.#config = config;
15
+ this.#router = router;
16
+ this.init(connection);
17
+ }
18
+
19
+ get channel() {
20
+ return this.#channel;
21
+ }
22
+
23
+ get session() {
24
+ return this.#session;
25
+ }
26
+
27
+ init(connection) {
28
+ const mux = Protomux.from(connection);
29
+ connection.userData = mux;
30
+
31
+ this.#channel = mux.createChannel({
32
+ protocol: 'network/v1',
33
+ onopen() { },
34
+ onclose() { }
35
+ });
36
+
37
+ this.#channel.open();
38
+
39
+ this.#session = this.#channel.addMessage({
40
+ encoding: c.raw,
41
+ onmessage: async (incomingMessage) => {
42
+ try {
43
+ if (b4a.isBuffer(incomingMessage)) {
44
+ await this.#router.route(incomingMessage, connection, this.#session);
45
+ } else {
46
+ throw new Error('NetworkMessages: v1 message must be a buffer');
47
+ }
48
+ } catch (error) {
49
+ console.error(`NetworkMessages: Failed to handle incoming v1 message: ${error.message}`);
50
+ }
51
+ }
52
+ });
53
+ }
54
+
55
+ send(message) {
56
+ this.#session.send(message);
57
+ }
58
+
59
+ close() {
60
+ this.#channel.close();
61
+ }
62
+ }
63
+
64
+ export default V1Protocol;