Skip to content

Commit

Permalink
feat: Add node AccountId to timeout/max attempt errors (#2631)
Browse files Browse the repository at this point in the history
* feat: Added NodeInfoError class and refactored a bit Executable max attemts/timeout errors, unit tests added

Signed-off-by: ivaylogarnev-limechain <[email protected]>

* refactor: Added console log of the node ip in the NodeChanel for demo purposes

Signed-off-by: ivaylogarnev-limechain <[email protected]>

* refactor: Added mapper for the node ips in NodeChannel

Signed-off-by: ivaylogarnev-limechain <[email protected]>

* refactor: Commented out allNetoworksIp array map

Signed-off-by: ivaylogarnev-limechain <[email protected]>

* tests: Added unit tests for NodeInfoError

Signed-off-by: ivaylogarnev-limechain <[email protected]>

* fix: Adjsuted units tests for NodeInfoError, export LocalProvider

Signed-off-by: ivaylogarnev-limechain <[email protected]>

* fix: Changed the file destination of LocalProvider export

Signed-off-by: ivaylogarnev-limechain <[email protected]>

* fix: Moved NodeInfoError related tests from executable to the NodeInfo file

Signed-off-by: ivaylogarnev-limechain <[email protected]>

* refactor: Fixed naming comments, refactored NodeChannel grpc error logic

Signed-off-by: ivaylogarnev-limechain <[email protected]>

* refactor: Added the node id error on WebChannel

Signed-off-by: ivaylogarnev-limechain <[email protected]>

* fix: Added JSDOC to the ClientConstants and changed the MaxAttemptsOrTimeoutError nodeId to string

Signed-off-by: ivaylogarnev-limechain <[email protected]>

* fix: Added additional check for the nodeAccoundId error in Executable.js

Signed-off-by: ivaylogarnev-limechain <[email protected]>

---------

Signed-off-by: ivaylogarnev-limechain <[email protected]>
  • Loading branch information
ivaylogarnev-limechain authored Nov 18, 2024
1 parent 7034135 commit 16a2911
Show file tree
Hide file tree
Showing 9 changed files with 301 additions and 5 deletions.
12 changes: 10 additions & 2 deletions src/Executable.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import List from "./transaction/List.js";
import * as hex from "./encoding/hex.js";
import HttpError from "./http/HttpError.js";
import Status from "./Status.js";
import MaxAttemptsOrTimeoutError from "./MaxAttemptsOrTimeoutError.js";

/**
* @typedef {import("./account/AccountId.js").default} AccountId
Expand Down Expand Up @@ -568,7 +569,12 @@ export default class Executable {
this._requestTimeout != null &&
startTime + this._requestTimeout <= Date.now()
) {
throw new Error("timeout exceeded");
throw new MaxAttemptsOrTimeoutError(
`timeout exceeded`,
this._nodeAccountIds.isEmpty
? "No node account ID set"
: this._nodeAccountIds.current.toString(),
);
}

let nodeAccountId;
Expand Down Expand Up @@ -741,10 +747,12 @@ export default class Executable {

// We'll only get here if we've run out of attempts, so we return an error wrapping the
// persistent error we saved before.
throw new Error(

throw new MaxAttemptsOrTimeoutError(
`max attempts of ${maxAttempts.toString()} was reached for request with last error being: ${
persistentError != null ? persistentError.toString() : ""
}`,
this._nodeAccountIds.current.toString(),
);
}

Expand Down
61 changes: 61 additions & 0 deletions src/MaxAttemptsOrTimeoutError.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*-
* ‌
* Hedera JavaScript SDK
* ​
* Copyright (C) 2020 - 2024 Hedera Hashgraph, LLC
* ​
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ‍
*/

/**
* @typedef {object} MaxAttemptsOrTimeoutErrorJSON
* @property {string} message
* @property {string} nodeAccountId
*
*/

export default class MaxAttemptsOrTimeoutError extends Error {
/**
* @param {string} message
* @param {string} nodeAccountId
*/
constructor(message, nodeAccountId) {
// Call the Error constructor with the message
super(message);

// Assign the nodeAccountId as a custom property
this.nodeAccountId = nodeAccountId;
}

toJSON() {
return {
message: this.message,
nodeAccountId: this.nodeAccountId,
};
}

/**
* @returns {string}
*/
toString() {
return JSON.stringify(this.toJSON());
}

/**
* @returns {MaxAttemptsOrTimeoutErrorJSON}
*/
valueOf() {
return this.toJSON();
}
}
10 changes: 9 additions & 1 deletion src/channel/NodeChannel.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { Client, credentials } from "@grpc/grpc-js";
import Channel from "./Channel.js";
import GrpcServicesError from "../grpc/GrpcServiceError.js";
import GrpcStatus from "../grpc/GrpcStatus.js";
import { ALL_NETWORK_IPS } from "../constants/ClientConstants.js";

/**
* @property {?HashgraphProto.proto.CryptoService} _crypto
Expand Down Expand Up @@ -102,7 +103,14 @@ export default class NodeChannel extends Channel {

this._client.waitForReady(deadline, (err) => {
if (err) {
callback(new GrpcServicesError(GrpcStatus.Timeout));
callback(
new GrpcServicesError(
GrpcStatus.Timeout,
ALL_NETWORK_IPS[
this._client.getChannel().getChannelzRef().name
],
),
);
} else {
this._client.makeUnaryRequest(
`/proto.${serviceName}/${method.name}`,
Expand Down
3 changes: 3 additions & 0 deletions src/channel/WebChannel.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
* ‍
*/

import { ALL_WEB_NETWORK_NODES } from "../constants/ClientConstants.js";
import GrpcServiceError from "../grpc/GrpcServiceError.js";
import GrpcStatus from "../grpc/GrpcStatus.js";
import HttpError from "../http/HttpError.js";
Expand Down Expand Up @@ -83,6 +84,7 @@ export default class WebChannel extends Channel {
if (grpcStatus != null && grpcMessage != null) {
const error = new GrpcServiceError(
GrpcStatus._fromValue(parseInt(grpcStatus)),
ALL_WEB_NETWORK_NODES[this._address].toString(),
);
error.message = grpcMessage;
callback(error, null);
Expand All @@ -96,6 +98,7 @@ export default class WebChannel extends Channel {
const err = new GrpcServiceError(
// retry on grpc web errors
GrpcStatus._fromValue(18),
ALL_WEB_NETWORK_NODES[this._address].toString(),
);
callback(err, null);
}
Expand Down
118 changes: 118 additions & 0 deletions src/constants/ClientConstants.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,121 @@ export const NATIVE_TESTNET = {
export const NATIVE_PREVIEWNET = {
"https://grpc-web.previewnet.myhbarwallet.com:443": new AccountId(3),
};

/**
* @type {Record<string, AccountId>}
*/
export const ALL_WEB_NETWORK_NODES = {
...MAINNET,
...WEB_TESTNET,
...WEB_PREVIEWNET,
};

/**
* @type {Record<string, string>}
*/
export const ALL_NETWORK_IPS = {
// MAINNET
"34.239.82.6:50211": "0.0.3",
"35.237.200.180:50211": "0.0.3",
"3.130.52.236:50211": "0.0.4",
"35.186.191.247:50211": "0.0.4",
"3.18.18.254:50211": "0.0.5",
"35.192.2.25:50211": "0.0.5",
"74.50.117.35:50211": "0.0.5",
"23.111.186.250:50211": "0.0.5",
"107.155.64.98:50211": "0.0.5",
"13.52.108.243:50211": "0.0.6",
"35.199.161.108:50211": "0.0.6",
"3.114.54.4:50211": "0.0.7",
"35.203.82.240:50211": "0.0.7",
"35.236.5.219:50211": "0.0.8",
"35.183.66.150:50211": "0.0.8",
"35.181.158.250:50211": "0.0.9",
"35.197.192.225:50211": "0.0.9",
"177.154.62.234:50211": "0.0.10",
"3.248.27.48:50211": "0.0.10",
"35.242.233.154:50211": "0.0.10",
"13.53.119.185:50211": "0.0.11",
"35.240.118.96:50211": "0.0.11",
"35.204.86.32:50211": "0.0.12",
"35.177.162.180:50211": "0.0.12",
"34.215.192.104:50211": "0.0.13",
"35.234.132.107:50211": "0.0.13",
"52.8.21.141:50211": "0.0.14",
"35.236.2.27:50211": "0.0.14",
"35.228.11.53:50211": "0.0.15",
"3.121.238.26:50211": "0.0.15",
"34.91.181.183:50211": "0.0.16",
"18.157.223.230:50211": "0.0.16",
"34.86.212.247:50211": "0.0.17",
"18.232.251.19:50211": "0.0.17",
"141.94.175.187:50211": "0.0.18",
"34.89.87.138:50211": "0.0.19",
"18.168.4.59:50211": "0.0.19",
"34.82.78.255:50211": "0.0.20",
"52.39.162.216:50211": "0.0.20",
"34.76.140.109:50211": "0.0.21",
"13.36.123.209:50211": "0.0.21",
"52.78.202.34:50211": "0.0.22",
"34.64.141.166:50211": "0.0.22",
"3.18.91.176:50211": "0.0.23",
"35.232.244.145:50211": "0.0.23",
"69.167.169.208:50211": "0.0.23",
"34.89.103.38:50211": "0.0.24",
"18.135.7.211:50211": "0.0.24",
"34.93.112.7:50211": "0.0.25",
"13.232.240.207:50211": "0.0.25",
"13.228.103.14:50211": "0.0.26",
"34.87.150.174:50211": "0.0.26",
"13.56.4.96:50211": "0.0.27",
"34.125.200.96:50211": "0.0.27",
"35.198.220.75:50211": "0.0.28",
"18.139.47.5:50211": "0.0.28",
"54.74.60.120:50211": "0.0.29",
"34.142.71.129:50211": "0.0.29",
"80.85.70.197:50211": "0.0.29",
"35.234.249.150:50211": "0.0.30",
"34.201.177.212:50211": "0.0.30",
"217.76.57.165:50211": "0.0.31",
"3.77.94.254:50211": "0.0.31",
"34.107.78.179:50211": "0.0.31",
"34.86.186.151:50211": "0.0.32",
"3.20.81.230:50211": "0.0.32",
"18.136.65.22:50211": "0.0.33",
"34.142.172.228:50211": "0.0.33",
"34.16.139.248:50211": "0.0.34",
"35.155.212.90:50211": "0.0.34",
// TESTNET
"34.94.106.61:50211": "0.0.3",
"50.18.132.211:50211": "0.0.3",
"3.212.6.13:50211": "0.0.4",
"35.237.119.55:50211": "0.0.4",
"35.245.27.193:50211": "0.0.5",
"52.20.18.86:50211": "0.0.5",
"34.83.112.116:50211": "0.0.6",
"54.70.192.33:50211": "0.0.6",
"34.94.160.4:50211": "0.0.7",
"54.176.199.109:50211": "0.0.7",
"35.155.49.147:50211": "0.0.8",
"34.106.102.218:50211": "0.0.8",
"34.133.197.230:50211": "0.0.9",
"52.14.252.207:50211": "0.0.9",
// LOCAL NODE
"127.0.0.1:50211": "0.0.3",
// PREVIEW NET
"3.211.248.172:50211": "0.0.3",
"35.231.208.148:50211": "0.0.3",
"35.199.15.177:50211": "0.0.4",
"3.133.213.146:50211": "0.0.4",
"35.225.201.195:50211": "0.0.5",
"52.15.105.130:50211": "0.0.5",
"54.241.38.1:50211": "0.0.6",
"35.247.109.135:50211": "0.0.6",
"54.177.51.127:50211": "0.0.7",
"35.235.65.51:50211": "0.0.7",
"34.106.247.65:50211": "0.0.8",
"35.83.89.171:50211": "0.0.8",
"50.18.17.93:50211": "0.0.9",
"34.125.23.49:50211": "0.0.9",
};
1 change: 1 addition & 0 deletions src/exports.js
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ export { default as FreezeType } from "./FreezeType.js";
export { default as TokenKeyValidation } from "./token/TokenKeyValidation.js";

export { default as StatusError } from "./StatusError.js";
export { default as MaxAttemptsOrTimeoutError } from "./MaxAttemptsOrTimeoutError.js";
export { default as PrecheckStatusError } from "./PrecheckStatusError.js";
export { default as ReceiptStatusError } from "./ReceiptStatusError.js";
export { default as LedgerId } from "./LedgerId.js";
Expand Down
8 changes: 7 additions & 1 deletion src/grpc/GrpcServiceError.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,9 @@ import GrpcStatus from "./GrpcStatus.js";
export default class GrpcServiceError extends Error {
/**
* @param {GrpcStatus} status
* @param {string} [nodeAccountId]
*/
constructor(status) {
constructor(status, nodeAccountId) {
super(
`gRPC service failed with: Status: ${status.toString()}, Code: ${status.valueOf()}`,
);
Expand All @@ -42,6 +43,11 @@ export default class GrpcServiceError extends Error {
*/
this.status = status;

/**
* Optional: node account ID associated with the error
*/
this.nodeAccountId = nodeAccountId;

this.name = "GrpcServiceError";

if (typeof Error.captureStackTrace !== "undefined") {
Expand Down
2 changes: 1 addition & 1 deletion test/unit/Executable.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ describe("Executable", function () {
),
).to.be.true;
});
});
});
91 changes: 91 additions & 0 deletions test/unit/MaxAttemptsOrTimeoutError.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import {
AccountId,
TransferTransaction,
Hbar,
MaxAttemptsOrTimeoutError,
} from "../../src/index.js";
import Mocker from "./Mocker.js";

describe("MaxAttemptsOrTimeoutError", function () {
let message;
let nodeAccountId;
let error;

beforeEach(function () {
message = "Test error message";
nodeAccountId = "0.0.3";

error = new MaxAttemptsOrTimeoutError(message, nodeAccountId);
});

it("should create an instance with correct properties", () => {
expect(error).to.be.instanceOf(MaxAttemptsOrTimeoutError);
expect(error.message).to.be.equal(message);
expect(error.nodeAccountId).to.be.equal(nodeAccountId);
});

it("toJSON should return correct JSON representation", () => {
const expectedJson = {
message,
nodeAccountId,
};

expect(error.toJSON()).to.be.deep.equal(expectedJson);
});

it("toString should return a JSON string", () => {
const expectedString = JSON.stringify({
message,
nodeAccountId,
});

expect(error.toString()).to.be.equal(expectedString);
});

it("valueOf should return the same result as toJSON", () => {
expect(error.valueOf()).to.be.deep.equal(error.toJSON());
});

describe("Transaction execution errors", function () {
let client, transaction;

beforeEach(async function () {
const setup = await Mocker.withResponses([]);
client = setup.client;
transaction = new TransferTransaction()
.addHbarTransfer("0.0.2", new Hbar(1))
.setNodeAccountIds([new AccountId(5)]);
});

it("should throw a timeout error when the timeout exceeds", async function () {
// Set the client's request timeout to 0 for testing
client.setRequestTimeout(0);
transaction = transaction.freezeWith(client);

try {
await transaction.execute(client);
throw new Error("Expected request to time out but it didn't.");
} catch (error) {
expect(error.message).to.include("timeout exceeded");
expect(error.nodeAccountId).to.equal("0.0.5");
}
});

it("should throw a max attempts error when max attempts is reached", async function () {
// Set the transaction's max attempts to 0 for testing
transaction = transaction.setMaxAttempts(0).freezeWith(client);

try {
await transaction.execute(client);
throw new Error(
"Expected request to fail due to max attempts being reached.",
);
} catch (error) {
expect(error.message).to.include(
"max attempts of 0 was reached for request with last error being:",
);
expect(error.nodeAccountId).to.equal("0.0.5");
}
});
});
});

0 comments on commit 16a2911

Please sign in to comment.