trac-msb 0.1.82 → 0.2.1
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/CI.yml +42 -0
- package/README.md +12 -0
- package/migration/.gitkeep +0 -0
- package/msb.mjs +40 -7
- package/package.json +40 -15
- package/proto/applyOperations.proto +117 -0
- package/rpc/constants.mjs +1 -0
- package/rpc/cors.mjs +15 -0
- package/rpc/create_server.mjs +72 -0
- package/rpc/handlers.mjs +245 -0
- package/rpc/routes/index.mjs +27 -0
- package/rpc/routes/v1.mjs +25 -0
- package/rpc/rpc_server.mjs +10 -0
- package/rpc/utils/helpers.mjs +74 -0
- package/scripts/generate-protobufs.js +42 -0
- package/src/core/network/Network.js +244 -0
- package/src/core/network/messaging/NetworkMessages.js +63 -0
- package/src/core/network/messaging/handlers/GetRequestHandler.js +112 -0
- package/src/core/network/messaging/handlers/ResponseHandler.js +108 -0
- package/src/core/network/messaging/handlers/RoleOperationHandler.js +116 -0
- package/src/core/network/messaging/handlers/SubnetworkOperationHandler.js +143 -0
- package/src/core/network/messaging/handlers/TransferOperationHandler.js +52 -0
- package/src/core/network/messaging/handlers/base/BaseOperationHandler.js +72 -0
- package/src/core/network/messaging/routes/NetworkMessageRouter.js +94 -0
- package/src/core/network/messaging/validators/AdminResponse.js +58 -0
- package/src/core/network/messaging/validators/CustomNodeResponse.js +46 -0
- package/src/core/network/messaging/validators/PartialBootstrapDeployment.js +34 -0
- package/src/core/network/messaging/validators/PartialRoleAccess.js +137 -0
- package/src/core/network/messaging/validators/PartialTransaction.js +64 -0
- package/src/core/network/messaging/validators/PartialTransfer.js +76 -0
- package/src/core/network/messaging/validators/ValidatorResponse.js +67 -0
- package/src/core/network/messaging/validators/base/BaseResponse.js +87 -0
- package/src/core/network/messaging/validators/base/PartialOperation.js +214 -0
- package/src/core/network/services/ConnectionManager.js +121 -0
- package/src/core/network/services/TransactionPoolService.js +99 -0
- package/src/core/network/services/TransactionRateLimiterService.js +130 -0
- package/src/core/network/services/ValidatorObserverService.js +123 -0
- package/src/core/state/State.js +3846 -0
- package/src/core/state/utils/address.js +71 -0
- package/src/core/state/utils/adminEntry.js +67 -0
- package/src/core/state/utils/balance.js +302 -0
- package/src/core/state/utils/deploymentEntry.js +75 -0
- package/src/core/state/utils/indexerEntry.js +105 -0
- package/src/core/state/utils/lengthEntry.js +94 -0
- package/src/core/state/utils/nodeEntry.js +346 -0
- package/src/core/state/utils/roles.js +53 -0
- package/src/core/state/utils/transaction.js +118 -0
- package/src/index.js +1095 -827
- package/src/messages/base/StateBuilder.js +25 -0
- package/src/messages/completeStateMessages/CompleteStateMessageBuilder.js +421 -0
- package/src/messages/completeStateMessages/CompleteStateMessageDirector.js +252 -0
- package/src/messages/completeStateMessages/CompleteStateMessageOperations.js +299 -0
- package/src/messages/partialStateMessages/PartialStateMessageBuilder.js +272 -0
- package/src/messages/partialStateMessages/PartialStateMessageDirector.js +137 -0
- package/src/messages/partialStateMessages/PartialStateMessageOperations.js +131 -0
- package/src/utils/Scheduler.js +118 -0
- package/src/utils/amountSerialization.js +110 -0
- package/src/utils/buffer.js +62 -0
- package/src/utils/check.js +473 -114
- package/src/utils/cli.js +138 -0
- package/src/utils/constants.js +105 -41
- package/src/utils/crypto.js +11 -0
- package/src/utils/fileUtils.js +192 -15
- package/src/utils/helpers.js +106 -0
- package/src/utils/migrationUtils.js +35 -0
- package/src/utils/normalizers.js +118 -0
- package/src/utils/operations.js +95 -0
- package/src/utils/protobuf/applyOperations.cjs +1373 -0
- package/src/utils/protobuf/operationHelpers.js +50 -0
- package/test/acceptance/v1/rpc.test.mjs +322 -0
- package/test/all.test.js +13 -8
- package/test/apply/addAdmin/addAdminBasic.test.js +68 -0
- package/test/apply/addAdmin/addAdminRecovery.test.js +125 -0
- package/test/apply/addIndexer.test.js +238 -0
- package/test/apply/addWhitelist.test.js +53 -0
- package/test/apply/addWriter.test.js +244 -0
- package/test/apply/apply.test.js +19 -0
- package/test/apply/banValidator.test.js +110 -0
- package/test/apply/postTx/invalidSubValues.test.js +104 -0
- package/test/apply/postTx/postTx.test.js +223 -0
- package/test/apply/removeIndexer.test.js +128 -0
- package/test/apply/removeWriter.test.js +167 -0
- package/test/apply/transfer.test.js +81 -0
- package/test/buffer/buffer.test.js +190 -0
- package/test/check/adminControlOperation.test.js +156 -0
- package/test/check/balanceInitializationOperation.test.js +54 -0
- package/test/check/bootstrapDeploymentOperation.test.js +105 -0
- package/test/check/check.test.js +17 -0
- package/test/check/common.test.js +418 -0
- package/test/check/coreAdminOperation.test.js +95 -0
- package/test/check/roleAccessOperation.test.js +254 -0
- package/test/check/transactionOperation.test.js +107 -0
- package/test/check/transferOperation.test.js +102 -0
- package/test/fileUtils/readAddressesFromWhitelistFile.test.js +93 -0
- package/test/fileUtils/readBalanceMigrationFile.test.js +148 -0
- package/test/fixtures/apply.fixtures.js +77 -0
- package/test/fixtures/assembleMessage.fixtures.js +40 -0
- package/test/fixtures/check.fixtures.js +427 -0
- package/test/fixtures/protobuf.fixtures.js +303 -0
- package/test/functions/amountSerialization.test.js +237 -0
- package/test/functions/applyOperations.test.js +91 -0
- package/test/functions/createHash.test.js +15 -0
- package/test/functions/functions.test.js +14 -0
- package/test/functions/isHexString.test.js +12 -0
- package/test/functions/normalizeHex.test.js +82 -0
- package/test/messageOperations/assembleAddIndexerMessage.test.js +21 -0
- package/test/messageOperations/assembleAddWriterMessage.test.js +16 -0
- package/test/messageOperations/assembleAdminMessage.test.js +69 -0
- package/test/messageOperations/assembleBanWriterMessage.test.js +16 -0
- package/test/messageOperations/assemblePostTransaction.test.js +442 -0
- package/test/messageOperations/assembleRemoveIndexerMessage.test.js +19 -0
- package/test/messageOperations/assembleRemoveWriterMessage.test.js +17 -0
- package/test/messageOperations/assembleWhitelistMessages.test.js +58 -0
- package/test/messageOperations/commonsStateMessageOperationsTest.js +277 -0
- package/test/messageOperations/stateMessageOperations.test.js +19 -0
- package/test/migrationUtils/validateAddressFromIncomingFile.test.js +92 -0
- package/test/network/ConnectionManager.test.js +219 -0
- package/test/network/connectionManagerTests.test.js +9 -0
- package/test/protobuf/protobuf.test.js +186 -0
- package/test/state/State.test.js +80 -0
- package/test/state/apply.addAdmin.basic.test.js +111 -0
- package/test/state/stateTestUtils.js +24 -0
- package/test/state/stateTests.test.js +23 -0
- package/test/state/utils/address.test.js +63 -0
- package/test/state/utils/adminEntry.test.js +51 -0
- package/test/state/utils/balance.test.js +320 -0
- package/test/state/utils/indexerEntry.test.js +83 -0
- package/test/state/utils/lengthEntry.test.js +54 -0
- package/test/state/utils/nodeEntry.test.js +298 -0
- package/test/state/utils/roles.test.js +44 -0
- package/test/state/utils/transaction.test.js +97 -0
- package/test/utils/regexHelper.js +3 -0
- package/test/utils/setupApplyTests.js +577 -0
- package/test/utils/wrapper.js +28 -0
- package/whitelist/.gitkeep +0 -0
- package/Whitelist/pubkeys.csv +0 -1
- package/src/network.js +0 -300
- package/src/utils/functions.js +0 -70
- package/src/utils/msgUtils.js +0 -137
- package/test/check.test.js +0 -21
- package/test/fileUtils.test.js +0 -16
- package/test/functions.test.js +0 -22
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
name: MSB-CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
pull_request:
|
|
8
|
+
branches:
|
|
9
|
+
- '*'
|
|
10
|
+
workflow_dispatch:
|
|
11
|
+
|
|
12
|
+
concurrency:
|
|
13
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
14
|
+
cancel-in-progress: true
|
|
15
|
+
|
|
16
|
+
jobs:
|
|
17
|
+
tests:
|
|
18
|
+
runs-on: ${{ matrix.os }}
|
|
19
|
+
|
|
20
|
+
strategy:
|
|
21
|
+
matrix:
|
|
22
|
+
node-version: [lts/*]
|
|
23
|
+
os: [ubuntu-latest, macos-latest, windows-latest]
|
|
24
|
+
steps:
|
|
25
|
+
- uses: actions/checkout@v4
|
|
26
|
+
- name: Use Node.js ${{ matrix.node-version }}
|
|
27
|
+
uses: actions/setup-node@v3
|
|
28
|
+
with:
|
|
29
|
+
node-version: ${{ matrix.node-version }}
|
|
30
|
+
cache: 'npm'
|
|
31
|
+
- name: Install dependencies
|
|
32
|
+
run: npm ci
|
|
33
|
+
- name: Run node tests
|
|
34
|
+
run: npm run test:node
|
|
35
|
+
- name: Install bare
|
|
36
|
+
run: npm i -g bare
|
|
37
|
+
- name: Run bare tests
|
|
38
|
+
run: npm run test:bare
|
|
39
|
+
# For now acceptance tests should be disable due to instability of the running MSB instance in the github actions environment
|
|
40
|
+
# Github machines are not powerful enough to run MSB, and we won't ever finish our tests.
|
|
41
|
+
# - name: Run rpc acceptance tests
|
|
42
|
+
# run: npm run test:acceptance
|
package/README.md
CHANGED
|
@@ -20,9 +20,21 @@ While the MSB supports native node-js, it is encouraged to use Pear:
|
|
|
20
20
|
cd main_settlement_bus
|
|
21
21
|
npm install -g pear
|
|
22
22
|
npm install
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
You can run the node in two modes:
|
|
26
|
+
1. Regular node (validator/indexer):
|
|
27
|
+
```js
|
|
23
28
|
pear run . store1
|
|
24
29
|
```
|
|
25
30
|
|
|
31
|
+
2. Admin node (access to administrative commands):
|
|
32
|
+
```js
|
|
33
|
+
pear run . admin
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
The admin mode provides access to additional commands such as `/add_admin`, `/add_whitelist`, `/balance_migration`, `/disable_initialization`, `/add_indexer`, `/remove_indexer`, and `/ban_writer`. These commands are only visible and available when running in admin mode.
|
|
37
|
+
|
|
26
38
|
**Deploy Bootstrap (admin):**
|
|
27
39
|
|
|
28
40
|
- Choose option 1)
|
|
File without changes
|
package/msb.mjs
CHANGED
|
@@ -1,14 +1,47 @@
|
|
|
1
1
|
import {MainSettlementBus} from './src/index.js';
|
|
2
2
|
|
|
3
|
+
const isPear = typeof Pear !== 'undefined';
|
|
4
|
+
const args = isPear ? Pear.config.args : process.argv.slice(2);
|
|
5
|
+
|
|
3
6
|
const opts = {
|
|
4
|
-
stores_directory : '
|
|
7
|
+
stores_directory : 'stores/',
|
|
5
8
|
store_name : typeof process !== "undefined" ? process.argv[2] : Pear.config.args[0],
|
|
6
|
-
bootstrap: '
|
|
7
|
-
channel: '
|
|
9
|
+
bootstrap: 'acbc3a4344d3a804101d40e53db1dda82b767646425af73599d4cd6577d69685',
|
|
10
|
+
channel: '0000trac0network0msb0mainnet0000',
|
|
11
|
+
enable_role_requester: false,
|
|
12
|
+
enable_wallet: true,
|
|
13
|
+
enable_validator_observer: true,
|
|
14
|
+
enable_interactive_mode: true,
|
|
15
|
+
disable_rate_limit: false,
|
|
16
|
+
enable_tx_apply_logs: false,
|
|
17
|
+
enable_error_apply_logs: false,
|
|
8
18
|
};
|
|
9
19
|
|
|
10
|
-
const
|
|
20
|
+
const rpc_opts = {
|
|
21
|
+
...opts,
|
|
22
|
+
enable_tx_apply_logs: false,
|
|
23
|
+
enable_error_apply_logs: false,
|
|
24
|
+
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const msb = new MainSettlementBus(args.includes('--rpc') ? rpc_opts : opts);
|
|
28
|
+
|
|
29
|
+
msb.ready().then(async () => {
|
|
30
|
+
const runRpc = args.includes('--rpc');
|
|
31
|
+
|
|
32
|
+
if (runRpc) {
|
|
33
|
+
console.log('Starting RPC server...');
|
|
34
|
+
const portIndex = args.indexOf('--port');
|
|
35
|
+
const port = (portIndex !== -1 && args[portIndex + 1]) ? parseInt(args[portIndex + 1], 10) : 5000;
|
|
36
|
+
const hostIndex = args.indexOf('--host');
|
|
37
|
+
const host = (hostIndex !== -1 && args[hostIndex + 1]) ? args[hostIndex + 1] : 'localhost';
|
|
38
|
+
|
|
39
|
+
const {startRpcServer} = await import('./rpc/rpc_server.mjs');
|
|
40
|
+
startRpcServer(msb, host, port);
|
|
41
|
+
} else {
|
|
42
|
+
console.log('RPC server will not be started.');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
msb.interactiveMode();
|
|
46
|
+
});
|
|
11
47
|
|
|
12
|
-
msb.ready()
|
|
13
|
-
.then(() => { msb.interactiveMode(); })
|
|
14
|
-
.catch(function () { });
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trac-msb",
|
|
3
3
|
"main": "msb.mjs",
|
|
4
|
-
"version": "0.1
|
|
4
|
+
"version": "0.2.1",
|
|
5
5
|
"pear": {
|
|
6
6
|
"name": "trac-msb",
|
|
7
7
|
"type": "terminal"
|
|
@@ -9,32 +9,57 @@
|
|
|
9
9
|
"type": "module",
|
|
10
10
|
"scripts": {
|
|
11
11
|
"dev": "pear run -d .",
|
|
12
|
-
"
|
|
13
|
-
"
|
|
12
|
+
"prod": "NODE_OPTIONS='--max-old-space-size=4096' pear run . ${npm_config_store}",
|
|
13
|
+
"dev-rpc": "pear run -d . ${npm_config_store} --rpc --port ${npm_config_port}",
|
|
14
|
+
"prod-rpc": "pear run . ${npm_config_store} --rpc --host ${npm_config_host} --port ${npm_config_port}",
|
|
15
|
+
"protobuf": "node scripts/generate-protobufs.js",
|
|
16
|
+
"test:acceptance": "node --experimental-vm-modules node_modules/jest/bin/jest.js --testTimeout=200000 test/acceptance/",
|
|
17
|
+
"test:node": "brittle-node -t 1200000 test/all.test.js",
|
|
18
|
+
"test:bare": "brittle-bare -t 1200000 test/all.test.js",
|
|
19
|
+
"test:node:cov": "brittle-node -c -t 1200000 test/all.test.js",
|
|
20
|
+
"test:bare:cov": "brittle-bare -c -t 1200000 test/all.test.js"
|
|
14
21
|
},
|
|
15
22
|
"dependencies": {
|
|
16
|
-
"
|
|
17
|
-
"
|
|
18
|
-
"corestore": "7.4.4",
|
|
23
|
+
"@tracsystems/blake3": "0.0.13",
|
|
24
|
+
"autobase": "7.20.1",
|
|
19
25
|
"b4a": "1.6.7",
|
|
26
|
+
"bare-crypto": "1.12.0",
|
|
27
|
+
"bare-fs": "4.5.0",
|
|
28
|
+
"bare-http1": "4.1.5",
|
|
20
29
|
"bare-readline": "1.0.7",
|
|
21
30
|
"bare-tty": "5.0.2",
|
|
22
|
-
"
|
|
31
|
+
"bech32": "2.0.0",
|
|
32
|
+
"compact-encoding": "2.18.0",
|
|
33
|
+
"corestore": "7.5.0",
|
|
34
|
+
"crypto": "npm:bare-node-crypto",
|
|
23
35
|
"fastest-validator": "1.19.0",
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
"
|
|
36
|
+
"http": "npm:bare-node-http",
|
|
37
|
+
"hyperbee": "2.26.5",
|
|
38
|
+
"hypercore": "11.18.3",
|
|
39
|
+
"hypercore-crypto": "3.6.1",
|
|
40
|
+
"hyperdht": "6.27.0",
|
|
41
|
+
"hyperswarm": "4.14.2",
|
|
42
|
+
"protocol-buffers-encodings": "1.2.0",
|
|
43
|
+
"protomux": "3.10.1",
|
|
29
44
|
"protomux-wakeup": "2.4.0",
|
|
30
45
|
"readline": "npm:bare-node-readline",
|
|
31
46
|
"ready-resource": "1.1.2",
|
|
32
|
-
"trac-wallet": "0.0.43",
|
|
47
|
+
"trac-wallet": "0.0.43-msb-r2.8",
|
|
33
48
|
"tty": "npm:bare-node-tty"
|
|
34
49
|
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"bare-os": "^3.6.1",
|
|
52
|
+
"bip39-mnemonic": "^2.4.0",
|
|
53
|
+
"brittle": "^3.17.1",
|
|
54
|
+
"esmock": "^2.7.2",
|
|
55
|
+
"jest": "^30.1.3",
|
|
56
|
+
"protocol-buffers": "^4.2.0",
|
|
57
|
+
"sinon": "^21.0.0",
|
|
58
|
+
"supertest": "^7.1.4",
|
|
59
|
+
"trac-crypto-api": "^0.1.1"
|
|
60
|
+
},
|
|
35
61
|
"publishConfig": {
|
|
36
62
|
"registry": "https://registry.npmjs.org",
|
|
37
|
-
"access": "public"
|
|
38
|
-
"brittle": "^3.16.2"
|
|
63
|
+
"access": "public"
|
|
39
64
|
}
|
|
40
65
|
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
syntax = "proto3";
|
|
2
|
+
|
|
3
|
+
package apply.operations;
|
|
4
|
+
|
|
5
|
+
//DO NOT CHANGE THE ORDER AFTER RELEASE - ONLY APPEND NEW ONES
|
|
6
|
+
enum OperationType {
|
|
7
|
+
UNKNOWN = 0;
|
|
8
|
+
ADD_ADMIN = 1; // complete
|
|
9
|
+
DISABLE_INITIALIZATION = 2; // complete
|
|
10
|
+
BALANCE_INITIALIZATION = 3; // complete
|
|
11
|
+
APPEND_WHITELIST = 4; // complete
|
|
12
|
+
ADD_WRITER = 5; // partial
|
|
13
|
+
REMOVE_WRITER = 6; // partial
|
|
14
|
+
ADMIN_RECOVERY = 7; // partial
|
|
15
|
+
ADD_INDEXER = 8; // complete
|
|
16
|
+
REMOVE_INDEXER = 9; // complete
|
|
17
|
+
BAN_VALIDATOR = 10; // complete
|
|
18
|
+
BOOTSTRAP_DEPLOYMENT = 11; // partial
|
|
19
|
+
TX = 12; // partial
|
|
20
|
+
TRANSFER = 13; // partial
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// add admin operation, disable_initialization (complete by default)
|
|
24
|
+
message CoreAdminOperation {
|
|
25
|
+
bytes tx = 1; // Transaction hash (unique identifier for the transaction)
|
|
26
|
+
bytes txv = 2; // Transaction expiration
|
|
27
|
+
bytes iw = 3; // incoming writer key (admin)
|
|
28
|
+
bytes in = 4; // Nonce of the invoker (admin)
|
|
29
|
+
bytes is = 5; // Signature of the invoker (admin)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// append_whitelist, add_indexer, remove_indexer, ban_writer (complete by default)
|
|
33
|
+
message AdminControlOperation {
|
|
34
|
+
bytes tx = 1; // Transaction hash (unique identifier for the transaction)
|
|
35
|
+
bytes txv = 2; // Transaction expiration
|
|
36
|
+
bytes ia = 3; // selected address to specific operation.
|
|
37
|
+
bytes in = 4; // Nonce of the invoker (admin)
|
|
38
|
+
bytes is = 5; // Signature of the invoker (admin)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// balance_initialization (complete by default)
|
|
42
|
+
message BalanceInitializationOperation {
|
|
43
|
+
bytes tx = 1; // Transaction hash (unique identifier for the transaction)
|
|
44
|
+
bytes txv = 2; // Transaction expiration
|
|
45
|
+
bytes ia = 3; // selected address to specific operation.
|
|
46
|
+
bytes am = 4; // Initial balance to be set for the address
|
|
47
|
+
bytes in = 5; // Nonce of the contract invoker (admin)
|
|
48
|
+
bytes is = 6; // Signature of the contract invoker (admin)
|
|
49
|
+
}
|
|
50
|
+
// token_transfer (partial - need to be signed by another writer to be complete)
|
|
51
|
+
message TransferOperation {
|
|
52
|
+
bytes tx = 1; // Transaction hash (unique identifier for the transaction)
|
|
53
|
+
bytes txv = 2; // Transaction expiration
|
|
54
|
+
bytes to = 3; // Address of the recipient (any account)
|
|
55
|
+
bytes am = 4; // Amount of tokens to transfer
|
|
56
|
+
bytes in = 5; // Nonce of the invoker (any account)
|
|
57
|
+
bytes is = 6; // Signature of the invoker (any account)
|
|
58
|
+
bytes va = 7; // Validator's address (optional)
|
|
59
|
+
bytes vn = 8; // Validator's nonce (optional)
|
|
60
|
+
bytes vs = 9; // Validator's signature (optional)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// add_writer, remove_writer, admin_recovery (partial - need to be signed by another writer to be complete)
|
|
64
|
+
message RoleAccessOperation {
|
|
65
|
+
bytes tx = 1; // Transaction hash (unique identifier for the transaction)
|
|
66
|
+
bytes txv = 2; // Transaction expiration
|
|
67
|
+
bytes iw = 3; // Writing key of the invoker (whitelisted node, admin to be recovered, Trac Network)
|
|
68
|
+
bytes in = 4; // Nonce of the invoker (whitelisted node, admin to be recovered, Trac Network)
|
|
69
|
+
bytes is = 5; // Signature of the invoker (whitelisted node, admin to be recovered)
|
|
70
|
+
bytes va = 6; // Validator's address (optional)
|
|
71
|
+
bytes vn = 7; // Validator's nonce (optional)
|
|
72
|
+
bytes vs = 8; // Validator's signature (optional)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// transaction - (partial - need to be signed by another writer to be complete)
|
|
76
|
+
message TxOperation {
|
|
77
|
+
bytes tx = 1; // Transaction hash (unique identifier for the transaction)
|
|
78
|
+
bytes txv = 2; // Transaction expiration
|
|
79
|
+
bytes iw = 3; // Writing key of the requesting node (external subnetwork)
|
|
80
|
+
bytes ch = 4; // Content hash (hash of the transaction's data)
|
|
81
|
+
bytes bs = 5; // External bootstrap contract
|
|
82
|
+
bytes mbs = 6; // MSB bootstrap key
|
|
83
|
+
bytes in = 7; // Nonce of the requesting node
|
|
84
|
+
bytes is = 8; // Requester's signature
|
|
85
|
+
bytes va = 9; // Validator's address (optional)
|
|
86
|
+
bytes vn = 10; // Validator's nonce (optional)
|
|
87
|
+
bytes vs = 11; // Validator's signature (optional)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// bootstrap deployment - (partial - need to be signed by another writer to be complete)
|
|
91
|
+
message BootstrapDeploymentOperation {
|
|
92
|
+
bytes tx = 1; // Transaction hash (unique identifier for the transaction)
|
|
93
|
+
bytes txv = 2; // Transaction expiration
|
|
94
|
+
bytes bs = 3; // Bootstrap contract address
|
|
95
|
+
bytes ic = 4; // Channel on which the contract will operate
|
|
96
|
+
bytes in = 5; // Nonce of the contract invoker
|
|
97
|
+
bytes is = 6; // Signature of the contract invoker
|
|
98
|
+
bytes va = 7; // Validator's address (optional)
|
|
99
|
+
bytes vn = 8; // Validator's nonce (optional)
|
|
100
|
+
bytes vs = 9; // Validator's signature (optional)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Main structure for the operations where messages are
|
|
104
|
+
message Operation {
|
|
105
|
+
OperationType type = 1;
|
|
106
|
+
bytes address = 2; // address of the invoker
|
|
107
|
+
|
|
108
|
+
oneof value {
|
|
109
|
+
CoreAdminOperation cao = 3;
|
|
110
|
+
AdminControlOperation aco = 4;
|
|
111
|
+
BalanceInitializationOperation bio = 5;
|
|
112
|
+
TransferOperation tro = 6;
|
|
113
|
+
RoleAccessOperation rao = 7;
|
|
114
|
+
BootstrapDeploymentOperation bdo = 8;
|
|
115
|
+
TxOperation txo = 9;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const MAX_SIGNED_LENGTH = 1000
|
package/rpc/cors.mjs
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export function applyCors(req, res) {
|
|
2
|
+
// Set CORS headers
|
|
3
|
+
res.setHeader('Access-Control-Allow-Origin', '*'); // Allow all origins, or specify a domain
|
|
4
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
|
5
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
|
|
6
|
+
|
|
7
|
+
// Handle preflight OPTIONS request
|
|
8
|
+
if (req.method === 'OPTIONS') {
|
|
9
|
+
res.writeHead(204);
|
|
10
|
+
res.end();
|
|
11
|
+
return true; // Return true to indicate the request was handled
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return false; // Return false to indicate the request should be handled by the main router
|
|
15
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// rpc_server.mjs
|
|
2
|
+
import http from 'http'
|
|
3
|
+
import { applyCors } from './cors.mjs';
|
|
4
|
+
import { routes } from './routes/index.mjs';
|
|
5
|
+
|
|
6
|
+
export const createServer = (msbInstance) => {
|
|
7
|
+
const server = http.createServer({}, async (req, res) => {
|
|
8
|
+
|
|
9
|
+
// --- 1. Define safe 'respond' utility (Payload MUST be an object) ---
|
|
10
|
+
const respond = (code, payload) => {
|
|
11
|
+
// FIX: Prevent attempts to write headers if a response has already started
|
|
12
|
+
if (res.headersSent) {
|
|
13
|
+
console.warn(`Attempted to send response (Code: ${code}) after headers were already sent. payload:`, payload);
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Enforce JSON content type for all responses
|
|
18
|
+
res.writeHead(code, { 'Content-Type': 'application/json' });
|
|
19
|
+
|
|
20
|
+
// CRITICAL: Always JSON.stringify the input object
|
|
21
|
+
res.end(JSON.stringify(payload));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// --- 2. Catch low-level request stream errors ---
|
|
25
|
+
req.on('error', (err) => {
|
|
26
|
+
console.error('Request stream error:', err);
|
|
27
|
+
// Use the safe respond utility
|
|
28
|
+
respond(500, { error: 'A stream-level request error occurred.' });
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
if (applyCors(req, res)) return;
|
|
32
|
+
|
|
33
|
+
// Find the matching route
|
|
34
|
+
let foundRoute = false;
|
|
35
|
+
|
|
36
|
+
// Extract the path without query parameters
|
|
37
|
+
const requestPath = req.url.split('?')[0];
|
|
38
|
+
|
|
39
|
+
// Sort routes by path length (longest first) to ensure more specific routes match first
|
|
40
|
+
const sortedRoutes = [...routes].sort((a, b) => b.path.length - a.path.length);
|
|
41
|
+
|
|
42
|
+
for (const route of sortedRoutes) {
|
|
43
|
+
// Exact path matching for base route, allow parameters after base path
|
|
44
|
+
const routeBase = route.path.endsWith('/') ? route.path.slice(0, -1) : route.path;
|
|
45
|
+
const requestParts = requestPath.split('/');
|
|
46
|
+
const routeParts = routeBase.split('/');
|
|
47
|
+
|
|
48
|
+
if (req.method === route.method &&
|
|
49
|
+
requestParts.length >= routeParts.length &&
|
|
50
|
+
routeParts.every((part, i) => part === requestParts[i])) {
|
|
51
|
+
|
|
52
|
+
foundRoute = true;
|
|
53
|
+
try {
|
|
54
|
+
// This try/catch covers synchronous errors and errors from awaited promises
|
|
55
|
+
// within the route.handler function.
|
|
56
|
+
await route.handler({ req, res, respond, msbInstance });
|
|
57
|
+
} catch (error) {
|
|
58
|
+
// Catch errors thrown directly from the handler (or its awaited parts)
|
|
59
|
+
console.error(`Error on ${route.path}:`, error);
|
|
60
|
+
respond(500, { error: 'An error occurred processing the request.' });
|
|
61
|
+
}
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (!foundRoute) {
|
|
67
|
+
respond(404, { error: 'Not Found' });
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
return server
|
|
72
|
+
}
|
package/rpc/handlers.mjs
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import { decodeBase64Payload, isBase64, sanitizeBulkPayloadsRequestBody, sanitizeTransferPayload, validatePayloadStructure } from "./utils/helpers.mjs"
|
|
2
|
+
import { MAX_SIGNED_LENGTH } from "./constants.mjs";
|
|
3
|
+
import { isHexString } from "../src/utils/helpers";
|
|
4
|
+
|
|
5
|
+
export async function handleBalance({ req, respond, msbInstance }) {
|
|
6
|
+
const [path, queryString] = req.url.split("?");
|
|
7
|
+
const parts = path.split("/").filter(Boolean);
|
|
8
|
+
const address = parts[2];
|
|
9
|
+
|
|
10
|
+
let confirmed = true; // default
|
|
11
|
+
if (queryString) {
|
|
12
|
+
const params = new URLSearchParams(queryString);
|
|
13
|
+
if (params.has("confirmed")) {
|
|
14
|
+
confirmed = params.get("confirmed") === "true";
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (!address) {
|
|
19
|
+
respond(400, { error: 'Wallet address is required' });
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const commandString =`/get_balance ${address} ${confirmed}`;
|
|
24
|
+
const nodeInfo = await msbInstance.handleCommand(commandString);
|
|
25
|
+
const balance = nodeInfo?.balance || 0;
|
|
26
|
+
respond(200, { address, balance });
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export async function handleTxv({ msbInstance, respond }) {
|
|
30
|
+
const commandString = '/get_txv';
|
|
31
|
+
const txvRaw = await msbInstance.handleCommand(commandString);
|
|
32
|
+
const txv = txvRaw.toString('hex');
|
|
33
|
+
respond(200, { txv });
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export async function handleFee({ msbInstance, respond }) {
|
|
37
|
+
const commandString = '/get_fee';
|
|
38
|
+
const fee = await msbInstance.handleCommand(commandString);
|
|
39
|
+
respond(200, { fee });
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export async function handleConfirmedLength({ msbInstance, respond }) {
|
|
43
|
+
const commandString = '/confirmed_length';
|
|
44
|
+
const confirmed_length = await msbInstance.handleCommand(commandString);
|
|
45
|
+
respond(200, { confirmed_length });
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export async function handleBroadcastTransaction({ msbInstance, respond, req }) {
|
|
49
|
+
let body = '';
|
|
50
|
+
req.on('data', chunk => {
|
|
51
|
+
body += chunk.toString();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
req.on('end', async () => {
|
|
55
|
+
try {
|
|
56
|
+
const { payload } = JSON.parse(body);
|
|
57
|
+
if (!payload) {
|
|
58
|
+
return respond(400, { error: 'Payload is missing.' });
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (!isBase64(payload)) {
|
|
62
|
+
return respond(400, { error: 'Payload must be a valid base64 string.' });
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const decodedPayload = decodeBase64Payload(payload);
|
|
66
|
+
validatePayloadStructure(decodedPayload);
|
|
67
|
+
const sanitizedPayload = sanitizeTransferPayload(decodedPayload);
|
|
68
|
+
const result = await msbInstance.handleCommand('/broadcast_transaction', null, sanitizedPayload);
|
|
69
|
+
respond(200, { result });
|
|
70
|
+
} catch (error) {
|
|
71
|
+
console.error('Error in handleBroadcastTransaction:', error);
|
|
72
|
+
// Use 400 for client errors (like bad JSON), 500 for server/command errors
|
|
73
|
+
const code = error instanceof SyntaxError ? 400 : 500;
|
|
74
|
+
respond(code, { error: code === 400 ? 'Invalid JSON payload.' : 'An error occurred processing the transaction.' });
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
req.on('error', (err) => {
|
|
79
|
+
console.error('Stream error in handleBroadcastTransaction:', err);
|
|
80
|
+
respond(500, { error: 'Request stream failed during body transfer.' });
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export async function handleTxHashes({ msbInstance, respond, req }) {
|
|
85
|
+
const startSignedLengthStr = req.url.split('/')[3];
|
|
86
|
+
const endSignedLengthStr = req.url.split('/')[4];
|
|
87
|
+
|
|
88
|
+
const startSignedLength = parseInt(startSignedLengthStr);
|
|
89
|
+
const endSignedLength = parseInt(endSignedLengthStr);
|
|
90
|
+
|
|
91
|
+
// 1. Check if the parsed values are valid numbers
|
|
92
|
+
if (isNaN(startSignedLength) || isNaN(endSignedLength)) {
|
|
93
|
+
return respond(400, { error: 'Params must be integer' });
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// 2. Check for non-negative numbers
|
|
97
|
+
// The requirement is "non-negative," which includes 0.
|
|
98
|
+
if (startSignedLength < 0 || endSignedLength < 0) {
|
|
99
|
+
return respond(400, { error: 'Params must be non-negative' });
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// 3. endSignedLength must be >= startSignedLength
|
|
103
|
+
if (endSignedLength < startSignedLength) {
|
|
104
|
+
return respond(400, { error: 'endSignedLength must be greater than or equal to startSignedLength.' });
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (endSignedLength - startSignedLength > MAX_SIGNED_LENGTH) {
|
|
108
|
+
return respond(400, { error: `The max range for signedLength must be ${MAX_SIGNED_LENGTH}.` });
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// 4. Get current confirmed length
|
|
112
|
+
const currentConfirmedLength = await msbInstance.handleCommand('/confirmed_length');
|
|
113
|
+
|
|
114
|
+
// 5. Adjust the end index to not exceed the confirmed length.
|
|
115
|
+
const adjustedEndLength = Math.min(endSignedLength, currentConfirmedLength)
|
|
116
|
+
|
|
117
|
+
// 6. Fetch txs hashes for the adjusted range, assuming the command takes start and end index.
|
|
118
|
+
const commandString = `/get_txs_hashes ${startSignedLength} ${adjustedEndLength}`;
|
|
119
|
+
const { hashes } = await msbInstance.handleCommand(commandString);
|
|
120
|
+
respond(200, { hashes });
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export async function handleUnconfirmedLength({ msbInstance, respond }) {
|
|
124
|
+
const commandString = '/unconfirmed_length';
|
|
125
|
+
const unconfirmed_length = await msbInstance.handleCommand(commandString);
|
|
126
|
+
respond(200, { unconfirmed_length });
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export async function handleTransactionDetails({ msbInstance, respond, req }) {
|
|
130
|
+
const hash = req.url.split('/')[3];
|
|
131
|
+
const commandString = `/get_tx_details ${hash}`;
|
|
132
|
+
const txDetails = await msbInstance.handleCommand(commandString);
|
|
133
|
+
respond(txDetails === null ? 404 : 200 , { txDetails });
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export async function handleFetchBulkTxPayloads({ msbInstance, respond, req }) {
|
|
137
|
+
let body = ''
|
|
138
|
+
let bytesRead = 0;
|
|
139
|
+
let limitBytes = 1_000_000;
|
|
140
|
+
let headersSent = false; // Add a flag to prevent double response
|
|
141
|
+
|
|
142
|
+
req.on('data', chunk => {
|
|
143
|
+
if (headersSent) return; // Stop processing if response has started/errored
|
|
144
|
+
|
|
145
|
+
bytesRead += chunk.length;
|
|
146
|
+
if (bytesRead > limitBytes) {
|
|
147
|
+
respond(413, { error: 'Request body too large.' });
|
|
148
|
+
headersSent = true;
|
|
149
|
+
req.destroy(); // Stop receiving data (GOOD PRACTICE)
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
body += chunk.toString();
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
req.on('end', async () => {
|
|
156
|
+
if (headersSent) return; // Don't process if an error already occurred
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
try {
|
|
160
|
+
if (body === null || body === ''){
|
|
161
|
+
return respond(400, { error: 'Missing payload.' });
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const sanitizedPayload = sanitizeBulkPayloadsRequestBody(body);
|
|
165
|
+
|
|
166
|
+
if (sanitizedPayload === null){
|
|
167
|
+
return respond(400, { error: 'Invalid payload.' });
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const { hashes } = sanitizedPayload;
|
|
171
|
+
|
|
172
|
+
if (!Array.isArray(hashes) || hashes.length === 0) {
|
|
173
|
+
return respond(400, { error: 'Missing hash list.' });
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (hashes.length > 1500) {
|
|
177
|
+
return respond(413, { error: 'Too many hashes. Max 1500 allowed per request.' });
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const uniqueHashes = [...new Set(hashes)];
|
|
181
|
+
|
|
182
|
+
const commandResult = await msbInstance.handleCommand( `/get_tx_payloads_bulk`, null, uniqueHashes)
|
|
183
|
+
|
|
184
|
+
const responseString = JSON.stringify(commandResult);
|
|
185
|
+
if (Buffer.byteLength(responseString, 'utf8') > 2_000_000) {
|
|
186
|
+
return respond(413, { error: 'Response too large. Reduce number of hashes.'});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return respond(200, commandResult);
|
|
190
|
+
} catch (error) {
|
|
191
|
+
console.error('Error in handleFetchBulkTxPayloads:', error);
|
|
192
|
+
// Use 400 for JSON errors, 500 otherwise
|
|
193
|
+
const code = error instanceof SyntaxError ? 400 : 500;
|
|
194
|
+
respond(code, { error: code === 400 ? 'Invalid request body format.' : 'An internal error occurred.' });
|
|
195
|
+
}
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
req.on('error', (err) => {
|
|
199
|
+
console.error('Stream error in handleFetchBulkTxPayloads:', err);
|
|
200
|
+
respond(500, { error: 'Request stream failed during body transfer.' });
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export async function handleTransactionExtendedDetails({ msbInstance, respond, req }) {
|
|
205
|
+
const [path, queryString] = req.url.split("?");
|
|
206
|
+
const pathParts = path.split('/');
|
|
207
|
+
const hash = pathParts[4];
|
|
208
|
+
|
|
209
|
+
if (!hash) {
|
|
210
|
+
return respond(400, { error: "Transaction hash is required" });
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (isHexString(hash) === false || hash.length !== 64) {
|
|
214
|
+
return respond(400, { error: "Invalid transaction hash format" });
|
|
215
|
+
}
|
|
216
|
+
let confirmed = true; // default
|
|
217
|
+
if (queryString) {
|
|
218
|
+
const params = new URLSearchParams(queryString);
|
|
219
|
+
if (params.has("confirmed")) {
|
|
220
|
+
const confirmedParam = params.get("confirmed");
|
|
221
|
+
if (confirmedParam !== "true" && confirmedParam !== "false") {
|
|
222
|
+
return respond(400, { error: 'Parameter "confirmed" must be exactly "true" or "false"' });
|
|
223
|
+
}
|
|
224
|
+
confirmed = confirmedParam === "true";
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
try {
|
|
229
|
+
let txDetails;
|
|
230
|
+
const commandString = `/get_extended_tx_details ${hash} ${confirmed}`;
|
|
231
|
+
txDetails = await msbInstance.handleCommand(commandString);
|
|
232
|
+
if (txDetails === null) {
|
|
233
|
+
respond(404, { error: `No payload found for tx hash: ${hash}` });
|
|
234
|
+
} else {
|
|
235
|
+
respond(200, txDetails);
|
|
236
|
+
}
|
|
237
|
+
} catch (error) {
|
|
238
|
+
if (error.message?.includes('No payload found for tx hash')) {
|
|
239
|
+
respond(404, { error: error.message });
|
|
240
|
+
} else {
|
|
241
|
+
console.error('Error in handleTransactionDetails:', error);
|
|
242
|
+
respond(500, { error: 'An error occurred processing the request.' });
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|