trac-msb 0.2.8 → 0.2.9
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/msb.mjs +3 -3
- package/package.json +8 -3
- package/proto/network.proto +74 -0
- package/rpc/create_server.js +2 -2
- package/rpc/handlers.js +2 -2
- package/rpc/rpc_server.js +2 -2
- package/rpc/rpc_services.js +44 -3
- package/rpc/utils/helpers.js +1 -1
- package/src/config/env.js +2 -0
- package/src/core/network/Network.js +29 -61
- 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 +1 -2
- 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 +16 -15
- package/src/core/network/{messaging → protocols/shared}/handlers/base/BaseOperationHandler.js +7 -11
- 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 +4 -4
- package/src/core/network/services/MessageOrchestrator.js +1 -1
- package/src/core/network/services/TransactionPoolService.js +1 -2
- package/src/core/network/services/TransactionRateLimiterService.js +5 -3
- package/src/core/state/State.js +1 -2
- package/src/index.js +153 -180
- 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/cli.js +0 -8
- package/src/utils/constants.js +34 -14
- package/src/utils/normalizers.js +84 -2
- package/src/utils/protobuf/network.cjs +840 -0
- package/src/utils/protobuf/operationHelpers.js +10 -0
- package/tests/acceptance/v1/rpc.test.mjs +1 -1
- package/tests/fixtures/networkV1.fixtures.js +84 -0
- package/tests/fixtures/protobuf.fixtures.js +83 -0
- package/tests/helpers/config.js +1 -1
- package/tests/helpers/setupApplyTests.js +53 -46
- 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 +203 -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/normalizers/normalizers.test.js +469 -0
- package/tests/unit/utils/protobuf/operationHelpers.test.js +120 -2
- 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
package/msb.mjs
CHANGED
|
@@ -15,8 +15,8 @@ const rpc = {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
const options = args.includes('--rpc') ? rpc : { storeName }
|
|
18
|
-
|
|
19
|
-
const msb = new MainSettlementBus(
|
|
18
|
+
const config = createConfig(ENV.MAINNET, options)
|
|
19
|
+
const msb = new MainSettlementBus(config);
|
|
20
20
|
|
|
21
21
|
msb.ready().then(async () => {
|
|
22
22
|
if (runRpc) {
|
|
@@ -25,7 +25,7 @@ msb.ready().then(async () => {
|
|
|
25
25
|
const port = (portIndex !== -1 && args[portIndex + 1]) ? parseInt(args[portIndex + 1], 10) : 5000;
|
|
26
26
|
const hostIndex = args.indexOf('--host');
|
|
27
27
|
const host = (hostIndex !== -1 && args[hostIndex + 1]) ? args[hostIndex + 1] : 'localhost';
|
|
28
|
-
startRpcServer(msb, host, port);
|
|
28
|
+
startRpcServer(msb, config , host, port);
|
|
29
29
|
} else {
|
|
30
30
|
console.log('RPC server will not be started.');
|
|
31
31
|
msb.interactiveMode();
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trac-msb",
|
|
3
3
|
"main": "msb.mjs",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.9",
|
|
5
5
|
"pear": {
|
|
6
6
|
"name": "trac-msb",
|
|
7
7
|
"type": "terminal"
|
|
@@ -49,8 +49,9 @@
|
|
|
49
49
|
"protomux-wakeup": "2.4.0",
|
|
50
50
|
"readline": "npm:bare-node-readline",
|
|
51
51
|
"ready-resource": "1.1.2",
|
|
52
|
-
"trac-wallet": "
|
|
53
|
-
"tty": "npm:bare-node-tty"
|
|
52
|
+
"trac-wallet": "1.0.1",
|
|
53
|
+
"tty": "npm:bare-node-tty",
|
|
54
|
+
"uuid": "^13.0.0"
|
|
54
55
|
},
|
|
55
56
|
"devDependencies": {
|
|
56
57
|
"bare-os": "^3.6.1",
|
|
@@ -66,5 +67,9 @@
|
|
|
66
67
|
"publishConfig": {
|
|
67
68
|
"registry": "https://registry.npmjs.org",
|
|
68
69
|
"access": "public"
|
|
70
|
+
},
|
|
71
|
+
"repository": {
|
|
72
|
+
"type": "git",
|
|
73
|
+
"url": "https://github.com/Trac-Systems/main_settlement_bus"
|
|
69
74
|
}
|
|
70
75
|
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
syntax = "proto3";
|
|
2
|
+
|
|
3
|
+
package network.v1;
|
|
4
|
+
|
|
5
|
+
enum MessageType {
|
|
6
|
+
MESSAGE_TYPE_UNSPECIFIED = 0;
|
|
7
|
+
MESSAGE_TYPE_VALIDATOR_CONNECTION_REQUEST = 1;
|
|
8
|
+
MESSAGE_TYPE_VALIDATOR_CONNECTION_RESPONSE = 2;
|
|
9
|
+
MESSAGE_TYPE_LIVENESS_REQUEST = 3;
|
|
10
|
+
MESSAGE_TYPE_LIVENESS_RESPONSE = 4;
|
|
11
|
+
MESSAGE_TYPE_BROADCAST_TRANSACTION_REQUEST = 5;
|
|
12
|
+
MESSAGE_TYPE_BROADCAST_TRANSACTION_RESPONSE = 6;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
enum ResultCode {
|
|
16
|
+
RESULT_CODE_UNSPECIFIED = 0;
|
|
17
|
+
RESULT_CODE_OK = 1;
|
|
18
|
+
RESULT_CODE_INVALID_PAYLOAD = 2;
|
|
19
|
+
RESULT_CODE_UNSUPPORTED_VERSION = 3;
|
|
20
|
+
RESULT_CODE_RATE_LIMITED = 4;
|
|
21
|
+
RESULT_CODE_TIMEOUT = 5;
|
|
22
|
+
RESULT_CODE_SIGNATURE_INVALID = 6;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
message ValidatorConnectionRequest {
|
|
26
|
+
string issuer_address = 1;
|
|
27
|
+
bytes nonce = 2;
|
|
28
|
+
bytes signature = 3;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
message ValidatorConnectionResponse {
|
|
32
|
+
string issuer_address = 1;
|
|
33
|
+
bytes nonce = 2;
|
|
34
|
+
bytes signature = 3;
|
|
35
|
+
ResultCode result = 4;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
message LivenessRequest {
|
|
39
|
+
bytes nonce = 1;
|
|
40
|
+
bytes signature = 2;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
message LivenessResponse {
|
|
44
|
+
bytes nonce = 1;
|
|
45
|
+
bytes signature = 2;
|
|
46
|
+
ResultCode result = 3;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
message BroadcastTransactionRequest {
|
|
50
|
+
bytes data = 1; // binary encoded payload
|
|
51
|
+
bytes nonce = 2;
|
|
52
|
+
bytes signature = 3;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
message BroadcastTransactionResponse {
|
|
56
|
+
bytes nonce = 1;
|
|
57
|
+
bytes signature = 2;
|
|
58
|
+
ResultCode result = 3;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
message MessageHeader {
|
|
62
|
+
MessageType type = 1;
|
|
63
|
+
string id = 2;
|
|
64
|
+
uint64 timestamp = 3;
|
|
65
|
+
oneof field {
|
|
66
|
+
ValidatorConnectionRequest validator_connection_request = 4;
|
|
67
|
+
ValidatorConnectionResponse validator_connection_response = 5;
|
|
68
|
+
LivenessRequest liveness_request = 6;
|
|
69
|
+
LivenessResponse liveness_response = 7;
|
|
70
|
+
BroadcastTransactionRequest broadcast_transaction_request = 8;
|
|
71
|
+
BroadcastTransactionResponse broadcast_transaction_response = 9;
|
|
72
|
+
}
|
|
73
|
+
repeated string capabilities = 10;
|
|
74
|
+
}
|
package/rpc/create_server.js
CHANGED
|
@@ -3,7 +3,7 @@ import http from 'http'
|
|
|
3
3
|
import { applyCors } from './cors.js';
|
|
4
4
|
import { routes } from './routes/index.js';
|
|
5
5
|
|
|
6
|
-
export const createServer = (msbInstance) => {
|
|
6
|
+
export const createServer = (msbInstance, config) => {
|
|
7
7
|
const server = http.createServer({}, async (req, res) => {
|
|
8
8
|
|
|
9
9
|
// --- 1. Define safe 'respond' utility (Payload MUST be an object) ---
|
|
@@ -53,7 +53,7 @@ export const createServer = (msbInstance) => {
|
|
|
53
53
|
try {
|
|
54
54
|
// This try/catch covers synchronous errors and errors from awaited promises
|
|
55
55
|
// within the route.handler function.
|
|
56
|
-
await route.handler({ req, res, respond,
|
|
56
|
+
await route.handler({ req, res, respond, msbInstance, config});
|
|
57
57
|
} catch (error) {
|
|
58
58
|
// Catch errors thrown directly from the handler (or its awaited parts)
|
|
59
59
|
console.error(`Error on ${route.path}:`, error);
|
package/rpc/handlers.js
CHANGED
|
@@ -52,7 +52,7 @@ export async function handleConfirmedLength({ msbInstance, respond }) {
|
|
|
52
52
|
respond(200, { confirmed_length });
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
export async function handleBroadcastTransaction({ msbInstance, respond, req }) {
|
|
55
|
+
export async function handleBroadcastTransaction({ msbInstance, config, respond, req }) {
|
|
56
56
|
let body = '';
|
|
57
57
|
req.on('data', chunk => {
|
|
58
58
|
body += chunk.toString();
|
|
@@ -72,7 +72,7 @@ export async function handleBroadcastTransaction({ msbInstance, respond, req })
|
|
|
72
72
|
const decodedPayload = decodeBase64Payload(payload);
|
|
73
73
|
validatePayloadStructure(decodedPayload);
|
|
74
74
|
const sanitizedPayload = sanitizeTransferPayload(decodedPayload);
|
|
75
|
-
const result = await broadcastTransaction(msbInstance, sanitizedPayload);
|
|
75
|
+
const result = await broadcastTransaction(msbInstance, config, sanitizedPayload);
|
|
76
76
|
respond(200, { result });
|
|
77
77
|
} catch (error) {
|
|
78
78
|
let code = error instanceof SyntaxError ? 400 : 500;
|
package/rpc/rpc_server.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { createServer } from "./create_server.js";
|
|
2
2
|
|
|
3
3
|
// Called by msb.mjs file
|
|
4
|
-
export function startRpcServer(msbInstance, host, port) {
|
|
5
|
-
const server = createServer(msbInstance)
|
|
4
|
+
export function startRpcServer(msbInstance, config ,host, port) {
|
|
5
|
+
const server = createServer(msbInstance, config)
|
|
6
6
|
|
|
7
7
|
return server.listen(port, host, () => {
|
|
8
8
|
console.log(`Running RPC with http at http://${host}:${port}`);
|
package/rpc/rpc_services.js
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import { bufferToBigInt } from "../src/utils/amountSerialization.js";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
normalizeDecodedPayloadForJson,
|
|
4
|
+
normalizeTransactionOperation,
|
|
5
|
+
normalizeTransferOperation
|
|
6
|
+
} from "../src/utils/normalizers.js";
|
|
3
7
|
import { get_confirmed_tx_info, get_unconfirmed_tx_info } from "../src/utils/cli.js";
|
|
8
|
+
import {OperationType} from "../src/utils/constants.js";
|
|
9
|
+
import b4a from "b4a";
|
|
10
|
+
import PartialTransaction from "../src/core/network/protocols/shared/validators/PartialTransaction.js";
|
|
11
|
+
import PartialTransfer from "../src/core/network/protocols/shared/validators/PartialTransfer.js";
|
|
4
12
|
|
|
5
13
|
export async function getBalance(msbInstance, address, confirmed) {
|
|
6
14
|
const state = msbInstance.state;
|
|
@@ -36,8 +44,41 @@ export async function getUnconfirmedLength(msbInstance) {
|
|
|
36
44
|
return msbInstance.state.getUnsignedLength();
|
|
37
45
|
}
|
|
38
46
|
|
|
39
|
-
export async function broadcastTransaction(msbInstance, payload) {
|
|
40
|
-
|
|
47
|
+
export async function broadcastTransaction(msbInstance, config, payload) {
|
|
48
|
+
if (!payload) {
|
|
49
|
+
throw new Error("Transaction payload is required for broadcasting.");
|
|
50
|
+
}
|
|
51
|
+
let normalizedPayload;
|
|
52
|
+
let isValid = false;
|
|
53
|
+
let hash;
|
|
54
|
+
|
|
55
|
+
const partialTransferValidator = new PartialTransfer(msbInstance.state, null , config);
|
|
56
|
+
const partialTransactionValidator = new PartialTransaction(msbInstance.state, null , config);
|
|
57
|
+
|
|
58
|
+
if (payload.type === OperationType.TRANSFER) {
|
|
59
|
+
normalizedPayload = normalizeTransferOperation(payload, config);
|
|
60
|
+
isValid = await partialTransferValidator.validate(normalizedPayload);
|
|
61
|
+
hash = b4a.toString(normalizedPayload.tro.tx, "hex");
|
|
62
|
+
} else if (payload.type === OperationType.TX) {
|
|
63
|
+
normalizedPayload = normalizeTransactionOperation(payload, config);
|
|
64
|
+
isValid = await partialTransactionValidator.validate(normalizedPayload);
|
|
65
|
+
hash = b4a.toString(normalizedPayload.txo.tx, "hex");
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (!isValid) {
|
|
69
|
+
throw new Error("Invalid transaction payload.");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const success = await msbInstance.broadcastPartialTransaction(payload);
|
|
73
|
+
|
|
74
|
+
if (!success) {
|
|
75
|
+
throw new Error("Failed to broadcast transaction after multiple attempts.");
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const signedLength = msbInstance.state.getSignedLength();
|
|
79
|
+
const unsignedLength = msbInstance.state.getUnsignedLength();
|
|
80
|
+
|
|
81
|
+
return { message: "Transaction broadcasted successfully.", signedLength, unsignedLength, tx: hash };
|
|
41
82
|
}
|
|
42
83
|
|
|
43
84
|
export async function getTxHashes(msbInstance, start, end) {
|
package/rpc/utils/helpers.js
CHANGED
package/src/config/env.js
CHANGED
|
@@ -6,6 +6,8 @@ export const ENV = {
|
|
|
6
6
|
DEVELOPMENT: 'development',
|
|
7
7
|
TESTNET1: 'testnet1'
|
|
8
8
|
}
|
|
9
|
+
// TODO: CREATE TEST ENV CONFIG SIMILAR TO MAINNET AND USE IT IN TESTS.
|
|
10
|
+
// TODO: CREATE TESTNET1 ENV CONFIG and update npm scripts to run node witn mainnet or testnet1.
|
|
9
11
|
|
|
10
12
|
const configData = {
|
|
11
13
|
[ENV.MAINNET]: {
|
|
@@ -4,7 +4,7 @@ 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,
|
|
@@ -18,7 +18,8 @@ import ConnectionManager from './services/ConnectionManager.js';
|
|
|
18
18
|
import MessageOrchestrator from './services/MessageOrchestrator.js';
|
|
19
19
|
import NetworkWalletFactory from './identity/NetworkWalletFactory.js';
|
|
20
20
|
import { EventType } from '../../utils/constants.js';
|
|
21
|
-
|
|
21
|
+
import { networkMessageFactory } from '../../messages/network/v1/networkMessageFactory.js';
|
|
22
|
+
import TransactionRateLimiterService from './services/TransactionRateLimiterService.js';
|
|
22
23
|
// -- Debug Mode --
|
|
23
24
|
// TODO: Implement a better debug system in the future. This is just temporary.
|
|
24
25
|
const DEBUG = false;
|
|
@@ -42,6 +43,7 @@ class Network extends ReadyResource {
|
|
|
42
43
|
#pendingConnections;
|
|
43
44
|
#connectTimeoutMs;
|
|
44
45
|
#maxPendingConnections;
|
|
46
|
+
#rateLimiter;
|
|
45
47
|
|
|
46
48
|
/**
|
|
47
49
|
* @param {State} state
|
|
@@ -53,18 +55,12 @@ class Network extends ReadyResource {
|
|
|
53
55
|
this.#config = config
|
|
54
56
|
this.#connectTimeoutMs = config.connectTimeoutMs || 5000;
|
|
55
57
|
this.#maxPendingConnections = config.maxPendingConnections || 50;
|
|
56
|
-
|
|
57
58
|
this.#pendingConnections = new Map();
|
|
58
59
|
this.#transactionPoolService = new TransactionPoolService(state, address, this.#config);
|
|
59
60
|
this.#validatorObserverService = new ValidatorObserverService(this, state, address, this.#config);
|
|
60
|
-
this.#networkMessages = new NetworkMessages(this, this.#config);
|
|
61
61
|
this.#validatorConnectionManager = new ConnectionManager(this.#config);
|
|
62
62
|
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;
|
|
63
|
+
|
|
68
64
|
}
|
|
69
65
|
|
|
70
66
|
get swarm() {
|
|
@@ -97,6 +93,7 @@ class Network extends ReadyResource {
|
|
|
97
93
|
}
|
|
98
94
|
|
|
99
95
|
async _close() {
|
|
96
|
+
// TODO: Implement better "await" logic for stopping services
|
|
100
97
|
console.log('Network: closing gracefully...');
|
|
101
98
|
this.transactionPoolService.stopPool();
|
|
102
99
|
await sleep(100);
|
|
@@ -119,7 +116,7 @@ class Network extends ReadyResource {
|
|
|
119
116
|
});
|
|
120
117
|
|
|
121
118
|
// VALIDATOR_CONNECTION_READY
|
|
122
|
-
this.on(EventType.VALIDATOR_CONNECTION_READY, ({ publicKey, type, connection }) => {
|
|
119
|
+
this.on(EventType.VALIDATOR_CONNECTION_READY, async ({ publicKey, type, connection }) => {
|
|
123
120
|
debugLog(`Network Event: VALIDATOR_CONNECTION_READY | PublicKey: ${publicKey} | Type: ${type}`);
|
|
124
121
|
const { timeoutId } = this.#pendingConnections.get(publicKey);
|
|
125
122
|
if (!timeoutId) return;
|
|
@@ -128,19 +125,15 @@ class Network extends ReadyResource {
|
|
|
128
125
|
this.#pendingConnections.delete(publicKey);
|
|
129
126
|
|
|
130
127
|
if (type === 'validator') {
|
|
131
|
-
|
|
132
|
-
this.#validatorConnectionManager.addValidator(target, connection);
|
|
133
|
-
this.#sendRequestByType(connection, type);
|
|
128
|
+
await connection.protocolSession.send(NETWORK_MESSAGE_TYPES.GET.VALIDATOR);
|
|
134
129
|
}
|
|
130
|
+
|
|
135
131
|
});
|
|
136
132
|
}
|
|
137
133
|
|
|
138
134
|
cleanupNetworkListeners() {
|
|
139
|
-
|
|
140
|
-
this.removeAllListeners(
|
|
141
|
-
|
|
142
|
-
// connect:ready
|
|
143
|
-
this.removeAllListeners('connect:ready');
|
|
135
|
+
this.removeAllListeners(EventType.VALIDATOR_CONNECTION_TIMEOUT);
|
|
136
|
+
this.removeAllListeners(EventType.VALIDATOR_CONNECTION_READY);
|
|
144
137
|
}
|
|
145
138
|
|
|
146
139
|
cleanupPendingConnections() {
|
|
@@ -167,12 +160,23 @@ class Network extends ReadyResource {
|
|
|
167
160
|
maxClientConnections: MAX_CLIENT_CONNECTIONS
|
|
168
161
|
});
|
|
169
162
|
|
|
163
|
+
this.#rateLimiter = new TransactionRateLimiterService(this.#swarm);
|
|
164
|
+
this.#networkMessages = new NetworkMessages(
|
|
165
|
+
state,
|
|
166
|
+
wrappedWallet,
|
|
167
|
+
this.#rateLimiter,
|
|
168
|
+
this.#transactionPoolService,
|
|
169
|
+
this.#validatorConnectionManager,
|
|
170
|
+
this.#config
|
|
171
|
+
);
|
|
172
|
+
|
|
170
173
|
console.log(`Channel: ${b4a.toString(this.#config.channel)}`);
|
|
171
|
-
this.#networkMessages.initializeMessageRouter(state, wrappedWallet);
|
|
172
174
|
|
|
173
175
|
this.#swarm.on('connection', async (connection) => {
|
|
174
|
-
|
|
175
|
-
|
|
176
|
+
// Per-peer connection initialization:
|
|
177
|
+
// - attach Protomux (legacy + v1 channels/messages)
|
|
178
|
+
// - attach connection.protocolSession (used later by tryConnect / orchestrators to send messages)
|
|
179
|
+
await this.#networkMessages.setupProtomuxMessages(connection);
|
|
176
180
|
|
|
177
181
|
// ATTENTION: Must be called AFTER the protomux init above
|
|
178
182
|
const stream = store.replicate(connection);
|
|
@@ -185,17 +189,9 @@ class Network extends ReadyResource {
|
|
|
185
189
|
}
|
|
186
190
|
|
|
187
191
|
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
|
-
|
|
192
|
+
this.#swarm.leavePeer(connection.remotePublicKey);
|
|
193
|
+
this.#validatorConnectionManager.remove(publicKey);
|
|
194
|
+
connection.protocolSession.close();
|
|
199
195
|
});
|
|
200
196
|
|
|
201
197
|
connection.on('error', (error) => {
|
|
@@ -209,7 +205,6 @@ class Network extends ReadyResource {
|
|
|
209
205
|
return;
|
|
210
206
|
}
|
|
211
207
|
console.error(error.message)
|
|
212
|
-
|
|
213
208
|
});
|
|
214
209
|
|
|
215
210
|
});
|
|
@@ -259,7 +254,7 @@ class Network extends ReadyResource {
|
|
|
259
254
|
const peerInfo = this.#swarm.peers.get(publicKey);
|
|
260
255
|
if (peerInfo) {
|
|
261
256
|
const connection = this.#swarm._allConnections.get(peerInfo.publicKey);
|
|
262
|
-
if (connection && connection.
|
|
257
|
+
if (connection && connection.protocolSession) {
|
|
263
258
|
await this.#finalizeConnection(publicKey, type, connection);
|
|
264
259
|
}
|
|
265
260
|
}
|
|
@@ -276,33 +271,6 @@ class Network extends ReadyResource {
|
|
|
276
271
|
debugLog(`Network.finalizeConnection: Connected to peer: ${publicKey} as type: ${type}`);
|
|
277
272
|
}
|
|
278
273
|
|
|
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
274
|
#getNetworkWalletWrapper(wallet, keyPair) {
|
|
307
275
|
if (!this.#identityProvider) {
|
|
308
276
|
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 {object} 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;
|