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

Fix web proxy #1395

Merged
merged 8 commits into from
Jan 16, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
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
29 changes: 17 additions & 12 deletions src/Executable.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -440,16 +441,22 @@ export default class Executable {
* Unlike `shouldRetry` this method does in fact still return a boolean
*
* @protected
* @param {GrpcServiceError} error
* @param {Error} error
petreze marked this conversation as resolved.
Show resolved Hide resolved
* @returns {boolean}
*/
_shouldRetryExceptionally(error) {
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 {
petreze marked this conversation as resolved.
Show resolved Hide resolved
// if we get to the 'else' statement, the 'error' is instanceof 'HttpError'
// and in this case, we have to retry always
return true;
}
}

/**
Expand Down Expand Up @@ -624,18 +631,16 @@ 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
) {
Expand Down
7 changes: 7 additions & 0 deletions src/channel/NativeChannel.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
/**
Expand Down Expand Up @@ -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} */
Expand Down
7 changes: 7 additions & 0 deletions src/channel/WebChannel.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -66,6 +68,11 @@ 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");
Expand Down
14 changes: 7 additions & 7 deletions src/client/ManagedNetwork.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -485,8 +486,7 @@ export default class ManagedNetwork {
*/
getNode(key) {
this._readmitNodes();

if (key != null) {
if (key != null && key != undefined) {
return /** @type {NetworkNodeT[]} */ (
this._network.get(key.toString())
)[0];
Expand Down
1 change: 1 addition & 0 deletions src/client/NativeClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -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),
petreze marked this conversation as resolved.
Show resolved Hide resolved
},

TESTNET: {
Expand Down
6 changes: 4 additions & 2 deletions src/client/Network.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

/**
Expand Down
15 changes: 0 additions & 15 deletions src/grpc/GrpcServiceError.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
}
44 changes: 44 additions & 0 deletions src/http/HttpError.js
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
56 changes: 56 additions & 0 deletions src/http/HttpStatus.js
Original file line number Diff line number Diff line change
@@ -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;
}
}