Skip to content

Commit

Permalink
Merge pull request #143 from onflow/get-system-transaction-endpoints
Browse files Browse the repository at this point in the history
Add GetSystemTransaction and GetSystemTransactionResult endpoints
  • Loading branch information
lealobanov authored Nov 22, 2024
2 parents a8a17a4 + e697552 commit 4b6ff14
Show file tree
Hide file tree
Showing 16 changed files with 669 additions and 587 deletions.
2 changes: 2 additions & 0 deletions java-example/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,9 @@ Below is a list of all Java code examples currently supported in this repo:
[Get transactions.](src/main/java/org/onflow/examples/java/getTransaction/GetTransactionAccessAPIConnector.java)

- Get transaction
- Get system transaction
- Get transaction result
- Get system transaction result
- Get transaction result by index

#### Sending Transactions
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,16 @@ public FlowTransaction getTransaction(FlowId txID) {
}
}

public FlowTransaction getSystemTransaction(FlowId blockId) {
FlowAccessApi.AccessApiCallResponse<FlowTransaction> response = accessAPI.getSystemTransaction(blockId);
if (response instanceof FlowAccessApi.AccessApiCallResponse.Success) {
return ((FlowAccessApi.AccessApiCallResponse.Success<FlowTransaction>) response).getData();
} else {
FlowAccessApi.AccessApiCallResponse.Error errorResponse = (FlowAccessApi.AccessApiCallResponse.Error) response;
throw new RuntimeException(errorResponse.getMessage(), errorResponse.getThrowable());
}
}

