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
@@ -1,356 +0,0 @@
1
- import test from 'brittle';
2
- import b4a from 'b4a';
3
- import PeerWallet from 'trac-wallet';
4
-
5
- import V1BaseOperation from '../../../../src/core/network/protocols/v1/validators/V1BaseOperation.js';
6
- import NetworkWalletFactory from '../../../../src/core/network/identity/NetworkWalletFactory.js';
7
- import NetworkMessageBuilder from '../../../../src/messages/network/v1/NetworkMessageBuilder.js';
8
- import {
9
- V1InvalidPayloadError,
10
- V1SignatureInvalidError,
11
- V1UnexpectedError
12
- } from '../../../../src/core/network/protocols/v1/V1ProtocolError.js';
13
- import {
14
- NetworkOperationType,
15
- ResultCode
16
- } from '../../../../src/utils/constants.js';
17
- import { testKeyPair1, testKeyPair2 } from '../../../fixtures/apply.fixtures.js';
18
- import { config } from '../../../helpers/config.js';
19
- import { errorMessageIncludes } from '../../../helpers/regexHelper.js';
20
-
21
- const createWallet = (fixture = testKeyPair1) => {
22
- const keyPair = {
23
- publicKey: b4a.from(fixture.publicKey, 'hex'),
24
- secretKey: b4a.from(fixture.secretKey, 'hex'),
25
- };
26
-
27
- return NetworkWalletFactory.provide({
28
- enableWallet: false,
29
- keyPair,
30
- networkPrefix: config.addressPrefix,
31
- });
32
- };
33
-
34
- const buildSignedPayload = async (wallet, type, options = {}) => {
35
- const builder = new NetworkMessageBuilder(wallet, config)
36
- .setType(type)
37
- .setId(`id-${type}-${Date.now()}-${Math.random()}`)
38
- .setTimestamp()
39
- .setCapabilities(['cap:a']);
40
-
41
- switch (type) {
42
- case NetworkOperationType.LIVENESS_REQUEST:
43
- break;
44
- case NetworkOperationType.LIVENESS_RESPONSE:
45
- builder.setResultCode(options.resultCode ?? ResultCode.OK);
46
- break;
47
- case NetworkOperationType.BROADCAST_TRANSACTION_REQUEST:
48
- builder.setData(options.data ?? b4a.from('abcd', 'hex'));
49
- break;
50
- case NetworkOperationType.BROADCAST_TRANSACTION_RESPONSE:
51
- builder
52
- .setResultCode(options.resultCode ?? ResultCode.OK)
53
- .setProof(options.proof ?? b4a.from('deadbeef', 'hex'))
54
- .setTimestampLedger(options.timestamp ?? Date.now());
55
- break;
56
- default:
57
- throw new Error(`Unsupported type in test helper: ${type}`);
58
- }
59
-
60
- await builder.buildPayload();
61
- return builder.getResult();
62
- };
63
-
64
- test('V1BaseOperation.validate throws "must be implemented" by default', async t => {
65
- const operation = new V1BaseOperation(config);
66
-
67
- await t.exception(
68
- async () => operation.validate({}, {}, {}),
69
- errorMessageIncludes('must be implemented')
70
- );
71
- });
72
-
73
- test('V1BaseOperation.isPayloadSchemaValid handles missing/invalid type cases', async t => {
74
- const operation = new V1BaseOperation(config);
75
-
76
- t.exception(
77
- () => operation.isPayloadSchemaValid(null),
78
- errorMessageIncludes('Payload or payload type is missing')
79
- );
80
-
81
- t.exception(
82
- () => operation.isPayloadSchemaValid({ type: null }),
83
- errorMessageIncludes('Payload or payload type is missing')
84
- );
85
-
86
- t.exception(
87
- () => operation.isPayloadSchemaValid({ type: 1.5 }),
88
- errorMessageIncludes('Operation type must be an integer')
89
- );
90
-
91
- t.exception(
92
- () => operation.isPayloadSchemaValid({ type: 0 }),
93
- errorMessageIncludes('Operation type is unspecified')
94
- );
95
-
96
- t.exception(
97
- () => operation.isPayloadSchemaValid({ type: 9999 }),
98
- errorMessageIncludes('Unknown operation type')
99
- );
100
- });
101
-
102
- test('V1BaseOperation.isPayloadSchemaValid accepts all supported message schemas', async t => {
103
- const operation = new V1BaseOperation(config);
104
- const wallet = createWallet();
105
-
106
- const payloads = [
107
- await buildSignedPayload(wallet, NetworkOperationType.LIVENESS_REQUEST),
108
- await buildSignedPayload(wallet, NetworkOperationType.LIVENESS_RESPONSE),
109
- await buildSignedPayload(wallet, NetworkOperationType.BROADCAST_TRANSACTION_REQUEST),
110
- await buildSignedPayload(wallet, NetworkOperationType.BROADCAST_TRANSACTION_RESPONSE),
111
- ];
112
-
113
- for (const payload of payloads) {
114
- operation.isPayloadSchemaValid(payload);
115
- }
116
-
117
- t.pass();
118
- });
119
-
120
- test('V1BaseOperation.validateSignature verifies valid signatures for all supported message types', async t => {
121
- const operation = new V1BaseOperation(config);
122
- const wallet = createWallet();
123
-
124
- const payloads = [
125
- await buildSignedPayload(wallet, NetworkOperationType.LIVENESS_REQUEST),
126
- await buildSignedPayload(wallet, NetworkOperationType.LIVENESS_RESPONSE),
127
- await buildSignedPayload(wallet, NetworkOperationType.BROADCAST_TRANSACTION_REQUEST),
128
- await buildSignedPayload(wallet, NetworkOperationType.BROADCAST_TRANSACTION_RESPONSE),
129
- ];
130
-
131
- for (const payload of payloads) {
132
- await operation.validateSignature(payload, wallet.publicKey);
133
- }
134
-
135
- t.pass();
136
- });
137
-
138
- test('V1BaseOperation.validateSignature throws V1SignatureInvalidError on wrong public key', async t => {
139
- const operation = new V1BaseOperation(config);
140
- const wallet = createWallet(testKeyPair1);
141
- const otherWallet = createWallet(testKeyPair2);
142
-
143
- const payload = await buildSignedPayload(wallet, NetworkOperationType.LIVENESS_REQUEST);
144
-
145
- await t.exception(
146
- async () => operation.validateSignature(payload, otherWallet.publicKey),
147
- errorMessageIncludes('signature verification failed')
148
- );
149
- });
150
-
151
- test('V1BaseOperation.validateSignature rethrows protocol-shaped build errors', async t => {
152
- const operation = new V1BaseOperation(config);
153
-
154
- const payload = {
155
- type: 0,
156
- id: 'id',
157
- timestamp: Date.now(),
158
- capabilities: [],
159
- };
160
-
161
- try {
162
- await operation.validateSignature(payload, b4a.alloc(32, 1));
163
- t.fail('expected validateSignature to throw');
164
- } catch (error) {
165
- t.ok(error instanceof V1InvalidPayloadError);
166
- t.is(error.resultCode, ResultCode.INVALID_PAYLOAD);
167
- t.ok(error.message.includes('Operation type is unspecified'));
168
- }
169
- });
170
-
171
- test('V1BaseOperation.validateSignature wraps non-protocol build errors as V1InvalidPayloadError', async t => {
172
- const operation = new V1BaseOperation(config);
173
-
174
- const payload = {
175
- type: NetworkOperationType.LIVENESS_REQUEST,
176
- id: 'id',
177
- timestamp: Date.now(),
178
- capabilities: [],
179
- };
180
-
181
- try {
182
- await operation.validateSignature(payload, b4a.alloc(32, 1));
183
- t.fail('expected validateSignature to throw');
184
- } catch (error) {
185
- t.ok(error instanceof V1InvalidPayloadError);
186
- t.is(error.resultCode, ResultCode.INVALID_PAYLOAD);
187
- t.ok(error.message.includes('Failed to build signature message'));
188
- }
189
- });
190
-
191
- test('V1BaseOperation.validateSignature throws V1InvalidPayloadError when hashing fails', async t => {
192
- const operation = new V1BaseOperation(config);
193
- const wallet = createWallet();
194
- const payload = await buildSignedPayload(wallet, NetworkOperationType.LIVENESS_REQUEST);
195
-
196
- const originalBlake3 = PeerWallet.blake3;
197
- PeerWallet.blake3 = async () => {
198
- throw new Error('hash fail');
199
- };
200
-
201
- t.teardown(() => {
202
- PeerWallet.blake3 = originalBlake3;
203
- });
204
-
205
- try {
206
- await operation.validateSignature(payload, wallet.publicKey);
207
- t.fail('expected validateSignature to throw');
208
- } catch (error) {
209
- t.ok(error instanceof V1InvalidPayloadError);
210
- t.ok(error.message.includes('Failed to hash signature message'));
211
- }
212
- });
213
-
214
- test('V1BaseOperation.validateSignature handles verify() throw as invalid signature', async t => {
215
- const operation = new V1BaseOperation(config);
216
- const wallet = createWallet();
217
- const payload = await buildSignedPayload(wallet, NetworkOperationType.LIVENESS_REQUEST);
218
-
219
- const originalVerify = PeerWallet.verify;
220
- PeerWallet.verify = () => {
221
- throw new Error('verify fail');
222
- };
223
-
224
- t.teardown(() => {
225
- PeerWallet.verify = originalVerify;
226
- });
227
-
228
- try {
229
- await operation.validateSignature(payload, wallet.publicKey);
230
- t.fail('expected validateSignature to throw');
231
- } catch (error) {
232
- t.ok(error instanceof V1SignatureInvalidError);
233
- t.is(error.resultCode, ResultCode.SIGNATURE_INVALID);
234
- }
235
- });
236
-
237
- test('V1BaseOperation.validateSignature enforces BROADCAST_TRANSACTION_RESPONSE proof/timestamp invariants', async t => {
238
- const operation = new V1BaseOperation(config);
239
- const wallet = createWallet();
240
-
241
- const validBase = await buildSignedPayload(wallet, NetworkOperationType.BROADCAST_TRANSACTION_RESPONSE);
242
-
243
- const cases = [
244
- {
245
- mutate: payload => {
246
- payload.broadcast_transaction_response.result = ResultCode.OK;
247
- payload.broadcast_transaction_response.proof = b4a.alloc(0);
248
- },
249
- match: 'Result code OK requires non-empty proof'
250
- },
251
- {
252
- mutate: payload => {
253
- payload.broadcast_transaction_response.result = ResultCode.TX_ACCEPTED_PROOF_UNAVAILABLE;
254
- payload.broadcast_transaction_response.proof = b4a.from('aa', 'hex');
255
- },
256
- match: 'TX_ACCEPTED_PROOF_UNAVAILABLE requires empty proof'
257
- },
258
- {
259
- mutate: payload => {
260
- payload.broadcast_transaction_response.result = ResultCode.TX_ACCEPTED_PROOF_UNAVAILABLE;
261
- payload.broadcast_transaction_response.proof = b4a.alloc(0);
262
- payload.broadcast_transaction_response.timestamp = 0;
263
- },
264
- match: 'TX_ACCEPTED_PROOF_UNAVAILABLE requires timestamp > 0'
265
- },
266
- {
267
- mutate: payload => {
268
- payload.broadcast_transaction_response.result = ResultCode.TIMEOUT;
269
- payload.broadcast_transaction_response.proof = b4a.from('aa', 'hex');
270
- payload.broadcast_transaction_response.timestamp = 0;
271
- },
272
- match: 'Non-OK result code requires empty proof'
273
- },
274
- {
275
- mutate: payload => {
276
- payload.broadcast_transaction_response.result = ResultCode.TIMEOUT;
277
- payload.broadcast_transaction_response.proof = b4a.alloc(0);
278
- payload.broadcast_transaction_response.timestamp = Date.now();
279
- },
280
- match: 'Non-OK result code requires timestamp to be 0'
281
- },
282
- ];
283
-
284
- for (const scenario of cases) {
285
- const payload = structuredClone(validBase);
286
- scenario.mutate(payload);
287
-
288
- await t.exception(
289
- async () => operation.validateSignature(payload, wallet.publicKey),
290
- errorMessageIncludes(scenario.match)
291
- );
292
- }
293
- });
294
-
295
- test('V1BaseOperation.validateSignature throws V1UnexpectedError for unknown operation type', async t => {
296
- const operation = new V1BaseOperation(config);
297
-
298
- const payload = {
299
- type: 9999,
300
- id: 'id',
301
- timestamp: Date.now(),
302
- capabilities: [],
303
- };
304
-
305
- await t.exception(
306
- async () => operation.validateSignature(payload, b4a.alloc(32, 1)),
307
- errorMessageIncludes('Unknown operation type')
308
- );
309
- });
310
-
311
- test('V1BaseOperation.validatePeerCorrectness validates response sender identity', t => {
312
- const operation = new V1BaseOperation(config);
313
- const remotePublicKey = b4a.alloc(32, 9);
314
-
315
- operation.validatePeerCorrectness(remotePublicKey, {
316
- requestedTo: b4a.toString(remotePublicKey, 'hex')
317
- });
318
-
319
- t.exception(
320
- () => operation.validatePeerCorrectness(remotePublicKey, { requestedTo: 'ff' }),
321
- errorMessageIncludes('Response sender mismatch')
322
- );
323
- });
324
-
325
- test('V1BaseOperation.validateResponseType supports expected mappings and rejects mismatches', t => {
326
- const operation = new V1BaseOperation(config);
327
-
328
- operation.validateResponseType(
329
- { type: NetworkOperationType.LIVENESS_RESPONSE },
330
- { id: '1', requestType: NetworkOperationType.LIVENESS_REQUEST }
331
- );
332
-
333
- operation.validateResponseType(
334
- { type: NetworkOperationType.BROADCAST_TRANSACTION_RESPONSE },
335
- { id: '2', requestType: NetworkOperationType.BROADCAST_TRANSACTION_REQUEST }
336
- );
337
-
338
- t.exception(
339
- () => operation.validateResponseType(
340
- { type: NetworkOperationType.BROADCAST_TRANSACTION_RESPONSE },
341
- { id: '3', requestType: NetworkOperationType.LIVENESS_REQUEST }
342
- ),
343
- errorMessageIncludes('Response type mismatch')
344
- );
345
-
346
- try {
347
- operation.validateResponseType(
348
- { type: NetworkOperationType.LIVENESS_RESPONSE },
349
- { id: '4', requestType: 9999 }
350
- );
351
- t.fail('expected validateResponseType to throw');
352
- } catch (error) {
353
- t.ok(error instanceof V1UnexpectedError);
354
- t.ok(error.message.includes('Unsupported pending request type'));
355
- }
356
- });
@@ -1,129 +0,0 @@
1
- import { test } from 'brittle';
2
- import b4a from 'b4a';
3
- import { TRAC_NETWORK_MSB_MAINNET_PREFIX } from 'trac-wallet/constants.js';
4
- import { config } from '../../../helpers/config.js';
5
- import { testKeyPair1, testKeyPair2 } from '../../../fixtures/apply.fixtures.js';
6
- import NetworkWalletFactory from '../../../../src/core/network/identity/NetworkWalletFactory.js';
7
- import V1BroadcastTransactionOperationHandler from '../../../../src/core/network/protocols/v1/handlers/V1BroadcastTransactionOperationHandler.js';
8
- import PartialTransactionValidator from '../../../../src/core/network/protocols/shared/validators/PartialTransactionValidator.js';
9
- import { TransactionPoolFullError } from '../../../../src/core/network/services/TransactionPoolService.js';
10
- import { V1NodeOverloadedError } from '../../../../src/core/network/protocols/v1/V1ProtocolError.js';
11
- import { applyStateMessageFactory } from '../../../../src/messages/state/applyStateMessageFactory.js';
12
-
13
- const createWallet = (keyPair) => {
14
- const normalizedKeyPair = {
15
- publicKey: b4a.from(keyPair.publicKey, 'hex'),
16
- secretKey: b4a.from(keyPair.secretKey, 'hex')
17
- };
18
-
19
- return NetworkWalletFactory.provide({
20
- enableWallet: false,
21
- keyPair: normalizedKeyPair,
22
- networkPrefix: TRAC_NETWORK_MSB_MAINNET_PREFIX
23
- });
24
- };
25
-
26
- test('V1BroadcastTransactionOperationHandler dispatchTransaction does not emit unhandledRejection when enqueue fails after pending commit registration', async t => {
27
- const originalValidate = PartialTransactionValidator.prototype.validate;
28
- PartialTransactionValidator.prototype.validate = async () => true;
29
-
30
- try {
31
- const txPoolService = {
32
- addTransaction() {
33
- throw new TransactionPoolFullError(1);
34
- }
35
- };
36
-
37
- const pendingRejectors = new Map();
38
- const transactionCommitService = {
39
- registerPendingCommit(txHash) {
40
- return new Promise((resolve, reject) => {
41
- pendingRejectors.set(txHash, reject);
42
- });
43
- },
44
- rejectPendingCommit(txHash, error) {
45
- const reject = pendingRejectors.get(txHash);
46
- if (!reject) return false;
47
- pendingRejectors.delete(txHash);
48
- reject(error);
49
- return true;
50
- }
51
- };
52
-
53
- const validatorWallet = createWallet(testKeyPair1);
54
- const requesterWallet = createWallet(testKeyPair2);
55
- const externalBootstrap = b4a.alloc(32, 0x11);
56
-
57
- const decodedTransaction = await applyStateMessageFactory(requesterWallet, config)
58
- .buildPartialTransactionOperationMessage(
59
- requesterWallet.address,
60
- b4a.alloc(32, 0x22),
61
- b4a.alloc(32, 0x33),
62
- b4a.alloc(32, 0x44),
63
- externalBootstrap,
64
- config.bootstrap,
65
- 'buffer'
66
- );
67
-
68
- const handler = new V1BroadcastTransactionOperationHandler(
69
- {},
70
- validatorWallet,
71
- { v1HandleRateLimit() {} },
72
- txPoolService,
73
- { getPendingRequest() { return null; } },
74
- transactionCommitService,
75
- config
76
- );
77
-
78
- let unhandled = null;
79
- const onUnhandled = (error) => {
80
- unhandled = error;
81
- };
82
-
83
- const detachUnhandled = (() => {
84
- const proc = globalThis.process;
85
- if (proc?.once && proc?.removeListener) {
86
- proc.once('unhandledRejection', onUnhandled);
87
- return () => proc.removeListener('unhandledRejection', onUnhandled);
88
- }
89
- if (typeof globalThis.addEventListener === 'function') {
90
- const listener = (event) => onUnhandled(event?.reason ?? event);
91
- globalThis.addEventListener('unhandledrejection', listener);
92
- return () => globalThis.removeEventListener('unhandledrejection', listener);
93
- }
94
- if ('onunhandledrejection' in globalThis) {
95
- const previous = globalThis.onunhandledrejection;
96
- globalThis.onunhandledrejection = (event) => {
97
- onUnhandled(event?.reason ?? event);
98
- if (typeof previous === 'function') {
99
- previous(event);
100
- }
101
- };
102
- return () => {
103
- globalThis.onunhandledrejection = previous;
104
- };
105
- }
106
- return null;
107
- })();
108
- try {
109
- let thrown = null;
110
- try {
111
- await handler.dispatchTransaction(decodedTransaction);
112
- } catch (error) {
113
- thrown = error;
114
- }
115
-
116
- t.ok(thrown, 'dispatchTransaction should throw when enqueue fails');
117
- t.ok(thrown instanceof V1NodeOverloadedError, 'should map tx pool full to V1NodeOverloadedError');
118
- await new Promise(resolve => setImmediate(resolve));
119
- } finally {
120
- if (detachUnhandled) {
121
- detachUnhandled();
122
- }
123
- }
124
-
125
- t.absent(unhandled, 'should not emit unhandledRejection');
126
- } finally {
127
- PartialTransactionValidator.prototype.validate = originalValidate;
128
- }
129
- });
@@ -1,53 +0,0 @@
1
- import test from 'brittle';
2
- import b4a from 'b4a';
3
-
4
- import V1BroadcastTransactionRequest from '../../../../src/core/network/protocols/v1/validators/V1BroadcastTransactionRequest.js';
5
- import { V1InvalidPayloadError } from '../../../../src/core/network/protocols/v1/V1ProtocolError.js';
6
- import { MAX_PARTIAL_TX_PAYLOAD_BYTE_SIZE } from '../../../../src/utils/constants.js';
7
- import { config } from '../../../helpers/config.js';
8
-
9
- test('V1BroadcastTransactionRequest.validate runs schema, size and signature validation', async t => {
10
- const validator = new V1BroadcastTransactionRequest(config);
11
- const calls = [];
12
-
13
- validator.isPayloadSchemaValid = () => calls.push('schema');
14
- validator.isDataPropertySizeValid = () => calls.push('size');
15
- validator.validateSignature = async () => calls.push('signature');
16
-
17
- const result = await validator.validate({}, b4a.alloc(32, 1));
18
-
19
- t.is(result, true);
20
- t.is(calls.length, 3);
21
- t.is(calls[0], 'schema');
22
- t.is(calls[1], 'size');
23
- t.is(calls[2], 'signature');
24
- });
25
-
26
- test('V1BroadcastTransactionRequest.isDataPropertySizeValid accepts max allowed payload size', t => {
27
- const validator = new V1BroadcastTransactionRequest(config);
28
- const payload = {
29
- broadcast_transaction_request: {
30
- data: b4a.alloc(MAX_PARTIAL_TX_PAYLOAD_BYTE_SIZE, 1)
31
- }
32
- };
33
-
34
- validator.isDataPropertySizeValid(payload);
35
- t.pass();
36
- });
37
-
38
- test('V1BroadcastTransactionRequest.isDataPropertySizeValid throws for oversized payload', t => {
39
- const validator = new V1BroadcastTransactionRequest(config);
40
- const payload = {
41
- broadcast_transaction_request: {
42
- data: b4a.alloc(MAX_PARTIAL_TX_PAYLOAD_BYTE_SIZE + 1, 1)
43
- }
44
- };
45
-
46
- try {
47
- validator.isDataPropertySizeValid(payload);
48
- t.fail('expected size validation to throw');
49
- } catch (error) {
50
- t.ok(error instanceof V1InvalidPayloadError);
51
- t.ok(error.message.includes('exceeds the maximum allowed byte size'));
52
- }
53
- });