trac-msb 0.2.11 → 0.2.12

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 (114) hide show
  1. package/.github/workflows/acceptance-tests.yml +38 -0
  2. package/.github/workflows/lint-pr-title.yml +26 -0
  3. package/.github/workflows/publish.yml +33 -0
  4. package/.github/workflows/unit-tests.yml +34 -0
  5. package/package.json +5 -10
  6. package/proto/network.proto +74 -0
  7. package/rpc/rpc_services.js +4 -22
  8. package/scripts/generate-protobufs.js +12 -37
  9. package/src/config/config.js +5 -26
  10. package/src/config/env.js +11 -25
  11. package/src/core/network/Network.js +36 -73
  12. package/src/core/network/protocols/LegacyProtocol.js +11 -21
  13. package/src/core/network/protocols/NetworkMessages.js +17 -38
  14. package/src/core/network/protocols/ProtocolInterface.js +2 -14
  15. package/src/core/network/protocols/ProtocolSession.js +17 -144
  16. package/src/core/network/protocols/V1Protocol.js +18 -37
  17. package/src/core/network/protocols/legacy/NetworkMessageRouter.js +19 -25
  18. package/src/core/network/protocols/legacy/handlers/{LegacyGetRequestHandler.js → GetRequestHandler.js} +6 -6
  19. package/src/core/network/protocols/legacy/handlers/ResponseHandler.js +37 -0
  20. package/src/core/network/protocols/{legacy/handlers/LegacyRoleOperationHandler.js → shared/handlers/RoleOperationHandler.js} +11 -18
  21. package/src/core/network/protocols/{legacy/handlers/LegacySubnetworkOperationHandler.js → shared/handlers/SubnetworkOperationHandler.js} +17 -28
  22. package/src/core/network/protocols/{legacy/handlers/LegacyTransferOperationHandler.js → shared/handlers/TransferOperationHandler.js} +11 -17
  23. package/src/core/network/protocols/{legacy/handlers/BaseStateOperationHandler.js → shared/handlers/base/BaseOperationHandler.js} +12 -23
  24. package/src/core/network/protocols/shared/validators/{PartialBootstrapDeploymentValidator.js → PartialBootstrapDeployment.js} +4 -9
  25. package/src/core/network/protocols/shared/validators/{PartialRoleAccessValidator.js → PartialRoleAccess.js} +17 -51
  26. package/src/core/network/protocols/shared/validators/{PartialTransactionValidator.js → PartialTransaction.js} +7 -21
  27. package/src/core/network/protocols/shared/validators/{PartialTransferValidator.js → PartialTransfer.js} +9 -26
  28. package/src/core/network/protocols/shared/validators/{PartialOperationValidator.js → base/PartialOperation.js} +25 -47
  29. package/src/core/network/protocols/v1/NetworkMessageRouter.js +7 -91
  30. package/src/core/network/services/ConnectionManager.js +94 -146
  31. package/src/core/network/services/MessageOrchestrator.js +27 -151
  32. package/src/core/network/services/TransactionPoolService.js +18 -129
  33. package/src/core/network/services/TransactionRateLimiterService.js +34 -52
  34. package/src/core/network/services/ValidatorObserverService.js +26 -18
  35. package/src/core/state/State.js +19 -70
  36. package/src/index.js +4 -5
  37. package/src/messages/network/v1/NetworkMessageBuilder.js +79 -59
  38. package/src/messages/network/v1/NetworkMessageDirector.js +50 -16
  39. package/src/utils/Scheduler.js +8 -0
  40. package/src/utils/constants.js +5 -71
  41. package/src/utils/helpers.js +1 -10
  42. package/src/utils/normalizers.js +0 -38
  43. package/src/utils/protobuf/network.cjs +840 -0
  44. package/src/utils/protobuf/operationHelpers.js +3 -24
  45. package/tests/acceptance/v1/account/account.test.mjs +2 -8
  46. package/tests/acceptance/v1/tx/tx.test.mjs +1 -23
  47. package/tests/acceptance/v1/tx-details/tx-details.test.mjs +6 -34
  48. package/tests/fixtures/networkV1.fixtures.js +28 -2
  49. package/tests/helpers/transactionPayloads.mjs +2 -2
  50. package/tests/unit/messages/network/NetworkMessageBuilder.test.js +79 -239
  51. package/tests/unit/messages/network/NetworkMessageDirector.test.js +77 -223
  52. package/tests/unit/network/ConnectionManager.test.js +191 -0
  53. package/tests/unit/network/networkModule.test.js +1 -4
  54. package/tests/unit/unit.test.js +2 -2
  55. package/tests/unit/utils/protobuf/operationHelpers.test.js +4 -2
  56. package/tests/unit/utils/utils.test.js +0 -1
  57. package/proto/network/v1/enums/message_type.proto +0 -16
  58. package/proto/network/v1/enums/result_code.proto +0 -84
  59. package/proto/network/v1/messages/broadcast_transaction_request.proto +0 -9
  60. package/proto/network/v1/messages/broadcast_transaction_response.proto +0 -13
  61. package/proto/network/v1/messages/liveness_request.proto +0 -8
  62. package/proto/network/v1/messages/liveness_response.proto +0 -11
  63. package/proto/network/v1/network_message.proto +0 -22
  64. package/src/core/network/protocols/connectionPolicies.js +0 -88
  65. package/src/core/network/protocols/legacy/handlers/LegacyResponseHandler.js +0 -23
  66. package/src/core/network/protocols/shared/errors/SharedValidatorRejectionError.js +0 -27
  67. package/src/core/network/protocols/v1/V1ProtocolError.js +0 -91
  68. package/src/core/network/protocols/v1/handlers/V1BaseOperationHandler.js +0 -65
  69. package/src/core/network/protocols/v1/handlers/V1BroadcastTransactionOperationHandler.js +0 -389
  70. package/src/core/network/protocols/v1/handlers/V1LivenessOperationHandler.js +0 -87
  71. package/src/core/network/protocols/v1/validators/V1BaseOperation.js +0 -211
  72. package/src/core/network/protocols/v1/validators/V1BroadcastTransactionRequest.js +0 -26
  73. package/src/core/network/protocols/v1/validators/V1BroadcastTransactionResponse.js +0 -276
  74. package/src/core/network/protocols/v1/validators/V1LivenessRequest.js +0 -15
  75. package/src/core/network/protocols/v1/validators/V1LivenessResponse.js +0 -17
  76. package/src/core/network/protocols/v1/validators/V1ValidationSchema.js +0 -210
  77. package/src/core/network/services/PendingRequestService.js +0 -172
  78. package/src/core/network/services/TransactionCommitService.js +0 -149
  79. package/src/core/network/services/ValidatorHealthCheckService.js +0 -127
  80. package/src/utils/deepEqualApplyPayload.js +0 -40
  81. package/src/utils/logger.js +0 -25
  82. package/src/utils/protobuf/networkV1.generated.cjs +0 -2460
  83. package/tests/unit/network/LegacyNetworkMessageRouter.test.js +0 -54
  84. package/tests/unit/network/ProtocolSession.test.js +0 -127
  85. package/tests/unit/network/services/ConnectionManager.test.js +0 -450
  86. package/tests/unit/network/services/MessageOrchestrator.test.js +0 -445
  87. package/tests/unit/network/services/PendingRequestService.test.js +0 -431
  88. package/tests/unit/network/services/TransactionCommitService.test.js +0 -246
  89. package/tests/unit/network/services/TransactionPoolService.test.js +0 -489
  90. package/tests/unit/network/services/TransactionRateLimiterService.test.js +0 -139
  91. package/tests/unit/network/services/ValidatorHealthCheckService.test.js +0 -115
  92. package/tests/unit/network/services/services.test.js +0 -17
  93. package/tests/unit/network/utils/v1TestUtils.js +0 -153
  94. package/tests/unit/network/v1/NetworkMessageRouterV1.test.js +0 -151
  95. package/tests/unit/network/v1/V1BaseOperation.test.js +0 -356
  96. package/tests/unit/network/v1/V1BroadcastTransactionOperationHandler.test.js +0 -129
  97. package/tests/unit/network/v1/V1BroadcastTransactionRequest.test.js +0 -53
  98. package/tests/unit/network/v1/V1BroadcastTransactionResponse.test.js +0 -512
  99. package/tests/unit/network/v1/V1LivenessRequest.test.js +0 -32
  100. package/tests/unit/network/v1/V1LivenessResponse.test.js +0 -45
  101. package/tests/unit/network/v1/V1ResultCode.test.js +0 -84
  102. package/tests/unit/network/v1/V1ValidationSchema.test.js +0 -13
  103. package/tests/unit/network/v1/connectionPolicies.test.js +0 -49
  104. package/tests/unit/network/v1/handlers/V1BaseOperationHandler.test.js +0 -284
  105. package/tests/unit/network/v1/handlers/V1BroadcastTransactionOperationHandler.test.js +0 -794
  106. package/tests/unit/network/v1/handlers/V1LivenessOperationHandler.test.js +0 -193
  107. package/tests/unit/network/v1/v1.handlers.test.js +0 -15
  108. package/tests/unit/network/v1/v1.test.js +0 -19
  109. package/tests/unit/network/v1/v1ValidationSchema/broadcastTransactionRequest.test.js +0 -119
  110. package/tests/unit/network/v1/v1ValidationSchema/broadcastTransactionResponse.test.js +0 -136
  111. package/tests/unit/network/v1/v1ValidationSchema/common.test.js +0 -308
  112. package/tests/unit/network/v1/v1ValidationSchema/livenessRequest.test.js +0 -90
  113. package/tests/unit/network/v1/v1ValidationSchema/livenessResponse.test.js +0 -133
  114. package/tests/unit/utils/deepEqualApplyPayload/deepEqualApplyPayload.test.js +0 -102
