trac-msb 0.2.13 → 0.2.15
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.
- package/.github/workflows/acceptance-tests.yml +38 -0
- package/.github/workflows/lint-pr-title.yml +26 -0
- package/.github/workflows/publish.yml +33 -0
- package/.github/workflows/unit-tests.yml +34 -0
- package/package.json +7 -12
- package/proto/network.proto +74 -0
- package/rpc/rpc_services.js +4 -22
- package/scripts/generate-protobufs.js +12 -37
- package/src/config/config.js +9 -26
- package/src/config/env.js +17 -27
- package/src/core/network/Network.js +36 -73
- package/src/core/network/protocols/LegacyProtocol.js +11 -21
- package/src/core/network/protocols/NetworkMessages.js +17 -38
- package/src/core/network/protocols/ProtocolInterface.js +2 -14
- package/src/core/network/protocols/ProtocolSession.js +17 -144
- package/src/core/network/protocols/V1Protocol.js +18 -37
- package/src/core/network/protocols/legacy/NetworkMessageRouter.js +19 -25
- package/src/core/network/protocols/legacy/handlers/{LegacyGetRequestHandler.js → GetRequestHandler.js} +6 -6
- package/src/core/network/protocols/legacy/handlers/ResponseHandler.js +37 -0
- package/src/core/network/protocols/{legacy/handlers/LegacyRoleOperationHandler.js → shared/handlers/RoleOperationHandler.js} +11 -18
- package/src/core/network/protocols/{legacy/handlers/LegacySubnetworkOperationHandler.js → shared/handlers/SubnetworkOperationHandler.js} +17 -28
- package/src/core/network/protocols/{legacy/handlers/LegacyTransferOperationHandler.js → shared/handlers/TransferOperationHandler.js} +11 -17
- package/src/core/network/protocols/{legacy/handlers/BaseStateOperationHandler.js → shared/handlers/base/BaseOperationHandler.js} +12 -23
- package/src/core/network/protocols/shared/validators/{PartialBootstrapDeploymentValidator.js → PartialBootstrapDeployment.js} +4 -9
- package/src/core/network/protocols/shared/validators/{PartialRoleAccessValidator.js → PartialRoleAccess.js} +17 -51
- package/src/core/network/protocols/shared/validators/{PartialTransactionValidator.js → PartialTransaction.js} +7 -21
- package/src/core/network/protocols/shared/validators/{PartialTransferValidator.js → PartialTransfer.js} +9 -26
- package/src/core/network/protocols/shared/validators/{PartialOperationValidator.js → base/PartialOperation.js} +25 -47
- package/src/core/network/protocols/v1/NetworkMessageRouter.js +7 -91
- package/src/core/network/services/ConnectionManager.js +94 -146
- package/src/core/network/services/MessageOrchestrator.js +27 -151
- package/src/core/network/services/TransactionPoolService.js +18 -129
- package/src/core/network/services/TransactionRateLimiterService.js +34 -52
- package/src/core/network/services/ValidatorObserverService.js +26 -18
- package/src/core/state/State.js +19 -70
- package/src/index.js +8 -6
- package/src/messages/network/v1/NetworkMessageBuilder.js +79 -59
- package/src/messages/network/v1/NetworkMessageDirector.js +50 -16
- package/src/utils/Scheduler.js +8 -0
- package/src/utils/constants.js +5 -71
- package/src/utils/helpers.js +1 -10
- package/src/utils/normalizers.js +0 -38
- package/src/utils/protobuf/network.cjs +840 -0
- package/src/utils/protobuf/operationHelpers.js +3 -24
- package/tests/acceptance/v1/account/account.test.mjs +2 -8
- package/tests/acceptance/v1/tx/tx.test.mjs +1 -23
- package/tests/acceptance/v1/tx-details/tx-details.test.mjs +6 -34
- package/tests/fixtures/assembleMessage.fixtures.js +8 -7
- package/tests/fixtures/networkV1.fixtures.js +28 -2
- package/tests/helpers/autobaseTestHelpers.js +5 -2
- package/tests/helpers/createTestSignature.js +3 -2
- package/tests/helpers/transactionPayloads.mjs +2 -2
- package/tests/unit/messages/network/NetworkMessageBuilder.test.js +79 -239
- package/tests/unit/messages/network/NetworkMessageDirector.test.js +77 -223
- package/tests/unit/messages/state/applyStateMessageBuilder.complete.test.js +5 -1
- package/tests/unit/messages/state/applyStateMessageBuilder.partial.test.js +5 -1
- package/tests/unit/network/ConnectionManager.test.js +191 -0
- package/tests/unit/network/networkModule.test.js +1 -4
- package/tests/unit/unit.test.js +2 -2
- package/tests/unit/utils/fileUtils/readAddressesFromWhitelistFile.test.js +2 -2
- package/tests/unit/utils/fileUtils/readBalanceMigrationFile.test.js +2 -2
- package/tests/unit/utils/protobuf/operationHelpers.test.js +4 -2
- package/tests/unit/utils/utils.test.js +0 -1
- package/proto/network/v1/enums/message_type.proto +0 -16
- package/proto/network/v1/enums/result_code.proto +0 -84
- package/proto/network/v1/messages/broadcast_transaction_request.proto +0 -9
- package/proto/network/v1/messages/broadcast_transaction_response.proto +0 -13
- package/proto/network/v1/messages/liveness_request.proto +0 -8
- package/proto/network/v1/messages/liveness_response.proto +0 -11
- package/proto/network/v1/network_message.proto +0 -22
- package/src/core/network/protocols/connectionPolicies.js +0 -88
- package/src/core/network/protocols/legacy/handlers/LegacyResponseHandler.js +0 -23
- package/src/core/network/protocols/shared/errors/SharedValidatorRejectionError.js +0 -27
- package/src/core/network/protocols/v1/V1ProtocolError.js +0 -91
- package/src/core/network/protocols/v1/handlers/V1BaseOperationHandler.js +0 -65
- package/src/core/network/protocols/v1/handlers/V1BroadcastTransactionOperationHandler.js +0 -389
- package/src/core/network/protocols/v1/handlers/V1LivenessOperationHandler.js +0 -87
- package/src/core/network/protocols/v1/validators/V1BaseOperation.js +0 -211
- package/src/core/network/protocols/v1/validators/V1BroadcastTransactionRequest.js +0 -26
- package/src/core/network/protocols/v1/validators/V1BroadcastTransactionResponse.js +0 -276
- package/src/core/network/protocols/v1/validators/V1LivenessRequest.js +0 -15
- package/src/core/network/protocols/v1/validators/V1LivenessResponse.js +0 -17
- package/src/core/network/protocols/v1/validators/V1ValidationSchema.js +0 -210
- package/src/core/network/services/PendingRequestService.js +0 -172
- package/src/core/network/services/TransactionCommitService.js +0 -149
- package/src/core/network/services/ValidatorHealthCheckService.js +0 -127
- package/src/utils/deepEqualApplyPayload.js +0 -40
- package/src/utils/logger.js +0 -25
- package/src/utils/protobuf/networkV1.generated.cjs +0 -2460
- package/tests/unit/network/LegacyNetworkMessageRouter.test.js +0 -54
- package/tests/unit/network/ProtocolSession.test.js +0 -127
- package/tests/unit/network/services/ConnectionManager.test.js +0 -450
- package/tests/unit/network/services/MessageOrchestrator.test.js +0 -445
- package/tests/unit/network/services/PendingRequestService.test.js +0 -431
- package/tests/unit/network/services/TransactionCommitService.test.js +0 -246
- package/tests/unit/network/services/TransactionPoolService.test.js +0 -489
- package/tests/unit/network/services/TransactionRateLimiterService.test.js +0 -139
- package/tests/unit/network/services/ValidatorHealthCheckService.test.js +0 -115
- package/tests/unit/network/services/services.test.js +0 -17
- package/tests/unit/network/utils/v1TestUtils.js +0 -153
- package/tests/unit/network/v1/NetworkMessageRouterV1.test.js +0 -151
- package/tests/unit/network/v1/V1BaseOperation.test.js +0 -356
- package/tests/unit/network/v1/V1BroadcastTransactionOperationHandler.test.js +0 -129
- package/tests/unit/network/v1/V1BroadcastTransactionRequest.test.js +0 -53
- package/tests/unit/network/v1/V1BroadcastTransactionResponse.test.js +0 -512
- package/tests/unit/network/v1/V1LivenessRequest.test.js +0 -32
- package/tests/unit/network/v1/V1LivenessResponse.test.js +0 -45
- package/tests/unit/network/v1/V1ResultCode.test.js +0 -84
- package/tests/unit/network/v1/V1ValidationSchema.test.js +0 -13
- package/tests/unit/network/v1/connectionPolicies.test.js +0 -49
- package/tests/unit/network/v1/handlers/V1BaseOperationHandler.test.js +0 -284
- package/tests/unit/network/v1/handlers/V1BroadcastTransactionOperationHandler.test.js +0 -794
- package/tests/unit/network/v1/handlers/V1LivenessOperationHandler.test.js +0 -193
- package/tests/unit/network/v1/v1.handlers.test.js +0 -15
- package/tests/unit/network/v1/v1.test.js +0 -19
- package/tests/unit/network/v1/v1ValidationSchema/broadcastTransactionRequest.test.js +0 -119
- package/tests/unit/network/v1/v1ValidationSchema/broadcastTransactionResponse.test.js +0 -136
- package/tests/unit/network/v1/v1ValidationSchema/common.test.js +0 -308
- package/tests/unit/network/v1/v1ValidationSchema/livenessRequest.test.js +0 -90
- package/tests/unit/network/v1/v1ValidationSchema/livenessResponse.test.js +0 -133
- package/tests/unit/utils/deepEqualApplyPayload/deepEqualApplyPayload.test.js +0 -102
|
@@ -1,431 +0,0 @@
|
|
|
1
|
-
import { test } from 'brittle';
|
|
2
|
-
import b4a from 'b4a';
|
|
3
|
-
import { v7 as uuidv7 } from 'uuid';
|
|
4
|
-
import sinon from 'sinon';
|
|
5
|
-
|
|
6
|
-
import PendingRequestService from '../../../../src/core/network/services/PendingRequestService.js';
|
|
7
|
-
import NetworkWalletFactory from '../../../../src/core/network/identity/NetworkWalletFactory.js';
|
|
8
|
-
import NetworkMessageBuilder from '../../../../src/messages/network/v1/NetworkMessageBuilder.js';
|
|
9
|
-
import { V1UnexpectedError } from '../../../../src/core/network/protocols/v1/V1ProtocolError.js';
|
|
10
|
-
import { NetworkOperationType, ResultCode } from '../../../../src/utils/constants.js';
|
|
11
|
-
import { errorMessageIncludes } from '../../../helpers/regexHelper.js';
|
|
12
|
-
import { config } from '../../../helpers/config.js';
|
|
13
|
-
import { testKeyPair1, testKeyPair2 } from '../../../fixtures/apply.fixtures.js';
|
|
14
|
-
import SharedValidatorRejectionError from '../../../../src/core/network/protocols/shared/errors/SharedValidatorRejectionError.js';
|
|
15
|
-
|
|
16
|
-
const validPeerA = testKeyPair1.publicKey;
|
|
17
|
-
const validPeerB = testKeyPair2.publicKey;
|
|
18
|
-
|
|
19
|
-
async function buildV1Request({ id = uuidv7() } = {}) {
|
|
20
|
-
const wallet = createWallet();
|
|
21
|
-
const builder = new NetworkMessageBuilder(wallet, config);
|
|
22
|
-
await builder
|
|
23
|
-
.setType(NetworkOperationType.LIVENESS_REQUEST)
|
|
24
|
-
.setId(id)
|
|
25
|
-
.setTimestamp()
|
|
26
|
-
.setCapabilities([])
|
|
27
|
-
.buildPayload();
|
|
28
|
-
return builder.getResult();
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
async function buildV1BroadcastRequest({ id = uuidv7(), data = b4a.from('deadbeef', 'hex') } = {}) {
|
|
32
|
-
const wallet = createWallet();
|
|
33
|
-
const builder = new NetworkMessageBuilder(wallet, config);
|
|
34
|
-
await builder
|
|
35
|
-
.setType(NetworkOperationType.BROADCAST_TRANSACTION_REQUEST)
|
|
36
|
-
.setId(id)
|
|
37
|
-
.setTimestamp()
|
|
38
|
-
.setData(data)
|
|
39
|
-
.setCapabilities([])
|
|
40
|
-
.buildPayload();
|
|
41
|
-
return builder.getResult();
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function createWallet() {
|
|
45
|
-
const keyPair = {
|
|
46
|
-
publicKey: b4a.from(testKeyPair1.publicKey, 'hex'),
|
|
47
|
-
secretKey: b4a.from(testKeyPair1.secretKey, 'hex')
|
|
48
|
-
};
|
|
49
|
-
return NetworkWalletFactory.provide({
|
|
50
|
-
enableWallet: false,
|
|
51
|
-
keyPair,
|
|
52
|
-
networkPrefix: config.addressPrefix
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
test('PendingRequestService registers and resolves v1 request', async t => {
|
|
57
|
-
const service = new PendingRequestService(config);
|
|
58
|
-
const peer = validPeerA;
|
|
59
|
-
const request = await buildV1Request();
|
|
60
|
-
|
|
61
|
-
const promise = service.registerPendingRequest(peer, request);
|
|
62
|
-
t.ok(service.has(request.id));
|
|
63
|
-
|
|
64
|
-
t.ok(service.resolvePendingRequest(request.id));
|
|
65
|
-
t.is(service.has(request.id), false);
|
|
66
|
-
await promise;
|
|
67
|
-
|
|
68
|
-
t.is(service.resolvePendingRequest(request.id), false);
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
test('PendingRequestService rejects and removes pending request', async t => {
|
|
72
|
-
const service = new PendingRequestService(config);
|
|
73
|
-
const peer = validPeerA;
|
|
74
|
-
const request = await buildV1Request();
|
|
75
|
-
|
|
76
|
-
const promise = service.registerPendingRequest(peer, request);
|
|
77
|
-
const expectedError = new Error('test');
|
|
78
|
-
|
|
79
|
-
t.ok(service.rejectPendingRequest(request.id, expectedError));
|
|
80
|
-
t.is(service.has(request.id), false);
|
|
81
|
-
|
|
82
|
-
try {
|
|
83
|
-
await promise;
|
|
84
|
-
t.fail('Expected pending request promise to reject');
|
|
85
|
-
} catch (error) {
|
|
86
|
-
t.ok(error instanceof V1UnexpectedError);
|
|
87
|
-
t.is(error.message, expectedError.message);
|
|
88
|
-
t.is(error.endConnection, true);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
t.is(service.rejectPendingRequest('missing', new Error('missing')), false);
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
test('PendingRequestService preserves typed result-coded errors', async t => {
|
|
95
|
-
const service = new PendingRequestService(config);
|
|
96
|
-
const peer = validPeerA;
|
|
97
|
-
const request = await buildV1Request();
|
|
98
|
-
|
|
99
|
-
const promise = service.registerPendingRequest(peer, request);
|
|
100
|
-
const expectedError = new SharedValidatorRejectionError(ResultCode.TX_INVALID_PAYLOAD, 'domain-invalid', true);
|
|
101
|
-
|
|
102
|
-
t.ok(service.rejectPendingRequest(request.id, expectedError));
|
|
103
|
-
|
|
104
|
-
try {
|
|
105
|
-
await promise;
|
|
106
|
-
t.fail('Expected pending request promise to reject');
|
|
107
|
-
} catch (error) {
|
|
108
|
-
t.is(error, expectedError);
|
|
109
|
-
t.is(error.resultCode, ResultCode.TX_INVALID_PAYLOAD);
|
|
110
|
-
t.is(error.message, 'domain-invalid');
|
|
111
|
-
t.is(error.endConnection, true);
|
|
112
|
-
}
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
test('PendingRequestService throws on duplicate request id', async t => {
|
|
116
|
-
const service = new PendingRequestService(config);
|
|
117
|
-
const peer = validPeerA;
|
|
118
|
-
const request = await buildV1Request();
|
|
119
|
-
|
|
120
|
-
const promise = service.registerPendingRequest(peer, request);
|
|
121
|
-
|
|
122
|
-
t.exception(
|
|
123
|
-
() => service.registerPendingRequest(peer, request),
|
|
124
|
-
errorMessageIncludes('already exists')
|
|
125
|
-
);
|
|
126
|
-
|
|
127
|
-
t.ok(service.resolvePendingRequest(request.id));
|
|
128
|
-
await promise;
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
test('PendingRequestService rejects pending request on timeout', async t => {
|
|
132
|
-
const clock = sinon.useFakeTimers({ now: 1 });
|
|
133
|
-
try {
|
|
134
|
-
const pendingRequestTimeout = 123;
|
|
135
|
-
const service = new PendingRequestService({
|
|
136
|
-
pendingRequestTimeout,
|
|
137
|
-
maxPendingRequestsInPendingRequestsService: 10
|
|
138
|
-
});
|
|
139
|
-
const peer = validPeerA;
|
|
140
|
-
const request = await buildV1Request();
|
|
141
|
-
|
|
142
|
-
const promise = service.registerPendingRequest(peer, request);
|
|
143
|
-
promise.catch(() => {});
|
|
144
|
-
t.ok(service.has(request.id));
|
|
145
|
-
|
|
146
|
-
await clock.runAllAsync();
|
|
147
|
-
|
|
148
|
-
try {
|
|
149
|
-
await promise;
|
|
150
|
-
t.fail('Expected pending request to time out');
|
|
151
|
-
} catch (error) {
|
|
152
|
-
t.ok(error?.message?.includes(`timed out after ${pendingRequestTimeout} ms`));
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
t.is(service.has(request.id), false);
|
|
156
|
-
} finally {
|
|
157
|
-
clock.restore();
|
|
158
|
-
sinon.restore();
|
|
159
|
-
}
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
test('PendingRequestService.close rejects all pending requests', async t => {
|
|
163
|
-
const service = new PendingRequestService(config);
|
|
164
|
-
const peer = validPeerA;
|
|
165
|
-
const request1 = await buildV1Request();
|
|
166
|
-
const request2 = await buildV1Request();
|
|
167
|
-
|
|
168
|
-
const promise1 = service.registerPendingRequest(peer, request1);
|
|
169
|
-
const promise2 = service.registerPendingRequest(peer, request2);
|
|
170
|
-
|
|
171
|
-
service.close();
|
|
172
|
-
t.is(service.has(request1.id), false);
|
|
173
|
-
t.is(service.has(request2.id), false);
|
|
174
|
-
|
|
175
|
-
const results = await Promise.allSettled([promise1, promise2]);
|
|
176
|
-
t.is(results[0].status, 'rejected');
|
|
177
|
-
t.ok(results[0]?.reason?.message?.includes(`Pending request ${request1.id} cancelled (shutdown).`));
|
|
178
|
-
t.is(results[1].status, 'rejected');
|
|
179
|
-
t.ok(results[1]?.reason?.message?.includes(`Pending request ${request2.id} cancelled (shutdown).`));
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
test('PendingRequestService rejects all pending requests for a specific peer', async t => {
|
|
183
|
-
const service = new PendingRequestService(config);
|
|
184
|
-
const peerA = validPeerA;
|
|
185
|
-
const peerB = validPeerB;
|
|
186
|
-
const requestA1 = await buildV1Request();
|
|
187
|
-
const requestA2 = await buildV1Request();
|
|
188
|
-
const requestB1 = await buildV1Request();
|
|
189
|
-
|
|
190
|
-
const promiseA1 = service.registerPendingRequest(peerA, requestA1);
|
|
191
|
-
const promiseA2 = service.registerPendingRequest(peerA, requestA2);
|
|
192
|
-
const promiseB1 = service.registerPendingRequest(peerB, requestB1);
|
|
193
|
-
|
|
194
|
-
const rejectedCount = service.rejectPendingRequestsForPeer(peerA, new Error('peer disconnected'));
|
|
195
|
-
t.is(rejectedCount, 2);
|
|
196
|
-
t.is(service.has(requestA1.id), false);
|
|
197
|
-
t.is(service.has(requestA2.id), false);
|
|
198
|
-
t.is(service.has(requestB1.id), true);
|
|
199
|
-
|
|
200
|
-
const results = await Promise.allSettled([promiseA1, promiseA2]);
|
|
201
|
-
t.is(results[0].status, 'rejected');
|
|
202
|
-
t.is(results[0].reason.message, 'peer disconnected');
|
|
203
|
-
t.is(results[1].status, 'rejected');
|
|
204
|
-
t.is(results[1].reason.message, 'peer disconnected');
|
|
205
|
-
|
|
206
|
-
t.ok(service.resolvePendingRequest(requestB1.id));
|
|
207
|
-
await promiseB1;
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
test('PendingRequestService stores only transaction data for broadcast requests', async t => {
|
|
211
|
-
const service = new PendingRequestService(config);
|
|
212
|
-
const peer = validPeerA;
|
|
213
|
-
const livenessRequest = await buildV1Request();
|
|
214
|
-
const broadcastRequest = await buildV1BroadcastRequest();
|
|
215
|
-
|
|
216
|
-
const livenessPromise = service.registerPendingRequest(peer, livenessRequest);
|
|
217
|
-
const broadcastPromise = service.registerPendingRequest(peer, broadcastRequest);
|
|
218
|
-
|
|
219
|
-
const livenessEntry = service.getPendingRequest(livenessRequest.id);
|
|
220
|
-
const broadcastEntry = service.getPendingRequest(broadcastRequest.id);
|
|
221
|
-
|
|
222
|
-
t.is(livenessEntry.requestTxData, null);
|
|
223
|
-
t.ok(b4a.isBuffer(broadcastEntry.requestTxData));
|
|
224
|
-
t.alike(broadcastEntry.requestTxData, broadcastRequest.broadcast_transaction_request.data);
|
|
225
|
-
t.is(Object.prototype.hasOwnProperty.call(livenessEntry, 'requestMessage'), false);
|
|
226
|
-
t.is(Object.prototype.hasOwnProperty.call(broadcastEntry, 'requestMessage'), false);
|
|
227
|
-
|
|
228
|
-
t.ok(service.resolvePendingRequest(livenessRequest.id));
|
|
229
|
-
t.ok(service.resolvePendingRequest(broadcastRequest.id));
|
|
230
|
-
await Promise.all([livenessPromise, broadcastPromise]);
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
test('PendingRequestService.isProbePending matches peer and liveness type', async t => {
|
|
234
|
-
const service = new PendingRequestService(config);
|
|
235
|
-
const peerA = validPeerA;
|
|
236
|
-
const peerB = validPeerB;
|
|
237
|
-
|
|
238
|
-
t.is(service.isProbePending(peerA), false);
|
|
239
|
-
t.is(service.isProbePending(peerB), false);
|
|
240
|
-
|
|
241
|
-
const broadcastRequestA = await buildV1BroadcastRequest();
|
|
242
|
-
const livenessRequestA = await buildV1Request();
|
|
243
|
-
const livenessRequestB = await buildV1Request();
|
|
244
|
-
|
|
245
|
-
const broadcastPromiseA = service.registerPendingRequest(peerA, broadcastRequestA);
|
|
246
|
-
t.is(service.isProbePending(peerA), false);
|
|
247
|
-
|
|
248
|
-
const livenessPromiseA = service.registerPendingRequest(peerA, livenessRequestA);
|
|
249
|
-
const livenessPromiseB = service.registerPendingRequest(peerB, livenessRequestB);
|
|
250
|
-
|
|
251
|
-
t.is(service.isProbePending(peerA), true);
|
|
252
|
-
t.is(service.isProbePending(peerB), true);
|
|
253
|
-
|
|
254
|
-
t.ok(service.resolvePendingRequest(livenessRequestA.id));
|
|
255
|
-
await livenessPromiseA;
|
|
256
|
-
t.is(service.isProbePending(peerA), false);
|
|
257
|
-
t.is(service.isProbePending(peerB), true);
|
|
258
|
-
|
|
259
|
-
t.ok(service.resolvePendingRequest(livenessRequestB.id));
|
|
260
|
-
await livenessPromiseB;
|
|
261
|
-
t.is(service.isProbePending(peerB), false);
|
|
262
|
-
|
|
263
|
-
t.ok(service.resolvePendingRequest(broadcastRequestA.id));
|
|
264
|
-
await broadcastPromiseA;
|
|
265
|
-
});
|
|
266
|
-
|
|
267
|
-
test('PendingRequestService enforces global pending request limit', async t => {
|
|
268
|
-
const service = new PendingRequestService({
|
|
269
|
-
pendingRequestTimeout: config.pendingRequestTimeout,
|
|
270
|
-
maxPendingRequestsInPendingRequestsService: 3
|
|
271
|
-
});
|
|
272
|
-
const peer = validPeerA;
|
|
273
|
-
|
|
274
|
-
const request0 = await buildV1Request({ id: 'limit-0' });
|
|
275
|
-
const request1 = await buildV1Request({ id: 'limit-1' });
|
|
276
|
-
const request2 = await buildV1Request({ id: 'limit-2' });
|
|
277
|
-
const overflowRequest = await buildV1Request({ id: 'limit-overflow' });
|
|
278
|
-
|
|
279
|
-
service.registerPendingRequest(peer, request0).catch(() => {});
|
|
280
|
-
service.registerPendingRequest(peer, request1).catch(() => {});
|
|
281
|
-
service.registerPendingRequest(peer, request2).catch(() => {});
|
|
282
|
-
|
|
283
|
-
t.exception(
|
|
284
|
-
() => service.registerPendingRequest(peer, overflowRequest),
|
|
285
|
-
errorMessageIncludes('Maximum number of pending requests reached.')
|
|
286
|
-
);
|
|
287
|
-
|
|
288
|
-
service.close();
|
|
289
|
-
});
|
|
290
|
-
|
|
291
|
-
test('PendingRequestService.stopPendingRequestTimeout stops timeout and handles missing id', async t => {
|
|
292
|
-
const clock = sinon.useFakeTimers({ now: 1 });
|
|
293
|
-
try {
|
|
294
|
-
const service = new PendingRequestService(config);
|
|
295
|
-
const peer = validPeerA;
|
|
296
|
-
const request = await buildV1Request();
|
|
297
|
-
|
|
298
|
-
const promise = service.registerPendingRequest(peer, request);
|
|
299
|
-
t.is(service.stopPendingRequestTimeout('missing-id'), false);
|
|
300
|
-
t.is(service.stopPendingRequestTimeout(request.id), true);
|
|
301
|
-
t.is(service.getPendingRequest(request.id)?.timeoutId, null);
|
|
302
|
-
|
|
303
|
-
let settled = false;
|
|
304
|
-
promise.then(
|
|
305
|
-
() => { settled = true; },
|
|
306
|
-
() => { settled = true; }
|
|
307
|
-
);
|
|
308
|
-
|
|
309
|
-
await clock.runAllAsync();
|
|
310
|
-
await Promise.resolve();
|
|
311
|
-
t.is(settled, false);
|
|
312
|
-
|
|
313
|
-
t.ok(service.resolvePendingRequest(request.id));
|
|
314
|
-
await promise;
|
|
315
|
-
} finally {
|
|
316
|
-
clock.restore();
|
|
317
|
-
sinon.restore();
|
|
318
|
-
}
|
|
319
|
-
});
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
test('PendingRequestService.getPendingRequest returns null for missing id', t => {
|
|
323
|
-
const service = new PendingRequestService(config);
|
|
324
|
-
t.is(service.getPendingRequest('missing-id'), null);
|
|
325
|
-
});
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
test('PendingRequestService rejects invalid registerPendingRequest input', async t => {
|
|
329
|
-
const service = new PendingRequestService(config);
|
|
330
|
-
const peer = validPeerA;
|
|
331
|
-
const validLivenessRequest = await buildV1Request({ id: 'invalid-peer' });
|
|
332
|
-
|
|
333
|
-
t.exception(
|
|
334
|
-
() => service.registerPendingRequest(peer, 'not-an-object'),
|
|
335
|
-
errorMessageIncludes('Pending request message must be an object.')
|
|
336
|
-
);
|
|
337
|
-
|
|
338
|
-
t.exception(
|
|
339
|
-
() => service.registerPendingRequest(peer, {
|
|
340
|
-
id: '',
|
|
341
|
-
type: NetworkOperationType.LIVENESS_REQUEST
|
|
342
|
-
}),
|
|
343
|
-
errorMessageIncludes('Pending request ID must be a non-empty string.')
|
|
344
|
-
);
|
|
345
|
-
|
|
346
|
-
t.exception(
|
|
347
|
-
() => service.registerPendingRequest(peer, {
|
|
348
|
-
id: 'invalid-type',
|
|
349
|
-
type: 'LIVENESS_REQUEST'
|
|
350
|
-
}),
|
|
351
|
-
errorMessageIncludes('Unsupported pending request type.')
|
|
352
|
-
);
|
|
353
|
-
|
|
354
|
-
t.exception(
|
|
355
|
-
() => service.registerPendingRequest(peer, {
|
|
356
|
-
id: 'unsupported-type',
|
|
357
|
-
type: 999
|
|
358
|
-
}),
|
|
359
|
-
errorMessageIncludes('Unsupported pending request type.')
|
|
360
|
-
);
|
|
361
|
-
|
|
362
|
-
t.exception(
|
|
363
|
-
() => service.registerPendingRequest('deadbeef', validLivenessRequest),
|
|
364
|
-
errorMessageIncludes('Invalid peer public key. Expected 32-byte hex string.')
|
|
365
|
-
);
|
|
366
|
-
|
|
367
|
-
t.is(service.getPendingRequest('unsupported-type'), null);
|
|
368
|
-
t.is(service.getPendingRequest('invalid-type'), null);
|
|
369
|
-
});
|
|
370
|
-
|
|
371
|
-
test('PendingRequestService.rejectPendingRequest falls back to Unexpected error message', async t => {
|
|
372
|
-
const service = new PendingRequestService(config);
|
|
373
|
-
const peer = validPeerA;
|
|
374
|
-
const request = await buildV1Request();
|
|
375
|
-
const promise = service.registerPendingRequest(peer, request);
|
|
376
|
-
|
|
377
|
-
t.ok(service.rejectPendingRequest(request.id, {}));
|
|
378
|
-
|
|
379
|
-
try {
|
|
380
|
-
await promise;
|
|
381
|
-
t.fail('Expected pending request promise to reject');
|
|
382
|
-
} catch (error) {
|
|
383
|
-
t.ok(error instanceof V1UnexpectedError);
|
|
384
|
-
t.is(error.message, 'Unexpected error');
|
|
385
|
-
t.is(error.endConnection, true);
|
|
386
|
-
}
|
|
387
|
-
});
|
|
388
|
-
|
|
389
|
-
test('PendingRequestService.close catches reject errors and continues cleanup', async t => {
|
|
390
|
-
const service = new PendingRequestService(config);
|
|
391
|
-
const peer = validPeerA;
|
|
392
|
-
const request = await buildV1Request();
|
|
393
|
-
const promise = service.registerPendingRequest(peer, request);
|
|
394
|
-
const entry = service.getPendingRequest(request.id);
|
|
395
|
-
const originalReject = entry.reject;
|
|
396
|
-
const logs = [];
|
|
397
|
-
const originalConsoleError = console.error;
|
|
398
|
-
|
|
399
|
-
console.error = (...args) => {
|
|
400
|
-
logs.push(args);
|
|
401
|
-
};
|
|
402
|
-
t.teardown(() => {
|
|
403
|
-
console.error = originalConsoleError;
|
|
404
|
-
});
|
|
405
|
-
|
|
406
|
-
entry.reject = error => {
|
|
407
|
-
originalReject(error);
|
|
408
|
-
throw new Error('forced reject failure');
|
|
409
|
-
};
|
|
410
|
-
|
|
411
|
-
service.close();
|
|
412
|
-
t.is(service.has(request.id), false);
|
|
413
|
-
t.is(logs.length, 1);
|
|
414
|
-
t.ok(String(logs[0][0]).includes('PendingRequestService.close: failed to reject pending request'));
|
|
415
|
-
|
|
416
|
-
const result = await Promise.allSettled([promise]);
|
|
417
|
-
t.is(result[0].status, 'rejected');
|
|
418
|
-
t.ok(result[0]?.reason?.message?.includes(`Pending request ${request.id} cancelled (shutdown).`));
|
|
419
|
-
});
|
|
420
|
-
|
|
421
|
-
test('PendingRequestService throws when registerPendingRequest receives null message', t => {
|
|
422
|
-
const service = new PendingRequestService(config);
|
|
423
|
-
const peer = validPeerA;
|
|
424
|
-
try {
|
|
425
|
-
service.registerPendingRequest(peer, null);
|
|
426
|
-
t.fail('Expected registerPendingRequest to throw for null message');
|
|
427
|
-
} catch (error) {
|
|
428
|
-
t.ok(error?.message?.includes('Pending request message must be an object.'));
|
|
429
|
-
}
|
|
430
|
-
});
|
|
431
|
-
|
|
@@ -1,246 +0,0 @@
|
|
|
1
|
-
import { test } from 'brittle';
|
|
2
|
-
import b4a from 'b4a';
|
|
3
|
-
import sinon from 'sinon';
|
|
4
|
-
|
|
5
|
-
import TransactionCommitService, {
|
|
6
|
-
PendingCommitAlreadyExistsError,
|
|
7
|
-
PendingCommitBufferFullError,
|
|
8
|
-
PendingCommitCancelledError,
|
|
9
|
-
PendingCommitInvalidTxHashError,
|
|
10
|
-
PendingCommitTimeoutError,
|
|
11
|
-
PendingCommitUnexpectedError
|
|
12
|
-
} from '../../../../src/core/network/services/TransactionCommitService.js';
|
|
13
|
-
import { TRANSACTION_COMMIT_SERVICE_BUFFER_SIZE } from '../../../../src/utils/constants.js';
|
|
14
|
-
import { errorMessageIncludes } from '../../../helpers/regexHelper.js';
|
|
15
|
-
import { config } from '../../../helpers/config.js';
|
|
16
|
-
|
|
17
|
-
function buildTxHashFromIndex(index) {
|
|
18
|
-
const buffer = b4a.alloc(32);
|
|
19
|
-
buffer.writeUInt32BE(index, 28);
|
|
20
|
-
return buffer.toString('hex');
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
test('TransactionCommitService registers and resolves pending commit', async t => {
|
|
24
|
-
const service = new TransactionCommitService(config);
|
|
25
|
-
const txHash = buildTxHashFromIndex(1);
|
|
26
|
-
const receipt = {
|
|
27
|
-
txHash,
|
|
28
|
-
blockNumber: 123,
|
|
29
|
-
proof: b4a.from('abcd', 'hex')
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
const promise = service.registerPendingCommit(txHash);
|
|
33
|
-
t.ok(service.has(txHash));
|
|
34
|
-
|
|
35
|
-
t.ok(service.resolvePendingCommit(txHash, receipt));
|
|
36
|
-
t.is(service.has(txHash), false);
|
|
37
|
-
t.alike(await promise, receipt);
|
|
38
|
-
|
|
39
|
-
t.is(service.resolvePendingCommit(txHash), false);
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
test('TransactionCommitService rejects pending commit with provided Error', async t => {
|
|
43
|
-
const service = new TransactionCommitService(config);
|
|
44
|
-
const txHash = buildTxHashFromIndex(2);
|
|
45
|
-
const promise = service.registerPendingCommit(txHash);
|
|
46
|
-
const expectedError = new Error('commit failed');
|
|
47
|
-
|
|
48
|
-
t.ok(service.rejectPendingCommit(txHash, expectedError));
|
|
49
|
-
t.is(service.has(txHash), false);
|
|
50
|
-
|
|
51
|
-
try {
|
|
52
|
-
await promise;
|
|
53
|
-
t.fail('Expected pending commit promise to reject');
|
|
54
|
-
} catch (error) {
|
|
55
|
-
t.is(error, expectedError);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
t.is(service.rejectPendingCommit(txHash, new Error('missing')), false);
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
test('TransactionCommitService rejects pending commit with fallback unexpected error', async t => {
|
|
62
|
-
const service = new TransactionCommitService(config);
|
|
63
|
-
const txHash = buildTxHashFromIndex(3);
|
|
64
|
-
const promise = service.registerPendingCommit(txHash);
|
|
65
|
-
|
|
66
|
-
t.ok(service.rejectPendingCommit(txHash, { reason: 'not-error-instance' }));
|
|
67
|
-
|
|
68
|
-
try {
|
|
69
|
-
await promise;
|
|
70
|
-
t.fail('Expected pending commit promise to reject');
|
|
71
|
-
} catch (error) {
|
|
72
|
-
t.ok(error instanceof PendingCommitUnexpectedError);
|
|
73
|
-
t.is(error.message, 'Unexpected commit error');
|
|
74
|
-
}
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
test('TransactionCommitService throws on duplicate txHash', async t => {
|
|
78
|
-
const service = new TransactionCommitService(config);
|
|
79
|
-
const txHash = buildTxHashFromIndex(4);
|
|
80
|
-
const promise = service.registerPendingCommit(txHash);
|
|
81
|
-
|
|
82
|
-
t.exception(
|
|
83
|
-
() => service.registerPendingCommit(txHash),
|
|
84
|
-
PendingCommitAlreadyExistsError
|
|
85
|
-
);
|
|
86
|
-
|
|
87
|
-
t.ok(service.resolvePendingCommit(txHash));
|
|
88
|
-
await promise;
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
test('TransactionCommitService validates txHash in all public methods', t => {
|
|
92
|
-
const service = new TransactionCommitService(config);
|
|
93
|
-
const invalidTxHashes = [
|
|
94
|
-
undefined,
|
|
95
|
-
null,
|
|
96
|
-
'',
|
|
97
|
-
'abc',
|
|
98
|
-
'g'.repeat(64),
|
|
99
|
-
'a'.repeat(62),
|
|
100
|
-
'a'.repeat(66),
|
|
101
|
-
b4a.alloc(32)
|
|
102
|
-
];
|
|
103
|
-
|
|
104
|
-
for (const invalidTxHash of invalidTxHashes) {
|
|
105
|
-
t.exception(
|
|
106
|
-
() => service.has(invalidTxHash),
|
|
107
|
-
PendingCommitInvalidTxHashError
|
|
108
|
-
);
|
|
109
|
-
t.exception(
|
|
110
|
-
() => service.registerPendingCommit(invalidTxHash),
|
|
111
|
-
PendingCommitInvalidTxHashError
|
|
112
|
-
);
|
|
113
|
-
t.exception(
|
|
114
|
-
() => service.getAndDeletePendingCommit(invalidTxHash),
|
|
115
|
-
PendingCommitInvalidTxHashError
|
|
116
|
-
);
|
|
117
|
-
t.exception(
|
|
118
|
-
() => service.resolvePendingCommit(invalidTxHash),
|
|
119
|
-
PendingCommitInvalidTxHashError
|
|
120
|
-
);
|
|
121
|
-
t.exception(
|
|
122
|
-
() => service.rejectPendingCommit(invalidTxHash, new Error('invalid')),
|
|
123
|
-
PendingCommitInvalidTxHashError
|
|
124
|
-
);
|
|
125
|
-
}
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
test('TransactionCommitService returns null/false for missing valid txHash', t => {
|
|
129
|
-
const service = new TransactionCommitService(config);
|
|
130
|
-
const missingTxHash = buildTxHashFromIndex(5);
|
|
131
|
-
|
|
132
|
-
t.is(service.has(missingTxHash), false);
|
|
133
|
-
t.is(service.getAndDeletePendingCommit(missingTxHash), null);
|
|
134
|
-
t.is(service.resolvePendingCommit(missingTxHash), false);
|
|
135
|
-
t.is(service.rejectPendingCommit(missingTxHash, new Error('missing')), false);
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
test('TransactionCommitService getAndDeletePendingCommit clears pending entry', async t => {
|
|
139
|
-
const clock = sinon.useFakeTimers({ now: 1 });
|
|
140
|
-
try {
|
|
141
|
-
const service = new TransactionCommitService(config);
|
|
142
|
-
const txHash = buildTxHashFromIndex(6);
|
|
143
|
-
const promise = service.registerPendingCommit(txHash);
|
|
144
|
-
const entry = service.getAndDeletePendingCommit(txHash);
|
|
145
|
-
|
|
146
|
-
t.ok(entry);
|
|
147
|
-
t.is(entry.txHash, txHash);
|
|
148
|
-
t.is(service.has(txHash), false);
|
|
149
|
-
t.is(service.getAndDeletePendingCommit(txHash), null);
|
|
150
|
-
|
|
151
|
-
let settled = false;
|
|
152
|
-
promise.then(
|
|
153
|
-
() => {
|
|
154
|
-
settled = true;
|
|
155
|
-
},
|
|
156
|
-
() => {
|
|
157
|
-
settled = true;
|
|
158
|
-
}
|
|
159
|
-
);
|
|
160
|
-
|
|
161
|
-
await clock.runAllAsync();
|
|
162
|
-
await Promise.resolve();
|
|
163
|
-
t.is(settled, false);
|
|
164
|
-
|
|
165
|
-
entry.resolve('manual-resolution');
|
|
166
|
-
t.is(await promise, 'manual-resolution');
|
|
167
|
-
} finally {
|
|
168
|
-
clock.restore();
|
|
169
|
-
sinon.restore();
|
|
170
|
-
}
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
test('TransactionCommitService rejects pending commit on timeout', async t => {
|
|
174
|
-
const clock = sinon.useFakeTimers({ now: 1 });
|
|
175
|
-
try {
|
|
176
|
-
const timeoutMs = config.txCommitTimeout;
|
|
177
|
-
const service = new TransactionCommitService(config);
|
|
178
|
-
const txHash = buildTxHashFromIndex(7);
|
|
179
|
-
const promise = service.registerPendingCommit(txHash);
|
|
180
|
-
promise.catch(() => {});
|
|
181
|
-
|
|
182
|
-
t.ok(service.has(txHash));
|
|
183
|
-
|
|
184
|
-
await clock.runAllAsync();
|
|
185
|
-
|
|
186
|
-
try {
|
|
187
|
-
await promise;
|
|
188
|
-
t.fail('Expected pending commit to time out');
|
|
189
|
-
} catch (error) {
|
|
190
|
-
t.ok(error instanceof PendingCommitTimeoutError);
|
|
191
|
-
t.ok(error.message.includes(`timed out after ${timeoutMs} ms`));
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
t.is(service.has(txHash), false);
|
|
195
|
-
} finally {
|
|
196
|
-
clock.restore();
|
|
197
|
-
sinon.restore();
|
|
198
|
-
}
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
test('TransactionCommitService enforces pending commit buffer size limit', async t => {
|
|
202
|
-
const service = new TransactionCommitService(config);
|
|
203
|
-
const promises = [];
|
|
204
|
-
|
|
205
|
-
for (let i = 0; i < TRANSACTION_COMMIT_SERVICE_BUFFER_SIZE; i++) {
|
|
206
|
-
const txHash = buildTxHashFromIndex(10_000 + i);
|
|
207
|
-
const promise = service.registerPendingCommit(txHash);
|
|
208
|
-
promise.catch(() => {});
|
|
209
|
-
promises.push(promise);
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
t.exception(
|
|
213
|
-
() => service.registerPendingCommit(buildTxHashFromIndex(10_000 + TRANSACTION_COMMIT_SERVICE_BUFFER_SIZE)),
|
|
214
|
-
errorMessageIncludes(`limit=${TRANSACTION_COMMIT_SERVICE_BUFFER_SIZE}`)
|
|
215
|
-
);
|
|
216
|
-
t.exception(
|
|
217
|
-
() => service.registerPendingCommit(buildTxHashFromIndex(20_000)),
|
|
218
|
-
PendingCommitBufferFullError
|
|
219
|
-
);
|
|
220
|
-
|
|
221
|
-
service.close();
|
|
222
|
-
const results = await Promise.allSettled(promises);
|
|
223
|
-
t.is(results.length, TRANSACTION_COMMIT_SERVICE_BUFFER_SIZE);
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
test('TransactionCommitService.close rejects all pending commits', async t => {
|
|
227
|
-
const service = new TransactionCommitService(config);
|
|
228
|
-
const txHashA = buildTxHashFromIndex(9);
|
|
229
|
-
const txHashB = buildTxHashFromIndex(10);
|
|
230
|
-
|
|
231
|
-
const promiseA = service.registerPendingCommit(txHashA);
|
|
232
|
-
const promiseB = service.registerPendingCommit(txHashB);
|
|
233
|
-
|
|
234
|
-
service.close();
|
|
235
|
-
t.is(service.has(txHashA), false);
|
|
236
|
-
t.is(service.has(txHashB), false);
|
|
237
|
-
|
|
238
|
-
const results = await Promise.allSettled([promiseA, promiseB]);
|
|
239
|
-
t.is(results[0].status, 'rejected');
|
|
240
|
-
t.ok(results[0].reason instanceof PendingCommitCancelledError);
|
|
241
|
-
t.ok(results[0].reason.message.includes(txHashA));
|
|
242
|
-
t.is(results[1].status, 'rejected');
|
|
243
|
-
t.ok(results[1].reason instanceof PendingCommitCancelledError);
|
|
244
|
-
t.ok(results[1].reason.message.includes(txHashB));
|
|
245
|
-
});
|
|
246
|
-
|