public FlowTransactionResult getTransactionResult(FlowId txID) {
FlowAccessApi.AccessApiCallResponse<FlowTransactionResult> response = accessAPI.getTransactionResultById(txID);
if (response instanceof FlowAccessApi.AccessApiCallResponse.Success) {
Expand All @@ -32,6 +42,16 @@ public FlowTransactionResult getTransactionResult(FlowId txID) {
}
}

public FlowTransactionResult getSystemTransactionResult(FlowId blockId) {
FlowAccessApi.AccessApiCallResponse<FlowTransactionResult> response = accessAPI.getSystemTransactionResult(blockId);
if (response instanceof FlowAccessApi.AccessApiCallResponse.Success) {
return ((FlowAccessApi.AccessApiCallResponse.Success<FlowTransactionResult>) response).getData();
} else {
FlowAccessApi.AccessApiCallResponse.Error errorResponse = (FlowAccessApi.AccessApiCallResponse.Error) response;
throw new RuntimeException(errorResponse.getMessage(), errorResponse.getThrowable());
}
}

public FlowTransactionResult getTransactionResultByIndex(FlowId blockId, Integer index) {
FlowAccessApi.AccessApiCallResponse<FlowTransactionResult> response = accessAPI.getTransactionResultByIndex(blockId, index);
if (response instanceof FlowAccessApi.AccessApiCallResponse.Success) {
Expand Down
2 changes: 2 additions & 0 deletions kotlin-example/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,9 @@ Below is a list of all Kotlin code examples currently supported in this repo:
[Get transactions.](src/main/kotlin/org/onflow/examples/kotlin/getTransaction/GetTransactionAccessAPIConnector.kt)

- Get transaction
- Get system transaction
- Get transaction result
- Get system transaction result
- Get transaction result by index

#### Sending Transactions
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,24 @@ class GetTransactionAccessAPIConnector(
is FlowAccessApi.AccessApiCallResponse.Error -> throw Exception(response.message, response.throwable)
}

fun getSystemTransaction(blockId: FlowId): FlowTransaction =
when (val response = accessAPI.getSystemTransaction(blockId)) {
is FlowAccessApi.AccessApiCallResponse.Success -> response.data
is FlowAccessApi.AccessApiCallResponse.Error -> throw Exception(response.message, response.throwable)
}

fun getTransactionResult(txID: FlowId): FlowTransactionResult =
when (val response = accessAPI.getTransactionResultById(txID)) {
is FlowAccessApi.AccessApiCallResponse.Success -> response.data
is FlowAccessApi.AccessApiCallResponse.Error -> throw Exception(response.message, response.throwable)
}

fun getSystemTransactionResult(blockId: FlowId): FlowTransactionResult =
when (val response = accessAPI.getSystemTransactionResult(blockId)) {
is FlowAccessApi.AccessApiCallResponse.Success -> response.data
is FlowAccessApi.AccessApiCallResponse.Error -> throw Exception(response.message, response.throwable)
}

fun getTransactionResultByIndex(blockId: FlowId, index: Int): FlowTransactionResult =
when (val response = accessAPI.getTransactionResultByIndex(blockId, index)) {
is FlowAccessApi.AccessApiCallResponse.Success -> response.data
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ 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.examples.kotlin.getTransaction.GetTransactionAccessAPIConnectorTest
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.FlowBlock

@FlowEmulatorProjectTest(flowJsonLocation = "../flow/flow.json")
internal class GetAccountBalanceAccessAPIConnectorTest {
Expand All @@ -18,10 +20,12 @@ internal class GetAccountBalanceAccessAPIConnectorTest {
lateinit var accessAPI: FlowAccessApi

private lateinit var balanceAPIConnector: GetAccountBalanceAccessAPIConnector
private lateinit var latestBlock: FlowBlock

@BeforeEach
fun setup() {
balanceAPIConnector = GetAccountBalanceAccessAPIConnector(accessAPI)
latestBlock = GetTransactionAccessAPIConnectorTest.fetchLatestBlockWithRetries(accessAPI)
}

@Test
Expand All @@ -36,17 +40,10 @@ internal class GetAccountBalanceAccessAPIConnectorTest {
@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
val balanceAtHeight = balanceAPIConnector.getBalanceAtBlockHeight(address, latestBlock.height)

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}")
}
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")
}

@Test
Expand All @@ -56,18 +53,11 @@ internal class GetAccountBalanceAccessAPIConnectorTest {
// 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
val blockHeight = latestBlock.height

// Fetch balance at the same block height
val balanceAtHeight = balanceAPIConnector.getBalanceAtBlockHeight(address, blockHeight)
// 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}")
}
Assertions.assertEquals(balanceAtLatest, balanceAtHeight, "Balance at latest block and specific block height should match")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.onflow.examples.kotlin.AccessAPIConnector
import org.onflow.examples.kotlin.getTransaction.GetTransactionAccessAPIConnectorTest
import org.onflow.flow.common.test.FlowEmulatorProjectTest
import org.onflow.flow.common.test.FlowServiceAccountCredentials
import org.onflow.flow.common.test.FlowTestClient
Expand All @@ -23,6 +24,7 @@ class GetCollectionAccessAPIConnectorTest {
private lateinit var accessAPIConnector: AccessAPIConnector

private lateinit var collectionId: FlowId
lateinit var block: FlowBlock

@BeforeEach
fun setup() {
Expand All @@ -36,10 +38,7 @@ class GetCollectionAccessAPIConnectorTest {
publicKey
)

val block = when (val response = accessAPI.getLatestBlock()) {
is FlowAccessApi.AccessApiCallResponse.Success -> response.data
is FlowAccessApi.AccessApiCallResponse.Error -> throw Exception(response.message, response.throwable)
}
block = GetTransactionAccessAPIConnectorTest.fetchLatestBlockWithRetries(accessAPI)
collectionId = block.collectionGuarantees.first().id
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package org.onflow.examples.kotlin.getProtocolState
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.onflow.examples.kotlin.getTransaction.GetTransactionAccessAPIConnectorTest
import org.onflow.flow.common.test.FlowEmulatorProjectTest
import org.onflow.flow.common.test.FlowTestClient
import org.onflow.flow.sdk.FlowAccessApi
Expand All @@ -20,6 +21,7 @@ internal class GetProtocolStateAccessAPIConnectorTest {
@BeforeEach
fun setup() {
protocolStateConnector = GetProtocolStateAccessAPIConnector(accessAPI)
block = GetTransactionAccessAPIConnectorTest.fetchLatestBlockWithRetries(accessAPI)
}

@Test
Expand All @@ -30,22 +32,12 @@ internal class GetProtocolStateAccessAPIConnectorTest {

@Test
fun `Can get protocol state snapshot by blockId`() {
block = when (val response = accessAPI.getLatestBlock()) {
is FlowAccessApi.AccessApiCallResponse.Success -> response.data
is FlowAccessApi.AccessApiCallResponse.Error -> throw Exception(response.message, response.throwable)
}

val latestSnapshot: FlowSnapshot = protocolStateConnector.getProtocolStateSnapshotByBlockId(block.id)
assertNotNull(latestSnapshot, ("Snapshot should not be null"))
}

@Test
fun `Can get protocol state snapshot by height`() {
block = when (val response = accessAPI.getLatestBlock()) {
is FlowAccessApi.AccessApiCallResponse.Success -> response.data
is FlowAccessApi.AccessApiCallResponse.Error -> throw Exception(response.message, response.throwable)
}

val latestSnapshot: FlowSnapshot = protocolStateConnector.getProtocolStateSnapshotByHeight(block.height)
assertNotNull(latestSnapshot, ("Snapshot should not be null"))
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package org.onflow.examples.kotlin.getTransaction

import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
Expand Down Expand Up @@ -37,10 +39,7 @@ internal class GetTransactionAccessAPIConnectorTest {
publicKey
)

block = when (val response = accessAPI.getLatestBlock()) {
is FlowAccessApi.AccessApiCallResponse.Success -> response.data
is FlowAccessApi.AccessApiCallResponse.Error -> throw Exception(response.message, response.throwable)
}
block = fetchLatestBlockWithRetries(accessAPI)
}

@Test
Expand All @@ -66,4 +65,19 @@ internal class GetTransactionAccessAPIConnectorTest {
assertNotNull(transactionResult, "Transaction result should not be null")
assertTrue(transactionResult.status === FlowTransactionStatus.SEALED, "Transaction should be sealed")
}

companion object {
fun fetchLatestBlockWithRetries(accessAPI: FlowAccessApi, retries: Int = 5, delayMillis: Long = 500): FlowBlock {
repeat(retries) { attempt ->
when (val response = accessAPI.getLatestBlock()) {
is FlowAccessApi.AccessApiCallResponse.Success -> return response.data
is FlowAccessApi.AccessApiCallResponse.Error -> {
println("Attempt ${attempt + 1} failed: ${response.message}. Retrying...")
runBlocking { delay(delayMillis) }
}
}
}
throw Exception("Failed to retrieve the latest block after $retries attempts.")
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package org.onflow.flow.sdk.transaction

import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import org.onflow.flow.sdk.*
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
Expand Down Expand Up @@ -43,8 +45,22 @@ class TransactionIntegrationTest {
fail("$errorMessage: ${e.message}")
}

private fun getLatestBlock(): FlowBlock =
safelyHandle({ Result.success(handleResult(accessAPI.getLatestBlock(true), LATEST_BLOCK_ERROR)) }, LATEST_BLOCK_ERROR)
private fun getLatestBlock(retries: Int = 5, delayMillis: Long = 500): FlowBlock {
repeat(retries) { attempt ->
try {
return safelyHandle(
{ Result.success(handleResult(accessAPI.getLatestBlock(true), LATEST_BLOCK_ERROR)) },
LATEST_BLOCK_ERROR
)
} catch (e: Exception) {
if (attempt == retries - 1) {
throw Exception("$LATEST_BLOCK_ERROR after $retries attempts", e)
}
runBlocking { delay(delayMillis) }
}
}
throw Exception(LATEST_BLOCK_ERROR)
}

private fun getAccountAtLatestBlock(address: FlowAddress): FlowAccount =
safelyHandle({ Result.success(handleResult(accessAPI.getAccountAtLatestBlock(address), ACCOUNT_ERROR)) }, ACCOUNT_ERROR)
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 @@ -40,6 +40,10 @@ interface AsyncFlowAccessApi {

fun getTransactionResultById(id: FlowId): CompletableFuture<FlowAccessApi.AccessApiCallResponse<FlowTransactionResult?>>

fun getSystemTransaction(blockId: FlowId): CompletableFuture<FlowAccessApi.AccessApiCallResponse<FlowTransaction?>>

fun getSystemTransactionResult(blockId: FlowId): CompletableFuture<FlowAccessApi.AccessApiCallResponse<FlowTransactionResult?>>

fun getTransactionResultByIndex(blockId: FlowId, index: Int): CompletableFuture<FlowAccessApi.AccessApiCallResponse<FlowTransactionResult>>

@Deprecated(
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 @@ -52,6 +52,10 @@ interface FlowAccessApi {

fun getTransactionResultById(id: FlowId): AccessApiCallResponse<FlowTransactionResult>

fun getSystemTransaction(blockId: FlowId): AccessApiCallResponse<FlowTransaction>

fun getSystemTransactionResult(blockId: FlowId): AccessApiCallResponse<FlowTransactionResult>

fun getTransactionResultByIndex(blockId: FlowId, index: Int): AccessApiCallResponse<FlowTransactionResult>

@Deprecated(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,34 @@ class AsyncFlowAccessApiImpl(
errorMessage = "Failed to get transaction result by index"
)

override fun getSystemTransaction(blockId: FlowId): CompletableFuture<FlowAccessApi.AccessApiCallResponse<FlowTransaction?>> =
handleApiCall(
apiCall = {
api.getSystemTransaction(
Access.GetSystemTransactionRequest
.newBuilder()
.setBlockId(blockId.byteStringValue)
.build()
)
},
transform = { if (it.hasTransaction()) FlowTransaction.of(it.transaction) else null },
errorMessage = "Failed to get system transaction by block ID"
)

override fun getSystemTransactionResult(blockId: FlowId): CompletableFuture<FlowAccessApi.AccessApiCallResponse<FlowTransactionResult?>> =
handleApiCall(
apiCall = {
api.getSystemTransactionResult(
Access.GetSystemTransactionResultRequest
.newBuilder()
.setBlockId(blockId.byteStringValue)
.build()
)
},
transform = { FlowTransactionResult.of(it) },
errorMessage = "Failed to get system transaction result by block ID"
)

private fun executeScript(
apiCall: () -> ListenableFuture<Access.ExecuteScriptResponse>,
errorMessage: String
Expand Down
Loading

0 comments on commit 4b6ff14

Please sign in to comment.