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
|
@@ -13,11 +13,10 @@ import {
|
|
|
13
13
|
idToBuffer,
|
|
14
14
|
timestampToBuffer
|
|
15
15
|
} from '../../../../src/utils/buffer.js';
|
|
16
|
+
import { addressToBuffer } from '../../../../src/core/state/utils/address.js';
|
|
16
17
|
import { config } from '../../../helpers/config.js';
|
|
17
18
|
import { asAddress } from '../../../helpers/address.js';
|
|
18
19
|
import { testKeyPair1 } from '../../../fixtures/apply.fixtures.js';
|
|
19
|
-
import { v7 as uuidv7 } from 'uuid';
|
|
20
|
-
import { errorMessageIncludes } from '../../../helpers/regexHelper.js';
|
|
21
20
|
|
|
22
21
|
function createWallet() {
|
|
23
22
|
const keyPair = {
|
|
@@ -35,15 +34,64 @@ function uniqueResultCodes() {
|
|
|
35
34
|
return [...new Set(Object.values(NetworkResultCode))].sort((a, b) => a - b);
|
|
36
35
|
}
|
|
37
36
|
|
|
37
|
+
test('NetworkMessageDirector builds validator connection request and verifies signature', async t => {
|
|
38
|
+
const wallet = createWallet();
|
|
39
|
+
const director = new NetworkMessageDirector(new NetworkMessageBuilder(wallet, config));
|
|
40
|
+
|
|
41
|
+
const id = '1';
|
|
42
|
+
const caps = ['cap:b', 'cap:a'];
|
|
43
|
+
|
|
44
|
+
const payload = await director.buildValidatorConnectionRequest(id, wallet.address, caps);
|
|
45
|
+
t.is(payload.type, NetworkOperationType.VALIDATOR_CONNECTION_REQUEST);
|
|
46
|
+
t.is(payload.id, id);
|
|
47
|
+
t.alike(payload.capabilities, caps);
|
|
48
|
+
|
|
49
|
+
const msg = createMessage(
|
|
50
|
+
payload.type,
|
|
51
|
+
idToBuffer(payload.id),
|
|
52
|
+
timestampToBuffer(payload.timestamp),
|
|
53
|
+
addressToBuffer(wallet.address, config.addressPrefix),
|
|
54
|
+
payload.validator_connection_request.nonce,
|
|
55
|
+
encodeCapabilities(caps)
|
|
56
|
+
);
|
|
57
|
+
const hash = await PeerWallet.blake3(msg);
|
|
58
|
+
t.ok(wallet.verify(payload.validator_connection_request.signature, hash, wallet.publicKey));
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test('NetworkMessageDirector builds liveness request and verifies signature', async t => {
|
|
62
|
+
const wallet = createWallet();
|
|
63
|
+
const director = new NetworkMessageDirector(new NetworkMessageBuilder(wallet, config));
|
|
64
|
+
|
|
65
|
+
const id = '1';
|
|
66
|
+
const caps = ['cap:b', 'cap:a'];
|
|
67
|
+
const data = b4a.from('ping', 'utf8');
|
|
68
|
+
|
|
69
|
+
const payload = await director.buildLivenessRequest(id, data, caps);
|
|
70
|
+
t.is(payload.type, NetworkOperationType.LIVENESS_REQUEST);
|
|
71
|
+
t.is(payload.id, id);
|
|
72
|
+
t.alike(payload.capabilities, caps);
|
|
73
|
+
|
|
74
|
+
const msg = createMessage(
|
|
75
|
+
payload.type,
|
|
76
|
+
idToBuffer(payload.id),
|
|
77
|
+
timestampToBuffer(payload.timestamp),
|
|
78
|
+
payload.liveness_request.nonce,
|
|
79
|
+
encodeCapabilities(caps)
|
|
80
|
+
);
|
|
81
|
+
const hash = await PeerWallet.blake3(msg);
|
|
82
|
+
t.ok(wallet.verify(payload.liveness_request.signature, hash, wallet.publicKey));
|
|
83
|
+
});
|
|
84
|
+
|
|
38
85
|
test('NetworkMessageDirector iterates liveness response ResultCode values', async t => {
|
|
39
86
|
const wallet = createWallet();
|
|
40
87
|
const director = new NetworkMessageDirector(new NetworkMessageBuilder(wallet, config));
|
|
41
88
|
|
|
42
|
-
const id =
|
|
89
|
+
const id = '1';
|
|
43
90
|
const caps = ['cap:b', 'cap:a'];
|
|
91
|
+
const data = b4a.from('ping', 'utf8');
|
|
44
92
|
|
|
45
93
|
for (const code of uniqueResultCodes()) {
|
|
46
|
-
const payload = await director.buildLivenessResponse(id, caps, code);
|
|
94
|
+
const payload = await director.buildLivenessResponse(id, data, caps, code);
|
|
47
95
|
t.is(payload.type, NetworkOperationType.LIVENESS_RESPONSE);
|
|
48
96
|
t.is(payload.liveness_response.result, code);
|
|
49
97
|
|
|
@@ -67,7 +115,7 @@ test('NetworkMessageDirector builds broadcast transaction request and verifies s
|
|
|
67
115
|
const wallet = createWallet();
|
|
68
116
|
const director = new NetworkMessageDirector(new NetworkMessageBuilder(wallet, config));
|
|
69
117
|
|
|
70
|
-
const id =
|
|
118
|
+
const id = '1';
|
|
71
119
|
const data = b4a.from('deadbeef', 'hex');
|
|
72
120
|
const caps = ['cap:b', 'cap:a'];
|
|
73
121
|
|
|
@@ -96,36 +144,19 @@ test('NetworkMessageDirector iterates broadcast transaction response ResultCode
|
|
|
96
144
|
const wallet = createWallet();
|
|
97
145
|
const director = new NetworkMessageDirector(new NetworkMessageBuilder(wallet, config));
|
|
98
146
|
|
|
99
|
-
const id =
|
|
147
|
+
const id = '1';
|
|
100
148
|
const caps = ['cap:b', 'cap:a'];
|
|
101
|
-
const proof = b4a.from('deadbeef', 'hex');
|
|
102
|
-
const timestamp = Date.now();
|
|
103
|
-
const emptyProof = b4a.alloc(0);
|
|
104
149
|
|
|
105
150
|
for (const code of uniqueResultCodes()) {
|
|
106
|
-
const
|
|
107
|
-
const proofUnavailable = code === NetworkResultCode.TX_ACCEPTED_PROOF_UNAVAILABLE;
|
|
108
|
-
const responseProof = includeProof ? proof : emptyProof;
|
|
109
|
-
const responseTimestamp = includeProof || proofUnavailable ? timestamp : 0;
|
|
110
|
-
const payload = await director.buildBroadcastTransactionResponse(
|
|
111
|
-
id,
|
|
112
|
-
caps,
|
|
113
|
-
code,
|
|
114
|
-
responseProof,
|
|
115
|
-
responseTimestamp
|
|
116
|
-
);
|
|
151
|
+
const payload = await director.buildBroadcastTransactionResponse(id, caps, code);
|
|
117
152
|
t.is(payload.type, NetworkOperationType.BROADCAST_TRANSACTION_RESPONSE);
|
|
118
153
|
t.is(payload.broadcast_transaction_response.result, code);
|
|
119
|
-
t.alike(payload.broadcast_transaction_response.proof, responseProof);
|
|
120
|
-
t.is(payload.broadcast_transaction_response.timestamp, responseTimestamp);
|
|
121
154
|
|
|
122
155
|
const msg = createMessage(
|
|
123
156
|
payload.type,
|
|
124
157
|
idToBuffer(payload.id),
|
|
125
158
|
timestampToBuffer(payload.timestamp),
|
|
126
159
|
payload.broadcast_transaction_response.nonce,
|
|
127
|
-
responseProof,
|
|
128
|
-
timestampToBuffer(responseTimestamp),
|
|
129
160
|
safeWriteUInt32BE(code, 0),
|
|
130
161
|
encodeCapabilities(caps)
|
|
131
162
|
);
|
|
@@ -134,214 +165,37 @@ test('NetworkMessageDirector iterates broadcast transaction response ResultCode
|
|
|
134
165
|
|
|
135
166
|
const decoded = decodeV1networkOperation(encodeV1networkOperation(payload));
|
|
136
167
|
t.is(decoded.broadcast_transaction_response.result, code);
|
|
137
|
-
t.alike(decoded.broadcast_transaction_response.proof, responseProof);
|
|
138
|
-
t.is(decoded.broadcast_transaction_response.timestamp, responseTimestamp);
|
|
139
168
|
}
|
|
140
169
|
});
|
|
141
170
|
|
|
142
|
-
test('NetworkMessageDirector
|
|
143
|
-
const wallet = createWallet();
|
|
144
|
-
const director = new NetworkMessageDirector(new NetworkMessageBuilder(wallet, config));
|
|
145
|
-
|
|
146
|
-
const id = uuidv7();
|
|
147
|
-
const caps = ['cap:b', 'cap:a'];
|
|
148
|
-
const proof = b4a.from('deadbeef', 'hex');
|
|
149
|
-
const timestamp = Date.now();
|
|
150
|
-
|
|
151
|
-
const payload = await director.buildBroadcastTransactionResponse(
|
|
152
|
-
id,
|
|
153
|
-
caps,
|
|
154
|
-
NetworkResultCode.OK,
|
|
155
|
-
proof,
|
|
156
|
-
timestamp
|
|
157
|
-
);
|
|
158
|
-
|
|
159
|
-
t.alike(payload.broadcast_transaction_response.proof, proof);
|
|
160
|
-
t.is(payload.broadcast_transaction_response.timestamp, timestamp);
|
|
161
|
-
|
|
162
|
-
const msg = createMessage(
|
|
163
|
-
payload.type,
|
|
164
|
-
idToBuffer(payload.id),
|
|
165
|
-
timestampToBuffer(payload.timestamp),
|
|
166
|
-
payload.broadcast_transaction_response.nonce,
|
|
167
|
-
proof,
|
|
168
|
-
timestampToBuffer(timestamp),
|
|
169
|
-
safeWriteUInt32BE(NetworkResultCode.OK, 0),
|
|
170
|
-
encodeCapabilities(caps)
|
|
171
|
-
);
|
|
172
|
-
const hash = await PeerWallet.blake3(msg);
|
|
173
|
-
t.ok(wallet.verify(payload.broadcast_transaction_response.signature, hash, wallet.publicKey));
|
|
174
|
-
|
|
175
|
-
const decoded = decodeV1networkOperation(encodeV1networkOperation(payload));
|
|
176
|
-
t.alike(decoded.broadcast_transaction_response.proof, proof);
|
|
177
|
-
t.is(decoded.broadcast_transaction_response.timestamp, timestamp);
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
test('NetworkMessageDirector rejects OK response when proof is provided without timestamp', async t => {
|
|
181
|
-
const wallet = createWallet();
|
|
182
|
-
const director = new NetworkMessageDirector(new NetworkMessageBuilder(wallet, config));
|
|
183
|
-
|
|
184
|
-
const id = uuidv7();
|
|
185
|
-
const caps = ['cap:b', 'cap:a'];
|
|
186
|
-
const proof = b4a.from('deadbeef', 'hex');
|
|
187
|
-
|
|
188
|
-
await t.exception(
|
|
189
|
-
() => director.buildBroadcastTransactionResponse(
|
|
190
|
-
id,
|
|
191
|
-
caps,
|
|
192
|
-
NetworkResultCode.OK,
|
|
193
|
-
proof
|
|
194
|
-
),
|
|
195
|
-
errorMessageIncludes('Result code OK requires non-empty proof and timestamp > 0.')
|
|
196
|
-
);
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
test('NetworkMessageDirector rejects OK response when timestamp is provided without proof', async t => {
|
|
200
|
-
const wallet = createWallet();
|
|
201
|
-
const director = new NetworkMessageDirector(new NetworkMessageBuilder(wallet, config));
|
|
202
|
-
|
|
203
|
-
const id = uuidv7();
|
|
204
|
-
const caps = ['cap:b', 'cap:a'];
|
|
205
|
-
const timestamp = Date.now();
|
|
206
|
-
|
|
207
|
-
await t.exception(
|
|
208
|
-
() => director.buildBroadcastTransactionResponse(
|
|
209
|
-
id,
|
|
210
|
-
caps,
|
|
211
|
-
NetworkResultCode.OK,
|
|
212
|
-
null,
|
|
213
|
-
timestamp
|
|
214
|
-
),
|
|
215
|
-
errorMessageIncludes('Result code OK requires non-empty proof and timestamp > 0.')
|
|
216
|
-
);
|
|
217
|
-
});
|
|
218
|
-
|
|
219
|
-
test('NetworkMessageDirector allows TX_ACCEPTED_PROOF_UNAVAILABLE response with timestamp and empty proof', async t => {
|
|
171
|
+
test('NetworkMessageDirector iterates validator connection response ResultCode values', async t => {
|
|
220
172
|
const wallet = createWallet();
|
|
221
173
|
const director = new NetworkMessageDirector(new NetworkMessageBuilder(wallet, config));
|
|
222
174
|
|
|
223
|
-
const id =
|
|
175
|
+
const id = '1';
|
|
224
176
|
const caps = ['cap:b', 'cap:a'];
|
|
225
|
-
const
|
|
226
|
-
const emptyProof = b4a.alloc(0);
|
|
227
|
-
|
|
228
|
-
const payload = await director.buildBroadcastTransactionResponse(
|
|
229
|
-
id,
|
|
230
|
-
caps,
|
|
231
|
-
NetworkResultCode.TX_ACCEPTED_PROOF_UNAVAILABLE,
|
|
232
|
-
emptyProof,
|
|
233
|
-
timestamp
|
|
234
|
-
);
|
|
235
|
-
|
|
236
|
-
t.alike(payload.broadcast_transaction_response.proof, emptyProof);
|
|
237
|
-
t.is(payload.broadcast_transaction_response.timestamp, timestamp);
|
|
238
|
-
t.is(payload.broadcast_transaction_response.result, NetworkResultCode.TX_ACCEPTED_PROOF_UNAVAILABLE);
|
|
239
|
-
|
|
240
|
-
const msg = createMessage(
|
|
241
|
-
payload.type,
|
|
242
|
-
idToBuffer(payload.id),
|
|
243
|
-
timestampToBuffer(payload.timestamp),
|
|
244
|
-
payload.broadcast_transaction_response.nonce,
|
|
245
|
-
emptyProof,
|
|
246
|
-
timestampToBuffer(timestamp),
|
|
247
|
-
safeWriteUInt32BE(NetworkResultCode.TX_ACCEPTED_PROOF_UNAVAILABLE, 0),
|
|
248
|
-
encodeCapabilities(caps)
|
|
249
|
-
);
|
|
250
|
-
const hash = await PeerWallet.blake3(msg);
|
|
251
|
-
t.ok(wallet.verify(payload.broadcast_transaction_response.signature, hash, wallet.publicKey));
|
|
252
|
-
|
|
253
|
-
const decoded = decodeV1networkOperation(encodeV1networkOperation(payload));
|
|
254
|
-
t.is(decoded.broadcast_transaction_response.timestamp, timestamp);
|
|
255
|
-
t.is(decoded.broadcast_transaction_response.result, NetworkResultCode.TX_ACCEPTED_PROOF_UNAVAILABLE);
|
|
256
|
-
});
|
|
257
|
-
|
|
258
|
-
test('NetworkMessageDirector rejects OK response when proof and timestamp are both missing', async t => {
|
|
259
|
-
const wallet = createWallet();
|
|
260
|
-
const director = new NetworkMessageDirector(new NetworkMessageBuilder(wallet, config));
|
|
261
|
-
|
|
262
|
-
const id = uuidv7();
|
|
263
|
-
const caps = ['cap:b', 'cap:a'];
|
|
264
|
-
|
|
265
|
-
await t.exception(
|
|
266
|
-
() => director.buildBroadcastTransactionResponse(
|
|
267
|
-
id,
|
|
268
|
-
caps,
|
|
269
|
-
NetworkResultCode.OK
|
|
270
|
-
),
|
|
271
|
-
errorMessageIncludes('Result code OK requires non-empty proof and timestamp > 0.')
|
|
272
|
-
);
|
|
273
|
-
});
|
|
274
|
-
|
|
275
|
-
test('NetworkMessageDirector rejects TX_ACCEPTED_PROOF_UNAVAILABLE response when timestamp is missing', async t => {
|
|
276
|
-
const wallet = createWallet();
|
|
277
|
-
const director = new NetworkMessageDirector(new NetworkMessageBuilder(wallet, config));
|
|
278
|
-
|
|
279
|
-
const id = uuidv7();
|
|
280
|
-
const caps = ['cap:b', 'cap:a'];
|
|
281
|
-
|
|
282
|
-
await t.exception(
|
|
283
|
-
() => director.buildBroadcastTransactionResponse(
|
|
284
|
-
id,
|
|
285
|
-
caps,
|
|
286
|
-
NetworkResultCode.TX_ACCEPTED_PROOF_UNAVAILABLE
|
|
287
|
-
),
|
|
288
|
-
errorMessageIncludes('Result code TX_ACCEPTED_PROOF_UNAVAILABLE requires timestamp > 0.')
|
|
289
|
-
);
|
|
290
|
-
});
|
|
177
|
+
const otherAddress = asAddress('36fdaf941de4afe602cbb1e2f56dc582466ef23fad1da55c09fd6dd841cbd117');
|
|
291
178
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
const director = new NetworkMessageDirector(new NetworkMessageBuilder(wallet, config));
|
|
295
|
-
|
|
296
|
-
const id = uuidv7();
|
|
297
|
-
const caps = ['cap:b', 'cap:a'];
|
|
298
|
-
|
|
299
|
-
await t.exception(
|
|
300
|
-
() => director.buildBroadcastTransactionResponse(
|
|
301
|
-
id,
|
|
302
|
-
caps,
|
|
303
|
-
NetworkResultCode.TX_ACCEPTED_PROOF_UNAVAILABLE,
|
|
304
|
-
b4a.from('deadbeef', 'hex'),
|
|
305
|
-
Date.now()
|
|
306
|
-
),
|
|
307
|
-
errorMessageIncludes('Result code TX_ACCEPTED_PROOF_UNAVAILABLE requires empty proof.')
|
|
308
|
-
);
|
|
309
|
-
});
|
|
310
|
-
|
|
311
|
-
test('NetworkMessageDirector rejects non-OK response when proof is non-empty', async t => {
|
|
312
|
-
const wallet = createWallet();
|
|
313
|
-
const director = new NetworkMessageDirector(new NetworkMessageBuilder(wallet, config));
|
|
314
|
-
|
|
315
|
-
const id = uuidv7();
|
|
316
|
-
const caps = ['cap:b', 'cap:a'];
|
|
317
|
-
|
|
318
|
-
await t.exception(
|
|
319
|
-
() => director.buildBroadcastTransactionResponse(
|
|
179
|
+
for (const code of uniqueResultCodes()) {
|
|
180
|
+
const payload = await director.buildValidatorConnectionResponse(
|
|
320
181
|
id,
|
|
182
|
+
otherAddress,
|
|
321
183
|
caps,
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
)
|
|
326
|
-
errorMessageIncludes('Non-OK result code requires empty proof.')
|
|
327
|
-
);
|
|
328
|
-
});
|
|
329
|
-
|
|
330
|
-
test('NetworkMessageDirector rejects non-OK response with timestamp > 0 unless proof is unavailable', async t => {
|
|
331
|
-
const wallet = createWallet();
|
|
332
|
-
const director = new NetworkMessageDirector(new NetworkMessageBuilder(wallet, config));
|
|
333
|
-
|
|
334
|
-
const id = uuidv7();
|
|
335
|
-
const caps = ['cap:b', 'cap:a'];
|
|
184
|
+
code
|
|
185
|
+
);
|
|
186
|
+
t.is(payload.type, NetworkOperationType.VALIDATOR_CONNECTION_RESPONSE);
|
|
187
|
+
t.is(payload.validator_connection_response.result, code);
|
|
336
188
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
id,
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
189
|
+
const msg = createMessage(
|
|
190
|
+
payload.type,
|
|
191
|
+
idToBuffer(payload.id),
|
|
192
|
+
timestampToBuffer(payload.timestamp),
|
|
193
|
+
addressToBuffer(otherAddress, config.addressPrefix),
|
|
194
|
+
payload.validator_connection_response.nonce,
|
|
195
|
+
safeWriteUInt32BE(code, 0),
|
|
196
|
+
encodeCapabilities(caps)
|
|
197
|
+
);
|
|
198
|
+
const hash = await PeerWallet.blake3(msg);
|
|
199
|
+
t.ok(wallet.verify(payload.validator_connection_response.signature, hash, wallet.publicKey));
|
|
200
|
+
}
|
|
347
201
|
});
|
|
@@ -12,7 +12,11 @@ const hex = (value, bytes) => value.repeat(bytes);
|
|
|
12
12
|
const toBuf = value => b4a.from(value, 'hex');
|
|
13
13
|
|
|
14
14
|
async function createWallet(mnemonic) {
|
|
15
|
-
const wallet = new PeerWallet({
|
|
15
|
+
const wallet = new PeerWallet({
|
|
16
|
+
mnemonic,
|
|
17
|
+
networkPrefix: config.addressPrefix,
|
|
18
|
+
derivationPath: config.derivationPath
|
|
19
|
+
});
|
|
16
20
|
await wallet.ready;
|
|
17
21
|
return wallet;
|
|
18
22
|
}
|
|
@@ -12,7 +12,11 @@ import { isAddressValid } from '../../../../src/core/state/utils/address.js';
|
|
|
12
12
|
const hex = (value, bytes) => value.repeat(bytes);
|
|
13
13
|
|
|
14
14
|
async function createWallet(mnemonic) {
|
|
15
|
-
const wallet = new PeerWallet({
|
|
15
|
+
const wallet = new PeerWallet({
|
|
16
|
+
mnemonic,
|
|
17
|
+
networkPrefix: config.addressPrefix,
|
|
18
|
+
derivationPath: config.derivationPath
|
|
19
|
+
});
|
|
16
20
|
await wallet.ready;
|
|
17
21
|
return wallet;
|
|
18
22
|
}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import sinon from "sinon";
|
|
2
|
+
import { hook, test } from 'brittle'
|
|
3
|
+
import { default as EventEmitter } from "bare-events"
|
|
4
|
+
import { testKeyPair1, testKeyPair2, testKeyPair3, testKeyPair4, testKeyPair5, testKeyPair6, testKeyPair7, testKeyPair8, testKeyPair9 } from "../../fixtures/apply.fixtures.js";
|
|
5
|
+
import ConnectionManager from "../../../src/core/network/services/ConnectionManager.js";
|
|
6
|
+
import { tick } from "../../helpers/setupApplyTests.js";
|
|
7
|
+
import b4a from 'b4a'
|
|
8
|
+
import { createConfig, ENV } from "../../../src/config/env.js";
|
|
9
|
+
|
|
10
|
+
const createConnection = (key) => {
|
|
11
|
+
const emitter = new EventEmitter()
|
|
12
|
+
emitter.protocolSession = {
|
|
13
|
+
has: (name) => name === 'legacy',
|
|
14
|
+
send: sinon.stub().resolves(),
|
|
15
|
+
};
|
|
16
|
+
emitter.connected = true
|
|
17
|
+
emitter.remotePublicKey = b4a.from(key, 'hex')
|
|
18
|
+
|
|
19
|
+
return { key: b4a.from(key, 'hex'), connection: emitter }
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const makeManager = (maxValidators = 6, conns = connections) => {
|
|
23
|
+
const merged = createConfig(ENV.DEVELOPMENT, { maxValidators })
|
|
24
|
+
const connectionManager = new ConnectionManager(merged)
|
|
25
|
+
|
|
26
|
+
conns.forEach(({ key, connection }) => {
|
|
27
|
+
connectionManager.addValidator(key, connection)
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
return connectionManager
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const reset = () => {
|
|
34
|
+
sinon.restore()
|
|
35
|
+
connections.forEach(connection => {
|
|
36
|
+
connection.connection.protocolSession.send.resetHistory()
|
|
37
|
+
})
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
let connections
|
|
41
|
+
hook('Initialize state', async () => {
|
|
42
|
+
connections = [
|
|
43
|
+
createConnection(testKeyPair1.publicKey),
|
|
44
|
+
createConnection(testKeyPair2.publicKey),
|
|
45
|
+
createConnection(testKeyPair3.publicKey),
|
|
46
|
+
createConnection(testKeyPair4.publicKey),
|
|
47
|
+
]
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('ConnectionManager', () => {
|
|
51
|
+
test('addValidator', async t => {
|
|
52
|
+
test('adds a validator', async t => {
|
|
53
|
+
reset()
|
|
54
|
+
const connectionManager = makeManager()
|
|
55
|
+
t.is(connectionManager.connectionCount(), connections.length, 'should have the same length')
|
|
56
|
+
const data = createConnection(testKeyPair5.publicKey)
|
|
57
|
+
connectionManager.addValidator(data.key, data.connection)
|
|
58
|
+
t.is(connectionManager.connectionCount(), connections.length + 1, 'should have the same length')
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
test('dont surpass maxConnections', async t => {
|
|
62
|
+
reset()
|
|
63
|
+
const maxConnections = 5
|
|
64
|
+
const connectionManager = makeManager(maxConnections)
|
|
65
|
+
t.is(connectionManager.connectionCount(), connections.length, 'should have the same length')
|
|
66
|
+
|
|
67
|
+
const toAdd = createConnection(testKeyPair5.publicKey)
|
|
68
|
+
connectionManager.addValidator(toAdd.key, toAdd.connection)
|
|
69
|
+
t.is(connectionManager.connectionCount(), maxConnections, 'should match the max connections')
|
|
70
|
+
|
|
71
|
+
const toNotAdd = createConnection(testKeyPair6.publicKey)
|
|
72
|
+
connectionManager.addValidator(toNotAdd.key, toNotAdd.connection)
|
|
73
|
+
t.is(connectionManager.connectionCount(), maxConnections, 'should not increase length')
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
test('does not add new validator when pool is full', async t => {
|
|
77
|
+
reset()
|
|
78
|
+
const maxConnections = 2
|
|
79
|
+
const localConnections = [
|
|
80
|
+
createConnection(testKeyPair1.publicKey),
|
|
81
|
+
createConnection(testKeyPair2.publicKey),
|
|
82
|
+
]
|
|
83
|
+
|
|
84
|
+
const connectionManager = makeManager(maxConnections)
|
|
85
|
+
localConnections.forEach(({ key, connection }) => {
|
|
86
|
+
connectionManager.addValidator(key, connection)
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
t.is(connectionManager.connectionCount(), maxConnections, 'pool should be full')
|
|
90
|
+
|
|
91
|
+
const newConn = createConnection(testKeyPair3.publicKey)
|
|
92
|
+
connectionManager.addValidator(newConn.key, newConn.connection)
|
|
93
|
+
|
|
94
|
+
t.is(connectionManager.connectionCount(), maxConnections, 'should stay at max size')
|
|
95
|
+
t.not(connectionManager.connected(newConn.key), 'new validator should not be in the pool')
|
|
96
|
+
|
|
97
|
+
const remainingOld = localConnections.filter(c => connectionManager.connected(c.key)).length
|
|
98
|
+
t.is(remainingOld, 2, 'all of the old validators should remain')
|
|
99
|
+
})
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
test('connected', async t => {
|
|
103
|
+
test('true', async t => {
|
|
104
|
+
reset()
|
|
105
|
+
const connectionManager = makeManager()
|
|
106
|
+
connections.forEach(con => {
|
|
107
|
+
t.ok(connectionManager.connected(con.key), 'should respond true')
|
|
108
|
+
})
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
test('false', async t => {
|
|
112
|
+
reset()
|
|
113
|
+
const connectionManager = makeManager()
|
|
114
|
+
t.ok(!connectionManager.connected(testKeyPair6.publicKey), 'should respond false')
|
|
115
|
+
})
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
test('send', async t => {
|
|
119
|
+
test('triggers send on messenger', async t => {
|
|
120
|
+
reset()
|
|
121
|
+
const connectionManager = makeManager()
|
|
122
|
+
|
|
123
|
+
const target = connectionManager.send([1,2,3,4])
|
|
124
|
+
|
|
125
|
+
const totalCalls = connections.reduce((sum, con) => sum + con.connection.protocolSession.send.callCount, 0)
|
|
126
|
+
t.is(totalCalls, 1, 'should send to exactly one validator')
|
|
127
|
+
t.ok(target, 'should return a target public key')
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
test('does not throw on individual send errors', async t => {
|
|
131
|
+
reset()
|
|
132
|
+
const errorConnections = [
|
|
133
|
+
createConnection(testKeyPair7.publicKey),
|
|
134
|
+
createConnection(testKeyPair8.publicKey),
|
|
135
|
+
]
|
|
136
|
+
|
|
137
|
+
errorConnections.forEach(con => {
|
|
138
|
+
con.connection.protocolSession.send = sinon.stub().throws(new Error())
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
const connectionManager = makeManager(5, errorConnections)
|
|
142
|
+
|
|
143
|
+
t.is(errorConnections.length, 2, 'should have two connections')
|
|
144
|
+
connectionManager.send([1,2,3,4])
|
|
145
|
+
t.ok(true, 'send should not throw even if individual sends fail')
|
|
146
|
+
})
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
test('on close', async t => {
|
|
150
|
+
test('removes from list', async t => {
|
|
151
|
+
reset()
|
|
152
|
+
const connectionManager = makeManager()
|
|
153
|
+
|
|
154
|
+
const connectionCount = connectionManager.connectionCount()
|
|
155
|
+
|
|
156
|
+
connections[1].connection.connected = false
|
|
157
|
+
connections[1].connection.emit('close')
|
|
158
|
+
await tick()
|
|
159
|
+
t.is(connectionCount, connectionManager.connectionCount() + 1, 'first on the list should have been called')
|
|
160
|
+
})
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
test('remove', async t => {
|
|
164
|
+
test('removes a validator by public key', async t => {
|
|
165
|
+
reset()
|
|
166
|
+
const connectionManager = makeManager()
|
|
167
|
+
const previousCount = connectionManager.connectionCount()
|
|
168
|
+
const lastValidator = connections.shift()
|
|
169
|
+
|
|
170
|
+
t.ok(connectionManager.connected(lastValidator.key), 'should be connected')
|
|
171
|
+
connectionManager.remove(lastValidator.key)
|
|
172
|
+
|
|
173
|
+
t.is(connectionManager.connectionCount(), previousCount - 1, 'should reduce the connection count')
|
|
174
|
+
t.ok(!connectionManager.connected(lastValidator.key), 'should be connected')
|
|
175
|
+
})
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
test('on close', async t => {
|
|
179
|
+
test('removes from list', async t => {
|
|
180
|
+
reset()
|
|
181
|
+
const connectionManager = makeManager()
|
|
182
|
+
|
|
183
|
+
const connectionCount = connectionManager.connectionCount()
|
|
184
|
+
|
|
185
|
+
connections[1].connection.connected = false
|
|
186
|
+
connections[1].connection.emit('close')
|
|
187
|
+
await tick()
|
|
188
|
+
t.is(connectionCount, connectionManager.connectionCount() + 1, 'first on the list should have been called')
|
|
189
|
+
})
|
|
190
|
+
})
|
|
191
|
+
})
|
|
@@ -2,11 +2,8 @@ import { default as test } from 'brittle';
|
|
|
2
2
|
|
|
3
3
|
async function runNetworkModuleTests() {
|
|
4
4
|
test.pause();
|
|
5
|
-
await import('./
|
|
5
|
+
await import('./ConnectionManager.test.js');
|
|
6
6
|
await import('./NetworkWalletFactory.test.js');
|
|
7
|
-
await import('./ProtocolSession.test.js');
|
|
8
|
-
await import('./services/services.test.js');
|
|
9
|
-
await import('./v1/v1.test.js');
|
|
10
7
|
test.resume();
|
|
11
8
|
}
|
|
12
9
|
|
package/tests/unit/unit.test.js
CHANGED
|
@@ -4,10 +4,10 @@ import { default as test } from 'brittle';
|
|
|
4
4
|
|
|
5
5
|
async function runTests() {
|
|
6
6
|
test.pause();
|
|
7
|
-
await import('./utils/utils.test.js');
|
|
8
|
-
await import('./messages/messages.test.js');
|
|
9
7
|
await import('./network/networkModule.test.js')
|
|
10
8
|
await import('./state/stateModule.test.js');
|
|
9
|
+
await import('./utils/utils.test.js');
|
|
10
|
+
await import('./messages/messages.test.js');
|
|
11
11
|
test.resume();
|
|
12
12
|
}
|
|
13
13
|
|
|
@@ -30,9 +30,9 @@ hook('Initialize dummy whitelist files', async t => {
|
|
|
30
30
|
// Edge: large file
|
|
31
31
|
let large = '';
|
|
32
32
|
const randomAddress = async () => {
|
|
33
|
-
const wallet = new PeerWallet();
|
|
33
|
+
const wallet = new PeerWallet({ derivationPath: config.derivationPath });
|
|
34
34
|
await wallet.ready;
|
|
35
|
-
await wallet.generateKeyPair();
|
|
35
|
+
await wallet.generateKeyPair(undefined, config.derivationPath);
|
|
36
36
|
return wallet.address;
|
|
37
37
|
};
|
|
38
38
|
for (let i = 0; i < 1000; i++) {
|
|
@@ -47,9 +47,9 @@ hook('Initialize dummy balance files', async t => {
|
|
|
47
47
|
// Edge: large file
|
|
48
48
|
let large = '';
|
|
49
49
|
const randomAddress = async () => {
|
|
50
|
-
const wallet = new PeerWallet();
|
|
50
|
+
const wallet = new PeerWallet({ derivationPath: config.derivationPath });
|
|
51
51
|
await wallet.ready;
|
|
52
|
-
await wallet.generateKeyPair();
|
|
52
|
+
await wallet.generateKeyPair(undefined, config.derivationPath);
|
|
53
53
|
return wallet.address;
|
|
54
54
|
}
|
|
55
55
|
for (let i = 0; i < 1000; i++) {
|
|
@@ -85,8 +85,8 @@ test('Decode throws on buffer with unknown wire type (skip case)', t => {
|
|
|
85
85
|
const bufWithWire7 = b4a.from([0x0F]);
|
|
86
86
|
try {
|
|
87
87
|
applyOperations.Operation.decode(bufWithWire7);
|
|
88
|
-
t.fail('Expected decode to throw on unknown wire type');
|
|
89
88
|
} catch (err) {
|
|
89
|
+
console.error('Caught error:', err);
|
|
90
90
|
t.ok(err instanceof Error && err.message.includes('Could not decode varint'), 'Should throw an error instance to be thrown for unknown wire type');
|
|
91
91
|
}
|
|
92
92
|
});
|
|
@@ -242,6 +242,8 @@ test('Protobuf encode/decode is order-independent for all operation types', t =>
|
|
|
242
242
|
|
|
243
243
|
test('encodeV1networkOperation/decodeV1networkOperation roundtrip for network v1 payloads', t => {
|
|
244
244
|
const payloadsHashMap = new Map([
|
|
245
|
+
['validatorConnectionRequest', networkV1Fixtures.payloadValidatorConnectionRequest],
|
|
246
|
+
['validatorConnectionResponse', networkV1Fixtures.payloadValidatorConnectionResponse],
|
|
245
247
|
['livenessRequest', networkV1Fixtures.payloadLivenessRequest],
|
|
246
248
|
['livenessResponse', networkV1Fixtures.payloadLivenessResponse],
|
|
247
249
|
['broadcastTransactionRequest', networkV1Fixtures.payloadBroadcastTransactionRequest],
|
|
@@ -252,7 +254,7 @@ test('encodeV1networkOperation/decodeV1networkOperation roundtrip for network v1
|
|
|
252
254
|
const encoded = encodeV1networkOperation(payload);
|
|
253
255
|
const decoded = decodeV1networkOperation(encoded);
|
|
254
256
|
t.ok(b4a.isBuffer(encoded) && encoded.length > 0, `Payload ${key} encodes to a non-empty buffer`);
|
|
255
|
-
t.
|
|
257
|
+
t.ok(JSON.stringify(decoded) === JSON.stringify(payload), `Payload ${key} decodes back correctly`);
|
|
256
258
|
}
|
|
257
259
|
});
|
|
258
260
|
|
|
@@ -6,7 +6,6 @@ async function runTests() {
|
|
|
6
6
|
test.pause();
|
|
7
7
|
|
|
8
8
|
await import('./check/check.test.js');
|
|
9
|
-
await import('./deepEqualApplyPayload/deepEqualApplyPayload.test.js');
|
|
10
9
|
await import('./protobuf/operationHelpers.test.js');
|
|
11
10
|
await import('./helpers/helpers.test.js');
|
|
12
11
|
await import('./fileUtils/readAddressesFromWhitelistFile.test.js');
|