trac-msb 0.2.9 → 0.2.11

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 (143) hide show
  1. package/CODE_OF_CONDUCT.md +128 -0
  2. package/README.md +33 -18
  3. package/docker-compose.yml +1 -0
  4. package/docs/trac_network_http_api.openapi.yaml +889 -0
  5. package/msb.mjs +4 -21
  6. package/package.json +16 -12
  7. package/proto/network/v1/enums/message_type.proto +16 -0
  8. package/proto/network/v1/enums/result_code.proto +84 -0
  9. package/proto/network/v1/messages/broadcast_transaction_request.proto +9 -0
  10. package/proto/network/v1/messages/broadcast_transaction_response.proto +13 -0
  11. package/proto/network/v1/messages/liveness_request.proto +8 -0
  12. package/proto/network/v1/messages/liveness_response.proto +11 -0
  13. package/proto/network/v1/network_message.proto +22 -0
  14. package/rpc/handlers.js +163 -90
  15. package/rpc/routes/v1.js +3 -1
  16. package/rpc/rpc_server.js +3 -3
  17. package/rpc/rpc_services.js +45 -31
  18. package/rpc/utils/helpers.js +82 -51
  19. package/scripts/generate-protobufs.js +37 -12
  20. package/src/config/args.js +46 -0
  21. package/src/config/config.js +99 -5
  22. package/src/config/env.js +86 -7
  23. package/src/core/network/Network.js +79 -46
  24. package/src/core/network/protocols/LegacyProtocol.js +21 -11
  25. package/src/core/network/protocols/NetworkMessages.js +38 -17
  26. package/src/core/network/protocols/ProtocolInterface.js +14 -2
  27. package/src/core/network/protocols/ProtocolSession.js +144 -17
  28. package/src/core/network/protocols/V1Protocol.js +37 -18
  29. package/src/core/network/protocols/connectionPolicies.js +88 -0
  30. package/src/core/network/protocols/legacy/NetworkMessageRouter.js +26 -20
  31. package/src/core/network/protocols/{shared/handlers/base/BaseOperationHandler.js → legacy/handlers/BaseStateOperationHandler.js} +25 -15
  32. package/src/core/network/protocols/legacy/handlers/{GetRequestHandler.js → LegacyGetRequestHandler.js} +6 -6
  33. package/src/core/network/protocols/legacy/handlers/LegacyResponseHandler.js +23 -0
  34. package/src/core/network/protocols/{shared/handlers/RoleOperationHandler.js → legacy/handlers/LegacyRoleOperationHandler.js} +20 -13
  35. package/src/core/network/protocols/{shared/handlers/SubnetworkOperationHandler.js → legacy/handlers/LegacySubnetworkOperationHandler.js} +29 -18
  36. package/src/core/network/protocols/{shared/handlers/TransferOperationHandler.js → legacy/handlers/LegacyTransferOperationHandler.js} +18 -12
  37. package/src/core/network/protocols/legacy/validators/base/BaseResponse.js +1 -1
  38. package/src/core/network/protocols/shared/errors/SharedValidatorRejectionError.js +27 -0
  39. package/src/core/network/protocols/shared/validators/{PartialBootstrapDeployment.js → PartialBootstrapDeploymentValidator.js} +9 -4
  40. package/src/core/network/protocols/shared/validators/{base/PartialOperation.js → PartialOperationValidator.js} +47 -25
  41. package/src/core/network/protocols/shared/validators/{PartialRoleAccess.js → PartialRoleAccessValidator.js} +51 -17
  42. package/src/core/network/protocols/shared/validators/{PartialTransaction.js → PartialTransactionValidator.js} +21 -7
  43. package/src/core/network/protocols/shared/validators/{PartialTransfer.js → PartialTransferValidator.js} +26 -9
  44. package/src/core/network/protocols/v1/NetworkMessageRouter.js +91 -7
  45. package/src/core/network/protocols/v1/V1ProtocolError.js +91 -0
  46. package/src/core/network/protocols/v1/handlers/V1BaseOperationHandler.js +65 -0
  47. package/src/core/network/protocols/v1/handlers/V1BroadcastTransactionOperationHandler.js +389 -0
  48. package/src/core/network/protocols/v1/handlers/V1LivenessOperationHandler.js +87 -0
  49. package/src/core/network/protocols/v1/validators/V1BaseOperation.js +211 -0
  50. package/src/core/network/protocols/v1/validators/V1BroadcastTransactionRequest.js +26 -0
  51. package/src/core/network/protocols/v1/validators/V1BroadcastTransactionResponse.js +276 -0
  52. package/src/core/network/protocols/v1/validators/V1LivenessRequest.js +15 -0
  53. package/src/core/network/protocols/v1/validators/V1LivenessResponse.js +17 -0
  54. package/src/core/network/protocols/v1/validators/V1ValidationSchema.js +210 -0
  55. package/src/core/network/services/ConnectionManager.js +147 -95
  56. package/src/core/network/services/MessageOrchestrator.js +152 -28
  57. package/src/core/network/services/PendingRequestService.js +172 -0
  58. package/src/core/network/services/TransactionCommitService.js +149 -0
  59. package/src/core/network/services/TransactionPoolService.js +133 -22
  60. package/src/core/network/services/TransactionRateLimiterService.js +57 -42
  61. package/src/core/network/services/ValidatorHealthCheckService.js +127 -0
  62. package/src/core/network/services/ValidatorObserverService.js +23 -32
  63. package/src/core/state/State.js +72 -22
  64. package/src/index.js +8 -5
  65. package/src/messages/network/v1/NetworkMessageBuilder.js +61 -81
  66. package/src/messages/network/v1/NetworkMessageDirector.js +16 -50
  67. package/src/messages/state/ApplyStateMessageBuilder.js +1 -1
  68. package/src/utils/Scheduler.js +0 -8
  69. package/src/utils/check.js +1 -1
  70. package/src/utils/constants.js +68 -19
  71. package/src/utils/deepEqualApplyPayload.js +40 -0
  72. package/src/utils/fileUtils.js +13 -0
  73. package/src/utils/helpers.js +10 -1
  74. package/src/utils/logger.js +25 -0
  75. package/src/utils/normalizers.js +38 -0
  76. package/src/utils/protobuf/networkV1.generated.cjs +2460 -0
  77. package/src/utils/protobuf/operationHelpers.js +24 -3
  78. package/src/utils/type.js +26 -0
  79. package/tests/acceptance/v1/account/account.test.mjs +8 -2
  80. package/tests/acceptance/v1/balance/balance.test.mjs +1 -2
  81. package/tests/acceptance/v1/broadcast-transaction/broadcast-transaction.test.mjs +26 -30
  82. package/tests/acceptance/v1/health/health.test.mjs +33 -0
  83. package/tests/acceptance/v1/rpc.test.mjs +3 -2
  84. package/tests/acceptance/v1/tx/tx.test.mjs +50 -17
  85. package/tests/acceptance/v1/tx-details/tx-details.test.mjs +60 -18
  86. package/tests/fixtures/check.fixtures.js +33 -32
  87. package/tests/fixtures/networkV1.fixtures.js +2 -27
  88. package/tests/fixtures/protobuf.fixtures.js +33 -32
  89. package/tests/helpers/StateNetworkFactory.js +2 -2
  90. package/tests/helpers/address.js +6 -0
  91. package/tests/helpers/autobaseTestHelpers.js +2 -1
  92. package/tests/helpers/config.js +2 -1
  93. package/tests/helpers/setupApplyTests.js +6 -10
  94. package/tests/helpers/transactionPayloads.mjs +2 -2
  95. package/tests/unit/messages/network/NetworkMessageBuilder.test.js +241 -81
  96. package/tests/unit/messages/network/NetworkMessageDirector.test.js +225 -81
  97. package/tests/unit/network/LegacyNetworkMessageRouter.test.js +54 -0
  98. package/tests/unit/network/ProtocolSession.test.js +127 -0
  99. package/tests/unit/network/networkModule.test.js +4 -1
  100. package/tests/unit/network/services/ConnectionManager.test.js +450 -0
  101. package/tests/unit/network/services/MessageOrchestrator.test.js +445 -0
  102. package/tests/unit/network/services/PendingRequestService.test.js +431 -0
  103. package/tests/unit/network/services/TransactionCommitService.test.js +246 -0
  104. package/tests/unit/network/services/TransactionPoolService.test.js +489 -0
  105. package/tests/unit/network/services/TransactionRateLimiterService.test.js +139 -0
  106. package/tests/unit/network/services/ValidatorHealthCheckService.test.js +115 -0
  107. package/tests/unit/network/services/services.test.js +17 -0
  108. package/tests/unit/network/utils/v1TestUtils.js +153 -0
  109. package/tests/unit/network/v1/NetworkMessageRouterV1.test.js +151 -0
  110. package/tests/unit/network/v1/V1BaseOperation.test.js +356 -0
  111. package/tests/unit/network/v1/V1BroadcastTransactionOperationHandler.test.js +129 -0
  112. package/tests/unit/network/v1/V1BroadcastTransactionRequest.test.js +53 -0
  113. package/tests/unit/network/v1/V1BroadcastTransactionResponse.test.js +512 -0
  114. package/tests/unit/network/v1/V1LivenessRequest.test.js +32 -0
  115. package/tests/unit/network/v1/V1LivenessResponse.test.js +45 -0
  116. package/tests/unit/network/v1/V1ResultCode.test.js +84 -0
  117. package/tests/unit/network/v1/V1ValidationSchema.test.js +13 -0
  118. package/tests/unit/network/v1/connectionPolicies.test.js +49 -0
  119. package/tests/unit/network/v1/handlers/V1BaseOperationHandler.test.js +284 -0
  120. package/tests/unit/network/v1/handlers/V1BroadcastTransactionOperationHandler.test.js +794 -0
  121. package/tests/unit/network/v1/handlers/V1LivenessOperationHandler.test.js +193 -0
  122. package/tests/unit/network/v1/v1.handlers.test.js +15 -0
  123. package/tests/unit/network/v1/v1.test.js +19 -0
  124. package/tests/unit/network/v1/v1ValidationSchema/broadcastTransactionRequest.test.js +119 -0
  125. package/tests/unit/network/v1/v1ValidationSchema/broadcastTransactionResponse.test.js +136 -0
  126. package/tests/unit/network/v1/v1ValidationSchema/common.test.js +308 -0
  127. package/tests/unit/network/v1/v1ValidationSchema/livenessRequest.test.js +90 -0
  128. package/tests/unit/network/v1/v1ValidationSchema/livenessResponse.test.js +133 -0
  129. package/tests/unit/unit.test.js +2 -2
  130. package/tests/unit/utils/deepEqualApplyPayload/deepEqualApplyPayload.test.js +102 -0
  131. package/tests/unit/utils/fileUtils/readAddressesFromWhitelistFile.test.js +4 -3
  132. package/tests/unit/utils/fileUtils/readBalanceMigrationFile.test.js +3 -2
  133. package/tests/unit/utils/migrationUtils/validateAddressFromIncomingFile.test.js +3 -2
  134. package/tests/unit/utils/protobuf/operationHelpers.test.js +2 -4
  135. package/tests/unit/utils/type/type.test.js +25 -0
  136. package/tests/unit/utils/utils.test.js +2 -0
  137. package/.github/workflows/acceptance-tests.yml +0 -42
  138. package/.github/workflows/publish.yml +0 -33
  139. package/.github/workflows/unit-tests.yml +0 -40
  140. package/proto/network.proto +0 -74
  141. package/src/core/network/protocols/legacy/handlers/ResponseHandler.js +0 -37
  142. package/src/utils/protobuf/network.cjs +0 -840
  143. package/tests/unit/network/ConnectionManager.test.js +0 -191
