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
package/msb.mjs
CHANGED
|
@@ -1,31 +1,14 @@
|
|
|
1
1
|
import { MainSettlementBus } from './src/index.js';
|
|
2
2
|
import { startRpcServer } from './rpc/rpc_server.js';
|
|
3
|
-
import {
|
|
3
|
+
import { isRpcEnabled, resolveConfig } from './src/config/args.js';
|
|
4
4
|
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
const args = pearApp?.args ?? runtimeArgs;
|
|
8
|
-
const runRpc = args.includes('--rpc');
|
|
9
|
-
const storeName = pearApp?.args?.[0] ?? runtimeArgs[0]
|
|
10
|
-
|
|
11
|
-
const rpc = {
|
|
12
|
-
storeName: pearApp?.args?.[0] ?? runtimeArgs[0],
|
|
13
|
-
enableWallet: false,
|
|
14
|
-
enableInteractiveMode: false
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const options = args.includes('--rpc') ? rpc : { storeName }
|
|
18
|
-
|
|
19
|
-
const msb = new MainSettlementBus(createConfig(ENV.MAINNET, options));
|
|
5
|
+
const config = resolveConfig()
|
|
6
|
+
const msb = new MainSettlementBus(config);
|
|
20
7
|
|
|
21
8
|
msb.ready().then(async () => {
|
|
22
|
-
if (
|
|
9
|
+
if (isRpcEnabled()) {
|
|
23
10
|
console.log('Starting RPC server...');
|
|
24
|
-
|
|
25
|
-
const port = (portIndex !== -1 && args[portIndex + 1]) ? parseInt(args[portIndex + 1], 10) : 5000;
|
|
26
|
-
const hostIndex = args.indexOf('--host');
|
|
27
|
-
const host = (hostIndex !== -1 && args[hostIndex + 1]) ? args[hostIndex + 1] : 'localhost';
|
|
28
|
-
startRpcServer(msb, host, port);
|
|
11
|
+
startRpcServer(msb, config);
|
|
29
12
|
} else {
|
|
30
13
|
console.log('RPC server will not be started.');
|
|
31
14
|
msb.interactiveMode();
|
package/package.json
CHANGED
|
@@ -1,20 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trac-msb",
|
|
3
3
|
"main": "msb.mjs",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.10",
|
|
5
5
|
"pear": {
|
|
6
6
|
"name": "trac-msb",
|
|
7
7
|
"type": "terminal"
|
|
8
8
|
},
|
|
9
9
|
"type": "module",
|
|
10
10
|
"scripts": {
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
"env-
|
|
16
|
-
"env-prod-rpc": "if [ -f .env ]; then set -a; . ./.env; set +a; fi; NODE_OPTIONS='--max-old-space-size=4096' pear run . ${MSB_STORE:-rpc-node-store} --rpc --host ${MSB_HOST:-127.0.0.1} --port ${MSB_PORT:-5000}",
|
|
17
|
-
"env-prod-rpc-docker": "if [ -f .env ]; then set -a; . ./.env; set +a; fi; NODE_OPTIONS='--max-old-space-size=4096' node msb.mjs ${MSB_STORE:-rpc-node-store} --rpc --host 0.0.0.0 --port ${MSB_PORT:-5000}",
|
|
11
|
+
"start": "NODE_OPTIONS='--max-old-space-size=4096' pear run .",
|
|
12
|
+
"rpc": "NODE_OPTIONS='--max-old-space-size=4096' pear run . --rpc --host ${npm_config_host} --port ${npm_config_port}",
|
|
13
|
+
"env": "if [ -f .env ]; then set -a; . ./.env; set +a; fi; NODE_OPTIONS='--max-old-space-size=4096' pear run . --stores-directory ${STORES_DIRECTORY:-stores} --network ${NETWORK:-mainnet}",
|
|
14
|
+
"env-rpc": "if [ -f .env ]; then set -a; . ./.env; set +a; fi; NODE_OPTIONS='--max-old-space-size=4096' pear run . --stores-directory ${STORES_DIRECTORY:-stores} --rpc --host ${MSB_HOST:-127.0.0.1} --port ${MSB_PORT:-5000} --network ${NETWORK:-mainnet}",
|
|
15
|
+
"env-rpc-docker": "if [ -f .env ]; then set -a; . ./.env; set +a; fi; NODE_OPTIONS='--max-old-space-size=4096' node msb.mjs --stores-directory ${STORES_DIRECTORY:-stores} --rpc --host 0.0.0.0 --port ${MSB_PORT:-5000} --network ${NETWORK:-mainnet}",
|
|
18
16
|
"protobuf": "node scripts/generate-protobufs.js",
|
|
19
17
|
"test:acceptance": "node --experimental-vm-modules node_modules/jest/bin/jest.js --testTimeout=200000 tests/acceptance/v1/rpc.test.mjs --runInBand",
|
|
20
18
|
"test:integration": "brittle-node -t 1200000 tests/integration/integration.test.js",
|
|
@@ -44,13 +42,15 @@
|
|
|
44
42
|
"hypercore-crypto": "3.6.1",
|
|
45
43
|
"hyperdht": "6.27.0",
|
|
46
44
|
"hyperswarm": "4.14.2",
|
|
45
|
+
"lodash": "^4.17.23",
|
|
47
46
|
"protocol-buffers-encodings": "1.2.0",
|
|
48
47
|
"protomux": "3.10.1",
|
|
49
48
|
"protomux-wakeup": "2.4.0",
|
|
50
49
|
"readline": "npm:bare-node-readline",
|
|
51
50
|
"ready-resource": "1.1.2",
|
|
52
|
-
"trac-wallet": "
|
|
53
|
-
"tty": "npm:bare-node-tty"
|
|
51
|
+
"trac-wallet": "1.0.2",
|
|
52
|
+
"tty": "npm:bare-node-tty",
|
|
53
|
+
"uuid": "^13.0.0"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
56
|
"bare-os": "^3.6.1",
|
|
@@ -66,5 +66,9 @@
|
|
|
66
66
|
"publishConfig": {
|
|
67
67
|
"registry": "https://registry.npmjs.org",
|
|
68
68
|
"access": "public"
|
|
69
|
+
},
|
|
70
|
+
"repository": {
|
|
71
|
+
"type": "git",
|
|
72
|
+
"url": "https://github.com/Trac-Systems/main_settlement_bus"
|
|
69
73
|
}
|
|
70
74
|
}
|
|
@@ -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
|
@@ -1,7 +1,17 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
decodeBase64Payload,
|
|
3
|
+
isBase64,
|
|
4
|
+
isValidTxHash,
|
|
5
|
+
sanitizeBulkPayloadsRequestBody,
|
|
6
|
+
sanitizeTransferPayload,
|
|
7
|
+
validatePayloadStructure,
|
|
8
|
+
hasSpacesInUrl,
|
|
9
|
+
BroadcastError,
|
|
10
|
+
ValidationError,
|
|
11
|
+
NotFoundError
|
|
12
|
+
} from "./utils/helpers.js"
|
|
2
13
|
import { MAX_SIGNED_LENGTH, ZERO_WK } from "./constants.js";
|
|
3
14
|
import { buildRequestUrl } from "./utils/url.js";
|
|
4
|
-
import { isHexString } from "../src/utils/helpers.js";
|
|
5
15
|
import {
|
|
6
16
|
getBalance,
|
|
7
17
|
getTxv,
|
|
@@ -12,28 +22,41 @@ import {
|
|
|
12
22
|
getTxHashes,
|
|
13
23
|
getTxDetails,
|
|
14
24
|
fetchBulkTxPayloads,
|
|
15
|
-
getExtendedTxDetails
|
|
25
|
+
getExtendedTxDetails,
|
|
16
26
|
} from "./rpc_services.js";
|
|
17
27
|
import { bufferToBigInt, licenseBufferToBigInt } from "../src/utils/amountSerialization.js";
|
|
18
28
|
import { isAddressValid } from "../src/core/state/utils/address.js";
|
|
19
29
|
import { getConfirmedParameter } from "./utils/confirmedParameter.js";
|
|
20
30
|
|
|
31
|
+
export async function handleHealth({ msbInstance, respond }) {
|
|
32
|
+
try {
|
|
33
|
+
const isReady = msbInstance && msbInstance.state;
|
|
34
|
+
if (isReady) return respond(200, { ok: true });
|
|
35
|
+
throw new Error("RPC_OFFLINE");
|
|
36
|
+
} catch (error) {
|
|
37
|
+
respond(503, { error: "Could not connect to RPC server" });
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
21
41
|
export async function handleBalance({ req, respond, msbInstance }) {
|
|
22
42
|
const url = buildRequestUrl(req);
|
|
23
43
|
const parts = url.pathname.split("/").filter(Boolean);
|
|
24
44
|
const address = parts[2];
|
|
25
45
|
|
|
26
|
-
const confirmedParam = getConfirmedParameter(url);
|
|
27
|
-
const confirmed = confirmedParam === null ? false : confirmedParam; // invalid -> fallback to unconfirmed
|
|
28
|
-
|
|
29
|
-
// TODO: VALIDATION?
|
|
30
46
|
if (!address) {
|
|
31
47
|
respond(400, { error: 'Wallet address is required' });
|
|
32
48
|
return;
|
|
33
49
|
}
|
|
34
50
|
|
|
35
|
-
const
|
|
36
|
-
|
|
51
|
+
const hrp = msbInstance.config.addressPrefix;
|
|
52
|
+
if (!isAddressValid(address, hrp)) {
|
|
53
|
+
respond(400, { error: 'Invalid account address format' });
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const nodeInfo = await getBalance(msbInstance, address, getConfirmedParameter(url) ?? false);
|
|
58
|
+
const balance = nodeInfo?.balance || "0";
|
|
59
|
+
|
|
37
60
|
respond(200, { address, balance });
|
|
38
61
|
}
|
|
39
62
|
|
|
@@ -52,46 +75,77 @@ export async function handleConfirmedLength({ msbInstance, respond }) {
|
|
|
52
75
|
respond(200, { confirmed_length });
|
|
53
76
|
}
|
|
54
77
|
|
|
55
|
-
export async function handleBroadcastTransaction({ msbInstance, respond, req }) {
|
|
78
|
+
export async function handleBroadcastTransaction({ msbInstance, config, respond, req }) {
|
|
56
79
|
let body = '';
|
|
80
|
+
const MAX_BODY_SIZE = 2_000_000;
|
|
81
|
+
let limitExceeded = false;
|
|
82
|
+
|
|
57
83
|
req.on('data', chunk => {
|
|
84
|
+
if (limitExceeded) return;
|
|
58
85
|
body += chunk.toString();
|
|
86
|
+
if (body.length > MAX_BODY_SIZE) {
|
|
87
|
+
limitExceeded = true;
|
|
88
|
+
respond(413, { error: 'Payload too large.' });
|
|
89
|
+
req.resume();
|
|
90
|
+
}
|
|
59
91
|
});
|
|
60
92
|
|
|
61
93
|
req.on('end', async () => {
|
|
94
|
+
if (limitExceeded) return;
|
|
95
|
+
|
|
62
96
|
try {
|
|
63
|
-
|
|
97
|
+
if (!body) {
|
|
98
|
+
throw new ValidationError("Invalid JSON payload.");
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
let parsedBody;
|
|
102
|
+
try {
|
|
103
|
+
parsedBody = JSON.parse(body);
|
|
104
|
+
} catch (e) {
|
|
105
|
+
throw new ValidationError("Invalid JSON payload.");
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const { payload } = parsedBody;
|
|
64
109
|
if (!payload) {
|
|
65
|
-
|
|
110
|
+
throw new ValidationError("Payload is missing.");
|
|
66
111
|
}
|
|
67
112
|
|
|
68
113
|
if (!isBase64(payload)) {
|
|
69
|
-
|
|
114
|
+
throw new ValidationError("Payload must be a valid base64 string.");
|
|
70
115
|
}
|
|
71
116
|
|
|
72
117
|
const decodedPayload = decodeBase64Payload(payload);
|
|
73
118
|
validatePayloadStructure(decodedPayload);
|
|
74
119
|
const sanitizedPayload = sanitizeTransferPayload(decodedPayload);
|
|
75
|
-
|
|
120
|
+
|
|
121
|
+
const result = await broadcastTransaction(msbInstance, config, sanitizedPayload);
|
|
76
122
|
respond(200, { result });
|
|
77
|
-
} catch (error) {
|
|
78
|
-
let code = error instanceof SyntaxError ? 400 : 500;
|
|
79
|
-
let errorMsg = code === 400 ? 'Invalid JSON payload.' : 'An error occurred processing the transaction.'
|
|
80
123
|
|
|
81
|
-
|
|
124
|
+
} catch (error) {
|
|
125
|
+
let code = 500;
|
|
126
|
+
let errorMsg = 'An error occurred processing the transaction.';
|
|
127
|
+
|
|
128
|
+
if (error instanceof ValidationError || error instanceof SyntaxError) {
|
|
129
|
+
code = 400;
|
|
130
|
+
errorMsg = error.message;
|
|
131
|
+
}
|
|
132
|
+
else if (error instanceof BroadcastError) {
|
|
82
133
|
code = 429;
|
|
83
|
-
errorMsg =
|
|
134
|
+
errorMsg = error.message;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (code === 500) {
|
|
138
|
+
console.error('Error in handleBroadcastTransaction:', error);
|
|
84
139
|
}
|
|
85
140
|
|
|
86
|
-
console.error('Error in handleBroadcastTransaction:', error);
|
|
87
|
-
// Use 400 for client errors (like bad JSON), 500 for server/command errors
|
|
88
141
|
respond(code, { error: errorMsg });
|
|
89
142
|
}
|
|
90
143
|
});
|
|
91
144
|
|
|
92
145
|
req.on('error', (err) => {
|
|
93
|
-
|
|
94
|
-
|
|
146
|
+
if (!limitExceeded) {
|
|
147
|
+
respond(500, { error: 'Request stream failed during body transfer.' });
|
|
148
|
+
}
|
|
95
149
|
});
|
|
96
150
|
}
|
|
97
151
|
|
|
@@ -104,18 +158,14 @@ export async function handleTxHashes({ msbInstance, respond, req }) {
|
|
|
104
158
|
const startSignedLength = parseInt(startSignedLengthStr);
|
|
105
159
|
const endSignedLength = parseInt(endSignedLengthStr);
|
|
106
160
|
|
|
107
|
-
// 1. Check if the parsed values are valid numbers
|
|
108
161
|
if (isNaN(startSignedLength) || isNaN(endSignedLength)) {
|
|
109
162
|
return respond(400, { error: 'Params must be integer' });
|
|
110
163
|
}
|
|
111
164
|
|
|
112
|
-
// 2. Check for non-negative numbers
|
|
113
|
-
// The requirement is "non-negative," which includes 0.
|
|
114
165
|
if (startSignedLength < 0 || endSignedLength < 0) {
|
|
115
166
|
return respond(400, { error: 'Params must be non-negative' });
|
|
116
167
|
}
|
|
117
168
|
|
|
118
|
-
// 3. endSignedLength must be >= startSignedLength
|
|
119
169
|
if (endSignedLength < startSignedLength) {
|
|
120
170
|
return respond(400, { error: 'endSignedLength must be greater than or equal to startSignedLength.' });
|
|
121
171
|
}
|
|
@@ -124,13 +174,9 @@ export async function handleTxHashes({ msbInstance, respond, req }) {
|
|
|
124
174
|
return respond(400, { error: `The max range for signedLength must be ${MAX_SIGNED_LENGTH}.` });
|
|
125
175
|
}
|
|
126
176
|
|
|
127
|
-
// 4. Get current confirmed length
|
|
128
177
|
const currentConfirmedLength = await getConfirmedLength(msbInstance);
|
|
129
|
-
|
|
130
|
-
// 5. Adjust the end index to not exceed the confirmed length.
|
|
131
178
|
const adjustedEndLength = Math.min(endSignedLength, currentConfirmedLength)
|
|
132
179
|
|
|
133
|
-
// 6. Fetch txs hashes for the adjusted range, assuming the command takes start and end index.
|
|
134
180
|
const { hashes } = await getTxHashes(msbInstance, startSignedLength, adjustedEndLength);
|
|
135
181
|
respond(200, { hashes });
|
|
136
182
|
}
|
|
@@ -141,57 +187,111 @@ export async function handleUnconfirmedLength({ msbInstance, respond }) {
|
|
|
141
187
|
}
|
|
142
188
|
|
|
143
189
|
export async function handleTransactionDetails({ msbInstance, respond, req }) {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
190
|
+
if (hasSpacesInUrl(req.url)) {
|
|
191
|
+
return respond(400, { error: "Invalid transaction hash format" });
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const url = buildRequestUrl(req);
|
|
195
|
+
const parts = url.pathname.split('/').filter(Boolean);
|
|
196
|
+
const rawHash = parts[parts.length - 1];
|
|
197
|
+
|
|
198
|
+
if (!rawHash || rawHash === 'tx') {
|
|
199
|
+
return respond(400, { error: "Transaction hash is required" });
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const normalizedHash = rawHash.toLowerCase();
|
|
203
|
+
if (!isValidTxHash(normalizedHash)) {
|
|
204
|
+
return respond(400, { error: "Invalid transaction hash format" });
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
try {
|
|
208
|
+
const txDetails = await getTxDetails(msbInstance, normalizedHash);
|
|
209
|
+
respond(200, { txDetails });
|
|
210
|
+
} catch (error) {
|
|
211
|
+
let code = 500;
|
|
212
|
+
let errorMsg = "Internal error";
|
|
213
|
+
|
|
214
|
+
if (error instanceof NotFoundError) {
|
|
215
|
+
code = 404;
|
|
216
|
+
errorMsg = error.message;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
respond(code, { [code === 404 ? 'txDetails' : 'error']: code === 404 ? null : errorMsg });
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export async function handleTransactionExtendedDetails({ msbInstance, respond, req }) {
|
|
224
|
+
if (hasSpacesInUrl(req.url)) {
|
|
225
|
+
return respond(400, { error: "Invalid transaction hash format" });
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const url = buildRequestUrl(req);
|
|
229
|
+
const pathParts = url.pathname.split('/').filter(Boolean);
|
|
230
|
+
const hashRaw = pathParts[pathParts.length - 1];
|
|
231
|
+
|
|
232
|
+
if (!hashRaw || hashRaw === 'details' || hashRaw === 'tx') {
|
|
233
|
+
return respond(400, { error: "Transaction hash is required" });
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const hash = hashRaw.toLowerCase();
|
|
237
|
+
if (!isValidTxHash(hash)) {
|
|
238
|
+
return respond(400, { error: "Invalid transaction hash format" });
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const confirmed = getConfirmedParameter(url);
|
|
242
|
+
if (confirmed === null) {
|
|
243
|
+
return respond(400, { error: 'Parameter "confirmed" must be exactly "true" or "false"' });
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
try {
|
|
247
|
+
const details = await getExtendedTxDetails(msbInstance, hash, confirmed);
|
|
248
|
+
respond(200, details);
|
|
249
|
+
} catch (error) {
|
|
250
|
+
let code = 500;
|
|
251
|
+
let errorMsg = 'An error occurred processing the request.';
|
|
252
|
+
|
|
253
|
+
if (error instanceof NotFoundError) {
|
|
254
|
+
code = 404;
|
|
255
|
+
errorMsg = error.message;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
respond(code, { error: errorMsg });
|
|
259
|
+
}
|
|
147
260
|
}
|
|
148
261
|
|
|
149
262
|
export async function handleFetchBulkTxPayloads({ msbInstance, respond, req }) {
|
|
150
263
|
let body = ''
|
|
151
264
|
let bytesRead = 0;
|
|
152
265
|
let limitBytes = 1_000_000;
|
|
153
|
-
let headersSent = false;
|
|
266
|
+
let headersSent = false;
|
|
154
267
|
|
|
155
268
|
req.on('data', chunk => {
|
|
156
|
-
if (headersSent) return;
|
|
157
|
-
|
|
269
|
+
if (headersSent) return;
|
|
158
270
|
bytesRead += chunk.length;
|
|
159
271
|
if (bytesRead > limitBytes) {
|
|
160
272
|
respond(413, { error: 'Request body too large.' });
|
|
161
273
|
headersSent = true;
|
|
162
|
-
req.destroy();
|
|
274
|
+
req.destroy();
|
|
163
275
|
return;
|
|
164
276
|
}
|
|
165
277
|
body += chunk.toString();
|
|
166
278
|
});
|
|
167
279
|
|
|
168
280
|
req.on('end', async () => {
|
|
169
|
-
if (headersSent) return;
|
|
170
|
-
|
|
281
|
+
if (headersSent) return;
|
|
171
282
|
|
|
172
283
|
try {
|
|
173
|
-
if (body
|
|
174
|
-
|
|
284
|
+
if (!body) {
|
|
285
|
+
throw new ValidationError("Missing payload.");
|
|
175
286
|
}
|
|
176
287
|
|
|
177
288
|
const sanitizedPayload = sanitizeBulkPayloadsRequestBody(body);
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
return respond(400, { error: 'Invalid payload.' });
|
|
289
|
+
if (!sanitizedPayload) {
|
|
290
|
+
throw new ValidationError("Invalid payload.");
|
|
181
291
|
}
|
|
182
292
|
|
|
183
293
|
const { hashes } = sanitizedPayload;
|
|
184
|
-
|
|
185
|
-
if (!Array.isArray(hashes) || hashes.length === 0) {
|
|
186
|
-
return respond(400, { error: 'Missing hash list.' });
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
if (hashes.length > 1500) {
|
|
190
|
-
return respond(413, { error: 'Too many hashes. Max 1500 allowed per request.' });
|
|
191
|
-
}
|
|
192
|
-
|
|
193
294
|
const uniqueHashes = [...new Set(hashes)];
|
|
194
|
-
|
|
195
295
|
const commandResult = await fetchBulkTxPayloads(msbInstance, uniqueHashes);
|
|
196
296
|
|
|
197
297
|
const responseString = JSON.stringify(commandResult);
|
|
@@ -201,50 +301,23 @@ export async function handleFetchBulkTxPayloads({ msbInstance, respond, req }) {
|
|
|
201
301
|
|
|
202
302
|
return respond(200, commandResult);
|
|
203
303
|
} catch (error) {
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
304
|
+
let code = 500;
|
|
305
|
+
let errorMsg = 'An internal error occurred.';
|
|
306
|
+
|
|
307
|
+
if (error instanceof ValidationError || error instanceof SyntaxError) {
|
|
308
|
+
code = 400;
|
|
309
|
+
errorMsg = error instanceof SyntaxError ? 'Invalid request body format.' : error.message;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
respond(code, { error: errorMsg });
|
|
208
313
|
}
|
|
209
314
|
})
|
|
210
315
|
|
|
211
316
|
req.on('error', (err) => {
|
|
212
|
-
console.error('Stream error in handleFetchBulkTxPayloads:', err);
|
|
213
317
|
respond(500, { error: 'Request stream failed during body transfer.' });
|
|
214
318
|
});
|
|
215
319
|
}
|
|
216
320
|
|
|
217
|
-
export async function handleTransactionExtendedDetails({ msbInstance, respond, req }) {
|
|
218
|
-
const url = buildRequestUrl(req);
|
|
219
|
-
const pathParts = url.pathname.split('/').filter(Boolean);
|
|
220
|
-
const hash = pathParts[3];
|
|
221
|
-
|
|
222
|
-
if (!hash) {
|
|
223
|
-
return respond(400, { error: "Transaction hash is required" });
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
if (isHexString(hash) === false || hash.length !== 64) {
|
|
227
|
-
return respond(400, { error: "Invalid transaction hash format" });
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
const confirmed = getConfirmedParameter(url);
|
|
231
|
-
if (confirmed === null) {
|
|
232
|
-
return respond(400, { error: 'Parameter "confirmed" must be exactly "true" or "false"' });
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
try {
|
|
236
|
-
const details = await getExtendedTxDetails(msbInstance, hash, confirmed);
|
|
237
|
-
respond(200, details);
|
|
238
|
-
} catch (error) {
|
|
239
|
-
if (error.message?.includes('No payload found for tx hash')) {
|
|
240
|
-
respond(404, { error: error.message });
|
|
241
|
-
} else {
|
|
242
|
-
console.error('Error in handleTransactionDetails:', error);
|
|
243
|
-
respond(500, { error: 'An error occurred processing the request.' });
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
|
|
248
321
|
export async function handleAccountDetails({ msbInstance, respond, req }) {
|
|
249
322
|
const url = buildRequestUrl(req);
|
|
250
323
|
const address = url.pathname.split('/').filter(Boolean)[2];
|
|
@@ -292,4 +365,4 @@ export async function handleAccountDetails({ msbInstance, respond, req }) {
|
|
|
292
365
|
balance: bufferToBigInt(nodeEntry.balance).toString(),
|
|
293
366
|
stakedBalance: bufferToBigInt(nodeEntry.stakedBalance).toString(),
|
|
294
367
|
});
|
|
295
|
-
}
|
|
368
|
+
}
|
package/rpc/routes/v1.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
handleHealth,
|
|
2
3
|
handleBalance,
|
|
3
4
|
handleTxv,
|
|
4
5
|
handleFee,
|
|
@@ -9,10 +10,11 @@ import {
|
|
|
9
10
|
handleTransactionDetails,
|
|
10
11
|
handleFetchBulkTxPayloads,
|
|
11
12
|
handleTransactionExtendedDetails,
|
|
12
|
-
handleAccountDetails
|
|
13
|
+
handleAccountDetails,
|
|
13
14
|
} from '../handlers.js';
|
|
14
15
|
|
|
15
16
|
export const v1Routes = [
|
|
17
|
+
{ method: 'GET', path: '/health', handler: handleHealth },
|
|
16
18
|
{ method: 'GET', path: '/balance', handler: handleBalance },
|
|
17
19
|
{ method: 'GET', path: '/txv', handler: handleTxv },
|
|
18
20
|
{ method: 'GET', path: '/fee', handler: handleFee },
|
package/rpc/rpc_server.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { createServer } from "./create_server.js";
|
|
2
2
|
|
|
3
3
|
// Called by msb.mjs file
|
|
4
|
-
export function startRpcServer(msbInstance,
|
|
5
|
-
const server = createServer(msbInstance)
|
|
4
|
+
export function startRpcServer(msbInstance, config) {
|
|
5
|
+
const server = createServer(msbInstance, config)
|
|
6
6
|
|
|
7
|
-
return server.listen(port, host, () => {
|
|
8
|
-
console.log(`Running RPC with http at http://${host}:${port}`);
|
|
7
|
+
return server.listen(config.port, config.host, () => {
|
|
8
|
+
console.log(`Running RPC with http at http://${config.host}:${config.port}`);
|
|
9
9
|
});
|
|
10
10
|
}
|