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,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
|
-
});
|