From bcd2eb6a68b674aebe2c5a87c29c60942880fbaa Mon Sep 17 00:00:00 2001 From: Jamie Li Date: Thu, 23 Oct 2025 09:41:19 -0700 Subject: [PATCH 01/11] Fix the InternalException thrown from notify_onchain_fund_sent in Sep24 deposit transaction --- .../stellar/reference/sep24/DepositService.kt | 43 ++++++++++--------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/kotlin-reference-server/src/main/kotlin/org/stellar/reference/sep24/DepositService.kt b/kotlin-reference-server/src/main/kotlin/org/stellar/reference/sep24/DepositService.kt index f31f13302..20974e806 100644 --- a/kotlin-reference-server/src/main/kotlin/org/stellar/reference/sep24/DepositService.kt +++ b/kotlin-reference-server/src/main/kotlin/org/stellar/reference/sep24/DepositService.kt @@ -23,7 +23,7 @@ class DepositService(private val cfg: Config, private val paymentClient: Payment amount: BigDecimal, account: String, asset: String, - memo: String? + memo: String?, ) { try { var transaction = sep24.getTransaction(transactionId) @@ -57,7 +57,7 @@ class DepositService(private val cfg: Config, private val paymentClient: Payment account, Asset.create(asset.replace("stellar:", "")), transaction.amountOut!!.amount!!, - memo + memo, ) // 6. Finalize Stellar anchor transaction @@ -149,29 +149,32 @@ class DepositService(private val cfg: Config, private val paymentClient: Payment private suspend fun finalizeStellarTransaction( transactionId: String, - stellarTransactionId: String + stellarTransactionId: String, ) { // SAC transfers submitted to RPC are asynchronous, we will need to retry // until the RPC returns a success response if (cfg.appSettings.rpcEnabled) { - flow { - sep24.rpcAction( - "notify_onchain_funds_sent", - NotifyOnchainFundsSentRequest( - transactionId = transactionId, - stellarTransactionId = stellarTransactionId, - ), - ) - } - .retryWhen { _, attempt -> - if (attempt < 5) { - delay(5_000) - return@retryWhen true - } else { - return@retryWhen false + val sep24Txn = sep24.getTransaction(stellarTransactionId) + if (sep24Txn.status == "pending_anchor") { + flow { + sep24.rpcAction( + "notify_onchain_funds_sent", + NotifyOnchainFundsSentRequest( + transactionId = transactionId, + stellarTransactionId = stellarTransactionId, + ), + ) } - } - .collect {} + .retryWhen { _, attempt -> + if (attempt < 5) { + delay(5_000) + return@retryWhen true + } else { + return@retryWhen false + } + } + .collect {} + } } } From a8f5ce83e3d745d112fdcad469ef575dd49f3c6b Mon Sep 17 00:00:00 2001 From: Jamie Li Date: Thu, 23 Oct 2025 13:48:40 -0700 Subject: [PATCH 02/11] Fix getTransaction failure --- .../org/stellar/reference/sep24/DepositService.kt | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/kotlin-reference-server/src/main/kotlin/org/stellar/reference/sep24/DepositService.kt b/kotlin-reference-server/src/main/kotlin/org/stellar/reference/sep24/DepositService.kt index 20974e806..d7fef8993 100644 --- a/kotlin-reference-server/src/main/kotlin/org/stellar/reference/sep24/DepositService.kt +++ b/kotlin-reference-server/src/main/kotlin/org/stellar/reference/sep24/DepositService.kt @@ -61,7 +61,7 @@ class DepositService(private val cfg: Config, private val paymentClient: Payment ) // 6. Finalize Stellar anchor transaction - finalizeStellarTransaction(transactionId, txHash) + finalizeStellarTransaction(transaction, txHash) } } @@ -123,11 +123,6 @@ class DepositService(private val cfg: Config, private val paymentClient: Payment "pending_anchor", "funds received, transaction is being processed", ) - sep24.patchTransaction( - transactionId, - "pending_stellar", - "funds received, transaction is being processed", - ) } } @@ -148,19 +143,19 @@ class DepositService(private val cfg: Config, private val paymentClient: Payment } private suspend fun finalizeStellarTransaction( - transactionId: String, + transaction: Transaction, stellarTransactionId: String, ) { // SAC transfers submitted to RPC are asynchronous, we will need to retry // until the RPC returns a success response if (cfg.appSettings.rpcEnabled) { - val sep24Txn = sep24.getTransaction(stellarTransactionId) - if (sep24Txn.status == "pending_anchor") { + println("Finalizing Stellar transaction ${transaction.id} with status ${transaction.status}") + if (transaction.status == "pending_anchor") { flow { sep24.rpcAction( "notify_onchain_funds_sent", NotifyOnchainFundsSentRequest( - transactionId = transactionId, + transactionId = transaction.id, stellarTransactionId = stellarTransactionId, ), ) From f872c1effce33597b760345bffcdba57ff598fb4 Mon Sep 17 00:00:00 2001 From: Jamie Li Date: Thu, 23 Oct 2025 14:00:05 -0700 Subject: [PATCH 03/11] clean up --- .../main/kotlin/org/stellar/reference/sep24/DepositService.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/kotlin-reference-server/src/main/kotlin/org/stellar/reference/sep24/DepositService.kt b/kotlin-reference-server/src/main/kotlin/org/stellar/reference/sep24/DepositService.kt index d7fef8993..0c34249af 100644 --- a/kotlin-reference-server/src/main/kotlin/org/stellar/reference/sep24/DepositService.kt +++ b/kotlin-reference-server/src/main/kotlin/org/stellar/reference/sep24/DepositService.kt @@ -149,7 +149,6 @@ class DepositService(private val cfg: Config, private val paymentClient: Payment // SAC transfers submitted to RPC are asynchronous, we will need to retry // until the RPC returns a success response if (cfg.appSettings.rpcEnabled) { - println("Finalizing Stellar transaction ${transaction.id} with status ${transaction.status}") if (transaction.status == "pending_anchor") { flow { sep24.rpcAction( From 7603c1425271fab2cc512466431c45cc24cf8dba Mon Sep 17 00:00:00 2001 From: Jamie Li Date: Thu, 23 Oct 2025 14:45:26 -0700 Subject: [PATCH 04/11] Add warning message when the transaction status isn't expected --- .../main/kotlin/org/stellar/reference/sep24/DepositService.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/kotlin-reference-server/src/main/kotlin/org/stellar/reference/sep24/DepositService.kt b/kotlin-reference-server/src/main/kotlin/org/stellar/reference/sep24/DepositService.kt index 0c34249af..b0c8e4849 100644 --- a/kotlin-reference-server/src/main/kotlin/org/stellar/reference/sep24/DepositService.kt +++ b/kotlin-reference-server/src/main/kotlin/org/stellar/reference/sep24/DepositService.kt @@ -168,6 +168,10 @@ class DepositService(private val cfg: Config, private val paymentClient: Payment } } .collect {} + } else { + log.warn { + "Transaction ${transaction.id} is in unexpected status ${transaction.status}, skipping notify_onchain_funds_sent" + } } } } From cb10ba0a34c175a6204ecfb9baa664ad2e5fde24 Mon Sep 17 00:00:00 2001 From: Jamie Li Date: Fri, 24 Oct 2025 09:46:18 -0700 Subject: [PATCH 05/11] Fix observer not updating the blocks read and processed when there is no filtered events received --- .../observer/stellar/StellarRpcPaymentObserver.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/platform/src/main/java/org/stellar/anchor/platform/observer/stellar/StellarRpcPaymentObserver.java b/platform/src/main/java/org/stellar/anchor/platform/observer/stellar/StellarRpcPaymentObserver.java index d33838ca4..45b678a33 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/observer/stellar/StellarRpcPaymentObserver.java +++ b/platform/src/main/java/org/stellar/anchor/platform/observer/stellar/StellarRpcPaymentObserver.java @@ -17,7 +17,6 @@ import java.util.stream.Stream; import lombok.Builder; import lombok.Getter; -import lombok.val; import org.stellar.anchor.api.asset.AssetInfo; import org.stellar.anchor.api.asset.StellarAssetInfo; import org.stellar.anchor.api.exception.AnchorException; @@ -119,12 +118,15 @@ private void fetchEvents() { try { GetEventsResponse response = sorobanServer.getEvents(buildEventRequest(cursor)); + metricLatestBlockRead.set(response.getLatestLedger()); + if (response.getEvents() != null && !response.getEvents().isEmpty()) { processEvents(response.getEvents()); } // Save the cursor for the next request cursor = response.getCursor(); saveCursor(cursor); + metricLatestBlockProcessed.set(response.getLatestLedger()); } catch (IOException ioex) { warnF( "Error fetching latest ledger: {}. ex={}. Wait for next retry.", @@ -136,8 +138,6 @@ private void fetchEvents() { private void processEvents(List events) { if (events == null || events.isEmpty()) return; debugF("Processing {} 'transfer' events", events.size()); - val lastEvent = events.get(events.size() - 1); - if (lastEvent != null) metricLatestBlockRead.set(lastEvent.getLedger()); for (EventInfo event : events) { ShouldProcessResult result = shouldProcess(event); @@ -145,8 +145,6 @@ private void processEvents(List events) { processTransferEvent(result); } } - - if (lastEvent != null) metricLatestBlockProcessed.set(lastEvent.getLedger()); } private void processTransferEvent(ShouldProcessResult result) { From 16c8c7e0aac00ae3132507d732af5af19cd5fb53 Mon Sep 17 00:00:00 2001 From: Jamie Li Date: Tue, 4 Nov 2025 11:18:18 -0800 Subject: [PATCH 06/11] Fix the reference server incorrectly handling async rpc calls --- .../stellar/reference/sep24/DepositService.kt | 69 ++++++++++++------- 1 file changed, 45 insertions(+), 24 deletions(-) diff --git a/kotlin-reference-server/src/main/kotlin/org/stellar/reference/sep24/DepositService.kt b/kotlin-reference-server/src/main/kotlin/org/stellar/reference/sep24/DepositService.kt index b0c8e4849..c5e6236e6 100644 --- a/kotlin-reference-server/src/main/kotlin/org/stellar/reference/sep24/DepositService.kt +++ b/kotlin-reference-server/src/main/kotlin/org/stellar/reference/sep24/DepositService.kt @@ -4,13 +4,13 @@ import io.github.oshai.kotlinlogging.KotlinLogging import java.math.BigDecimal import java.math.RoundingMode import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.flow -import kotlinx.coroutines.flow.retryWhen +import kotlinx.coroutines.withTimeout import org.stellar.reference.client.PaymentClient import org.stellar.reference.data.* import org.stellar.reference.service.SepHelper import org.stellar.reference.transactionWithRetry import org.stellar.sdk.Asset +import org.stellar.sdk.responses.sorobanrpc.GetTransactionResponse private val log = KotlinLogging.logger {} @@ -51,18 +51,19 @@ class DepositService(private val cfg: Config, private val paymentClient: Payment // 7. Finalize custody Stellar anchor transaction finalizeCustodyStellarTransaction(transactionId) } else { + lateinit var txHash: String transactionWithRetry { - val txHash = + txHash = paymentClient.send( account, Asset.create(asset.replace("stellar:", "")), transaction.amountOut!!.amount!!, memo, ) - - // 6. Finalize Stellar anchor transaction - finalizeStellarTransaction(transaction, txHash) } + waitForValidTransaction(paymentClient, txHash) + // 6. Finalize Stellar anchor transaction + finalizeStellarTransaction(transaction, txHash) } log.info { "Transaction completed: $transactionId" } @@ -78,6 +79,37 @@ class DepositService(private val cfg: Config, private val paymentClient: Payment } } + suspend fun waitForValidTransaction( + paymentClient: PaymentClient, + txHash: String, + maxAttempts: Int = 10, + timeoutMs: Long? = null, + ): GetTransactionResponse { + suspend fun poll(): GetTransactionResponse { + var attempt = 0 + while (attempt < maxAttempts) { + val txn = paymentClient.getTransaction(txHash) + if (txn?.status == GetTransactionResponse.GetTransactionStatus.SUCCESS) { + return txn + } + if (txn?.status == GetTransactionResponse.GetTransactionStatus.FAILED) { + throw IllegalStateException("Transaction $txHash failed") + } + attempt++ + delay(1000L) + } + throw IllegalStateException( + "Transaction $txHash did not reach a valid status after $maxAttempts attempts" + ) + } + + return if (timeoutMs != null) { + withTimeout(timeoutMs) { poll() } + } else { + poll() + } + } + private suspend fun initiateTransfer(transactionId: String, amount: BigDecimal, asset: String) { val fee = calculateFee(amount) val stellarAsset = "stellar:$asset" @@ -150,24 +182,13 @@ class DepositService(private val cfg: Config, private val paymentClient: Payment // until the RPC returns a success response if (cfg.appSettings.rpcEnabled) { if (transaction.status == "pending_anchor") { - flow { - sep24.rpcAction( - "notify_onchain_funds_sent", - NotifyOnchainFundsSentRequest( - transactionId = transaction.id, - stellarTransactionId = stellarTransactionId, - ), - ) - } - .retryWhen { _, attempt -> - if (attempt < 5) { - delay(5_000) - return@retryWhen true - } else { - return@retryWhen false - } - } - .collect {} + sep24.rpcAction( + "notify_onchain_funds_sent", + NotifyOnchainFundsSentRequest( + transactionId = transaction.id, + stellarTransactionId = stellarTransactionId, + ), + ) } else { log.warn { "Transaction ${transaction.id} is in unexpected status ${transaction.status}, skipping notify_onchain_funds_sent" From 8809a3b5a0aab8a3f1a63c42cfc614980d5ec203 Mon Sep 17 00:00:00 2001 From: Jamie Li Date: Tue, 4 Nov 2025 11:29:22 -0800 Subject: [PATCH 07/11] Fix some warnings --- .../main/kotlin/org/stellar/reference/sep24/DepositService.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kotlin-reference-server/src/main/kotlin/org/stellar/reference/sep24/DepositService.kt b/kotlin-reference-server/src/main/kotlin/org/stellar/reference/sep24/DepositService.kt index c5e6236e6..9e01c141b 100644 --- a/kotlin-reference-server/src/main/kotlin/org/stellar/reference/sep24/DepositService.kt +++ b/kotlin-reference-server/src/main/kotlin/org/stellar/reference/sep24/DepositService.kt @@ -79,7 +79,7 @@ class DepositService(private val cfg: Config, private val paymentClient: Payment } } - suspend fun waitForValidTransaction( + private suspend fun waitForValidTransaction( paymentClient: PaymentClient, txHash: String, maxAttempts: Int = 10, From 35d41499b06d4d50a20e5269d9fa92cdddaf73c4 Mon Sep 17 00:00:00 2001 From: Jamie Li Date: Tue, 4 Nov 2025 11:49:03 -0800 Subject: [PATCH 08/11] Remove custody tests --- .github/workflows/sub_extended_tests.yml | 25 ------------------------ 1 file changed, 25 deletions(-) diff --git a/.github/workflows/sub_extended_tests.yml b/.github/workflows/sub_extended_tests.yml index fb81ba907..f9e1a694c 100644 --- a/.github/workflows/sub_extended_tests.yml +++ b/.github/workflows/sub_extended_tests.yml @@ -68,31 +68,6 @@ jobs: - name: Run Kafka, Postgres, and Sep24 UI with docker compose run: docker compose -f /home/runner/anchor-platform/service-runner/src/main/resources/docker-compose-test.yaml up -d --build - # `custody` Tests - - name: Start `custody` configuration - env: - TEST_PROFILE_NAME: custody - KT_REFERENCE_SERVER_CONFIG: /home/runner/anchor-platform/service-runner/src/main/resources/config/reference-config.yaml - run: | - cd /home/runner/anchor-platform - ./gradlew startServersWithTestProfile & - echo "PID=$!" >> $GITHUB_ENV - - - name: Wait for the sep server to start and get ready - uses: mydea/action-wait-for-api@v1 - with: - url: "http://localhost:8080/.well-known/stellar.toml" - interval: "1" - timeout: "600" - - - name: Run `custody` configuration tests - env: - TEST_PROFILE_NAME: custody - run: | - cd /home/runner/anchor-platform - ./gradlew :service-runner:clean :extended-tests:clean :extended-tests:test --tests org.stellar.anchor.platform.suite.CustodyTestSuite - kill -9 $PID - # `rpc` Tests - name: Start `rpc` configuration env: From cc60882d4fbd6d340ca6f4ea9d44b6f5d75c5e6e Mon Sep 17 00:00:00 2001 From: Jamie Li Date: Tue, 4 Nov 2025 14:28:25 -0800 Subject: [PATCH 09/11] Remove CodeQL from GH workflows --- .github/workflows/on_pull_request.yml | 3 -- .github/workflows/sub_codeql_analysis.yml | 43 ----------------------- 2 files changed, 46 deletions(-) delete mode 100644 .github/workflows/sub_codeql_analysis.yml diff --git a/.github/workflows/on_pull_request.yml b/.github/workflows/on_pull_request.yml index 396e69832..a61310d81 100644 --- a/.github/workflows/on_pull_request.yml +++ b/.github/workflows/on_pull_request.yml @@ -24,9 +24,6 @@ jobs: group: ap-test-job cancel-in-progress: false - codeql_analysis: - uses: ./.github/workflows/sub_codeql_analysis.yml - complete: if: always() needs: [ gradle_build, essential_tests, extended_tests, rust_build, codeql_analysis ] diff --git a/.github/workflows/sub_codeql_analysis.yml b/.github/workflows/sub_codeql_analysis.yml deleted file mode 100644 index 4f8ccc759..000000000 --- a/.github/workflows/sub_codeql_analysis.yml +++ /dev/null @@ -1,43 +0,0 @@ -name: CodeQL Analysis Workflow - -on: - # allows this workflow to be called from another workflow - workflow_dispatch: - workflow_call: - schedule: - - cron: '29 9 * * 5' - -jobs: - analyze: - name: Analyze (${{ matrix.language }}) - runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} - timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 360 }} - permissions: - security-events: write - packages: read - - strategy: - fail-fast: false - matrix: - include: - - language: java-kotlin - build-mode: autobuild - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Set up Gradle properties - run: | - echo "kotlin.daemon.jvmargs=-Xmx2g" >> gradle.properties - - - name: Initialize CodeQL - uses: github/codeql-action/init@v3 - with: - languages: ${{ matrix.language }} - build-mode: ${{ matrix.build-mode }} - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 - with: - category: "/language:${{matrix.language}}" From 8a2fbbfeb5568e7017bc6be06253a71a82980c61 Mon Sep 17 00:00:00 2001 From: Jamie Li Date: Tue, 4 Nov 2025 14:30:54 -0800 Subject: [PATCH 10/11] Fix workflow error --- .github/workflows/on_pull_request.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/on_pull_request.yml b/.github/workflows/on_pull_request.yml index a61310d81..428f7bd1c 100644 --- a/.github/workflows/on_pull_request.yml +++ b/.github/workflows/on_pull_request.yml @@ -26,7 +26,7 @@ jobs: complete: if: always() - needs: [ gradle_build, essential_tests, extended_tests, rust_build, codeql_analysis ] + needs: [ gradle_build, essential_tests, extended_tests, rust_build ] runs-on: ubuntu-22.04 steps: - if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') From 657074a9141e40971f3d9153c9b104f44793733b Mon Sep 17 00:00:00 2001 From: Jamie Li Date: Wed, 5 Nov 2025 09:25:49 -0800 Subject: [PATCH 11/11] Increase version to 4.1.3 --- README.md | 2 +- build.gradle.kts | 2 +- service-runner/src/main/resources/version-info.properties | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index fca17eb21..fa05077d6 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ [![License](https://badgen.net/badge/license/Apache%202/blue?icon=github&label=License)](https://github.com/stellar/anchor-platform/blob/develop/LICENSE) [![GitHub Version](https://badgen.net/github/release/stellar/anchor-platform?icon=github&label=Latest%20release)](https://github.com/stellar/anchor-platform/releases) -[![Docker](https://badgen.net/badge/Latest%20Release/v4.1.1/blue?icon=docker)](https://hub.docker.com/r/stellar/anchor-platform/tags?page=1&name=4.1.1) +[![Docker](https://badgen.net/badge/Latest%20Release/v4.1.3/blue?icon=docker)](https://hub.docker.com/r/stellar/anchor-platform/tags?page=1&name=4.1.3) ![Develop Branch](https://github.com/stellar/anchor-platform/actions/workflows/on_push_to_develop.yml/badge.svg?branch=develop)
diff --git a/build.gradle.kts b/build.gradle.kts index e9dfde637..fb371f011 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -213,7 +213,7 @@ subprojects { allprojects { group = "org.stellar.anchor-sdk" - version = "4.1.1" + version = "4.1.3" tasks.jar { manifest { diff --git a/service-runner/src/main/resources/version-info.properties b/service-runner/src/main/resources/version-info.properties index fd19c0f5d..143a5922b 100644 --- a/service-runner/src/main/resources/version-info.properties +++ b/service-runner/src/main/resources/version-info.properties @@ -1 +1 @@ -version=4.1.1 \ No newline at end of file +version=4.1.3 \ No newline at end of file