trac-msb 0.2.12 → 0.2.13

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/package.json +9 -4
  2. package/proto/network/v1/enums/message_type.proto +16 -0
  3. package/proto/network/v1/enums/result_code.proto +84 -0
  4. package/proto/network/v1/messages/broadcast_transaction_request.proto +9 -0
  5. package/proto/network/v1/messages/broadcast_transaction_response.proto +13 -0
  6. package/proto/network/v1/messages/liveness_request.proto +8 -0
  7. package/proto/network/v1/messages/liveness_response.proto +11 -0
  8. package/proto/network/v1/network_message.proto +22 -0
  9. package/rpc/rpc_services.js +22 -4
  10. package/scripts/generate-protobufs.js +37 -12
  11. package/src/config/config.js +26 -5
  12. package/src/config/env.js +25 -11
  13. package/src/core/network/Network.js +73 -36
  14. package/src/core/network/protocols/LegacyProtocol.js +21 -11
  15. package/src/core/network/protocols/NetworkMessages.js +38 -17
  16. package/src/core/network/protocols/ProtocolInterface.js +14 -2
  17. package/src/core/network/protocols/ProtocolSession.js +144 -17
  18. package/src/core/network/protocols/V1Protocol.js +37 -18
  19. package/src/core/network/protocols/connectionPolicies.js +88 -0
  20. package/src/core/network/protocols/legacy/NetworkMessageRouter.js +25 -19
  21. package/src/core/network/protocols/{shared/handlers/base/BaseOperationHandler.js → legacy/handlers/BaseStateOperationHandler.js} +23 -12
  22. package/src/core/network/protocols/legacy/handlers/{GetRequestHandler.js → LegacyGetRequestHandler.js} +6 -6
  23. package/src/core/network/protocols/legacy/handlers/LegacyResponseHandler.js +23 -0
  24. package/src/core/network/protocols/{shared/handlers/RoleOperationHandler.js → legacy/handlers/LegacyRoleOperationHandler.js} +18 -11
  25. package/src/core/network/protocols/{shared/handlers/SubnetworkOperationHandler.js → legacy/handlers/LegacySubnetworkOperationHandler.js} +28 -17
  26. package/src/core/network/protocols/{shared/handlers/TransferOperationHandler.js → legacy/handlers/LegacyTransferOperationHandler.js} +17 -11
  27. package/src/core/network/protocols/shared/errors/SharedValidatorRejectionError.js +27 -0
  28. package/src/core/network/protocols/shared/validators/{PartialBootstrapDeployment.js → PartialBootstrapDeploymentValidator.js} +9 -4
  29. package/src/core/network/protocols/shared/validators/{base/PartialOperation.js → PartialOperationValidator.js} +47 -25
  30. package/src/core/network/protocols/shared/validators/{PartialRoleAccess.js → PartialRoleAccessValidator.js} +51 -17
  31. package/src/core/network/protocols/shared/validators/{PartialTransaction.js → PartialTransactionValidator.js} +21 -7
  32. package/src/core/network/protocols/shared/validators/{PartialTransfer.js → PartialTransferValidator.js} +26 -9
  33. package/src/core/network/protocols/v1/NetworkMessageRouter.js +91 -7
  34. package/src/core/network/protocols/v1/V1ProtocolError.js +91 -0
  35. package/src/core/network/protocols/v1/handlers/V1BaseOperationHandler.js +65 -0
  36. package/src/core/network/protocols/v1/handlers/V1BroadcastTransactionOperationHandler.js +389 -0
  37. package/src/core/network/protocols/v1/handlers/V1LivenessOperationHandler.js +87 -0
  38. package/src/core/network/protocols/v1/validators/V1BaseOperation.js +211 -0
  39. package/src/core/network/protocols/v1/validators/V1BroadcastTransactionRequest.js +26 -0
  40. package/src/core/network/protocols/v1/validators/V1BroadcastTransactionResponse.js +276 -0
  41. package/src/core/network/protocols/v1/validators/V1LivenessRequest.js +15 -0
  42. package/src/core/network/protocols/v1/validators/V1LivenessResponse.js +17 -0
  43. package/src/core/network/protocols/v1/validators/V1ValidationSchema.js +210 -0
  44. package/src/core/network/services/ConnectionManager.js +146 -94
  45. package/src/core/network/services/MessageOrchestrator.js +151 -27
  46. package/src/core/network/services/PendingRequestService.js +172 -0
  47. package/src/core/network/services/TransactionCommitService.js +149 -0
  48. package/src/core/network/services/TransactionPoolService.js +129 -18
  49. package/src/core/network/services/TransactionRateLimiterService.js +52 -34
  50. package/src/core/network/services/ValidatorHealthCheckService.js +127 -0
  51. package/src/core/network/services/ValidatorObserverService.js +18 -26
  52. package/src/core/state/State.js +70 -19
  53. package/src/index.js +5 -4
  54. package/src/messages/network/v1/NetworkMessageBuilder.js +59 -79
  55. package/src/messages/network/v1/NetworkMessageDirector.js +16 -50
  56. package/src/utils/Scheduler.js +0 -8
  57. package/src/utils/constants.js +71 -5
  58. package/src/utils/deepEqualApplyPayload.js +40 -0
  59. package/src/utils/helpers.js +10 -1
  60. package/src/utils/logger.js +25 -0
  61. package/src/utils/normalizers.js +38 -0
  62. package/src/utils/protobuf/networkV1.generated.cjs +2460 -0
  63. package/src/utils/protobuf/operationHelpers.js +24 -3
  64. package/tests/acceptance/v1/account/account.test.mjs +8 -2
  65. package/tests/acceptance/v1/tx/tx.test.mjs +23 -1
  66. package/tests/acceptance/v1/tx-details/tx-details.test.mjs +34 -6
  67. package/tests/fixtures/networkV1.fixtures.js +2 -28
  68. package/tests/helpers/transactionPayloads.mjs +2 -2
  69. package/tests/unit/messages/network/NetworkMessageBuilder.test.js +239 -79
  70. package/tests/unit/messages/network/NetworkMessageDirector.test.js +223 -77
  71. package/tests/unit/network/LegacyNetworkMessageRouter.test.js +54 -0
  72. package/tests/unit/network/ProtocolSession.test.js +127 -0
  73. package/tests/unit/network/networkModule.test.js +4 -1
  74. package/tests/unit/network/services/ConnectionManager.test.js +450 -0
  75. package/tests/unit/network/services/MessageOrchestrator.test.js +445 -0
  76. package/tests/unit/network/services/PendingRequestService.test.js +431 -0
  77. package/tests/unit/network/services/TransactionCommitService.test.js +246 -0
  78. package/tests/unit/network/services/TransactionPoolService.test.js +489 -0
  79. package/tests/unit/network/services/TransactionRateLimiterService.test.js +139 -0
  80. package/tests/unit/network/services/ValidatorHealthCheckService.test.js +115 -0
  81. package/tests/unit/network/services/services.test.js +17 -0
  82. package/tests/unit/network/utils/v1TestUtils.js +153 -0
  83. package/tests/unit/network/v1/NetworkMessageRouterV1.test.js +151 -0
  84. package/tests/unit/network/v1/V1BaseOperation.test.js +356 -0
  85. package/tests/unit/network/v1/V1BroadcastTransactionOperationHandler.test.js +129 -0
  86. package/tests/unit/network/v1/V1BroadcastTransactionRequest.test.js +53 -0
  87. package/tests/unit/network/v1/V1BroadcastTransactionResponse.test.js +512 -0
  88. package/tests/unit/network/v1/V1LivenessRequest.test.js +32 -0
  89. package/tests/unit/network/v1/V1LivenessResponse.test.js +45 -0
  90. package/tests/unit/network/v1/V1ResultCode.test.js +84 -0
  91. package/tests/unit/network/v1/V1ValidationSchema.test.js +13 -0
  92. package/tests/unit/network/v1/connectionPolicies.test.js +49 -0
  93. package/tests/unit/network/v1/handlers/V1BaseOperationHandler.test.js +284 -0
  94. package/tests/unit/network/v1/handlers/V1BroadcastTransactionOperationHandler.test.js +794 -0
  95. package/tests/unit/network/v1/handlers/V1LivenessOperationHandler.test.js +193 -0
  96. package/tests/unit/network/v1/v1.handlers.test.js +15 -0
  97. package/tests/unit/network/v1/v1.test.js +19 -0
  98. package/tests/unit/network/v1/v1ValidationSchema/broadcastTransactionRequest.test.js +119 -0
  99. package/tests/unit/network/v1/v1ValidationSchema/broadcastTransactionResponse.test.js +136 -0
  100. package/tests/unit/network/v1/v1ValidationSchema/common.test.js +308 -0
  101. package/tests/unit/network/v1/v1ValidationSchema/livenessRequest.test.js +90 -0
  102. package/tests/unit/network/v1/v1ValidationSchema/livenessResponse.test.js +133 -0
  103. package/tests/unit/unit.test.js +2 -2
  104. package/tests/unit/utils/deepEqualApplyPayload/deepEqualApplyPayload.test.js +102 -0
  105. package/tests/unit/utils/protobuf/operationHelpers.test.js +2 -4
  106. package/tests/unit/utils/utils.test.js +1 -0
  107. package/.github/workflows/acceptance-tests.yml +0 -38
  108. package/.github/workflows/lint-pr-title.yml +0 -26
  109. package/.github/workflows/publish.yml +0 -33
  110. package/.github/workflows/unit-tests.yml +0 -34
  111. package/proto/network.proto +0 -74
  112. package/src/core/network/protocols/legacy/handlers/ResponseHandler.js +0 -37
  113. package/src/utils/protobuf/network.cjs +0 -840
  114. package/tests/unit/network/ConnectionManager.test.js +0 -191
