trac-msb 0.2.8 → 0.2.10
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 +7 -11
- package/.github/workflows/lint-pr-title.yml +26 -0
- package/.github/workflows/unit-tests.yml +2 -8
- 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 +5 -22
- package/package.json +14 -10
- package/proto/network.proto +74 -0
- package/rpc/create_server.js +2 -2
- package/rpc/handlers.js +165 -92
- package/rpc/routes/v1.js +3 -1
- package/rpc/rpc_server.js +4 -4
- package/rpc/rpc_services.js +62 -25
- package/rpc/utils/helpers.js +83 -52
- package/src/config/args.js +46 -0
- package/src/config/config.js +78 -5
- package/src/config/env.js +70 -3
- package/src/core/network/Network.js +34 -70
- package/src/core/network/identity/NetworkWalletFactory.js +2 -2
- package/src/core/network/protocols/LegacyProtocol.js +67 -0
- package/src/core/network/protocols/NetworkMessages.js +48 -0
- package/src/core/network/protocols/ProtocolInterface.js +31 -0
- package/src/core/network/protocols/ProtocolSession.js +59 -0
- package/src/core/network/protocols/V1Protocol.js +64 -0
- package/src/core/network/protocols/legacy/NetworkMessageRouter.js +84 -0
- package/src/core/network/protocols/legacy/handlers/GetRequestHandler.js +53 -0
- package/src/core/network/protocols/legacy/handlers/ResponseHandler.js +37 -0
- package/src/core/network/{messaging → protocols/legacy}/validators/base/BaseResponse.js +2 -3
- package/src/core/network/protocols/shared/handlers/RoleOperationHandler.js +88 -0
- package/src/core/network/protocols/shared/handlers/SubnetworkOperationHandler.js +93 -0
- package/src/core/network/{messaging → protocols/shared}/handlers/TransferOperationHandler.js +17 -16
- package/src/core/network/{messaging → protocols/shared}/handlers/base/BaseOperationHandler.js +10 -15
- package/src/core/network/{messaging → protocols/shared}/validators/PartialBootstrapDeployment.js +2 -2
- package/src/core/network/{messaging → protocols/shared}/validators/PartialRoleAccess.js +5 -5
- package/src/core/network/{messaging → protocols/shared}/validators/PartialTransaction.js +4 -4
- package/src/core/network/{messaging → protocols/shared}/validators/PartialTransfer.js +4 -4
- package/src/core/network/{messaging → protocols/shared}/validators/base/PartialOperation.js +14 -12
- package/src/core/network/protocols/v1/NetworkMessageRouter.js +15 -0
- package/src/core/network/services/ConnectionManager.js +5 -5
- package/src/core/network/services/MessageOrchestrator.js +2 -2
- package/src/core/network/services/TransactionPoolService.js +5 -6
- package/src/core/network/services/TransactionRateLimiterService.js +12 -13
- package/src/core/network/services/ValidatorObserverService.js +5 -6
- package/src/core/state/State.js +3 -5
- package/src/index.js +156 -181
- package/src/messages/network/v1/NetworkMessageBuilder.js +325 -0
- package/src/messages/network/v1/NetworkMessageDirector.js +137 -0
- package/src/messages/network/v1/networkMessageFactory.js +12 -0
- package/src/messages/state/ApplyStateMessageBuilder.js +661 -0
- package/src/messages/state/ApplyStateMessageDirector.js +516 -0
- package/src/messages/state/applyStateMessageFactory.js +12 -0
- package/src/utils/buffer.js +53 -1
- package/src/utils/check.js +1 -1
- package/src/utils/cli.js +0 -8
- package/src/utils/constants.js +33 -30
- package/src/utils/fileUtils.js +13 -0
- package/src/utils/normalizers.js +84 -2
- package/src/utils/protobuf/network.cjs +840 -0
- package/src/utils/protobuf/operationHelpers.js +10 -0
- package/src/utils/type.js +26 -0
- 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 +4 -3
- package/tests/acceptance/v1/tx/tx.test.mjs +27 -16
- package/tests/acceptance/v1/tx-details/tx-details.test.mjs +26 -12
- package/tests/fixtures/check.fixtures.js +33 -32
- package/tests/fixtures/networkV1.fixtures.js +85 -0
- package/tests/fixtures/protobuf.fixtures.js +109 -25
- 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 +59 -56
- package/tests/unit/messages/messages.test.js +12 -0
- package/tests/unit/messages/network/NetworkMessageBuilder.test.js +276 -0
- package/tests/unit/messages/network/NetworkMessageDirector.test.js +201 -0
- package/tests/unit/messages/state/applyStateMessageBuilder.complete.test.js +521 -0
- package/tests/unit/messages/state/applyStateMessageBuilder.partial.test.js +233 -0
- package/tests/unit/network/ConnectionManager.test.js +6 -5
- package/tests/unit/network/networkModule.test.js +3 -2
- package/tests/unit/state/apply/addAdmin/addAdminHappyPathScenario.js +10 -6
- package/tests/unit/state/apply/addAdmin/addAdminScenarioHelpers.js +9 -6
- package/tests/unit/state/apply/addAdmin/state.apply.addAdmin.test.js +10 -7
- package/tests/unit/state/apply/addIndexer/addIndexerScenarioHelpers.js +18 -21
- package/tests/unit/state/apply/addWriter/addWriterScenarioHelpers.js +53 -38
- package/tests/unit/state/apply/adminRecovery/adminRecoveryScenarioHelpers.js +46 -35
- package/tests/unit/state/apply/appendWhitelist/appendWhitelistScenarioHelpers.js +13 -16
- package/tests/unit/state/apply/balanceInitialization/balanceInitializationScenarioHelpers.js +17 -11
- package/tests/unit/state/apply/banValidator/banValidatorScenarioHelpers.js +11 -12
- package/tests/unit/state/apply/bootstrapDeployment/bootstrapDeploymentScenarioHelpers.js +9 -7
- package/tests/unit/state/apply/common/commonScenarioHelper.js +15 -14
- package/tests/unit/state/apply/common/payload-structure/initializationDisabledScenario.js +9 -4
- package/tests/unit/state/apply/disableInitialization/disableInitializationScenarioHelpers.js +17 -11
- package/tests/unit/state/apply/removeWriter/removeWriterScenarioHelpers.js +19 -14
- package/tests/unit/state/apply/transfer/transferDoubleSpendAcrossValidatorsScenario.js +37 -29
- package/tests/unit/state/apply/transfer/transferScenarioHelpers.js +9 -7
- package/tests/unit/state/apply/txOperation/txOperationScenarioHelpers.js +11 -9
- package/tests/unit/unit.test.js +1 -1
- package/tests/unit/utils/buffer/buffer.test.js +62 -1
- 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/normalizers/normalizers.test.js +469 -0
- package/tests/unit/utils/protobuf/operationHelpers.test.js +120 -2
- package/tests/unit/utils/type/type.test.js +25 -0
- package/tests/unit/utils/utils.test.js +1 -0
- package/docs/networking-dualstack-plan.md +0 -75
- package/docs/networking-layer-redesign.md +0 -155
- package/src/core/network/messaging/NetworkMessages.js +0 -64
- package/src/core/network/messaging/handlers/GetRequestHandler.js +0 -113
- package/src/core/network/messaging/handlers/ResponseHandler.js +0 -107
- package/src/core/network/messaging/handlers/RoleOperationHandler.js +0 -114
- package/src/core/network/messaging/handlers/SubnetworkOperationHandler.js +0 -149
- package/src/core/network/messaging/routes/NetworkMessageRouter.js +0 -98
- package/src/core/network/messaging/validators/AdminResponse.js +0 -58
- package/src/core/network/messaging/validators/CustomNodeResponse.js +0 -46
- package/src/messages/base/StateBuilder.js +0 -25
- package/src/messages/completeStateMessages/CompleteStateMessageBuilder.js +0 -425
- package/src/messages/completeStateMessages/CompleteStateMessageDirector.js +0 -252
- package/src/messages/completeStateMessages/CompleteStateMessageOperations.js +0 -296
- package/src/messages/partialStateMessages/PartialStateMessageBuilder.js +0 -272
- package/src/messages/partialStateMessages/PartialStateMessageDirector.js +0 -137
- package/src/messages/partialStateMessages/PartialStateMessageOperations.js +0 -138
- package/tests/integration/apply/addAdmin/addAdminBasic.test.js +0 -69
- package/tests/integration/apply/addAdmin/addAdminRecovery.test.js +0 -126
- package/tests/integration/apply/addIndexer.test.js +0 -239
- package/tests/integration/apply/addWhitelist.test.js +0 -53
- package/tests/integration/apply/addWriter.test.js +0 -245
- package/tests/integration/apply/apply.test.js +0 -19
- package/tests/integration/apply/banValidator.test.js +0 -116
- package/tests/integration/apply/postTx/invalidSubValues.test.js +0 -103
- package/tests/integration/apply/postTx/postTx.test.js +0 -196
- package/tests/integration/apply/removeIndexer.test.js +0 -132
- package/tests/integration/apply/removeWriter.test.js +0 -168
- package/tests/integration/apply/transfer.test.js +0 -83
- package/tests/integration/integration.test.js +0 -9
- package/tests/unit/messageOperations/assembleAddIndexerMessage.test.js +0 -21
- package/tests/unit/messageOperations/assembleAddWriterMessage.test.js +0 -17
- package/tests/unit/messageOperations/assembleAdminMessage.test.js +0 -68
- package/tests/unit/messageOperations/assembleBanWriterMessage.test.js +0 -17
- package/tests/unit/messageOperations/assemblePostTransaction.test.js +0 -424
- package/tests/unit/messageOperations/assembleRemoveIndexerMessage.test.js +0 -19
- package/tests/unit/messageOperations/assembleRemoveWriterMessage.test.js +0 -17
- package/tests/unit/messageOperations/assembleWhitelistMessages.test.js +0 -59
- package/tests/unit/messageOperations/commonsStateMessageOperationsTest.js +0 -278
- package/tests/unit/messageOperations/stateMessageOperations.test.js +0 -19
- /package/src/core/network/{messaging → protocols/legacy}/validators/ValidatorResponse.js +0 -0
- /package/src/utils/{operations.js → applyOperations.js} +0 -0
|
@@ -4,21 +4,18 @@ import w from 'protomux-wakeup';
|
|
|
4
4
|
import b4a from 'b4a';
|
|
5
5
|
import TransactionPoolService from './services/TransactionPoolService.js';
|
|
6
6
|
import ValidatorObserverService from './services/ValidatorObserverService.js';
|
|
7
|
-
import NetworkMessages from './
|
|
7
|
+
import NetworkMessages from './protocols/NetworkMessages.js';
|
|
8
8
|
import { sleep } from '../../utils/helpers.js';
|
|
9
9
|
import {
|
|
10
10
|
TRAC_NAMESPACE,
|
|
11
|
-
MAX_PEERS,
|
|
12
|
-
MAX_PARALLEL,
|
|
13
|
-
MAX_SERVER_CONNECTIONS,
|
|
14
|
-
MAX_CLIENT_CONNECTIONS,
|
|
15
11
|
NETWORK_MESSAGE_TYPES
|
|
16
12
|
} from '../../utils/constants.js';
|
|
17
13
|
import ConnectionManager from './services/ConnectionManager.js';
|
|
18
14
|
import MessageOrchestrator from './services/MessageOrchestrator.js';
|
|
19
15
|
import NetworkWalletFactory from './identity/NetworkWalletFactory.js';
|
|
20
16
|
import { EventType } from '../../utils/constants.js';
|
|
21
|
-
|
|
17
|
+
import { networkMessageFactory } from '../../messages/network/v1/networkMessageFactory.js';
|
|
18
|
+
import TransactionRateLimiterService from './services/TransactionRateLimiterService.js';
|
|
22
19
|
// -- Debug Mode --
|
|
23
20
|
// TODO: Implement a better debug system in the future. This is just temporary.
|
|
24
21
|
const DEBUG = false;
|
|
@@ -42,10 +39,11 @@ class Network extends ReadyResource {
|
|
|
42
39
|
#pendingConnections;
|
|
43
40
|
#connectTimeoutMs;
|
|
44
41
|
#maxPendingConnections;
|
|
42
|
+
#rateLimiter;
|
|
45
43
|
|
|
46
44
|
/**
|
|
47
45
|
* @param {State} state
|
|
48
|
-
* @param {
|
|
46
|
+
* @param {Config} config
|
|
49
47
|
* @param {string} address
|
|
50
48
|
**/
|
|
51
49
|
constructor(state, config, address = null) {
|
|
@@ -53,18 +51,12 @@ class Network extends ReadyResource {
|
|
|
53
51
|
this.#config = config
|
|
54
52
|
this.#connectTimeoutMs = config.connectTimeoutMs || 5000;
|
|
55
53
|
this.#maxPendingConnections = config.maxPendingConnections || 50;
|
|
56
|
-
|
|
57
54
|
this.#pendingConnections = new Map();
|
|
58
55
|
this.#transactionPoolService = new TransactionPoolService(state, address, this.#config);
|
|
59
56
|
this.#validatorObserverService = new ValidatorObserverService(this, state, address, this.#config);
|
|
60
|
-
this.#networkMessages = new NetworkMessages(this, this.#config);
|
|
61
57
|
this.#validatorConnectionManager = new ConnectionManager(this.#config);
|
|
62
58
|
this.#validatorMessageOrchestrator = new MessageOrchestrator(this.#validatorConnectionManager, state, this.#config);
|
|
63
|
-
|
|
64
|
-
this.admin = null;
|
|
65
|
-
this.validator = null;
|
|
66
|
-
this.custom_stream = null;
|
|
67
|
-
this.custom_node = null;
|
|
59
|
+
|
|
68
60
|
}
|
|
69
61
|
|
|
70
62
|
get swarm() {
|
|
@@ -97,6 +89,7 @@ class Network extends ReadyResource {
|
|
|
97
89
|
}
|
|
98
90
|
|
|
99
91
|
async _close() {
|
|
92
|
+
// TODO: Implement better "await" logic for stopping services
|
|
100
93
|
console.log('Network: closing gracefully...');
|
|
101
94
|
this.transactionPoolService.stopPool();
|
|
102
95
|
await sleep(100);
|
|
@@ -119,7 +112,7 @@ class Network extends ReadyResource {
|
|
|
119
112
|
});
|
|
120
113
|
|
|
121
114
|
// VALIDATOR_CONNECTION_READY
|
|
122
|
-
this.on(EventType.VALIDATOR_CONNECTION_READY, ({ publicKey, type, connection }) => {
|
|
115
|
+
this.on(EventType.VALIDATOR_CONNECTION_READY, async ({ publicKey, type, connection }) => {
|
|
123
116
|
debugLog(`Network Event: VALIDATOR_CONNECTION_READY | PublicKey: ${publicKey} | Type: ${type}`);
|
|
124
117
|
const { timeoutId } = this.#pendingConnections.get(publicKey);
|
|
125
118
|
if (!timeoutId) return;
|
|
@@ -128,19 +121,15 @@ class Network extends ReadyResource {
|
|
|
128
121
|
this.#pendingConnections.delete(publicKey);
|
|
129
122
|
|
|
130
123
|
if (type === 'validator') {
|
|
131
|
-
|
|
132
|
-
this.#validatorConnectionManager.addValidator(target, connection);
|
|
133
|
-
this.#sendRequestByType(connection, type);
|
|
124
|
+
await connection.protocolSession.send(NETWORK_MESSAGE_TYPES.GET.VALIDATOR);
|
|
134
125
|
}
|
|
126
|
+
|
|
135
127
|
});
|
|
136
128
|
}
|
|
137
129
|
|
|
138
130
|
cleanupNetworkListeners() {
|
|
139
|
-
|
|
140
|
-
this.removeAllListeners(
|
|
141
|
-
|
|
142
|
-
// connect:ready
|
|
143
|
-
this.removeAllListeners('connect:ready');
|
|
131
|
+
this.removeAllListeners(EventType.VALIDATOR_CONNECTION_TIMEOUT);
|
|
132
|
+
this.removeAllListeners(EventType.VALIDATOR_CONNECTION_READY);
|
|
144
133
|
}
|
|
145
134
|
|
|
146
135
|
cleanupPendingConnections() {
|
|
@@ -161,18 +150,29 @@ class Network extends ReadyResource {
|
|
|
161
150
|
this.#swarm = new Hyperswarm({
|
|
162
151
|
keyPair,
|
|
163
152
|
bootstrap: this.#config.dhtBootstrap,
|
|
164
|
-
maxPeers:
|
|
165
|
-
maxParallel:
|
|
166
|
-
maxServerConnections:
|
|
167
|
-
maxClientConnections:
|
|
153
|
+
maxPeers: this.#config.maxPeers,
|
|
154
|
+
maxParallel: this.#config.maxParallel,
|
|
155
|
+
maxServerConnections: this.#config.maxServerConnections,
|
|
156
|
+
maxClientConnections: this.#config.maxClientConnections
|
|
168
157
|
});
|
|
169
158
|
|
|
159
|
+
this.#rateLimiter = new TransactionRateLimiterService(this.#swarm, this.#config);
|
|
160
|
+
this.#networkMessages = new NetworkMessages(
|
|
161
|
+
state,
|
|
162
|
+
wrappedWallet,
|
|
163
|
+
this.#rateLimiter,
|
|
164
|
+
this.#transactionPoolService,
|
|
165
|
+
this.#validatorConnectionManager,
|
|
166
|
+
this.#config
|
|
167
|
+
);
|
|
168
|
+
|
|
170
169
|
console.log(`Channel: ${b4a.toString(this.#config.channel)}`);
|
|
171
|
-
this.#networkMessages.initializeMessageRouter(state, wrappedWallet);
|
|
172
170
|
|
|
173
171
|
this.#swarm.on('connection', async (connection) => {
|
|
174
|
-
|
|
175
|
-
|
|
172
|
+
// Per-peer connection initialization:
|
|
173
|
+
// - attach Protomux (legacy + v1 channels/messages)
|
|
174
|
+
// - attach connection.protocolSession (used later by tryConnect / orchestrators to send messages)
|
|
175
|
+
await this.#networkMessages.setupProtomuxMessages(connection);
|
|
176
176
|
|
|
177
177
|
// ATTENTION: Must be called AFTER the protomux init above
|
|
178
178
|
const stream = store.replicate(connection);
|
|
@@ -185,17 +185,9 @@ class Network extends ReadyResource {
|
|
|
185
185
|
}
|
|
186
186
|
|
|
187
187
|
connection.on('close', () => {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
if (this.custom_stream === connection) {
|
|
194
|
-
this.custom_stream = null;
|
|
195
|
-
this.custom_node = null;
|
|
196
|
-
}
|
|
197
|
-
try { message_channel.close() } catch (e) { }
|
|
198
|
-
|
|
188
|
+
this.#swarm.leavePeer(connection.remotePublicKey);
|
|
189
|
+
this.#validatorConnectionManager.remove(publicKey);
|
|
190
|
+
connection.protocolSession.close();
|
|
199
191
|
});
|
|
200
192
|
|
|
201
193
|
connection.on('error', (error) => {
|
|
@@ -209,7 +201,6 @@ class Network extends ReadyResource {
|
|
|
209
201
|
return;
|
|
210
202
|
}
|
|
211
203
|
console.error(error.message)
|
|
212
|
-
|
|
213
204
|
});
|
|
214
205
|
|
|
215
206
|
});
|
|
@@ -259,7 +250,7 @@ class Network extends ReadyResource {
|
|
|
259
250
|
const peerInfo = this.#swarm.peers.get(publicKey);
|
|
260
251
|
if (peerInfo) {
|
|
261
252
|
const connection = this.#swarm._allConnections.get(peerInfo.publicKey);
|
|
262
|
-
if (connection && connection.
|
|
253
|
+
if (connection && connection.protocolSession) {
|
|
263
254
|
await this.#finalizeConnection(publicKey, type, connection);
|
|
264
255
|
}
|
|
265
256
|
}
|
|
@@ -276,33 +267,6 @@ class Network extends ReadyResource {
|
|
|
276
267
|
debugLog(`Network.finalizeConnection: Connected to peer: ${publicKey} as type: ${type}`);
|
|
277
268
|
}
|
|
278
269
|
|
|
279
|
-
async #sendRequestByType(stream, type) {
|
|
280
|
-
const waitFor = {
|
|
281
|
-
validator: () => this.validatorConnectionManager.connectionCount(),
|
|
282
|
-
admin: () => this.admin_stream,
|
|
283
|
-
node: () => this.custom_stream
|
|
284
|
-
}[type];
|
|
285
|
-
|
|
286
|
-
if (type === 'validator') {
|
|
287
|
-
await stream.messenger.send(NETWORK_MESSAGE_TYPES.GET.VALIDATOR);
|
|
288
|
-
} else if (type === 'admin') {
|
|
289
|
-
await stream.messenger.send(NETWORK_MESSAGE_TYPES.GET.ADMIN);
|
|
290
|
-
} else if (type === 'node') {
|
|
291
|
-
await stream.messenger.send(NETWORK_MESSAGE_TYPES.GET.NODE);
|
|
292
|
-
} else {
|
|
293
|
-
return;
|
|
294
|
-
}
|
|
295
|
-
await this.spinLock(() => !waitFor())
|
|
296
|
-
};
|
|
297
|
-
|
|
298
|
-
async spinLock(conditionFn, maxIterations = 1500, intervalMs = 10) {
|
|
299
|
-
let counter = 0;
|
|
300
|
-
while (conditionFn() && counter < maxIterations) {
|
|
301
|
-
await sleep(intervalMs);
|
|
302
|
-
counter++;
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
|
|
306
270
|
#getNetworkWalletWrapper(wallet, keyPair) {
|
|
307
271
|
if (!this.#identityProvider) {
|
|
308
272
|
this.#identityProvider = NetworkWalletFactory.provide({
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import PeerWallet from 'trac-wallet';
|
|
2
2
|
import b4a from 'b4a';
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
class NetworkWalletFactory {
|
|
5
5
|
static provide(options = {}) {
|
|
6
6
|
const {
|
|
7
7
|
enableWallet,
|
|
@@ -28,7 +28,7 @@ export class NetworkWalletFactory {
|
|
|
28
28
|
// TODO: Once Wallet class in trac-wallet exposes a constructor/factory that accepts an existing keyPair
|
|
29
29
|
// (e.g. Wallet.fromKeyPair({ publicKey, secretKey }, networkPrefix)), replace EphemeralWallet
|
|
30
30
|
// with a thin wrapper around that functionality instead of duplicating signing/verification logic.
|
|
31
|
-
class EphemeralWallet {
|
|
31
|
+
export class EphemeralWallet {
|
|
32
32
|
#publicKey;
|
|
33
33
|
#secretKey;
|
|
34
34
|
#address;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import Protomux from 'protomux';
|
|
2
|
+
import ProtocolInterface from './ProtocolInterface.js';
|
|
3
|
+
import b4a from 'b4a';
|
|
4
|
+
import c from 'compact-encoding';
|
|
5
|
+
|
|
6
|
+
class LegacyProtocol extends ProtocolInterface {
|
|
7
|
+
#channel;
|
|
8
|
+
#session;
|
|
9
|
+
#config;
|
|
10
|
+
#router;
|
|
11
|
+
|
|
12
|
+
constructor(router, connection, config) {
|
|
13
|
+
super(router, connection, config);
|
|
14
|
+
this.#config = config;
|
|
15
|
+
this.#router = router;
|
|
16
|
+
this.init(connection);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
get channel() {
|
|
20
|
+
return this.#channel;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
get session() {
|
|
24
|
+
return this.#session;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
init(connection) {
|
|
28
|
+
// TODO: Abstract in a separate function
|
|
29
|
+
const mux = Protomux.from(connection);
|
|
30
|
+
connection.userData = mux;
|
|
31
|
+
|
|
32
|
+
this.#channel = mux.createChannel({
|
|
33
|
+
protocol: b4a.toString(this.#config.channel, 'utf8'),
|
|
34
|
+
onopen() { },
|
|
35
|
+
onclose() { }
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
this.#channel.open();
|
|
39
|
+
|
|
40
|
+
// Todo: Abstract in a separate function
|
|
41
|
+
this.#session = this.#channel.addMessage({
|
|
42
|
+
encoding: c.json,
|
|
43
|
+
onmessage: async (incomingMessage) => {
|
|
44
|
+
try {
|
|
45
|
+
if (typeof incomingMessage === 'object' || typeof incomingMessage === 'string') {
|
|
46
|
+
await this.#router.route(incomingMessage, connection, this.#session);
|
|
47
|
+
} else {
|
|
48
|
+
throw new Error('NetworkMessages: Received message is undefined');
|
|
49
|
+
}
|
|
50
|
+
} catch (error) {
|
|
51
|
+
console.error(`NetworkMessages: Failed to handle incoming message: ${error.message}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
send(message) {
|
|
58
|
+
this.#session.send(message);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
close() {
|
|
62
|
+
this.#channel.close();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export default LegacyProtocol;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
|
|
2
|
+
import b4a from 'b4a';
|
|
3
|
+
import NetworkMessageRouter from './legacy/NetworkMessageRouter.js';
|
|
4
|
+
import NetworkMessageRouterV1 from './v1/NetworkMessageRouter.js';
|
|
5
|
+
import ProtocolSession from './ProtocolSession.js';
|
|
6
|
+
import LegacyProtocol from './LegacyProtocol.js';
|
|
7
|
+
import V1Protocol from './V1Protocol.js';
|
|
8
|
+
class NetworkMessages {
|
|
9
|
+
#legacyMessageRouter;
|
|
10
|
+
#v1MessageRouter;
|
|
11
|
+
#config;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @param {Config} config
|
|
15
|
+
**/
|
|
16
|
+
constructor(state, wallet, rateLimiterService, txPoolService, connectionManager, config) {
|
|
17
|
+
this.#config = config;
|
|
18
|
+
this.#initializeMessageRouter(state, wallet, rateLimiterService, txPoolService, connectionManager);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
#initializeMessageRouter(state, wallet, rateLimiterService, txPoolService, connectionManager) {
|
|
22
|
+
this.#legacyMessageRouter = new NetworkMessageRouter(
|
|
23
|
+
state,
|
|
24
|
+
wallet,
|
|
25
|
+
rateLimiterService,
|
|
26
|
+
txPoolService,
|
|
27
|
+
connectionManager,
|
|
28
|
+
this.#config
|
|
29
|
+
);
|
|
30
|
+
this.#v1MessageRouter = new NetworkMessageRouterV1(this.#config);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async setupProtomuxMessages(connection) {
|
|
34
|
+
// Attach a Protomux instance to this Hyperswarm connection.
|
|
35
|
+
// Protomux multiplexes multiple logical protocol channels over a single encrypted stream.
|
|
36
|
+
|
|
37
|
+
const legacyProtocol = new LegacyProtocol(this.#legacyMessageRouter, connection, this.#config);
|
|
38
|
+
const v1Protocol = new V1Protocol(this.#v1MessageRouter, connection, this.#config);
|
|
39
|
+
|
|
40
|
+
// ProtocolSession is attached to the Hyperswarm connection so other parts of the system (e.g. tryConnect)
|
|
41
|
+
// can send messages without knowing how Protomux was initialized.
|
|
42
|
+
const protocolSession = new ProtocolSession(legacyProtocol, v1Protocol);
|
|
43
|
+
connection.protocolSession = protocolSession;
|
|
44
|
+
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export default NetworkMessages;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ProtocolInterface serves as a base class for all network protocol implementations.
|
|
3
|
+
* @interface
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
class ProtocolInterface {
|
|
7
|
+
|
|
8
|
+
// TODO: Refactor this so we don't need to pass a reference for the whole network instance
|
|
9
|
+
constructor(router, connection, config) {
|
|
10
|
+
if (new.target === ProtocolInterface) {
|
|
11
|
+
throw new Error('ProtocolInterface cannot be instantiated directly');
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
init(connection) {
|
|
16
|
+
// Abstract method. Need to be implemented by subclasses.
|
|
17
|
+
throw new Error('init() method must be implemented by subclass');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
send(message) {
|
|
21
|
+
// Abstract method. Need to be implemented by subclasses.
|
|
22
|
+
throw new Error('send() method must be implemented by subclass');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
close() {
|
|
26
|
+
// Abstract method. Need to be implemented by subclasses.
|
|
27
|
+
throw new Error('close() method must be implemented by subclass');
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export default ProtocolInterface;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// ProtocolSession is a per-peer (per Hyperswarm connection) wrapper that exposes the available
|
|
2
|
+
// protocol messengers (legacy JSON, v1 binary) in one place.
|
|
3
|
+
//
|
|
4
|
+
// Why it exists:
|
|
5
|
+
// - `setupProtomuxMessages(connection)` creates Protomux channels/messages for a specific peer.
|
|
6
|
+
class ProtocolSession {
|
|
7
|
+
#legacyProtocol;
|
|
8
|
+
#v1Protocol;
|
|
9
|
+
|
|
10
|
+
constructor(legacyProtocol, v1Protocol) {
|
|
11
|
+
// These are Protomux "message" objects (returned by channel.addMessage).
|
|
12
|
+
// They are connection-scoped and expose .send(...), already wired to the channel's encoding.
|
|
13
|
+
this.#legacyProtocol = legacyProtocol;
|
|
14
|
+
this.#v1Protocol = v1Protocol;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
getLegacy() {
|
|
18
|
+
return this.#legacyProtocol;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
getV1() {
|
|
22
|
+
return this.#v1Protocol;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
get(protocol) {
|
|
26
|
+
if (protocol === 'legacy') return this.#legacyProtocol;
|
|
27
|
+
if (protocol === 'v1') return this.#v1Protocol;
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
has(protocol) {
|
|
32
|
+
return Boolean(this.get(protocol));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
send(message) {
|
|
36
|
+
// TODO: Support v1 messages
|
|
37
|
+
this.#legacyProtocol.send(message);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
close() {
|
|
41
|
+
if (this.#legacyProtocol) {
|
|
42
|
+
try {
|
|
43
|
+
this.#legacyProtocol.close();
|
|
44
|
+
} catch (e) {
|
|
45
|
+
console.error('Failed to close legacy channel:', e); // TODO: Think about throwing instead
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (this.#v1Protocol) {
|
|
50
|
+
try {
|
|
51
|
+
this.#v1Protocol.close();
|
|
52
|
+
} catch (e) {
|
|
53
|
+
console.error('Failed to close v1 channel:', e); // TODO: Think about throwing instead
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export default ProtocolSession;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import Protomux from 'protomux';
|
|
2
|
+
import ProtocolInterface from './ProtocolInterface.js';
|
|
3
|
+
import b4a from 'b4a';
|
|
4
|
+
import c from 'compact-encoding';
|
|
5
|
+
|
|
6
|
+
class V1Protocol extends ProtocolInterface {
|
|
7
|
+
#channel;
|
|
8
|
+
#session;
|
|
9
|
+
#config;
|
|
10
|
+
#router;
|
|
11
|
+
|
|
12
|
+
constructor(router, connection, config) {
|
|
13
|
+
super(router, connection, config);
|
|
14
|
+
this.#config = config;
|
|
15
|
+
this.#router = router;
|
|
16
|
+
this.init(connection);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
get channel() {
|
|
20
|
+
return this.#channel;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
get session() {
|
|
24
|
+
return this.#session;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
init(connection) {
|
|
28
|
+
const mux = Protomux.from(connection);
|
|
29
|
+
connection.userData = mux;
|
|
30
|
+
|
|
31
|
+
this.#channel = mux.createChannel({
|
|
32
|
+
protocol: 'network/v1',
|
|
33
|
+
onopen() { },
|
|
34
|
+
onclose() { }
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
this.#channel.open();
|
|
38
|
+
|
|
39
|
+
this.#session = this.#channel.addMessage({
|
|
40
|
+
encoding: c.raw,
|
|
41
|
+
onmessage: async (incomingMessage) => {
|
|
42
|
+
try {
|
|
43
|
+
if (b4a.isBuffer(incomingMessage)) {
|
|
44
|
+
await this.#router.route(incomingMessage, connection, this.#session);
|
|
45
|
+
} else {
|
|
46
|
+
throw new Error('NetworkMessages: v1 message must be a buffer');
|
|
47
|
+
}
|
|
48
|
+
} catch (error) {
|
|
49
|
+
console.error(`NetworkMessages: Failed to handle incoming v1 message: ${error.message}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
send(message) {
|
|
56
|
+
this.#session.send(message);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
close() {
|
|
60
|
+
this.#channel.close();
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export default V1Protocol;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import b4a from "b4a";
|
|
2
|
+
import GetRequestHandler from "./handlers/GetRequestHandler.js";
|
|
3
|
+
import ResponseHandler from "./handlers/ResponseHandler.js";
|
|
4
|
+
import RoleOperationHandler from "../shared/handlers/RoleOperationHandler.js";
|
|
5
|
+
import SubnetworkOperationHandler from "../shared/handlers/SubnetworkOperationHandler.js";
|
|
6
|
+
import TransferOperationHandler from "../shared/handlers/TransferOperationHandler.js";
|
|
7
|
+
import { NETWORK_MESSAGE_TYPES } from '../../../../utils/constants.js';
|
|
8
|
+
import * as operation from '../../../../utils/applyOperations.js';
|
|
9
|
+
import State from "../../../state/State.js";
|
|
10
|
+
import PeerWallet from "trac-wallet";
|
|
11
|
+
|
|
12
|
+
class NetworkMessageRouter {
|
|
13
|
+
#handlers;
|
|
14
|
+
#config;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @param {State} state
|
|
18
|
+
* @param {PeerWallet} wallet
|
|
19
|
+
* @param {TransactionRateLimiterService} rateLimiterService
|
|
20
|
+
* @param {TransactionPoolService} txPoolService
|
|
21
|
+
* @param {ConnectionManager} connectionManager
|
|
22
|
+
* @param {Config} config
|
|
23
|
+
**/
|
|
24
|
+
constructor(state, wallet, rateLimiterService, txPoolService, connectionManager, config) {
|
|
25
|
+
this.#config = config;
|
|
26
|
+
|
|
27
|
+
this.#handlers = {
|
|
28
|
+
get: new GetRequestHandler(wallet, state),
|
|
29
|
+
response: new ResponseHandler(state, wallet, connectionManager, this.#config),
|
|
30
|
+
roleTransaction: new RoleOperationHandler(state, wallet, rateLimiterService, txPoolService, this.#config),
|
|
31
|
+
subNetworkTransaction: new SubnetworkOperationHandler(state, wallet, rateLimiterService, txPoolService, this.#config),
|
|
32
|
+
tracNetworkTransaction: new TransferOperationHandler(state, wallet, rateLimiterService, txPoolService, this.#config),
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// NOTE: messageProtomux can be deleted, ad this is a session, and we can extract this from connection
|
|
37
|
+
async route(incomingMessage, connection, messageProtomux) {
|
|
38
|
+
const channelString = b4a.toString(this.#config.channel, 'utf8');
|
|
39
|
+
if (this.#isGetRequest(incomingMessage)) {
|
|
40
|
+
await this.#handlers.get.handle(incomingMessage, messageProtomux, connection, channelString);
|
|
41
|
+
}
|
|
42
|
+
else if (this.#isResponse(incomingMessage)) {
|
|
43
|
+
await this.#handlers.response.handle(incomingMessage, connection, channelString);
|
|
44
|
+
}
|
|
45
|
+
else if (this.#isRoleAccessOperation(incomingMessage)) {
|
|
46
|
+
await this.#handlers.roleTransaction.handle(incomingMessage, connection);
|
|
47
|
+
}
|
|
48
|
+
else if (this.#isSubnetworkOperation(incomingMessage)) {
|
|
49
|
+
await this.#handlers.subNetworkTransaction.handle(incomingMessage, connection);
|
|
50
|
+
}
|
|
51
|
+
else if (this.#isTransferOperation(incomingMessage)) {
|
|
52
|
+
await this.#handlers.tracNetworkTransaction.handle(incomingMessage, connection);
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
throw new Error(`Failed to route message. Pubkey of requester is ${connection.remotePublicKey ? b4a.toString(connection.remotePublicKey, 'hex') : 'unknown'}`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
#isGetRequest(message) {
|
|
61
|
+
return Object.values(NETWORK_MESSAGE_TYPES.GET).includes(message);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
#isResponse(message) {
|
|
66
|
+
return Object.values(NETWORK_MESSAGE_TYPES.RESPONSE).includes(message.op);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
#isRoleAccessOperation(message) {
|
|
70
|
+
return operation.isRoleAccess(message.type)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
#isSubnetworkOperation(message) {
|
|
74
|
+
return operation.isTransaction(message.type) ||
|
|
75
|
+
operation.isBootstrapDeployment(message.type)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
#isTransferOperation(message) {
|
|
79
|
+
return operation.isTransfer(message.type)
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
export default NetworkMessageRouter;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { NETWORK_MESSAGE_TYPES } from '../../../../../utils/constants.js';
|
|
2
|
+
import PeerWallet from 'trac-wallet';
|
|
3
|
+
import b4a from 'b4a';
|
|
4
|
+
|
|
5
|
+
class GetRequestHandler {
|
|
6
|
+
#wallet;
|
|
7
|
+
#state;
|
|
8
|
+
|
|
9
|
+
constructor(wallet, state) {
|
|
10
|
+
this.#wallet = wallet;
|
|
11
|
+
this.#state = state;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
get state() {
|
|
15
|
+
return this.#state;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async handle(message, messageProtomux, connection, channelString) {
|
|
19
|
+
switch (message) {
|
|
20
|
+
case NETWORK_MESSAGE_TYPES.GET.VALIDATOR:
|
|
21
|
+
await this.handleGetValidatorResponse(messageProtomux, connection, channelString);
|
|
22
|
+
break;
|
|
23
|
+
default:
|
|
24
|
+
throw new Error(`Unhandled GET type: ${message}`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async handleGetValidatorResponse(messageProtomux, connection, channelString) {
|
|
29
|
+
const nonce = PeerWallet.generateNonce().toString('hex');
|
|
30
|
+
const payload = {
|
|
31
|
+
op: 'validatorResponse',
|
|
32
|
+
wk: this.state.writingKey.toString('hex'),
|
|
33
|
+
address: this.#wallet.address,
|
|
34
|
+
nonce: nonce,
|
|
35
|
+
channel: channelString,
|
|
36
|
+
issuer: connection.remotePublicKey.toString('hex'),
|
|
37
|
+
timestamp: Date.now(),
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
const hashInput = b4a.from(JSON.stringify(payload), 'utf8');
|
|
42
|
+
const hash = await PeerWallet.blake3(hashInput);
|
|
43
|
+
const sig = this.#wallet.sign(hash);
|
|
44
|
+
|
|
45
|
+
const responseMessage = {
|
|
46
|
+
...payload,
|
|
47
|
+
sig: sig.toString('hex'),
|
|
48
|
+
};
|
|
49
|
+
messageProtomux.send(responseMessage);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export default GetRequestHandler;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import ValidatorResponse from '../validators/ValidatorResponse.js';
|
|
2
|
+
import PeerWallet from 'trac-wallet';
|
|
3
|
+
|
|
4
|
+
class ResponseHandler {
|
|
5
|
+
#responseValidator;
|
|
6
|
+
#connectionManager;
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
constructor(state, wallet, connectionManager ,config) {
|
|
10
|
+
this.#responseValidator = new ValidatorResponse(state, wallet, config);
|
|
11
|
+
this.#connectionManager = connectionManager;
|
|
12
|
+
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async handle(message, connection, channelString) {
|
|
16
|
+
await this.#handleValidatorResponse(message, connection, channelString);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async #handleValidatorResponse(message, connection, channelString) {
|
|
20
|
+
const isValid = await this.#responseValidator.validate(message, channelString);
|
|
21
|
+
if (isValid) {
|
|
22
|
+
const validatorAddressString = message.address;
|
|
23
|
+
const validatorPublicKey = PeerWallet.decodeBech32m(validatorAddressString);
|
|
24
|
+
|
|
25
|
+
if (this.#connectionManager.connected(validatorPublicKey)) {
|
|
26
|
+
return;
|
|
27
|
+
// TODO: What we should return? Or maybe we should throw?
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
this.#connectionManager.addValidator(validatorPublicKey, connection)
|
|
31
|
+
} else {
|
|
32
|
+
throw new Error("Validator response verification failed");
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export default ResponseHandler;
|