Skip to content

Feature/hip 1299 changing node account#3

Open
emiliyank wants to merge 32 commits intomainfrom
feature/hip-1299-changing-node-account-id
Open

Feature/hip 1299 changing node account#3
emiliyank wants to merge 32 commits intomainfrom
feature/hip-1299-changing-node-account-id

Conversation

@emiliyank
Copy link
Copy Markdown
Owner

@emiliyank emiliyank commented Dec 2, 2025

Summary by CodeRabbit

  • New Features

    • Added a public method to refresh the client network from the address book.
  • Bug Fixes

    • Retry logic now advances to the next node on invalid-node responses, maps those responses to server errors, triggers address-book refreshes, and refines node health/backoff handling and logging.
  • Tests

    • Expanded unit and integration tests for invalid-node handling, node updates, retries, and address-book behavior.
  • Chores

    • CI workflow updated to standardize version strings, ensure consistent prepare steps, and skip a specific node-update check.

✏️ Tip: You can customize this high-level summary in your review settings.

…ccount-id' into feature/hip-1299-changing-node-account-id
…ccount-id' into feature/hip-1299-changing-node-account-id
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Dec 2, 2025

Walkthrough

Adds Client.updateNetworkFromAddressBook(); changes Executable retry/control flow to treat INVALID_NODE_ACCOUNT as SERVER_ERROR, advance to the next node, increase offending-node backoff, and schedule an address-book update; updates CI Hiero action and Gradle invocations; adds unit and extensive integration tests for node updates.

Changes

Cohort / File(s) Summary
CI / Build configuration
\.github/workflows/build.yml
Updated Prepare Hiero Solo action to hiero-solo-action@fbca3e7a99ce9aa8a250563a81187abe115e0dad (v0.16); quoted hieroVersion values; added mirrorNodeVersion entries; appended --rerun-tasks and -PskipNodeUpdateTest=true to test/gradle invocations; aligned command argument ordering.
Client network API
sdk/src/main/java/com/hedera/hashgraph/sdk/Client.java
Added public synchronized Client updateNetworkFromAddressBook() which computes address-book fileId for current shard/realm, runs an AddressBookQuery synchronously, calls setNetworkFromAddressBook(...), logs progress, handles TimeoutException/exceptions, and returns this.
Retry & execution flow
sdk/src/main/java/com/hedera/hashgraph/sdk/Executable.java
Treats INVALID_NODE_ACCOUNT as SERVER_ERROR and maps execution state accordingly; removed implicit early node advancement in request construction and added explicit advanceRequest() at retry points (connection failures, retryable errors, server errors, generic retries); schedule address-book update and call increaseBackoff for offending nodes; avoid decreaseBackoff on INVALID_NODE_ACCOUNT; extended handleResponse signature to include an optional Client; replicated changes in async paths; added TRACE-level guards and adjusted logging.
Unit tests (retry behavior)
sdk/src/test/java/com/hedera/hashgraph/sdk/ExecutableTest.java
Adjusted expectations for INVALID_NODE_ACCOUNT mapping to SERVER_ERROR; added imports and tests shouldMarkNodeAsUnusableOnInvalidNodeAccountId and shouldTriggerAddressBookUpdateOnInvalidNodeAccountId; verify increaseBackoff and address-book update scheduling behavior.
Integration tests (node updates)
sdk/src/testIntegration/.../NodeUpdateTransactionIntegrationTest.java
Added extensive HIP-1299 integration tests for NodeUpdateTransaction covering node account ID changes (signing scenarios, same-account, invalid/missing accounts, deleted accounts, address-book updates, cross-node retries); added helpers and AssertJ imports; some tests annotated @Disabled.

Sequence Diagram

