From 164cff8576e97b3f42b491fb8e69557e0ade8674 Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Thu, 19 Jun 2025 02:07:08 +0900 Subject: [PATCH 1/7] Bump version --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 1f6d80d..18fd739 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -22,7 +22,7 @@ allprojects { } group = "org.onflow.flow" - val defaultVersion = "0.0.18" + val defaultVersion = "0.0.19" version = System.getenv("GITHUB_REF")?.split('/')?.last() ?: defaultVersion } From 5e230243d61b131ef550fa3e6a499014441040f2 Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Thu, 19 Jun 2025 02:22:31 +0900 Subject: [PATCH 2/7] Bump version --- build.gradle.kts | 2 +- .../org/onflow/flow/infrastructure/ApiBase.kt | 21 ++++--------------- 2 files changed, 5 insertions(+), 18 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 18fd739..dff683f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -22,7 +22,7 @@ allprojects { } group = "org.onflow.flow" - val defaultVersion = "0.0.19" + val defaultVersion = "0.0.20" version = System.getenv("GITHUB_REF")?.split('/')?.last() ?: defaultVersion } diff --git a/flow/src/commonMain/kotlin/org/onflow/flow/infrastructure/ApiBase.kt b/flow/src/commonMain/kotlin/org/onflow/flow/infrastructure/ApiBase.kt index 6d1822a..4d958f8 100644 --- a/flow/src/commonMain/kotlin/org/onflow/flow/infrastructure/ApiBase.kt +++ b/flow/src/commonMain/kotlin/org/onflow/flow/infrastructure/ApiBase.kt @@ -20,24 +20,11 @@ open class ApiBase { connectTimeoutMillis = 20000L // Increased to 20 seconds socketTimeoutMillis = 45000L // Increased to 45 seconds } - + install(HttpRequestRetry) { - retryOnServerErrors(maxRetries = 5) - retryOnException(maxRetries = 3) { _, cause -> - // Retry on connection-related exceptions - cause.message?.contains("Connection reset by peer", ignoreCase = true) == true || - cause.message?.contains("IOException", ignoreCase = true) == true || - cause.message?.contains("ConnectException", ignoreCase = true) == true || - cause.message?.contains("SocketTimeoutException", ignoreCase = true) == true || - cause.message?.contains("Channel was closed", ignoreCase = true) == true || - cause.message?.contains("ClosedReceiveChannelException", ignoreCase = true) == true || - cause.message?.contains("ClosedSendChannelException", ignoreCase = true) == true || - cause.message?.contains("TLS", ignoreCase = true) == true || - // Check class names for Kotlin exceptions - cause.javaClass.simpleName.contains("ClosedReceiveChannelException", ignoreCase = true) || - cause.javaClass.simpleName.contains("ClosedSendChannelException", ignoreCase = true) - } - exponentialDelay(base = 2.0, maxDelayMs = 10000L) + retryOnServerErrors(maxRetries = 3) + retryOnException(maxRetries = 3, retryOnTimeout = true) + exponentialDelay() // Use exponential backoff } install(Logging) { From 7f2f12e161bcd45cb68230781bd8a3b627b97698 Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Thu, 19 Jun 2025 02:27:49 +0900 Subject: [PATCH 3/7] Bump version --- .../org/onflow/flow/infrastructure/ApiBase.kt | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/flow/src/commonMain/kotlin/org/onflow/flow/infrastructure/ApiBase.kt b/flow/src/commonMain/kotlin/org/onflow/flow/infrastructure/ApiBase.kt index 4d958f8..613d5cd 100644 --- a/flow/src/commonMain/kotlin/org/onflow/flow/infrastructure/ApiBase.kt +++ b/flow/src/commonMain/kotlin/org/onflow/flow/infrastructure/ApiBase.kt @@ -2,7 +2,6 @@ package org.onflow.flow.infrastructure import io.ktor.client.HttpClient import io.ktor.client.plugins.HttpRequestRetry -import io.ktor.client.plugins.HttpTimeout import io.ktor.client.plugins.contentnegotiation.ContentNegotiation import io.ktor.client.plugins.logging.DEFAULT import io.ktor.client.plugins.logging.Logger @@ -15,16 +14,9 @@ open class ApiBase { companion object { val client = HttpClient { - install(HttpTimeout) { - requestTimeoutMillis = 45000L // Increased to 45 seconds - connectTimeoutMillis = 20000L // Increased to 20 seconds - socketTimeoutMillis = 45000L // Increased to 45 seconds - } - install(HttpRequestRetry) { - retryOnServerErrors(maxRetries = 3) - retryOnException(maxRetries = 3, retryOnTimeout = true) - exponentialDelay() // Use exponential backoff + retryOnServerErrors(maxRetries = 5) + exponentialDelay() } install(Logging) { @@ -42,4 +34,4 @@ open class ApiBase { } } -} +} \ No newline at end of file From a2165a0652dbb53dc27681f036294017204fafe5 Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Thu, 19 Jun 2025 02:31:09 +0900 Subject: [PATCH 4/7] Bump version --- .../org/onflow/flow/FlowTransactionTests.kt | 154 ------------------ 1 file changed, 154 deletions(-) diff --git a/flow/src/commonTest/kotlin/org/onflow/flow/FlowTransactionTests.kt b/flow/src/commonTest/kotlin/org/onflow/flow/FlowTransactionTests.kt index d5b1dc2..ac69d12 100644 --- a/flow/src/commonTest/kotlin/org/onflow/flow/FlowTransactionTests.kt +++ b/flow/src/commonTest/kotlin/org/onflow/flow/FlowTransactionTests.kt @@ -498,160 +498,6 @@ class FlowTransactionTests { println("✅ Signature debug test completed") } - /** - * Debug test to verify multi-signer signing process - */ - @OptIn(ExperimentalStdlibApi::class) - @Test - fun testMultiSignerDebug() = runBlocking { - // ===== SETUP ===== - val proposerAddress = "c6de0d94160377cd" - val proposerPrivateKey = "c9c0f04adddf7674d265c395de300a65a777d3ec412bba5bfdfd12cffbbb78d9" - val payerAddress = "10711015c370a95c" - val payerPrivateKey = "38ebd09b83e221e406b176044a65350333b3a5280ed3f67227bd80d55ac91a0f" - - // Get on-chain account information - val proposerAccount = api.getAccount(proposerAddress) - val payerAccount = api.getAccount(payerAddress) - val proposerKey = proposerAccount.keys!!.first { it.index.toInt() == 0 } - val payerKey = payerAccount.keys!!.first { it.index.toInt() == 0 } - - // Create signers with on-chain algorithms - val proposerSigner = Crypto.getSigner( - Crypto.decodePrivateKey(proposerPrivateKey, proposerKey.signingAlgorithm), - proposerKey.hashingAlgorithm - ).apply { - address = proposerAddress - keyIndex = 0 - } - - val payerSigner = Crypto.getSigner( - Crypto.decodePrivateKey(payerPrivateKey, payerKey.signingAlgorithm), - payerKey.hashingAlgorithm - ).apply { - address = payerAddress - keyIndex = 0 - } - - val latestBlock = api.getBlockHeader() - - // ===== TRANSACTION BUILDING ===== - val transaction = TransactionBuilder( - script = """ - transaction { - prepare(signer: auth(Storage) &Account) { - log("Multi-signer debug test") - } - } - """.trimIndent() - ) - .withProposalKey(proposerAddress, 0, proposerKey.sequenceNumber.toBigInteger()) - .withPayer(payerAddress) - .withAuthorizers(listOf(proposerAddress)) - .withReferenceBlockId(latestBlock.id) - .build() - - println("MULTI-SIGNER DEBUG:") - println("Initial transaction payload signatures: ${transaction.payloadSignatures.size}") - println("Initial transaction envelope signatures: ${transaction.envelopeSignatures.size}") - - // Step 1: Test payload signing manually (corrected approach) - val payloadMessageToSign = transaction.payloadMessage() - val proposerPayloadSig = proposerSigner.sign(payloadMessageToSign) // Fix: Use corrected approach - - println("Payload message to sign: ${payloadMessageToSign.toHexString()}") - println("Proposer payload signature: ${proposerPayloadSig.toHexString()}") - - // Add the payload signature - val txWithPayloadSig = transaction.copy( - payloadSignatures = listOf( - TransactionSignature( - address = proposerAddress, - keyIndex = 0, - signature = proposerPayloadSig.toHexString() - ) - ) - ) - - println("Transaction after adding payload sig - payloadSignatures: ${txWithPayloadSig.payloadSignatures.size}") - - // Step 2: Test envelope signing manually (corrected approach) - val envelopeMessageToSign = txWithPayloadSig.envelopeMessage() - val payerEnvelopeSig = payerSigner.sign(envelopeMessageToSign) // Fix: Use corrected approach - - println("Envelope message to sign: ${envelopeMessageToSign.toHexString()}") - println("Payer envelope signature: ${payerEnvelopeSig.toHexString()}") - - // Test automatic signing vs manual signing - val autoSignedTransaction = transaction.sign(listOf(proposerSigner, payerSigner)) - - println("Auto-signed transaction - payloadSignatures: ${autoSignedTransaction.payloadSignatures.size}") - println("Auto-signed transaction - envelopeSignatures: ${autoSignedTransaction.envelopeSignatures.size}") - - if (autoSignedTransaction.payloadSignatures.isNotEmpty()) { - println("Auto payload signature: ${autoSignedTransaction.payloadSignatures[0].signature}") - } - if (autoSignedTransaction.envelopeSignatures.isNotEmpty()) { - println("Auto envelope signature: ${autoSignedTransaction.envelopeSignatures[0].signature}") - } - - // Compare signatures - val manualPayloadSig = proposerPayloadSig.toHexString() - val autoPayloadSig = if (autoSignedTransaction.payloadSignatures.isNotEmpty()) - autoSignedTransaction.payloadSignatures[0].signature else "NONE" - - val manualEnvelopeSig = payerEnvelopeSig.toHexString() - val autoEnvelopeSig = if (autoSignedTransaction.envelopeSignatures.isNotEmpty()) - autoSignedTransaction.envelopeSignatures[0].signature else "NONE" - - println("Manual vs Auto payload signature match: ${manualPayloadSig == autoPayloadSig}") - println("Manual vs Auto envelope signature match: ${manualEnvelopeSig == autoEnvelopeSig}") - - // The key test: Do both payload messages for signing match? - val manualPayloadMessage = transaction.payloadMessage() - val autoPayloadMessage = transaction.payloadMessage() // These should be identical - - println("Manual vs Auto payload message match: ${manualPayloadMessage.contentEquals(autoPayloadMessage)}") - - // CRITICAL TEST: Try submitting the manual transaction - println("Testing manual transaction submission...") - val manualTransaction = txWithPayloadSig.copy( - envelopeSignatures = listOf( - TransactionSignature( - address = payerAddress, - keyIndex = 0, - signature = payerEnvelopeSig.toHexString() - ) - ) - ) - - try { - val manualResult = api.sendTransaction(manualTransaction) - println("Manual transaction submitted successfully: ${manualResult.id}") - - api.waitForSeal(manualResult.id!!) - println("Manual transaction sealed successfully!") - - } catch (e: Exception) { - println("Manual transaction failed: ${e.message}") - } - - // TEST: Try submitting the automatic transaction - println("Testing automatic transaction submission...") - try { - val autoResult = api.sendTransaction(autoSignedTransaction) - println("Auto transaction submitted successfully: ${autoResult.id}") - - api.waitForSeal(autoResult.id!!) - println("Auto transaction sealed successfully!") - - } catch (e: Exception) { - println("Auto transaction failed: ${e.message}") - } - - println("✅ Multi-signer debug test completed") - } - /** * Test UFix64 scientific notation handling in real transactions * This test verifies that values like 1.0E-8 are properly encoded and don't cause transaction failures From e2f831191a00d9b2c61d12bb9d1803be43f32a78 Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Thu, 19 Jun 2025 09:16:34 +0900 Subject: [PATCH 5/7] Wait for FINAL state --- flow/src/commonMain/kotlin/org/onflow/flow/FlowApi.kt | 8 ++++---- .../kotlin/org/onflow/flow/apis/AccountsApi.kt | 2 +- .../kotlin/org/onflow/flow/apis/BlocksApi.kt | 4 ++-- .../kotlin/org/onflow/flow/apis/ScriptsApi.kt | 2 +- .../kotlin/org/onflow/flow/apis/TransactionsApi.kt | 11 +++++------ 5 files changed, 13 insertions(+), 14 deletions(-) diff --git a/flow/src/commonMain/kotlin/org/onflow/flow/FlowApi.kt b/flow/src/commonMain/kotlin/org/onflow/flow/FlowApi.kt index fac1442..b3d0b54 100644 --- a/flow/src/commonMain/kotlin/org/onflow/flow/FlowApi.kt +++ b/flow/src/commonMain/kotlin/org/onflow/flow/FlowApi.kt @@ -29,18 +29,18 @@ class FlowApi(val chainId: ChainIdProvider) { private val scriptsApi = ScriptsApi(baseUrl) private val transactionsApi = TransactionsApi(baseUrl) - suspend fun getAccount(address: String, blockHeight: String? = null, blockStatus: BlockStatus = BlockStatus.SEALED): Account { + suspend fun getAccount(address: String, blockHeight: String? = null, blockStatus: BlockStatus = BlockStatus.FINAL): Account { return accountsApi.getAccount(address, blockHeight, blockStatus) } - suspend fun getBlock(id: String? = null, blockHeight: String? = null, blockStatus: BlockStatus = BlockStatus.SEALED): Block { + suspend fun getBlock(id: String? = null, blockHeight: String? = null, blockStatus: BlockStatus = BlockStatus.FINAL): Block { return blocksApi.getBlock(id, blockHeight, blockStatus) } suspend fun getBlockHeader( id: String? = null, blockHeight: String? = null, - blockStatus: BlockStatus = BlockStatus.SEALED + blockStatus: BlockStatus = BlockStatus.FINAL ): BlockHeader { return blocksApi.getBlockHeader(id, blockHeight, blockStatus) } @@ -71,7 +71,7 @@ class FlowApi(val chainId: ChainIdProvider) { arguments: List? = null, blockId: String? = null, blockHeight: String? = null, - blockStatus: BlockStatus = BlockStatus.SEALED + blockStatus: BlockStatus = BlockStatus.FINAL ): Cadence.Value { return scriptsApi.executeScript(script, arguments, blockId, blockHeight, blockStatus) } diff --git a/flow/src/commonMain/kotlin/org/onflow/flow/apis/AccountsApi.kt b/flow/src/commonMain/kotlin/org/onflow/flow/apis/AccountsApi.kt index 3e18f64..0a474fb 100644 --- a/flow/src/commonMain/kotlin/org/onflow/flow/apis/AccountsApi.kt +++ b/flow/src/commonMain/kotlin/org/onflow/flow/apis/AccountsApi.kt @@ -37,7 +37,7 @@ internal class AccountsApi(val baseUrl: String) : ApiBase() { * @param blockStatus The status of the block to query (FINAL or SEALED). Defaults to FINAL. * @return Account */ - internal suspend fun getAccount(address: String, blockHeight: String? = null, blockStatus: BlockStatus = BlockStatus.SEALED): Account { + internal suspend fun getAccount(address: String, blockHeight: String? = null, blockStatus: BlockStatus = BlockStatus.FINAL): Account { val expand = setOf("contracts", "keys") return if (blockHeight != null) { request(address, blockHeight, expand) diff --git a/flow/src/commonMain/kotlin/org/onflow/flow/apis/BlocksApi.kt b/flow/src/commonMain/kotlin/org/onflow/flow/apis/BlocksApi.kt index 1945f93..26e32a4 100644 --- a/flow/src/commonMain/kotlin/org/onflow/flow/apis/BlocksApi.kt +++ b/flow/src/commonMain/kotlin/org/onflow/flow/apis/BlocksApi.kt @@ -54,7 +54,7 @@ internal class BlocksApi(val baseUrl: String) : ApiBase() { }.body() } - internal suspend fun getBlock(id: String? = null, blockHeight: String? = null, blockStatus: BlockStatus = BlockStatus.SEALED): Block { + internal suspend fun getBlock(id: String? = null, blockHeight: String? = null, blockStatus: BlockStatus = BlockStatus.FINAL): Block { val expand = setOf("payload") return if (id != null) { requestBlocksById(id, expand).first() @@ -68,7 +68,7 @@ internal class BlocksApi(val baseUrl: String) : ApiBase() { internal suspend fun getBlockHeader( id: String? = null, blockHeight: String? = null, - blockStatus: BlockStatus = BlockStatus.SEALED + blockStatus: BlockStatus = BlockStatus.FINAL ): BlockHeader { return getBlock(id, blockHeight, blockStatus).header } diff --git a/flow/src/commonMain/kotlin/org/onflow/flow/apis/ScriptsApi.kt b/flow/src/commonMain/kotlin/org/onflow/flow/apis/ScriptsApi.kt index 7a35d0c..d2e0271 100644 --- a/flow/src/commonMain/kotlin/org/onflow/flow/apis/ScriptsApi.kt +++ b/flow/src/commonMain/kotlin/org/onflow/flow/apis/ScriptsApi.kt @@ -56,7 +56,7 @@ class ScriptsApi(val baseUrl: String) : ApiBase() { arguments: List? = null, blockId: String? = null, blockHeight: String? = null, - blockStatus: BlockStatus = BlockStatus.SEALED + blockStatus: BlockStatus = BlockStatus.FINAL ): Cadence.Value { val request = ScriptsPostRequest( script.encodeBase64(), diff --git a/flow/src/commonMain/kotlin/org/onflow/flow/apis/TransactionsApi.kt b/flow/src/commonMain/kotlin/org/onflow/flow/apis/TransactionsApi.kt index def7aed..cb94e45 100644 --- a/flow/src/commonMain/kotlin/org/onflow/flow/apis/TransactionsApi.kt +++ b/flow/src/commonMain/kotlin/org/onflow/flow/apis/TransactionsApi.kt @@ -100,10 +100,9 @@ internal class TransactionsApi(val baseUrl: String) : ApiBase() { try { val result = getTransactionResult(transactionId) when (result.status ?: TransactionStatus.EMPTY) { - TransactionStatus.SEALED -> { - if (result.errorMessage.isNotBlank() || result.execution == TransactionExecution.failure) { - throw RuntimeException("Transaction failed: ${result.errorMessage}") - } + TransactionStatus.FINALIZED -> { + // Transaction is finalized, return immediately without checking for errors + // since finalized transactions haven't been executed yet return result } @@ -146,9 +145,9 @@ internal class TransactionsApi(val baseUrl: String) : ApiBase() { } val timeoutMessage = if (lastError != null) { - "Transaction not sealed after $maxAttempts attempts. Last error: ${lastError.message}" + "Transaction not finalized after $maxAttempts attempts. Last error: ${lastError.message}" } else { - "Transaction not sealed after $maxAttempts attempts" + "Transaction not finalized after $maxAttempts attempts" } throw RuntimeException(timeoutMessage) } From 377bc4b53955fdd35273112a7fee76acb45eba82 Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Thu, 19 Jun 2025 09:16:50 +0900 Subject: [PATCH 6/7] Wait for FINAL state --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index dff683f..6f65ce5 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -22,7 +22,7 @@ allprojects { } group = "org.onflow.flow" - val defaultVersion = "0.0.20" + val defaultVersion = "0.0.21" version = System.getenv("GITHUB_REF")?.split('/')?.last() ?: defaultVersion } From 1e6c8d4b6007d5fb10c0b19e53ae1ffc271b5359 Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Thu, 19 Jun 2025 09:53:54 +0900 Subject: [PATCH 7/7] Removed sealed classes --- build.gradle.kts | 2 +- .../onflow/flow/websocket/FlowWebSocketMessage.kt | 12 +++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 6f65ce5..1c8da1d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -22,7 +22,7 @@ allprojects { } group = "org.onflow.flow" - val defaultVersion = "0.0.21" + val defaultVersion = "0.0.22" version = System.getenv("GITHUB_REF")?.split('/')?.last() ?: defaultVersion } diff --git a/flow/src/commonMain/kotlin/org/onflow/flow/websocket/FlowWebSocketMessage.kt b/flow/src/commonMain/kotlin/org/onflow/flow/websocket/FlowWebSocketMessage.kt index 09b0237..fbfa897 100644 --- a/flow/src/commonMain/kotlin/org/onflow/flow/websocket/FlowWebSocketMessage.kt +++ b/flow/src/commonMain/kotlin/org/onflow/flow/websocket/FlowWebSocketMessage.kt @@ -6,9 +6,11 @@ import kotlinx.serialization.json.JsonElement import org.onflow.flow.models.Event import org.onflow.flow.models.TransactionResult -@Serializable -sealed class FlowWebSocketMessage { - abstract val subscriptionId: String? +/** + * Base interface for WebSocket messages to avoid sealed class ASM transformation issues + */ +interface FlowWebSocketMessage { + val subscriptionId: String? } @Serializable @@ -18,7 +20,7 @@ data class FlowWebSocketRequest( val action: String, val topic: String? = null, val arguments: Map? = null -) : FlowWebSocketMessage() +) : FlowWebSocketMessage @Serializable data class FlowWebSocketResponse( @@ -28,7 +30,7 @@ data class FlowWebSocketResponse( val topic: String? = null, val payload: JsonElement? = null, val error: FlowWebSocketError? = null -) : FlowWebSocketMessage() +) : FlowWebSocketMessage @Serializable data class BlockEventPayload(