@@ -13,10 +13,11 @@ import {
13
13
  idToBuffer,
14
14
  timestampToBuffer
15
15
  } from '../../../../src/utils/buffer.js';
16
- import { addressToBuffer } from '../../../../src/core/state/utils/address.js';
17
16
  import { config } from '../../../helpers/config.js';
18
17
  import { asAddress } from '../../../helpers/address.js';
19
18
  import { testKeyPair1 } from '../../../fixtures/apply.fixtures.js';
19
+ import { v7 as uuidv7 } from 'uuid';
20
+ import { errorMessageIncludes } from '../../../helpers/regexHelper.js';
20
21
 
21
22
  function createWallet() {
22
23
  const keyPair = {
@@ -34,64 +35,15 @@ function uniqueResultCodes() {
34
35
  return [...new Set(Object.values(NetworkResultCode))].sort((a, b) => a - b);
35
36
  }
36
37
 
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
-
85
38
  test('NetworkMessageDirector iterates liveness response ResultCode values', async t => {
86
39
  const wallet = createWallet();
87
40
  const director = new NetworkMessageDirector(new NetworkMessageBuilder(wallet, config));
88
41
 
89
- const id = '1';
42
+ const id = uuidv7();
90
43
  const caps = ['cap:b', 'cap:a'];
91
- const data = b4a.from('ping', 'utf8');
92
44
 
93
45
  for (const code of uniqueResultCodes()) {
94
- const payload = await director.buildLivenessResponse(id, data, caps, code);
46
+ const payload = await director.buildLivenessResponse(id, caps, code);
95
47
  t.is(payload.type, NetworkOperationType.LIVENESS_RESPONSE);
96
48
  t.is(payload.liveness_response.result, code);
97
49
 
@@ -115,7 +67,7 @@ test('NetworkMessageDirector builds broadcast transaction request and verifies s
115
67
  const wallet = createWallet();
116
68
  const director = new NetworkMessageDirector(new NetworkMessageBuilder(wallet, config));
117
69
 
118
- const id = '1';
70
+ const id = uuidv7();
119
71
  const data = b4a.from('deadbeef', 'hex');
120
72
  const caps = ['cap:b', 'cap:a'];
121
73
 
@@ -144,19 +96,36 @@ test('NetworkMessageDirector iterates broadcast transaction response ResultCode
144
96
  const wallet = createWallet();
145
97
  const director = new NetworkMessageDirector(new NetworkMessageBuilder(wallet, config));
146
98
 
147
- const id = '1';
99
+ const id = uuidv7();
148
100
  const caps = ['cap:b', 'cap:a'];
101
+ const proof = b4a.from('deadbeef', 'hex');
102
+ const timestamp = Date.now();
103
+ const emptyProof = b4a.alloc(0);
149
104
 
150
105
  for (const code of uniqueResultCodes()) {
151
- const payload = await director.buildBroadcastTransactionResponse(id, caps, code);
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
+ );
152
117
  t.is(payload.type, NetworkOperationType.BROADCAST_TRANSACTION_RESPONSE);
153
118
  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);
154
121
 
155
122
  const msg = createMessage(
156
123
  payload.type,
157
124
  idToBuffer(payload.id),
158
125
  timestampToBuffer(payload.timestamp),
159
126
  payload.broadcast_transaction_response.nonce,
127
+ responseProof,
128
+ timestampToBuffer(responseTimestamp),
160
129
  safeWriteUInt32BE(code, 0),
161
130
  encodeCapabilities(caps)
162
131
  );
@@ -165,37 +134,214 @@ test('NetworkMessageDirector iterates broadcast transaction response ResultCode
165
134
 
166
135
  const decoded = decodeV1networkOperation(encodeV1networkOperation(payload));
167
136
  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);
168
139
  }
169
140
  });
170
141
 
171
- test('NetworkMessageDirector iterates validator connection response ResultCode values', async t => {
142
+ test('NetworkMessageDirector builds broadcast transaction response with proof and timestamp', async t => {
172
143
  const wallet = createWallet();
173
144
  const director = new NetworkMessageDirector(new NetworkMessageBuilder(wallet, config));
174
145
 
175
- const id = '1';
146
+ const id = uuidv7();
176
147
  const caps = ['cap:b', 'cap:a'];
177
- const otherAddress = asAddress('36fdaf941de4afe602cbb1e2f56dc582466ef23fad1da55c09fd6dd841cbd117');
148
+ const proof = b4a.from('deadbeef', 'hex');
149
+ const timestamp = Date.now();
178
150
 
179
- for (const code of uniqueResultCodes()) {
180
- const payload = await director.buildValidatorConnectionResponse(
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(
181
190
  id,
182
- otherAddress,
183
191
  caps,
184
- code
185
- );
186
- t.is(payload.type, NetworkOperationType.VALIDATOR_CONNECTION_RESPONSE);
187
- t.is(payload.validator_connection_response.result, code);
192
+ NetworkResultCode.OK,
193
+ proof
194
+ ),
195
+ errorMessageIncludes('Result code OK requires non-empty proof and timestamp > 0.')
196
+ );
197
+ });
188
198
 
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
- }
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 => {
220
+ const wallet = createWallet();
221
+ const director = new NetworkMessageDirector(new NetworkMessageBuilder(wallet, config));
222
+
223
+ const id = uuidv7();
224
+ 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
+ });
291
+
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(
320
+ id,
321
+ 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'];
336
+
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
+ );
201
347
  });
@@ -0,0 +1,54 @@
1
+ import { test } from 'brittle';
2
+ import sinon from 'sinon';
3
+ import b4a from 'b4a';
4
+ import { config } from '../../helpers/config.js';
5
+ import NetworkMessageRouter from '../../../src/core/network/protocols/legacy/NetworkMessageRouter.js';
6
+ import LegacyGetRequestHandler from '../../../src/core/network/protocols/legacy/handlers/LegacyGetRequestHandler.js';
7
+ import LegacyResponseHandler from '../../../src/core/network/protocols/legacy/handlers/LegacyResponseHandler.js';
8
+ import { NETWORK_MESSAGE_TYPES } from '../../../src/utils/constants.js';
9
+
10
+ const makeConnection = (sandbox) => ({
11
+ remotePublicKey: b4a.alloc(32, 0x01),
12
+ protocolSession: {
13
+ setLegacyAsPreferredProtocol: sandbox.stub()
14
+ },
15
+ end: sandbox.stub()
16
+ });
17
+
18
+ const makeRouterContext = (t) => {
19
+ const sandbox = sinon.createSandbox();
20
+ t.teardown(() => sandbox.restore());
21
+
22
+ const getHandler = sandbox.stub(LegacyGetRequestHandler.prototype, 'handle').resolves();
23
+ const responseHandler = sandbox.stub(LegacyResponseHandler.prototype, 'handle').resolves();
24
+ const router = new NetworkMessageRouter({}, { address: 'test-wallet' }, {}, {}, config);
25
+ const connection = makeConnection(sandbox);
26
+
27
+ return { connection, getHandler, responseHandler, router };
28
+ };
29
+
30
+ test('LegacyNetworkMessageRouter', async (t) => {
31
+ await t.test('routes legacy string GET messages', async (t) => {
32
+ const { connection, getHandler, responseHandler, router } = makeRouterContext(t);
33
+ const message = NETWORK_MESSAGE_TYPES.GET.VALIDATOR;
34
+
35
+ await router.route(message, connection);
36
+
37
+ t.ok(connection.protocolSession.setLegacyAsPreferredProtocol.calledOnce, 'should prefer legacy protocol');
38
+ t.ok(getHandler.calledOnce, 'should route GET message');
39
+ t.ok(responseHandler.notCalled, 'should not route response handler');
40
+ t.is(getHandler.firstCall.args[0], message, 'passes GET message through to handler');
41
+ });
42
+
43
+ await t.test('routes legacy object response messages', async (t) => {
44
+ const { connection, getHandler, responseHandler, router } = makeRouterContext(t);
45
+ const message = { op: NETWORK_MESSAGE_TYPES.RESPONSE.VALIDATOR };
46
+
47
+ await router.route(message, connection);
48
+
49
+ t.ok(connection.protocolSession.setLegacyAsPreferredProtocol.calledOnce, 'should prefer legacy protocol');
50
+ t.ok(responseHandler.calledOnce, 'should route response message');
51
+ t.ok(getHandler.notCalled, 'should not route GET handler');
52
+ t.is(responseHandler.firstCall.args[0], message, 'passes response message through to handler');
53
+ });
54
+ });
@@ -0,0 +1,127 @@
1
+ import { test } from 'brittle';
2
+ import sinon from 'sinon';
3
+ import b4a from 'b4a';
4
+
5
+ import NetworkWalletFactory from '../../../src/core/network/identity/NetworkWalletFactory.js';
6
+ import ProtocolSession from '../../../src/core/network/protocols/ProtocolSession.js';
7
+ import { ResultCode } from '../../../src/utils/constants.js';
8
+ import { config } from '../../helpers/config.js';
9
+ import { testKeyPair1 } from '../../fixtures/apply.fixtures.js';
10
+
11
+ function createWallet() {
12
+ const keyPair = {
13
+ publicKey: b4a.from(testKeyPair1.publicKey, 'hex'),
14
+ secretKey: b4a.from(testKeyPair1.secretKey, 'hex')
15
+ };
16
+ return NetworkWalletFactory.provide({
17
+ enableWallet: false,
18
+ keyPair,
19
+ networkPrefix: config.addressPrefix
20
+ });
21
+ }
22
+
23
+ function makeProtocol(sendStub) {
24
+ return {
25
+ send: sendStub ?? sinon.stub().resolves(ResultCode.OK),
26
+ sendAndForget: sinon.stub(),
27
+ decode: sinon.stub(),
28
+ close: sinon.stub()
29
+ };
30
+ }
31
+
32
+ test('ProtocolSession', (t) => {
33
+ t.teardown(() => sinon.restore());
34
+
35
+ test('probe sets preferred protocol to v1 on OK', async (t) => {
36
+ const v1Send = sinon.stub().resolves(ResultCode.OK);
37
+ const session = new ProtocolSession(
38
+ makeProtocol(),
39
+ makeProtocol(v1Send),
40
+ createWallet(),
41
+ config
42
+ );
43
+
44
+ await session.probe();
45
+ t.is(session.preferredProtocol, session.supportedProtocols.V1);
46
+ t.ok(v1Send.calledOnce);
47
+ });
48
+
49
+ test('probe sets preferred protocol to legacy on non-OK', async (t) => {
50
+ const v1Send = sinon.stub().resolves(ResultCode.TIMEOUT);
51
+ const session = new ProtocolSession(
52
+ makeProtocol(),
53
+ makeProtocol(v1Send),
54
+ createWallet(),
55
+ config
56
+ );
57
+
58
+ await session.probe();
59
+ t.is(session.preferredProtocol, session.supportedProtocols.LEGACY);
60
+ t.ok(v1Send.calledOnce);
61
+ });
62
+
63
+ test('probe sets preferred protocol to legacy on rejection', async (t) => {
64
+ const v1Send = sinon.stub().rejects(new Error('boom'));
65
+ const session = new ProtocolSession(
66
+ makeProtocol(),
67
+ makeProtocol(v1Send),
68
+ createWallet(),
69
+ config
70
+ );
71
+
72
+ await session.probe();
73
+ t.is(session.preferredProtocol, session.supportedProtocols.LEGACY);
74
+ t.ok(v1Send.calledOnce);
75
+ });
76
+
77
+ test('sendHealthCheck returns OK when preferred is v1', async (t) => {
78
+ const v1Send = sinon.stub().resolves(ResultCode.OK);
79
+ const session = new ProtocolSession(
80
+ makeProtocol(),
81
+ makeProtocol(v1Send),
82
+ createWallet(),
83
+ config
84
+ );
85
+
86
+ session.setV1AsPreferredProtocol();
87
+ const result = await session.sendHealthCheck();
88
+ t.is(result, ResultCode.OK);
89
+ t.ok(v1Send.calledOnce);
90
+ });
91
+
92
+ test('sendHealthCheck returns OK when preferred is legacy', async (t) => {
93
+ const session = new ProtocolSession(
94
+ makeProtocol(),
95
+ makeProtocol(),
96
+ createWallet(),
97
+ config
98
+ );
99
+
100
+ session.setLegacyAsPreferredProtocol();
101
+ const result = await session.sendHealthCheck();
102
+ t.is(result, ResultCode.OK);
103
+ });
104
+
105
+ test('sendHealthCheck returns UNSPECIFIED when not probed', async (t) => {
106
+ const session = new ProtocolSession(
107
+ makeProtocol(),
108
+ makeProtocol(),
109
+ createWallet(),
110
+ config
111
+ );
112
+
113
+ const result = await session.sendHealthCheck();
114
+ t.is(result, ResultCode.UNSPECIFIED);
115
+ });
116
+
117
+ test('isHealthCheckSupported throws when not probed', async (t) => {
118
+ const session = new ProtocolSession(
119
+ makeProtocol(),
120
+ makeProtocol(),
121
+ createWallet(),
122
+ config
123
+ );
124
+
125
+ await t.exception.all(() => session.isHealthCheckSupported());
126
+ });
127
+ });
@@ -2,8 +2,11 @@ import { default as test } from 'brittle';
2
2
 
3
3
  async function runNetworkModuleTests() {
4
4
  test.pause();
5
- await import('./ConnectionManager.test.js');
5
+ await import('./LegacyNetworkMessageRouter.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');
7
10
  test.resume();
8
11
  }
9
12