sequenceDiagram
    participant Client
    participant Executable
    participant Node
    participant Network

    Client->>Executable: execute(request)
    Executable->>Node: send gRPC request
    alt Node returns INVALID_NODE_ACCOUNT
        Node-->>Executable: INVALID_NODE_ACCOUNT
        Executable->>Executable: map to SERVER_ERROR
        Executable->>Executable: advanceRequest()  %% switch to next node
        Executable->>Network: increaseBackoff(offendingNode)
        par async address-book update
            Executable->>Client: schedule updateNetworkFromAddressBook()
            Client->>Client: perform AddressBookQuery
            Client->>Network: setNetworkFromAddressBook(...)
        and immediate retry on next node
            Executable->>Node: retry request on next node
        end
    else Node returns success/other
        Node-->>Executable: response / status
        Executable->>Client: handleResponse(..., Client) and return result
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

  • Inspect sync vs async parity and all updated handleResponse(..., Client) call sites in Executable.java.
  • Verify correct placement of advanceRequest() to avoid double-advancing or skipped nodes.
  • Confirm backoff increase/decrease semantics and that decreaseBackoff() is not applied for INVALID_NODE_ACCOUNT.
  • Review thread-safety and race conditions around updateNetworkFromAddressBook() and setNetworkFromAddressBook(...).
  • Validate new integration tests for setup/teardown correctness and potential flakiness.

Poem

🐰
I nudged the nodes that would not play,
Skipped to the next and cleared the way.
I fetched the book and fixed the map,
Retries now land without mishap.
A little hop — the network's gay.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 21.43% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Feature/hip 1299 changing node account' directly references HIP-1299 and the core change (changing node account), accurately reflecting the main purpose of this feature pull request.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/hip-1299-changing-node-account-id

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e562e98 and 42af3f3.

📒 Files selected for processing (1)
  • .github/workflows/build.yml (4 hunks)
🔇 Additional comments (4)
.github/workflows/build.yml (4)

179-179: Version consistency: Hiero Solo Action updated uniformly.

All three workflow sections (test, test-dab, run-examples) have been consistently updated to use hiero-solo-action@fbca3e7a99ce9aa8a250563a81187abe115e0dad # v0.16. The uniform commit hash confirms intentional, coordinated updates across the CI pipeline.

Also applies to: 248-248, 304-304


182-182: YAML best practice: Version strings consistently quoted.

All hieroVersion values are now enclosed in quotes ("v0.69.0-alpha.1", "v0.68.0"), which is consistent with YAML best practices for version identifiers containing periods and special characters. This prevents potential parsing ambiguities.

Also applies to: 252-252, 307-307


253-253: Mirror node version explicitly declared for reproducibility.

The addition of mirrorNodeVersion: "v0.142.0" in both the test-dab (dual-mode) and run-examples sections improves reproducibility by explicitly pinning mirror node versions alongside the Hiero versions. The consistent version string across both sections ensures test environment uniformity.

Also applies to: 308-308


200-200: Test isolation: Node update tests separated from main test suite.

The addition of -PskipNodeUpdateTest=true indicates that node update tests are intentionally excluded from the main test job and isolated to the dedicated test-dab job (line 268-269), which runs with dual-mode Hiero configuration. This separation is appropriate for environment-sensitive tests that require specialized infrastructure setup, and aligns with the PR's focus on node account handling.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (4)
sdk/src/main/java/com/hedera/hashgraph/sdk/Client.java (1)

1433-1436: Consider propagating InterruptedException properly.

When catching Exception broadly, if an InterruptedException occurs (which can be wrapped in TimeoutException or thrown directly), the thread's interrupted status should be restored:

     } catch (TimeoutException e) {
         logger.warn("Failed to fetch address book: {}", e.getMessage());
+    } catch (InterruptedException e) {
+        logger.warn("Address book update interrupted: {}", e.getMessage());
+        Thread.currentThread().interrupt();
     } catch (Exception e) {
         logger.warn("Failed to update address book", e);
     }
sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/NodeUpdateTransactionIntegrationTest.java (3)

4-8: Remove redundant static imports.

Lines 5-6 are redundant since line 4 already imports all static members from Assertions. Also, consider using explicit imports instead of the wildcard on line 8 for better readability.

-import static org.assertj.core.api.Assertions.*;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatCode;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;

199-201: Consider using a polling mechanism instead of fixed sleep.

The 10-second Thread.sleep() for mirror node synchronization is brittle. Consider using a polling/retry mechanism with a shorter interval that checks if the data is available, up to a maximum timeout. This would make tests faster when the mirror node syncs quickly and more reliable when it takes longer.

Example approach:

// Poll for mirror node sync with exponential backoff
int maxAttempts = 20;
for (int i = 0; i < maxAttempts; i++) {
    try {
        // Try operation that depends on mirror node data
        break;
    } catch (Exception e) {
        if (i == maxAttempts - 1) throw e;
        Thread.sleep(500 * (1 << Math.min(i, 4))); // 500ms, 1s, 2s, 4s, 8s...
    }
}

