trac-msb 0.2.15 → 0.2.16
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
|
@@ -8,22 +8,16 @@ import NetworkMessages from './protocols/NetworkMessages.js';
|
|
|
8
8
|
import { sleep } from '../../utils/helpers.js';
|
|
9
9
|
import {
|
|
10
10
|
TRAC_NAMESPACE,
|
|
11
|
-
|
|
11
|
+
EventType
|
|
12
12
|
} from '../../utils/constants.js';
|
|
13
13
|
import ConnectionManager from './services/ConnectionManager.js';
|
|
14
14
|
import MessageOrchestrator from './services/MessageOrchestrator.js';
|
|
15
15
|
import NetworkWalletFactory from './identity/NetworkWalletFactory.js';
|
|
16
|
-
import { EventType } from '../../utils/constants.js';
|
|
17
|
-
import { networkMessageFactory } from '../../messages/network/v1/networkMessageFactory.js';
|
|
18
16
|
import TransactionRateLimiterService from './services/TransactionRateLimiterService.js';
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
if (DEBUG) {
|
|
24
|
-
console.log('DEBUG [Network] ==> ', ...args);
|
|
25
|
-
}
|
|
26
|
-
};
|
|
17
|
+
import PendingRequestService from './services/PendingRequestService.js';
|
|
18
|
+
import TransactionCommitService from "./services/TransactionCommitService.js";
|
|
19
|
+
import ValidatorHealthCheckService from './services/ValidatorHealthCheckService.js';
|
|
20
|
+
import { Logger } from '../../utils/logger.js';
|
|
27
21
|
|
|
28
22
|
const wakeup = new w();
|
|
29
23
|
|
|
@@ -40,6 +34,11 @@ class Network extends ReadyResource {
|
|
|
40
34
|
#connectTimeoutMs;
|
|
41
35
|
#maxPendingConnections;
|
|
42
36
|
#rateLimiter;
|
|
37
|
+
#pendingRequestsService;
|
|
38
|
+
#transactionCommitService;
|
|
39
|
+
#wallet;
|
|
40
|
+
#validatorHealthCheckService;
|
|
41
|
+
#logger;
|
|
43
42
|
|
|
44
43
|
/**
|
|
45
44
|
* @param {State} state
|
|
@@ -52,11 +51,13 @@ class Network extends ReadyResource {
|
|
|
52
51
|
this.#connectTimeoutMs = config.connectTimeoutMs || 5000;
|
|
53
52
|
this.#maxPendingConnections = config.maxPendingConnections || 50;
|
|
54
53
|
this.#pendingConnections = new Map();
|
|
55
|
-
this.#
|
|
54
|
+
this.#transactionCommitService = new TransactionCommitService(this.#config);
|
|
55
|
+
this.#transactionPoolService = new TransactionPoolService(state, address, this.#transactionCommitService ,this.#config);
|
|
56
56
|
this.#validatorObserverService = new ValidatorObserverService(this, state, address, this.#config);
|
|
57
57
|
this.#validatorConnectionManager = new ConnectionManager(this.#config);
|
|
58
58
|
this.#validatorMessageOrchestrator = new MessageOrchestrator(this.#validatorConnectionManager, state, this.#config);
|
|
59
|
-
|
|
59
|
+
this.#pendingRequestsService = new PendingRequestService(this.#config);
|
|
60
|
+
this.#logger = new Logger(this.#config);
|
|
60
61
|
}
|
|
61
62
|
|
|
62
63
|
get swarm() {
|
|
@@ -80,7 +81,7 @@ class Network extends ReadyResource {
|
|
|
80
81
|
}
|
|
81
82
|
|
|
82
83
|
async _open() {
|
|
83
|
-
|
|
84
|
+
this.#logger.info('Network initialization...');
|
|
84
85
|
|
|
85
86
|
this.setupNetworkListeners();
|
|
86
87
|
|
|
@@ -89,15 +90,19 @@ class Network extends ReadyResource {
|
|
|
89
90
|
}
|
|
90
91
|
|
|
91
92
|
async _close() {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
this.transactionPoolService.stopPool();
|
|
93
|
+
this.#logger.info('Network: closing gracefully...');
|
|
94
|
+
await this.transactionPoolService.stopPool();
|
|
95
95
|
await sleep(100);
|
|
96
|
-
this.#validatorObserverService.stopValidatorObserver();
|
|
96
|
+
await this.#validatorObserverService.stopValidatorObserver();
|
|
97
97
|
await sleep(5_000);
|
|
98
|
+
if (this.#validatorHealthCheckService) {
|
|
99
|
+
await this.#validatorHealthCheckService.close();
|
|
100
|
+
}
|
|
98
101
|
|
|
99
102
|
this.cleanupNetworkListeners();
|
|
100
103
|
this.cleanupPendingConnections();
|
|
104
|
+
this.#pendingRequestsService.close();
|
|
105
|
+
this.#transactionCommitService.close();
|
|
101
106
|
|
|
102
107
|
if (this.#swarm !== null) {
|
|
103
108
|
this.#swarm.destroy();
|
|
@@ -105,23 +110,41 @@ class Network extends ReadyResource {
|
|
|
105
110
|
}
|
|
106
111
|
|
|
107
112
|
setupNetworkListeners() {
|
|
108
|
-
// VALIDATOR_CONNECTION_TIMEOUT
|
|
109
113
|
this.on(EventType.VALIDATOR_CONNECTION_TIMEOUT, ({ publicKey, type, timeoutMs }) => {
|
|
110
|
-
|
|
114
|
+
this.#logger.debug(`Network Event: VALIDATOR_CONNECTION_TIMEOUT | PublicKey: ${publicKey} | Type: ${type} | TimeoutMs: ${timeoutMs}`);
|
|
111
115
|
this.#pendingConnections.delete(publicKey);
|
|
112
116
|
});
|
|
113
117
|
|
|
114
|
-
// VALIDATOR_CONNECTION_READY
|
|
115
118
|
this.on(EventType.VALIDATOR_CONNECTION_READY, async ({ publicKey, type, connection }) => {
|
|
116
|
-
|
|
119
|
+
this.#logger.debug(`Network Event: VALIDATOR_CONNECTION_READY | PublicKey: ${publicKey} | Type: ${type}`);
|
|
117
120
|
const { timeoutId } = this.#pendingConnections.get(publicKey);
|
|
121
|
+
|
|
118
122
|
if (!timeoutId) return;
|
|
119
123
|
|
|
120
124
|
clearTimeout(timeoutId);
|
|
121
125
|
this.#pendingConnections.delete(publicKey);
|
|
122
126
|
|
|
123
127
|
if (type === 'validator') {
|
|
124
|
-
|
|
128
|
+
try {
|
|
129
|
+
await connection.protocolSession.probe();
|
|
130
|
+
} catch (err) {
|
|
131
|
+
this.#logger.debug(`failed to probe peer with publicKey ${publicKey}: ${err?.message ?? err}`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
this.#validatorConnectionManager.addValidator(publicKey, connection);
|
|
135
|
+
|
|
136
|
+
let healthCheckSupported = false;
|
|
137
|
+
try {
|
|
138
|
+
healthCheckSupported = connection.protocolSession.isHealthCheckSupported();
|
|
139
|
+
} catch (err) {
|
|
140
|
+
this.#logger.debug(`health check support unknown for peer with publicKey ${publicKey}: ${err?.message ?? err}`);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (healthCheckSupported) {
|
|
144
|
+
this.#validatorHealthCheckService.start(publicKey);
|
|
145
|
+
} else {
|
|
146
|
+
this.#validatorHealthCheckService.stop(publicKey);
|
|
147
|
+
}
|
|
125
148
|
}
|
|
126
149
|
|
|
127
150
|
});
|
|
@@ -146,7 +169,9 @@ class Network extends ReadyResource {
|
|
|
146
169
|
) {
|
|
147
170
|
if (!this.#swarm) {
|
|
148
171
|
const keyPair = await this.initializeNetworkingKeyPair(store, wallet);
|
|
149
|
-
|
|
172
|
+
this.#wallet = this.#getNetworkWalletWrapper(wallet, keyPair);
|
|
173
|
+
this.#validatorMessageOrchestrator.setWallet(this.#wallet);
|
|
174
|
+
|
|
150
175
|
this.#swarm = new Hyperswarm({
|
|
151
176
|
keyPair,
|
|
152
177
|
bootstrap: this.#config.dhtBootstrap,
|
|
@@ -159,14 +184,18 @@ class Network extends ReadyResource {
|
|
|
159
184
|
this.#rateLimiter = new TransactionRateLimiterService(this.#swarm, this.#config);
|
|
160
185
|
this.#networkMessages = new NetworkMessages(
|
|
161
186
|
state,
|
|
162
|
-
|
|
187
|
+
this.#wallet,
|
|
163
188
|
this.#rateLimiter,
|
|
164
189
|
this.#transactionPoolService,
|
|
165
|
-
this.#
|
|
190
|
+
this.#pendingRequestsService,
|
|
191
|
+
this.#transactionCommitService,
|
|
166
192
|
this.#config
|
|
167
193
|
);
|
|
194
|
+
this.#validatorHealthCheckService = new ValidatorHealthCheckService(this.#config);
|
|
195
|
+
await this.#validatorHealthCheckService.ready();
|
|
196
|
+
this.#validatorConnectionManager.subscribeToHealthChecks(this.#validatorHealthCheckService);
|
|
168
197
|
|
|
169
|
-
|
|
198
|
+
this.#logger.info(`Channel: ${b4a.toString(this.#config.channel)}`);
|
|
170
199
|
|
|
171
200
|
this.#swarm.on('connection', async (connection) => {
|
|
172
201
|
// Per-peer connection initialization:
|
|
@@ -185,12 +214,20 @@ class Network extends ReadyResource {
|
|
|
185
214
|
}
|
|
186
215
|
|
|
187
216
|
connection.on('close', () => {
|
|
217
|
+
this.#pendingRequestsService.rejectPendingRequestsForPeer(
|
|
218
|
+
publicKey,
|
|
219
|
+
new Error('Connection closed before response')
|
|
220
|
+
);
|
|
188
221
|
this.#swarm.leavePeer(connection.remotePublicKey);
|
|
189
222
|
this.#validatorConnectionManager.remove(publicKey);
|
|
190
223
|
connection.protocolSession.close();
|
|
191
224
|
});
|
|
192
225
|
|
|
193
226
|
connection.on('error', (error) => {
|
|
227
|
+
this.#pendingRequestsService.rejectPendingRequestsForPeer(
|
|
228
|
+
publicKey,
|
|
229
|
+
error ?? new Error('Connection error before response')
|
|
230
|
+
);
|
|
194
231
|
if (
|
|
195
232
|
error && error.message && (
|
|
196
233
|
error.message.includes('connection reset by peer') ||
|
|
@@ -200,7 +237,7 @@ class Network extends ReadyResource {
|
|
|
200
237
|
// TODO: decide if we want to handle this error in a specific way. It generates a lot of logs.
|
|
201
238
|
return;
|
|
202
239
|
}
|
|
203
|
-
|
|
240
|
+
this.#logger.error(error?.message ?? 'Unknown network connection error');
|
|
204
241
|
});
|
|
205
242
|
|
|
206
243
|
});
|
|
@@ -232,7 +269,7 @@ class Network extends ReadyResource {
|
|
|
232
269
|
async tryConnect(publicKey, type = null) {
|
|
233
270
|
if (this.#swarm === null) throw new Error('Network swarm is not initialized');
|
|
234
271
|
if (this.#pendingConnections.has(publicKey) || this.#pendingConnections.size >= this.#maxPendingConnections) {
|
|
235
|
-
|
|
272
|
+
this.#logger.debug(`Network.tryConnect: Connection to peer: ${publicKey} as type: ${type} is already pending or max pending connections reached.`);
|
|
236
273
|
return;
|
|
237
274
|
}
|
|
238
275
|
|
|
@@ -250,21 +287,21 @@ class Network extends ReadyResource {
|
|
|
250
287
|
const peerInfo = this.#swarm.peers.get(publicKey);
|
|
251
288
|
if (peerInfo) {
|
|
252
289
|
const connection = this.#swarm._allConnections.get(peerInfo.publicKey);
|
|
253
|
-
|
|
290
|
+
|
|
291
|
+
if (connection &&
|
|
292
|
+
connection.protocolSession &&
|
|
293
|
+
!connection.protocolSession.isProbed() &&
|
|
294
|
+
!this.#pendingRequestsService.isProbePending(connection.remotePublicKey.toString('hex'))
|
|
295
|
+
) {
|
|
254
296
|
await this.#finalizeConnection(publicKey, type, connection);
|
|
255
297
|
}
|
|
256
298
|
}
|
|
257
299
|
}
|
|
258
300
|
|
|
259
|
-
async isConnected(publicKey) {
|
|
260
|
-
return this.#swarm.peers.has(publicKey) &&
|
|
261
|
-
this.#swarm.peers.get(publicKey).connectedTime != -1
|
|
262
|
-
}
|
|
263
|
-
|
|
264
301
|
async #finalizeConnection(publicKey, type, connection) {
|
|
265
302
|
if (!this.#pendingConnections.has(publicKey)) return;
|
|
266
303
|
this.emit(EventType.VALIDATOR_CONNECTION_READY, { publicKey, type, connection });
|
|
267
|
-
|
|
304
|
+
this.#logger.debug(`Network.finalizeConnection: Connected to peer: ${publicKey} as type: ${type}`);
|
|
268
305
|
}
|
|
269
306
|
|
|
270
307
|
#getNetworkWalletWrapper(wallet, keyPair) {
|
|
@@ -9,8 +9,8 @@ class LegacyProtocol extends ProtocolInterface {
|
|
|
9
9
|
#config;
|
|
10
10
|
#router;
|
|
11
11
|
|
|
12
|
-
constructor(router, connection, config) {
|
|
13
|
-
super(router, connection, config);
|
|
12
|
+
constructor(router, connection, pendingRequestServiceInstance = null, config) {
|
|
13
|
+
super(router, connection, pendingRequestServiceInstance, config);
|
|
14
14
|
this.#config = config;
|
|
15
15
|
this.#router = router;
|
|
16
16
|
this.init(connection);
|
|
@@ -41,20 +41,30 @@ class LegacyProtocol extends ProtocolInterface {
|
|
|
41
41
|
this.#session = this.#channel.addMessage({
|
|
42
42
|
encoding: c.json,
|
|
43
43
|
onmessage: async (incomingMessage) => {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
44
|
+
this.#router.route(incomingMessage, connection).catch((err) => {
|
|
45
|
+
console.error(`LegacyProtocol: unhandled router error: ${err.message}`);
|
|
46
|
+
try {
|
|
47
|
+
connection.end();
|
|
48
|
+
} catch {
|
|
49
49
|
}
|
|
50
|
-
}
|
|
51
|
-
console.error(`NetworkMessages: Failed to handle incoming message: ${error.message}`);
|
|
52
|
-
}
|
|
50
|
+
});
|
|
53
51
|
}
|
|
54
52
|
});
|
|
55
53
|
}
|
|
56
54
|
|
|
57
|
-
|
|
55
|
+
// TODO: Legacy protocol does not require encoding. Consider removing this method after refactoring v1 and the protocol interface
|
|
56
|
+
decode(message) {
|
|
57
|
+
// No-op for legacy protocol
|
|
58
|
+
return message;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async send(message) {
|
|
62
|
+
this.sendAndForget(message);
|
|
63
|
+
// TODO: Change 'null' to an appropriate response if needed in the future
|
|
64
|
+
return Promise.resolve(null); // This is to maintain consistency with the ProtocolInterface and v1 protocol.
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
sendAndForget(message) {
|
|
58
68
|
this.#session.send(message);
|
|
59
69
|
}
|
|
60
70
|
|
|
@@ -1,47 +1,68 @@
|
|
|
1
|
-
|
|
2
|
-
import b4a from 'b4a';
|
|
3
1
|
import NetworkMessageRouter from './legacy/NetworkMessageRouter.js';
|
|
4
2
|
import NetworkMessageRouterV1 from './v1/NetworkMessageRouter.js';
|
|
5
3
|
import ProtocolSession from './ProtocolSession.js';
|
|
6
4
|
import LegacyProtocol from './LegacyProtocol.js';
|
|
7
5
|
import V1Protocol from './V1Protocol.js';
|
|
6
|
+
|
|
8
7
|
class NetworkMessages {
|
|
9
8
|
#legacyMessageRouter;
|
|
10
9
|
#v1MessageRouter;
|
|
11
10
|
#config;
|
|
11
|
+
#wallet;
|
|
12
|
+
#pendingRequestsService;
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
constructor(
|
|
15
|
+
state,
|
|
16
|
+
wallet,
|
|
17
|
+
rateLimiterService,
|
|
18
|
+
txPoolService,
|
|
19
|
+
pendingRequestsService,
|
|
20
|
+
transactionCommitService,
|
|
21
|
+
config
|
|
22
|
+
) {
|
|
17
23
|
this.#config = config;
|
|
18
|
-
this.#
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
#initializeMessageRouter(state, wallet, rateLimiterService, txPoolService, connectionManager) {
|
|
24
|
+
this.#wallet = wallet;
|
|
25
|
+
this.#pendingRequestsService = pendingRequestsService;
|
|
22
26
|
this.#legacyMessageRouter = new NetworkMessageRouter(
|
|
23
27
|
state,
|
|
24
28
|
wallet,
|
|
25
29
|
rateLimiterService,
|
|
26
30
|
txPoolService,
|
|
27
|
-
connectionManager,
|
|
28
31
|
this.#config
|
|
29
32
|
);
|
|
30
|
-
|
|
33
|
+
|
|
34
|
+
this.#v1MessageRouter = new NetworkMessageRouterV1(
|
|
35
|
+
state,
|
|
36
|
+
wallet,
|
|
37
|
+
rateLimiterService,
|
|
38
|
+
txPoolService,
|
|
39
|
+
pendingRequestsService,
|
|
40
|
+
transactionCommitService,
|
|
41
|
+
this.#config
|
|
42
|
+
);
|
|
31
43
|
}
|
|
32
44
|
|
|
33
45
|
async setupProtomuxMessages(connection) {
|
|
34
46
|
// Attach a Protomux instance to this Hyperswarm connection.
|
|
35
47
|
// Protomux multiplexes multiple logical protocol channels over a single encrypted stream.
|
|
36
48
|
|
|
37
|
-
const legacyProtocol = new LegacyProtocol(
|
|
38
|
-
|
|
49
|
+
const legacyProtocol = new LegacyProtocol(
|
|
50
|
+
this.#legacyMessageRouter,
|
|
51
|
+
connection,
|
|
52
|
+
null,
|
|
53
|
+
this.#config
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
const v1Protocol = new V1Protocol(
|
|
57
|
+
this.#v1MessageRouter,
|
|
58
|
+
connection,
|
|
59
|
+
this.#pendingRequestsService,
|
|
60
|
+
this.#config
|
|
61
|
+
);
|
|
39
62
|
|
|
40
63
|
// ProtocolSession is attached to the Hyperswarm connection so other parts of the system (e.g. tryConnect)
|
|
41
64
|
// can send messages without knowing how Protomux was initialized.
|
|
42
|
-
|
|
43
|
-
connection.protocolSession = protocolSession;
|
|
44
|
-
|
|
65
|
+
connection.protocolSession = new ProtocolSession(legacyProtocol, v1Protocol, this.#wallet, this.#config);
|
|
45
66
|
}
|
|
46
67
|
}
|
|
47
68
|
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
class ProtocolInterface {
|
|
7
7
|
|
|
8
8
|
// TODO: Refactor this so we don't need to pass a reference for the whole network instance
|
|
9
|
-
constructor(router, connection, config) {
|
|
9
|
+
constructor(router, connection, pendingRequestService, config) {
|
|
10
10
|
if (new.target === ProtocolInterface) {
|
|
11
11
|
throw new Error('ProtocolInterface cannot be instantiated directly');
|
|
12
12
|
}
|
|
@@ -17,11 +17,23 @@ class ProtocolInterface {
|
|
|
17
17
|
throw new Error('init() method must be implemented by subclass');
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
// TODO: This method is only kept here because of v1, but it should be probably removed from the interface
|
|
21
|
+
// Remove it after we finish refactoring v1 protocol
|
|
22
|
+
decode(message) {
|
|
23
|
+
// Abstract method. Need to be implemented by subclasses.
|
|
24
|
+
throw new Error('decode() method must be implemented by subclass');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async send(message) {
|
|
21
28
|
// Abstract method. Need to be implemented by subclasses.
|
|
22
29
|
throw new Error('send() method must be implemented by subclass');
|
|
23
30
|
}
|
|
24
31
|
|
|
32
|
+
sendAndForget(message) {
|
|
33
|
+
// Abstract method. Need to be implemented by subclasses.
|
|
34
|
+
throw new Error('sendAndForget() method must be implemented by subclass');
|
|
35
|
+
}
|
|
36
|
+
|
|
25
37
|
close() {
|
|
26
38
|
// Abstract method. Need to be implemented by subclasses.
|
|
27
39
|
throw new Error('close() method must be implemented by subclass');
|
|
@@ -1,40 +1,158 @@
|
|
|
1
|
-
// ProtocolSession is a per-peer (per Hyperswarm connection)
|
|
1
|
+
// ProtocolSession is a per-peer (per Hyperswarm connection) bridge that exposes the available
|
|
2
2
|
// protocol messengers (legacy JSON, v1 binary) in one place.
|
|
3
3
|
//
|
|
4
4
|
// Why it exists:
|
|
5
5
|
// - `setupProtomuxMessages(connection)` creates Protomux channels/messages for a specific peer.
|
|
6
|
+
|
|
7
|
+
import { networkMessageFactory } from '../../../messages/network/v1/networkMessageFactory.js';
|
|
8
|
+
import { generateUUID } from '../../../utils/helpers.js';
|
|
9
|
+
import { NETWORK_CAPABILITIES, ResultCode } from '../../../utils/constants.js';
|
|
10
|
+
import { Logger } from '../../../utils/logger.js';
|
|
11
|
+
|
|
6
12
|
class ProtocolSession {
|
|
7
13
|
#legacyProtocol;
|
|
8
14
|
#v1Protocol;
|
|
15
|
+
#preferredProtocol = null;
|
|
16
|
+
#activeProtocol = null;
|
|
17
|
+
#supportedProtocols = {
|
|
18
|
+
LEGACY: 'legacy',
|
|
19
|
+
V1: 'v1'
|
|
20
|
+
}
|
|
21
|
+
#wallet;
|
|
22
|
+
#config;
|
|
23
|
+
#capabilities;
|
|
24
|
+
#logger;
|
|
9
25
|
|
|
10
|
-
constructor(legacyProtocol, v1Protocol) {
|
|
26
|
+
constructor(legacyProtocol, v1Protocol, wallet, config) {
|
|
11
27
|
// These are Protomux "message" objects (returned by channel.addMessage).
|
|
12
28
|
// They are connection-scoped and expose .send(...), already wired to the channel's encoding.
|
|
13
29
|
this.#legacyProtocol = legacyProtocol;
|
|
14
30
|
this.#v1Protocol = v1Protocol;
|
|
31
|
+
|
|
32
|
+
this.#activeProtocol = this.#v1Protocol;
|
|
33
|
+
this.#wallet = wallet;
|
|
34
|
+
this.#config = config;
|
|
35
|
+
this.#capabilities = NETWORK_CAPABILITIES;
|
|
36
|
+
this.#logger = new Logger(config);
|
|
15
37
|
}
|
|
16
38
|
|
|
17
|
-
|
|
18
|
-
return this.#
|
|
39
|
+
get preferredProtocol() {
|
|
40
|
+
return this.#preferredProtocol;
|
|
19
41
|
}
|
|
20
42
|
|
|
21
|
-
|
|
22
|
-
return this.#
|
|
43
|
+
get supportedProtocols() {
|
|
44
|
+
return this.#supportedProtocols;
|
|
23
45
|
}
|
|
24
46
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
if (protocol === 'v1') return this.#v1Protocol;
|
|
28
|
-
return null;
|
|
47
|
+
isProbed() {
|
|
48
|
+
return this.#preferredProtocol !== null;
|
|
29
49
|
}
|
|
30
50
|
|
|
31
|
-
|
|
32
|
-
|
|
51
|
+
setLegacyAsPreferredProtocol() {
|
|
52
|
+
if (this.isProbed()) {
|
|
53
|
+
// TODO: SOMETIMES WE ARE PROBING NODE FOR MULTIPLE AMOUNT OF TIME, THIS IS BAD. WE NEED TO INVESTIGATE THIS.
|
|
54
|
+
//this.#logger.warn(`ProtocolSession: preferred protocol is already set and cannot be changed to LEGACY. Current preferred protocol: ${this.#preferredProtocol}`);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
this.#preferredProtocol = this.#supportedProtocols.LEGACY;
|
|
58
|
+
this.#activeProtocol = this.#legacyProtocol;
|
|
59
|
+
this.#logger.debug('ProtocolSession: set preferred protocol to LEGACY');
|
|
33
60
|
}
|
|
34
61
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
62
|
+
setV1AsPreferredProtocol() {
|
|
63
|
+
if (this.isProbed()) {
|
|
64
|
+
// TODO: SOMETIMES WE ARE PROBING NODE FOR MULTIPLE AMOUNT OF TIME, THIS IS BAD. WE NEED TO INVESTIGATE THIS.
|
|
65
|
+
//this.#logger.warn(`ProtocolSession: preferred protocol is already set and cannot be changed to V1. Current preferred protocol: ${this.#preferredProtocol}`);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
this.#preferredProtocol = this.#supportedProtocols.V1;
|
|
70
|
+
this.#activeProtocol = this.#v1Protocol;
|
|
71
|
+
this.#logger.debug('ProtocolSession: set preferred protocol to V1');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Probes the peer to determine which protocol version they support/prefer.
|
|
76
|
+
* This is needed to know if the connected peer supports the new v1 protocol
|
|
77
|
+
* or if we should fall back to legacy for this connection.
|
|
78
|
+
*
|
|
79
|
+
* TODO: After legacy protocol is retired, we can remove the concept of "probing" and just use v1 directly.
|
|
80
|
+
* For now, this is needed to determine which protocol to use for health checks.
|
|
81
|
+
* A good future improvement would be to implement a more robust negotiation mechanism that doesn't rely on timeouts
|
|
82
|
+
* (e.g. peer sends a "hello" message indicating supported protocol versions right after connection is established).
|
|
83
|
+
*/
|
|
84
|
+
async probe() {
|
|
85
|
+
if (this.isProbed()) {
|
|
86
|
+
this.#logger.warn(`ProtocolSession: preferred protocol is already set. Skipping probe. Current preferred protocol: ${this.#preferredProtocol}`);
|
|
87
|
+
return; // TODO: Consider not returning silently
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
try {
|
|
91
|
+
const message = await this.#buildLivenessRequest();
|
|
92
|
+
if (!this.#v1Protocol) {
|
|
93
|
+
throw new Error('ProtocolSession: v1 protocol not available for probing');
|
|
94
|
+
}
|
|
95
|
+
const result = await this.#v1Protocol.send(message);
|
|
96
|
+
if (result !== ResultCode.OK) {
|
|
97
|
+
// TODO: Think about how to handle failure result codes after legacy protocol is retired
|
|
98
|
+
this.#logger.warn(`ProtocolSession: v1 protocol probe failed with non-OK result code: ${result}`);
|
|
99
|
+
this.setLegacyAsPreferredProtocol();
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
this.setV1AsPreferredProtocol();
|
|
103
|
+
} catch (err) {
|
|
104
|
+
this.#logger.debug(`ProtocolSession: v1 protocol probe failed, falling back to legacy. Details: ${err?.message ?? err}`);
|
|
105
|
+
this.setLegacyAsPreferredProtocol();
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Sends a single health check message to a peer.
|
|
111
|
+
* This is used by the ValidatorHealthCheckService.
|
|
112
|
+
* @returns {Promise<ResultCode>} Result code indicating success or failure of the health check.
|
|
113
|
+
*/
|
|
114
|
+
async sendHealthCheck() {
|
|
115
|
+
switch (this.#preferredProtocol) {
|
|
116
|
+
case this.#supportedProtocols.V1:
|
|
117
|
+
try {
|
|
118
|
+
const message = await this.#buildLivenessRequest();
|
|
119
|
+
return await this.#v1Protocol.send(message);
|
|
120
|
+
}
|
|
121
|
+
catch (err) {
|
|
122
|
+
this.#logger.error(`ProtocolSession: v1 health check failed: ${err?.message ?? err}`);
|
|
123
|
+
return ResultCode.UNEXPECTED_ERROR; // TODO: Consider just propagating the error instead
|
|
124
|
+
}
|
|
125
|
+
case this.#supportedProtocols.LEGACY:
|
|
126
|
+
this.#logger.warn('ProtocolSession: health check not supported on LEGACY protocol');
|
|
127
|
+
return ResultCode.OK; // TODO: Consider implementing a new result code (e.g. NOT_SUPPORTED) instead of returning OK
|
|
128
|
+
default:
|
|
129
|
+
this.#logger.warn('ProtocolSession: preferred protocol not set. Call probe() first.');
|
|
130
|
+
return ResultCode.UNSPECIFIED; // TODO: Define a more specific result code.
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Tells whether the connected peer supports health checks, which is a feature of the v1 protocol.
|
|
136
|
+
* @returns {Boolean} True if health checks are supported in the preferred protocol, false otherwise.
|
|
137
|
+
*/
|
|
138
|
+
isHealthCheckSupported() {
|
|
139
|
+
if (this.#preferredProtocol === null) {
|
|
140
|
+
throw new Error('ProtocolSession: preferred protocol not set. Call probe() first.');
|
|
141
|
+
}
|
|
142
|
+
return this.#preferredProtocol === this.#supportedProtocols.V1;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// TODO: Consider moving this method to be used only in V1 internally, just like 'encode'
|
|
146
|
+
decode(message) {
|
|
147
|
+
return this.#activeProtocol.decode(message);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async send(message) {
|
|
151
|
+
return this.#activeProtocol.send(message);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
sendAndForget(message) {
|
|
155
|
+
this.#activeProtocol.sendAndForget(message);
|
|
38
156
|
}
|
|
39
157
|
|
|
40
158
|
close() {
|
|
@@ -42,7 +160,7 @@ class ProtocolSession {
|
|
|
42
160
|
try {
|
|
43
161
|
this.#legacyProtocol.close();
|
|
44
162
|
} catch (e) {
|
|
45
|
-
|
|
163
|
+
this.#logger.error(`ProtocolSession: failed to close legacy channel: ${e?.message ?? e}`); // TODO: Think about throwing instead
|
|
46
164
|
}
|
|
47
165
|
}
|
|
48
166
|
|
|
@@ -50,10 +168,19 @@ class ProtocolSession {
|
|
|
50
168
|
try {
|
|
51
169
|
this.#v1Protocol.close();
|
|
52
170
|
} catch (e) {
|
|
53
|
-
|
|
171
|
+
this.#logger.error(`ProtocolSession: failed to close v1 channel: ${e?.message ?? e}`); // TODO: Think about throwing instead
|
|
54
172
|
}
|
|
55
173
|
}
|
|
56
174
|
}
|
|
175
|
+
|
|
176
|
+
async #buildLivenessRequest() {
|
|
177
|
+
if (!this.#wallet || !this.#config) {
|
|
178
|
+
throw new Error('ProtocolSession: wallet/config not set for liveness request');
|
|
179
|
+
}
|
|
180
|
+
const requestId = generateUUID();
|
|
181
|
+
return await networkMessageFactory(this.#wallet, this.#config)
|
|
182
|
+
.buildLivenessRequest(requestId, this.#capabilities);
|
|
183
|
+
}
|
|
57
184
|
}
|
|
58
185
|
|
|
59
186
|
export default ProtocolSession;
|