From 9b27efb6c1c2179eb130e348aaac99569565e700 Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Thu, 4 Jul 2024 20:58:39 +1000 Subject: [PATCH 1/4] Move new methods to clean PR --- .../org/onflow/flow/sdk/AsyncFlowAccessApi.kt | 6 ++ .../org/onflow/flow/sdk/FlowAccessApi.kt | 7 ++ .../flow/sdk/impl/AsyncFlowAccessApiImpl.kt | 68 ++++++++++++ .../onflow/flow/sdk/impl/FlowAccessApiImpl.kt | 44 ++++++++ src/main/kotlin/org/onflow/flow/sdk/models.kt | 12 +++ .../org/onflow/flow/sdk/FlowAccessApiTest.kt | 80 ++++++++++++++ .../sdk/impl/AsyncFlowAccessApiImplTest.kt | 101 ++++++++++++++++++ .../flow/sdk/impl/FlowAccessApiImplTest.kt | 74 +++++++++++++ 8 files changed, 392 insertions(+) diff --git a/src/main/kotlin/org/onflow/flow/sdk/AsyncFlowAccessApi.kt b/src/main/kotlin/org/onflow/flow/sdk/AsyncFlowAccessApi.kt index eb5bd94..3276899 100644 --- a/src/main/kotlin/org/onflow/flow/sdk/AsyncFlowAccessApi.kt +++ b/src/main/kotlin/org/onflow/flow/sdk/AsyncFlowAccessApi.kt @@ -49,4 +49,10 @@ interface AsyncFlowAccessApi { fun getNetworkParameters(): CompletableFuture> fun getLatestProtocolStateSnapshot(): CompletableFuture> + + fun getTransactionsByBlockId(id: FlowId): CompletableFuture>> + + fun getTransactionResultsByBlockId(id: FlowId): CompletableFuture>> + + fun getExecutionResultByBlockId(id: FlowId): CompletableFuture> } diff --git a/src/main/kotlin/org/onflow/flow/sdk/FlowAccessApi.kt b/src/main/kotlin/org/onflow/flow/sdk/FlowAccessApi.kt index 7dab538..84e22a2 100644 --- a/src/main/kotlin/org/onflow/flow/sdk/FlowAccessApi.kt +++ b/src/main/kotlin/org/onflow/flow/sdk/FlowAccessApi.kt @@ -53,4 +53,11 @@ interface FlowAccessApi { fun getNetworkParameters(): AccessApiCallResponse fun getLatestProtocolStateSnapshot(): AccessApiCallResponse + + fun getTransactionsByBlockId(id: FlowId): AccessApiCallResponse> + + fun getTransactionResultsByBlockId(id: FlowId): AccessApiCallResponse> + + fun getExecutionResultByBlockId(id: FlowId): AccessApiCallResponse + } diff --git a/src/main/kotlin/org/onflow/flow/sdk/impl/AsyncFlowAccessApiImpl.kt b/src/main/kotlin/org/onflow/flow/sdk/impl/AsyncFlowAccessApiImpl.kt index 0daadc4..748ee6e 100644 --- a/src/main/kotlin/org/onflow/flow/sdk/impl/AsyncFlowAccessApiImpl.kt +++ b/src/main/kotlin/org/onflow/flow/sdk/impl/AsyncFlowAccessApiImpl.kt @@ -513,6 +513,74 @@ class AsyncFlowAccessApiImpl( CompletableFuture.completedFuture(FlowAccessApi.AccessApiCallResponse.Error("Failed to get latest protocol state snapshot", e)) } } + + override fun getTransactionsByBlockId(id: FlowId): CompletableFuture>> { + return try { + completableFuture( + try { + api.getTransactionsByBlockID( + Access.GetTransactionsByBlockIDRequest.newBuilder().setBlockId(id.byteStringValue).build() + ) + } catch (e: Exception) { + return CompletableFuture.completedFuture(FlowAccessApi.AccessApiCallResponse.Error("Failed to get transactions by block ID", e)) + } + ).handle { response, ex -> + if (ex != null) { + FlowAccessApi.AccessApiCallResponse.Error("Failed to get transactions by block ID", ex) + } else { + FlowAccessApi.AccessApiCallResponse.Success(response.transactionsList.map { FlowTransaction.of(it) }) + } + } + } catch (e: Exception) { + CompletableFuture.completedFuture(FlowAccessApi.AccessApiCallResponse.Error("Failed to get transactions by block ID", e)) + } + } + + override fun getTransactionResultsByBlockId(id: FlowId): CompletableFuture>> { + return try { + completableFuture( + try { + api.getTransactionResultsByBlockID( + Access.GetTransactionsByBlockIDRequest.newBuilder().setBlockId(id.byteStringValue).build() + ) + } catch (e: Exception) { + return CompletableFuture.completedFuture(FlowAccessApi.AccessApiCallResponse.Error("Failed to get transaction results by block ID", e)) + } + ).handle { response, ex -> + if (ex != null) { + FlowAccessApi.AccessApiCallResponse.Error("Failed to get transaction results by block ID", ex) + } else { + FlowAccessApi.AccessApiCallResponse.Success(response.transactionResultsList.map { FlowTransactionResult.of(it) }) + } + } + } catch (e: Exception) { + CompletableFuture.completedFuture(FlowAccessApi.AccessApiCallResponse.Error("Failed to get transaction results by block ID", e)) + } + } + + override fun getExecutionResultByBlockId(id: FlowId): CompletableFuture> { + return try { + completableFuture( + try { + api.getExecutionResultByID(Access.GetExecutionResultByIDRequest.newBuilder().setId(id.byteStringValue).build()) + } catch (e: Exception) { + return CompletableFuture.completedFuture(FlowAccessApi.AccessApiCallResponse.Error("Failed to get execution result by block ID", e)) + } + ).handle { response, ex -> + if (ex != null) { + FlowAccessApi.AccessApiCallResponse.Error("Failed to get execution result by block ID", ex) + } else { + if (response.hasExecutionResult()) { + FlowAccessApi.AccessApiCallResponse.Success(ExecutionResult.of(response)) + } else { + FlowAccessApi.AccessApiCallResponse.Error("Execution result not found") + } + } + } + } catch (e: Exception) { + CompletableFuture.completedFuture(FlowAccessApi.AccessApiCallResponse.Error("Failed to get execution result by block ID", e)) + } + } } fun completableFuture(future: ListenableFuture): CompletableFuture { diff --git a/src/main/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImpl.kt b/src/main/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImpl.kt index 2fbf415..7b0578e 100644 --- a/src/main/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImpl.kt +++ b/src/main/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImpl.kt @@ -335,4 +335,48 @@ class FlowAccessApiImpl( FlowAccessApi.AccessApiCallResponse.Error("Failed to get latest protocol state snapshot", e) } } + + + override fun getTransactionsByBlockId(id: FlowId): FlowAccessApi.AccessApiCallResponse> { + return try { + val ret = api.getTransactionsByBlockID( + Access.GetTransactionsByBlockIDRequest.newBuilder() + .setBlockId(id.byteStringValue) + .build() + ) + FlowAccessApi.AccessApiCallResponse.Success(ret.transactionsList.map { FlowTransaction.of(it) }) + } catch (e: Exception) { + FlowAccessApi.AccessApiCallResponse.Error("Failed to get transactions by block ID", e) + } + } + + override fun getTransactionResultsByBlockId(id: FlowId): FlowAccessApi.AccessApiCallResponse> { + return try { + val ret = api.getTransactionResultsByBlockID( + Access.GetTransactionsByBlockIDRequest.newBuilder() + .setBlockId(id.byteStringValue) + .build() + ) + FlowAccessApi.AccessApiCallResponse.Success(ret.transactionResultsList.map { FlowTransactionResult.of(it) }) + } catch (e: Exception) { + FlowAccessApi.AccessApiCallResponse.Error("Failed to get transaction results by block ID", e) + } + } + + override fun getExecutionResultByBlockId(id: FlowId): FlowAccessApi.AccessApiCallResponse { + return try { + val ret = api.getExecutionResultByID( + Access.GetExecutionResultByIDRequest.newBuilder() + .setId(id.byteStringValue) + .build() + ) + if (ret.hasExecutionResult()) { + FlowAccessApi.AccessApiCallResponse.Success(ExecutionResult.of(ret)) + } else { + FlowAccessApi.AccessApiCallResponse.Error("Execution result not found") + } + } catch (e: Exception) { + FlowAccessApi.AccessApiCallResponse.Error("Failed to get execution result by block ID", e) + } + } } diff --git a/src/main/kotlin/org/onflow/flow/sdk/models.kt b/src/main/kotlin/org/onflow/flow/sdk/models.kt index 502f8a4..cdb6d54 100644 --- a/src/main/kotlin/org/onflow/flow/sdk/models.kt +++ b/src/main/kotlin/org/onflow/flow/sdk/models.kt @@ -675,6 +675,18 @@ data class FlowBlock( } } +data class ExecutionResult( + val id: FlowId, + val parentId: FlowId +) : Serializable { + companion object { + fun of(grpcExecutionResult: Access.ExecutionResultByIDResponse) = ExecutionResult( + id = FlowId.of(grpcExecutionResult.executionResult.blockId.toByteArray()), + parentId = FlowId.of(grpcExecutionResult.executionResult.previousResultId.toByteArray()) + ) + } +} + data class FlowCollectionGuarantee( val id: FlowId, val signatures: List diff --git a/src/test/kotlin/org/onflow/flow/sdk/FlowAccessApiTest.kt b/src/test/kotlin/org/onflow/flow/sdk/FlowAccessApiTest.kt index 463ef6c..463df83 100644 --- a/src/test/kotlin/org/onflow/flow/sdk/FlowAccessApiTest.kt +++ b/src/test/kotlin/org/onflow/flow/sdk/FlowAccessApiTest.kt @@ -243,4 +243,84 @@ class FlowAccessApiTest { assertEquals(FlowAccessApi.AccessApiCallResponse.Success(snapshot), result) } + + @Test + fun `Test getTransactionsByBlockId`() { + val flowAccessApi = mock(FlowAccessApi::class.java) + val blockId = FlowId("01") + val transactions = listOf(FlowTransaction.of(TransactionOuterClass.Transaction.getDefaultInstance())) + `when`(flowAccessApi.getTransactionsByBlockId(blockId)).thenReturn(FlowAccessApi.AccessApiCallResponse.Success(transactions)) + + val result = flowAccessApi.getTransactionsByBlockId(blockId) + + assertEquals(FlowAccessApi.AccessApiCallResponse.Success(transactions), result) + } + + @Test + fun `Test getTransactionsByBlockId with multiple results`() { + val flowAccessApi = mock(FlowAccessApi::class.java) + val blockId = FlowId("01") + + val transaction1 = FlowTransaction(FlowScript("script1"), emptyList(), FlowId.of("01".toByteArray()), 123L, FlowTransactionProposalKey(FlowAddress("02"), 1, 123L), FlowAddress("02"), emptyList()) + + val transaction2 = FlowTransaction(FlowScript("script2"), emptyList(), FlowId.of("02".toByteArray()), 456L, FlowTransactionProposalKey(FlowAddress("03"), 2, 456L), FlowAddress("03"), emptyList()) + + val transactions = listOf(transaction1, transaction2) + + `when`(flowAccessApi.getTransactionsByBlockId(blockId)).thenReturn(FlowAccessApi.AccessApiCallResponse.Success(transactions)) + + val result = flowAccessApi.getTransactionsByBlockId(blockId) + + assertEquals(FlowAccessApi.AccessApiCallResponse.Success(transactions), result) + + assertEquals(2, transactions.size) + assertEquals(transaction1, transactions[0]) + assertEquals(transaction2, transactions[1]) + } + + @Test + fun `Test getTransactionResultsByBlockId`() { + val flowAccessApi = mock(FlowAccessApi::class.java) + val blockId = FlowId("01") + val transactionResults = listOf(FlowTransactionResult.of(Access.TransactionResultResponse.getDefaultInstance())) + `when`(flowAccessApi.getTransactionResultsByBlockId(blockId)).thenReturn(FlowAccessApi.AccessApiCallResponse.Success(transactionResults)) + + val result = flowAccessApi.getTransactionResultsByBlockId(blockId) + + assertEquals(FlowAccessApi.AccessApiCallResponse.Success(transactionResults), result) + } + + @Test + fun `Test getTransactionResultsByBlockId with multiple results`() { + val flowAccessApi = mock(FlowAccessApi::class.java) + val blockId = FlowId("01") + + val transactionResult1 = FlowTransactionResult(FlowTransactionStatus.SEALED, 1, "message1", emptyList()) + + val transactionResult2 = FlowTransactionResult(FlowTransactionStatus.SEALED, 2, "message2", emptyList()) + + val transactions = listOf(transactionResult1, transactionResult2) + + `when`(flowAccessApi.getTransactionResultsByBlockId(blockId)).thenReturn(FlowAccessApi.AccessApiCallResponse.Success(transactions)) + + val result = flowAccessApi.getTransactionResultsByBlockId(blockId) + + assertEquals(FlowAccessApi.AccessApiCallResponse.Success(transactions), result) + + assertEquals(2, FlowAccessApi.AccessApiCallResponse.Success(transactions).data.size) + assertEquals(transactionResult1, FlowAccessApi.AccessApiCallResponse.Success(transactions).data[0]) + assertEquals(transactionResult2, FlowAccessApi.AccessApiCallResponse.Success(transactions).data[1]) + } + + @Test + fun `Test getExecutionResultByBlockId`() { + val flowAccessApi = mock(FlowAccessApi::class.java) + val blockId = FlowId("01") + val executionResult = ExecutionResult.of(Access.ExecutionResultByIDResponse.getDefaultInstance()) + `when`(flowAccessApi.getExecutionResultByBlockId(blockId)).thenReturn(FlowAccessApi.AccessApiCallResponse.Success(executionResult)) + + val result = flowAccessApi.getExecutionResultByBlockId(blockId) + + assertEquals(FlowAccessApi.AccessApiCallResponse.Success(executionResult), result) + } } diff --git a/src/test/kotlin/org/onflow/flow/sdk/impl/AsyncFlowAccessApiImplTest.kt b/src/test/kotlin/org/onflow/flow/sdk/impl/AsyncFlowAccessApiImplTest.kt index 154e7a5..4b88598 100644 --- a/src/test/kotlin/org/onflow/flow/sdk/impl/AsyncFlowAccessApiImplTest.kt +++ b/src/test/kotlin/org/onflow/flow/sdk/impl/AsyncFlowAccessApiImplTest.kt @@ -5,20 +5,30 @@ import com.google.common.util.concurrent.SettableFuture import com.google.protobuf.ByteString import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows import org.mockito.ArgumentMatchers.any import org.mockito.Mockito.mock import org.mockito.Mockito.`when` import org.onflow.flow.sdk.* import org.onflow.protobuf.access.Access import org.onflow.protobuf.access.AccessAPIGrpc +import org.onflow.protobuf.entities.ExecutionResultOuterClass import org.onflow.protobuf.entities.TransactionOuterClass import java.math.BigDecimal import java.time.LocalDateTime +import java.util.concurrent.Executors +import java.util.concurrent.TimeUnit +import java.util.concurrent.TimeoutException class AsyncFlowAccessApiImplTest { private val api = mock(AccessAPIGrpc.AccessAPIFutureStub::class.java) private val asyncFlowAccessApi = AsyncFlowAccessApiImpl(api) + companion object { + val BLOCK_ID_BYTES = byteArrayOf(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1) + val PARENT_ID_BYTES = byteArrayOf(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2) + } + private fun setupFutureMock(response: T): ListenableFuture { val future: ListenableFuture = SettableFuture.create() (future as SettableFuture).set(response) @@ -305,4 +315,95 @@ class AsyncFlowAccessApiImplTest { result as FlowAccessApi.AccessApiCallResponse.Success assertEquals(mockFlowSnapshot, result.data) } + + @Test + fun `test getTransactionsByBlockId`() { + val blockId = FlowId("01") + val transactions = listOf(FlowTransaction.of(TransactionOuterClass.Transaction.getDefaultInstance())) + val response = Access.TransactionsResponse.newBuilder().addAllTransactions(transactions.map { it.builder().build() }).build() + `when`(api.getTransactionsByBlockID(any())).thenReturn(setupFutureMock(response)) + + val result = asyncFlowAccessApi.getTransactionsByBlockId(blockId).get() + assert(result is FlowAccessApi.AccessApiCallResponse.Success) + result as FlowAccessApi.AccessApiCallResponse.Success + assertEquals(transactions, result.data) + } + + @Test + fun `test getTransactionsByBlockId with multiple results`() { + val blockId = FlowId("01") + val transaction1 = FlowTransaction.of(TransactionOuterClass.Transaction.getDefaultInstance()) + val transaction2 = FlowTransaction.of(TransactionOuterClass.Transaction.newBuilder().setReferenceBlockId(ByteString.copyFromUtf8("02")).build()) + val transactions = listOf(transaction1, transaction2) + val response = Access.TransactionsResponse.newBuilder().addAllTransactions(transactions.map { it.builder().build() }).build() + `when`(api.getTransactionsByBlockID(any())).thenReturn(setupFutureMock(response)) + + val result = asyncFlowAccessApi.getTransactionsByBlockId(blockId).get() + assert(result is FlowAccessApi.AccessApiCallResponse.Success) + result as FlowAccessApi.AccessApiCallResponse.Success + assertEquals(2, result.data.size) + assertEquals(transaction1, result.data[0]) + assertEquals(transaction2, result.data[1]) + } + + @Test + fun `test getTransactionResultsByBlockId`() { + val blockId = FlowId("01") + val transactionResults = listOf(FlowTransactionResult.of(Access.TransactionResultResponse.getDefaultInstance())) + val response = Access.TransactionResultsResponse.newBuilder().addAllTransactionResults(transactionResults.map { it.builder().build() }).build() + `when`(api.getTransactionResultsByBlockID(any())).thenReturn(setupFutureMock(response)) + + val result = asyncFlowAccessApi.getTransactionResultsByBlockId(blockId).get() + assert(result is FlowAccessApi.AccessApiCallResponse.Success) + result as FlowAccessApi.AccessApiCallResponse.Success + assertEquals(transactionResults, result.data) + } + + @Test + fun `test getTransactionResultsByBlockId with multiple results`() { + val blockId = FlowId("01") + val transactionResult1 = FlowTransactionResult.of(Access.TransactionResultResponse.newBuilder().setStatus(TransactionOuterClass.TransactionStatus.SEALED).setStatusCode(1).setErrorMessage("message1").build()) + val transactionResult2 = FlowTransactionResult.of(Access.TransactionResultResponse.newBuilder().setStatus(TransactionOuterClass.TransactionStatus.SEALED).setStatusCode(2).setErrorMessage("message2").build()) + val transactionResults = listOf(transactionResult1, transactionResult2) + val response = Access.TransactionResultsResponse.newBuilder().addAllTransactionResults(transactionResults.map { it.builder().build() }).build() + `when`(api.getTransactionResultsByBlockID(any())).thenReturn(setupFutureMock(response)) + + val result = asyncFlowAccessApi.getTransactionResultsByBlockId(blockId).get() + assert(result is FlowAccessApi.AccessApiCallResponse.Success) + result as FlowAccessApi.AccessApiCallResponse.Success + assertEquals(2, result.data.size) + assertEquals(transactionResult1, result.data[0]) + assertEquals(transactionResult2, result.data[1]) + } + + @Test + fun `test getExecutionResultByBlockId`() { + val blockId = FlowId("01") + val executionResult = ExecutionResult(FlowId("01"), FlowId("02")) + val response = Access.ExecutionResultByIDResponse.newBuilder().setExecutionResult(ExecutionResultOuterClass.ExecutionResult.newBuilder().setBlockId(ByteString.copyFrom(BLOCK_ID_BYTES)).setPreviousResultId(ByteString.copyFrom(PARENT_ID_BYTES)).build()).build() + `when`(api.getExecutionResultByID(any())).thenReturn(setupFutureMock(response)) + + val result = asyncFlowAccessApi.getExecutionResultByBlockId(blockId).get() + assert(result is FlowAccessApi.AccessApiCallResponse.Success) + result as FlowAccessApi.AccessApiCallResponse.Success + assertEquals(executionResult, result.data) + } + + @Test + fun `test getTransactionsByBlockId timeout exception`() { + val blockId = FlowId("01") + val future: ListenableFuture = SettableFuture.create() + `when`(api.getTransactionsByBlockID(any())).thenReturn(future) + + val executor = Executors.newSingleThreadExecutor() + executor.submit { + assertThrows { + asyncFlowAccessApi.getTransactionsByBlockId(blockId).get(1, TimeUnit.SECONDS) + } + } + + executor.shutdown() + executor.awaitTermination(2, TimeUnit.SECONDS) + } + } diff --git a/src/test/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImplTest.kt b/src/test/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImplTest.kt index 04f21fb..5c89714 100644 --- a/src/test/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImplTest.kt +++ b/src/test/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImplTest.kt @@ -10,6 +10,7 @@ import org.mockito.ArgumentMatchers.any import org.mockito.Mockito.* import org.onflow.protobuf.access.Access import org.onflow.protobuf.access.AccessAPIGrpc +import org.onflow.protobuf.entities.ExecutionResultOuterClass import org.onflow.protobuf.entities.TransactionOuterClass import java.io.ByteArrayOutputStream import java.io.PrintStream @@ -310,6 +311,79 @@ class FlowAccessApiImplTest { assertResultSuccess(result) { assertEquals(mockFlowSnapshot, it) } } + @Test + fun `Test getTransactionsByBlockId`() { + val blockId = FlowId("01") + val transactions = listOf(FlowTransaction.of(TransactionOuterClass.Transaction.getDefaultInstance())) + val response = Access.TransactionsResponse.newBuilder().addAllTransactions(transactions.map { it.builder().build() }).build() + + `when`(mockApi.getTransactionsByBlockID(any())).thenReturn(response) + + val result = flowAccessApiImpl.getTransactionsByBlockId(blockId) + assertResultSuccess(result) { assertEquals(transactions, it) } + } + + @Test + fun `Test getTransactionsByBlockId with multiple results`() { + val blockId = FlowId("01") + val transaction1 = FlowTransaction.of(TransactionOuterClass.Transaction.getDefaultInstance()) + val transaction2 = FlowTransaction.of(TransactionOuterClass.Transaction.newBuilder().setReferenceBlockId(ByteString.copyFromUtf8("02")).build()) + val transactions = listOf(transaction1, transaction2) + val response = Access.TransactionsResponse.newBuilder().addAllTransactions(transactions.map { it.builder().build() }).build() + + `when`(mockApi.getTransactionsByBlockID(any())).thenReturn(response) + + val result = flowAccessApiImpl.getTransactionsByBlockId(blockId) + assertResultSuccess(result) { + assertEquals(2, it.size) + assertEquals(transaction1, it[0]) + assertEquals(transaction2, it[1]) + } + } + + @Test + fun `Test getTransactionResultsByBlockId`() { + val blockId = FlowId("01") + val transactionResults = listOf(FlowTransactionResult.of(Access.TransactionResultResponse.getDefaultInstance())) + val response = Access.TransactionResultsResponse.newBuilder().addAllTransactionResults(transactionResults.map { it.builder().build() }).build() + + `when`(mockApi.getTransactionResultsByBlockID(any())).thenReturn(response) + + val result = flowAccessApiImpl.getTransactionResultsByBlockId(blockId) + assertResultSuccess(result) { assertEquals(transactionResults, it) } + } + + @Test + fun `Test getTransactionResultsByBlockId with multiple results`() { + val blockId = FlowId("01") + val transactionResult1 = FlowTransactionResult.of(Access.TransactionResultResponse.newBuilder().setStatus (TransactionOuterClass.TransactionStatus.SEALED).setStatusCode(1).setErrorMessage("message1").build()) + val transactionResult2 = FlowTransactionResult.of(Access.TransactionResultResponse.newBuilder().setStatus(TransactionOuterClass.TransactionStatus.SEALED).setStatusCode(2).setErrorMessage("message2").build()) + val transactionResults = listOf(transactionResult1, transactionResult2) + val response = Access.TransactionResultsResponse.newBuilder().addAllTransactionResults(transactionResults.map { it.builder().build() }).build() + + `when`(mockApi.getTransactionResultsByBlockID(any())).thenReturn(response) + + val result = flowAccessApiImpl.getTransactionResultsByBlockId(blockId) + assertResultSuccess(result) { + assertEquals(2, it.size) + assertEquals(transactionResult1, it[0]) + assertEquals(transactionResult2, it[1]) + } + } + + @Test + fun `Test getExecutionResultByBlockId`() { + val blockId = FlowId("01") + val executionResult = ExecutionResult(FlowId("01"), FlowId("02")) + val response = Access.ExecutionResultByIDResponse.newBuilder().setExecutionResult(ExecutionResultOuterClass.ExecutionResult.newBuilder().setBlockId(blockId.byteStringValue).setPreviousResultId((FlowId("02").byteStringValue)).build()).build() + + `when`(mockApi.getExecutionResultByID(any())).thenReturn(response) + + val result = flowAccessApiImpl.getExecutionResultByBlockId(blockId) + assertResultSuccess(result) { assertEquals(executionResult, it) } + } + + private fun assertResultSuccess(result: FlowAccessApi.AccessApiCallResponse, assertions: (T) -> Unit) { when (result) { is FlowAccessApi.AccessApiCallResponse.Success -> assertions(result.data) From 5f25d22a921285bd977afb54c95e919c3ae6c99b Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Thu, 4 Jul 2024 21:04:21 +1000 Subject: [PATCH 2/4] Move new methods to clean PR --- src/main/kotlin/org/onflow/flow/sdk/FlowAccessApi.kt | 1 - src/main/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImpl.kt | 1 - 2 files changed, 2 deletions(-) diff --git a/src/main/kotlin/org/onflow/flow/sdk/FlowAccessApi.kt b/src/main/kotlin/org/onflow/flow/sdk/FlowAccessApi.kt index 84e22a2..2dc7a69 100644 --- a/src/main/kotlin/org/onflow/flow/sdk/FlowAccessApi.kt +++ b/src/main/kotlin/org/onflow/flow/sdk/FlowAccessApi.kt @@ -59,5 +59,4 @@ interface FlowAccessApi { fun getTransactionResultsByBlockId(id: FlowId): AccessApiCallResponse> fun getExecutionResultByBlockId(id: FlowId): AccessApiCallResponse - } diff --git a/src/main/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImpl.kt b/src/main/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImpl.kt index 7b0578e..9b85d3c 100644 --- a/src/main/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImpl.kt +++ b/src/main/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImpl.kt @@ -336,7 +336,6 @@ class FlowAccessApiImpl( } } - override fun getTransactionsByBlockId(id: FlowId): FlowAccessApi.AccessApiCallResponse> { return try { val ret = api.getTransactionsByBlockID( From 7b686171d0b0df934e1c21bd6eee2e06fd3e7f14 Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Sat, 6 Jul 2024 20:00:14 +1000 Subject: [PATCH 3/4] Add missing fields --- .../org/onflow/flow/sdk/AsyncFlowAccessApi.kt | 2 +- .../org/onflow/flow/sdk/FlowAccessApi.kt | 2 +- .../flow/sdk/impl/AsyncFlowAccessApiImpl.kt | 4 +- .../onflow/flow/sdk/impl/FlowAccessApiImpl.kt | 4 +- src/main/kotlin/org/onflow/flow/sdk/models.kt | 104 +++++++++++++++++- .../org/onflow/flow/sdk/FlowAccessApiTest.kt | 2 +- .../sdk/impl/AsyncFlowAccessApiImplTest.kt | 44 +++++++- .../flow/sdk/impl/FlowAccessApiImplTest.kt | 16 ++- 8 files changed, 156 insertions(+), 22 deletions(-) diff --git a/src/main/kotlin/org/onflow/flow/sdk/AsyncFlowAccessApi.kt b/src/main/kotlin/org/onflow/flow/sdk/AsyncFlowAccessApi.kt index 3276899..4b6ce90 100644 --- a/src/main/kotlin/org/onflow/flow/sdk/AsyncFlowAccessApi.kt +++ b/src/main/kotlin/org/onflow/flow/sdk/AsyncFlowAccessApi.kt @@ -54,5 +54,5 @@ interface AsyncFlowAccessApi { fun getTransactionResultsByBlockId(id: FlowId): CompletableFuture>> - fun getExecutionResultByBlockId(id: FlowId): CompletableFuture> + fun getExecutionResultByBlockId(id: FlowId): CompletableFuture> } diff --git a/src/main/kotlin/org/onflow/flow/sdk/FlowAccessApi.kt b/src/main/kotlin/org/onflow/flow/sdk/FlowAccessApi.kt index 2dc7a69..6ffb564 100644 --- a/src/main/kotlin/org/onflow/flow/sdk/FlowAccessApi.kt +++ b/src/main/kotlin/org/onflow/flow/sdk/FlowAccessApi.kt @@ -58,5 +58,5 @@ interface FlowAccessApi { fun getTransactionResultsByBlockId(id: FlowId): AccessApiCallResponse> - fun getExecutionResultByBlockId(id: FlowId): AccessApiCallResponse + fun getExecutionResultByBlockId(id: FlowId): AccessApiCallResponse } diff --git a/src/main/kotlin/org/onflow/flow/sdk/impl/AsyncFlowAccessApiImpl.kt b/src/main/kotlin/org/onflow/flow/sdk/impl/AsyncFlowAccessApiImpl.kt index 748ee6e..405e3ac 100644 --- a/src/main/kotlin/org/onflow/flow/sdk/impl/AsyncFlowAccessApiImpl.kt +++ b/src/main/kotlin/org/onflow/flow/sdk/impl/AsyncFlowAccessApiImpl.kt @@ -558,7 +558,7 @@ class AsyncFlowAccessApiImpl( } } - override fun getExecutionResultByBlockId(id: FlowId): CompletableFuture> { + override fun getExecutionResultByBlockId(id: FlowId): CompletableFuture> { return try { completableFuture( try { @@ -571,7 +571,7 @@ class AsyncFlowAccessApiImpl( FlowAccessApi.AccessApiCallResponse.Error("Failed to get execution result by block ID", ex) } else { if (response.hasExecutionResult()) { - FlowAccessApi.AccessApiCallResponse.Success(ExecutionResult.of(response)) + FlowAccessApi.AccessApiCallResponse.Success(FlowExecutionResult.of(response)) } else { FlowAccessApi.AccessApiCallResponse.Error("Execution result not found") } diff --git a/src/main/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImpl.kt b/src/main/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImpl.kt index 9b85d3c..9363142 100644 --- a/src/main/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImpl.kt +++ b/src/main/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImpl.kt @@ -362,7 +362,7 @@ class FlowAccessApiImpl( } } - override fun getExecutionResultByBlockId(id: FlowId): FlowAccessApi.AccessApiCallResponse { + override fun getExecutionResultByBlockId(id: FlowId): FlowAccessApi.AccessApiCallResponse { return try { val ret = api.getExecutionResultByID( Access.GetExecutionResultByIDRequest.newBuilder() @@ -370,7 +370,7 @@ class FlowAccessApiImpl( .build() ) if (ret.hasExecutionResult()) { - FlowAccessApi.AccessApiCallResponse.Success(ExecutionResult.of(ret)) + FlowAccessApi.AccessApiCallResponse.Success(FlowExecutionResult.of(ret)) } else { FlowAccessApi.AccessApiCallResponse.Error("Execution result not found") } diff --git a/src/main/kotlin/org/onflow/flow/sdk/models.kt b/src/main/kotlin/org/onflow/flow/sdk/models.kt index cdb6d54..e135dc1 100644 --- a/src/main/kotlin/org/onflow/flow/sdk/models.kt +++ b/src/main/kotlin/org/onflow/flow/sdk/models.kt @@ -675,14 +675,106 @@ data class FlowBlock( } } -data class ExecutionResult( - val id: FlowId, - val parentId: FlowId +data class FlowChunk( + val collectionIndex: Int, + val startState: ByteArray, + val eventCollection: ByteArray, + val blockId: FlowId, + val totalComputationUsed: Long, + val numberOfTransactions: Int, + val index: Long, + val endState: ByteArray, + val executionDataId: FlowId, + val stateDeltaCommitment: ByteArray, +) : Serializable { + companion object { + fun of(grpcExecutionResult: ExecutionResultOuterClass.Chunk) = FlowChunk( + collectionIndex = grpcExecutionResult.collectionIndex, + startState = grpcExecutionResult.startState.toByteArray(), + eventCollection = grpcExecutionResult.eventCollection.toByteArray(), + blockId = FlowId.of(grpcExecutionResult.blockId.toByteArray()), + totalComputationUsed = grpcExecutionResult.totalComputationUsed, + numberOfTransactions = grpcExecutionResult.numberOfTransactions, + index = grpcExecutionResult.index, + endState = grpcExecutionResult.endState.toByteArray(), + executionDataId = FlowId.of(grpcExecutionResult.executionDataId.toByteArray()), + stateDeltaCommitment = grpcExecutionResult.stateDeltaCommitment.toByteArray() + ) + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is FlowChunk) return false + + if (collectionIndex != other.collectionIndex) return false + if (!startState.contentEquals(other.startState)) return false + if (!eventCollection.contentEquals(other.eventCollection)) return false + if (blockId != other.blockId) return false + if (totalComputationUsed != other.totalComputationUsed) return false + if (numberOfTransactions != other.numberOfTransactions) return false + if (index != other.index) return false + if (!endState.contentEquals(other.endState)) return false + if (executionDataId != other.executionDataId) return false + if (!stateDeltaCommitment.contentEquals(other.stateDeltaCommitment)) return false + + return true + } + + override fun hashCode(): Int { + var result = collectionIndex + result = 31 * result + startState.contentHashCode() + result = 31 * result + eventCollection.contentHashCode() + result = 31 * result + blockId.hashCode() + result = 31 * result + totalComputationUsed.hashCode() + result = 31 * result + numberOfTransactions + result = 31 * result + index.hashCode() + result = 31 * result + endState.contentHashCode() + result = 31 * result + executionDataId.hashCode() + result = 31 * result + stateDeltaCommitment.contentHashCode() + return result + } +} + +data class FlowServiceEvent( + val type: String, + val payload: ByteArray, +) : Serializable { + companion object { + fun of(grpcExecutionResult: ExecutionResultOuterClass.ServiceEvent) = FlowServiceEvent( + type = grpcExecutionResult.type, + payload = grpcExecutionResult.payload.toByteArray(), + ) + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is FlowServiceEvent) return false + + if (type != other.type) return false + if (!payload.contentEquals(other.payload)) return false + + return true + } + + override fun hashCode(): Int { + var result = type.hashCode() + result = 31 * result + payload.contentHashCode() + return result + } +} + +data class FlowExecutionResult( + val blockId: FlowId, + val previousResultId: FlowId, + val chunks: List, + val serviceEvents: List, ) : Serializable { companion object { - fun of(grpcExecutionResult: Access.ExecutionResultByIDResponse) = ExecutionResult( - id = FlowId.of(grpcExecutionResult.executionResult.blockId.toByteArray()), - parentId = FlowId.of(grpcExecutionResult.executionResult.previousResultId.toByteArray()) + fun of(grpcExecutionResult: Access.ExecutionResultByIDResponse) = FlowExecutionResult( + blockId = FlowId.of(grpcExecutionResult.executionResult.blockId.toByteArray()), + previousResultId = FlowId.of(grpcExecutionResult.executionResult.previousResultId.toByteArray()), + chunks = grpcExecutionResult.executionResult.chunksList.map { FlowChunk.of(it) }, + serviceEvents = grpcExecutionResult.executionResult.serviceEventsList.map { FlowServiceEvent.of(it) }, ) } } diff --git a/src/test/kotlin/org/onflow/flow/sdk/FlowAccessApiTest.kt b/src/test/kotlin/org/onflow/flow/sdk/FlowAccessApiTest.kt index 463df83..4e37df7 100644 --- a/src/test/kotlin/org/onflow/flow/sdk/FlowAccessApiTest.kt +++ b/src/test/kotlin/org/onflow/flow/sdk/FlowAccessApiTest.kt @@ -316,7 +316,7 @@ class FlowAccessApiTest { fun `Test getExecutionResultByBlockId`() { val flowAccessApi = mock(FlowAccessApi::class.java) val blockId = FlowId("01") - val executionResult = ExecutionResult.of(Access.ExecutionResultByIDResponse.getDefaultInstance()) + val executionResult = FlowExecutionResult.of(Access.ExecutionResultByIDResponse.getDefaultInstance()) `when`(flowAccessApi.getExecutionResultByBlockId(blockId)).thenReturn(FlowAccessApi.AccessApiCallResponse.Success(executionResult)) val result = flowAccessApi.getExecutionResultByBlockId(blockId) diff --git a/src/test/kotlin/org/onflow/flow/sdk/impl/AsyncFlowAccessApiImplTest.kt b/src/test/kotlin/org/onflow/flow/sdk/impl/AsyncFlowAccessApiImplTest.kt index 4b88598..2a5a5c8 100644 --- a/src/test/kotlin/org/onflow/flow/sdk/impl/AsyncFlowAccessApiImplTest.kt +++ b/src/test/kotlin/org/onflow/flow/sdk/impl/AsyncFlowAccessApiImplTest.kt @@ -179,7 +179,7 @@ class AsyncFlowAccessApiImplTest { val accountResponse = Access.GetAccountResponse.newBuilder().setAccount(flowAccount.builder().build()).build() `when`(api.getAccount(any())).thenReturn(setupFutureMock(accountResponse)) - val result = asyncFlowAccessApi.getAccountByAddress(flowAddress).get() + val result = asyncFlowAccessApi.getAccountAtLatestBlock(flowAddress).get() assert(result is FlowAccessApi.AccessApiCallResponse.Success) result as FlowAccessApi.AccessApiCallResponse.Success assertEquals(flowAccount.address, result.data.address) @@ -379,8 +379,45 @@ class AsyncFlowAccessApiImplTest { @Test fun `test getExecutionResultByBlockId`() { val blockId = FlowId("01") - val executionResult = ExecutionResult(FlowId("01"), FlowId("02")) - val response = Access.ExecutionResultByIDResponse.newBuilder().setExecutionResult(ExecutionResultOuterClass.ExecutionResult.newBuilder().setBlockId(ByteString.copyFrom(BLOCK_ID_BYTES)).setPreviousResultId(ByteString.copyFrom(PARENT_ID_BYTES)).build()).build() + + val chunks = listOf(FlowChunk(collectionIndex = 1, startState = ByteArray(0), eventCollection = ByteArray(0), blockId = FlowId("01"), totalComputationUsed = 1000L, numberOfTransactions = 10, index = 1L, endState = ByteArray(0), executionDataId = FlowId("02"), stateDeltaCommitment = ByteArray(0))) + + val serviceEvents = listOf(FlowServiceEvent(type = "ServiceEventType", payload = ByteArray(0))) + + val executionResult = FlowExecutionResult(blockId = FlowId("01"), previousResultId = FlowId("02"), chunks = chunks, serviceEvents = serviceEvents) + + val grpcChunks = chunks.map { + ExecutionResultOuterClass.Chunk.newBuilder() + .setCollectionIndex(it.collectionIndex) + .setStartState(ByteString.copyFrom(it.startState)) + .setEventCollection(ByteString.copyFrom(it.eventCollection)) + .setBlockId(ByteString.copyFrom(it.blockId.bytes)) + .setTotalComputationUsed(it.totalComputationUsed) + .setNumberOfTransactions(it.numberOfTransactions) + .setIndex(it.index) + .setEndState(ByteString.copyFrom(it.endState)) + .setExecutionDataId(ByteString.copyFrom(it.executionDataId.bytes)) + .setStateDeltaCommitment(ByteString.copyFrom(it.stateDeltaCommitment)) + .build() + } + + val grpcServiceEvents = serviceEvents.map { + ExecutionResultOuterClass.ServiceEvent.newBuilder() + .setType(it.type) + .setPayload(ByteString.copyFrom(it.payload)) + .build() + } + + val response = Access.ExecutionResultByIDResponse.newBuilder() + .setExecutionResult( + ExecutionResultOuterClass.ExecutionResult.newBuilder() + .setBlockId(ByteString.copyFrom(BLOCK_ID_BYTES)) + .setPreviousResultId(ByteString.copyFrom(PARENT_ID_BYTES)) + .addAllChunks(grpcChunks) + .addAllServiceEvents(grpcServiceEvents) + .build() + ).build() + `when`(api.getExecutionResultByID(any())).thenReturn(setupFutureMock(response)) val result = asyncFlowAccessApi.getExecutionResultByBlockId(blockId).get() @@ -405,5 +442,4 @@ class AsyncFlowAccessApiImplTest { executor.shutdown() executor.awaitTermination(2, TimeUnit.SECONDS) } - } diff --git a/src/test/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImplTest.kt b/src/test/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImplTest.kt index 5c89714..1a2b2ef 100644 --- a/src/test/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImplTest.kt +++ b/src/test/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImplTest.kt @@ -356,7 +356,7 @@ class FlowAccessApiImplTest { @Test fun `Test getTransactionResultsByBlockId with multiple results`() { val blockId = FlowId("01") - val transactionResult1 = FlowTransactionResult.of(Access.TransactionResultResponse.newBuilder().setStatus (TransactionOuterClass.TransactionStatus.SEALED).setStatusCode(1).setErrorMessage("message1").build()) + val transactionResult1 = FlowTransactionResult.of(Access.TransactionResultResponse.newBuilder().setStatus(TransactionOuterClass.TransactionStatus.SEALED).setStatusCode(1).setErrorMessage("message1").build()) val transactionResult2 = FlowTransactionResult.of(Access.TransactionResultResponse.newBuilder().setStatus(TransactionOuterClass.TransactionStatus.SEALED).setStatusCode(2).setErrorMessage("message2").build()) val transactionResults = listOf(transactionResult1, transactionResult2) val response = Access.TransactionResultsResponse.newBuilder().addAllTransactionResults(transactionResults.map { it.builder().build() }).build() @@ -374,16 +374,22 @@ class FlowAccessApiImplTest { @Test fun `Test getExecutionResultByBlockId`() { val blockId = FlowId("01") - val executionResult = ExecutionResult(FlowId("01"), FlowId("02")) - val response = Access.ExecutionResultByIDResponse.newBuilder().setExecutionResult(ExecutionResultOuterClass.ExecutionResult.newBuilder().setBlockId(blockId.byteStringValue).setPreviousResultId((FlowId("02").byteStringValue)).build()).build() + val grpcExecutionResult = ExecutionResultOuterClass.ExecutionResult.newBuilder() + .setBlockId(ByteString.copyFromUtf8("01")) + .setPreviousResultId(ByteString.copyFromUtf8("02")) + .addChunks(ExecutionResultOuterClass.Chunk.newBuilder().build()) + .addServiceEvents(ExecutionResultOuterClass.ServiceEvent.newBuilder().build()) + .build() + val response = Access.ExecutionResultByIDResponse.newBuilder().setExecutionResult(grpcExecutionResult).build() `when`(mockApi.getExecutionResultByID(any())).thenReturn(response) val result = flowAccessApiImpl.getExecutionResultByBlockId(blockId) - assertResultSuccess(result) { assertEquals(executionResult, it) } + assertResultSuccess(result) { + assertEquals(FlowExecutionResult.of(response), it) + } } - private fun assertResultSuccess(result: FlowAccessApi.AccessApiCallResponse, assertions: (T) -> Unit) { when (result) { is FlowAccessApi.AccessApiCallResponse.Success -> assertions(result.data) From b6f17f4c76f1fc80a0613e462f8b661a15248f77 Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Sat, 6 Jul 2024 20:09:02 +1000 Subject: [PATCH 4/4] Add missing fields --- .../org/onflow/flow/sdk/impl/AsyncFlowAccessApiImplTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/kotlin/org/onflow/flow/sdk/impl/AsyncFlowAccessApiImplTest.kt b/src/test/kotlin/org/onflow/flow/sdk/impl/AsyncFlowAccessApiImplTest.kt index 2a5a5c8..5645677 100644 --- a/src/test/kotlin/org/onflow/flow/sdk/impl/AsyncFlowAccessApiImplTest.kt +++ b/src/test/kotlin/org/onflow/flow/sdk/impl/AsyncFlowAccessApiImplTest.kt @@ -179,7 +179,7 @@ class AsyncFlowAccessApiImplTest { val accountResponse = Access.GetAccountResponse.newBuilder().setAccount(flowAccount.builder().build()).build() `when`(api.getAccount(any())).thenReturn(setupFutureMock(accountResponse)) - val result = asyncFlowAccessApi.getAccountAtLatestBlock(flowAddress).get() + val result = asyncFlowAccessApi.getAccountByAddress(flowAddress).get() assert(result is FlowAccessApi.AccessApiCallResponse.Success) result as FlowAccessApi.AccessApiCallResponse.Success assertEquals(flowAccount.address, result.data.address)