trac-msb 0.2.9 → 0.2.11
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/CODE_OF_CONDUCT.md +128 -0
- package/README.md +33 -18
- package/docker-compose.yml +1 -0
- package/docs/trac_network_http_api.openapi.yaml +889 -0
- package/msb.mjs +4 -21
- package/package.json +16 -12
- 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/handlers.js +163 -90
- package/rpc/routes/v1.js +3 -1
- package/rpc/rpc_server.js +3 -3
- package/rpc/rpc_services.js +45 -31
- package/rpc/utils/helpers.js +82 -51
- package/scripts/generate-protobufs.js +37 -12
- package/src/config/args.js +46 -0
- package/src/config/config.js +99 -5
- package/src/config/env.js +86 -7
- package/src/core/network/Network.js +79 -46
- 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 +26 -20
- package/src/core/network/protocols/{shared/handlers/base/BaseOperationHandler.js → legacy/handlers/BaseStateOperationHandler.js} +25 -15
- 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} +20 -13
- package/src/core/network/protocols/{shared/handlers/SubnetworkOperationHandler.js → legacy/handlers/LegacySubnetworkOperationHandler.js} +29 -18
- package/src/core/network/protocols/{shared/handlers/TransferOperationHandler.js → legacy/handlers/LegacyTransferOperationHandler.js} +18 -12
- package/src/core/network/protocols/legacy/validators/base/BaseResponse.js +1 -1
- 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 +147 -95
- package/src/core/network/services/MessageOrchestrator.js +152 -28
- 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 +133 -22
- package/src/core/network/services/TransactionRateLimiterService.js +57 -42
- package/src/core/network/services/ValidatorHealthCheckService.js +127 -0
- package/src/core/network/services/ValidatorObserverService.js +23 -32
- package/src/core/state/State.js +72 -22
- package/src/index.js +8 -5
- package/src/messages/network/v1/NetworkMessageBuilder.js +61 -81
- package/src/messages/network/v1/NetworkMessageDirector.js +16 -50
- package/src/messages/state/ApplyStateMessageBuilder.js +1 -1
- package/src/utils/Scheduler.js +0 -8
- package/src/utils/check.js +1 -1
- package/src/utils/constants.js +68 -19
- package/src/utils/deepEqualApplyPayload.js +40 -0
- package/src/utils/fileUtils.js +13 -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/src/utils/type.js +26 -0
- package/tests/acceptance/v1/account/account.test.mjs +8 -2
- package/tests/acceptance/v1/balance/balance.test.mjs +1 -2
- package/tests/acceptance/v1/broadcast-transaction/broadcast-transaction.test.mjs +26 -30
- package/tests/acceptance/v1/health/health.test.mjs +33 -0
- package/tests/acceptance/v1/rpc.test.mjs +3 -2
- package/tests/acceptance/v1/tx/tx.test.mjs +50 -17
- package/tests/acceptance/v1/tx-details/tx-details.test.mjs +60 -18
- package/tests/fixtures/check.fixtures.js +33 -32
- package/tests/fixtures/networkV1.fixtures.js +2 -27
- package/tests/fixtures/protobuf.fixtures.js +33 -32
- package/tests/helpers/StateNetworkFactory.js +2 -2
- package/tests/helpers/address.js +6 -0
- package/tests/helpers/autobaseTestHelpers.js +2 -1
- package/tests/helpers/config.js +2 -1
- package/tests/helpers/setupApplyTests.js +6 -10
- package/tests/helpers/transactionPayloads.mjs +2 -2
- package/tests/unit/messages/network/NetworkMessageBuilder.test.js +241 -81
- package/tests/unit/messages/network/NetworkMessageDirector.test.js +225 -81
- 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/fileUtils/readAddressesFromWhitelistFile.test.js +4 -3
- package/tests/unit/utils/fileUtils/readBalanceMigrationFile.test.js +3 -2
- package/tests/unit/utils/migrationUtils/validateAddressFromIncomingFile.test.js +3 -2
- package/tests/unit/utils/protobuf/operationHelpers.test.js +2 -4
- package/tests/unit/utils/type/type.test.js +25 -0
- package/tests/unit/utils/utils.test.js +2 -0
- package/.github/workflows/acceptance-tests.yml +0 -42
- package/.github/workflows/publish.yml +0 -33
- package/.github/workflows/unit-tests.yml +0 -40
- 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
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { test } from 'brittle';
|
|
2
2
|
import b4a from 'b4a';
|
|
3
3
|
import PeerWallet from 'trac-wallet';
|
|
4
|
-
import { TRAC_NETWORK_MSB_MAINNET_PREFIX } from 'trac-wallet/constants.js';
|
|
5
|
-
|
|
6
4
|
import NetworkWalletFactory from '../../../../src/core/network/identity/NetworkWalletFactory.js';
|
|
7
5
|
import NetworkMessageDirector from '../../../../src/messages/network/v1/NetworkMessageDirector.js';
|
|
8
6
|
import NetworkMessageBuilder from '../../../../src/messages/network/v1/NetworkMessageBuilder.js';
|
|
@@ -15,9 +13,11 @@ import {
|
|
|
15
13
|
idToBuffer,
|
|
16
14
|
timestampToBuffer
|
|
17
15
|
} from '../../../../src/utils/buffer.js';
|
|
18
|
-
import { addressToBuffer } from '../../../../src/core/state/utils/address.js';
|
|
19
16
|
import { config } from '../../../helpers/config.js';
|
|
17
|
+
import { asAddress } from '../../../helpers/address.js';
|
|
20
18
|
import { testKeyPair1 } from '../../../fixtures/apply.fixtures.js';
|
|
19
|
+
import { v7 as uuidv7 } from 'uuid';
|
|
20
|
+
import { errorMessageIncludes } from '../../../helpers/regexHelper.js';
|
|
21
21
|
|
|
22
22
|
function createWallet() {
|
|
23
23
|
const keyPair = {
|
|
@@ -27,7 +27,7 @@ function createWallet() {
|
|
|
27
27
|
return NetworkWalletFactory.provide({
|
|
28
28
|
enableWallet: false,
|
|
29
29
|
keyPair,
|
|
30
|
-
networkPrefix:
|
|
30
|
+
networkPrefix: config.addressPrefix
|
|
31
31
|
});
|
|
32
32
|
}
|
|
33
33
|
|
|
@@ -35,64 +35,15 @@ function uniqueResultCodes() {
|
|
|
35
35
|
return [...new Set(Object.values(NetworkResultCode))].sort((a, b) => a - b);
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
test('NetworkMessageDirector builds validator connection request and verifies signature', async t => {
|
|
39
|
-
const wallet = createWallet();
|
|
40
|
-
const director = new NetworkMessageDirector(new NetworkMessageBuilder(wallet, config));
|
|
41
|
-
|
|
42
|
-
const id = '1';
|
|
43
|
-
const caps = ['cap:b', 'cap:a'];
|
|
44
|
-
|
|
45
|
-
const payload = await director.buildValidatorConnectionRequest(id, wallet.address, caps);
|
|
46
|
-
t.is(payload.type, NetworkOperationType.VALIDATOR_CONNECTION_REQUEST);
|
|
47
|
-
t.is(payload.id, id);
|
|
48
|
-
t.alike(payload.capabilities, caps);
|
|
49
|
-
|
|
50
|
-
const msg = createMessage(
|
|
51
|
-
payload.type,
|
|
52
|
-
idToBuffer(payload.id),
|
|
53
|
-
timestampToBuffer(payload.timestamp),
|
|
54
|
-
addressToBuffer(wallet.address, config.addressPrefix),
|
|
55
|
-
payload.validator_connection_request.nonce,
|
|
56
|
-
encodeCapabilities(caps)
|
|
57
|
-
);
|
|
58
|
-
const hash = await PeerWallet.blake3(msg);
|
|
59
|
-
t.ok(wallet.verify(payload.validator_connection_request.signature, hash, wallet.publicKey));
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
test('NetworkMessageDirector builds liveness request and verifies signature', async t => {
|
|
63
|
-
const wallet = createWallet();
|
|
64
|
-
const director = new NetworkMessageDirector(new NetworkMessageBuilder(wallet, config));
|
|
65
|
-
|
|
66
|
-
const id = '1';
|
|
67
|
-
const caps = ['cap:b', 'cap:a'];
|
|
68
|
-
const data = b4a.from('ping', 'utf8');
|
|
69
|
-
|
|
70
|
-
const payload = await director.buildLivenessRequest(id, data, caps);
|
|
71
|
-
t.is(payload.type, NetworkOperationType.LIVENESS_REQUEST);
|
|
72
|
-
t.is(payload.id, id);
|
|
73
|
-
t.alike(payload.capabilities, caps);
|
|
74
|
-
|
|
75
|
-
const msg = createMessage(
|
|
76
|
-
payload.type,
|
|
77
|
-
idToBuffer(payload.id),
|
|
78
|
-
timestampToBuffer(payload.timestamp),
|
|
79
|
-
payload.liveness_request.nonce,
|
|
80
|
-
encodeCapabilities(caps)
|
|
81
|
-
);
|
|
82
|
-
const hash = await PeerWallet.blake3(msg);
|
|
83
|
-
t.ok(wallet.verify(payload.liveness_request.signature, hash, wallet.publicKey));
|
|
84
|
-
});
|
|
85
|
-
|
|
86
38
|
test('NetworkMessageDirector iterates liveness response ResultCode values', async t => {
|
|
87
39
|
const wallet = createWallet();
|
|
88
40
|
const director = new NetworkMessageDirector(new NetworkMessageBuilder(wallet, config));
|
|
89
41
|
|
|
90
|
-
const id =
|
|
42
|
+
const id = uuidv7();
|
|
91
43
|
const caps = ['cap:b', 'cap:a'];
|
|
92
|
-
const data = b4a.from('ping', 'utf8');
|
|
93
44
|
|
|
94
45
|
for (const code of uniqueResultCodes()) {
|
|
95
|
-
const payload = await director.buildLivenessResponse(id,
|
|
46
|
+
const payload = await director.buildLivenessResponse(id, caps, code);
|
|
96
47
|
t.is(payload.type, NetworkOperationType.LIVENESS_RESPONSE);
|
|
97
48
|
t.is(payload.liveness_response.result, code);
|
|
98
49
|
|
|
@@ -116,7 +67,7 @@ test('NetworkMessageDirector builds broadcast transaction request and verifies s
|
|
|
116
67
|
const wallet = createWallet();
|
|
117
68
|
const director = new NetworkMessageDirector(new NetworkMessageBuilder(wallet, config));
|
|
118
69
|
|
|
119
|
-
const id =
|
|
70
|
+
const id = uuidv7();
|
|
120
71
|
const data = b4a.from('deadbeef', 'hex');
|
|
121
72
|
const caps = ['cap:b', 'cap:a'];
|
|
122
73
|
|
|
@@ -145,19 +96,36 @@ test('NetworkMessageDirector iterates broadcast transaction response ResultCode
|
|
|
145
96
|
const wallet = createWallet();
|
|
146
97
|
const director = new NetworkMessageDirector(new NetworkMessageBuilder(wallet, config));
|
|
147
98
|
|
|
148
|
-
const id =
|
|
99
|
+
const id = uuidv7();
|
|
149
100
|
const caps = ['cap:b', 'cap:a'];
|
|
101
|
+
const proof = b4a.from('deadbeef', 'hex');
|
|
102
|
+
const timestamp = Date.now();
|
|
103
|
+
const emptyProof = b4a.alloc(0);
|
|
150
104
|
|
|
151
105
|
for (const code of uniqueResultCodes()) {
|
|
152
|
-
const
|
|
106
|
+
const includeProof = code === NetworkResultCode.OK;
|
|
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
|
+
);
|
|
153
117
|
t.is(payload.type, NetworkOperationType.BROADCAST_TRANSACTION_RESPONSE);
|
|
154
118
|
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);
|
|
155
121
|
|
|
156
122
|
const msg = createMessage(
|
|
157
123
|
payload.type,
|
|
158
124
|
idToBuffer(payload.id),
|
|
159
125
|
timestampToBuffer(payload.timestamp),
|
|
160
126
|
payload.broadcast_transaction_response.nonce,
|
|
127
|
+
responseProof,
|
|
128
|
+
timestampToBuffer(responseTimestamp),
|
|
161
129
|
safeWriteUInt32BE(code, 0),
|
|
162
130
|
encodeCapabilities(caps)
|
|
163
131
|
);
|
|
@@ -166,38 +134,214 @@ test('NetworkMessageDirector iterates broadcast transaction response ResultCode
|
|
|
166
134
|
|
|
167
135
|
const decoded = decodeV1networkOperation(encodeV1networkOperation(payload));
|
|
168
136
|
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);
|
|
169
139
|
}
|
|
170
140
|
});
|
|
171
141
|
|
|
172
|
-
test('NetworkMessageDirector
|
|
142
|
+
test('NetworkMessageDirector builds broadcast transaction response with proof and timestamp', async t => {
|
|
173
143
|
const wallet = createWallet();
|
|
174
144
|
const director = new NetworkMessageDirector(new NetworkMessageBuilder(wallet, config));
|
|
175
145
|
|
|
176
|
-
const id =
|
|
146
|
+
const id = uuidv7();
|
|
177
147
|
const caps = ['cap:b', 'cap:a'];
|
|
178
|
-
const
|
|
179
|
-
|
|
148
|
+
const proof = b4a.from('deadbeef', 'hex');
|
|
149
|
+
const timestamp = Date.now();
|
|
180
150
|
|
|
181
|
-
|
|
182
|
-
|
|
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(
|
|
183
190
|
id,
|
|
184
|
-
otherAddress,
|
|
185
191
|
caps,
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
192
|
+
NetworkResultCode.OK,
|
|
193
|
+
proof
|
|
194
|
+
),
|
|
195
|
+
errorMessageIncludes('Result code OK requires non-empty proof and timestamp > 0.')
|
|
196
|
+
);
|
|
197
|
+
});
|
|
190
198
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
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 => {
|
|
220
|
+
const wallet = createWallet();
|
|
221
|
+
const director = new NetworkMessageDirector(new NetworkMessageBuilder(wallet, config));
|
|
222
|
+
|
|
223
|
+
const id = uuidv7();
|
|
224
|
+
const caps = ['cap:b', 'cap:a'];
|
|
225
|
+
const timestamp = Date.now();
|
|
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
|
+
});
|
|
291
|
+
|
|
292
|
+
test('NetworkMessageDirector rejects TX_ACCEPTED_PROOF_UNAVAILABLE response when proof is non-empty', async t => {
|
|
293
|
+
const wallet = createWallet();
|
|
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(
|
|
320
|
+
id,
|
|
321
|
+
caps,
|
|
322
|
+
NetworkResultCode.INVALID_PAYLOAD,
|
|
323
|
+
b4a.from('deadbeef', 'hex'),
|
|
324
|
+
0
|
|
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'];
|
|
336
|
+
|
|
337
|
+
await t.exception(
|
|
338
|
+
() => director.buildBroadcastTransactionResponse(
|
|
339
|
+
id,
|
|
340
|
+
caps,
|
|
341
|
+
NetworkResultCode.INVALID_PAYLOAD,
|
|
342
|
+
null,
|
|
343
|
+
Date.now()
|
|
344
|
+
),
|
|
345
|
+
errorMessageIncludes('Non-OK result code requires timestamp to be 0, except TX_ACCEPTED_PROOF_UNAVAILABLE.')
|
|
346
|
+
);
|
|
203
347
|
});
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { test } from 'brittle';
|
|
2
|
+
import sinon from 'sinon';
|
|
3
|
+
import b4a from 'b4a';
|
|
4
|
+
import { config } from '../../helpers/config.js';
|
|
5
|
+
import NetworkMessageRouter from '../../../src/core/network/protocols/legacy/NetworkMessageRouter.js';
|
|
6
|
+
import LegacyGetRequestHandler from '../../../src/core/network/protocols/legacy/handlers/LegacyGetRequestHandler.js';
|
|
7
|
+
import LegacyResponseHandler from '../../../src/core/network/protocols/legacy/handlers/LegacyResponseHandler.js';
|
|
8
|
+
import { NETWORK_MESSAGE_TYPES } from '../../../src/utils/constants.js';
|
|
9
|
+
|
|
10
|
+
const makeConnection = (sandbox) => ({
|
|
11
|
+
remotePublicKey: b4a.alloc(32, 0x01),
|
|
12
|
+
protocolSession: {
|
|
13
|
+
setLegacyAsPreferredProtocol: sandbox.stub()
|
|
14
|
+
},
|
|
15
|
+
end: sandbox.stub()
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
const makeRouterContext = (t) => {
|
|
19
|
+
const sandbox = sinon.createSandbox();
|
|
20
|
+
t.teardown(() => sandbox.restore());
|
|
21
|
+
|
|
22
|
+
const getHandler = sandbox.stub(LegacyGetRequestHandler.prototype, 'handle').resolves();
|
|
23
|
+
const responseHandler = sandbox.stub(LegacyResponseHandler.prototype, 'handle').resolves();
|
|
24
|
+
const router = new NetworkMessageRouter({}, { address: 'test-wallet' }, {}, {}, config);
|
|
25
|
+
const connection = makeConnection(sandbox);
|
|
26
|
+
|
|
27
|
+
return { connection, getHandler, responseHandler, router };
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
test('LegacyNetworkMessageRouter', async (t) => {
|
|
31
|
+
await t.test('routes legacy string GET messages', async (t) => {
|
|
32
|
+
const { connection, getHandler, responseHandler, router } = makeRouterContext(t);
|
|
33
|
+
const message = NETWORK_MESSAGE_TYPES.GET.VALIDATOR;
|
|
34
|
+
|
|
35
|
+
await router.route(message, connection);
|
|
36
|
+
|
|
37
|
+
t.ok(connection.protocolSession.setLegacyAsPreferredProtocol.calledOnce, 'should prefer legacy protocol');
|
|
38
|
+
t.ok(getHandler.calledOnce, 'should route GET message');
|
|
39
|
+
t.ok(responseHandler.notCalled, 'should not route response handler');
|
|
40
|
+
t.is(getHandler.firstCall.args[0], message, 'passes GET message through to handler');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
await t.test('routes legacy object response messages', async (t) => {
|
|
44
|
+
const { connection, getHandler, responseHandler, router } = makeRouterContext(t);
|
|
45
|
+
const message = { op: NETWORK_MESSAGE_TYPES.RESPONSE.VALIDATOR };
|
|
46
|
+
|
|
47
|
+
await router.route(message, connection);
|
|
48
|
+
|
|
49
|
+
t.ok(connection.protocolSession.setLegacyAsPreferredProtocol.calledOnce, 'should prefer legacy protocol');
|
|
50
|
+
t.ok(responseHandler.calledOnce, 'should route response message');
|
|
51
|
+
t.ok(getHandler.notCalled, 'should not route GET handler');
|
|
52
|
+
t.is(responseHandler.firstCall.args[0], message, 'passes response message through to handler');
|
|
53
|
+
});
|
|
54
|
+
});
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { test } from 'brittle';
|
|
2
|
+
import sinon from 'sinon';
|
|
3
|
+
import b4a from 'b4a';
|
|
4
|
+
|
|
5
|
+
import NetworkWalletFactory from '../../../src/core/network/identity/NetworkWalletFactory.js';
|
|
6
|
+
import ProtocolSession from '../../../src/core/network/protocols/ProtocolSession.js';
|
|
7
|
+
import { ResultCode } from '../../../src/utils/constants.js';
|
|
8
|
+
import { config } from '../../helpers/config.js';
|
|
9
|
+
import { testKeyPair1 } from '../../fixtures/apply.fixtures.js';
|
|
10
|
+
|
|
11
|
+
function createWallet() {
|
|
12
|
+
const keyPair = {
|
|
13
|
+
publicKey: b4a.from(testKeyPair1.publicKey, 'hex'),
|
|
14
|
+
secretKey: b4a.from(testKeyPair1.secretKey, 'hex')
|
|
15
|
+
};
|
|
16
|
+
return NetworkWalletFactory.provide({
|
|
17
|
+
enableWallet: false,
|
|
18
|
+
keyPair,
|
|
19
|
+
networkPrefix: config.addressPrefix
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function makeProtocol(sendStub) {
|
|
24
|
+
return {
|
|
25
|
+
send: sendStub ?? sinon.stub().resolves(ResultCode.OK),
|
|
26
|
+
sendAndForget: sinon.stub(),
|
|
27
|
+
decode: sinon.stub(),
|
|
28
|
+
close: sinon.stub()
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
test('ProtocolSession', (t) => {
|
|
33
|
+
t.teardown(() => sinon.restore());
|
|
34
|
+
|
|
35
|
+
test('probe sets preferred protocol to v1 on OK', async (t) => {
|
|
36
|
+
const v1Send = sinon.stub().resolves(ResultCode.OK);
|
|
37
|
+
const session = new ProtocolSession(
|
|
38
|
+
makeProtocol(),
|
|
39
|
+
makeProtocol(v1Send),
|
|
40
|
+
createWallet(),
|
|
41
|
+
config
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
await session.probe();
|
|
45
|
+
t.is(session.preferredProtocol, session.supportedProtocols.V1);
|
|
46
|
+
t.ok(v1Send.calledOnce);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test('probe sets preferred protocol to legacy on non-OK', async (t) => {
|
|
50
|
+
const v1Send = sinon.stub().resolves(ResultCode.TIMEOUT);
|
|
51
|
+
const session = new ProtocolSession(
|
|
52
|
+
makeProtocol(),
|
|
53
|
+
makeProtocol(v1Send),
|
|
54
|
+
createWallet(),
|
|
55
|
+
config
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
await session.probe();
|
|
59
|
+
t.is(session.preferredProtocol, session.supportedProtocols.LEGACY);
|
|
60
|
+
t.ok(v1Send.calledOnce);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test('probe sets preferred protocol to legacy on rejection', async (t) => {
|
|
64
|
+
const v1Send = sinon.stub().rejects(new Error('boom'));
|
|
65
|
+
const session = new ProtocolSession(
|
|
66
|
+
makeProtocol(),
|
|
67
|
+
makeProtocol(v1Send),
|
|
68
|
+
createWallet(),
|
|
69
|
+
config
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
await session.probe();
|
|
73
|
+
t.is(session.preferredProtocol, session.supportedProtocols.LEGACY);
|
|
74
|
+
t.ok(v1Send.calledOnce);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test('sendHealthCheck returns OK when preferred is v1', async (t) => {
|
|
78
|
+
const v1Send = sinon.stub().resolves(ResultCode.OK);
|
|
79
|
+
const session = new ProtocolSession(
|
|
80
|
+
makeProtocol(),
|
|
81
|
+
makeProtocol(v1Send),
|
|
82
|
+
createWallet(),
|
|
83
|
+
config
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
session.setV1AsPreferredProtocol();
|
|
87
|
+
const result = await session.sendHealthCheck();
|
|
88
|
+
t.is(result, ResultCode.OK);
|
|
89
|
+
t.ok(v1Send.calledOnce);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test('sendHealthCheck returns OK when preferred is legacy', async (t) => {
|
|
93
|
+
const session = new ProtocolSession(
|
|
94
|
+
makeProtocol(),
|
|
95
|
+
makeProtocol(),
|
|
96
|
+
createWallet(),
|
|
97
|
+
config
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
session.setLegacyAsPreferredProtocol();
|
|
101
|
+
const result = await session.sendHealthCheck();
|
|
102
|
+
t.is(result, ResultCode.OK);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test('sendHealthCheck returns UNSPECIFIED when not probed', async (t) => {
|
|
106
|
+
const session = new ProtocolSession(
|
|
107
|
+
makeProtocol(),
|
|
108
|
+
makeProtocol(),
|
|
109
|
+
createWallet(),
|
|
110
|
+
config
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
const result = await session.sendHealthCheck();
|
|
114
|
+
t.is(result, ResultCode.UNSPECIFIED);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test('isHealthCheckSupported throws when not probed', async (t) => {
|
|
118
|
+
const session = new ProtocolSession(
|
|
119
|
+
makeProtocol(),
|
|
120
|
+
makeProtocol(),
|
|
121
|
+
createWallet(),
|
|
122
|
+
config
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
await t.exception.all(() => session.isHealthCheckSupported());
|
|
126
|
+
});
|
|
127
|
+
});
|
|
@@ -2,8 +2,11 @@ import { default as test } from 'brittle';
|
|
|
2
2
|
|
|
3
3
|
async function runNetworkModuleTests() {
|
|
4
4
|
test.pause();
|
|
5
|
-
await import('./
|
|
5
|
+
await import('./LegacyNetworkMessageRouter.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');
|
|
7
10
|
test.resume();
|
|
8
11
|
}
|
|
9
12
|
|