From 6d0f746777d5e007ffe6fedbf1b3fe9fad11525a Mon Sep 17 00:00:00 2001 From: Philip Liu <12836897+philipliu@users.noreply.github.com> Date: Mon, 5 Aug 2024 10:41:19 -0400 Subject: [PATCH 01/16] Use docker compose v2 (#1448) --- .github/workflows/sub_essential_tests.yml | 4 ++-- .github/workflows/sub_extended_tests.yml | 4 ++-- .../01 - Contributing/A - Development Environment.md | 12 ++++++------ service-runner/build.gradle.kts | 4 ++-- .../platform/run_profiles/RunDockerDevStackNoWait.kt | 2 +- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/workflows/sub_essential_tests.yml b/.github/workflows/sub_essential_tests.yml index 6b0b1c250f..ca4cf119d9 100644 --- a/.github/workflows/sub_essential_tests.yml +++ b/.github/workflows/sub_essential_tests.yml @@ -50,10 +50,10 @@ jobs: - name: Pull Stellar Validation Tests Docker Image run: docker pull stellar/anchor-tests:latest & - - name: Run Kafka, Postgres, and Sep24 UI with docker-compose + - name: Run Kafka, Postgres, and Sep24 UI with docker compose env: TEST_PROFILE_NAME: default - run: docker-compose -f /home/runner/java-stellar-anchor-sdk/service-runner/src/main/resources/docker-compose-test.yaml up -d --build + run: docker compose -f /home/runner/java-stellar-anchor-sdk/service-runner/src/main/resources/docker-compose-test.yaml up -d --build - name: Run sep server, platform server, observer, and reference servers for integration tests env: diff --git a/.github/workflows/sub_extended_tests.yml b/.github/workflows/sub_extended_tests.yml index 567d935974..90aac095fb 100644 --- a/.github/workflows/sub_extended_tests.yml +++ b/.github/workflows/sub_extended_tests.yml @@ -46,8 +46,8 @@ jobs: ############################################# - - name: Run Kafka, Postgres, and Sep24 UI with docker-compose - run: docker-compose -f /home/runner/java-stellar-anchor-sdk/service-runner/src/main/resources/docker-compose-test.yaml up -d --build + - name: Run Kafka, Postgres, and Sep24 UI with docker compose + run: docker compose -f /home/runner/java-stellar-anchor-sdk/service-runner/src/main/resources/docker-compose-test.yaml up -d --build # `custody` Tests - name: Start `custody` configuration diff --git a/docs/01 - Contributing/A - Development Environment.md b/docs/01 - Contributing/A - Development Environment.md index 9f063db1fc..af3f2f4573 100644 --- a/docs/01 - Contributing/A - Development Environment.md +++ b/docs/01 - Contributing/A - Development Environment.md @@ -11,7 +11,7 @@ * [Clean](#clean) * [Build](#build) * [Running Unit Tests](#running-unit-tests) - * [Running `docker-compose` up for development](#running-docker-compose-up-for-development) + * [Running `docker compose` up for development](#running-docker-compose-up-for-development) * [Starting all servers](#starting-all-servers) * [Set up the Git Hooks](#set-up-the-git-hooks) * [Set up the Development Environment with IntelliJ IDEA](#set-up-the-development-environment-with-intellij-idea) @@ -111,11 +111,11 @@ Run all tests: `./gradlew test` Run subproject tests: `./gradlew :[subproject]:test` -### Running `docker-compose start` for Kafka, Postgres, and SEP24 Reference UI +### Running `docker compose start` for Kafka, Postgres, and SEP24 Reference UI `./gradlew dockerComposeStart` -### Running `docker-compose stop` to shutdown Kafka, Postgres, and SEP24 Reference UI +### Running `docker compose stop` to shutdown Kafka, Postgres, and SEP24 Reference UI `./gradlew dockerComposeStop` @@ -218,7 +218,7 @@ the `service-runner/src/main/resources/profiles` folder. If you would like to debug the Platform server, you can do so by running the -- Make sure `docker` and `docker-compose` is available on your local machine. +- Make sure `docker` is available on your local machine. - Check if there are previous docker containers running on your machine. If there are, please stop and delete them. - Run `Docker - Run Dev Stack - Kafka, Postgres, SEP24 Reference UI` to start the development stack. - Debug `Sep Server: default` to start the SEP server. @@ -229,7 +229,7 @@ If you would like to debug the unit tests or the end-to-end tests, there are two #### Option 1: Run the servers from IntelliJ -- Make sure `docker` and `docker-compose` is available on your local machine. +- Make sure `docker` is available on your local machine. - Check if there are previous docker containers running on your machine. If there are, please stop and delete them. - Run `Docker - Run Dev Stack - Kafka, Postgres, SEP24 Reference UI` to start the development stack. - Run `Test Profile: default` to run the servers with the `default` profile. @@ -237,7 +237,7 @@ If you would like to debug the unit tests or the end-to-end tests, there are two ### Option 2: Run the servers and tests from Gradle -- Make sure `docker` and `docker-compose` is available on your local machine. +- Make sure `docker` is available on your local machine. - Check if there are previous docker containers running on your machine. If there are, please stop and delete them. - Navigate to the directory to the project folder - `./gradlew dockerComposeStart` to start the development stack. diff --git a/service-runner/build.gradle.kts b/service-runner/build.gradle.kts index fae2ee354f..1a6df10f2b 100644 --- a/service-runner/build.gradle.kts +++ b/service-runner/build.gradle.kts @@ -61,9 +61,9 @@ tasks.register("startServersWithTestProfile") { mainClass.set("org.stellar.anchor.platform.run_profiles.RunTestProfile") } -/** Run docker-compose up to start Postgres, Kafka, etc. */ +/** Run docker compose up to start Postgres, Kafka, etc. */ tasks.register("dockerComposeStart") { - println("Running docker-compose to start Postgres, Kafka ,etc.") + println("Running docker compose to start Postgres, Kafka ,etc.") group = "application" classpath = sourceSets["main"].runtimeClasspath mainClass.set("org.stellar.anchor.platform.run_profiles.RunDockerDevStackNoWait") diff --git a/service-runner/src/main/kotlin/org/stellar/anchor/platform/run_profiles/RunDockerDevStackNoWait.kt b/service-runner/src/main/kotlin/org/stellar/anchor/platform/run_profiles/RunDockerDevStackNoWait.kt index 38f6d60a34..b8dea29ce0 100644 --- a/service-runner/src/main/kotlin/org/stellar/anchor/platform/run_profiles/RunDockerDevStackNoWait.kt +++ b/service-runner/src/main/kotlin/org/stellar/anchor/platform/run_profiles/RunDockerDevStackNoWait.kt @@ -8,7 +8,7 @@ import org.stellar.anchor.platform.* fun main() = runBlocking { testProfileExecutor = TestProfileExecutor(TestConfig()) // The "registerShutdownHook(testProfileExecutor)" is commented out to avoid shutting down the - // docker-compose stack when the JVM is shutdown. + // docker compose stack when the JVM is shutdown. testProfileExecutor.start(true) { it.env[RUN_DOCKER] = "true" it.env[RUN_ALL_SERVERS] = "false" From 3b35f291fe01e7aa92f2dc2b97e060415153bae4 Mon Sep 17 00:00:00 2001 From: Philip Liu <12836897+philipliu@users.noreply.github.com> Date: Tue, 6 Aug 2024 13:46:12 -0400 Subject: [PATCH 02/16] Fix RequestOnchainFunds incorrect hash memo (#1447) ### Description This sets the `memo` value to the request `memo` value. ### Context `memo.toString()` returns the hex value of the memo but the `memo` in the Payment object is in Base64 format, so the observer will never find the transaction in the database. ### Testing - `./gradlew test` ### Documentation N/A ### Known limitations N/A --- .../rpc/RequestOnchainFundsHandler.java | 4 +- .../rpc/RequestOnchainFundsHandlerTest.kt | 85 +++++++++---------- 2 files changed, 44 insertions(+), 45 deletions(-) diff --git a/platform/src/main/java/org/stellar/anchor/platform/rpc/RequestOnchainFundsHandler.java b/platform/src/main/java/org/stellar/anchor/platform/rpc/RequestOnchainFundsHandler.java index f014279c9c..c5568406f2 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/rpc/RequestOnchainFundsHandler.java +++ b/platform/src/main/java/org/stellar/anchor/platform/rpc/RequestOnchainFundsHandler.java @@ -252,7 +252,7 @@ protected void updateTransactionWithRpcRequest( if (sep6DepositInfoGenerator instanceof Sep6DepositInfoNoneGenerator) { Memo memo = makeMemo(request.getMemo(), request.getMemoType()); if (memo != null) { - txn6.setMemo(memo.toString()); + txn6.setMemo(request.getMemo()); txn6.setMemoType(memoTypeString(memoType(memo))); } txn6.setWithdrawAnchorAccount(request.getDestinationAccount()); @@ -286,7 +286,7 @@ protected void updateTransactionWithRpcRequest( if (sep24DepositInfoGenerator instanceof Sep24DepositInfoNoneGenerator) { Memo memo = makeMemo(request.getMemo(), request.getMemoType()); if (memo != null) { - txn24.setMemo(memo.toString()); + txn24.setMemo(request.getMemo()); txn24.setMemoType(memoTypeString(memoType(memo))); } txn24.setWithdrawAnchorAccount(request.getDestinationAccount()); diff --git a/platform/src/test/kotlin/org/stellar/anchor/platform/rpc/RequestOnchainFundsHandlerTest.kt b/platform/src/test/kotlin/org/stellar/anchor/platform/rpc/RequestOnchainFundsHandlerTest.kt index 29afb38d27..68c7d56d55 100644 --- a/platform/src/test/kotlin/org/stellar/anchor/platform/rpc/RequestOnchainFundsHandlerTest.kt +++ b/platform/src/test/kotlin/org/stellar/anchor/platform/rpc/RequestOnchainFundsHandlerTest.kt @@ -65,7 +65,6 @@ class RequestOnchainFundsHandlerTest { private const val STELLAR_USDC_ISSUER = "GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN" private const val TEXT_MEMO = "testMemo" - private const val TEXT_MEMO_2 = "testMemo2" private const val HASH_MEMO = "YWYwOTk2M2QtNzU3Mi00NGQ4LWE5MDktMmY2YzMzNTY=" private const val TEXT_MEMO_TYPE = "text" private const val HASH_MEMO_TYPE = "hash" @@ -558,8 +557,8 @@ class RequestOnchainFundsHandlerTest { .amountOut(AmountAssetRequest("0.9", FIAT_USD)) .feeDetails(Amount("0.1", STELLAR_USDC).toRate()) .amountExpected(AmountRequest("1")) - .memo(TEXT_MEMO) - .memoType(TEXT_MEMO_TYPE) + .memo(HASH_MEMO) + .memoType(HASH_MEMO_TYPE) .destinationAccount(DESTINATION_ACCOUNT) .build() val txn24 = JdbcSep24Transaction() @@ -603,8 +602,8 @@ class RequestOnchainFundsHandlerTest { expectedSep24Txn.amountFee = "0.1" expectedSep24Txn.amountFeeAsset = STELLAR_USDC expectedSep24Txn.amountExpected = "1" - expectedSep24Txn.memo = TEXT_MEMO - expectedSep24Txn.memoType = TEXT_MEMO_TYPE + expectedSep24Txn.memo = HASH_MEMO + expectedSep24Txn.memoType = HASH_MEMO_TYPE expectedSep24Txn.toAccount = DESTINATION_ACCOUNT expectedSep24Txn.withdrawAnchorAccount = DESTINATION_ACCOUNT @@ -624,8 +623,8 @@ class RequestOnchainFundsHandlerTest { expectedResponse.feeDetails = Amount("0.1", STELLAR_USDC).toRate() expectedResponse.amountExpected = Amount("1", STELLAR_USDC) expectedResponse.updatedAt = sep24TxnCapture.captured.updatedAt - expectedResponse.memo = TEXT_MEMO - expectedResponse.memoType = TEXT_MEMO_TYPE + expectedResponse.memo = HASH_MEMO + expectedResponse.memoType = HASH_MEMO_TYPE expectedResponse.destinationAccount = DESTINATION_ACCOUNT JSONAssert.assertEquals( @@ -685,7 +684,7 @@ class RequestOnchainFundsHandlerTest { txn24.requestAssetIssuer = STELLAR_USDC_ISSUER val sep24TxnCapture = slot() val anchorEventCapture = slot() - val depositInfo = SepDepositInfo(DESTINATION_ACCOUNT_2, TEXT_MEMO_2, TEXT_MEMO_TYPE) + val depositInfo = SepDepositInfo(DESTINATION_ACCOUNT_2, TEXT_MEMO, TEXT_MEMO_TYPE) every { txn6Store.findByTransactionId(any()) } returns null every { txn24Store.findByTransactionId(TX_ID) } returns txn24 @@ -721,7 +720,7 @@ class RequestOnchainFundsHandlerTest { expectedSep24Txn.amountFee = "0.1" expectedSep24Txn.amountFeeAsset = STELLAR_USDC expectedSep24Txn.amountExpected = "1" - expectedSep24Txn.memo = TEXT_MEMO_2 + expectedSep24Txn.memo = TEXT_MEMO expectedSep24Txn.memoType = TEXT_MEMO_TYPE expectedSep24Txn.toAccount = DESTINATION_ACCOUNT_2 expectedSep24Txn.withdrawAnchorAccount = DESTINATION_ACCOUNT_2 @@ -742,7 +741,7 @@ class RequestOnchainFundsHandlerTest { expectedResponse.feeDetails = Amount("0.1", STELLAR_USDC).toRate() expectedResponse.amountExpected = Amount("1", STELLAR_USDC) expectedResponse.updatedAt = sep24TxnCapture.captured.updatedAt - expectedResponse.memo = TEXT_MEMO_2 + expectedResponse.memo = TEXT_MEMO expectedResponse.memoType = TEXT_MEMO_TYPE expectedResponse.destinationAccount = DESTINATION_ACCOUNT_2 @@ -888,8 +887,8 @@ class RequestOnchainFundsHandlerTest { .amountIn(AmountAssetRequest("1", STELLAR_USDC)) .amountOut(AmountAssetRequest("0.9", FIAT_USD)) .amountFee(AmountAssetRequest("0.1", STELLAR_USDC)) - .memo(TEXT_MEMO) - .memoType(TEXT_MEMO_TYPE) + .memo(HASH_MEMO) + .memoType(HASH_MEMO_TYPE) .destinationAccount(DESTINATION_ACCOUNT) .userActionRequiredBy(actionRequiredBy) .build() @@ -933,8 +932,8 @@ class RequestOnchainFundsHandlerTest { expectedSep24Txn.amountFee = "0.1" expectedSep24Txn.amountFeeAsset = STELLAR_USDC expectedSep24Txn.amountExpected = "1" - expectedSep24Txn.memo = TEXT_MEMO - expectedSep24Txn.memoType = TEXT_MEMO_TYPE + expectedSep24Txn.memo = HASH_MEMO + expectedSep24Txn.memoType = HASH_MEMO_TYPE expectedSep24Txn.toAccount = DESTINATION_ACCOUNT expectedSep24Txn.withdrawAnchorAccount = DESTINATION_ACCOUNT expectedSep24Txn.userActionRequiredBy = actionRequiredBy @@ -955,8 +954,8 @@ class RequestOnchainFundsHandlerTest { expectedResponse.feeDetails = Amount("0.1", STELLAR_USDC).toRate() expectedResponse.amountExpected = Amount("1", STELLAR_USDC) expectedResponse.updatedAt = sep24TxnCapture.captured.updatedAt - expectedResponse.memo = TEXT_MEMO - expectedResponse.memoType = TEXT_MEMO_TYPE + expectedResponse.memo = HASH_MEMO + expectedResponse.memoType = HASH_MEMO_TYPE expectedResponse.destinationAccount = DESTINATION_ACCOUNT expectedResponse.userActionRequiredBy = actionRequiredBy @@ -989,8 +988,8 @@ class RequestOnchainFundsHandlerTest { val request = RequestOnchainFundsRequest.builder() .transactionId(TX_ID) - .memo(TEXT_MEMO) - .memoType(TEXT_MEMO_TYPE) + .memo(HASH_MEMO) + .memoType(HASH_MEMO_TYPE) .destinationAccount(DESTINATION_ACCOUNT) .build() val txn24 = JdbcSep24Transaction() @@ -1040,8 +1039,8 @@ class RequestOnchainFundsHandlerTest { expectedSep24Txn.amountFee = "0.1" expectedSep24Txn.amountFeeAsset = STELLAR_USDC expectedSep24Txn.amountExpected = "1" - expectedSep24Txn.memo = TEXT_MEMO - expectedSep24Txn.memoType = TEXT_MEMO_TYPE + expectedSep24Txn.memo = HASH_MEMO + expectedSep24Txn.memoType = HASH_MEMO_TYPE expectedSep24Txn.toAccount = DESTINATION_ACCOUNT expectedSep24Txn.withdrawAnchorAccount = DESTINATION_ACCOUNT @@ -1061,8 +1060,8 @@ class RequestOnchainFundsHandlerTest { expectedResponse.feeDetails = Amount("0.1", STELLAR_USDC).toRate() expectedResponse.amountExpected = Amount("1", STELLAR_USDC) expectedResponse.updatedAt = sep24TxnCapture.captured.updatedAt - expectedResponse.memo = TEXT_MEMO - expectedResponse.memoType = TEXT_MEMO_TYPE + expectedResponse.memo = HASH_MEMO + expectedResponse.memoType = HASH_MEMO_TYPE expectedResponse.destinationAccount = DESTINATION_ACCOUNT JSONAssert.assertEquals( @@ -1223,8 +1222,8 @@ class RequestOnchainFundsHandlerTest { .amountOut(AmountAssetRequest("0.9", FIAT_USD)) .feeDetails(Amount("0.1", STELLAR_USDC).toRate()) .amountExpected(AmountRequest("1")) - .memo(TEXT_MEMO) - .memoType(TEXT_MEMO_TYPE) + .memo(HASH_MEMO) + .memoType(HASH_MEMO_TYPE) .destinationAccount(DESTINATION_ACCOUNT) .build() val txn6 = JdbcSep6Transaction() @@ -1269,8 +1268,8 @@ class RequestOnchainFundsHandlerTest { expectedSep6Txn.amountFee = "0.1" expectedSep6Txn.amountFeeAsset = STELLAR_USDC expectedSep6Txn.amountExpected = "1" - expectedSep6Txn.memo = TEXT_MEMO - expectedSep6Txn.memoType = TEXT_MEMO_TYPE + expectedSep6Txn.memo = HASH_MEMO + expectedSep6Txn.memoType = HASH_MEMO_TYPE expectedSep6Txn.withdrawAnchorAccount = DESTINATION_ACCOUNT JSONAssert.assertEquals( @@ -1295,8 +1294,8 @@ class RequestOnchainFundsHandlerTest { expectedResponse.feeDetails = Amount("0.1", STELLAR_USDC).toRate() expectedResponse.amountExpected = Amount("1", STELLAR_USDC) expectedResponse.updatedAt = sep6TxnCapture.captured.updatedAt - expectedResponse.memo = TEXT_MEMO - expectedResponse.memoType = TEXT_MEMO_TYPE + expectedResponse.memo = HASH_MEMO + expectedResponse.memoType = HASH_MEMO_TYPE expectedResponse.customers = Customers(StellarId(null, null, null), StellarId(null, null, null)) JSONAssert.assertEquals( @@ -1357,7 +1356,7 @@ class RequestOnchainFundsHandlerTest { txn6.requestAssetIssuer = STELLAR_USDC_ISSUER val sep6TxnCapture = slot() val anchorEventCapture = slot() - val depositInfo = SepDepositInfo(DESTINATION_ACCOUNT_2, TEXT_MEMO_2, TEXT_MEMO_TYPE) + val depositInfo = SepDepositInfo(DESTINATION_ACCOUNT_2, TEXT_MEMO, TEXT_MEMO_TYPE) every { txn6Store.findByTransactionId(TX_ID) } returns txn6 every { txn24Store.findByTransactionId(any()) } returns null @@ -1392,7 +1391,7 @@ class RequestOnchainFundsHandlerTest { expectedSep6Txn.amountFee = "0.1" expectedSep6Txn.amountFeeAsset = STELLAR_USDC expectedSep6Txn.amountExpected = "1" - expectedSep6Txn.memo = TEXT_MEMO_2 + expectedSep6Txn.memo = TEXT_MEMO expectedSep6Txn.memoType = TEXT_MEMO_TYPE expectedSep6Txn.withdrawAnchorAccount = DESTINATION_ACCOUNT_2 @@ -1412,7 +1411,7 @@ class RequestOnchainFundsHandlerTest { expectedResponse.feeDetails = Amount("0.1", STELLAR_USDC).toRate() expectedResponse.amountExpected = Amount("1", STELLAR_USDC) expectedResponse.updatedAt = sep6TxnCapture.captured.updatedAt - expectedResponse.memo = TEXT_MEMO_2 + expectedResponse.memo = TEXT_MEMO expectedResponse.memoType = TEXT_MEMO_TYPE expectedResponse.customers = Customers(StellarId(null, null, null), StellarId(null, null, null)) @@ -1449,8 +1448,8 @@ class RequestOnchainFundsHandlerTest { .amountIn(AmountAssetRequest("1", STELLAR_USDC)) .amountOut(AmountAssetRequest("0.9", FIAT_USD)) .amountFee(AmountAssetRequest("0.1", STELLAR_USDC)) - .memo(TEXT_MEMO) - .memoType(TEXT_MEMO_TYPE) + .memo(HASH_MEMO) + .memoType(HASH_MEMO_TYPE) .destinationAccount(DESTINATION_ACCOUNT) .build() val txn6 = JdbcSep6Transaction() @@ -1490,8 +1489,8 @@ class RequestOnchainFundsHandlerTest { expectedSep6Txn.amountFee = "0.1" expectedSep6Txn.amountFeeAsset = STELLAR_USDC expectedSep6Txn.amountExpected = "1" - expectedSep6Txn.memo = TEXT_MEMO - expectedSep6Txn.memoType = TEXT_MEMO_TYPE + expectedSep6Txn.memo = HASH_MEMO + expectedSep6Txn.memoType = HASH_MEMO_TYPE expectedSep6Txn.withdrawAnchorAccount = DESTINATION_ACCOUNT JSONAssert.assertEquals( @@ -1510,8 +1509,8 @@ class RequestOnchainFundsHandlerTest { expectedResponse.feeDetails = Amount("0.1", STELLAR_USDC).toRate() expectedResponse.amountExpected = Amount("1", STELLAR_USDC) expectedResponse.updatedAt = sep6TxnCapture.captured.updatedAt - expectedResponse.memo = TEXT_MEMO - expectedResponse.memoType = TEXT_MEMO_TYPE + expectedResponse.memo = HASH_MEMO + expectedResponse.memoType = HASH_MEMO_TYPE expectedResponse.customers = Customers(StellarId(null, null, null), StellarId(null, null, null)) JSONAssert.assertEquals( @@ -1544,8 +1543,8 @@ class RequestOnchainFundsHandlerTest { val request = RequestOnchainFundsRequest.builder() .transactionId(TX_ID) - .memo(TEXT_MEMO) - .memoType(TEXT_MEMO_TYPE) + .memo(HASH_MEMO) + .memoType(HASH_MEMO_TYPE) .destinationAccount(DESTINATION_ACCOUNT) .build() val txn6 = JdbcSep6Transaction() @@ -1593,8 +1592,8 @@ class RequestOnchainFundsHandlerTest { expectedSep6Txn.amountFee = "0.1" expectedSep6Txn.amountFeeAsset = STELLAR_USDC expectedSep6Txn.amountExpected = "1" - expectedSep6Txn.memo = TEXT_MEMO - expectedSep6Txn.memoType = TEXT_MEMO_TYPE + expectedSep6Txn.memo = HASH_MEMO + expectedSep6Txn.memoType = HASH_MEMO_TYPE expectedSep6Txn.withdrawAnchorAccount = DESTINATION_ACCOUNT JSONAssert.assertEquals( @@ -1613,8 +1612,8 @@ class RequestOnchainFundsHandlerTest { expectedResponse.feeDetails = expectedResponse.amountFee.toRate() expectedResponse.amountExpected = Amount("1", STELLAR_USDC) expectedResponse.updatedAt = sep6TxnCapture.captured.updatedAt - expectedResponse.memo = TEXT_MEMO - expectedResponse.memoType = TEXT_MEMO_TYPE + expectedResponse.memo = HASH_MEMO + expectedResponse.memoType = HASH_MEMO_TYPE expectedResponse.customers = Customers(StellarId(null, null, null), StellarId(null, null, null)) JSONAssert.assertEquals( From 10ec5635d1774c53bd6e313c4566c207f0906d14 Mon Sep 17 00:00:00 2001 From: Philip Liu <12836897+philipliu@users.noreply.github.com> Date: Tue, 6 Aug 2024 17:40:57 -0400 Subject: [PATCH 03/16] [ANCHOR-748] Add quote_id to GET /transaction responses (#1450) --- .../anchor/api/sep/sep24/TransactionResponse.java | 3 +++ .../api/sep/sep31/Sep31GetTransactionResponse.java | 3 +++ .../main/java/org/stellar/anchor/sep24/Sep24Helper.java | 1 + .../java/org/stellar/anchor/sep31/Sep31Transaction.java | 1 + .../org/stellar/anchor/sep6/Sep6TransactionUtils.java | 1 + .../kotlin/org/stellar/anchor/sep24/Sep24ServiceTest.kt | 9 ++++----- .../org/stellar/anchor/sep24/Sep24ServiceTestHelper.kt | 3 +++ .../kotlin/org/stellar/anchor/sep31/Sep31ServiceTest.kt | 1 + .../org/stellar/anchor/sep31/Sep31TransactionTest.kt | 1 + .../org/stellar/anchor/sep6/Sep6TransactionUtilsTest.kt | 1 + 10 files changed, 19 insertions(+), 5 deletions(-) diff --git a/api-schema/src/main/java/org/stellar/anchor/api/sep/sep24/TransactionResponse.java b/api-schema/src/main/java/org/stellar/anchor/api/sep/sep24/TransactionResponse.java index 5db4814578..6d207d57c7 100644 --- a/api-schema/src/main/java/org/stellar/anchor/api/sep/sep24/TransactionResponse.java +++ b/api-schema/src/main/java/org/stellar/anchor/api/sep/sep24/TransactionResponse.java @@ -43,6 +43,9 @@ public class TransactionResponse { @SerializedName("fee_details") FeeDetails feeDetails; + @SerializedName("quote_id") + String quoteId; + @SerializedName("started_at") Instant startedAt; diff --git a/api-schema/src/main/java/org/stellar/anchor/api/sep/sep31/Sep31GetTransactionResponse.java b/api-schema/src/main/java/org/stellar/anchor/api/sep/sep31/Sep31GetTransactionResponse.java index e2c19b8830..4c333a5f9a 100644 --- a/api-schema/src/main/java/org/stellar/anchor/api/sep/sep31/Sep31GetTransactionResponse.java +++ b/api-schema/src/main/java/org/stellar/anchor/api/sep/sep31/Sep31GetTransactionResponse.java @@ -51,6 +51,9 @@ public static class TransactionResponse { @SerializedName("fee_details") FeeDetails feeDetails; + @SerializedName("quote_id") + String quoteId; + @SerializedName("amount_fee_asset") String amountFeeAsset; diff --git a/core/src/main/java/org/stellar/anchor/sep24/Sep24Helper.java b/core/src/main/java/org/stellar/anchor/sep24/Sep24Helper.java index 4fc821bf7f..353cf4d2d1 100644 --- a/core/src/main/java/org/stellar/anchor/sep24/Sep24Helper.java +++ b/core/src/main/java/org/stellar/anchor/sep24/Sep24Helper.java @@ -25,6 +25,7 @@ static void setSharedTransactionResponseFields(TransactionResponse txnR, Sep24Tr if (txn.getToAccount() != null) txnR.setTo(txn.getToAccount()); if (txn.getStartedAt() != null) txnR.setStartedAt(txn.getStartedAt()); if (txn.getCompletedAt() != null) txnR.setCompletedAt(txn.getCompletedAt()); + if (txn.getQuoteId() != null) txnR.setQuoteId(txn.getQuoteId()); if (txn.getUserActionRequiredBy() != null) txnR.setUserActionRequiredBy(txn.getUserActionRequiredBy()); } diff --git a/core/src/main/java/org/stellar/anchor/sep31/Sep31Transaction.java b/core/src/main/java/org/stellar/anchor/sep31/Sep31Transaction.java index e16bd10e9a..24b9ee1e65 100644 --- a/core/src/main/java/org/stellar/anchor/sep31/Sep31Transaction.java +++ b/core/src/main/java/org/stellar/anchor/sep31/Sep31Transaction.java @@ -146,6 +146,7 @@ default Sep31GetTransactionResponse toSep31GetTransactionResponse() { .amountFee(getAmountFee()) .amountFeeAsset(getAmountFeeAsset()) .feeDetails(getFeeDetails()) + .quoteId(getQuoteId()) .stellarAccountId(getStellarAccountId()) .stellarMemo(getStellarMemo()) .stellarMemoType(getStellarMemoType()) diff --git a/core/src/main/java/org/stellar/anchor/sep6/Sep6TransactionUtils.java b/core/src/main/java/org/stellar/anchor/sep6/Sep6TransactionUtils.java index feba9aac54..c92f1bdd8e 100644 --- a/core/src/main/java/org/stellar/anchor/sep6/Sep6TransactionUtils.java +++ b/core/src/main/java/org/stellar/anchor/sep6/Sep6TransactionUtils.java @@ -52,6 +52,7 @@ public static Sep6TransactionResponse fromTxn( .amountFee(txn.getAmountFee()) .amountFeeAsset(txn.getAmountFeeAsset()) .feeDetails(txn.getFeeDetails()) + .quoteId(txn.getQuoteId()) .startedAt(txn.getStartedAt().toString()) .updatedAt(txn.getUpdatedAt().toString()) .completedAt(txn.getCompletedAt() != null ? txn.getCompletedAt().toString() : null) diff --git a/core/src/test/kotlin/org/stellar/anchor/sep24/Sep24ServiceTest.kt b/core/src/test/kotlin/org/stellar/anchor/sep24/Sep24ServiceTest.kt index fc38bcf263..40140ce6fd 100644 --- a/core/src/test/kotlin/org/stellar/anchor/sep24/Sep24ServiceTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/sep24/Sep24ServiceTest.kt @@ -25,15 +25,12 @@ import org.stellar.anchor.TestConstants.Companion.TEST_CLIENT_NAME import org.stellar.anchor.TestConstants.Companion.TEST_HOME_DOMAIN import org.stellar.anchor.TestConstants.Companion.TEST_MEMO import org.stellar.anchor.TestConstants.Companion.TEST_OFFCHAIN_ASSET +import org.stellar.anchor.TestConstants.Companion.TEST_QUOTE_ID import org.stellar.anchor.TestConstants.Companion.TEST_TRANSACTION_ID_0 import org.stellar.anchor.TestConstants.Companion.TEST_TRANSACTION_ID_1 import org.stellar.anchor.TestHelper import org.stellar.anchor.api.callback.FeeIntegration -import org.stellar.anchor.api.exception.BadRequestException -import org.stellar.anchor.api.exception.SepException -import org.stellar.anchor.api.exception.SepNotAuthorizedException -import org.stellar.anchor.api.exception.SepNotFoundException -import org.stellar.anchor.api.exception.SepValidationException +import org.stellar.anchor.api.exception.* import org.stellar.anchor.api.sep.sep24.GetTransactionRequest import org.stellar.anchor.api.sep.sep24.GetTransactionsRequest import org.stellar.anchor.asset.AssetService @@ -563,12 +560,14 @@ internal class Sep24ServiceTest { assertEquals(response.transactions[0].kind, kind) assertEquals(response.transactions[0].startedAt, TEST_STARTED_AT) assertEquals(response.transactions[0].completedAt, TEST_COMPLETED_AT) + assertEquals(response.transactions[0].quoteId, TEST_QUOTE_ID) assertEquals(response.transactions[1].id, TEST_TRANSACTION_ID_1) assertEquals(response.transactions[1].status, "completed") assertEquals(response.transactions[1].kind, kind) assertEquals(response.transactions[1].startedAt, TEST_STARTED_AT) assertEquals(response.transactions[1].completedAt, TEST_COMPLETED_AT) + assertEquals(response.transactions[1].quoteId, TEST_QUOTE_ID) } @ParameterizedTest diff --git a/core/src/test/kotlin/org/stellar/anchor/sep24/Sep24ServiceTestHelper.kt b/core/src/test/kotlin/org/stellar/anchor/sep24/Sep24ServiceTestHelper.kt index 891ae752bd..bad7f66c3d 100644 --- a/core/src/test/kotlin/org/stellar/anchor/sep24/Sep24ServiceTestHelper.kt +++ b/core/src/test/kotlin/org/stellar/anchor/sep24/Sep24ServiceTestHelper.kt @@ -6,6 +6,7 @@ import org.stellar.anchor.TestConstants.Companion.TEST_AMOUNT import org.stellar.anchor.TestConstants.Companion.TEST_ASSET import org.stellar.anchor.TestConstants.Companion.TEST_ASSET_ISSUER_ACCOUNT_ID import org.stellar.anchor.TestConstants.Companion.TEST_OFFCHAIN_ASSET +import org.stellar.anchor.TestConstants.Companion.TEST_QUOTE_ID fun createTestTransactionRequest(quoteID: String? = null): MutableMap { val request = @@ -67,6 +68,7 @@ fun createTestTransactions(kind: String): MutableList { txn.protocol = "sep24" txn.amountIn = "321.4" txn.amountOut = "321.4" + txn.quoteId = TEST_QUOTE_ID txns.add(txn) txn = PojoSep24Transaction() @@ -85,6 +87,7 @@ fun createTestTransactions(kind: String): MutableList { txn.protocol = "sep24" txn.amountIn = "456.7" txn.amountOut = "456.7" + txn.quoteId = TEST_QUOTE_ID txns.add(txn) return txns diff --git a/core/src/test/kotlin/org/stellar/anchor/sep31/Sep31ServiceTest.kt b/core/src/test/kotlin/org/stellar/anchor/sep31/Sep31ServiceTest.kt index aaaa27f502..136b3932f4 100644 --- a/core/src/test/kotlin/org/stellar/anchor/sep31/Sep31ServiceTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/sep31/Sep31ServiceTest.kt @@ -495,6 +495,7 @@ class Sep31ServiceTest { .amountFee("2") .amountFeeAsset("USDC") .feeDetails(FeeDetails("2", "USDC")) + .quoteId("quote_id") .stellarAccountId("GAYR3FVW2PCXTNHHWHEAFOCKZQV4PEY2ZKGIKB47EKPJ3GSBYA52XJBY") .stellarMemo("123456") .stellarMemoType("text") diff --git a/core/src/test/kotlin/org/stellar/anchor/sep31/Sep31TransactionTest.kt b/core/src/test/kotlin/org/stellar/anchor/sep31/Sep31TransactionTest.kt index d2a3dc0ccd..3c86490dac 100644 --- a/core/src/test/kotlin/org/stellar/anchor/sep31/Sep31TransactionTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/sep31/Sep31TransactionTest.kt @@ -158,6 +158,7 @@ class Sep31TransactionTest { .amountFee("2.0000") .amountFeeAsset(fiatUSD) .feeDetails(FeeDetails("2.0000", fiatUSD)) + .quoteId("quote-id") .stellarAccountId(TEST_ACCOUNT) .stellarMemo(TEST_MEMO) .stellarMemoType("text") diff --git a/core/src/test/kotlin/org/stellar/anchor/sep6/Sep6TransactionUtilsTest.kt b/core/src/test/kotlin/org/stellar/anchor/sep6/Sep6TransactionUtilsTest.kt index a8c7288081..24c7b8a772 100644 --- a/core/src/test/kotlin/org/stellar/anchor/sep6/Sep6TransactionUtilsTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/sep6/Sep6TransactionUtilsTest.kt @@ -49,6 +49,7 @@ class Sep6TransactionUtilsTest { "total": "1.00", "asset": "USD" }, + "quote_id": "quote-id", "from": "1234", "to": "$TEST_ASSET_ISSUER_ACCOUNT_ID", "deposit_memo_type": "text", From 01c767e02e842c31aadbddce3a99c506fe324ddb Mon Sep 17 00:00:00 2001 From: Philip Liu <12836897+philipliu@users.noreply.github.com> Date: Mon, 12 Aug 2024 17:06:20 -0400 Subject: [PATCH 04/16] [ANCHOR-752] Remove amount_out requirement in RequestOnchainFunds (#1451) --- .../rpc/RequestOnchainFundsHandler.java | 26 +++- .../rpc/RequestOnchainFundsHandlerTest.kt | 129 +++++++++++++++++- 2 files changed, 144 insertions(+), 11 deletions(-) diff --git a/platform/src/main/java/org/stellar/anchor/platform/rpc/RequestOnchainFundsHandler.java b/platform/src/main/java/org/stellar/anchor/platform/rpc/RequestOnchainFundsHandler.java index c5568406f2..eebb0ce58f 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/rpc/RequestOnchainFundsHandler.java +++ b/platform/src/main/java/org/stellar/anchor/platform/rpc/RequestOnchainFundsHandler.java @@ -14,6 +14,7 @@ import java.util.Collections; import java.util.HashSet; import java.util.Set; +import org.apache.commons.lang3.StringUtils; import org.stellar.anchor.api.exception.AnchorException; import org.stellar.anchor.api.exception.BadRequestException; import org.stellar.anchor.api.exception.SepException; @@ -87,15 +88,13 @@ protected void validate(JdbcSepTransaction txn, RequestOnchainFundsRequest reque // If none of the accepted combinations of input parameters satisfies -> throw an exception if (!((request.getAmountIn() == null - && request.getAmountOut() == null && request.getAmountFee() == null && request.getFeeDetails() == null && request.getAmountExpected() == null) || (request.getAmountIn() != null - && request.getAmountOut() != null && (request.getAmountFee() != null || request.getFeeDetails() != null)))) { throw new InvalidParamsException( - "All or none of the amount_in, amount_out, and (fee_details or amount_fee) should be set"); + "All (amount_out is optional) or none of the amount_in, amount_out, and (fee_details or amount_fee) should be set"); } // In case 2nd predicate in previous IF statement was TRUE @@ -141,7 +140,26 @@ protected void validate(JdbcSepTransaction txn, RequestOnchainFundsRequest reque throw new InvalidParamsException("amount_in is required"); } if (request.getAmountOut() == null && txn.getAmountOut() == null) { - throw new InvalidParamsException("amount_out is required"); + if (SEP_6 == Sep.from(txn.getProtocol())) { + JdbcSep6Transaction txn6 = (JdbcSep6Transaction) txn; + if (txn6.getQuoteId() != null) { + throw new InvalidParamsException( + "amount_out is required for transactions with firm quotes"); + } + if (StringUtils.equals(txn6.getAmountInAsset(), txn6.getAmountOutAsset())) { + throw new InvalidParamsException("amount_out is required for non-exchange transactions"); + } + } + if (SEP_24 == Sep.from(txn.getProtocol())) { + JdbcSep24Transaction txn24 = (JdbcSep24Transaction) txn; + if (txn24.getQuoteId() != null) { + throw new InvalidParamsException( + "amount_out is required for transactions with firm quotes"); + } + if (StringUtils.equals(txn24.getAmountInAsset(), txn24.getAmountOutAsset())) { + throw new InvalidParamsException("amount_out is required for non-exchange transactions"); + } + } } if (request.getAmountFee() == null && request.getFeeDetails() == null diff --git a/platform/src/test/kotlin/org/stellar/anchor/platform/rpc/RequestOnchainFundsHandlerTest.kt b/platform/src/test/kotlin/org/stellar/anchor/platform/rpc/RequestOnchainFundsHandlerTest.kt index 68c7d56d55..065c8266af 100644 --- a/platform/src/test/kotlin/org/stellar/anchor/platform/rpc/RequestOnchainFundsHandlerTest.kt +++ b/platform/src/test/kotlin/org/stellar/anchor/platform/rpc/RequestOnchainFundsHandlerTest.kt @@ -11,6 +11,7 @@ import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.CsvSource +import org.junit.jupiter.params.provider.ValueSource import org.skyscreamer.jsonassert.JSONAssert import org.skyscreamer.jsonassert.JSONCompareMode import org.stellar.anchor.api.event.AnchorEvent @@ -27,10 +28,7 @@ import org.stellar.anchor.api.rpc.method.AmountAssetRequest import org.stellar.anchor.api.rpc.method.AmountRequest import org.stellar.anchor.api.rpc.method.RequestOnchainFundsRequest import org.stellar.anchor.api.sep.SepTransactionStatus.* -import org.stellar.anchor.api.shared.Amount -import org.stellar.anchor.api.shared.Customers -import org.stellar.anchor.api.shared.SepDepositInfo -import org.stellar.anchor.api.shared.StellarId +import org.stellar.anchor.api.shared.* import org.stellar.anchor.asset.AssetService import org.stellar.anchor.asset.DefaultAssetService import org.stellar.anchor.config.CustodyConfig @@ -194,13 +192,14 @@ class RequestOnchainFundsHandlerTest { } @Test - fun test_handle_withoutAmounts_amount_out_absent() { + fun test_handle_withoutAmounts_fee_absent() { val request = RequestOnchainFundsRequest.builder().transactionId(TX_ID).build() val txn24 = JdbcSep24Transaction() txn24.status = INCOMPLETE.toString() txn24.kind = WITHDRAWAL.kind txn24.amountIn = "1" txn24.amountInAsset = STELLAR_USDC + txn24.feeDetails = FeeDetails() val sep24TxnCapture = slot() every { txn6Store.findByTransactionId(any()) } returns null @@ -209,7 +208,7 @@ class RequestOnchainFundsHandlerTest { every { txn24Store.save(capture(sep24TxnCapture)) } returns null val ex = assertThrows { handler.handle(request) } - assertEquals("amount_out is required", ex.message) + assertEquals("fee_details or amount_fee is required", ex.message) verify(exactly = 0) { txn6Store.save(any()) } verify(exactly = 0) { txn24Store.save(any()) } @@ -263,7 +262,7 @@ class RequestOnchainFundsHandlerTest { val ex = assertThrows { handler.handle(request) } assertEquals( - "All or none of the amount_in, amount_out, and (fee_details or amount_fee) should be set", + "All (amount_out is optional) or none of the amount_in, amount_out, and (fee_details or amount_fee) should be set", ex.message ) @@ -548,6 +547,63 @@ class RequestOnchainFundsHandlerTest { verify(exactly = 0) { sepTransactionCounter.increment() } } + @Test + fun test_handle_sep24_with_quote_amount_out_missing() { + val request = + RequestOnchainFundsRequest.builder() + .amountIn(AmountAssetRequest("1", STELLAR_USDC)) + .feeDetails(Amount("0.1", STELLAR_USDC).toRate()) + .transactionId(TX_ID) + .build() + val txn24 = JdbcSep24Transaction() + txn24.status = INCOMPLETE.toString() + txn24.kind = WITHDRAWAL.kind + txn24.quoteId = "testQuoteId" + val sep24TxnCapture = slot() + + every { txn6Store.findByTransactionId(any()) } returns null + every { txn24Store.findByTransactionId(TX_ID) } returns txn24 + every { txn31Store.findByTransactionId(any()) } returns null + every { txn24Store.save(capture(sep24TxnCapture)) } returns null + + val ex = assertThrows { handler.handle(request) } + assertEquals("amount_out is required for transactions with firm quotes", ex.message) + + verify(exactly = 0) { txn6Store.save(any()) } + verify(exactly = 0) { txn24Store.save(any()) } + verify(exactly = 0) { txn31Store.save(any()) } + verify(exactly = 0) { sepTransactionCounter.increment() } + } + + @Test + fun test_handle_sep24_with_simple_quote() { + val request = + RequestOnchainFundsRequest.builder() + .amountIn(AmountAssetRequest("1", STELLAR_USDC)) + .feeDetails(Amount("0.1", STELLAR_USDC).toRate()) + .transactionId(TX_ID) + .build() + val txn24 = JdbcSep24Transaction() + txn24.status = INCOMPLETE.toString() + txn24.kind = WITHDRAWAL.kind + txn24.amountInAsset = STELLAR_USDC + txn24.amountOutAsset = STELLAR_USDC + val sep24TxnCapture = slot() + + every { txn6Store.findByTransactionId(any()) } returns null + every { txn24Store.findByTransactionId(TX_ID) } returns txn24 + every { txn31Store.findByTransactionId(any()) } returns null + every { txn24Store.save(capture(sep24TxnCapture)) } returns null + + val ex = assertThrows { handler.handle(request) } + assertEquals("amount_out is required for non-exchange transactions", ex.message) + + verify(exactly = 0) { txn6Store.save(any()) } + verify(exactly = 0) { txn24Store.save(any()) } + verify(exactly = 0) { txn31Store.save(any()) } + verify(exactly = 0) { sepTransactionCounter.increment() } + } + @Test fun test_handle_ok_sep24_withExpectedAmount() { val request = @@ -1212,6 +1268,65 @@ class RequestOnchainFundsHandlerTest { verify(exactly = 0) { sepTransactionCounter.increment() } } + @ParameterizedTest + @ValueSource(strings = ["withdrawal", "withdrawal-exchange"]) + fun test_handle_sep6_with_quote_amount_out_missing(kind: String) { + val request = + RequestOnchainFundsRequest.builder() + .amountIn(AmountAssetRequest("1", STELLAR_USDC)) + .feeDetails(Amount("0.1", STELLAR_USDC).toRate()) + .transactionId(TX_ID) + .build() + val txn6 = JdbcSep6Transaction() + txn6.status = INCOMPLETE.toString() + txn6.kind = kind + txn6.quoteId = "testQuoteId" + val sep6TxnCapture = slot() + + every { txn6Store.findByTransactionId(any()) } returns txn6 + every { txn24Store.findByTransactionId(TX_ID) } returns null + every { txn31Store.findByTransactionId(any()) } returns null + every { txn6Store.save(capture(sep6TxnCapture)) } returns null + + val ex = assertThrows { handler.handle(request) } + assertEquals("amount_out is required for transactions with firm quotes", ex.message) + + verify(exactly = 0) { txn6Store.save(any()) } + verify(exactly = 0) { txn24Store.save(any()) } + verify(exactly = 0) { txn31Store.save(any()) } + verify(exactly = 0) { sepTransactionCounter.increment() } + } + + @ParameterizedTest + @ValueSource(strings = ["withdrawal", "withdrawal-exchange"]) + fun test_handle_sep6_with_simple_quote(kind: String) { + val request = + RequestOnchainFundsRequest.builder() + .amountIn(AmountAssetRequest("1", STELLAR_USDC)) + .feeDetails(Amount("0.1", STELLAR_USDC).toRate()) + .transactionId(TX_ID) + .build() + val txn6 = JdbcSep6Transaction() + txn6.status = INCOMPLETE.toString() + txn6.kind = kind + txn6.amountInAsset = STELLAR_USDC + txn6.amountOutAsset = STELLAR_USDC + val sep6TxnCapture = slot() + + every { txn6Store.findByTransactionId(any()) } returns txn6 + every { txn24Store.findByTransactionId(TX_ID) } returns null + every { txn31Store.findByTransactionId(any()) } returns null + every { txn6Store.save(capture(sep6TxnCapture)) } returns null + + val ex = assertThrows { handler.handle(request) } + assertEquals("amount_out is required for non-exchange transactions", ex.message) + + verify(exactly = 0) { txn6Store.save(any()) } + verify(exactly = 0) { txn24Store.save(any()) } + verify(exactly = 0) { txn31Store.save(any()) } + verify(exactly = 0) { sepTransactionCounter.increment() } + } + @CsvSource(value = ["withdrawal", "withdrawal-exchange"]) @ParameterizedTest fun test_handle_ok_sep6_withExpectedAmount(kind: String) { From b4b860f7e3b9c2692de0a21854070f9b2ca91597 Mon Sep 17 00:00:00 2001 From: Jamie Li Date: Tue, 13 Aug 2024 10:44:56 -0700 Subject: [PATCH 05/16] [ANCHOR-756] Upgrade docker-compose-rule and require DOCKER_LOCATION and DOCKER_COMPOSE_RULE system env (#1457) ### Description In JDK17, the ProcessEnvironment.theEnvironment was set to static final which does not allow us to set DOCKER_LOCATION and DOCKER_COMPOSE_LOCATION on Windows. **To fix:** - Upgrade docker rule library - Instead of setting WIN_DOCKER_LOCATION, we require setting DOCKER_LOCATION and DOCKER_COMPOSE_LOCATION and print out errors when not set. ### Context - Fix the Windows dev env not able to start the docker containers. ### Testing - `./gradlew test` ### Documentation NA ### Known limitations NA --- gradle/libs.versions.toml | 2 +- .../anchor/platform/TestProfileRunner.kt | 57 ++----------------- 2 files changed, 7 insertions(+), 52 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3317bc1526..cdff4ff007 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -15,7 +15,7 @@ commons-codec = "1.15" commons-io = "2.11.0" commons-validator = "1.8.0" commons-text = "1.10.0" -docker-compose-rule = "1.9.0" +docker-compose-rule = "2.3.0" dotenv = "2.3.2" exposed = "0.49.0" findbugs-jsr305 = "3.0.2" diff --git a/service-runner/src/main/kotlin/org/stellar/anchor/platform/TestProfileRunner.kt b/service-runner/src/main/kotlin/org/stellar/anchor/platform/TestProfileRunner.kt index 9cde43be4c..46cf66f158 100644 --- a/service-runner/src/main/kotlin/org/stellar/anchor/platform/TestProfileRunner.kt +++ b/service-runner/src/main/kotlin/org/stellar/anchor/platform/TestProfileRunner.kt @@ -7,7 +7,6 @@ import com.palantir.docker.compose.configuration.ProjectName import com.palantir.docker.compose.connection.waiting.HealthChecks import java.io.File import java.lang.Thread.sleep -import java.lang.reflect.Field import java.util.* import kotlinx.coroutines.* import org.springframework.context.ConfigurableApplicationContext @@ -140,13 +139,14 @@ class TestProfileExecutor(val config: TestConfig) { private fun startDocker() { if (shouldStartDockerCompose) { info("Starting docker compose...") - if (isWindows()) { - setupWindowsEnv() - } - - info("Initializing TestProfileRunner...") val dockerComposeFile = getResourceFile("docker-compose-test.yaml") val userHomeFolder = File(System.getProperty("user.home")) + if (isWindows()) { + System.getenv("DOCKER_LOCATION") + ?: throw RuntimeException("DOCKER_LOCATION env variable is not set") + System.getenv("DOCKER_COMPOSE_LOCATION") + ?: throw RuntimeException("DOCKER_COMPOSE_LOCATION env variable is not set") + } docker = DockerComposeExtension.builder() .saveLogsTo("${userHomeFolder}/docker-logs/anchor-platform-integration-test") @@ -160,9 +160,6 @@ class TestProfileExecutor(val config: TestConfig) { .build() docker.beforeAll(null) - - // TODO: Check server readiness instead of wait for 5 seconds - sleep(5000) } } @@ -189,46 +186,4 @@ class TestProfileExecutor(val config: TestConfig) { private fun isWindows(): Boolean { return System.getProperty("os.name").lowercase(Locale.getDefault()).contains("win") } - - private fun setupWindowsEnv() { - val windowsDockerLocation = - System.getenv("WIN_DOCKER_LOCATION") - ?: throw RuntimeException("WIN_DOCKER_LOCATION env variable is not set") - - setEnv(mapOf("DOCKER_LOCATION" to File(windowsDockerLocation, "docker.exe").absolutePath)) - setEnv( - mapOf( - "DOCKER_COMPOSE_LOCATION" to File(windowsDockerLocation, "docker-compose.exe").absolutePath - ) - ) - } - - @Suppress("UNCHECKED_CAST") - private fun setEnv(envs: Map?) { - try { - val processEnvironmentClass = Class.forName("java.lang.ProcessEnvironment") - val theEnvironmentField: Field = processEnvironmentClass.getDeclaredField("theEnvironment") - theEnvironmentField.isAccessible = true - val env = theEnvironmentField.get(null) as MutableMap - env.putAll(envs!!) - val theCaseInsensitiveEnvironmentField: Field = - processEnvironmentClass.getDeclaredField("theCaseInsensitiveEnvironment") - theCaseInsensitiveEnvironmentField.isAccessible = true - val cienv = theCaseInsensitiveEnvironmentField.get(null) as MutableMap - cienv.putAll(envs) - } catch (e: NoSuchFieldException) { - val classes = Collections::class.java.declaredClasses - val env = System.getenv() - for (cl in classes) { - if ("java.util.Collections\$UnmodifiableMap" == cl.name) { - val field: Field = cl.getDeclaredField("m") - field.isAccessible = true - val obj: Any = field.get(env) - val map = obj as MutableMap - map.clear() - map.putAll(envs!!) - } - } - } - } } From f54e4c1c497b2ca9d7f5bc3e0f985499168a47ca Mon Sep 17 00:00:00 2001 From: Gleb Date: Tue, 13 Aug 2024 11:26:29 -0700 Subject: [PATCH 06/16] clean rate request (#1456) ### Description Clean rate request ### Context Rate request have `id` field that is never used ### Testing - `./gradlew test` ### Documentation N/A (already not in the spec) ### Known limitations N/A Co-authored-by: Jamie Li --- .../anchor/api/callback/GetRateRequest.java | 2 - .../integrationtest/CallbackApiTests.kt | 67 ------- .../reference/callbacks/rate/RateRoute.kt | 1 - .../reference/callbacks/rate/RateService.kt | 167 +++++++++--------- 4 files changed, 79 insertions(+), 158 deletions(-) diff --git a/api-schema/src/main/java/org/stellar/anchor/api/callback/GetRateRequest.java b/api-schema/src/main/java/org/stellar/anchor/api/callback/GetRateRequest.java index ada4585ad9..99f5cfd8ec 100644 --- a/api-schema/src/main/java/org/stellar/anchor/api/callback/GetRateRequest.java +++ b/api-schema/src/main/java/org/stellar/anchor/api/callback/GetRateRequest.java @@ -43,8 +43,6 @@ public class GetRateRequest { @SerializedName("client_id") String clientId; - String id; - public enum Type { @SerializedName("indicative") INDICATIVE("indicative"), diff --git a/essential-tests/src/testFixtures/kotlin/org/stellar/anchor/platform/integrationtest/CallbackApiTests.kt b/essential-tests/src/testFixtures/kotlin/org/stellar/anchor/platform/integrationtest/CallbackApiTests.kt index 01789b675b..e08588ab90 100644 --- a/essential-tests/src/testFixtures/kotlin/org/stellar/anchor/platform/integrationtest/CallbackApiTests.kt +++ b/essential-tests/src/testFixtures/kotlin/org/stellar/anchor/platform/integrationtest/CallbackApiTests.kt @@ -1,10 +1,6 @@ package org.stellar.anchor.platform.integrationtest import com.google.gson.Gson -import java.time.Instant -import java.time.ZoneId -import java.time.ZonedDateTime -import java.time.format.DateTimeFormatter import java.util.* import java.util.concurrent.TimeUnit import okhttp3.OkHttpClient @@ -116,69 +112,6 @@ class CallbackApiTests : AbstractIntegrationTests(TestConfig()) { JSONAssert.assertEquals(wantBody, org.stellar.anchor.platform.gson.toJson(result), true) } - @Test - fun testRate_firm() { - val rate = - rriClient - .getRate( - GetRateRequest.builder() - .type(GetRateRequest.Type.FIRM) - .sellAsset(FIAT_USD) - .buyAsset(STELLAR_USD) - .buyAmount("100") - .build() - ) - .rate - Assertions.assertNotNull(rate) - - // check if id is a valid UUID - val id = rate.id - Assertions.assertDoesNotThrow { UUID.fromString(id) } - var gotExpiresAt: Instant? = null - val expiresAtStr = rate.expiresAt!!.toString() - Assertions.assertDoesNotThrow { - gotExpiresAt = DateTimeFormatter.ISO_INSTANT.parse(rate.expiresAt!!.toString(), Instant::from) - } - - val wantExpiresAt = - ZonedDateTime.now(ZoneId.of("UTC")) - .plusDays(1) - .withHour(12) - .withMinute(0) - .withSecond(0) - .withNano(0) - assertEquals(wantExpiresAt.toInstant(), gotExpiresAt) - - // check if rate was persisted by getting the rate with ID - val gotQuote = rriClient.getRate(GetRateRequest.builder().id(rate.id).build()) - assertEquals(rate.id, gotQuote.rate.id) - assertEquals("1.02", gotQuote.rate.price) - - val wantBody = - """{ - "rate":{ - "id": "$id", - "price":"1.02", - "sell_amount": "103", - "buy_amount": "100", - "expires_at": "$expiresAtStr", - "fee": { - "total": "1.00", - "asset": "$FIAT_USD", - "details": [ - { - "name": "Sell fee", - "description": "Fee related to selling the asset.", - "amount": "1.00" - } - ] - } - } - }""" - .trimMargin() - JSONAssert.assertEquals(wantBody, org.stellar.anchor.platform.gson.toJson(gotQuote), true) - } - @Test fun testGetFee() { // Create sender customer diff --git a/kotlin-reference-server/src/main/kotlin/org/stellar/reference/callbacks/rate/RateRoute.kt b/kotlin-reference-server/src/main/kotlin/org/stellar/reference/callbacks/rate/RateRoute.kt index 3251cb2318..67ed1d236e 100644 --- a/kotlin-reference-server/src/main/kotlin/org/stellar/reference/callbacks/rate/RateRoute.kt +++ b/kotlin-reference-server/src/main/kotlin/org/stellar/reference/callbacks/rate/RateRoute.kt @@ -30,7 +30,6 @@ fun Route.rate(rateService: RateService) { .countryCode(call.parameters["country_code"]) .expireAfter(call.parameters["expire_after"]) .clientId(call.parameters["client_id"]) - .id(call.parameters["id"]) .build() val response = GsonUtils.getInstance().toJson(rateService.getRate(request)) call.respond(response) diff --git a/kotlin-reference-server/src/main/kotlin/org/stellar/reference/callbacks/rate/RateService.kt b/kotlin-reference-server/src/main/kotlin/org/stellar/reference/callbacks/rate/RateService.kt index fe78da5107..8d69ab5d61 100644 --- a/kotlin-reference-server/src/main/kotlin/org/stellar/reference/callbacks/rate/RateService.kt +++ b/kotlin-reference-server/src/main/kotlin/org/stellar/reference/callbacks/rate/RateService.kt @@ -19,97 +19,88 @@ import org.stellar.reference.model.Quote class RateService(private val quoteRepository: QuoteRepository) { private val scale = 4 fun getRate(request: GetRateRequest): GetRateResponse { - val rate = - when { - request.id != null -> getRate(request.id) - else -> { - validateRequest(request) - val price = - getPrice(request.sellAsset!!, request.buyAsset!!)?.let { getDecimal(it, scale) } - ?: throw RuntimeException( - "Price not found for ${request.sellAsset} and ${request.buyAsset}" - ) - val buyAmount = request.buyAmount?.let { getDecimal(it, scale) } - val sellAmount = request.sellAmount?.let { getDecimal(it, scale) } - val fee = getFee(request.sellAsset!!, request.buyAsset!!) - val feeAmount = fee.total.toBigDecimal() - - val finalBuyAmount = - sellAmount?.subtract(feeAmount)?.divide(price, RoundingMode.HALF_DOWN)?.also { - if (it <= BigDecimal.ZERO) { - throw RuntimeException("Buy amount must be greater than zero") - } - } - ?: buyAmount - val finalSellAmount = - buyAmount?.setScale(10, RoundingMode.HALF_DOWN)?.multiply(price)?.add(feeAmount) - ?: sellAmount - val finalPrice = - finalSellAmount - ?.setScale(10, RoundingMode.HALF_DOWN) - ?.subtract(feeAmount) - ?.divide(finalBuyAmount, RoundingMode.HALF_DOWN) - ?: price - val finalTotalPrice = finalSellAmount?.divide(finalBuyAmount, 10, RoundingMode.HALF_DOWN) - - if (request.type == GetRateRequest.Type.INDICATIVE) { - return GetRateResponse.indicativePrice( - getString(finalPrice, 10), - getString(finalSellAmount!!, scale), - getString(finalBuyAmount!!, scale), - fee - ) - } - - val expiresAfter = - request.expireAfter?.let { ZonedDateTime.parse(it).toInstant() } ?: Instant.now() - val expiresAt = - ZonedDateTime.ofInstant(expiresAfter, ZoneId.of("UTC")) - .plusDays(1) - .withHour(12) - .withMinute(0) - .withSecond(0) - .withNano(0) - .toInstant() - val quote = - Quote( - id = UUID.randomUUID().toString(), - sellAsset = request.sellAsset, - sellAmount = getString(finalSellAmount!!, scale), - sellDeliveryMethod = request.sellDeliveryMethod, - buyAsset = request.buyAsset, - buyAmount = getString(finalBuyAmount!!, scale), - buyDeliveryMethod = request.buyDeliveryMethod, - countryCode = request.countryCode, - createdAt = Instant.now(), - expiresAt = expiresAt, - clientId = request.clientId, - price = getString(finalPrice, 10), - totalPrice = getString(finalTotalPrice!!, 10), - fee = - org.stellar.reference.model.FeeDetails( - fee.total, - fee.asset, - fee.details.map { - org.stellar.reference.model.FeeDescription(it.name, it.description, it.amount) - } - ) - ) - quoteRepository.create(quote) - - val rate = - GetRateResponse.Rate.builder() - .id(quote.id) - .price(quote.price) - .sellAmount(quote.sellAmount) - .buyAmount(quote.buyAmount) - .expiresAt(quote.expiresAt) - .fee(fee) - .build() - return GetRateResponse(rate) + validateRequest(request) + val price = + getPrice(request.sellAsset!!, request.buyAsset!!)?.let { getDecimal(it, scale) } + ?: throw RuntimeException( + "Price not found for ${request.sellAsset} and ${request.buyAsset}" + ) + val buyAmount = request.buyAmount?.let { getDecimal(it, scale) } + val sellAmount = request.sellAmount?.let { getDecimal(it, scale) } + val fee = getFee(request.sellAsset!!, request.buyAsset!!) + val feeAmount = fee.total.toBigDecimal() + + val finalBuyAmount = + sellAmount?.subtract(feeAmount)?.divide(price, RoundingMode.HALF_DOWN)?.also { + if (it <= BigDecimal.ZERO) { + throw RuntimeException("Buy amount must be greater than zero") } } + ?: buyAmount + val finalSellAmount = + buyAmount?.setScale(10, RoundingMode.HALF_DOWN)?.multiply(price)?.add(feeAmount) ?: sellAmount + val finalPrice = + finalSellAmount + ?.setScale(10, RoundingMode.HALF_DOWN) + ?.subtract(feeAmount) + ?.divide(finalBuyAmount, RoundingMode.HALF_DOWN) + ?: price + val finalTotalPrice = finalSellAmount?.divide(finalBuyAmount, 10, RoundingMode.HALF_DOWN) + + if (request.type == GetRateRequest.Type.INDICATIVE) { + return GetRateResponse.indicativePrice( + getString(finalPrice, 10), + getString(finalSellAmount!!, scale), + getString(finalBuyAmount!!, scale), + fee + ) + } + val expiresAfter = + request.expireAfter?.let { ZonedDateTime.parse(it).toInstant() } ?: Instant.now() + val expiresAt = + ZonedDateTime.ofInstant(expiresAfter, ZoneId.of("UTC")) + .plusDays(1) + .withHour(12) + .withMinute(0) + .withSecond(0) + .withNano(0) + .toInstant() + val quote = + Quote( + id = UUID.randomUUID().toString(), + sellAsset = request.sellAsset, + sellAmount = getString(finalSellAmount!!, scale), + sellDeliveryMethod = request.sellDeliveryMethod, + buyAsset = request.buyAsset, + buyAmount = getString(finalBuyAmount!!, scale), + buyDeliveryMethod = request.buyDeliveryMethod, + countryCode = request.countryCode, + createdAt = Instant.now(), + expiresAt = expiresAt, + clientId = request.clientId, + price = getString(finalPrice, 10), + totalPrice = getString(finalTotalPrice!!, 10), + fee = + org.stellar.reference.model.FeeDetails( + fee.total, + fee.asset, + fee.details.map { + org.stellar.reference.model.FeeDescription(it.name, it.description, it.amount) + } + ) + ) + quoteRepository.create(quote) + + val rate = + GetRateResponse.Rate.builder() + .id(quote.id) + .price(quote.price) + .sellAmount(quote.sellAmount) + .buyAmount(quote.buyAmount) + .expiresAt(quote.expiresAt) + .fee(fee) + .build() return GetRateResponse(rate) } From 8910c6f55b93ebd42eb9732302297854c1a2f5ae Mon Sep 17 00:00:00 2001 From: Philip Liu <12836897+philipliu@users.noreply.github.com> Date: Tue, 13 Aug 2024 14:55:58 -0400 Subject: [PATCH 07/16] [ANCHOR-764] Update NotifyCustomerInfoUpdated RPC (#1445) ### Description Addresses https://github.com/stellar/java-stellar-anchor-sdk/issues/1444. This change: - Updates the `NotifyCustomerInfoUpdated` RPC so that it updates the transaction based on the requested Customer's status and sends a Customer updated event. `customerId` and `type` are new parameters needed to distinguish whether a customer is a SEP-31 receiver, sender, or a SEP-6 customer. - Updates SEP-12 service such that it passes the full SEP-12 customer into the `AnchorEvent`. Previously only the customer ID was present in the event. This is not a breaking change since the SEP-12 customer object is serializer backward compatible since it also contains an ID. - Sends a SEP-12 status callback to all clients. Callback configuration and end-to-end tests will be added in another PR. ### Context N/A ### Testing - `./gradlew test` ### Documentation stellar-docs PR will be created ### Known limitations N/A --- .../api/callback/SendEventRequestPayload.java | 5 +- .../stellar/anchor/api/event/AnchorEvent.java | 11 +- .../api/platform/PlatformTransactionData.java | 2 + .../NotifyCustomerInfoUpdatedRequest.java | 8 +- .../stellar/anchor/sep12/Sep12Service.java | 8 +- .../stellar/anchor/sep12/Sep12ServiceTest.kt | 15 +- .../eventprocessor/EventProcessorBeans.java | 6 + .../component/platform/RpcActionBeans.java | 6 + .../event/ClientStatusCallbackHandler.java | 55 +-- .../platform/event/EventProcessorManager.java | 5 + .../rpc/NotifyCustomerInfoUpdatedHandler.java | 79 +++- .../anchor/platform/rpc/RpcMethodHandler.java | 2 +- .../event/ClientStatusCallbackHandlerTest.kt | 4 + .../NotifyCustomerInfoUpdatedHandlerTest.kt | 363 +++++++++++++++++- 14 files changed, 518 insertions(+), 51 deletions(-) diff --git a/api-schema/src/main/java/org/stellar/anchor/api/callback/SendEventRequestPayload.java b/api-schema/src/main/java/org/stellar/anchor/api/callback/SendEventRequestPayload.java index 4785202b20..71cc22847e 100644 --- a/api-schema/src/main/java/org/stellar/anchor/api/callback/SendEventRequestPayload.java +++ b/api-schema/src/main/java/org/stellar/anchor/api/callback/SendEventRequestPayload.java @@ -26,7 +26,10 @@ public static SendEventRequestPayload from(AnchorEvent event) { SendEventRequestPayload payload = new SendEventRequestPayload(); switch (event.getType()) { case CUSTOMER_UPDATED: - payload.setCustomer(event.getCustomer()); + payload.setCustomer( + event.getCustomer() != null + ? CustomerUpdatedResponse.builder().id(event.getCustomer().getId()).build() + : null); case QUOTE_CREATED: payload.setQuote(event.getQuote()); case TRANSACTION_CREATED: diff --git a/api-schema/src/main/java/org/stellar/anchor/api/event/AnchorEvent.java b/api-schema/src/main/java/org/stellar/anchor/api/event/AnchorEvent.java index 096cb200eb..5dcf2fe78d 100644 --- a/api-schema/src/main/java/org/stellar/anchor/api/event/AnchorEvent.java +++ b/api-schema/src/main/java/org/stellar/anchor/api/event/AnchorEvent.java @@ -2,16 +2,13 @@ import com.google.gson.annotations.SerializedName; import lombok.*; -import org.stellar.anchor.api.platform.CustomerUpdatedResponse; import org.stellar.anchor.api.platform.GetQuoteResponse; import org.stellar.anchor.api.platform.GetTransactionResponse; +import org.stellar.anchor.api.sep.sep12.Sep12GetCustomerResponse; /** - * The Event object is used for event notification. - * - * @see Events - * Schema + * The internal event object is used for event notification. They should be mapped to the + * appropriate schemas for the API and status callback use cases. */ @Builder @Data @@ -23,7 +20,7 @@ public class AnchorEvent { String sep; GetTransactionResponse transaction; GetQuoteResponse quote; - CustomerUpdatedResponse customer; + Sep12GetCustomerResponse customer; public enum Type { @SerializedName("transaction_created") diff --git a/api-schema/src/main/java/org/stellar/anchor/api/platform/PlatformTransactionData.java b/api-schema/src/main/java/org/stellar/anchor/api/platform/PlatformTransactionData.java index cd127ee000..5b62317168 100644 --- a/api-schema/src/main/java/org/stellar/anchor/api/platform/PlatformTransactionData.java +++ b/api-schema/src/main/java/org/stellar/anchor/api/platform/PlatformTransactionData.java @@ -124,6 +124,8 @@ public class PlatformTransactionData { public enum Sep { @SerializedName("6") SEP_6(6), + @SerializedName("12") + SEP_12(12), @SuppressWarnings("unused") @SerializedName("24") SEP_24(24), diff --git a/api-schema/src/main/java/org/stellar/anchor/api/rpc/method/NotifyCustomerInfoUpdatedRequest.java b/api-schema/src/main/java/org/stellar/anchor/api/rpc/method/NotifyCustomerInfoUpdatedRequest.java index 4b76bef0f1..5622d536e0 100644 --- a/api-schema/src/main/java/org/stellar/anchor/api/rpc/method/NotifyCustomerInfoUpdatedRequest.java +++ b/api-schema/src/main/java/org/stellar/anchor/api/rpc/method/NotifyCustomerInfoUpdatedRequest.java @@ -1,5 +1,6 @@ package org.stellar.anchor.api.rpc.method; +import com.google.gson.annotations.SerializedName; import lombok.AllArgsConstructor; import lombok.Data; import lombok.EqualsAndHashCode; @@ -9,4 +10,9 @@ @SuperBuilder @AllArgsConstructor @EqualsAndHashCode(callSuper = false) -public class NotifyCustomerInfoUpdatedRequest extends RpcMethodParamsRequest {} +public class NotifyCustomerInfoUpdatedRequest extends RpcMethodParamsRequest { + @SerializedName("customer_id") + String customerId; + + String type; +} diff --git a/core/src/main/java/org/stellar/anchor/sep12/Sep12Service.java b/core/src/main/java/org/stellar/anchor/sep12/Sep12Service.java index b2128d7d3d..502161ea29 100644 --- a/core/src/main/java/org/stellar/anchor/sep12/Sep12Service.java +++ b/core/src/main/java/org/stellar/anchor/sep12/Sep12Service.java @@ -1,5 +1,6 @@ package org.stellar.anchor.sep12; +import static org.stellar.anchor.api.platform.PlatformTransactionData.Sep.SEP_12; import static org.stellar.anchor.util.Log.infoF; import static org.stellar.anchor.util.MetricConstants.*; import static org.stellar.anchor.util.MetricConstants.SEP12_CUSTOMER; @@ -19,7 +20,6 @@ import org.stellar.anchor.api.callback.*; import org.stellar.anchor.api.event.AnchorEvent; import org.stellar.anchor.api.exception.*; -import org.stellar.anchor.api.platform.CustomerUpdatedResponse; import org.stellar.anchor.api.platform.GetTransactionResponse; import org.stellar.anchor.api.sep.sep12.*; import org.stellar.anchor.apiclient.PlatformApiClient; @@ -108,14 +108,16 @@ public Sep12PutCustomerResponse putCustomer(Sep10Jwt token, Sep12PutCustomerRequ PutCustomerResponse response = customerIntegration.putCustomer(PutCustomerRequest.from(request)); + GetCustomerResponse updatedCustomer = + customerIntegration.getCustomer(GetCustomerRequest.builder().id(response.getId()).build()); // Only publish event if the customer was updated. eventSession.publish( AnchorEvent.builder() .id(UUID.randomUUID().toString()) - .sep("12") + .sep(SEP_12.getSep().toString()) .type(AnchorEvent.Type.CUSTOMER_UPDATED) - .customer(CustomerUpdatedResponse.builder().id(response.getId()).build()) + .customer(GetCustomerResponse.to(updatedCustomer)) .build()); // increment counter diff --git a/core/src/test/kotlin/org/stellar/anchor/sep12/Sep12ServiceTest.kt b/core/src/test/kotlin/org/stellar/anchor/sep12/Sep12ServiceTest.kt index 8509ebcf67..cadb090064 100644 --- a/core/src/test/kotlin/org/stellar/anchor/sep12/Sep12ServiceTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/sep12/Sep12ServiceTest.kt @@ -6,14 +6,16 @@ import io.mockk.* import io.mockk.impl.annotations.MockK import java.time.Instant import kotlin.test.assertNotNull -import org.junit.jupiter.api.* import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertInstanceOf +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertDoesNotThrow +import org.junit.jupiter.api.assertThrows import org.skyscreamer.jsonassert.JSONAssert import org.stellar.anchor.api.callback.* import org.stellar.anchor.api.event.AnchorEvent import org.stellar.anchor.api.exception.* -import org.stellar.anchor.api.platform.CustomerUpdatedResponse import org.stellar.anchor.api.platform.GetTransactionResponse import org.stellar.anchor.api.sep.sep12.Sep12CustomerRequestBase import org.stellar.anchor.api.sep.sep12.Sep12GetCustomerRequest @@ -257,11 +259,15 @@ class Sep12ServiceTest { fun `Test put customer request ok`() { // mock `PUT {callbackApi}/customer` response val callbackApiPutRequestSlot = slot() + val callbackApiGetRequestSlot = slot() val kycUpdateEventSlot = slot() val mockCallbackApiPutCustomerResponse = PutCustomerResponse() + val mockCallbackApiGetCustomerResponse = GetCustomerResponse() mockCallbackApiPutCustomerResponse.id = "customer-id" every { customerIntegration.putCustomer(capture(callbackApiPutRequestSlot)) } returns mockCallbackApiPutCustomerResponse + every { customerIntegration.getCustomer(capture(callbackApiGetRequestSlot)) } returns + mockCallbackApiGetCustomerResponse every { eventSession.publish(capture(kycUpdateEventSlot)) } returns Unit // Execute the request @@ -303,12 +309,15 @@ class Sep12ServiceTest { .build() assertEquals(wantCallbackApiPutRequest, callbackApiPutRequestSlot.captured) + val wantCallbackApiGetCustomerResponse = GetCustomerRequest.builder().id("customer-id").build() + assertEquals(wantCallbackApiGetCustomerResponse, callbackApiGetRequestSlot.captured) + // validate the published event assertNotNull(kycUpdateEventSlot.captured.id) assertEquals("12", kycUpdateEventSlot.captured.sep) assertEquals(AnchorEvent.Type.CUSTOMER_UPDATED, kycUpdateEventSlot.captured.type) assertEquals( - CustomerUpdatedResponse(mockCallbackApiPutCustomerResponse.id), + GetCustomerResponse.to(mockCallbackApiGetCustomerResponse), kycUpdateEventSlot.captured.customer, ) diff --git a/platform/src/main/java/org/stellar/anchor/platform/component/eventprocessor/EventProcessorBeans.java b/platform/src/main/java/org/stellar/anchor/platform/component/eventprocessor/EventProcessorBeans.java index e7709c4486..648d5a7266 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/component/eventprocessor/EventProcessorBeans.java +++ b/platform/src/main/java/org/stellar/anchor/platform/component/eventprocessor/EventProcessorBeans.java @@ -2,10 +2,13 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; import org.stellar.anchor.MoreInfoUrlConstructor; +import org.stellar.anchor.api.callback.CustomerIntegration; import org.stellar.anchor.asset.AssetService; import org.stellar.anchor.config.SecretConfig; import org.stellar.anchor.event.EventService; +import org.stellar.anchor.platform.component.sep.ApiClientBeans; import org.stellar.anchor.platform.config.CallbackApiConfig; import org.stellar.anchor.platform.config.EventProcessorConfig; import org.stellar.anchor.platform.config.PropertyClientsConfig; @@ -15,6 +18,7 @@ import org.stellar.anchor.sep6.Sep6TransactionStore; @Configuration +@Import(ApiClientBeans.class) public class EventProcessorBeans { @Bean @@ -25,6 +29,7 @@ EventProcessorManager eventProcessorManager( PropertyClientsConfig clientsConfig, EventService eventService, AssetService assetService, + CustomerIntegration customerIntegration, Sep6TransactionStore sep6TransactionStore, Sep24TransactionStore sep24TransactionStore, Sep31TransactionStore sep31TransactionStore, @@ -37,6 +42,7 @@ EventProcessorManager eventProcessorManager( clientsConfig, eventService, assetService, + customerIntegration, sep6TransactionStore, sep24TransactionStore, sep31TransactionStore, diff --git a/platform/src/main/java/org/stellar/anchor/platform/component/platform/RpcActionBeans.java b/platform/src/main/java/org/stellar/anchor/platform/component/platform/RpcActionBeans.java index 2dd466326f..3b91896ae6 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/component/platform/RpcActionBeans.java +++ b/platform/src/main/java/org/stellar/anchor/platform/component/platform/RpcActionBeans.java @@ -3,12 +3,15 @@ import java.util.List; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.stellar.anchor.api.callback.CustomerIntegration; import org.stellar.anchor.asset.AssetService; import org.stellar.anchor.config.CustodyConfig; import org.stellar.anchor.custody.CustodyService; import org.stellar.anchor.event.EventService; import org.stellar.anchor.horizon.Horizon; import org.stellar.anchor.metrics.MetricsService; +import org.stellar.anchor.platform.component.sep.ApiClientBeans; import org.stellar.anchor.platform.config.PropertyCustodyConfig; import org.stellar.anchor.platform.config.RpcConfig; import org.stellar.anchor.platform.data.JdbcTransactionPendingTrustRepo; @@ -22,6 +25,7 @@ import org.stellar.anchor.sep6.Sep6TransactionStore; @Configuration +@Import(ApiClientBeans.class) public class RpcActionBeans { @Bean @@ -405,6 +409,7 @@ NotifyCustomerInfoUpdatedHandler notifyCustomerInfoUpdatedHandler( Sep24TransactionStore txn24Store, Sep31TransactionStore txn31Store, RequestValidator requestValidator, + CustomerIntegration customerIntegration, AssetService assetService, EventService eventService, MetricsService metricsService) { @@ -413,6 +418,7 @@ NotifyCustomerInfoUpdatedHandler notifyCustomerInfoUpdatedHandler( txn24Store, txn31Store, requestValidator, + customerIntegration, assetService, eventService, metricsService); diff --git a/platform/src/main/java/org/stellar/anchor/platform/event/ClientStatusCallbackHandler.java b/platform/src/main/java/org/stellar/anchor/platform/event/ClientStatusCallbackHandler.java index aa5418f410..c448451ebd 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/event/ClientStatusCallbackHandler.java +++ b/platform/src/main/java/org/stellar/anchor/platform/event/ClientStatusCallbackHandler.java @@ -18,8 +18,10 @@ import okhttp3.Request; import okhttp3.Response; import org.stellar.anchor.MoreInfoUrlConstructor; +import org.stellar.anchor.api.callback.CustomerIntegration; import org.stellar.anchor.api.event.AnchorEvent; import org.stellar.anchor.api.exception.AnchorException; +import org.stellar.anchor.api.exception.InternalServerErrorException; import org.stellar.anchor.api.exception.SepException; import org.stellar.anchor.api.platform.GetTransactionResponse; import org.stellar.anchor.api.sep.sep24.Sep24GetTransactionResponse; @@ -48,6 +50,7 @@ public class ClientStatusCallbackHandler extends EventHandler { private final SecretConfig secretConfig; private final ClientConfig clientConfig; private final Sep6TransactionStore sep6TransactionStore; + private final CustomerIntegration customerIntegration; private final AssetService assetService; private final MoreInfoUrlConstructor sep6MoreInfoUrlConstructor; private final MoreInfoUrlConstructor sep24MoreInfoUrlConstructor; @@ -56,6 +59,7 @@ public ClientStatusCallbackHandler( SecretConfig secretConfig, ClientConfig clientConfig, Sep6TransactionStore sep6TransactionStore, + CustomerIntegration customerIntegration, AssetService assetService, MoreInfoUrlConstructor sep6MoreInfoUrlConstructor, MoreInfoUrlConstructor sep24MoreInfoUrlConstructor) { @@ -64,13 +68,14 @@ public ClientStatusCallbackHandler( this.clientConfig = clientConfig; this.assetService = assetService; this.sep6TransactionStore = sep6TransactionStore; + this.customerIntegration = customerIntegration; this.sep6MoreInfoUrlConstructor = sep6MoreInfoUrlConstructor; this.sep24MoreInfoUrlConstructor = sep24MoreInfoUrlConstructor; } @Override boolean handleEvent(AnchorEvent event) throws IOException { - if (event.getTransaction() != null) { + if (event.getTransaction() != null || event.getCustomer() != null) { KeyPair signer = KeyPair.fromSecretSeed(secretConfig.getSep10SigningSeed()); Request request = buildHttpRequest(signer, event); Response response = httpClient.newCall(request).execute(); @@ -110,27 +115,33 @@ public static Request buildHttpRequest(KeyPair signer, String payload, String ur } private String getPayload(AnchorEvent event) throws AnchorException { - switch (event.getTransaction().getSep()) { - case SEP_6: - // TODO: remove dependence on the transaction store - Sep6Transaction sep6Txn = - sep6TransactionStore.findByTransactionId(event.getTransaction().getId()); - org.stellar.anchor.api.sep.sep6.GetTransactionResponse sep6TxnRes = - new org.stellar.anchor.api.sep.sep6.GetTransactionResponse( - Sep6TransactionUtils.fromTxn(sep6Txn, sep6MoreInfoUrlConstructor, null)); - return json(sep6TxnRes); - case SEP_24: - Sep24Transaction sep24Txn = fromSep24Txn(event.getTransaction()); - Sep24GetTransactionResponse txn24Response = - Sep24GetTransactionResponse.of( - fromTxn(assetService, sep24MoreInfoUrlConstructor, sep24Txn, null)); - return json(txn24Response); - case SEP_31: - Sep31Transaction sep31Txn = fromSep31Txn(event.getTransaction()); - return json(sep31Txn.toSep31GetTransactionResponse()); - default: - throw new SepException( - String.format("Unsupported SEP: %s", event.getTransaction().getSep())); + if (event.getTransaction() != null) { + switch (event.getTransaction().getSep()) { + case SEP_6: + // TODO: remove dependence on the transaction store + Sep6Transaction sep6Txn = + sep6TransactionStore.findByTransactionId(event.getTransaction().getId()); + org.stellar.anchor.api.sep.sep6.GetTransactionResponse sep6TxnRes = + new org.stellar.anchor.api.sep.sep6.GetTransactionResponse( + Sep6TransactionUtils.fromTxn(sep6Txn, sep6MoreInfoUrlConstructor, null)); + return json(sep6TxnRes); + case SEP_24: + Sep24Transaction sep24Txn = fromSep24Txn(event.getTransaction()); + Sep24GetTransactionResponse txn24Response = + Sep24GetTransactionResponse.of( + fromTxn(assetService, sep24MoreInfoUrlConstructor, sep24Txn, null)); + return json(txn24Response); + case SEP_31: + Sep31Transaction sep31Txn = fromSep31Txn(event.getTransaction()); + return json(sep31Txn.toSep31GetTransactionResponse()); + default: + throw new SepException( + String.format("Unsupported SEP: %s", event.getTransaction().getSep())); + } + } else if (event.getCustomer() != null) { + return json(event.getCustomer()); + } else { + throw new InternalServerErrorException("Event must have either a transaction or a customer"); } } diff --git a/platform/src/main/java/org/stellar/anchor/platform/event/EventProcessorManager.java b/platform/src/main/java/org/stellar/anchor/platform/event/EventProcessorManager.java index c2ec99bf75..5a713427d5 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/event/EventProcessorManager.java +++ b/platform/src/main/java/org/stellar/anchor/platform/event/EventProcessorManager.java @@ -12,6 +12,7 @@ import lombok.SneakyThrows; import org.apache.commons.lang3.StringUtils; import org.stellar.anchor.MoreInfoUrlConstructor; +import org.stellar.anchor.api.callback.CustomerIntegration; import org.stellar.anchor.api.exception.AnchorException; import org.stellar.anchor.api.exception.InternalServerErrorException; import org.stellar.anchor.asset.AssetService; @@ -36,6 +37,7 @@ public class EventProcessorManager { private final PropertyClientsConfig clientsConfig; private final EventService eventService; private final AssetService assetService; + private final CustomerIntegration customerIntegration; private final Sep6TransactionStore sep6TransactionStore; private final Sep24TransactionStore sep24TransactionStore; private final Sep31TransactionStore sep31TransactionStore; @@ -50,6 +52,7 @@ public EventProcessorManager( PropertyClientsConfig clientsConfig, EventService eventService, AssetService assetService, + CustomerIntegration customerIntegration, Sep6TransactionStore sep6TransactionStore, Sep24TransactionStore sep24TransactionStore, Sep31TransactionStore sep31TransactionStore, @@ -61,6 +64,7 @@ public EventProcessorManager( this.clientsConfig = clientsConfig; this.eventService = eventService; this.assetService = assetService; + this.customerIntegration = customerIntegration; this.sep6TransactionStore = sep6TransactionStore; this.sep24TransactionStore = sep24TransactionStore; this.sep31TransactionStore = sep31TransactionStore; @@ -115,6 +119,7 @@ public void start() { secretConfig, clientConfig, sep6TransactionStore, + customerIntegration, assetService, sep6MoreInfoUrlConstructor, sep24MoreInfoUrlConstructor))); diff --git a/platform/src/main/java/org/stellar/anchor/platform/rpc/NotifyCustomerInfoUpdatedHandler.java b/platform/src/main/java/org/stellar/anchor/platform/rpc/NotifyCustomerInfoUpdatedHandler.java index 1b206d6018..f4f10ceb78 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/rpc/NotifyCustomerInfoUpdatedHandler.java +++ b/platform/src/main/java/org/stellar/anchor/platform/rpc/NotifyCustomerInfoUpdatedHandler.java @@ -1,12 +1,17 @@ package org.stellar.anchor.platform.rpc; import static java.util.Collections.emptySet; -import static org.stellar.anchor.api.platform.PlatformTransactionData.Sep.SEP_31; +import static org.stellar.anchor.api.platform.PlatformTransactionData.Sep.*; import static org.stellar.anchor.api.rpc.method.RpcMethod.NOTIFY_CUSTOMER_INFO_UPDATED; -import static org.stellar.anchor.api.sep.SepTransactionStatus.PENDING_CUSTOMER_INFO_UPDATE; -import static org.stellar.anchor.api.sep.SepTransactionStatus.PENDING_RECEIVER; +import static org.stellar.anchor.api.sep.SepTransactionStatus.*; import java.util.Set; +import java.util.UUID; +import org.stellar.anchor.api.callback.CustomerIntegration; +import org.stellar.anchor.api.callback.GetCustomerRequest; +import org.stellar.anchor.api.callback.GetCustomerResponse; +import org.stellar.anchor.api.event.AnchorEvent; +import org.stellar.anchor.api.exception.AnchorException; import org.stellar.anchor.api.exception.BadRequestException; import org.stellar.anchor.api.exception.rpc.InvalidParamsException; import org.stellar.anchor.api.exception.rpc.InvalidRequestException; @@ -14,6 +19,7 @@ import org.stellar.anchor.api.rpc.method.NotifyCustomerInfoUpdatedRequest; import org.stellar.anchor.api.rpc.method.RpcMethod; import org.stellar.anchor.api.sep.SepTransactionStatus; +import org.stellar.anchor.api.sep.sep12.Sep12Status; import org.stellar.anchor.asset.AssetService; import org.stellar.anchor.event.EventService; import org.stellar.anchor.metrics.MetricsService; @@ -25,12 +31,15 @@ public class NotifyCustomerInfoUpdatedHandler extends RpcMethodHandler { + private final CustomerIntegration customerIntegration; + private final EventService.Session eventSession; public NotifyCustomerInfoUpdatedHandler( Sep6TransactionStore txn6Store, Sep24TransactionStore txn24Store, Sep31TransactionStore txn31Store, RequestValidator requestValidator, + CustomerIntegration customerIntegration, AssetService assetService, EventService eventService, MetricsService metricsService) { @@ -43,6 +52,9 @@ public NotifyCustomerInfoUpdatedHandler( eventService, metricsService, NotifyCustomerInfoUpdatedRequest.class); + this.customerIntegration = customerIntegration; + this.eventSession = + eventService.createSession(this.getClass().getName(), EventService.EventQueue.TRANSACTION); } @Override @@ -58,16 +70,67 @@ public RpcMethod getRpcMethod() { @Override protected SepTransactionStatus getNextStatus( - JdbcSepTransaction txn, NotifyCustomerInfoUpdatedRequest request) { - return PENDING_RECEIVER; + JdbcSepTransaction txn, NotifyCustomerInfoUpdatedRequest request) throws AnchorException { + String status = null; + if (request.getCustomerId() != null) { + GetCustomerResponse customer = + customerIntegration.getCustomer( + GetCustomerRequest.builder() + .transactionId(txn.getId()) + .id(request.getCustomerId()) + .type(request.getType()) + .build()); + status = customer.getStatus(); + eventSession.publish( + AnchorEvent.builder() + .id(UUID.randomUUID().toString()) + .sep(SEP_12.getSep().toString()) + .type(AnchorEvent.Type.CUSTOMER_UPDATED) + .customer(GetCustomerResponse.to(customer)) + .build()); + } + + if (SEP_6 == Sep.from(txn.getProtocol())) { + if (status == null) { + return PENDING_ANCHOR; + } + switch (Sep12Status.valueOf(status)) { + case ACCEPTED, PROCESSING: + return PENDING_ANCHOR; + case NEEDS_INFO: + return PENDING_CUSTOMER_INFO_UPDATE; + case REJECTED: + return ERROR; + } + } + if (SEP_31 == Sep.from(txn.getProtocol())) { + if (status == null) { + return PENDING_RECEIVER; + } + switch (Sep12Status.valueOf(status)) { + case ACCEPTED, PROCESSING: + return PENDING_RECEIVER; + case NEEDS_INFO: + return PENDING_CUSTOMER_INFO_UPDATE; + case REJECTED: + return ERROR; + } + } + throw new InvalidRequestException( + String.format( + "RPC method[%s] is not supported for protocol[%s]", getRpcMethod(), txn.getProtocol())); } @Override protected Set getSupportedStatuses(JdbcSepTransaction txn) { - if (SEP_31 == Sep.from(txn.getProtocol())) { - return Set.of(PENDING_CUSTOMER_INFO_UPDATE); + switch (Sep.from(txn.getProtocol())) { + case SEP_6: + return Set.of(PENDING_ANCHOR, PENDING_CUSTOMER_INFO_UPDATE); + case SEP_31: + return Set.of(PENDING_RECEIVER, PENDING_CUSTOMER_INFO_UPDATE); + default: + return emptySet(); } - return emptySet(); } @Override diff --git a/platform/src/main/java/org/stellar/anchor/platform/rpc/RpcMethodHandler.java b/platform/src/main/java/org/stellar/anchor/platform/rpc/RpcMethodHandler.java index 190145507d..8ade4bc157 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/rpc/RpcMethodHandler.java +++ b/platform/src/main/java/org/stellar/anchor/platform/rpc/RpcMethodHandler.java @@ -128,7 +128,7 @@ public GetTransactionResponse handle(Object requestParams) throws AnchorExceptio public abstract RpcMethod getRpcMethod(); protected abstract SepTransactionStatus getNextStatus(JdbcSepTransaction txn, T request) - throws InvalidRequestException, InvalidParamsException; + throws AnchorException; protected abstract Set getSupportedStatuses(JdbcSepTransaction txn); diff --git a/platform/src/test/kotlin/org/stellar/anchor/platform/event/ClientStatusCallbackHandlerTest.kt b/platform/src/test/kotlin/org/stellar/anchor/platform/event/ClientStatusCallbackHandlerTest.kt index e33353ca84..050aae2c7a 100644 --- a/platform/src/test/kotlin/org/stellar/anchor/platform/event/ClientStatusCallbackHandlerTest.kt +++ b/platform/src/test/kotlin/org/stellar/anchor/platform/event/ClientStatusCallbackHandlerTest.kt @@ -11,6 +11,7 @@ import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import org.stellar.anchor.LockAndMockStatic import org.stellar.anchor.LockAndMockTest +import org.stellar.anchor.api.callback.CustomerIntegration import org.stellar.anchor.api.event.AnchorEvent import org.stellar.anchor.api.platform.GetTransactionResponse import org.stellar.anchor.api.platform.PlatformTransactionData @@ -45,6 +46,7 @@ class ClientStatusCallbackHandlerTest { @MockK(relaxed = true) private lateinit var sep24TransactionStore: Sep24TransactionStore @MockK(relaxed = true) private lateinit var sep31TransactionStore: Sep31TransactionStore @MockK(relaxed = true) private lateinit var assetService: AssetService + @MockK(relaxed = true) private lateinit var customerIntegration: CustomerIntegration @MockK(relaxed = true) lateinit var sep6MoreInfoUrlConstructor: Sep6MoreInfoUrlConstructor @MockK(relaxed = true) lateinit var sep24MoreInfoUrlConstructor: Sep24MoreInfoUrlConstructor @@ -63,6 +65,7 @@ class ClientStatusCallbackHandlerTest { every { sep24TransactionStore.findByTransactionId(any()) } returns null assetService = mockk() + customerIntegration = mockk() sep6MoreInfoUrlConstructor = mockk() sep24MoreInfoUrlConstructor = mockk() @@ -80,6 +83,7 @@ class ClientStatusCallbackHandlerTest { secretConfig, clientConfig, sep6TransactionStore, + customerIntegration, assetService, sep6MoreInfoUrlConstructor, sep24MoreInfoUrlConstructor diff --git a/platform/src/test/kotlin/org/stellar/anchor/platform/rpc/NotifyCustomerInfoUpdatedHandlerTest.kt b/platform/src/test/kotlin/org/stellar/anchor/platform/rpc/NotifyCustomerInfoUpdatedHandlerTest.kt index 19e3ab5e4d..b841c7c70c 100644 --- a/platform/src/test/kotlin/org/stellar/anchor/platform/rpc/NotifyCustomerInfoUpdatedHandlerTest.kt +++ b/platform/src/test/kotlin/org/stellar/anchor/platform/rpc/NotifyCustomerInfoUpdatedHandlerTest.kt @@ -9,16 +9,23 @@ import kotlin.test.assertTrue import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.CsvSource +import org.junit.jupiter.params.provider.ValueSource import org.skyscreamer.jsonassert.JSONAssert import org.skyscreamer.jsonassert.JSONCompareMode +import org.stellar.anchor.api.callback.CustomerIntegration +import org.stellar.anchor.api.callback.GetCustomerRequest +import org.stellar.anchor.api.callback.GetCustomerResponse import org.stellar.anchor.api.event.AnchorEvent import org.stellar.anchor.api.exception.rpc.InvalidParamsException import org.stellar.anchor.api.exception.rpc.InvalidRequestException import org.stellar.anchor.api.platform.GetTransactionResponse +import org.stellar.anchor.api.platform.PlatformTransactionData import org.stellar.anchor.api.platform.PlatformTransactionData.Kind.RECEIVE -import org.stellar.anchor.api.platform.PlatformTransactionData.Sep.SEP_31 -import org.stellar.anchor.api.platform.PlatformTransactionData.Sep.SEP_38 +import org.stellar.anchor.api.platform.PlatformTransactionData.Sep.* import org.stellar.anchor.api.rpc.method.NotifyCustomerInfoUpdatedRequest +import org.stellar.anchor.api.sep.SepTransactionStatus import org.stellar.anchor.api.sep.SepTransactionStatus.* import org.stellar.anchor.api.shared.Amount import org.stellar.anchor.api.shared.Customers @@ -28,6 +35,7 @@ import org.stellar.anchor.event.EventService import org.stellar.anchor.event.EventService.Session import org.stellar.anchor.metrics.MetricsService import org.stellar.anchor.platform.data.JdbcSep31Transaction +import org.stellar.anchor.platform.data.JdbcSep6Transaction import org.stellar.anchor.platform.service.AnchorMetrics.PLATFORM_RPC_TRANSACTION import org.stellar.anchor.platform.validator.RequestValidator import org.stellar.anchor.sep24.Sep24TransactionStore @@ -40,6 +48,9 @@ class NotifyCustomerInfoUpdatedHandlerTest { companion object { private val gson = GsonUtils.getInstance() private const val TX_ID = "testId" + private const val TEST_CUSTOMER_ID = "testCustomerId" + private const val TEST_CUSTOMER_TYPE = "type" + private const val TEST_MESSAGE = "customer updated" private const val VALIDATION_ERROR_MESSAGE = "Invalid request" } @@ -51,6 +62,8 @@ class NotifyCustomerInfoUpdatedHandlerTest { @MockK(relaxed = true) private lateinit var requestValidator: RequestValidator + @MockK(relaxed = true) private lateinit var customerIntegration: CustomerIntegration + @MockK(relaxed = true) private lateinit var assetService: AssetService @MockK(relaxed = true) private lateinit var eventService: EventService @@ -74,6 +87,7 @@ class NotifyCustomerInfoUpdatedHandlerTest { txn24Store, txn31Store, requestValidator, + customerIntegration, assetService, eventService, metricsService @@ -98,6 +112,7 @@ class NotifyCustomerInfoUpdatedHandlerTest { ex.message ) + verify(exactly = 0) { customerIntegration.getCustomer(any()) } verify(exactly = 0) { txn6Store.save(any()) } verify(exactly = 0) { txn24Store.save(any()) } verify(exactly = 0) { txn31Store.save(any()) } @@ -105,7 +120,7 @@ class NotifyCustomerInfoUpdatedHandlerTest { } @Test - fun test_handle_unsupportedStatus() { + fun test_handle_sep31_unsupportedStatus() { val request = NotifyCustomerInfoUpdatedRequest.builder().transactionId(TX_ID).build() val txn31 = JdbcSep31Transaction() txn31.status = INCOMPLETE.toString() @@ -120,6 +135,7 @@ class NotifyCustomerInfoUpdatedHandlerTest { ex.message ) + verify(exactly = 0) { customerIntegration.getCustomer(any()) } verify(exactly = 0) { txn6Store.save(any()) } verify(exactly = 0) { txn24Store.save(any()) } verify(exactly = 0) { txn31Store.save(any()) } @@ -127,7 +143,7 @@ class NotifyCustomerInfoUpdatedHandlerTest { } @Test - fun test_handle_invalidRequest() { + fun test_handle_sep31_invalidRequest() { val request = NotifyCustomerInfoUpdatedRequest.builder().transactionId(TX_ID).build() val txn31 = JdbcSep31Transaction() txn31.status = PENDING_CUSTOMER_INFO_UPDATE.toString() @@ -141,6 +157,7 @@ class NotifyCustomerInfoUpdatedHandlerTest { val ex = assertThrows { handler.handle(request) } assertEquals(VALIDATION_ERROR_MESSAGE, ex.message?.trimIndent()) + verify(exactly = 0) { customerIntegration.getCustomer(any()) } verify(exactly = 0) { txn6Store.save(any()) } verify(exactly = 0) { txn24Store.save(any()) } verify(exactly = 0) { txn31Store.save(any()) } @@ -148,7 +165,7 @@ class NotifyCustomerInfoUpdatedHandlerTest { } @Test - fun test_handle_ok() { + fun test_handle_sep31_ok_without_id() { val request = NotifyCustomerInfoUpdatedRequest.builder().transactionId(TX_ID).build() val txn31 = JdbcSep31Transaction() txn31.status = PENDING_CUSTOMER_INFO_UPDATE.toString() @@ -168,6 +185,7 @@ class NotifyCustomerInfoUpdatedHandlerTest { val response = handler.handle(request) val endDate = Instant.now() + verify(exactly = 0) { customerIntegration.getCustomer(any()) } verify(exactly = 0) { txn6Store.save(any()) } verify(exactly = 0) { txn24Store.save(any()) } verify(exactly = 1) { sepTransactionCounter.increment() } @@ -216,4 +234,339 @@ class NotifyCustomerInfoUpdatedHandlerTest { assertTrue(sep31TxnCapture.captured.updatedAt >= startDate) assertTrue(sep31TxnCapture.captured.updatedAt <= endDate) } + + @ParameterizedTest + @CsvSource( + "ACCEPTED, pending_receiver, pending_receiver", + "ACCEPTED, pending_customer_info_update, pending_receiver", + "PROCESSING, pending_receiver, pending_receiver", + "PROCESSING, pending_customer_info_update, pending_receiver", + "NEEDS_INFO, pending_receiver, pending_customer_info_update", + "NEEDS_INFO, pending_customer_info_update, pending_customer_info_update", + "REJECTED, pending_receiver, error", + "REJECTED, pending_customer_info_update, error", + ) + fun test_handle_sep31_ok_with_id(customerStatus: String, oldStatus: String, newStatus: String) { + val request = + NotifyCustomerInfoUpdatedRequest.builder() + .transactionId(TX_ID) + .customerId(TEST_CUSTOMER_ID) + .type(TEST_CUSTOMER_TYPE) + .message(TEST_MESSAGE) + .build() + val txn31 = JdbcSep31Transaction() + txn31.id = TX_ID + txn31.status = oldStatus + txn31.userActionRequiredBy = Instant.now() + val sep31TxnCapture = slot() + val anchorEventCapture = mutableListOf() + + val customer = GetCustomerResponse.builder().status(customerStatus).build() + every { + customerIntegration.getCustomer( + GetCustomerRequest.builder() + .transactionId(TX_ID) + .id(TEST_CUSTOMER_ID) + .type(TEST_CUSTOMER_TYPE) + .build() + ) + } returns customer + every { txn6Store.findByTransactionId(any()) } returns null + every { txn24Store.findByTransactionId(any()) } returns null + every { txn31Store.findByTransactionId(TX_ID) } returns txn31 + every { txn31Store.save(capture(sep31TxnCapture)) } returns null + every { eventSession.publish(capture(anchorEventCapture)) } just Runs + every { metricsService.counter(PLATFORM_RPC_TRANSACTION, "SEP", "sep31") } returns + sepTransactionCounter + + val startDate = Instant.now() + val response = handler.handle(request) + val endDate = Instant.now() + + verify(exactly = 0) { txn6Store.save(any()) } + verify(exactly = 0) { txn24Store.save(any()) } + verify(exactly = 1) { sepTransactionCounter.increment() } + + val expectedSep31Txn = JdbcSep31Transaction() + expectedSep31Txn.id = TX_ID + expectedSep31Txn.status = newStatus + expectedSep31Txn.updatedAt = sep31TxnCapture.captured.updatedAt + expectedSep31Txn.requiredInfoMessage = TEST_MESSAGE + + JSONAssert.assertEquals( + gson.toJson(expectedSep31Txn), + gson.toJson(sep31TxnCapture.captured), + JSONCompareMode.STRICT + ) + + val expectedResponse = GetTransactionResponse() + expectedResponse.id = TX_ID + expectedResponse.sep = SEP_31 + expectedResponse.kind = RECEIVE + expectedResponse.status = SepTransactionStatus.from(newStatus) + expectedResponse.amountIn = Amount() + expectedResponse.amountOut = Amount() + expectedResponse.amountFee = Amount() + expectedResponse.amountExpected = Amount() + expectedResponse.updatedAt = sep31TxnCapture.captured.updatedAt + expectedResponse.message = TEST_MESSAGE + expectedResponse.customers = Customers(StellarId(), StellarId()) + + JSONAssert.assertEquals( + gson.toJson(expectedResponse), + gson.toJson(response), + JSONCompareMode.STRICT + ) + + val expectedCustomerEvent = + AnchorEvent.builder() + .id(anchorEventCapture[0].id) + .sep(SEP_12.sep.toString()) + .type(AnchorEvent.Type.CUSTOMER_UPDATED) + .customer(GetCustomerResponse.to(customer)) + .build() + + JSONAssert.assertEquals( + gson.toJson(expectedCustomerEvent), + gson.toJson(anchorEventCapture[0]), + JSONCompareMode.STRICT + ) + + val expectedEvent = + AnchorEvent.builder() + .id(anchorEventCapture[1].id) + .sep(SEP_31.sep.toString()) + .type(AnchorEvent.Type.TRANSACTION_STATUS_CHANGED) + .transaction(expectedResponse) + .build() + + JSONAssert.assertEquals( + gson.toJson(expectedEvent), + gson.toJson(anchorEventCapture[1]), + JSONCompareMode.STRICT + ) + + assertTrue(sep31TxnCapture.captured.updatedAt >= startDate) + assertTrue(sep31TxnCapture.captured.updatedAt <= endDate) + } + + @Test + fun test_handle_sep6_invalidRequest() { + val request = NotifyCustomerInfoUpdatedRequest.builder().transactionId(TX_ID).build() + val txn6 = JdbcSep6Transaction() + txn6.status = PENDING_CUSTOMER_INFO_UPDATE.toString() + + every { txn6Store.findByTransactionId(TX_ID) } returns txn6 + every { txn24Store.findByTransactionId(any()) } returns null + every { txn31Store.findByTransactionId(any()) } returns null + every { requestValidator.validate(request) } throws + InvalidParamsException(VALIDATION_ERROR_MESSAGE) + + val ex = assertThrows { handler.handle(request) } + assertEquals(VALIDATION_ERROR_MESSAGE, ex.message?.trimIndent()) + + verify(exactly = 0) { customerIntegration.getCustomer(any()) } + verify(exactly = 0) { txn6Store.save(any()) } + verify(exactly = 0) { txn24Store.save(any()) } + verify(exactly = 0) { txn31Store.save(any()) } + verify(exactly = 0) { sepTransactionCounter.increment() } + } + + @ParameterizedTest + @ValueSource(strings = ["deposit", "withdrawal"]) + fun test_handle_sep6_ok_without_id(kind: String) { + val request = NotifyCustomerInfoUpdatedRequest.builder().transactionId(TX_ID).build() + val txn6 = JdbcSep6Transaction() + txn6.status = PENDING_CUSTOMER_INFO_UPDATE.toString() + txn6.kind = kind + txn6.userActionRequiredBy = Instant.now() + val sep6TxnCapture = slot() + val anchorEventCapture = slot() + + every { txn6Store.findByTransactionId(TX_ID) } returns txn6 + every { txn24Store.findByTransactionId(any()) } returns null + every { txn31Store.findByTransactionId(any()) } returns null + every { txn6Store.save(capture(sep6TxnCapture)) } returns null + every { eventSession.publish(capture(anchorEventCapture)) } just Runs + every { metricsService.counter(PLATFORM_RPC_TRANSACTION, "SEP", "sep6") } returns + sepTransactionCounter + + val startDate = Instant.now() + val response = handler.handle(request) + val endDate = Instant.now() + + verify(exactly = 0) { customerIntegration.getCustomer(any()) } + verify(exactly = 0) { txn24Store.save(any()) } + verify(exactly = 0) { txn31Store.save(any()) } + verify(exactly = 1) { sepTransactionCounter.increment() } + + val expectedSep6Txn = JdbcSep6Transaction() + expectedSep6Txn.status = PENDING_ANCHOR.toString() + expectedSep6Txn.kind = kind + expectedSep6Txn.updatedAt = sep6TxnCapture.captured.updatedAt + + JSONAssert.assertEquals( + gson.toJson(expectedSep6Txn), + gson.toJson(sep6TxnCapture.captured), + JSONCompareMode.STRICT + ) + + val expectedResponse = GetTransactionResponse() + expectedResponse.sep = SEP_6 + expectedResponse.kind = PlatformTransactionData.Kind.from(kind) + expectedResponse.status = PENDING_ANCHOR + expectedResponse.updatedAt = sep6TxnCapture.captured.updatedAt + expectedResponse.amountExpected = Amount(null, "") + expectedResponse.customers = Customers(StellarId(), StellarId()) + + JSONAssert.assertEquals( + gson.toJson(expectedResponse), + gson.toJson(response), + JSONCompareMode.STRICT + ) + + val expectedEvent = + AnchorEvent.builder() + .id(anchorEventCapture.captured.id) + .sep(SEP_6.sep.toString()) + .type(AnchorEvent.Type.TRANSACTION_STATUS_CHANGED) + .transaction(expectedResponse) + .build() + + JSONAssert.assertEquals( + gson.toJson(expectedEvent), + gson.toJson(anchorEventCapture.captured), + JSONCompareMode.STRICT + ) + + assertTrue(sep6TxnCapture.captured.updatedAt >= startDate) + assertTrue(sep6TxnCapture.captured.updatedAt <= endDate) + } + + @ParameterizedTest + @CsvSource( + "deposit, ACCEPTED, pending_anchor, pending_anchor", + "deposit, ACCEPTED, pending_customer_info_update, pending_anchor", + "deposit, PROCESSING, pending_anchor, pending_anchor", + "deposit, PROCESSING, pending_customer_info_update, pending_anchor", + "deposit, NEEDS_INFO, pending_anchor, pending_customer_info_update", + "deposit, NEEDS_INFO, pending_customer_info_update, pending_customer_info_update", + "deposit, REJECTED, pending_anchor, error", + "deposit, REJECTED, pending_customer_info_update, error", + "withdrawal, ACCEPTED, pending_anchor, pending_anchor", + "withdrawal, ACCEPTED, pending_customer_info_update, pending_anchor", + "withdrawal, PROCESSING, pending_anchor, pending_anchor", + "withdrawal, PROCESSING, pending_customer_info_update, pending_anchor", + "withdrawal, NEEDS_INFO, pending_anchor, pending_customer_info_update", + "withdrawal, NEEDS_INFO, pending_customer_info_update, pending_customer_info_update", + "withdrawal, REJECTED, pending_anchor, error", + "withdrawal, REJECTED, pending_customer_info_update, error", + ) + fun test_handle_sep6_ok_with_id( + kind: String, + customerStatus: String, + oldStatus: String, + newStatus: String + ) { + val request = + NotifyCustomerInfoUpdatedRequest.builder() + .transactionId(TX_ID) + .customerId(TEST_CUSTOMER_ID) + .type(TEST_CUSTOMER_TYPE) + .message(TEST_MESSAGE) + .build() + val txn6 = JdbcSep6Transaction() + txn6.id = TX_ID + txn6.status = oldStatus + txn6.kind = kind + txn6.userActionRequiredBy = Instant.now() + val sep6TxnCapture = slot() + val anchorEventCapture = mutableListOf() + + val customer = GetCustomerResponse.builder().status(customerStatus).build() + every { + customerIntegration.getCustomer( + GetCustomerRequest.builder() + .id(TEST_CUSTOMER_ID) + .transactionId(TX_ID) + .type(TEST_CUSTOMER_TYPE) + .build() + ) + } returns customer + every { txn6Store.findByTransactionId(TX_ID) } returns txn6 + every { txn24Store.findByTransactionId(any()) } returns null + every { txn31Store.findByTransactionId(any()) } returns null + every { txn6Store.save(capture(sep6TxnCapture)) } returns null + every { eventSession.publish(capture(anchorEventCapture)) } just Runs + every { metricsService.counter(PLATFORM_RPC_TRANSACTION, "SEP", "sep6") } returns + sepTransactionCounter + + val startDate = Instant.now() + val response = handler.handle(request) + val endDate = Instant.now() + + verify(exactly = 0) { txn24Store.save(any()) } + verify(exactly = 0) { txn31Store.save(any()) } + verify(exactly = 1) { sepTransactionCounter.increment() } + + val expectedSep6Txn = JdbcSep6Transaction() + expectedSep6Txn.id = TX_ID + expectedSep6Txn.status = newStatus + expectedSep6Txn.kind = kind + expectedSep6Txn.updatedAt = sep6TxnCapture.captured.updatedAt + expectedSep6Txn.message = TEST_MESSAGE + + JSONAssert.assertEquals( + gson.toJson(expectedSep6Txn), + gson.toJson(sep6TxnCapture.captured), + JSONCompareMode.STRICT + ) + + val expectedResponse = GetTransactionResponse() + expectedResponse.id = TX_ID + expectedResponse.sep = SEP_6 + expectedResponse.kind = PlatformTransactionData.Kind.from(kind) + expectedResponse.status = SepTransactionStatus.from(newStatus) + expectedResponse.updatedAt = sep6TxnCapture.captured.updatedAt + expectedResponse.amountExpected = Amount(null, "") + expectedResponse.message = TEST_MESSAGE + expectedResponse.customers = Customers(StellarId(), StellarId()) + + JSONAssert.assertEquals( + gson.toJson(expectedResponse), + gson.toJson(response), + JSONCompareMode.STRICT + ) + + val expectedCustomerEvent = + AnchorEvent.builder() + .id(anchorEventCapture[0].id) + .sep(SEP_12.sep.toString()) + .type(AnchorEvent.Type.CUSTOMER_UPDATED) + .customer(GetCustomerResponse.to(customer)) + .build() + + JSONAssert.assertEquals( + gson.toJson(expectedCustomerEvent), + gson.toJson(anchorEventCapture[0]), + JSONCompareMode.STRICT + ) + + val expectedTxnEvent = + AnchorEvent.builder() + .id(anchorEventCapture[1].id) + .sep(SEP_6.sep.toString()) + .type(AnchorEvent.Type.TRANSACTION_STATUS_CHANGED) + .transaction(expectedResponse) + .build() + + JSONAssert.assertEquals( + gson.toJson(expectedTxnEvent), + gson.toJson(anchorEventCapture[1]), + JSONCompareMode.STRICT + ) + + assertTrue(sep6TxnCapture.captured.updatedAt >= startDate) + assertTrue(sep6TxnCapture.captured.updatedAt <= endDate) + } } From 89e01ebffc446a6c2bf2456e2a236203909d9741 Mon Sep 17 00:00:00 2001 From: Jamie Li Date: Wed, 14 Aug 2024 10:54:16 -0700 Subject: [PATCH 08/16] [CHORE] Organize the IntelliJ Run Configurations (#1458) ### Description Organize the IntelliJ Run Configurations ### Context Improve the readability of the IntelliJ Run Configurations ### Testing - `./gradlew test` ### Documentation N/A ### Known limitations N/A --- .run/Custody Server_ custody.run.xml | 12 +++++++++++- .run/Event Processing Server_ default.run.xml | 12 +++++++++++- .run/Platform Server_ default.run.xml | 12 +++++++++++- .run/Reference Server_ default.run.xml | 12 +++++++++++- .run/Sep Server_ default.run.xml | 12 +++++++++++- .run/Stellar Observer_ default.run.xml | 12 +++++++++++- .run/Test Profile_ auth-apikey-custody.run.xml | 12 +++++++++++- .run/Test Profile_ auth-apikey-platform.run.xml | 12 +++++++++++- .run/Test Profile_ auth-jwt-custody.run.xml | 12 +++++++++++- .run/Test Profile_ auth-jwt-platform.run.xml | 12 +++++++++++- .run/Test Profile_ custody.run.xml | 12 +++++++++++- .run/Test Profile_ default.run.xml | 12 +++++++++++- .run/Test Profile_ host.docker.internal.run.xml | 12 +++++++++++- .run/Test Profile_ rpc.run.xml | 12 +++++++++++- .run/Wallet Reference Server_ default.run.xml | 12 +++++++++++- 15 files changed, 165 insertions(+), 15 deletions(-) diff --git a/.run/Custody Server_ custody.run.xml b/.run/Custody Server_ custody.run.xml index 6d72f93a07..f522449602 100644 --- a/.run/Custody Server_ custody.run.xml +++ b/.run/Custody Server_ custody.run.xml @@ -1,8 +1,18 @@ - +