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

Merged
merged 10 commits into from
Oct 25, 2024
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package org.onflow.examples.java.getAccountBalance;

import org.onflow.flow.sdk.FlowAccessApi;
import org.onflow.flow.sdk.FlowAddress;

public class GetAccountBalanceAccessAPIConnector {
private final FlowAccessApi accessAPI;

public GetAccountBalanceAccessAPIConnector(FlowAccessApi accessAPI) {
this.accessAPI = accessAPI;
}

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());
}
}
lealobanov marked this conversation as resolved.
Show resolved Hide resolved

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());
}
}
lealobanov marked this conversation as resolved.
Show resolved Hide resolved
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package org.onflow.examples.java.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;
import org.onflow.flow.sdk.FlowAddress;
import org.onflow.flow.sdk.FlowBlock;

@FlowEmulatorProjectTest(flowJsonLocation = "../flow/flow.json")
public class GetAccountBalanceAccessAPIConnectorTest {

@FlowTestClient
private FlowAccessApi accessAPI;

@FlowServiceAccountCredentials
private TestAccount serviceAccount;

private GetAccountBalanceAccessAPIConnector balanceAPIConnector;

@BeforeEach
public void setup() {
balanceAPIConnector = new GetAccountBalanceAccessAPIConnector(accessAPI);
}

@Test
public void testCanFetchBalanceAtLatestBlock() {
FlowAddress address = serviceAccount.getFlowAddress();
long balance = balanceAPIConnector.getBalanceAtLatestBlock(address);

Assertions.assertTrue(balance >= 0, "Balance at the latest block should be non-negative");
}

@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());
}
}
lealobanov marked this conversation as resolved.
Show resolved Hide resolved

@Test
public void testBalancesAtLatestBlockAndSpecificHeightShouldMatch() {
FlowAddress address = serviceAccount.getFlowAddress();

long balanceAtLatest = balanceAPIConnector.getBalanceAtLatestBlock(address);
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();

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

@coderabbitai coderabbitai bot Oct 23, 2024

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

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Will look into this

Copy link
Contributor

Choose a reason for hiding this comment

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

@lealobanov, thank you for looking into this!

(_/)
(^_^)


// Ensure balances match
Assertions.assertEquals(balanceAtLatest, balanceAtHeight, "Balance at latest block and specific block height should match");
} else {
FlowAccessApi.AccessApiCallResponse.Error errorResponse = (FlowAccessApi.AccessApiCallResponse.Error) latestBlockResponse;
Assertions.fail("Failed to fetch the latest block: " + errorResponse.getMessage());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package org.onflow.examples.kotlin.getAccountBalance

import org.onflow.flow.sdk.*

internal class GetAccountBalanceAccessAPIConnector(
private val accessAPI: FlowAccessApi
) {
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)
}
lealobanov marked this conversation as resolved.
Show resolved Hide resolved

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)
}
lealobanov marked this conversation as resolved.
Show resolved Hide resolved
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,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")
}
lealobanov marked this conversation as resolved.
Show resolved Hide resolved

@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}")
}
}
}
lealobanov marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,75 @@ class TransactionIntegrationTest {
assertThat(blockHeader.height).isEqualTo(latestBlock.height)
}

@Test
fun `Can get account balance at latest block`() {
val address = serviceAccount.flowAddress

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

assertThat(balanceResponse).isNotNull

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

val normalizedBalance = balanceResponse / 100_000_000L

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

@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())
}
lealobanov marked this conversation as resolved.
Show resolved Hide resolved

