From 218b5e2ea38d5b36c5a23dd86053bc0cb6f3a25b Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Sun, 20 Oct 2024 22:19:53 +0900 Subject: [PATCH 1/9] FlowBlockSeal protobuf updates --- .../main/kotlin/org/onflow/flow/sdk/models.kt | 63 ++++++++- .../sdk/models/FlowAggregatedSignatureTest.kt | 83 ++++++++++++ .../flow/sdk/models/FlowBlockSealTest.kt | 120 ++++++++++++++++++ 3 files changed, 261 insertions(+), 5 deletions(-) create mode 100644 sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowAggregatedSignatureTest.kt create mode 100644 sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowBlockSealTest.kt diff --git a/sdk/src/main/kotlin/org/onflow/flow/sdk/models.kt b/sdk/src/main/kotlin/org/onflow/flow/sdk/models.kt index 5e6a0b67..3e18c809 100644 --- a/sdk/src/main/kotlin/org/onflow/flow/sdk/models.kt +++ b/sdk/src/main/kotlin/org/onflow/flow/sdk/models.kt @@ -1201,27 +1201,80 @@ data class FlowCollectionGuarantee( } data class FlowBlockSeal( - val id: FlowId, + val blockId: FlowId, val executionReceiptId: FlowId, val executionReceiptSignatures: List, - val resultApprovalSignatures: List + val resultApprovalSignatures: List, + val finalState: ByteArray, + val resultId: FlowId, + val aggregatedApprovalSigs: List, ) : Serializable { companion object { @JvmStatic fun of(value: BlockSealOuterClass.BlockSeal) = FlowBlockSeal( - id = FlowId.of(value.blockId.toByteArray()), + blockId = FlowId.of(value.blockId.toByteArray()), executionReceiptId = FlowId.of(value.executionReceiptId.toByteArray()), executionReceiptSignatures = value.executionReceiptSignaturesList.map { FlowSignature(it.toByteArray()) }, - resultApprovalSignatures = value.executionReceiptSignaturesList.map { FlowSignature(it.toByteArray()) } + resultApprovalSignatures = value.resultApprovalSignaturesList.map { FlowSignature(it.toByteArray()) }, + finalState = value.finalState.toByteArray(), + resultId = FlowId.of(value.resultId.toByteArray()), + aggregatedApprovalSigs = value.aggregatedApprovalSigsList.map { FlowAggregatedSignature.of(it) }, ) } @JvmOverloads fun builder(builder: BlockSealOuterClass.BlockSeal.Builder = BlockSealOuterClass.BlockSeal.newBuilder()): BlockSealOuterClass.BlockSeal.Builder = builder - .setBlockId(id.byteStringValue) + .setBlockId(blockId.byteStringValue) .setExecutionReceiptId(executionReceiptId.byteStringValue) .addAllExecutionReceiptSignatures(executionReceiptSignatures.map { it.byteStringValue }) .addAllResultApprovalSignatures(resultApprovalSignatures.map { it.byteStringValue }) + .setFinalState(UnsafeByteOperations.unsafeWrap(finalState)) + .setResultId(resultId.byteStringValue) + .addAllAggregatedApprovalSigs(aggregatedApprovalSigs.map { it.builder().build() }) + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is FlowBlockSeal) return false + + if (blockId != other.blockId) return false + if (executionReceiptId != other.executionReceiptId) return false + if (executionReceiptSignatures != other.executionReceiptSignatures) return false + if (resultApprovalSignatures != other.resultApprovalSignatures) return false + if (!finalState.contentEquals(other.finalState)) return false + if (resultId != other.resultId) return false + if (aggregatedApprovalSigs != other.aggregatedApprovalSigs) return false + + return true + } + + override fun hashCode(): Int { + var result = blockId.hashCode() + result = 31 * result + executionReceiptId.hashCode() + result = 31 * result + executionReceiptSignatures.hashCode() + result = 31 * result + resultApprovalSignatures.hashCode() + result = 31 * result + finalState.contentHashCode() + result = 31 * result + resultId.hashCode() + result = 31 * result + aggregatedApprovalSigs.hashCode() + return result + } +} + +data class FlowAggregatedSignature( + val verifierSignatures: List, + val signerIds: List, +) : Serializable { + companion object { + @JvmStatic + fun of(value: BlockSealOuterClass.AggregatedSignature) = FlowAggregatedSignature( + verifierSignatures = value.verifierSignaturesList.map { FlowSignature(it.toByteArray()) }, + signerIds = value.signerIdsList.map { FlowId.of(it.toByteArray()) } + ) + } + + @JvmOverloads + fun builder(builder: BlockSealOuterClass.AggregatedSignature.Builder = BlockSealOuterClass.AggregatedSignature.newBuilder()): BlockSealOuterClass.AggregatedSignature.Builder = builder + .addAllVerifierSignatures(verifierSignatures.map { it.byteStringValue }) + .addAllSignerIds(signerIds.map { it.byteStringValue }) } data class FlowCollection( diff --git a/sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowAggregatedSignatureTest.kt b/sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowAggregatedSignatureTest.kt new file mode 100644 index 00000000..9dbd9f6e --- /dev/null +++ b/sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowAggregatedSignatureTest.kt @@ -0,0 +1,83 @@ +package org.onflow.flow.sdk.models + +import org.onflow.flow.sdk.FlowAggregatedSignature +import org.onflow.flow.sdk.FlowId +import org.onflow.flow.sdk.FlowSignature +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test +import org.mockito.Mockito +import org.onflow.protobuf.entities.BlockSealOuterClass + +class FlowAggregatedSignatureTest { + @Test + fun `test FlowAggregatedSignature equals and hashCode`() { + val signature1 = FlowSignature("signature1".toByteArray()) + val signature2 = FlowSignature("signature2".toByteArray()) + + val signerId1 = FlowId.of("signerId1".toByteArray()) + val signerId2 = FlowId.of("signerId2".toByteArray()) + + val aggregatedSignature1 = FlowAggregatedSignature( + verifierSignatures = listOf(signature1), + signerIds = listOf(signerId1) + ) + + val aggregatedSignature2 = FlowAggregatedSignature( + verifierSignatures = listOf(signature1), + signerIds = listOf(signerId1) + ) + + val aggregatedSignature3 = FlowAggregatedSignature( + verifierSignatures = listOf(signature2), + signerIds = listOf(signerId2) + ) + + // Test equality + assertEquals(aggregatedSignature1, aggregatedSignature2) + assertNotEquals(aggregatedSignature1, aggregatedSignature3) + + // Test hashCode + assertEquals(aggregatedSignature1.hashCode(), aggregatedSignature2.hashCode()) + assertNotEquals(aggregatedSignature1.hashCode(), aggregatedSignature3.hashCode()) + } + + @Test + fun `test FlowAggregatedSignature of function`() { + // Mock BlockSealOuterClass.AggregatedSignature + val aggregatedSignatureProto = Mockito.mock(BlockSealOuterClass.AggregatedSignature::class.java) + + val signatureBytes = "signature".toByteArray() + val signerIdBytes = "signerId".toByteArray() + + Mockito.`when`(aggregatedSignatureProto.verifierSignaturesList).thenReturn( + listOf(com.google.protobuf.ByteString.copyFrom(signatureBytes)) + ) + Mockito.`when`(aggregatedSignatureProto.signerIdsList).thenReturn( + listOf(com.google.protobuf.ByteString.copyFrom(signerIdBytes)) + ) + + val flowAggregatedSignature = FlowAggregatedSignature.of(aggregatedSignatureProto) + + assertEquals(1, flowAggregatedSignature.verifierSignatures.size) + assertEquals(FlowSignature(signatureBytes), flowAggregatedSignature.verifierSignatures[0]) + + assertEquals(1, flowAggregatedSignature.signerIds.size) + assertEquals(FlowId.of(signerIdBytes), flowAggregatedSignature.signerIds[0]) + } + + @Test + fun `test FlowAggregatedSignature builder function`() { + val signature1 = FlowSignature("signature1".toByteArray()) + val signerId1 = FlowId.of("signerId1".toByteArray()) + + val aggregatedSignature = FlowAggregatedSignature( + verifierSignatures = listOf(signature1), + signerIds = listOf(signerId1) + ) + + val builderResult = aggregatedSignature.builder() + + assertEquals(listOf(signature1.byteStringValue), builderResult.verifierSignaturesList) + assertEquals(listOf(signerId1.byteStringValue), builderResult.signerIdsList) + } +} diff --git a/sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowBlockSealTest.kt b/sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowBlockSealTest.kt new file mode 100644 index 00000000..95d0fe25 --- /dev/null +++ b/sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowBlockSealTest.kt @@ -0,0 +1,120 @@ +package org.onflow.flow.sdk.models + +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test +import org.mockito.Mockito +import org.onflow.flow.sdk.FlowAggregatedSignature +import org.onflow.flow.sdk.FlowBlockSeal +import org.onflow.flow.sdk.FlowId +import org.onflow.flow.sdk.FlowSignature +import org.onflow.protobuf.entities.BlockSealOuterClass + +class FlowBlockSealTest { + @Test + fun `test FlowBlockSeal equals and hashCode`() { + val blockId = FlowId.of("blockId".toByteArray()) + val executionReceiptId = FlowId.of("executionReceiptId".toByteArray()) + val resultId = FlowId.of("resultId".toByteArray()) + val finalState = "finalState".toByteArray() + + val signature1 = FlowSignature("signature1".toByteArray()) + val signature2 = FlowSignature("signature2".toByteArray()) + + val aggregatedSignature1 = FlowAggregatedSignature( + verifierSignatures = listOf(signature1), + signerIds = listOf(blockId) + ) + + val seal1 = FlowBlockSeal( + blockId = blockId, + executionReceiptId = executionReceiptId, + executionReceiptSignatures = listOf(signature1), + resultApprovalSignatures = listOf(signature2), + finalState = finalState, + resultId = resultId, + aggregatedApprovalSigs = listOf(aggregatedSignature1) + ) + + val seal2 = FlowBlockSeal( + blockId = blockId, + executionReceiptId = executionReceiptId, + executionReceiptSignatures = listOf(signature1), + resultApprovalSignatures = listOf(signature2), + finalState = finalState, + resultId = resultId, + aggregatedApprovalSigs = listOf(aggregatedSignature1) + ) + + // Testing equals + assertEquals(seal1, seal2) + + // Testing hashCode + assertEquals(seal1.hashCode(), seal2.hashCode()) + } + + @Test + fun `test FlowBlockSeal of function`() { + // Mock BlockSealOuterClass.BlockSeal + val blockSealProto = Mockito.mock(BlockSealOuterClass.BlockSeal::class.java) + + val blockIdBytes = "blockId".toByteArray() + val executionReceiptIdBytes = "executionReceiptId".toByteArray() + val resultIdBytes = "resultId".toByteArray() + val finalStateBytes = "finalState".toByteArray() + + Mockito.`when`(blockSealProto.blockId).thenReturn(com.google.protobuf.ByteString.copyFrom(blockIdBytes)) + Mockito.`when`(blockSealProto.executionReceiptId).thenReturn(com.google.protobuf.ByteString.copyFrom(executionReceiptIdBytes)) + Mockito.`when`(blockSealProto.resultId).thenReturn(com.google.protobuf.ByteString.copyFrom(resultIdBytes)) + Mockito.`when`(blockSealProto.finalState).thenReturn(com.google.protobuf.ByteString.copyFrom(finalStateBytes)) + Mockito.`when`(blockSealProto.executionReceiptSignaturesList).thenReturn(listOf( + com.google.protobuf.ByteString.copyFrom("signature1".toByteArray()) + )) + Mockito.`when`(blockSealProto.resultApprovalSignaturesList).thenReturn(listOf( + com.google.protobuf.ByteString.copyFrom("signature2".toByteArray()) + )) + Mockito.`when`(blockSealProto.aggregatedApprovalSigsList).thenReturn(listOf( + BlockSealOuterClass.AggregatedSignature.newBuilder().build() + )) + + val flowBlockSeal = FlowBlockSeal.of(blockSealProto) + + assertEquals(FlowId.of(blockIdBytes), flowBlockSeal.blockId) + assertEquals(FlowId.of(executionReceiptIdBytes), flowBlockSeal.executionReceiptId) + assertEquals(FlowId.of(resultIdBytes), flowBlockSeal.resultId) + assertArrayEquals(finalStateBytes, flowBlockSeal.finalState) + } + + @Test + fun `test FlowBlockSeal builder function`() { + val blockId = FlowId.of("blockId".toByteArray()) + val executionReceiptId = FlowId.of("executionReceiptId".toByteArray()) + val resultId = FlowId.of("resultId".toByteArray()) + val finalState = "finalState".toByteArray() + + val signature1 = FlowSignature("signature1".toByteArray()) + val signature2 = FlowSignature("signature2".toByteArray()) + val aggregatedSignature1 = FlowAggregatedSignature( + verifierSignatures = listOf(signature1), + signerIds = listOf(blockId) + ) + + val seal = FlowBlockSeal( + blockId = blockId, + executionReceiptId = executionReceiptId, + executionReceiptSignatures = listOf(signature1), + resultApprovalSignatures = listOf(signature2), + finalState = finalState, + resultId = resultId, + aggregatedApprovalSigs = listOf(aggregatedSignature1) + ) + + val builderResult = seal.builder() + + assertEquals(blockId.byteStringValue, builderResult.blockId) + assertEquals(executionReceiptId.byteStringValue, builderResult.executionReceiptId) + assertEquals(resultId.byteStringValue, builderResult.resultId) + assertEquals(finalState.toList(), builderResult.finalState.toByteArray().toList()) + assertEquals(listOf(signature1.byteStringValue), builderResult.executionReceiptSignaturesList) + assertEquals(listOf(signature2.byteStringValue), builderResult.resultApprovalSignaturesList) + } +} From b7308f1768f661fef4f227703a2da58c88ea1d59 Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Sun, 20 Oct 2024 22:34:36 +0900 Subject: [PATCH 2/9] Lint --- .../sdk/models/FlowAggregatedSignatureTest.kt | 10 +++- .../flow/sdk/models/FlowBlockSealTest.kt | 46 +++++++++++++------ 2 files changed, 41 insertions(+), 15 deletions(-) diff --git a/sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowAggregatedSignatureTest.kt b/sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowAggregatedSignatureTest.kt index 9dbd9f6e..d9afd4e2 100644 --- a/sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowAggregatedSignatureTest.kt +++ b/sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowAggregatedSignatureTest.kt @@ -50,10 +50,16 @@ class FlowAggregatedSignatureTest { val signerIdBytes = "signerId".toByteArray() Mockito.`when`(aggregatedSignatureProto.verifierSignaturesList).thenReturn( - listOf(com.google.protobuf.ByteString.copyFrom(signatureBytes)) + listOf( + com.google.protobuf.ByteString + .copyFrom(signatureBytes) + ) ) Mockito.`when`(aggregatedSignatureProto.signerIdsList).thenReturn( - listOf(com.google.protobuf.ByteString.copyFrom(signerIdBytes)) + listOf( + com.google.protobuf.ByteString + .copyFrom(signerIdBytes) + ) ) val flowAggregatedSignature = FlowAggregatedSignature.of(aggregatedSignatureProto) diff --git a/sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowBlockSealTest.kt b/sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowBlockSealTest.kt index 95d0fe25..1de6d035 100644 --- a/sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowBlockSealTest.kt +++ b/sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowBlockSealTest.kt @@ -62,19 +62,39 @@ class FlowBlockSealTest { val resultIdBytes = "resultId".toByteArray() val finalStateBytes = "finalState".toByteArray() - Mockito.`when`(blockSealProto.blockId).thenReturn(com.google.protobuf.ByteString.copyFrom(blockIdBytes)) - Mockito.`when`(blockSealProto.executionReceiptId).thenReturn(com.google.protobuf.ByteString.copyFrom(executionReceiptIdBytes)) - Mockito.`when`(blockSealProto.resultId).thenReturn(com.google.protobuf.ByteString.copyFrom(resultIdBytes)) - Mockito.`when`(blockSealProto.finalState).thenReturn(com.google.protobuf.ByteString.copyFrom(finalStateBytes)) - Mockito.`when`(blockSealProto.executionReceiptSignaturesList).thenReturn(listOf( - com.google.protobuf.ByteString.copyFrom("signature1".toByteArray()) - )) - Mockito.`when`(blockSealProto.resultApprovalSignaturesList).thenReturn(listOf( - com.google.protobuf.ByteString.copyFrom("signature2".toByteArray()) - )) - Mockito.`when`(blockSealProto.aggregatedApprovalSigsList).thenReturn(listOf( - BlockSealOuterClass.AggregatedSignature.newBuilder().build() - )) + Mockito.`when`(blockSealProto.blockId).thenReturn( + com.google.protobuf.ByteString + .copyFrom(blockIdBytes) + ) + Mockito.`when`(blockSealProto.executionReceiptId).thenReturn( + com.google.protobuf.ByteString + .copyFrom(executionReceiptIdBytes) + ) + Mockito.`when`(blockSealProto.resultId).thenReturn( + com.google.protobuf.ByteString + .copyFrom(resultIdBytes) + ) + Mockito.`when`(blockSealProto.finalState).thenReturn( + com.google.protobuf.ByteString + .copyFrom(finalStateBytes) + ) + Mockito.`when`(blockSealProto.executionReceiptSignaturesList).thenReturn( + listOf( + com.google.protobuf.ByteString + .copyFrom("signature1".toByteArray()) + ) + ) + Mockito.`when`(blockSealProto.resultApprovalSignaturesList).thenReturn( + listOf( + com.google.protobuf.ByteString + .copyFrom("signature2".toByteArray()) + ) + ) + Mockito.`when`(blockSealProto.aggregatedApprovalSigsList).thenReturn( + listOf( + BlockSealOuterClass.AggregatedSignature.newBuilder().build() + ) + ) val flowBlockSeal = FlowBlockSeal.of(blockSealProto) From 1f4ab77d696ffbdb68c248e5dc6e0b322714ca46 Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Sun, 20 Oct 2024 23:23:25 +0900 Subject: [PATCH 3/9] Add handling for null pointer exception --- .../main/kotlin/org/onflow/flow/sdk/models.kt | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/sdk/src/main/kotlin/org/onflow/flow/sdk/models.kt b/sdk/src/main/kotlin/org/onflow/flow/sdk/models.kt index 3e18c809..981b4067 100644 --- a/sdk/src/main/kotlin/org/onflow/flow/sdk/models.kt +++ b/sdk/src/main/kotlin/org/onflow/flow/sdk/models.kt @@ -1520,15 +1520,17 @@ data class FlowNodeVersionInfo( .setProtocolVersion(protocolVersion) .setSporkRootBlockHeight(sporkRootBlockHeight) .setNodeRootBlockHeight(nodeRootBlockHeight) - .setCompatibleRange( + .apply { compatibleRange?.let { - NodeVersionInfoOuterClass.CompatibleRange - .newBuilder() - .setStartHeight(it.startHeight) - .setEndHeight(it.endHeight) - .build() + setCompatibleRange( + NodeVersionInfoOuterClass.CompatibleRange + .newBuilder() + .setStartHeight(it.startHeight) + .setEndHeight(it.endHeight) + .build() + ) } - ) + } override fun equals(other: Any?): Boolean { if (this === other) return true From 575a93786adb0360a77b8b56a2ccc4602c20bc52 Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Tue, 22 Oct 2024 02:42:00 +0900 Subject: [PATCH 4/9] WIP: Get account balance endpoints --- .../kotlin/org/onflow/flow/sdk/FlowAccessApi.kt | 4 ++++ .../org/onflow/flow/sdk/impl/FlowAccessApiImpl.kt | 13 +++++++++++++ 2 files changed, 17 insertions(+) diff --git a/sdk/src/main/kotlin/org/onflow/flow/sdk/FlowAccessApi.kt b/sdk/src/main/kotlin/org/onflow/flow/sdk/FlowAccessApi.kt index f18239c3..200cda25 100644 --- a/sdk/src/main/kotlin/org/onflow/flow/sdk/FlowAccessApi.kt +++ b/sdk/src/main/kotlin/org/onflow/flow/sdk/FlowAccessApi.kt @@ -26,6 +26,10 @@ interface FlowAccessApi { fun getLatestBlock(sealed: Boolean = true): AccessApiCallResponse + fun getAccountBalanceAtLatestBlock(address: FlowAddress): AccessApiCallResponse + + fun getAccountBalanceAtBlockHeight(address: FlowAddress, height: Long): AccessApiCallResponse + fun getBlockById(id: FlowId): AccessApiCallResponse fun getBlockByHeight(height: Long): AccessApiCallResponse diff --git a/sdk/src/main/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImpl.kt b/sdk/src/main/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImpl.kt index 751cb94e..7df7e09e 100644 --- a/sdk/src/main/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImpl.kt +++ b/sdk/src/main/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImpl.kt @@ -96,6 +96,19 @@ class FlowAccessApiImpl( FlowAccessApi.AccessApiCallResponse.Error("Failed to get latest block", e) } + override fun getAccountBalanceAtLatestBlock(address: FlowAddress): FlowAccessApi.AccessApiCallResponse = + 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 getBlockById(id: FlowId): FlowAccessApi.AccessApiCallResponse = try { val ret = api.getBlockByID( From aa8a22616a0453c406dfd24c193bd83b14814a38 Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Tue, 22 Oct 2024 02:49:37 +0900 Subject: [PATCH 5/9] WIP: Get account balance endpoints --- .../org/onflow/flow/sdk/AsyncFlowAccessApi.kt | 4 ++ .../flow/sdk/impl/AsyncFlowAccessApiImpl.kt | 51 +++++++++++++++++++ .../onflow/flow/sdk/impl/FlowAccessApiImpl.kt | 14 +++++ 3 files changed, 69 insertions(+) diff --git a/sdk/src/main/kotlin/org/onflow/flow/sdk/AsyncFlowAccessApi.kt b/sdk/src/main/kotlin/org/onflow/flow/sdk/AsyncFlowAccessApi.kt index 78be56d3..ebe72a6d 100644 --- a/sdk/src/main/kotlin/org/onflow/flow/sdk/AsyncFlowAccessApi.kt +++ b/sdk/src/main/kotlin/org/onflow/flow/sdk/AsyncFlowAccessApi.kt @@ -14,6 +14,10 @@ interface AsyncFlowAccessApi { fun getLatestBlock(sealed: Boolean = true): CompletableFuture> + fun getAccountBalanceAtLatestBlock(address: FlowAddress): CompletableFuture> + + fun getAccountBalanceAtBlockHeight(address: FlowAddress, height: Long): CompletableFuture> + fun getBlockById(id: FlowId): CompletableFuture> fun getBlockByHeight(height: Long): CompletableFuture> diff --git a/sdk/src/main/kotlin/org/onflow/flow/sdk/impl/AsyncFlowAccessApiImpl.kt b/sdk/src/main/kotlin/org/onflow/flow/sdk/impl/AsyncFlowAccessApiImpl.kt index f5fd81e1..a2366fe2 100644 --- a/sdk/src/main/kotlin/org/onflow/flow/sdk/impl/AsyncFlowAccessApiImpl.kt +++ b/sdk/src/main/kotlin/org/onflow/flow/sdk/impl/AsyncFlowAccessApiImpl.kt @@ -152,6 +152,57 @@ class AsyncFlowAccessApiImpl( } } + override fun getAccountBalanceAtLatestBlock(address: FlowAddress): CompletableFuture> { + 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> { + 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> { return try { completableFuture( diff --git a/sdk/src/main/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImpl.kt b/sdk/src/main/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImpl.kt index 7df7e09e..256ecbc7 100644 --- a/sdk/src/main/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImpl.kt +++ b/sdk/src/main/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImpl.kt @@ -109,6 +109,20 @@ class FlowAccessApiImpl( FlowAccessApi.AccessApiCallResponse.Error("Failed to get account balance at latest block", e) } + override fun getAccountBalanceAtBlockHeight(address: FlowAddress, height: Long): FlowAccessApi.AccessApiCallResponse = + 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 = try { val ret = api.getBlockByID( From 93e6aeefc2963b505b9d6dd4b4031fe2c7aae7f0 Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Wed, 23 Oct 2024 01:41:56 +0900 Subject: [PATCH 6/9] Update unit tests for account balance endpoints --- .../org/onflow/flow/sdk/FlowAccessApiTest.kt | 62 +++++++++++++++ .../sdk/impl/AsyncFlowAccessApiImplTest.kt | 64 ++++++++++++++++ .../flow/sdk/impl/FlowAccessApiImplTest.kt | 75 +++++++++++++++++++ 3 files changed, 201 insertions(+) diff --git a/sdk/src/test/kotlin/org/onflow/flow/sdk/FlowAccessApiTest.kt b/sdk/src/test/kotlin/org/onflow/flow/sdk/FlowAccessApiTest.kt index 9c825ea8..1cd29158 100644 --- a/sdk/src/test/kotlin/org/onflow/flow/sdk/FlowAccessApiTest.kt +++ b/sdk/src/test/kotlin/org/onflow/flow/sdk/FlowAccessApiTest.kt @@ -88,6 +88,68 @@ class FlowAccessApiTest { assertEquals(FlowAccessApi.AccessApiCallResponse.Success(block), result) } + @Test + fun `Test getAccountBalanceAtLatestBlock success`() { + val flowAccessApi = mock(FlowAccessApi::class.java) + val flowAddress = FlowAddress("01") + val expectedBalance = 1000L + val response = FlowAccessApi.AccessApiCallResponse.Success(expectedBalance) + + `when`(flowAccessApi.getAccountBalanceAtLatestBlock(flowAddress)).thenReturn(response) + + val result = flowAccessApi.getAccountBalanceAtLatestBlock(flowAddress) + + assertEquals(response, result) + verify(flowAccessApi).getAccountBalanceAtLatestBlock(flowAddress) + } + + @Test + fun `Test getAccountBalanceAtLatestBlock failure`() { + val flowAccessApi = mock(FlowAccessApi::class.java) + val flowAddress = FlowAddress("01") + val exception = RuntimeException("Test exception") + val response = FlowAccessApi.AccessApiCallResponse.Error("Failed to get account balance at latest block", exception) + + `when`(flowAccessApi.getAccountBalanceAtLatestBlock(flowAddress)).thenReturn(response) + + val result = flowAccessApi.getAccountBalanceAtLatestBlock(flowAddress) + + assertEquals(response, result) + verify(flowAccessApi).getAccountBalanceAtLatestBlock(flowAddress) + } + + @Test + fun `Test getAccountBalanceAtBlockHeight success`() { + val flowAccessApi = mock(FlowAccessApi::class.java) + val flowAddress = FlowAddress("01") + val blockHeight = 123L + val expectedBalance = 1000L + val response = FlowAccessApi.AccessApiCallResponse.Success(expectedBalance) + + `when`(flowAccessApi.getAccountBalanceAtBlockHeight(flowAddress, blockHeight)).thenReturn(response) + + val result = flowAccessApi.getAccountBalanceAtBlockHeight(flowAddress, blockHeight) + + assertEquals(response, result) + verify(flowAccessApi).getAccountBalanceAtBlockHeight(flowAddress, blockHeight) + } + + @Test + fun `Test getAccountBalanceAtBlockHeight failure`() { + val flowAccessApi = mock(FlowAccessApi::class.java) + val flowAddress = FlowAddress("01") + val blockHeight = 123L + val exception = RuntimeException("Test exception") + val response = FlowAccessApi.AccessApiCallResponse.Error("Failed to get account balance at block height", exception) + + `when`(flowAccessApi.getAccountBalanceAtBlockHeight(flowAddress, blockHeight)).thenReturn(response) + + val result = flowAccessApi.getAccountBalanceAtBlockHeight(flowAddress, blockHeight) + + assertEquals(response, result) + verify(flowAccessApi).getAccountBalanceAtBlockHeight(flowAddress, blockHeight) + } + @Test fun `Test getCollectionById`() { val flowAccessApi = mock(FlowAccessApi::class.java) diff --git a/sdk/src/test/kotlin/org/onflow/flow/sdk/impl/AsyncFlowAccessApiImplTest.kt b/sdk/src/test/kotlin/org/onflow/flow/sdk/impl/AsyncFlowAccessApiImplTest.kt index a0c98d00..24c4b0c8 100644 --- a/sdk/src/test/kotlin/org/onflow/flow/sdk/impl/AsyncFlowAccessApiImplTest.kt +++ b/sdk/src/test/kotlin/org/onflow/flow/sdk/impl/AsyncFlowAccessApiImplTest.kt @@ -205,6 +205,70 @@ class AsyncFlowAccessApiImplTest { assertEquals(mockBlock, result.data) } + @Test + fun `test getAccountBalanceAtLatestBlock success`() { + val flowAddress = FlowAddress("01") + val expectedBalance = 1000L + val balanceResponse = Access.AccountBalanceResponse + .newBuilder() + .setBalance(expectedBalance) + .build() + + `when`(api.getAccountBalanceAtLatestBlock(any())).thenReturn(setupFutureMock(balanceResponse)) + + val result = asyncFlowAccessApi.getAccountBalanceAtLatestBlock(flowAddress).get() + assert(result is FlowAccessApi.AccessApiCallResponse.Success) + result as FlowAccessApi.AccessApiCallResponse.Success + assertEquals(expectedBalance, result.data) + } + + @Test + fun `test getAccountBalanceAtLatestBlock failure`() { + val flowAddress = FlowAddress("01") + val exception = RuntimeException("Test exception") + + `when`(api.getAccountBalanceAtLatestBlock(any())).thenThrow(exception) + + val result = asyncFlowAccessApi.getAccountBalanceAtLatestBlock(flowAddress).get() + assert(result is FlowAccessApi.AccessApiCallResponse.Error) + result as FlowAccessApi.AccessApiCallResponse.Error + assertEquals("Failed to get account balance at latest block", result.message) + assertEquals(exception, result.throwable) + } + + @Test + fun `test getAccountBalanceAtBlockHeight success`() { + val flowAddress = FlowAddress("01") + val blockHeight = 123L + val expectedBalance = 1000L + val balanceResponse = Access.AccountBalanceResponse + .newBuilder() + .setBalance(expectedBalance) + .build() + + `when`(api.getAccountBalanceAtBlockHeight(any())).thenReturn(setupFutureMock(balanceResponse)) + + val result = asyncFlowAccessApi.getAccountBalanceAtBlockHeight(flowAddress, blockHeight).get() + assert(result is FlowAccessApi.AccessApiCallResponse.Success) + result as FlowAccessApi.AccessApiCallResponse.Success + assertEquals(expectedBalance, result.data) + } + + @Test + fun `test getAccountBalanceAtBlockHeight failure`() { + val flowAddress = FlowAddress("01") + val blockHeight = 123L + val exception = RuntimeException("Test exception") + + `when`(api.getAccountBalanceAtBlockHeight(any())).thenThrow(exception) + + val result = asyncFlowAccessApi.getAccountBalanceAtBlockHeight(flowAddress, blockHeight).get() + assert(result is FlowAccessApi.AccessApiCallResponse.Error) + result as FlowAccessApi.AccessApiCallResponse.Error + assertEquals("Failed to get account balance at block height", result.message) + assertEquals(exception, result.throwable) + } + @Test fun `test getCollectionById`() { val collectionId = FlowId("01") diff --git a/sdk/src/test/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImplTest.kt b/sdk/src/test/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImplTest.kt index 1a0afc68..743e1656 100644 --- a/sdk/src/test/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImplTest.kt +++ b/sdk/src/test/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImplTest.kt @@ -163,6 +163,81 @@ class FlowAccessApiImplTest { assertResultSuccess(result) { assertEquals(mockBlock, it) } } + @Test + fun `Test getAccountBalanceAtLatestBlock success`() { + val flowAddress = FlowAddress("01") + val expectedBalance = 1000L + val response = Access.AccountBalanceResponse + .newBuilder() + .setBalance(expectedBalance) + .build() + + `when`(mockApi.getAccountBalanceAtLatestBlock(any())).thenReturn(response) + + val result = flowAccessApiImpl.getAccountBalanceAtLatestBlock(flowAddress) + assertResultSuccess(result) { assertEquals(expectedBalance, it) } + + verify(mockApi).getAccountBalanceAtLatestBlock( + Access.GetAccountBalanceAtLatestBlockRequest + .newBuilder() + .setAddress(flowAddress.byteStringValue) + .build() + ) + } + + @Test + fun `Test getAccountBalanceAtLatestBlock failure`() { + val flowAddress = FlowAddress("01") + val exception = RuntimeException("Test exception") + + `when`(mockApi.getAccountBalanceAtLatestBlock(any())).thenThrow(exception) + + val result = flowAccessApiImpl.getAccountBalanceAtLatestBlock(flowAddress) + + assertTrue(result is FlowAccessApi.AccessApiCallResponse.Error) + assertEquals("Failed to get account balance at latest block", (result as FlowAccessApi.AccessApiCallResponse.Error).message) + assertEquals(exception, result.throwable) + } + + @Test + fun `Test getAccountBalanceAtBlockHeight success`() { + val flowAddress = FlowAddress("01") + val blockHeight = 123L + val expectedBalance = 1000L + val response = Access.AccountBalanceResponse + .newBuilder() + .setBalance(expectedBalance) + .build() + + `when`(mockApi.getAccountBalanceAtBlockHeight(any())).thenReturn(response) + + val result = flowAccessApiImpl.getAccountBalanceAtBlockHeight(flowAddress, blockHeight) + assertResultSuccess(result) { assertEquals(expectedBalance, it) } + + verify(mockApi).getAccountBalanceAtBlockHeight( + Access.GetAccountBalanceAtBlockHeightRequest + .newBuilder() + .setAddress(flowAddress.byteStringValue) + .setBlockHeight(blockHeight) + .build() + ) + } + + @Test + fun `Test getAccountBalanceAtBlockHeight failure`() { + val flowAddress = FlowAddress("01") + val blockHeight = 123L + val exception = RuntimeException("Test exception") + + `when`(mockApi.getAccountBalanceAtBlockHeight(any())).thenThrow(exception) + + val result = flowAccessApiImpl.getAccountBalanceAtBlockHeight(flowAddress, blockHeight) + + assertTrue(result is FlowAccessApi.AccessApiCallResponse.Error) + assertEquals("Failed to get account balance at block height", (result as FlowAccessApi.AccessApiCallResponse.Error).message) + assertEquals(exception, result.throwable) + } + @Test fun `Test getCollectionById`() { val collectionId = FlowId("01") From 26f6a897acb484080fafdf238460063715c60071 Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Wed, 23 Oct 2024 02:10:57 +0900 Subject: [PATCH 7/9] Update integration tests --- .../GetAccountBalanceAccessAPIConnector.kt | 21 ++++++ .../transaction/TransactionIntegrationTest.kt | 70 +++++++++++++++++++ .../org/onflow/flow/sdk/AsyncFlowAccessApi.kt | 4 +- 3 files changed, 93 insertions(+), 2 deletions(-) create mode 100644 kotlin-example/src/main/kotlin/org/onflow/examples/kotlin/getAccountBalance/GetAccountBalanceAccessAPIConnector.kt diff --git a/kotlin-example/src/main/kotlin/org/onflow/examples/kotlin/getAccountBalance/GetAccountBalanceAccessAPIConnector.kt b/kotlin-example/src/main/kotlin/org/onflow/examples/kotlin/getAccountBalance/GetAccountBalanceAccessAPIConnector.kt new file mode 100644 index 00000000..255f3d45 --- /dev/null +++ b/kotlin-example/src/main/kotlin/org/onflow/examples/kotlin/getAccountBalance/GetAccountBalanceAccessAPIConnector.kt @@ -0,0 +1,21 @@ +package org.onflow.examples.kotlin.getAccountBalance + +import org.onflow.flow.sdk.* + +internal class GetBalanceAccessAPIConnector( + private val accessAPI: FlowAccessApi +) { + fun getBalanceAtLatestBlock(address: FlowAddress): Long { + return when (val response = accessAPI.getAccountBalanceAtLatestBlock(address)) { + is FlowAccessApi.AccessApiCallResponse.Success -> response.data + is FlowAccessApi.AccessApiCallResponse.Error -> throw Exception(response.message, response.throwable) + } + } + + fun getBalanceAtBlockHeight(address: FlowAddress, height: Long): Long { + return when (val response = accessAPI.getAccountBalanceAtBlockHeight(address, height)) { + is FlowAccessApi.AccessApiCallResponse.Success -> response.data + is FlowAccessApi.AccessApiCallResponse.Error -> throw Exception(response.message, response.throwable) + } + } +} diff --git a/sdk/src/intTest/org/onflow/flow/sdk/transaction/TransactionIntegrationTest.kt b/sdk/src/intTest/org/onflow/flow/sdk/transaction/TransactionIntegrationTest.kt index 9471bc6b..79f96c62 100644 --- a/sdk/src/intTest/org/onflow/flow/sdk/transaction/TransactionIntegrationTest.kt +++ b/sdk/src/intTest/org/onflow/flow/sdk/transaction/TransactionIntegrationTest.kt @@ -141,6 +141,76 @@ 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()) + } + + @Test fun `Can get latest block`() { val latestBlock = try { diff --git a/sdk/src/main/kotlin/org/onflow/flow/sdk/AsyncFlowAccessApi.kt b/sdk/src/main/kotlin/org/onflow/flow/sdk/AsyncFlowAccessApi.kt index ebe72a6d..edf22143 100644 --- a/sdk/src/main/kotlin/org/onflow/flow/sdk/AsyncFlowAccessApi.kt +++ b/sdk/src/main/kotlin/org/onflow/flow/sdk/AsyncFlowAccessApi.kt @@ -14,9 +14,9 @@ interface AsyncFlowAccessApi { fun getLatestBlock(sealed: Boolean = true): CompletableFuture> - fun getAccountBalanceAtLatestBlock(address: FlowAddress): CompletableFuture> + fun getAccountBalanceAtLatestBlock(address: FlowAddress): CompletableFuture> - fun getAccountBalanceAtBlockHeight(address: FlowAddress, height: Long): CompletableFuture> + fun getAccountBalanceAtBlockHeight(address: FlowAddress, height: Long): CompletableFuture> fun getBlockById(id: FlowId): CompletableFuture> From 90ce567ee650b955d08ea84516bf00f59870ef4c Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Wed, 23 Oct 2024 02:17:05 +0900 Subject: [PATCH 8/9] Setup kotlin examples --- .../GetAccountBalanceAccessAPIConnector.kt | 12 ++- ...GetAccountBalanceAccessAPIConnectorTest.kt | 73 +++++++++++++++++++ 2 files changed, 78 insertions(+), 7 deletions(-) create mode 100644 kotlin-example/src/test/kotlin/org/onflow/examples/kotlin/getAccountBalance/GetAccountBalanceAccessAPIConnectorTest.kt diff --git a/kotlin-example/src/main/kotlin/org/onflow/examples/kotlin/getAccountBalance/GetAccountBalanceAccessAPIConnector.kt b/kotlin-example/src/main/kotlin/org/onflow/examples/kotlin/getAccountBalance/GetAccountBalanceAccessAPIConnector.kt index 255f3d45..89ce67cb 100644 --- a/kotlin-example/src/main/kotlin/org/onflow/examples/kotlin/getAccountBalance/GetAccountBalanceAccessAPIConnector.kt +++ b/kotlin-example/src/main/kotlin/org/onflow/examples/kotlin/getAccountBalance/GetAccountBalanceAccessAPIConnector.kt @@ -2,20 +2,18 @@ package org.onflow.examples.kotlin.getAccountBalance import org.onflow.flow.sdk.* -internal class GetBalanceAccessAPIConnector( +internal class GetAccountBalanceAccessAPIConnector( private val accessAPI: FlowAccessApi ) { - fun getBalanceAtLatestBlock(address: FlowAddress): Long { - return when (val response = accessAPI.getAccountBalanceAtLatestBlock(address)) { + 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) } - } - fun getBalanceAtBlockHeight(address: FlowAddress, height: Long): Long { - return when (val response = accessAPI.getAccountBalanceAtBlockHeight(address, height)) { + 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) } - } } diff --git a/kotlin-example/src/test/kotlin/org/onflow/examples/kotlin/getAccountBalance/GetAccountBalanceAccessAPIConnectorTest.kt b/kotlin-example/src/test/kotlin/org/onflow/examples/kotlin/getAccountBalance/GetAccountBalanceAccessAPIConnectorTest.kt new file mode 100644 index 00000000..923f07b6 --- /dev/null +++ b/kotlin-example/src/test/kotlin/org/onflow/examples/kotlin/getAccountBalance/GetAccountBalanceAccessAPIConnectorTest.kt @@ -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") + } + + @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}") + } + } +} From 397339e4367b28ac2ab1f0580369aba62a89bf21 Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Wed, 23 Oct 2024 17:34:14 +0900 Subject: [PATCH 9/9] Setup java examples --- .../GetAccountBalanceAccessAPIConnector.java | 34 ++++++++ ...tAccountBalanceAccessAPIConnectorTest.java | 77 +++++++++++++++++++ .../transaction/TransactionIntegrationTest.kt | 1 - 3 files changed, 111 insertions(+), 1 deletion(-) create mode 100644 java-example/src/main/java/org/onflow/examples/java/getAccountBalance/GetAccountBalanceAccessAPIConnector.java create mode 100644 java-example/src/test/java/org/onflow/examples/java/getAccountBalance/GetAccountBalanceAccessAPIConnectorTest.java diff --git a/java-example/src/main/java/org/onflow/examples/java/getAccountBalance/GetAccountBalanceAccessAPIConnector.java b/java-example/src/main/java/org/onflow/examples/java/getAccountBalance/GetAccountBalanceAccessAPIConnector.java new file mode 100644 index 00000000..cee6b611 --- /dev/null +++ b/java-example/src/main/java/org/onflow/examples/java/getAccountBalance/GetAccountBalanceAccessAPIConnector.java @@ -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 response = accessAPI.getAccountBalanceAtLatestBlock(address); + + if (response instanceof FlowAccessApi.AccessApiCallResponse.Success) { + return ((FlowAccessApi.AccessApiCallResponse.Success) response).getData(); + } else { + FlowAccessApi.AccessApiCallResponse.Error errorResponse = (FlowAccessApi.AccessApiCallResponse.Error) response; + throw new RuntimeException(errorResponse.getMessage(), errorResponse.getThrowable()); + } + } + + public long getBalanceAtBlockHeight(FlowAddress address, long height) { + FlowAccessApi.AccessApiCallResponse response = accessAPI.getAccountBalanceAtBlockHeight(address, height); + + if (response instanceof FlowAccessApi.AccessApiCallResponse.Success) { + return ((FlowAccessApi.AccessApiCallResponse.Success) response).getData(); + } else { + FlowAccessApi.AccessApiCallResponse.Error errorResponse = (FlowAccessApi.AccessApiCallResponse.Error) response; + throw new RuntimeException(errorResponse.getMessage(), errorResponse.getThrowable()); + } + } +} diff --git a/java-example/src/test/java/org/onflow/examples/java/getAccountBalance/GetAccountBalanceAccessAPIConnectorTest.java b/java-example/src/test/java/org/onflow/examples/java/getAccountBalance/GetAccountBalanceAccessAPIConnectorTest.java new file mode 100644 index 00000000..6b0eb15a --- /dev/null +++ b/java-example/src/test/java/org/onflow/examples/java/getAccountBalance/GetAccountBalanceAccessAPIConnectorTest.java @@ -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 latestBlockResponse = accessAPI.getLatestBlock(true); + + if (latestBlockResponse instanceof FlowAccessApi.AccessApiCallResponse.Success) { + FlowBlock latestBlock = ((FlowAccessApi.AccessApiCallResponse.Success) 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()); + } + } + + @Test + public void testBalancesAtLatestBlockAndSpecificHeightShouldMatch() { + FlowAddress address = serviceAccount.getFlowAddress(); + + long balanceAtLatest = balanceAPIConnector.getBalanceAtLatestBlock(address); + FlowAccessApi.AccessApiCallResponse latestBlockResponse = accessAPI.getLatestBlock(true); + + if (latestBlockResponse instanceof FlowAccessApi.AccessApiCallResponse.Success) { + FlowBlock latestBlock = ((FlowAccessApi.AccessApiCallResponse.Success) latestBlockResponse).getData(); + long blockHeight = latestBlock.getHeight(); + + // Fetch balance at the same block height + long balanceAtHeight = balanceAPIConnector.getBalanceAtBlockHeight(address, blockHeight); + + // 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()); + } + } +} diff --git a/sdk/src/intTest/org/onflow/flow/sdk/transaction/TransactionIntegrationTest.kt b/sdk/src/intTest/org/onflow/flow/sdk/transaction/TransactionIntegrationTest.kt index 79f96c62..ab5a504b 100644 --- a/sdk/src/intTest/org/onflow/flow/sdk/transaction/TransactionIntegrationTest.kt +++ b/sdk/src/intTest/org/onflow/flow/sdk/transaction/TransactionIntegrationTest.kt @@ -210,7 +210,6 @@ class TransactionIntegrationTest { assertThat(normalizedBalance).isEqualTo(account.balance.toBigInteger().longValueExact()) } - @Test fun `Can get latest block`() { val latestBlock = try {