trac-msb 0.2.0 → 0.2.2

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/LICENSE CHANGED
@@ -186,7 +186,7 @@
186
186
  same "printed page" as the copyright notice for easier
187
187
  identification within third-party archives.
188
188
 
189
- Copyright [yyyy] [name of copyright owner]
189
+ Copyright 2025 Trac Systems UG
190
190
 
191
191
  Licensed under the Apache License, Version 2.0 (the "License");
192
192
  you may not use this file except in compliance with the License.
@@ -198,4 +198,4 @@
198
198
  distributed under the License is distributed on an "AS IS" BASIS,
199
199
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
200
  See the License for the specific language governing permissions and
201
- limitations under the License.
201
+ limitations under the License.
package/SECURITY.md ADDED
@@ -0,0 +1,54 @@
1
+ # Security Policy
2
+
3
+ ## Supported Versions
4
+
5
+ The following table shows which versions of this project are currently receiving security updates.
6
+
7
+ | Version | Supported |
8
+ | -------- | ------------------ |
9
+ | 0.2.x | :white_check_mark: |
10
+ | < 0.2 | :x: |
11
+
12
+ Older versions (< 0.2.x) are no longer supported.
13
+ Please upgrade to the latest release to ensure you receive security fixes.
14
+
15
+ ---
16
+
17
+ ## Reporting a Vulnerability
18
+
19
+ If you discover a security vulnerability affecting the TRAC Network,
20
+ please **do not disclose it publicly** (e.g., on social media, Discord, or GitHub Issues).
21
+
22
+ Instead, report it responsibly and confidentially through one of the following contacts:
23
+
24
+ - 📧 **info@trac.network** — protocol, API, infrastructure, or tooling vulnerabilities
25
+
26
+ Alternatively, you can use the **“Report a vulnerability”** option on GitHub if available.
27
+
28
+ ---
29
+
30
+ ### Responsible Disclosure Guidelines
31
+
32
+ - **Do not exploit or test vulnerabilities on mainnet.**
33
+ Use **testnet** environments or isolated local nodes for proof-of-concepts (PoCs).
34
+ - Include clear and reproducible details in your report:
35
+ - affected **component or module**,
36
+ - minimal **proof of concept (PoC)** showing the issue,
37
+ - expected vs. actual behavior,
38
+ - estimated **impact** (e.g., fund loss, network instability, or data integrity issue),
39
+ - any relevant **logs or transaction hashes** if applicable.
40
+ - Please avoid:
41
+ - phishing or social engineering,
42
+ - denial-of-service (DoS) or spam tests,
43
+ - public disclosure before coordinated remediation.
44
+
45
+ ---
46
+
47
+ ### Response Process
48
+
49
+ - You will receive an **acknowledgment within 72 hours** of submission.
50
+ - The TRAC Network security team will investigate and validate the issue.
51
+ - If confirmed, we’ll provide updates on the **remediation plan and timeline**.
52
+ - After a fix is deployed, we may publicly recognize your contribution (with your consent).
53
+
54
+ Thank you for helping us keep the TRAC Network ecosystem secure and resilient 💙
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "trac-msb",
3
3
  "main": "msb.mjs",
