trac-msb 0.2.5 → 0.2.7
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/publish.yml +6 -5
- package/msb.mjs +8 -12
- package/package.json +1 -1
- package/rpc/handlers.mjs +26 -33
- package/rpc/rpc_services.js +126 -0
- package/src/core/network/Network.js +29 -16
- package/src/core/network/services/ConnectionManager.js +252 -60
- package/src/core/network/services/MessageOrchestrator.js +95 -0
- package/src/core/network/services/ValidatorObserverService.js +56 -20
- package/src/index.js +200 -388
- package/src/utils/cliCommands.js +280 -0
- package/src/utils/constants.js +5 -4
- package/tests/acceptance/v1/broadcast-transaction/broadcast-transaction.test.mjs +6 -9
- package/tests/acceptance/v1/tx/tx.test.mjs +7 -12
- package/tests/acceptance/v1/tx-details/tx-details.test.mjs +14 -22
- package/tests/helpers/autobaseTestHelpers.js +47 -0
- package/tests/helpers/transactionPayloads.mjs +78 -0
- package/tests/unit/network/ConnectionManager.test.js +38 -69
|
@@ -27,12 +27,13 @@ jobs:
|
|
|
27
27
|
VERSION="${TAG#v}"
|
|
28
28
|
echo "Version from tag: $VERSION"
|
|
29
29
|
npm version "$VERSION" --no-git-tag-version
|
|
30
|
+
|
|
31
|
+
# unit tests are temporarily disabled because they lost stability on GH runners.
|
|
32
|
+
#- name: Install dependencies
|
|
33
|
+
# run: npm ci
|
|
30
34
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
- name: Run unit tests
|
|
35
|
-
run: npm run test:unit:all
|
|
35
|
+
#- name: Run unit tests
|
|
36
|
+
# run: npm run test:unit:all
|
|
36
37
|
|
|
37
38
|
- name: Publish to npm
|
|
38
39
|
env:
|
package/msb.mjs
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import {MainSettlementBus} from './src/index.js';
|
|
1
|
+
import { MainSettlementBus } from './src/index.js';
|
|
2
2
|
|
|
3
3
|
const pearApp = typeof Pear !== 'undefined' ? (Pear.app ?? Pear.config) : undefined;
|
|
4
4
|
const runtimeArgs = typeof process !== 'undefined' ? process.argv.slice(2) : [];
|
|
5
5
|
const args = pearApp?.args ?? runtimeArgs;
|
|
6
|
+
const runRpc = args.includes('--rpc');
|
|
6
7
|
|
|
7
8
|
const opts = {
|
|
8
|
-
stores_directory
|
|
9
|
-
store_name
|
|
9
|
+
stores_directory: 'stores/',
|
|
10
|
+
store_name: pearApp?.args?.[0] ?? runtimeArgs[0],
|
|
10
11
|
bootstrap: 'acbc3a4344d3a804101d40e53db1dda82b767646425af73599d4cd6577d69685',
|
|
11
12
|
channel: '0000trac0network0msb0mainnet0000',
|
|
12
13
|
enable_role_requester: false,
|
|
@@ -23,15 +24,12 @@ const rpc_opts = {
|
|
|
23
24
|
enable_tx_apply_logs: false,
|
|
24
25
|
enable_error_apply_logs: false,
|
|
25
26
|
enable_wallet: false,
|
|
26
|
-
enable_interactive_mode:
|
|
27
|
-
|
|
28
|
-
}
|
|
27
|
+
enable_interactive_mode: false,
|
|
28
|
+
};
|
|
29
29
|
|
|
30
|
-
const msb = new MainSettlementBus(
|
|
30
|
+
const msb = new MainSettlementBus(runRpc ? rpc_opts : opts);
|
|
31
31
|
|
|
32
32
|
msb.ready().then(async () => {
|
|
33
|
-
const runRpc = args.includes('--rpc');
|
|
34
|
-
|
|
35
33
|
if (runRpc) {
|
|
36
34
|
console.log('Starting RPC server...');
|
|
37
35
|
const portIndex = args.indexOf('--port');
|
|
@@ -42,8 +40,6 @@ msb.ready().then(async () => {
|
|
|
42
40
|
const {startRpcServer} = await import('./rpc/rpc_server.mjs');
|
|
43
41
|
startRpcServer(msb, host, port);
|
|
44
42
|
} else {
|
|
45
|
-
|
|
43
|
+
msb.interactiveMode();
|
|
46
44
|
}
|
|
47
|
-
|
|
48
|
-
msb.interactiveMode();
|
|
49
45
|
});
|
package/package.json
CHANGED
package/rpc/handlers.mjs
CHANGED
|
@@ -2,6 +2,18 @@ import { decodeBase64Payload, isBase64, sanitizeBulkPayloadsRequestBody, sanitiz
|
|
|
2
2
|
import { MAX_SIGNED_LENGTH, ZERO_WK } from "./constants.mjs";
|
|
3
3
|
import { buildRequestUrl } from "./utils/url.mjs";
|
|
4
4
|
import { isHexString } from "../src/utils/helpers.js";
|
|
5
|
+
import {
|
|
6
|
+
getBalance,
|
|
7
|
+
getTxv,
|
|
8
|
+
getFee,
|
|
9
|
+
getConfirmedLength,
|
|
10
|
+
getUnconfirmedLength,
|
|
11
|
+
broadcastTransaction,
|
|
12
|
+
getTxHashes,
|
|
13
|
+
getTxDetails,
|
|
14
|
+
fetchBulkTxPayloads,
|
|
15
|
+
getExtendedTxDetails
|
|
16
|
+
} from "./rpc_services.js";
|
|
5
17
|
import { bufferToBigInt, licenseBufferToBigInt } from "../src/utils/amountSerialization.js";
|
|
6
18
|
import { isAddressValid } from "../src/core/state/utils/address.js";
|
|
7
19
|
import { getConfirmedParameter } from "./utils/confirmedParameter.mjs";
|
|
@@ -20,28 +32,23 @@ export async function handleBalance({ req, respond, msbInstance }) {
|
|
|
20
32
|
return;
|
|
21
33
|
}
|
|
22
34
|
|
|
23
|
-
const
|
|
24
|
-
const nodeInfo = await msbInstance.handleCommand(commandString);
|
|
35
|
+
const nodeInfo = await getBalance(msbInstance, address, confirmed);
|
|
25
36
|
const balance = nodeInfo?.balance || 0;
|
|
26
37
|
respond(200, { address, balance });
|
|
27
38
|
}
|
|
28
39
|
|
|
29
40
|
export async function handleTxv({ msbInstance, respond }) {
|
|
30
|
-
const
|
|
31
|
-
const txvRaw = await msbInstance.handleCommand(commandString);
|
|
32
|
-
const txv = txvRaw.toString('hex');
|
|
41
|
+
const txv = await getTxv(msbInstance);
|
|
33
42
|
respond(200, { txv });
|
|
34
43
|
}
|
|
35
44
|
|
|
36
45
|
export async function handleFee({ msbInstance, respond }) {
|
|
37
|
-
const
|
|
38
|
-
const fee = await msbInstance.handleCommand(commandString);
|
|
46
|
+
const fee = await getFee(msbInstance);
|
|
39
47
|
respond(200, { fee });
|
|
40
48
|
}
|
|
41
49
|
|
|
42
50
|
export async function handleConfirmedLength({ msbInstance, respond }) {
|
|
43
|
-
const
|
|
44
|
-
const confirmed_length = await msbInstance.handleCommand(commandString);
|
|
51
|
+
const confirmed_length = await getConfirmedLength(msbInstance);
|
|
45
52
|
respond(200, { confirmed_length });
|
|
46
53
|
}
|
|
47
54
|
|
|
@@ -65,7 +72,7 @@ export async function handleBroadcastTransaction({ msbInstance, respond, req })
|
|
|
65
72
|
const decodedPayload = decodeBase64Payload(payload);
|
|
66
73
|
validatePayloadStructure(decodedPayload);
|
|
67
74
|
const sanitizedPayload = sanitizeTransferPayload(decodedPayload);
|
|
68
|
-
const result = await msbInstance
|
|
75
|
+
const result = await broadcastTransaction(msbInstance, sanitizedPayload);
|
|
69
76
|
respond(200, { result });
|
|
70
77
|
} catch (error) {
|
|
71
78
|
let code = error instanceof SyntaxError ? 400 : 500;
|
|
@@ -118,33 +125,25 @@ export async function handleTxHashes({ msbInstance, respond, req }) {
|
|
|
118
125
|
}
|
|
119
126
|
|
|
120
127
|
// 4. Get current confirmed length
|
|
121
|
-
const currentConfirmedLength = await msbInstance
|
|
128
|
+
const currentConfirmedLength = await getConfirmedLength(msbInstance);
|
|
122
129
|
|
|
123
130
|
// 5. Adjust the end index to not exceed the confirmed length.
|
|
124
131
|
const adjustedEndLength = Math.min(endSignedLength, currentConfirmedLength)
|
|
125
132
|
|
|
126
133
|
// 6. Fetch txs hashes for the adjusted range, assuming the command takes start and end index.
|
|
127
|
-
const
|
|
128
|
-
const { hashes } = await msbInstance.handleCommand(commandString);
|
|
134
|
+
const { hashes } = await getTxHashes(msbInstance, startSignedLength, adjustedEndLength);
|
|
129
135
|
respond(200, { hashes });
|
|
130
136
|
}
|
|
131
137
|
|
|
132
138
|
export async function handleUnconfirmedLength({ msbInstance, respond }) {
|
|
133
|
-
|
|
134
|
-
const commandString = '/unconfirmed_length';
|
|
135
|
-
const unconfirmed_length = await msbInstance.handleCommand(commandString);
|
|
139
|
+
const unconfirmed_length = await getUnconfirmedLength(msbInstance);
|
|
136
140
|
respond(200, { unconfirmed_length });
|
|
137
141
|
}
|
|
138
142
|
|
|
139
143
|
export async function handleTransactionDetails({ msbInstance, respond, req }) {
|
|
140
|
-
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
const hash = pathParts[2];
|
|
144
|
-
|
|
145
|
-
const commandString = `/get_tx_details ${hash}`;
|
|
146
|
-
const txDetails = await msbInstance.handleCommand(commandString);
|
|
147
|
-
respond(txDetails === null ? 404 : 200, { txDetails });
|
|
144
|
+
const hash = req.url.split('/')[3];
|
|
145
|
+
const txDetails = await getTxDetails(msbInstance, hash);
|
|
146
|
+
respond(txDetails === null ? 404 : 200 , { txDetails });
|
|
148
147
|
}
|
|
149
148
|
|
|
150
149
|
export async function handleFetchBulkTxPayloads({ msbInstance, respond, req }) {
|
|
@@ -193,7 +192,7 @@ export async function handleFetchBulkTxPayloads({ msbInstance, respond, req }) {
|
|
|
193
192
|
|
|
194
193
|
const uniqueHashes = [...new Set(hashes)];
|
|
195
194
|
|
|
196
|
-
const commandResult = await msbInstance
|
|
195
|
+
const commandResult = await fetchBulkTxPayloads(msbInstance, uniqueHashes);
|
|
197
196
|
|
|
198
197
|
const responseString = JSON.stringify(commandResult);
|
|
199
198
|
if (Buffer.byteLength(responseString, 'utf8') > 2_000_000) {
|
|
@@ -234,14 +233,8 @@ export async function handleTransactionExtendedDetails({ msbInstance, respond, r
|
|
|
234
233
|
}
|
|
235
234
|
|
|
236
235
|
try {
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
txDetails = await msbInstance.handleCommand(commandString);
|
|
240
|
-
if (txDetails === null) {
|
|
241
|
-
respond(404, { error: `No payload found for tx hash: ${hash}` });
|
|
242
|
-
} else {
|
|
243
|
-
respond(200, txDetails);
|
|
244
|
-
}
|
|
236
|
+
const details = await getExtendedTxDetails(msbInstance, hash, confirmed);
|
|
237
|
+
respond(200, details);
|
|
245
238
|
} catch (error) {
|
|
246
239
|
if (error.message?.includes('No payload found for tx hash')) {
|
|
247
240
|
respond(404, { error: error.message });
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { bufferToBigInt } from "../src/utils/amountSerialization.js";
|
|
2
|
+
import { normalizeDecodedPayloadForJson } from "../src/utils/normalizers.js";
|
|
3
|
+
import { get_confirmed_tx_info, get_unconfirmed_tx_info } from "../src/utils/cli.js";
|
|
4
|
+
|
|
5
|
+
export async function getBalance(msbInstance, address, confirmed) {
|
|
6
|
+
const state = msbInstance.state;
|
|
7
|
+
const useUnconfirmed = confirmed === false;
|
|
8
|
+
|
|
9
|
+
const nodeEntry = useUnconfirmed
|
|
10
|
+
? await state.getNodeEntryUnsigned(address)
|
|
11
|
+
: await state.getNodeEntry(address);
|
|
12
|
+
|
|
13
|
+
if (!nodeEntry) return undefined;
|
|
14
|
+
|
|
15
|
+
return {
|
|
16
|
+
address,
|
|
17
|
+
balance: bufferToBigInt(nodeEntry.balance).toString(),
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function getTxv(msbInstance) {
|
|
22
|
+
const txv = await msbInstance.state.getIndexerSequenceState();
|
|
23
|
+
return txv.toString("hex");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export async function getFee(msbInstance) {
|
|
27
|
+
const feeBuffer = msbInstance.state.getFee();
|
|
28
|
+
return bufferToBigInt(feeBuffer).toString();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export async function getConfirmedLength(msbInstance) {
|
|
32
|
+
return msbInstance.state.getSignedLength();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export async function getUnconfirmedLength(msbInstance) {
|
|
36
|
+
return msbInstance.state.getUnsignedLength();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export async function broadcastTransaction(msbInstance, payload) {
|
|
40
|
+
return msbInstance.broadcastTransactionCommand(payload);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export async function getTxHashes(msbInstance, start, end) {
|
|
44
|
+
const hashes = await msbInstance.state.confirmedTransactionsBetween(start, end);
|
|
45
|
+
return { hashes };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export async function getTxDetails(msbInstance, hash) {
|
|
49
|
+
const rawPayload = await get_confirmed_tx_info(msbInstance.state, hash);
|
|
50
|
+
if (!rawPayload) {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return normalizeDecodedPayloadForJson(rawPayload.decoded);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export async function fetchBulkTxPayloads(msbInstance, hashes) {
|
|
58
|
+
if (!Array.isArray(hashes) || hashes.length === 0) {
|
|
59
|
+
throw new Error("Missing hash list.");
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (hashes.length > 1500) {
|
|
63
|
+
throw new Error("Length of input tx hashes exceeded.");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const res = { results: [], missing: [] };
|
|
67
|
+
|
|
68
|
+
const promises = hashes.map((hash) => get_confirmed_tx_info(msbInstance.state, hash));
|
|
69
|
+
const results = await Promise.all(promises);
|
|
70
|
+
|
|
71
|
+
results.forEach((result, index) => {
|
|
72
|
+
const hash = hashes[index];
|
|
73
|
+
if (result === null || result === undefined) {
|
|
74
|
+
res.missing.push(hash);
|
|
75
|
+
} else {
|
|
76
|
+
const decodedResult = normalizeDecodedPayloadForJson(result.decoded);
|
|
77
|
+
res.results.push({ hash, payload: decodedResult });
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
return res;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export async function getExtendedTxDetails(msbInstance, hash, confirmed) {
|
|
85
|
+
const state = msbInstance.state;
|
|
86
|
+
|
|
87
|
+
if (confirmed) {
|
|
88
|
+
const rawPayload = await get_confirmed_tx_info(state, hash);
|
|
89
|
+
if (!rawPayload) {
|
|
90
|
+
throw new Error(`No payload found for tx hash: ${hash}`);
|
|
91
|
+
}
|
|
92
|
+
const confirmedLength = await state.getTransactionConfirmedLength(hash);
|
|
93
|
+
if (confirmedLength === null) {
|
|
94
|
+
throw new Error(`No confirmed length found for tx hash: ${hash} in confirmed mode`);
|
|
95
|
+
}
|
|
96
|
+
const normalizedPayload = normalizeDecodedPayloadForJson(rawPayload.decoded, true);
|
|
97
|
+
const feeBuffer = state.getFee();
|
|
98
|
+
return {
|
|
99
|
+
txDetails: normalizedPayload,
|
|
100
|
+
confirmed_length: confirmedLength,
|
|
101
|
+
fee: bufferToBigInt(feeBuffer).toString(),
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const rawPayload = await get_unconfirmed_tx_info(state, hash);
|
|
106
|
+
if (!rawPayload) {
|
|
107
|
+
throw new Error(`No payload found for tx hash: ${hash}`);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const normalizedPayload = normalizeDecodedPayloadForJson(rawPayload.decoded, true);
|
|
111
|
+
const length = await state.getTransactionConfirmedLength(hash);
|
|
112
|
+
if (length === null) {
|
|
113
|
+
return {
|
|
114
|
+
txDetails: normalizedPayload,
|
|
115
|
+
confirmed_length: 0,
|
|
116
|
+
fee: "0",
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const feeBuffer = state.getFee();
|
|
121
|
+
return {
|
|
122
|
+
txDetails: normalizedPayload,
|
|
123
|
+
confirmed_length: length,
|
|
124
|
+
fee: bufferToBigInt(feeBuffer).toString(),
|
|
125
|
+
};
|
|
126
|
+
}
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
DHT_BOOTSTRAPS
|
|
18
18
|
} from '../../utils/constants.js';
|
|
19
19
|
import ConnectionManager from './services/ConnectionManager.js';
|
|
20
|
+
import MessageOrchestrator from './services/MessageOrchestrator.js';
|
|
20
21
|
import NetworkWalletFactory from './identity/NetworkWalletFactory.js';
|
|
21
22
|
const wakeup = new w();
|
|
22
23
|
|
|
@@ -29,6 +30,7 @@ class Network extends ReadyResource {
|
|
|
29
30
|
#transactionPoolService;
|
|
30
31
|
#validatorObserverService;
|
|
31
32
|
#validatorConnectionManager;
|
|
33
|
+
#validatorMessageOrchestrator;
|
|
32
34
|
#options;
|
|
33
35
|
#identityProvider = null;
|
|
34
36
|
|
|
@@ -41,6 +43,7 @@ class Network extends ReadyResource {
|
|
|
41
43
|
this.#validatorObserverService = new ValidatorObserverService(this, state, address, options);
|
|
42
44
|
this.#networkMessages = new NetworkMessages(this, options);
|
|
43
45
|
this.#validatorConnectionManager = new ConnectionManager({ maxValidators: options.max_validators });
|
|
46
|
+
this.#validatorMessageOrchestrator = new MessageOrchestrator(this.#validatorConnectionManager, state);
|
|
44
47
|
this.admin_stream = null;
|
|
45
48
|
this.admin = null;
|
|
46
49
|
this.validator = null;
|
|
@@ -68,6 +71,10 @@ class Network extends ReadyResource {
|
|
|
68
71
|
return this.#validatorConnectionManager;
|
|
69
72
|
}
|
|
70
73
|
|
|
74
|
+
get validatorMessageOrchestrator() {
|
|
75
|
+
return this.#validatorMessageOrchestrator;
|
|
76
|
+
}
|
|
77
|
+
|
|
71
78
|
async _open() {
|
|
72
79
|
console.log('Network initialization...');
|
|
73
80
|
this.transactionPoolService.start();
|
|
@@ -161,29 +168,35 @@ class Network extends ReadyResource {
|
|
|
161
168
|
}
|
|
162
169
|
|
|
163
170
|
async tryConnect(publicKey, type = null) {
|
|
164
|
-
if (
|
|
171
|
+
if (this.#swarm === null) throw new Error('Network swarm is not initialized');
|
|
165
172
|
|
|
166
|
-
|
|
167
|
-
|
|
173
|
+
const target = b4a.from(publicKey, 'hex');
|
|
174
|
+
if (!this.#swarm.peers.has(publicKey)) {
|
|
175
|
+
this.#swarm.joinPeer(target);
|
|
168
176
|
let cnt = 0;
|
|
169
|
-
while (
|
|
170
|
-
if (cnt >= 1500) break;
|
|
177
|
+
while (!this.#swarm.peers.has(publicKey) && cnt < 1500) { // TODO: Get rid of the magic number and add a config option for this
|
|
171
178
|
await sleep(10);
|
|
172
179
|
cnt += 1;
|
|
173
180
|
}
|
|
174
181
|
}
|
|
175
182
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
183
|
+
const peerInfo = this.#swarm.peers.get(publicKey);
|
|
184
|
+
if (!peerInfo) return;
|
|
185
|
+
|
|
186
|
+
// Wait for the swarm to establish the connection and for protomux to attach
|
|
187
|
+
let stream = this.#swarm._allConnections.get(peerInfo.publicKey);
|
|
188
|
+
let attempts = 0;
|
|
189
|
+
while ((!stream || !stream.messenger) && attempts < 1500) { // TODO: Get rid of the magic number and add a config option
|
|
190
|
+
await sleep(10);
|
|
191
|
+
attempts += 1;
|
|
192
|
+
stream = this.#swarm._allConnections.get(peerInfo.publicKey);
|
|
193
|
+
}
|
|
194
|
+
if (!stream || !stream.messenger) return;
|
|
195
|
+
|
|
196
|
+
if (type === 'validator') {
|
|
197
|
+
this.#validatorConnectionManager.addValidator(target, stream);
|
|
186
198
|
}
|
|
199
|
+
await this.#sendRequestByType(stream, type);
|
|
187
200
|
}
|
|
188
201
|
|
|
189
202
|
async isConnected(publicKey) {
|
|
@@ -207,7 +220,7 @@ class Network extends ReadyResource {
|
|
|
207
220
|
} else {
|
|
208
221
|
return;
|
|
209
222
|
}
|
|
210
|
-
await this.spinLock(() => !waitFor)
|
|
223
|
+
await this.spinLock(() => !waitFor())
|
|
211
224
|
};
|
|
212
225
|
|
|
213
226
|
async spinLock(conditionFn, maxIterations = 1500, intervalMs = 10) {
|