trac-msb 0.2.8 → 0.2.10

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 (151) hide show
  1. package/.github/workflows/acceptance-tests.yml +7 -11
  2. package/.github/workflows/lint-pr-title.yml +26 -0
  3. package/.github/workflows/unit-tests.yml +2 -8
  4. package/CODE_OF_CONDUCT.md +128 -0
  5. package/README.md +33 -18
  6. package/docker-compose.yml +1 -0
  7. package/docs/trac_network_http_api.openapi.yaml +889 -0
  8. package/msb.mjs +5 -22
  9. package/package.json +14 -10
  10. package/proto/network.proto +74 -0
  11. package/rpc/create_server.js +2 -2
  12. package/rpc/handlers.js +165 -92
  13. package/rpc/routes/v1.js +3 -1
  14. package/rpc/rpc_server.js +4 -4
  15. package/rpc/rpc_services.js +62 -25
  16. package/rpc/utils/helpers.js +83 -52
  17. package/src/config/args.js +46 -0
  18. package/src/config/config.js +78 -5
  19. package/src/config/env.js +70 -3
  20. package/src/core/network/Network.js +34 -70
  21. package/src/core/network/identity/NetworkWalletFactory.js +2 -2
  22. package/src/core/network/protocols/LegacyProtocol.js +67 -0
  23. package/src/core/network/protocols/NetworkMessages.js +48 -0
  24. package/src/core/network/protocols/ProtocolInterface.js +31 -0
  25. package/src/core/network/protocols/ProtocolSession.js +59 -0
  26. package/src/core/network/protocols/V1Protocol.js +64 -0
  27. package/src/core/network/protocols/legacy/NetworkMessageRouter.js +84 -0
  28. package/src/core/network/protocols/legacy/handlers/GetRequestHandler.js +53 -0
  29. package/src/core/network/protocols/legacy/handlers/ResponseHandler.js +37 -0
  30. package/src/core/network/{messaging → protocols/legacy}/validators/base/BaseResponse.js +2 -3
  31. package/src/core/network/protocols/shared/handlers/RoleOperationHandler.js +88 -0
  32. package/src/core/network/protocols/shared/handlers/SubnetworkOperationHandler.js +93 -0
  33. package/src/core/network/{messaging → protocols/shared}/handlers/TransferOperationHandler.js +17 -16
  34. package/src/core/network/{messaging → protocols/shared}/handlers/base/BaseOperationHandler.js +10 -15
  35. package/src/core/network/{messaging → protocols/shared}/validators/PartialBootstrapDeployment.js +2 -2
  36. package/src/core/network/{messaging → protocols/shared}/validators/PartialRoleAccess.js +5 -5
  37. package/src/core/network/{messaging → protocols/shared}/validators/PartialTransaction.js +4 -4
  38. package/src/core/network/{messaging → protocols/shared}/validators/PartialTransfer.js +4 -4
  39. package/src/core/network/{messaging → protocols/shared}/validators/base/PartialOperation.js +14 -12
  40. package/src/core/network/protocols/v1/NetworkMessageRouter.js +15 -0
  41. package/src/core/network/services/ConnectionManager.js +5 -5
  42. package/src/core/network/services/MessageOrchestrator.js +2 -2
  43. package/src/core/network/services/TransactionPoolService.js +5 -6
  44. package/src/core/network/services/TransactionRateLimiterService.js +12 -13
  45. package/src/core/network/services/ValidatorObserverService.js +5 -6
  46. package/src/core/state/State.js +3 -5
  47. package/src/index.js +156 -181
  48. package/src/messages/network/v1/NetworkMessageBuilder.js +325 -0
  49. package/src/messages/network/v1/NetworkMessageDirector.js +137 -0
  50. package/src/messages/network/v1/networkMessageFactory.js +12 -0
  51. package/src/messages/state/ApplyStateMessageBuilder.js +661 -0
  52. package/src/messages/state/ApplyStateMessageDirector.js +516 -0
  53. package/src/messages/state/applyStateMessageFactory.js +12 -0
  54. package/src/utils/buffer.js +53 -1
  55. package/src/utils/check.js +1 -1
  56. package/src/utils/cli.js +0 -8
  57. package/src/utils/constants.js +33 -30
  58. package/src/utils/fileUtils.js +13 -0
  59. package/src/utils/normalizers.js +84 -2
  60. package/src/utils/protobuf/network.cjs +840 -0
  61. package/src/utils/protobuf/operationHelpers.js +10 -0
  62. package/src/utils/type.js +26 -0
  63. package/tests/acceptance/v1/balance/balance.test.mjs +1 -2
  64. package/tests/acceptance/v1/broadcast-transaction/broadcast-transaction.test.mjs +26 -30
  65. package/tests/acceptance/v1/health/health.test.mjs +33 -0
  66. package/tests/acceptance/v1/rpc.test.mjs +4 -3
  67. package/tests/acceptance/v1/tx/tx.test.mjs +27 -16
  68. package/tests/acceptance/v1/tx-details/tx-details.test.mjs +26 -12
  69. package/tests/fixtures/check.fixtures.js +33 -32
  70. package/tests/fixtures/networkV1.fixtures.js +85 -0
  71. package/tests/fixtures/protobuf.fixtures.js +109 -25
  72. package/tests/helpers/StateNetworkFactory.js +2 -2
  73. package/tests/helpers/address.js +6 -0
  74. package/tests/helpers/autobaseTestHelpers.js +2 -1
  75. package/tests/helpers/config.js +2 -1
  76. package/tests/helpers/setupApplyTests.js +59 -56
  77. package/tests/unit/messages/messages.test.js +12 -0
  78. package/tests/unit/messages/network/NetworkMessageBuilder.test.js +276 -0
  79. package/tests/unit/messages/network/NetworkMessageDirector.test.js +201 -0
  80. package/tests/unit/messages/state/applyStateMessageBuilder.complete.test.js +521 -0
  81. package/tests/unit/messages/state/applyStateMessageBuilder.partial.test.js +233 -0
  82. package/tests/unit/network/ConnectionManager.test.js +6 -5
  83. package/tests/unit/network/networkModule.test.js +3 -2
  84. package/tests/unit/state/apply/addAdmin/addAdminHappyPathScenario.js +10 -6
  85. package/tests/unit/state/apply/addAdmin/addAdminScenarioHelpers.js +9 -6
  86. package/tests/unit/state/apply/addAdmin/state.apply.addAdmin.test.js +10 -7
  87. package/tests/unit/state/apply/addIndexer/addIndexerScenarioHelpers.js +18 -21
  88. package/tests/unit/state/apply/addWriter/addWriterScenarioHelpers.js +53 -38
  89. package/tests/unit/state/apply/adminRecovery/adminRecoveryScenarioHelpers.js +46 -35
  90. package/tests/unit/state/apply/appendWhitelist/appendWhitelistScenarioHelpers.js +13 -16
  91. package/tests/unit/state/apply/balanceInitialization/balanceInitializationScenarioHelpers.js +17 -11
  92. package/tests/unit/state/apply/banValidator/banValidatorScenarioHelpers.js +11 -12
  93. package/tests/unit/state/apply/bootstrapDeployment/bootstrapDeploymentScenarioHelpers.js +9 -7
  94. package/tests/unit/state/apply/common/commonScenarioHelper.js +15 -14
  95. package/tests/unit/state/apply/common/payload-structure/initializationDisabledScenario.js +9 -4
  96. package/tests/unit/state/apply/disableInitialization/disableInitializationScenarioHelpers.js +17 -11
  97. package/tests/unit/state/apply/removeWriter/removeWriterScenarioHelpers.js +19 -14
  98. package/tests/unit/state/apply/transfer/transferDoubleSpendAcrossValidatorsScenario.js +37 -29
  99. package/tests/unit/state/apply/transfer/transferScenarioHelpers.js +9 -7
  100. package/tests/unit/state/apply/txOperation/txOperationScenarioHelpers.js +11 -9
  101. package/tests/unit/unit.test.js +1 -1
  102. package/tests/unit/utils/buffer/buffer.test.js +62 -1
  103. package/tests/unit/utils/fileUtils/readAddressesFromWhitelistFile.test.js +4 -3
  104. package/tests/unit/utils/fileUtils/readBalanceMigrationFile.test.js +3 -2
  105. package/tests/unit/utils/migrationUtils/validateAddressFromIncomingFile.test.js +3 -2
  106. package/tests/unit/utils/normalizers/normalizers.test.js +469 -0
  107. package/tests/unit/utils/protobuf/operationHelpers.test.js +120 -2
  108. package/tests/unit/utils/type/type.test.js +25 -0
  109. package/tests/unit/utils/utils.test.js +1 -0
  110. package/docs/networking-dualstack-plan.md +0 -75
  111. package/docs/networking-layer-redesign.md +0 -155
  112. package/src/core/network/messaging/NetworkMessages.js +0 -64
  113. package/src/core/network/messaging/handlers/GetRequestHandler.js +0 -113
  114. package/src/core/network/messaging/handlers/ResponseHandler.js +0 -107
  115. package/src/core/network/messaging/handlers/RoleOperationHandler.js +0 -114
  116. package/src/core/network/messaging/handlers/SubnetworkOperationHandler.js +0 -149
  117. package/src/core/network/messaging/routes/NetworkMessageRouter.js +0 -98
  118. package/src/core/network/messaging/validators/AdminResponse.js +0 -58
  119. package/src/core/network/messaging/validators/CustomNodeResponse.js +0 -46
  120. package/src/messages/base/StateBuilder.js +0 -25
  121. package/src/messages/completeStateMessages/CompleteStateMessageBuilder.js +0 -425
  122. package/src/messages/completeStateMessages/CompleteStateMessageDirector.js +0 -252
  123. package/src/messages/completeStateMessages/CompleteStateMessageOperations.js +0 -296
  124. package/src/messages/partialStateMessages/PartialStateMessageBuilder.js +0 -272
  125. package/src/messages/partialStateMessages/PartialStateMessageDirector.js +0 -137
  126. package/src/messages/partialStateMessages/PartialStateMessageOperations.js +0 -138
  127. package/tests/integration/apply/addAdmin/addAdminBasic.test.js +0 -69
  128. package/tests/integration/apply/addAdmin/addAdminRecovery.test.js +0 -126
  129. package/tests/integration/apply/addIndexer.test.js +0 -239
  130. package/tests/integration/apply/addWhitelist.test.js +0 -53
  131. package/tests/integration/apply/addWriter.test.js +0 -245
  132. package/tests/integration/apply/apply.test.js +0 -19
  133. package/tests/integration/apply/banValidator.test.js +0 -116
  134. package/tests/integration/apply/postTx/invalidSubValues.test.js +0 -103
  135. package/tests/integration/apply/postTx/postTx.test.js +0 -196
  136. package/tests/integration/apply/removeIndexer.test.js +0 -132
  137. package/tests/integration/apply/removeWriter.test.js +0 -168
  138. package/tests/integration/apply/transfer.test.js +0 -83
  139. package/tests/integration/integration.test.js +0 -9
  140. package/tests/unit/messageOperations/assembleAddIndexerMessage.test.js +0 -21
  141. package/tests/unit/messageOperations/assembleAddWriterMessage.test.js +0 -17
  142. package/tests/unit/messageOperations/assembleAdminMessage.test.js +0 -68
  143. package/tests/unit/messageOperations/assembleBanWriterMessage.test.js +0 -17
  144. package/tests/unit/messageOperations/assemblePostTransaction.test.js +0 -424
  145. package/tests/unit/messageOperations/assembleRemoveIndexerMessage.test.js +0 -19
  146. package/tests/unit/messageOperations/assembleRemoveWriterMessage.test.js +0 -17
  147. package/tests/unit/messageOperations/assembleWhitelistMessages.test.js +0 -59
  148. package/tests/unit/messageOperations/commonsStateMessageOperationsTest.js +0 -278
  149. package/tests/unit/messageOperations/stateMessageOperations.test.js +0 -19
  150. /package/src/core/network/{messaging → protocols/legacy}/validators/ValidatorResponse.js +0 -0
  151. /package/src/utils/{operations.js → applyOperations.js} +0 -0