4
- "version": "0.2.0",
4
+ "version": "0.2.2",
5
5
  "pear": {
6
6
  "name": "trac-msb",
7
7
  "type": "terminal"
@@ -32,9 +32,23 @@ export const createServer = (msbInstance) => {
32
32
 
33
33
  // Find the matching route
34
34
  let foundRoute = false;
35
- for (const route of routes) {
36
- // Simple path matching
37
- if (req.method === route.method && req.url.startsWith(route.path)) {
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
+
38
52
  foundRoute = true;
39
53
  try {
40
54
  // This try/catch covers synchronous errors and errors from awaited promises
@@ -43,8 +57,6 @@ export const createServer = (msbInstance) => {
43
57
  } catch (error) {
44
58
  // Catch errors thrown directly from the handler (or its awaited parts)
45
59
  console.error(`Error on ${route.path}:`, error);
46
-
47
- // FIX: Pass an object payload
48
60
  respond(500, { error: 'An error occurred processing the request.' });
49
61
  }
50
62
  break;
package/rpc/handlers.mjs CHANGED
@@ -1,5 +1,6 @@
1
- import {decodeBase64Payload, isBase64, sanitizeBulkPayloadsRequestBody, sanitizeTransferPayload, validatePayloadStructure} from "./utils/helpers.mjs"
1
+ import { decodeBase64Payload, isBase64, sanitizeBulkPayloadsRequestBody, sanitizeTransferPayload, validatePayloadStructure } from "./utils/helpers.mjs"
2
2
  import { MAX_SIGNED_LENGTH } from "./constants.mjs";
3
+ import { isHexString } from "../src/utils/helpers";
3
4
 
4
5
  export async function handleBalance({ req, respond, msbInstance }) {
5
6
  const [path, queryString] = req.url.split("?");
@@ -198,4 +199,47 @@ export async function handleFetchBulkTxPayloads({ msbInstance, respond, req }) {
198
199
  console.error('Stream error in handleFetchBulkTxPayloads:', err);
199
200
  respond(500, { error: 'Request stream failed during body transfer.' });
200
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
+ }
201
245
  }
package/rpc/routes/v1.mjs CHANGED
@@ -7,7 +7,8 @@ import {
7
7
  handleTxHashes,
8
8
  handleUnconfirmedLength,
9
9
  handleTransactionDetails,
10
- handleFetchBulkTxPayloads
10
+ handleFetchBulkTxPayloads,
11
+ handleTransactionExtendedDetails
11
12
  } from '../handlers.mjs';
12
13
 
13
14
  export const v1Routes = [
@@ -20,4 +21,5 @@ export const v1Routes = [
20
21
  { method: 'GET', path: '/unconfirmed-length', handler: handleUnconfirmedLength },
21
22
  { method: 'GET', path: '/tx', handler: handleTransactionDetails },
22
23
  { method: 'POST', path: '/tx-payloads-bulk', handler: handleFetchBulkTxPayloads },
24
+ { method: 'GET', path: '/tx/details', handler: handleTransactionExtendedDetails },
23
25
  ];
@@ -5,6 +5,6 @@ export function startRpcServer(msbInstance, host, port) {
5
5
  const server = createServer(msbInstance)
6
6
 
7
7
  return server.listen(port, host, () => {
8
- console.log(`Running RPC with https at https://${host}:${port}`);
8
+ console.log(`Running RPC with http at http://${host}:${port}`);
9
9
  });
10
10
  }
@@ -17,7 +17,7 @@ import {
17
17
  TRAC_NAMESPACE,
18
18
  CustomEventType
19
19
  } from '../../utils/constants.js';
20
- import { isHexString, sleep } from '../../utils/helpers.js';
20
+ import { isHexString, sleep, isTransactionRecordPut } from '../../utils/helpers.js';
21
21
  import PeerWallet from 'trac-wallet';
22
22
  import Check from '../../utils/check.js';
23
23
  import { safeDecodeApplyOperation } from '../../utils/protobuf/operationHelpers.js';
@@ -265,6 +265,24 @@ class State extends ReadyResource {
265
265
  return b4a.equals(initialization, safeWriteUInt32BE(0, 0))
266
266
  }
267
267
  }
268
+ async getTransactionConfirmedLength(hash) {
269
+ if (!isHexString(hash) || hash.length !== 64) {
270
+ throw new Error("Invalid hash format");
271
+ }
272
+
273
+ const confirmedLength = this.getSignedLength();
274
+ const historyStream = this.#base.view.createHistoryStream({
275
+ gte: 0,
276
+ lte: confirmedLength
277
+ });
278
+
279
+ for await (const entry of historyStream) {
280
+ if (isTransactionRecordPut(entry) && entry.key === hash) {
281
+ return entry.seq;
282
+ }
283
+ }
284
+ return null;
285
+ }
268
286
 
269
287
  async confirmedTransactionsBetween(startSignedLength, endSignedLength) {
270
288
  if (!Number.isInteger(startSignedLength) || !Number.isInteger(endSignedLength)) {
package/src/index.js CHANGED
@@ -8,7 +8,7 @@ import readline from "readline";
8
8
  import tty from "tty";
9
9
 
10
10
  import { sleep, getFormattedIndexersWithAddresses, isHexString, convertAdminCoreOperationPayloadToHex } from "./utils/helpers.js";
11
- import { verifyDag, printHelp, printWalletInfo, get_tx_info, printBalance } from "./utils/cli.js";
11
+ import { verifyDag, printHelp, printWalletInfo, get_confirmed_tx_info, printBalance, get_unconfirmed_tx_info } from "./utils/cli.js";
12
12
  import CompleteStateMessageOperations from "./messages/completeStateMessages/CompleteStateMessageOperations.js";
13
13
  import { safeDecodeApplyOperation } from "./utils/protobuf/operationHelpers.js";
14
14
  import { bufferToAddress, isAddressValid } from "./core/state/utils/address.js";
@@ -821,11 +821,11 @@ export class MainSettlementBus extends ReadyResource {
821
821
  }
822
822
 
823
823
  const { addressBalancePair, totalBalance, totalAddresses, addresses } = await fileUtils.readBalanceMigrationFile();
824
-
824
+
825
825
  for (let i = 0; i < addresses.length; i++) {
826
826
  await migrationUtils.validateAddressFromIncomingFile(this.#state, addresses[i].address, adminEntry);
827
827
  }
828
-
828
+
829
829
  await fileUtils.validateBalanceMigrationData(addresses);
830
830
  const migrationNumber = await fileUtils.getNextMigrationNumber();
831
831
  await fileUtils.createMigrationEntryFile(addressBalancePair, migrationNumber);
@@ -1070,7 +1070,7 @@ export class MainSettlementBus extends ReadyResource {
1070
1070
  } else if (input.startsWith("/get_tx_info")) {
1071
1071
  const splitted = input.split(" ");
1072
1072
  const txHash = splitted[1];
1073
- const txInfo = await get_tx_info(this.#state, txHash);
1073
+ const txInfo = await get_confirmed_tx_info(this.#state, txHash);
1074
1074
  if (txInfo) {
1075
1075
  console.log(`Payload for transaction hash ${txHash}:`);
1076
1076
  console.log(txInfo.decoded);
@@ -1199,7 +1199,7 @@ export class MainSettlementBus extends ReadyResource {
1199
1199
  this.network.validatorConnectionManager.rotate() // force change connection rotation for the next retry
1200
1200
  }
1201
1201
 
1202
- return { message: "Transaction broadcasted successfully.", signedLength, unsignedLength };
1202
+ return { message: "Transaction broadcasted successfully.", signedLength, unsignedLength, tx: hash };
1203
1203
  } else {
1204
1204
  // Handle case where payload is missing if called internally without one.
1205
1205
  throw new Error("Transaction payload is required for broadcast_transaction command.");
@@ -1218,7 +1218,7 @@ export class MainSettlementBus extends ReadyResource {
1218
1218
  throw new Error("Length of input tx hashes exceeded.");
1219
1219
  }
1220
1220
 
1221
- const promises = hashes.map(hash => get_tx_info(this.#state, hash));
1221
+ const promises = hashes.map(hash => get_confirmed_tx_info(this.#state, hash));
1222
1222
  const results = await Promise.all(promises);
1223
1223
 
1224
1224
  // Iterate and categorize
@@ -1251,9 +1251,8 @@ export class MainSettlementBus extends ReadyResource {
1251
1251
  } else if (input.startsWith("/get_tx_details")) {
1252
1252
  const splitted = input.split(' ')
1253
1253
  const hash = splitted[1];
1254
-
1255
1254
  try {
1256
- const rawPayload = await get_tx_info(this.#state, hash);
1255
+ const rawPayload = await get_confirmed_tx_info(this.#state, hash);
1257
1256
  if (!rawPayload) {
1258
1257
  console.log(`No payload found for tx hash: ${hash}`)
1259
1258
  return null
@@ -1264,6 +1263,52 @@ export class MainSettlementBus extends ReadyResource {
1264
1263
  throw new Error("Invalid params to perform the request.", error.message);
1265
1264
  }
1266
1265
  }
1266
+ else if (input.startsWith("/get_extended_tx_details")) {
1267
+ const splitted = input.split(' ');
1268
+ const hash = splitted[1];
1269
+ const confirmed = splitted[2] === 'true';
1270
+
1271
+ if (confirmed) {
1272
+ const rawPayload = await get_confirmed_tx_info(this.#state, hash);
1273
+ if (!rawPayload) {
1274
+ throw new Error(`No payload found for tx hash: ${hash}`);
1275
+ }
1276
+ const confirmedLength = await this.#state.getTransactionConfirmedLength(hash);
1277
+ const normalizedPayload = normalizeDecodedPayloadForJson(rawPayload.decoded, true);
1278
+ if (confirmedLength === null) {
1279
+ throw new Error(`No confirmed length found for tx hash: ${hash} in confirmed mode`);
1280
+ }
1281
+ const fee = this.#state.getFee();
1282
+ return {
1283
+ txDetails: normalizedPayload,
1284
+ confirmed_length: confirmedLength,
1285
+ fee: bufferToBigInt(fee).toString()
1286
+ }
1287
+ }
1288
+ else {
1289
+ const rawPayload = await get_unconfirmed_tx_info(this.#state, hash);
1290
+ if (!rawPayload) {
1291
+ throw new Error(`No payload found for tx hash: ${hash}`);
1292
+ }
1293
+ const normalizedPayload = normalizeDecodedPayloadForJson(rawPayload.decoded, true);
1294
+ const length = await this.#state.getTransactionConfirmedLength(hash)
1295
+ if (length === null) {
1296
+ return {
1297
+ txDetails: normalizedPayload,
1298
+ confirmed_length: 0,
1299
+ fee: '0'
1300
+ }
1301
+ }
1302
+
1303
+ const fee = this.#state.getFee();
1304
+ return {
1305
+ txDetails: normalizedPayload,
1306
+ confirmed_length: length,
1307
+ fee: bufferToBigInt(fee).toString()
1308
+ }
1309
+
1310
+ }
1311
+ }
1267
1312
  }
1268
1313
  if (rl) rl.prompt();
1269
1314
  }
package/src/utils/cli.js CHANGED
@@ -103,7 +103,7 @@ export const printBalance = async (address, state, wallet_enabled) => {
103
103
  }
104
104
  }
105
105
 
106
- export const get_tx_info = async (state_instance, txHash) => {
106
+ export const get_confirmed_tx_info = async (state_instance, txHash) => {
107
107
  const payload = await state_instance.getSigned(txHash);
108
108
  if (!payload) {
109
109
  return null
@@ -119,3 +119,20 @@ export const get_tx_info = async (state_instance, txHash) => {
119
119
  decoded
120
120
  }
121
121
  }
122
+
123
+ export const get_unconfirmed_tx_info = async (state_instance, txHash) => {
124
+ const payload = await state_instance.get(txHash);
125
+ if (!payload) {
126
+ return null
127
+ }
128
+
129
+ const decoded = safeDecodeApplyOperation(payload);
130
+ if (!decoded) {
131
+ throw new Error(`Failed to decode payload for transaction hash: ${txHash}`);
132
+ }
133
+
134
+ return {
135
+ payload,
136
+ decoded
137
+ }
138
+ }
@@ -97,3 +97,10 @@ export function convertAdminCoreOperationPayloadToHex(payload) {
97
97
  },
98
98
  };
99
99
  }
100
+
101
+ export function isTransactionRecordPut(entry) {
102
+ const isPut = entry.type === "put";
103
+ const isHex = isHexString(entry.key);
104
+ const is64 = entry.key.length === 64;
105
+ return isPut && isHex && is64;
106
+ }
@@ -28,7 +28,7 @@ const setupNetwork = async () => {
28
28
  store_name: '/admin'
29
29
  }
30
30
 
31
- const peer =await setupMsbAdmin(testKeyPair1, tmpDirectory, rpcOpts)
31
+ const peer = await setupMsbAdmin(testKeyPair1, tmpDirectory, rpcOpts)
32
32
  const writer = await setupMsbWriter(peer, 'writer', testKeyPair2, tmpDirectory, peer.options);
33
33
  return { writer, peer }
34
34
  }
@@ -76,7 +76,7 @@ describe("API acceptance tests", () => {
76
76
  it("< 1000", async () => {
77
77
  const res = await request(server).get("/v1/tx-hashes/1/1001")
78
78
  expect(res.statusCode).toBe(200)
79
- expect(res.body).toEqual({
79
+ expect(res.body).toEqual({
80
80
  hashes: expect.arrayContaining([
81
81
  expect.objectContaining({
82
82
  hash: expect.any(String),
@@ -124,6 +124,7 @@ describe("API acceptance tests", () => {
124
124
  message: "Transaction broadcasted successfully.",
125
125
  signedLength: expect.any(Number),
126
126
  unsignedLength: expect.any(Number),
127
+ tx: expect.any(String)
127
128
  }
128
129
  })
129
130
  })
@@ -137,8 +138,8 @@ describe("API acceptance tests", () => {
137
138
  const res = await request(server)
138
139
  .post("/v1/tx-payloads-bulk")
139
140
  .set("Accept", "application/json")
140
- .send(JSON.stringify( payload ))
141
-
141
+ .send(JSON.stringify(payload))
142
+
142
143
  expect(res.statusCode).toBe(200)
143
144
  expect(res.body).toMatchObject({
144
145
  results: expect.arrayOf(
@@ -146,7 +147,177 @@ describe("API acceptance tests", () => {
146
147
  hash: expect.any(String),
147
148
  })
148
149
  ),
149
- missing:[]
150
+ missing: []
150
151
  })
151
152
  })
153
+
154
+ describe('GET /v1/tx/details', () => {
155
+
156
+ it("positive case - should return 200 for valid already broadcasted hash confirmed and unconfirmed", async () => {
157
+ const txData = await tracCrypto.transaction.preBuild(
158
+ wallet.address,
159
+ wallet.address,
160
+ b4a.toString($TNK(1n), 'hex'),
161
+ b4a.toString(await msb.state.getIndexerSequenceState(), 'hex')
162
+ );
163
+
164
+ const payload = tracCrypto.transaction.build(txData, b4a.from(wallet.secretKey, 'hex'));
165
+ const broadcastRes = await request(server)
166
+ .post("/v1/broadcast-transaction")
167
+ .set("Accept", "application/json")
168
+ .send(JSON.stringify({ payload }));
169
+ expect(broadcastRes.statusCode).toBe(200);
170
+
171
+ const resConfirmed = await request(server)
172
+ .get(`/v1/tx/details/${txData.hash.toString('hex')}?confirmed=true`);
173
+ expect(resConfirmed.statusCode).toBe(200);
174
+
175
+ expect(resConfirmed.body).toMatchObject({
176
+ txDetails: expect.any(Object),
177
+ confirmed_length: expect.any(Number),
178
+ fee: expect.any(String)
179
+ })
180
+
181
+ const resUnconfirmed = await request(server)
182
+ .get(`/v1/tx/details/${txData.hash.toString('hex')}?confirmed=false`);
183
+ expect(resUnconfirmed.statusCode).toBe(200);
184
+
185
+ expect(resUnconfirmed.body).toMatchObject({
186
+ txDetails: expect.any(Object),
187
+ confirmed_length: expect.any(Number),
188
+ fee: expect.any(String)
189
+ })
190
+ });
191
+
192
+ it("should handle null confirmed_length for unconfirmed transaction", async () => {
193
+ const txData = await tracCrypto.transaction.preBuild(
194
+ wallet.address,
195
+ wallet.address,
196
+ b4a.toString($TNK(1n), 'hex'),
197
+ b4a.toString(await msb.state.getIndexerSequenceState(), 'hex')
198
+ );
199
+
200
+ const payload = tracCrypto.transaction.build(txData, b4a.from(wallet.secretKey, 'hex'));
201
+
202
+ const originalGetConfirmedLength = msb.state.getTransactionConfirmedLength;
203
+ msb.state.getTransactionConfirmedLength = async () => null;
204
+
205
+ try {
206
+ const broadcastRes = await request(server)
207
+ .post("/v1/broadcast-transaction")
208
+ .set("Accept", "application/json")
209
+ .send(JSON.stringify({ payload }));
210
+ expect(broadcastRes.statusCode).toBe(200);
211
+
212
+ const res = await request(server)
213
+ .get(`/v1/tx/details/${txData.hash.toString('hex')}?confirmed=false`);
214
+ expect(res.statusCode).toBe(200);
215
+
216
+ expect(res.body).toMatchObject({
217
+ txDetails: expect.any(Object),
218
+ confirmed_length: 0,
219
+ fee: '0'
220
+ });
221
+ } finally {
222
+ msb.state.getTransactionConfirmedLength = originalGetConfirmedLength;
223
+ }
224
+ });
225
+
226
+ it("should return 404 for non-existent transaction hash", async () => {
227
+ const nonExistentHash = "0b4d1c1dac48af13212f616601d7399457476a0b644850875b7f4b79df6ff89c";
228
+ const res = await request(server)
229
+ .get(`/v1/tx/details/${nonExistentHash}`);
230
+
231
+ expect(res.statusCode).toBe(404);
232
+ expect(res.body).toEqual({
233
+ error: `No payload found for tx hash: ${nonExistentHash}`
234
+ });
235
+ });
236
+
237
+ it("should return 400 for invalid hash format (too short)", async () => {
238
+ const invalidHash = '0'.repeat(63);
239
+ const res = await request(server)
240
+ .get(`/v1/tx/details/${invalidHash}`);
241
+
242
+ expect(res.statusCode).toBe(400);
243
+ expect(res.body).toEqual({
244
+ error: "Invalid transaction hash format"
245
+ });
246
+ });
247
+
248
+ it("should return 400 for invalid hash format (non-hex)", async () => {
249
+ const invalidHash = 'Z'.repeat(64);
250
+ const res = await request(server)
251
+ .get(`/v1/tx/details/${invalidHash}`);
252
+
253
+ expect(res.statusCode).toBe(400);
254
+ expect(res.body).toEqual({
255
+ error: "Invalid transaction hash format"
256
+ });
257
+ });
258
+
259
+ it("should return 400 for invalid confirmed parameter", async () => {
260
+ const hash = "0b4d1c1dac48af13212f616601d7399457476a0b644850875b7f4b79df6ff89c";
261
+
262
+ const res = await request(server)
263
+ .get(`/v1/tx/details/${hash}?confirmed=invalid`);
264
+
265
+ expect(res.statusCode).toBe(400);
266
+ expect(res.body).toEqual({
267
+ error: 'Parameter "confirmed" must be exactly "true" or "false"'
268
+ });
269
+ });
270
+
271
+ it("should return 400 for invalid confirmed parameter case (UPPERCASE)", async () => {
272
+ const hash = "0b4d1c1dac48af13212f616601d7399457476a0b644850875b7f4b79df6ff89c";
273
+ const res = await request(server).get(`/v1/tx/details/${hash}?confirmed=TRUE`);
274
+ expect(res.statusCode).toBe(400);
275
+ expect(res.body).toEqual({
276
+ error: 'Parameter "confirmed" must be exactly "true" or "false"'
277
+ });
278
+ });
279
+
280
+ it("should return 400 when no hash provided", async () => {
281
+ const res = await request(server)
282
+ .get('/v1/tx/details');
283
+
284
+ expect(res.statusCode).toBe(400);
285
+ expect(res.body).toEqual({
286
+ error: "Transaction hash is required"
287
+ });
288
+ });
289
+
290
+ it("should return 400 for hash with invalid characters", async () => {
291
+ const invalidHash = '0b4d1c1dac48$af13212f6166017399457476a0b644850875b7f4b79df6ff89c';
292
+ const res = await request(server)
293
+ .get(`/v1/tx/details/${invalidHash}`);
294
+
295
+ expect(res.statusCode).toBe(400);
296
+ expect(res.body).toEqual({
297
+ error: "Invalid transaction hash format"
298
+ });
299
+ });
300
+
301
+ it("should return 400 for hash with special characters", async () => {
302
+ const invalidHash = '!@#$%^&*'.repeat(8);
303
+ const res = await request(server)
304
+ .get(`/v1/tx/details/${invalidHash}`);
305
+
306
+ expect(res.statusCode).toBe(400);
307
+ expect(res.body).toEqual({
308
+ error: "Invalid transaction hash format"
309
+ });
310
+ });
311
+
312
+ it("should return 400 for hash with spaces", async () => {
313
+ const invalidHash = '0b4d1c1dac48af13212f616601d7399457476a0b644850875b7 4b79df6ff89c';
314
+ const res = await request(server)
315
+ .get(`/v1/tx/details/${invalidHash}`);
316
+
317
+ expect(res.statusCode).toBe(400);
318
+ expect(res.body).toEqual({
319
+ error: "Invalid transaction hash format"
320
+ });
321
+ });
322
+ })
152
323
  })
package/test/all.test.js CHANGED
@@ -6,15 +6,16 @@ async function runTests() {
6
6
  test.pause();
7
7
 
8
8
  await import('./state/stateTests.test.js');
9
- await import('./check/check.test.js');
10
- await import('./protobuf/protobuf.test.js');
11
- await import('./functions/functions.test.js');
12
- await import('./fileUtils/readAddressesFromWhitelistFile.test.js');
13
- await import('./fileUtils/readBalanceMigrationFile.test.js');
14
- await import('./migrationUtils/validateAddressFromIncomingFile.test.js');
9
+ // await import('./state/apply.addAdmin.basic.test.js');
10
+ // await import('./check/check.test.js');
11
+ // await import('./protobuf/protobuf.test.js');
12
+ // await import('./functions/functions.test.js');
13
+ // await import('./fileUtils/readAddressesFromWhitelistFile.test.js');
14
+ // await import('./fileUtils/readBalanceMigrationFile.test.js');
15
+ // await import('./migrationUtils/validateAddressFromIncomingFile.test.js');
15
16
  // await import('./messageOperations/stateMessageOperations.test.js');
16
- await import('./buffer/buffer.test.js')
17
- await import('./network/connectionManagerTests.test.js')
17
+ // await import('./buffer/buffer.test.js')
18
+ // await import('./network/connectionManagerTests.test.js')
18
19
  // await import('./apply/apply.test.js'); // This test has been disabled because Github CI fails due to lack of resources. This test can still be run locally but sometimes it hangs when destroying resources.
19
20
  test.resume();
20
21
  }