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,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;
|
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
import Protomux from 'protomux';
|
|
2
2
|
import ProtocolInterface from './ProtocolInterface.js';
|
|
3
|
-
import b4a from 'b4a';
|
|
4
3
|
import c from 'compact-encoding';
|
|
4
|
+
import {encodeV1networkOperation, decodeV1networkOperation} from '../../../utils/protobuf/operationHelpers.js';
|
|
5
5
|
|
|
6
6
|
class V1Protocol extends ProtocolInterface {
|
|
7
7
|
#channel;
|
|
8
8
|
#session;
|
|
9
|
-
#config;
|
|
10
9
|
#router;
|
|
10
|
+
#publicKeyHex;
|
|
11
|
+
#pendingRequestService;
|
|
11
12
|
|
|
12
|
-
constructor(router, connection, config) {
|
|
13
|
-
super(router, connection, config);
|
|
14
|
-
this.#config = config;
|
|
13
|
+
constructor(router, connection, pendingRequestService, config) {
|
|
14
|
+
super(router, connection, pendingRequestService, config);
|
|
15
15
|
this.#router = router;
|
|
16
|
+
this.#publicKeyHex = connection.remotePublicKey.toString('hex');
|
|
17
|
+
this.#pendingRequestService = pendingRequestService;
|
|
16
18
|
this.init(connection);
|
|
17
19
|
}
|
|
18
20
|
|
|
@@ -30,30 +32,47 @@ class V1Protocol extends ProtocolInterface {
|
|
|
30
32
|
|
|
31
33
|
this.#channel = mux.createChannel({
|
|
32
34
|
protocol: 'network/v1',
|
|
33
|
-
onopen() {
|
|
34
|
-
|
|
35
|
+
onopen() {
|
|
36
|
+
},
|
|
37
|
+
onclose() {
|
|
38
|
+
}
|
|
35
39
|
});
|
|
36
40
|
|
|
37
41
|
this.#channel.open();
|
|
38
42
|
|
|
39
43
|
this.#session = this.#channel.addMessage({
|
|
40
44
|
encoding: c.raw,
|
|
41
|
-
onmessage:
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
onmessage: (incomingMessage) => {
|
|
46
|
+
this.#router.route(incomingMessage, connection).catch((err) => {
|
|
47
|
+
console.error(`V1Protocol: unhandled router error: ${err.message}`);
|
|
48
|
+
try {
|
|
49
|
+
connection.end();
|
|
50
|
+
} catch {
|
|
47
51
|
}
|
|
48
|
-
}
|
|
49
|
-
console.error(`NetworkMessages: Failed to handle incoming v1 message: ${error.message}`);
|
|
50
|
-
}
|
|
52
|
+
});
|
|
51
53
|
}
|
|
52
54
|
});
|
|
53
55
|
}
|
|
54
56
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
+
// TODO: Consider making this method private/internal-only, just like 'encode'
|
|
58
|
+
// NOTE: This method might be moved to v1/NetworkMessageRouter.js as it is only used there
|
|
59
|
+
decode(message) {
|
|
60
|
+
return decodeV1networkOperation(message);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async send(message) {
|
|
64
|
+
const encodedMessage = encodeV1networkOperation(message);
|
|
65
|
+
const msgReplyPromise = this.#pendingRequestService.registerPendingRequest(this.#publicKeyHex, message);
|
|
66
|
+
try {
|
|
67
|
+
this.#session.send(encodedMessage);
|
|
68
|
+
} catch (error) {
|
|
69
|
+
this.#pendingRequestService.rejectPendingRequest(message.id, error);
|
|
70
|
+
}
|
|
71
|
+
return msgReplyPromise;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
sendAndForget(message) {
|
|
75
|
+
this.#session.send(encodeV1networkOperation(message));
|
|
57
76
|
}
|
|
58
77
|
|
|
59
78
|
close() {
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { ResultCode } from '../../../utils/constants.js';
|
|
2
|
+
|
|
3
|
+
// TODO: Consider how to behave with ResultCode.UNSPECIFIED
|
|
4
|
+
|
|
5
|
+
export const SENDER_ACTION = Object.freeze({
|
|
6
|
+
UNDEFINED: 'UNDEFINED',
|
|
7
|
+
SUCCESS: 'SUCCESS',
|
|
8
|
+
ROTATE: 'ROTATE',
|
|
9
|
+
NO_ROTATE: 'NO_ROTATE',
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
const SUCCESS_CODES = new Set([
|
|
13
|
+
ResultCode.OK,
|
|
14
|
+
]);
|
|
15
|
+
|
|
16
|
+
const ROTATE_CODES = new Set([
|
|
17
|
+
ResultCode.UNSPECIFIED,
|
|
18
|
+
ResultCode.INVALID_PAYLOAD,
|
|
19
|
+
ResultCode.RATE_LIMITED,
|
|
20
|
+
ResultCode.SIGNATURE_INVALID,
|
|
21
|
+
ResultCode.UNEXPECTED_ERROR,
|
|
22
|
+
ResultCode.TIMEOUT,
|
|
23
|
+
ResultCode.NODE_HAS_NO_WRITE_ACCESS,
|
|
24
|
+
ResultCode.TX_ACCEPTED_PROOF_UNAVAILABLE,
|
|
25
|
+
ResultCode.NODE_OVERLOADED,
|
|
26
|
+
ResultCode.OPERATION_TYPE_UNKNOWN,
|
|
27
|
+
ResultCode.SCHEMA_VALIDATION_FAILED,
|
|
28
|
+
ResultCode.REQUESTER_ADDRESS_INVALID,
|
|
29
|
+
ResultCode.REQUESTER_PUBLIC_KEY_INVALID,
|
|
30
|
+
ResultCode.TX_HASH_MISMATCH,
|
|
31
|
+
ResultCode.TX_SIGNATURE_INVALID,
|
|
32
|
+
ResultCode.TX_EXPIRED,
|
|
33
|
+
ResultCode.TX_ALREADY_EXISTS,
|
|
34
|
+
ResultCode.OPERATION_ALREADY_COMPLETED,
|
|
35
|
+
ResultCode.REQUESTER_NOT_FOUND,
|
|
36
|
+
ResultCode.INSUFFICIENT_FEE_BALANCE,
|
|
37
|
+
ResultCode.EXTERNAL_BOOTSTRAP_EQUALS_MSB_BOOTSTRAP,
|
|
38
|
+
ResultCode.SELF_VALIDATION_FORBIDDEN,
|
|
39
|
+
ResultCode.ROLE_NODE_ENTRY_NOT_FOUND,
|
|
40
|
+
ResultCode.ROLE_NODE_ALREADY_WRITER,
|
|
41
|
+
ResultCode.ROLE_NODE_NOT_WHITELISTED,
|
|
42
|
+
ResultCode.ROLE_NODE_NOT_WRITER,
|
|
43
|
+
ResultCode.ROLE_NODE_IS_INDEXER,
|
|
44
|
+
ResultCode.ROLE_ADMIN_ENTRY_MISSING,
|
|
45
|
+
ResultCode.ROLE_INVALID_RECOVERY_CASE,
|
|
46
|
+
ResultCode.ROLE_UNKNOWN_OPERATION,
|
|
47
|
+
ResultCode.ROLE_INVALID_WRITER_KEY,
|
|
48
|
+
ResultCode.ROLE_INSUFFICIENT_FEE_BALANCE,
|
|
49
|
+
ResultCode.MSB_BOOTSTRAP_MISMATCH,
|
|
50
|
+
ResultCode.EXTERNAL_BOOTSTRAP_NOT_DEPLOYED,
|
|
51
|
+
ResultCode.EXTERNAL_BOOTSTRAP_TX_MISSING,
|
|
52
|
+
ResultCode.EXTERNAL_BOOTSTRAP_MISMATCH,
|
|
53
|
+
ResultCode.BOOTSTRAP_ALREADY_EXISTS,
|
|
54
|
+
ResultCode.TRANSFER_RECIPIENT_ADDRESS_INVALID,
|
|
55
|
+
ResultCode.TRANSFER_RECIPIENT_PUBLIC_KEY_INVALID,
|
|
56
|
+
ResultCode.TRANSFER_AMOUNT_TOO_LARGE,
|
|
57
|
+
ResultCode.TRANSFER_SENDER_NOT_FOUND,
|
|
58
|
+
ResultCode.TRANSFER_INSUFFICIENT_BALANCE,
|
|
59
|
+
ResultCode.TRANSFER_RECIPIENT_BALANCE_OVERFLOW,
|
|
60
|
+
ResultCode.TX_HASH_INVALID_FORMAT,
|
|
61
|
+
ResultCode.INTERNAL_ENQUEUE_VALIDATION_FAILED,
|
|
62
|
+
ResultCode.TX_COMMITTED_RECEIPT_MISSING,
|
|
63
|
+
ResultCode.VALIDATOR_RESPONSE_TX_TYPE_INVALID,
|
|
64
|
+
ResultCode.VALIDATOR_RESPONSE_TX_TYPE_UNKNOWN,
|
|
65
|
+
ResultCode.VALIDATOR_RESPONSE_TX_TYPE_UNSUPPORTED,
|
|
66
|
+
ResultCode.VALIDATOR_RESPONSE_SCHEMA_INVALID,
|
|
67
|
+
ResultCode.PENDING_REQUEST_MISSING_TX_DATA,
|
|
68
|
+
ResultCode.PROOF_PAYLOAD_MISMATCH,
|
|
69
|
+
ResultCode.VALIDATOR_WRITER_KEY_NOT_REGISTERED,
|
|
70
|
+
ResultCode.VALIDATOR_ADDRESS_MISMATCH,
|
|
71
|
+
ResultCode.VALIDATOR_NODE_ENTRY_NOT_FOUND,
|
|
72
|
+
ResultCode.VALIDATOR_NODE_NOT_WRITER,
|
|
73
|
+
ResultCode.VALIDATOR_WRITER_KEY_MISMATCH,
|
|
74
|
+
ResultCode.VALIDATOR_TX_OBJECT_INVALID,
|
|
75
|
+
ResultCode.VALIDATOR_VA_MISSING,
|
|
76
|
+
ResultCode.TX_INVALID_PAYLOAD
|
|
77
|
+
]);
|
|
78
|
+
|
|
79
|
+
const NO_ROTATE_CODES = new Set([
|
|
80
|
+
ResultCode.TX_ALREADY_PENDING,
|
|
81
|
+
]);
|
|
82
|
+
|
|
83
|
+
export function resultToValidatorAction(resultCode) {
|
|
84
|
+
if (SUCCESS_CODES.has(resultCode)) return SENDER_ACTION.SUCCESS;
|
|
85
|
+
if (ROTATE_CODES.has(resultCode)) return SENDER_ACTION.ROTATE;
|
|
86
|
+
if (NO_ROTATE_CODES.has(resultCode)) return SENDER_ACTION.NO_ROTATE;
|
|
87
|
+
return SENDER_ACTION.UNDEFINED;
|
|
88
|
+
}
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import b4a from "b4a";
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import
|
|
2
|
+
import _ from 'lodash';
|
|
3
|
+
import LegacyGetRequestHandler from "./handlers/LegacyGetRequestHandler.js";
|
|
4
|
+
import LegacyResponseHandler from "./handlers/LegacyResponseHandler.js";
|
|
5
|
+
import LegacyRoleOperationHandler from "./handlers/LegacyRoleOperationHandler.js";
|
|
6
|
+
import LegacySubnetworkOperationHandler from "./handlers/LegacySubnetworkOperationHandler.js";
|
|
7
|
+
import LegacyTransferOperationHandler from "./handlers/LegacyTransferOperationHandler.js";
|
|
7
8
|
import { NETWORK_MESSAGE_TYPES } from '../../../../utils/constants.js';
|
|
8
9
|
import * as operation from '../../../../utils/applyOperations.js';
|
|
9
|
-
import State from "../../../state/State.js";
|
|
10
|
-
import PeerWallet from "trac-wallet";
|
|
11
10
|
|
|
12
11
|
class NetworkMessageRouter {
|
|
13
12
|
#handlers;
|
|
@@ -18,29 +17,32 @@ class NetworkMessageRouter {
|
|
|
18
17
|
* @param {PeerWallet} wallet
|
|
19
18
|
* @param {TransactionRateLimiterService} rateLimiterService
|
|
20
19
|
* @param {TransactionPoolService} txPoolService
|
|
21
|
-
* @param {
|
|
22
|
-
* @param {object} config
|
|
20
|
+
* @param {Config} config
|
|
23
21
|
**/
|
|
24
|
-
constructor(state, wallet, rateLimiterService, txPoolService,
|
|
22
|
+
constructor(state, wallet, rateLimiterService, txPoolService, config) {
|
|
25
23
|
this.#config = config;
|
|
26
24
|
|
|
27
25
|
this.#handlers = {
|
|
28
|
-
get: new
|
|
29
|
-
response: new
|
|
30
|
-
roleTransaction: new
|
|
31
|
-
subNetworkTransaction: new
|
|
32
|
-
tracNetworkTransaction: new
|
|
26
|
+
get: new LegacyGetRequestHandler(wallet, state),
|
|
27
|
+
response: new LegacyResponseHandler(state, wallet, this.#config),
|
|
28
|
+
roleTransaction: new LegacyRoleOperationHandler(state, wallet, rateLimiterService, txPoolService, this.#config),
|
|
29
|
+
subNetworkTransaction: new LegacySubnetworkOperationHandler(state, wallet, rateLimiterService, txPoolService, this.#config),
|
|
30
|
+
tracNetworkTransaction: new LegacyTransferOperationHandler(state, wallet, rateLimiterService, txPoolService, this.#config),
|
|
31
|
+
|
|
33
32
|
}
|
|
34
33
|
}
|
|
35
34
|
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
async route(incomingMessage, connection) {
|
|
36
|
+
this.#preValidate(incomingMessage);
|
|
38
37
|
const channelString = b4a.toString(this.#config.channel, 'utf8');
|
|
38
|
+
|
|
39
|
+
// We received a legacy message, so we set the connection protocol accordingly
|
|
40
|
+
connection.protocolSession.setLegacyAsPreferredProtocol();
|
|
39
41
|
if (this.#isGetRequest(incomingMessage)) {
|
|
40
|
-
await this.#handlers.get.handle(incomingMessage,
|
|
42
|
+
await this.#handlers.get.handle(incomingMessage, connection, channelString);
|
|
41
43
|
}
|
|
42
44
|
else if (this.#isResponse(incomingMessage)) {
|
|
43
|
-
await this.#handlers.response.handle(incomingMessage,
|
|
45
|
+
await this.#handlers.response.handle(incomingMessage, channelString);
|
|
44
46
|
}
|
|
45
47
|
else if (this.#isRoleAccessOperation(incomingMessage)) {
|
|
46
48
|
await this.#handlers.roleTransaction.handle(incomingMessage, connection);
|
|
@@ -54,14 +56,18 @@ class NetworkMessageRouter {
|
|
|
54
56
|
else {
|
|
55
57
|
throw new Error(`Failed to route message. Pubkey of requester is ${connection.remotePublicKey ? b4a.toString(connection.remotePublicKey, 'hex') : 'unknown'}`);
|
|
56
58
|
}
|
|
59
|
+
}
|
|
57
60
|
|
|
61
|
+
#preValidate(message) {
|
|
62
|
+
if (!_.isPlainObject(message) && typeof message !== 'string') {
|
|
63
|
+
throw new Error('Invalid message format: expected object or string.');
|
|
64
|
+
}
|
|
58
65
|
}
|
|
59
66
|
|
|
60
67
|
#isGetRequest(message) {
|
|
61
68
|
return Object.values(NETWORK_MESSAGE_TYPES.GET).includes(message);
|
|
62
69
|
}
|
|
63
70
|
|
|
64
|
-
|
|
65
71
|
#isResponse(message) {
|
|
66
72
|
return Object.values(NETWORK_MESSAGE_TYPES.RESPONSE).includes(message.op);
|
|
67
73
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import b4a from 'b4a';
|
|
2
|
-
import {
|
|
2
|
+
import {publicKeyToAddress} from "../../../../../utils/helpers.js";
|
|
3
3
|
|
|
4
|
-
class
|
|
4
|
+
class BaseStateOperationHandler {
|
|
5
5
|
#state;
|
|
6
6
|
#wallet;
|
|
7
7
|
#rateLimiter;
|
|
@@ -13,11 +13,11 @@ class BaseOperationHandler {
|
|
|
13
13
|
* @param {PeerWallet} wallet
|
|
14
14
|
* @param {TransactionRateLimiterService} rateLimiter
|
|
15
15
|
* @param {TransactionPoolService} txPoolService
|
|
16
|
-
* @param {
|
|
16
|
+
* @param {Config} config
|
|
17
17
|
**/
|
|
18
18
|
constructor(state, wallet, rateLimiter, txPoolService, config) {
|
|
19
|
-
if (new.target ===
|
|
20
|
-
throw new Error('
|
|
19
|
+
if (new.target === BaseStateOperationHandler) {
|
|
20
|
+
throw new Error('BaseStateOperationHandler is abstract and cannot be instantiated directly');
|
|
21
21
|
}
|
|
22
22
|
this.#state = state;
|
|
23
23
|
this.#wallet = wallet;
|
|
@@ -30,7 +30,7 @@ class BaseOperationHandler {
|
|
|
30
30
|
// Validate if operation can be processed:
|
|
31
31
|
// - Non-writable nodes cannot process operations
|
|
32
32
|
// - Regular indexers cannot process operations
|
|
33
|
-
// - Admin-indexer can process operations only when network has less than
|
|
33
|
+
// - Admin-indexer can process operations only when network has less than maxWritersForAdminIndexerConnection writers
|
|
34
34
|
const isAllowedToValidate = await this.#state.allowedToValidate(this.#wallet.address);
|
|
35
35
|
const isAdminAllowedToValidate = await this.#state.isAdminAllowedToValidate();
|
|
36
36
|
const canValidate = isAllowedToValidate || isAdminAllowedToValidate;
|
|
@@ -38,18 +38,18 @@ class BaseOperationHandler {
|
|
|
38
38
|
throw new Error('OperationHandler: State is not writable or is an indexer without admin privileges.');
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
if (b4a.byteLength(JSON.stringify(payload)) > MAX_PARTIAL_TX_PAYLOAD_BYTE_SIZE) {
|
|
46
|
-
throw new Error(`OperationHandler: Payload size exceeds maximum limit of ${MAX_PARTIAL_TX_PAYLOAD_BYTE_SIZE} bytes by ${b4a.byteLength(JSON.stringify(payload)) - MAX_PARTIAL_TX_PAYLOAD_BYTE_SIZE} bytes.`);
|
|
41
|
+
this.#txPoolService.validateEnqueue();
|
|
42
|
+
if (b4a.byteLength(JSON.stringify(payload)) > this.#config.transactionPoolSize) {
|
|
43
|
+
throw new Error(
|
|
44
|
+
`OperationHandler: Payload size exceeds maximum limit of ${this.#config.transactionPoolSize} bytes by ${b4a.byteLength(JSON.stringify(payload)) - this.#config.transactionPoolSize} bytes.`);
|
|
47
45
|
}
|
|
48
46
|
|
|
49
47
|
if (!this.#config.disableRateLimit) {
|
|
50
|
-
const shouldDisconnect = this.#rateLimiter.
|
|
48
|
+
const shouldDisconnect = this.#rateLimiter.legacyHandleRateLimit(connection);
|
|
51
49
|
if (shouldDisconnect) {
|
|
52
|
-
throw new Error(
|
|
50
|
+
throw new Error(
|
|
51
|
+
`OperationHandler: Rate limit exceeded for ${publicKeyToAddress(connection.remotePublicKey, this.#config)}. Disconnecting...`
|
|
52
|
+
);
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
55
|
}
|
|
@@ -59,9 +59,19 @@ class BaseOperationHandler {
|
|
|
59
59
|
await this.handleOperation(payload, connection);
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
enqueueTransaction(txHash, encodedOperation) {
|
|
63
|
+
try {
|
|
64
|
+
this.#txPoolService.addTransaction(txHash, encodedOperation);
|
|
65
|
+
} catch (error) {
|
|
66
|
+
throw new Error(
|
|
67
|
+
`${this.constructor.name}: Failed to add transaction ${txHash} to transaction pool: ${error?.message ?? String(error)}`
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
62
72
|
async handleOperation(payload, connection) {
|
|
63
73
|
throw new Error('handleOperation must be implemented by child class');
|
|
64
74
|
}
|
|
65
75
|
|
|
66
76
|
}
|
|
67
|
-
export default
|
|
77
|
+
export default BaseStateOperationHandler;
|
|
@@ -2,7 +2,7 @@ import { NETWORK_MESSAGE_TYPES } from '../../../../../utils/constants.js';
|
|
|
2
2
|
import PeerWallet from 'trac-wallet';
|
|
3
3
|
import b4a from 'b4a';
|
|
4
4
|
|
|
5
|
-
class
|
|
5
|
+
class LegacyGetRequestHandler {
|
|
6
6
|
#wallet;
|
|
7
7
|
#state;
|
|
8
8
|
|
|
@@ -15,17 +15,17 @@ class GetRequestHandler {
|
|
|
15
15
|
return this.#state;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
async handle(message,
|
|
18
|
+
async handle(message, connection, channelString) {
|
|
19
19
|
switch (message) {
|
|
20
20
|
case NETWORK_MESSAGE_TYPES.GET.VALIDATOR:
|
|
21
|
-
await this.handleGetValidatorResponse(
|
|
21
|
+
await this.handleGetValidatorResponse(connection, channelString);
|
|
22
22
|
break;
|
|
23
23
|
default:
|
|
24
24
|
throw new Error(`Unhandled GET type: ${message}`);
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
async handleGetValidatorResponse(
|
|
28
|
+
async handleGetValidatorResponse(connection, channelString) {
|
|
29
29
|
const nonce = PeerWallet.generateNonce().toString('hex');
|
|
30
30
|
const payload = {
|
|
31
31
|
op: 'validatorResponse',
|
|
@@ -46,8 +46,8 @@ class GetRequestHandler {
|
|
|
46
46
|
...payload,
|
|
47
47
|
sig: sig.toString('hex'),
|
|
48
48
|
};
|
|
49
|
-
|
|
49
|
+
connection.protocolSession.sendAndForget(responseMessage)
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
export default
|
|
53
|
+
export default LegacyGetRequestHandler;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import ValidatorResponse from '../validators/ValidatorResponse.js';
|
|
2
|
+
|
|
3
|
+
class LegacyResponseHandler {
|
|
4
|
+
#responseValidator;
|
|
5
|
+
|
|
6
|
+
constructor(state, wallet, config) {
|
|
7
|
+
this.#responseValidator = new ValidatorResponse(state, wallet, config);
|
|
8
|
+
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async handle(message, channelString) {
|
|
12
|
+
await this.#handleValidatorResponse(message, channelString);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async #handleValidatorResponse(message, channelString) {
|
|
16
|
+
const isValid = await this.#responseValidator.validate(message, channelString);
|
|
17
|
+
if (!isValid) {
|
|
18
|
+
throw new Error("Validator response verification failed");
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export default LegacyResponseHandler;
|