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.
- package/package.json +9 -4
- package/proto/network/v1/enums/message_type.proto +16 -0
- package/proto/network/v1/enums/result_code.proto +84 -0
- package/proto/network/v1/messages/broadcast_transaction_request.proto +9 -0
- package/proto/network/v1/messages/broadcast_transaction_response.proto +13 -0
- package/proto/network/v1/messages/liveness_request.proto +8 -0
- package/proto/network/v1/messages/liveness_response.proto +11 -0
- package/proto/network/v1/network_message.proto +22 -0
- package/rpc/rpc_services.js +22 -4
- package/scripts/generate-protobufs.js +37 -12
- package/src/config/config.js +26 -5
- package/src/config/env.js +25 -11
- package/src/core/network/Network.js +73 -36
- package/src/core/network/protocols/LegacyProtocol.js +21 -11
- package/src/core/network/protocols/NetworkMessages.js +38 -17
- package/src/core/network/protocols/ProtocolInterface.js +14 -2
- package/src/core/network/protocols/ProtocolSession.js +144 -17
- package/src/core/network/protocols/V1Protocol.js +37 -18
- package/src/core/network/protocols/connectionPolicies.js +88 -0
- package/src/core/network/protocols/legacy/NetworkMessageRouter.js +25 -19
- package/src/core/network/protocols/{shared/handlers/base/BaseOperationHandler.js → legacy/handlers/BaseStateOperationHandler.js} +23 -12
- package/src/core/network/protocols/legacy/handlers/{GetRequestHandler.js → LegacyGetRequestHandler.js} +6 -6
- package/src/core/network/protocols/legacy/handlers/LegacyResponseHandler.js +23 -0
- package/src/core/network/protocols/{shared/handlers/RoleOperationHandler.js → legacy/handlers/LegacyRoleOperationHandler.js} +18 -11
- package/src/core/network/protocols/{shared/handlers/SubnetworkOperationHandler.js → legacy/handlers/LegacySubnetworkOperationHandler.js} +28 -17
- package/src/core/network/protocols/{shared/handlers/TransferOperationHandler.js → legacy/handlers/LegacyTransferOperationHandler.js} +17 -11
- package/src/core/network/protocols/shared/errors/SharedValidatorRejectionError.js +27 -0
- package/src/core/network/protocols/shared/validators/{PartialBootstrapDeployment.js → PartialBootstrapDeploymentValidator.js} +9 -4
- package/src/core/network/protocols/shared/validators/{base/PartialOperation.js → PartialOperationValidator.js} +47 -25
- package/src/core/network/protocols/shared/validators/{PartialRoleAccess.js → PartialRoleAccessValidator.js} +51 -17
- package/src/core/network/protocols/shared/validators/{PartialTransaction.js → PartialTransactionValidator.js} +21 -7
- package/src/core/network/protocols/shared/validators/{PartialTransfer.js → PartialTransferValidator.js} +26 -9
- package/src/core/network/protocols/v1/NetworkMessageRouter.js +91 -7
- package/src/core/network/protocols/v1/V1ProtocolError.js +91 -0
- package/src/core/network/protocols/v1/handlers/V1BaseOperationHandler.js +65 -0
- package/src/core/network/protocols/v1/handlers/V1BroadcastTransactionOperationHandler.js +389 -0
- package/src/core/network/protocols/v1/handlers/V1LivenessOperationHandler.js +87 -0
- package/src/core/network/protocols/v1/validators/V1BaseOperation.js +211 -0
- package/src/core/network/protocols/v1/validators/V1BroadcastTransactionRequest.js +26 -0
- package/src/core/network/protocols/v1/validators/V1BroadcastTransactionResponse.js +276 -0
- package/src/core/network/protocols/v1/validators/V1LivenessRequest.js +15 -0
- package/src/core/network/protocols/v1/validators/V1LivenessResponse.js +17 -0
- package/src/core/network/protocols/v1/validators/V1ValidationSchema.js +210 -0
- package/src/core/network/services/ConnectionManager.js +146 -94
- package/src/core/network/services/MessageOrchestrator.js +151 -27
- package/src/core/network/services/PendingRequestService.js +172 -0
- package/src/core/network/services/TransactionCommitService.js +149 -0
- package/src/core/network/services/TransactionPoolService.js +129 -18
- package/src/core/network/services/TransactionRateLimiterService.js +52 -34
- package/src/core/network/services/ValidatorHealthCheckService.js +127 -0
- package/src/core/network/services/ValidatorObserverService.js +18 -26
- package/src/core/state/State.js +70 -19
- package/src/index.js +5 -4
- package/src/messages/network/v1/NetworkMessageBuilder.js +59 -79
- package/src/messages/network/v1/NetworkMessageDirector.js +16 -50
- package/src/utils/Scheduler.js +0 -8
- package/src/utils/constants.js +71 -5
- package/src/utils/deepEqualApplyPayload.js +40 -0
- package/src/utils/helpers.js +10 -1
- package/src/utils/logger.js +25 -0
- package/src/utils/normalizers.js +38 -0
- package/src/utils/protobuf/networkV1.generated.cjs +2460 -0
- package/src/utils/protobuf/operationHelpers.js +24 -3
- package/tests/acceptance/v1/account/account.test.mjs +8 -2
- package/tests/acceptance/v1/tx/tx.test.mjs +23 -1
- package/tests/acceptance/v1/tx-details/tx-details.test.mjs +34 -6
- package/tests/fixtures/networkV1.fixtures.js +2 -28
- package/tests/helpers/transactionPayloads.mjs +2 -2
- package/tests/unit/messages/network/NetworkMessageBuilder.test.js +239 -79
- package/tests/unit/messages/network/NetworkMessageDirector.test.js +223 -77
- package/tests/unit/network/LegacyNetworkMessageRouter.test.js +54 -0
- package/tests/unit/network/ProtocolSession.test.js +127 -0
- package/tests/unit/network/networkModule.test.js +4 -1
- package/tests/unit/network/services/ConnectionManager.test.js +450 -0
- package/tests/unit/network/services/MessageOrchestrator.test.js +445 -0
- package/tests/unit/network/services/PendingRequestService.test.js +431 -0
- package/tests/unit/network/services/TransactionCommitService.test.js +246 -0
- package/tests/unit/network/services/TransactionPoolService.test.js +489 -0
- package/tests/unit/network/services/TransactionRateLimiterService.test.js +139 -0
- package/tests/unit/network/services/ValidatorHealthCheckService.test.js +115 -0
- package/tests/unit/network/services/services.test.js +17 -0
- package/tests/unit/network/utils/v1TestUtils.js +153 -0
- package/tests/unit/network/v1/NetworkMessageRouterV1.test.js +151 -0
- package/tests/unit/network/v1/V1BaseOperation.test.js +356 -0
- package/tests/unit/network/v1/V1BroadcastTransactionOperationHandler.test.js +129 -0
- package/tests/unit/network/v1/V1BroadcastTransactionRequest.test.js +53 -0
- package/tests/unit/network/v1/V1BroadcastTransactionResponse.test.js +512 -0
- package/tests/unit/network/v1/V1LivenessRequest.test.js +32 -0
- package/tests/unit/network/v1/V1LivenessResponse.test.js +45 -0
- package/tests/unit/network/v1/V1ResultCode.test.js +84 -0
- package/tests/unit/network/v1/V1ValidationSchema.test.js +13 -0
- package/tests/unit/network/v1/connectionPolicies.test.js +49 -0
- package/tests/unit/network/v1/handlers/V1BaseOperationHandler.test.js +284 -0
- package/tests/unit/network/v1/handlers/V1BroadcastTransactionOperationHandler.test.js +794 -0
- package/tests/unit/network/v1/handlers/V1LivenessOperationHandler.test.js +193 -0
- package/tests/unit/network/v1/v1.handlers.test.js +15 -0
- package/tests/unit/network/v1/v1.test.js +19 -0
- package/tests/unit/network/v1/v1ValidationSchema/broadcastTransactionRequest.test.js +119 -0
- package/tests/unit/network/v1/v1ValidationSchema/broadcastTransactionResponse.test.js +136 -0
- package/tests/unit/network/v1/v1ValidationSchema/common.test.js +308 -0
- package/tests/unit/network/v1/v1ValidationSchema/livenessRequest.test.js +90 -0
- package/tests/unit/network/v1/v1ValidationSchema/livenessResponse.test.js +133 -0
- package/tests/unit/unit.test.js +2 -2
- package/tests/unit/utils/deepEqualApplyPayload/deepEqualApplyPayload.test.js +102 -0
- package/tests/unit/utils/protobuf/operationHelpers.test.js +2 -4
- package/tests/unit/utils/utils.test.js +1 -0
- package/.github/workflows/acceptance-tests.yml +0 -38
- package/.github/workflows/lint-pr-title.yml +0 -26
- package/.github/workflows/publish.yml +0 -33
- package/.github/workflows/unit-tests.yml +0 -34
- package/proto/network.proto +0 -74
- package/src/core/network/protocols/legacy/handlers/ResponseHandler.js +0 -37
- package/src/utils/protobuf/network.cjs +0 -840
- 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
|
+
});
|