@@ -1,8 +1,6 @@
1
1
  import { test } from 'brittle';
2
2
  import b4a from 'b4a';
3
3
  import PeerWallet from 'trac-wallet';
4
- import { TRAC_NETWORK_MSB_MAINNET_PREFIX } from 'trac-wallet/constants.js';
5
-
6
4
  import NetworkWalletFactory from '../../../../src/core/network/identity/NetworkWalletFactory.js';
7
5
  import NetworkMessageDirector from '../../../../src/messages/network/v1/NetworkMessageDirector.js';
8
6
  import NetworkMessageBuilder from '../../../../src/messages/network/v1/NetworkMessageBuilder.js';
@@ -15,9 +13,11 @@ import {
15
13
  idToBuffer,
16
14
  timestampToBuffer
17
15
  } from '../../../../src/utils/buffer.js';
18
- import { addressToBuffer } from '../../../../src/core/state/utils/address.js';
19
16
  import { config } from '../../../helpers/config.js';
17
+ import { asAddress } from '../../../helpers/address.js';
20
18
  import { testKeyPair1 } from '../../../fixtures/apply.fixtures.js';
19
+ import { v7 as uuidv7 } from 'uuid';
20
+ import { errorMessageIncludes } from '../../../helpers/regexHelper.js';
21
21
 