68-125: Document why this test is disabled.

The @Disabled annotation should include a reason explaining why the test is disabled and what needs to be done to enable it. This helps future maintainers understand the test's status.

 @Test
-@Disabled
+@Disabled("Requires proper node admin key setup - see HIP-1299 implementation")
 @DisplayName(
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ff1a4fd and 073a3b3.

📒 Files selected for processing (5)
  • .github/workflows/build.yml (3 hunks)
  • sdk/src/main/java/com/hedera/hashgraph/sdk/Client.java (1 hunks)
  • sdk/src/main/java/com/hedera/hashgraph/sdk/Executable.java (12 hunks)
  • sdk/src/test/java/com/hedera/hashgraph/sdk/ExecutableTest.java (3 hunks)
  • sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/NodeUpdateTransactionIntegrationTest.java (2 hunks)
🔇 Additional comments (9)
.github/workflows/build.yml (1)

202-203: LGTM - Test configuration flags added appropriately.

The --rerun-tasks flag ensures consistent test execution, and -PskipNodeUpdateTest=true correctly excludes node update tests from the standard test job since they require the DAB (Dynamic Address Book) environment tested separately.

sdk/src/test/java/com/hedera/hashgraph/sdk/ExecutableTest.java (2)

533-562: LGTM - Good coverage for INVALID_NODE_ACCOUNT handling.

The test properly validates that:

  1. INVALID_NODE_ACCOUNT triggers retry behavior across multiple nodes
  2. network.increaseBackoff() is called to mark nodes as unhealthy
  3. Eventually throws MaxAttemptsExceededException when all nodes fail

564-592: Test correctly documents async limitations.

The comment on lines 590-591 appropriately acknowledges that the async address book update cannot be easily verified in this unit test context. The integration tests provide that coverage.

sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/NodeUpdateTransactionIntegrationTest.java (1)

527-624: LGTM - Comprehensive integration test for network configuration updates.

This test effectively validates the end-to-end flow:

  1. Initial network configuration verification
  2. Node account ID update via NodeUpdateTransaction
  3. SDK handling of INVALID_NODE_ACCOUNT with retry
  4. Verification that subsequent transactions work with the new node account ID
  5. Proper cleanup by reverting the node account ID
sdk/src/main/java/com/hedera/hashgraph/sdk/Executable.java (5)

446-455: LGTM - Consistent INVALID_NODE_ACCOUNT handling.

The implementation correctly:

  1. Advances to the next node first
  2. Logs the issue at TRACE level
  3. Triggers address book update for network refresh
  4. Marks the node as unhealthy via increaseBackoff()

This aligns with the Go SDK's behavior for executionStateRetryWithAnotherNode.


792-803: Consider async address book update to avoid blocking.

In the async execution path, client.updateNetworkFromAddressBook() is a synchronous call that performs network I/O. While the method internally catches exceptions, it could still block the async flow briefly.

Since the method already logs and swallows errors, this is acceptable for now, but for improved async purity, consider adding an async variant (updateNetworkFromAddressBookAsync()) in the future.


903-906: LGTM - Correct execution state mapping.

Mapping INVALID_NODE_ACCOUNT to SERVER_ERROR ensures immediate retry with the next node without delay, which is the correct behavior when a node's account ID has changed.


1011-1016: LGTM - Correctly skips backoff decrease for invalid nodes.

Not calling decreaseBackoff() for INVALID_NODE_ACCOUNT is correct because:

  1. The node should remain marked as unhealthy
  2. The actual backoff increase happens after advanceRequest() in the calling code

This matches the comment explaining that INVALID_NODE_ACCOUNT handling happens after advancement.


698-702: Good documentation of the design change.

The comment clearly explains that node advancement now happens explicitly in retry logic after error detection, rather than automatically in getRequestForExecute(). This helps future maintainers understand the flow.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (3)
sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/NodeUpdateTransactionIntegrationTest.java (3)

4-8: Remove redundant explicit imports.

The wildcard import on line 4 (import static org.assertj.core.api.Assertions.*;) already includes assertThat and assertThatThrownBy, making lines 5-6 redundant.

Similarly, line 8 uses a wildcard SDK import which is generally discouraged as it can lead to namespace pollution and makes it harder to track dependencies.

-import static org.assertj.core.api.Assertions.*;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatCode;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;

-import com.hedera.hashgraph.sdk.*;
+import com.hedera.hashgraph.sdk.AccountCreateTransaction;
+import com.hedera.hashgraph.sdk.AccountDeleteTransaction;
+import com.hedera.hashgraph.sdk.AccountId;
+import com.hedera.hashgraph.sdk.Client;
+import com.hedera.hashgraph.sdk.Endpoint;
+import com.hedera.hashgraph.sdk.Hbar;
+import com.hedera.hashgraph.sdk.Key;
+import com.hedera.hashgraph.sdk.NodeUpdateTransaction;
+import com.hedera.hashgraph.sdk.PrivateKey;
+import com.hedera.hashgraph.sdk.ReceiptStatusException;
+import com.hedera.hashgraph.sdk.Status;

199-200: Consider replacing fixed Thread.sleep with polling.

Fixed 10-second sleeps (also at lines 486 and 547) waiting for mirror node synchronization can cause flaky tests or unnecessary delays. Consider implementing a polling mechanism that checks for the expected state with a timeout.

For example, a helper method that polls until the address book reflects the change:

private void waitForMirrorNodeSync(Client client, AccountId expectedNodeAccountId, Duration timeout) 
    throws Exception {
    var deadline = Instant.now().plus(timeout);
    while (Instant.now().isBefore(deadline)) {
        try {
            client.updateNetworkFromAddressBook();
            if (client.getNetwork().containsValue(expectedNodeAccountId)) {
                return;
            }
        } catch (Exception ignored) {}
        Thread.sleep(1000);
    }
    throw new TimeoutException("Mirror node did not sync within timeout");
}

70-74: Add explanation for why test is disabled.

The test shouldSucceedWhenUpdatingNodeAccountIdWithProperSignatures is marked @Disabled without any explanation (unlike line 346-348 which has a TODO comment). Consider adding a comment explaining why this test is disabled and what needs to change for it to be enabled.

 @Test
 @Disabled
+// TODO - Test requires node admin key to be available for signing. Currently blocked on local network setup.
 @DisplayName(
     "Given a node with an existing account ID, when a NodeUpdateTransaction is submitted to change the account ID to a new valid account with signatures from both the node admin key and the account ID key, then the transaction succeeds")
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 073a3b3 and 45410b7.

📒 Files selected for processing (2)
  • sdk/src/main/java/com/hedera/hashgraph/sdk/Client.java (1 hunks)
  • sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/NodeUpdateTransactionIntegrationTest.java (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/NodeUpdateTransactionIntegrationTest.java (2)
sdk/src/main/java/com/hedera/hashgraph/sdk/Client.java (1)
  • Client (39-1677)
sdk/src/main/java/com/hedera/hashgraph/sdk/PrivateKey.java (1)
  • PrivateKey (20-460)
sdk/src/main/java/com/hedera/hashgraph/sdk/Client.java (1)
sdk/src/main/java/com/hedera/hashgraph/sdk/FileId.java (1)
  • FileId (14-304)
🔇 Additional comments (3)
sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/NodeUpdateTransactionIntegrationTest.java (3)

159-241: Good coverage of INVALID_NODE_ACCOUNT retry flow.

This test comprehensively validates the SDK's ability to detect INVALID_NODE_ACCOUNT errors, retry with alternate nodes, and successfully complete transactions. The cleanup at the end to revert the node account ID helps ensure test isolation.


568-602: Good use of helper methods to reduce test duplication.

The extracted helper methods (createAccount, updateNodeAccountId, executeAccountCreate) effectively reduce code duplication across test methods. This improves maintainability and makes the test intent clearer.


527-566: Validates address book update integration correctly.

This test verifies the critical behavior that after encountering INVALID_NODE_ACCOUNT, the SDK updates its network configuration with the latest node account IDs. The assertion at lines 556-561 that checks client.getNetwork() contains the new account ID directly validates the updateNetworkFromAddressBook() integration.

Comment on lines +1407 to +1437
/**
* Trigger an immediate address book update to refresh the client's network with the latest node information.
* This is useful when encountering INVALID_NODE_ACCOUNT_ID errors to ensure subsequent transactions
* use the correct node account IDs.
*
* @return {@code this}
*/
public synchronized Client updateNetworkFromAddressBook() {
try {
var fileId = FileId.getAddressBookFileIdFor(this.shard, this.realm);

logger.debug("Fetching address book from file {}", fileId);

// Execute synchronously - no async complexity
var addressBook = new AddressBookQuery().setFileId(fileId).execute(this); // ← Synchronous!

logger.debug("Received address book with {} nodes", addressBook.nodeAddresses.size());

// Update the network
this.setNetworkFromAddressBook(addressBook);

logger.info("Address book update completed successfully");

} catch (TimeoutException e) {
logger.warn("Failed to fetch address book: {}", e.getMessage());
} catch (Exception e) {
logger.warn("Failed to update address book", e);
}

return this;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

InterruptedException handling may lose thread interrupt status.

The setNetworkFromAddressBook method declares throws InterruptedException, but when caught by the generic Exception catch block at line 1432, the thread's interrupt status is not restored. This can cause issues in concurrent environments where interrupt propagation is expected.

Consider either:

  1. Catching InterruptedException explicitly and restoring the interrupt status
  2. Or re-throwing as an unchecked exception
     } catch (TimeoutException e) {
         logger.warn("Failed to fetch address book: {}", e.getMessage());
+    } catch (InterruptedException e) {
+        Thread.currentThread().interrupt();
+        logger.warn("Address book update interrupted", e);
     } catch (Exception e) {
         logger.warn("Failed to update address book", e);
     }
🤖 Prompt for AI Agents
In sdk/src/main/java/com/hedera/hashgraph/sdk/Client.java around lines 1407 to
1437, the generic Exception catch will swallow InterruptedException thrown by
setNetworkFromAddressBook and fail to restore the thread's interrupt status;
explicitly catch InterruptedException before the generic catch, call
Thread.currentThread().interrupt() to restore the interrupt status, log or
wrap/rethrow if needed, and then return (or rethrow as an unchecked exception)
so the interrupt is not lost; keep the existing TimeoutException and generic
Exception handlers after this new InterruptedException handler.

@emiliyank emiliyank force-pushed the feature/hip-1299-changing-node-account-id branch from 45410b7 to ddadcde Compare December 2, 2025 11:18
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
sdk/src/main/java/com/hedera/hashgraph/sdk/Client.java (1)

1430-1434: InterruptedException handling still not addressed.

The setNetworkFromAddressBook method at line 1426 declares throws InterruptedException, but when caught by the generic Exception catch block at line 1432, the thread's interrupt status is not restored. This was flagged in a previous review and remains unaddressed.

Apply this diff to properly handle InterruptedException:

     } catch (TimeoutException e) {
         logger.warn("Failed to fetch address book: {}", e.getMessage());
+    } catch (InterruptedException e) {
+        Thread.currentThread().interrupt();
+        logger.warn("Address book update interrupted", e);
     } catch (Exception e) {
         logger.warn("Failed to update address book", e);
     }
sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/NodeUpdateTransactionIntegrationTest.java (1)

193-196: Remove debug System.out.println statements.

These debug print statements should be removed from the test code. This was flagged in a previous review and remains unaddressed.

-            System.out.println("Transaction node: " + resp.nodeId);
-            System.out.println("Receipt query nodes: " + resp.getReceiptQuery().getNodeAccountIds());
-            System.out.println("Client network: " + client.getNetwork());
             receipt = resp.setValidateStatus(true).getReceipt(client);
🧹 Nitpick comments (2)
sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/NodeUpdateTransactionIntegrationTest.java (1)

4-6: Remove redundant imports.

Line 4 uses a wildcard import import static org.assertj.core.api.Assertions.*; which already includes assertThat and assertThatThrownBy. Lines 5-6 are redundant.

-import static org.assertj.core.api.Assertions.*;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatCode;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;

Alternatively, keep only the wildcard import if you prefer brevity.

sdk/src/main/java/com/hedera/hashgraph/sdk/Executable.java (1)

789-812: Async path also uses synchronous address book update.

Similar to the sync path, the updateNetworkFromAddressBook() call at line 802 is synchronous. In an async context, this blocks the CompletableFuture chain. Consider whether this should use an async variant (e.g., CompletableFuture.runAsync(() -> client.updateNetworkFromAddressBook(), client.executor)).

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 45410b7 and ddadcde.

📒 Files selected for processing (3)
  • sdk/src/main/java/com/hedera/hashgraph/sdk/Client.java (1 hunks)
  • sdk/src/main/java/com/hedera/hashgraph/sdk/Executable.java (12 hunks)
  • sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/NodeUpdateTransactionIntegrationTest.java (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/NodeUpdateTransactionIntegrationTest.java (3)
sdk/src/main/java/com/hedera/hashgraph/sdk/Client.java (1)
  • Client (39-1677)
sdk/src/main/java/com/hedera/hashgraph/sdk/PrivateKey.java (1)
  • PrivateKey (20-460)
sdk/src/main/java/com/hedera/hashgraph/sdk/Hbar.java (1)
  • Hbar (16-253)
sdk/src/main/java/com/hedera/hashgraph/sdk/Client.java (1)
sdk/src/main/java/com/hedera/hashgraph/sdk/FileId.java (1)
  • FileId (14-304)
🔇 Additional comments (10)
sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/NodeUpdateTransactionIntegrationTest.java (2)

568-602: LGTM!

Good extraction of helper methods to reduce code duplication across test methods. The createAccount, updateNodeAccountId, and executeAccountCreate helpers improve test readability and maintainability.


70-125: LGTM!

Well-structured test with clear Given/When/Then documentation. The test properly verifies that updating a node's account ID with correct signatures succeeds.

sdk/src/main/java/com/hedera/hashgraph/sdk/Executable.java (8)

405-408: LGTM!

Correctly advances to the next node before retrying after a connection failure. This ensures the retry uses a different node.


427-434: LGTM!

Proper advancement to next node before retrying when an exception requires retry.


460-465: LGTM!

Correctly advances request after applying delay in the RETRY case.


695-703: LGTM!

Good documentation clarifying that advanceRequest() is now called explicitly in the retry logic after error detection, rather than automatically in getRequestForExecute().


743-753: LGTM!

Async path correctly advances to the next node before retrying after connection failure, consistent with the sync path.


905-914: LGTM!

Correct mapping of INVALID_NODE_ACCOUNT to SERVER_ERROR execution state. This ensures the SDK immediately retries with the next node when a node's account ID has changed, as per HIP-1299.


1013-1018: LGTM!

Correct handling: for INVALID_NODE_ACCOUNT, the node's backoff is not decreased because it will be marked as unhealthy. For other statuses, backoff is decreased as before.


1045-1056: LGTM!

Correctly suppresses the SERVER_ERROR warning for INVALID_NODE_ACCOUNT since it's handled with TRACE-level logging in the caller after advanceRequest().

@emiliyank emiliyank force-pushed the feature/hip-1299-changing-node-account-id branch from ddadcde to cda1929 Compare December 2, 2025 12:09
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
sdk/src/main/java/com/hedera/hashgraph/sdk/Executable.java (1)

790-812: Synchronous call in async path may block executor thread.

The updateNetworkFromAddressBook() call at line 802 is synchronous and will block the executor thread while fetching the address book. In the async execution path, this could impact throughput if multiple requests encounter INVALID_NODE_ACCOUNT simultaneously.

Also, the comment at line 801 incorrectly states "async".

Consider making this truly async in the async path:

-                                            // Schedule async address book update
-                                            client.updateNetworkFromAddressBook();
+                                            // Update address book asynchronously to avoid blocking
+                                            CompletableFuture.runAsync(
+                                                client::updateNetworkFromAddressBook,
+                                                client.executor
+                                            );

Alternatively, if synchronous behavior is intentional, update the comment to reflect this.

♻️ Duplicate comments (3)
sdk/src/main/java/com/hedera/hashgraph/sdk/Client.java (1)

1407-1437: InterruptedException handling still loses thread interrupt status.

The setNetworkFromAddressBook method declares throws InterruptedException (line 539), but when caught by the generic Exception catch block at line 1432, the thread's interrupt status is not restored. This was flagged in a previous review but hasn't been addressed.

Apply this diff to properly handle InterruptedException:

     } catch (TimeoutException e) {
         logger.warn("Failed to fetch address book: {}", e.getMessage());
+    } catch (InterruptedException e) {
+        Thread.currentThread().interrupt();
+        logger.warn("Address book update interrupted", e);
     } catch (Exception e) {
         logger.warn("Failed to update address book", e);
     }
sdk/src/main/java/com/hedera/hashgraph/sdk/Executable.java (1)

440-456: Address book update is synchronous, not async.

The comment at line 451 says "Schedule async address book update" but updateNetworkFromAddressBook() is synchronous (blocks on network round-trip). This was flagged in a previous review.

Update the comment to reflect the synchronous nature:

-                    // Schedule async address book update
+                    // Synchronously update address book (blocks on network call)
                     client.updateNetworkFromAddressBook();
sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/NodeUpdateTransactionIntegrationTest.java (1)

180-182: Remove debug System.out.println statements.

These debug print statements should be removed from test code. Use the test framework's logging or assertions if this information is needed for debugging.

-            System.out.println("Transaction node: " + resp.nodeId);
-            System.out.println("Receipt query nodes: " + resp.getReceiptQuery().getNodeAccountIds());
-            System.out.println("Client network: " + client.getNetwork());
             var receipt = resp.setValidateStatus(true).getReceipt(client);
🧹 Nitpick comments (2)
sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/NodeUpdateTransactionIntegrationTest.java (2)

4-6: Redundant static imports.

Line 4 uses a wildcard import import static org.assertj.core.api.Assertions.*; which already includes assertThat and assertThatThrownBy. Lines 5 and 6 are redundant.

-import static org.assertj.core.api.Assertions.*;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatCode;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;

Alternatively, keep only the wildcard import if you prefer brevity.


186-188: Consider replacing fixed sleep with polling mechanism.

Thread.sleep(10000) is a brittle approach for waiting on mirror node data import. The timing may vary across environments, leading to flaky tests.

Consider implementing a polling mechanism with timeout, or extracting this into a reusable helper method that polls for the expected state. This pattern appears in multiple tests (lines 187, 437, 480).

private void waitForMirrorNodeSync(Client client, Duration timeout) throws Exception {
    var deadline = Instant.now().plus(timeout);
    while (Instant.now().isBefore(deadline)) {
        try {
            // Attempt to verify mirror node has synced
            client.updateNetworkFromAddressBook();
            return;
        } catch (Exception e) {
            Thread.sleep(1000);
        }
    }
    throw new TimeoutException("Mirror node did not sync within timeout");
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ddadcde and cda1929.

📒 Files selected for processing (3)
  • sdk/src/main/java/com/hedera/hashgraph/sdk/Client.java (1 hunks)
  • sdk/src/main/java/com/hedera/hashgraph/sdk/Executable.java (12 hunks)
  • sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/NodeUpdateTransactionIntegrationTest.java (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/NodeUpdateTransactionIntegrationTest.java (3)
sdk/src/main/java/com/hedera/hashgraph/sdk/Client.java (1)
  • Client (39-1677)
sdk/src/main/java/com/hedera/hashgraph/sdk/PrivateKey.java (1)
  • PrivateKey (20-460)
sdk/src/main/java/com/hedera/hashgraph/sdk/Hbar.java (1)
  • Hbar (16-253)
🔇 Additional comments (6)
sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/NodeUpdateTransactionIntegrationTest.java (2)

501-535: Good test helper extraction.

The createAccount, updateNodeAccountId, and executeAccountCreate helper methods effectively reduce code duplication and improve test readability.


68-118: Test appropriately disabled with clear reasoning.

The test shouldSucceedWhenUpdatingNodeAccountIdWithProperSignatures is marked @Disabled, which is appropriate while the underlying functionality is being implemented or debugged. The test structure and assertions look correct.

sdk/src/main/java/com/hedera/hashgraph/sdk/Executable.java (4)

905-908: Correct mapping of INVALID_NODE_ACCOUNT to SERVER_ERROR.

Mapping INVALID_NODE_ACCOUNT to SERVER_ERROR triggers immediate retry with the next node, which is the correct behavior when a node's account ID has changed. The comment clearly explains the rationale.


1013-1056: Well-structured handling of INVALID_NODE_ACCOUNT in response processing.

The changes correctly:

  1. Skip decreaseBackoff for INVALID_NODE_ACCOUNT since the node will be marked unhealthy elsewhere
  2. Suppress redundant warning logs for INVALID_NODE_ACCOUNT in the SERVER_ERROR case
  3. Add clear comments explaining the behavior alignment with Go SDK

695-703: Good refactoring of node advancement logic.

Moving advanceRequest() calls to explicit retry points after error detection (rather than in getRequestForExecute) is a cleaner approach that makes the control flow more predictable.


406-407: Consistent node advancement on connection failure.

Adding advanceRequest() after connection failure detection ensures the next retry uses a different node. This pattern is applied consistently across all retry paths.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant