From f0c6274f118c8578ebd74fe680e6d0503679f69a Mon Sep 17 00:00:00 2001 From: venilinvasilev Date: Mon, 10 Nov 2025 11:50:55 +0200 Subject: [PATCH 01/10] feat: changing node accounts ids wip Signed-off-by: venilinvasilev --- .github/workflows/build.yml | 57 +++ src/Executable.js | 29 ++ src/Status.js | 12 + src/client/Network.js | 7 +- src/transaction/Transaction.js | 1 + .../dual-mode/NodeUpdateIntegrationTest.js | 413 ++++++++++++++++++ ...st-browser-integration-dual-mode.config.ts | 96 ++++ test/vitest-browser-integration.config.ts | 1 + ...itest-node-integration-dual-mode.config.ts | 35 ++ test/vitest-node-integration.config.ts | 1 + 10 files changed, 646 insertions(+), 6 deletions(-) create mode 100644 test/integration/dual-mode/NodeUpdateIntegrationTest.js create mode 100644 test/vitest-browser-integration-dual-mode.config.ts create mode 100644 test/vitest-node-integration-dual-mode.config.ts diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 36285d4c33..f1b9722cbb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -169,6 +169,63 @@ 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: Prepare Hiero Solo + id: solo + uses: hiero-ledger/hiero-solo-action@9471711c98a56179def6123e1040ab6c2e668881 # branch: 75-add-support-for-multiple-consensus-nodes + with: + installMirrorNode: true + hieroVersion: v0.68.1-rc.1 + mirrorNodeVersion: v0.142.0 + dualMode: true + + - 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: npx vitest --coverage --poolOptions.threads.singleThread --config=test/vitest-node-integration-dual-mode.config.ts NodeUpdateIntegrationTest.js + examples: name: Run examples using Node ${{ matrix.node }} runs-on: hiero-client-sdk-linux-medium diff --git a/src/Executable.js b/src/Executable.js index f7ea41d3d2..8f9a2a94c6 100644 --- a/src/Executable.js +++ b/src/Executable.js @@ -800,6 +800,35 @@ 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 { + await client.updateNetwork(); + } 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, diff --git a/src/Status.js b/src/Status.js index 1ce6c2afb5..2a754c83a8 100644 --- a/src/Status.js +++ b/src/Status.js @@ -742,6 +742,8 @@ export default class Status { return "GRPC_WEB_PROXY_NOT_SUPPORTED"; case Status.NftTransfersOnlyAllowedForNonFungibleUnique: return "NFT_TRANSFERS_ONLY_ALLOWED_FOR_NON_FUNGIBLE_UNIQUE"; + case Status.NodeAccountHasZeroBalance: + return "NODE_ACCOUNT_HAS_ZERO_BALANCE"; default: return `UNKNOWN (${this._code})`; } @@ -1472,6 +1474,8 @@ export default class Status { return Status.GrpcWebProxyNotSupported; case 400: return Status.NftTransfersOnlyAllowedForNonFungibleUnique; + case 526: + return Status.NodeAccountHasZeroBalance; default: throw new Error( `(BUG) Status.fromCode() does not handle code: ${code}`, @@ -3351,3 +3355,11 @@ Status.GrpcWebProxyNotSupported = new Status(399); * An NFT transfers list referenced a token type other than NON_FUNGIBLE_UNIQUE. */ Status.NftTransfersOnlyAllowedForNonFungibleUnique = new Status(400); + +/** + * This operation cannot be completed because the target + * account has a zero balance.
+ * Node accounts require a positive balance. The transaction may be + * resubmitted once the account has been funded. + */ +Status.NodeAccountHasZeroBalance = new Status(526); diff --git a/src/client/Network.js b/src/client/Network.js index ffe36e1938..e3f974279a 100644 --- a/src/client/Network.js +++ b/src/client/Network.js @@ -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._healthyNodes.length; } /** diff --git a/src/transaction/Transaction.js b/src/transaction/Transaction.js index 4717ef1dc4..ba5e2e1ca2 100644 --- a/src/transaction/Transaction.js +++ b/src/transaction/Transaction.js @@ -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]; diff --git a/test/integration/dual-mode/NodeUpdateIntegrationTest.js b/test/integration/dual-mode/NodeUpdateIntegrationTest.js new file mode 100644 index 0000000000..daf04e36aa --- /dev/null +++ b/test/integration/dual-mode/NodeUpdateIntegrationTest.js @@ -0,0 +1,413 @@ +// SPDX-License-Identifier: Apache-2.0 + +import { + AccountId, + AccountCreateTransaction, + AccountDeleteTransaction, + Hbar, + NodeUpdateTransaction, + PrivateKey, + Status, + ServiceEndpoint, +} from "../../../src/exports.js"; +import { Client } from "../../../src/index.js"; + +describe("Node Update Integration Network Tests", function () { + let client; + let operatorAccountId; + let operatorKey; + + beforeEach(function () { + // Initialize client with integration network + client = Client.forNetwork({ + "127.0.0.1:50211": "0.0.3", + "127.0.0.1:51211": "0.0.4", + }).setMirrorNetwork(["localhost:5600"]); + + // Set the operator to be account 0.0.2 + operatorAccountId = AccountId.fromString("0.0.2"); + operatorKey = PrivateKey.fromStringDer( + "302e020100300506032b65700422042091132178e72057a1d7528025956fe39b0b847f200ab59b2fdd367017f3087137", + ); + + client.setOperator(operatorAccountId, operatorKey); + }); + + afterEach(function () { + if (client) { + client.close(); + } + }); + + it("should execute node update transaction", async function () { + const response = await new NodeUpdateTransaction() + .setNodeId(0) + .setNodeAccountIds([AccountId.fromString("0.0.4")]) + .setDescription("testUpdated") + .setDeclineReward(true) + .setGrpcWebProxyEndpoint( + new ServiceEndpoint() + .setDomainName("testWebUpdatedsdfsdfsdfsdf.com") + .setPort(123456), + ) + .execute(client); + + const receipt = await response.getReceipt(client); + expect(receipt.status).to.equal(Status.Success); + }); + + it("should delete grpc web proxy endpoint", async function () { + const response = await new NodeUpdateTransaction() + .setNodeId(0) + .deleteGrpcWebProxyEndpoint() + .execute(client); + + const receipt = await response.getReceipt(client); + expect(receipt.status).to.equal(Status.Success); + }); + + it("should change node account ID and revert back", async function () { + // Change node account ID from 0.0.3 to 0.0.2 + const response1 = await new NodeUpdateTransaction() + .setNodeId(0) + .setAccountId(AccountId.fromString("0.0.2")) + .execute(client); + + const receipt1 = await response1.getReceipt(client); + expect(receipt1.status).to.equal(Status.Success); + + // Revert the ID back to 0.0.3 + const response2 = await new NodeUpdateTransaction() + .setNodeId(0) + .setAccountId(AccountId.fromString("0.0.3")) + .execute(client); + + const receipt2 = await response2.getReceipt(client); + expect(receipt2.status).to.equal(Status.Success); + }); + + it("should fail with INVALID_SIGNATURE when updating without admin key", async function () { + // Create a new account to be the operator + const newOperatorKey = PrivateKey.generateED25519(); + const createResp = await new AccountCreateTransaction() + .setKey(newOperatorKey.publicKey) + .setInitialBalance(new Hbar(2)) + .execute(client); + const createReceipt = await createResp.getReceipt(client); + const newOperator = createReceipt.accountId; + + // Set the new account as operator + client.setOperator(newOperator, newOperatorKey); + + try { + // Try to update node account ID without admin key signature + const response = await new NodeUpdateTransaction() + .setNodeId(0) + .setDescription("testUpdated") + .setAccountId(AccountId.fromString("0.0.50")) + .execute(client); + + await response.getReceipt(client); + expect.fail("Should have thrown INVALID_SIGNATURE error"); + } catch (error) { + expect(error.message).to.include("INVALID_SIGNATURE"); + } + }); + + it("should change node account ID to the same account", async function () { + const response = await new NodeUpdateTransaction() + .setNodeId(0) + .setNodeAccountIds([AccountId.fromString("0.0.4")]) + .setAccountId(AccountId.fromString("0.0.3")) + .execute(client); + + const receipt = await response.getReceipt(client); + expect(receipt.status).to.equal(Status.Success); + }); + + it("should fail when changing to non-existent account ID", async function () { + try { + const response = await new NodeUpdateTransaction() + .setNodeId(0) + .setDescription("testUpdated") + .setAccountId(AccountId.fromString("0.0.999999999")) + .execute(client); + + await response.getReceipt(client); + expect.fail("Should have thrown INVALID_SIGNATURE error"); + } catch (error) { + expect(error.message).to.include("INVALID_SIGNATURE"); + } + }); + + it("should fail when changing node account ID without account key", async function () { + // Create a new account + const newKey = PrivateKey.generateED25519(); + const createResp = await new AccountCreateTransaction() + .setKey(newKey.publicKey) + .setInitialBalance(new Hbar(2)) + .execute(client); + + const createReceipt = await createResp.getReceipt(client); + const newNodeAccountId = createReceipt.accountId; + + try { + // Try to set node account ID to new account without signing with new account's key + const response = await new NodeUpdateTransaction() + .setNodeId(0) + .setDescription("testUpdated") + .setAccountId(newNodeAccountId) + .execute(client); + + await response.getReceipt(client); + expect.fail("Should have thrown INVALID_SIGNATURE error"); + } catch (error) { + expect(error.message).to.include("INVALID_SIGNATURE"); + } + }); + + it("should fail when changing to deleted account ID", async function () { + // Create a new account + const newAccountKey = PrivateKey.generateED25519(); + const createResp = await new AccountCreateTransaction() + .setKey(newAccountKey.publicKey) + .execute(client); + + const createReceipt = await createResp.getReceipt(client); + const newAccount = createReceipt.accountId; + + // Delete the account + const deleteResp = await ( + await new AccountDeleteTransaction() + .setAccountId(newAccount) + .setTransferAccountId(client.operatorAccountId) + .freezeWith(client) + .sign(newAccountKey) + ).execute(client); + + await deleteResp.getReceipt(client); + + try { + // Try to set node account ID to deleted account + const updateResp = await ( + await new NodeUpdateTransaction() + .setNodeId(0) + .setDescription("testUpdated") + .setAccountId(newAccount) + .freezeWith(client) + .sign(newAccountKey) + ).execute(client); + + await updateResp.getReceipt(client); + expect.fail("Should have thrown ACCOUNT_DELETED error"); + } catch (error) { + expect(error.message).to.include("ACCOUNT_DELETED"); + } + }); + + it("should fail when new node account has zero balance", async function () { + // Create a new account with zero balance + const newAccountKey = PrivateKey.generateED25519(); + const createResp = await new AccountCreateTransaction() + .setKey(newAccountKey.publicKey) + .execute(client); + + const createReceipt = await createResp.getReceipt(client); + const newAccount = createReceipt.accountId; + + try { + // Try to set node account ID to account with zero balance + const updateResp = await ( + await new NodeUpdateTransaction() + .setNodeId(0) + .setNodeAccountIds([AccountId.fromString("0.0.4")]) + .setDescription("testUpdated") + .setAccountId(newAccount) + .freezeWith(client) + .sign(newAccountKey) + ).execute(client); + + await updateResp.getReceipt(client); + expect.fail( + "Should have thrown NODE_ACCOUNT_HAS_ZERO_BALANCE error", + ); + } catch (error) { + expect(error.message).to.include("NODE_ACCOUNT_HAS_ZERO_BALANCE"); + } + }); + + it("should update addressbook and retry after node account ID change", async function () { + // Create the account that will be the new node account ID + const newAccountKey = PrivateKey.generateED25519(); + const createResp = await new AccountCreateTransaction() + .setKey(newAccountKey.publicKey) + .setInitialBalance(new Hbar(1)) + .execute(client); + + const createReceipt = await createResp.getReceipt(client); + const newNodeAccountID = createReceipt.accountId; + + // Update node account ID (0.0.8 -> newNodeAccountID) + const updateResp = await ( + await new NodeUpdateTransaction() + .setNodeId(1) + .setNodeAccountIds([AccountId.fromString("0.0.3")]) + .setAccountId(newNodeAccountID) + .freezeWith(client) + .sign(newAccountKey) + ).execute(client); + + await updateResp.getReceipt(client); + + // Wait for mirror node to import data + await new Promise((resolve) => setTimeout(resolve, 5000)); + + const anotherNewKey = PrivateKey.generateED25519(); + // Submit to the updated node - should trigger addressbook refresh + const testResp = await new AccountCreateTransaction() + .setKey(anotherNewKey.publicKey) + .setNodeAccountIds([ + AccountId.fromString("0.0.4"), + AccountId.fromString("0.0.3"), + ]) + .execute(client); + + const testReceipt = await testResp.getReceipt(client); + expect(testReceipt.status).to.equal(Status.Success); + // Verify address book has been updated + const network = client.network; + const hasNewNodeAccount = Object.values(network).some( + (accountId) => accountId.toString() === newNodeAccountID.toString(), + ); + expect(hasNewNodeAccount).to.be.true; + + // Find the address of the newly added node + const newNodeAddress = Object.entries(network).find( + ([, accountId]) => + accountId.toString() === newNodeAccountID.toString(), + )?.[0]; + + // Assert the address matches the expected value + expect(newNodeAddress).to.equal( + "network-node2-svc.solo.svc.cluster.local:50211", + ); + + // This is not an ideal workaround - reconstruct the network state + // because the mirror node returns a different address than expected + if ( + newNodeAddress === "network-node2-svc.solo.svc.cluster.local:50211" + ) { + const oldNetworkState = { ...network }; + delete oldNetworkState[newNodeAddress]; + const newNetworkState = { + ...oldNetworkState, + "network-node2-svc.solo.svc.cluster.local:51211": + newNodeAccountID, + }; + client.setNetwork(newNetworkState); + } + + // This transaction should succeed with the new node account ID + const finalResp = await new AccountCreateTransaction() + .setKey(anotherNewKey.publicKey) + .setNodeAccountIds([newNodeAccountID]) + .execute(client); + + const finalReceipt = await finalResp.getReceipt(client); + expect(finalReceipt.status).to.equal(Status.Success); + + // Revert the node account ID + const revertResp = await new NodeUpdateTransaction() + .setNodeId(1) + .setNodeAccountIds([AccountId.fromString("0.0.3")]) + .setAccountId(AccountId.fromString("0.0.4")) + .execute(client); + + await revertResp.getReceipt(client); + }); + + it("should handle node account ID change without mirror node setup", async function () { + // Create a client without mirror network + const networkClient = Client.forNetwork({ + "127.0.0.1:50211": "0.0.3", + "127.0.0.1:51211": "0.0.4", + }); + + networkClient.setOperator(operatorAccountId, operatorKey); + + try { + // Create the account that will be the new node account ID + const newAccountKey = PrivateKey.generateED25519(); + const createResp = await new AccountCreateTransaction() + .setKey(newAccountKey.publicKey) + .setNodeAccountIds([ + AccountId.fromString("0.0.3"), + AccountId.fromString("0.0.4"), + ]) + .setInitialBalance(new Hbar(1)) + .execute(networkClient); + + const createReceipt = await createResp.getReceipt(networkClient); + const newNodeAccountID = createReceipt.accountId; + + // Update node account ID + const updateResp = await ( + await new NodeUpdateTransaction() + .setNodeId(0) + .setAccountId(newNodeAccountID) + .freezeWith(networkClient) + .sign(newAccountKey) + ).execute(networkClient); + + await updateResp.getReceipt(networkClient); + + // Wait for changes to propagate + await new Promise((resolve) => setTimeout(resolve, 5000)); + + const anotherNewKey = PrivateKey.generateED25519(); + + // Submit transaction - should retry since no mirror node to update addressbook + const testResp = await new AccountCreateTransaction() + .setKey(anotherNewKey.publicKey) + .setNodeAccountIds([ + AccountId.fromString("0.0.3"), + AccountId.fromString("0.0.4"), + ]) + .execute(networkClient); + + const testReceipt = await testResp.getReceipt(networkClient); + expect(testReceipt.status).to.equal(Status.Success); + + // Verify address book has NOT been updated (no mirror node) + const network = networkClient.network; + const node1 = Object.entries(network).find( + ([, accountId]) => accountId.toString() === "0.0.3", + ); + const node2 = Object.entries(network).find( + ([, accountId]) => accountId.toString() === "0.0.4", + ); + + expect(node1).to.not.be.undefined; + expect(node2).to.not.be.undefined; + + // This transaction should succeed with retries + const finalResp = await new AccountCreateTransaction() + .setKey(anotherNewKey.publicKey) + .execute(networkClient); + + const finalReceipt = await finalResp.getReceipt(networkClient); + expect(finalReceipt.status).to.equal(Status.Success); + + // Revert the node account ID + const revertResp = await new NodeUpdateTransaction() + .setNodeId(0) + .setNodeAccountIds([AccountId.fromString("0.0.4")]) + .setAccountId(AccountId.fromString("0.0.3")) + .execute(networkClient); + + await revertResp.getReceipt(networkClient); + } finally { + networkClient.close(); + } + }); +}); diff --git a/test/vitest-browser-integration-dual-mode.config.ts b/test/vitest-browser-integration-dual-mode.config.ts new file mode 100644 index 0000000000..5534c4d05a --- /dev/null +++ b/test/vitest-browser-integration-dual-mode.config.ts @@ -0,0 +1,96 @@ +import { defineConfig } from "vitest/config"; + +import path from "path"; +import fs from "fs"; + +const pkg = JSON.parse( + fs.readFileSync(path.resolve(__dirname, "../package.json"), "utf-8"), +); + +/** @type {import("vitest").UserConfig} */ +export default defineConfig({ + test: { + environment: "jsdom", + watch: false, + globals: true, + + browser: { + screenshotFailures: false, + headless: true, + provider: "playwright", + enabled: true, + instances: [{ browser: "chromium" }], + }, + include: ["test/integration/dual-mode/**/*.js"], + hookTimeout: 120000, + testTimeout: 120000, + maxWorkers: 4, + minWorkers: 4, + coverage: { + include: ["src/**/*.js"], + provider: "v8", + reporter: ["text-summary", "lcov"], + reportsDirectory: "./coverage", + }, + }, + define: { + __SDK_VERSION__: JSON.stringify(pkg.version), + "import.meta.env.VITE_OPERATOR_ID": JSON.stringify( + process.env.OPERATOR_ID || "", + ), + "import.meta.env.VITE_OPERATOR_KEY": JSON.stringify( + process.env.OPERATOR_KEY || "", + ), + "import.meta.env.VITE_HEDERA_NETWORK": JSON.stringify( + process.env.HEDERA_NETWORK || "", + ), + }, + resolve: { + alias: { + // redirect src/ to src/browser + // note that this is NOT needed when consuming this package as the browser field in package.json + // will take care of this + "../../src/index.js": "../../src/browser.js", + "../src/index.js": "../src/browser.js", + // TODO: extract `encoding/hex.js` etc into a variable and call a function to generate + // all the prefixes. + "../../../src/encoding/hex.js": + "../../../src/encoding/hex.browser.js", + "../../src/encoding/hex.js": "../../src/encoding/hex.browser.js", + "../src/encoding/hex.js": "../src/encoding/hex.browser.js", + "src/encoding/hex.js": "src/encoding/hex.browser.js", + "../encoding/hex.js": "../encoding/hex.browser.js", + "./encoding/hex.js": "./encoding/hex.browser.js", + "../src/encoding/utf8.js": "../src/encoding/utf8.browser.js", + "../../src/encoding/utf8.js": "../../src/encoding/utf8.browser.js", + "../encoding/utf8.js": "../encoding/utf8.browser.js", + "../src/cryptography/sha384.js": + "../src/cryptography/sha384.browser.js", + "../cryptography/sha384.js": "../cryptography/sha384.browser.js", + "./client/NodeIntegrationTestEnv.js": + "./client/WebIntegrationTestEnv.js", + "../integration/client/NodeIntegrationTestEnv.js": + "../integration/client/WebIntegrationTestEnv.js", + "../../src/client/NodeClient.js": "../../src/client/WebClient.js", + "../../../src/client/NodeClient.js": + "../../../src/client/WebClient.js", + "./client/NodeClient.js": "./client/WebClient.js", + "../../../src/LocalProvider.js": "../../../src/LocalProviderWeb.js", + "../../src/LocalProvider.js": "../../src/LocalProviderWeb.js", + "../src/LocalProvider.js": "../src/LocalProviderWeb.js", + "src/LocalProvider.js": "src/LocalProviderWeb.js", + + // Add more comprehensive aliases for NodeIntegrationTestEnv + "NodeIntegrationTestEnv.js": "WebIntegrationTestEnv.js", + "./client/NodeIntegrationTestEnv": "./client/WebIntegrationTestEnv", + "../client/NodeIntegrationTestEnv": + "../client/WebIntegrationTestEnv", + "../../client/NodeIntegrationTestEnv": + "../../client/WebIntegrationTestEnv", + "../../../src/client/NodeIntegrationTestEnv": + "../../../src/client/WebIntegrationTestEnv", + // Add aliases for NodeClient + "NodeClient.js": "WebClient.js", + }, + }, +}); diff --git a/test/vitest-browser-integration.config.ts b/test/vitest-browser-integration.config.ts index d51339950c..2c9b1e99e2 100644 --- a/test/vitest-browser-integration.config.ts +++ b/test/vitest-browser-integration.config.ts @@ -32,6 +32,7 @@ export default defineConfig({ "test/integration/TopicMessageIntegrationTest.js", "test/integration/TokenNftsUpdateTransactionIntegrationTest.js", "test/integration/ClientIntegrationTest.js", + "test/integration/dual-mode/**/*.js", ], hookTimeout: 120000, testTimeout: 120000, diff --git a/test/vitest-node-integration-dual-mode.config.ts b/test/vitest-node-integration-dual-mode.config.ts new file mode 100644 index 0000000000..843724ea39 --- /dev/null +++ b/test/vitest-node-integration-dual-mode.config.ts @@ -0,0 +1,35 @@ +import { defineConfig } from "vitest/config"; + +import path from "path"; +import fs from "fs"; + +const pkg = JSON.parse( + fs.readFileSync(path.resolve(__dirname, "../package.json"), "utf-8"), +); + +/** @type {import("vitest").UserConfig} */ +export default defineConfig({ + test: { + allowOnly: true, + watch: false, + globals: true, + environment: "node", + include: ["test/integration/dual-mode/**/*.js"], + exclude: [ + "test/integration/client/*", + "test/integration/resources/*", + "test/integration/utils/*", + ], + hookTimeout: 120000, + testTimeout: 120000, + coverage: { + include: ["src/**/*.js"], + provider: "v8", + reporter: ["text-summary", "lcov"], + reportsDirectory: "./coverage", + }, + }, + define: { + __SDK_VERSION__: JSON.stringify(pkg.version), + }, +}); diff --git a/test/vitest-node-integration.config.ts b/test/vitest-node-integration.config.ts index 1478ec1285..a3ad4420fd 100644 --- a/test/vitest-node-integration.config.ts +++ b/test/vitest-node-integration.config.ts @@ -19,6 +19,7 @@ export default defineConfig({ "test/integration/resources/*", "test/integration/utils/*", "test/integration/contents.js", + "test/integration/dual-mode/**/*.js", ], hookTimeout: 120000, testTimeout: 120000, From e926bea31ce02099e72bbab86fc4003b6db7252f Mon Sep 17 00:00:00 2001 From: venilinvasilev Date: Thu, 13 Nov 2025 15:00:47 +0200 Subject: [PATCH 02/10] fix: dab tests Signed-off-by: venilinvasilev --- .github/workflows/build.yml | 2 +- Taskfile.yml | 6 ++ src/Executable.js | 10 ++- src/channel/WebChannel.js | 4 +- src/client/Network.js | 2 +- test/integration/dual-mode/NodeConstants.js | 10 +++ .../dual-mode/NodeUpdateIntegrationTest.js | 69 ++++++++++++------- test/integration/dual-mode/WebConstants.js | 10 +++ ...st-browser-integration-dual-mode.config.ts | 11 ++- ...itest-node-integration-dual-mode.config.ts | 3 +- 10 files changed, 94 insertions(+), 33 deletions(-) create mode 100644 test/integration/dual-mode/NodeConstants.js create mode 100644 test/integration/dual-mode/WebConstants.js diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f1b9722cbb..75e3349bb9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -224,7 +224,7 @@ jobs: - name: Run DAB Integration Tests if: ${{ steps.build-sdk.conclusion == 'success' && !cancelled() && always() }} - run: npx vitest --coverage --poolOptions.threads.singleThread --config=test/vitest-node-integration-dual-mode.config.ts NodeUpdateIntegrationTest.js + run: task test:integration:dual-mode examples: name: Run examples using Node ${{ matrix.node }} diff --git a/Taskfile.yml b/Taskfile.yml index 313e078667..bd8ab5ce00 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -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" diff --git a/src/Executable.js b/src/Executable.js index 8f9a2a94c6..bd32bca6ce 100644 --- a/src/Executable.js +++ b/src/Executable.js @@ -815,7 +815,15 @@ export default class Executable { // 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 { - await client.updateNetwork(); + 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 = diff --git a/src/channel/WebChannel.js b/src/channel/WebChannel.js index dae35b50f9..016e17933c 100644 --- a/src/channel/WebChannel.js +++ b/src/channel/WebChannel.js @@ -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") ); } diff --git a/src/client/Network.js b/src/client/Network.js index e3f974279a..dea535cc4b 100644 --- a/src/client/Network.js +++ b/src/client/Network.js @@ -284,7 +284,7 @@ export default class Network extends ManagedNetwork { if (this._maxNodesPerTransaction > 0) { return this._maxNodesPerTransaction; } - return this._healthyNodes.length; + return this._nodes.length; } /** diff --git a/test/integration/dual-mode/NodeConstants.js b/test/integration/dual-mode/NodeConstants.js new file mode 100644 index 0000000000..3ae68eb312 --- /dev/null +++ b/test/integration/dual-mode/NodeConstants.js @@ -0,0 +1,10 @@ +const node2Address = "network-node2-svc.solo.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 }; diff --git a/test/integration/dual-mode/NodeUpdateIntegrationTest.js b/test/integration/dual-mode/NodeUpdateIntegrationTest.js index daf04e36aa..b841289b04 100644 --- a/test/integration/dual-mode/NodeUpdateIntegrationTest.js +++ b/test/integration/dual-mode/NodeUpdateIntegrationTest.js @@ -11,18 +11,35 @@ import { ServiceEndpoint, } from "../../../src/exports.js"; import { Client } from "../../../src/index.js"; - -describe("Node Update Integration Network Tests", function () { +import { + mirrorNetwork, + node2Address, + node2PortToReplace, + network, +} from "./NodeConstants.js"; + +const restoreOriginalGrpcWebProxyEndpoint = async (client) => { + const response = await new NodeUpdateTransaction() + .setNodeId(1) + .setNodeAccountIds([AccountId.fromString("0.0.3")]) + .setGrpcWebProxyEndpoint( + new ServiceEndpoint() + .setDomainName("envoy-proxy-node2-svc.solo.svc.cluster.local") + .setPort(8080), + ) + .execute(client); + const receipt = await response.getReceipt(client); + expect(receipt.status).to.equal(Status.Success); +}; + +describe("Node Update Integration Tests", function () { let client; let operatorAccountId; let operatorKey; beforeEach(function () { // Initialize client with integration network - client = Client.forNetwork({ - "127.0.0.1:50211": "0.0.3", - "127.0.0.1:51211": "0.0.4", - }).setMirrorNetwork(["localhost:5600"]); + client = Client.forNetwork(network).setMirrorNetwork(mirrorNetwork); // Set the operator to be account 0.0.2 operatorAccountId = AccountId.fromString("0.0.2"); @@ -41,8 +58,8 @@ describe("Node Update Integration Network Tests", function () { it("should execute node update transaction", async function () { const response = await new NodeUpdateTransaction() - .setNodeId(0) - .setNodeAccountIds([AccountId.fromString("0.0.4")]) + .setNodeId(1) + .setNodeAccountIds([AccountId.fromString("0.0.3")]) .setDescription("testUpdated") .setDeclineReward(true) .setGrpcWebProxyEndpoint( @@ -58,18 +75,23 @@ describe("Node Update Integration Network Tests", function () { it("should delete grpc web proxy endpoint", async function () { const response = await new NodeUpdateTransaction() - .setNodeId(0) + .setNodeId(1) + .setNodeAccountIds([AccountId.fromString("0.0.3")]) .deleteGrpcWebProxyEndpoint() .execute(client); const receipt = await response.getReceipt(client); expect(receipt.status).to.equal(Status.Success); + + // Restore the original grpc web proxy endpoint + await restoreOriginalGrpcWebProxyEndpoint(client); }); it("should change node account ID and revert back", async function () { // Change node account ID from 0.0.3 to 0.0.2 const response1 = await new NodeUpdateTransaction() .setNodeId(0) + .setNodeAccountIds([AccountId.fromString("0.0.4")]) .setAccountId(AccountId.fromString("0.0.2")) .execute(client); @@ -79,6 +101,7 @@ describe("Node Update Integration Network Tests", function () { // Revert the ID back to 0.0.3 const response2 = await new NodeUpdateTransaction() .setNodeId(0) + .setNodeAccountIds([AccountId.fromString("0.0.4")]) .setAccountId(AccountId.fromString("0.0.3")) .execute(client); @@ -115,10 +138,11 @@ describe("Node Update Integration Network Tests", function () { }); it("should change node account ID to the same account", async function () { + console.log(client.network); const response = await new NodeUpdateTransaction() - .setNodeId(0) - .setNodeAccountIds([AccountId.fromString("0.0.4")]) - .setAccountId(AccountId.fromString("0.0.3")) + .setNodeId(1) + .setNodeAccountIds([AccountId.fromString("0.0.3")]) + .setAccountId(AccountId.fromString("0.0.4")) .execute(client); const receipt = await response.getReceipt(client); @@ -271,7 +295,7 @@ describe("Node Update Integration Network Tests", function () { AccountId.fromString("0.0.3"), ]) .execute(client); - + console.log("tuka se ebava v maikata"); const testReceipt = await testResp.getReceipt(client); expect(testReceipt.status).to.equal(Status.Success); // Verify address book has been updated @@ -288,21 +312,19 @@ describe("Node Update Integration Network Tests", function () { )?.[0]; // Assert the address matches the expected value - expect(newNodeAddress).to.equal( - "network-node2-svc.solo.svc.cluster.local:50211", - ); + expect(newNodeAddress).to.equal(node2Address); // This is not an ideal workaround - reconstruct the network state // because the mirror node returns a different address than expected - if ( - newNodeAddress === "network-node2-svc.solo.svc.cluster.local:50211" - ) { + if (newNodeAddress === node2Address) { const oldNetworkState = { ...network }; delete oldNetworkState[newNodeAddress]; const newNetworkState = { ...oldNetworkState, - "network-node2-svc.solo.svc.cluster.local:51211": - newNodeAccountID, + [node2Address.replace( + node2Address.split(":")[1], + node2PortToReplace, + )]: newNodeAccountID, }; client.setNetwork(newNetworkState); } @@ -328,10 +350,7 @@ describe("Node Update Integration Network Tests", function () { it("should handle node account ID change without mirror node setup", async function () { // Create a client without mirror network - const networkClient = Client.forNetwork({ - "127.0.0.1:50211": "0.0.3", - "127.0.0.1:51211": "0.0.4", - }); + const networkClient = Client.forNetwork(network); networkClient.setOperator(operatorAccountId, operatorKey); diff --git a/test/integration/dual-mode/WebConstants.js b/test/integration/dual-mode/WebConstants.js new file mode 100644 index 0000000000..fdcf71a83a --- /dev/null +++ b/test/integration/dual-mode/WebConstants.js @@ -0,0 +1,10 @@ +const node2Address = "envoy-proxy-node2-svc.solo.svc.cluster.local:8080"; +const node2PortToReplace = 8081; +const network = { + "localhost:8080": "0.0.3", + "localhost:8081": "0.0.4", +}; + +const mirrorNetwork = ["localhost:5551"]; + +export { network, mirrorNetwork, node2Address, node2PortToReplace }; diff --git a/test/vitest-browser-integration-dual-mode.config.ts b/test/vitest-browser-integration-dual-mode.config.ts index 5534c4d05a..bbe3368e1d 100644 --- a/test/vitest-browser-integration-dual-mode.config.ts +++ b/test/vitest-browser-integration-dual-mode.config.ts @@ -22,10 +22,15 @@ export default defineConfig({ instances: [{ browser: "chromium" }], }, include: ["test/integration/dual-mode/**/*.js"], + exclude: [ + "test/integration/client/*", + "test/integration/resources/*", + "test/integration/utils/*", + "test/integration/dual-mode/NodeConstants.js", + "test/integration/dual-mode/WebConstants.js", + ], hookTimeout: 120000, testTimeout: 120000, - maxWorkers: 4, - minWorkers: 4, coverage: { include: ["src/**/*.js"], provider: "v8", @@ -79,7 +84,7 @@ export default defineConfig({ "../../src/LocalProvider.js": "../../src/LocalProviderWeb.js", "../src/LocalProvider.js": "../src/LocalProviderWeb.js", "src/LocalProvider.js": "src/LocalProviderWeb.js", - + "./NodeConstants.js": "./WebConstants.js", // Add more comprehensive aliases for NodeIntegrationTestEnv "NodeIntegrationTestEnv.js": "WebIntegrationTestEnv.js", "./client/NodeIntegrationTestEnv": "./client/WebIntegrationTestEnv", diff --git a/test/vitest-node-integration-dual-mode.config.ts b/test/vitest-node-integration-dual-mode.config.ts index 843724ea39..b14b65c87b 100644 --- a/test/vitest-node-integration-dual-mode.config.ts +++ b/test/vitest-node-integration-dual-mode.config.ts @@ -10,7 +10,6 @@ const pkg = JSON.parse( /** @type {import("vitest").UserConfig} */ export default defineConfig({ test: { - allowOnly: true, watch: false, globals: true, environment: "node", @@ -19,6 +18,8 @@ export default defineConfig({ "test/integration/client/*", "test/integration/resources/*", "test/integration/utils/*", + "test/integration/dual-mode/NodeConstants.js", + "test/integration/dual-mode/WebConstants.js", ], hookTimeout: 120000, testTimeout: 120000, From 67e80f40ec422157890572a1b348425ed7283542 Mon Sep 17 00:00:00 2001 From: venilinvasilev Date: Thu, 13 Nov 2025 16:47:55 +0200 Subject: [PATCH 03/10] ci: use latest solo action Signed-off-by: venilinvasilev --- .github/workflows/build.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 75e3349bb9..3a946a7854 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -209,12 +209,13 @@ jobs: - name: Prepare Hiero Solo id: solo - uses: hiero-ledger/hiero-solo-action@9471711c98a56179def6123e1040ab6c2e668881 # branch: 75-add-support-for-multiple-consensus-nodes + uses: hiero-ledger/hiero-solo-action@33f19f2eb8cbc49a61567a0781f3bc37bf2a32aa # support gualGrpxProxyPort with: installMirrorNode: true hieroVersion: v0.68.1-rc.1 mirrorNodeVersion: v0.142.0 dualMode: true + dualModeGrpcProxyPort: 8081 - name: Set Operator Account run: | From 7fdd41ca0b7642b9fd96ee488174d623180bac06 Mon Sep 17 00:00:00 2001 From: venilinvasilev Date: Thu, 13 Nov 2025 17:34:49 +0200 Subject: [PATCH 04/10] ci: add playwright install step Signed-off-by: venilinvasilev --- .github/workflows/build.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3a946a7854..42310db482 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -206,6 +206,13 @@ jobs: - 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 From 4795ddf6e9ef8e115da64e4696395ed46c0821be Mon Sep 17 00:00:00 2001 From: venilinvasilev Date: Thu, 13 Nov 2025 19:55:43 +0200 Subject: [PATCH 05/10] ci: add correct grpc proxy port Signed-off-by: venilinvasilev --- .github/workflows/build.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 42310db482..7e95af7e77 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -206,7 +206,7 @@ jobs: - 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() }} @@ -221,6 +221,7 @@ jobs: installMirrorNode: true hieroVersion: v0.68.1-rc.1 mirrorNodeVersion: v0.142.0 + grpcProxyPort: 8080 dualMode: true dualModeGrpcProxyPort: 8081 From 329b97b7ca35aaee25819a538089586f42b3a87a Mon Sep 17 00:00:00 2001 From: venilinvasilev Date: Mon, 17 Nov 2025 06:52:47 +0200 Subject: [PATCH 06/10] fix: make sure to extract solo namespace var Signed-off-by: venilinvasilev --- test/integration/dual-mode/NodeConstants.js | 3 ++- test/integration/dual-mode/NodeUpdateIntegrationTest.js | 6 +----- test/integration/dual-mode/SharedConstants.js | 3 +++ 3 files changed, 6 insertions(+), 6 deletions(-) create mode 100644 test/integration/dual-mode/SharedConstants.js diff --git a/test/integration/dual-mode/NodeConstants.js b/test/integration/dual-mode/NodeConstants.js index 3ae68eb312..0e2c13cb53 100644 --- a/test/integration/dual-mode/NodeConstants.js +++ b/test/integration/dual-mode/NodeConstants.js @@ -1,4 +1,5 @@ -const node2Address = "network-node2-svc.solo.svc.cluster.local:50211"; +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", diff --git a/test/integration/dual-mode/NodeUpdateIntegrationTest.js b/test/integration/dual-mode/NodeUpdateIntegrationTest.js index b841289b04..791c9836b1 100644 --- a/test/integration/dual-mode/NodeUpdateIntegrationTest.js +++ b/test/integration/dual-mode/NodeUpdateIntegrationTest.js @@ -23,9 +23,7 @@ const restoreOriginalGrpcWebProxyEndpoint = async (client) => { .setNodeId(1) .setNodeAccountIds([AccountId.fromString("0.0.3")]) .setGrpcWebProxyEndpoint( - new ServiceEndpoint() - .setDomainName("envoy-proxy-node2-svc.solo.svc.cluster.local") - .setPort(8080), + new ServiceEndpoint().setDomainName(node2Address).setPort(8080), ) .execute(client); const receipt = await response.getReceipt(client); @@ -138,7 +136,6 @@ describe("Node Update Integration Tests", function () { }); it("should change node account ID to the same account", async function () { - console.log(client.network); const response = await new NodeUpdateTransaction() .setNodeId(1) .setNodeAccountIds([AccountId.fromString("0.0.3")]) @@ -295,7 +292,6 @@ describe("Node Update Integration Tests", function () { AccountId.fromString("0.0.3"), ]) .execute(client); - console.log("tuka se ebava v maikata"); const testReceipt = await testResp.getReceipt(client); expect(testReceipt.status).to.equal(Status.Success); // Verify address book has been updated diff --git a/test/integration/dual-mode/SharedConstants.js b/test/integration/dual-mode/SharedConstants.js new file mode 100644 index 0000000000..8499329fe9 --- /dev/null +++ b/test/integration/dual-mode/SharedConstants.js @@ -0,0 +1,3 @@ +// This should be exactly the same as the namespace in the Hiero Solo action +// otherwise the DAB tests will fail, as domain names for nodes will not be resolved +export const SOLO_NAMESPACE = "solo"; From 67122f8f88e8b7a962257c83e83102f3196acc08 Mon Sep 17 00:00:00 2001 From: venilinvasilev Date: Mon, 17 Nov 2025 10:27:21 +0200 Subject: [PATCH 07/10] chore: exclude constants from tests Signed-off-by: venilinvasilev --- test/vitest-browser-integration-dual-mode.config.ts | 1 + test/vitest-node-integration-dual-mode.config.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/test/vitest-browser-integration-dual-mode.config.ts b/test/vitest-browser-integration-dual-mode.config.ts index bbe3368e1d..aa3db1b3cd 100644 --- a/test/vitest-browser-integration-dual-mode.config.ts +++ b/test/vitest-browser-integration-dual-mode.config.ts @@ -28,6 +28,7 @@ export default defineConfig({ "test/integration/utils/*", "test/integration/dual-mode/NodeConstants.js", "test/integration/dual-mode/WebConstants.js", + "test/integration/dual-mode/SharedConstants.js", ], hookTimeout: 120000, testTimeout: 120000, diff --git a/test/vitest-node-integration-dual-mode.config.ts b/test/vitest-node-integration-dual-mode.config.ts index b14b65c87b..931b17179f 100644 --- a/test/vitest-node-integration-dual-mode.config.ts +++ b/test/vitest-node-integration-dual-mode.config.ts @@ -20,6 +20,7 @@ export default defineConfig({ "test/integration/utils/*", "test/integration/dual-mode/NodeConstants.js", "test/integration/dual-mode/WebConstants.js", + "test/integration/dual-mode/SharedConstants.js", ], hookTimeout: 120000, testTimeout: 120000, From 50bd27261f57633aa57da4337e4a94ad6da1b674 Mon Sep 17 00:00:00 2001 From: venilinvasilev Date: Mon, 17 Nov 2025 10:52:27 +0200 Subject: [PATCH 08/10] chore: fix namespace interpolation Signed-off-by: venilinvasilev --- test/integration/dual-mode/WebConstants.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/integration/dual-mode/WebConstants.js b/test/integration/dual-mode/WebConstants.js index fdcf71a83a..c990a06d95 100644 --- a/test/integration/dual-mode/WebConstants.js +++ b/test/integration/dual-mode/WebConstants.js @@ -1,4 +1,5 @@ -const node2Address = "envoy-proxy-node2-svc.solo.svc.cluster.local:8080"; +import { SOLO_NAMESPACE } from "./SharedConstants.js"; +const node2Address = `envoy-proxy-node2-svc.${SOLO_NAMESPACE}.svc.cluster.local:8080`; const node2PortToReplace = 8081; const network = { "localhost:8080": "0.0.3", From 5e5fa1b3b14027cbb64605b2080ea8298b3a41b1 Mon Sep 17 00:00:00 2001 From: venilinvasilev Date: Mon, 17 Nov 2025 17:09:42 +0200 Subject: [PATCH 09/10] fix: endpoint set Signed-off-by: venilinvasilev --- test/integration/dual-mode/NodeUpdateIntegrationTest.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/integration/dual-mode/NodeUpdateIntegrationTest.js b/test/integration/dual-mode/NodeUpdateIntegrationTest.js index 791c9836b1..c4451482b9 100644 --- a/test/integration/dual-mode/NodeUpdateIntegrationTest.js +++ b/test/integration/dual-mode/NodeUpdateIntegrationTest.js @@ -23,7 +23,9 @@ const restoreOriginalGrpcWebProxyEndpoint = async (client) => { .setNodeId(1) .setNodeAccountIds([AccountId.fromString("0.0.3")]) .setGrpcWebProxyEndpoint( - new ServiceEndpoint().setDomainName(node2Address).setPort(8080), + new ServiceEndpoint() + .setDomainName(node2Address.split(":")[0]) + .setPort(Number(node2Address.split(":")[1])), ) .execute(client); const receipt = await response.getReceipt(client); From 4fc0eefe60a7af35759d62da8599d3cef05234e9 Mon Sep 17 00:00:00 2001 From: Ivaylo Nikolov Date: Fri, 28 Nov 2025 11:52:34 +0200 Subject: [PATCH 10/10] chore: bump solo action Signed-off-by: Ivaylo Nikolov --- .github/workflows/build.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7e95af7e77..e942576cd2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -108,7 +108,7 @@ jobs: - name: Prepare Hiero Solo id: solo - uses: hiero-ledger/hiero-solo-action@b76850c1ac44466900f8e7412b309c3aa0f539c1 # v0.12 + uses: hiero-ledger/hiero-solo-action@fbca3e7a99ce9aa8a250563a81187abe115e0dad # v0.16 with: installMirrorNode: true hieroVersion: v0.65.0 @@ -216,7 +216,7 @@ jobs: - name: Prepare Hiero Solo id: solo - uses: hiero-ledger/hiero-solo-action@33f19f2eb8cbc49a61567a0781f3bc37bf2a32aa # support gualGrpxProxyPort + uses: hiero-ledger/hiero-solo-action@fbca3e7a99ce9aa8a250563a81187abe115e0dad # 0.16.0 with: installMirrorNode: true hieroVersion: v0.68.1-rc.1 @@ -274,7 +274,7 @@ jobs: - name: Prepare Hiero Solo id: solo - uses: hiero-ledger/hiero-solo-action@b76850c1ac44466900f8e7412b309c3aa0f539c1 # v0.12 + uses: hiero-ledger/hiero-solo-action@fbca3e7a99ce9aa8a250563a81187abe115e0dad # v0.16 with: installMirrorNode: true hieroVersion: v0.65.0