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,49 @@
1
+ import { test } from 'brittle';
2
+
3
+ import { resultToValidatorAction, SENDER_ACTION } from '../../../../src/core/network/protocols/connectionPolicies.js';
4
+ import { ResultCode } from '../../../../src/utils/constants.js';
5
+
6
+ test('connectionPolicies maps OK to SUCCESS', t => {
7
+ t.is(
8
+ resultToValidatorAction(ResultCode.OK),
9
+ SENDER_ACTION.SUCCESS
10
+ );
11
+ });
12
+
13
+ test('connectionPolicies maps TX_ALREADY_PENDING to NO_ROTATE', t => {
14
+ t.is(
15
+ resultToValidatorAction(ResultCode.TX_ALREADY_PENDING),
16
+ SENDER_ACTION.NO_ROTATE
17
+ );
18
+ });
19
+
20
+ test('connectionPolicies maps TX_COMMITTED_RECEIPT_MISSING to ROTATE', t => {
21
+ t.is(
22
+ resultToValidatorAction(ResultCode.TX_COMMITTED_RECEIPT_MISSING),
23
+ SENDER_ACTION.ROTATE
24
+ );
25
+ });
26
+
27
+ test('connectionPolicies maps unknown result code to UNDEFINED', t => {
28
+ t.is(
29
+ resultToValidatorAction(999999),
30
+ SENDER_ACTION.UNDEFINED
31
+ );
32
+ });
33
+
34
+ test('connectionPolicies maps every ResultCode constant to a concrete sender action', t => {
35
+ const unassigned = [];
36
+
37
+ for (const [name, code] of Object.entries(ResultCode)) {
38
+ const action = resultToValidatorAction(code);
39
+ if (action === SENDER_ACTION.UNDEFINED) {
40
+ unassigned.push(`${name}(${code})`);
41
+ }
42
+ }
43
+
44
+ t.alike(
45
+ unassigned,
46
+ [],
47
+ `Unassigned ResultCode entries in policy: ${unassigned.join(', ')}`
48
+ );
49
+ });
@@ -0,0 +1,284 @@
1
+ import test from 'brittle';
2
+ import V1BaseOperationHandler from '../../../../../src/core/network/protocols/v1/handlers/V1BaseOperationHandler.js';
3
+ import {V1ProtocolError, V1UnexpectedError} from '../../../../../src/core/network/protocols/v1/V1ProtocolError.js';
4
+
5
+ class MockRateLimiter {
6
+ constructor() { this.called = false; }
7
+ v1HandleRateLimit(conn) {
8
+ this.called = true;
9
+ this.conn = conn;
10
+ }
11
+ }
12
+
13
+ class MockPendingReqService {
14
+ constructor() {
15
+ this.entries = {};
16
+ this.stopped = [];
17
+ this.resolved = [];
18
+ this.rejected = [];
19
+ this.shouldReject = true;
20
+ }
21
+ getPendingRequest(id) { return this.entries[id]; }
22
+ stopPendingRequestTimeout(id) { this.stopped.push(id); }
23
+ resolvePendingRequest(id, code) { this.resolved.push({ id, code }); }
24
+ rejectPendingRequest(id, err) {
25
+ if (this.shouldReject) this.rejected.push({ id, err });
26
+ return this.shouldReject;
27
+ }
28
+ }
29
+
30
+ const mockConfig = { disableRateLimit: false };
31
+
32
+ test('constructor: stores provided config -> config getter returns same reference', async (t) => {
33
+ const handler = new V1BaseOperationHandler(null, null, mockConfig);
34
+ t.is(handler.config, mockConfig, 'Should return the config passed in the constructor');
35
+ });
36
+
37
+ test('applyRateLimit: rate limit enabled -> calls rate limiter with connection', async (t) => {
38
+ const rateLimiter = new MockRateLimiter();
39
+ const handler = new V1BaseOperationHandler(rateLimiter, null, mockConfig);
40
+ const conn = { id: 1 };
41
+
42
+ handler.applyRateLimit(conn);
43
+
44
+ t.ok(rateLimiter.called, 'Should call the rate limiter when enabled');
45
+ t.is(rateLimiter.conn, conn, 'Should pass the connection to the rate limiter');
46
+ });
47
+
48
+ test('applyRateLimit: disableRateLimit=true -> skips rate limiter call', async (t) => {
49
+ const rateLimiter = new MockRateLimiter();
50
+ const handler = new V1BaseOperationHandler(rateLimiter, null, { disableRateLimit: true });
51
+
52
+ handler.applyRateLimit({});
53
+
54
+ t.absent(rateLimiter.called, 'Should NOT call the rate limiter when disableRateLimit is true');
55
+ });
56
+
57
+ test('resolvePendingResponse: pending request missing -> returns false', async (t) => {
58
+ const pendingReq = new MockPendingReqService();
59
+ const handler = new V1BaseOperationHandler(null, pendingReq, mockConfig);
60
+
61
+ const result = await handler.resolvePendingResponse({ id: 'msg-123' }, {}, {}, () => {}, {});
62
+
63
+ t.is(result, false, 'Should return false if the pending request does not exist');
64
+ });
65
+
66
+ test('resolvePendingResponse: valid pending response -> stops timeout and resolves request', async (t) => {
67
+ const pendingReq = new MockPendingReqService();
68
+ pendingReq.entries['msg-123'] = { id: 'msg-123' };
69
+
70
+ const handler = new V1BaseOperationHandler(null, pendingReq, mockConfig);
71
+
72
+ let validated = false;
73
+ const validatorMock = { async validate() { validated = true; } };
74
+ const extractCodeMock = () => 'SUCCESS';
75
+
76
+ const result = await handler.resolvePendingResponse(
77
+ { id: 'msg-123' },
78
+ {},
79
+ validatorMock,
80
+ extractCodeMock,
81
+ {}
82
+ );
83
+
84
+ t.is(result, true, 'Should return true after resolving the request');
85
+ t.is(pendingReq.stopped[0], 'msg-123', 'Should stop the timeout');
86
+ t.ok(validated, 'Should call validation');
87
+ t.is(pendingReq.resolved[0].code, 'SUCCESS', 'Should extract resultCode and resolve');
88
+ });
89
+
90
+ test('resolvePendingResponse: validator throws -> propagates validation error', async (t) => {
91
+ const pendingReq = new MockPendingReqService();
92
+ pendingReq.entries['msg-123'] = { id: 'msg-123' };
93
+
94
+ const handler = new V1BaseOperationHandler(null, pendingReq, mockConfig);
95
+
96
+ const validatorMock = {
97
+ async validate() { throw new Error('Validation Failed'); }
98
+ };
99
+
100
+ await t.exception(async () => {
101
+ await handler.resolvePendingResponse(
102
+ { id: 'msg-123' },
103
+ {},
104
+ validatorMock,
105
+ () => {},
106
+ {}
107
+ );
108
+ }, /Validation Failed/, 'Should propagate the validation error');
109
+ });
110
+
111
+ test('handlePendingResponseError: request already rejected -> does not close connection', async (t) => {
112
+ const pendingReq = new MockPendingReqService();
113
+ pendingReq.shouldReject = false;
114
+
115
+ const handler = new V1BaseOperationHandler(null, pendingReq, mockConfig);
116
+
117
+ let ended = false;
118
+ const connMock = { end: () => { ended = true; } };
119
+
120
+ handler.handlePendingResponseError('msg-123', connMock, new Error('test'), 'step');
121
+
122
+ t.absent(ended, 'Should NOT end the connection if the request was already rejected');
123
+ });
124
+
125
+ test('handlePendingResponseError: unknown native error -> maps to V1UnexpectedError', async (t) => {
126
+ const pendingReq = new MockPendingReqService();
127
+ const handler = new V1BaseOperationHandler(null, pendingReq, mockConfig);
128
+
129
+ // Bypass display logic for isolation
130
+ handler.displayError = () => {};
131
+
132
+ let ended = false;
133
+ const connMock = {
134
+ end: () => { ended = true; },
135
+ remotePublicKey: Buffer.alloc(32)
136
+ };
137
+
138
+ handler.handlePendingResponseError(
139
+ 'msg-123',
140
+ connMock,
141
+ new Error('Random native error'),
142
+ 'test-step'
143
+ );
144
+
145
+ t.is(pendingReq.rejected.length, 1, 'Should reject the pending request');
146
+
147
+ const capturedError = pendingReq.rejected[0].err;
148
+
149
+ t.ok(capturedError instanceof V1UnexpectedError, 'Should map to V1UnexpectedError');
150
+ t.is(capturedError.message, 'Random native error', 'Should preserve original message');
151
+ t.absent(ended, 'V1UnexpectedError should not close the connection by default');
152
+ });
153
+
154
+ test('handlePendingResponseError: protocol error with endConnection=true -> does not close connection directly', async (t) => {
155
+ const pendingReq = new MockPendingReqService();
156
+ const handler = new V1BaseOperationHandler(null, pendingReq, mockConfig);
157
+
158
+ handler.displayError = () => {};
159
+
160
+ let ended = false;
161
+ const connMock = {
162
+ end: () => { ended = true; },
163
+ remotePublicKey: Buffer.alloc(32)
164
+ };
165
+
166
+ const protocolError = new V1ProtocolError(999, 'FATAL_ERROR', true);
167
+
168
+ handler.handlePendingResponseError(
169
+ 'msg-123',
170
+ connMock,
171
+ protocolError,
172
+ 'test-step'
173
+ );
174
+
175
+ t.is(
176
+ pendingReq.rejected[0].err,
177
+ protocolError,
178
+ 'Should pass protocol error without wrapping'
179
+ );
180
+
181
+ t.absent(ended, 'Connection closing is delegated to ConnectionManager');
182
+ });
183
+
184
+ test('handlePendingResponseError: delegates logging -> calls displayError', async (t) => {
185
+ const pendingReq = new MockPendingReqService();
186
+ const handler = new V1BaseOperationHandler(null, pendingReq, { hrp: 'trac' });
187
+
188
+ let called = false;
189
+ handler.displayError = () => { called = true; };
190
+
191
+ handler.handlePendingResponseError(
192
+ 'msg-1',
193
+ { end() {}, remotePublicKey: Buffer.alloc(32) },
194
+ new Error('boom'),
195
+ 'step'
196
+ );
197
+
198
+ t.ok(called);
199
+ });
200
+
201
+ test('handlePendingResponseError: protocol-shaped error -> keeps original error instance', async (t) => {
202
+ const pendingReq = new MockPendingReqService();
203
+ const handler = new V1BaseOperationHandler(null, pendingReq, mockConfig);
204
+
205
+ const protocolError = new V1ProtocolError(123, 'protocol-shaped', false);
206
+
207
+ handler.displayError = () => {};
208
+
209
+ handler.handlePendingResponseError(
210
+ 'msg-1',
211
+ { end() {}, remotePublicKey: Buffer.alloc(32) },
212
+ protocolError,
213
+ 'step'
214
+ );
215
+
216
+ t.is(
217
+ pendingReq.rejected[0].err,
218
+ protocolError,
219
+ 'Should not wrap protocol error'
220
+ );
221
+ });
222
+
223
+ test('handlePendingResponseError: undefined error -> uses Unexpected error fallback', async (t) => {
224
+ const pendingReq = new MockPendingReqService();
225
+ const handler = new V1BaseOperationHandler(null, pendingReq, mockConfig);
226
+
227
+ handler.displayError = () => {};
228
+
229
+ handler.handlePendingResponseError(
230
+ 'msg-123',
231
+ { end() {}, remotePublicKey: Buffer.alloc(32) },
232
+ undefined,
233
+ 'step'
234
+ );
235
+
236
+ const captured = pendingReq.rejected[0].err;
237
+
238
+ t.ok(captured instanceof V1UnexpectedError);
239
+ t.is(captured.message, 'Unexpected error');
240
+ });
241
+
242
+ test('displayError: real implementation with invalid config -> throws', async (t) => {
243
+ const pendingReq = new MockPendingReqService();
244
+
245
+ const handler = new V1BaseOperationHandler(
246
+ null,
247
+ pendingReq,
248
+ {} // intentionally invalid config
249
+ );
250
+
251
+ const originalConsoleError = console.error;
252
+ console.error = () => {};
253
+
254
+ await t.exception(() => {
255
+ handler.displayError(
256
+ 'step',
257
+ Buffer.alloc(33, 1),
258
+ new Error('boom')
259
+ );
260
+ });
261
+
262
+ console.error = originalConsoleError;
263
+
264
+ t.pass();
265
+ });
266
+
267
+ test('handlePendingResponseError: primitive error value -> uses Unexpected error fallback', async (t) => {
268
+ const pendingReq = new MockPendingReqService();
269
+ const handler = new V1BaseOperationHandler(null, pendingReq, mockConfig);
270
+
271
+ handler.displayError = () => {};
272
+
273
+ handler.handlePendingResponseError(
274
+ 'msg-123',
275
+ { end() {}, remotePublicKey: Buffer.alloc(32) },
276
+ 'string error',
277
+ 'step'
278
+ );
279
+
280
+ const captured = pendingReq.rejected[0].err;
281
+
282
+ t.ok(captured instanceof V1UnexpectedError);
283
+ t.is(captured.message, 'Unexpected error');
284
+ });