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
@@ -0,0 +1,794 @@
1
+ import test from 'brittle';
2
+ import b4a from 'b4a';
3
+
4
+ import V1BroadcastTransactionOperationHandler from '../../../../../src/core/network/protocols/v1/handlers/V1BroadcastTransactionOperationHandler.js';
5
+ import V1BroadcastTransactionRequest from '../../../../../src/core/network/protocols/v1/validators/V1BroadcastTransactionRequest.js';
6
+ import {V1ProtocolError} from '../../../../../src/core/network/protocols/v1/V1ProtocolError.js';
7
+
8
+ import {
9
+ ResultCode,
10
+ OperationType
11
+ } from '../../../../../src/utils/constants.js';
12
+
13
+ import * as PoolErrors from '../../../../../src/core/network/services/TransactionPoolService.js';
14
+ import * as CommitErrors from '../../../../../src/core/network/services/TransactionCommitService.js';
15
+
16
+
17
+ import PartialRoleAccessValidator from '../../../../../src/core/network/protocols/shared/validators/PartialRoleAccessValidator.js';
18
+ import PartialBootstrapDeploymentValidator from '../../../../../src/core/network/protocols/shared/validators/PartialBootstrapDeploymentValidator.js';
19
+ import PartialTransactionValidator from '../../../../../src/core/network/protocols/shared/validators/PartialTransactionValidator.js';
20
+ import PartialTransferValidator from '../../../../../src/core/network/protocols/shared/validators/PartialTransferValidator.js';
21
+ import { config as testConfig } from '../../../../helpers/config.js';
22
+ import { errorMessageIncludes } from '../../../../helpers/regexHelper.js';
23
+
24
+ const originalValidatorMethods = new Map([
25
+ [V1BroadcastTransactionRequest, V1BroadcastTransactionRequest.prototype.validate],
26
+ [PartialRoleAccessValidator, PartialRoleAccessValidator.prototype.validate],
27
+ [PartialBootstrapDeploymentValidator, PartialBootstrapDeploymentValidator.prototype.validate],
28
+ [PartialTransactionValidator, PartialTransactionValidator.prototype.validate],
29
+ [PartialTransferValidator, PartialTransferValidator.prototype.validate]
30
+ ]);
31
+
32
+ const VALID_ADDR = 'trac123z3gfpr2epjwww7ntm3m6ud2fhmq0tvts27p2f5mx3qkecsutlqfys769';
33
+ const VALID_TO_ADDR = 'trac1mqktwme8fvklrds4hlhfy6lhmsu9qgfn3c3kuhz7c5zwjt8rc3dqj9tx7h';
34
+ const VALID_PUB = b4a.alloc(33, 2);
35
+
36
+ const basePayload = () => ({
37
+ tx: b4a.alloc(32),
38
+ txv: b4a.alloc(32),
39
+ in: b4a.alloc(32),
40
+ is: b4a.alloc(64)
41
+ });
42
+
43
+ const roleAccessPayload = () => ({
44
+ ...basePayload(),
45
+ iw: b4a.alloc(32)
46
+ });
47
+
48
+ const transferPayload = () => ({
49
+ ...basePayload(),
50
+ to: VALID_TO_ADDR,
51
+ am: b4a.alloc(16)
52
+ });
53
+
54
+ const transactionPayload = () => ({
55
+ ...basePayload(),
56
+ iw: b4a.alloc(32),
57
+ ch: b4a.alloc(32),
58
+ bs: b4a.alloc(32),
59
+ mbs: b4a.alloc(32)
60
+ });
61
+
62
+ const bootstrapDeploymentPayload = () => ({
63
+ ...basePayload(),
64
+ bs: b4a.alloc(32),
65
+ ic: b4a.alloc(32)
66
+ });
67
+
68
+ function restoreValidatorMethods() {
69
+ for (const [Validator, originalValidate] of originalValidatorMethods.entries()) {
70
+ Validator.prototype.validate = originalValidate;
71
+ }
72
+ }
73
+
74
+ function setupHandler(t, overrides = {}) {
75
+ restoreValidatorMethods();
76
+ t.teardown(() => {
77
+ restoreValidatorMethods();
78
+ });
79
+
80
+ // Bypass all validation layers
81
+ [
82
+ V1BroadcastTransactionRequest,
83
+ PartialRoleAccessValidator,
84
+ PartialBootstrapDeploymentValidator,
85
+ PartialTransactionValidator,
86
+ PartialTransferValidator
87
+ ].forEach(v => v.prototype.validate = async () => true);
88
+
89
+ const state = overrides.state || {
90
+ allowedToValidate: async () => true,
91
+ isAdminAllowedToValidate: async () => true
92
+ };
93
+
94
+ const wallet = overrides.wallet || {
95
+ address: VALID_ADDR,
96
+ getPublicKey: () => VALID_PUB,
97
+ sign: () => b4a.alloc(64)
98
+ };
99
+
100
+ const txPool = overrides.txPool || {
101
+ validateEnqueue() {},
102
+ addTransaction() {}
103
+ };
104
+
105
+ const commitService = overrides.commit || {
106
+ registerPendingCommit: () =>
107
+ Promise.resolve({ proof: b4a.alloc(32), timestamp: 5 }),
108
+ rejectPendingCommit() {}
109
+ };
110
+ const config = overrides.config || testConfig;
111
+
112
+ const handler = new V1BroadcastTransactionOperationHandler(
113
+ state,
114
+ wallet,
115
+ { v1HandleRateLimit() {} },
116
+ txPool,
117
+ { resolvePendingRequest() {} },
118
+ commitService,
119
+ config
120
+ );
121
+
122
+ handler.displayError = () => {};
123
+ return handler;
124
+ }
125
+
126
+ function mockConn(assertFn) {
127
+ return {
128
+ remotePublicKey: b4a.alloc(32),
129
+ protocolSession: {
130
+ sendAndForget: assertFn || (() => {})
131
+ },
132
+ async flush() {},
133
+ end() {}
134
+ };
135
+ }
136
+
137
+ test('handleRequest: dispatches all supported operation types -> sends response', async t => {
138
+
139
+ const scenarios = [
140
+ { type: OperationType.ADD_WRITER, key: 'rao', data: roleAccessPayload() },
141
+ { type: OperationType.REMOVE_WRITER, key: 'rao', data: roleAccessPayload() },
142
+ { type: OperationType.ADMIN_RECOVERY, key: 'rao', data: roleAccessPayload() },
143
+ { type: OperationType.TRANSFER, key: 'tro', data: transferPayload() },
144
+ { type: OperationType.TX, key: 'txo', data: transactionPayload() },
145
+ { type: OperationType.BOOTSTRAP_DEPLOYMENT, key: 'bdo', data: bootstrapDeploymentPayload() }
146
+ ];
147
+
148
+ for (const s of scenarios) {
149
+ const handler = setupHandler(t);
150
+ handler.decodeApplyOperation = () => ({
151
+ type: s.type,
152
+ address: VALID_ADDR,
153
+ [s.key]: s.data
154
+ });
155
+
156
+ await handler.handleRequest(
157
+ { id: b4a.alloc(32), broadcast_transaction_request: { data: b4a.alloc(1) } },
158
+ mockConn()
159
+ );
160
+ }
161
+
162
+ t.pass();
163
+ });
164
+
165
+ test('dispatchTransaction: missing/invalid type -> throws invalid payload error', async t => {
166
+
167
+ const handler = setupHandler(t);
168
+
169
+ await t.exception(
170
+ async () => handler.dispatchTransaction(null),
171
+ errorMessageIncludes('Decoded transaction type is missing')
172
+ );
173
+
174
+ await t.exception(
175
+ async () => handler.dispatchTransaction({ type: 0 }),
176
+ errorMessageIncludes('Decoded transaction type is missing')
177
+ );
178
+
179
+ await t.exception(
180
+ async () => handler.dispatchTransaction({ type: 999 }),
181
+ errorMessageIncludes('Unsupported transaction type')
182
+ );
183
+ });
184
+
185
+ test('Commit service not configured', async t => {
186
+
187
+ const handler = setupHandler(t, { commit: null });
188
+
189
+ handler.decodeApplyOperation = () => ({
190
+ type: OperationType.TX,
191
+ address: VALID_ADDR,
192
+ txo: transactionPayload()
193
+ });
194
+
195
+ await handler.handleRequest(
196
+ { id: b4a.alloc(32), broadcast_transaction_request: { data: b4a.alloc(1) } },
197
+ mockConn(res => {
198
+ t.is(
199
+ res.broadcast_transaction_response.result,
200
+ ResultCode.INTERNAL_ERROR
201
+ );
202
+ })
203
+ );
204
+ });
205
+
206
+ test('TxPool validateEnqueue full', async t => {
207
+
208
+ const handler = setupHandler(t, {
209
+ txPool: {
210
+ validateEnqueue() {
211
+ throw new PoolErrors.TransactionPoolFullError();
212
+ }
213
+ }
214
+ });
215
+
216
+ handler.decodeApplyOperation = () => ({
217
+ type: OperationType.TX,
218
+ address: VALID_ADDR,
219
+ txo: transactionPayload()
220
+ });
221
+
222
+ await handler.handleRequest(
223
+ { id: b4a.alloc(32), broadcast_transaction_request: { data: b4a.alloc(1) } },
224
+ mockConn(res => {
225
+ t.is(res.broadcast_transaction_response.result, ResultCode.NODE_OVERLOADED);
226
+ })
227
+ );
228
+ });
229
+
230
+ test('Commit registration error mapping', async t => {
231
+
232
+ const errors = [
233
+ CommitErrors.PendingCommitInvalidTxHashError,
234
+ CommitErrors.PendingCommitAlreadyExistsError,
235
+ CommitErrors.PendingCommitBufferFullError
236
+ ];
237
+
238
+ for (const Err of errors) {
239
+
240
+ const handler = setupHandler(t, {
241
+ commit: {
242
+ registerPendingCommit() { throw new Err('x'); },
243
+ rejectPendingCommit() {}
244
+ }
245
+ });
246
+
247
+ handler.decodeApplyOperation = () => ({
248
+ type: OperationType.TX,
249
+ address: VALID_ADDR,
250
+ txo: transactionPayload()
251
+ });
252
+
253
+ await handler.handleRequest(
254
+ { id: b4a.alloc(32), broadcast_transaction_request: { data: b4a.alloc(1) } },
255
+ mockConn()
256
+ );
257
+ }
258
+
259
+ t.pass();
260
+ });
261
+
262
+ test('TxPool addTransaction error mapping', async t => {
263
+
264
+ const poolErrors = [
265
+ PoolErrors.TransactionPoolFullError,
266
+ PoolErrors.TransactionPoolAlreadyQueuedError,
267
+ PoolErrors.TransactionPoolInvalidIncomingDataError
268
+ ];
269
+
270
+ for (const Err of poolErrors) {
271
+
272
+ const handler = setupHandler(t, {
273
+ txPool: {
274
+ validateEnqueue() {},
275
+ addTransaction() { throw new Err('x'); }
276
+ }
277
+ });
278
+
279
+ handler.decodeApplyOperation = () => ({
280
+ type: OperationType.TX,
281
+ address: VALID_ADDR,
282
+ txo: transactionPayload()
283
+ });
284
+
285
+ await handler.handleRequest(
286
+ { id: b4a.alloc(32), broadcast_transaction_request: { data: b4a.alloc(1) } },
287
+ mockConn()
288
+ );
289
+ }
290
+
291
+ t.pass();
292
+ });
293
+
294
+ test('Pending commit rejection branches', async t => {
295
+
296
+ const errors = [
297
+ new PoolErrors.TransactionPoolProofUnavailableError('x', 7),
298
+ new PoolErrors.TransactionPoolMissingCommitReceiptError('x'),
299
+ new CommitErrors.PendingCommitTimeoutError('x')
300
+ ];
301
+
302
+ for (const err of errors) {
303
+
304
+ const handler = setupHandler(t, {
305
+ commit: {
306
+ registerPendingCommit() {
307
+ return Promise.reject(err);
308
+ },
309
+ rejectPendingCommit() {}
310
+ }
311
+ });
312
+
313
+ handler.decodeApplyOperation = () => ({
314
+ type: OperationType.TX,
315
+ address: VALID_ADDR,
316
+ txo: transactionPayload()
317
+ });
318
+
319
+ await handler.handleRequest(
320
+ { id: b4a.alloc(32), broadcast_transaction_request: { data: b4a.alloc(1) } },
321
+ mockConn()
322
+ );
323
+ }
324
+
325
+ t.pass();
326
+ });
327
+
328
+ test('Capability validation failure', async t => {
329
+
330
+ const handler = setupHandler(t, {
331
+ state: {
332
+ allowedToValidate: async () => false,
333
+ isAdminAllowedToValidate: async () => false
334
+ }
335
+ });
336
+
337
+ handler.decodeApplyOperation = () => ({
338
+ type: OperationType.TX,
339
+ address: VALID_ADDR,
340
+ txo: transactionPayload()
341
+ });
342
+
343
+ await handler.handleRequest(
344
+ { id: b4a.alloc(32), broadcast_transaction_request: { data: b4a.alloc(1) } },
345
+ mockConn(res => {
346
+ t.is(res.broadcast_transaction_response.result, ResultCode.NODE_HAS_NO_WRITE_ACCESS);
347
+ })
348
+ );
349
+ });
350
+
351
+ test('Response build failure branch', async t => {
352
+
353
+ const handler = setupHandler(t);
354
+ handler.decodeApplyOperation = () => ({
355
+ type: OperationType.TX,
356
+ address: VALID_ADDR,
357
+ txo: transactionPayload()
358
+ });
359
+
360
+ const conn = {
361
+ remotePublicKey: b4a.alloc(32),
362
+ protocolSession: {
363
+ sendAndForget() { throw new Error('fail'); }
364
+ },
365
+ end() {}
366
+ };
367
+
368
+ await handler.handleRequest(
369
+ { id: b4a.alloc(32), broadcast_transaction_request: { data: b4a.alloc(1) } },
370
+ conn
371
+ );
372
+
373
+ t.pass();
374
+ });
375
+
376
+ test('handleResponse: resolvePendingResponse throws -> delegates to handlePendingResponseError', async t => {
377
+
378
+ const handler = setupHandler(t);
379
+
380
+ handler.resolvePendingResponse = async () => {
381
+ throw new Error('boom');
382
+ };
383
+
384
+ handler.handlePendingResponseError = () => {};
385
+
386
+ await handler.handleResponse(
387
+ { id: b4a.alloc(32) },
388
+ { remotePublicKey: b4a.alloc(32), end() {} }
389
+ );
390
+
391
+ t.pass();
392
+ });
393
+
394
+ test('Sanitize removes null completion fields', async t => {
395
+
396
+ const handler = setupHandler(t);
397
+
398
+ const tx = {
399
+ type: OperationType.TX,
400
+ address: VALID_ADDR,
401
+ txo: {
402
+ ...transactionPayload(),
403
+ va: null,
404
+ vn: null,
405
+ vs: null
406
+ }
407
+ };
408
+
409
+ handler.decodeApplyOperation = () => tx;
410
+
411
+ await handler.handleRequest(
412
+ { id: b4a.alloc(32), broadcast_transaction_request: { data: b4a.alloc(1) } },
413
+ mockConn()
414
+ );
415
+
416
+ t.absent(tx.txo.va);
417
+ t.absent(tx.txo.vn);
418
+ t.absent(tx.txo.vs);
419
+ });
420
+
421
+ test('Proof unavailable without timestamp branch', async t => {
422
+
423
+ const handler = setupHandler(t, {
424
+ commit: {
425
+ registerPendingCommit() {
426
+ return Promise.reject(
427
+ new PoolErrors.TransactionPoolProofUnavailableError('x')
428
+ );
429
+ },
430
+ rejectPendingCommit() {}
431
+ }
432
+ });
433
+
434
+ handler.decodeApplyOperation = () => ({
435
+ type: OperationType.TX,
436
+ address: VALID_ADDR,
437
+ txo: transactionPayload()
438
+ });
439
+
440
+ await handler.handleRequest(
441
+ { id: b4a.alloc(32), broadcast_transaction_request: { data: b4a.alloc(1) } },
442
+ mockConn()
443
+ );
444
+
445
+ t.pass();
446
+ });
447
+
448
+ test('Proof unavailable timestamp <= 0 branch', async t => {
449
+
450
+ const handler = setupHandler(t, {
451
+ commit: {
452
+ registerPendingCommit() {
453
+ return Promise.reject(
454
+ new PoolErrors.TransactionPoolProofUnavailableError('x', 0)
455
+ );
456
+ },
457
+ rejectPendingCommit() {}
458
+ }
459
+ });
460
+
461
+ handler.decodeApplyOperation = () => ({
462
+ type: OperationType.TX,
463
+ address: VALID_ADDR,
464
+ txo: transactionPayload()
465
+ });
466
+
467
+ await handler.handleRequest(
468
+ { id: b4a.alloc(32), broadcast_transaction_request: { data: b4a.alloc(1) } },
469
+ mockConn()
470
+ );
471
+
472
+ t.pass();
473
+ });
474
+
475
+ test('Request validator failure mapping branch', async t => {
476
+
477
+ const handler = setupHandler(t);
478
+
479
+ V1BroadcastTransactionRequest.prototype.validate = async () => {
480
+ throw new Error('validation boom');
481
+ };
482
+
483
+ await handler.handleRequest(
484
+ { id: b4a.alloc(32), broadcast_transaction_request: { data: b4a.alloc(1) } },
485
+ mockConn()
486
+ );
487
+
488
+ t.pass();
489
+ });
490
+
491
+ test('handleRequest: flushes response before closing when protocol error requests disconnect', async t => {
492
+ const handler = setupHandler(t);
493
+ const events = [];
494
+
495
+ V1BroadcastTransactionRequest.prototype.validate = async () => {
496
+ throw new V1ProtocolError(ResultCode.INVALID_PAYLOAD, 'validation boom', true);
497
+ };
498
+
499
+ const connection = {
500
+ remotePublicKey: b4a.alloc(32),
501
+ protocolSession: {
502
+ sendAndForget() {
503
+ events.push('send');
504
+ }
505
+ },
506
+ async flush() {
507
+ events.push('flush');
508
+ return true;
509
+ },
510
+ end() {
511
+ events.push('end');
512
+ }
513
+ };
514
+
515
+ await handler.handleRequest(
516
+ { id: 'msg-flush-close', broadcast_transaction_request: { data: b4a.alloc(1) } },
517
+ connection
518
+ );
519
+
520
+ t.alike(events, ['send', 'flush', 'end']);
521
+ });
522
+
523
+ test('decodeApplyOperation failure branch', async t => {
524
+
525
+ const handler = setupHandler(t);
526
+
527
+ handler.decodeApplyOperation = () => {
528
+ throw new Error('decode fail');
529
+ };
530
+
531
+ await handler.handleRequest(
532
+ { id: b4a.alloc(32), broadcast_transaction_request: { data: b4a.alloc(1) } },
533
+ mockConn()
534
+ );
535
+
536
+ t.pass();
537
+ });
538
+
539
+ test('Response build internal failure branch', async t => {
540
+
541
+ const badWallet = {
542
+ address: VALID_ADDR,
543
+ getPublicKey: () => VALID_PUB,
544
+ sign: () => { throw new Error('sign fail'); }
545
+ };
546
+
547
+ const handler = new V1BroadcastTransactionOperationHandler(
548
+ {
549
+ allowedToValidate: async () => true,
550
+ isAdminAllowedToValidate: async () => true
551
+ },
552
+ badWallet,
553
+ { v1HandleRateLimit() {} },
554
+ { validateEnqueue() {}, addTransaction() {} },
555
+ { resolvePendingRequest() {} },
556
+ {
557
+ registerPendingCommit: () =>
558
+ Promise.resolve({ proof: b4a.alloc(32), timestamp: 1 }),
559
+ rejectPendingCommit() {}
560
+ },
561
+ testConfig
562
+ );
563
+
564
+ handler.displayError = () => {};
565
+
566
+ handler.decodeApplyOperation = () => ({
567
+ type: OperationType.TX,
568
+ address: VALID_ADDR,
569
+ txo: transactionPayload()
570
+ });
571
+
572
+ await handler.handleRequest(
573
+ { id: b4a.alloc(32), broadcast_transaction_request: { data: b4a.alloc(1) } },
574
+ mockConn()
575
+ );
576
+
577
+ t.pass();
578
+ });
579
+
580
+
581
+ test('Unsupported role access subtype', async t => {
582
+
583
+ const handler = setupHandler(t);
584
+
585
+ await t.exception(
586
+ async () => handler.dispatchTransaction({
587
+ type: 1234,
588
+ address: VALID_ADDR,
589
+ rao: roleAccessPayload(),
590
+ // unsupported operation type
591
+ }),
592
+ errorMessageIncludes('Unsupported transaction type')
593
+ );
594
+ });
595
+
596
+ test('Role access switch default branch', async t => {
597
+
598
+ const handler = setupHandler(t);
599
+
600
+ await t.exception(
601
+ async () => handler.dispatchTransaction({
602
+ type: OperationType.ADD_WRITER + 999, // still integer but not matched
603
+ address: VALID_ADDR,
604
+ rao: roleAccessPayload()
605
+ }),
606
+ errorMessageIncludes('Unsupported transaction type')
607
+ );
608
+ });
609
+
610
+ test('TransactionPoolMissingCommitReceiptError via receipt branch', async t => {
611
+
612
+ const handler = setupHandler(t, {
613
+ commit: {
614
+ registerPendingCommit() {
615
+ return Promise.reject(
616
+ new PoolErrors.TransactionPoolMissingCommitReceiptError('x')
617
+ );
618
+ },
619
+ rejectPendingCommit() {}
620
+ }
621
+ });
622
+
623
+ handler.decodeApplyOperation = () => ({
624
+ type: OperationType.TX,
625
+ address: VALID_ADDR,
626
+ txo: transactionPayload()
627
+ });
628
+
629
+ let capturedResultCode = null;
630
+ await handler.handleRequest(
631
+ { id: 'receipt-missing-id', broadcast_transaction_request: { data: b4a.alloc(1) } },
632
+ mockConn(res => {
633
+ capturedResultCode = res.broadcast_transaction_response.result;
634
+ })
635
+ );
636
+
637
+ t.is(capturedResultCode, ResultCode.TX_COMMITTED_RECEIPT_MISSING);
638
+ });
639
+
640
+ test('PendingCommitBufferFullError mapping branch', async t => {
641
+
642
+ const handler = setupHandler(t, {
643
+ commit: {
644
+ registerPendingCommit() {
645
+ throw new CommitErrors.PendingCommitBufferFullError('x');
646
+ },
647
+ rejectPendingCommit() {}
648
+ }
649
+ });
650
+
651
+ handler.decodeApplyOperation = () => ({
652
+ type: OperationType.TX,
653
+ address: VALID_ADDR,
654
+ txo: transactionPayload()
655
+ });
656
+
657
+ await handler.handleRequest(
658
+ { id: b4a.alloc(32), broadcast_transaction_request: { data: b4a.alloc(1) } },
659
+ mockConn()
660
+ );
661
+
662
+ t.pass();
663
+ });
664
+
665
+ test('handleResponse extractor real path', async t => {
666
+
667
+ const handler = setupHandler(t);
668
+
669
+ handler.resolvePendingResponse = async (msg, conn, validator, extractor) => {
670
+ const result = extractor({
671
+ broadcast_transaction_response: { result: 123 }
672
+ });
673
+ t.is(result, 123);
674
+ };
675
+
676
+ await handler.handleResponse(
677
+ { id: b4a.alloc(32) },
678
+ { remotePublicKey: b4a.alloc(32), end() {} }
679
+ );
680
+
681
+ t.pass();
682
+ });
683
+
684
+ test('getOperationPayloadKey null branch', async t => {
685
+
686
+ const handler = setupHandler(t);
687
+
688
+ handler.decodeApplyOperation = () => ({
689
+ type: 9999, // not recognized by isRoleAccess/isTransaction/etc
690
+ address: VALID_ADDR
691
+ });
692
+
693
+ await handler.handleRequest(
694
+ { id: b4a.alloc(32), broadcast_transaction_request: { data: b4a.alloc(1) } },
695
+ mockConn()
696
+ );
697
+
698
+ t.pass();
699
+ });
700
+
701
+ test('TransactionPoolInvalidIncomingDataError mapping', async t => {
702
+
703
+ const handler = setupHandler(t, {
704
+ txPool: {
705
+ validateEnqueue() {},
706
+ addTransaction() {
707
+ throw new PoolErrors.TransactionPoolInvalidIncomingDataError('x');
708
+ }
709
+ }
710
+ });
711
+
712
+ handler.decodeApplyOperation = () => ({
713
+ type: OperationType.TX,
714
+ address: VALID_ADDR,
715
+ txo: transactionPayload()
716
+ });
717
+
718
+ await handler.handleRequest(
719
+ { id: b4a.alloc(32), broadcast_transaction_request: { data: b4a.alloc(1) } },
720
+ mockConn(res => {
721
+ t.is(res.broadcast_transaction_response.result, ResultCode.INTERNAL_ERROR);
722
+ })
723
+ );
724
+ });
725
+
726
+ test('Unknown receipt error rethrow branch', async t => {
727
+
728
+ const handler = setupHandler(t, {
729
+ commit: {
730
+ registerPendingCommit() {
731
+ return Promise.reject(new Error('weird'));
732
+ },
733
+ rejectPendingCommit() {}
734
+ }
735
+ });
736
+
737
+ handler.decodeApplyOperation = () => ({
738
+ type: OperationType.TX,
739
+ address: VALID_ADDR,
740
+ txo: transactionPayload()
741
+ });
742
+
743
+ await handler.handleRequest(
744
+ { id: b4a.alloc(32), broadcast_transaction_request: { data: b4a.alloc(1) } },
745
+ mockConn()
746
+ );
747
+
748
+ t.pass();
749
+ });
750
+
751
+ test('validateEnqueue rethrow unknown error branch', async t => {
752
+
753
+ const handler = setupHandler(t, {
754
+ txPool: {
755
+ validateEnqueue() { throw new Error('boom'); }
756
+ }
757
+ });
758
+
759
+ handler.decodeApplyOperation = () => ({
760
+ type: OperationType.TX,
761
+ address: VALID_ADDR,
762
+ txo: transactionPayload()
763
+ });
764
+
765
+ await handler.handleRequest(
766
+ { id: b4a.alloc(32), broadcast_transaction_request: { data: b4a.alloc(1) } },
767
+ mockConn()
768
+ );
769
+
770
+ t.pass();
771
+ });
772
+
773
+ test('Capability OR branch admin true', async t => {
774
+
775
+ const handler = setupHandler(t, {
776
+ state: {
777
+ allowedToValidate: async () => false,
778
+ isAdminAllowedToValidate: async () => true
779
+ }
780
+ });
781
+
782
+ handler.decodeApplyOperation = () => ({
783
+ type: OperationType.TX,
784
+ address: VALID_ADDR,
785
+ txo: transactionPayload()
786
+ });
787
+
788
+ await handler.handleRequest(
789
+ { id: b4a.alloc(32), broadcast_transaction_request: { data: b4a.alloc(1) } },
790
+ mockConn()
791
+ );
792
+
793
+ t.pass();
794
+ });