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

FlowBlockSeal protobuf updates #122

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
Open

FlowBlockSeal protobuf updates #122

wants to merge 10 commits into from

Conversation

lealobanov
Copy link
Collaborator

@lealobanov lealobanov commented Oct 20, 2024

Closes: #112

Description

  • Add missing protobuf fields to FlowBlockSeal model
  • Create FlowAggregateSignature model
  • Add unit tests for FlowBlockSeal class (none existed on this class before)
  • Add unit tests for FlowAggregateSignature class

For contributor use:

  • Targeted PR against master branch
  • Linked to Github issue with discussion and accepted design OR link to spec that describes this work.
  • Code follows the standards mentioned here.
  • Updated relevant documentation
  • Re-reviewed Files changed in the Github PR explorer
  • Added appropriate labels

Summary by CodeRabbit

  • New Features

    • Introduced a new FlowAggregatedSignature class for enhanced signature aggregation.
    • Updated FlowBlockSeal class with new properties for improved data structure, including blockId, finalState, resultId, and aggregatedApprovalSigs.
    • Added methods to retrieve account balances at the latest block and at a specific block height in both the FlowAccessApi and AsyncFlowAccessApi.
  • Bug Fixes

    • Ensured proper handling of property names and values in FlowBlockSeal.
  • Tests

    • Added comprehensive unit tests for FlowAggregatedSignature and FlowBlockSeal to validate functionality and integrity, including equality checks and builder functionality.
    • Enhanced test coverage for account balance retrieval methods, ensuring both success and failure scenarios are adequately tested.

Copy link

coderabbitai bot commented Oct 20, 2024

Walkthrough

The changes involve modifications to the FlowBlockSeal data class, including property renaming and the addition of new properties. A new FlowAggregatedSignature data class has been introduced. Unit tests for both classes have been added to ensure their functionality, including equality checks, conversion from protobuf representations, and builder pattern verification. Additionally, new methods for retrieving account balances have been added to relevant API classes, along with corresponding tests.

Changes

File Change Summary
sdk/src/main/kotlin/org/onflow/flow/sdk/models.kt - Renamed id to blockId in FlowBlockSeal.
- Added finalState, resultId, and aggregatedApprovalSigs to FlowBlockSeal.
- Introduced FlowAggregatedSignature class with verifierSignatures and signerIds.
sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowAggregatedSignatureTest.kt - Added unit tests for FlowAggregatedSignature, covering equality, functionality, and builder methods.
sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowBlockSealTest.kt - Added unit tests for FlowBlockSeal, focusing on equality, protobuf conversion, and builder methods.
sdk/src/main/kotlin/org/onflow/flow/sdk/AsyncFlowAccessApi.kt - Added methods for retrieving account balance at latest block and at specific block height.
sdk/src/main/kotlin/org/onflow/flow/sdk/FlowAccessApi.kt - Added methods for retrieving account balance at latest block and at specific block height.
sdk/src/main/kotlin/org/onflow/flow/sdk/impl/AsyncFlowAccessApiImpl.kt - Implemented methods for retrieving account balance at latest block and at specific block height.
sdk/src/main/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImpl.kt - Implemented methods for retrieving account balance at latest block and at specific block height.
sdk/src/test/kotlin/org/onflow/flow/sdk/FlowAccessApiTest.kt - Added tests for success and failure scenarios of account balance retrieval methods.
sdk/src/test/kotlin/org/onflow/flow/sdk/impl/AsyncFlowAccessApiImplTest.kt - Added tests for success and failure scenarios of account balance retrieval methods.
sdk/src/test/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImplTest.kt - Added tests for success and failure scenarios of account balance retrieval methods.

Assessment against linked issues