@Test
fun `Can get latest block`() {
val latestBlock = try {
Expand Down
4 changes: 4 additions & 0 deletions sdk/src/main/kotlin/org/onflow/flow/sdk/AsyncFlowAccessApi.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ interface AsyncFlowAccessApi {

fun getLatestBlock(sealed: Boolean = true): CompletableFuture<FlowAccessApi.AccessApiCallResponse<FlowBlock>>

fun getAccountBalanceAtLatestBlock(address: FlowAddress): CompletableFuture<FlowAccessApi.AccessApiCallResponse<Long>>

fun getAccountBalanceAtBlockHeight(address: FlowAddress, height: Long): CompletableFuture<FlowAccessApi.AccessApiCallResponse<Long>>

fun getBlockById(id: FlowId): CompletableFuture<FlowAccessApi.AccessApiCallResponse<FlowBlock?>>

fun getBlockByHeight(height: Long): CompletableFuture<FlowAccessApi.AccessApiCallResponse<FlowBlock?>>
Expand Down
4 changes: 4 additions & 0 deletions sdk/src/main/kotlin/org/onflow/flow/sdk/FlowAccessApi.kt
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ interface FlowAccessApi {

fun getLatestBlock(sealed: Boolean = true): AccessApiCallResponse<FlowBlock>

fun getAccountBalanceAtLatestBlock(address: FlowAddress): AccessApiCallResponse<Long>

fun getAccountBalanceAtBlockHeight(address: FlowAddress, height: Long): AccessApiCallResponse<Long>

fun getBlockById(id: FlowId): AccessApiCallResponse<FlowBlock>

fun getBlockByHeight(height: Long): AccessApiCallResponse<FlowBlock>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,57 @@ class AsyncFlowAccessApiImpl(
}
}

override fun getAccountBalanceAtLatestBlock(address: FlowAddress): CompletableFuture<FlowAccessApi.AccessApiCallResponse<Long>> {
return try {
completableFuture(
try {
api.getAccountBalanceAtLatestBlock(
Access.GetAccountBalanceAtLatestBlockRequest
.newBuilder()
.setAddress(address.byteStringValue)
.build()
)
} catch (e: Exception) {
return CompletableFuture.completedFuture(FlowAccessApi.AccessApiCallResponse.Error("Failed to get account balance at latest block", e))
}
).handle { response, ex ->
if (ex != null) {
FlowAccessApi.AccessApiCallResponse.Error("Failed to get account balance at latest block", ex)
} else {
FlowAccessApi.AccessApiCallResponse.Success(response.balance)
}
}
} catch (e: Exception) {
CompletableFuture.completedFuture(FlowAccessApi.AccessApiCallResponse.Error("Failed to get account balance at latest block", e))
}
}

override fun getAccountBalanceAtBlockHeight(address: FlowAddress, height: Long): CompletableFuture<FlowAccessApi.AccessApiCallResponse<Long>> {
return try {
completableFuture(
try {
api.getAccountBalanceAtBlockHeight(
Access.GetAccountBalanceAtBlockHeightRequest
.newBuilder()
.setAddress(address.byteStringValue)
.setBlockHeight(height)
.build()
)
} catch (e: Exception) {
return CompletableFuture.completedFuture(FlowAccessApi.AccessApiCallResponse.Error("Failed to get account balance at block height", e))
}
).handle { response, ex ->
if (ex != null) {
FlowAccessApi.AccessApiCallResponse.Error("Failed to get account balance at block height", ex)
} else {
FlowAccessApi.AccessApiCallResponse.Success(response.balance)
}
}
} catch (e: Exception) {
CompletableFuture.completedFuture(FlowAccessApi.AccessApiCallResponse.Error("Failed to get account balance at block height", e))
}
}

override fun getBlockById(id: FlowId): CompletableFuture<FlowAccessApi.AccessApiCallResponse<FlowBlock?>> {
return try {
completableFuture(
Expand Down
27 changes: 27 additions & 0 deletions sdk/src/main/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImpl.kt
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,33 @@ class FlowAccessApiImpl(
FlowAccessApi.AccessApiCallResponse.Error("Failed to get latest block", e)
}

override fun getAccountBalanceAtLatestBlock(address: FlowAddress): FlowAccessApi.AccessApiCallResponse<Long> =
try {
val ret = api.getAccountBalanceAtLatestBlock(
Access.GetAccountBalanceAtLatestBlockRequest
.newBuilder()
.setAddress(address.byteStringValue)
.build()
)
FlowAccessApi.AccessApiCallResponse.Success(ret.balance)
} catch (e: Exception) {
FlowAccessApi.AccessApiCallResponse.Error("Failed to get account balance at latest block", e)
}

override fun getAccountBalanceAtBlockHeight(address: FlowAddress, height: Long): FlowAccessApi.AccessApiCallResponse<Long> =
try {
val ret = api.getAccountBalanceAtBlockHeight(
Access.GetAccountBalanceAtBlockHeightRequest
.newBuilder()
.setAddress(address.byteStringValue)
.setBlockHeight(height)
.build()
)
FlowAccessApi.AccessApiCallResponse.Success(ret.balance)
} catch (e: Exception) {
FlowAccessApi.AccessApiCallResponse.Error("Failed to get account balance at block height", e)
}

override fun getBlockById(id: FlowId): FlowAccessApi.AccessApiCallResponse<FlowBlock> =
try {
val ret = api.getBlockByID(
Expand Down
Loading
Loading