From f5f9617a39f94ef70b92032d591063d03fc1c906 Mon Sep 17 00:00:00 2001 From: ochikov Date: Wed, 11 Jan 2023 12:37:33 +0200 Subject: [PATCH 1/7] web proxy issues tracking Signed-off-by: ochikov --- src/Executable.js | 7 +++++-- src/channel/WebChannel.js | 12 +++++++++++- src/client/ManagedNetwork.js | 9 +++++++-- src/client/NativeClient.js | 1 + 4 files changed, 24 insertions(+), 5 deletions(-) diff --git a/src/Executable.js b/src/Executable.js index d8bd0148f..75e259eb5 100644 --- a/src/Executable.js +++ b/src/Executable.js @@ -444,6 +444,7 @@ export default class Executable { * @returns {boolean} */ _shouldRetryExceptionally(error) { + console.log(error.status._code); return ( error.status._code === GrpcStatus.Unavailable._code || error.status._code === GrpcStatus.ResourceExhausted._code || @@ -534,6 +535,7 @@ export default class Executable { // The retry loop for (let attempt = 1; attempt <= maxAttempts; attempt += 1) { + console.log("ATTEMPT: ", attempt); // Determine if we've exceeded request timeout if ( this._requestTimeout != null && @@ -562,7 +564,7 @@ export default class Executable { `NodeAccountId not recognized: ${nodeAccountId.toString()}` ); } - + console.log("The node used is:", node); // Get the log ID for the request. const logId = this._getLogId(); Logger.debug( @@ -639,6 +641,7 @@ export default class Executable { this._shouldRetryExceptionally(error) && attempt <= maxAttempts ) { + console.log("INCREASE BACKOFF and remove the node"); // Increase the backoff for the particular node and remove it from // the healthy node list client._network.increaseBackoff(node); @@ -666,7 +669,7 @@ export default class Executable { if (err != null) { persistentError = err; } - + console.log("shouldRetry", shouldRetry); // Determine by the executing state what we should do switch (shouldRetry) { case ExecutionState.Retry: diff --git a/src/channel/WebChannel.js b/src/channel/WebChannel.js index d4636f477..f0caeb4ae 100644 --- a/src/channel/WebChannel.js +++ b/src/channel/WebChannel.js @@ -70,8 +70,18 @@ export default class WebChannel extends Channel { // Check headers for gRPC errors const grpcStatus = response.headers.get("grpc-status"); const grpcMessage = response.headers.get("grpc-message"); - + // console.log("RESPONCE1"); + // console.log(grpcStatus); + // console.log(grpcMessage); + if (!response.ok) { + const error = new GrpcServiceError( + GrpcStatus._fromValue(14) + ); + error.message = `Error ${response.status}`; + callback(error, null); + } if (grpcStatus != null && grpcMessage != null) { + console.log("grpcStatus", grpcStatus); const error = new GrpcServiceError( GrpcStatus._fromValue(parseInt(grpcStatus)) ); diff --git a/src/client/ManagedNetwork.js b/src/client/ManagedNetwork.js index 84b3e1267..bd0af6d3f 100644 --- a/src/client/ManagedNetwork.js +++ b/src/client/ManagedNetwork.js @@ -484,6 +484,8 @@ export default class ManagedNetwork { * @returns {NetworkNodeT} */ getNode(key) { + console.log("GET NODE and Key", key); + console.log("BEFORE healthy nodes", this._healthyNodes); this._readmitNodes(); if (key != null) { @@ -492,9 +494,10 @@ export default class ManagedNetwork { )[0]; } else { if (this._healthyNodes.length == 0) { + console.log("failed to find a healthy working node"); throw new Error("failed to find a healthy working node"); } - + console.log("AFTER healthy nodes", this._healthyNodes); return this._healthyNodes[ Math.floor(Math.random() * this._healthyNodes.length) ]; @@ -506,9 +509,11 @@ export default class ManagedNetwork { */ increaseBackoff(node) { node.increaseBackoff(); - + console.log(this._healthyNodes); + console.log(this._healthyNodes.length); for (let i = 0; i < this._healthyNodes.length; i++) { if (this._healthyNodes[i] == node) { + console.log("SPLICE IT", node); this._healthyNodes.splice(i, 1); } } diff --git a/src/client/NativeClient.js b/src/client/NativeClient.js index 06fe8168a..f1002a473 100644 --- a/src/client/NativeClient.js +++ b/src/client/NativeClient.js @@ -50,6 +50,7 @@ export const Network = { MAINNET: { "https://grpc-web.myhbarwallet.com:443": new AccountId(3), + "https://node01-00-grpc.swirlds.com:443": new AccountId(4), }, TESTNET: { From 271efb6dea261916b13f1b1ecaf17098a084e571 Mon Sep 17 00:00:00 2001 From: Petar Tonev Date: Wed, 11 Jan 2023 17:49:47 +0200 Subject: [PATCH 2/7] added nodeAccountIds unlocking Signed-off-by: Petar Tonev --- src/Executable.js | 13 ++++++++++++- src/channel/WebChannel.js | 1 + src/client/ManagedNetwork.js | 8 ++++---- src/transaction/List.js | 10 ++++++++++ src/transaction/Transaction.js | 2 +- 5 files changed, 28 insertions(+), 6 deletions(-) diff --git a/src/Executable.js b/src/Executable.js index 75e259eb5..072aedef8 100644 --- a/src/Executable.js +++ b/src/Executable.js @@ -133,6 +133,8 @@ export default class Executable { * @returns {?AccountId[]} */ get nodeAccountIds() { + console.log(`in nodeAccountIds: ${this._nodeAccountIds.locked}`) + console.log(`in nodeAccountIds: ${this._nodeAccountIds.list}`) if (this._nodeAccountIds.isEmpty) { return null; } else { @@ -505,7 +507,7 @@ export default class Executable { // Some request need to perform additional requests before the executing // such as paid queries need to fetch the cost of the query before // finally executing the actual query. - await this._beforeExecute(client); + //await this._beforeExecute(client); // If the max backoff on the request is not set, use the default value in client if (this._maxBackoff == null) { @@ -536,6 +538,7 @@ export default class Executable { // The retry loop for (let attempt = 1; attempt <= maxAttempts; attempt += 1) { console.log("ATTEMPT: ", attempt); + await this._beforeExecute(client); // Determine if we've exceeded request timeout if ( this._requestTimeout != null && @@ -547,6 +550,9 @@ export default class Executable { let nodeAccountId; let node; + console.log(`Executable LOCKED: ${this._nodeAccountIds.locked}`) + console.log(`Executable CURRENT: ${this._nodeAccountIds.current}`) + console.log(`Executable CURRENT: ${JSON.stringify(this._nodeAccountIds)}`) // If node account IDs is locked then use the node account IDs // from the list, otherwise build a new list of one node account ID // using the entire network @@ -558,6 +564,9 @@ export default class Executable { nodeAccountId = node.accountId; this._nodeAccountIds.setList([nodeAccountId]); } + console.log(`TOOK NODE`); + console.log(`Executable NODE: ${JSON.stringify(node)}`) + if (node == null) { throw new Error( @@ -645,6 +654,8 @@ export default class Executable { // Increase the backoff for the particular node and remove it from // the healthy node list client._network.increaseBackoff(node); + this._nodeAccountIds.setUnlocked(); + this._nodeAccountIds.clear(); continue; } diff --git a/src/channel/WebChannel.js b/src/channel/WebChannel.js index f0caeb4ae..2ebe79f11 100644 --- a/src/channel/WebChannel.js +++ b/src/channel/WebChannel.js @@ -74,6 +74,7 @@ export default class WebChannel extends Channel { // console.log(grpcStatus); // console.log(grpcMessage); if (!response.ok) { + console.log(`HTTP error: ${response.status}`); const error = new GrpcServiceError( GrpcStatus._fromValue(14) ); diff --git a/src/client/ManagedNetwork.js b/src/client/ManagedNetwork.js index bd0af6d3f..cbce397cf 100644 --- a/src/client/ManagedNetwork.js +++ b/src/client/ManagedNetwork.js @@ -484,11 +484,11 @@ export default class ManagedNetwork { * @returns {NetworkNodeT} */ getNode(key) { - console.log("GET NODE and Key", key); - console.log("BEFORE healthy nodes", this._healthyNodes); + console.log("ManagedNetwork: key", key); + console.log("ManagedNetwork healthy node 1:", this._healthyNodes[0]); + console.log("ManagedNetwork healthy node 2:", this._healthyNodes[1]); this._readmitNodes(); - - if (key != null) { + if (key != null && key != undefined) { return /** @type {NetworkNodeT[]} */ ( this._network.get(key.toString()) )[0]; diff --git a/src/transaction/List.js b/src/transaction/List.js index 5399fad6f..4ba90b459 100644 --- a/src/transaction/List.js +++ b/src/transaction/List.js @@ -75,6 +75,16 @@ export default class List { this.locked = true; return this; } + + /** + * Unlocks the list. + * + * @returns {this} + */ + setUnlocked() { + this.locked = false; + return this; + } /** * Clear the list diff --git a/src/transaction/Transaction.js b/src/transaction/Transaction.js index 8e352466e..9a667d721 100644 --- a/src/transaction/Transaction.js +++ b/src/transaction/Transaction.js @@ -1369,7 +1369,7 @@ export default class Transaction extends Executable { Logger.debug( `[${this._getLogId()}] received status ${status.toString()}` ); - + console.log(`received status ${status.toString()}`); // Based on the status what execution state are we in switch (status) { case Status.Busy: From 71bc795619c295196f43fa266cfda7791b746248 Mon Sep 17 00:00:00 2001 From: Petar Tonev Date: Fri, 13 Jan 2023 12:09:10 +0200 Subject: [PATCH 3/7] unhealthy nodes now are handled correctly Signed-off-by: Petar Tonev --- src/Executable.js | 49 ++++++++++++----------------- src/channel/NativeChannel.js | 7 +++++ src/channel/WebChannel.js | 20 +++++------- src/client/ManagedNetwork.js | 21 +++++-------- src/client/Network.js | 6 ++-- src/grpc/GrpcServiceError.js | 15 --------- src/http/HttpError.js | 44 ++++++++++++++++++++++++++ src/http/HttpStatus.js | 56 ++++++++++++++++++++++++++++++++++ src/transaction/List.js | 10 ------ src/transaction/Transaction.js | 2 +- 10 files changed, 148 insertions(+), 82 deletions(-) create mode 100644 src/http/HttpError.js create mode 100644 src/http/HttpStatus.js diff --git a/src/Executable.js b/src/Executable.js index 072aedef8..1f684951d 100644 --- a/src/Executable.js +++ b/src/Executable.js @@ -23,6 +23,7 @@ import GrpcStatus from "./grpc/GrpcStatus.js"; import List from "./transaction/List.js"; import Logger from "js-logger"; import * as hex from "./encoding/hex.js"; +import HttpError from "./http/HttpError.js"; /** * @typedef {import("./account/AccountId.js").default} AccountId @@ -133,8 +134,6 @@ export default class Executable { * @returns {?AccountId[]} */ get nodeAccountIds() { - console.log(`in nodeAccountIds: ${this._nodeAccountIds.locked}`) - console.log(`in nodeAccountIds: ${this._nodeAccountIds.list}`) if (this._nodeAccountIds.isEmpty) { return null; } else { @@ -442,17 +441,22 @@ export default class Executable { * Unlike `shouldRetry` this method does in fact still return a boolean * * @protected - * @param {GrpcServiceError} error + * @param {Error} error * @returns {boolean} */ _shouldRetryExceptionally(error) { - console.log(error.status._code); - return ( - error.status._code === GrpcStatus.Unavailable._code || - error.status._code === GrpcStatus.ResourceExhausted._code || - (error.status._code === GrpcStatus.Internal._code && - RST_STREAM.test(error.message)) - ); + if (error instanceof GrpcServiceError) { + return ( + error.status._code === GrpcStatus.Unavailable._code || + error.status._code === GrpcStatus.ResourceExhausted._code || + (error.status._code === GrpcStatus.Internal._code && + RST_STREAM.test(error.message)) + ); + } else { + // if we get to the 'else' statement, the 'error' is instanceof 'HttpError' + // and in this case, we have to retry always + return true; + } } /** @@ -507,7 +511,7 @@ export default class Executable { // Some request need to perform additional requests before the executing // such as paid queries need to fetch the cost of the query before // finally executing the actual query. - //await this._beforeExecute(client); + await this._beforeExecute(client); // If the max backoff on the request is not set, use the default value in client if (this._maxBackoff == null) { @@ -537,8 +541,6 @@ export default class Executable { // The retry loop for (let attempt = 1; attempt <= maxAttempts; attempt += 1) { - console.log("ATTEMPT: ", attempt); - await this._beforeExecute(client); // Determine if we've exceeded request timeout if ( this._requestTimeout != null && @@ -550,9 +552,6 @@ export default class Executable { let nodeAccountId; let node; - console.log(`Executable LOCKED: ${this._nodeAccountIds.locked}`) - console.log(`Executable CURRENT: ${this._nodeAccountIds.current}`) - console.log(`Executable CURRENT: ${JSON.stringify(this._nodeAccountIds)}`) // If node account IDs is locked then use the node account IDs // from the list, otherwise build a new list of one node account ID // using the entire network @@ -564,16 +563,13 @@ export default class Executable { nodeAccountId = node.accountId; this._nodeAccountIds.setList([nodeAccountId]); } - console.log(`TOOK NODE`); - console.log(`Executable NODE: ${JSON.stringify(node)}`) - if (node == null) { throw new Error( `NodeAccountId not recognized: ${nodeAccountId.toString()}` ); } - console.log("The node used is:", node); + // Get the log ID for the request. const logId = this._getLogId(); Logger.debug( @@ -635,27 +631,22 @@ export default class Executable { } catch (err) { // If we received a grpc status error we need to determine if // we should retry on this error, or err from the request entirely. - const error = GrpcServiceError._fromResponse( - /** @type {Error} */ (err) - ); + const error = (/** @type {Error} */ (err)); // Save the error in case we retry persistentError = error; Logger.debug( - `[${logId}] received gRPC error ${JSON.stringify(error)}` + `[${logId}] received error ${JSON.stringify(error)}` ); if ( - error instanceof GrpcServiceError && + (error instanceof GrpcServiceError || error instanceof HttpError) && this._shouldRetryExceptionally(error) && attempt <= maxAttempts ) { - console.log("INCREASE BACKOFF and remove the node"); // Increase the backoff for the particular node and remove it from // the healthy node list client._network.increaseBackoff(node); - this._nodeAccountIds.setUnlocked(); - this._nodeAccountIds.clear(); continue; } @@ -680,7 +671,7 @@ export default class Executable { if (err != null) { persistentError = err; } - console.log("shouldRetry", shouldRetry); + // Determine by the executing state what we should do switch (shouldRetry) { case ExecutionState.Retry: diff --git a/src/channel/NativeChannel.js b/src/channel/NativeChannel.js index c98e85d2b..c8eaf05a7 100644 --- a/src/channel/NativeChannel.js +++ b/src/channel/NativeChannel.js @@ -20,6 +20,8 @@ import Channel, { encodeRequest, decodeUnaryResponse } from "./Channel.js"; import * as base64 from "../encoding/base64.native.js"; +import HttpError from "../http/HttpError.js"; +import HttpStatus from "../http/HttpStatus.js"; export default class NativeChannel extends Channel { /** @@ -71,6 +73,11 @@ export default class NativeChannel extends Channel { } ); + if (!response.ok) { + const error = new HttpError(HttpStatus._fromValue(response.status)); + callback(error, null); + } + const blob = await response.blob(); /** @type {string} */ diff --git a/src/channel/WebChannel.js b/src/channel/WebChannel.js index 2ebe79f11..dbbf49aed 100644 --- a/src/channel/WebChannel.js +++ b/src/channel/WebChannel.js @@ -20,6 +20,8 @@ import GrpcServiceError from "../grpc/GrpcServiceError.js"; import GrpcStatus from "../grpc/GrpcStatus.js"; +import HttpError from "../http/HttpError.js"; +import HttpStatus from "../http/HttpStatus.js"; import Channel, { encodeRequest, decodeUnaryResponse } from "./Channel.js"; export default class WebChannel extends Channel { @@ -66,23 +68,17 @@ export default class WebChannel extends Channel { body: encodeRequest(requestData), } ); + + if (!response.ok) { + const error = new HttpError(HttpStatus._fromValue(response.status)); + callback(error, null); + } // Check headers for gRPC errors const grpcStatus = response.headers.get("grpc-status"); const grpcMessage = response.headers.get("grpc-message"); - // console.log("RESPONCE1"); - // console.log(grpcStatus); - // console.log(grpcMessage); - if (!response.ok) { - console.log(`HTTP error: ${response.status}`); - const error = new GrpcServiceError( - GrpcStatus._fromValue(14) - ); - error.message = `Error ${response.status}`; - callback(error, null); - } + if (grpcStatus != null && grpcMessage != null) { - console.log("grpcStatus", grpcStatus); const error = new GrpcServiceError( GrpcStatus._fromValue(parseInt(grpcStatus)) ); diff --git a/src/client/ManagedNetwork.js b/src/client/ManagedNetwork.js index cbce397cf..b373b40f1 100644 --- a/src/client/ManagedNetwork.js +++ b/src/client/ManagedNetwork.js @@ -281,12 +281,13 @@ export default class ManagedNetwork { } // Get a random node - const node = this.getNode(); - - if (!keys.has(node.getKey())) { - keys.add(node.getKey()); - nodes.push(node); + let node = this.getNode(); + + while (keys.has(node.getKey())) { + node = this.getNode(); } + keys.add(node.getKey()); + nodes.push(node); } return nodes; @@ -484,9 +485,6 @@ export default class ManagedNetwork { * @returns {NetworkNodeT} */ getNode(key) { - console.log("ManagedNetwork: key", key); - console.log("ManagedNetwork healthy node 1:", this._healthyNodes[0]); - console.log("ManagedNetwork healthy node 2:", this._healthyNodes[1]); this._readmitNodes(); if (key != null && key != undefined) { return /** @type {NetworkNodeT[]} */ ( @@ -494,10 +492,9 @@ export default class ManagedNetwork { )[0]; } else { if (this._healthyNodes.length == 0) { - console.log("failed to find a healthy working node"); throw new Error("failed to find a healthy working node"); } - console.log("AFTER healthy nodes", this._healthyNodes); + return this._healthyNodes[ Math.floor(Math.random() * this._healthyNodes.length) ]; @@ -509,11 +506,9 @@ export default class ManagedNetwork { */ increaseBackoff(node) { node.increaseBackoff(); - console.log(this._healthyNodes); - console.log(this._healthyNodes.length); + for (let i = 0; i < this._healthyNodes.length; i++) { if (this._healthyNodes[i] == node) { - console.log("SPLICE IT", node); this._healthyNodes.splice(i, 1); } } diff --git a/src/client/Network.js b/src/client/Network.js index 03a5ab3f7..04453cce3 100644 --- a/src/client/Network.js +++ b/src/client/Network.js @@ -249,8 +249,10 @@ export default class Network extends ManagedNetwork { if (this._maxNodesPerTransaction > 0) { return this._maxNodesPerTransaction; } - - return (this._nodes.length + 3 - 1) / 3; + // ultimately it does not matter if we round up or down + // if we round up, we will eventually take one more healthy node for execution + // and we would hit the 'nodes.length == count' check in _getNumberOfMostHealthyNodes() less often + return Math.ceil((this._nodes.length + 3 - 1) / 3); } /** diff --git a/src/grpc/GrpcServiceError.js b/src/grpc/GrpcServiceError.js index 163c987fc..511fea2f0 100644 --- a/src/grpc/GrpcServiceError.js +++ b/src/grpc/GrpcServiceError.js @@ -46,19 +46,4 @@ export default class GrpcServiceError extends Error { Error.captureStackTrace(this, GrpcServiceError); } } - - /** - * @param {Error & { code?: number; details?: string }} obj - * @returns {Error} - */ - static _fromResponse(obj) { - if (obj.code != null && obj.details != null) { - const status = GrpcStatus._fromValue(obj.code); - const err = new GrpcServiceError(status); - err.message = obj.details; - return err; - } else { - return /** @type {Error} */ (obj); - } - } } diff --git a/src/http/HttpError.js b/src/http/HttpError.js new file mode 100644 index 000000000..df6c0e307 --- /dev/null +++ b/src/http/HttpError.js @@ -0,0 +1,44 @@ +/*- + * ‌ + * Hedera JavaScript SDK + * ​ + * Copyright (C) 2020 - 2022 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. + * ‍ + */ + +import HttpStatus from "./HttpStatus.js"; + +/** + * Describes how the http request failed. + */ +export default class HttpError extends Error { + /** + * @param {HttpStatus} status + */ + constructor(status) { + super(`failed with error code: ${status.toString()}`); + + /** + * @readonly + */ + this.status = status; + + this.name = "HttpError"; + + if (typeof Error.captureStackTrace !== "undefined") { + Error.captureStackTrace(this, HttpError); + } + } +} diff --git a/src/http/HttpStatus.js b/src/http/HttpStatus.js new file mode 100644 index 000000000..096851346 --- /dev/null +++ b/src/http/HttpStatus.js @@ -0,0 +1,56 @@ +/*- + * ‌ + * Hedera JavaScript SDK + * ​ + * Copyright (C) 2020 - 2022 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. + * ‍ + */ + +export default class HttpStatus { + /** + * @hideconstructor + * @internal + * @param {number} code + */ + constructor(code) { + /** @readonly */ + this._code = code; + + Object.freeze(this); + } + + /** + * @internal + * @param {number} code + * @returns {HttpStatus} + */ + static _fromValue(code) { + return new HttpStatus(code); + } + + /** + * @returns {string} + */ + toString() { + return this._code.toString(); + } + + /** + * @returns {number} + */ + valueOf() { + return this._code; + } +} \ No newline at end of file diff --git a/src/transaction/List.js b/src/transaction/List.js index 4ba90b459..5399fad6f 100644 --- a/src/transaction/List.js +++ b/src/transaction/List.js @@ -75,16 +75,6 @@ export default class List { this.locked = true; return this; } - - /** - * Unlocks the list. - * - * @returns {this} - */ - setUnlocked() { - this.locked = false; - return this; - } /** * Clear the list diff --git a/src/transaction/Transaction.js b/src/transaction/Transaction.js index 9a667d721..8e352466e 100644 --- a/src/transaction/Transaction.js +++ b/src/transaction/Transaction.js @@ -1369,7 +1369,7 @@ export default class Transaction extends Executable { Logger.debug( `[${this._getLogId()}] received status ${status.toString()}` ); - console.log(`received status ${status.toString()}`); + // Based on the status what execution state are we in switch (status) { case Status.Busy: From 69d843232b76a6dd9da3f91bc1ea1e78ffb0603d Mon Sep 17 00:00:00 2001 From: Petar Tonev Date: Fri, 13 Jan 2023 14:09:29 +0200 Subject: [PATCH 4/7] added additional proxies urls and after task build refactoring Signed-off-by: Petar Tonev --- src/Executable.js | 5 +++-- src/channel/NativeChannel.js | 4 +++- src/channel/WebChannel.js | 6 ++++-- src/client/ManagedNetwork.js | 2 +- src/client/NativeClient.js | 24 ++++++++++++++++++++++++ src/client/WebClient.js | 24 ++++++++++++++++++++++++ src/grpc/GrpcServiceError.js | 2 +- src/http/HttpError.js | 2 +- src/http/HttpStatus.js | 2 +- 9 files changed, 62 insertions(+), 9 deletions(-) diff --git a/src/Executable.js b/src/Executable.js index 1f684951d..46031a8cb 100644 --- a/src/Executable.js +++ b/src/Executable.js @@ -631,7 +631,7 @@ export default class Executable { } catch (err) { // If we received a grpc status error we need to determine if // we should retry on this error, or err from the request entirely. - const error = (/** @type {Error} */ (err)); + const error = /** @type {Error} */ (err); // Save the error in case we retry persistentError = error; @@ -640,7 +640,8 @@ export default class Executable { ); if ( - (error instanceof GrpcServiceError || error instanceof HttpError) && + (error instanceof GrpcServiceError || + error instanceof HttpError) && this._shouldRetryExceptionally(error) && attempt <= maxAttempts ) { diff --git a/src/channel/NativeChannel.js b/src/channel/NativeChannel.js index c8eaf05a7..e51000c10 100644 --- a/src/channel/NativeChannel.js +++ b/src/channel/NativeChannel.js @@ -74,7 +74,9 @@ export default class NativeChannel extends Channel { ); if (!response.ok) { - const error = new HttpError(HttpStatus._fromValue(response.status)); + const error = new HttpError( + HttpStatus._fromValue(response.status) + ); callback(error, null); } diff --git a/src/channel/WebChannel.js b/src/channel/WebChannel.js index dbbf49aed..bdde1aad3 100644 --- a/src/channel/WebChannel.js +++ b/src/channel/WebChannel.js @@ -68,9 +68,11 @@ export default class WebChannel extends Channel { body: encodeRequest(requestData), } ); - + if (!response.ok) { - const error = new HttpError(HttpStatus._fromValue(response.status)); + const error = new HttpError( + HttpStatus._fromValue(response.status) + ); callback(error, null); } diff --git a/src/client/ManagedNetwork.js b/src/client/ManagedNetwork.js index b373b40f1..4bb1c3f99 100644 --- a/src/client/ManagedNetwork.js +++ b/src/client/ManagedNetwork.js @@ -282,7 +282,7 @@ export default class ManagedNetwork { // Get a random node let node = this.getNode(); - + while (keys.has(node.getKey())) { node = this.getNode(); } diff --git a/src/client/NativeClient.js b/src/client/NativeClient.js index f1002a473..12ee1eb4c 100644 --- a/src/client/NativeClient.js +++ b/src/client/NativeClient.js @@ -51,6 +51,30 @@ export const Network = { MAINNET: { "https://grpc-web.myhbarwallet.com:443": new AccountId(3), "https://node01-00-grpc.swirlds.com:443": new AccountId(4), + "https://node02.swirldslabs.com:443": new AccountId(5), + "https://node03.swirldslabs.com:443": new AccountId(6), + "https://node04.swirldslabs.com:443": new AccountId(7), + "https://node05.swirldslabs.com:443": new AccountId(8), + "https://node06.swirldslabs.com:443": new AccountId(9), + "https://node07.swirldslabs.com:443": new AccountId(10), + "https://node08.swirldslabs.com:443": new AccountId(11), + "https://node09.swirldslabs.com:443": new AccountId(12), + "https://node10.swirldslabs.com:443": new AccountId(13), + "https://node11.swirldslabs.com:443": new AccountId(14), + "https://node12.swirldslabs.com:443": new AccountId(15), + "https://node13.swirldslabs.com:443": new AccountId(16), + "https://node14.swirldslabs.com:443": new AccountId(17), + "https://node16.swirldslabs.com:443": new AccountId(19), + "https://node17.swirldslabs.com:443": new AccountId(20), + "https://node18.swirldslabs.com:443": new AccountId(21), + "https://node19.swirldslabs.com:443": new AccountId(22), + "https://node20.swirldslabs.com:443": new AccountId(23), + "https://node21.swirldslabs.com:443": new AccountId(24), + "https://node22.swirldslabs.com:443": new AccountId(25), + "https://node23.swirldslabs.com:443": new AccountId(26), + "https://node24.swirldslabs.com:443": new AccountId(27), + "https://node25.swirldslabs.com:443": new AccountId(28), + "https://node26.swirldslabs.com:443": new AccountId(29), }, TESTNET: { diff --git a/src/client/WebClient.js b/src/client/WebClient.js index 876bc257d..72eb5f422 100644 --- a/src/client/WebClient.js +++ b/src/client/WebClient.js @@ -51,6 +51,30 @@ export const Network = { MAINNET: { "https://grpc-web.myhbarwallet.com:443": new AccountId(3), "https://node01-00-grpc.swirlds.com:443": new AccountId(4), + "https://node02.swirldslabs.com:443": new AccountId(5), + "https://node03.swirldslabs.com:443": new AccountId(6), + "https://node04.swirldslabs.com:443": new AccountId(7), + "https://node05.swirldslabs.com:443": new AccountId(8), + "https://node06.swirldslabs.com:443": new AccountId(9), + "https://node07.swirldslabs.com:443": new AccountId(10), + "https://node08.swirldslabs.com:443": new AccountId(11), + "https://node09.swirldslabs.com:443": new AccountId(12), + "https://node10.swirldslabs.com:443": new AccountId(13), + "https://node11.swirldslabs.com:443": new AccountId(14), + "https://node12.swirldslabs.com:443": new AccountId(15), + "https://node13.swirldslabs.com:443": new AccountId(16), + "https://node14.swirldslabs.com:443": new AccountId(17), + "https://node16.swirldslabs.com:443": new AccountId(19), + "https://node17.swirldslabs.com:443": new AccountId(20), + "https://node18.swirldslabs.com:443": new AccountId(21), + "https://node19.swirldslabs.com:443": new AccountId(22), + "https://node20.swirldslabs.com:443": new AccountId(23), + "https://node21.swirldslabs.com:443": new AccountId(24), + "https://node22.swirldslabs.com:443": new AccountId(25), + "https://node23.swirldslabs.com:443": new AccountId(26), + "https://node24.swirldslabs.com:443": new AccountId(27), + "https://node25.swirldslabs.com:443": new AccountId(28), + "https://node26.swirldslabs.com:443": new AccountId(29), }, TESTNET: { diff --git a/src/grpc/GrpcServiceError.js b/src/grpc/GrpcServiceError.js index 511fea2f0..9af1958ec 100644 --- a/src/grpc/GrpcServiceError.js +++ b/src/grpc/GrpcServiceError.js @@ -17,7 +17,7 @@ * limitations under the License. * ‍ */ - +// eslint-disable-next-line @typescript-eslint/no-unused-vars import GrpcStatus from "./GrpcStatus.js"; /** diff --git a/src/http/HttpError.js b/src/http/HttpError.js index df6c0e307..634f63229 100644 --- a/src/http/HttpError.js +++ b/src/http/HttpError.js @@ -17,7 +17,7 @@ * limitations under the License. * ‍ */ - +// eslint-disable-next-line @typescript-eslint/no-unused-vars import HttpStatus from "./HttpStatus.js"; /** diff --git a/src/http/HttpStatus.js b/src/http/HttpStatus.js index 096851346..8fc4d97eb 100644 --- a/src/http/HttpStatus.js +++ b/src/http/HttpStatus.js @@ -53,4 +53,4 @@ export default class HttpStatus { valueOf() { return this._code; } -} \ No newline at end of file +} From ec17ae2dc22bf22562464a715c0e0f85a13a33d9 Mon Sep 17 00:00:00 2001 From: Petar Tonev Date: Fri, 13 Jan 2023 16:25:48 +0200 Subject: [PATCH 5/7] add urls to a new client constants file Signed-off-by: Petar Tonev --- src/client/NativeClient.js | 45 +++++------------------- src/client/WebClient.js | 58 +++++-------------------------- src/constants/ClientConstants.js | 59 ++++++++++++++++++++++++++++++++ 3 files changed, 77 insertions(+), 85 deletions(-) create mode 100644 src/constants/ClientConstants.js diff --git a/src/client/NativeClient.js b/src/client/NativeClient.js index 12ee1eb4c..aec5d8974 100644 --- a/src/client/NativeClient.js +++ b/src/client/NativeClient.js @@ -20,8 +20,14 @@ import Client from "./Client.js"; import NativeChannel from "../channel/NativeChannel.js"; +// eslint-disable-next-line @typescript-eslint/no-unused-vars import AccountId from "../account/AccountId.js"; import LedgerId from "../LedgerId.js"; +import { + MAINNET, + NATIVE_TESTNET, + NATIVE_PREVIEWNET, +} from "../constants/ClientConstants.js"; /** * @typedef {import("./Client.js").ClientConfiguration} ClientConfiguration @@ -48,42 +54,9 @@ export const Network = { } }, - MAINNET: { - "https://grpc-web.myhbarwallet.com:443": new AccountId(3), - "https://node01-00-grpc.swirlds.com:443": new AccountId(4), - "https://node02.swirldslabs.com:443": new AccountId(5), - "https://node03.swirldslabs.com:443": new AccountId(6), - "https://node04.swirldslabs.com:443": new AccountId(7), - "https://node05.swirldslabs.com:443": new AccountId(8), - "https://node06.swirldslabs.com:443": new AccountId(9), - "https://node07.swirldslabs.com:443": new AccountId(10), - "https://node08.swirldslabs.com:443": new AccountId(11), - "https://node09.swirldslabs.com:443": new AccountId(12), - "https://node10.swirldslabs.com:443": new AccountId(13), - "https://node11.swirldslabs.com:443": new AccountId(14), - "https://node12.swirldslabs.com:443": new AccountId(15), - "https://node13.swirldslabs.com:443": new AccountId(16), - "https://node14.swirldslabs.com:443": new AccountId(17), - "https://node16.swirldslabs.com:443": new AccountId(19), - "https://node17.swirldslabs.com:443": new AccountId(20), - "https://node18.swirldslabs.com:443": new AccountId(21), - "https://node19.swirldslabs.com:443": new AccountId(22), - "https://node20.swirldslabs.com:443": new AccountId(23), - "https://node21.swirldslabs.com:443": new AccountId(24), - "https://node22.swirldslabs.com:443": new AccountId(25), - "https://node23.swirldslabs.com:443": new AccountId(26), - "https://node24.swirldslabs.com:443": new AccountId(27), - "https://node25.swirldslabs.com:443": new AccountId(28), - "https://node26.swirldslabs.com:443": new AccountId(29), - }, - - TESTNET: { - "https://grpc-web.testnet.myhbarwallet.com:443": new AccountId(3), - }, - - PREVIEWNET: { - "https://grpc-web.previewnet.myhbarwallet.com:443": new AccountId(3), - }, + MAINNET: MAINNET, + TESTNET: NATIVE_TESTNET, + PREVIEWNET: NATIVE_PREVIEWNET, }; /** diff --git a/src/client/WebClient.js b/src/client/WebClient.js index 72eb5f422..a9bf80098 100644 --- a/src/client/WebClient.js +++ b/src/client/WebClient.js @@ -20,8 +20,14 @@ import Client from "./Client.js"; import WebChannel from "../channel/WebChannel.js"; +// eslint-disable-next-line @typescript-eslint/no-unused-vars import AccountId from "../account/AccountId.js"; import LedgerId from "../LedgerId.js"; +import { + MAINNET, + WEB_TESTNET, + WEB_PREVIEWNET, +} from "../constants/ClientConstants.js"; /** * @typedef {import("./Client.js").ClientConfiguration} ClientConfiguration @@ -48,54 +54,9 @@ export const Network = { } }, - MAINNET: { - "https://grpc-web.myhbarwallet.com:443": new AccountId(3), - "https://node01-00-grpc.swirlds.com:443": new AccountId(4), - "https://node02.swirldslabs.com:443": new AccountId(5), - "https://node03.swirldslabs.com:443": new AccountId(6), - "https://node04.swirldslabs.com:443": new AccountId(7), - "https://node05.swirldslabs.com:443": new AccountId(8), - "https://node06.swirldslabs.com:443": new AccountId(9), - "https://node07.swirldslabs.com:443": new AccountId(10), - "https://node08.swirldslabs.com:443": new AccountId(11), - "https://node09.swirldslabs.com:443": new AccountId(12), - "https://node10.swirldslabs.com:443": new AccountId(13), - "https://node11.swirldslabs.com:443": new AccountId(14), - "https://node12.swirldslabs.com:443": new AccountId(15), - "https://node13.swirldslabs.com:443": new AccountId(16), - "https://node14.swirldslabs.com:443": new AccountId(17), - "https://node16.swirldslabs.com:443": new AccountId(19), - "https://node17.swirldslabs.com:443": new AccountId(20), - "https://node18.swirldslabs.com:443": new AccountId(21), - "https://node19.swirldslabs.com:443": new AccountId(22), - "https://node20.swirldslabs.com:443": new AccountId(23), - "https://node21.swirldslabs.com:443": new AccountId(24), - "https://node22.swirldslabs.com:443": new AccountId(25), - "https://node23.swirldslabs.com:443": new AccountId(26), - "https://node24.swirldslabs.com:443": new AccountId(27), - "https://node25.swirldslabs.com:443": new AccountId(28), - "https://node26.swirldslabs.com:443": new AccountId(29), - }, - - TESTNET: { - "https://testnet-node00-00-grpc.hedera.com:443": new AccountId(3), - "https://testnet-node01-00-grpc.hedera.com:443": new AccountId(4), - "https://testnet-node02-00-grpc.hedera.com:443": new AccountId(5), - "https://testnet-node03-00-grpc.hedera.com:443": new AccountId(6), - "https://testnet-node04-00-grpc.hedera.com:443": new AccountId(7), - "https://testnet-node05-00-grpc.hedera.com:443": new AccountId(8), - "https://testnet-node06-00-grpc.hedera.com:443": new AccountId(9), - }, - - PREVIEWNET: { - "https://previewnet-node00-00-grpc.hedera.com:443": new AccountId(3), - "https://previewnet-node01-00-grpc.hedera.com:443": new AccountId(4), - "https://previewnet-node02-00-grpc.hedera.com:443": new AccountId(5), - "https://previewnet-node03-00-grpc.hedera.com:443": new AccountId(6), - "https://previewnet-node04-00-grpc.hedera.com:443": new AccountId(7), - "https://previewnet-node05-00-grpc.hedera.com:443": new AccountId(8), - "https://previewnet-node06-00-grpc.hedera.com:443": new AccountId(9), - }, + MAINNET: MAINNET, + TESTNET: WEB_TESTNET, + PREVIEWNET: WEB_PREVIEWNET, }; /** @@ -107,7 +68,6 @@ export default class WebClient extends Client { */ constructor(props) { super(props); - if (props != null) { if (typeof props.network === "string") { switch (props.network) { diff --git a/src/constants/ClientConstants.js b/src/constants/ClientConstants.js new file mode 100644 index 000000000..7fb2a398f --- /dev/null +++ b/src/constants/ClientConstants.js @@ -0,0 +1,59 @@ +import AccountId from "../account/AccountId.js"; + +// MAINNET node proxies are the same for both 'WebClient' and 'NativeClient' +export const MAINNET = { + "https://grpc-web.myhbarwallet.com:443": new AccountId(3), + "https://node01-00-grpc.swirlds.com:443": new AccountId(4), + "https://node02.swirldslabs.com:443": new AccountId(5), + "https://node03.swirldslabs.com:443": new AccountId(6), + "https://node04.swirldslabs.com:443": new AccountId(7), + "https://node05.swirldslabs.com:443": new AccountId(8), + "https://node06.swirldslabs.com:443": new AccountId(9), + "https://node07.swirldslabs.com:443": new AccountId(10), + "https://node08.swirldslabs.com:443": new AccountId(11), + "https://node09.swirldslabs.com:443": new AccountId(12), + "https://node10.swirldslabs.com:443": new AccountId(13), + "https://node11.swirldslabs.com:443": new AccountId(14), + "https://node12.swirldslabs.com:443": new AccountId(15), + "https://node13.swirldslabs.com:443": new AccountId(16), + "https://node14.swirldslabs.com:443": new AccountId(17), + "https://node16.swirldslabs.com:443": new AccountId(19), + "https://node17.swirldslabs.com:443": new AccountId(20), + "https://node18.swirldslabs.com:443": new AccountId(21), + "https://node19.swirldslabs.com:443": new AccountId(22), + "https://node20.swirldslabs.com:443": new AccountId(23), + "https://node21.swirldslabs.com:443": new AccountId(24), + "https://node22.swirldslabs.com:443": new AccountId(25), + "https://node23.swirldslabs.com:443": new AccountId(26), + "https://node24.swirldslabs.com:443": new AccountId(27), + "https://node25.swirldslabs.com:443": new AccountId(28), + "https://node26.swirldslabs.com:443": new AccountId(29), +}; + +export const WEB_TESTNET = { + "https://testnet-node00-00-grpc.hedera.com:443": new AccountId(3), + "https://testnet-node01-00-grpc.hedera.com:443": new AccountId(4), + "https://testnet-node02-00-grpc.hedera.com:443": new AccountId(5), + "https://testnet-node03-00-grpc.hedera.com:443": new AccountId(6), + "https://testnet-node04-00-grpc.hedera.com:443": new AccountId(7), + "https://testnet-node05-00-grpc.hedera.com:443": new AccountId(8), + "https://testnet-node06-00-grpc.hedera.com:443": new AccountId(9), +}; + +export const WEB_PREVIEWNET = { + "https://previewnet-node00-00-grpc.hedera.com:443": new AccountId(3), + "https://previewnet-node01-00-grpc.hedera.com:443": new AccountId(4), + "https://previewnet-node02-00-grpc.hedera.com:443": new AccountId(5), + "https://previewnet-node03-00-grpc.hedera.com:443": new AccountId(6), + "https://previewnet-node04-00-grpc.hedera.com:443": new AccountId(7), + "https://previewnet-node05-00-grpc.hedera.com:443": new AccountId(8), + "https://previewnet-node06-00-grpc.hedera.com:443": new AccountId(9), +}; + +export const NATIVE_TESTNET = { + "https://grpc-web.testnet.myhbarwallet.com:443": new AccountId(3), +}; + +export const NATIVE_PREVIEWNET = { + "https://grpc-web.previewnet.myhbarwallet.com:443": new AccountId(3), +}; From 56c0b0244d0830a7f8ac5bcd23b528640bc1f651 Mon Sep 17 00:00:00 2001 From: Petar Tonev Date: Sun, 15 Jan 2023 08:53:12 +0200 Subject: [PATCH 6/7] fix integration tests Signed-off-by: Petar Tonev --- src/Executable.js | 4 +++- src/client/ManagedNetwork.js | 10 +++++----- src/client/Network.js | 2 +- src/grpc/GrpcServiceError.js | 17 ++++++++++++++++- 4 files changed, 25 insertions(+), 8 deletions(-) diff --git a/src/Executable.js b/src/Executable.js index 46031a8cb..a0bd0f7e0 100644 --- a/src/Executable.js +++ b/src/Executable.js @@ -631,7 +631,9 @@ export default class Executable { } catch (err) { // If we received a grpc status error we need to determine if // we should retry on this error, or err from the request entirely. - const error = /** @type {Error} */ (err); + const error = GrpcServiceError._fromResponse( + /** @type {Error} */ (err) + ); // Save the error in case we retry persistentError = error; diff --git a/src/client/ManagedNetwork.js b/src/client/ManagedNetwork.js index 4bb1c3f99..8dca174ca 100644 --- a/src/client/ManagedNetwork.js +++ b/src/client/ManagedNetwork.js @@ -282,12 +282,12 @@ export default class ManagedNetwork { // Get a random node let node = this.getNode(); - - while (keys.has(node.getKey())) { - node = this.getNode(); + if (!keys.has(node.getKey())) { + keys.add(node.getKey()); + nodes.push(node); + } else { + i--; } - keys.add(node.getKey()); - nodes.push(node); } return nodes; diff --git a/src/client/Network.js b/src/client/Network.js index 04453cce3..e001a121c 100644 --- a/src/client/Network.js +++ b/src/client/Network.js @@ -252,7 +252,7 @@ export default class Network extends ManagedNetwork { // ultimately it does not matter if we round up or down // if we round up, we will eventually take one more healthy node for execution // and we would hit the 'nodes.length == count' check in _getNumberOfMostHealthyNodes() less often - return Math.ceil((this._nodes.length + 3 - 1) / 3); + return Math.floor((this._nodes.length + 3 - 1) / 3); } /** diff --git a/src/grpc/GrpcServiceError.js b/src/grpc/GrpcServiceError.js index 9af1958ec..163c987fc 100644 --- a/src/grpc/GrpcServiceError.js +++ b/src/grpc/GrpcServiceError.js @@ -17,7 +17,7 @@ * limitations under the License. * ‍ */ -// eslint-disable-next-line @typescript-eslint/no-unused-vars + import GrpcStatus from "./GrpcStatus.js"; /** @@ -46,4 +46,19 @@ export default class GrpcServiceError extends Error { Error.captureStackTrace(this, GrpcServiceError); } } + + /** + * @param {Error & { code?: number; details?: string }} obj + * @returns {Error} + */ + static _fromResponse(obj) { + if (obj.code != null && obj.details != null) { + const status = GrpcStatus._fromValue(obj.code); + const err = new GrpcServiceError(status); + err.message = obj.details; + return err; + } else { + return /** @type {Error} */ (obj); + } + } } From 693ed6b2403c8919a71ce9ce3da4134557c82607 Mon Sep 17 00:00:00 2001 From: Petar Tonev Date: Sun, 15 Jan 2023 20:57:25 +0200 Subject: [PATCH 7/7] handle some corner cases Signed-off-by: Petar Tonev --- src/client/ManagedNetwork.js | 11 ++++++++++- src/client/Network.js | 4 +++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/client/ManagedNetwork.js b/src/client/ManagedNetwork.js index 8dca174ca..6ad16977f 100644 --- a/src/client/ManagedNetwork.js +++ b/src/client/ManagedNetwork.js @@ -69,6 +69,14 @@ export default class ManagedNetwork { * @type {NetworkNodeT[]} */ this._healthyNodes = []; + + /** + * Count of unhealthy nodes. + * + * @protected + * @type {number} + */ + this._unhealthyNodesCount = 0; /** @type {(address: string, cert?: string) => ChannelT} */ this._createNetworkChannel = createNetworkChannel; @@ -276,7 +284,7 @@ export default class ManagedNetwork { // `this._healthyNodes.length` times. This can result in a shorter // list than `count`, but that is much better than running forever for (let i = 0; i < this._healthyNodes.length; i++) { - if (nodes.length == count) { + if (nodes.length == count - this._unhealthyNodesCount) { break; } @@ -510,6 +518,7 @@ export default class ManagedNetwork { for (let i = 0; i < this._healthyNodes.length; i++) { if (this._healthyNodes[i] == node) { this._healthyNodes.splice(i, 1); + this._unhealthyNodesCount++; } } } diff --git a/src/client/Network.js b/src/client/Network.js index e001a121c..4de6172c2 100644 --- a/src/client/Network.js +++ b/src/client/Network.js @@ -252,7 +252,9 @@ export default class Network extends ManagedNetwork { // ultimately it does not matter if we round up or down // if we round up, we will eventually take one more healthy node for execution // and we would hit the 'nodes.length == count' check in _getNumberOfMostHealthyNodes() less often - return Math.floor((this._nodes.length + 3 - 1) / 3); + return (this._nodes.length <= 9) + ? this._nodes.length + : Math.floor((this._nodes.length + 3 - 1) / 3) } /**