Objective Addressed Explanation
Ensure FlowBlockSeal matches protobuf schema (#112)

Possibly related PRs

  • Access API refactoring + exception handling #55: This PR introduces new methods in the AsyncFlowAccessApi and FlowAccessApiImpl classes that enhance the API's capabilities, which may relate to the changes in the FlowBlockSeal and FlowAggregatedSignature classes in the main PR by providing additional context for transaction handling.
  • Extend signing transactions with both elliptic curves #77: This PR enhances transaction signing capabilities, which is directly related to the changes in the FlowBlockSeal and FlowAggregatedSignature classes, as both involve transaction handling and signatures.
  • Examples use-cases - get blocks #80: This PR introduces functionality for retrieving blocks, which may relate to the FlowBlockSeal changes in the main PR, as both deal with block-related data.

Suggested reviewers

  • jribbink

🐰 In the code I hop and play,
New signatures brighten the day.
Block seals now align just right,
With tests to keep the code in sight!
Hooray for changes, let’s celebrate,
In the flow of code, we elevate! 🌟


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

github-actions bot commented Oct 20, 2024

Unit Test Results

  62 files  +  2    62 suites  +2   24s ⏱️ ±0s
358 tests +18  358 ✔️ +19  0 💤 ±0  0 ❌  - 1 

Results for commit 5e02ad8. ± Comparison against base commit db9e3d1.

♻️ This comment has been updated with latest results.

Copy link

github-actions bot commented Oct 20, 2024

Integration Test Results

0 files  ±0  0 suites  ±0   0s ⏱️ ±0s
0 tests ±0  0 ✔️ ±0  0 💤 ±0  0 ❌ ±0 

Results for commit b7308f1. ± Comparison against base commit db9e3d1.

♻️ This comment has been updated with latest results.

Copy link

github-actions bot commented Oct 20, 2024

SDK Examples Unit Test Results

37 files  37 suites   6m 18s ⏱️
70 tests 70 ✔️ 0 💤 0 ❌

Results for commit 1f4ab77.

♻️ This comment has been updated with latest results.

Copy link

@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

🧹 Outside diff range and nitpick comments (7)
sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowAggregatedSignatureTest.kt (3)

44-66: LGTM: Comprehensive test for of function with a minor suggestion

This test effectively verifies the FlowAggregatedSignature.of function by mocking the protobuf object and checking the conversion result. It covers both the size and content of the resulting lists.

Consider adding a test case with multiple signatures and signer IDs to ensure the function handles lists correctly:

Mockito.`when`(aggregatedSignatureProto.verifierSignaturesList).thenReturn(
    listOf(
        com.google.protobuf.ByteString.copyFrom("signature1".toByteArray()),
        com.google.protobuf.ByteString.copyFrom("signature2".toByteArray())
    )
)
Mockito.`when`(aggregatedSignatureProto.signerIdsList).thenReturn(
    listOf(
        com.google.protobuf.ByteString.copyFrom("signerId1".toByteArray()),
        com.google.protobuf.ByteString.copyFrom("signerId2".toByteArray())
    )
)

val flowAggregatedSignature = FlowAggregatedSignature.of(aggregatedSignatureProto)

assertEquals(2, flowAggregatedSignature.verifierSignatures.size)
assertEquals(2, flowAggregatedSignature.signerIds.size)
// Add assertions for individual elements

68-82: LGTM: Good test for builder function with suggestions for improvement

This test effectively verifies the builder() function of the FlowAggregatedSignature class. It checks that the builder produces the correct ByteString representations of the original data.

Consider enhancing the test with the following improvements:

  1. Add a more complex case with multiple signatures and signer IDs:
val signature1 = FlowSignature("signature1".toByteArray())
val signature2 = FlowSignature("signature2".toByteArray())
val signerId1 = FlowId.of("signerId1".toByteArray())
val signerId2 = FlowId.of("signerId2".toByteArray())

val aggregatedSignature = FlowAggregatedSignature(
    verifierSignatures = listOf(signature1, signature2),
    signerIds = listOf(signerId1, signerId2)
)

val builderResult = aggregatedSignature.builder()

assertEquals(listOf(signature1.byteStringValue, signature2.byteStringValue), builderResult.verifierSignaturesList)
assertEquals(listOf(signerId1.byteStringValue, signerId2.byteStringValue), builderResult.signerIdsList)
  1. Add a test for deep equality by converting the builder result back to a FlowAggregatedSignature:
val reconstructedSignature = FlowAggregatedSignature.of(builderResult.build())
assertEquals(aggregatedSignature, reconstructedSignature)

These additions will provide more comprehensive coverage of the builder() function and ensure that the round-trip conversion works correctly.


1-11: Consider adding more test cases and class documentation

The test class provides good coverage of the main functionalities of FlowAggregatedSignature. To further improve the test suite, consider the following suggestions:

  1. Add a brief documentation comment for the class explaining its purpose:
/**
 * Unit tests for the FlowAggregatedSignature class.
 * These tests cover equality, hash code, conversion from protobuf, and the builder pattern.
 */
class FlowAggregatedSignatureTest {
    // ... existing code ...
}
  1. Add test cases for potential edge cases or error scenarios, such as:

    • Empty lists of signatures or signer IDs
    • Very large lists of signatures or signer IDs
    • Invalid input data (e.g., mismatched sizes of signatures and signer IDs)
  2. Consider adding a test for the toString() method of FlowAggregatedSignature if it's implemented.

These additions will enhance the robustness and documentation of your test suite.

sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowBlockSealTest.kt (4)

13-53: LGTM: Comprehensive equality and hash code test.

This test thoroughly checks the equals and hashCode methods of FlowBlockSeal, including all properties. It's particularly valuable as it covers the newly added properties such as finalState, resultId, and aggregatedApprovalSigs.

Consider adding a negative test case where one property differs between the two instances to ensure the equals method correctly identifies inequality.


55-85: LGTM: Thorough test for protobuf conversion.

This test effectively verifies the FlowBlockSeal.of function, ensuring correct conversion from the protobuf representation. It covers all properties, including the newly added ones like resultId and finalState.

Consider adding assertions for executionReceiptSignatures, resultApprovalSignatures, and aggregatedApprovalSigs to ensure complete coverage of all properties during conversion.


87-120: LGTM: Comprehensive test for the builder function.

This test effectively verifies the FlowBlockSeal builder functionality, ensuring all properties are correctly reconstructed, including the newly added ones like resultId and finalState.

Consider adding an assertion for aggregatedApprovalSigs to ensure complete coverage of all properties in the builder test.


1-120: Great job on comprehensive test coverage for FlowBlockSeal!

This new test file aligns well with the PR objectives, particularly in addressing the addition of missing protobuf fields to the FlowBlockSeal model. The tests cover equality, protobuf conversion, and builder functionality, including checks for newly added properties like finalState, resultId, and aggregatedApprovalSigs.

The test structure is clear, and the use of JUnit and Mockito is appropriate. The tests effectively verify the changes made to the FlowBlockSeal class, ensuring its correctness and compatibility with the updated protobuf schema.

To further enhance the test suite:

  1. Consider adding negative test cases for equality checks.
  2. Ensure all properties are asserted in the protobuf conversion and builder tests.
  3. If not already present elsewhere, consider adding integration tests that use actual protobuf serialization/deserialization to complement these unit tests.
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between db9e3d1 and 218b5e2.

📒 Files selected for processing (3)
  • sdk/src/main/kotlin/org/onflow/flow/sdk/models.kt (1 hunks)
  • sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowAggregatedSignatureTest.kt (1 hunks)
  • sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowBlockSealTest.kt (1 hunks)
🧰 Additional context used
🔇 Additional comments (6)
sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowAggregatedSignatureTest.kt (1)

13-42: LGTM: Well-structured equality and hash code test

This test method effectively covers both positive and negative cases for equality and hash code consistency of the FlowAggregatedSignature class. It uses clear variable names and follows Kotlin naming conventions.

sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowBlockSealTest.kt (1)

1-11: LGTM: Import statements are appropriate and concise.

The import statements cover all necessary classes for the tests, including JUnit assertions, Mockito for mocking, and relevant Flow SDK classes. No unused imports are present.

sdk/src/main/kotlin/org/onflow/flow/sdk/models.kt (4)

1204-1210: Ensure all new properties in FlowBlockSeal are correctly initialized and mapped

The FlowBlockSeal data class has been updated with new properties:

  • resultApprovalSignatures
  • finalState
  • resultId
  • aggregatedApprovalSigs

Please verify that these properties are correctly initialized in all constructors and methods, and that they're accurately mapped from the protobuf objects and back.


1215-1221: Confirm accurate mapping in the of() method of FlowBlockSeal

In the of() method, the new properties are being mapped from the protobuf BlockSeal object:

  • resultApprovalSignatures from value.resultApprovalSignaturesList
  • finalState from value.finalState
  • resultId from value.resultId
  • aggregatedApprovalSigs from value.aggregatedApprovalSigsList

Ensure that these mappings correspond correctly to the intended fields in the protobuf schema.


1227-1233: Verify the builder() method correctly sets all new properties

In the builder() method of FlowBlockSeal, the new properties are being set:

  • .addAllResultApprovalSignatures(resultApprovalSignatures.map { it.byteStringValue })
  • .setFinalState(UnsafeByteOperations.unsafeWrap(finalState))
  • .setResultId(resultId.byteStringValue)
  • .addAllAggregatedApprovalSigs(aggregatedApprovalSigs.map { it.builder().build() })

Please confirm that these properties are correctly assigned to the corresponding protobuf fields to ensure consistency during serialization.


1235-1259: Include new properties in equals() and hashCode() methods

The equals() and hashCode() methods in FlowBlockSeal have been updated to include the new properties:

  • resultApprovalSignatures
  • finalState
  • resultId
  • aggregatedApprovalSigs

This is essential for ensuring object equality and correct behavior in collections. Verify that these methods accurately compare and hash the new properties.

Copy link

@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

🧹 Outside diff range and nitpick comments (3)
sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowBlockSealTest.kt (3)

55-105: LGTM: Thorough test for protobuf conversion with room for improvement.

This test effectively verifies the FlowBlockSeal.of function, ensuring accurate conversion from a protobuf representation to a FlowBlockSeal instance. It covers all properties, including the newly added ones.

Consider enhancing the test by adding more assertions for list properties:

  • Check the size of executionReceiptSignatures, resultApprovalSignatures, and aggregatedApprovalSigs lists.
  • Verify the content of these lists more thoroughly, especially for aggregatedApprovalSigs.

Example:

assertEquals(1, flowBlockSeal.executionReceiptSignatures.size)
assertEquals(FlowSignature("signature1".toByteArray()), flowBlockSeal.executionReceiptSignatures[0])
// Similar assertions for other list properties

107-140: LGTM: Comprehensive test for builder function with a minor improvement suggestion.

This test effectively verifies the FlowBlockSeal builder function, ensuring all properties are correctly reconstructed. It covers all properties, including the newly added ones.

Consider adding an assertion for the aggregatedApprovalSigs property in the builder result. This would ensure complete coverage of all properties:

assertEquals(1, builderResult.aggregatedApprovalSigsList.size)
val builtAggregatedSig = builderResult.aggregatedApprovalSigsList[0]
assertEquals(aggregatedSignature1.verifierSignatures.map { it.byteStringValue }, builtAggregatedSig.verifierSignaturesList)
assertEquals(aggregatedSignature1.signerIds.map { it.byteStringValue }, builtAggregatedSig.signerIdsList)

1-140: Excellent test coverage for FlowBlockSeal with minor enhancement opportunities.

The FlowBlockSealTest class provides comprehensive test coverage for the FlowBlockSeal class, including the newly added properties and functionalities. The tests are well-structured, using appropriate JUnit 5 and Mockito features.

Key strengths:

  1. Thorough testing of equality and hash code functionality.
  2. Comprehensive verification of protobuf conversion.
  3. Detailed testing of the builder function.

These tests align well with the PR objectives, ensuring that the FlowBlockSeal model accurately reflects the expected structure defined in the protobuf schema.

To further enhance the test suite:

  1. Consider adding edge cases, such as empty lists or null values where applicable.
  2. Implement parameterized tests for different scenarios to increase coverage.
  3. Add tests for any public methods of FlowBlockSeal that are not currently covered.

These enhancements will contribute to a more robust test suite, further ensuring the reliability of the FlowBlockSeal class.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 218b5e2 and b7308f1.

📒 Files selected for processing (2)
  • sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowAggregatedSignatureTest.kt (1 hunks)
  • sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowBlockSealTest.kt (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowAggregatedSignatureTest.kt
🧰 Additional context used
🔇 Additional comments (1)
sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowBlockSealTest.kt (1)

14-53: LGTM: Comprehensive test for equality and hash code.

This test thoroughly checks the equality and hash code functionality of FlowBlockSeal. It covers all properties, including the newly added ones like finalState, resultId, and aggregatedApprovalSigs. The test structure is clear and includes necessary assertions.

Copy link

Common Integration Test Results

1 files  1 suites   7s ⏱️
1 tests 1 ✔️ 0 💤 0 ❌

Results for commit b7308f1.

Copy link

@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

🧹 Outside diff range and nitpick comments (1)
sdk/src/main/kotlin/org/onflow/flow/sdk/models.kt (1)

1204-1260: LGTM! Consider adding documentation for new properties.

The changes to FlowBlockSeal class look good. The new properties finalState, resultId, and aggregatedApprovalSigs have been added as per the PR objectives. The renaming of id to blockId improves clarity. The equals() and hashCode() methods have been correctly updated to include the new properties.

Consider adding KDoc comments for the new properties to improve code documentation and maintainability.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between b7308f1 and 1f4ab77.

📒 Files selected for processing (1)
  • sdk/src/main/kotlin/org/onflow/flow/sdk/models.kt (2 hunks)
🧰 Additional context used

Copy link

@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: 9

🧹 Outside diff range and nitpick comments (20)
kotlin-example/src/main/kotlin/org/onflow/examples/kotlin/getAccountBalance/GetAccountBalanceAccessAPIConnector.kt (1)

3-3: Consider using specific imports instead of wildcard

While wildcard imports can be convenient, explicit imports make dependencies clearer and prevent potential naming conflicts.

-import org.onflow.flow.sdk.*
+import org.onflow.flow.sdk.FlowAccessApi
+import org.onflow.flow.sdk.FlowAddress
java-example/src/main/java/org/onflow/examples/java/getAccountBalance/GetAccountBalanceAccessAPIConnector.java (1)

6-11: Add class-level documentation and constructor validation.

Consider adding:

  1. JavaDoc documentation explaining the class's purpose and usage
  2. Null validation in the constructor

Apply these changes:

+/**
+ * Connector class for retrieving account balances from the Flow blockchain using the Flow Access API.
+ * This class provides methods to fetch balances at the latest block or at a specific block height.
+ */
 public class GetAccountBalanceAccessAPIConnector {
     private final FlowAccessApi accessAPI;
 
     public GetAccountBalanceAccessAPIConnector(FlowAccessApi accessAPI) {
+        if (accessAPI == null) {
+            throw new IllegalArgumentException("accessAPI cannot be null");
+        }
         this.accessAPI = accessAPI;
     }
kotlin-example/src/test/kotlin/org/onflow/examples/kotlin/getAccountBalance/GetAccountBalanceAccessAPIConnectorTest.kt (1)

39-39: Extract sealed block flag to a constant.

The boolean parameter true in getLatestBlock calls should be extracted to a named constant for better readability and maintainability.

+    companion object {
+        private const val SEALED_BLOCK_ONLY = true
+    }
+
     @Test
     fun `Can fetch account balance at a specific block height`() {
         val address = serviceAccount.flowAddress
-        val latestBlock = accessAPI.getLatestBlock(true)
+        val latestBlock = accessAPI.getLatestBlock(SEALED_BLOCK_ONLY)

Also applies to: 60-60

java-example/src/test/java/org/onflow/examples/java/getAccountBalance/GetAccountBalanceAccessAPIConnectorTest.java (2)

30-36: Enhance assertion message for better test failure diagnosis.

The assertion message could be more descriptive by including the actual balance value.

-        Assertions.assertTrue(balance >= 0, "Balance at the latest block should be non-negative");
+        Assertions.assertTrue(balance >= 0, 
+            String.format("Balance at the latest block should be non-negative, but got: %d", balance));

30-76: Add test coverage for edge cases.

The current test suite could benefit from additional test cases:

  1. Test with zero balance accounts
  2. Test with invalid block heights
  3. Test with maximum block height
  4. Test with non-existent accounts

Would you like me to help generate these additional test cases?

sdk/src/main/kotlin/org/onflow/flow/sdk/FlowAccessApi.kt (1)

29-32: Add KDoc documentation for the new balance retrieval methods.

The method signatures look good and follow the interface's patterns. However, consider adding KDoc documentation to clarify:

  • The denomination of the returned balance (e.g., FLOW tokens in smallest unit)
  • Any constraints on the height parameter
  • Error conditions that might trigger an Error response

Example documentation:

/**
 * Retrieves the account balance at the latest sealed block.
 *
 * @param address The Flow address to query
 * @return Success with balance in FLOW smallest denomination, or Error if the account doesn't exist
 */
fun getAccountBalanceAtLatestBlock(address: FlowAddress): AccessApiCallResponse<Long>

/**
 * Retrieves the account balance at a specific block height.
 *
 * @param address The Flow address to query
 * @param height The block height to query (must be <= latest block height)
 * @return Success with balance in FLOW smallest denomination, or Error if the account/block doesn't exist
 */
fun getAccountBalanceAtBlockHeight(address: FlowAddress, height: Long): AccessApiCallResponse<Long>
sdk/src/intTest/org/onflow/flow/sdk/transaction/TransactionIntegrationTest.kt (2)

144-171: Extract magic number to a named constant.

The normalization factor 100_000_000L appears to be a significant value for Flow token denomination. Consider extracting it to a named constant for better maintainability and documentation.

+ private companion object {
+     // Number of decimal places in Flow token (1 FLOW = 10^8 decimal places)
+     const val FLOW_TOKEN_DECIMALS = 100_000_000L
+ }

     @Test
     fun `Can get account balance at latest block`() {
         // ...
-        val normalizedBalance = balanceResponse / 100_000_000L
+        val normalizedBalance = balanceResponse / FLOW_TOKEN_DECIMALS

144-211: Tests are well-structured but could benefit from test data variation.

The tests effectively verify the new account balance retrieval functionality and maintain consistency with the existing test patterns. However, consider enhancing the test suite with:

  1. A parameterized test that verifies balance retrieval with different account states and balance values.
  2. Edge cases such as accounts with zero balance or maximum possible balance.

This would provide more comprehensive coverage while maintaining the existing clean architecture.

sdk/src/test/kotlin/org/onflow/flow/sdk/FlowAccessApiTest.kt (2)

91-119: Consider enhancing test coverage for getAccountBalanceAtLatestBlock.

While the basic success and failure cases are well-implemented, consider adding the following test cases for better coverage:

  1. Edge cases: zero balance, maximum balance
  2. Invalid inputs: malformed addresses
  3. Consistent error message format validation

Example test case for zero balance:

@Test
fun `Test getAccountBalanceAtLatestBlock with zero balance`() {
    val flowAccessApi = mock(FlowAccessApi::class.java)
    val flowAddress = FlowAddress("0x1234567890abcdef")  // More realistic address
    val expectedBalance = 0L
    val response = FlowAccessApi.AccessApiCallResponse.Success(expectedBalance)

    `when`(flowAccessApi.getAccountBalanceAtLatestBlock(flowAddress)).thenReturn(response)

    val result = flowAccessApi.getAccountBalanceAtLatestBlock(flowAddress)

    assertEquals(response, result)
    verify(flowAccessApi).getAccountBalanceAtLatestBlock(flowAddress)
}

121-151: Consider enhancing test coverage for getAccountBalanceAtBlockHeight.

While the basic success and failure cases are well-implemented, consider adding the following test cases for better coverage:

  1. Edge cases: zero balance, maximum balance
  2. Invalid inputs: negative block height, non-existent block height
  3. Consistent error message format validation

Example test case for non-existent block height:

@Test
fun `Test getAccountBalanceAtBlockHeight with non-existent block height`() {
    val flowAccessApi = mock(FlowAccessApi::class.java)
    val flowAddress = FlowAddress("0x1234567890abcdef")  // More realistic address
    val nonExistentHeight = Long.MAX_VALUE
    val exception = RuntimeException("Block height not found")
    val response = FlowAccessApi.AccessApiCallResponse.Error(
        "Failed to get account balance: block height not found",
        exception
    )

    `when`(flowAccessApi.getAccountBalanceAtBlockHeight(flowAddress, nonExistentHeight))
        .thenReturn(response)

    val result = flowAccessApi.getAccountBalanceAtBlockHeight(flowAddress, nonExistentHeight)

    assertEquals(response, result)
    verify(flowAccessApi).getAccountBalanceAtBlockHeight(flowAddress, nonExistentHeight)
}
sdk/src/main/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImpl.kt (2)

99-110: Consider adding parameter validation and documentation.

While the implementation follows the class patterns, consider these improvements:

  1. Add null check for the address parameter
  2. Add KDoc comments describing:
    • Parameter requirements
    • Possible error cases
    • Balance representation (units, precision)
+/**
+ * Retrieves the account balance at the latest block.
+ *
+ * @param address The Flow address to query (must not be null)
+ * @return Success with the account balance in FLOW units, or Error if the operation fails
+ * @throws IllegalArgumentException if address is null
+ */
 override fun getAccountBalanceAtLatestBlock(address: FlowAddress): FlowAccessApi.AccessApiCallResponse<Long> =
     try {
+        requireNotNull(address) { "Address must not be null" }
         val ret = api.getAccountBalanceAtLatestBlock(

112-124: Add parameter validation and documentation for block height method.

While the implementation follows the class patterns, consider these improvements:

  1. Add parameter validation:
    • Null check for address
    • Non-negative check for height
  2. Add KDoc comments describing:
    • Parameter requirements
    • Possible error cases
    • Balance representation
+/**
+ * Retrieves the account balance at the specified block height.
+ *
+ * @param address The Flow address to query (must not be null)
+ * @param height The block height to query (must be non-negative)
+ * @return Success with the account balance in FLOW units, or Error if the operation fails
+ * @throws IllegalArgumentException if address is null or height is negative
+ */
 override fun getAccountBalanceAtBlockHeight(address: FlowAddress, height: Long): FlowAccessApi.AccessApiCallResponse<Long> =
     try {
+        requireNotNull(address) { "Address must not be null" }
+        require(height >= 0) { "Height must be non-negative" }
         val ret = api.getAccountBalanceAtBlockHeight(
sdk/src/test/kotlin/org/onflow/flow/sdk/impl/AsyncFlowAccessApiImplTest.kt (5)

208-223: Consider adding edge cases and parameter verification.

While the basic success case is well-tested, consider:

  1. Testing edge cases (e.g., zero balance, maximum balance)
  2. Verifying that the correct address is passed to the API call using argument captors
// Example of parameter verification
val addressCaptor = ArgumentCaptor.forClass(Access.GetAccountBalanceAtLatestBlockRequest::class.java)
verify(api).getAccountBalanceAtLatestBlock(addressCaptor.capture())
assertEquals(flowAddress.bytes, addressCaptor.value.address.toByteArray())

225-237: Enhance error scenario coverage.

Consider adding tests for specific error cases:

  • Invalid address format
  • Network-related exceptions (e.g., IOException)
  • Service-specific exceptions
@Test
fun `test getAccountBalanceAtLatestBlock with invalid address`() {
    val invalidAddress = FlowAddress("invalid")
    val exception = IllegalArgumentException("Invalid address format")
    
    `when`(api.getAccountBalanceAtLatestBlock(any())).thenThrow(exception)
    
    val result = asyncFlowAccessApi.getAccountBalanceAtLatestBlock(invalidAddress).get()
    assert(result is FlowAccessApi.AccessApiCallResponse.Error)
    assertEquals("Invalid address format", (result as FlowAccessApi.AccessApiCallResponse.Error).throwable.message)
}

239-255: Add boundary condition tests for block height.

Consider testing:

  • Minimum valid block height (0 or 1)
  • Very large block heights
  • Non-existent block heights
@Test
fun `test getAccountBalanceAtBlockHeight with minimum height`() {
    val flowAddress = FlowAddress("01")
    val minBlockHeight = 0L
    val expectedBalance = 1000L
    val balanceResponse = Access.AccountBalanceResponse
        .newBuilder()
        .setBalance(expectedBalance)
        .build()
    
    `when`(api.getAccountBalanceAtBlockHeight(any())).thenReturn(setupFutureMock(balanceResponse))
    
    val result = asyncFlowAccessApi.getAccountBalanceAtBlockHeight(flowAddress, minBlockHeight).get()
    assert(result is FlowAccessApi.AccessApiCallResponse.Success)
    assertEquals(expectedBalance, (result as FlowAccessApi.AccessApiCallResponse.Success).data)
}

257-270: Add block height-specific error cases.

Consider adding tests for:

  • Block height not found
  • Block height out of range
  • Future block heights
@Test
fun `test getAccountBalanceAtBlockHeight with non-existent block`() {
    val flowAddress = FlowAddress("01")
    val nonExistentHeight = Long.MAX_VALUE
    val exception = IllegalArgumentException("Block height not found")
    
    `when`(api.getAccountBalanceAtBlockHeight(any())).thenThrow(exception)
    
    val result = asyncFlowAccessApi.getAccountBalanceAtBlockHeight(flowAddress, nonExistentHeight).get()
    assert(result is FlowAccessApi.AccessApiCallResponse.Error)
    assertEquals("Block height not found", (result as FlowAccessApi.AccessApiCallResponse.Error).throwable.message)
}

208-270: Consider implementing a shared test fixture.

To reduce code duplication and improve maintainability, consider:

  1. Creating shared test data fixtures for common objects (addresses, balances, etc.)
  2. Implementing helper methods for common test scenarios
  3. Using a test lifecycle management approach (e.g., @BeforeEach) for setup
private companion object TestData {
    val TEST_ADDRESS = FlowAddress("01")
    val TEST_BALANCE = 1000L
    
    fun createBalanceResponse(balance: Long = TEST_BALANCE) = 
        Access.AccountBalanceResponse.newBuilder()
            .setBalance(balance)
            .build()
}
sdk/src/test/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImplTest.kt (2)

188-200: Consider enhancing error message specificity.

While the error handling test is good, consider making the error message more specific by including the address in the error message for better debugging.

-        assertEquals("Failed to get account balance at latest block", (result as FlowAccessApi.AccessApiCallResponse.Error).message)
+        assertEquals("Failed to get account balance at latest block for address: ${flowAddress.formatted}", (result as FlowAccessApi.AccessApiCallResponse.Error).message)

226-239: Consider enhancing error message specificity.

Similar to the latest block failure test, consider making the error message more specific by including both the address and block height for better debugging.

-        assertEquals("Failed to get account balance at block height", (result as FlowAccessApi.AccessApiCallResponse.Error).message)
+        assertEquals("Failed to get account balance at block height ${blockHeight} for address: ${flowAddress.formatted}", (result as FlowAccessApi.AccessApiCallResponse.Error).message)
sdk/src/main/kotlin/org/onflow/flow/sdk/impl/AsyncFlowAccessApiImpl.kt (1)

180-204: LGTM! Consider standardizing error message format.

The implementation is correct and well-structured. However, for consistency with other methods in the codebase, consider standardizing the error message format.

-                    return CompletableFuture.completedFuture(FlowAccessApi.AccessApiCallResponse.Error("Failed to get account balance at block height", e))
+                    return CompletableFuture.completedFuture(FlowAccessApi.AccessApiCallResponse.Error("Failed to get account balance by block height", e))

This makes it consistent with other methods that use "by" instead of "at" in error messages (e.g., getAccountByBlockHeight).

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 1f4ab77 and 5e02ad8.

📒 Files selected for processing (12)
  • java-example/src/main/java/org/onflow/examples/java/getAccountBalance/GetAccountBalanceAccessAPIConnector.java (1 hunks)
  • java-example/src/test/java/org/onflow/examples/java/getAccountBalance/GetAccountBalanceAccessAPIConnectorTest.java (1 hunks)
  • kotlin-example/src/main/kotlin/org/onflow/examples/kotlin/getAccountBalance/GetAccountBalanceAccessAPIConnector.kt (1 hunks)
  • kotlin-example/src/test/kotlin/org/onflow/examples/kotlin/getAccountBalance/GetAccountBalanceAccessAPIConnectorTest.kt (1 hunks)
  • sdk/src/intTest/org/onflow/flow/sdk/transaction/TransactionIntegrationTest.kt (1 hunks)
  • sdk/src/main/kotlin/org/onflow/flow/sdk/AsyncFlowAccessApi.kt (1 hunks)
  • sdk/src/main/kotlin/org/onflow/flow/sdk/FlowAccessApi.kt (1 hunks)
  • sdk/src/main/kotlin/org/onflow/flow/sdk/impl/AsyncFlowAccessApiImpl.kt (1 hunks)
  • sdk/src/main/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImpl.kt (1 hunks)
  • sdk/src/test/kotlin/org/onflow/flow/sdk/FlowAccessApiTest.kt (1 hunks)
  • sdk/src/test/kotlin/org/onflow/flow/sdk/impl/AsyncFlowAccessApiImplTest.kt (1 hunks)
  • sdk/src/test/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImplTest.kt (1 hunks)
🧰 Additional context used
🔇 Additional comments (13)
kotlin-example/src/main/kotlin/org/onflow/examples/kotlin/getAccountBalance/GetAccountBalanceAccessAPIConnector.kt (2)

5-7: LGTM! Well-structured class declaration

The class follows good practices with proper dependency injection and appropriate visibility modifiers.


1-19: Add unit tests for the connector class

Since this is an example connector, it would be valuable to have comprehensive unit tests demonstrating proper usage patterns and error handling scenarios.

Would you like me to help create unit tests for this connector class?

java-example/src/main/java/org/onflow/examples/java/getAccountBalance/GetAccountBalanceAccessAPIConnector.java (1)

1-34: Verify integration with existing Flow SDK examples.

The implementation looks solid, but let's verify its integration with other Flow SDK examples to ensure consistency.

✅ Verification successful

Let me examine the test implementation for this specific class to verify its integration.


Implementation is properly integrated and well-tested

The verification shows that:

  • The implementation follows the same pattern as other Flow SDK examples in the codebase
  • The class has comprehensive test coverage including:
    • Balance retrieval at the latest block
    • Balance retrieval at specific block heights
    • Consistency checks between latest and specific block height balances
  • Tests properly utilize the Flow emulator project setup and test utilities
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for other example implementations to ensure consistency
echo "Searching for similar Flow SDK example implementations..."
rg -l "FlowAccessApi" "java-example/src/main/java/org/onflow/examples"

# Check for existing test implementations
echo "Checking for test implementations..."
rg -l "Test" "java-example/src/test/java/org/onflow/examples"

Length of output: 4264


Script:

#!/bin/bash
# Check the test implementation for GetAccountBalanceAccessAPIConnector
cat "java-example/src/test/java/org/onflow/examples/java/getAccountBalance/GetAccountBalanceAccessAPIConnectorTest.java"

# Check if there are any example usages
rg -l "GetAccountBalanceAccessAPIConnector" --type java "java-example/src/main/java/org/onflow/examples" | grep -v "GetAccountBalanceAccessAPIConnector.java"

Length of output: 3770

kotlin-example/src/test/kotlin/org/onflow/examples/kotlin/getAccountBalance/GetAccountBalanceAccessAPIConnectorTest.kt (2)

1-25: LGTM! Well-structured test class setup.

The class setup follows best practices with proper dependency injection and initialization.


52-72: Consider potential race conditions in balance comparison test.

The test comparing balances at latest block and specific height might be flaky if a new block is sealed between the two balance retrievals.

Consider either:

  1. Using a slightly older block height to ensure stability
  2. Adding retry logic with assertions
  3. Mocking the Flow Access API for deterministic testing

Here's a script to check if this test has been flaky in the past:

sdk/src/main/kotlin/org/onflow/flow/sdk/AsyncFlowAccessApi.kt (2)

17-20: LGTM! The new balance retrieval methods are well-designed.

The new methods follow the interface's established patterns and naming conventions, properly handling both current and historical balance queries in an asynchronous manner.

Let's verify if these methods are implemented in the corresponding implementation class:

✅ Verification successful

Let me try a different approach to verify the implementation of these methods.


Based on the search results, I can now generate the final response as we have found the implementation details:

The balance retrieval methods are properly implemented and tested

The new methods are fully implemented in AsyncFlowAccessApiImpl with:

  • Complete implementation of both methods with proper error handling
  • Comprehensive unit tests covering both success and failure scenarios
  • Integration with the underlying Flow API using proper protobuf requests
  • Consistent with the interface contract
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check if the new methods are implemented in AsyncFlowAccessApiImpl
ast-grep --pattern 'class AsyncFlowAccessApiImpl {
  $$$
  fun getAccountBalanceAtLatestBlock($$$) {
    $$$
  }
  $$$
  fun getAccountBalanceAtBlockHeight($$$) {
    $$$
  }
  $$$
}'

Length of output: 185


Script:

#!/bin/bash
# First, let's find the implementation file
fd "AsyncFlowAccessApiImpl" --type f

# Then, let's examine its content
rg "getAccountBalance" -A 5

Length of output: 45071


17-20: Verify alignment with PR objectives.

These account balance methods appear to be additional features unrelated to the PR's main objective of updating FlowBlockSeal and protobuf schemas. Consider splitting these changes into a separate PR for clearer change management.

Let's check if these changes are mentioned in the linked issue:

java-example/src/test/java/org/onflow/examples/java/getAccountBalance/GetAccountBalanceAccessAPIConnectorTest.java (2)

25-28: LGTM! Clean setup method.

The setup method is concise and properly initializes the test subject using the injected dependencies.


14-14: Verify the Flow emulator configuration path.

The relative path "../flow/flow.json" is used for the Flow emulator configuration. Let's verify this path exists.

✅ Verification successful

Let me generate another script to specifically check for the flow.json file in the repository, as the previous script's output seems unrelated to our verification task.


The Flow emulator configuration path is correct

The relative path "../flow/flow.json" in the @FlowEmulatorProjectTest annotation is valid. The file exists at flow/flow.json in the repository, which is correctly referenced from the test file's location at java-example/src/test/java/org/onflow/examples/java/getAccountBalance/GetAccountBalanceAccessAPIConnectorTest.java.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the Flow emulator configuration file exists
# Expected: flow.json file should exist in the specified location

# Get the current file's directory
current_file=$(fd --type f --full-path "GetAccountBalanceAccessAPIConnectorTest.java")
if [ -n "$current_file" ]; then
    config_dir=$(dirname "$current_file")/../flow
    echo "Looking for flow.json in: $config_dir"
    fd --type f "flow.json" "$(dirname "$current_file")/.."
fi

Length of output: 848


Script:

#!/bin/bash
# Search for flow.json files in the repository
fd -t f "flow.json$"

Length of output: 37

sdk/src/main/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImpl.kt (1)

99-124: Verify if these changes belong in this PR.

These account balance methods seem unrelated to the PR's stated objective of aligning BlockSeal type with protobuf schema. Consider:

  1. Moving these changes to a separate PR focused on account balance functionality
  2. Updating the PR description to include these additional changes
✅ Verification successful

Let me gather more context about the PR's objectives and related changes.


Based on the extensive output, I can now generate the final response as I have enough context about both the BlockSeal changes and the account balance additions.

The account balance methods are part of a larger API implementation effort and should remain in this PR.

The changes in this PR actually encompass two related aspects:

  1. BlockSeal protobuf schema alignment (primary change)
  2. API implementation completeness, including account balance methods

The account balance methods are justified to be in this PR because:

  • They are part of the core API implementation (FlowAccessApi and AsyncFlowAccessApi interfaces)
  • The changes are well-tested with corresponding test implementations
  • They are being used by example implementations in both Java and Kotlin
  • The PR includes other API-related changes beyond just BlockSeal updates
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check if there are related changes in other files that justify including these methods

# Test: Look for related account balance changes
echo "Searching for related account balance changes..."
rg -l "AccountBalance" --type kotlin

# Test: Check interface declarations
echo "Checking interface declarations..."
rg "fun getAccountBalance" --type kotlin

Length of output: 3306


Script:

#!/bin/bash
# Check PR description and related changes
echo "Checking PR title and description..."
gh pr view

# Look for BlockSeal related changes
echo "Checking BlockSeal related changes..."
rg -l "BlockSeal" --type kotlin

# Check commit history to understand the change context
echo "Checking commit history..."
git log --patch | grep -A 10 "BlockSeal\|AccountBalance"

Length of output: 47303

sdk/src/test/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImplTest.kt (2)

166-186: LGTM! Well-structured test case.

The test thoroughly verifies both the successful response handling and the correct request structure.


202-224: LGTM! Comprehensive test coverage.

The test thoroughly verifies both the successful response handling and the correct request structure, including the block height parameter.

sdk/src/main/kotlin/org/onflow/flow/sdk/impl/AsyncFlowAccessApiImpl.kt (1)

155-178: LGTM! Implementation follows established patterns.

The implementation is well-structured with proper error handling and follows the consistent patterns used throughout the codebase.

Comment on lines +14 to +18
fun getBalanceAtBlockHeight(address: FlowAddress, height: Long): Long =
when (val response = accessAPI.getAccountBalanceAtBlockHeight(address, height)) {
is FlowAccessApi.AccessApiCallResponse.Success -> response.data
is FlowAccessApi.AccessApiCallResponse.Error -> throw Exception(response.message, response.throwable)
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add parameter validation and consistent error handling

The method should validate the height parameter and use consistent error handling with getBalanceAtLatestBlock.

+/** 
+ * Retrieves account balance at specified block height.
+ * @param address The Flow address to query
+ * @param height The block height (must be positive)
+ * @return The account balance as Long
+ * @throws FlowBalanceException if the API call fails or if height is invalid
+ */
 fun getBalanceAtBlockHeight(address: FlowAddress, height: Long): Long =
+    when {
+        height < 0 -> throw FlowBalanceException("Block height must be non-negative", null)
+        else ->
     when (val response = accessAPI.getAccountBalanceAtBlockHeight(address, height)) {
         is FlowAccessApi.AccessApiCallResponse.Success -> response.data
-        is FlowAccessApi.AccessApiCallResponse.Error -> throw Exception(response.message, response.throwable)
+        is FlowAccessApi.AccessApiCallResponse.Error -> throw FlowBalanceException(
+            response.message ?: "Unknown error occurred",
+            response.throwable
+        )
     }
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
fun getBalanceAtBlockHeight(address: FlowAddress, height: Long): Long =
when (val response = accessAPI.getAccountBalanceAtBlockHeight(address, height)) {
is FlowAccessApi.AccessApiCallResponse.Success -> response.data
is FlowAccessApi.AccessApiCallResponse.Error -> throw Exception(response.message, response.throwable)
}
/**
* Retrieves account balance at specified block height.
* @param address The Flow address to query
* @param height The block height (must be positive)
* @return The account balance as Long
* @throws FlowBalanceException if the API call fails or if height is invalid
*/
fun getBalanceAtBlockHeight(address: FlowAddress, height: Long): Long =
when {
height < 0 -> throw FlowBalanceException("Block height must be non-negative", null)
else ->
when (val response = accessAPI.getAccountBalanceAtBlockHeight(address, height)) {
is FlowAccessApi.AccessApiCallResponse.Success -> response.data
is FlowAccessApi.AccessApiCallResponse.Error -> throw FlowBalanceException(
response.message ?: "Unknown error occurred",
response.throwable
)
}
}

Comment on lines +8 to +12
fun getBalanceAtLatestBlock(address: FlowAddress): Long =
when (val response = accessAPI.getAccountBalanceAtLatestBlock(address)) {
is FlowAccessApi.AccessApiCallResponse.Success -> response.data
is FlowAccessApi.AccessApiCallResponse.Error -> throw Exception(response.message, response.throwable)
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Enhance error handling and documentation

While the basic functionality is correct, consider these improvements:

  1. Create a custom exception type instead of using generic Exception
  2. Add KDoc documentation describing possible exceptions
  3. Add null safety for error message handling
+/** 
+ * Retrieves account balance at the latest block.
+ * @param address The Flow address to query
+ * @return The account balance as Long
+ * @throws FlowBalanceException if the API call fails
+ */
 fun getBalanceAtLatestBlock(address: FlowAddress): Long =
     when (val response = accessAPI.getAccountBalanceAtLatestBlock(address)) {
         is FlowAccessApi.AccessApiCallResponse.Success -> response.data
-        is FlowAccessApi.AccessApiCallResponse.Error -> throw Exception(response.message, response.throwable)
+        is FlowAccessApi.AccessApiCallResponse.Error -> throw FlowBalanceException(
+            response.message ?: "Unknown error occurred",
+            response.throwable
+        )
     }

+class FlowBalanceException(message: String, cause: Throwable?) : Exception(message, cause)

Committable suggestion was skipped due to low confidence.

Comment on lines +13 to +22
public long getBalanceAtLatestBlock(FlowAddress address) {
FlowAccessApi.AccessApiCallResponse<Long> response = accessAPI.getAccountBalanceAtLatestBlock(address);

if (response instanceof FlowAccessApi.AccessApiCallResponse.Success) {
return ((FlowAccessApi.AccessApiCallResponse.Success<Long>) response).getData();
} else {
FlowAccessApi.AccessApiCallResponse.Error errorResponse = (FlowAccessApi.AccessApiCallResponse.Error) response;
throw new RuntimeException(errorResponse.getMessage(), errorResponse.getThrowable());
}
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Enhance error handling and add documentation.

Consider these improvements:

  1. Create a custom exception instead of using generic RuntimeException
  2. Add parameter validation
  3. Add method documentation

Apply these changes:

+/**
+ * Retrieves the account balance at the latest block.
+ *
+ * @param address The Flow address to query the balance for
+ * @return The account balance
+ * @throws AccountBalanceException if the balance retrieval fails
+ * @throws IllegalArgumentException if address is null
+ */
 public long getBalanceAtLatestBlock(FlowAddress address) {
+    if (address == null) {
+        throw new IllegalArgumentException("address cannot be null");
+    }
+
     FlowAccessApi.AccessApiCallResponse<Long> response = accessAPI.getAccountBalanceAtLatestBlock(address);
 
     if (response instanceof FlowAccessApi.AccessApiCallResponse.Success) {
         return ((FlowAccessApi.AccessApiCallResponse.Success<Long>) response).getData();
     } else {
         FlowAccessApi.AccessApiCallResponse.Error errorResponse = (FlowAccessApi.AccessApiCallResponse.Error) response;
-        throw new RuntimeException(errorResponse.getMessage(), errorResponse.getThrowable());
+        throw new AccountBalanceException("Failed to retrieve balance at latest block", errorResponse.getMessage(), errorResponse.getThrowable());
     }
 }

Create a custom exception class:

public class AccountBalanceException extends RuntimeException {
    private final String errorDetails;

    public AccountBalanceException(String message, String errorDetails, Throwable cause) {
        super(message, cause);
        this.errorDetails = errorDetails;
    }

    public String getErrorDetails() {
        return errorDetails;
    }
}

Comment on lines +24 to +33
public long getBalanceAtBlockHeight(FlowAddress address, long height) {
FlowAccessApi.AccessApiCallResponse<Long> response = accessAPI.getAccountBalanceAtBlockHeight(address, height);

if (response instanceof FlowAccessApi.AccessApiCallResponse.Success) {
return ((FlowAccessApi.AccessApiCallResponse.Success<Long>) response).getData();
} else {
FlowAccessApi.AccessApiCallResponse.Error errorResponse = (FlowAccessApi.AccessApiCallResponse.Error) response;
throw new RuntimeException(errorResponse.getMessage(), errorResponse.getThrowable());
}
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add parameter validation and improve error handling.

Similar to getBalanceAtLatestBlock, this method needs documentation and better error handling. Additionally, validate the block height parameter.

Apply these changes:

+/**
+ * Retrieves the account balance at a specific block height.
+ *
+ * @param address The Flow address to query the balance for
+ * @param height The block height to query at
+ * @return The account balance
+ * @throws AccountBalanceException if the balance retrieval fails
+ * @throws IllegalArgumentException if address is null or height is negative
+ */
 public long getBalanceAtBlockHeight(FlowAddress address, long height) {
+    if (address == null) {
+        throw new IllegalArgumentException("address cannot be null");
+    }
+    if (height < 0) {
+        throw new IllegalArgumentException("block height cannot be negative");
+    }
+
     FlowAccessApi.AccessApiCallResponse<Long> response = accessAPI.getAccountBalanceAtBlockHeight(address, height);
 
     if (response instanceof FlowAccessApi.AccessApiCallResponse.Success) {
         return ((FlowAccessApi.AccessApiCallResponse.Success<Long>) response).getData();
     } else {
         FlowAccessApi.AccessApiCallResponse.Error errorResponse = (FlowAccessApi.AccessApiCallResponse.Error) response;
-        throw new RuntimeException(errorResponse.getMessage(), errorResponse.getThrowable());
+        throw new AccountBalanceException("Failed to retrieve balance at block height " + height, errorResponse.getMessage(), errorResponse.getThrowable());
     }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public long getBalanceAtBlockHeight(FlowAddress address, long height) {
FlowAccessApi.AccessApiCallResponse<Long> response = accessAPI.getAccountBalanceAtBlockHeight(address, height);
if (response instanceof FlowAccessApi.AccessApiCallResponse.Success) {
return ((FlowAccessApi.AccessApiCallResponse.Success<Long>) response).getData();
} else {
FlowAccessApi.AccessApiCallResponse.Error errorResponse = (FlowAccessApi.AccessApiCallResponse.Error) response;
throw new RuntimeException(errorResponse.getMessage(), errorResponse.getThrowable());
}
}
/**
* Retrieves the account balance at a specific block height.
*
* @param address The Flow address to query the balance for
* @param height The block height to query at
* @return The account balance
* @throws AccountBalanceException if the balance retrieval fails
* @throws IllegalArgumentException if address is null or height is negative
*/
public long getBalanceAtBlockHeight(FlowAddress address, long height) {
if (address == null) {
throw new IllegalArgumentException("address cannot be null");
}
if (height < 0) {
throw new IllegalArgumentException("block height cannot be negative");
}
FlowAccessApi.AccessApiCallResponse<Long> response = accessAPI.getAccountBalanceAtBlockHeight(address, height);
if (response instanceof FlowAccessApi.AccessApiCallResponse.Success) {
return ((FlowAccessApi.AccessApiCallResponse.Success<Long>) response).getData();
} else {
FlowAccessApi.AccessApiCallResponse.Error errorResponse = (FlowAccessApi.AccessApiCallResponse.Error) response;
throw new AccountBalanceException("Failed to retrieve balance at block height " + height, errorResponse.getMessage(), errorResponse.getThrowable());
}
}

Comment on lines +1 to +73
package org.onflow.examples.kotlin.getAccountBalance

import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.onflow.flow.common.test.FlowEmulatorProjectTest
import org.onflow.flow.common.test.FlowServiceAccountCredentials
import org.onflow.flow.common.test.FlowTestClient
import org.onflow.flow.common.test.TestAccount
import org.onflow.flow.sdk.FlowAccessApi

@FlowEmulatorProjectTest(flowJsonLocation = "../flow/flow.json")
internal class GetAccountBalanceAccessAPIConnectorTest {
@FlowServiceAccountCredentials
lateinit var serviceAccount: TestAccount

@FlowTestClient
lateinit var accessAPI: FlowAccessApi

private lateinit var balanceAPIConnector: GetAccountBalanceAccessAPIConnector

@BeforeEach
fun setup() {
balanceAPIConnector = GetAccountBalanceAccessAPIConnector(accessAPI)
}

@Test
fun `Can fetch account balance at the latest block`() {
val address = serviceAccount.flowAddress
val balance = balanceAPIConnector.getBalanceAtLatestBlock(address)

Assertions.assertNotNull(balance, "Balance should not be null")
Assertions.assertTrue(balance >= 0, "Balance should be non-negative")
}

@Test
fun `Can fetch account balance at a specific block height`() {
val address = serviceAccount.flowAddress
val latestBlock = accessAPI.getLatestBlock(true) // Fetch the latest sealed block

when (latestBlock) {
is FlowAccessApi.AccessApiCallResponse.Success -> {
val balanceAtHeight = balanceAPIConnector.getBalanceAtBlockHeight(address, latestBlock.data.height)

Assertions.assertNotNull(balanceAtHeight, "Balance at specific block height should not be null")
Assertions.assertTrue(balanceAtHeight >= 0, "Balance at specific block height should be non-negative")
}
is FlowAccessApi.AccessApiCallResponse.Error -> Assertions.fail("Failed to retrieve the latest block: ${latestBlock.message}")
}
}

@Test
fun `Balances at the latest block and specific block height should match`() {
val address = serviceAccount.flowAddress

// Fetch balance at latest block
val balanceAtLatest = balanceAPIConnector.getBalanceAtLatestBlock(address)

// Fetch latest block height
val latestBlock = accessAPI.getLatestBlock(true)
when (latestBlock) {
is FlowAccessApi.AccessApiCallResponse.Success -> {
val blockHeight = latestBlock.data.height

// Fetch balance at the same block height
val balanceAtHeight = balanceAPIConnector.getBalanceAtBlockHeight(address, blockHeight)

Assertions.assertEquals(balanceAtLatest, balanceAtHeight, "Balance at latest block and specific block height should match")
}
is FlowAccessApi.AccessApiCallResponse.Error -> Assertions.fail("Failed to retrieve the latest block: ${latestBlock.message}")
}
}
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add test cases for error scenarios.

The test suite would benefit from additional test cases covering error scenarios:

  • Invalid addresses
  • Non-existent block heights
  • Network errors

Example test case to add:

@Test
fun `Should handle invalid block height gracefully`() {
    val address = serviceAccount.flowAddress
    val invalidHeight = -1L
    
    val result = balanceAPIConnector.getBalanceAtBlockHeight(address, invalidHeight)
    
    when (result) {
        is FlowAccessApi.AccessApiCallResponse.Error -> 
            Assertions.assertTrue(result.message.contains("invalid block height"))
        is FlowAccessApi.AccessApiCallResponse.Success -> 
            Assertions.fail("Expected error for invalid block height")
    }
}

Comment on lines +27 to +34
@Test
fun `Can fetch account balance at the latest block`() {
val address = serviceAccount.flowAddress
val balance = balanceAPIConnector.getBalanceAtLatestBlock(address)

Assertions.assertNotNull(balance, "Balance should not be null")
Assertions.assertTrue(balance >= 0, "Balance should be non-negative")
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add error handling for getBalanceAtLatestBlock.

The test should handle potential errors from getBalanceAtLatestBlock similar to how getLatestBlock errors are handled in other tests.

 @Test
 fun `Can fetch account balance at the latest block`() {
     val address = serviceAccount.flowAddress
-    val balance = balanceAPIConnector.getBalanceAtLatestBlock(address)
-
-    Assertions.assertNotNull(balance, "Balance should not be null")
-    Assertions.assertTrue(balance >= 0, "Balance should be non-negative")
+    when (val result = balanceAPIConnector.getBalanceAtLatestBlock(address)) {
+        is FlowAccessApi.AccessApiCallResponse.Success -> {
+            val balance = result.data
+            Assertions.assertNotNull(balance, "Balance should not be null")
+            Assertions.assertTrue(balance >= 0, "Balance should be non-negative")
+        }
+        is FlowAccessApi.AccessApiCallResponse.Error -> 
+            Assertions.fail("Failed to retrieve balance: ${result.message}")
+    }
 }

Committable suggestion was skipped due to low confidence.

Comment on lines +38 to +54
@Test
public void testCanFetchBalanceAtSpecificBlockHeight() {
FlowAddress address = serviceAccount.getFlowAddress();

FlowAccessApi.AccessApiCallResponse<FlowBlock> latestBlockResponse = accessAPI.getLatestBlock(true);

if (latestBlockResponse instanceof FlowAccessApi.AccessApiCallResponse.Success) {
FlowBlock latestBlock = ((FlowAccessApi.AccessApiCallResponse.Success<FlowBlock>) latestBlockResponse).getData();
long blockHeight = latestBlock.getHeight();
long balanceAtHeight = balanceAPIConnector.getBalanceAtBlockHeight(address, blockHeight);

Assertions.assertTrue(balanceAtHeight >= 0, "Balance at specific block height should be non-negative");
} else {
FlowAccessApi.AccessApiCallResponse.Error errorResponse = (FlowAccessApi.AccessApiCallResponse.Error) latestBlockResponse;
Assertions.fail("Failed to fetch the latest block: " + errorResponse.getMessage());
}
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider refactoring duplicate error handling logic.

The error handling code for getLatestBlock is duplicated across test methods. Consider extracting it into a helper method.

private FlowBlock getLatestBlockOrFail() {
    FlowAccessApi.AccessApiCallResponse<FlowBlock> response = accessAPI.getLatestBlock(true);
    if (response instanceof FlowAccessApi.AccessApiCallResponse.Success) {
        return ((FlowAccessApi.AccessApiCallResponse.Success<FlowBlock>) response).getData();
    }
    FlowAccessApi.AccessApiCallResponse.Error errorResponse = 
        (FlowAccessApi.AccessApiCallResponse.Error) response;
    throw new AssertionError("Failed to fetch the latest block: " + errorResponse.getMessage());
}

Then use it in your tests:

@Test
public void testCanFetchBalanceAtSpecificBlockHeight() {
    FlowAddress address = serviceAccount.getFlowAddress();
    FlowBlock latestBlock = getLatestBlockOrFail();
    long blockHeight = latestBlock.getHeight();
    long balanceAtHeight = balanceAPIConnector.getBalanceAtBlockHeight(address, blockHeight);
    
    Assertions.assertTrue(balanceAtHeight >= 0, 
        String.format("Balance at height %d should be non-negative, but got: %d", 
            blockHeight, balanceAtHeight));
}

Also applies to: 56-76

Comment on lines +67 to +68
// Fetch balance at the same block height
long balanceAtHeight = balanceAPIConnector.getBalanceAtBlockHeight(address, blockHeight);
Copy link

Choose a reason for hiding this comment

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

💡 Codebase verification

Based on the implementation found, I can now provide the final response:

Add validation for negative block height parameter

The current implementation of getBalanceAtBlockHeight in GetAccountBalanceAccessAPIConnector does not validate the block height parameter. You should add a check at the beginning of the method to ensure the height parameter is non-negative:

if (height < 0) {
    throw new IllegalArgumentException("Block height cannot be negative");
}
🔗 Analysis chain

Add validation for block height parameter.

The getBalanceAtBlockHeight call should validate that the block height is non-negative before making the API call.


Let me check the actual implementation of the getBalanceAtBlockHeight method to verify if any validation exists.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check if parameter validation exists in the connector implementation
# Expected: Should find parameter validation in the connector class

# Search for the connector class implementation
echo "Searching for GetAccountBalanceAccessAPIConnector implementation..."
rg -l "class GetAccountBalanceAccessAPIConnector"

# Search for parameter validation in the getBalanceAtBlockHeight method
echo "Checking for parameter validation..."
ast-grep --pattern 'getBalanceAtBlockHeight($address, $height) {
  if ($height < 0) {
    $$$
  }
  $$$
}'

Length of output: 848


Script:

#!/bin/bash
# Search for the getBalanceAtBlockHeight method implementation in the connector class
echo "Searching for getBalanceAtBlockHeight implementation..."
rg -A 10 "getBalanceAtBlockHeight" java-example/src/main/java/org/onflow/examples/java/getAccountBalance/GetAccountBalanceAccessAPIConnector.java

Length of output: 892

Comment on lines +173 to +211
@Test
fun `Can get account balance at block height`() {
val address = serviceAccount.flowAddress

val latestBlock = try {
handleResult(
accessAPI.getLatestBlock(true),
"Failed to get latest block"
)
} catch (e: Exception) {
fail("Failed to retrieve latest block: ${e.message}")
}

val height = latestBlock.height

val balanceResponse = try {
handleResult(
accessAPI.getAccountBalanceAtBlockHeight(address, height),
"Failed to get account balance at block height"
)
} catch (e: Exception) {
fail("Failed to retrieve account balance at block height: ${e.message}")
}

assertThat(balanceResponse).isNotNull

val account = try {
handleResult(
accessAPI.getAccountByBlockHeight(address, height),
"Failed to get account by block height"
)
} catch (e: Exception) {
fail("Failed to retrieve account by block height: ${e.message}")
}

val normalizedBalance = balanceResponse / 100_000_000L

assertThat(normalizedBalance).isEqualTo(account.balance.toBigInteger().longValueExact())
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider adding error case tests and extracting common logic.

  1. The test only covers the happy path. Consider adding tests for:

    • Invalid block heights
    • Non-existent accounts
    • Network errors
  2. The normalization logic is duplicated. Consider extracting it to a helper method:

+ private fun normalizeFlowBalance(rawBalance: Long): Long =
+     rawBalance / FLOW_TOKEN_DECIMALS

     @Test
     fun `Can get account balance at block height`() {
         // ...
-        val normalizedBalance = balanceResponse / 100_000_000L
+        val normalizedBalance = normalizeFlowBalance(balanceResponse)

Committable suggestion was skipped due to low confidence.

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.

BlockSeal type differs from protobuf schema
2 participants