trac-msb 0.2.9 → 0.2.11
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/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 +4 -21
- package/package.json +16 -12
- package/proto/network/v1/enums/message_type.proto +16 -0
- package/proto/network/v1/enums/result_code.proto +84 -0
- package/proto/network/v1/messages/broadcast_transaction_request.proto +9 -0
- package/proto/network/v1/messages/broadcast_transaction_response.proto +13 -0
- package/proto/network/v1/messages/liveness_request.proto +8 -0
- package/proto/network/v1/messages/liveness_response.proto +11 -0
- package/proto/network/v1/network_message.proto +22 -0
- package/rpc/handlers.js +163 -90
- package/rpc/routes/v1.js +3 -1
- package/rpc/rpc_server.js +3 -3
- package/rpc/rpc_services.js +45 -31
- package/rpc/utils/helpers.js +82 -51
- package/scripts/generate-protobufs.js +37 -12
- package/src/config/args.js +46 -0
- package/src/config/config.js +99 -5
- package/src/config/env.js +86 -7
- package/src/core/network/Network.js +79 -46
- package/src/core/network/protocols/LegacyProtocol.js +21 -11
- package/src/core/network/protocols/NetworkMessages.js +38 -17
- package/src/core/network/protocols/ProtocolInterface.js +14 -2
- package/src/core/network/protocols/ProtocolSession.js +144 -17
- package/src/core/network/protocols/V1Protocol.js +37 -18
- package/src/core/network/protocols/connectionPolicies.js +88 -0
- package/src/core/network/protocols/legacy/NetworkMessageRouter.js +26 -20
- package/src/core/network/protocols/{shared/handlers/base/BaseOperationHandler.js → legacy/handlers/BaseStateOperationHandler.js} +25 -15
- package/src/core/network/protocols/legacy/handlers/{GetRequestHandler.js → LegacyGetRequestHandler.js} +6 -6
- package/src/core/network/protocols/legacy/handlers/LegacyResponseHandler.js +23 -0
- package/src/core/network/protocols/{shared/handlers/RoleOperationHandler.js → legacy/handlers/LegacyRoleOperationHandler.js} +20 -13
- package/src/core/network/protocols/{shared/handlers/SubnetworkOperationHandler.js → legacy/handlers/LegacySubnetworkOperationHandler.js} +29 -18
- package/src/core/network/protocols/{shared/handlers/TransferOperationHandler.js → legacy/handlers/LegacyTransferOperationHandler.js} +18 -12
- package/src/core/network/protocols/legacy/validators/base/BaseResponse.js +1 -1
- package/src/core/network/protocols/shared/errors/SharedValidatorRejectionError.js +27 -0
- package/src/core/network/protocols/shared/validators/{PartialBootstrapDeployment.js → PartialBootstrapDeploymentValidator.js} +9 -4
- package/src/core/network/protocols/shared/validators/{base/PartialOperation.js → PartialOperationValidator.js} +47 -25
- package/src/core/network/protocols/shared/validators/{PartialRoleAccess.js → PartialRoleAccessValidator.js} +51 -17
- package/src/core/network/protocols/shared/validators/{PartialTransaction.js → PartialTransactionValidator.js} +21 -7
- package/src/core/network/protocols/shared/validators/{PartialTransfer.js → PartialTransferValidator.js} +26 -9
- package/src/core/network/protocols/v1/NetworkMessageRouter.js +91 -7
- package/src/core/network/protocols/v1/V1ProtocolError.js +91 -0
- package/src/core/network/protocols/v1/handlers/V1BaseOperationHandler.js +65 -0
- package/src/core/network/protocols/v1/handlers/V1BroadcastTransactionOperationHandler.js +389 -0
- package/src/core/network/protocols/v1/handlers/V1LivenessOperationHandler.js +87 -0
- package/src/core/network/protocols/v1/validators/V1BaseOperation.js +211 -0
- package/src/core/network/protocols/v1/validators/V1BroadcastTransactionRequest.js +26 -0
- package/src/core/network/protocols/v1/validators/V1BroadcastTransactionResponse.js +276 -0
- package/src/core/network/protocols/v1/validators/V1LivenessRequest.js +15 -0
- package/src/core/network/protocols/v1/validators/V1LivenessResponse.js +17 -0
- package/src/core/network/protocols/v1/validators/V1ValidationSchema.js +210 -0
- package/src/core/network/services/ConnectionManager.js +147 -95
- package/src/core/network/services/MessageOrchestrator.js +152 -28
- package/src/core/network/services/PendingRequestService.js +172 -0
- package/src/core/network/services/TransactionCommitService.js +149 -0
- package/src/core/network/services/TransactionPoolService.js +133 -22
- package/src/core/network/services/TransactionRateLimiterService.js +57 -42
- package/src/core/network/services/ValidatorHealthCheckService.js +127 -0
- package/src/core/network/services/ValidatorObserverService.js +23 -32
- package/src/core/state/State.js +72 -22
- package/src/index.js +8 -5
- package/src/messages/network/v1/NetworkMessageBuilder.js +61 -81
- package/src/messages/network/v1/NetworkMessageDirector.js +16 -50
- package/src/messages/state/ApplyStateMessageBuilder.js +1 -1
- package/src/utils/Scheduler.js +0 -8
- package/src/utils/check.js +1 -1
- package/src/utils/constants.js +68 -19
- package/src/utils/deepEqualApplyPayload.js +40 -0
- package/src/utils/fileUtils.js +13 -0
- package/src/utils/helpers.js +10 -1
- package/src/utils/logger.js +25 -0
- package/src/utils/normalizers.js +38 -0
- package/src/utils/protobuf/networkV1.generated.cjs +2460 -0
- package/src/utils/protobuf/operationHelpers.js +24 -3
- package/src/utils/type.js +26 -0
- package/tests/acceptance/v1/account/account.test.mjs +8 -2
- 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 +3 -2
- package/tests/acceptance/v1/tx/tx.test.mjs +50 -17
- package/tests/acceptance/v1/tx-details/tx-details.test.mjs +60 -18
- package/tests/fixtures/check.fixtures.js +33 -32
- package/tests/fixtures/networkV1.fixtures.js +2 -27
- package/tests/fixtures/protobuf.fixtures.js +33 -32
- 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 +6 -10
- package/tests/helpers/transactionPayloads.mjs +2 -2
- package/tests/unit/messages/network/NetworkMessageBuilder.test.js +241 -81
- package/tests/unit/messages/network/NetworkMessageDirector.test.js +225 -81
- package/tests/unit/network/LegacyNetworkMessageRouter.test.js +54 -0
- package/tests/unit/network/ProtocolSession.test.js +127 -0
- package/tests/unit/network/networkModule.test.js +4 -1
- package/tests/unit/network/services/ConnectionManager.test.js +450 -0
- package/tests/unit/network/services/MessageOrchestrator.test.js +445 -0
- package/tests/unit/network/services/PendingRequestService.test.js +431 -0
- package/tests/unit/network/services/TransactionCommitService.test.js +246 -0
- package/tests/unit/network/services/TransactionPoolService.test.js +489 -0
- package/tests/unit/network/services/TransactionRateLimiterService.test.js +139 -0
- package/tests/unit/network/services/ValidatorHealthCheckService.test.js +115 -0
- package/tests/unit/network/services/services.test.js +17 -0
- package/tests/unit/network/utils/v1TestUtils.js +153 -0
- package/tests/unit/network/v1/NetworkMessageRouterV1.test.js +151 -0
- package/tests/unit/network/v1/V1BaseOperation.test.js +356 -0
- package/tests/unit/network/v1/V1BroadcastTransactionOperationHandler.test.js +129 -0
- package/tests/unit/network/v1/V1BroadcastTransactionRequest.test.js +53 -0
- package/tests/unit/network/v1/V1BroadcastTransactionResponse.test.js +512 -0
- package/tests/unit/network/v1/V1LivenessRequest.test.js +32 -0
- package/tests/unit/network/v1/V1LivenessResponse.test.js +45 -0
- package/tests/unit/network/v1/V1ResultCode.test.js +84 -0
- package/tests/unit/network/v1/V1ValidationSchema.test.js +13 -0
- package/tests/unit/network/v1/connectionPolicies.test.js +49 -0
- package/tests/unit/network/v1/handlers/V1BaseOperationHandler.test.js +284 -0
- package/tests/unit/network/v1/handlers/V1BroadcastTransactionOperationHandler.test.js +794 -0
- package/tests/unit/network/v1/handlers/V1LivenessOperationHandler.test.js +193 -0
- package/tests/unit/network/v1/v1.handlers.test.js +15 -0
- package/tests/unit/network/v1/v1.test.js +19 -0
- package/tests/unit/network/v1/v1ValidationSchema/broadcastTransactionRequest.test.js +119 -0
- package/tests/unit/network/v1/v1ValidationSchema/broadcastTransactionResponse.test.js +136 -0
- package/tests/unit/network/v1/v1ValidationSchema/common.test.js +308 -0
- package/tests/unit/network/v1/v1ValidationSchema/livenessRequest.test.js +90 -0
- package/tests/unit/network/v1/v1ValidationSchema/livenessResponse.test.js +133 -0
- package/tests/unit/unit.test.js +2 -2
- package/tests/unit/utils/deepEqualApplyPayload/deepEqualApplyPayload.test.js +102 -0
- 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/protobuf/operationHelpers.test.js +2 -4
- package/tests/unit/utils/type/type.test.js +25 -0
- package/tests/unit/utils/utils.test.js +2 -0
- package/.github/workflows/acceptance-tests.yml +0 -42
- package/.github/workflows/publish.yml +0 -33
- package/.github/workflows/unit-tests.yml +0 -40
- package/proto/network.proto +0 -74
- package/src/core/network/protocols/legacy/handlers/ResponseHandler.js +0 -37
- package/src/utils/protobuf/network.cjs +0 -840
- package/tests/unit/network/ConnectionManager.test.js +0 -191
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
import applyOperations from './applyOperations.cjs';
|
|
2
|
-
import
|
|
2
|
+
import networkV1Generated from './networkV1.generated.cjs';
|
|
3
3
|
import b4a from 'b4a';
|
|
4
4
|
|
|
5
|
+
const networkV1Operations = networkV1Generated.network.v1;
|
|
6
|
+
const NETWORK_TO_OBJECT_OPTIONS = Object.freeze({
|
|
7
|
+
enums: Number,
|
|
8
|
+
longs: Number,
|
|
9
|
+
bytes: Buffer,
|
|
10
|
+
defaults: true,
|
|
11
|
+
arrays: true,
|
|
12
|
+
oneofs: false
|
|
13
|
+
});
|
|
14
|
+
|
|
5
15
|
/**
|
|
6
16
|
* Safely encodes an operation using `applyOperations.Operation.encode`.
|
|
7
17
|
* If the encoding fails (e.g., due to an invalid payload), returns an empty Buffer.
|
|
@@ -36,6 +46,14 @@ export const safeDecodeApplyOperation = (payload) => {
|
|
|
36
46
|
return null;
|
|
37
47
|
}
|
|
38
48
|
|
|
49
|
+
export const unsafeDecodeApplyOperation= (payload) => {
|
|
50
|
+
return applyOperations.Operation.decode(payload);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export const unsafeEncodeApplyOperation = (payload) => {
|
|
54
|
+
return applyOperations.Operation.encode(payload);
|
|
55
|
+
}
|
|
56
|
+
|
|
39
57
|
export const normalizeIncomingMessage = (message) => {
|
|
40
58
|
if (!message) return null;
|
|
41
59
|
if (b4a.isBuffer(message)) {
|
|
@@ -51,10 +69,13 @@ export const normalizeIncomingMessage = (message) => {
|
|
|
51
69
|
};
|
|
52
70
|
|
|
53
71
|
export const encodeV1networkOperation = (payload) => {
|
|
54
|
-
return networkV1Operations.MessageHeader.encode(payload);
|
|
72
|
+
return b4a.from(networkV1Operations.MessageHeader.encode(payload).finish());
|
|
55
73
|
}
|
|
56
74
|
|
|
57
75
|
|
|
58
76
|
export const decodeV1networkOperation = (payload) => {
|
|
59
|
-
return networkV1Operations.MessageHeader.
|
|
77
|
+
return networkV1Operations.MessageHeader.toObject(
|
|
78
|
+
networkV1Operations.MessageHeader.decode(payload),
|
|
79
|
+
NETWORK_TO_OBJECT_OPTIONS
|
|
80
|
+
);
|
|
60
81
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import _ from "lodash"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Checks if `value` is considered defined akin to RoR `#defined?`.
|
|
5
|
+
*
|
|
6
|
+
* @static
|
|
7
|
+
* @param {*} value The value to check.
|
|
8
|
+
* @returns {boolean} Returns `false` if `value` is nullish, else `true`.
|
|
9
|
+
* @example
|
|
10
|
+
*
|
|
11
|
+
* isDefined(undefined);
|
|
12
|
+
* // => false
|
|
13
|
+
*
|
|
14
|
+
* isDefined(null);
|
|
15
|
+
* // => false
|
|
16
|
+
*
|
|
17
|
+
* isDefined(void 0);
|
|
18
|
+
* // => false
|
|
19
|
+
*
|
|
20
|
+
* isDefined(NaN);
|
|
21
|
+
* // => false
|
|
22
|
+
*/
|
|
23
|
+
export function isDefined(value) {
|
|
24
|
+
return !_.isNil(value) && !_.isNaN(value)
|
|
25
|
+
}
|
|
26
|
+
|
|
@@ -108,11 +108,17 @@ export const registerAccountTests = (context) => {
|
|
|
108
108
|
|
|
109
109
|
it("returns 500 on internal error", async () => {
|
|
110
110
|
const originalGetNodeEntry = context.rpcMsb.state.getNodeEntry
|
|
111
|
+
const failingAddress = randomAddress(context.rpcMsb.config.addressPrefix)
|
|
111
112
|
|
|
112
|
-
context.rpcMsb.state.getNodeEntry = async () => {
|
|
113
|
+
context.rpcMsb.state.getNodeEntry = async (address) => {
|
|
114
|
+
if (address === failingAddress) {
|
|
115
|
+
throw new Error("test")
|
|
116
|
+
}
|
|
117
|
+
return originalGetNodeEntry.call(context.rpcMsb.state, address)
|
|
118
|
+
}
|
|
113
119
|
|
|
114
120
|
try {
|
|
115
|
-
const res = await request(context.server).get(`/v1/account/${
|
|
121
|
+
const res = await request(context.server).get(`/v1/account/${failingAddress}`)
|
|
116
122
|
expect(res.statusCode).toBe(500)
|
|
117
123
|
expect(res.body).toEqual({ error: 'An error occurred processing the request.' })
|
|
118
124
|
} finally {
|
|
@@ -44,8 +44,7 @@ export const registerBalanceTests = (context) => {
|
|
|
44
44
|
expect(BigInt(res.body.balance)).toBe(0n)
|
|
45
45
|
})
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
it.skip("returns zero balance for an invalid address format", async () => {
|
|
47
|
+
it("returns 400 for an invalid address format", async () => {
|
|
49
48
|
const invalidAddress = "not-a-valid-address"
|
|
50
49
|
const res = await request(context.server).get(`/v1/balance/${invalidAddress}`)
|
|
51
50
|
expect(res.statusCode).toBe(400)
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import request from "supertest"
|
|
2
2
|
import b4a from "b4a"
|
|
3
|
-
import { $TNK } from "../../../../src/core/state/utils/balance.js"
|
|
4
3
|
import { buildRpcSelfTransferPayload, waitForConnection } from "../../../helpers/transactionPayloads.mjs"
|
|
5
4
|
|
|
6
5
|
const toBase64 = (value) => b4a.toString(b4a.from(JSON.stringify(value)), "base64")
|
|
@@ -52,8 +51,7 @@ export const registerBroadcastTransactionTests = (context) => {
|
|
|
52
51
|
expect(res.body).toEqual({ error: "Payload must be a valid base64 string." })
|
|
53
52
|
})
|
|
54
53
|
|
|
55
|
-
|
|
56
|
-
it.skip("returns 400 when decoded payload is not valid JSON", async () => {
|
|
54
|
+
it("returns 400 when decoded payload is not valid JSON", async () => {
|
|
57
55
|
const invalidJsonBase64 = b4a.toString(b4a.from("{{invalid"), "base64")
|
|
58
56
|
|
|
59
57
|
await waitForConnection(context.rpcMsb)
|
|
@@ -66,8 +64,7 @@ export const registerBroadcastTransactionTests = (context) => {
|
|
|
66
64
|
expect(res.body).toEqual({ error: "Decoded payload is not valid JSON." })
|
|
67
65
|
})
|
|
68
66
|
|
|
69
|
-
|
|
70
|
-
it.skip("returns 400 for invalid transaction structure", async () => {
|
|
67
|
+
it("returns 400 for invalid transaction structure", async () => {
|
|
71
68
|
const invalidStructure = {
|
|
72
69
|
type: 1,
|
|
73
70
|
address: context.wallet.address,
|
|
@@ -83,38 +80,37 @@ export const registerBroadcastTransactionTests = (context) => {
|
|
|
83
80
|
expect(res.body).toEqual({ error: "Invalid payload structure." })
|
|
84
81
|
})
|
|
85
82
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
const
|
|
89
|
-
const payload = toBase64({ type: 1, address: context.wallet.address, txo: { large: largeString } })
|
|
83
|
+
it("returns 413 when payload exceeds size limit", async () => {
|
|
84
|
+
const largeString = "a".repeat(2_100_000);
|
|
85
|
+
const payload = toBase64({ type: 1, address: context.wallet.address, txo: { large: largeString } });
|
|
90
86
|
|
|
91
|
-
await waitForConnection(context.rpcMsb)
|
|
87
|
+
await waitForConnection(context.rpcMsb);
|
|
92
88
|
const res = await request(context.server)
|
|
93
89
|
.post("/v1/broadcast-transaction")
|
|
94
90
|
.set("Accept", "application/json")
|
|
95
|
-
.send(JSON.stringify({ payload }))
|
|
91
|
+
.send(JSON.stringify({ payload }));
|
|
96
92
|
|
|
97
|
-
expect(res.statusCode).toBe(413)
|
|
98
|
-
})
|
|
93
|
+
expect(res.statusCode).toBe(413);
|
|
94
|
+
});
|
|
99
95
|
|
|
100
|
-
it.skip("returns 429 on repeated broadcast failures", async () => {
|
|
101
|
-
// TODO: Would require forcing msb to throw 'Failed to broadcast transaction after multiple attempts.'
|
|
102
|
-
const txData = await tracCrypto.transaction.preBuild(
|
|
103
|
-
context.wallet.address,
|
|
104
|
-
context.wallet.address,
|
|
105
|
-
b4a.toString($TNK(1n), 'hex'),
|
|
106
|
-
b4a.toString(await context.rpcMsb.state.getIndexerSequenceState(), 'hex')
|
|
107
|
-
)
|
|
108
96
|
|
|
109
|
-
|
|
110
|
-
await
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
.set("Accept", "application/json")
|
|
114
|
-
.send(JSON.stringify({ payload }))
|
|
97
|
+
it("returns 429 on repeated broadcast failures", async () => {
|
|
98
|
+
const { payload } = await buildRpcSelfTransferPayload(context, context.rpcMsb.state, 1n);
|
|
99
|
+
const originalMethod = context.rpcMsb.broadcastPartialTransaction;
|
|
100
|
+
context.rpcMsb.broadcastPartialTransaction = async () => false;
|
|
115
101
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
102
|
+
try {
|
|
103
|
+
await waitForConnection(context.rpcMsb);
|
|
104
|
+
const res = await request(context.server)
|
|
105
|
+
.post("/v1/broadcast-transaction")
|
|
106
|
+
.set("Accept", "application/json")
|
|
107
|
+
.send(JSON.stringify({ payload }));
|
|
108
|
+
|
|
109
|
+
expect(res.statusCode).toBe(429);
|
|
110
|
+
expect(res.body).toEqual({ error: "Failed to broadcast transaction after multiple attempts." });
|
|
111
|
+
} finally {
|
|
112
|
+
context.rpcMsb.broadcastPartialTransaction = originalMethod;
|
|
113
|
+
}
|
|
114
|
+
});
|
|
119
115
|
})
|
|
120
116
|
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import request from "supertest"
|
|
2
|
+
|
|
3
|
+
export const registerHealthTests = (context) => {
|
|
4
|
+
describe("GET /v1/health", () => {
|
|
5
|
+
it("should return 200 and ok:true when healthy", async () => {
|
|
6
|
+
const res = await request(context.server).get("/v1/health")
|
|
7
|
+
expect(res.statusCode).toBe(200)
|
|
8
|
+
expect(res.body).toEqual({ ok: true })
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
it("should return 503 when the state is unavailable", async () => {
|
|
12
|
+
const originalState = context.rpcMsb.state;
|
|
13
|
+
Object.defineProperty(context.rpcMsb, 'state', {
|
|
14
|
+
get: () => null,
|
|
15
|
+
configurable: true
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
const res = await request(context.server).get("/v1/health")
|
|
20
|
+
|
|
21
|
+
expect(res.statusCode).toBe(503)
|
|
22
|
+
expect(res.body).toEqual({
|
|
23
|
+
error: "Could not connect to RPC server"
|
|
24
|
+
})
|
|
25
|
+
} finally {
|
|
26
|
+
Object.defineProperty(context.rpcMsb, 'state', {
|
|
27
|
+
get: () => originalState,
|
|
28
|
+
configurable: true
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
})
|
|
32
|
+
})
|
|
33
|
+
}
|
|
@@ -13,6 +13,7 @@ import { registerTxDetailsTests } from "./tx-details/tx-details.test.mjs"
|
|
|
13
13
|
import { registerTxTests } from "./tx/tx.test.mjs"
|
|
14
14
|
import { registerTxvTests } from "./txv/txv.test.mjs"
|
|
15
15
|
import { registerUnconfirmedLengthTests } from "./unconfirmed-length/unconfirmed-length.test.mjs"
|
|
16
|
+
import { registerHealthTests } from "./health/health.test.mjs"
|
|
16
17
|
|
|
17
18
|
let toClose
|
|
18
19
|
let tmpDirectory
|
|
@@ -37,8 +38,7 @@ const setupNetwork = async () => {
|
|
|
37
38
|
enableInteractiveMode: false,
|
|
38
39
|
disableRateLimit: true,
|
|
39
40
|
enableTxApplyLogs: false,
|
|
40
|
-
storesDirectory: `${tmpDirectory}/
|
|
41
|
-
storeName: '/admin'
|
|
41
|
+
storesDirectory: `${tmpDirectory}/admin/`,
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
const admin = await setupMsbAdmin(testKeyPair1, tmpDirectory, rpcOpts)
|
|
@@ -91,4 +91,5 @@ describe("API acceptance tests", () => {
|
|
|
91
91
|
registerTxPayloadsBulkTests(testContext)
|
|
92
92
|
registerTxDetailsTests(testContext)
|
|
93
93
|
registerAccountTests(testContext)
|
|
94
|
+
registerHealthTests(testContext)
|
|
94
95
|
})
|
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
import request from "supertest"
|
|
2
2
|
import { buildRpcSelfTransferPayload, waitForConnection } from "../../../helpers/transactionPayloads.mjs"
|
|
3
|
+
import { sleep } from "../../../../src/utils/helpers.js"
|
|
4
|
+
|
|
5
|
+
const TX_ENDPOINT_TIMEOUT_MS = 4000
|
|
6
|
+
const TX_ENDPOINT_RETRY_INTERVAL_MS = 100
|
|
7
|
+
|
|
8
|
+
const waitForStatusCode = async (requestFactory, expectedStatusCode, timeoutMs = TX_ENDPOINT_TIMEOUT_MS) => {
|
|
9
|
+
const startedAt = Date.now()
|
|
10
|
+
let response = null
|
|
11
|
+
|
|
12
|
+
while ((Date.now() - startedAt) < timeoutMs) {
|
|
13
|
+
response = await requestFactory()
|
|
14
|
+
if (response.statusCode === expectedStatusCode) {
|
|
15
|
+
return response
|
|
16
|
+
}
|
|
17
|
+
await sleep(TX_ENDPOINT_RETRY_INTERVAL_MS)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return response
|
|
21
|
+
}
|
|
3
22
|
|
|
4
23
|
export const registerTxTests = (context) => {
|
|
5
24
|
describe("GET /v1/tx/:hash", () => {
|
|
@@ -17,7 +36,10 @@ export const registerTxTests = (context) => {
|
|
|
17
36
|
.send(JSON.stringify({ payload }))
|
|
18
37
|
expect(broadcastRes.statusCode).toBe(200)
|
|
19
38
|
|
|
20
|
-
const res = await
|
|
39
|
+
const res = await waitForStatusCode(
|
|
40
|
+
() => request(context.server).get(`/v1/tx/${txHashHex}`),
|
|
41
|
+
200
|
|
42
|
+
)
|
|
21
43
|
expect(res.statusCode).toBe(200)
|
|
22
44
|
expect(res.body).toMatchObject({ txDetails: expect.any(Object) })
|
|
23
45
|
})
|
|
@@ -29,70 +51,81 @@ export const registerTxTests = (context) => {
|
|
|
29
51
|
expect(res.body).toEqual({ txDetails: null })
|
|
30
52
|
})
|
|
31
53
|
|
|
32
|
-
|
|
33
|
-
it.skip("returns 400 for invalid hash format (too short)", async () => {
|
|
54
|
+
it("returns 400 for invalid hash format (too short)", async () => {
|
|
34
55
|
const invalidHash = '0'.repeat(63)
|
|
35
56
|
const res = await request(context.server).get(`/v1/tx/${invalidHash}`)
|
|
36
57
|
expect(res.statusCode).toBe(400)
|
|
37
58
|
expect(res.body).toEqual({ error: "Invalid transaction hash format" })
|
|
38
59
|
})
|
|
39
60
|
|
|
40
|
-
it
|
|
61
|
+
it("returns 400 for invalid hash format (non-hex)", async () => {
|
|
41
62
|
const invalidHash = 'Z'.repeat(64)
|
|
42
63
|
const res = await request(context.server).get(`/v1/tx/${invalidHash}`)
|
|
43
64
|
expect(res.statusCode).toBe(400)
|
|
44
65
|
expect(res.body).toEqual({ error: "Invalid transaction hash format" })
|
|
45
66
|
})
|
|
46
67
|
|
|
47
|
-
it
|
|
68
|
+
it("returns 400 when no hash provided", async () => {
|
|
48
69
|
const res = await request(context.server).get('/v1/tx')
|
|
49
70
|
expect(res.statusCode).toBe(400)
|
|
50
71
|
expect(res.body).toEqual({ error: "Transaction hash is required" })
|
|
51
72
|
})
|
|
52
73
|
|
|
53
|
-
it
|
|
74
|
+
it("returns 400 for hash with invalid characters", async () => {
|
|
54
75
|
const invalidHash = '0b4d1c1dac48$af13212f6166017399457476a0b644850875b7f4b79df6ff89c'
|
|
55
76
|
const res = await request(context.server).get(`/v1/tx/${invalidHash}`)
|
|
56
77
|
expect(res.statusCode).toBe(400)
|
|
57
78
|
expect(res.body).toEqual({ error: "Invalid transaction hash format" })
|
|
58
79
|
})
|
|
59
80
|
|
|
60
|
-
it
|
|
81
|
+
it("returns 400 for hash with special characters", async () => {
|
|
61
82
|
const invalidHash = '!@#$%^&*'.repeat(8)
|
|
62
83
|
const res = await request(context.server).get(`/v1/tx/${invalidHash}`)
|
|
63
84
|
expect(res.statusCode).toBe(400)
|
|
64
85
|
expect(res.body).toEqual({ error: "Invalid transaction hash format" })
|
|
65
86
|
})
|
|
66
87
|
|
|
67
|
-
it
|
|
88
|
+
it("returns 400 for hash with spaces", async () => {
|
|
68
89
|
const invalidHash = '0b4d1c1dac48af13212f616601d7399457476a0b644850875b7 4b79df6ff89c'
|
|
69
90
|
const res = await request(context.server).get(`/v1/tx/${invalidHash}`)
|
|
70
91
|
expect(res.statusCode).toBe(400)
|
|
71
92
|
expect(res.body).toEqual({ error: "Invalid transaction hash format" })
|
|
72
93
|
})
|
|
73
94
|
|
|
74
|
-
it
|
|
95
|
+
it("returns 400 for hash with 0x prefix", async () => {
|
|
75
96
|
const hash = "0x" + "0".repeat(62)
|
|
76
97
|
const res = await request(context.server).get(`/v1/tx/${hash}`)
|
|
77
98
|
expect(res.statusCode).toBe(400)
|
|
78
99
|
expect(res.body).toEqual({ error: "Invalid transaction hash format" })
|
|
79
100
|
})
|
|
80
101
|
|
|
81
|
-
it
|
|
102
|
+
it("returns 400 for odd-length hex", async () => {
|
|
82
103
|
const hash = "a".repeat(63)
|
|
83
104
|
const res = await request(context.server).get(`/v1/tx/${hash}`)
|
|
84
105
|
expect(res.statusCode).toBe(400)
|
|
85
106
|
expect(res.body).toEqual({ error: "Invalid transaction hash format" })
|
|
86
107
|
})
|
|
87
108
|
|
|
88
|
-
it.skip("accepts uppercase hex", async () => {
|
|
89
|
-
const hash = "A".repeat(64)
|
|
90
|
-
const res = await request(context.server).get(`/v1/tx/${hash}`)
|
|
91
|
-
expect(res.statusCode).toBe(200)
|
|
92
|
-
})
|
|
93
109
|
|
|
94
|
-
it
|
|
95
|
-
const
|
|
110
|
+
it("accepts uppercase hex", async () => {
|
|
111
|
+
const { payload, txHashHex } = await buildRpcSelfTransferPayload(context, context.rpcMsb.state, 1n);
|
|
112
|
+
|
|
113
|
+
// Send the transaction
|
|
114
|
+
await request(context.server)
|
|
115
|
+
.post("/v1/broadcast-transaction")
|
|
116
|
+
.send(JSON.stringify({ payload }));
|
|
117
|
+
|
|
118
|
+
// Waits for the node indexer to process
|
|
119
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
120
|
+
|
|
121
|
+
const uppercaseHash = txHashHex.toUpperCase();
|
|
122
|
+
const res = await request(context.server).get(`/v1/tx/${uppercaseHash}`);
|
|
123
|
+
|
|
124
|
+
expect(res.statusCode).toBe(200);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("returns 400 for trailing space hash", async () => {
|
|
128
|
+
const hash = "a".repeat(64) + "%20" // Forcing space
|
|
96
129
|
const res = await request(context.server).get(`/v1/tx/${hash}`)
|
|
97
130
|
expect(res.statusCode).toBe(400)
|
|
98
131
|
})
|
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
import request from "supertest"
|
|
2
2
|
import { buildRpcSelfTransferPayload, waitForConnection } from "../../../helpers/transactionPayloads.mjs"
|
|
3
|
+
import { sleep } from "../../../../src/utils/helpers.js"
|
|
4
|
+
|
|
5
|
+
const TX_DETAILS_TIMEOUT_MS = 4000
|
|
6
|
+
const TX_DETAILS_RETRY_INTERVAL_MS = 100
|
|
7
|
+
|
|
8
|
+
const waitForStatusCode = async (requestFactory, expectedStatusCode, timeoutMs = TX_DETAILS_TIMEOUT_MS) => {
|
|
9
|
+
const startedAt = Date.now()
|
|
10
|
+
let response = null
|
|
11
|
+
|
|
12
|
+
while ((Date.now() - startedAt) < timeoutMs) {
|
|
13
|
+
response = await requestFactory()
|
|
14
|
+
if (response.statusCode === expectedStatusCode) {
|
|
15
|
+
return response
|
|
16
|
+
}
|
|
17
|
+
await sleep(TX_DETAILS_RETRY_INTERVAL_MS)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return response
|
|
21
|
+
}
|
|
3
22
|
|
|
4
23
|
export const registerTxDetailsTests = (context) => {
|
|
5
24
|
describe("GET /v1/tx/details", () => {
|
|
@@ -17,8 +36,11 @@ export const registerTxDetailsTests = (context) => {
|
|
|
17
36
|
.send(JSON.stringify({ payload }))
|
|
18
37
|
expect(broadcastRes.statusCode).toBe(200)
|
|
19
38
|
|
|
20
|
-
const resConfirmed = await
|
|
21
|
-
|
|
39
|
+
const resConfirmed = await waitForStatusCode(
|
|
40
|
+
() => request(context.server)
|
|
41
|
+
.get(`/v1/tx/details/${txHashHex}?confirmed=true`),
|
|
42
|
+
200
|
|
43
|
+
)
|
|
22
44
|
expect(resConfirmed.statusCode).toBe(200)
|
|
23
45
|
|
|
24
46
|
expect(resConfirmed.body).toMatchObject({
|
|
@@ -27,8 +49,11 @@ export const registerTxDetailsTests = (context) => {
|
|
|
27
49
|
fee: expect.any(String)
|
|
28
50
|
})
|
|
29
51
|
|
|
30
|
-
const resUnconfirmed = await
|
|
31
|
-
|
|
52
|
+
const resUnconfirmed = await waitForStatusCode(
|
|
53
|
+
() => request(context.server)
|
|
54
|
+
.get(`/v1/tx/details/${txHashHex}?confirmed=false`),
|
|
55
|
+
200
|
|
56
|
+
)
|
|
32
57
|
expect(resUnconfirmed.statusCode).toBe(200)
|
|
33
58
|
|
|
34
59
|
expect(resUnconfirmed.body).toMatchObject({
|
|
@@ -57,8 +82,11 @@ export const registerTxDetailsTests = (context) => {
|
|
|
57
82
|
.send(JSON.stringify({ payload }))
|
|
58
83
|
expect(broadcastRes.statusCode).toBe(200)
|
|
59
84
|
|
|
60
|
-
const res = await
|
|
61
|
-
|
|
85
|
+
const res = await waitForStatusCode(
|
|
86
|
+
() => request(context.server)
|
|
87
|
+
.get(`/v1/tx/details/${txHashHex}?confirmed=false`),
|
|
88
|
+
200
|
|
89
|
+
)
|
|
62
90
|
expect(res.statusCode).toBe(200)
|
|
63
91
|
|
|
64
92
|
expect(res.body).toMatchObject({
|
|
@@ -182,18 +210,32 @@ export const registerTxDetailsTests = (context) => {
|
|
|
182
210
|
expect(res.body).toEqual({ error: "Invalid transaction hash format" })
|
|
183
211
|
})
|
|
184
212
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
})
|
|
213
|
+
it("accepts uppercase hex", async () => {
|
|
214
|
+
const { payload, txHashHex } = await buildRpcSelfTransferPayload(
|
|
215
|
+
context,
|
|
216
|
+
context.rpcMsb.state,
|
|
217
|
+
1n
|
|
218
|
+
);
|
|
192
219
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
220
|
+
await waitForConnection(context.rpcMsb);
|
|
221
|
+
await request(context.server)
|
|
222
|
+
.post("/v1/broadcast-transaction")
|
|
223
|
+
.send(JSON.stringify({ payload }));
|
|
224
|
+
|
|
225
|
+
const upperHash = txHashHex.toUpperCase();
|
|
226
|
+
const res = await request(context.server)
|
|
227
|
+
.get(`/v1/tx/details/${upperHash}?confirmed=false`);
|
|
228
|
+
|
|
229
|
+
expect(res.statusCode).toBe(200);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it("returns 400 for trailing space hash", async () => {
|
|
233
|
+
const hash = "a".repeat(64) + " ";
|
|
234
|
+
const res = await request(context.server)
|
|
235
|
+
.get(`/v1/tx/details/${encodeURIComponent(hash)}`);
|
|
236
|
+
|
|
237
|
+
expect(res.statusCode).toBe(400);
|
|
238
|
+
expect(res.body).toEqual({ error: "Invalid transaction hash format" });
|
|
239
|
+
});
|
|
198
240
|
})
|
|
199
241
|
}
|