Skip to content
66 changes: 66 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,72 @@ jobs:
token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: true

dab-tests:
name: DAB Tests using Node ${{ matrix.node }}
runs-on: hiero-client-sdk-linux-medium
strategy:
matrix:
node: [ "22" ]

steps:
- name: Harden Runner
uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1
with:
egress-policy: audit

- name: Checkout Code
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
submodules: recursive

- name: Install Task
uses: arduino/setup-task@b91d5d2c96a56797b48ac1e0e89220bf64044611 # v2.0.0
with:
version: 3.35.1

- name: Install PNPM
uses: step-security/action-setup@3d419c73e38e670dbffe349ffff26dd13c164640 # v4.2.0
with:
version: 9.15.5

- name: Setup Node
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
with:
node-version: ${{ matrix.node }}
cache: pnpm

- name: Build @hashgraph/sdk
id: build-sdk
run: task build

- name: Install Playwright Dependencies
id: playwright-deps
if: ${{ steps.build-sdk.conclusion == 'success' && !cancelled() && always() }}
run: |
sudo npx playwright install-deps
npx playwright install

- name: Prepare Hiero Solo
id: solo
uses: hiero-ledger/hiero-solo-action@33f19f2eb8cbc49a61567a0781f3bc37bf2a32aa # support gualGrpxProxyPort
with:
installMirrorNode: true
hieroVersion: v0.68.1-rc.1
mirrorNodeVersion: v0.142.0
grpcProxyPort: 8080
dualMode: true
dualModeGrpcProxyPort: 8081

- name: Set Operator Account
run: |
echo "OPERATOR_KEY=${{ steps.solo.outputs.ed25519PrivateKey }}" >> $GITHUB_ENV
echo "OPERATOR_ID=${{ steps.solo.outputs.ed25519AccountId }}" >> $GITHUB_ENV
echo "HEDERA_NETWORK=local-node" >> $GITHUB_ENV

- name: Run DAB Integration Tests
if: ${{ steps.build-sdk.conclusion == 'success' && !cancelled() && always() }}
run: task test:integration:dual-mode

examples:
name: Run examples using Node ${{ matrix.node }}
runs-on: hiero-client-sdk-linux-medium
Expand Down
6 changes: 6 additions & 0 deletions Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,12 @@ tasks:
cmds:
- npx vitest --coverage --config=test/vitest-node-integration.config.ts
- npx vitest --coverage --config=test/vitest-browser-integration.config.ts

"test:integration:dual-mode":
cmds:
- npx vitest --poolOptions.threads.singleThread --config=test/vitest-node-integration-dual-mode.config.ts
- npx vitest --poolOptions.threads.singleThread --config=test/vitest-browser-integration-dual-mode.config.ts

"update:proto":
deps:
- "proto:update"
Expand Down
37 changes: 37 additions & 0 deletions src/Executable.js
Original file line number Diff line number Diff line change
Expand Up @@ -800,6 +800,43 @@ export default class Executable {
// Determine by the executing state what we should do
switch (shouldRetry) {
case ExecutionState.Retry:
// Special handling for INVALID_NODE_ACCOUNT: mark node as unusable
// and update network to get latest node account IDs
if (status === Status.InvalidNodeAccount) {
if (this._logger) {
this._logger.debug(
`[${this._getLogId()}] node with accountId: ${node.accountId.toString()} and proxy IP: ${node.address.toString()} has invalid node account ID, marking as unhealthy and updating network`,
);
}

// Mark the node as unusable by increasing its backoff and removing it from the healthy nodes list
client._network.increaseBackoff(node);

// Initiate addressbook query and update the client's network
// This will make the SDK client have the latest node account IDs for subsequent transactions
try {
if (client.mirrorNetwork.length > 0) {
await client.updateNetwork();
} else {
if (this._logger) {
this._logger.warn(
"Cannot update address book: no mirror network configured. Retrying with existing network configuration.",
);
}
}
} catch (error) {
if (this._logger) {
const errorMessage =
error instanceof Error
? error.message
: String(error);
this._logger.trace(
`failed to update client address book after INVALID_NODE_ACCOUNT_ID: ${errorMessage}`,
);
}
}
}

await delayForAttempt(
isLocalNode,
attempt,
Expand Down
4 changes: 3 additions & 1 deletion src/channel/WebChannel.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,9 @@ export default class WebChannel extends Channel {
*/
_shouldUseHttps(address) {
return !(
address.includes("localhost") || address.includes("127.0.0.1")
address.includes("localhost") ||
address.includes("127.0.0.1") ||
address.includes(".cluster.local")
);
}

Expand Down
7 changes: 1 addition & 6 deletions src/client/Network.js
Original file line number Diff line number Diff line change
Expand Up @@ -284,12 +284,7 @@ export default class Network extends ManagedNetwork {
if (this._maxNodesPerTransaction > 0) {
return this._maxNodesPerTransaction;
}
// 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 this._nodes.length <= 9
? this._nodes.length
: Math.floor((this._nodes.length + 3 - 1) / 3);
return this._nodes.length;
}

/**
Expand Down
1 change: 1 addition & 0 deletions src/transaction/Transaction.js
Original file line number Diff line number Diff line change
Expand Up @@ -1966,6 +1966,7 @@ export default class Transaction extends Executable {
case Status.Unknown:
case Status.PlatformTransactionNotCreated:
case Status.PlatformNotActive:
case Status.InvalidNodeAccount:
return [status, ExecutionState.Retry];
case Status.Ok:
return [status, ExecutionState.Finished];
Expand Down
11 changes: 11 additions & 0 deletions test/integration/dual-mode/NodeConstants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { SOLO_NAMESPACE } from "./SharedConstants.js";
const node2Address = `network-node2-svc.${SOLO_NAMESPACE}.svc.cluster.local:50211`;
const node2PortToReplace = 51211;
const network = {
"127.0.0.1:50211": "0.0.3",
"127.0.0.1:51211": "0.0.4",
};

const mirrorNetwork = ["localhost:5600"];

export { network, mirrorNetwork, node2Address, node2PortToReplace };
Loading
Loading