trac-msb 0.2.8 → 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 (123) hide show
  1. package/msb.mjs +3 -3
  2. package/package.json +8 -3
  3. package/proto/network.proto +74 -0
  4. package/rpc/create_server.js +2 -2
  5. package/rpc/handlers.js +2 -2
  6. package/rpc/rpc_server.js +2 -2
  7. package/rpc/rpc_services.js +44 -3
  8. package/rpc/utils/helpers.js +1 -1
  9. package/src/config/env.js +2 -0
  10. package/src/core/network/Network.js +29 -61
  11. package/src/core/network/identity/NetworkWalletFactory.js +2 -2
  12. package/src/core/network/protocols/LegacyProtocol.js +67 -0
  13. package/src/core/network/protocols/NetworkMessages.js +48 -0
  14. package/src/core/network/protocols/ProtocolInterface.js +31 -0
  15. package/src/core/network/protocols/ProtocolSession.js +59 -0
  16. package/src/core/network/protocols/V1Protocol.js +64 -0
  17. package/src/core/network/protocols/legacy/NetworkMessageRouter.js +84 -0
  18. package/src/core/network/protocols/legacy/handlers/GetRequestHandler.js +53 -0
  19. package/src/core/network/protocols/legacy/handlers/ResponseHandler.js +37 -0
  20. package/src/core/network/{messaging → protocols/legacy}/validators/base/BaseResponse.js +1 -2
  21. package/src/core/network/protocols/shared/handlers/RoleOperationHandler.js +88 -0
  22. package/src/core/network/protocols/shared/handlers/SubnetworkOperationHandler.js +93 -0
  23. package/src/core/network/{messaging → protocols/shared}/handlers/TransferOperationHandler.js +16 -15
  24. package/src/core/network/{messaging → protocols/shared}/handlers/base/BaseOperationHandler.js +7 -11
  25. package/src/core/network/{messaging → protocols/shared}/validators/PartialBootstrapDeployment.js +2 -2
  26. package/src/core/network/{messaging → protocols/shared}/validators/PartialRoleAccess.js +5 -5
  27. package/src/core/network/{messaging → protocols/shared}/validators/PartialTransaction.js +4 -4
  28. package/src/core/network/{messaging → protocols/shared}/validators/PartialTransfer.js +4 -4
  29. package/src/core/network/{messaging → protocols/shared}/validators/base/PartialOperation.js +14 -12
  30. package/src/core/network/protocols/v1/NetworkMessageRouter.js +15 -0
  31. package/src/core/network/services/ConnectionManager.js +4 -4
  32. package/src/core/network/services/MessageOrchestrator.js +1 -1
  33. package/src/core/network/services/TransactionPoolService.js +1 -2
  34. package/src/core/network/services/TransactionRateLimiterService.js +5 -3
  35. package/src/core/state/State.js +1 -2
  36. package/src/index.js +153 -180
  37. package/src/messages/network/v1/NetworkMessageBuilder.js +325 -0
  38. package/src/messages/network/v1/NetworkMessageDirector.js +137 -0
  39. package/src/messages/network/v1/networkMessageFactory.js +12 -0
  40. package/src/messages/state/ApplyStateMessageBuilder.js +661 -0
  41. package/src/messages/state/ApplyStateMessageDirector.js +516 -0
  42. package/src/messages/state/applyStateMessageFactory.js +12 -0
  43. package/src/utils/buffer.js +53 -1
  44. package/src/utils/cli.js +0 -8
  45. package/src/utils/constants.js +34 -14
  46. package/src/utils/normalizers.js +84 -2
  47. package/src/utils/protobuf/network.cjs +840 -0
  48. package/src/utils/protobuf/operationHelpers.js +10 -0
  49. package/tests/acceptance/v1/rpc.test.mjs +1 -1
  50. package/tests/fixtures/networkV1.fixtures.js +84 -0
  51. package/tests/fixtures/protobuf.fixtures.js +83 -0
  52. package/tests/helpers/config.js +1 -1
  53. package/tests/helpers/setupApplyTests.js +53 -46
  54. package/tests/unit/messages/messages.test.js +12 -0
  55. package/tests/unit/messages/network/NetworkMessageBuilder.test.js +276 -0
  56. package/tests/unit/messages/network/NetworkMessageDirector.test.js +203 -0
  57. package/tests/unit/messages/state/applyStateMessageBuilder.complete.test.js +521 -0
  58. package/tests/unit/messages/state/applyStateMessageBuilder.partial.test.js +233 -0
  59. package/tests/unit/network/ConnectionManager.test.js +6 -5
  60. package/tests/unit/network/networkModule.test.js +3 -2
  61. package/tests/unit/state/apply/addAdmin/addAdminHappyPathScenario.js +10 -6
  62. package/tests/unit/state/apply/addAdmin/addAdminScenarioHelpers.js +9 -6
  63. package/tests/unit/state/apply/addAdmin/state.apply.addAdmin.test.js +10 -7
  64. package/tests/unit/state/apply/addIndexer/addIndexerScenarioHelpers.js +18 -21
  65. package/tests/unit/state/apply/addWriter/addWriterScenarioHelpers.js +53 -38
  66. package/tests/unit/state/apply/adminRecovery/adminRecoveryScenarioHelpers.js +46 -35
  67. package/tests/unit/state/apply/appendWhitelist/appendWhitelistScenarioHelpers.js +13 -16
  68. package/tests/unit/state/apply/balanceInitialization/balanceInitializationScenarioHelpers.js +17 -11
  69. package/tests/unit/state/apply/banValidator/banValidatorScenarioHelpers.js +11 -12
  70. package/tests/unit/state/apply/bootstrapDeployment/bootstrapDeploymentScenarioHelpers.js +9 -7
  71. package/tests/unit/state/apply/common/commonScenarioHelper.js +15 -14
  72. package/tests/unit/state/apply/common/payload-structure/initializationDisabledScenario.js +9 -4
  73. package/tests/unit/state/apply/disableInitialization/disableInitializationScenarioHelpers.js +17 -11
  74. package/tests/unit/state/apply/removeWriter/removeWriterScenarioHelpers.js +19 -14
  75. package/tests/unit/state/apply/transfer/transferDoubleSpendAcrossValidatorsScenario.js +37 -29
  76. package/tests/unit/state/apply/transfer/transferScenarioHelpers.js +9 -7
  77. package/tests/unit/state/apply/txOperation/txOperationScenarioHelpers.js +11 -9
  78. package/tests/unit/unit.test.js +1 -1
  79. package/tests/unit/utils/buffer/buffer.test.js +62 -1
  80. package/tests/unit/utils/normalizers/normalizers.test.js +469 -0
  81. package/tests/unit/utils/protobuf/operationHelpers.test.js +120 -2
  82. package/docs/networking-dualstack-plan.md +0 -75
  83. package/docs/networking-layer-redesign.md +0 -155
  84. package/src/core/network/messaging/NetworkMessages.js +0 -64
  85. package/src/core/network/messaging/handlers/GetRequestHandler.js +0 -113
  86. package/src/core/network/messaging/handlers/ResponseHandler.js +0 -107
  87. package/src/core/network/messaging/handlers/RoleOperationHandler.js +0 -114
  88. package/src/core/network/messaging/handlers/SubnetworkOperationHandler.js +0 -149
  89. package/src/core/network/messaging/routes/NetworkMessageRouter.js +0 -98
  90. package/src/core/network/messaging/validators/AdminResponse.js +0 -58
  91. package/src/core/network/messaging/validators/CustomNodeResponse.js +0 -46
  92. package/src/messages/base/StateBuilder.js +0 -25
  93. package/src/messages/completeStateMessages/CompleteStateMessageBuilder.js +0 -425
  94. package/src/messages/completeStateMessages/CompleteStateMessageDirector.js +0 -252
  95. package/src/messages/completeStateMessages/CompleteStateMessageOperations.js +0 -296
  96. package/src/messages/partialStateMessages/PartialStateMessageBuilder.js +0 -272
  97. package/src/messages/partialStateMessages/PartialStateMessageDirector.js +0 -137
  98. package/src/messages/partialStateMessages/PartialStateMessageOperations.js +0 -138
  99. package/tests/integration/apply/addAdmin/addAdminBasic.test.js +0 -69
  100. package/tests/integration/apply/addAdmin/addAdminRecovery.test.js +0 -126
  101. package/tests/integration/apply/addIndexer.test.js +0 -239
  102. package/tests/integration/apply/addWhitelist.test.js +0 -53
  103. package/tests/integration/apply/addWriter.test.js +0 -245
  104. package/tests/integration/apply/apply.test.js +0 -19
  105. package/tests/integration/apply/banValidator.test.js +0 -116
  106. package/tests/integration/apply/postTx/invalidSubValues.test.js +0 -103
  107. package/tests/integration/apply/postTx/postTx.test.js +0 -196
  108. package/tests/integration/apply/removeIndexer.test.js +0 -132
  109. package/tests/integration/apply/removeWriter.test.js +0 -168
  110. package/tests/integration/apply/transfer.test.js +0 -83
  111. package/tests/integration/integration.test.js +0 -9
  112. package/tests/unit/messageOperations/assembleAddIndexerMessage.test.js +0 -21
  113. package/tests/unit/messageOperations/assembleAddWriterMessage.test.js +0 -17
  114. package/tests/unit/messageOperations/assembleAdminMessage.test.js +0 -68
  115. package/tests/unit/messageOperations/assembleBanWriterMessage.test.js +0 -17
  116. package/tests/unit/messageOperations/assemblePostTransaction.test.js +0 -424
  117. package/tests/unit/messageOperations/assembleRemoveIndexerMessage.test.js +0 -19
  118. package/tests/unit/messageOperations/assembleRemoveWriterMessage.test.js +0 -17
  119. package/tests/unit/messageOperations/assembleWhitelistMessages.test.js +0 -59
  120. package/tests/unit/messageOperations/commonsStateMessageOperationsTest.js +0 -278
  121. package/tests/unit/messageOperations/stateMessageOperations.test.js +0 -19
  122. /package/src/core/network/{messaging → protocols/legacy}/validators/ValidatorResponse.js +0 -0
  123. /package/src/utils/{operations.js → applyOperations.js} +0 -0