@@ -13,11 +13,10 @@ import {
13
13
  idToBuffer,
14
14
  timestampToBuffer
15
15
  } from '../../../../src/utils/buffer.js';
16
+ import { addressToBuffer } from '../../../../src/core/state/utils/address.js';
16
17
  import { config } from '../../../helpers/config.js';
17
18
  import { asAddress } from '../../../helpers/address.js';
18
19
  import { testKeyPair1 } from '../../../fixtures/apply.fixtures.js';
19
- import { v7 as uuidv7 } from 'uuid';
20
- import { errorMessageIncludes } from '../../../helpers/regexHelper.js';
21
20
 
22
21
  function createWallet() {
23
22
  const keyPair = {
@@ -35,15 +34,64 @@ function uniqueResultCodes() {
35
34
  return [...new Set(Object.values(NetworkResultCode))].sort((a, b) => a - b);
36
35
  }
37
36
 
37
+ test('NetworkMessageDirector builds validator connection request and verifies signature', async t => {
38
+ const wallet = createWallet();
39
+ const director = new NetworkMessageDirector(new NetworkMessageBuilder(wallet, config));
40
+
41
+ const id = '1';
42
+ const caps = ['cap:b', 'cap:a'];
43
+
44
+ const payload = await director.buildValidatorConnectionRequest(id, wallet.address, caps);
45
+ t.is(payload.type, NetworkOperationType.VALIDATOR_CONNECTION_REQUEST);
46
+ t.is(payload.id, id);
47
+ t.alike(payload.capabilities, caps);
48
+
49
+ const msg = createMessage(
50
+ payload.type,
51
+ idToBuffer(payload.id),
52
+ timestampToBuffer(payload.timestamp),
53
+ addressToBuffer(wallet.address, config.addressPrefix),
54
+ payload.validator_connection_request.nonce,
55
+ encodeCapabilities(caps)
56
+ );
57
+ const hash = await PeerWallet.blake3(msg);
58
+ t.ok(wallet.verify(payload.validator_connection_request.signature, hash, wallet.publicKey));
59
+ });
60
+
61
+ test('NetworkMessageDirector builds liveness request and verifies signature', async t => {
62
+ const wallet = createWallet();
63
+ const director = new NetworkMessageDirector(new NetworkMessageBuilder(wallet, config));
64
+
65
+ const id = '1';
66
+ const caps = ['cap:b', 'cap:a'];
67
+ const data = b4a.from('ping', 'utf8');
68
+
69
+ const payload = await director.buildLivenessRequest(id, data, caps);
70
+ t.is(payload.type, NetworkOperationType.LIVENESS_REQUEST);
71
+ t.is(payload.id, id);
72
+ t.alike(payload.capabilities, caps);
73
+
74
+ const msg = createMessage(
75
+ payload.type,
76
+ idToBuffer(payload.id),
77
+ timestampToBuffer(payload.timestamp),
78
+ payload.liveness_request.nonce,
79
+ encodeCapabilities(caps)
80
+ );
81
+ const hash = await PeerWallet.blake3(msg);
82
+ t.ok(wallet.verify(payload.liveness_request.signature, hash, wallet.publicKey));
83
+ });
84
+
38
85
  test('NetworkMessageDirector iterates liveness response ResultCode values', async t => {
39
86
  const wallet = createWallet();
40
87
  const director = new NetworkMessageDirector(new NetworkMessageBuilder(wallet, config));
41
88
 
42
- const id = uuidv7();
89
+ const id = '1';
43
90
  const caps = ['cap:b', 'cap:a'];
91
+ const data = b4a.from('ping', 'utf8');
44
92
 
45
93
  for (const code of uniqueResultCodes()) {
46
- const payload = await director.buildLivenessResponse(id, caps, code);
94
+ const payload = await director.buildLivenessResponse(id, data, caps, code);
47
95
  t.is(payload.type, NetworkOperationType.LIVENESS_RESPONSE);
48
96
  t.is(payload.liveness_response.result, code);
49
97
 
@@ -67,7 +115,7 @@ test('NetworkMessageDirector builds broadcast transaction request and verifies s
67
115
  const wallet = createWallet();
68
116
  const director = new NetworkMessageDirector(new NetworkMessageBuilder(wallet, config));
69
117
 
70
- const id = uuidv7();
118
+ const id = '1';
71
119
  const data = b4a.from('deadbeef', 'hex');
72
120
  const caps = ['cap:b', 'cap:a'];
73
121
 
@@ -96,36 +144,19 @@ test('NetworkMessageDirector iterates broadcast transaction response ResultCode
96
144
  const wallet = createWallet();
97
145
  const director = new NetworkMessageDirector(new NetworkMessageBuilder(wallet, config));
98
146
 
99
- const id = uuidv7();
147
+ const id = '1';
100
148
  const caps = ['cap:b', 'cap:a'];
101
- const proof = b4a.from('deadbeef', 'hex');
102
- const timestamp = Date.now();
103
- const emptyProof = b4a.alloc(0);
104
149
 
105
150
  for (const code of uniqueResultCodes()) {
106
- const includeProof = code === NetworkResultCode.OK;
107
- const proofUnavailable = code === NetworkResultCode.TX_ACCEPTED_PROOF_UNAVAILABLE;
108
- const responseProof = includeProof ? proof : emptyProof;
109
- const responseTimestamp = includeProof || proofUnavailable ? timestamp : 0;
110
- const payload = await director.buildBroadcastTransactionResponse(
111
- id,
112
- caps,
113
- code,
114
- responseProof,
115
- responseTimestamp
116
- );
151
+ const payload = await director.buildBroadcastTransactionResponse(id, caps, code);
117
152
  t.is(payload.type, NetworkOperationType.BROADCAST_TRANSACTION_RESPONSE);
118
153
  t.is(payload.broadcast_transaction_response.result, code);
119
- t.alike(payload.broadcast_transaction_response.proof, responseProof);
120
- t.is(payload.broadcast_transaction_response.timestamp, responseTimestamp);
121
154
 
122
155
  const msg = createMessage(
123
156
  payload.type,
124
157
  idToBuffer(payload.id),
125
158
  timestampToBuffer(payload.timestamp),
126
159
  payload.broadcast_transaction_response.nonce,
127
- responseProof,
128
- timestampToBuffer(responseTimestamp),
129
160
  safeWriteUInt32BE(code, 0),
130
161
  encodeCapabilities(caps)
131
162
  );
@@ -134,214 +165,37 @@ test('NetworkMessageDirector iterates broadcast transaction response ResultCode
134
165
 
135
166
  const decoded = decodeV1networkOperation(encodeV1networkOperation(payload));
136
167
  t.is(decoded.broadcast_transaction_response.result, code);
137
- t.alike(decoded.broadcast_transaction_response.proof, responseProof);
138
- t.is(decoded.broadcast_transaction_response.timestamp, responseTimestamp);
139
168
  }
140
169
  });
141
170
 
142
- test('NetworkMessageDirector builds broadcast transaction response with proof and timestamp', async t => {
143
- const wallet = createWallet();
144
- const director = new NetworkMessageDirector(new NetworkMessageBuilder(wallet, config));
145
-
146
- const id = uuidv7();
147
- const caps = ['cap:b', 'cap:a'];
148
- const proof = b4a.from('deadbeef', 'hex');
149
- const timestamp = Date.now();
150
-
151
- const payload = await director.buildBroadcastTransactionResponse(
152
- id,
153
- caps,
154
- NetworkResultCode.OK,
155
- proof,
156
- timestamp
157
- );
158
-
159
- t.alike(payload.broadcast_transaction_response.proof, proof);
160
- t.is(payload.broadcast_transaction_response.timestamp, timestamp);
161
-
162
- const msg = createMessage(
163
- payload.type,
164
- idToBuffer(payload.id),
165
- timestampToBuffer(payload.timestamp),
166
- payload.broadcast_transaction_response.nonce,
167
- proof,
168
- timestampToBuffer(timestamp),
169
- safeWriteUInt32BE(NetworkResultCode.OK, 0),
170
- encodeCapabilities(caps)
171
- );
172
- const hash = await PeerWallet.blake3(msg);
173
- t.ok(wallet.verify(payload.broadcast_transaction_response.signature, hash, wallet.publicKey));
174
-
175
- const decoded = decodeV1networkOperation(encodeV1networkOperation(payload));
176
- t.alike(decoded.broadcast_transaction_response.proof, proof);
177
- t.is(decoded.broadcast_transaction_response.timestamp, timestamp);
178
- });
179
-
180
- test('NetworkMessageDirector rejects OK response when proof is provided without timestamp', async t => {
181
- const wallet = createWallet();
182
- const director = new NetworkMessageDirector(new NetworkMessageBuilder(wallet, config));
183
-
184
- const id = uuidv7();
185
- const caps = ['cap:b', 'cap:a'];
186
- const proof = b4a.from('deadbeef', 'hex');
187
-
188
- await t.exception(
189
- () => director.buildBroadcastTransactionResponse(
190
- id,
191
- caps,
192
- NetworkResultCode.OK,
193
- proof
194
- ),
195
- errorMessageIncludes('Result code OK requires non-empty proof and timestamp > 0.')
196
- );
197
- });
198
-
199
- test('NetworkMessageDirector rejects OK response when timestamp is provided without proof', async t => {
200
- const wallet = createWallet();
201
- const director = new NetworkMessageDirector(new NetworkMessageBuilder(wallet, config));
202
-
203
- const id = uuidv7();
204
- const caps = ['cap:b', 'cap:a'];
205
- const timestamp = Date.now();
206
-
207
- await t.exception(
208
- () => director.buildBroadcastTransactionResponse(
209
- id,
210
- caps,
211
- NetworkResultCode.OK,
212
- null,
213
- timestamp
214
- ),
215
- errorMessageIncludes('Result code OK requires non-empty proof and timestamp > 0.')
216
- );
217
- });
218
-
219
- test('NetworkMessageDirector allows TX_ACCEPTED_PROOF_UNAVAILABLE response with timestamp and empty proof', async t => {
171
+ test('NetworkMessageDirector iterates validator connection response ResultCode values', async t => {
220
172
  const wallet = createWallet();
221
173
  const director = new NetworkMessageDirector(new NetworkMessageBuilder(wallet, config));
222
174
 
223
- const id = uuidv7();
175
+ const id = '1';
224
176
  const caps = ['cap:b', 'cap:a'];
225
- const timestamp = Date.now();
226
- const emptyProof = b4a.alloc(0);
227
-
228
- const payload = await director.buildBroadcastTransactionResponse(
229
- id,
230
- caps,
231
- NetworkResultCode.TX_ACCEPTED_PROOF_UNAVAILABLE,
232
- emptyProof,
233
- timestamp
234
- );
235
-
236
- t.alike(payload.broadcast_transaction_response.proof, emptyProof);
237
- t.is(payload.broadcast_transaction_response.timestamp, timestamp);
238
- t.is(payload.broadcast_transaction_response.result, NetworkResultCode.TX_ACCEPTED_PROOF_UNAVAILABLE);
239
-
240
- const msg = createMessage(
241
- payload.type,
242
- idToBuffer(payload.id),
243
- timestampToBuffer(payload.timestamp),
244
- payload.broadcast_transaction_response.nonce,
245
- emptyProof,
246
- timestampToBuffer(timestamp),
247
- safeWriteUInt32BE(NetworkResultCode.TX_ACCEPTED_PROOF_UNAVAILABLE, 0),
248
- encodeCapabilities(caps)
249
- );
250
- const hash = await PeerWallet.blake3(msg);
251
- t.ok(wallet.verify(payload.broadcast_transaction_response.signature, hash, wallet.publicKey));
252
-
253
- const decoded = decodeV1networkOperation(encodeV1networkOperation(payload));
254
- t.is(decoded.broadcast_transaction_response.timestamp, timestamp);
255
- t.is(decoded.broadcast_transaction_response.result, NetworkResultCode.TX_ACCEPTED_PROOF_UNAVAILABLE);
256
- });
257
-
258
- test('NetworkMessageDirector rejects OK response when proof and timestamp are both missing', async t => {
259
- const wallet = createWallet();
260
- const director = new NetworkMessageDirector(new NetworkMessageBuilder(wallet, config));
261
-
262
- const id = uuidv7();
263
- const caps = ['cap:b', 'cap:a'];
264
-
265
- await t.exception(
266
- () => director.buildBroadcastTransactionResponse(
267
- id,
268
- caps,
269
- NetworkResultCode.OK
270
- ),
271
- errorMessageIncludes('Result code OK requires non-empty proof and timestamp > 0.')
272
- );
273
- });
274
-
275
- test('NetworkMessageDirector rejects TX_ACCEPTED_PROOF_UNAVAILABLE response when timestamp is missing', async t => {
276
- const wallet = createWallet();
277
- const director = new NetworkMessageDirector(new NetworkMessageBuilder(wallet, config));
278
-
279
- const id = uuidv7();
280
- const caps = ['cap:b', 'cap:a'];
281
-
282
- await t.exception(
283
- () => director.buildBroadcastTransactionResponse(
284
- id,
285
- caps,
286
- NetworkResultCode.TX_ACCEPTED_PROOF_UNAVAILABLE
287
- ),
288
- errorMessageIncludes('Result code TX_ACCEPTED_PROOF_UNAVAILABLE requires timestamp > 0.')
289
- );
290
- });
177
+ const otherAddress = asAddress('36fdaf941de4afe602cbb1e2f56dc582466ef23fad1da55c09fd6dd841cbd117');
291
178
 
292
- test('NetworkMessageDirector rejects TX_ACCEPTED_PROOF_UNAVAILABLE response when proof is non-empty', async t => {
293
- const wallet = createWallet();
294
- const director = new NetworkMessageDirector(new NetworkMessageBuilder(wallet, config));
295
-
296
- const id = uuidv7();
297
- const caps = ['cap:b', 'cap:a'];
298
-
299
- await t.exception(
300
- () => director.buildBroadcastTransactionResponse(
301
- id,
302
- caps,
303
- NetworkResultCode.TX_ACCEPTED_PROOF_UNAVAILABLE,
304
- b4a.from('deadbeef', 'hex'),
305
- Date.now()
306
- ),
307
- errorMessageIncludes('Result code TX_ACCEPTED_PROOF_UNAVAILABLE requires empty proof.')
308
- );
309
- });
310
-
311
- test('NetworkMessageDirector rejects non-OK response when proof is non-empty', async t => {
312
- const wallet = createWallet();
313
- const director = new NetworkMessageDirector(new NetworkMessageBuilder(wallet, config));
314
-
315
- const id = uuidv7();
316
- const caps = ['cap:b', 'cap:a'];
317
-
318
- await t.exception(
319
- () => director.buildBroadcastTransactionResponse(
179
+ for (const code of uniqueResultCodes()) {
180
+ const payload = await director.buildValidatorConnectionResponse(
320
181
  id,
182
+ otherAddress,
321
183
  caps,
322
- NetworkResultCode.INVALID_PAYLOAD,
323
- b4a.from('deadbeef', 'hex'),
324
- 0
325
- ),
326
- errorMessageIncludes('Non-OK result code requires empty proof.')
327
- );
328
- });
329
-
330
- test('NetworkMessageDirector rejects non-OK response with timestamp > 0 unless proof is unavailable', async t => {
331
- const wallet = createWallet();
332
- const director = new NetworkMessageDirector(new NetworkMessageBuilder(wallet, config));
333
-
334
- const id = uuidv7();
335
- const caps = ['cap:b', 'cap:a'];
184
+ code
185
+ );
186
+ t.is(payload.type, NetworkOperationType.VALIDATOR_CONNECTION_RESPONSE);
187
+ t.is(payload.validator_connection_response.result, code);
336
188
 
337
- await t.exception(
338
- () => director.buildBroadcastTransactionResponse(
339
- id,
340
- caps,
341
- NetworkResultCode.INVALID_PAYLOAD,
342
- null,
343
- Date.now()
344
- ),
345
- errorMessageIncludes('Non-OK result code requires timestamp to be 0, except TX_ACCEPTED_PROOF_UNAVAILABLE.')
346
- );
189
+ const msg = createMessage(
190
+ payload.type,
191
+ idToBuffer(payload.id),
192
+ timestampToBuffer(payload.timestamp),
193
+ addressToBuffer(otherAddress, config.addressPrefix),
194
+ payload.validator_connection_response.nonce,
195
+ safeWriteUInt32BE(code, 0),
196
+ encodeCapabilities(caps)
197
+ );
198
+ const hash = await PeerWallet.blake3(msg);
199
+ t.ok(wallet.verify(payload.validator_connection_response.signature, hash, wallet.publicKey));
200
+ }
347
201
  });
@@ -0,0 +1,191 @@
1
+ import sinon from "sinon";
2
+ import { hook, test } from 'brittle'
3
+ import { default as EventEmitter } from "bare-events"
4
+ import { testKeyPair1, testKeyPair2, testKeyPair3, testKeyPair4, testKeyPair5, testKeyPair6, testKeyPair7, testKeyPair8, testKeyPair9 } from "../../fixtures/apply.fixtures.js";
5
+ import ConnectionManager from "../../../src/core/network/services/ConnectionManager.js";
6
+ import { tick } from "../../helpers/setupApplyTests.js";
7
+ import b4a from 'b4a'
8
+ import { createConfig, ENV } from "../../../src/config/env.js";
9
+
10
+ const createConnection = (key) => {
11
+ const emitter = new EventEmitter()
12
+ emitter.protocolSession = {
13
+ has: (name) => name === 'legacy',
14
+ send: sinon.stub().resolves(),
15
+ };
16
+ emitter.connected = true
17
+ emitter.remotePublicKey = b4a.from(key, 'hex')
18
+
19
+ return { key: b4a.from(key, 'hex'), connection: emitter }
20
+ }
21
+
22
+ const makeManager = (maxValidators = 6, conns = connections) => {
23
+ const merged = createConfig(ENV.DEVELOPMENT, { maxValidators })
24
+ const connectionManager = new ConnectionManager(merged)
25
+
26
+ conns.forEach(({ key, connection }) => {
27
+ connectionManager.addValidator(key, connection)
28
+ });
29
+
30
+ return connectionManager
31
+ }
32
+
33
+ const reset = () => {
34
+ sinon.restore()
35
+ connections.forEach(connection => {
36
+ connection.connection.protocolSession.send.resetHistory()
37
+ })
38
+ }
39
+
40
+ let connections
41
+ hook('Initialize state', async () => {
42
+ connections = [
43
+ createConnection(testKeyPair1.publicKey),
44
+ createConnection(testKeyPair2.publicKey),
45
+ createConnection(testKeyPair3.publicKey),
46
+ createConnection(testKeyPair4.publicKey),
47
+ ]
48
+ });
49
+
50
+ test('ConnectionManager', () => {
51
+ test('addValidator', async t => {
52
+ test('adds a validator', async t => {
53
+ reset()
54
+ const connectionManager = makeManager()
55
+ t.is(connectionManager.connectionCount(), connections.length, 'should have the same length')
56
+ const data = createConnection(testKeyPair5.publicKey)
57
+ connectionManager.addValidator(data.key, data.connection)
58
+ t.is(connectionManager.connectionCount(), connections.length + 1, 'should have the same length')
59
+ })
60
+
61
+ test('dont surpass maxConnections', async t => {
62
+ reset()
63
+ const maxConnections = 5
64
+ const connectionManager = makeManager(maxConnections)
65
+ t.is(connectionManager.connectionCount(), connections.length, 'should have the same length')
66
+
67
+ const toAdd = createConnection(testKeyPair5.publicKey)
68
+ connectionManager.addValidator(toAdd.key, toAdd.connection)
69
+ t.is(connectionManager.connectionCount(), maxConnections, 'should match the max connections')
70
+
71
+ const toNotAdd = createConnection(testKeyPair6.publicKey)
72
+ connectionManager.addValidator(toNotAdd.key, toNotAdd.connection)
73
+ t.is(connectionManager.connectionCount(), maxConnections, 'should not increase length')
74
+ })
75
+
76
+ test('does not add new validator when pool is full', async t => {
77
+ reset()
78
+ const maxConnections = 2
79
+ const localConnections = [
80
+ createConnection(testKeyPair1.publicKey),
81
+ createConnection(testKeyPair2.publicKey),
82
+ ]
83
+
84
+ const connectionManager = makeManager(maxConnections)
85
+ localConnections.forEach(({ key, connection }) => {
86
+ connectionManager.addValidator(key, connection)
87
+ })
88
+
89
+ t.is(connectionManager.connectionCount(), maxConnections, 'pool should be full')
90
+
91
+ const newConn = createConnection(testKeyPair3.publicKey)
92
+ connectionManager.addValidator(newConn.key, newConn.connection)
93
+
94
+ t.is(connectionManager.connectionCount(), maxConnections, 'should stay at max size')
95
+ t.not(connectionManager.connected(newConn.key), 'new validator should not be in the pool')
96
+
97
+ const remainingOld = localConnections.filter(c => connectionManager.connected(c.key)).length
98
+ t.is(remainingOld, 2, 'all of the old validators should remain')
99
+ })
100
+ })
101
+
102
+ test('connected', async t => {
103
+ test('true', async t => {
104
+ reset()
105
+ const connectionManager = makeManager()
106
+ connections.forEach(con => {
107
+ t.ok(connectionManager.connected(con.key), 'should respond true')
108
+ })
109
+ })
110
+
111
+ test('false', async t => {
112
+ reset()
113
+ const connectionManager = makeManager()
114
+ t.ok(!connectionManager.connected(testKeyPair6.publicKey), 'should respond false')
115
+ })
116
+ })
117
+
118
+ test('send', async t => {
119
+ test('triggers send on messenger', async t => {
120
+ reset()
121
+ const connectionManager = makeManager()
122
+
123
+ const target = connectionManager.send([1,2,3,4])
124
+
125
+ const totalCalls = connections.reduce((sum, con) => sum + con.connection.protocolSession.send.callCount, 0)
126
+ t.is(totalCalls, 1, 'should send to exactly one validator')
127
+ t.ok(target, 'should return a target public key')
128
+ })
129
+
130
+ test('does not throw on individual send errors', async t => {
131
+ reset()
132
+ const errorConnections = [
133
+ createConnection(testKeyPair7.publicKey),
134
+ createConnection(testKeyPair8.publicKey),
135
+ ]
136
+
137
+ errorConnections.forEach(con => {
138
+ con.connection.protocolSession.send = sinon.stub().throws(new Error())
139
+ })
140
+
141
+ const connectionManager = makeManager(5, errorConnections)
142
+
143
+ t.is(errorConnections.length, 2, 'should have two connections')
144
+ connectionManager.send([1,2,3,4])
145
+ t.ok(true, 'send should not throw even if individual sends fail')
146
+ })
147
+ })
148
+
149
+ test('on close', async t => {
150
+ test('removes from list', async t => {
151
+ reset()
152
+ const connectionManager = makeManager()
153
+
154
+ const connectionCount = connectionManager.connectionCount()
155
+
156
+ connections[1].connection.connected = false
157
+ connections[1].connection.emit('close')
158
+ await tick()
159
+ t.is(connectionCount, connectionManager.connectionCount() + 1, 'first on the list should have been called')
160
+ })
161
+ })
162
+
163
+ test('remove', async t => {
164
+ test('removes a validator by public key', async t => {
165
+ reset()
166
+ const connectionManager = makeManager()
167
+ const previousCount = connectionManager.connectionCount()
168
+ const lastValidator = connections.shift()
169
+
170
+ t.ok(connectionManager.connected(lastValidator.key), 'should be connected')
171
+ connectionManager.remove(lastValidator.key)
172
+
173
+ t.is(connectionManager.connectionCount(), previousCount - 1, 'should reduce the connection count')
174
+ t.ok(!connectionManager.connected(lastValidator.key), 'should be connected')
175
+ })
176
+ })
177
+
178
+ test('on close', async t => {
179
+ test('removes from list', async t => {
180
+ reset()
181
+ const connectionManager = makeManager()
182
+
183
+ const connectionCount = connectionManager.connectionCount()
184
+
185
+ connections[1].connection.connected = false
186
+ connections[1].connection.emit('close')
187
+ await tick()
188
+ t.is(connectionCount, connectionManager.connectionCount() + 1, 'first on the list should have been called')
189
+ })
190
+ })
191
+ })
@@ -2,11 +2,8 @@ import { default as test } from 'brittle';
2
2
 
3
3
  async function runNetworkModuleTests() {
4
4
  test.pause();
5
- await import('./LegacyNetworkMessageRouter.test.js');
5
+ await import('./ConnectionManager.test.js');
6
6
  await import('./NetworkWalletFactory.test.js');
7
- await import('./ProtocolSession.test.js');
8
- await import('./services/services.test.js');
9
- await import('./v1/v1.test.js');
10
7
  test.resume();
11
8
  }
12
9
 
@@ -4,10 +4,10 @@ import { default as test } from 'brittle';
4
4
 
5
5
  async function runTests() {
6
6
  test.pause();
7
- await import('./utils/utils.test.js');
8
- await import('./messages/messages.test.js');
9
7
  await import('./network/networkModule.test.js')
10
8
  await import('./state/stateModule.test.js');
9
+ await import('./utils/utils.test.js');
10
+ await import('./messages/messages.test.js');
11
11
  test.resume();
12
12
  }
13
13
 
@@ -85,8 +85,8 @@ test('Decode throws on buffer with unknown wire type (skip case)', t => {
85
85
  const bufWithWire7 = b4a.from([0x0F]);
86
86
  try {
87
87
  applyOperations.Operation.decode(bufWithWire7);
88
- t.fail('Expected decode to throw on unknown wire type');
89
88
  } catch (err) {
89
+ console.error('Caught error:', err);
90
90
  t.ok(err instanceof Error && err.message.includes('Could not decode varint'), 'Should throw an error instance to be thrown for unknown wire type');
91
91
  }
92
92
  });
@@ -242,6 +242,8 @@ test('Protobuf encode/decode is order-independent for all operation types', t =>
242
242
 
243
243
  test('encodeV1networkOperation/decodeV1networkOperation roundtrip for network v1 payloads', t => {
244
244
  const payloadsHashMap = new Map([
245
+ ['validatorConnectionRequest', networkV1Fixtures.payloadValidatorConnectionRequest],
246
+ ['validatorConnectionResponse', networkV1Fixtures.payloadValidatorConnectionResponse],
245
247
  ['livenessRequest', networkV1Fixtures.payloadLivenessRequest],
246
248
  ['livenessResponse', networkV1Fixtures.payloadLivenessResponse],
247
249
  ['broadcastTransactionRequest', networkV1Fixtures.payloadBroadcastTransactionRequest],
@@ -252,7 +254,7 @@ test('encodeV1networkOperation/decodeV1networkOperation roundtrip for network v1
252
254
  const encoded = encodeV1networkOperation(payload);
253
255
  const decoded = decodeV1networkOperation(encoded);
254
256
  t.ok(b4a.isBuffer(encoded) && encoded.length > 0, `Payload ${key} encodes to a non-empty buffer`);
255
- t.alike(decoded, payload, `Payload ${key} decodes back correctly`);
257
+ t.ok(JSON.stringify(decoded) === JSON.stringify(payload), `Payload ${key} decodes back correctly`);
256
258
  }
257
259
  });
258
260
 
@@ -6,7 +6,6 @@ async function runTests() {
6
6
  test.pause();
7
7
 
8
8
  await import('./check/check.test.js');
9
- await import('./deepEqualApplyPayload/deepEqualApplyPayload.test.js');
10
9
  await import('./protobuf/operationHelpers.test.js');
11
10
  await import('./helpers/helpers.test.js');
12
11
  await import('./fileUtils/readAddressesFromWhitelistFile.test.js');
@@ -1,16 +0,0 @@
1
- syntax = "proto3";
2
-
3
- package network.v1;
4
-
5
- // Warning: Do not change numeric values of existing enum fields.
6
- // They are part of the public API.
7
- // New values may be added, but existing values must never be renumbered or reused.
8
- // Use [deprecated = true] while phasing out a value that still remains in the schema.
9
- // If a value is removed, reserve its number (and preferably its name).
10
- enum MessageType {
11
- MESSAGE_TYPE_UNSPECIFIED = 0;
12
- MESSAGE_TYPE_LIVENESS_REQUEST = 1;
13
- MESSAGE_TYPE_LIVENESS_RESPONSE = 2;
14
- MESSAGE_TYPE_BROADCAST_TRANSACTION_REQUEST = 3;
15
- MESSAGE_TYPE_BROADCAST_TRANSACTION_RESPONSE = 4;
16
- }