Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add node AccountId to timeout/max attempt errors #2631

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
3e92d6d
feat: Added NodeInfoError class and refactored a bit Executable max a…
ivaylogarnev-limechain Nov 1, 2024
f582f65
Merge branch 'main' into feat/consensus-node-id-on-error
ivaylogarnev-limechain Nov 1, 2024
a0d9b98
refactor: Added console log of the node ip in the NodeChanel for demo…
ivaylogarnev-limechain Nov 1, 2024
cf1107a
refactor: Added mapper for the node ips in NodeChannel
ivaylogarnev-limechain Nov 4, 2024
66a79cd
refactor: Commented out allNetoworksIp array map
ivaylogarnev-limechain Nov 4, 2024
5522653
tests: Added unit tests for NodeInfoError
ivaylogarnev-limechain Nov 5, 2024
edb816b
fix: Adjsuted units tests for NodeInfoError, export LocalProvider
ivaylogarnev-limechain Nov 5, 2024
f2e6471
fix: Changed the file destination of LocalProvider export
ivaylogarnev-limechain Nov 5, 2024
4c94fa4
fix: Moved NodeInfoError related tests from executable to the NodeInf…
ivaylogarnev-limechain Nov 7, 2024
5b8e684
refactor: Fixed naming comments, refactored NodeChannel grpc error logic
ivaylogarnev-limechain Nov 12, 2024
3dd8c01
refactor: Added the node id error on WebChannel
ivaylogarnev-limechain Nov 14, 2024
a593751
fix: Added JSDOC to the ClientConstants and changed the MaxAttemptsOr…
ivaylogarnev-limechain Nov 14, 2024
b8df0d6
Merge branch 'main' into feat/consensus-node-id-on-error
ivaylogarnev-limechain Nov 14, 2024
b46ff33
fix: Added additional check for the nodeAccoundId error in Executable.js
ivaylogarnev-limechain Nov 15, 2024
7e1a6fd
Merge branch 'feat/consensus-node-id-on-error' of github.com:hashgrap…
ivaylogarnev-limechain Nov 15, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@
if (grpcStatus != null && grpcMessage != null) {
const error = new GrpcServiceError(
GrpcStatus._fromValue(parseInt(grpcStatus)),
ALL_WEB_NETWORK_NODES[this._address].toString(),

Check warning on line 87 in src/channel/WebChannel.js

View check run for this annotation

Codecov / codecov/patch

src/channel/WebChannel.js#L87

Added line #L87 was not covered by tests
);
error.message = grpcMessage;
callback(error, null);
Expand All @@ -96,6 +98,7 @@
const err = new GrpcServiceError(
// retry on grpc web errors
GrpcStatus._fromValue(18),
ALL_WEB_NETWORK_NODES[this._address].toString(),

Check warning on line 101 in src/channel/WebChannel.js

View check run for this annotation

Codecov / codecov/patch

src/channel/WebChannel.js#L101

Added line #L101 was not covered by tests
);
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");
}
});
});
});