package/msb.mjs CHANGED
@@ -15,8 +15,8 @@ const rpc = {
15
15
  }
16
16
 
17
17
  const options = args.includes('--rpc') ? rpc : { storeName }
18
-
19
- const msb = new MainSettlementBus(createConfig(ENV.MAINNET, options));
18
+ const config = createConfig(ENV.MAINNET, options)
19
+ const msb = new MainSettlementBus(config);
20
20
 
21
21
  msb.ready().then(async () => {
22
22
  if (runRpc) {
@@ -25,7 +25,7 @@ msb.ready().then(async () => {
25
25
  const port = (portIndex !== -1 && args[portIndex + 1]) ? parseInt(args[portIndex + 1], 10) : 5000;
26
26
  const hostIndex = args.indexOf('--host');
27
27
  const host = (hostIndex !== -1 && args[hostIndex + 1]) ? args[hostIndex + 1] : 'localhost';
28
- startRpcServer(msb, host, port);
28
+ startRpcServer(msb, config , host, port);
29
29
  } else {
30
30
  console.log('RPC server will not be started.');
31
31
  msb.interactiveMode();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "trac-msb",
3
3
  "main": "msb.mjs",
4
- "version": "0.2.8",
4
+ "version": "0.2.9",
5
5
  "pear": {
6
6
  "name": "trac-msb",
7
7
  "type": "terminal"
@@ -49,8 +49,9 @@
49
49
  "protomux-wakeup": "2.4.0",
50
50
  "readline": "npm:bare-node-readline",
51
51
  "ready-resource": "1.1.2",
52
- "trac-wallet": "0.0.43-msb-r2.9",
53
- "tty": "npm:bare-node-tty"
52
+ "trac-wallet": "1.0.1",
53
+ "tty": "npm:bare-node-tty",
54
+ "uuid": "^13.0.0"
54
55
  },
55
56
  "devDependencies": {
56
57
  "bare-os": "^3.6.1",
@@ -66,5 +67,9 @@
66
67
  "publishConfig": {
67
68
  "registry": "https://registry.npmjs.org",
68
69
  "access": "public"
70
+ },
71
+ "repository": {
72
+ "type": "git",
73
+ "url": "https://github.com/Trac-Systems/main_settlement_bus"
69
74
  }
70
75
  }
@@ -0,0 +1,74 @@
1
+ syntax = "proto3";
2
+
3
+ package network.v1;
4
+
5
+ enum MessageType {
6
+ MESSAGE_TYPE_UNSPECIFIED = 0;
7
+ MESSAGE_TYPE_VALIDATOR_CONNECTION_REQUEST = 1;
8
+ MESSAGE_TYPE_VALIDATOR_CONNECTION_RESPONSE = 2;
9
+ MESSAGE_TYPE_LIVENESS_REQUEST = 3;
10
+ MESSAGE_TYPE_LIVENESS_RESPONSE = 4;
11
+ MESSAGE_TYPE_BROADCAST_TRANSACTION_REQUEST = 5;
12
+ MESSAGE_TYPE_BROADCAST_TRANSACTION_RESPONSE = 6;
13
+ }
14
+
15
+ enum ResultCode {
16
+ RESULT_CODE_UNSPECIFIED = 0;
17
+ RESULT_CODE_OK = 1;
18
+ RESULT_CODE_INVALID_PAYLOAD = 2;
19
+ RESULT_CODE_UNSUPPORTED_VERSION = 3;
20
+ RESULT_CODE_RATE_LIMITED = 4;
21
+ RESULT_CODE_TIMEOUT = 5;
22
+ RESULT_CODE_SIGNATURE_INVALID = 6;
23
+ }
24
+
25
+ message ValidatorConnectionRequest {
26
+ string issuer_address = 1;
27
+ bytes nonce = 2;
28
+ bytes signature = 3;
29
+ }
30
+
31
+ message ValidatorConnectionResponse {
32
+ string issuer_address = 1;
33
+ bytes nonce = 2;
34
+ bytes signature = 3;
35
+ ResultCode result = 4;
36
+ }
37
+
38
+ message LivenessRequest {
39
+ bytes nonce = 1;
40
+ bytes signature = 2;
41
+ }
42
+
43
+ message LivenessResponse {
44
+ bytes nonce = 1;
45
+ bytes signature = 2;
46
+ ResultCode result = 3;
47
+ }
48
+
49
+ message BroadcastTransactionRequest {
50
+ bytes data = 1; // binary encoded payload
51
+ bytes nonce = 2;
52
+ bytes signature = 3;
53
+ }
54
+
55
+ message BroadcastTransactionResponse {
56
+ bytes nonce = 1;
57
+ bytes signature = 2;
58
+ ResultCode result = 3;
59
+ }
60
+
61
+ message MessageHeader {
62
+ MessageType type = 1;
63
+ string id = 2;
64
+ uint64 timestamp = 3;
65
+ oneof field {
66
+ ValidatorConnectionRequest validator_connection_request = 4;
67
+ ValidatorConnectionResponse validator_connection_response = 5;
68
+ LivenessRequest liveness_request = 6;
69
+ LivenessResponse liveness_response = 7;
70
+ BroadcastTransactionRequest broadcast_transaction_request = 8;
71
+ BroadcastTransactionResponse broadcast_transaction_response = 9;
72
+ }
73
+ repeated string capabilities = 10;
74
+ }
@@ -3,7 +3,7 @@ import http from 'http'
3
3
  import { applyCors } from './cors.js';
4
4
  import { routes } from './routes/index.js';
5
5
 
6
- export const createServer = (msbInstance) => {
6
+ export const createServer = (msbInstance, config) => {
7
7
  const server = http.createServer({}, async (req, res) => {
8
8
 
9
9
  // --- 1. Define safe 'respond' utility (Payload MUST be an object) ---
@@ -53,7 +53,7 @@ export const createServer = (msbInstance) => {
53
53
  try {
54
54
  // This try/catch covers synchronous errors and errors from awaited promises
55
55
  // within the route.handler function.
56
- await route.handler({ req, res, respond, msbInstance });
56
+ await route.handler({ req, res, respond, msbInstance, config});
57
57
  } catch (error) {
58
58
  // Catch errors thrown directly from the handler (or its awaited parts)
59
59
  console.error(`Error on ${route.path}:`, error);
package/rpc/handlers.js CHANGED
@@ -52,7 +52,7 @@ export async function handleConfirmedLength({ msbInstance, respond }) {
52
52
  respond(200, { confirmed_length });
53
53
  }
54
54
 
55
- export async function handleBroadcastTransaction({ msbInstance, respond, req }) {
55
+ export async function handleBroadcastTransaction({ msbInstance, config, respond, req }) {
56
56
  let body = '';
57
57
  req.on('data', chunk => {
58
58
  body += chunk.toString();
@@ -72,7 +72,7 @@ export async function handleBroadcastTransaction({ msbInstance, respond, req })
72
72
  const decodedPayload = decodeBase64Payload(payload);
73
73
  validatePayloadStructure(decodedPayload);
74
74
  const sanitizedPayload = sanitizeTransferPayload(decodedPayload);
75
- const result = await broadcastTransaction(msbInstance, sanitizedPayload);
75
+ const result = await broadcastTransaction(msbInstance, config, sanitizedPayload);
76
76
  respond(200, { result });
77
77
  } catch (error) {
78
78
  let code = error instanceof SyntaxError ? 400 : 500;
package/rpc/rpc_server.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import { createServer } from "./create_server.js";
2
2
 
3
3
  // Called by msb.mjs file
4
- export function startRpcServer(msbInstance, host, port) {
5
- const server = createServer(msbInstance)
4
+ export function startRpcServer(msbInstance, config ,host, port) {
5
+ const server = createServer(msbInstance, config)
6
6
 
7
7
  return server.listen(port, host, () => {
8
8
  console.log(`Running RPC with http at http://${host}:${port}`);
@@ -1,6 +1,14 @@
1
1
  import { bufferToBigInt } from "../src/utils/amountSerialization.js";
2
- import { normalizeDecodedPayloadForJson } from "../src/utils/normalizers.js";
2
+ import {
3
+ normalizeDecodedPayloadForJson,
4
+ normalizeTransactionOperation,
5
+ normalizeTransferOperation
6
+ } from "../src/utils/normalizers.js";
3
7
  import { get_confirmed_tx_info, get_unconfirmed_tx_info } from "../src/utils/cli.js";
8
+ import {OperationType} from "../src/utils/constants.js";
9
+ import b4a from "b4a";
10
+ import PartialTransaction from "../src/core/network/protocols/shared/validators/PartialTransaction.js";
11
+ import PartialTransfer from "../src/core/network/protocols/shared/validators/PartialTransfer.js";
4
12
 
5
13
  export async function getBalance(msbInstance, address, confirmed) {
6
14
  const state = msbInstance.state;
@@ -36,8 +44,41 @@ export async function getUnconfirmedLength(msbInstance) {
36
44
  return msbInstance.state.getUnsignedLength();
37
45
  }
38
46
 
39
- export async function broadcastTransaction(msbInstance, payload) {
40
- return msbInstance.broadcastTransactionCommand(payload);
47
+ export async function broadcastTransaction(msbInstance, config, payload) {
48
+ if (!payload) {
49
+ throw new Error("Transaction payload is required for broadcasting.");
50
+ }
51
+ let normalizedPayload;
52
+ let isValid = false;
53
+ let hash;
54
+
55
+ const partialTransferValidator = new PartialTransfer(msbInstance.state, null , config);
56
+ const partialTransactionValidator = new PartialTransaction(msbInstance.state, null , config);
57
+
58
+ if (payload.type === OperationType.TRANSFER) {
59
+ normalizedPayload = normalizeTransferOperation(payload, config);
60
+ isValid = await partialTransferValidator.validate(normalizedPayload);
61
+ hash = b4a.toString(normalizedPayload.tro.tx, "hex");
62
+ } else if (payload.type === OperationType.TX) {
63
+ normalizedPayload = normalizeTransactionOperation(payload, config);
64
+ isValid = await partialTransactionValidator.validate(normalizedPayload);
65
+ hash = b4a.toString(normalizedPayload.txo.tx, "hex");
66
+ }
67
+
68
+ if (!isValid) {
69
+ throw new Error("Invalid transaction payload.");
70
+ }
71
+
72
+ const success = await msbInstance.broadcastPartialTransaction(payload);
73
+
74
+ if (!success) {
75
+ throw new Error("Failed to broadcast transaction after multiple attempts.");
76
+ }
77
+
78
+ const signedLength = msbInstance.state.getSignedLength();
79
+ const unsignedLength = msbInstance.state.getUnsignedLength();
80
+
81
+ return { message: "Transaction broadcasted successfully.", signedLength, unsignedLength, tx: hash };
41
82
  }
42
83
 
43
84
  export async function getTxHashes(msbInstance, start, end) {
@@ -1,5 +1,5 @@
1
1
  import b4a from "b4a"
2
- import { operationToPayload } from "../../src/utils/operations.js"
2
+ import { operationToPayload } from "../../src/utils/applyOperations.js"
3
3
  export function decodeBase64Payload(base64) {
4
4
  let decodedPayloadString
5
5
  try {
package/src/config/env.js CHANGED
@@ -6,6 +6,8 @@ export const ENV = {
6
6
  DEVELOPMENT: 'development',
7
7
  TESTNET1: 'testnet1'
8
8
  }
9
+ // TODO: CREATE TEST ENV CONFIG SIMILAR TO MAINNET AND USE IT IN TESTS.
10
+ // TODO: CREATE TESTNET1 ENV CONFIG and update npm scripts to run node witn mainnet or testnet1.
9
11
 
10
12
  const configData = {
11
13
  [ENV.MAINNET]: {
@@ -4,7 +4,7 @@ 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,
@@ -18,7 +18,8 @@ import ConnectionManager from './services/ConnectionManager.js';
18
18
  import MessageOrchestrator from './services/MessageOrchestrator.js';
19
19
  import NetworkWalletFactory from './identity/NetworkWalletFactory.js';
20
20
  import { EventType } from '../../utils/constants.js';
21
-
21
+ import { networkMessageFactory } from '../../messages/network/v1/networkMessageFactory.js';
22
+ import TransactionRateLimiterService from './services/TransactionRateLimiterService.js';
22
23
  // -- Debug Mode --
23
24
  // TODO: Implement a better debug system in the future. This is just temporary.
24
25
  const DEBUG = false;
@@ -42,6 +43,7 @@ class Network extends ReadyResource {
42
43
  #pendingConnections;
43
44
  #connectTimeoutMs;
44
45
  #maxPendingConnections;
46
+ #rateLimiter;
45
47
 
46
48
  /**
47
49
  * @param {State} state
@@ -53,18 +55,12 @@ class Network extends ReadyResource {
53
55
  this.#config = config
54
56
  this.#connectTimeoutMs = config.connectTimeoutMs || 5000;
55
57
  this.#maxPendingConnections = config.maxPendingConnections || 50;
56
-
57
58
  this.#pendingConnections = new Map();
58
59
  this.#transactionPoolService = new TransactionPoolService(state, address, this.#config);
59
60
  this.#validatorObserverService = new ValidatorObserverService(this, state, address, this.#config);
60
- this.#networkMessages = new NetworkMessages(this, this.#config);
61
61
  this.#validatorConnectionManager = new ConnectionManager(this.#config);
62
62
  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;
63
+
68
64
  }
69
65
 
70
66
  get swarm() {
@@ -97,6 +93,7 @@ class Network extends ReadyResource {
97
93
  }
98
94
 
99
95
  async _close() {
96
+ // TODO: Implement better "await" logic for stopping services
100
97
  console.log('Network: closing gracefully...');
101
98
  this.transactionPoolService.stopPool();
102
99
  await sleep(100);
@@ -119,7 +116,7 @@ class Network extends ReadyResource {
119
116
  });
120
117
 
121
118
  // VALIDATOR_CONNECTION_READY
122
- this.on(EventType.VALIDATOR_CONNECTION_READY, ({ publicKey, type, connection }) => {
119
+ this.on(EventType.VALIDATOR_CONNECTION_READY, async ({ publicKey, type, connection }) => {
123
120
  debugLog(`Network Event: VALIDATOR_CONNECTION_READY | PublicKey: ${publicKey} | Type: ${type}`);
124
121
  const { timeoutId } = this.#pendingConnections.get(publicKey);
125
122
  if (!timeoutId) return;
@@ -128,19 +125,15 @@ class Network extends ReadyResource {
128
125
  this.#pendingConnections.delete(publicKey);
129
126
 
130
127
  if (type === 'validator') {
131
- const target = b4a.from(publicKey, 'hex');
132
- this.#validatorConnectionManager.addValidator(target, connection);
133
- this.#sendRequestByType(connection, type);
128
+ await connection.protocolSession.send(NETWORK_MESSAGE_TYPES.GET.VALIDATOR);
134
129
  }
130
+
135
131
  });
136
132
  }
137
133
 
138
134
  cleanupNetworkListeners() {
139
- // connect:timeout
140
- this.removeAllListeners('connect:timeout');
141
-
142
- // connect:ready
143
- this.removeAllListeners('connect:ready');
135
+ this.removeAllListeners(EventType.VALIDATOR_CONNECTION_TIMEOUT);
136
+ this.removeAllListeners(EventType.VALIDATOR_CONNECTION_READY);
144
137
  }
145
138
 
146
139
  cleanupPendingConnections() {
@@ -167,12 +160,23 @@ class Network extends ReadyResource {
167
160
  maxClientConnections: MAX_CLIENT_CONNECTIONS
168
161
  });
169
162
 
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
+
170
173
  console.log(`Channel: ${b4a.toString(this.#config.channel)}`);
171
- this.#networkMessages.initializeMessageRouter(state, wrappedWallet);
172
174
 
173
175
  this.#swarm.on('connection', async (connection) => {
174
- const { message_channel, message } = await this.#networkMessages.setupProtomuxMessages(connection);
175
- 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);
176
180
 
177
181
  // ATTENTION: Must be called AFTER the protomux init above
178
182
  const stream = store.replicate(connection);
@@ -185,17 +189,9 @@ class Network extends ReadyResource {
185
189
  }
186
190
 
187
191
  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
-
192
+ this.#swarm.leavePeer(connection.remotePublicKey);
193
+ this.#validatorConnectionManager.remove(publicKey);
194
+ connection.protocolSession.close();
199
195
  });
200
196
 
201
197
  connection.on('error', (error) => {
@@ -209,7 +205,6 @@ class Network extends ReadyResource {
209
205
  return;
210
206
  }
211
207
  console.error(error.message)
212
-
213
208
  });
214
209
 
215
210
  });
@@ -259,7 +254,7 @@ class Network extends ReadyResource {
259
254
  const peerInfo = this.#swarm.peers.get(publicKey);
260
255
  if (peerInfo) {
261
256
  const connection = this.#swarm._allConnections.get(peerInfo.publicKey);
262
- if (connection && connection.messenger) {
257
+ if (connection && connection.protocolSession) {
263
258
  await this.#finalizeConnection(publicKey, type, connection);
264
259
  }
265
260
  }
@@ -276,33 +271,6 @@ class Network extends ReadyResource {
276
271
  debugLog(`Network.finalizeConnection: Connected to peer: ${publicKey} as type: ${type}`);
277
272
  }
278
273
 
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
274
  #getNetworkWalletWrapper(wallet, keyPair) {
307
275
  if (!this.#identityProvider) {
308
276
  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 {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;