22
22
  function createWallet() {
23
23
  const keyPair = {
@@ -27,7 +27,7 @@ function createWallet() {
27
27
  return NetworkWalletFactory.provide({
28
28
  enableWallet: false,
29
29
  keyPair,
30
- networkPrefix: TRAC_NETWORK_MSB_MAINNET_PREFIX
30
+ networkPrefix: config.addressPrefix
31
31
  });
32
32
  }
33
33
 
@@ -35,64 +35,15 @@ function uniqueResultCodes() {
35
35
  return [...new Set(Object.values(NetworkResultCode))].sort((a, b) => a - b);
36
36
  }
37
37
 
38
- test('NetworkMessageDirector builds validator connection request and verifies signature', async t => {
39
- const wallet = createWallet();
40
- const director = new NetworkMessageDirector(new NetworkMessageBuilder(wallet, config));
41
-
42
- const id = '1';
43
- const caps = ['cap:b', 'cap:a'];
44
-
45
- const payload = await director.buildValidatorConnectionRequest(id, wallet.address, caps);
46
- t.is(payload.type, NetworkOperationType.VALIDATOR_CONNECTION_REQUEST);
47
- t.is(payload.id, id);
48
- t.alike(payload.capabilities, caps);
49
-
50
- const msg = createMessage(
51
- payload.type,
52
- idToBuffer(payload.id),
53
- timestampToBuffer(payload.timestamp),
54
- addressToBuffer(wallet.address, config.addressPrefix),
55
- payload.validator_connection_request.nonce,
56
- encodeCapabilities(caps)
57
- );
58
- const hash = await PeerWallet.blake3(msg);
59
- t.ok(wallet.verify(payload.validator_connection_request.signature, hash, wallet.publicKey));
60
- });
61
-
62
- test('NetworkMessageDirector builds liveness request and verifies signature', async t => {
63
- const wallet = createWallet();
64
- const director = new NetworkMessageDirector(new NetworkMessageBuilder(wallet, config));
65
-
66
- const id = '1';
67
- const caps = ['cap:b', 'cap:a'];
68
- const data = b4a.from('ping', 'utf8');
69
-
70
- const payload = await director.buildLivenessRequest(id, data, caps);
71
- t.is(payload.type, NetworkOperationType.LIVENESS_REQUEST);
72
- t.is(payload.id, id);
73
- t.alike(payload.capabilities, caps);
74
-
75
- const msg = createMessage(
76
- payload.type,
77
- idToBuffer(payload.id),
78
- timestampToBuffer(payload.timestamp),
79
- payload.liveness_request.nonce,
80
- encodeCapabilities(caps)
81
- );
82
- const hash = await PeerWallet.blake3(msg);
83
- t.ok(wallet.verify(payload.liveness_request.signature, hash, wallet.publicKey));
84
- });
85
-
86
38
  test('NetworkMessageDirector iterates liveness response ResultCode values', async t => {
87
39
  const wallet = createWallet();
88
40
  const director = new NetworkMessageDirector(new NetworkMessageBuilder(wallet, config));
89
41
 
90
- const id = '1';
42
+ const id = uuidv7();
91
43
  const caps = ['cap:b', 'cap:a'];
92
- const data = b4a.from('ping', 'utf8');
93
44
 
94
45
  for (const code of uniqueResultCodes()) {
95
- const payload = await director.buildLivenessResponse(id, data, caps, code);
46
+ const payload = await director.buildLivenessResponse(id, caps, code);
96
47
  t.is(payload.type, NetworkOperationType.LIVENESS_RESPONSE);
97
48
  t.is(payload.liveness_response.result, code);
98
49
 
@@ -116,7 +67,7 @@ test('NetworkMessageDirector builds broadcast transaction request and verifies s
116
67
  const wallet = createWallet();
117
68
  const director = new NetworkMessageDirector(new NetworkMessageBuilder(wallet, config));
118
69
 
119
- const id = '1';
70
+ const id = uuidv7();
120
71
  const data = b4a.from('deadbeef', 'hex');
121
72
  const caps = ['cap:b', 'cap:a'];
122
73
 
@@ -145,19 +96,36 @@ test('NetworkMessageDirector iterates broadcast transaction response ResultCode
145
96
  const wallet = createWallet();
146
97
  const director = new NetworkMessageDirector(new NetworkMessageBuilder(wallet, config));
147
98
 
148
- const id = '1';
99
+ const id = uuidv7();
149
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);
150
104
 
151
105
  for (const code of uniqueResultCodes()) {
152
- 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
+ );
153
117
  t.is(payload.type, NetworkOperationType.BROADCAST_TRANSACTION_RESPONSE);
154
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);
155
121
 
156
122
  const msg = createMessage(
157
123
  payload.type,
158
124
  idToBuffer(payload.id),
159
125
  timestampToBuffer(payload.timestamp),
160
126
  payload.broadcast_transaction_response.nonce,
127
+ responseProof,
128
+ timestampToBuffer(responseTimestamp),
161
129
  safeWriteUInt32BE(code, 0),
162
130
  encodeCapabilities(caps)
163
131
  );
@@ -166,38 +134,214 @@ test('NetworkMessageDirector iterates broadcast transaction response ResultCode
166
134
 
167
135
  const decoded = decodeV1networkOperation(encodeV1networkOperation(payload));
168
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);
169
139
  }
170
140
  });
171
141
 
172
- test('NetworkMessageDirector iterates validator connection response ResultCode values', async t => {
142
+ test('NetworkMessageDirector builds broadcast transaction response with proof and timestamp', async t => {
173
143
  const wallet = createWallet();
174
144
  const director = new NetworkMessageDirector(new NetworkMessageBuilder(wallet, config));
175
145
 
176
- const id = '1';
146
+ const id = uuidv7();
177
147
  const caps = ['cap:b', 'cap:a'];
178
- const otherAddress =
179
- 'trac1xm76l9qaujh7vqktk8302mw9sfrxau3l45w62hqfl4kasswt6yts0autkh';
148
+ const proof = b4a.from('deadbeef', 'hex');
149
+ const timestamp = Date.now();
180
150
 
181
- for (const code of uniqueResultCodes()) {
182
- 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(
183
190
  id,
184
- otherAddress,
185
191
  caps,
186
- code
187
- );
188
- t.is(payload.type, NetworkOperationType.VALIDATOR_CONNECTION_RESPONSE);
189
- 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
+ });
190
198
 
191
- const msg = createMessage(
192
- payload.type,
193
- idToBuffer(payload.id),
194
- timestampToBuffer(payload.timestamp),
195
- addressToBuffer(otherAddress, config.addressPrefix),
196
- payload.validator_connection_response.nonce,
197
- safeWriteUInt32BE(code, 0),
198
- encodeCapabilities(caps)
199
- );
200
- const hash = await PeerWallet.blake3(msg);
201
- t.ok(wallet.verify(payload.validator_connection_response.signature, hash, wallet.publicKey));
202
- }
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
+ );
203
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