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,445 @@
|
|
|
1
|
+
import { hook, test } from 'brittle';
|
|
2
|
+
import sinon from 'sinon';
|
|
3
|
+
import b4a from 'b4a';
|
|
4
|
+
|
|
5
|
+
import { createConfig, ENV } from '../../../../src/config/env.js';
|
|
6
|
+
import MessageOrchestrator from '../../../../src/core/network/services/MessageOrchestrator.js';
|
|
7
|
+
import { OperationType, ResultCode } from '../../../../src/utils/constants.js';
|
|
8
|
+
import NetworkWalletFactory from '../../../../src/core/network/identity/NetworkWalletFactory.js';
|
|
9
|
+
import { testKeyPair1, testKeyPair2 } from '../../../fixtures/apply.fixtures.js';
|
|
10
|
+
import { publicKeyToAddress } from '../../../../src/utils/helpers.js';
|
|
11
|
+
import { ConnectionManagerError } from '../../../../src/core/network/services/ConnectionManager.js';
|
|
12
|
+
import { V1TimeoutError } from '../../../../src/core/network/protocols/v1/V1ProtocolError.js';
|
|
13
|
+
|
|
14
|
+
const VALIDATOR_KEY = testKeyPair2.publicKey;
|
|
15
|
+
|
|
16
|
+
const createWallet = (config) => {
|
|
17
|
+
const keyPair = {
|
|
18
|
+
publicKey: b4a.from(testKeyPair1.publicKey, 'hex'),
|
|
19
|
+
secretKey: b4a.from(testKeyPair1.secretKey, 'hex'),
|
|
20
|
+
};
|
|
21
|
+
return NetworkWalletFactory.provide({
|
|
22
|
+
enableWallet: false,
|
|
23
|
+
keyPair,
|
|
24
|
+
networkPrefix: config.addressPrefix,
|
|
25
|
+
});
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const createTransferMessage = (config, wallet) => ({
|
|
29
|
+
type: OperationType.TRANSFER,
|
|
30
|
+
address: wallet.address,
|
|
31
|
+
tro: {
|
|
32
|
+
tx: 'aa'.repeat(32),
|
|
33
|
+
txv: 'bb'.repeat(32),
|
|
34
|
+
in: 'cc'.repeat(32),
|
|
35
|
+
to: publicKeyToAddress(testKeyPair2.publicKey, config),
|
|
36
|
+
am: '00'.repeat(16),
|
|
37
|
+
is: 'dd'.repeat(64),
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const createConnectionManager = ({
|
|
42
|
+
preferredProtocol = 'v1',
|
|
43
|
+
sendSingleMessage = sinon.stub().resolves(ResultCode.OK),
|
|
44
|
+
sentCount = 0,
|
|
45
|
+
connectedValidators = [VALIDATOR_KEY],
|
|
46
|
+
} = {}) => ({
|
|
47
|
+
pickRandomConnectedValidator: sinon.stub().returns(VALIDATOR_KEY),
|
|
48
|
+
pickRandomValidator: sinon.stub().callsFake((validators) => validators[0] ?? null),
|
|
49
|
+
connectedValidators: sinon.stub().returns(connectedValidators),
|
|
50
|
+
getConnection: sinon.stub().returns({
|
|
51
|
+
protocolSession: {
|
|
52
|
+
preferredProtocol,
|
|
53
|
+
supportedProtocols: {
|
|
54
|
+
LEGACY: 'legacy',
|
|
55
|
+
V1: 'v1',
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}),
|
|
59
|
+
sendSingleMessage,
|
|
60
|
+
remove: sinon.stub(),
|
|
61
|
+
incrementSentCount: sinon.stub(),
|
|
62
|
+
getSentCount: sinon.stub().returns(sentCount),
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
hook('setup', () => {
|
|
66
|
+
sinon.stub(console, 'log');
|
|
67
|
+
sinon.stub(console, 'warn');
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
hook('teardown', () => {
|
|
71
|
+
sinon.restore();
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test('MessageOrchestrator.send returns false for unsupported protocol', async t => {
|
|
75
|
+
const config = createConfig(ENV.DEVELOPMENT, {});
|
|
76
|
+
const connectionManager = createConnectionManager({ preferredProtocol: 'unknown' });
|
|
77
|
+
const orchestrator = new MessageOrchestrator(connectionManager, { get: async () => null }, config);
|
|
78
|
+
const wallet = createWallet(config);
|
|
79
|
+
const message = createTransferMessage(config, wallet);
|
|
80
|
+
|
|
81
|
+
orchestrator.setWallet(wallet);
|
|
82
|
+
const result = await orchestrator.send(message, 0);
|
|
83
|
+
|
|
84
|
+
t.is(result, false);
|
|
85
|
+
t.is(connectionManager.sendSingleMessage.callCount, 0);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test('MessageOrchestrator.send V1 matrix: OK -> SUCCESS', async t => {
|
|
89
|
+
const config = createConfig(ENV.DEVELOPMENT, {});
|
|
90
|
+
const connectionManager = createConnectionManager({
|
|
91
|
+
sendSingleMessage: sinon.stub().resolves(ResultCode.OK),
|
|
92
|
+
sentCount: 0,
|
|
93
|
+
});
|
|
94
|
+
const orchestrator = new MessageOrchestrator(connectionManager, { get: async () => null }, config);
|
|
95
|
+
const wallet = createWallet(config);
|
|
96
|
+
const message = createTransferMessage(config, wallet);
|
|
97
|
+
|
|
98
|
+
orchestrator.setWallet(wallet);
|
|
99
|
+
const result = await orchestrator.send(message);
|
|
100
|
+
|
|
101
|
+
t.is(result, true);
|
|
102
|
+
t.is(connectionManager.incrementSentCount.callCount, 1);
|
|
103
|
+
t.is(connectionManager.remove.callCount, 0);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test('MessageOrchestrator.send V1 matrix: TIMEOUT -> ROTATE', async t => {
|
|
107
|
+
const config = createConfig(ENV.DEVELOPMENT, {});
|
|
108
|
+
const connectionManager = createConnectionManager({
|
|
109
|
+
sendSingleMessage: sinon.stub().resolves(ResultCode.TIMEOUT),
|
|
110
|
+
});
|
|
111
|
+
const orchestrator = new MessageOrchestrator(connectionManager, { get: async () => null }, config);
|
|
112
|
+
const wallet = createWallet(config);
|
|
113
|
+
const message = createTransferMessage(config, wallet);
|
|
114
|
+
|
|
115
|
+
orchestrator.setWallet(wallet);
|
|
116
|
+
const result = await orchestrator.send(message);
|
|
117
|
+
|
|
118
|
+
t.is(result, false);
|
|
119
|
+
t.is(connectionManager.sendSingleMessage.callCount, 1);
|
|
120
|
+
t.is(connectionManager.remove.callCount, 1);
|
|
121
|
+
t.is(connectionManager.incrementSentCount.callCount, 0);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test('MessageOrchestrator.send V1 matrix: TX_ALREADY_PENDING -> NO_ROTATE', async t => {
|
|
125
|
+
const config = createConfig(ENV.DEVELOPMENT, {});
|
|
126
|
+
const connectionManager = createConnectionManager({
|
|
127
|
+
sendSingleMessage: sinon.stub().resolves(ResultCode.TX_ALREADY_PENDING),
|
|
128
|
+
});
|
|
129
|
+
const orchestrator = new MessageOrchestrator(connectionManager, { get: async () => null }, config);
|
|
130
|
+
const wallet = createWallet(config);
|
|
131
|
+
const message = createTransferMessage(config, wallet);
|
|
132
|
+
|
|
133
|
+
orchestrator.setWallet(wallet);
|
|
134
|
+
const result = await orchestrator.send(message);
|
|
135
|
+
|
|
136
|
+
t.is(result, false);
|
|
137
|
+
t.is(connectionManager.sendSingleMessage.callCount, 1);
|
|
138
|
+
t.is(connectionManager.remove.callCount, 0);
|
|
139
|
+
t.is(connectionManager.incrementSentCount.callCount, 0);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test('MessageOrchestrator.send V1 matrix: unknown code -> UNDEFINED', async t => {
|
|
143
|
+
const config = createConfig(ENV.DEVELOPMENT, {});
|
|
144
|
+
const connectionManager = createConnectionManager({
|
|
145
|
+
sendSingleMessage: sinon.stub().resolves(99999),
|
|
146
|
+
});
|
|
147
|
+
const orchestrator = new MessageOrchestrator(connectionManager, { get: async () => null }, config);
|
|
148
|
+
const wallet = createWallet(config);
|
|
149
|
+
const message = createTransferMessage(config, wallet);
|
|
150
|
+
|
|
151
|
+
orchestrator.setWallet(wallet);
|
|
152
|
+
const result = await orchestrator.send(message);
|
|
153
|
+
|
|
154
|
+
t.is(result, false);
|
|
155
|
+
t.is(connectionManager.sendSingleMessage.callCount, 1);
|
|
156
|
+
t.is(connectionManager.remove.callCount, 1);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
test('MessageOrchestrator.send removes validator when threshold reached on success', async t => {
|
|
160
|
+
const config = createConfig(ENV.DEVELOPMENT, {});
|
|
161
|
+
const connectionManager = createConnectionManager({
|
|
162
|
+
sendSingleMessage: sinon.stub().resolves(ResultCode.OK),
|
|
163
|
+
sentCount: config.messageThreshold,
|
|
164
|
+
});
|
|
165
|
+
const orchestrator = new MessageOrchestrator(connectionManager, { get: async () => null }, config);
|
|
166
|
+
const wallet = createWallet(config);
|
|
167
|
+
const message = createTransferMessage(config, wallet);
|
|
168
|
+
|
|
169
|
+
orchestrator.setWallet(wallet);
|
|
170
|
+
const result = await orchestrator.send(message);
|
|
171
|
+
|
|
172
|
+
t.is(result, true);
|
|
173
|
+
t.is(connectionManager.incrementSentCount.callCount, 1);
|
|
174
|
+
t.is(connectionManager.remove.callCount, 1);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
test('MessageOrchestrator.send retries on ConnectionManagerError without removing validator', async t => {
|
|
178
|
+
const config = createConfig(ENV.DEVELOPMENT, { maxRetries: 2 });
|
|
179
|
+
const sendSingleMessage = sinon.stub();
|
|
180
|
+
sendSingleMessage.onFirstCall().rejects(new ConnectionManagerError('disconnected'));
|
|
181
|
+
sendSingleMessage.onSecondCall().resolves(ResultCode.OK);
|
|
182
|
+
|
|
183
|
+
const connectionManager = createConnectionManager({ sendSingleMessage, sentCount: 0 });
|
|
184
|
+
const orchestrator = new MessageOrchestrator(connectionManager, { get: async () => null }, config);
|
|
185
|
+
const wallet = createWallet(config);
|
|
186
|
+
const message = createTransferMessage(config, wallet);
|
|
187
|
+
|
|
188
|
+
orchestrator.setWallet(wallet);
|
|
189
|
+
const result = await orchestrator.send(message);
|
|
190
|
+
|
|
191
|
+
t.is(result, true);
|
|
192
|
+
t.is(sendSingleMessage.callCount, 2);
|
|
193
|
+
t.is(connectionManager.remove.callCount, 0);
|
|
194
|
+
t.is(connectionManager.incrementSentCount.callCount, 1);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
test('MessageOrchestrator.send retries on generic catch error with remove + retry', async t => {
|
|
198
|
+
const config = createConfig(ENV.DEVELOPMENT, { maxRetries: 2 });
|
|
199
|
+
const sendSingleMessage = sinon.stub();
|
|
200
|
+
sendSingleMessage.onFirstCall().rejects(new Error('response validation failed'));
|
|
201
|
+
sendSingleMessage.onSecondCall().resolves(ResultCode.OK);
|
|
202
|
+
|
|
203
|
+
const connectionManager = createConnectionManager({ sendSingleMessage, sentCount: 0 });
|
|
204
|
+
const orchestrator = new MessageOrchestrator(connectionManager, { get: async () => null }, config);
|
|
205
|
+
const wallet = createWallet(config);
|
|
206
|
+
const message = createTransferMessage(config, wallet);
|
|
207
|
+
|
|
208
|
+
orchestrator.setWallet(wallet);
|
|
209
|
+
const result = await orchestrator.send(message);
|
|
210
|
+
|
|
211
|
+
t.is(result, true);
|
|
212
|
+
t.is(sendSingleMessage.callCount, 2);
|
|
213
|
+
t.is(connectionManager.remove.callCount, 1);
|
|
214
|
+
t.is(connectionManager.incrementSentCount.callCount, 1);
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
test('MessageOrchestrator.send max retries guard returns false immediately', async t => {
|
|
218
|
+
const config = createConfig(ENV.DEVELOPMENT, { maxRetries: 1 });
|
|
219
|
+
const connectionManager = createConnectionManager({
|
|
220
|
+
sendSingleMessage: sinon.stub().resolves(ResultCode.OK),
|
|
221
|
+
});
|
|
222
|
+
const orchestrator = new MessageOrchestrator(connectionManager, { get: async () => null }, config);
|
|
223
|
+
const wallet = createWallet(config);
|
|
224
|
+
const message = createTransferMessage(config, wallet);
|
|
225
|
+
|
|
226
|
+
orchestrator.setWallet(wallet);
|
|
227
|
+
const result = await orchestrator.send(message, 2);
|
|
228
|
+
|
|
229
|
+
t.is(result, false);
|
|
230
|
+
t.is(connectionManager.pickRandomConnectedValidator.callCount, 0);
|
|
231
|
+
t.is(connectionManager.sendSingleMessage.callCount, 0);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
test('MessageOrchestrator.send timeout split: pending timeout rejection goes through catch and retries', async t => {
|
|
235
|
+
const config = createConfig(ENV.DEVELOPMENT, { maxRetries: 2 });
|
|
236
|
+
const sendSingleMessage = sinon.stub();
|
|
237
|
+
sendSingleMessage.onFirstCall().rejects(new V1TimeoutError('pending request timeout', false));
|
|
238
|
+
sendSingleMessage.onSecondCall().resolves(ResultCode.OK);
|
|
239
|
+
|
|
240
|
+
const connectionManager = createConnectionManager({ sendSingleMessage });
|
|
241
|
+
const orchestrator = new MessageOrchestrator(connectionManager, { get: async () => null }, config);
|
|
242
|
+
const wallet = createWallet(config);
|
|
243
|
+
const message = createTransferMessage(config, wallet);
|
|
244
|
+
|
|
245
|
+
orchestrator.setWallet(wallet);
|
|
246
|
+
const result = await orchestrator.send(message);
|
|
247
|
+
|
|
248
|
+
t.is(result, true);
|
|
249
|
+
t.is(sendSingleMessage.callCount, 2);
|
|
250
|
+
t.is(connectionManager.remove.callCount, 1);
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
test('MessageOrchestrator.send timeout split: TIMEOUT result code stays in then path and does not retry', async t => {
|
|
254
|
+
const config = createConfig(ENV.DEVELOPMENT, { maxRetries: 2 });
|
|
255
|
+
const sendSingleMessage = sinon.stub().resolves(ResultCode.TIMEOUT);
|
|
256
|
+
const connectionManager = createConnectionManager({ sendSingleMessage });
|
|
257
|
+
const orchestrator = new MessageOrchestrator(connectionManager, { get: async () => null }, config);
|
|
258
|
+
const wallet = createWallet(config);
|
|
259
|
+
const message = createTransferMessage(config, wallet);
|
|
260
|
+
|
|
261
|
+
orchestrator.setWallet(wallet);
|
|
262
|
+
const result = await orchestrator.send(message);
|
|
263
|
+
|
|
264
|
+
t.is(result, false);
|
|
265
|
+
t.is(sendSingleMessage.callCount, 1);
|
|
266
|
+
t.is(connectionManager.remove.callCount, 1);
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
test('MessageOrchestrator.send validation split: thrown validation error goes through catch', async t => {
|
|
270
|
+
const config = createConfig(ENV.DEVELOPMENT, { maxRetries: 2 });
|
|
271
|
+
const sendSingleMessage = sinon.stub();
|
|
272
|
+
sendSingleMessage.onFirstCall().rejects(new Error('validator response validation failed'));
|
|
273
|
+
sendSingleMessage.onSecondCall().resolves(ResultCode.OK);
|
|
274
|
+
|
|
275
|
+
const connectionManager = createConnectionManager({ sendSingleMessage });
|
|
276
|
+
const orchestrator = new MessageOrchestrator(connectionManager, { get: async () => null }, config);
|
|
277
|
+
const wallet = createWallet(config);
|
|
278
|
+
const message = createTransferMessage(config, wallet);
|
|
279
|
+
|
|
280
|
+
orchestrator.setWallet(wallet);
|
|
281
|
+
const result = await orchestrator.send(message);
|
|
282
|
+
|
|
283
|
+
t.is(result, true);
|
|
284
|
+
t.is(sendSingleMessage.callCount, 2);
|
|
285
|
+
t.is(connectionManager.remove.callCount, 1);
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
test('MessageOrchestrator.send validation split: non-OK result code stays in then and uses policy', async t => {
|
|
289
|
+
const config = createConfig(ENV.DEVELOPMENT, { maxRetries: 2 });
|
|
290
|
+
const sendSingleMessage = sinon.stub().resolves(ResultCode.SCHEMA_VALIDATION_FAILED);
|
|
291
|
+
const connectionManager = createConnectionManager({ sendSingleMessage });
|
|
292
|
+
const orchestrator = new MessageOrchestrator(connectionManager, { get: async () => null }, config);
|
|
293
|
+
const wallet = createWallet(config);
|
|
294
|
+
const message = createTransferMessage(config, wallet);
|
|
295
|
+
|
|
296
|
+
orchestrator.setWallet(wallet);
|
|
297
|
+
const result = await orchestrator.send(message);
|
|
298
|
+
|
|
299
|
+
t.is(result, false);
|
|
300
|
+
t.is(sendSingleMessage.callCount, 1);
|
|
301
|
+
t.is(connectionManager.remove.callCount, 1);
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
test('MessageOrchestrator.send legacy path succeeds and increments sent count', async t => {
|
|
305
|
+
const config = createConfig(ENV.DEVELOPMENT, { maxRetries: 0 });
|
|
306
|
+
const sendSingleMessage = sinon.stub().resolves(true);
|
|
307
|
+
const connectionManager = createConnectionManager({
|
|
308
|
+
preferredProtocol: 'legacy',
|
|
309
|
+
sendSingleMessage,
|
|
310
|
+
sentCount: 0,
|
|
311
|
+
});
|
|
312
|
+
const orchestrator = new MessageOrchestrator(connectionManager, { get: async () => null }, config);
|
|
313
|
+
const wallet = createWallet(config);
|
|
314
|
+
const message = createTransferMessage(config, wallet);
|
|
315
|
+
sinon.stub(orchestrator, 'waitForUnsignedState').resolves(true);
|
|
316
|
+
|
|
317
|
+
orchestrator.setWallet(wallet);
|
|
318
|
+
const result = await orchestrator.send(message);
|
|
319
|
+
|
|
320
|
+
t.is(result, true);
|
|
321
|
+
t.is(sendSingleMessage.callCount, 1);
|
|
322
|
+
t.is(connectionManager.incrementSentCount.callCount, 1);
|
|
323
|
+
t.is(connectionManager.remove.callCount, 0);
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
test('MessageOrchestrator.send legacy path false result removes validator and retries', async t => {
|
|
327
|
+
const config = createConfig(ENV.DEVELOPMENT, { maxRetries: 1 });
|
|
328
|
+
const sendSingleMessage = sinon.stub().resolves(true);
|
|
329
|
+
const connectionManager = createConnectionManager({
|
|
330
|
+
preferredProtocol: 'legacy',
|
|
331
|
+
sendSingleMessage,
|
|
332
|
+
});
|
|
333
|
+
const orchestrator = new MessageOrchestrator(connectionManager, { get: async () => null }, config);
|
|
334
|
+
const wallet = createWallet(config);
|
|
335
|
+
const message = createTransferMessage(config, wallet);
|
|
336
|
+
const waitStub = sinon.stub(orchestrator, 'waitForUnsignedState');
|
|
337
|
+
waitStub.onFirstCall().resolves(false);
|
|
338
|
+
waitStub.onSecondCall().resolves(true);
|
|
339
|
+
|
|
340
|
+
orchestrator.setWallet(wallet);
|
|
341
|
+
const result = await orchestrator.send(message);
|
|
342
|
+
|
|
343
|
+
t.is(result, true);
|
|
344
|
+
t.is(sendSingleMessage.callCount, 2);
|
|
345
|
+
t.is(connectionManager.remove.callCount, 1);
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
test('MessageOrchestrator.send legacy path catches send error and retries', async t => {
|
|
349
|
+
const config = createConfig(ENV.DEVELOPMENT, { maxRetries: 1 });
|
|
350
|
+
const sendSingleMessage = sinon.stub();
|
|
351
|
+
sendSingleMessage.onFirstCall().rejects(new Error('legacy send failed'));
|
|
352
|
+
sendSingleMessage.onSecondCall().resolves(true);
|
|
353
|
+
|
|
354
|
+
const connectionManager = createConnectionManager({
|
|
355
|
+
preferredProtocol: 'legacy',
|
|
356
|
+
sendSingleMessage,
|
|
357
|
+
});
|
|
358
|
+
const orchestrator = new MessageOrchestrator(connectionManager, { get: async () => null }, config);
|
|
359
|
+
const wallet = createWallet(config);
|
|
360
|
+
const message = createTransferMessage(config, wallet);
|
|
361
|
+
sinon.stub(orchestrator, 'waitForUnsignedState').resolves(true);
|
|
362
|
+
|
|
363
|
+
orchestrator.setWallet(wallet);
|
|
364
|
+
const result = await orchestrator.send(message);
|
|
365
|
+
|
|
366
|
+
t.is(result, true);
|
|
367
|
+
t.is(sendSingleMessage.callCount, 2);
|
|
368
|
+
t.is(connectionManager.remove.callCount, 0);
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
test('MessageOrchestrator.waitForUnsignedState returns true when state entry appears', async t => {
|
|
372
|
+
const clock = sinon.useFakeTimers({ now: 1 });
|
|
373
|
+
try {
|
|
374
|
+
const config = createConfig(ENV.DEVELOPMENT, {});
|
|
375
|
+
const state = {
|
|
376
|
+
get: sinon.stub()
|
|
377
|
+
.onFirstCall().resolves(null)
|
|
378
|
+
.onSecondCall().resolves({ tx: 'found' }),
|
|
379
|
+
};
|
|
380
|
+
const orchestrator = new MessageOrchestrator(createConnectionManager(), state, config);
|
|
381
|
+
|
|
382
|
+
const pending = orchestrator.waitForUnsignedState('tx-hash', 500);
|
|
383
|
+
await clock.tickAsync(450);
|
|
384
|
+
const result = await pending;
|
|
385
|
+
|
|
386
|
+
t.is(result, true);
|
|
387
|
+
t.ok(state.get.callCount >= 2);
|
|
388
|
+
} finally {
|
|
389
|
+
clock.restore();
|
|
390
|
+
}
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
test('MessageOrchestrator.waitForUnsignedState returns false on timeout', async t => {
|
|
394
|
+
const clock = sinon.useFakeTimers({ now: 1 });
|
|
395
|
+
try {
|
|
396
|
+
const config = createConfig(ENV.DEVELOPMENT, {});
|
|
397
|
+
const state = { get: sinon.stub().resolves(null) };
|
|
398
|
+
const orchestrator = new MessageOrchestrator(createConnectionManager(), state, config);
|
|
399
|
+
|
|
400
|
+
const pending = orchestrator.waitForUnsignedState('tx-hash', 400);
|
|
401
|
+
await clock.tickAsync(1000);
|
|
402
|
+
const result = await pending;
|
|
403
|
+
|
|
404
|
+
t.is(result, false);
|
|
405
|
+
t.ok(state.get.callCount >= 1);
|
|
406
|
+
} finally {
|
|
407
|
+
clock.restore();
|
|
408
|
+
}
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
test('MessageOrchestrator.send V1 avoids selecting validator with requester address when possible', async t => {
|
|
412
|
+
const config = createConfig(ENV.DEVELOPMENT, {});
|
|
413
|
+
const requesterValidatorKey = testKeyPair1.publicKey;
|
|
414
|
+
const otherValidatorKey = testKeyPair2.publicKey;
|
|
415
|
+
const sendSingleMessage = sinon.stub().resolves(ResultCode.OK);
|
|
416
|
+
|
|
417
|
+
const connectionManager = createConnectionManager({
|
|
418
|
+
sendSingleMessage,
|
|
419
|
+
connectedValidators: [requesterValidatorKey, otherValidatorKey],
|
|
420
|
+
});
|
|
421
|
+
connectionManager.getConnection = sinon.stub().returns({
|
|
422
|
+
protocolSession: {
|
|
423
|
+
preferredProtocol: 'v1',
|
|
424
|
+
supportedProtocols: {
|
|
425
|
+
LEGACY: 'legacy',
|
|
426
|
+
V1: 'v1',
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
const orchestrator = new MessageOrchestrator(connectionManager, { get: async () => null }, config);
|
|
432
|
+
const requesterAddress = publicKeyToAddress(requesterValidatorKey, config);
|
|
433
|
+
const wallet = createWallet(config);
|
|
434
|
+
const message = {
|
|
435
|
+
...createTransferMessage(config, wallet),
|
|
436
|
+
address: requesterAddress,
|
|
437
|
+
};
|
|
438
|
+
|
|
439
|
+
orchestrator.setWallet(wallet);
|
|
440
|
+
const result = await orchestrator.send(message);
|
|
441
|
+
|
|
442
|
+
t.is(result, true);
|
|
443
|
+
t.is(sendSingleMessage.callCount, 1);
|
|
444
|
+
t.is(sendSingleMessage.firstCall.args[1], otherValidatorKey);
|
|
445
|
+
});
|