@@ -4,21 +4,18 @@ 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 './messaging/NetworkMessages.js';
7
+ import NetworkMessages from './protocols/NetworkMessages.js';
8
8
  import { sleep } from '../../utils/helpers.js';
9
9
  import {
10
10
  TRAC_NAMESPACE,
11
- MAX_PEERS,
12
- MAX_PARALLEL,
13
- MAX_SERVER_CONNECTIONS,
14
- MAX_CLIENT_CONNECTIONS,
15
11
  NETWORK_MESSAGE_TYPES
16
12
  } from '../../utils/constants.js';
17
13
  import ConnectionManager from './services/ConnectionManager.js';
18
14
  import MessageOrchestrator from './services/MessageOrchestrator.js';
19
15
  import NetworkWalletFactory from './identity/NetworkWalletFactory.js';
20
16
  import { EventType } from '../../utils/constants.js';
21
-
17
+ import { networkMessageFactory } from '../../messages/network/v1/networkMessageFactory.js';
18
+ import TransactionRateLimiterService from './services/TransactionRateLimiterService.js';
22
19
  // -- Debug Mode --
23
20
  // TODO: Implement a better debug system in the future. This is just temporary.
24
21
  const DEBUG = false;
@@ -42,10 +39,11 @@ class Network extends ReadyResource {
42
39
  #pendingConnections;
43
40
  #connectTimeoutMs;
44
41
  #maxPendingConnections;
42
+ #rateLimiter;
45
43
 
46
44
  /**
47
45
  * @param {State} state
48
- * @param {object} config
46
+ * @param {Config} config
49
47
  * @param {string} address
50
48
  **/
51
49
  constructor(state, config, address = null) {
@@ -53,18 +51,12 @@ class Network extends ReadyResource {
53
51
  this.#config = config
54
52
  this.#connectTimeoutMs = config.connectTimeoutMs || 5000;
55
53
  this.#maxPendingConnections = config.maxPendingConnections || 50;
56
-
57
54
  this.#pendingConnections = new Map();
58
55
  this.#transactionPoolService = new TransactionPoolService(state, address, this.#config);
59
56
  this.#validatorObserverService = new ValidatorObserverService(this, state, address, this.#config);
60
- this.#networkMessages = new NetworkMessages(this, this.#config);
61
57
  this.#validatorConnectionManager = new ConnectionManager(this.#config);
62
58
  this.#validatorMessageOrchestrator = new MessageOrchestrator(this.#validatorConnectionManager, state, this.#config);
63
- this.admin_stream = null;
64
- this.admin = null;
65
- this.validator = null;
66
- this.custom_stream = null;
67
- this.custom_node = null;
59
+
68
60
  }
69
61
 
70
62
  get swarm() {
@@ -97,6 +89,7 @@ class Network extends ReadyResource {
97
89
  }
98
90
 
99
91
  async _close() {
92
+ // TODO: Implement better "await" logic for stopping services
100
93
  console.log('Network: closing gracefully...');
101
94
  this.transactionPoolService.stopPool();
102
95
  await sleep(100);
@@ -119,7 +112,7 @@ class Network extends ReadyResource {
119
112
  });
120
113
 
121
114
  // VALIDATOR_CONNECTION_READY
122
- this.on(EventType.VALIDATOR_CONNECTION_READY, ({ publicKey, type, connection }) => {
115
+ this.on(EventType.VALIDATOR_CONNECTION_READY, async ({ publicKey, type, connection }) => {
123
116
  debugLog(`Network Event: VALIDATOR_CONNECTION_READY | PublicKey: ${publicKey} | Type: ${type}`);
124
117
  const { timeoutId } = this.#pendingConnections.get(publicKey);
125
118
  if (!timeoutId) return;
@@ -128,19 +121,15 @@ class Network extends ReadyResource {
128
121
  this.#pendingConnections.delete(publicKey);
129
122
 
130
123
  if (type === 'validator') {
131
- const target = b4a.from(publicKey, 'hex');
132
- this.#validatorConnectionManager.addValidator(target, connection);
133
- this.#sendRequestByType(connection, type);
124
+ await connection.protocolSession.send(NETWORK_MESSAGE_TYPES.GET.VALIDATOR);
134
125
  }
126
+
135
127
  });
136
128
  }
137
129
 
138
130
  cleanupNetworkListeners() {
139
- // connect:timeout
140
- this.removeAllListeners('connect:timeout');
141
-
142
- // connect:ready
143
- this.removeAllListeners('connect:ready');
131
+ this.removeAllListeners(EventType.VALIDATOR_CONNECTION_TIMEOUT);
132
+ this.removeAllListeners(EventType.VALIDATOR_CONNECTION_READY);
144
133
  }
145
134
 
146
135
  cleanupPendingConnections() {
@@ -161,18 +150,29 @@ class Network extends ReadyResource {
161
150
  this.#swarm = new Hyperswarm({
162
151
  keyPair,
163
152
  bootstrap: this.#config.dhtBootstrap,
164
- maxPeers: MAX_PEERS,
165
- maxParallel: MAX_PARALLEL,
166
- maxServerConnections: MAX_SERVER_CONNECTIONS,
167
- maxClientConnections: MAX_CLIENT_CONNECTIONS
153
+ maxPeers: this.#config.maxPeers,
154
+ maxParallel: this.#config.maxParallel,
155
+ maxServerConnections: this.#config.maxServerConnections,
156
+ maxClientConnections: this.#config.maxClientConnections
168
157
  });
169
158
 
159
+ this.#rateLimiter = new TransactionRateLimiterService(this.#swarm, this.#config);
160
+ this.#networkMessages = new NetworkMessages(
161
+ state,
162
+ wrappedWallet,
163
+ this.#rateLimiter,
164
+ this.#transactionPoolService,
165
+ this.#validatorConnectionManager,
166
+ this.#config
167
+ );
168
+
170
169
  console.log(`Channel: ${b4a.toString(this.#config.channel)}`);
171
- this.#networkMessages.initializeMessageRouter(state, wrappedWallet);
172
170
 
173
171
  this.#swarm.on('connection', async (connection) => {
174
- const { message_channel, message } = await this.#networkMessages.setupProtomuxMessages(connection);
175
- connection.messenger = message;
172
+ // Per-peer connection initialization:
173
+ // - attach Protomux (legacy + v1 channels/messages)
174
+ // - attach connection.protocolSession (used later by tryConnect / orchestrators to send messages)
175
+ await this.#networkMessages.setupProtomuxMessages(connection);
176
176
 
177
177
  // ATTENTION: Must be called AFTER the protomux init above
178
178
  const stream = store.replicate(connection);
@@ -185,17 +185,9 @@ class Network extends ReadyResource {
185
185
  }
186
186
 
187
187
  connection.on('close', () => {
188
- if (this.admin_stream === connection) {
189
- this.admin_stream = null;
190
- this.admin = null;
191
- }
192
-
193
- if (this.custom_stream === connection) {
194
- this.custom_stream = null;
195
- this.custom_node = null;
196
- }
197
- try { message_channel.close() } catch (e) { }
198
-
188
+ this.#swarm.leavePeer(connection.remotePublicKey);
189
+ this.#validatorConnectionManager.remove(publicKey);
190
+ connection.protocolSession.close();
199
191
  });
200
192
 
201
193
  connection.on('error', (error) => {
@@ -209,7 +201,6 @@ class Network extends ReadyResource {
209
201
  return;
210
202
  }
211
203
  console.error(error.message)
212
-
213
204
  });
214
205
 
215
206
  });
@@ -259,7 +250,7 @@ class Network extends ReadyResource {
259
250
  const peerInfo = this.#swarm.peers.get(publicKey);
260
251
  if (peerInfo) {
261
252
  const connection = this.#swarm._allConnections.get(peerInfo.publicKey);
262
- if (connection && connection.messenger) {
253
+ if (connection && connection.protocolSession) {
263
254
  await this.#finalizeConnection(publicKey, type, connection);
264
255
  }
265
256
  }
@@ -276,33 +267,6 @@ class Network extends ReadyResource {
276
267
  debugLog(`Network.finalizeConnection: Connected to peer: ${publicKey} as type: ${type}`);
277
268
  }
278
269
 
279
- async #sendRequestByType(stream, type) {
280
- const waitFor = {
281
- validator: () => this.validatorConnectionManager.connectionCount(),
282
- admin: () => this.admin_stream,
283
- node: () => this.custom_stream
284
- }[type];
285
-
286
- if (type === 'validator') {
287
- await stream.messenger.send(NETWORK_MESSAGE_TYPES.GET.VALIDATOR);
288
- } else if (type === 'admin') {
289
- await stream.messenger.send(NETWORK_MESSAGE_TYPES.GET.ADMIN);
290
- } else if (type === 'node') {
291
- await stream.messenger.send(NETWORK_MESSAGE_TYPES.GET.NODE);
292
- } else {
293
- return;
294
- }
295
- await this.spinLock(() => !waitFor())
296
- };
297
-
298
- async spinLock(conditionFn, maxIterations = 1500, intervalMs = 10) {
299
- let counter = 0;
300
- while (conditionFn() && counter < maxIterations) {
301
- await sleep(intervalMs);
302
- counter++;
303
- }
304
- }
305
-
306
270
  #getNetworkWalletWrapper(wallet, keyPair) {
307
271
  if (!this.#identityProvider) {
308
272
  this.#identityProvider = NetworkWalletFactory.provide({
@@ -1,7 +1,7 @@
1
1
  import PeerWallet from 'trac-wallet';
2
2
  import b4a from 'b4a';
3
3
 
4
- export class NetworkWalletFactory {
4
+ class NetworkWalletFactory {
5
5
  static provide(options = {}) {
6
6
  const {
7
7
  enableWallet,
@@ -28,7 +28,7 @@ export class NetworkWalletFactory {
28
28
  // TODO: Once Wallet class in trac-wallet exposes a constructor/factory that accepts an existing keyPair
29
29
  // (e.g. Wallet.fromKeyPair({ publicKey, secretKey }, networkPrefix)), replace EphemeralWallet
30
30
  // with a thin wrapper around that functionality instead of duplicating signing/verification logic.
31
- class EphemeralWallet {
31
+ export class EphemeralWallet {
32
32
  #publicKey;
33
33
  #secretKey;
34
34
  #address;
@@ -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 {Config} 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;
@@ -0,0 +1,84 @@
1
+ import b4a from "b4a";
2
+ import GetRequestHandler from "./handlers/GetRequestHandler.js";
3
+ import ResponseHandler from "./handlers/ResponseHandler.js";
4
+ import RoleOperationHandler from "../shared/handlers/RoleOperationHandler.js";
5
+ import SubnetworkOperationHandler from "../shared/handlers/SubnetworkOperationHandler.js";
6
+ import TransferOperationHandler from "../shared/handlers/TransferOperationHandler.js";
7
+ import { NETWORK_MESSAGE_TYPES } from '../../../../utils/constants.js';
8
+ import * as operation from '../../../../utils/applyOperations.js';
9
+ import State from "../../../state/State.js";
10
+ import PeerWallet from "trac-wallet";
11
+
12
+ class NetworkMessageRouter {
13
+ #handlers;
14
+ #config;
15
+
16
+ /**
17
+ * @param {State} state
18
+ * @param {PeerWallet} wallet
19
+ * @param {TransactionRateLimiterService} rateLimiterService
20
+ * @param {TransactionPoolService} txPoolService
21
+ * @param {ConnectionManager} connectionManager
22
+ * @param {Config} config
23
+ **/
24
+ constructor(state, wallet, rateLimiterService, txPoolService, connectionManager, config) {
25
+ this.#config = config;
26
+
27
+ this.#handlers = {
28
+ get: new GetRequestHandler(wallet, state),
29
+ response: new ResponseHandler(state, wallet, connectionManager, this.#config),
30
+ roleTransaction: new RoleOperationHandler(state, wallet, rateLimiterService, txPoolService, this.#config),
31
+ subNetworkTransaction: new SubnetworkOperationHandler(state, wallet, rateLimiterService, txPoolService, this.#config),
32
+ tracNetworkTransaction: new TransferOperationHandler(state, wallet, rateLimiterService, txPoolService, this.#config),
33
+ }
34
+ }
35
+
36
+ // NOTE: messageProtomux can be deleted, ad this is a session, and we can extract this from connection
37
+ async route(incomingMessage, connection, messageProtomux) {
38
+ const channelString = b4a.toString(this.#config.channel, 'utf8');
39
+ if (this.#isGetRequest(incomingMessage)) {
40
+ await this.#handlers.get.handle(incomingMessage, messageProtomux, connection, channelString);
41
+ }
42
+ else if (this.#isResponse(incomingMessage)) {
43
+ await this.#handlers.response.handle(incomingMessage, connection, channelString);
44
+ }
45
+ else if (this.#isRoleAccessOperation(incomingMessage)) {
46
+ await this.#handlers.roleTransaction.handle(incomingMessage, connection);
47
+ }
48
+ else if (this.#isSubnetworkOperation(incomingMessage)) {
49
+ await this.#handlers.subNetworkTransaction.handle(incomingMessage, connection);
50
+ }
51
+ else if (this.#isTransferOperation(incomingMessage)) {
52
+ await this.#handlers.tracNetworkTransaction.handle(incomingMessage, connection);
53
+ }
54
+ else {
55
+ throw new Error(`Failed to route message. Pubkey of requester is ${connection.remotePublicKey ? b4a.toString(connection.remotePublicKey, 'hex') : 'unknown'}`);
56
+ }
57
+
58
+ }
59
+
60
+ #isGetRequest(message) {
61
+ return Object.values(NETWORK_MESSAGE_TYPES.GET).includes(message);
62
+ }
63
+
64
+
65
+ #isResponse(message) {
66
+ return Object.values(NETWORK_MESSAGE_TYPES.RESPONSE).includes(message.op);
67
+ }
68
+
69
+ #isRoleAccessOperation(message) {
70
+ return operation.isRoleAccess(message.type)
71
+ }
72
+
73
+ #isSubnetworkOperation(message) {
74
+ return operation.isTransaction(message.type) ||
75
+ operation.isBootstrapDeployment(message.type)
76
+ }
77
+
78
+ #isTransferOperation(message) {
79
+ return operation.isTransfer(message.type)
80
+ }
81
+ }
82
+
83
+
84
+ export default NetworkMessageRouter;
@@ -0,0 +1,53 @@
1
+ import { NETWORK_MESSAGE_TYPES } from '../../../../../utils/constants.js';
2
+ import PeerWallet from 'trac-wallet';
3
+ import b4a from 'b4a';
4
+
5
+ class GetRequestHandler {
6
+ #wallet;
7
+ #state;
8
+
9
+ constructor(wallet, state) {
10
+ this.#wallet = wallet;
11
+ this.#state = state;
12
+ }
13
+
14
+ get state() {
15
+ return this.#state;
16
+ }
17
+
18
+ async handle(message, messageProtomux, connection, channelString) {
19
+ switch (message) {
20
+ case NETWORK_MESSAGE_TYPES.GET.VALIDATOR:
21
+ await this.handleGetValidatorResponse(messageProtomux, connection, channelString);
22
+ break;
23
+ default:
24
+ throw new Error(`Unhandled GET type: ${message}`);
25
+ }
26
+ }
27
+
28
+ async handleGetValidatorResponse(messageProtomux, connection, channelString) {
29
+ const nonce = PeerWallet.generateNonce().toString('hex');
30
+ const payload = {
31
+ op: 'validatorResponse',
32
+ wk: this.state.writingKey.toString('hex'),
33
+ address: this.#wallet.address,
34
+ nonce: nonce,
35
+ channel: channelString,
36
+ issuer: connection.remotePublicKey.toString('hex'),
37
+ timestamp: Date.now(),
38
+ };
39
+
40
+
41
+ const hashInput = b4a.from(JSON.stringify(payload), 'utf8');
42
+ const hash = await PeerWallet.blake3(hashInput);
43
+ const sig = this.#wallet.sign(hash);
44
+
45
+ const responseMessage = {
46
+ ...payload,
47
+ sig: sig.toString('hex'),
48
+ };
49
+ messageProtomux.send(responseMessage);
50
+ }
51
+ }
52
+
53
+ export default GetRequestHandler;
@@ -0,0 +1,37 @@
1
+ import ValidatorResponse from '../validators/ValidatorResponse.js';
2
+ import PeerWallet from 'trac-wallet';
3
+
4
+ class ResponseHandler {
5
+ #responseValidator;
6
+ #connectionManager;
7
+
8
+
9
+ constructor(state, wallet, connectionManager ,config) {
10
+ this.#responseValidator = new ValidatorResponse(state, wallet, config);
11
+ this.#connectionManager = connectionManager;
12
+
13
+ }
14
+
15
+ async handle(message, connection, channelString) {
16
+ await this.#handleValidatorResponse(message, connection, channelString);
17
+ }
18
+
19
+ async #handleValidatorResponse(message, connection, channelString) {
20
+ const isValid = await this.#responseValidator.validate(message, channelString);
21
+ if (isValid) {
22
+ const validatorAddressString = message.address;
23
+ const validatorPublicKey = PeerWallet.decodeBech32m(validatorAddressString);
24
+
25
+ if (this.#connectionManager.connected(validatorPublicKey)) {
26
+ return;
27
+ // TODO: What we should return? Or maybe we should throw?
28
+ }
29
+
30
+ this.#connectionManager.addValidator(validatorPublicKey, connection)
31
+ } else {
32
+ throw new Error("Validator response verification failed");
33
+ }
34
+ }
35
+ }
36
+
37
+ export default ResponseHandler;